Repository navigation
[10/13] refactor: tool-call repair is a trait - #258
Merged
Merged
Conversation
cunninghamcard-bit
marked this pull request as ready for review
October 13, 2026 07:01
This was referenced Oct 13, 2026
google-vertex's Node MaaS and xAI wrappers pass no headers to createOpenAICompatible. Their fetch spreads the resolved provider headers, then `Authorization`, over the request's headers (google-vertex-maas-provider-node.ts:34-58, google-vertex-xai-provider-node.ts:33-55). So a provider header and the bearer token win over a per-call header of the same name. The openai-compatible config gains `fetch_headers`, layered after the per-call headers in its single header merge. The partner sub-providers put their headers and token there, so auth errors keep their types and the headers still follow the credentialed-origin rule on redirects. This also fixes the node wrapper line numbers cited in the C15 test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…d replay (#229 NP2-5) The Part 2 acceptance list asks for core-level tests of the stream and result types. For the stream and results: - `TextStreamPart` has exactly the AI SDK's 26 part types, in the union's order (stream-text-result.ts:581-607), read from the exported TypeScript type so a new variant fails the test; - interleaved text and reasoning segments keep the position of their start part while deltas update them by id (stream-text.ts:1540-1600), and the response message replays them in that order; - a preliminary tool result reaches the stream but not the step content (stream-text.ts:1648-1650); - the same model output gives the same response messages whether it was generated or streamed. finish-step and abort are covered by the existing framing and abort tests. The content-part and message variant counts belong to the content layer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…nt event The JSONL recorder's bounded queue drops an event when it is full or the writer is gone, and both recorders mark a call inconsistent on a duplicate attempt or an exchange update that matches no exchange. Both only counted or remembered it: try_finalize, write_ready_all and the ring's finalize and flush still wrote the call with complete = true, which the alignment doc's completion barrier (§4.3) forbids. send_or_drop now records the call id of the event it drops, shared with the writer, and the writer writes a call that lost an event or is marked inconsistent with complete = false, whether its last event or a flush writes it. The ring recorder does the same for inconsistent calls. The recording schema number is unchanged. Tests drive the writer with a full queue and with duplicate / unmatched events, for both write paths and for the ring, and check that an intact call next to them stays complete. Fixes #232 S2-5. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
TranscriptionModelV4.doStream is optional (@ai-sdk/provider@4.0.22 src/transcription-model/v4/transcription-model-v4.ts:44-46), and experimental_streamTranscribe rejects a model without it before any I/O (ai@7.0.127 src/transcribe/stream-transcribe.ts:125-138). aimux had a default method that failed with UnsupportedFunctionality, so a missing method could only be found by calling it. - TranscriptionModel::do_stream returns Option<BoxFuture<..>>, None by default, as the alignment doc (§3.1, D22) specifies. - stream_transcribe reports None as UnsupportedFunctionality with the upstream message. - OpenAI, Azure, Google, Vertex, xAI and ElevenLabs return Some; per-model checks (a model ID that cannot stream) stay inside the future, as in the upstream doStream bodies. Groq, Mistral and the other batch-only models return None. - Tests: transcription_model::tests::a_model_without_do_stream_cannot_stream, groq_transcription_test::the_model_has_no_streaming_method. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
createAnthropicStreamError sets `data: 'data' in error ? error.data :
error` (anthropic-language-model.ts:91). aimux built its own
`{type: "error", error: {type, message}}` instead. The error-chunk
schema's `code` and `data` are now read (anthropic-api.ts:1450-1459).
A present `data` is the error's data, even when it is null. Without one,
the data is the error as the schema parses it: type, message and any of
code, statusCode and isRetryable that the frame carries, with unknown
keys dropped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
aimux-replay's live decision replay accepted only Jev recordings and built a registry holding Jev alone, while language recordings were rebuilt from default_providers(). A decision recorded with OpenAI or a runtime preset could only be replayed with --mock. Both paths now rebuild from one registry, replay_registry(): every built-in provider with default settings, the recorded one created again through create_provider() with --api-key when given, and Jev (outside the built-in set) when the recording names it, with --api-key or TYPESAFE_API_KEY as before. The test records an OpenAI decision against a local server, replays it through run_replay with another key, and checks the second request reaches /v1/decisions with that key; a second test checks the registry resolves ollama and Jev decision models and reports an unknown name. Fixes #232 B17. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…U3, U5) - streamRetries: upstream streamText can re-call the model after a mid-stream error (ai 7.0.127 stream-text.ts:374, :1948-1953, :2495-2665); aimux does not implement it yet and behaves as upstream does when it is unset. - additionalRetryableError (util/retry-with-exponential-backoff.ts:70-88, generate-image.ts:181-182) is listed as a difference. - first_chunk_ms is armed before do_stream, upstream arms it when the response stream starts (stream-text.ts:2228-2243, :2671); changing it needs do_stream to return on headers, which is undecided. - Video: the poll settings belong to the operation and the poll timeout races every status check (generate-video.ts:543-549, :586-625); a caller's idempotency key goes to start and status alike (:520-536); the per-batch key derivation is a product difference (:338-372). - The provider default retry, composite zero retry and model retry_config paragraphs say the alignment document withdraws them. - Fix the generate-video.ts line numbers cited in video_model.rs (they pointed at 7.0.122 lines; commit c5200fd's message has the same stale numbers). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
A custom provider lost the fallback provider's model discovery, so a registry entry built with custom_provider could no longer list models. Discovery is an aimux extension; it now follows upstream's files/skills forwarding (ai@7.0.127 src/registry/custom-provider.ts:268-283): the configured CustomProviderOptions::discovery, else the fallback's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
to_chat_completion put the provider's logprobs metadata straight into
choices[].logprobs: a token list for chat and a list of lists for
Responses. The Chat Completions wire format has an object there,
`{ "content": [{ token, logprob, bytes, top_logprobs }], "refusal": null }`.
The OpenAI chat model keeps only `choice.logprobs.content`
(openai-chat-language-model.ts:459-461), so the converter wraps it.
Responses lists (openai-responses-language-model.ts:1062-1080) are joined
in output order. A token without `bytes` gets null, which the wire format
allows. DeepSeek's object (`{content, reasoning_content}`,
deepseek-chat-language-model.ts:497) is passed through as it is.
round_trip_with_logprobs sends an OpenAI chat completion with logprobs
through generate_text and the converter and gets the same object back.
The C17 Azure test now expects the wire shape.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
… gaps The §9.1 known-gaps list gains two entries. Bedrock Mantle is still a registry preset (OpenAI-compatible chat). Upstream createBedrockMantle is a full provider with chat and responses models and its own auth. The AWS SigV4 fetches also sign GET requests (list_models). Upstream signs only POST requests with a body. The model list is aimux-only and needs the signature, so this difference is kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The trace scope key isolates by provider name and model id; the V4 model carries no base URL, so every aimux-web call of one provider shared one scope and two endpoints matched each other's LCP history. - TraceLayer::with_scope_name names the scope instead of the model's provider(); records still report provider(). The layer's comment says same-name endpoints are not isolated unless the host names each one. - aimux-web names the scope "<provider> @ <base_url>" when the call's base_url is not the provider's default. - RFC-0015 revision line: the ScopeKey formula in §6 describes master. Fixes #232 B18. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The V4 layer writes schema 3 with an identity-only ProviderRecord and replays live from the host registry (decision recordings for any provider). A call that lost an event or was marked inconsistent is written with complete: false, and the sensitive headers include x-prodia-key and *subscription-key. Until the V4 layers merge, the RFC body still describes master. Refs #232. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
SpeechResult gains usage: Option<JsonObject>, as SpeechModelV4Result's usage?: JSONObject (@ai-sdk/provider@4.0.22 src/speech-model/v4/speech-model-v4-result.ts:23-27). Google speech passes its usageMetadata through unchanged and leaves usage unset when the response has none or null (@ai-sdk/google@4.0.87 src/google-speech-model.ts:356-358, google-speech-api.ts:35). The other vendors report none. Telemetry of speech usage is not part of this change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Supported question types are a property of the model, as upstream DecisionModelV4.supportedQuestionTypes (@ai-sdk/provider@4.0.22 src/decision-model/v4/decision-model-v4.ts:11), so the recording input no longer stores input.decision_capabilities and replay no longer reads it. The live recording fixture drops the field; the replay mock accepts every question type and size and takes rounding and the probability source from the recorded result. Recording schema stays 3. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…ader rules Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Upstream `createOpenResponses` wraps its headers in `withUserAgentSuffix(.., options.userAgentSuffix ?? "ai-sdk-open-responses/" + VERSION)` (open-responses-provider.ts:114-127), and postJsonToApi then appends the provider-utils part of the provider-utils 2.0.58 depends on. aimux sent no package part. `@ai-sdk/open-responses` 2.0.58 is now pinned: VERSIONS.json carries the registry `dist.integrity` (sha512-VF48lc...), which equals the sha512 of the registry tarball, whose contents equal the extracted sources the port was checked against. The recorder's package.json, package-lock.json (regenerated with `npm install --package-lock-only`, which only adds this entry) and PINNED_ONLY list it too. aisdk_pins gains OPEN_RESPONSES (2.0.58, provider-utils 5.0.53, its package.json dependency), covered by the existing drift tests. The provider sends `ai-sdk-open-responses/2.0.58` through the package transport, and `OpenResponsesProviderSettings::user_agent_suffix` replaces the package part as `userAgentSuffix` does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Upstream parses `choices[].logprobs.content` with a zod schema of
`{token, logprob, top_logprobs: [{token, logprob}]}` (openai-chat-api.ts
:71-87, chunk :159-175), which strips `bytes` and any other key, and sets
`providerMetadata.openai.logprobs` only when `content` is not null
(openai-chat-language-model.ts:459-461 generate, :613-615 stream). aimux
copied the raw list, `bytes` included, and set `logprobs: null` for a
null `content`.
Both paths now go through `logprobs_content`, which keeps exactly the
schema's keys. `to_chat_completion` therefore returns `"bytes": null` for
an OpenAI chat result, and `round_trip_with_logprobs` expects that.
DeepSeek's own schema keeps `bytes` and is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…e checks, namespaced kinds Merges fx3/p3 (layer 3 at ed0e436, which already contains layers 1 and 2). Conflicts resolved keeping both sides: - Step content is layer 3's core ContentPart: StepResult.content and the text result accessors use ContentPart, tool_results return TypedToolResult, files come from the File { file, .. } part, and reasoning converts to AssistantContentPart (no signature). - response_messages.rs is layer 3's builder (tool message sorted by tool call order, tool_error), plus this layer's is_deep_equal_data approval filter and its tests, typed_tool_result and approval_response for the stream path. The C47 tool-message sorting is not added a second time. - generate_text keeps this layer's step pipeline and gains layer 3's tool choice check (generate-text.ts:1155-1176) and the invalid-call tool error after the content, built with this layer's invalid_tool_call_error. - stream_text checks the tool choice on the finish chunk: the step finishes with an error finish reason (raw reason kept) and an error part follows (stream-language-model-call.ts:644-687). A consumed stream records tool results, tool errors and approval responses where they arrive (stream-text.ts:1529-1538, :1648-1650). - TextStreamPart::Custom.kind is a NamespacedKind, as StreamPart::Custom. - apply_tool_call_repair_to_result works on the typed steps and also rewrites or drops the invalid call's tool error and its tool-message result. - The restored RFC-0026 OpenAI output, MoA and router tests are ported to TextStreamPart, ResponseInfo and the text() accessor; the AiMuxError variant-set test includes NoOutputGenerated; layer 3's content and tool-choice tests read the step content and the FinishStep / Finish parts. - FFI, Node and Python error mappings keep NoOutputGenerated and add ToolChoiceViolation to the response-data code / class. - MIGRATION-NS.md stays deleted; generated TS regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
google-vertex's Node MaaS and xAI wrappers spread the resolved provider headers over `init.headers` in their fetch, after postToApi built the user-agent (google-vertex-maas-provider-node.ts:43-49, google-vertex-xai-provider-node.ts:41-47; provider-utils post-to-api.ts:100-104). A provider-level `user-agent` therefore leaves nothing of the package part or the `ai-sdk-provider-utils` part. aimux layered it over the per-call headers but the helper layer still appended `ai-sdk-provider-utils/5.0.51`. `CompatModelConfig::request_headers` now also returns the fetch headers' `user-agent`, and `with_transport` sends it through a per-request transport that sets it as the whole header after the helper layer. The headers are still resolved once, at the config level, so a header or token error keeps its type. `transport_user_agent` leaves a header with no provider-utils part and no suffix exactly as it is, so the package transport after it does not touch the caller's value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
… replay (U5, K1, K7, C45, C47) - §3.5: object generation is the deprecated wrapper over `output` (generate-object.ts:120, stream-object.ts:190); the separate object spans are withdrawn. - §4.5 / §9.3: async jobs follow generateVideo's start/status model, the caller's idempotency key and the poll budget (generate-video.ts:520-536, :576-625); doGenerate-only models are retried whole as upstream does (generate-image.ts:206, transcribe.ts:243). - §5: per-batch idempotency keys (product difference, :338-372), stream retries and the first-chunk timeout's start (not aligned yet, stream-text.ts:374, :2228-2243). - Part 2: the result convenience fields are deprecated upstream (generate-text-result.ts:52-160, stream-text-result.ts:148-330); the response-message table gains the empty-reasoning, invalid-input, inputSchemaInput, tool-message order and tool-error encoding rows (to-response-messages.ts:43, :93-94, :122, :133-146, :203, :220-265); the deferred-result paragraph records aimux's provider-tool approximation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Upstream `downloadBlob` calls `fetchUntrustedUrl({url, abortSignal})`
with no headers (download-blob.ts:106-142, fetch-untrusted-url.ts:46-86),
so nothing adds a user-agent; only `getFromApi` and `postToApi` append
`ai-sdk-provider-utils/<v>` (get-from-api.ts:77-90). The OpenAI-compatible
image model downloads URL files with `downloadBlob(file.url)`
(openai-compatible-image-model.ts:241-244). aimux routed that download
through `get_from_api`, which sent `ai-sdk-provider-utils/5.0.53`.
aimux-provider-utils gains `download_blob`: a guarded GET with no caller
headers and no provider-utils part (an internal flag on the prepared
request skips the append). The OpenAI-compatible image model uses it.
Validated `get_from_api` downloads (Luma, Replicate, fal, Recraft, BFL,
Gladia; upstream `getFromApi({validateUrl: true})`) keep the part.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Upstream reads a URL image file or mask with `downloadBlob(file.url,
{abortSignal})` (openai-image-model.ts:166, :357), which goes through
`fetchUntrustedUrl` (provider-utils download-blob.ts:106-142): the target
is validated, redirects are checked hop by hop, and no headers are sent.
aimux fetched it with a plain `get_from_api` on the default transport, so
a private-network URL was fetched and `ai-sdk-provider-utils/5.0.53` was
sent.
`image_file_to_bytes` now calls `download_blob(url, abort_signal)`. A
non-public target is refused before any request, as upstream; a failed
download is a status-code error. CHANGELOG has a Breaking (security)
bullet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Upstream `GoogleFiles.uploadFile` starts the resumable upload and sends the bytes with the provider's plain `fetch`, not `postToApi` (google-files.ts:84-92, :118-126), so neither request gets the `ai-sdk-provider-utils/<v>` part that postToApi appends (post-to-api.ts:100-104). The init request carries the provider headers, whose user-agent is `ai-sdk-google/4.0.87` (google-provider.ts:258-269); the byte upload carries only its two `X-Goog-Upload-*` headers, so no user-agent. aimux sent the part on both. `HttpRequest` gains a public `omit_provider_utils_user_agent` flag, which sends the user-agent as given (or none). The Google upload sets it on both requests (the upload stays validated, deliberately stricter than upstream), and `download_blob` now sets it too instead of its private prepared-request tweak. The poll stays a `getFromApi` and keeps the part. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…n settings (#230 C7) `CallOptions` loses `max_retries`, `timeout` and `session_id`. The V4 call options have none of them (@ai-sdk/provider language-model-v4-call-options.ts:8-138); `maxRetries` and `timeout` are request options of generateText / streamText (ai prompt/request-options.ts:104-127), reported on the operation's start event (generate-text-events.ts:54-62). `GenerateTextOptions` keeps all three, so the JSON bindings are unchanged. `generate_text` and `stream_text` run their model calls inside `operation::OperationSettings::scope`, a task-local scope. The router and MoA take the child retry count from `OperationSettings::current()`, the trace layer takes the session id from it, and the recorder writes the three settings into `input.options` under the keys they had, so replay restores them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings [6/8] (and the [5/8] and [1/8] work it merged) ahead of [7/8], so [8/8] can start while [7/8] is finished. Conflicts: ApiCallError keeps [8/8]'s optional status_code serialization with [6/8]'s doc text; xAI keeps [8/8]'s realtime web_socket with [6/8]'s package fetch; ApiCallError.ts keeps [8/8]'s shape (regenerated later in this layer). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…ings Merges fx3/p4 (layer 4 at 822def8, which contains layers 1 to 3). Layer 5 already had layer 1; layers 2 and 3 arrive through layer 4. Conflicts resolved keeping both intents: - Retry: layer 5 deleted RetryConfig and retry_config(), so the core operations, the router and MoA call prepare_retries(max_retries, abort) with the operation's count (OperationSettings, layer 4's C7). The MoA test's retry_config() assertion is dropped. - CallOptions has neither layer 4's max_retries / timeout / session_id nor layer 5's body_overrides. Replay restores the three operation settings from the record; InputRecord's doc names neither call_id nor body_overrides. - trace/layer.rs keeps layer 5's with_scope_name and layer 4's session id from the operation scope. - VideoModel has neither poll_config() (layer 4's C19) nor retry_config() (layer 5). RunwayML keeps layer 5's factory settings and loses its poll constants; its tests pass poll per call (fast_poll). - AiMuxError has both LoadApiKey / LoadSetting and MissingToolResults / ToolChoiceViolation; the FFI, Node and Python mappings carry all four. - Provider code keeps layer 5's factories and exchange helpers, with layer 3's IndexMap namespace maps (anthropic and google files) and layer 2's LanguageModelV4ToolChoice imports. Layer 5's removals (groq tools and chat option parsing in openai/convert.rs, the xAI chat converter) stay removed. - provider-utils keeps layer 5's sigv4_fetch and drops the retry module; the tracker's argument state stays private as layer 2 made it. - Layer 5 removed the hand-synced web types; the files layers 2 to 4 added there are not kept, and the console imports TextStreamPart from @aimux/types. - Bindings and docs: layer 4's result and stream shapes with layer 5's RequestInfo / ResponseInfo (Swift), deepseek mocks (Node, Go) and the removed body_overrides. CHANGELOG: layer 4's "All bindings" section goes before "Added". - fixtures/aisdk/VERSIONS.json takes layer 2's provider-utils 5.0.55 entry. Compile fixes for the merged code follow in separate commits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Layer 5 removed the model-level retry settings and added the LoadApiKey / LoadSetting errors; layer 4 brought tests and comments written against the old sets. - The video test that races status retries against the poll budget set a 60 s delay through retry_config(). The failed status check now carries `retry-after-ms: 30000`, which the retry honours exactly below 60 s, so the delay does not depend on the jittered backoff. - The router test no longer asserts retry_config() of the router. - The error variant-set test lists LoadApiKey and LoadSetting, and builds NoSuchProvider with its layer 5 fields. - The max_retries comments of the call options and the composite child count say that None retries twice, the AI SDK default, without the retry_config() clause. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…tool-call-repair-is-a-trait Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GqcmS2Qe8c4L99HtjtbDD
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
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
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
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
…ite-json-repair-in-idiomatic-rust Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GqcmS2Qe8c4L99HtjtbDD
…tool-call-repair-is-a-trait Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GqcmS2Qe8c4L99HtjtbDD
…ings #252 took max_retries, timeout and session_id off CallOptions and made them operation settings, but RFC-0015, RFC-0016, RFC-0017, RFC-0023, RFC-0024, RFC-0029 and RFC-0041 still named them as CallOptions fields. Each of those RFCs gets a later-change line in its header: the three are set on GenerateTextOptions, generate_text and stream_text put them in OperationSettings, and code below the model interface reads them with OperationSettings::current(), as the AI SDK keeps maxRetries and timeout on generateText and not in LanguageModelV4CallOptions; recordings still write them under the old keys. The passages that describe the current shape are rewritten (RFC-0015 §5 and §8, RFC-0017 §2.4, the RFC-0023 §1 table, RFC-0024 §1.2 and §3.1, RFC-0029 §5.4, the RFC-0041 §10.2 row); dated implementation records keep the field where it was then. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in 38b6cde: the RFCs that named max_retries, timeout and session_id as CallOptions fields now say they are operation settings. 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 38b6cde: the RFCs that named max_retries, timeout and session_id as CallOptions fields now say they are operation settings. 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 38b6cde: the RFCs that named max_retries, timeout and session_id as CallOptions fields now say they are operation settings. 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 38b6cde: the RFCs that named max_retries, timeout and session_id as CallOptions fields now say they are operation settings. 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 |
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
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [3/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
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [4/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
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [5/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
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [6/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
Brings in master at ec338c9 (#262, #246 and #247, each squashed into one commit) through [7/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
…ite-json-repair-in-idiomatic-rust Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GqcmS2Qe8c4L99HtjtbDD
…tool-call-repair-is-a-trait Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GqcmS2Qe8c4L99HtjtbDD
eric8810
changed the base branch from
v4/9-rewrite-json-repair-in-idiomatic-rust
to
master
October 17, 2026 14:54
master 带入下方各层的 squash;全部冲突与自动合并差异收敛到本层树 (层树为作者维护、CI 全绿的真相;squash 内容已包含于层树),零差异。
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: the tool-call repair hook (the Rust form of the AI SDK's
experimental_repairToolCall) is a boxed closure,ToolCallRepair(Arc<dyn Fn(ToolCallRepairContext) -> Pin<Box<dyn Future<...>>>>). It can only be written as a closure.After: the hook is a trait,
RepairToolCall(async_trait, likeLanguageModel), andToolCallRepairholds anArc<dyn RepairToolCall>. A struct can implement it. Closures still work through a blanket impl, soToolCallRepair::new(closure)is unchanged.ToolCallRepair::from(hook)is new. This follows how Rust libraries such as rig express hooks (rig 9e7cb60,crates/rig-agent/src/agent/hook.rs).Type of change
Refactor, no public break.
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
Nonekeeps the original error;ErrbecomesToolCallRepair { original_error, cause }.repair_tool_callstays#[serde(skip)]), generated TS types, the C ABI and every binding. They have their own repair entry points and only use the error variant.Verification
cargo test --workspaceat [8/8] (65ca6be)cargo test --workspacewith this changeRepairToolCalldoc example andrepair_hook_can_be_a_trait_implementationtool_input_testtests; reverted-D warnings,gen_ts_types.py --check,gen_providers_doc.py --check,check_provider_boundaries.shall pass. Locally, clippy 1.97 also flags#[must_use]onFetchResponse::bytes_streaminaimux-provider-utils/src/fetch.rs, which predates this series and passes CI's toolchain