Skip to content

refactor(core)!: align public core types with the AI SDK V4 shapes - #219

Closed
cunninghamcard-bit wants to merge 67 commits into
rfc-0036/integrationfrom
rfc-0036/provider-result-types
Closed

cunninghamcard-bit wants to merge 67 commits into
rfc-0036/integrationfrom
rfc-0036/provider-result-types

Conversation

@cunninghamcard-bit

@cunninghamcard-bit cunninghamcard-bit commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What

Core public types in aimux-core take the AI SDK V4 provider and ai shapes: tool calls and results, output content, provider options and metadata, the provider prompt, and provider results. Base rfc-0036/integration; first of four (A to D), precedes B. Replaces #215, #216, #217 and #218, and takes over the range previously shown by #219.

Layers

Branch PR Content Diff against its updated base
rfc-0036/tool-types #215 One tool-call and one tool-result type per protocol layer 136 files, 1107 insertions(+), 25133 deletions(-)
rfc-0036/output-content-types #216 One type each for source, generated file, reasoning output, response metadata 127 files, 3896 insertions(+), 22622 deletions(-)
rfc-0036/provider-options-types #217 Provider options and metadata as namespace to JSON object 154 files, 1583 insertions(+), 7718 deletions(-)
rfc-0036/provider-prompt-types #218 Provider prompt by role, one file part 93 files, 4177 insertions(+), 12601 deletions(-)
rfc-0036/provider-result-types #219 Provider results carry request and response information 61 files, 986 insertions(+), 627 deletions(-)

Before and after

Area Before After Upstream reference
Provider tool calls Variants repeated fields; stream input mixed raw text and parsed JSON RawToolCall with input: String in GenerateContent and StreamPart; compatibility decoder and raw_tool_input heuristic deleted LanguageModelV4ToolCall
Call-layer tool calls Separate raw-call definition and heuristics ToolCall holds parsed Value (original string if parsing fails), invalid, error TypedToolCall
Provider-executed results Unused struct; repeated enum payloads Shared ToolResult: required tool_name, optional dynamic, provider_metadata LanguageModelV4ToolResult
Tool choice Always supplied CallOptions.tool_choice optional; generate_text, stream_text supply auto for a nonempty tool list prepare-tool-choice.ts
Tool examples, args Arbitrary JSON; nonobject args FunctionTool.input_examples is { input: object }; ProviderTool.args is an object LanguageModelV4FunctionTool, LanguageModelV4ProviderTool
Thought signatures Signature fields; with_signature path In provider metadata (output), provider options (input) Google and Vertex modules
Sources Inline fields; reduced SourcePart Source URL/document union LanguageModelV4Source
Generated files Repeated fields GeneratedFile with GeneratedFileData (data or URL; URL keeps original_url) LanguageModelV4File, SharedV4FileDataUrl
Reasoning, content, usage Text-only reasoning; no custom content, reasoning files, approval requests ReasoningOutput keeps metadata; reasoning is text or ReasoningFileOutput; new content variants in provider content and streams; InputTokenUsage and OutputTokenUsage replace one usage type, raw usage is a JSON object LanguageModelV4ReasoningFile, LanguageModelV4CustomContent, LanguageModelV4ToolApprovalRequest, LanguageModelV4Usage
Output conversions Citations and thought output missing or misclassified OpenAI Responses maps file, container-file, file-path citations to document sources; Google and Vertex map retrieved documents; Vertex emits inline files and thought text as file/reasoning output Upstream provider modules
Provider options, metadata Option<Value>; arbitrary JSON maps SharedProviderOptions, SharedProviderMetadata = HashMap<String, JsonObject>; provider_namespace rejects non-objects SharedV4ProviderOptions, SharedV4ProviderMetadata
Option fields Reviewed paths passed invalid values or forwarded unknown keys Schema-directed parsing in OpenAI chat and Responses, Groq, DeepSeek, Anthropic, xAI Responses, Cohere, Google, Mistral: invalid known fields error, unknown fields discarded; Cohere, Google, Mistral converters return errors Upstream option schema modules
Cohere, Bedrock options Invalid thinking defaulted to enabled; fractional budget dropped; only bedrock read Invalid objects error; numeric tokenBudget kept; amazonBedrock first cohereLanguageModelChatOptions, amazon-bedrock-chat-language-model.ts
Provider prompt One struct with call-layer ContentPart for all roles System (string), user, assistant, tool messages with own part unions and hyphenated tags; assistant adds reasoning-file, custom; tool adds tool-approval-response; one FilePart with tagged FileData LanguageModelV4Message, LanguageModelV4FilePart
Tool result part Free JSON result, error flags Required tool_name; output is text, json, execution-denied, error-text, error-json or content (text, file, custom) LanguageModelV4ToolResultOutput
Vendor prompt conversion Inputs dropped or coerced OpenAI non-object tool input becomes {}; DeepSeek resolves its references, Groq and Compatible reject them; Responses references become file IDs; Google tool content URLs become fileData; Bedrock emits S3 image locations; Cohere, Mistral, xAI, Vertex reject unsupported inputs convert-to-* modules
Provider result GenerateResultWire flattened request body, response headers GenerateResult serializes directly with optional request: RequestInfo, response: ResponseInfo; StreamResponseInfo has optional headers LanguageModelV4GenerateResult, LanguageModelV4StreamResult
Call result No top-level request; flat raw path GenerateTextResult.request, response (defaults for missing metadata); raw is GenerateResult without them; aggregation keeps request and last step's response generate-text.ts, stream-text.ts
Stream chunks TextStreamPart was the provider union Distinct TextStreamPart; metadata in FinishStep.response; no output gives NoOutputGenerated stream-text.ts, NoOutputGeneratedError
OpenAI Responses, Azure streaming Returned request body had stream: true Only the HTTP body has it openai-responses-language-model.ts

TypeScript-only constraints (JSONSchema7, dotted IDs, NonNullable, URL, Date, numeric types, inner undefined in JSONObject) are not enforced upstream at runtime; Rust keeps Value, String, u32 and adds no checks.

Tests

  • Deleted contract-tests/ (self-generated JSON, runner) and the 16 tests reading it; they recorded Rust's former output. CI, scripts and docs stop using them.
  • Deleted hand-written tests that pin removed shapes, including invented-data tests in openai_output.rs and moa.rs.
  • Restored and adapted: tool-choice cases from the upstream OpenAI, Groq, Mistral, Bedrock suites; tracker metadata, Google files and Bedrock message-conversion tests; Groq tool-argument and reasoning cases, Google model-path cases, Mistral strict-tool case.
  • Migrated retained upstream ports and cassette replays; cassette_full_test.rs matches TextStreamPart; stream tests expect FinishStep then Finish; round-trip callers use generate_text_result_to_chat_completion. Hand-written end-to-end files remain.
  • Older provider suites (some annotated as upstream ports) are deleted; no full coverage is claimed. DeepSeek request and Groq request-body cases return on B; bedrock_remaining_test.rs, anthropic_remaining_test.rs return in C. Cassettes and fixtures/ unchanged.

Not in this pull request

  • Provider ToolChoice serializes auto, none, required as bare strings; upstream uses tagged objects. D does not name ToolChoice.
  • Serde naming, optional-field encoding, required-nullable Node fields: covered by D. The call-layer ModelMessage tool-result shape: covered by D. D does not mention the TextStreamPart tool-result payload (still the provider ToolResult), a tool-error event for invalid calls, or the stream's Result error channel.
  • A repaired call failing validation is still wrapped as a repair error; invalid-call fallback uses plain JSON parsing, not the secure parser. D rethrows a repair function's parse failure as is.
  • Anthropic document citation sources, Bedrock response body and user-file media detection, OpenAI Responses approval-ID mapping, custom-tool async replay: covered by C.
  • No namespace-constant migration or boundary script; no claim of exhaustive schema or per-vendor test parity. Other model families' result contracts need separate comparison.

Relation to the maintainer's design

Reference order: the pinned AI SDK source first; where it has no counterpart (C ABI, bindings, recording, code generation), the maintainer's RFCs 0039 (ops and bindings, #209), 0040 (provider construction and auth, #210), 0041 (replay governance, #211) and 0042 (V4 type boundaries, #212), which are Drafts that refine the #200 design doc docs/aisdk-architecture-alignment.md; that design doc only where those RFCs defer to it. Where an RFC restates upstream behaviour differently from the pinned source, upstream wins and the RFC gets an amendment request. The stack targets rfc-0036/integration; RFC-0039 §11 and RFC-0040 §7.2 require the whole closure to switch to master at once, so items marked "before master" must land on integration first. "Branch E" is the follow-up branch stacked on D, in progress.

  • Crate split (before master). RFC-0040 §2 and RFC-0042 §1 place the V4 traits and types in aimux-provider, user ModelMessage and transport in aimux-provider-utils, factories and protocol code in aimux-providers, operations and registry in aimux, recording and replay in aimux-devtools; the docs: full-chain AI SDK alignment design and impact map #200 design doc §2.1 adds that no compatibility re-exports remain. This stack keeps all code in aimux-core (56 files, about 22,900 lines in its top two directory levels); behaviour is unaffected, and those dependency directions are not yet enforced by the compiler.
  • Provider options and metadata (agrees). RFC-0042 §6.2: namespace → JSON object, unknown keys kept. This PR's SharedProviderOptions and SharedProviderMetadata have that shape.
  • Other items are in the PRs they concern: the recording schema and descriptors in B; tool-call repair, the error JSON, code generation, the C ABI and Kotlin in D, with their implementation in branch E.

Verification

Gate on the head c16749d6: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, cargo doc with -D warnings, tests of core, provider-utils, providers, ffi, web, replay and cli: 2,364 passed, 0 failed, 8 ignored; Node and Python bindings cargo check; gen_ts_types.py --check; gen_providers_doc.py --check. Cassettes and fixtures/aisdk unchanged; contract-tests/fixtures/tool-call-repair.json and wire-format.json (fixtures generated by aimux itself) are deleted. Diff against the base: 330 files changed, 10710 insertions(+), 67662 deletions(-). After the restack onto the current master, only this head was tested; the intermediate layers were checked with fmt and clippy only.

Restacked head 5342962b. Restack (2026-10-09): merged master (#213, SSE) and the rewritten #204 tracker down the stack by merge, no history rewrite. This layer re-applies its V4 output change (StreamPart::ToolCall(RawToolCall { input: String, .. }), provider_namespace) to the rewritten tracker and updates the MIGRATION-NS.md note on tool-call metadata. Checked: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, tests of provider-utils and providers (103 test binaries, 0 failed). cargo doc, binding checks and the generator --check scripts were not rerun.

Not verified

No GitHub check runs were attached at the last refresh. Go, Java, Kotlin, Swift and Flutter were not compiled locally in that refresh; Node npm test and Python pytest were not run. Live vendor calls and unported upstream cases are outside validation.

🤖 Generated with Claude Code

chenhaonan and others added 30 commits October 4, 2026 16:56
aimux-core defined "tool call" five times and "tool result" four times, with
`input` meaning raw text in some and parsed JSON in others, so
`raw_tool_input` had to guess which. Mirror the AI SDK: one type per layer.

- Provider output: `tool::RawToolCall` (moved from parse_tool_call), input is
  always the raw argument string. `GenerateContent::ToolCall(RawToolCall)`.
- Core output: `tool::ToolCall`, input always parsed. Unchanged.
- `tool::ToolResult` is now the union of the old variant fields and is the
  payload of both `GenerateContent::ToolResult` and `StreamPart::ToolResult`.
- `StreamPart<C = RawToolCall>` is generic over the tool-call payload;
  `TextStreamPart = StreamPart<ToolCall>` is what `stream_text` produces and
  later stages consume. `map_tool_call` re-emits the parsed call.
- ts-rs exports the generic with `concrete(C = ToolCall)`, so the TS shape of
  the stream part is unchanged; bindings regenerated.
- Deleted `raw_tool_input`, `deserialize_tool_call_input` (and its legacy-shape
  test) and every Value::String round trip of tool input.

Wire format is unchanged except that a provider-layer `StreamPart::ToolCall`
input is now always a JSON string. aimux-providers, aimux-ffi, bindings and
tools are intentionally not updated here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Rebuilding the raw call from an invalid `ToolCall` whose error did not
record the argument text used `input.to_string()`, which turns the stored
text `{broken` into the JSON string literal "{broken" and makes malformed
arguments look valid on the next parse. `invalid_tool_call` stores
unparsable text as a JSON string by definition, so that string is returned
as is; a structured input is serialized. The AI SDK keeps the original
`toolCall.input` here (parse-tool-call.ts).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…types

Follows the core change: a provider emits `RawToolCall` (raw argument
text) in `GenerateContent::ToolCall` and `StreamPart::ToolCall`, and
`tool::ToolResult` for provider-executed results. Streams after
`stream_text` are `TextStreamPart`.

- Vendor packages: variants built with the shared structs; the places that
  wrapped the argument text in `Value::String` pass the text itself.
- Tests: provider-layer inputs are compared as strings; tests reading a
  `stream_text` stream match the parsed `ToolCall`.
- FFI, Node, Python: the forwarded stream is typed `TextStreamPart`.
  Exported symbols and JSON output are unchanged.
- Generated TypeScript types regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
With one struct per layer, the remaining places that passed or rebuilt a
tool call or result field by field take the struct:

- `response_messages::tool_result` takes a `ToolResult` instead of seven
  parameters; the two callers in generate.rs no longer destructure one to
  pass the same fields on.
- parse_tool_call.rs: the three places that copied every field between
  `RawToolCall` and `ToolCall` share one private conversion each way.
- replay.rs passes the recorded `RawToolCall` through.

No behavior or public API change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…shape note

- CHANGELOG (Unreleased, Breaking): the new types, where each is used, and
  the three wire-format changes.
- docs/api/gaps.md §9 said a pre-refactor `GenerateResult` with a parsed
  `input` keeps loading through a compatibility deserializer. That
  deserializer is removed; the section now says so and describes the
  provider stream and `TextStreamPart`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
aimux-core defined a source, a generated file and a reasoning segment twice
each: inline in `GenerateContent`/`StreamPart` and again as the lossy
`SourcePart`/`FilePart`/`ReasoningPart` of the text results, which dropped
`provider_metadata` (so the reasoning signature never reached the top level).
`StreamPart::ResponseMetadata` repeated the fields of `types::ResponseMetadata`.
Mirror the AI SDK: one type, reused in content, stream parts and the result
projections.

- `result::Source`, `result::GeneratedFile`, `result::ReasoningOutput` are the
  payloads of `GenerateContent::{Source,File,Reasoning}` and
  `StreamPart::{Source,File}`, and the elements of `sources`, `files` and
  `reasoning` on `GenerateTextResult` and `StreamTextResultAggregated`.
- `StreamPart::ResponseMetadata` wraps `types::ResponseMetadata`.
- Deleted `SourcePart`, `FilePart`, `ReasoningPart`.
- `ResponseMessageBuilder::reasoning` takes the `ReasoningOutput`.

Variant JSON is unchanged; the top-level `sources`/`files`/`reasoning` arrays
now carry the full element (additive `provider_metadata`, and `url`/`title`
serialize as null when absent). aimux-providers, aimux-ffi, bindings and tools
are intentionally not updated here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…ent types

Follow-up to the aimux-core change: providers and their tests build
`Source`, `GeneratedFile`, `ReasoningOutput` and `ResponseMetadata` payloads in
the newtype variants instead of repeating the field lists. TypeScript types are
regenerated (`SourcePart`/`FilePart`/`ReasoningPart` become `Source`,
`GeneratedFile`, `ReasoningOutput`; `StreamPart` and `GenerateContent` reference
them), the Node API doc names the new types, and the changelog records the
change as breaking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…adata

The AI SDK types both as Record<string, JSONObject> (namespace -> object).
aimux-core carried four looser representations: ProviderMetadata was an
arbitrary serde_json::Value, SharedProvider{Options,Metadata} mapped a
namespace to any Value, and per-part / per-message options were Option<Value>.

Now JsonObject is serde_json::Map, SharedProviderOptions and
SharedProviderMetadata are HashMap<String, JsonObject>, and types::ProviderMetadata
aliases SharedProviderMetadata. Every provider_options field (parts, messages,
call/generate options, tools) is Option<SharedProviderOptions> and every
provider_metadata field (content, stream parts, results, tool types) is
Option<ProviderMetadata>. Valid {"ns": {...}} JSON serializes as before; shapes
whose namespace value is not an object are now rejected on deserialization.

shared::provider_namespace(namespace, object) builds a one-namespace map from a
json! literal, replacing the several hundred json!({"ns": {...}}) sites in the
vendor packages. Core and provider-utils tests are converted; generated
TypeScript bindings are regenerated for aimux-core. aimux-providers, ffi and
the aimux-web target do not compile until they are migrated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…tions and metadata

Follows the core change: provider options and provider metadata are
`HashMap<String, JsonObject>` (namespace -> JSON object), the AI SDK's
`Record<string, JSONObject>`. Every vendor package now builds its metadata
with `provider_namespace(ns, json!({..}))` or by inserting an object under
its namespace, and reads options with `options.get(ns)`, which yields the
object directly. The emitted JSON is unchanged: same namespaces, same keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…nd metadata

- Provider tests build options with `provider_namespace` or from a JSON
  literal and compare metadata by namespace; assertions are unchanged.
- FFI, Node and Python: the streaming-transcription session options carry
  `SharedProviderOptions`, so a non-object namespace value is rejected when
  the options JSON is parsed instead of reaching the provider.
- CHANGELOG: the typed shape and what it rejects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…vider prompts by hand

What: deletes 34 test files under aimux-providers/tests. Each one builds
provider prompts from the old message struct and asserts request JSON or
parsed output that a hand-written mock server returns.

Why: the next commit replaces the provider prompt type. These files pin
Rust's own earlier behaviour rather than a recording or an upstream sample,
so they are removed instead of being rewritten. The same files are removed
on the provider factory branch. Recorded-cassette tests, upstream-sample
tests and the remaining suites stay and are converted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: `LanguageModelPrompt` is now `Vec<LanguageModelMessage>`, an enum by
role: a system message holds a string; user, assistant and tool messages
hold their own part unions (`UserPart`, `AssistantPart`, `ToolPart`). The
part structs (`TextPart`, `FilePart`, `ReasoningPart`, `ToolCallPart`,
`ToolResultPart`) are each defined once. A provider sees exactly one file
part, whose `data` is the existing `FileData` union.
`convert_to_language_model_prompt` is the single conversion from user
messages: it folds the five user-facing file variants into `FilePart` and
rejects a part its role does not allow, or a system message that is not
plain text, with `InvalidPrompt`. `LanguageModelPromptMessage` is removed.

Why: this is the shape of upstream's `LanguageModelV4Message`. Before, every
provider received the user-facing part union for every role, so each
converter repeated five file arms and handled role and part combinations
that cannot occur. The user-facing `ModelMessage` and `ContentPart` and
their JSON are unchanged; the tool result fields and the two signature
fields keep their current form and are listed as remaining differences.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…and tests

What: the vendor converters (OpenAI chat and Responses, Open Responses,
Anthropic, Bedrock, Google, Cohere, Mistral, xAI, Hugging Face), the replay
helpers, the web and replay tools and the remaining tests match on
`LanguageModelMessage` and the per-role parts. The five file arms in each
converter become one arm that matches `FilePart.data`; arms for role and
part combinations the types rule out are deleted. TypeScript types are
regenerated.

Why: follow-up to the core change. Request JSON for a given prompt is
unchanged; the recorded-cassette and upstream-sample tests pass without any
change to cassettes or fixtures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
… upstream does

What: `GenerateResult` has `request: Option<RequestInfo>` and
`response: Option<ResponseInfo>`; `ResponseInfo` holds the response id,
timestamp and model id next to headers and body. `StreamResult` has
`request` and `response: Option<StreamResponseInfo>` (headers only). The
flat `request_body` and `response_headers` fields are removed, and
`response` is no longer required. Vendors, tools, FFI and tests build and
read the new fields. The call layer fills a missing response id, timestamp
and model id where upstream `generateText` and `streamText` do, so a
provider no longer has to invent them.

Why: this is the shape of upstream's `LanguageModelV4GenerateResult` and
`LanguageModelV4StreamResult`. Before, a provider had to return response
metadata even when the API gave none, and the response body had no place.

Kept on purpose: the user-facing results and their JSON do not change. The
`raw` field of `GenerateTextResult` still serializes the flat keys through a
small adapter, because six language bindings parse that shape; it goes away
with `raw` in the user-result pull request. No cassette or fixture changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
The changelog is what binding authors read before upgrading; the prompt type
change alters the provider trait input and the recorded prompt JSON.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…ds as breaking

The provider trait output changes shape, so implementers of a custom
provider need the old and new field names side by side.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: Cohere and Mistral decide image versus document by the top-level
media type, so a bare "image" is an image again. Inline text file data is
rejected where upstream rejects it (OpenAI Responses, Mistral, Cohere
images) and Google falls back to text/plain for an incomplete media type.
The provider tool-result part no longer accepts "output" as a second name
for "result".

Why: an independent review found that the move to one file part changed
these three behaviours; the alias gave one field two spellings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the media-type, text-data and tool-result alias fixes up the
stack so every branch above the prompt types has them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…fields

What: provider content and stream parts gain custom, reasoning-file and
tool-approval-request. A generated file holds data or a URL only. Input
and output token details are separate types with upstream's fields, and
raw usage is a JSON object. Request and response information carry body
and headers, and a URL file keeps its original URL. Hand-written tests
that pinned the old shapes are deleted rather than rewritten.

Why: an independent field-by-field comparison with the pinned upstream
content, stream-part, usage and result types found these differences.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: provider_namespace returns an error for a non-object value instead
of replacing it with an empty object. Callers follow. Hand-written tests
that pinned the old behaviour are deleted rather than rewritten.

Why: upstream types provider options as namespace to JSON object; silently
replacing a caller's value hid mistakes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…uments

What: CallOptions.tool_choice is optional and each vendor omits it when
unset, as its upstream package does. FunctionTool.input_examples is a list
of { input: object } and ProviderTool.args is a JSON object. Hand-written
tests that pinned the old shapes are deleted rather than rewritten.

Why: an independent field-by-field comparison with the pinned upstream tool
types found these three differences. The project keeps cassette replays,
upstream samples and contract fixtures as its tests; those still pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the tool type changes up the stack. Conflicts were confined to
hand-written test files both branches had trimmed; files that the prompt
types branch deletes anyway are removed here, the rest keep this branch's
side.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the content and tool type changes up the stack. The newly
arrived code is converted to this branch's typed provider options and
provider metadata. Hand-written tests that no longer compile are deleted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: brings up the tool, content and provider-option type changes and
converts this branch's code to them. In the provider prompt, a tool result
has a required tool name and a structured output with upstream's six
variants instead of a free JSON value plus flags. Reasoning and tool-call
signatures travel in provider options under the vendor namespace. The
assistant side gains reasoning-file and custom parts and the tool side
gains tool-approval-response. Hand-written tests that no longer compile
are deleted.

Why: a field-by-field comparison with the pinned upstream prompt types
found these differences. The user-level message types are unchanged; the
contract fixture pins them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: removes stream_error_semantics_test.rs and
tool_result_and_reasoning_fix_test.rs.

Why: the project keeps cassette replays, upstream samples and contract
fixtures as its tests. These two files asserted behaviour written by hand
against a mock and had been rewritten rather than deleted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the tool, content, provider-option and prompt type changes up
the stack. This branch's request and response information is kept and the
newly arrived code is converted to it. Hand-written tests that no longer
compile are deleted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: removes 86 test functions and in-file test modules that the branch
below had deleted and the last merge resolution restored.

Why: the project keeps cassette replays, upstream samples and contract
fixtures as its tests. The difference from the branch below is now only
this branch's own work on request and response information.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: the type tags of provider prompt parts and tool result outputs are
tool-call, tool-result, reasoning-file, tool-approval-response,
execution-denied, error-text, error-json.

Why: these types are new in this stack and no fixture pins them, so they
take upstream's spelling instead of snake_case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the tag spelling fix up the stack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
@cunninghamcard-bit

cunninghamcard-bit commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

审批类型与 core 处理需要按最新职责边界收窄(核对 head c16749d6)。

本轮确认的范围调整:aimux 不承担审批运行时,也不定义统一的审批公共类型/语义;审批相关的定义、解释和处理由上层负责。这是对 #200 §0.4 原先“保留审批 variant”范围的收窄,不是否认 AI SDK 中存在这些类型。

本 PR 已实际引入:

请移除这套统一审批公共类型、专用错误及 core 关联/重建逻辑,连同相应导出和类型参数一起调整。调整时遵守已有的数据返回契约,不破坏已承诺的 raw/metadata 等通用行为。本评论不要求为审批新增原始输入透传、Raw 事件、回放或审批往返能力,也不将这些新能力作为删除审批专用类型的前置条件。

此范围调整需与 #220 的 provider 转换、#225 的 wire/绑定及设计文档同步。

chenhaonan and others added 2 commits October 9, 2026 14:27
Amp-Thread-ID: https://ampcode.com/threads/T-01a11e83-bf7b-75bb-900b-34851e6f12ee
Co-authored-by: Amp <amp@ampcode.com>

# Conflicts:
#	aimux-provider-utils/src/streaming_tool_call_tracker.rs
#	aimux-provider-utils/tests/streaming_tool_call_tracker_test.rs
#	bindings/node/src/types/InputRecord.ts
What: `apply_tool_call_repair_to_result` rewrote `tool_calls` and the
`response_messages` transcript but left `content` with the unrepaired call.
After repairing `{"city":7}` to `{"city":"Tokyo"}`, `tool_calls` said Tokyo
while `content` still said 7, so one result described two different calls.
The matching `content` entry, and the call an approval request embeds, are
now replaced with the repaired call. A repair that renames the call also
renames the transcript's approval request, so it keeps pointing at the call.
`raw` still holds the provider's output unchanged.

Why: every field of a result has to describe the same tool call; a host
reading `content` acted on the arguments the repair had replaced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeURmKQzedTSiu2vL3yVes
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