feat: Add typed structured outputs for all six SDKs - #2590
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
* Fix C# codegen for runtime schema unions Extract single-variant union handling from #2590 and support referenced enum and nested unions exposed by CLI 1.0.84-6. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve polymorphic API shape for single-variant unions Use the existing STJ hierarchy for tagged unions regardless of variant count. Cover one-to-two-variant compatibility and preserve existing nullable reference output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generate all language RPC wrappers from the local runtime schema, expose per-run output schemas, and correlate schema-bearing waits using originatingMessageId. Include real-provider recording/replay E2Es through the locally built runtime for raw schemas, tools, steering, batches, and overlapping typed sends. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Report pinned-schema drift without automatically rewriting draft Java output. Keep failure visibility, retain auto-regeneration for ready PRs, and restore the locally generated Java API after the initial workflow regenerated it against the old published runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerate event types for all six SDK languages and document isFinalReply. Add real-provider Node and C# direct-send E2Es that parse the final correlated reply while stop hooks block idle, plus regressions preserving SendAndWait rejection on later errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerate all SDK contracts from the local runtime, remove the provisional final-reply flag, and select correlated responses at idle. Share the real stop-hook correction capture between Node and C# and preserve existing captures while updating direct-send coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refresh batch contracts, cover late steering with a shared provider capture, and reject malformed typed-wait arguments instead of sending unformatted requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerate all language contracts from runtime 145f0fc7d0. Preserve the Node permission-source export after it moves into shared event definitions. Exercise output-only terminal finalization with stop-hook correction and synchronous size/HydraFusion rejection through both Node and C# SDKs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use main's polymorphic C# response format, preserve rebased lifecycle coverage, remove the unreleased codegen workaround, and honor the E2E provider matrix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ad9ca89 to
a15fab4
Compare
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for #2590 · copilot · sonnet50 · 46.9 AIC · ⌖ 12.3 AIC · ⊞ 8.3K
Reuse each SDK's tool schema conventions, correlate final run output, cover admission and lifecycle failures, and replay shared real-runtime E2Es. Enable Rust derive coverage in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply the existing SA_ONSTACK repair to native hosts as well as legacy CLI hosts. Cover both entrypoint modes and preserve handlers while fixing in-process child cleanup crashes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The C# generator regression test is not executed, and one .NET E2E bypasses backend-provider configuration.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds provider-native structured outputs and typed result helpers across all six SDKs, with documentation, unit tests, E2Es, and replay snapshots.
Changes:
- Adds per-message schemas and typed response APIs.
- Implements correlated structured-response waiting and validation.
- Expands cross-language testing and documentation.
| File | Description |
|---|---|
test/snapshots/structured_output/typed_wait_returns_stop_hook_correction.yaml |
Captures stop-hook correction. |
test/snapshots/structured_output/typed_wait_returns_stop_hook_correction_after_terminal_tool.yaml |
Captures terminal-tool correction. |
test/snapshots/structured_output/typed_wait_returns_late_steering_response.yaml |
Captures late steering. |
test/snapshots/structured_output/sends_explicit_schema_for_message_and_batch.yaml |
Captures explicit schemas and batches. |
test/snapshots/structured_output/send_selects_correlated_response_after_idle.yaml |
Captures correlated final output. |
test/snapshots/structured_output/node_zod_typed_result_after_terminal_tool_and_steering.yaml |
Captures Node typed tool flow. |
test/snapshots/structured_output/node_send_selects_correlated_response_after_idle.yaml |
Captures Node event correlation. |
test/snapshots/structured_output/node_raw_schema_and_unformatted_followup.yaml |
Captures schema clearing. |
test/snapshots/structured_output/node_generated_rpc_accepts_a_batch_response_format.yaml |
Captures Node batch RPC format. |
test/snapshots/structured_output/node_concurrent_typed_sends_return_their_own_results.yaml |
Captures Node concurrent sends. |
test/snapshots/structured_output/infers_typed_result_after_custom_tool.yaml |
Captures typed tool output. |
test/snapshots/structured_output/concurrent_typed_sends_return_their_own_results.yaml |
Captures shared concurrent behavior. |
scripts/codegen/csharp.test.ts |
Adds C# union-generation regression tests. |
rust/tests/session_test.rs |
Adds Rust structured-output unit tests. |
rust/tests/e2e/structured_output.rs |
Adds Rust structured-output E2Es. |
rust/tests/e2e.rs |
Registers Rust E2Es. |
rust/src/types.rs |
Adds Rust response schema options. |
rust/src/session.rs |
Implements Rust typed correlated waits. |
rust/README.md |
Documents Rust structured output. |
python/test_structured_output.py |
Adds Python unit coverage. |
python/README.md |
Documents Python structured output. |
python/e2e/test_structured_output_e2e.py |
Adds Python E2Es. |
python/copilot/session.py |
Implements Python typed structured output. |
nodejs/test/structured-output.test.ts |
Adds Node unit coverage. |
nodejs/test/e2e/structured_output.e2e.test.ts |
Adds Node E2Es. |
nodejs/src/types.ts |
Defines Node response schema types. |
nodejs/src/session.ts |
Implements Node typed structured waits. |
nodejs/src/schema.ts |
Centralizes schema conversion helpers. |
nodejs/src/index.ts |
Exports the response schema type. |
nodejs/src/client.ts |
Reuses shared schema conversion. |
nodejs/README.md |
Documents Node structured output. |
java/sdk/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java |
Tests Java response schema generation. |
java/sdk/src/test/java/com/github/copilot/StructuredOutputTest.java |
Adds Java unit coverage. |
java/sdk/src/test/java/com/github/copilot/StructuredOutputE2ETest.java |
Adds Java E2Es. |
java/sdk/src/main/resources/META-INF/services/javax.annotation.processing.Processor |
Registers the response processor. |
java/sdk/src/main/java/module-info.java |
Exposes the processor as a service. |
java/sdk/src/main/java/com/github/copilot/tool/SchemaGenerator.java |
Adds closed response schemas and recursion checks. |
java/sdk/src/main/java/com/github/copilot/tool/CopilotResponseProcessor.java |
Generates response metadata. |
java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java |
Adds wire response format support. |
java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java |
Adds explicit response schemas. |
java/sdk/src/main/java/com/github/copilot/ResponseSchemas.java |
Loads generated schema metadata. |
java/sdk/src/main/java/com/github/copilot/CopilotSession.java |
Implements Java typed waits. |
java/sdk/src/main/java/com/github/copilot/CopilotResponse.java |
Adds the response annotation. |
java/sdk/pom.xml |
Enables response processing for tests. |
java/README.md |
Documents Java structured output. |
go/types.go |
Adds Go response schema options. |
go/structured_output.go |
Implements Go typed structured output. |
go/structured_output_test.go |
Adds Go unit coverage. |
go/session.go |
Sends schemas and selects structured waits. |
go/README.md |
Documents Go structured output. |
go/message_source_test.go |
Supports pre-acknowledgement event tests. |
go/internal/ffihost/sigonstack_linux_test.go |
Expands signal-handler coverage. |
go/internal/ffihost/ffihost.go |
Rearms signal handlers for embedded entrypoints. |
go/internal/e2e/structured_output_e2e_test.go |
Adds Go E2Es. |
dotnet/test/Unit/ClientSessionLifetimeTests.cs |
Extends the fake runtime for correlation tests. |
dotnet/test/Harness/ReplayProxy.cs |
Captures tool-choice metadata. |
dotnet/test/E2E/StructuredOutputE2ETests.cs |
Adds .NET structured-output E2Es. |
dotnet/src/Types.cs |
Adds .NET response schema options. |
dotnet/src/Session.StructuredOutput.cs |
Implements generic typed waits. |
dotnet/src/Session.cs |
Sends response formats and routes waits. |
dotnet/README.md |
Documents .NET structured output. |
CONTRIBUTING.md |
Documents local runtime and snapshot workflows. |
.github/workflows/rust-sdk-tests.yml |
Enables Rust derive coverage in CI. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for #2590 · copilot · sonnet50 · 178.2 AIC · ⌖ 12.6 AIC · ⊞ 8.3K
Wire C# union regression tests into Codegen Check, route the rejection E2E through backend configuration, and fix the Rust doctest session path with the optional derive feature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the other SDK typed helpers and verify no JSON-RPC request is sent for an incompatible immediate-mode invocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
|
On the coverage question in #2590 (comment): yes, the 32 MiB ceiling and unsupported-provider rejection are shared runtime validation, deliberately covered through Node/.NET E2Es rather than duplicating those cases in all six SDKs. The other SDKs exercise raw-schema forwarding and typed results through the same runtime with shared provider recordings, plus language-specific correlation, correction, and failure-path unit coverage. The Node immediate-mode discrepancy from the earlier review was fixed in 77bee95. |
Run the same eight real-CAPI recorded flows and admission rejection in all six languages, asserting inferred schemas, streaming, and per-run isolation. Reuse shared captures without changing recorded responses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Keep the structured-output PR free of RPC generator tests and workflow changes following the separately merged codegen fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
SteveSandersonMS
left a comment
There was a problem hiding this comment.
Three issues in the newly introduced typed-output paths, reproduced during the console-app review. Pre-existing issues are excluded.
Initialize copied C# options with reflection only when enabled, align Pydantic validation with inferred aliases including nested models, and deserialize Rust wide integers directly from JSON text. Add regression coverage and reflection-enabled C# CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SDK Consistency Review — PR #2590This PR adds the Structured Outputs (experimental) feature consistently across all six SDKs (Node.js/TypeScript, Python, Go, .NET, Java, Rust), plus shared E2E test snapshots under What was checked
ConclusionNo cross-SDK inconsistencies found. This is a well-coordinated, simultaneous six-language feature addition with matching API shapes (accounting for language idioms), matching validation/error semantics, and shared test fixtures. No inline review comments needed.
|

Summary
Adds provider-native structured output to all six SDKs: provide a JSON Schema, or use an idiomatic typed helper to infer the schema and parse the result.
Addresses #1185. The runtime support shipped in CLI 1.0.86-0 via runtime #19652. This PR uses the generated contracts already on
main; it contains no RPC codegen changes.How it works
originatingMessageId. This is not a promise of one assistant message per user message.Each language has nine E2E scenarios. All six reuse the same eight recordings from actual CAPI calls; admission-rejection cases make no provider calls. Coverage includes tools, streaming, concurrent schemas, steering, corrections, raw/batch RPCs, and schema clearing.
TypeScript / Node.js
Pass a Zod schema as the second argument to infer, parse, and validate the result.
Python
Use a Pydantic model, as with custom-tool parameters.
Requires Pydantic 2.11+. Validation uses the schema's aliases consistently, including in nested models.
Go
Use the package-level generic helper; schema inference reuses the existing custom-tool generator.
C#
Use the generic overload with the same schema inference as custom tools.
Optional
serializerOptionscontrol both schema inference and deserialization without being mutated. For Native AOT or reflection-disabled applications, supply source-generated options, such asAnswerJsonContext.Default.Options.Java
Annotate the result type and enable
CopilotResponseProcessor, using the existing custom-tool annotation-processing approach and schema generator.No new schema library. Custom Jackson mappings and recursive types require an explicit schema.
Rust
Enable the existing
derivefeature and use Serde/schemars, as with typed custom tools.Deserialization preserves
i128/u128precision and rejects top-level JSON null.sendvs.sendAndWaitsendreturns a user-message ID. Capture assistant messages, wait forsession.idle, then parse the last message whoseoriginatingMessageIdmatches that ID, if present. TypedsendAndWaitdoes this for you.These happy-path examples assume one send on an initially idle, non-autopilot session. They reuse the types above and omit session setup, routine imports, and error handling. The callback examples keep messages by origin because events can arrive before
sendreturns its ID; Rust's subscription already buffers events.Here
promptis"What is 19 + 23?". For Go, C#, and Java,schemais this JSON Schema represented as amap[string]any,JsonElement, orMap<String, Object>, respectively:{"type":"object","properties":{"value":{"type":"integer"}},"required":["value"],"additionalProperties":false}TypeScript / Node.js
Wait and parse:
const answer = await session.sendAndWait(prompt, answerSchema);Send and listen:
Python
Wait and parse:
answer = await session.send_and_wait_typed(prompt, Answer)Send and listen:
Go
Wait and parse:
answer, err := copilot.SendAndWait[Answer](ctx, session, copilot.MessageOptions{Prompt: prompt})Send and listen:
C#
Wait and parse:
var answer = await session.SendAndWaitAsync<Answer>(prompt);Send and listen:
Java
Wait and parse:
Answer answer = session.sendAndWait(prompt, Answer.class).get();Send and listen:
Rust
Wait and parse:
let answer: Answer = session.send_and_wait_typed(prompt).await?;Send and listen: