mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai): preserve Codex end_turn for debugging (#7766)
This commit is contained in:
parent
541ed488d8
commit
c3e7bc60a1
4 changed files with 24 additions and 6 deletions
|
|
@ -662,7 +662,7 @@ async function processStream(
|
|||
grammarToolInputProperties: ReadonlyMap<string, string>,
|
||||
options?: OpenAICodexResponsesOptions,
|
||||
): Promise<void> {
|
||||
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {
|
||||
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output), output, stream, model, {
|
||||
serviceTier: options?.serviceTier,
|
||||
grammarToolInputProperties,
|
||||
resolveServiceTier: resolveCodexServiceTier,
|
||||
|
|
@ -719,7 +719,10 @@ function extractCodexEventError(event: Record<string, unknown>): { code?: string
|
|||
};
|
||||
}
|
||||
|
||||
async function* mapCodexEvents(events: AsyncIterable<Record<string, unknown>>): AsyncGenerator<ResponseStreamEvent> {
|
||||
async function* mapCodexEvents(
|
||||
events: AsyncIterable<Record<string, unknown>>,
|
||||
output: AssistantMessage,
|
||||
): AsyncGenerator<ResponseStreamEvent> {
|
||||
for await (const event of events) {
|
||||
const type = typeof event.type === "string" ? event.type : undefined;
|
||||
if (!type) continue;
|
||||
|
|
@ -740,7 +743,10 @@ async function* mapCodexEvents(events: AsyncIterable<Record<string, unknown>>):
|
|||
}
|
||||
|
||||
if (type === "response.done" || type === "response.completed" || type === "response.incomplete") {
|
||||
const response = (event as { response?: { status?: unknown } }).response;
|
||||
const response = (event as { response?: { status?: unknown; end_turn?: unknown } }).response;
|
||||
if (typeof response?.end_turn === "boolean") {
|
||||
output.endTurn = response.end_turn;
|
||||
}
|
||||
const normalizedResponse = response
|
||||
? { ...response, status: normalizeCodexStatus(response.status) }
|
||||
: response;
|
||||
|
|
@ -1504,7 +1510,7 @@ async function processWebSocketStream(
|
|||
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
||||
await processResponsesStream(
|
||||
startWebSocketOutputOnFirstEvent(
|
||||
mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)),
|
||||
mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output),
|
||||
onStart,
|
||||
),
|
||||
output,
|
||||
|
|
|
|||
|
|
@ -424,6 +424,11 @@ export interface AssistantMessage {
|
|||
deferred?: DeferredHandle;
|
||||
errorMessage?: string;
|
||||
rawStopReason?: string;
|
||||
/**
|
||||
* Provider indication of whether the model explicitly ended its turn.
|
||||
* Preserved for debugging and does not currently affect agent control flow.
|
||||
*/
|
||||
endTurn?: boolean;
|
||||
timestamp: number; // Unix timestamp in milliseconds
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,11 @@ function decodeCodexRequestBody(body: RequestInit["body"] | undefined): Record<s
|
|||
function buildSSEPayload({
|
||||
status,
|
||||
includeDone = false,
|
||||
endTurn,
|
||||
}: {
|
||||
status: "completed" | "incomplete";
|
||||
includeDone?: boolean;
|
||||
endTurn?: boolean;
|
||||
}): string {
|
||||
const terminalType = status === "incomplete" ? "response.incomplete" : "response.completed";
|
||||
const events = [
|
||||
|
|
@ -75,6 +77,7 @@ function buildSSEPayload({
|
|||
type: terminalType,
|
||||
response: {
|
||||
status,
|
||||
end_turn: endTurn,
|
||||
incomplete_details: status === "incomplete" ? { reason: "max_output_tokens" } : null,
|
||||
usage: {
|
||||
input_tokens: 5,
|
||||
|
|
@ -210,7 +213,7 @@ describe("openai-codex streaming", () => {
|
|||
process.env.PI_CODING_AGENT_DIR = tempDir;
|
||||
const token = mockToken();
|
||||
const encoder = new TextEncoder();
|
||||
const sse = buildSSEPayload({ status: "completed", includeDone: true });
|
||||
const sse = buildSSEPayload({ status: "completed", includeDone: true, endTurn: false });
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
|
|
@ -263,6 +266,7 @@ describe("openai-codex streaming", () => {
|
|||
|
||||
expect(result.content.find((c) => c.type === "text")?.text).toBe("Hello");
|
||||
expect(result.stopReason).toBe("stop");
|
||||
expect(result.endTurn).toBe(false);
|
||||
});
|
||||
|
||||
it("maps response.incomplete to stopReason length even when the SSE body stays open", async () => {
|
||||
|
|
@ -1273,6 +1277,7 @@ describe("openai-codex streaming", () => {
|
|||
type: "response.completed",
|
||||
response: {
|
||||
status: "completed",
|
||||
end_turn: false,
|
||||
usage: {
|
||||
input_tokens: 5,
|
||||
output_tokens: 3,
|
||||
|
|
@ -1317,12 +1322,13 @@ describe("openai-codex streaming", () => {
|
|||
messages: [{ role: "user", content: "Say hello", timestamp: 1 }],
|
||||
};
|
||||
|
||||
await streamSimpleOpenAICodexResponses(model, context, {
|
||||
const result = await streamSimpleOpenAICodexResponses(model, context, {
|
||||
apiKey: token,
|
||||
sessionId: "session-auto",
|
||||
transport: "auto",
|
||||
}).result();
|
||||
|
||||
expect(result.endTurn).toBe(false);
|
||||
expect(sentBodies).toHaveLength(1);
|
||||
expect(capturedWebSocketHeaders?.["session-id"]).toBe("session-auto");
|
||||
expect(capturedWebSocketHeaders?.session_id).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ type _AiAssistantMessageFieldsAccountedFor = Assert<
|
|||
| "deferred"
|
||||
| "errorMessage"
|
||||
| "rawStopReason"
|
||||
| "endTurn"
|
||||
| "timestamp"
|
||||
>
|
||||
>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue