@rouvanpm/rouva 0.4.0 → 0.5.0
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 -2
- package/dist/index.d.mts +106 -4
- package/dist/index.d.ts +106 -4
- package/dist/index.js +25 -0
- package/dist/index.mjs +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,8 +105,10 @@ const res = await rouva.chat.completions.create({
|
|
|
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).
|
|
109
|
+
- **`parallel_tool_calls`** — with `tools`: `false` allows at most one tool call per turn (Claude included).
|
|
108
110
|
|
|
109
|
-
Unsupported OpenAI options (`logit_bias`, `logprobs`, `
|
|
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.
|
|
110
112
|
|
|
111
113
|
## Unlisted models
|
|
112
114
|
|
|
@@ -128,7 +130,7 @@ Non-tools requests on `/v1` honor the named model exactly unless the workspace o
|
|
|
128
130
|
|
|
129
131
|
## Tool use
|
|
130
132
|
|
|
131
|
-
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` 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.
|
|
132
134
|
|
|
133
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.
|
|
134
136
|
|
|
@@ -354,6 +356,25 @@ if (id) {
|
|
|
354
356
|
|
|
355
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).
|
|
356
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
|
+
|
|
357
378
|
## Structured output
|
|
358
379
|
|
|
359
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
|
@@ -143,6 +143,27 @@ interface ChatCompletionParams {
|
|
|
143
143
|
* `rouva.run()` doesn't accept it. See https://rouva.io/docs#structured-output
|
|
144
144
|
*/
|
|
145
145
|
response_format?: ResponseFormat | null;
|
|
146
|
+
/**
|
|
147
|
+
* How hard a reasoning model thinks (gateway support since 2026-10-21).
|
|
148
|
+
* OpenAI models take it as is; Claude gets it as `output_config.effort`.
|
|
149
|
+
* 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; gpt-6.1-sol
|
|
151
|
+
* low–xhigh; Claude Sonnet / Opus 4.6 low, medium, high, max; newer Claude
|
|
152
|
+
* models also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
153
|
+
* 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 (top_p is then
|
|
156
|
+
* a 400; tools are served through OpenAI's Responses API). `rouva.run()`
|
|
157
|
+
* doesn't accept it.
|
|
158
|
+
* See https://rouva.io/docs#reasoning-effort
|
|
159
|
+
*/
|
|
160
|
+
reasoning_effort?: ReasoningEffort | null;
|
|
161
|
+
/**
|
|
162
|
+
* With `tools` only: `false` limits the model to at most one tool call per
|
|
163
|
+
* turn. Forwarded to OpenAI-format providers; for Claude the gateway sets
|
|
164
|
+
* `disable_parallel_tool_use` on `tool_choice`. `rouva.run()` doesn't accept it.
|
|
165
|
+
*/
|
|
166
|
+
parallel_tool_calls?: boolean | null;
|
|
146
167
|
/**
|
|
147
168
|
* Stream the response. Client-side only — controls whether `create()`
|
|
148
169
|
* returns a ReadableStream or a buffered ChatCompletion; never sent to
|
|
@@ -155,8 +176,10 @@ interface ChatCompletionParams {
|
|
|
155
176
|
* Anthropic `{ name, description, input_schema }`). Requires `model`.
|
|
156
177
|
* Tool schemas are provider-specific, so a tools request only ever moves to
|
|
157
178
|
* another model of the SAME provider (a routing override, or cheaper-model
|
|
158
|
-
* routing on /v1).
|
|
159
|
-
*
|
|
179
|
+
* routing on /v1). gpt-6.1-sol and gpt-5.6-* take tools only through
|
|
180
|
+
* OpenAI's Responses API: the gateway uses it for you and converts both ways
|
|
181
|
+
* (you still send and receive Chat Completions), and there `seed`, `stop` and
|
|
182
|
+
* a `max_tokens` below 16 are a 400. `rouva.run()` still refuses these models.
|
|
160
183
|
*/
|
|
161
184
|
tools?: Array<Record<string, unknown>>;
|
|
162
185
|
/**
|
|
@@ -165,6 +188,66 @@ interface ChatCompletionParams {
|
|
|
165
188
|
*/
|
|
166
189
|
tool_choice?: string | Record<string, unknown>;
|
|
167
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* What `rouva.explain()` returns when the request would be sent: the model
|
|
193
|
+
* Rouva would pick, why, and the most it could cost — nothing was sent,
|
|
194
|
+
* recorded or reserved.
|
|
195
|
+
*/
|
|
196
|
+
interface RouvaExplanation {
|
|
197
|
+
dry_run: true;
|
|
198
|
+
would_send: true;
|
|
199
|
+
endpoint: '/api/gateway/messages' | '/v1/chat/completions';
|
|
200
|
+
provider: string;
|
|
201
|
+
model: string;
|
|
202
|
+
/** The model you named, or null. */
|
|
203
|
+
requested_model: string | null;
|
|
204
|
+
task_type: string | null;
|
|
205
|
+
route_reason: RouvaRouteReason;
|
|
206
|
+
/** How the prompt was classified (e.g. `jev`, `embedding`, `cache`). */
|
|
207
|
+
classified_by: string | null;
|
|
208
|
+
/** Jev's confidence (0–1), when Jev classified it. */
|
|
209
|
+
classifier_confidence: number | null;
|
|
210
|
+
classifier_latency_ms: number | null;
|
|
211
|
+
max_tokens: number;
|
|
212
|
+
stream: boolean;
|
|
213
|
+
/** The model's billed rates in USD per million tokens, or null when it has no known price. */
|
|
214
|
+
price_per_1m: {
|
|
215
|
+
input: number;
|
|
216
|
+
output: number;
|
|
217
|
+
} | null;
|
|
218
|
+
/**
|
|
219
|
+
* The most the request could cost in USD — what a spend-capped key reserves
|
|
220
|
+
* (the input as sent plus max_tokens of output, plus reasoning and
|
|
221
|
+
* summarization allowances where they apply). null: no known price.
|
|
222
|
+
*/
|
|
223
|
+
max_cost_usd: number | null;
|
|
224
|
+
/** A long conversation's older turns would be summarized first (Claude). */
|
|
225
|
+
summarize_history: boolean;
|
|
226
|
+
spend_capped: boolean;
|
|
227
|
+
}
|
|
228
|
+
/** What `rouva.explain()` returns when the real request would be refused. */
|
|
229
|
+
interface RouvaExplainedRefusal {
|
|
230
|
+
dry_run: true;
|
|
231
|
+
would_send: false;
|
|
232
|
+
/** The status the real request would get. */
|
|
233
|
+
status: number;
|
|
234
|
+
/** The error it would get. */
|
|
235
|
+
error: unknown;
|
|
236
|
+
/** `X-Rouva-Refusal`, when Rouva marks it (e.g. `unbounded`, `key_cap`). */
|
|
237
|
+
refusal: string | null;
|
|
238
|
+
/** The model Rouva had chosen before refusing, when it had chosen one. */
|
|
239
|
+
provider: string | null;
|
|
240
|
+
model: string | null;
|
|
241
|
+
route_reason: RouvaRouteReason | null;
|
|
242
|
+
}
|
|
243
|
+
type RouvaExplainResult = RouvaExplanation | RouvaExplainedRefusal;
|
|
244
|
+
/** Options for `rouva.explain()`. */
|
|
245
|
+
interface ExplainOptions extends RequestOptions {
|
|
246
|
+
/** `'v1'` explains the same body sent to /v1/chat/completions (which pins the named model). */
|
|
247
|
+
endpoint?: 'messages' | 'v1';
|
|
248
|
+
}
|
|
249
|
+
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
250
|
+
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
168
251
|
/** `response_format` values the gateway accepts. */
|
|
169
252
|
type ResponseFormat = {
|
|
170
253
|
type: 'text';
|
|
@@ -468,6 +551,25 @@ declare class Rouva {
|
|
|
468
551
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
469
552
|
private _getRequest;
|
|
470
553
|
private _post;
|
|
554
|
+
/**
|
|
555
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
556
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
557
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
558
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
559
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
560
|
+
* there). When the real request would be refused, resolves with
|
|
561
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
562
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
563
|
+
*
|
|
564
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
565
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
569
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
570
|
+
* else console.log(e.status, e.error)
|
|
571
|
+
*/
|
|
572
|
+
explain(params: ChatCompletionParams, options?: ExplainOptions): Promise<RouvaExplainResult>;
|
|
471
573
|
private _fetch;
|
|
472
574
|
private _createChatCompletion;
|
|
473
575
|
}
|
|
@@ -508,7 +610,7 @@ declare class RouvaError extends Error {
|
|
|
508
610
|
readonly requestId?: string;
|
|
509
611
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
510
612
|
readonly body: unknown;
|
|
511
|
-
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
613
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request' | 'Explain', status: number, headers: Headers, rawBody: string);
|
|
512
614
|
}
|
|
513
615
|
|
|
514
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, 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 };
|
|
616
|
+
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
|
@@ -143,6 +143,27 @@ interface ChatCompletionParams {
|
|
|
143
143
|
* `rouva.run()` doesn't accept it. See https://rouva.io/docs#structured-output
|
|
144
144
|
*/
|
|
145
145
|
response_format?: ResponseFormat | null;
|
|
146
|
+
/**
|
|
147
|
+
* How hard a reasoning model thinks (gateway support since 2026-10-21).
|
|
148
|
+
* OpenAI models take it as is; Claude gets it as `output_config.effort`.
|
|
149
|
+
* 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; gpt-6.1-sol
|
|
151
|
+
* low–xhigh; Claude Sonnet / Opus 4.6 low, medium, high, max; newer Claude
|
|
152
|
+
* models also xhigh; GPT-4 models and Claude Haiku 4.5 don't take it. Routing
|
|
153
|
+
* 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 (top_p is then
|
|
156
|
+
* a 400; tools are served through OpenAI's Responses API). `rouva.run()`
|
|
157
|
+
* doesn't accept it.
|
|
158
|
+
* See https://rouva.io/docs#reasoning-effort
|
|
159
|
+
*/
|
|
160
|
+
reasoning_effort?: ReasoningEffort | null;
|
|
161
|
+
/**
|
|
162
|
+
* With `tools` only: `false` limits the model to at most one tool call per
|
|
163
|
+
* turn. Forwarded to OpenAI-format providers; for Claude the gateway sets
|
|
164
|
+
* `disable_parallel_tool_use` on `tool_choice`. `rouva.run()` doesn't accept it.
|
|
165
|
+
*/
|
|
166
|
+
parallel_tool_calls?: boolean | null;
|
|
146
167
|
/**
|
|
147
168
|
* Stream the response. Client-side only — controls whether `create()`
|
|
148
169
|
* returns a ReadableStream or a buffered ChatCompletion; never sent to
|
|
@@ -155,8 +176,10 @@ interface ChatCompletionParams {
|
|
|
155
176
|
* Anthropic `{ name, description, input_schema }`). Requires `model`.
|
|
156
177
|
* Tool schemas are provider-specific, so a tools request only ever moves to
|
|
157
178
|
* another model of the SAME provider (a routing override, or cheaper-model
|
|
158
|
-
* routing on /v1).
|
|
159
|
-
*
|
|
179
|
+
* routing on /v1). gpt-6.1-sol and gpt-5.6-* take tools only through
|
|
180
|
+
* OpenAI's Responses API: the gateway uses it for you and converts both ways
|
|
181
|
+
* (you still send and receive Chat Completions), and there `seed`, `stop` and
|
|
182
|
+
* a `max_tokens` below 16 are a 400. `rouva.run()` still refuses these models.
|
|
160
183
|
*/
|
|
161
184
|
tools?: Array<Record<string, unknown>>;
|
|
162
185
|
/**
|
|
@@ -165,6 +188,66 @@ interface ChatCompletionParams {
|
|
|
165
188
|
*/
|
|
166
189
|
tool_choice?: string | Record<string, unknown>;
|
|
167
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* What `rouva.explain()` returns when the request would be sent: the model
|
|
193
|
+
* Rouva would pick, why, and the most it could cost — nothing was sent,
|
|
194
|
+
* recorded or reserved.
|
|
195
|
+
*/
|
|
196
|
+
interface RouvaExplanation {
|
|
197
|
+
dry_run: true;
|
|
198
|
+
would_send: true;
|
|
199
|
+
endpoint: '/api/gateway/messages' | '/v1/chat/completions';
|
|
200
|
+
provider: string;
|
|
201
|
+
model: string;
|
|
202
|
+
/** The model you named, or null. */
|
|
203
|
+
requested_model: string | null;
|
|
204
|
+
task_type: string | null;
|
|
205
|
+
route_reason: RouvaRouteReason;
|
|
206
|
+
/** How the prompt was classified (e.g. `jev`, `embedding`, `cache`). */
|
|
207
|
+
classified_by: string | null;
|
|
208
|
+
/** Jev's confidence (0–1), when Jev classified it. */
|
|
209
|
+
classifier_confidence: number | null;
|
|
210
|
+
classifier_latency_ms: number | null;
|
|
211
|
+
max_tokens: number;
|
|
212
|
+
stream: boolean;
|
|
213
|
+
/** The model's billed rates in USD per million tokens, or null when it has no known price. */
|
|
214
|
+
price_per_1m: {
|
|
215
|
+
input: number;
|
|
216
|
+
output: number;
|
|
217
|
+
} | null;
|
|
218
|
+
/**
|
|
219
|
+
* The most the request could cost in USD — what a spend-capped key reserves
|
|
220
|
+
* (the input as sent plus max_tokens of output, plus reasoning and
|
|
221
|
+
* summarization allowances where they apply). null: no known price.
|
|
222
|
+
*/
|
|
223
|
+
max_cost_usd: number | null;
|
|
224
|
+
/** A long conversation's older turns would be summarized first (Claude). */
|
|
225
|
+
summarize_history: boolean;
|
|
226
|
+
spend_capped: boolean;
|
|
227
|
+
}
|
|
228
|
+
/** What `rouva.explain()` returns when the real request would be refused. */
|
|
229
|
+
interface RouvaExplainedRefusal {
|
|
230
|
+
dry_run: true;
|
|
231
|
+
would_send: false;
|
|
232
|
+
/** The status the real request would get. */
|
|
233
|
+
status: number;
|
|
234
|
+
/** The error it would get. */
|
|
235
|
+
error: unknown;
|
|
236
|
+
/** `X-Rouva-Refusal`, when Rouva marks it (e.g. `unbounded`, `key_cap`). */
|
|
237
|
+
refusal: string | null;
|
|
238
|
+
/** The model Rouva had chosen before refusing, when it had chosen one. */
|
|
239
|
+
provider: string | null;
|
|
240
|
+
model: string | null;
|
|
241
|
+
route_reason: RouvaRouteReason | null;
|
|
242
|
+
}
|
|
243
|
+
type RouvaExplainResult = RouvaExplanation | RouvaExplainedRefusal;
|
|
244
|
+
/** Options for `rouva.explain()`. */
|
|
245
|
+
interface ExplainOptions extends RequestOptions {
|
|
246
|
+
/** `'v1'` explains the same body sent to /v1/chat/completions (which pins the named model). */
|
|
247
|
+
endpoint?: 'messages' | 'v1';
|
|
248
|
+
}
|
|
249
|
+
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
250
|
+
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
168
251
|
/** `response_format` values the gateway accepts. */
|
|
169
252
|
type ResponseFormat = {
|
|
170
253
|
type: 'text';
|
|
@@ -468,6 +551,25 @@ declare class Rouva {
|
|
|
468
551
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
469
552
|
private _getRequest;
|
|
470
553
|
private _post;
|
|
554
|
+
/**
|
|
555
|
+
* Dry run (`POST /api/gateway/explain`): what Rouva would do with this
|
|
556
|
+
* request — the model and provider it would pick, why (`route_reason`), how
|
|
557
|
+
* the prompt was classified, and the most it could cost (`max_cost_usd`) —
|
|
558
|
+
* without calling a provider, recording a request or reserving spend. Takes
|
|
559
|
+
* the same params as `chat.completions.create()` (`stream` is ignored, as
|
|
560
|
+
* there). When the real request would be refused, resolves with
|
|
561
|
+
* `would_send: false` and the status and error it would get. Throws
|
|
562
|
+
* `RouvaError` for a bad key or the explain rate limit (429, `refusal: 'rate'`).
|
|
563
|
+
*
|
|
564
|
+
* The prompt is classified as for a real request. Not covered: the
|
|
565
|
+
* duplicate-request cache, a spend cap's remaining budget, provider errors.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* const e = await rouva.explain({ model: 'gpt-6-sol', messages })
|
|
569
|
+
* if (e.would_send) console.log(e.model, e.route_reason, e.max_cost_usd)
|
|
570
|
+
* else console.log(e.status, e.error)
|
|
571
|
+
*/
|
|
572
|
+
explain(params: ChatCompletionParams, options?: ExplainOptions): Promise<RouvaExplainResult>;
|
|
471
573
|
private _fetch;
|
|
472
574
|
private _createChatCompletion;
|
|
473
575
|
}
|
|
@@ -508,7 +610,7 @@ declare class RouvaError extends Error {
|
|
|
508
610
|
readonly requestId?: string;
|
|
509
611
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
510
612
|
readonly body: unknown;
|
|
511
|
-
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
613
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request' | 'Explain', status: number, headers: Headers, rawBody: string);
|
|
512
614
|
}
|
|
513
615
|
|
|
514
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, 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 };
|
|
616
|
+
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