From 9b48625c6a8ff151daa7f98266a2a1adf705fc71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:04:05 +0000 Subject: [PATCH] Use Codex structured output schema in adapter Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- skills/rig/engines/codex.ts | 10 ++++++++-- skills/rig/rig.ts | 6 +++++- src/engines/codex.test.ts | 23 +++++++++++++++++++++++ src/rig.test.ts | 5 ++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/skills/rig/engines/codex.ts b/skills/rig/engines/codex.ts index ad7e487..9fbfac7 100644 --- a/skills/rig/engines/codex.ts +++ b/skills/rig/engines/codex.ts @@ -35,11 +35,17 @@ export function codexEngine(options: CodexEngineOptions = {}): AgentFactory { const signal = askOptions.signal ? AbortSignal.any([askOptions.signal, closeController.signal]) : closeController.signal; - const activeTurn = thread.run(prompt, { signal }); + const activeTurn = thread.run(prompt, { + signal, + ...(askOptions.outputSchema !== undefined && { outputSchema: askOptions.outputSchema }), + }); activeTurns.add(activeTurn); try { const turn = await activeTurn; - return turn.finalResponse; + if (typeof turn.finalResponse === "string") { + return turn.finalResponse; + } + return JSON.stringify(turn.finalResponse); } finally { activeTurns.delete(activeTurn); } diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index 36bcd05..112b218 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -466,6 +466,7 @@ export type AgentOptions = { export type AgentAskOptions = { signal?: AbortSignal; + outputSchema?: JsonSchemaObject; }; export interface Agent { @@ -1906,7 +1907,10 @@ export function agent(spec: AgentSpec): AgentFn { await runAgentAddons(runtime.addons, context, async () => { lastResponse = await runtimeAgent.ask( context.prompt, - context.signal ? { signal: context.signal } : undefined, + { + ...(context.signal ? { signal: context.signal } : {}), + outputSchema: toJsonSchema(context.outputSchema), + }, ); context.response = lastResponse; debugAgentResponse({ agent: normalizedSpec.name, turn, response: lastResponse }); diff --git a/src/engines/codex.test.ts b/src/engines/codex.test.ts index e3318bc..4960a4b 100644 --- a/src/engines/codex.test.ts +++ b/src/engines/codex.test.ts @@ -69,6 +69,29 @@ it("uses Codex defaults when optional configuration is absent", async () => { expect(mocks.startThread).toHaveBeenCalledWith({ model: "gpt-5-codex" }); }); +it("forwards output schemas to Codex and stringifies structured responses", async () => { + const runtimeAgent = await codexEngine()({ model: "gpt-5-codex" }); + const outputSchema = { + type: "object", + properties: { + summary: { type: "string" }, + }, + required: ["summary"], + additionalProperties: false, + } as const; + mocks.run.mockResolvedValueOnce({ finalResponse: { summary: "done" } }); + + await expect(runtimeAgent.ask("summarize", { outputSchema })).resolves.toBe(JSON.stringify({ summary: "done" })); + + expect(mocks.run).toHaveBeenCalledWith( + "summarize", + expect.objectContaining({ + signal: expect.any(AbortSignal), + outputSchema, + }), + ); +}); + it("rejects non-string system messages", () => { const factory = codexEngine(); diff --git a/src/rig.test.ts b/src/rig.test.ts index a25c693..213d7e5 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -288,7 +288,10 @@ describe("agent invocation", () => { await expect(greet({ text: "Hi" })).resolves.toEqual({ text: "custom" }); expect(factory).toHaveBeenCalledWith({ model: "custom-model" }); - expect(ask).toHaveBeenCalledWith(expect.stringContaining("Hi"), undefined); + expect(ask).toHaveBeenCalledWith( + expect.stringContaining("Hi"), + expect.objectContaining({ outputSchema: expect.any(Object) }), + ); expect(close).toHaveBeenCalledOnce(); expect(mocks.copilotClientCtor).not.toHaveBeenCalled(); });