Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
27b56cf
feat(core)!: own operation retries, timeouts, and the stream lifecycle
cunninghamcard-bit Aug 24, 2026
1ac7b54
feat(provider-utils)!: single-exchange helpers and response handlers
cunninghamcard-bit Aug 24, 2026
abb4291
refactor(providers)!: migrate to Core retry and the handler API
cunninghamcard-bit Aug 24, 2026
80ea1ba
feat(ffi)!: align the C ABI with the pipeline
cunninghamcard-bit Aug 24, 2026
d3d766f
feat(bindings)!: expose retry, timeout, and video surfaces
cunninghamcard-bit Aug 24, 2026
5ddfd0f
docs: ship the AI SDK request-pipeline spec
cunninghamcard-bit Aug 24, 2026
ec43e02
Merge remote-tracking branch 'origin/master' into codex/ai-sdk-operat…
cunninghamcard-bit Aug 25, 2026
d93c591
Merge remote-tracking branch 'origin/master' into codex/ai-sdk-operat…
cunninghamcard-bit Aug 29, 2026
1c5e73b
fix: end the xAI stream on a terminal error; clamp the retry hint
cunninghamcard-bit Aug 31, 2026
03880e0
refactor: delete the boilerplate the pipeline migration created
cunninghamcard-bit Aug 31, 2026
01da456
fix(core): deep-merge start/completion provider metadata for video ge…
cunninghamcard-bit Sep 3, 2026
ea9a33d
fix(provider-utils): cap successful JSON bodies separately from binar…
cunninghamcard-bit Sep 3, 2026
0baf004
fix(providers): apply Core retry to file uploads (OpenAI, Anthropic, …
cunninghamcard-bit Sep 3, 2026
c38bfd4
fix(providers): retry assemblyai/gladia upload and submit stages inde…
cunninghamcard-bit Sep 3, 2026
857f54c
fix(core): batch video generation by max_videos_per_call and validate n
cunninghamcard-bit Sep 3, 2026
7a83301
fix(core): measure stream timeouts at the producer, not the consumer
cunninghamcard-bit Sep 3, 2026
8fd89fe
docs(core): link merge_provider_metadata as plain code, not an intra-…
cunninghamcard-bit Sep 3, 2026
f315b86
refactor(core): shrink the video batching fix
cunninghamcard-bit Sep 3, 2026
6b187c9
refactor(providers,docs): de-duplicate the retry-placement rationale
cunninghamcard-bit Sep 3, 2026
edd9864
refactor(core,providers): trim the pump rationale; drop a whole-body …
cunninghamcard-bit Sep 3, 2026
5087653
fix(bindings): keep the explicit query in Node/Python search()
cunninghamcard-bit Sep 3, 2026
26ccdac
fix(java): type VideoCallOptions.fps as Long, not Double
cunninghamcard-bit Sep 3, 2026
6a644cc
fix(web): write fixtures with RECORDING_SCHEMA, not a hardcoded 1
cunninghamcard-bit Sep 3, 2026
7ec05c7
fix(bindings): let Node/Python pass options to transcribe/rerank/search
cunninghamcard-bit Sep 3, 2026
2d850db
docs: record that stream_text now requires a tokio runtime
cunninghamcard-bit Sep 3, 2026
ebf0a22
fix(core): scope caller video idempotency keys to each batch
cunninghamcard-bit Sep 7, 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ aimux/
├── aimux-core # Core abstractions: LanguageModel / Provider / Message / StreamPart
├── aimux-providers # 329 provider implementations (251 registry-backed + native)
├── aimux-stream # SSE / NDJSON stream parsing
├── aimux-provider-utils # HTTP utilities: retry, backoff, error parsing, API-key loading
├── aimux-provider-utils # One-exchange HTTP helpers, response handlers, API-key loading
├── aimux-ffi # C ABI (opaque handles + JSON results + owned aimux_error_t *) for non-native bindings
└── tools/ # aimux-cli (cache probe) · aimux-replay · aimux-web (console)
```
Expand All @@ -122,7 +122,7 @@ cargo add aimux-core aimux-providers
| `aimux-core` | Core abstractions: `LanguageModel` / `Provider` / `Message` / `StreamPart` | [crates.io](https://crates.io/crates/aimux-core) |
| `aimux-providers` | 325 provider implementations | [crates.io](https://crates.io/crates/aimux-providers) |
| `aimux-stream` | SSE / NDJSON stream parsing | [crates.io](https://crates.io/crates/aimux-stream) |
| `aimux-provider-utils` | HTTP utilities: retry, backoff, error parsing | [crates.io](https://crates.io/crates/aimux-provider-utils) |
| `aimux-provider-utils` | One-exchange HTTP helpers and typed response handlers | [crates.io](https://crates.io/crates/aimux-provider-utils) |
| `aimux-ffi` | C ABI for non-native bindings | [crates.io](https://crates.io/crates/aimux-ffi) |

**Node.js**:
Expand Down
10 changes: 9 additions & 1 deletion aimux-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ futures = { workspace = true }
async-stream = { workspace = true }
tracing = { workspace = true }
ts-rs = { workspace = true }
# Deliberately narrower than the workspace-wide `full`: core's production
# code uses only timers (`time`) and `select!` (`macros`). Keeping the
# declaration at the real API surface documents what Core actually asks
# of the runtime (RFC-0016 had removed core's tokio dependency; RFC-0031
# reintroduces it for operation deadlines and retry backoff).
tokio = { version = "1", features = ["time", "macros", "rt", "sync"] }
tokio-util = "0.7"
httpdate = { workspace = true }
rand = { workspace = true }
base64 = "0.22"

[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt"] }
tokio = { workspace = true, features = ["macros", "rt", "test-util"] }

[lints]
workspace = true
37 changes: 37 additions & 0 deletions aimux-core/src/abort_signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Cooperative caller cancellation for Core operations.

use tokio_util::sync::CancellationToken;

/// A cancellation signal analogous to the Web `AbortSignal`.
///
/// Timeouts are deliberately not encoded as cancellation reasons. Core owns
/// timeout deadlines and returns [`crate::AiMuxError::Timeout`] directly;
/// this type represents only caller-requested cancellation.
#[derive(Debug, Clone, Default)]
pub struct AbortSignal {
token: CancellationToken,
}

impl AbortSignal {
/// Create a fresh signal.
#[must_use]
pub fn new() -> Self {
Self::default()
}

/// Cancel with the default reason.
pub fn abort(&self) {
self.token.cancel();
}

/// Whether cancellation has been requested.
#[must_use]
pub fn is_aborted(&self) -> bool {
self.token.is_cancelled()
}

/// Resolve when cancellation is requested.
pub fn cancelled(&self) -> impl std::future::Future<Output = ()> + Send + 'static {
self.token.clone().cancelled_owned()
}
}
43 changes: 39 additions & 4 deletions aimux-core/src/embedding_model.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! The `EmbeddingModel` trait — the provider-facing interface for text embeddings.
//! The `EmbeddingModel` trait — the provider-facing interface for text embeddings.
//!
//! Aligned with Vercel AI SDK `EmbeddingModelV4`
//! (`reference/ai/packages/provider/src/embedding-model/v4/`).
Expand All @@ -11,9 +11,8 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;

use crate::error::AiMuxError;
use crate::shared::{
AbortSignal, SharedHeaders, SharedProviderMetadata, SharedProviderOptions, Warning,
};
use crate::shared::{SharedHeaders, SharedProviderMetadata, SharedProviderOptions, Warning};
use crate::{AbortSignal, retry, timeout};

/// A single embedding vector.
///
Expand All @@ -36,6 +35,12 @@ pub struct EmbeddingCallOptions {
#[ts(skip)]
pub abort_signal: Option<AbortSignal>,

/// Per-call retry override. `None` uses the model default.
pub max_retries: Option<u32>,

/// Per-call operation timeout.
pub timeout: Option<crate::options::TimeoutConfiguration>,

/// Additional provider-specific options, keyed by provider name.
pub provider_options: Option<SharedProviderOptions>,

Expand All @@ -49,6 +54,8 @@ impl EmbeddingCallOptions {
Self {
values: vec![value.into()],
abort_signal: None,
max_retries: None,
timeout: None,
provider_options: None,
headers: None,
}
Expand Down Expand Up @@ -121,6 +128,10 @@ pub trait EmbeddingModel: Send + Sync {
/// Provider-specific model ID, e.g. `"text-embedding-3-small"`.
fn model_id(&self) -> &str;

fn retry_config(&self) -> crate::retry::RetryConfig {
crate::retry::RetryConfig::default()
}

/// Limit of how many embeddings can be generated in a single API call.
///
/// `None` means the model has no fixed limit. The TS spec allows this to
Expand All @@ -136,3 +147,27 @@ pub trait EmbeddingModel: Send + Sync {
async fn do_embed(&self, options: &EmbeddingCallOptions)
-> Result<EmbeddingResult, AiMuxError>;
}

/// User-facing embedding operation with Core-owned retry and timeout.
///
/// # Errors
///
/// Returns the provider failure, retry exhaustion, timeout, or caller abort.
pub async fn embed(
model: &dyn EmbeddingModel,
options: EmbeddingCallOptions,
) -> Result<EmbeddingResult, AiMuxError> {
let timeout = timeout::OperationTimeout::new(options.timeout.unwrap_or_default())?;
let abort_signal = options.abort_signal.clone();
let retries = retry::prepare_retries(
options.max_retries,
model.retry_config(),
abort_signal.clone(),
);
timeout::run(
retries.retry(|| model.do_embed(&options)),
abort_signal.as_ref(),
timeout,
)
.await
}
Loading
Loading