Skip to content

refactor(core)!: type provider options and provider metadata as namespace to JSON object - #217

Draft
cunninghamcard-bit wants to merge 9 commits into
rfc-0036/output-content-typesfrom
rfc-0036/provider-options-types
Draft

cunninghamcard-bit wants to merge 9 commits into
rfc-0036/output-content-typesfrom
rfc-0036/provider-options-types

Conversation

@cunninghamcard-bit

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

Copy link
Copy Markdown
Contributor

What

This pull request replaces arbitrary JSON provider options and metadata with namespace maps whose values are JSON objects, matching SharedV4ProviderOptions and SharedV4ProviderMetadata. It also corrects the reviewed vendor option readers so invalid known fields produce errors and unknown schema fields are discarded rather than leaking into requests. It is based on rfc-0036/output-content-types and precedes rfc-0036/provider-prompt-types in the protocol and provider-factory stack.

Before and after

Type or behavior Before After and upstream reference
Provider options Option<Value> on parts and messages; maps of arbitrary JSON values elsewhere SharedProviderOptions = HashMap<String, JsonObject>, following provider/src/shared/v4/shared-v4-provider-options.ts
Provider metadata Value and direct JSON result fields SharedProviderMetadata = HashMap<String, JsonObject>; types::ProviderMetadata aliases it, following SharedV4ProviderMetadata and ai/src/types/provider-metadata.ts
Namespace construction Ad hoc JSON namespace objects provider_namespace builds a namespace map and rejects a non-object input; valid JSON object payloads retain their representation
Known option fields and unknown keys Reviewed paths passed through invalid values, silently defaulted them, or forwarded unknown keys Schema-directed parsing in the OpenAI chat and Responses, Groq, DeepSeek, Anthropic, xAI Responses, Cohere, Google and Mistral paths; optional and nullish fields are treated separately, following the corresponding upstream option schema modules
Cohere thinking Invalid thinking values could default to enabled; fractional token budgets were dropped Invalid objects and declared fields return errors; numeric tokenBudget values are preserved, following cohereLanguageModelChatOptions
Bedrock chat namespace Only bedrock was read amazonBedrock takes precedence, with bedrock as fallback, following amazon-bedrock-chat-language-model.ts
Error propagation and image options Some converters could not return option parsing errors; image options could forward unknown keys Cohere, Google and Mistral converters return errors through model callers, including shared Vertex paths; Google and Vertex image option readers select declared fields

The namespace value must now be an object: scalars, arrays and null cannot deserialize as namespace values. This is distinct from a declared field inside that object being explicitly null, which is accepted or rejected according to its upstream schema.

Upstream JSONObject also permits an inner property value of undefined in TypeScript. Rust keeps JSON Value, and generated Node types do not express that additional value: the type generator cannot represent it and JSON has no undefined value. The upstream object type itself does not add runtime validation; the vendor schemas do.

Tests

Hand-written conversion and option tests are deleted rather than used to define behavior that conflicts with upstream. Retained upstream ports are migrated to typed namespace maps. The streaming tool-call tracker metadata cases, Google files valid and unknown option cases, and Bedrock message-conversion tests removed during the initial migration are restored and adapted to the new types. Cassette replay coverage is kept; aimux-providers/tests/cassettes and fixtures/ have no changes in this branch diff.

Not in this pull request

  • Serde naming remains a maintainer decision: snake_case fields, PascalCase external tags, Option values serialized as null, and generated Node fields that are required and nullable. Changing the namespace value type does not settle the binding wire format.
  • The call-layer ModelMessage tool-result shape remains a maintainer decision; this pull request changes its provider option and metadata types, not that result representation.
  • A namespace-constant migration and a dedicated boundary script are not added. Upstream readers also use namespace literals; they are not required to represent SharedV4ProviderOptions.
  • This pull request does not claim exhaustive runtime schema or provider behavior parity. It corrects the reviewed option-reading paths; the later prompt, factory and vendor branches address their own scopes.

Not verified

No cargo commands or runtime tests were run for this documentation rewrite. Node npm test and Python pytest were not run. Restored mock HTTP tests require local port binding and are not runnable here. The schema comparison is a source review, not a runtime gate result.

Verification

Gate on the final commit 3ccf7dd1: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, cargo doc, tests of core, provider-utils, providers, ffi, web, replay and cli: 2,627 passed, 0 failed; Node and Python bindings cargo check; provider boundary script; gen_ts_types.py --check, gen_providers_doc.py --check. Cassettes and fixtures/aisdk unchanged relative to the base. 150 files, +1571 / −7714.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS

chenhaonan and others added 9 commits October 4, 2026 23:21
…adata

The AI SDK types both as Record<string, JSONObject> (namespace -> object).
aimux-core carried four looser representations: ProviderMetadata was an
arbitrary serde_json::Value, SharedProvider{Options,Metadata} mapped a
namespace to any Value, and per-part / per-message options were Option<Value>.

Now JsonObject is serde_json::Map, SharedProviderOptions and
SharedProviderMetadata are HashMap<String, JsonObject>, and types::ProviderMetadata
aliases SharedProviderMetadata. Every provider_options field (parts, messages,
call/generate options, tools) is Option<SharedProviderOptions> and every
provider_metadata field (content, stream parts, results, tool types) is
Option<ProviderMetadata>. Valid {"ns": {...}} JSON serializes as before; shapes
whose namespace value is not an object are now rejected on deserialization.

shared::provider_namespace(namespace, object) builds a one-namespace map from a
json! literal, replacing the several hundred json!({"ns": {...}}) sites in the
vendor packages. Core and provider-utils tests are converted; generated
TypeScript bindings are regenerated for aimux-core. aimux-providers, ffi and
the aimux-web target do not compile until they are migrated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…tions and metadata

Follows the core change: provider options and provider metadata are
`HashMap<String, JsonObject>` (namespace -> JSON object), the AI SDK's
`Record<string, JSONObject>`. Every vendor package now builds its metadata
with `provider_namespace(ns, json!({..}))` or by inserting an object under
its namespace, and reads options with `options.get(ns)`, which yields the
object directly. The emitted JSON is unchanged: same namespaces, same keys.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…nd metadata

- Provider tests build options with `provider_namespace` or from a JSON
  literal and compare metadata by namespace; assertions are unchanged.
- FFI, Node and Python: the streaming-transcription session options carry
  `SharedProviderOptions`, so a non-object namespace value is rejected when
  the options JSON is parsed instead of reaching the provider.
- CHANGELOG: the typed shape and what it rejects.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
What: provider_namespace returns an error for a non-object value instead
of replacing it with an empty object. Callers follow. Hand-written tests
that pinned the old behaviour are deleted rather than rewritten.

Why: upstream types provider options as namespace to JSON object; silently
replacing a caller's value hid mistakes.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Why: carry the content and tool type changes up the stack. The newly
arrived code is converted to this branch's typed provider options and
provider metadata. Hand-written tests that no longer compile are deleted.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…ntract fixtures

Why: carry up the stack the source union, the removal of the separate
thought-signature field, the deletion of the self-generated contract
fixtures and the changes below this branch. This branch's code is
converted to them.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…tions as upstream schemas do; restore ported tests

What: Bedrock reads the amazonBedrock namespace first and falls back to
bedrock. Vendor option parsing follows each upstream schema: nullish
fields accept null, optional ones do not, wrong types are errors, unknown
keys are dropped before the request body (OpenAI, Groq, DeepSeek,
Anthropic, xAI, and partly Cohere, Google, Mistral). Restores the ported
tracker metadata cases, Google files option cases and the Bedrock
message-conversion tests deleted earlier.

Why: review found options passed through unvalidated where upstream
rejects or strips them, and ported tests deleted instead of adapted.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…stral; stop unknown keys leaking

What: the Cohere, Google and Mistral option readers return errors for
null where upstream's schema rejects it (thinking, structuredOutputs,
safePrompt) and Cohere accepts a numeric token budget; the converters
return Result and the model, Vertex and Azure paths propagate it. OpenAI
Responses nested fields and the Google and Vertex image options no longer
pass unknown keys into the request body.

Why: completes the option-validation alignment started in the previous
commit; upstream strips unknown keys and fails on these nulls.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS

This branch has not been deployed

No deployments
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