Repository navigation
[9/13] refactor!: rewrite json_repair in idiomatic Rust - #257
Merged
Merged
Conversation
6 of 12 tasks
cunninghamcard-bit
marked this pull request as ready for review
October 13, 2026 03:53
…V4 release @ai-sdk/provider 4.0.22 renamed the experimental EvaluationModelV4 to DecisionModelV4 (src/decision-model/v4/decision-model-v4.ts:6-17) and EvaluationUnsupportedQuestionTypeError to DecisionUnsupportedQuestionTypeError. The decision model follows that release, so the baseline moves to it. - VERSIONS.json pins 4.0.22 with its integrity. - The recorder's package.json and package-lock.json name the same version (regenerated with npm install --package-lock-only --ignore-scripts; the pinned vendor packages keep their own 4.0.21 copies). - aisdk_provider_pin_test fails when the pin drifts from 4.0.22. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…lts (#229 C41 stream and results, fork #27 part) As streamText's model call does (ai stream-language-model-call.ts:760-792), a provider's tool result in the stream becomes a `ToolResult` part holding a `TypedToolResult` (`tool_call_id`, `tool_name`, `input` of the call it answers, `output`, `provider_executed: true`, the chunk's `dynamic`, `provider_metadata`), or a `ToolError` part holding a `TypedToolError` when the provider marks it as an error. A result whose call is not in the stream (a deferred result) has no `input`. Before, the stream forwarded the provider's `ToolResult` with `result` and `is_error`. `TypedToolResult` and `TypedToolError` follow ai generate-text/ tool-result.ts:10-42 and tool-error.ts:6-36 (the user-level tool result's `providerExecuted` of fork #27); `toolMetadata` and `title` are absent, as aimux has no user-level tool definitions. Upstream drops the provider's `preliminary` flag at this point; aimux keeps it, so a preliminary result stays out of the step content as on the generated path and as the alignment doc's rule 6 asks. A consumed stream keeps the results and the provider's error results in the step content. The OpenAI chat output renders a provider error result as `[tool error: name]` text, as the generated path does. The bindings decode the new `ToolResult` shape and `ToolError` (Go keeps raw payloads and needs no change). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…error (#229 C41) As streamText's model call does (ai stream-language-model-call.ts:709-725), a client tool call that could not be parsed is followed in the stream by a `ToolError` part with the call's input, its error message (`getErrorMessage`) and `dynamic: true`. An invalid provider-executed call gets none. The error message is the error's display text, as layer 3 uses for the generated path's tool error. This branch's step content has no core tool error part, so a consumed stream does not keep it; the core content parts of layer 3 do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…-agent Upstream the `ai-sdk-provider-utils/<v>` part comes from the provider-utils the calling package imports (provider-utils post-to-api.ts:100-104, get-from-api.ts:77-81). npm installs each package's exact dependency, so the version follows the package's package.json: 5.0.51 for azure, amazon-bedrock, cohere, deepseek, groq, mistral, xai and google-vertex, and 5.0.53 for openai, openai-compatible, anthropic, anthropic-aws and google. google-vertex runs MaaS and xAI on its own openai-compatible 3.0.59. shared::aisdk_pins is now a per-package table (name, version, provider_utils), and each entry cites its package.json. Every pinned package's transport goes through shared::package_fetch, which rewrites the provider-utils part the helper layer adds. A per-call user-agent therefore keeps the package's part, as upstream does. The provider-utils crate's default part is no longer tied to the VERSIONS.json provider-utils pin, which follows the port. The drift tests check each package's version against VERSIONS.json and its provider-utils against the dependency in the recorder lock, including google-vertex's nested packages. package_user_agent_test pins the full user-agent of every package as literals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…l-approval-response (#229 C41) `TextStreamPart` gains `ToolOutputDenied` (ai generate-text/ tool-output-denied.ts:7-15) and `ToolApprovalResponse` (the `ToolApprovalResponseOutput` of tool-approval-response-output.ts:7-35), in upstream's order (stream-text-result.ts:581-607), which completes the AI SDK's 26 part types. `tool-output-denied` is a stream part only, not a core content part (content-part.ts:12-33). aimux's stream does not produce them yet. Upstream answers approvals and reports denied tools when it executes tools (execute-tools-from-stream.ts: 170-210), which aimux does not, and reports the approvals denied in the prompt's last tool message, provider-executed ones included, before the first model call (stream-text.ts:2030-2090); that needs the prompt's approval collection (collect-tool-approvals.ts), which is not ported. The bindings decode both parts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
/api/calls and /api/cache-probe resolved the key from the request spec, then the Settings key store, then the provider's env var, and passed the request's base_url to the provider unchanged. A client could point base_url at its own host and receive the console's key. When the base URL differs from the provider's own default, only a key carried in the request (plaintext, loopback binds only) is used: the Settings key and the env var are not read, env: references are refused, and a call without a key goes out with an empty key instead of the provider's env fallback. Every built-in provider is reachable from the console on this layer, and an empty key does not stop the ones that read other credentials from the environment (AWS SigV4 credentials, the Vertex access token, the Codex sign-in, the DataForSEO login), so those refuse a custom base_url. A base_url equal to the provider's default keeps the normal key order. aimux-providers gains provider_default_base_url(), the endpoint create_provider()'s factory calls when given no base URL (the package's DEFAULT_BASE_URL, now crate-visible, or the registry row's URL; None when the URL is built from settings). Replay rebuilds the provider from the recording's identity with its default base URL (recordings store no URL on this layer), so it never calls a custom base URL. The test sends calls with a custom base_url to a local server and checks that neither the stored keys nor the env keys arrive, that env: and a Bedrock call are refused before reaching it, and that a key carried in the request does arrive. README, RFC-0029 §5.5 and the alignment doc's §0.6 note say the same. Carries master's bf0d19e (#228 B12) to the V4 code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
An `{"error": ...}` chunk on the chat stream is passed on as it is
upstream (openai-compatible-chat-language-model.ts:588-595). aimux
took a numeric `code` in 400..=599 as the status, which made a code
like 429 retryable. The error now keeps the code as provider_code with
no status, as the completion path already did since N3. Presets and
the Vertex MaaS and xAI sub-providers share this chat model.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
…N31-13-1) The master branch's chunk-timer matrix (tests/stream_timeout_matrix_test.rs) and the stream pipeline tests in generate.rs were deleted on the way to V4 without replacement. They come back as `generate::pipeline_tests`, adjusted to the AI SDK's full stream: - the chunk timer is armed and reset only by output chunks (stream-text.ts:182-225, :2842-2857), and a consumer's delay does not count against it (§13-17, -18); - dropping the stream leaves no armed timer behind (§13-21b); - the first event a provider peeks inside do_stream is kept, the first-chunk budget measures its arrival rather than the first poll, and it bounds a do_stream that never returns (§8.3); - the model's finish ends the stream while the connection stays open, a provider error part does not hide the finish, a malformed frame is reported and the stream goes on, a transport error ends it; - a retryable first error (429) is retried before the stream is returned, a non-retryable one is returned once (stream-text.ts:2409-2420). The pinned AI SDK package ships no stream-text tests, so these keep aimux's own cases with the upstream source cited. The first-chunk tests follow the current design (timer armed at operation start, U3) and change if that does. 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 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
…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
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
…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
|
谢谢评审。你看的是旧 head 540fef5,这一层已随整套栈更新,下面以更新后的为准。
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
eric8810
changed the base branch from
v4/8-public-json-equals-the-ai-sdk-json
to
master
October 17, 2026 14:46
master 带入下方各层的 squash;全部冲突与自动合并差异收敛到本层树 (层树为作者维护、CI 全绿的真相;squash 内容已包含于层树),零差异。
9 of 12 tasks
16 tasks
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:
json_repair(the partial-JSON repair used for streamed tool inputs andgenerate_object) is a line-for-line port of the AI SDK'sfix-json.tsandparse-partial-json.ts. It copies the input into aVec<char>, tracks positions with anisizeand a-1sentinel, keeps a flat state stack with.expect("never empty"), and returns a{ value: Option<Value>, state }pair where which fields are set depends on the state.After: the same behaviour, written in Rust shape. It scans the borrowed
&str, keeps a stack of open values (Vec<Frame>, empty stack = done, no panic path), and returnsPartialJson::{Parsed(Value), Repaired(Value), Failed}. Upstream'sundefined-inputstate is the caller'sOption(text.map(PartialJson::parse)).This is the first of the Rust-adaptation PRs stacked on the AI SDK migration. Each one keeps behaviour identical to the PR below it and proves it.
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
parse_partial_json,ParsePartialJsonResultandParsePartialJsonStateare replaced byPartialJson::parse. No deprecated aliases, per the alignment doc's one-shot rule (D31). The only callers were the four upstream cases inaimux-core/tests/util_test.rs. No JSON, C ABI or binding changes. CHANGELOG updated.aimux-core/tests/fixtures/json_repair_cases.jsonlholds 740 inputs with the old port'sfix_jsonoutput and parse outcome (one shortest input per outcome / ending bucket out of 300,000 generated ones, plus hand-picked edges), checked byjson_repair_cases_test.rs. The upstream cases stay inutil_test.rs.Verification
json_repair.rs(number start, literal tails, unicode escapes, array/object separators, outcome kind): all 14 caughtaimux-core/tests/util_test.rs: 51 cases (47 fix-json, 4 parse-partial-json) passai@7.0.127fix-json.tsandprovider-utils@5.0.53secure-json-parse.tsunder node 22 (runner not committed):fix_jsonmatches upstream on all of them. The previous port differed on 63, all a character outside the BMP right after a number or literal in an array; the scanner now steps through UTF-16 code units as upstream does, so"[1😀"repairs to"[1😀]"on both sides-D warnings,gen_ts_types.py --check,gen_providers_doc.py --check,check_provider_boundaries.sh; CI on this branch