Skip to content

feat(providers)!: AI SDK-shaped provider factories, request layer and callers (source) - #224

Closed
cunninghamcard-bit wants to merge 18 commits into
rfc-0036/upstream-samplesfrom
rfc-0036/provider-factory-source
Closed

cunninghamcard-bit wants to merge 18 commits into
rfc-0036/upstream-samplesfrom
rfc-0036/provider-factory-source

Conversation

@cunninghamcard-bit

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

Copy link
Copy Markdown
Contributor

What

Move provider construction, the shared request layer and their callers to package factories shaped by the AI SDK. This is the source and caller migration after rfc-0036/upstream-samples, following factory-test-removal, and before provider-factory (#214), which adds further tests and documentation. The branch also restores upstream-ported tests removed before the factory migration and includes the converter and response-parser changes needed by these implementations. Core, provider-utils, vendor packages, FFI, language bindings, CLI, web console and replay move together because they share the changed creation and request interfaces.

Before and after

Area Before After and upstream reference
Construction Callers use the previous model construction paths and provider-specific setup. Package create_* factories, settings and default instances create models through Provider; createOpenAI, createAnthropic and the other package provider modules are the references. Built-in create_provider, provider_names and default_providers assemble package factories and compatible presets; this built-in table is an aimux extension.
OpenAI default FFI, Node and Python select chat even though the Rust factory selects Responses. All these default paths use Provider::language_model, selecting Responses as createOpenAI does; explicit chat and completion remain available. The Responses factory supplies the file- prefix from openai-provider.ts.
Settings Vendor settings expose extra resolvable keys or request transforms, and omit supported hooks. OpenAI, Anthropic, Google, Mistral, Cohere, Groq, DeepSeek, xAI and OpenAI-compatible settings use static Option<String> keys and header maps. Packages with an environment-key fallback load it when requests run; OpenAI-compatible sends no authorization for an absent or empty key. Azure and PresetSettings retain resolvable keys; Azure also has a resolvable token provider. Mistral, Cohere and Anthropic expose the upstream generateId hook. A dynamic request credential does not make every settings field dynamic.
Compatible requests URL support and usage conversion cannot be supplied; the body transform reaches other modalities. OpenAICompatibleProviderSettings includes supported_urls and convert_usage; transform_request_body applies only to chat, as in openai-compatible-provider.ts. Groq and DeepSeek have their own package settings and conversion behavior.
Request execution Transport and retry responsibilities are mixed with provider implementations. Injectable fetch, resolvable values, response handlers and shared media-type detection live in provider-utils; Core owns call retry. Ordinary API redirects are followed only within the issuing origin. User-Agent suffixes append to existing headers, following withUserAgentSuffix and postToApi.
Azure authentication Token resolution precedes final request-header merging. A caller-provided Authorization header suppresses token-provider resolution after merging, following azure-openai-provider.ts.
Registry and middleware No caller-assembled registry or complete wrapping callbacks. create_provider_registry resolves combined IDs at the first separator and applies language/image middleware. Wrappers accept model/provider ID overrides; language hooks receive both generation and streaming callbacks, following createProviderRegistry, wrapLanguageModel and wrapImageModel. NoSuchProvider carries provider ID, model ID, model type and available providers, matching the fields of NoSuchProviderError.
Response and prompt conversion Anthropic iteration metadata loses fields; Mistral combines response choices; some file conversions use fallback representations. Anthropic preserves iteration model/cache fields and conditions custom-name metadata on the corresponding options, following anthropic-language-model.ts. Mistral reads the first choice and assembles tool-call argument fragments, following mistral-chat-language-model.ts. Cohere and Mistral detect bare image media types; the Bedrock public converter rejects unsupported tool-content file types. These changes are part of the source split.

Upstream constraints expressed only through TypeScript types, including JSONSchema7, dotted IDs and kinds, NonNullable, URL, Date and number domains, do not themselves enforce runtime validation upstream. Rust retains Value, String and u32 representations where applicable.

Vendor option schemas live in each package: the shared OpenAI-style option parser takes its field schema from the caller, and OpenAI, Azure, Groq and DeepSeek own theirs in options.rs (Azure passes the OpenAI schema). Anthropic spells its namespace and the skill type value once in options.rs. The files model exposes no specification version; the two migrated files tests keep only the provider-name assertion. This is what scripts/check_provider_boundaries.sh requires.

Tests

Migrate the sixteen upstream-ported embedding, image, speech, transcription, files and video test files to the factory API; these test upstream behavior through the new factory API. Restore the DeepSeek strict-option cases instead of replacing them with implementation-specific assertions. Delete the hand-written tests introduced in anthropic/options.rs and provider-utils fetch.rs. Existing cassette replay suites remain and their caller setup follows the new factories; recorded cassette files are unchanged. Existing tests in mixed files are adjusted rather than all being removed: this branch does not claim that every remaining hand-written test has been deleted or that every upstream case has been ported.

Not in this pull request

  • Serde field naming, external tags and optional-field encoding await the maintainer's decision: Rust retains snake_case fields, PascalCase external tags and Option values serialized as null; generated Node fields can remain required and nullable.
  • The by-name entries in aimux_providers::provider used by FFI, Node and Python await the maintainer's decision. The new built-in assembly does not settle whether those entries should remain public.
  • The call-layer ModelMessage tool-result shape and remaining call-layer tool-result/error behavior await the maintainer's decision; factory migration does not resolve that public contract.
  • Google speech, transcription and evaluation; xAI image, speech, transcription, video and files; Groq transcription; Mistral speech and transcription; OpenAI skills; and registry evaluationModel/customProvider are not ported here. Their scope awaits the maintainer's decision.
  • Vertex application-default credentials, Azure/Vertex transcription routing and Azure Foundry Responses explicit message-item typing are fixed on rfc-0036/vendor-alignment; those later changes are outside this source branch.
  • specificationVersion remains deliberately omitted under the project's public-surface decision and boundary check. Search/discovery and built-in assembly remain aimux extensions rather than upstream parity claims.
  • The fixtures/aisdk samples (recordings of the published AI SDK packages over a mocked fetch) are unchanged here; the replay tests that read them belong to provider-factory (feat(providers)!: AI SDK-shaped provider factories, request layer and replay tests #214).

Stack refresh (2026-10-07)

Head 94942c40 follows rfc-0036/upstream-samples at 6b8b8432. The stack includes master at 0b365b39 and the updated #203 (3e2d9fc7), #204 (b897cb21) and #213 (7c0933b1). Updated parents were merged into their children to preserve the existing public merge history; the PR base and Draft state are unchanged.

The Decision/Jev additions from master now use the factory-era Provider interface, call-level retries and caller-supplied replay registry. Provider recordings contain identity only; Decision capabilities are recorded on InputRecord for offline replay. The native Jev constructor remains an aimux extension. The Jev recording envelope is migrated to schema 3 without changing its recorded HTTP request/response bodies. Removed obsolete DecisionModel config_snapshot/specification_version hooks. The SSE regression uses the new OpenAI factory API.

Verification

  • Relative to the updated immediate base: 435 files changed, 36665 insertions(+), 24801 deletions(-).
  • Rust formatting and workspace Clippy passed during this layer’s refresh. Final integration at local wire-format 415e37e1 passes cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, and the provider boundary script.
  • Full cargo test --workspace at final wire-format: 2,819 passed, 0 failed, 6 ignored, including doctests. The intermediate factory-source integration also passed 2,434 tests before the final obsolete Decision hook removal. These are integration results, not a claim that the full suite was rerun at every PR head.
  • Generated TypeScript synchronization, Node build:typed, and web production build passed at the wire-format layer.

Not verified

No GitHub check runs are attached to this Draft stack head at the time of this update. Go, Java, Kotlin, Swift and Flutter bindings were not compiled locally in this refresh; Node npm test and Python pytest were not run. Live vendor calls and unported upstream cases remain outside this validation.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS

chenhaonan and others added 18 commits October 6, 2026 01:09
What: the source of the provider-factory work in one step: the injectable
request layer, resolvable settings and call-level retry in provider-utils;
the Provider trait and provider registry in core; a create_* factory per
vendor package, the OpenAI-compatible package, Groq and DeepSeek as their
own packages, the built-in provider table and by-name creation; and the
ffi, language bindings, CLI, web console and replay following the new
creation path. Existing tests that still apply are adjusted.

Why: third of four parts split out of the provider-factory pull request.
The changes to core and provider-utils break every caller, so the source
of all crates has to move together to compile; this part is the smallest
unit that passes the gate. New tests and documentation follow in the last
part. The commit-by-commit history of this work is in that pull request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…low upstream; migrate ported model tests

What: bindings use the same default language model as Rust (Responses);
Azure resolves its token provider only without an Authorization header;
the Responses factory sets the file id prefix; every request carries the
upstream `ai-sdk-<package>/<version>` user-agent suffix added once in
provider-utils; vendor settings match upstream (plain api keys, generate
id, no extra fields; compatible gains supported urls and usage
conversion and applies the request transform to chat only); language and
image middleware take the upstream wrapping hooks and id overrides;
NoSuchProvider carries model id, type and available providers; Anthropic
usage iterations keep their fields. Removes the OpenAIChatModel alias and
a tool-model name in a comment. Migrates the sixteen test files ported
from upstream (embedding, image, speech, transcription, files, video) to
the factory API and fixes the five divergences they exposed; restores two
DeepSeek strict tests; deletes hand-written tests in options.rs and
fetch.rs.

Why: review against the pinned provider packages found these factory and
request-layer differences; ported tests are migrated, not deleted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry up the stack the fixes made after the third independent
review on the branches below; this branch's code is converted to them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…converter errors, Anthropic generate_id

What: Cohere and Mistral resolve a bare image media type by detection
and reject what cannot be detected, in upstream's order and wording; the
public Bedrock message converter returns an unsupported-functionality
error for a tool-content file type it cannot send instead of a txt
document; Anthropic settings gain generate_id and the model uses it for
source ids as upstream does.

Why: items left open by the previous fix commit; the media type helper
only exists from this branch upward.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry up the stack the fixes made after the third independent
review on the branches below; this branch's code is converted to them.

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

What: the shared OpenAI-style option parser takes the field schema from
the caller; OpenAI, Azure, Groq and DeepSeek each own their schema in
their options.rs and Azure passes the OpenAI one. Anthropic spells its
namespace and the skill type value once in options.rs. The files model
no longer exposes a specification version; the two migrated tests keep
their provider-name assertion only.

Why: the provider boundary check forbids vendor names in shared
packages and the removed version member; the earlier commit had
reintroduced both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry up the stack the fixes made after the third independent
review on the branches below; this branch's code is converted to them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the formatting commit up the stack; no code change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: the branch below keeps the sixteen upstream-ported test files in
their old form; this branch already carries them migrated to the factory
API, so the tree is unchanged.

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

Copy link
Copy Markdown
Contributor Author

Folded into #214 while consolidating the RFC-0036 stack from twelve pull requests into four. This branch is unchanged; its commits are part of the range #214 now shows (base rfc-0036/provider-result-types).

🤖 Generated with Claude Code

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.

1 participant