Repository navigation
[3/8] refactor!: messages and content follow the AI SDK - #251
Merged
Merged
Conversation
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a0eb91-5e79-775e-975e-b85e2ac49ab6 (cherry picked from commit 7f68ac3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 758d0bc)
The returned impl Iterator is already must_use, so clippy's double_must_use rejected it. (provider.rs part of commit 8397424) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…e removal - scripts/gen_providers_doc.py: the `provider_name` module no longer exists; the docstrings and the section-header guard now name only `provider`. `--check` output is unchanged. - docs/internal/phase4-audit.md: add a follow-up note (same style as RFC-0020) so the historical audit no longer reads as if the generated ProviderName types and their drift tests still exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS (cherry picked from commit dfcd54f)
The A2 line said Node would keep a generated string-literal union for completion. It does not: overlay-registered provider names can never be part of a compile-time union, so keeping one would keep the second name path this PR removes. Record the actual A2 scope. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS (cherry picked from commit 3e2d9fc)
Pull requests into a branch other than master got no CI, so a stacked PR could not show it was green against the PR below it. The pull_request trigger now has no branch filter. A new push to a PR cancels that PR's run still in progress; pushes to master are never cancelled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…that pin Rust-only behaviour The V4 migration that follows replaces these tests with tests ported from the AI SDK. They are deleted first, on their own, so the PRs after this one show only code changes. This commit removes only test files (Rust, the seven bindings, the shared contract fixtures and their Node runner) and the CI steps that ran the contract fixtures. No library code changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Amp-Thread-ID: https://ampcode.com/threads/T-01a0eb91-5e79-775e-975e-b85e2ac49ab6 Co-authored-by: Amp <amp@ampcode.com> (cherry picked from commit dc1cd14)
…ia alias - Wire*.ts were hand-maintained mirrors of wire.rs and had already drifted (WireContentPart lacked provider_executed). Derive TS on the wire structs and export them to web/src/types/generated/ through gen_ts_types.py, which now handles two targets and checks both in CI. - Core types resolve through the @aimux/types tsconfig alias instead of five-level relative paths into bindings/node. - gen_ts_types.py sync no longer rewrites files that differ only by ts-rs end-of-line spaces. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 69c1351)
No crate in the workspace referenced the tracker; providers accumulate streamed tool-call deltas themselves. Drops the module, its 787-line test file and the re-exported companion types. Breaking for aimux-stream's public Rust API; noted in CHANGELOG under Unreleased. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EYDYDWYcPsDmjBjFeuDVe (cherry picked from commit fe8758f)
Re-adds the tracker as a port of the current @ai-sdk/provider-utils streaming-tool-call-tracker.ts instead of the older index-only version removed in the previous commit. - Correlate deltas by wire id, index and function name; drop ambiguous ones - Unique tool-call ids with bounded suffixes; blank generator output falls back to "tool-call" - Ignore unmatched blank function names; retain blank-name continuations - Flush in index order only when every call has an index - New StreamingToolCallArgumentState / starts_with_structured_value port - process_delta and flush return the emitted parts instead of buffering Tests are ported case for case from the upstream suites (45 tracker cases, 9 argument-state cases). No caller in the workspace uses the tracker yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EYDYDWYcPsDmjBjFeuDVe (cherry picked from commit 6035201)
…nd wire the OpenAI stream to it The AI SDK keeps StreamingToolCallTracker in @ai-sdk/provider-utils, where every provider that speaks the OpenAI chat-completions wire format imports it. aimux had the tracker in aimux-stream (the eventsource-parser role), with its own ToolCallStreamPart event type and no callers, while openai/model.rs kept an index-only accumulator. This commit puts the pieces where the AI SDK has them. aimux-stream - StreamingToolCallTracker, StreamingToolCallArgumentState, starts_with_structured_value and ToolCallStreamPart move out; the crate is SSE decoding only. serde_json becomes a dev-dependency (test fixture); the `tool-calls` keyword is dropped. aimux-provider-utils - streaming_tool_call_tracker.rs / streaming_tool_call_argument_state.rs land here, logic unchanged. The tracker emits aimux_core::StreamPart ToolInputStart / ToolInputDelta / ToolInputEnd / ToolCall directly, so there is no second event type (RFC-0036: ToolCallStreamPart must not be a second public protocol). The generic metadata parameter becomes serde_json::Value / ProviderMetadata; TrackerError converts into AiMuxError::InvalidResponseData. The ported upstream tests (45 + 9) move with it, projecting StreamPart back to the upstream event shape. aimux-providers - openai/model.rs replaces its HashMap<usize, ToolCallAccumulator> with the tracker: tool_calls deltas are correlated by wire id, index and function name instead of index alone; id-less continuations follow their call; indices reused across parallel calls stay distinct; ambiguous deltas are dropped; a call without a wire id gets a generated `tool-call` / `tool-call-N` id instead of an empty string; a new call without a function name ends the stream with InvalidResponseData (AI SDK behaviour) instead of starting a call with an empty name. - DeltaToolCall.index is Option<usize> (the AI SDK schema is `index: z.number().nullish()`). Every registry-backed provider goes through openai/model.rs, so this is the one plug point until S4-2 folds the mistral and xai chat copies into the same implementation. Verification: cargo test -p aimux-stream -p aimux-provider-utils; cargo test -p aimux-providers (129 suites, 3041 passed, 0 failed, including the cassette replays); cargo clippy -p aimux-stream -p aimux-provider-utils -p aimux-providers --all-targets -- -D warnings; cargo fmt --all -- --check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS (cherry picked from commit df4d09f)
…-state port Carried over from the SSE test port commit so the two split branches leave the file identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS (cherry picked from commit 7bc200e)
Describes only the final API: the tracker lives in aimux-provider-utils and emits StreamPart directly; the intermediate ToolCallStreamPart- returning API from earlier commits is not mentioned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS (cherry picked from commit 264ade3)
… Rust Same behaviour as the upstream port (checked differentially against the previous implementation on ~1.1M random deltas), with Rust structure: - Correlate by linear scan over per-call wire ids and indices instead of id/index hash maps re-sorted on every delta; no per-delta allocations besides the emitted parts. - `finish(self)` consumes the tracker, so the never-true-before-flush `has_finished` state is gone and post-flush use cannot compile. - `StreamingToolCallDelta<'a>` borrows from the provider wire type and carries `provider_metadata`, replacing `StreamingToolCallFunction`, the untyped `extra` bag, the test-only builders and the two boxed metadata hooks. - `process` / `finish` append to a caller-owned buffer. - Drop the unreachable `TrackerError::IdExhausted`. - The argument state scans bytes and models completion as its own state. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a11e83-bf7b-75bb-900b-34851e6f12ee (cherry picked from commit b0fd923)
…es and docs With the tracker in aimux-provider-utils, aimux-stream only decodes SSE: serde_json is needed by its tests alone, and the tool-calls keyword no longer applies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
- Drop the redundant #[must_use] on provider_names(); the returned impl Iterator is already must_use, so clippy::double_must_use failed CI. - Gate the wire types' ts-rs export tests behind a ts-export feature that only gen_ts_types.py enables. A plain cargo test previously wrote Wire*.ts into bindings/node/src/types via the workspace-wide TS_RS_EXPORT_DIR, which made gen_ts_types.py --check fail on stale files. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 8397424)
…ow the AI SDK V4 types - Content: one source, file and reasoning type per layer. A source is a url source or a document source; file citations and grounding documents map to document sources. The missing upstream content variants and usage fields are added. - Provider options and provider metadata have one typed shape. Vendor packages build and read them; a provider option that is not an object is an error, unknown keys no longer leak, and option parse errors surface for Cohere, Google and Mistral. Bedrock reads amazonBedrock before bedrock. - The provider prompt is modelled by role with one file part. Part tags use upstream's hyphenated names; references, files and tool output convert as upstream does, and file parts keep upstream's media-type and text-data rules. - The #226 decision adapters and Jev follow the typed options, metadata and split usage types. - Hand-written tests that built provider prompts by hand or pinned Rust-only behaviour are deleted; ported upstream tests are restored. FFI, bindings and their generated types follow. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
One tool struct per layer, field for field with the pinned upstream types:
- CallOptions.tool_choice is optional. Vendors omit an unset choice, as
their upstream packages do; generate_text and stream_text default it to
auto when tools are given, as upstream prepare-tool-choice does.
- FunctionTool.input_examples is a list of { input: object } and
ProviderTool.args is a JSON object.
- Tool calls carry no separate thought signature. The value travels in
provider metadata on output and provider options on input, under the
vendor namespace, where each upstream vendor package reads and writes it.
- Call sites pass the tool structs instead of their field lists.
The fifteen tool-choice tests ported from upstream (OpenAI, Groq, Mistral,
Bedrock) are kept and adapted; hand-written tests that pinned the old
shapes are deleted. CHANGELOG and docs/api/gaps.md record the wire-format
changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Since messages follow the AI SDK V4 types, a reasoning part keeps its signature in provider_options.anthropic.signature, as the Node test already does. The Go round-trip test still sent the removed top-level `signature`, so no thinking block reached the wire. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…uctor The content field was added to StreamTextResultAggregated and to its generated fromJson, but not to the constructor, so lib/types.g.dart did not compile and every test file importing the types failed to load. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
6 of 12 tasks
…more Deleting the hand-written typed GenerateTextResult test left _TypedArgs.maxOutputTokens and .temperature without a caller, which flutter analyze reports as unused_element_parameter warnings and fails the job. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The file is a working note for the provider options / metadata namespace migration and says on line 3 that it must not be committed. Nothing in the tree links to it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
fixtures/aisdk/VERSIONS.json lists the seven AI SDK packages that the
upstream comparison tests are pinned to: ai 7.0.127, @ai-sdk/provider
4.0.21, @ai-sdk/provider-utils 5.0.53, @ai-sdk/anthropic 4.0.71,
@ai-sdk/google 4.0.87, @ai-sdk/openai 4.0.83 and
@ai-sdk/openai-compatible 3.0.62.
Every entry has one shape, {"version": "<x.y.z>", "integrity":
"sha512-<base64>"}, where integrity is the sha512 SRI of the npm tarball
(the registry's dist.integrity). A bare version string is not a valid
entry. The file used to arrive later in the stack, together with the
provider factories, as bare version strings; it comes first now so that
every later change can be checked against the pinned upstream sources.
Ported from fork commit 9d7508c.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
scripts/fetch-aisdk.sh downloads every package pinned in
fixtures/aisdk/VERSIONS.json with npm pack and unpacks it into
target/aisdk/<package>@<version>/, so the upstream sources that the
comparison tests follow can be read locally at exactly the pinned
versions.
- The whole VERSIONS.json is checked before anything is downloaded:
every entry must be {"version", "integrity": "sha512-..."}, with a
plain npm package name (it becomes a path) and an exact version.
- Each tarball's sha512 must equal the pinned integrity, and its
package.json must name the pinned package and version.
- A package is unpacked into a work directory under target/aisdk and
renamed into place only after its checks pass, so a failure never
leaves a partial tree. A rerun replaces each tree with identical
content.
AISDK_VERSIONS and AISDK_DEST override the input file and the
destination. scripts/fetch-aisdk.test.mjs (node --test) runs the script
against an npm registry served from the test process, with a temporary
npm cache and empty npm config files, so it needs no network.
Ported from fork commit 19db928. Unlike the fork, a bare version string
or a missing integrity is an error instead of a fallback to npm view,
because VERSIONS.json has a single format.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
scripts/fetch-aisdk.sh now verifies every package before it places any. Phase 1 fetches, checks and unpacks all packages into a staging directory; phase 2 moves the old trees aside, places the new ones and puts the old ones back if a move fails. A network error, a missing version or an integrity or version mismatch in any package leaves target/aisdk exactly as it was. The new test "a failing package leaves every destination as it was" fails on the per-package version. scripts/aisdk-fixtures/versions.mjs is what the fixture recorder uses to read and rewrite fixtures/aisdk/VERSIONS.json: - readVersions / checkVersions accept only the object form, with the same name, version and sha512 checks as fetch-aisdk.sh. - versionsDocument keeps the pinned integrity when the version is unchanged, takes it from package-lock.json when the lock pins the same version, and fails when neither has it or when the two disagree. It never writes an entry without integrity. versions.test.mjs (node --test) covers this and checks the committed VERSIONS.json. Its lockfile comparison is skipped until scripts/aisdk-fixtures/package-lock.json exists. Ported from fork commits ed1564b and 1ce4eb6. Unlike the fork, versionsDocument fails instead of writing a version-only entry, because VERSIONS.json has a single format. The recorder (record.mjs) is not part of this change; it switches to versionsDocument when it is added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
… 5.0.55 does The tracker followed @ai-sdk/provider-utils 5.0.53, which merges a second parallel call into the first when the stream reuses or omits labels: a named delta with an unseen id and empty arguments resolved to the matching call even after that call's structured arguments were complete, and an unlabeled continuation with several open calls was dropped as ambiguous. 5.0.55 (5094ebd) adds `hasEmptyArguments` (src/streaming-tool-call-tracker.ts:131, :140-141), passes `name != null && hasEmptyArguments` as the start evidence on the unseen-id path (:275-285), resolves the unlabeled path through `resolveMatchingToolCall` (:305-308), and lets that function pick the only candidate without complete structured arguments (:324-354). The Rust tracker now does the same. The three test cases added upstream in 5.0.55 (src/streaming-tool-call-tracker.test.ts:248-302, :304-341, :343-381 at tag @ai-sdk/provider-utils@5.0.55) are ported; the first and third fail on the previous tracker. fixtures/aisdk/VERSIONS.json pins provider-utils 5.0.55 with its registry integrity; the other entries are the object-form pins of the fork's VERSIONS.json. The user-agent version string is left alone. The alignment doc's #185 row records the new baseline. Closes #228 B10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
stream_text's pump stops polling at the terminal part and drops the provider stream, so TraceRecordingStream never saw end of stream for a completed call. Its Drop then traced every completed stream_text call as "stream dropped before completion" and the LCP lookup was skipped. The wrapper now writes the record when Finish passes, with Finish's usage. A natural end of stream without Finish and a drop mid-flight are recorded as before, and a stream that is drained after Finish is still recorded once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
cc5446f cut the TraceLayer half of cache_probe_test.rs only because it stopped compiling after the Usage and GenerateResult type changes. The ten tests come back on the current API: generate and stream records with usage, TTFT and session chain; part errors, transport errors and failed calls with their error text and counts; an early drop still recorded; the LCP upper bound in aggregates; scope keys isolating providers that share a model id; the R-2.2 route-affinity suppression and the C4-2 independent-evidence case. The TTFT check now reads the value; the deleted test matched "ttft_ms": in the JSONL, which a null also matches. Four new tests pin the call id taken from the recording context, body id and headers read from the result, a file part counted as first output, and the raw usage object kept on the record. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Under an enabled recorder, generate_text and stream_text through a TraceLayer trace each call under its recording's id, and replaying that recording sends the call the model saw. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
cc5446f deleted the three Finding 1 tests when the file variants became tuple variants, leaving their two Gemini image cassettes unreferenced. They come back on the current types: one generated PNG with its full base64 payload, text and image kept in order, and the streamed file part. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
cc5446f deleted the four in-crate request-replay tests, so replay_with_model had no test at this layer. They come back on the current types: a recording is rebuilt and resent, overrides apply, bad input options fail with a parse error, and differing provider options miss the exact matcher. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The only conflict was scripts/gen_ts_types.py. It keeps #247's multi-target structure (TARGETS, per-target sync and check) and drops the EXCLUDED set and manifest's exclude parameter: #246 deletes bindings/node/src/types/ProviderName.ts, so nothing is left to exclude. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
aimux-core's #[ts(export)] attributes were ungated, so every plain cargo test rewrote bindings/node/src/types through the TS_RS_EXPORT_DIR in .cargo/config.toml. aimux-core now has the same ts-export feature as tools/aimux-web (tools/aimux-web/Cargo.toml:21-23), and each of its 158 exports is #[cfg_attr(feature = "ts-export", ts(export))]. scripts/gen_ts_types.py enables the feature for the core target, and --check also fails on any ts(export attribute outside that gate in either crate's source, printing file:line. The .cargo/config.toml comment and the web README now say that cargo test writes nothing and the generator is the only writer. gen_ts_types.py --check passes: 159 node files and 8 web files are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
StreamingToolCallTracker::new() used "tool-call" as its id generator, so an OpenAI chat stream without wire ids produced tool-call, tool-call-1, and so on. Upstream defaults to the random generator (provider-utils@5.0.55 src/streaming-tool-call-tracker.ts:114) and uses "tool-call" only when a generator returns a blank id (:461). new() now defaults to generate_id (provider-utils@5.0.55 src/generate-id.ts), and FALLBACK_TOOL_CALL_ID is only the blank-output fallback. generate_id.rs is the same file a later layer of this stack adds, so the restack has nothing to merge there. The tracker tests that assert ids pass their generator explicitly; a new test checks that new() gives a call without a wire id a 16-character id. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in the tool call tracker's default generate_id. No conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Stacks the series on #247 (which carries #246): aimux-core's ts-rs exports run only under the ts-export feature that scripts/gen_ts_types.py enables, and the generated ProviderName types are gone. Each later layer gates the exports it adds in its own merge, so no layer leaves an ungated export. Resolution: scripts/local-ci.sh keeps this layer's version without the retired contract section; CHANGELOG keeps both sides, the ts-rs entry as a Fixed section before Removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #247 (with #246) through the layer below: ts-rs exports run only under the ts-export feature, and the generated ProviderName types are gone. Resolution: the aimux-core files keep this layer's code, and every export in them, including the ones this layer adds (ToolChoice, the tool and stream-part types), is gated as #[cfg_attr(feature = "ts-export", ts(export))]; CHANGELOG keeps both sides. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #247 (with #246) through the layer below: ts-rs exports run only under the ts-export feature, and the generated ProviderName types are gone. Resolution: the aimux-core files keep this layer's code, and every export in them, including the ones this layer adds to the message, content, result and stream types, is gated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #266 (a blank key on a custom base URL is sent as an empty key, as the AI SDK does for apiKey: ''). Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #266 (a blank key on a custom base URL is sent as an empty key, as the AI SDK does for apiKey: ''). Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #266 (a blank key on a custom base URL is sent as an empty key, as the AI SDK does for apiKey: ''). Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #266 (a blank key on a custom base URL is sent as an empty key, as the AI SDK does for apiKey: ''). Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in #266 (a blank key on a custom base URL is sent as an empty key, as the AI SDK does for apiKey: ''). Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Contributor
Author
Generated by Claude Code |
The fetch test pointed npm's user and global config at the same empty
file. npm refuses that ("double-loading config ... as global, previously
loaded as user") and exits before any request, so on CI every test that
downloads failed with "npm pack failed". It passed in the cloud only
because the environment's NPM_CONFIG_USERCONFIG overrode the test's own
setting.
The test now writes two files, and drops inherited npm_config_* variables
in any case before setting its own, so it runs the same everywhere.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in 6ca248e: the fetch test gives npm separate user and global config files, so the AI SDK pin scripts step in the generator drift job passes on CI. Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in 6ca248e: the fetch test gives npm separate user and global config files, so the AI SDK pin scripts step in the generator drift job passes on CI. Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
eric8810
approved these changes
Oct 17, 2026
eric8810
left a comment
Contributor
There was a problem hiding this comment.
复核通过,撤回修改请求。逐项核实:openai_output_tool_input_test.rs 恢复(5 个原测试 + 原文保真矩阵)✓;trace 层 10 个测试按新接口恢复并新增 4 个(call_id 取自录制上下文的三条路径、结果取值、文件部件、raw usage)✓;恢复过程中发现并修复"正常结束的 stream_text 被记为流提前丢弃、跳过 LCP 审计"的真实缺陷(4b212066)——这正说明该套件值得恢复 ✓;metadata 未知 namespace/嵌套 null 保真测试、Gemini inlineData 测试与 cassette、FFI 测试、trace_recording_correlation_test.rs(录制回放链路自动化)全部落地 ✓;ToolResultPartJson 旧写侧在 [8/8] 删除、上游 tool-result 语义疑问已答(用户层仅 output)✓。批准。
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [1/8], whose master merge eric made (2a0b02b). The tree is unchanged: this branch already had all three. Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [2/8]. The tree is unchanged: this branch already had all three. Resolution: no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
eric8810
changed the base branch from
v4/2-tool-calls-follow-the-ai-sdk
to
master
October 17, 2026 13:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by li
Summary
Before: core content, provider options, provider metadata and the provider prompt each have aimux-specific shapes, and vendors read options as untyped JSON. User messages are a role plus one content-part enum shared by every role, with no message-level provider options; reasoning parts carry a top-level
signature; text results reuse a content shape generic over the provider layer. Provider namespace maps are unordered, and a custom content kind is any string. The prompt goes to the model without upstream's checks, so a tool call with no result or a response that breaks the tool choice is not reported. The router unit tests and the error variant-set test are gone, and a scratchMIGRATION-NS.mdsits in the repository root.After: user messages are the AI SDK's
ModelMessage: a union by role, each role with its own content parts and message-levelproviderOptions, including tool approval responses and the deprecated tool output items (accepted with a deprecation warning).generate_textandstream_textstandardize the prompt as upstream does: empty messages are an invalid prompt, consecutive tool messages are combined, a tool call without a result fails withMissingToolResults, and a response that breaks the tool choice fails withToolChoiceViolation. Text results carry the AI SDK's coreContentPart(11 kinds, including tool errors and approval requests and responses), separate from the V4 provider content. There is one source, file and reasoning type per layer, and reasoning has no top-level signature. Provider options and metadata have one typed, insertion-ordered shape that each vendor package builds and reads; a non-object option is an error and unknown keys no longer leak. The provider prompt is modelled by role with one file part, using upstream's hyphenated part tags, and references, files and tool output convert as upstream does. A custom content kind must be${string}.${string}, andCallOptionsno longer carries a call id. FFI, bindings and their generated types follow; the router and variant-set tests are back.Type of change
Checklist
cargo fmt --all -- --checkpassescargo clippy --workspace --all-targets -- -D warningspassescargo test --workspacepasses (cassette playback — no network/keys)README.md,docs/,rfc/,bindings/README.md)Notes for reviewers
@ai-sdk/provider-utils5.0.53types/(model messages and content parts; unchanged in 5.0.55);ai7.0.127prompt/convert-to-language-model-prompt,prompt/standardize-prompt,generate-text/content-part,to-response-messages;@ai-sdk/provider4.0.21 shared provider options, metadata and references.call_idand the step helper leaveCallOptionshere; the retry count, timeout and session id follow in PR 4.messagesare still accepted, andinstructionsstays a string. Both would change the JSON that the bindings, CLI and web console send, so they wait for the binding migration.AIMUX_E_INVALID_PROMPTandAIMUX_E_INVALID_RESPONSE_DATA, and Node and Python raiseInvalidPromptErrorandInvalidResponseDataError.tool-output-deniedis only a stream part upstream, so it comes with the stream parts in PR 4. So do provider tool results with no matching call (NP2-3) and the stream tool error for an invalid call.titleandtoolMetadata: aimux has no user-level tool set (as in PR 2).content::ContentPart,MessageContentandRoleare replaced byUserContentPart,AssistantContentPart,ToolContentPartand their content types. The user tool result carries a typedoutput; the earlierresult/is_errorJSON is still read.GenerateTextResult.contentisVec<ContentPart>(the core part).TextContentis gone in Rust; Python keeps the name for the new union.ReasoningOutputhas nosignature, andextract_reasoning_signatureis removed. Signatures live in each provider's own namespace.SharedProviderOptionsandSharedProviderMetadataare oneNamespaceMap(IndexMap<String, JsonObject>);SharedProviderReferenceis ordered as well.convert_to_language_model_prompttakes the provider id as a third argument.CallOptions.call_idis removed; the id comes from the recording context.CallOptions::for_stepis now crate-private.MissingToolResultsandToolChoiceViolation, mapped in the FFI, Node and Python.cargo test --workspacepass;scripts/gen_ts_types.py --checkis clean; the Node binding type-checks withtsc. The other bindings treat result content as opaque JSON; their suites run in CI.ToolChoiceViolation, TOOLDEF-3 line refs and empty tool input{}; part 2 Q1 scope note, the §3.5 speech and transcription tracing note, and the tool-call acceptance row;docs/api/node.md,docs/api/rust.md; CHANGELOG Breaking bullets.null) and adds 4: the call id comes from the recording context on the success, stream and error paths, body/id/headers come from the result, a file part counts as the first output, and the raw usage object is kept.ffi-x86_64-unknown-linux-gnuffi-aarch64-apple-darwinffi-x86_64-pc-windows-msvcnode-x86_64-unknown-linux-gnunode-aarch64-unknown-linux-gnunode-aarch64-apple-darwinnode-x86_64-apple-darwinnode-x86_64-pc-windows-msvcnode-aarch64-pc-windows-msvc