Skip to content

feat: Add typed structured outputs for all six SDKs - #2590

Merged
SteveSandersonMS merged 15 commits into
mainfrom
sdk/typed-structured-output
Sep 18, 2026
Merged

SteveSandersonMS merged 15 commits into
mainfrom
sdk/typed-structured-output

Conversation

@SteveSandersonMS

@SteveSandersonMS SteveSandersonMS commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

  • A schema belongs to one submitted run, including its tool calls, steering, and stop-hook corrections. Independent sends do not inherit it.
  • Typed helpers wait for non-autopilot session idle and select the last matching root assistant message using originatingMessageId. This is not a promise of one assistant message per user message.
  • Streaming stays text. Immediate steering must omit the schema. Timeouts/cancellation stop waiting, not agent work.
  • Provider schema restrictions apply. Node/Zod and Python/Pydantic validate results; other languages use their normal JSON deserialization, not full JSON Schema validation.

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.

import { z } from "zod";

const answerSchema = z.object({ value: z.number().int() });
const answer = await session.sendAndWait("What is 19 + 23?", answerSchema);
console.log(answer.value); // inferred as number

Python

Use a Pydantic model, as with custom-tool parameters.

from pydantic import BaseModel, ConfigDict

class Answer(BaseModel):
    model_config = ConfigDict(extra="forbid")
    value: int

answer = await session.send_and_wait_typed("What is 19 + 23?", Answer)
print(answer.value)

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.

type Answer struct {
    Value int `json:"value"`
}

answer, err := copilot.SendAndWait[Answer](ctx, session, copilot.MessageOptions{
    Prompt: "What is 19 + 23?",
})
if err != nil {
    return err
}
fmt.Println(answer.Value)

C#

Use the generic overload with the same schema inference as custom tools.

var answer = await session.SendAndWaitAsync<Answer>("What is 19 + 23?");
Console.WriteLine(answer.Value);

public sealed record Answer(int Value);

Optional serializerOptions control both schema inference and deserialization without being mutated. For Native AOT or reflection-disabled applications, supply source-generated options, such as AnswerJsonContext.Default.Options.

Java

Annotate the result type and enable CopilotResponseProcessor, using the existing custom-tool annotation-processing approach and schema generator.

@CopilotResponse
public record Answer(int value) {}

Answer answer = session.sendAndWait("What is 19 + 23?", Answer.class).get();
System.out.println(answer.value());

No new schema library. Custom Jackson mappings and recursive types require an explicit schema.

Rust

Enable the existing derive feature and use Serde/schemars, as with typed custom tools.

#[derive(serde::Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
struct Answer {
    value: i32,
}

let answer: Answer = session.send_and_wait_typed("What is 19 + 23?").await?;
println!("{}", answer.value);

Deserialization preserves i128/u128 precision and rejects top-level JSON null.

send vs. sendAndWait

send returns a user-message ID. Capture assistant messages, wait for session.idle, then parse the last message whose originatingMessageId matches that ID, if present. Typed sendAndWait does 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 send returns its ID; Rust's subscription already buffers events.

Here prompt is "What is 19 + 23?". For Go, C#, and Java, schema is this JSON Schema represented as a map[string]any, JsonElement, or Map<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:

const messages = new Map<string, string>();
let onIdle!: () => void;
const idle = new Promise<void>(resolve => { onIdle = resolve; });
const unsubscribe = session.on(event => {
    if (event.agentId) return;
    if (event.type === "assistant.message" && event.data.originatingMessageId)
        messages.set(event.data.originatingMessageId, event.data.content);
    if (event.type === "session.idle") onIdle();
});

const id = await session.send({ prompt, responseSchema: answerSchema });
await idle;
unsubscribe();
const content = messages.get(id);
if (content) {
    const answer = answerSchema.parse(JSON.parse(content));
    console.log(answer.value);
}

Python

Wait and parse: answer = await session.send_and_wait_typed(prompt, Answer)

Send and listen:

import asyncio
from copilot.session_events import AssistantMessageData, SessionIdleData

messages = {}
idle = asyncio.Event()

def on_event(event):
    if event.agent_id:
        return
    if isinstance(event.data, AssistantMessageData):
        messages[event.data.originating_message_id] = event.data.content
    if isinstance(event.data, SessionIdleData):
        idle.set()

unsubscribe = session.on(on_event)
message_id = await session.send(prompt, response_schema=Answer)
await idle.wait()
unsubscribe()
if content := messages.get(message_id):
    answer = Answer.model_validate_json(content)
    print(answer.value)

Go

Wait and parse: answer, err := copilot.SendAndWait[Answer](ctx, session, copilot.MessageOptions{Prompt: prompt})

Send and listen:

var messages sync.Map
idle := make(chan struct{}, 1)
unsubscribe := session.On(func(event copilot.SessionEvent) {
    if event.AgentID != nil { return }
    switch data := event.Data.(type) {
    case *copilot.AssistantMessageData:
        if data.OriginatingMessageID != nil {
            messages.Store(*data.OriginatingMessageID, data.Content)
        }
    case *copilot.SessionIdleData:
        idle <- struct{}{}
    }
})
defer unsubscribe()

id, err := session.Send(ctx, copilot.MessageOptions{Prompt: prompt, ResponseSchema: schema})
if err != nil { return err }
<-idle
if content, ok := messages.Load(id); ok {
    var answer Answer
    if err := json.Unmarshal([]byte(content.(string)), &answer); err != nil { return err }
    fmt.Println(answer.Value)
}

C#

Wait and parse: var answer = await session.SendAndWaitAsync<Answer>(prompt);

Send and listen:

var messages = new ConcurrentDictionary<string, string>();
var idle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = session.On<SessionEvent>(e =>
{
    if (e.AgentId is not null) return;
    if (e is AssistantMessageEvent message && message.Data.OriginatingMessageId is { } origin)
        messages[origin] = message.Data.Content;
    if (e is SessionIdleEvent) idle.TrySetResult();
});

var id = await session.SendAsync(new MessageOptions { Prompt = prompt, ResponseSchema = schema });
await idle.Task;
if (messages.TryGetValue(id, out var content))
{
    var answer = JsonSerializer.Deserialize<Answer>(content, new JsonSerializerOptions(JsonSerializerDefaults.Web));
    Console.WriteLine(answer!.Value);
}

Java

Wait and parse: Answer answer = session.sendAndWait(prompt, Answer.class).get();

Send and listen:

var messages = new ConcurrentHashMap<String, String>();
var idle = new CompletableFuture<Void>();
try (var subscription = session.on(event -> {
    if (event.getAgentId() != null) return;
    if (event instanceof AssistantMessageEvent message && message.getData().originatingMessageId() != null)
        messages.put(message.getData().originatingMessageId(), message.getData().content());
    if (event instanceof SessionIdleEvent) idle.complete(null);
})) {
    String id = session.send(new MessageOptions().setPrompt(prompt).setResponseSchema(schema)).get();
    idle.get();
    String content = messages.get(id);
    if (content != null) {
        Answer answer = new ObjectMapper().readValue(content, Answer.class);
        System.out.println(answer.value());
    }
}

Rust

Wait and parse: let answer: Answer = session.send_and_wait_typed(prompt).await?;

Send and listen:

use github_copilot_sdk::{MessageOptions, tool::schema_for};

let mut events = session.subscribe();
let id = session.send(MessageOptions::new(prompt).with_response_schema(schema_for::<Answer>())).await?;
let mut content = None;
loop {
    let event = events.recv().await?;
    if event.agent_id.is_some() { continue; }
    if event.event_type == "assistant.message" && event.data["originatingMessageId"] == id {
        content = event.data["content"].as_str().map(str::to_owned);
    }
    if event.event_type == "session.idle" {
        if let Some(text) = content {
            let answer: Answer = serde_json::from_str(&text)?;
            println!("{}", answer.value);
        }
        break;
    }
}
drop(events);

@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Sep 9, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

SteveSandersonMS added a commit that referenced this pull request Sep 14, 2026
* 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>
SteveSandersonMS and others added 7 commits September 18, 2026 08:32
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>
@SteveSandersonMS
SteveSandersonMS force-pushed the sdk/typed-structured-output branch from ad9ca89 to a15fab4 Compare September 18, 2026 08:43
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@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.

Generated by SDK Consistency Review Agent for #2590 · copilot · sonnet50 · 46.9 AIC · ⌖ 12.3 AIC · ⊞ 8.3K

Comment thread nodejs/src/types.ts
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>
@SteveSandersonMS SteveSandersonMS changed the title feat: Add typed structured outputs for Node and .NET feat: Add typed structured outputs for all six SDKs Sep 18, 2026
Comment thread python/test_structured_output.py
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>
@SteveSandersonMS
SteveSandersonMS marked this pull request as ready for review September 18, 2026 10:15
@SteveSandersonMS
SteveSandersonMS requested a review from a team as a code owner September 18, 2026 10:15
Copilot AI balanced review requested due to automatic review settings September 18, 2026 10:15

Copilot AI 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.

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 Medium severity

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.

Comment thread dotnet/test/E2E/StructuredOutputE2ETests.cs Outdated
Comment thread scripts/codegen/csharp.test.ts Outdated
@github-actions

This comment has been minimized.

@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.

Generated by SDK Consistency Review Agent for #2590 · copilot · sonnet50 · 178.2 AIC · ⌖ 12.6 AIC · ⊞ 8.3K

Comment thread nodejs/src/session.ts
SteveSandersonMS and others added 2 commits September 18, 2026 10:25
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>
@github-actions

This comment has been minimized.

@SteveSandersonMS

Copy link
Copy Markdown
Contributor Author

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>
@github-actions

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>
@github-actions

This comment has been minimized.

@SteveSandersonMS SteveSandersonMS left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Three issues in the newly introduced typed-output paths, reproduced during the console-app review. Pre-existing issues are excluded.

Comment thread dotnet/src/Session.StructuredOutput.cs
Comment thread python/copilot/session.py
Comment thread rust/src/session.rs Outdated
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>
@github-actions

Copy link
Copy Markdown
Contributor

SDK Consistency Review — PR #2590

This 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 test/snapshots/structured_output/ and a CONTRIBUTING.md section documenting the shared cross-language test methodology.

What was checked

  • Field naming parity: responseSchema (Node/Java), response_schema (Python/Rust), ResponseSchema (Go/.NET) — all follow each language's casing convention for the same concept.
  • Typed helper method parity:
    • Node: sendAndWait<TResult>(options, responseSchema, timeout?) (overload)
    • Python: send_and_wait_typed(prompt, response_type, ...)
    • Go: package-level generic SendAndWait[T](ctx, session, options) (Go has no generic methods, so a free function is the idiomatic equivalent)
    • .NET: SendAndWaitAsync<TResult>(prompt/options, serializerOptions?, timeout?, ct)
    • Java: sendAndWait(prompt/options, responseType, timeoutMs?) using Class<T>
    • Rust: send_and_wait_typed<T>(opts), gated behind the new derive feature (consistent with existing schema_for gating for custom tools)
  • Validation rules consistent everywhere: rejects combining an explicit ResponseSchema with the typed helper, and rejects mode: "immediate"/steering with structured output.
  • Behavioral parity: 60s default timeout, null-JSON-content rejected as an error (not silently returned), non-autopilot idle + originating-message correlation semantics, stop-hook correction handling — all documented identically per-language in each README.
  • Shared E2E fixtures: all languages reuse the same 8 snapshot captures in test/snapshots/structured_output/ rather than language-specific copies, per the new CONTRIBUTING.md guidance.
  • CI updates (.github/workflows/dotnet-sdk-tests.yml, rust-sdk-tests.yml) correctly wire up the new reflection-enabled test variant (.NET) and derive feature flag (Rust) needed for this feature's tests.

Conclusion

No 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.

Generated by SDK Consistency Review Agent for #2590 · copilot · sonnet50 · 61.5 AIC · ⌖ 12.4 AIC · ⊞ 8.3K ·

@SteveSandersonMS
SteveSandersonMS merged commit fc44743 into main Sep 18, 2026
195 of 197 checks passed
@SteveSandersonMS
SteveSandersonMS deleted the sdk/typed-structured-output branch September 18, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants