Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
103d23a
refactor(providers): remove generated provider name types
cunninghamcard-bit Sep 29, 2026
837aaaf
docs(changelog): record removal of generated ProviderName as breaking
cunninghamcard-bit Sep 29, 2026
9523030
fix(providers): drop the redundant #[must_use] on provider_names()
claude Oct 11, 2026
94c48ab
chore: drop stale provider_name references left after the ProviderNam…
cunninghamcard-bit Oct 1, 2026
1434ae1
docs(roadmap): Node drops the ProviderName string-literal union too
claude Oct 3, 2026
94630bf
refactor(web): consume generated core types directly
cunninghamcard-bit Sep 29, 2026
2f66709
refactor(web): generate wire types with ts-rs and import core types v…
cunninghamcard-bit Sep 29, 2026
043f88f
fix: address review findings on provider_names and wire export
cunninghamcard-bit Sep 29, 2026
3fcf1da
Merge master into the ProviderName removal: Bedrock SigV4 and secret …
claude Oct 15, 2026
ec7ab7c
chore: delete the web console's generated ProviderName copy
claude Oct 15, 2026
ea0384a
Merge master into the web wire types: tiered CI (#263), Bedrock SigV4…
claude Oct 15, 2026
1bd6a55
ci: type-check the web console against the generated types
claude Oct 15, 2026
be87f81
docs(web): note that cargo test rewrites the tracked node types
claude Oct 15, 2026
2c86792
Merge the ProviderName removal (#246) into the web wire types
claude Oct 16, 2026
0b78344
build(core): ts-rs exports run only from the generator
claude Oct 16, 2026
7a4b023
Merge master into #246: the #266 docs wording
claude Oct 16, 2026
453f611
Merge #246 into #247: master's #266 docs wording
claude Oct 16, 2026
bcb84e6
Merge master into web wire types: keep this branch gen_ts_types.py
eric8810 Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ts-rs export target: generate TypeScript bindings directly into the Node
# binding's types directory (single source of truth = Rust types, no copy step).
# ts-rs export target for the Node bindings (source of truth = Rust types).
# Plain cargo test no longer exports: both crates gate exports behind ts-export.
# Only scripts/gen_ts_types.py enables the feature and writes the types.
# Overrides ts-rs's default `./bindings` (per-crate), which used to produce a
# second, drift-prone copy in aimux-core/bindings/.
# No `force = true`: scripts/gen_ts_types.py overrides this with a temporary
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ jobs:
# - Documentation only (top-level Markdown, docs/, rfc/, assets/): nothing.
# docs/api/providers.md is generated and checked, so it counts as code.
# - Only inside bindings/<name>/: that binding's jobs. The Rust workspace
# does not read the bindings, so nothing else can break.
# does not read the bindings, so nothing else can break. bindings/node/
# also runs the web console typecheck: the generated types it imports
# live there.
# - aimux-ffi/: the Rust jobs and every binding that links the C ABI. Node
# and Python link the Rust crates directly, not the C ABI. Go is tested
# inside ffi-build, Java and Kotlin share one job.
Expand Down Expand Up @@ -145,6 +147,7 @@ jobs:
$(job flutter-binding aimux-ffi bindings/flutter)
$(job flutter-example-build aimux-ffi bindings/flutter)
$(job swift-contract-tests aimux-ffi bindings/swift)
$(job web-typecheck bindings/node)
\"contract-tests\": $code}"
run=$(jq -c . <<< "$run")
printf 'code=%s\nshared=%s\nplatforms=%s\nrun=%s\n' \
Expand Down Expand Up @@ -684,6 +687,26 @@ jobs:
run: |
swift test -Xlinker -L${{ github.workspace }}/target/release

# ── Web console typecheck: every @aimux/types import must still resolve ──
# The console imports the ts-rs types from bindings/node/src/types. The
# generator check above only compares files; this type-checks every import,
# so a renamed or removed Rust type fails here (RFC-0036 §13.1 A4).
web-typecheck:
name: Web console typecheck
runs-on: ubuntu-latest
needs: changes
if: fromJSON(needs.changes.outputs.run)['web-typecheck']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Type-check the web console (vue-tsc --noEmit)
working-directory: tools/aimux-web/web
run: |
npm ci
npm run typecheck

# The one check to require in branch protection. It runs whatever happened
# above: a job may be skipped only because "changes" decided it is not
# needed; any other skip, failure or cancellation fails it. A new job goes
Expand All @@ -703,6 +726,7 @@ jobs:
- flutter-example-build
- contract-tests
- swift-contract-tests
- web-typecheck
runs-on: ubuntu-latest
steps:
- env:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and the AI SDK has no counterpart. `tokio` is now a dev-dependency only and
the unused direct `serde` dependency is dropped.

### Fixed

- Gated aimux-core's ts-rs exports behind `ts-export`, so plain `cargo test`
no longer rewrites tracked TypeScript types. `scripts/gen_ts_types.py`
enables exports and rejects ungated export attributes with `--check`.

## [0.5.0] - 2026-09-27

**Breaking release.** 13 PRs since 0.3.0: the cross-language error model
Expand Down
5 changes: 5 additions & 0 deletions aimux-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ keywords = ["llm", "ai", "openai", "anthropic", "provider"]
categories = ["api-bindings", "asynchronous"]
documentation = "https://docs.rs/aimux-core"

[features]
# Emit the wire types' ts-rs export tests. Enabled only by
# scripts/gen_ts_types.py, which points TS_RS_EXPORT_DIR at a temp directory.
ts-export = []

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion aimux-core/src/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ts_rs::TS;
/// Shared between `ModelMessage` (user-facing) and `LanguageModelPrompt` (provider-facing).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum ContentPart {
/// A text segment.
Text {
Expand Down
28 changes: 14 additions & 14 deletions aimux-core/src/decision_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub type DecisionState = serde_json::Value;

/// An inline image shared by all questions, placed before the textual state.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionImage {
pub data: crate::shared::FileBytes,
pub media_type: String,
Expand All @@ -28,7 +28,7 @@ pub struct DecisionImage {
/// A native choice value, distinct from the canonical option label used as a key.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
#[serde(untagged)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum DecisionValue {
Text(String),
Boolean(bool),
Expand All @@ -37,7 +37,7 @@ pub enum DecisionValue {
/// Native question text or structured guidance, serialized without coercion.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(untagged)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum DecisionDescription {
Text(String),
Object(#[ts(type = "Record<string, unknown>")] serde_json::Map<String, serde_json::Value>),
Expand All @@ -60,7 +60,7 @@ impl From<&str> for DecisionDescription {
/// Convert into `DecisionDescription` to use it in `DecisionQuestion::Score`.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(deny_unknown_fields)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionScoreLevel {
pub label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -90,7 +90,7 @@ impl DecisionDescription {

#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(deny_unknown_fields)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionBooleanCriteria {
#[serde(rename = "true", default, skip_serializing_if = "Option::is_none")]
pub true_description: Option<DecisionDescription>,
Expand All @@ -99,7 +99,7 @@ pub struct DecisionBooleanCriteria {
}

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionOption {
pub label: String,
/// Native value; when omitted, the label is sent as the value.
Expand All @@ -111,7 +111,7 @@ pub struct DecisionOption {

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum DecisionQuestion {
Boolean {
id: String,
Expand Down Expand Up @@ -151,7 +151,7 @@ impl DecisionQuestion {
}

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionCallOptions {
#[serde(skip)]
#[ts(skip)]
Expand Down Expand Up @@ -267,7 +267,7 @@ impl DecisionCallOptions {
/// calibration guarantee. Raw provider metadata retains calibration details.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum DecisionProbabilitySource {
Native,
LogitScoring,
Expand All @@ -291,7 +291,7 @@ impl std::str::FromStr for DecisionProbabilitySource {

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum DecisionAnswer {
/// The provider declined this question; other answers remain usable.
Refusal,
Expand All @@ -317,7 +317,7 @@ pub enum DecisionAnswer {
}

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionCapabilities {
#[serde(default)]
pub supports_images: bool,
Expand All @@ -341,7 +341,7 @@ pub struct DecisionCapabilities {
/// Declared decimal rounding precision. None means only floating-point noise
/// is tolerated, not decimal rounding. Providers must report their actual rule.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionRounding {
pub probability_decimals: Option<u8>,
pub score_decimals: Option<u8>,
Expand All @@ -361,14 +361,14 @@ impl DecisionRounding {
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionResponse {
pub headers: Option<SharedHeaders>,
pub body: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct DecisionResult {
#[serde(default)]
pub rounding: DecisionRounding,
Expand Down
8 changes: 4 additions & 4 deletions aimux-core/src/embedding_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub type Embedding = Vec<f32>;
///
/// Aligned with V4 `EmbeddingModelV4CallOptions`.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct EmbeddingCallOptions {
/// List of text values to generate embeddings for.
pub values: Vec<String>,
Expand Down Expand Up @@ -64,7 +64,7 @@ impl EmbeddingCallOptions {

/// Token usage for an embedding call. Embeddings only report input tokens.
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct EmbeddingUsage {
/// Number of input tokens consumed.
pub tokens: u32,
Expand All @@ -74,7 +74,7 @@ pub struct EmbeddingUsage {
///
/// Aligned with V4 `EmbeddingModelV4Result`.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct EmbeddingResult {
/// Generated embeddings, in the same order as the input `values`.
pub embeddings: Vec<Embedding>,
Expand All @@ -94,7 +94,7 @@ pub struct EmbeddingResult {

/// Debugging response info specific to embedding calls.
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct EmbeddingResponse {
/// Response headers.
pub headers: Option<SharedHeaders>,
Expand Down
8 changes: 4 additions & 4 deletions aimux-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use ts_rs::TS;
/// This keeps transport and response failures self-contained and matches the
/// AI SDK's `APICallError` contract.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct ApiCallError {
/// Sanitized request URL. Required for every API-derived failure.
pub url: String,
Expand Down Expand Up @@ -93,7 +93,7 @@ impl ApiCallError {
/// Why the retry wrapper stopped retrying.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum RetryErrorReason {
/// Every permitted retry attempt failed with a retryable error.
MaxRetriesExceeded,
Expand All @@ -103,7 +103,7 @@ pub enum RetryErrorReason {

/// Complete error history for a retried model operation.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct RetryError {
pub reason: RetryErrorReason,
pub errors: Vec<AiMuxError>,
Expand Down Expand Up @@ -164,7 +164,7 @@ impl std::fmt::Display for ApiCallError {
/// failure came from. `ApiCallError` is boxed only to keep the Rust enum
/// compact; serde and every binding still observe the same object shape.
#[derive(Debug, Clone, Serialize, Deserialize, TS, Error)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum AiMuxError {
#[error("API call error: {0}")]
ApiCall(Box<ApiCallError>),
Expand Down
6 changes: 3 additions & 3 deletions aimux-core/src/files_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::shared::{
/// `SharedV4FileData` (URLs and provider references are not valid uploads), so
/// this is a dedicated two-variant enum rather than the full [`crate::shared::FileData`].
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub enum UploadFileData {
/// Raw bytes (`Uint8Array`) or a base64-encoded string.
Data { data: FileBytes },
Expand All @@ -34,7 +34,7 @@ pub enum UploadFileData {
///
/// Aligned with V4 `FilesV4UploadFileCallOptions`.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct UploadFileCallOptions {
/// The file data (raw bytes/base64 or inline text).
pub data: UploadFileData,
Expand Down Expand Up @@ -71,7 +71,7 @@ impl UploadFileCallOptions {
///
/// Aligned with V4 `FilesV4UploadFileResult`.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct UploadFileResult {
/// A provider reference mapping provider names to provider-specific file
/// identifiers.
Expand Down
6 changes: 3 additions & 3 deletions aimux-core/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn is_output_chunk(part: &StreamPart) -> bool {
/// Unlike `CallOptions` (provider-facing), this does not include `prompt`
/// (passed separately) and defaults are more ergonomic.
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct GenerateTextOptions {
pub max_output_tokens: Option<u32>,
pub temperature: Option<f64>,
Expand Down Expand Up @@ -141,7 +141,7 @@ impl GenerateTextOptions {

/// Result of `generate_text` (user-facing).
#[derive(Debug, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct GenerateTextResult {
/// The generated text (concatenated from all text content parts).
pub text: String,
Expand Down Expand Up @@ -193,7 +193,7 @@ pub struct GenerateTextResult {
/// Result of `generate_object` (user-facing, M12). The parsed JSON object plus
/// convenience fields from the underlying `generate_text` call.
#[derive(Debug, Serialize, Deserialize, TS)]
#[ts(export)]
#[cfg_attr(feature = "ts-export", ts(export))]
pub struct GenerateObjectResult {
/// The parsed JSON object returned by the model.
pub object: Value,
Expand Down
Loading
Loading