Merge pull request #747 from Routstr/feat/systemone-typesafe

feat(systemone): add TypeSafe System One decision endpoint
This commit is contained in:
9qeklajc
2026-09-20 16:04:11 +02:00
committed by GitHub
9 changed files with 630 additions and 1 deletions
+78
View File
@@ -200,6 +200,84 @@ POST /v1/embeddings
}
```
## System One (TypeSafe Decisions)
### Evaluate State
Evaluate a state against typed questions (noul / choice / score) on a TypeSafe
System One decision model (e.g. `jev-latest`). Requires a `typesafe` upstream
provider on the node.
```http
POST /v1/systemone
```
**Request Body:**
```json
{
"model": "jev-latest",
"state": "Help! My payouts have been failing for 3 days.",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?"
}
}
}
```
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `model` | string | Yes | - | TypeSafe alias (`jev-latest`, `jev-preview`) or versioned id (`jev-1.13.0`) |
| `state` | string/object/array | Yes | - | Content to evaluate |
| `questions` | map<string, Question> | Yes | - | Typed questions; answers keyed identically |
**Response:**
```json
{
"model": "jev-latest",
"answers": {
"is_urgent": {
"type": "noul",
"noul": 0.95
}
},
"usage": {
"input_tokens": 312,
"output_tokens": 48
}
}
```
Billing is input-token based (output tokens are free on Jev); the response's
`usage` is the settlement seam, exactly like embeddings.
**Notes:**
- The response `model` echoes the id you requested (e.g. `jev-latest`), not the
resolved build (`jev-1.13.0`) TypeSafe returns. Routstr also adds its standard
`id`, `cost`, `metadata.routstr` and `usage.*_msats` fields.
- TypeSafe's `GET /v1/models` lists aliases only; the node additionally seeds
the known versioned ids so they can be requested directly.
- TypeSafe answers `429 Too Many Requests` and `529 Overloaded` when throttled.
Both are forwarded as upstream errors; retry with exponential backoff.
**Enabling the provider:**
1. Admin UI → Providers → *TypeSafe* (base URL is fixed to
`https://api.typesafe.ai/v1`), paste your `api.typesafe.ai` key. Or
`POST /admin/api/upstream-providers` with
`{"provider_type": "typesafe", "api_key": "<key>"}`.
2. On a node with an empty provider table, setting `TYPESAFE_API_KEY` seeds
the provider automatically.
3. `jev-latest`, `jev-preview` and `jev-1.13.0` are catalogued with the
published rate ($0.042 per million input tokens, output free). Override the
model row if TypeSafe changes pricing.
## Images (Coming Soon)
### Create Image
+1
View File
@@ -104,6 +104,7 @@ Standard OpenAI-compatible endpoints:
- **Responses**: `/v1/responses`
- **Chat Completions**: `/v1/chat/completions`
- **Embeddings**: `/v1/embeddings`
- **System One**: `/v1/systemone` (TypeSafe decision models)
- **Completions**: `/v1/completions` *(planned)*
- **Images**: `/v1/images/generations` *(planned)*
- **Audio**: `/v1/audio/transcriptions` *(planned)*
+4
View File
@@ -262,6 +262,10 @@ _ALLOWED_ENDPOINTS: dict[str, frozenset[str]] = {
"responses": frozenset({"POST"}),
"messages": frozenset({"POST"}),
"embeddings": frozenset({"POST"}),
# TypeSafe System One decision endpoint: POST {state, model, questions}
# -> {answers, usage}. Non-streaming, JSON in/out; billed from the
# response's usage exactly like embeddings.
"systemone": frozenset({"POST"}),
"models": frozenset({"GET"}),
"attestation": frozenset({"GET"}),
"tee/attestation": frozenset({"GET"}),
+2
View File
@@ -12,6 +12,7 @@ from .perplexity import PerplexityUpstreamProvider
from .ppqai import PPQAIUpstreamProvider
from .routstr import RoutstrUpstreamProvider
from .tinfoil import TinfoilUpstreamProvider
from .typesafe import TypeSafeUpstreamProvider
from .xai import XAIUpstreamProvider
upstream_provider_classes: list[type[BaseUpstreamProvider]] = [
@@ -28,6 +29,7 @@ upstream_provider_classes: list[type[BaseUpstreamProvider]] = [
PPQAIUpstreamProvider,
RoutstrUpstreamProvider,
TinfoilUpstreamProvider,
TypeSafeUpstreamProvider,
XAIUpstreamProvider,
]
"""List of all upstream classes"""
+2 -1
View File
@@ -303,7 +303,7 @@ def _openai_completion_path(path: str) -> str | None:
def _x_cashu_path_has_settlement_handler(path: str) -> bool:
canonical = path.rstrip("/")
return _openai_completion_path(canonical) is not None or canonical.endswith(
("embeddings", "messages", "messages/count_tokens")
("embeddings", "messages", "messages/count_tokens", "systemone")
)
@@ -3155,6 +3155,7 @@ class BaseUpstreamProvider:
or path.endswith("embeddings")
or path.endswith("messages")
or path.endswith("messages/count_tokens")
or path.endswith("systemone")
):
if path.endswith("messages"):
client_wants_streaming = False
+1
View File
@@ -273,6 +273,7 @@ async def _seed_providers_from_settings(
("FIREWORKS_API_KEY", "fireworks", None, None),
("XAI_API_KEY", "xai", None, None),
("TINFOIL_API_KEY", "tinfoil", None, None),
("TYPESAFE_API_KEY", "typesafe", None, None),
]
for env_key, provider_type, _, _ in env_mappings:
+143
View File
@@ -0,0 +1,143 @@
"""Upstream provider for the TypeSafe System One API.
``POST /v1/systemone`` takes ``{state, model, questions}`` and returns
``{model, answers, usage}``. The ``usage`` shape (``input_tokens`` /
``output_tokens``) is what :func:`routstr.payment.usage.normalize_usage`
already parses, so billing needs no dialect handling. This provider only
assembles the catalog: ``GET /v1/models`` lists names without prices.
"""
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, Any
import httpx
from ..core.logging import get_logger
from ..payment.models import Architecture, Model, Pricing, TopProvider
from .base import BaseUpstreamProvider
if TYPE_CHECKING:
from ..core.db import UpstreamProviderRow
logger = get_logger(__name__)
# USD per token (https://docs.typesafe.ai/models). Output is free.
_INPUT_RATE_USD = 0.042 / 1_000_000
_OUTPUT_RATE_USD = 0.0
# The listing returns aliases only; versioned ids are accepted but unlisted.
_VERSIONED_MODEL_IDS = ("jev-1.13.0",)
_CONTEXT_LENGTH = 64_000
_MODELS_TIMEOUT_SECONDS = 30.0
def _parse_release_date(value: object) -> int:
if not isinstance(value, str) or not value:
return 0
try:
return int(datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp())
except ValueError:
return 0
def _build_model(name: str, entry: dict[str, Any] | None = None) -> Model:
entry = entry or {}
description = entry.get("description")
if not isinstance(description, str) or not description:
description = f"TypeSafe System One model {name}"
return Model(
id=name,
name=name,
created=_parse_release_date(entry.get("release_date")),
description=description,
context_length=_CONTEXT_LENGTH,
architecture=Architecture(
modality="text->decisions",
input_modalities=["text"],
output_modalities=["decisions"],
tokenizer="Other",
instruct_type=None,
),
pricing=Pricing(prompt=_INPUT_RATE_USD, completion=_OUTPUT_RATE_USD),
top_provider=TopProvider(context_length=_CONTEXT_LENGTH),
)
def _models_from_listing(data: object) -> list[Model]:
entries = data.get("models", []) if isinstance(data, dict) else data
if not isinstance(entries, list):
entries = []
models: dict[str, Model] = {}
for entry in entries:
name = entry.get("name") if isinstance(entry, dict) else None
if isinstance(name, str) and name and name not in models:
models[name] = _build_model(name, entry)
for name in _VERSIONED_MODEL_IDS:
if name not in models:
models[name] = _build_model(name)
return list(models.values())
class TypeSafeUpstreamProvider(BaseUpstreamProvider):
"""Upstream provider for the TypeSafe System One decision API."""
provider_type = "typesafe"
default_base_url = "https://api.typesafe.ai/v1"
platform_url = "https://docs.typesafe.ai"
def __init__(self, api_key: str, provider_fee: float = 1.0):
super().__init__(
base_url=self.default_base_url,
api_key=api_key,
provider_fee=provider_fee,
)
@classmethod
def _build_from_row(
cls, provider_row: "UpstreamProviderRow"
) -> "TypeSafeUpstreamProvider":
return cls(
api_key=provider_row.api_key,
provider_fee=provider_row.provider_fee,
)
@classmethod
def get_provider_metadata(cls) -> dict[str, object]:
return {
"id": cls.provider_type,
"name": "TypeSafe",
"default_base_url": cls.default_base_url,
"fixed_base_url": True,
"platform_url": cls.platform_url,
"can_create_account": False,
"can_topup": False,
"can_show_balance": False,
}
def transform_model_name(self, model_id: str) -> str:
return model_id.removeprefix("typesafe/")
async def fetch_models(self) -> list[Model]:
"""Fetch the catalog; return an empty list on failure so init never breaks."""
url = f"{self.base_url}/models"
headers = {"Authorization": f"Bearer {self.api_key}"}
try:
async with httpx.AsyncClient(timeout=_MODELS_TIMEOUT_SECONDS) as client:
response = await client.get(url, headers=headers)
response.raise_for_status()
data = response.json()
except Exception as exc:
logger.warning(
"Failed to fetch TypeSafe model catalog",
extra={"url": url, "error": str(exc)},
exc_info=True,
)
return []
return _models_from_listing(data)
+221
View File
@@ -0,0 +1,221 @@
"""Integration test: POST /v1/systemone proxied and billed end-to-end.
The TypeSafe decision endpoint shares the request plumbing with embeddings:
model id in the top-level ``model`` field, flat ``usage`` in the response.
This test pins the whole path — allowlist, auth, forwarding, and settlement
billed from the response's usage — with only the network hop mocked.
"""
import json
from typing import Any, AsyncGenerator
from unittest.mock import AsyncMock, patch
import httpx
import pytest
from httpx import AsyncClient
from sqlmodel.ext.asyncio.session import AsyncSession
from routstr.payment.models import Architecture, Model, Pricing
from routstr.proxy import refresh_model_maps
from routstr.upstream.base import BaseUpstreamProvider
TYPESAFE_BASE_URL = "https://api.typesafe.ai/v1"
SYSTEMONE_REQUEST = {
"model": "jev-latest",
"state": "Help! My payouts have been failing for 3 days.",
"questions": {
"is_urgent": {"type": "noul", "instructions": "Does this convey urgency?"}
},
}
SYSTEMONE_RESPONSE = {
"model": "jev-latest",
"answers": {
"is_urgent": {"type": "noul", "noul": 0.95},
},
"usage": {"input_tokens": 1000, "output_tokens": 200},
}
class _StaticTypeSafeProvider(BaseUpstreamProvider):
"""Upstream provider with a fixed TypeSafe model catalog."""
def __init__(self, base_url: str, api_key: str, fee: float, model: Model) -> None:
super().__init__(base_url, api_key, fee)
self.provider_type = "typesafe"
self._static_model = model
def get_cached_models(self) -> list[Model]:
return [self._static_model]
async def refresh_models_cache(self) -> None:
pass
def _jev_model(prompt_sats: float = 0.001, completion_sats: float = 0.0) -> Model:
return Model(
id="jev-latest",
name="jev-latest",
created=1,
description="TypeSafe System One decision model",
context_length=64_000,
architecture=Architecture(
modality="text->decisions",
input_modalities=["text"],
output_modalities=["decisions"],
tokenizer="Other",
instruct_type=None,
),
pricing=Pricing(
prompt=prompt_sats, completion=completion_sats, max_cost=50.0
),
sats_pricing=Pricing(
prompt=prompt_sats, completion=completion_sats, max_cost=50.0
),
)
@pytest.fixture
async def typesafe_provider_maps(
patched_db_engine: None,
) -> AsyncGenerator[_StaticTypeSafeProvider, None]:
"""Install a TypeSafe provider with a priced jev-latest model."""
provider = _StaticTypeSafeProvider(
TYPESAFE_BASE_URL,
"key-typesafe",
1.0,
_jev_model(),
)
from routstr import proxy
original_upstreams = proxy.get_upstreams()
with patch("routstr.proxy._upstreams", [provider]):
await refresh_model_maps()
yield provider
with patch("routstr.proxy._upstreams", original_upstreams):
await refresh_model_maps()
@pytest.mark.integration
@pytest.mark.asyncio
async def test_systemone_forwarded_and_billed_from_usage(
authenticated_client: AsyncClient,
typesafe_provider_maps: _StaticTypeSafeProvider,
integration_session: AsyncSession,
) -> None:
"""A systemone request reaches api.typesafe.ai and bills input tokens."""
sent_requests: list[httpx.Request] = []
async def fake_transport(
request: httpx.Request, *args: Any, **kwargs: Any
) -> httpx.Response:
sent_requests.append(request)
return httpx.Response(
200,
content=json.dumps(SYSTEMONE_RESPONSE).encode(),
headers={"content-type": "application/json"},
)
with (
patch(
"httpx.AsyncHTTPTransport.handle_async_request",
side_effect=fake_transport,
),
patch(
"routstr.payment.cost_calculation.sats_usd_price",
return_value=0.0005,
),
):
response = await authenticated_client.post(
"/v1/systemone",
json=SYSTEMONE_REQUEST,
)
assert response.status_code == 200, response.text
payload = response.json()
# The answers pass through untouched.
assert payload["answers"]["is_urgent"]["noul"] == pytest.approx(0.95)
# Exactly one upstream hop, aimed at TypeSafe's endpoint with the
# provider's model spelling.
assert len(sent_requests) == 1
sent = sent_requests[0]
assert str(sent.url) == "https://api.typesafe.ai/v1/systemone"
assert json.loads(sent.content)["model"] == "jev-latest"
# Billed from usage: 1000 input tokens at 0.001 sats/token = 1000 msats;
# 200 output tokens at 0 sats = 0. Settled cost is exposed on the body.
assert payload["cost"]["input_msats"] == 1000
assert payload["cost"]["output_msats"] == 0
assert payload["cost"]["total_msats"] == 1000
@pytest.mark.integration
@pytest.mark.asyncio
async def test_systemone_with_x_cashu_settles(
authenticated_client: AsyncClient,
typesafe_provider_maps: _StaticTypeSafeProvider,
testmint_wallet: Any,
) -> None:
"""The X-Cashu settlement gate admits systemone and refunds the delta."""
token = await testmint_wallet.mint_tokens(10_000)
async def fake_transport(
request: httpx.Request, *args: Any, **kwargs: Any
) -> httpx.Response:
return httpx.Response(
200,
content=json.dumps(SYSTEMONE_RESPONSE).encode(),
headers={"content-type": "application/json"},
)
# The redemption and refund helpers are bound into base.py at import time,
# so the app fixture's wallet patches do not reach the proxy's x-cashu path.
with (
patch(
"httpx.AsyncHTTPTransport.handle_async_request",
side_effect=fake_transport,
),
patch(
"routstr.upstream.base.recieve_token",
AsyncMock(return_value=(10_000, "sat", "https://mint.test")),
),
patch(
"routstr.upstream.base.send_token",
AsyncMock(return_value="cashuBrefundtoken"),
),
# send_refund derives the mint from the token it just created; the
# fake token has no mint to parse.
patch(
"routstr.upstream.base.token_mint_url",
return_value="https://mint.test",
),
# cost_calculation binds sats_usd_price at import time, so the price
# patch in the app fixture does not reach it.
patch(
"routstr.payment.cost_calculation.sats_usd_price",
return_value=0.0005,
),
# Mint trust is node state that other suites mutate; this test is
# about the endpoint gate, not mint policy.
patch(
"routstr.payment.helpers.is_trusted_source_mint",
return_value=True,
),
):
response = await authenticated_client.post(
"/v1/systemone",
json=SYSTEMONE_REQUEST,
headers={"X-Cashu": token},
)
# Not rejected as an unsupported endpoint (that returns 400 with
# x_cashu_unsupported_endpoint), and settled rather than streamed raw.
assert response.status_code == 200, response.text
payload = response.json()
assert payload["answers"]["is_urgent"]["type"] == "noul"
# A refund header exists when the token exceeded the settled cost.
assert response.headers.get("X-Cashu") is not None
+178
View File
@@ -0,0 +1,178 @@
"""Unit tests for the TypeSafe System One provider integration.
Covers the three integration seams the systemone endpoint touches:
* the proxy path allowlist admits ``systemone`` (POST only) and nothing that
merely looks like it;
* the X-Cashu settlement gate treats ``systemone`` as a settleable endpoint;
* the provider maps TypeSafe's pricing-less model listing onto priced
``Model`` objects with usable rates.
"""
from __future__ import annotations
import os
from typing import Any
os.environ.setdefault("UPSTREAM_BASE_URL", "http://test")
os.environ.setdefault("UPSTREAM_API_KEY", "test")
from unittest.mock import AsyncMock, MagicMock, patch # noqa: E402
import pytest # noqa: E402
from routstr.proxy import _forwarding_allowed # noqa: E402
from routstr.upstream.base import _x_cashu_path_has_settlement_handler # noqa: E402
from routstr.upstream.typesafe import TypeSafeUpstreamProvider # noqa: E402
@pytest.mark.parametrize(
("path", "method"),
[
("v1/systemone", "POST"),
("systemone", "POST"),
("v1/systemone/", "POST"),
],
)
def test_systemone_is_forwarded(path: str, method: str) -> None:
assert _forwarding_allowed(path, method) is True
@pytest.mark.parametrize(
("path", "method"),
[
("v1/systemone", "GET"), # billed endpoint is POST-only
("v1/systemone", "DELETE"),
("systemonedump", "POST"), # longer segment must not match
("v1/systemone/secret", "POST"), # trailing id segment widens nothing
("v1/systemone/../admin", "POST"), # traversal spelling is screened
],
)
def test_systemone_lookalikes_are_refused(path: str, method: str) -> None:
assert _forwarding_allowed(path, method) is False
def test_systemone_has_x_cashu_settlement_handler() -> None:
assert _x_cashu_path_has_settlement_handler("v1/systemone") is True
assert _x_cashu_path_has_settlement_handler("systemone/") is True
def test_provider_metadata_is_complete() -> None:
meta = TypeSafeUpstreamProvider.get_provider_metadata()
assert meta["id"] == "typesafe"
assert meta["default_base_url"] == "https://api.typesafe.ai/v1"
assert meta["fixed_base_url"] is True
def test_provider_transform_model_name() -> None:
provider = TypeSafeUpstreamProvider(api_key="test")
assert provider.transform_model_name("typesafe/jev-latest") == "jev-latest"
assert provider.transform_model_name("jev-latest") == "jev-latest"
def _mock_models_response(payload: dict) -> MagicMock:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.raise_for_status = MagicMock()
mock_response.json.return_value = payload
return mock_response
def _patch_client(mock_response: MagicMock) -> Any:
mock_client = MagicMock()
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
mock_client.get = AsyncMock(return_value=mock_response)
return patch(
"routstr.upstream.typesafe.httpx.AsyncClient",
return_value=mock_client,
)
@pytest.mark.asyncio
async def test_fetch_models_prices_the_listing() -> None:
provider = TypeSafeUpstreamProvider(api_key="test")
payload = {
"models": [
{
"name": "jev-latest",
"description": "The most recent stable release.",
"release_date": "2026-09-17",
},
{
"name": "jev-preview",
"description": "The most recent release.",
"release_date": "2026-09-17",
},
]
}
with _patch_client(_mock_models_response(payload)):
models = await provider.fetch_models()
assert [m.id for m in models] == ["jev-latest", "jev-preview", "jev-1.13.0"]
for model in models:
# Input priced, output free; rates usable (zero is a real price).
assert model.pricing.prompt == pytest.approx(0.042 / 1_000_000)
assert model.pricing.completion == 0.0
assert model.context_length == 64_000
assert model.architecture.input_modalities == ["text"]
assert model.architecture.output_modalities == ["decisions"]
@pytest.mark.asyncio
async def test_fetch_models_handles_error() -> None:
provider = TypeSafeUpstreamProvider(api_key="test")
mock_response = MagicMock()
mock_response.raise_for_status = MagicMock(
side_effect=RuntimeError("upstream down")
)
with (
_patch_client(mock_response),
patch("routstr.upstream.typesafe.logger") as mock_logger,
):
models = await provider.fetch_models()
assert models == []
mock_logger.warning.assert_called_once()
@pytest.mark.asyncio
async def test_fetch_models_defaults_unknown_model_rates() -> None:
"""A newly listed model still gets a usable default rate."""
provider = TypeSafeUpstreamProvider(api_key="test")
payload = {
"models": [
{"name": "jev-2.0", "description": "Future model", "release_date": None}
]
}
with _patch_client(_mock_models_response(payload)):
models = await provider.fetch_models()
by_id = {m.id: m for m in models}
assert "jev-2.0" in by_id
assert by_id["jev-2.0"].pricing.prompt == pytest.approx(0.042 / 1_000_000)
assert by_id["jev-2.0"].created == 0
@pytest.mark.asyncio
async def test_fetch_models_does_not_duplicate_listed_versioned_id() -> None:
provider = TypeSafeUpstreamProvider(api_key="test")
payload = {
"models": [
{
"name": "jev-1.13.0",
"description": "Jev 1.13",
"release_date": "2026-09-17T00:00:00Z",
}
]
}
with _patch_client(_mock_models_response(payload)):
models = await provider.fetch_models()
assert [m.id for m in models] == ["jev-1.13.0"]
assert models[0].description == "Jev 1.13"
assert models[0].created > 0