Skip to content

[5/8] refactor!: providers are AI SDK factories - #253

Merged
eric8810 merged 224 commits into
masterfrom
v4/5-providers-are-ai-sdk-factories
Oct 17, 2026
Merged

eric8810 merged 224 commits into
masterfrom
v4/5-providers-are-ai-sdk-factories

Conversation

@cunninghamcard-bit

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

Copy link
Copy Markdown
Contributor

Requested by li

Summary

Before: each vendor is a config struct behind aimux's own provider trait, settings are read once at construction (an invalid env base URL panics), and consumers build providers through per-vendor constructors. There is one global proxy, supported_urls is synchronous, there is no wrap_provider, and URLs in prompts, audio input and video results are passed on as they are. The moved code still has the #228 problems that #248 fixes on master: secrets in Debug output, URL userinfo in logs, unredacted Prodia and Azure key headers in recordings, the Flutter finalizer signature, the Python GIL, and aimux-web sending stored keys to any base_url. A recording that lost an event is still marked complete, aimux-replay replays decision recordings live only for Jev, and the realtime WebSocket tests are gone.

After: providers take the AI SDK ProviderV4 shape with a provider registry, and every vendor is a factory as in its upstream package; providers and models report specification_version() "v4". The request layer has injectable fetch, a proxy per transport, and settings resolved per request, so an invalid env base URL fails each call instead of panicking. FFI, the bindings, the CLI, the web console and replay build providers by name through create_provider. supported_urls is async, and wrap_provider wraps a whole provider. generate_text, stream_text, transcribe and generate_video download the URLs a model cannot fetch, through a guarded download the caller can replace, as the AI SDK operations do. The master fixes are carried onto the new code. Recordings keep only the provider identity, stay on schema 2, load what master wrote, and are marked incomplete after a lost or inconsistent event. aimux-replay replays any provider's decision recordings live, and each custom aimux-web endpoint gets its own trace scope.

This is one PR on purpose: splitting it would need a temporary adapter between the old and new provider interfaces that the next PR deletes again. The commit message and the sections below go vendor by vendor so it can be reviewed in parts.

Type of change

  • Bug fix (non-breaking)
  • New feature (non-breaking)
  • Breaking change
  • New provider
  • New / updated binding
  • Docs / RFC

Checklist

  • cargo fmt --all -- --check passes
  • cargo clippy --workspace --all-targets -- -D warnings passes
  • cargo test --workspace passes (cassette playback — no network/keys)
  • Added or updated cassette tests / contract fixtures for behavior changes
  • Updated relevant docs (README.md, docs/, rfc/, bindings/README.md)
  • Commit messages follow Conventional Commits

Notes for reviewers

CI artifact (zip) master a8e3a99 [4/8] 4ce9aa0 this PR 87e652c change vs [4/8] change vs master
ffi-x86_64-unknown-linux-gnu 3.77 MB 4.06 MB 4.67 MB +599.8 KiB (+15.14%) +884.6 KiB (+24.06%)
ffi-aarch64-apple-darwin 3.12 MB 3.35 MB 3.88 MB +513.5 KiB (+15.70%) +740.0 KiB (+24.30%)
ffi-x86_64-pc-windows-msvc 3.03 MB 3.25 MB 3.76 MB +499.1 KiB (+15.73%) +717.0 KiB (+24.26%)
node-x86_64-unknown-linux-gnu 55.30 MB 58.07 MB 62.84 MB +4653.0 KiB (+8.20%) +7363.2 KiB (+13.64%)
node-aarch64-unknown-linux-gnu 53.25 MB 55.88 MB 60.43 MB +4435.3 KiB (+8.13%) +7004.8 KiB (+13.47%)
node-aarch64-apple-darwin 15.06 MB 16.09 MB 18.33 MB +2187.4 KiB (+13.92%) +3191.4 KiB (+21.70%)
node-x86_64-apple-darwin 13.60 MB 14.52 MB 16.60 MB +2028.7 KiB (+14.31%) +2930.5 KiB (+22.07%)
node-x86_64-pc-windows-msvc 10.57 MB 11.34 MB 13.20 MB +1822.6 KiB (+16.46%) +2575.2 KiB (+24.96%)
node-aarch64-pc-windows-msvc 9.87 MB 10.61 MB 12.40 MB +1748.2 KiB (+16.88%) +2462.9 KiB (+25.54%)

Cargo.lock gains no crate in this PR, so the growth is the PR's own code: every vendor is rebuilt as an AI SDK factory on the shared request layer. In aimux-core, aimux-providers and aimux-provider-utils, Rust grows by about 15,300 lines net: about 6,400 under src/ (in-file unit test modules included) and about 8,900 under tests/.

chenhaonan and others added 25 commits October 11, 2026 16:30
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
…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
…ion as upstream does

- Provider generate and stream results carry request and response
  information, as the upstream V4 result types do; raw is the provider
  result itself.
- The response metadata stream part is absorbed into the finish step, and
  request and response are carried onto the call-layer results.
- A tool-call repair applied to a result also updates the result's content.

CHANGELOG records the result fields as breaking; the migration notes
describe tool-call metadata on StreamingToolCallDelta.

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
…5-providers-are-ai-sdk-factories

# Conflicts:
#	CHANGELOG.md
#	scripts/local-ci.sh
…est layer

One change, without a transition layer: the old provider interface and
its configs are removed in the same commit that adds the new ones.

Request layer (aimux-provider-utils):
- Injectable fetch, resolvable settings (values or functions evaluated per
  request) and the AI SDK request helpers. API keys and settings load per
  request, with LoadApiKey and LoadSetting errors.

Provider interface:
- Provider takes the AI SDK ProviderV4 shape, with a provider registry.

Vendor packages, each a factory as in its upstream package:
- openai-compatible, with the presets read from the registry table at run
  time.
- The OpenAI family: openai, azure, codex, huggingface, xai.
- groq and deepseek.
- The Anthropic and Google families: anthropic, anthropic_aws, google,
  vertex, bedrock, aws_polly, open_responses.
- The remaining vendors: mistral, cohere, voyage, elevenlabs, jev and 28
  single-modality vendors.

Consumers:
- FFI, the bindings, the CLI, the web console and replay build providers
  by name through create_provider. Recordings keep only the provider
  identity. Retries are call options; provider configs reject the removed
  keys, and a missing key or an unfilled preset parameter fails at call
  time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The returned boxed stream is already must_use, so clippy 1.99
(double_must_use) rejects the attribute under -D warnings.

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
…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
…ned code

The results layer adds AIMUX_E_NO_OUTPUT_GENERATED = 18 (the AI SDK's
NoOutputGeneratedError) and every binding maps it, but the Go, Java, Kotlin
and Flutter tests still used 18 as the first value outside the enum. They now
check that 18 resolves to NoOutputGenerated and that 19 is rejected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
claude added 13 commits October 16, 2026 08:53
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 #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 with every
export gated, including the result, step and output types this layer
adds. The web console's copies of the core types, which this layer had
updated, stay deleted as #247 deletes them; engine.ts and client.ts
import TextStreamPart and the other core types from @aimux/types and
the wire types from @/types/generated, and the web typecheck passes.
docs/api/reference.md keeps this layer's rows without the ProviderName
row; 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: core files keep this layer's code with every export gated,
including the ones this layer adds; model_id.rs stays deleted (this
layer removes ModelId); aimux-providers lib.rs and provider.rs keep
this layer's version, which already exposes provider_names() from
default_providers; gen_ts_types.py gains the export-gate check on top
of this layer's targets; reference.md keeps this layer's
ProviderOptions row; CHANGELOG keeps both sides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
The entry for supportsDeferredResults read as if every tool result without
its call were checked. Only generate_text checks it, as upstream's
generateText does (ai@7.0.127 convert-language-model-content.ts:96-103);
stream_text, like streamText, passes the result on without its input
(stream-language-model-call.ts:760-798), which
stream_tool_results_and_errors_follow_the_ai_sdk already pins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
Brings in the CHANGELOG wording from [4/8]: only generate_text checks a
tool result without its call; stream_text passes it on, as streamText does.

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
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: rfc/0029-web-console.md and tools/aimux-web/README.md keep
this layer's text, which already says what #266 says and adds which
providers cannot take a custom base_url and where Replay sends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa

Copy link
Copy Markdown
Contributor Author
eric 的意见 我们的处理 位置
重定向默认跟随,和 RFC-0040 "默认不跟随"的字面不一致 和上游 postToApi 一样默认跟随(provider-utils 5.0.55 post-to-api.ts:98-107 没有传 redirect)。凭证锁在原请求的 origin,跨 origin 后只留 User-Agent、不再签名,最多 10 跳,比上游(只去 Authorization、20 跳)严。RFC-0040 第 3.2 节加了修订说明,对齐文档第 5 节写了这个差异(b7f1b7f8);补了两条测试:第 11 跳失败、同 origin 跳转按新 URL 重新签名(20a0ee5c) rfc/0040-provider-auth.md 第 3.2 节、aimux-provider-utils/tests/api_redirect_test.rs
base_url_env 和 OptionalEnv 缺直接测试 补了四条串行测试:设置 OLLAMA_BASE_URL 后请求发到那里且逐请求读取、显式 base_url 优先、非法值在请求时报出变量名、OLLAMA_API_KEY 有值才发 Bearer(a2ba4192) aimux-providers/tests/presets_test.rs
registry 校验做成能单测的函数 load_registry 的校验改成接收 JSON 文本的 parse_registry,加了 10 条单元测试,逐条喂违反规则的行(ed69b7e7) aimux-providers/src/preset.rs
provider.rs 的按名构造 shim 写明去向 模块头写明由 #265 删除:那一层的 C ABI 和绑定改走 ops 表,不再调用它 aimux-providers/src/provider.rs

Generated by Claude Code

claude and others added 12 commits October 16, 2026 11:18
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
…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
master 带入 #262/#246/#247 的 squash;三个冲突文件取本分支版本
(ci.yml 为已调和 #262 任务表 + web-typecheck + pin 脚本测试的超集),
gen_ts_types.py 自动合并。
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
@eric8810
eric8810 changed the base branch from v4/4-results-and-streams-follow-the-ai-sdk to master October 17, 2026 13:44
eric8810 and others added 2 commits October 17, 2026 21:45
master 带入下方各层的 squash;冲突为 squash 不对称,取本分支版本
(层分支内容 = master 同内容 + 本层改写),删除型按本层意图 git rm。
…r the master merge

The master merge (c0e6ec4) took master's side in two test files, so the
Rust core job failed to compile: elevenlabs_speech_test.rs lost the
HashMap import its helper still uses, and openrouter_test.rs compared the
tool-call input as a string, while on this layer it is a JSON value. Both
files are back to this layer's version; the tree then equals the layer
before the merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3WxDBWBA4ituJjXsHaEAa
@eric8810
eric8810 merged commit 9e6ca81 into master Oct 17, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants