@rouvanpm/rouva 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/dist/index.d.mts +97 -15
- package/dist/index.d.ts +97 -15
- package/dist/index.js +25 -0
- package/dist/index.mjs +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ console.log(response.choices[0].message.content)
|
|
|
24
24
|
|
|
25
25
|
## Provider agnostic
|
|
26
26
|
|
|
27
|
-
Rouva works with all connected providers — Anthropic, OpenAI, Gemini, DeepSeek, Mistral, Moonshot, xAI, Z.ai
|
|
27
|
+
Rouva works with all connected providers — Anthropic, OpenAI, Gemini, DeepSeek, Mistral, Moonshot, xAI, Z.ai, Alibaba Qwen and MiniMax. You can request a specific model, force a specific provider, or omit both and let Rouva route to the cheapest capable model automatically.
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
30
|
// Request a specific model
|
|
@@ -98,14 +98,14 @@ const res = await rouva.chat.completions.create({
|
|
|
98
98
|
})
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
- **`temperature`** — sampling temperature between 0 and 1. The gateway omits it for models that only accept their default: OpenAI reasoning models (gpt-5 / gpt-6 families) and the Claude models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Fable).
|
|
101
|
+
- **`temperature`** — sampling temperature between 0 and 1. The gateway omits it for models that only accept their default: OpenAI reasoning models (gpt-5 / gpt-6 families) and the Claude models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Haiku 5.5, Fable), and models whose sampling is fixed (Kimi K3 / K3.1 Pro, MiniMax).
|
|
102
102
|
- **`max_tokens`** / **`max_completion_tokens`** — either name works. Values below 1024 also steer auto-routing away from reasoning models, which would otherwise spend the whole budget on hidden reasoning tokens.
|
|
103
103
|
- **`top_p`** — nucleus sampling; `top_p: 1` is treated as omitted. OpenAI reasoning models don't support it: routing avoids them when it's set, and pinning one returns a 400. The gateway omits it for the Claude models that reject it (as for `temperature`).
|
|
104
104
|
- **`stop`** — a string or up to 4 stop sequences (Anthropic receives them as `stop_sequences`). OpenAI and xAI reasoning models (e.g. gpt-5, grok-4.5 / 4.6 / 4.7) reject it: routing avoids them when it's set, and pinning one returns a 400.
|
|
105
105
|
- **`metadata`** — up to 10 string key-value pairs (keys up to 64 characters, values up to 256, no null bytes) stored with the request's usage record; not sent to the provider.
|
|
106
106
|
- **`seed`** — best-effort deterministic sampling. Only OpenAI honors it, so it requires an OpenAI `model` and pins the request to that exact model (no routing, no fallbacks).
|
|
107
107
|
- **`response_format`** — JSON mode or a JSON schema; see [Structured output](#structured-output).
|
|
108
|
-
- **`reasoning_effort`** — `'none'` … `'xhigh'`, or `'max'` for Claude. Which values a model takes depends on the model; routing only picks one that takes yours; a listed OpenAI or Claude model you name that doesn't gets a 400, and other providers' models get the value as sent. On gpt-6-luna / gpt-6-sol, `'none'` turns reasoning off. See [the docs](https://rouva.io/docs#reasoning-effort).
|
|
108
|
+
- **`reasoning_effort`** — `'none'` … `'xhigh'`, or `'max'` for Claude. Which values a model takes depends on the model; routing only picks one that takes yours; a listed OpenAI or Claude model you name that doesn't gets a 400, and other providers' models get the value as sent. On gpt-6-luna / gpt-6-sol / gpt-6.1-terra, `'none'` turns reasoning off. See [the docs](https://rouva.io/docs#reasoning-effort).
|
|
109
109
|
- **`parallel_tool_calls`** — with `tools`: `false` allows at most one tool call per turn (Claude included).
|
|
110
110
|
|
|
111
111
|
Unsupported OpenAI options (`logit_bias`, `logprobs`, `n > 1`, the legacy `functions`/`function_call` fields, …) are rejected by the gateway with an explicit 400 rather than silently ignored.
|
|
@@ -130,7 +130,7 @@ Non-tools requests on `/v1` honor the named model exactly unless the workspace o
|
|
|
130
130
|
|
|
131
131
|
## Tool use
|
|
132
132
|
|
|
133
|
-
Tools are forwarded to your target provider verbatim — define them in the **provider's own format** (OpenAI `{ type: "function", function: {...} }` or Anthropic `{ name, description, input_schema }`) and pin the matching `model`. Tool schemas are provider-specific, so `model` is required and the gateway returns a 400 without it.
|
|
133
|
+
Tools are forwarded to your target provider verbatim — define them in the **provider's own format** (OpenAI `{ type: "function", function: {...} }` or Anthropic `{ name, description, input_schema }`) and pin the matching `model`. Tool schemas are provider-specific, so `model` is required and the gateway returns a 400 without it. `gpt-6.1-sol`, `gpt-6-astra` and `gpt-5.6-*` take tools only through OpenAI's Responses API: the gateway uses it for you and converts both ways, so you still send and receive Chat Completions (`seed`, `stop` and a `max_tokens` below 16 are a 400 there). `rouva.run()` still refuses these models.
|
|
134
134
|
|
|
135
135
|
A tools request only ever moves to another model of the **same provider**: an account routing override for the task (when it targets the same provider), or — on the `/v1` endpoint with Intelligent Routing enabled — the cheapest capable model within that provider, using the named `model` as a cost ceiling (see [OpenAI-compatible endpoint](#openai-compatible-endpoint)). Otherwise the named model is honored exactly.
|
|
136
136
|
|
|
@@ -356,6 +356,25 @@ if (id) {
|
|
|
356
356
|
|
|
357
357
|
`pending` means it hasn't settled yet — retry after a moment. `incomplete` means it settled from partial usage (a stream cut short), so the provider may have billed more; `settled` and `scored` costs are final. A key reads only its own requests. A duplicate-cache replay has no `request_id` (nothing new was recorded or charged).
|
|
358
358
|
|
|
359
|
+
## Explain a routing decision — `rouva.explain()`
|
|
360
|
+
|
|
361
|
+
A dry run: the model Rouva would pick for a request, why, and the most it could cost — without calling a provider, recording a request or reserving spend.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
365
|
+
|
|
366
|
+
if (e.would_send) {
|
|
367
|
+
console.log(e.model, e.provider) // e.g. 'gpt-6-luna', 'openai'
|
|
368
|
+
console.log(e.route_reason) // 'intelligent' | 'override' | 'allowlist' | 'direct' | …
|
|
369
|
+
console.log(e.task_type) // e.g. 'summarize'
|
|
370
|
+
console.log(e.max_cost_usd) // what a spend-capped key would reserve
|
|
371
|
+
} else {
|
|
372
|
+
console.log(e.status, e.error) // the refusal the real request would get
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
It takes the same params as `chat.completions.create()`; pass `{ endpoint: 'v1' }` to explain the same body sent to `/v1/chat/completions`. The prompt is classified as for a real request, and explain has its own per-minute rate limit (a `RouvaError` with `refusal: 'rate'`). Not covered: the duplicate-request cache, a spend cap's remaining budget, and what the provider answers.
|
|
377
|
+
|
|
359
378
|
## Structured output
|
|
360
379
|
|
|
361
380
|
`response_format` asks for JSON: `{ type: 'json_object' }` (JSON mode — your prompt must mention "json") or `{ type: 'json_schema', json_schema: { name, schema, strict } }` (the answer follows your schema). Rouva only routes to models that serve the format; see [the docs](https://rouva.io/docs#structured-output) for which models do.
|
package/dist/index.d.mts
CHANGED
|
@@ -50,9 +50,9 @@ interface ContentBlockMessage {
|
|
|
50
50
|
* valid when `tools` is set on the request.
|
|
51
51
|
*/
|
|
52
52
|
type ChatMessage = Message | AssistantToolCallMessage | ToolResultMessage | ContentBlockMessage;
|
|
53
|
-
type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral' | 'moonshot' | 'xai' | 'zai' | 'alibaba' | (string & {});
|
|
53
|
+
type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral' | 'moonshot' | 'xai' | 'zai' | 'alibaba' | 'minimax' | (string & {});
|
|
54
54
|
/**
|
|
55
|
-
* Supported models across all providers (the gateway's list as of SDK 0.
|
|
55
|
+
* Supported models across all providers (the gateway's list as of SDK 0.5.1).
|
|
56
56
|
* Omit `model` entirely to let Rouva route to the cheapest capable model automatically.
|
|
57
57
|
*
|
|
58
58
|
* Other IDs are accepted by the type for forward compatibility. On
|
|
@@ -64,7 +64,7 @@ type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral'
|
|
|
64
64
|
*
|
|
65
65
|
* `'gpt-5.6'` is a deprecated alias the gateway maps to `'gpt-5.6-sol'`.
|
|
66
66
|
*/
|
|
67
|
-
type RouvaModel = 'claude-fable-5-1' | 'claude-fable-5' | 'claude-opus-5-5' | 'claude-opus-5' | 'claude-opus-4-8' | 'claude-opus-4-7' | 'claude-opus-4-6' | 'claude-sonnet-5-5' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001' | 'gpt-6.1-sol' | 'gpt-6-sol' | 'gpt-6-luna' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5.6' | 'gpt-5' | 'gpt-5.4-mini' | 'gpt-5-mini' | 'gpt-5-nano' | 'gpt-4.1' | 'gpt-4.1-mini' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-3.8-flash' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-3.5-flash-lite' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'deepseek-flash' | 'deepseek-flash-think' | 'deepseek-v4-pro' | 'mistral-large-latest' | 'mistral-small-latest' | 'kimi-k3' | 'kimi-k2.6' | 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-5.3' | 'glm-5.2' | 'glm-4.7-flash' | 'qwen3.8-omni-flash' | 'qwen3.8-flash' | 'qwen3.7-plus' | 'qwen-max' | 'qwen-plus' | 'qwen-flash' | (string & {});
|
|
67
|
+
type RouvaModel = 'claude-fable-5-1' | 'claude-fable-5' | 'claude-opus-5-5' | 'claude-opus-5' | 'claude-opus-4-8' | 'claude-opus-4-7' | 'claude-opus-4-6' | 'claude-sonnet-5-5' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-5-5' | 'claude-haiku-4-5-20251001' | 'gpt-6-astra' | 'gpt-6.1-sol' | 'gpt-6.1-terra' | 'gpt-6-sol' | 'gpt-6-luna' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5.6' | 'gpt-5' | 'gpt-5.4-mini' | 'gpt-5-mini' | 'gpt-5-nano' | 'gpt-4.1' | 'gpt-4.1-mini' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-4-argon' | 'gemini-3.8-flash' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-3.5-flash-lite' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'deepseek-flash' | 'deepseek-flash-think' | 'deepseek-v4-pro' | 'mistral-large-latest' | 'mistral-small-latest' | 'kimi-k3.1-pro' | 'kimi-k3' | 'kimi-k2.6' | 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-5.3' | 'glm-5.2' | 'glm-4.7-flash' | 'qwen3.8-omni-flash' | 'qwen3.8-flash' | 'qwen3.7-plus' | 'qwen-max' | 'qwen-plus' | 'qwen-flash' | 'MiniMax-M3.1-Flash' | 'MiniMax-M3' | (string & {});
|
|
68
68
|
/** Anthropic-style text block — the array form of the `system` param */
|
|
69
69
|
interface SystemTextBlock {
|
|
70
70
|
type: 'text';
|
|
@@ -108,7 +108,8 @@ interface ChatCompletionParams {
|
|
|
108
108
|
* Sampling temperature 0–1.
|
|
109
109
|
* The gateway omits it for models that only accept their default: OpenAI
|
|
110
110
|
* reasoning models (gpt-5 / gpt-6 families) and the Claude models that reject
|
|
111
|
-
* it (Opus 4.7 and newer, Sonnet 5 and newer, Fable)
|
|
111
|
+
* it (Opus 4.7 and newer, Sonnet 5 and newer, Haiku 5.5, Fable), and for
|
|
112
|
+
* models whose sampling is fixed (Kimi K3 / K3.1 Pro, MiniMax).
|
|
112
113
|
*/
|
|
113
114
|
temperature?: number;
|
|
114
115
|
/**
|
|
@@ -116,7 +117,8 @@ interface ChatCompletionParams {
|
|
|
116
117
|
* default) is treated as omitted. OpenAI reasoning models (gpt-5 / gpt-6
|
|
117
118
|
* families, o-series) don't support it: routing avoids them when top_p is
|
|
118
119
|
* set, and pinning one returns a 400. The gateway omits it for the Claude
|
|
119
|
-
* models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Fable)
|
|
120
|
+
* models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Haiku 5.5, Fable),
|
|
121
|
+
* and for models whose sampling is fixed (Kimi K3 / K3.1 Pro, MiniMax).
|
|
120
122
|
*/
|
|
121
123
|
top_p?: number;
|
|
122
124
|
/**
|
|
@@ -147,13 +149,14 @@ interface ChatCompletionParams {
|
|
|
147
149
|
* How hard a reasoning model thinks (gateway support since 2026-10-21).
|
|
148
150
|
* OpenAI models take it as is; Claude gets it as `output_config.effort`.
|
|
149
151
|
* Values depend on the model: gpt-5 / -mini / -nano take minimal–high;
|
|
150
|
-
* gpt-5.4-mini, gpt-5.6-*, gpt-6-luna / -sol take none–xhigh;
|
|
151
|
-
* low–xhigh; Claude Sonnet / Opus 4.6 low, medium,
|
|
152
|
-
* models also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
152
|
+
* gpt-5.4-mini, gpt-5.6-*, gpt-6-luna / -sol, gpt-6.1-terra take none–xhigh;
|
|
153
|
+
* gpt-6.1-sol and gpt-6-astra low–xhigh; Claude Sonnet / Opus 4.6 low, medium,
|
|
154
|
+
* high, max; newer Claude models (incl. Haiku 5.5) also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
153
155
|
* only picks a model that takes the value; a listed OpenAI or Claude model you
|
|
154
|
-
* name that doesn't is a 400 (other providers' models get the value as sent). On gpt-6-luna / -sol, `'none'` turns reasoning off (temperature,
|
|
155
|
-
* top_p and tools then work) and any other value turns it on (
|
|
156
|
-
*
|
|
156
|
+
* name that doesn't is a 400 (other providers' models get the value as sent). On gpt-6-luna / -sol and gpt-6.1-terra, `'none'` turns reasoning off (temperature,
|
|
157
|
+
* top_p and tools then work) and any other value turns it on (top_p is then
|
|
158
|
+
* a 400; tools are served through OpenAI's Responses API). `rouva.run()`
|
|
159
|
+
* doesn't accept it.
|
|
157
160
|
* See https://rouva.io/docs#reasoning-effort
|
|
158
161
|
*/
|
|
159
162
|
reasoning_effort?: ReasoningEffort | null;
|
|
@@ -175,8 +178,10 @@ interface ChatCompletionParams {
|
|
|
175
178
|
* Anthropic `{ name, description, input_schema }`). Requires `model`.
|
|
176
179
|
* Tool schemas are provider-specific, so a tools request only ever moves to
|
|
177
180
|
* another model of the SAME provider (a routing override, or cheaper-model
|
|
178
|
-
* routing on /v1).
|
|
179
|
-
*
|
|
181
|
+
* routing on /v1). gpt-6.1-sol, gpt-6-astra and gpt-5.6-* take tools only through
|
|
182
|
+
* OpenAI's Responses API: the gateway uses it for you and converts both ways
|
|
183
|
+
* (you still send and receive Chat Completions), and there `seed`, `stop` and
|
|
184
|
+
* a `max_tokens` below 16 are a 400. `rouva.run()` still refuses these models.
|
|
180
185
|
*/
|
|
181
186
|
tools?: Array<Record<string, unknown>>;
|
|
182
187
|
/**
|
|
@@ -185,6 +190,64 @@ interface ChatCompletionParams {
|
|
|
185
190
|
*/
|
|
186
191
|
tool_choice?: string | Record<string, unknown>;
|
|
187
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* What `rouva.explain()` returns when the request would be sent: the model
|
|
195
|
+
* Rouva would pick, why, and the most it could cost — nothing was sent,
|
|
196
|
+
* recorded or reserved.
|
|
197
|
+
*/
|
|
198
|
+
interface RouvaExplanation {
|
|
199
|
+
dry_run: true;
|
|
200
|
+
would_send: true;
|
|
201
|
+
endpoint: '/api/gateway/messages' | '/v1/chat/completions';
|
|
202
|
+
provider: string;
|
|
203
|
+
model: string;
|
|
204
|
+
/** The model you named, or null. */
|
|
205
|
+
requested_model: string | null;
|
|
206
|
+
task_type: string | null;
|
|
207
|
+
route_reason: RouvaRouteReason;
|
|
208
|
+
/** How the prompt was classified (e.g. `jev`, `embedding`, `cache`). */
|
|
209
|
+
classified_by: string | null;
|
|
210
|
+
/** Jev's confidence (0–1), when Jev classified it. */
|
|
211
|
+
classifier_confidence: number | null;
|
|
212
|
+
classifier_latency_ms: number | null;
|
|
213
|
+
max_tokens: number;
|
|
214
|
+
stream: boolean;
|
|
215
|
+
/** The model's billed rates in USD per million tokens, or null when it has no known price. */
|
|
216
|
+
price_per_1m: {
|
|
217
|
+
input: number;
|
|
218
|
+
output: number;
|
|
219
|
+
} | null;
|
|
220
|
+
/**
|
|
221
|
+
* The most the request could cost in USD — what a spend-capped key reserves
|
|
222
|
+
* (the input as sent plus max_tokens of output, plus reasoning and
|
|
223
|
+
* summarization allowances where they apply). null: no known price.
|
|
224
|
+
*/
|
|
225
|
+
max_cost_usd: number | null;
|
|
226
|
+
/** A long conversation's older turns would be summarized first (Claude). */
|
|
227
|
+
summarize_history: boolean;
|
|
228
|
+
spend_capped: boolean;
|
|
229
|
+
}
|
|
230
|
+
/** What `rouva.explain()` returns when the real request would be refused. */
|
|
231
|
+
interface RouvaExplainedRefusal {
|
|
232
|
+
dry_run: true;
|
|
233
|
+
would_send: false;
|
|
234
|
+
/** The status the real request would get. */
|
|
235
|
+
status: number;
|
|
236
|
+
/** The error it would get. */
|
|
237
|
+
error: unknown;
|
|
238
|
+
/** `X-Rouva-Refusal`, when Rouva marks it (e.g. `unbounded`, `key_cap`). */
|
|
239
|
+
refusal: string | null;
|
|
240
|
+
/** The model Rouva had chosen before refusing, when it had chosen one. */
|
|
241
|
+
provider: string | null;
|
|
242
|
+
model: string | null;
|
|
243
|
+
route_reason: RouvaRouteReason | null;
|
|
244
|
+
}
|
|
245
|
+
type RouvaExplainResult = RouvaExplanation | RouvaExplainedRefusal;
|
|
246
|
+
/** Options for `rouva.explain()`. */
|
|
247
|
+
interface ExplainOptions extends RequestOptions {
|
|
248
|
+
/** `'v1'` explains the same body sent to /v1/chat/completions (which pins the named model). */
|
|
249
|
+
endpoint?: 'messages' | 'v1';
|
|
250
|
+
}
|
|
188
251
|
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
189
252
|
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
190
253
|
/** `response_format` values the gateway accepts. */
|
|
@@ -490,6 +553,25 @@ declare class Rouva {
|
|
|
490
553
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
491
554
|
private _getRequest;
|
|
492
555
|
private _post;
|
|
556
|
+
/**
|
|
557
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
558
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
559
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
560
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
561
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
562
|
+
* there). When the real request would be refused, resolves with
|
|
563
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
564
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
565
|
+
*
|
|
566
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
567
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
571
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
572
|
+
* else console.log(e.status, e.error)
|
|
573
|
+
*/
|
|
574
|
+
explain(params: ChatCompletionParams, options?: ExplainOptions): Promise<RouvaExplainResult>;
|
|
493
575
|
private _fetch;
|
|
494
576
|
private _createChatCompletion;
|
|
495
577
|
}
|
|
@@ -530,7 +612,7 @@ declare class RouvaError extends Error {
|
|
|
530
612
|
readonly requestId?: string;
|
|
531
613
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
532
614
|
readonly body: unknown;
|
|
533
|
-
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
615
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request' | 'Explain', status: number, headers: Headers, rawBody: string);
|
|
534
616
|
}
|
|
535
617
|
|
|
536
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, type ReasoningEffort, type RequestOptions, type ResponseFormat, type ResponseMessage, Rouva, RouvaError, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaRequest, type RouvaRequestStatus, type RouvaResponseMeta, type RouvaRouteReason, type RouvaStream, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
|
618
|
+
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type ExplainOptions, type FunctionToolCall, type Message, type ReasoningEffort, type RequestOptions, type ResponseFormat, type ResponseMessage, Rouva, RouvaError, type RouvaExplainResult, type RouvaExplainedRefusal, type RouvaExplanation, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaRequest, type RouvaRequestStatus, type RouvaResponseMeta, type RouvaRouteReason, type RouvaStream, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -50,9 +50,9 @@ interface ContentBlockMessage {
|
|
|
50
50
|
* valid when `tools` is set on the request.
|
|
51
51
|
*/
|
|
52
52
|
type ChatMessage = Message | AssistantToolCallMessage | ToolResultMessage | ContentBlockMessage;
|
|
53
|
-
type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral' | 'moonshot' | 'xai' | 'zai' | 'alibaba' | (string & {});
|
|
53
|
+
type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral' | 'moonshot' | 'xai' | 'zai' | 'alibaba' | 'minimax' | (string & {});
|
|
54
54
|
/**
|
|
55
|
-
* Supported models across all providers (the gateway's list as of SDK 0.
|
|
55
|
+
* Supported models across all providers (the gateway's list as of SDK 0.5.1).
|
|
56
56
|
* Omit `model` entirely to let Rouva route to the cheapest capable model automatically.
|
|
57
57
|
*
|
|
58
58
|
* Other IDs are accepted by the type for forward compatibility. On
|
|
@@ -64,7 +64,7 @@ type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral'
|
|
|
64
64
|
*
|
|
65
65
|
* `'gpt-5.6'` is a deprecated alias the gateway maps to `'gpt-5.6-sol'`.
|
|
66
66
|
*/
|
|
67
|
-
type RouvaModel = 'claude-fable-5-1' | 'claude-fable-5' | 'claude-opus-5-5' | 'claude-opus-5' | 'claude-opus-4-8' | 'claude-opus-4-7' | 'claude-opus-4-6' | 'claude-sonnet-5-5' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001' | 'gpt-6.1-sol' | 'gpt-6-sol' | 'gpt-6-luna' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5.6' | 'gpt-5' | 'gpt-5.4-mini' | 'gpt-5-mini' | 'gpt-5-nano' | 'gpt-4.1' | 'gpt-4.1-mini' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-3.8-flash' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-3.5-flash-lite' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'deepseek-flash' | 'deepseek-flash-think' | 'deepseek-v4-pro' | 'mistral-large-latest' | 'mistral-small-latest' | 'kimi-k3' | 'kimi-k2.6' | 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-5.3' | 'glm-5.2' | 'glm-4.7-flash' | 'qwen3.8-omni-flash' | 'qwen3.8-flash' | 'qwen3.7-plus' | 'qwen-max' | 'qwen-plus' | 'qwen-flash' | (string & {});
|
|
67
|
+
type RouvaModel = 'claude-fable-5-1' | 'claude-fable-5' | 'claude-opus-5-5' | 'claude-opus-5' | 'claude-opus-4-8' | 'claude-opus-4-7' | 'claude-opus-4-6' | 'claude-sonnet-5-5' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-5-5' | 'claude-haiku-4-5-20251001' | 'gpt-6-astra' | 'gpt-6.1-sol' | 'gpt-6.1-terra' | 'gpt-6-sol' | 'gpt-6-luna' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5.6' | 'gpt-5' | 'gpt-5.4-mini' | 'gpt-5-mini' | 'gpt-5-nano' | 'gpt-4.1' | 'gpt-4.1-mini' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-4-argon' | 'gemini-3.8-flash' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-3.5-flash-lite' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'deepseek-flash' | 'deepseek-flash-think' | 'deepseek-v4-pro' | 'mistral-large-latest' | 'mistral-small-latest' | 'kimi-k3.1-pro' | 'kimi-k3' | 'kimi-k2.6' | 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-5.3' | 'glm-5.2' | 'glm-4.7-flash' | 'qwen3.8-omni-flash' | 'qwen3.8-flash' | 'qwen3.7-plus' | 'qwen-max' | 'qwen-plus' | 'qwen-flash' | 'MiniMax-M3.1-Flash' | 'MiniMax-M3' | (string & {});
|
|
68
68
|
/** Anthropic-style text block — the array form of the `system` param */
|
|
69
69
|
interface SystemTextBlock {
|
|
70
70
|
type: 'text';
|
|
@@ -108,7 +108,8 @@ interface ChatCompletionParams {
|
|
|
108
108
|
* Sampling temperature 0–1.
|
|
109
109
|
* The gateway omits it for models that only accept their default: OpenAI
|
|
110
110
|
* reasoning models (gpt-5 / gpt-6 families) and the Claude models that reject
|
|
111
|
-
* it (Opus 4.7 and newer, Sonnet 5 and newer, Fable)
|
|
111
|
+
* it (Opus 4.7 and newer, Sonnet 5 and newer, Haiku 5.5, Fable), and for
|
|
112
|
+
* models whose sampling is fixed (Kimi K3 / K3.1 Pro, MiniMax).
|
|
112
113
|
*/
|
|
113
114
|
temperature?: number;
|
|
114
115
|
/**
|
|
@@ -116,7 +117,8 @@ interface ChatCompletionParams {
|
|
|
116
117
|
* default) is treated as omitted. OpenAI reasoning models (gpt-5 / gpt-6
|
|
117
118
|
* families, o-series) don't support it: routing avoids them when top_p is
|
|
118
119
|
* set, and pinning one returns a 400. The gateway omits it for the Claude
|
|
119
|
-
* models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Fable)
|
|
120
|
+
* models that reject it (Opus 4.7 and newer, Sonnet 5 and newer, Haiku 5.5, Fable),
|
|
121
|
+
* and for models whose sampling is fixed (Kimi K3 / K3.1 Pro, MiniMax).
|
|
120
122
|
*/
|
|
121
123
|
top_p?: number;
|
|
122
124
|
/**
|
|
@@ -147,13 +149,14 @@ interface ChatCompletionParams {
|
|
|
147
149
|
* How hard a reasoning model thinks (gateway support since 2026-10-21).
|
|
148
150
|
* OpenAI models take it as is; Claude gets it as `output_config.effort`.
|
|
149
151
|
* Values depend on the model: gpt-5 / -mini / -nano take minimal–high;
|
|
150
|
-
* gpt-5.4-mini, gpt-5.6-*, gpt-6-luna / -sol take none–xhigh;
|
|
151
|
-
* low–xhigh; Claude Sonnet / Opus 4.6 low, medium,
|
|
152
|
-
* models also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
152
|
+
* gpt-5.4-mini, gpt-5.6-*, gpt-6-luna / -sol, gpt-6.1-terra take none–xhigh;
|
|
153
|
+
* gpt-6.1-sol and gpt-6-astra low–xhigh; Claude Sonnet / Opus 4.6 low, medium,
|
|
154
|
+
* high, max; newer Claude models (incl. Haiku 5.5) also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
153
155
|
* only picks a model that takes the value; a listed OpenAI or Claude model you
|
|
154
|
-
* name that doesn't is a 400 (other providers' models get the value as sent). On gpt-6-luna / -sol, `'none'` turns reasoning off (temperature,
|
|
155
|
-
* top_p and tools then work) and any other value turns it on (
|
|
156
|
-
*
|
|
156
|
+
* name that doesn't is a 400 (other providers' models get the value as sent). On gpt-6-luna / -sol and gpt-6.1-terra, `'none'` turns reasoning off (temperature,
|
|
157
|
+
* top_p and tools then work) and any other value turns it on (top_p is then
|
|
158
|
+
* a 400; tools are served through OpenAI's Responses API). `rouva.run()`
|
|
159
|
+
* doesn't accept it.
|
|
157
160
|
* See https://rouva.io/docs#reasoning-effort
|
|
158
161
|
*/
|
|
159
162
|
reasoning_effort?: ReasoningEffort | null;
|
|
@@ -175,8 +178,10 @@ interface ChatCompletionParams {
|
|
|
175
178
|
* Anthropic `{ name, description, input_schema }`). Requires `model`.
|
|
176
179
|
* Tool schemas are provider-specific, so a tools request only ever moves to
|
|
177
180
|
* another model of the SAME provider (a routing override, or cheaper-model
|
|
178
|
-
* routing on /v1).
|
|
179
|
-
*
|
|
181
|
+
* routing on /v1). gpt-6.1-sol, gpt-6-astra and gpt-5.6-* take tools only through
|
|
182
|
+
* OpenAI's Responses API: the gateway uses it for you and converts both ways
|
|
183
|
+
* (you still send and receive Chat Completions), and there `seed`, `stop` and
|
|
184
|
+
* a `max_tokens` below 16 are a 400. `rouva.run()` still refuses these models.
|
|
180
185
|
*/
|
|
181
186
|
tools?: Array<Record<string, unknown>>;
|
|
182
187
|
/**
|
|
@@ -185,6 +190,64 @@ interface ChatCompletionParams {
|
|
|
185
190
|
*/
|
|
186
191
|
tool_choice?: string | Record<string, unknown>;
|
|
187
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* What `rouva.explain()` returns when the request would be sent: the model
|
|
195
|
+
* Rouva would pick, why, and the most it could cost — nothing was sent,
|
|
196
|
+
* recorded or reserved.
|
|
197
|
+
*/
|
|
198
|
+
interface RouvaExplanation {
|
|
199
|
+
dry_run: true;
|
|
200
|
+
would_send: true;
|
|
201
|
+
endpoint: '/api/gateway/messages' | '/v1/chat/completions';
|
|
202
|
+
provider: string;
|
|
203
|
+
model: string;
|
|
204
|
+
/** The model you named, or null. */
|
|
205
|
+
requested_model: string | null;
|
|
206
|
+
task_type: string | null;
|
|
207
|
+
route_reason: RouvaRouteReason;
|
|
208
|
+
/** How the prompt was classified (e.g. `jev`, `embedding`, `cache`). */
|
|
209
|
+
classified_by: string | null;
|
|
210
|
+
/** Jev's confidence (0–1), when Jev classified it. */
|
|
211
|
+
classifier_confidence: number | null;
|
|
212
|
+
classifier_latency_ms: number | null;
|
|
213
|
+
max_tokens: number;
|
|
214
|
+
stream: boolean;
|
|
215
|
+
/** The model's billed rates in USD per million tokens, or null when it has no known price. */
|
|
216
|
+
price_per_1m: {
|
|
217
|
+
input: number;
|
|
218
|
+
output: number;
|
|
219
|
+
} | null;
|
|
220
|
+
/**
|
|
221
|
+
* The most the request could cost in USD — what a spend-capped key reserves
|
|
222
|
+
* (the input as sent plus max_tokens of output, plus reasoning and
|
|
223
|
+
* summarization allowances where they apply). null: no known price.
|
|
224
|
+
*/
|
|
225
|
+
max_cost_usd: number | null;
|
|
226
|
+
/** A long conversation's older turns would be summarized first (Claude). */
|
|
227
|
+
summarize_history: boolean;
|
|
228
|
+
spend_capped: boolean;
|
|
229
|
+
}
|
|
230
|
+
/** What `rouva.explain()` returns when the real request would be refused. */
|
|
231
|
+
interface RouvaExplainedRefusal {
|
|
232
|
+
dry_run: true;
|
|
233
|
+
would_send: false;
|
|
234
|
+
/** The status the real request would get. */
|
|
235
|
+
status: number;
|
|
236
|
+
/** The error it would get. */
|
|
237
|
+
error: unknown;
|
|
238
|
+
/** `X-Rouva-Refusal`, when Rouva marks it (e.g. `unbounded`, `key_cap`). */
|
|
239
|
+
refusal: string | null;
|
|
240
|
+
/** The model Rouva had chosen before refusing, when it had chosen one. */
|
|
241
|
+
provider: string | null;
|
|
242
|
+
model: string | null;
|
|
243
|
+
route_reason: RouvaRouteReason | null;
|
|
244
|
+
}
|
|
245
|
+
type RouvaExplainResult = RouvaExplanation | RouvaExplainedRefusal;
|
|
246
|
+
/** Options for `rouva.explain()`. */
|
|
247
|
+
interface ExplainOptions extends RequestOptions {
|
|
248
|
+
/** `'v1'` explains the same body sent to /v1/chat/completions (which pins the named model). */
|
|
249
|
+
endpoint?: 'messages' | 'v1';
|
|
250
|
+
}
|
|
188
251
|
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
189
252
|
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
190
253
|
/** `response_format` values the gateway accepts. */
|
|
@@ -490,6 +553,25 @@ declare class Rouva {
|
|
|
490
553
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
491
554
|
private _getRequest;
|
|
492
555
|
private _post;
|
|
556
|
+
/**
|
|
557
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
558
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
559
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
560
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
561
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
562
|
+
* there). When the real request would be refused, resolves with
|
|
563
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
564
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
565
|
+
*
|
|
566
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
567
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
571
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
572
|
+
* else console.log(e.status, e.error)
|
|
573
|
+
*/
|
|
574
|
+
explain(params: ChatCompletionParams, options?: ExplainOptions): Promise<RouvaExplainResult>;
|
|
493
575
|
private _fetch;
|
|
494
576
|
private _createChatCompletion;
|
|
495
577
|
}
|
|
@@ -530,7 +612,7 @@ declare class RouvaError extends Error {
|
|
|
530
612
|
readonly requestId?: string;
|
|
531
613
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
532
614
|
readonly body: unknown;
|
|
533
|
-
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
615
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request' | 'Explain', status: number, headers: Headers, rawBody: string);
|
|
534
616
|
}
|
|
535
617
|
|
|
536
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, type ReasoningEffort, type RequestOptions, type ResponseFormat, type ResponseMessage, Rouva, RouvaError, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaRequest, type RouvaRequestStatus, type RouvaResponseMeta, type RouvaRouteReason, type RouvaStream, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
|
618
|
+
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type ExplainOptions, type FunctionToolCall, type Message, type ReasoningEffort, type RequestOptions, type ResponseFormat, type ResponseMessage, Rouva, RouvaError, type RouvaExplainResult, type RouvaExplainedRefusal, type RouvaExplanation, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaRequest, type RouvaRequestStatus, type RouvaResponseMeta, type RouvaRouteReason, type RouvaStream, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
package/dist/index.js
CHANGED
|
@@ -492,6 +492,31 @@ var Rouva = class {
|
|
|
492
492
|
_post(path, body, extraHeaders, options) {
|
|
493
493
|
return this._fetch("POST", path, body, extraHeaders, options);
|
|
494
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
497
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
498
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
499
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
500
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
501
|
+
* there). When the real request would be refused, resolves with
|
|
502
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
503
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
504
|
+
*
|
|
505
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
506
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
510
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
511
|
+
* else console.log(e.status, e.error)
|
|
512
|
+
*/
|
|
513
|
+
async explain(params, options) {
|
|
514
|
+
const { stream: _stream, ...body } = params;
|
|
515
|
+
const query = options?.endpoint === "v1" ? "?endpoint=v1" : "";
|
|
516
|
+
const res = await this._post(`/api/gateway/explain${query}`, body, {}, options);
|
|
517
|
+
if (!res.ok) throw await toRouvaError("Explain", res);
|
|
518
|
+
return res.json();
|
|
519
|
+
}
|
|
495
520
|
// One request path for every endpoint: auth, JSON body, optional abort signal.
|
|
496
521
|
_fetch(method, path, body, extraHeaders, options) {
|
|
497
522
|
return fetch(`${this.baseURL}${path}`, {
|
package/dist/index.mjs
CHANGED
|
@@ -465,6 +465,31 @@ var Rouva = class {
|
|
|
465
465
|
_post(path, body, extraHeaders, options) {
|
|
466
466
|
return this._fetch("POST", path, body, extraHeaders, options);
|
|
467
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
470
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
471
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
472
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
473
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
474
|
+
* there). When the real request would be refused, resolves with
|
|
475
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
476
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
477
|
+
*
|
|
478
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
479
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
483
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
484
|
+
* else console.log(e.status, e.error)
|
|
485
|
+
*/
|
|
486
|
+
async explain(params, options) {
|
|
487
|
+
const { stream: _stream, ...body } = params;
|
|
488
|
+
const query = options?.endpoint === "v1" ? "?endpoint=v1" : "";
|
|
489
|
+
const res = await this._post(`/api/gateway/explain${query}`, body, {}, options);
|
|
490
|
+
if (!res.ok) throw await toRouvaError("Explain", res);
|
|
491
|
+
return res.json();
|
|
492
|
+
}
|
|
468
493
|
// One request path for every endpoint: auth, JSON body, optional abort signal.
|
|
469
494
|
_fetch(method, path, body, extraHeaders, options) {
|
|
470
495
|
return fetch(`${this.baseURL}${path}`, {
|
package/package.json
CHANGED