Skip to content

Enable Codex structured output in Rig Codex adapter - #283

Merged
pelikhan merged 1 commit into
mainfrom
copilot/codex-adapter-structured-output
Jul 29, 2026
Merged

Enable Codex structured output in Rig Codex adapter#283
pelikhan merged 1 commit into
mainfrom
copilot/codex-adapter-structured-output

Conversation

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Rig’s Codex adapter was treating Codex responses as plain text, so it did not leverage Codex’s native schema-constrained output path. This change routes Rig’s output schema into Codex per turn and normalizes structured responses back into Rig’s existing parse/validate flow.

  • Runtime schema propagation

    • Extended AgentAskOptions with outputSchema.
    • In the agent turn loop, pass toJsonSchema(context.outputSchema) into runtimeAgent.ask(...) so engines can consume structured-output metadata.
  • Codex adapter integration

    • Updated codexEngine to forward outputSchema to thread.run(prompt, { ... }).
    • Preserved Rig’s string-based engine contract by converting non-string turn.finalResponse to JSON.stringify(...).
  • Behavioral coverage

    • Added Codex-engine coverage for schema forwarding and structured finalResponse normalization.
    • Updated SDK-neutral invocation expectation to include propagated outputSchema.
const turn = await thread.run(prompt, {
  signal,
  outputSchema, // forwarded from Rig output schema
});

const response =
  typeof turn.finalResponse === "string"
    ? turn.finalResponse
    : JSON.stringify(turn.finalResponse);

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Ensure Codex adapter uses Codex structured output Enable Codex structured output in Rig Codex adapter Jul 29, 2026
Copilot AI requested a review from pelikhan July 29, 2026 13:05
@pelikhan
pelikhan marked this pull request as ready for review July 29, 2026 13:23
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@pelikhan
pelikhan merged commit 818d644 into main Jul 29, 2026
7 checks passed

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — commenting with a few targeted improvements.

📋 Key Themes & Highlights

Key Themes

  • Missing edge-case tests: the null/undefined finalResponse path and the string-branch-with-schema path are untested.
  • Implicit contract broadening: outputSchema is now forwarded to all engines via AgentAskOptions, not just Codex — worth documenting so future engine authors know they can safely ignore it.

Positive Highlights

  • ✅ Clean integration: the structured-output path follows the existing askOptions pattern without a bespoke bypass.
  • ✅ Good test coverage of the happy path (schema forwarding + structured response serialisation).
  • ✅ Minimal footprint — the change is surgical and doesn't touch unrelated code.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 32.2 AIC · ⌖ 4.31 AIC · ⊞ 6.3K
Comment /matt to run again

if (typeof turn.finalResponse === "string") {
return turn.finalResponse;
}
return JSON.stringify(turn.finalResponse);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Missing edge case: finalResponse could be nullJSON.stringify(null) returns "null", which propagates silently as a valid response but will fail downstream schema validation.

💡 Suggested test + guard
mocks.run.mockResolvedValueOnce({ finalResponse: null });
await expect(runtimeAgent.ask('x', {})).rejects.toThrow(); // or resolves.toBe('')

Consider throwing if finalResponse is nullish when an outputSchema was requested, or explicitly document that null stringifies to "null".

Comment thread skills/rig/rig.ts
context.signal ? { signal: context.signal } : undefined,
{
...(context.signal ? { signal: context.signal } : {}),
outputSchema: toJsonSchema(context.outputSchema),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] outputSchema is now always passed (even when undefined from toJsonSchema) to every engine, not just Codex. Engines that don't use it will silently ignore it, but this couples the generic AgentAskOptions contract to a Codex-specific feature.

💡 Context

The Copilot engine (line 564 in rig.ts) discards askOptions.outputSchema today because it only reads signal. That is safe now, but any future engine that spreads askOptions directly into its SDK call could accidentally forward an unknown field. Consider documenting that engines are free to ignore outputSchema, or narrowing the propagation to engines that declare support.

Comment thread src/engines/codex.test.ts
mocks.run.mockResolvedValueOnce({ finalResponse: { summary: "done" } });

await expect(runtimeAgent.ask("summarize", { outputSchema })).resolves.toBe(JSON.stringify({ summary: "done" }));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test only covers the structured-object path. Add a test confirming that a plain string finalResponse is returned as-is (no double-serialisation) when outputSchema is also provided — this is the boundary between the two branches in the production code.

💡 Suggested test
it('returns string finalResponse unchanged when outputSchema is set', async () => {
  const runtimeAgent = await codexEngine()({ model: 'gpt-5-codex' });
  mocks.run.mockResolvedValueOnce({ finalResponse: '{"summary":"done"}' });
  await expect(
    runtimeAgent.ask('summarize', { outputSchema: { type: 'object' } })
  ).resolves.toBe('{"summary":"done"}');
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants