Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 9 additions & 7 deletions provider-anthropic/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion provider-anthropic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ path = "src/lib.rs"

[dependencies]
llm-router = { path = "../llm-router" }
iii-sdk = "=0.19.2"
iii-sdk = "=0.20.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Must stay on the same schemars major as iii-sdk so the derived
Expand Down
9 changes: 5 additions & 4 deletions provider-anthropic/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::errors::upstream_unavailable;
use crate::request::{auth_header, ANTHROPIC_VERSION};
use crate::{router_client, state, PROVIDER_ID};
use futures::future::BoxFuture;
use iii_sdk::{IIIError, III};
use iii_sdk::errors::Error;
use iii_sdk::IIIClient;
use llm_router::types::model::Model;
use llm_router::types::router::{RefreshModelsRequest, RefreshModelsResponse};
use serde_json::Value;
Expand Down Expand Up @@ -136,7 +137,7 @@ async fn fetch_live_models(
}

/// The refresh flow; returns the reconciled slice size.
pub async fn refresh_models(iii: &III, http: &reqwest::Client) -> Result<usize, IIIError> {
pub async fn refresh_models(iii: &IIIClient, http: &reqwest::Client) -> Result<usize, Error> {
let token = state::load_token(iii).await;
let resolved = router_client::resolve(iii, token.as_deref()).await?;

Expand Down Expand Up @@ -166,9 +167,9 @@ pub async fn refresh_models(iii: &III, http: &reqwest::Client) -> Result<usize,
}

pub fn make_refresh_models(
iii: III,
iii: IIIClient,
http: reqwest::Client,
) -> impl Fn(RefreshModelsRequest) -> BoxFuture<'static, Result<RefreshModelsResponse, IIIError>>
) -> impl Fn(RefreshModelsRequest) -> BoxFuture<'static, Result<RefreshModelsResponse, Error>>
+ Send
+ Sync
+ 'static {
Expand Down
22 changes: 11 additions & 11 deletions provider-anthropic/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Upstream failure → shared ErrorKind taxonomy (spec § provider protocol
//! rule 5: five providers MUST NOT invent five taxonomies).
use iii_sdk::IIIError;
use iii_sdk::errors::Error;
use llm_router::types::events::ErrorKind;
use serde_json::Value;

Expand All @@ -24,9 +24,9 @@ pub fn classify(status: Option<u16>, message: &str) -> ErrorKind {
}

/// Map router bus errors surfaced through `router::provider::resolve`.
pub fn classify_bus_error(err: &IIIError) -> ErrorKind {
pub fn classify_bus_error(err: &Error) -> ErrorKind {
match err {
IIIError::Remote { code, .. } if code == "router/registration_rejected" => {
Error::Remote { code, .. } if code == "router/registration_rejected" => {
ErrorKind::Permanent
}
_ => ErrorKind::Transient,
Expand Down Expand Up @@ -69,8 +69,8 @@ fn is_context_overflow_message(message: &str) -> bool {

/// Invalid handler input surfaced on the bus in the `{ code, message }`
/// convention (same shape RouterError uses on the router side).
pub fn invalid_request(message: impl Into<String>) -> IIIError {
IIIError::Remote {
pub fn invalid_request(message: impl Into<String>) -> Error {
Error::Remote {
code: "provider/invalid_request".to_string(),
message: message.into(),
stacktrace: None,
Expand All @@ -81,13 +81,13 @@ pub fn invalid_request(message: impl Into<String>) -> IIIError {
/// the provider's `invalid_request` wire error. Used with
/// `RegisterFunction::new_async_with_bad_request` so typed schemas are emitted
/// while the malformed-payload contract stays `provider/invalid_request`.
pub fn invalid_request_from_serde(e: serde_json::Error) -> IIIError {
pub fn invalid_request_from_serde(e: serde_json::Error) -> Error {
invalid_request(format!("bad ProviderStreamInput: {e}"))
}

/// Discovery hit a transient upstream failure — caller keeps the old slice.
pub fn upstream_unavailable(message: impl Into<String>) -> IIIError {
IIIError::Remote {
pub fn upstream_unavailable(message: impl Into<String>) -> Error {
Error::Remote {
code: "provider/upstream_unavailable".to_string(),
message: message.into(),
stacktrace: None,
Expand Down Expand Up @@ -140,7 +140,7 @@ mod tests {

#[test]
fn registration_rejected_is_permanent_on_the_bus() {
let err = IIIError::Remote {
let err = Error::Remote {
code: "router/registration_rejected".into(),
message: "bad token".into(),
stacktrace: None,
Expand All @@ -151,11 +151,11 @@ mod tests {
#[test]
fn bus_error_codes_are_worker_prefixed() {
match invalid_request("x") {
IIIError::Remote { code, .. } => assert_eq!(code, "provider/invalid_request"),
Error::Remote { code, .. } => assert_eq!(code, "provider/invalid_request"),
other => panic!("want Remote, got {other:?}"),
}
match upstream_unavailable("x") {
IIIError::Remote { code, .. } => assert_eq!(code, "provider/upstream_unavailable"),
Error::Remote { code, .. } => assert_eq!(code, "provider/upstream_unavailable"),
other => panic!("want Remote, got {other:?}"),
}
}
Expand Down
3 changes: 2 additions & 1 deletion provider-anthropic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
//! are warned about instead of silently dropped.

use clap::Parser;
use iii_sdk::{register_worker, InitOptions, WorkerMetadata};
use iii_sdk::runtime::WorkerMetadata;
use iii_sdk::{register_worker, InitOptions};
use provider_anthropic::register::register_provider;

#[derive(Parser, Debug)]
Expand Down
16 changes: 9 additions & 7 deletions provider-anthropic/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use crate::errors::invalid_request_from_serde;
use crate::stream_fn::make_stream;
use crate::surface;
use crate::{router_client, state, PROVIDER_ID};
use iii_sdk::{IIIError, RegisterFunction, RegisterTriggerInput, III};
use iii_sdk::errors::Error;
use iii_sdk::protocol::RegisterTriggerInput;
use iii_sdk::{IIIClient, RegisterFunction};
use llm_router::types::router::{
ProviderDeclaration, ProviderDefaults, ProviderReadyAck, RouterReadyEvent,
};
Expand Down Expand Up @@ -37,7 +39,7 @@ pub fn declaration() -> ProviderDeclaration {

/// One registration attempt: declare (with the persisted token when present)
/// and persist the token the router returns.
pub async fn declare_once(iii: &III) -> Result<(), IIIError> {
pub async fn declare_once(iii: &IIIClient) -> Result<(), Error> {
let token = state::load_token(iii).await;
let mut payload = serde_json::to_value(declaration()).expect("serializable declaration");
if let Some(t) = &token {
Expand All @@ -52,7 +54,7 @@ pub async fn declare_once(iii: &III) -> Result<(), IIIError> {
Ok(())
}

async fn persist_registration_token(iii: &III, token: &str) -> Result<(), IIIError> {
async fn persist_registration_token(iii: &IIIClient, token: &str) -> Result<(), Error> {
let mut delay = Duration::from_millis(200);
for attempt in 0..5 {
match state::store_token(iii, token).await {
Expand All @@ -73,7 +75,7 @@ async fn persist_registration_token(iii: &III, token: &str) -> Result<(), IIIErr
/// Retry until acknowledged: covers provider-before-router boot order.
/// A token mismatch also lands here — it never resolves on its own and
/// needs the operator to clear the binding (logged every attempt).
pub async fn declare_with_backoff(iii: III) {
pub async fn declare_with_backoff(iii: IIIClient) {
let mut delay = Duration::from_millis(500);
loop {
match declare_once(&iii).await {
Expand All @@ -93,15 +95,15 @@ pub async fn declare_with_backoff(iii: III) {
/// Register, then populate the catalog from the live API. The declaration
/// carries no models, so the slice is empty until this refresh lands;
/// failures are logged and left to the next config-change refresh.
pub async fn declare_and_refresh(iii: III, http: reqwest::Client) {
pub async fn declare_and_refresh(iii: IIIClient, http: reqwest::Client) {
declare_with_backoff(iii.clone()).await;
match refresh_models(&iii, &http).await {
Ok(count) => println!("[provider-anthropic] catalog refreshed: {count} models"),
Err(e) => eprintln!("[provider-anthropic] post-register refresh failed ({e})"),
}
}

pub async fn register_provider(iii: III) -> Result<(), IIIError> {
pub async fn register_provider(iii: IIIClient) -> Result<(), Error> {
// Streaming uses no total timeout (the router owns stream budgets);
// connect failures surface fast.
let http = reqwest::Client::builder()
Expand Down Expand Up @@ -133,7 +135,7 @@ pub async fn register_provider(iii: III) -> Result<(), IIIError> {
let (iii, http) = (iii_ready.clone(), http_ready.clone());
async move {
tokio::spawn(declare_and_refresh(iii, http));
Ok::<_, IIIError>(ProviderReadyAck { ok: true })
Ok::<_, Error>(ProviderReadyAck { ok: true })
}
})
.description(surface::ON_ROUTER_READY_DESC),
Expand Down
23 changes: 16 additions & 7 deletions provider-anthropic/src/router_client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Thin wrappers over the router's provider-protocol functions. All calls
//! carry the registration token (identity binding, spec adaptation #1).
use crate::PROVIDER_ID;
use iii_sdk::{IIIError, TriggerRequest, III};
use iii_sdk::errors::Error;
use iii_sdk::protocol::TriggerRequest;
use iii_sdk::IIIClient;
use llm_router::types::model::Model;
use llm_router::types::router::ProviderResolveResponse;
use serde_json::{json, Value};

async fn call(iii: &III, function_id: &str, payload: Value) -> Result<Value, IIIError> {
async fn call(iii: &IIIClient, function_id: &str, payload: Value) -> Result<Value, Error> {
iii.trigger(TriggerRequest {
function_id: function_id.into(),
payload,
Expand All @@ -17,21 +19,28 @@ async fn call(iii: &III, function_id: &str, payload: Value) -> Result<Value, III
}

/// `router::provider::resolve` — credential + effective settings.
pub async fn resolve(iii: &III, token: Option<&str>) -> Result<ProviderResolveResponse, IIIError> {
pub async fn resolve(
iii: &IIIClient,
token: Option<&str>,
) -> Result<ProviderResolveResponse, Error> {
let mut payload = json!({ "id": PROVIDER_ID });
if let Some(t) = token {
payload["token"] = json!(t);
}
let raw = call(iii, "router::provider::resolve", payload).await?;
serde_json::from_value(raw).map_err(|e| IIIError::Remote {
serde_json::from_value(raw).map_err(|e| Error::Remote {
code: "provider/bad_resolve_response".into(),
message: e.to_string(),
stacktrace: None,
})
}

/// `router::models::reconcile` — replace this provider's catalog slice.
pub async fn reconcile(iii: &III, models: Vec<Model>, token: Option<&str>) -> Result<(), IIIError> {
pub async fn reconcile(
iii: &IIIClient,
models: Vec<Model>,
token: Option<&str>,
) -> Result<(), Error> {
let mut payload = json!({
"provider": PROVIDER_ID,
"models": serde_json::to_value(models).expect("serializable models"),
Expand All @@ -44,7 +53,7 @@ pub async fn reconcile(iii: &III, models: Vec<Model>, token: Option<&str>) -> Re
}

/// `router::models::get` — authoritative catalog record (None when absent).
pub async fn models_get(iii: &III, model_id: &str) -> Option<Model> {
pub async fn models_get(iii: &IIIClient, model_id: &str) -> Option<Model> {
let raw = call(
iii,
"router::models::get",
Expand All @@ -56,6 +65,6 @@ pub async fn models_get(iii: &III, model_id: &str) -> Option<Model> {
}

/// `router::provider::register` — returns the registration token to persist.
pub async fn register(iii: &III, declaration: Value) -> Result<Value, IIIError> {
pub async fn register(iii: &IIIClient, declaration: Value) -> Result<Value, Error> {
call(iii, "router::provider::register", declaration).await
}
8 changes: 5 additions & 3 deletions provider-anthropic/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Registration-token persistence in iii-state (engine `state::*` functions,
//! binary-worker.md § 7). The raw token lives here, under the provider's own
//! scope; the router persists only its sha256 hash.
use iii_sdk::{IIIError, TriggerRequest, III};
use iii_sdk::errors::Error;
use iii_sdk::protocol::TriggerRequest;
use iii_sdk::IIIClient;
use serde_json::{json, Value};

pub const STATE_SCOPE: &str = "provider-anthropic";
const TOKEN_KEY: &str = "registration_token";

pub async fn load_token(iii: &III) -> Option<String> {
pub async fn load_token(iii: &IIIClient) -> Option<String> {
let value = iii
.trigger(TriggerRequest {
function_id: "state::get".into(),
Expand All @@ -20,7 +22,7 @@ pub async fn load_token(iii: &III) -> Option<String> {
value.as_str().map(String::from)
}

pub async fn store_token(iii: &III, token: &str) -> Result<(), IIIError> {
pub async fn store_token(iii: &IIIClient, token: &str) -> Result<(), Error> {
iii.trigger(TriggerRequest {
function_id: "state::set".into(),
payload: json!({ "scope": STATE_SCOPE, "key": TOKEN_KEY, "value": Value::from(token) }),
Expand Down
9 changes: 5 additions & 4 deletions provider-anthropic/src/stream_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use crate::upstream::{spawn_upstream, UpstreamArgs};
use crate::wire::cache::cache_enabled;
use crate::{router_client, state};
use futures::future::BoxFuture;
use iii_sdk::{IIIError, III};
use iii_sdk::errors::Error;
use iii_sdk::IIIClient;
use llm_router::channels::open_sink;
use llm_router::chat::relay::FrameSink;
use llm_router::types::events::{AssistantMessageEvent, ErrorKind};
Expand All @@ -22,9 +23,9 @@ use tokio::sync::mpsc;
pub const PING_INTERVAL: Duration = Duration::from_secs(30);

pub fn make_stream(
iii: III,
iii: IIIClient,
http: reqwest::Client,
) -> impl Fn(ProviderStreamInput) -> BoxFuture<'static, Result<ProviderStreamOutput, IIIError>>
) -> impl Fn(ProviderStreamInput) -> BoxFuture<'static, Result<ProviderStreamOutput, Error>>
+ Send
+ Sync
+ 'static {
Expand All @@ -46,7 +47,7 @@ fn send_event(sink: &dyn FrameSink, ev: &AssistantMessageEvent) -> Result<(), ()
}

async fn run_stream_call(
iii: &III,
iii: &IIIClient,
http: reqwest::Client,
input: ProviderStreamInput,
sink: &dyn FrameSink,
Expand Down
Loading
Loading