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
10 changes: 6 additions & 4 deletions api/tinyhumans.backend.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"url": "https://api.tinyhumans.ai/swagger.json",
"title": "TinyHumans API",
"version": "1.0.0",
"pathCount": 231,
"totalOperationCount": 256,
"operationCount": 204,
"pathCount": 233,
"totalOperationCount": 258,
"operationCount": 206,
"supplementalOperationCount": 13,
"excludedAdminOperationCount": 44,
"excludedWebhookOperationCount": 12,
Expand All @@ -34,7 +34,7 @@
"name": "agentIntegrations",
"basePath": "/agent-integrations",
"auth": "mixed",
"operationCount": 73,
"operationCount": 75,
"tags": [
"Agent Integrations",
"OpenHuman parity"
Expand Down Expand Up @@ -99,6 +99,8 @@
"POST /agent-integrations/openrouter/embeddings",
"POST /agent-integrations/openrouter/images",
"POST /agent-integrations/openrouter/messages",
"POST /agent-integrations/openrouter/systemone",
"POST /agent-integrations/openrouter/v1/systemone",
"POST /agent-integrations/openrouter/videos",
"POST /agent-integrations/parallel/chat",
"POST /agent-integrations/parallel/dataset",
Expand Down
16 changes: 15 additions & 1 deletion src/api/agent_integrations/openrouter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
//! [`DynamicResponse`], matching how [`crate::api::inference`] treats the
//! OpenAI-compatible surface.
//!
//! Four request shapes share the one provider:
//! Five request shapes share the one provider:
//!
//! - OpenAI chat and text completions.
//! - Anthropic-format messages, which the Anthropic SDK can call directly.
//! - TypeSafe System One requests for Jev typed decisions.
//! - Embeddings, which resolve against a **separate** upstream catalog — an
//! embedding slug is not valid on the chat routes, or vice versa.
//! - Image and video generation, billed per generation rather than per token
Expand Down Expand Up @@ -182,6 +183,19 @@ impl AgentIntegrationsApi<'_> {
.await
}

/// Evaluate typed questions with a Jev System One model.
///
/// The request and response use TypeSafe's wire shape. For a fully typed
/// client, point `tinyjevclient` at the companion `/v1/systemone` alias by
/// using `/agent-integrations/openrouter` as its base URL.
pub async fn openrouter_system_one(
&self,
request: &impl Serialize,
) -> Result<DynamicResponse, Error> {
self.passthrough("/agent-integrations/openrouter/systemone", request)
Comment thread
senamakel marked this conversation as resolved.
.await
}

/// List embedding models. A separate catalog from the chat models.
pub async fn list_openrouter_embedding_models(
&self,
Expand Down
2 changes: 2 additions & 0 deletions src/generated_public_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub const PUBLIC_ROUTES: &[(&str, &str)] = &[
("GET", "/agent-integrations/openrouter/images/models"),
("POST", "/agent-integrations/openrouter/messages"),
("GET", "/agent-integrations/openrouter/models"),
("POST", "/agent-integrations/openrouter/systemone"),
Comment thread
senamakel marked this conversation as resolved.
("POST", "/agent-integrations/openrouter/v1/systemone"),
("POST", "/agent-integrations/openrouter/videos"),
("GET", "/agent-integrations/openrouter/videos/{jobId}"),
("GET", "/agent-integrations/openrouter/videos/{jobId}/content"),
Expand Down
5 changes: 3 additions & 2 deletions tests/openapi_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ fn generated_rust_routes_match_the_public_manifest() {
// last a year).
// 203 -> 204: `POST /opencompany/instances/{slug}/update`, the
// owner-triggered "update to latest" for a hosted company.
assert_eq!(manifest["source"]["operationCount"], 204);
// 204 -> 206: OpenRouter System One plus its TypeSafe-compatible alias.
assert_eq!(manifest["source"]["operationCount"], 206);
// 14 -> 13: `GET /orchestration/v1/steering` left with that family.
assert_eq!(manifest["source"]["supplementalOperationCount"], 13);
// 37 -> 39: the two service-token operations on
Expand All @@ -181,7 +182,7 @@ fn generated_rust_routes_match_the_public_manifest() {
// post's cover and body figures. Same token as the other blog writes.
assert_eq!(manifest["source"]["excludedAdminOperationCount"], 44);
assert_eq!(manifest["source"]["excludedWebhookOperationCount"], 12);
assert_eq!(rust_routes.len(), 204);
assert_eq!(rust_routes.len(), 206);
assert_eq!(rust_routes, manifest_routes);
assert!(rust_routes
.iter()
Expand Down
39 changes: 39 additions & 0 deletions tests/openrouter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,45 @@ async fn messages_speaks_the_anthropic_shape() {
assert_eq!(response["usage"]["input_tokens"], 12);
}

#[tokio::test]
async fn system_one_preserves_the_typesafe_wire_shape() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/agent-integrations/openrouter/systemone"))
.and(body_json(json!({
"model": "jev-latest",
"state": {"ticket": "I was charged twice"},
"questions": {
"refund": {"type": "noul", "instructions": "Is a refund requested?"}
}
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "gen-dec-1",
"model": "typesafe/jev-1.13-20260917",
"provider": "TypeSafe",
"answers": {"refund": {"type": "noul", "noul": 0.98}},
"usage": {"input_tokens": 275, "output_tokens": 20, "cost": 0.00003}
})))
.mount(&server)
.await;

let response = TinyHumansClient::new(server.uri())
.agent_integrations()
.openrouter_system_one(&json!({
"model": "jev-latest",
"state": {"ticket": "I was charged twice"},
"questions": {
"refund": {"type": "noul", "instructions": "Is a refund requested?"}
}
}))
.await
.unwrap();

assert_eq!(response["answers"]["refund"]["type"], "noul");
assert_eq!(response["answers"]["refund"]["noul"], 0.98);
assert_eq!(response["usage"]["cost"], 0.00003);
}

#[tokio::test]
async fn embedding_models_come_from_their_own_catalog() {
let server = MockServer::start().await;
Expand Down
Loading