@rouvanpm/rouva 0.3.2 → 0.4.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 +57 -6
- package/dist/index.d.mts +156 -6
- package/dist/index.d.ts +156 -6
- package/dist/index.js +46 -16
- package/dist/index.mjs +46 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,13 +80,13 @@ while (true) {
|
|
|
80
80
|
|
|
81
81
|
Streaming responses are normalized to OpenAI-style SSE chunks, even when Rouva routes the request to Anthropic.
|
|
82
82
|
|
|
83
|
-
`stream` is a client-side toggle: `stream: true` returns the raw `ReadableStream
|
|
83
|
+
`stream` is a client-side toggle: `stream: true` returns the raw `ReadableStream` (with the routing metadata on `_rouva` — see [Response metadata](#response-metadata)), omitting it (or `stream: false`) returns a buffered `ChatCompletion`. It is never sent to the gateway.
|
|
84
84
|
|
|
85
85
|
## Request parameters
|
|
86
86
|
|
|
87
87
|
```typescript
|
|
88
88
|
const res = await rouva.chat.completions.create({
|
|
89
|
-
model: 'gpt-
|
|
89
|
+
model: 'gpt-6-luna', // required when using seed (OpenAI only)
|
|
90
90
|
messages: [{ role: 'user', content: 'Write a haiku about routing.' }],
|
|
91
91
|
system: 'You are a concise assistant.', // string or Anthropic-style text blocks
|
|
92
92
|
max_tokens: 1024, // default 4096 (or max_completion_tokens)
|
|
@@ -104,8 +104,11 @@ const res = await rouva.chat.completions.create({
|
|
|
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
|
+
- **`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).
|
|
107
110
|
|
|
108
|
-
Unsupported OpenAI options (`
|
|
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.
|
|
109
112
|
|
|
110
113
|
## Unlisted models
|
|
111
114
|
|
|
@@ -313,20 +316,67 @@ Sessions are optional — requests without an active session are tracked individ
|
|
|
313
316
|
|
|
314
317
|
## Response metadata
|
|
315
318
|
|
|
316
|
-
|
|
319
|
+
Every response says what Rouva decided, in `_rouva` — on a buffered completion, and on the stream `stream: true` returns (the headers arrive before the first chunk):
|
|
317
320
|
|
|
318
321
|
```typescript
|
|
319
322
|
const res = await rouva.chat.completions.create({ messages })
|
|
320
323
|
|
|
321
324
|
console.log(res._rouva)
|
|
322
325
|
// {
|
|
323
|
-
//
|
|
326
|
+
// request_id: '7c0e…', // your request log's id for it
|
|
327
|
+
// model_used: 'gpt-6-luna',
|
|
324
328
|
// provider_used: 'openai',
|
|
329
|
+
// requested_model: 'gpt-6-sol', // only when Rouva served a different model
|
|
325
330
|
// task_type: 'summarize',
|
|
326
|
-
//
|
|
331
|
+
// route_reason: 'intelligent', // override | allowlist | fallback | no_key_fallback | direct | escalation | dedup
|
|
332
|
+
// dedup: true // only when served from the duplicate-request cache
|
|
327
333
|
// }
|
|
328
334
|
```
|
|
329
335
|
|
|
336
|
+
### Cost, savings and quality score — `rouva.requests.get(id)`
|
|
337
|
+
|
|
338
|
+
A request's cost is known only once its last chunk has arrived, and its quality score once the judge has run, so read them back by `request_id`:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import type { RouvaStream } from '@rouvanpm/rouva'
|
|
342
|
+
|
|
343
|
+
const stream = await rouva.chat.completions.create({ messages, stream: true }) as RouvaStream
|
|
344
|
+
// ...consume the stream...
|
|
345
|
+
|
|
346
|
+
const id = stream._rouva?.request_id // absent on a duplicate-cache replay (cost 0)
|
|
347
|
+
if (id) {
|
|
348
|
+
const req = await rouva.requests.get(id)
|
|
349
|
+
console.log(req.status) // 'pending' | 'incomplete' | 'settled' | 'scored'
|
|
350
|
+
console.log(req.cost_usd) // e.g. 0.000412
|
|
351
|
+
console.log(req.saved_usd) // vs the model you named (negative when the route cost more)
|
|
352
|
+
console.log(req.quality_score) // 0–1, once scored
|
|
353
|
+
console.log(req.dashboard_url) // opens it in your request log
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
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
|
+
|
|
359
|
+
## Structured output
|
|
360
|
+
|
|
361
|
+
`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.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import type { ChatCompletion } from '@rouvanpm/rouva'
|
|
365
|
+
|
|
366
|
+
const res = await rouva.chat.completions.create({
|
|
367
|
+
messages: [{ role: 'user', content: 'Extract the city: "Flying to Paris on Monday."' }],
|
|
368
|
+
response_format: {
|
|
369
|
+
type: 'json_schema',
|
|
370
|
+
json_schema: {
|
|
371
|
+
name: 'city',
|
|
372
|
+
strict: true,
|
|
373
|
+
schema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'], additionalProperties: false },
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
}) as ChatCompletion
|
|
377
|
+
const { city } = JSON.parse(res.choices[0].message.content!)
|
|
378
|
+
```
|
|
379
|
+
|
|
330
380
|
## Errors
|
|
331
381
|
|
|
332
382
|
Any non-2xx response throws a `RouvaError` (a subclass of `Error`, so existing `catch` blocks keep working):
|
|
@@ -341,6 +391,7 @@ try {
|
|
|
341
391
|
console.log(e.refusal) // 'key_cap' | 'member_cap' | 'rate' | … — Rouva refused before calling the provider
|
|
342
392
|
console.log(e.retryAfter) // seconds, from Retry-After
|
|
343
393
|
console.log(e.error) // the gateway's message
|
|
394
|
+
console.log(e.requestId) // when the request was recorded — rouva.requests.get(e.requestId)
|
|
344
395
|
}
|
|
345
396
|
}
|
|
346
397
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -132,6 +132,37 @@ interface ChatCompletionParams {
|
|
|
132
132
|
* no cheaper-model substitution, no cross-provider fallbacks.
|
|
133
133
|
*/
|
|
134
134
|
seed?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Ask for JSON output (gateway support since 2026-10-20). `json_object` is
|
|
137
|
+
* JSON mode: the prompt (a message or `system`) must contain the word
|
|
138
|
+
* "json", or the gateway returns a 400. `json_schema` is Structured Outputs:
|
|
139
|
+
* the answer follows your schema. Routing only picks a model that serves the
|
|
140
|
+
* format; Claude models serve `json_schema` only (every object needs
|
|
141
|
+
* `additionalProperties: false`), and not when the conversation ends with an
|
|
142
|
+
* assistant message. `{ type: 'text' }` or `null` is the default (no-op).
|
|
143
|
+
* `rouva.run()` doesn't accept it. See https://rouva.io/docs#structured-output
|
|
144
|
+
*/
|
|
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 (tools and
|
|
156
|
+
* top_p are then a 400). `rouva.run()` doesn't accept it.
|
|
157
|
+
* See https://rouva.io/docs#reasoning-effort
|
|
158
|
+
*/
|
|
159
|
+
reasoning_effort?: ReasoningEffort | null;
|
|
160
|
+
/**
|
|
161
|
+
* With `tools` only: `false` limits the model to at most one tool call per
|
|
162
|
+
* turn. Forwarded to OpenAI-format providers; for Claude the gateway sets
|
|
163
|
+
* `disable_parallel_tool_use` on `tool_choice`. `rouva.run()` doesn't accept it.
|
|
164
|
+
*/
|
|
165
|
+
parallel_tool_calls?: boolean | null;
|
|
135
166
|
/**
|
|
136
167
|
* Stream the response. Client-side only — controls whether `create()`
|
|
137
168
|
* returns a ReadableStream or a buffered ChatCompletion; never sent to
|
|
@@ -154,6 +185,24 @@ interface ChatCompletionParams {
|
|
|
154
185
|
*/
|
|
155
186
|
tool_choice?: string | Record<string, unknown>;
|
|
156
187
|
}
|
|
188
|
+
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
189
|
+
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
190
|
+
/** `response_format` values the gateway accepts. */
|
|
191
|
+
type ResponseFormat = {
|
|
192
|
+
type: 'text';
|
|
193
|
+
} | {
|
|
194
|
+
type: 'json_object';
|
|
195
|
+
} | {
|
|
196
|
+
type: 'json_schema';
|
|
197
|
+
json_schema: {
|
|
198
|
+
/** 1–64 letters, digits, underscores or dashes. */
|
|
199
|
+
name: string;
|
|
200
|
+
/** A JSON Schema object. */
|
|
201
|
+
schema: Record<string, unknown>;
|
|
202
|
+
strict?: boolean;
|
|
203
|
+
description?: string;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
157
206
|
/** Assistant message in a completed response; `tool_calls` present when the model requested tools */
|
|
158
207
|
interface ResponseMessage {
|
|
159
208
|
role: 'assistant';
|
|
@@ -180,13 +229,42 @@ interface ChatCompletion {
|
|
|
180
229
|
/** Rouva metadata — cost, savings, routing decision */
|
|
181
230
|
_rouva?: RouvaResponseMeta;
|
|
182
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Why Rouva sent a request to the model it did (header `X-Rouva-Route-Reason`):
|
|
234
|
+
* `intelligent` (Intelligent Routing chose it), `override` (an account routing
|
|
235
|
+
* override), `allowlist` (a workspace member's allowed-model list),
|
|
236
|
+
* `fallback` (a retry after a provider error), `escalation` (`run()` stepped
|
|
237
|
+
* up a model), `no_key_fallback` (the named provider has no key, so a
|
|
238
|
+
* connected one stood in), `direct` (the model you named, as named), or
|
|
239
|
+
* `dedup` (a replay of an identical request — no new call was made).
|
|
240
|
+
*/
|
|
241
|
+
type RouvaRouteReason = 'intelligent' | 'override' | 'allowlist' | 'fallback' | 'escalation' | 'no_key_fallback' | 'direct' | 'dedup' | (string & {});
|
|
183
242
|
interface RouvaResponseMeta {
|
|
184
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* The request's id in your Rouva request log (`X-Rouva-Request-Id`). Pass it
|
|
245
|
+
* to `rouva.requests.get(id)` for the request's cost, savings and quality
|
|
246
|
+
* score. Absent on a duplicate-cache replay (no new request was recorded).
|
|
247
|
+
*/
|
|
248
|
+
request_id?: string;
|
|
249
|
+
/** The model that served the request — after a fallback, the fallback model. */
|
|
185
250
|
model_used?: string;
|
|
186
|
-
/**
|
|
251
|
+
/** That model's provider, e.g. `openai`, `anthropic`. */
|
|
187
252
|
provider_used?: string;
|
|
188
|
-
/**
|
|
253
|
+
/** The model you named, when Rouva served a different one. */
|
|
254
|
+
requested_model?: string;
|
|
255
|
+
/** Task type classified by Rouva, e.g. `code`; absent when not classified. */
|
|
189
256
|
task_type?: string;
|
|
257
|
+
/** Why this model was chosen. */
|
|
258
|
+
route_reason?: RouvaRouteReason;
|
|
259
|
+
/**
|
|
260
|
+
* What the request cost in USD, when the gateway sent it in a header: only
|
|
261
|
+
* on non-streaming responses once the cost is final, and as 0 on a
|
|
262
|
+
* duplicate-cache replay (`dedup: true`). The SDK streams, so otherwise read
|
|
263
|
+
* cost with `rouva.requests.get(request_id)`.
|
|
264
|
+
*/
|
|
265
|
+
cost_usd?: number;
|
|
266
|
+
/** Saved against the model you named (or the priciest candidate), in USD; negative when the route cost more. Same availability as `cost_usd`. */
|
|
267
|
+
saved_usd?: number;
|
|
190
268
|
/**
|
|
191
269
|
* True when the gateway answered from its duplicate-request cache (an
|
|
192
270
|
* identical request within the last 60 seconds) instead of calling the
|
|
@@ -199,6 +277,48 @@ interface RouvaResponseMeta {
|
|
|
199
277
|
*/
|
|
200
278
|
cache?: string;
|
|
201
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The stream `chat.completions.create({ stream: true })` returns: OpenAI-shaped
|
|
282
|
+
* SSE bytes, plus the routing metadata from the response headers (known
|
|
283
|
+
* before the first chunk).
|
|
284
|
+
*/
|
|
285
|
+
type RouvaStream = ReadableStream<Uint8Array> & {
|
|
286
|
+
_rouva?: RouvaResponseMeta;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* A request's settlement state: `pending` (not settled yet — in flight, or it
|
|
290
|
+
* never reached the provider), `incomplete` (settled from partial usage, e.g. a
|
|
291
|
+
* stream cut short — the provider may have billed more), `settled` (tokens and
|
|
292
|
+
* cost are final), `scored` (final, and a quality score is recorded).
|
|
293
|
+
*/
|
|
294
|
+
type RouvaRequestStatus = 'pending' | 'incomplete' | 'settled' | 'scored' | (string & {});
|
|
295
|
+
/** One request from your request log — `rouva.requests.get(id)`. */
|
|
296
|
+
interface RouvaRequest {
|
|
297
|
+
id: string;
|
|
298
|
+
/** ISO timestamp. */
|
|
299
|
+
created_at: string;
|
|
300
|
+
status: RouvaRequestStatus;
|
|
301
|
+
provider: string;
|
|
302
|
+
model: string;
|
|
303
|
+
requested_model: string | null;
|
|
304
|
+
task_type: string | null;
|
|
305
|
+
route_reason: RouvaRouteReason | null;
|
|
306
|
+
/** 0 while pending. */
|
|
307
|
+
input_tokens: number;
|
|
308
|
+
output_tokens: number;
|
|
309
|
+
/** USD; 0 while pending. Final once `status` is `settled` or `scored`. */
|
|
310
|
+
cost_usd: number;
|
|
311
|
+
/** USD saved against the model you named (or the priciest candidate); negative when the route cost more. */
|
|
312
|
+
saved_usd: number;
|
|
313
|
+
/** 0–1, or null until a score is recorded (or when none is). */
|
|
314
|
+
quality_score: number | null;
|
|
315
|
+
/** Whether the judge scored it, or why not (then `quality_score` is the heuristic checks'). */
|
|
316
|
+
judge_status: string | null;
|
|
317
|
+
finish_reason: string | null;
|
|
318
|
+
session_id: string | null;
|
|
319
|
+
/** Opens this request in the dashboard's request log. */
|
|
320
|
+
dashboard_url: string;
|
|
321
|
+
}
|
|
202
322
|
interface RunParams {
|
|
203
323
|
/**
|
|
204
324
|
* Conversation history to seed the agent loop with.
|
|
@@ -301,9 +421,30 @@ declare class Rouva {
|
|
|
301
421
|
private _sessionId;
|
|
302
422
|
readonly chat: {
|
|
303
423
|
completions: {
|
|
304
|
-
create(params: ChatCompletionParams, options?: RequestOptions): Promise<ChatCompletion |
|
|
424
|
+
create(params: ChatCompletionParams, options?: RequestOptions): Promise<ChatCompletion | RouvaStream>;
|
|
305
425
|
};
|
|
306
426
|
};
|
|
427
|
+
readonly requests: {
|
|
428
|
+
/**
|
|
429
|
+
* Read one request from your request log (`GET /api/gateway/requests/{id}`)
|
|
430
|
+
* by the id a response carried (`_rouva.request_id`, header
|
|
431
|
+
* `X-Rouva-Request-Id`): its model, routing reason, tokens, cost, savings and
|
|
432
|
+
* quality score. A key reads only its own requests (another's id is a 404).
|
|
433
|
+
*
|
|
434
|
+
* A stream's cost is recorded just after its last chunk, and the quality
|
|
435
|
+
* score once the judge has run (a few seconds), so check `status`:
|
|
436
|
+
* `pending` → not settled yet, retry shortly; `incomplete` → settled from
|
|
437
|
+
* partial usage; `settled` → cost final; `scored` → cost final and scored.
|
|
438
|
+
* Throws `RouvaError` on a non-2xx response (a 503 carries `retryAfter`).
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* const stream = await rouva.chat.completions.create({ messages, stream: true }) as RouvaStream
|
|
442
|
+
* // ...consume the stream...
|
|
443
|
+
* const req = await rouva.requests.get(stream._rouva!.request_id!)
|
|
444
|
+
* console.log(req.status, req.cost_usd, req.saved_usd)
|
|
445
|
+
*/
|
|
446
|
+
get(id: string, options?: RequestOptions): Promise<RouvaRequest>;
|
|
447
|
+
};
|
|
307
448
|
constructor(options: RouvaOptions);
|
|
308
449
|
/**
|
|
309
450
|
* Start a new agent session. Every subsequent `chat.completions.create()`
|
|
@@ -347,7 +488,9 @@ declare class Rouva {
|
|
|
347
488
|
* console.log(result.session_id) // dashboard session ID
|
|
348
489
|
*/
|
|
349
490
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
491
|
+
private _getRequest;
|
|
350
492
|
private _post;
|
|
493
|
+
private _fetch;
|
|
351
494
|
private _createChatCompletion;
|
|
352
495
|
}
|
|
353
496
|
|
|
@@ -378,9 +521,16 @@ declare class RouvaError extends Error {
|
|
|
378
521
|
readonly retryAfter?: number;
|
|
379
522
|
/** The gateway's error message (`error` field), when the body is JSON. */
|
|
380
523
|
readonly error?: string;
|
|
524
|
+
/**
|
|
525
|
+
* The request's id in your Rouva request log (`X-Rouva-Request-Id`), when
|
|
526
|
+
* the error came after the request was recorded — e.g. a provider error.
|
|
527
|
+
* Look it up with `rouva.requests.get(requestId)`. Absent on errors Rouva
|
|
528
|
+
* returns before recording the request (validation 400s, auth).
|
|
529
|
+
*/
|
|
530
|
+
readonly requestId?: string;
|
|
381
531
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
382
532
|
readonly body: unknown;
|
|
383
|
-
constructor(kind: 'Gateway' | 'Run', status: number, headers: Headers, rawBody: string);
|
|
533
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
384
534
|
}
|
|
385
535
|
|
|
386
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, type RequestOptions, type ResponseMessage, Rouva, RouvaError, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaResponseMeta, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -132,6 +132,37 @@ interface ChatCompletionParams {
|
|
|
132
132
|
* no cheaper-model substitution, no cross-provider fallbacks.
|
|
133
133
|
*/
|
|
134
134
|
seed?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Ask for JSON output (gateway support since 2026-10-20). `json_object` is
|
|
137
|
+
* JSON mode: the prompt (a message or `system`) must contain the word
|
|
138
|
+
* "json", or the gateway returns a 400. `json_schema` is Structured Outputs:
|
|
139
|
+
* the answer follows your schema. Routing only picks a model that serves the
|
|
140
|
+
* format; Claude models serve `json_schema` only (every object needs
|
|
141
|
+
* `additionalProperties: false`), and not when the conversation ends with an
|
|
142
|
+
* assistant message. `{ type: 'text' }` or `null` is the default (no-op).
|
|
143
|
+
* `rouva.run()` doesn't accept it. See https://rouva.io/docs#structured-output
|
|
144
|
+
*/
|
|
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 (tools and
|
|
156
|
+
* top_p are then a 400). `rouva.run()` doesn't accept it.
|
|
157
|
+
* See https://rouva.io/docs#reasoning-effort
|
|
158
|
+
*/
|
|
159
|
+
reasoning_effort?: ReasoningEffort | null;
|
|
160
|
+
/**
|
|
161
|
+
* With `tools` only: `false` limits the model to at most one tool call per
|
|
162
|
+
* turn. Forwarded to OpenAI-format providers; for Claude the gateway sets
|
|
163
|
+
* `disable_parallel_tool_use` on `tool_choice`. `rouva.run()` doesn't accept it.
|
|
164
|
+
*/
|
|
165
|
+
parallel_tool_calls?: boolean | null;
|
|
135
166
|
/**
|
|
136
167
|
* Stream the response. Client-side only — controls whether `create()`
|
|
137
168
|
* returns a ReadableStream or a buffered ChatCompletion; never sent to
|
|
@@ -154,6 +185,24 @@ interface ChatCompletionParams {
|
|
|
154
185
|
*/
|
|
155
186
|
tool_choice?: string | Record<string, unknown>;
|
|
156
187
|
}
|
|
188
|
+
/** `reasoning_effort` values the gateway accepts (which ones a model takes varies). */
|
|
189
|
+
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
190
|
+
/** `response_format` values the gateway accepts. */
|
|
191
|
+
type ResponseFormat = {
|
|
192
|
+
type: 'text';
|
|
193
|
+
} | {
|
|
194
|
+
type: 'json_object';
|
|
195
|
+
} | {
|
|
196
|
+
type: 'json_schema';
|
|
197
|
+
json_schema: {
|
|
198
|
+
/** 1–64 letters, digits, underscores or dashes. */
|
|
199
|
+
name: string;
|
|
200
|
+
/** A JSON Schema object. */
|
|
201
|
+
schema: Record<string, unknown>;
|
|
202
|
+
strict?: boolean;
|
|
203
|
+
description?: string;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
157
206
|
/** Assistant message in a completed response; `tool_calls` present when the model requested tools */
|
|
158
207
|
interface ResponseMessage {
|
|
159
208
|
role: 'assistant';
|
|
@@ -180,13 +229,42 @@ interface ChatCompletion {
|
|
|
180
229
|
/** Rouva metadata — cost, savings, routing decision */
|
|
181
230
|
_rouva?: RouvaResponseMeta;
|
|
182
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Why Rouva sent a request to the model it did (header `X-Rouva-Route-Reason`):
|
|
234
|
+
* `intelligent` (Intelligent Routing chose it), `override` (an account routing
|
|
235
|
+
* override), `allowlist` (a workspace member's allowed-model list),
|
|
236
|
+
* `fallback` (a retry after a provider error), `escalation` (`run()` stepped
|
|
237
|
+
* up a model), `no_key_fallback` (the named provider has no key, so a
|
|
238
|
+
* connected one stood in), `direct` (the model you named, as named), or
|
|
239
|
+
* `dedup` (a replay of an identical request — no new call was made).
|
|
240
|
+
*/
|
|
241
|
+
type RouvaRouteReason = 'intelligent' | 'override' | 'allowlist' | 'fallback' | 'escalation' | 'no_key_fallback' | 'direct' | 'dedup' | (string & {});
|
|
183
242
|
interface RouvaResponseMeta {
|
|
184
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* The request's id in your Rouva request log (`X-Rouva-Request-Id`). Pass it
|
|
245
|
+
* to `rouva.requests.get(id)` for the request's cost, savings and quality
|
|
246
|
+
* score. Absent on a duplicate-cache replay (no new request was recorded).
|
|
247
|
+
*/
|
|
248
|
+
request_id?: string;
|
|
249
|
+
/** The model that served the request — after a fallback, the fallback model. */
|
|
185
250
|
model_used?: string;
|
|
186
|
-
/**
|
|
251
|
+
/** That model's provider, e.g. `openai`, `anthropic`. */
|
|
187
252
|
provider_used?: string;
|
|
188
|
-
/**
|
|
253
|
+
/** The model you named, when Rouva served a different one. */
|
|
254
|
+
requested_model?: string;
|
|
255
|
+
/** Task type classified by Rouva, e.g. `code`; absent when not classified. */
|
|
189
256
|
task_type?: string;
|
|
257
|
+
/** Why this model was chosen. */
|
|
258
|
+
route_reason?: RouvaRouteReason;
|
|
259
|
+
/**
|
|
260
|
+
* What the request cost in USD, when the gateway sent it in a header: only
|
|
261
|
+
* on non-streaming responses once the cost is final, and as 0 on a
|
|
262
|
+
* duplicate-cache replay (`dedup: true`). The SDK streams, so otherwise read
|
|
263
|
+
* cost with `rouva.requests.get(request_id)`.
|
|
264
|
+
*/
|
|
265
|
+
cost_usd?: number;
|
|
266
|
+
/** Saved against the model you named (or the priciest candidate), in USD; negative when the route cost more. Same availability as `cost_usd`. */
|
|
267
|
+
saved_usd?: number;
|
|
190
268
|
/**
|
|
191
269
|
* True when the gateway answered from its duplicate-request cache (an
|
|
192
270
|
* identical request within the last 60 seconds) instead of calling the
|
|
@@ -199,6 +277,48 @@ interface RouvaResponseMeta {
|
|
|
199
277
|
*/
|
|
200
278
|
cache?: string;
|
|
201
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The stream `chat.completions.create({ stream: true })` returns: OpenAI-shaped
|
|
282
|
+
* SSE bytes, plus the routing metadata from the response headers (known
|
|
283
|
+
* before the first chunk).
|
|
284
|
+
*/
|
|
285
|
+
type RouvaStream = ReadableStream<Uint8Array> & {
|
|
286
|
+
_rouva?: RouvaResponseMeta;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* A request's settlement state: `pending` (not settled yet — in flight, or it
|
|
290
|
+
* never reached the provider), `incomplete` (settled from partial usage, e.g. a
|
|
291
|
+
* stream cut short — the provider may have billed more), `settled` (tokens and
|
|
292
|
+
* cost are final), `scored` (final, and a quality score is recorded).
|
|
293
|
+
*/
|
|
294
|
+
type RouvaRequestStatus = 'pending' | 'incomplete' | 'settled' | 'scored' | (string & {});
|
|
295
|
+
/** One request from your request log — `rouva.requests.get(id)`. */
|
|
296
|
+
interface RouvaRequest {
|
|
297
|
+
id: string;
|
|
298
|
+
/** ISO timestamp. */
|
|
299
|
+
created_at: string;
|
|
300
|
+
status: RouvaRequestStatus;
|
|
301
|
+
provider: string;
|
|
302
|
+
model: string;
|
|
303
|
+
requested_model: string | null;
|
|
304
|
+
task_type: string | null;
|
|
305
|
+
route_reason: RouvaRouteReason | null;
|
|
306
|
+
/** 0 while pending. */
|
|
307
|
+
input_tokens: number;
|
|
308
|
+
output_tokens: number;
|
|
309
|
+
/** USD; 0 while pending. Final once `status` is `settled` or `scored`. */
|
|
310
|
+
cost_usd: number;
|
|
311
|
+
/** USD saved against the model you named (or the priciest candidate); negative when the route cost more. */
|
|
312
|
+
saved_usd: number;
|
|
313
|
+
/** 0–1, or null until a score is recorded (or when none is). */
|
|
314
|
+
quality_score: number | null;
|
|
315
|
+
/** Whether the judge scored it, or why not (then `quality_score` is the heuristic checks'). */
|
|
316
|
+
judge_status: string | null;
|
|
317
|
+
finish_reason: string | null;
|
|
318
|
+
session_id: string | null;
|
|
319
|
+
/** Opens this request in the dashboard's request log. */
|
|
320
|
+
dashboard_url: string;
|
|
321
|
+
}
|
|
202
322
|
interface RunParams {
|
|
203
323
|
/**
|
|
204
324
|
* Conversation history to seed the agent loop with.
|
|
@@ -301,9 +421,30 @@ declare class Rouva {
|
|
|
301
421
|
private _sessionId;
|
|
302
422
|
readonly chat: {
|
|
303
423
|
completions: {
|
|
304
|
-
create(params: ChatCompletionParams, options?: RequestOptions): Promise<ChatCompletion |
|
|
424
|
+
create(params: ChatCompletionParams, options?: RequestOptions): Promise<ChatCompletion | RouvaStream>;
|
|
305
425
|
};
|
|
306
426
|
};
|
|
427
|
+
readonly requests: {
|
|
428
|
+
/**
|
|
429
|
+
* Read one request from your request log (`GET /api/gateway/requests/{id}`)
|
|
430
|
+
* by the id a response carried (`_rouva.request_id`, header
|
|
431
|
+
* `X-Rouva-Request-Id`): its model, routing reason, tokens, cost, savings and
|
|
432
|
+
* quality score. A key reads only its own requests (another's id is a 404).
|
|
433
|
+
*
|
|
434
|
+
* A stream's cost is recorded just after its last chunk, and the quality
|
|
435
|
+
* score once the judge has run (a few seconds), so check `status`:
|
|
436
|
+
* `pending` → not settled yet, retry shortly; `incomplete` → settled from
|
|
437
|
+
* partial usage; `settled` → cost final; `scored` → cost final and scored.
|
|
438
|
+
* Throws `RouvaError` on a non-2xx response (a 503 carries `retryAfter`).
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* const stream = await rouva.chat.completions.create({ messages, stream: true }) as RouvaStream
|
|
442
|
+
* // ...consume the stream...
|
|
443
|
+
* const req = await rouva.requests.get(stream._rouva!.request_id!)
|
|
444
|
+
* console.log(req.status, req.cost_usd, req.saved_usd)
|
|
445
|
+
*/
|
|
446
|
+
get(id: string, options?: RequestOptions): Promise<RouvaRequest>;
|
|
447
|
+
};
|
|
307
448
|
constructor(options: RouvaOptions);
|
|
308
449
|
/**
|
|
309
450
|
* Start a new agent session. Every subsequent `chat.completions.create()`
|
|
@@ -347,7 +488,9 @@ declare class Rouva {
|
|
|
347
488
|
* console.log(result.session_id) // dashboard session ID
|
|
348
489
|
*/
|
|
349
490
|
run(params: RunParams, options?: RequestOptions): Promise<RunResult>;
|
|
491
|
+
private _getRequest;
|
|
350
492
|
private _post;
|
|
493
|
+
private _fetch;
|
|
351
494
|
private _createChatCompletion;
|
|
352
495
|
}
|
|
353
496
|
|
|
@@ -378,9 +521,16 @@ declare class RouvaError extends Error {
|
|
|
378
521
|
readonly retryAfter?: number;
|
|
379
522
|
/** The gateway's error message (`error` field), when the body is JSON. */
|
|
380
523
|
readonly error?: string;
|
|
524
|
+
/**
|
|
525
|
+
* The request's id in your Rouva request log (`X-Rouva-Request-Id`), when
|
|
526
|
+
* the error came after the request was recorded — e.g. a provider error.
|
|
527
|
+
* Look it up with `rouva.requests.get(requestId)`. Absent on errors Rouva
|
|
528
|
+
* returns before recording the request (validation 400s, auth).
|
|
529
|
+
*/
|
|
530
|
+
readonly requestId?: string;
|
|
381
531
|
/** The parsed JSON body, or the raw text when it isn't JSON. */
|
|
382
532
|
readonly body: unknown;
|
|
383
|
-
constructor(kind: 'Gateway' | 'Run', status: number, headers: Headers, rawBody: string);
|
|
533
|
+
constructor(kind: 'Gateway' | 'Run' | 'Request', status: number, headers: Headers, rawBody: string);
|
|
384
534
|
}
|
|
385
535
|
|
|
386
|
-
export { type AssistantToolCallMessage, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type ChatMessage, type ContentBlockMessage, type FunctionToolCall, type Message, type RequestOptions, type ResponseMessage, Rouva, RouvaError, type RouvaModel, type RouvaOptions, type RouvaProvider, type RouvaResponseMeta, type RunParams, type RunResult, type SystemTextBlock, type ToolResultMessage };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -357,17 +357,30 @@ async function readChatCompletionFromSse(stream, metadata) {
|
|
|
357
357
|
}
|
|
358
358
|
function getRouvaMetadata(headers) {
|
|
359
359
|
const read = (name) => headers.get(name) || void 0;
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
360
|
+
const usd = (name) => {
|
|
361
|
+
const raw = read(name);
|
|
362
|
+
const n = raw === void 0 ? NaN : Number(raw);
|
|
363
|
+
return Number.isFinite(n) ? n : void 0;
|
|
364
|
+
};
|
|
365
|
+
const meta = {
|
|
366
|
+
// X-Rouva-Row-Id is the same id under its older name.
|
|
367
|
+
request_id: read("x-rouva-request-id") ?? read("x-rouva-row-id"),
|
|
368
|
+
model_used: read("x-rouva-model"),
|
|
369
|
+
provider_used: read("x-rouva-provider"),
|
|
370
|
+
requested_model: read("x-rouva-requested-model"),
|
|
371
|
+
// X-Rouva-Task is the older name, still sent alongside.
|
|
372
|
+
task_type: read("x-rouva-task-type") ?? read("x-rouva-task"),
|
|
373
|
+
route_reason: read("x-rouva-route-reason"),
|
|
374
|
+
cost_usd: usd("x-rouva-cost-usd"),
|
|
375
|
+
saved_usd: usd("x-rouva-saved-usd"),
|
|
376
|
+
// Duplicate-request cache hit (an identical request within 60s, not charged).
|
|
377
|
+
// (The old x-rouva-cache header went away with the semantic cache.)
|
|
378
|
+
dedup: headers.get("x-rouva-dedup") === "1" ? true : void 0
|
|
370
379
|
};
|
|
380
|
+
for (const key of Object.keys(meta)) {
|
|
381
|
+
if (meta[key] === void 0) delete meta[key];
|
|
382
|
+
}
|
|
383
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
371
384
|
}
|
|
372
385
|
|
|
373
386
|
// src/errors.ts
|
|
@@ -379,6 +392,7 @@ var RouvaError = class extends Error {
|
|
|
379
392
|
this.refusal = headers.get("x-rouva-refusal") ?? void 0;
|
|
380
393
|
const retry = Number(headers.get("retry-after"));
|
|
381
394
|
this.retryAfter = headers.get("retry-after") !== null && Number.isFinite(retry) ? retry : void 0;
|
|
395
|
+
this.requestId = headers.get("x-rouva-request-id") || headers.get("x-rouva-row-id") || void 0;
|
|
382
396
|
let parsed = rawBody;
|
|
383
397
|
try {
|
|
384
398
|
parsed = JSON.parse(rawBody);
|
|
@@ -412,6 +426,9 @@ var Rouva = class {
|
|
|
412
426
|
create: (params, options2) => this._createChatCompletion(params, options2)
|
|
413
427
|
}
|
|
414
428
|
};
|
|
429
|
+
this.requests = {
|
|
430
|
+
get: (id, options2) => this._getRequest(id, options2)
|
|
431
|
+
};
|
|
415
432
|
}
|
|
416
433
|
/**
|
|
417
434
|
* Start a new agent session. Every subsequent `chat.completions.create()`
|
|
@@ -466,16 +483,25 @@ var Rouva = class {
|
|
|
466
483
|
if (!res.ok) throw await toRouvaError("Run", res);
|
|
467
484
|
return res.json();
|
|
468
485
|
}
|
|
469
|
-
|
|
486
|
+
async _getRequest(id, options) {
|
|
487
|
+
if (typeof id !== "string" || !id) throw new Error("[Rouva] request id is required");
|
|
488
|
+
const res = await this._fetch("GET", `/api/gateway/requests/${encodeURIComponent(id)}`, void 0, {}, options);
|
|
489
|
+
if (!res.ok) throw await toRouvaError("Request", res);
|
|
490
|
+
return res.json();
|
|
491
|
+
}
|
|
470
492
|
_post(path, body, extraHeaders, options) {
|
|
493
|
+
return this._fetch("POST", path, body, extraHeaders, options);
|
|
494
|
+
}
|
|
495
|
+
// One request path for every endpoint: auth, JSON body, optional abort signal.
|
|
496
|
+
_fetch(method, path, body, extraHeaders, options) {
|
|
471
497
|
return fetch(`${this.baseURL}${path}`, {
|
|
472
|
-
method
|
|
498
|
+
method,
|
|
473
499
|
headers: {
|
|
474
|
-
"Content-Type": "application/json",
|
|
500
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {},
|
|
475
501
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
476
502
|
...extraHeaders
|
|
477
503
|
},
|
|
478
|
-
body: JSON.stringify(body),
|
|
504
|
+
...method === "POST" ? { body: JSON.stringify(body) } : {},
|
|
479
505
|
...options?.signal ? { signal: options.signal } : {}
|
|
480
506
|
});
|
|
481
507
|
}
|
|
@@ -486,8 +512,12 @@ var Rouva = class {
|
|
|
486
512
|
if (!res.ok) throw await toRouvaError("Gateway", res);
|
|
487
513
|
if (!res.body) throw new Error("[Rouva] No response body from gateway");
|
|
488
514
|
const normalizedStream = normalizeGatewayStream(res.body);
|
|
489
|
-
|
|
490
|
-
|
|
515
|
+
const metadata = getRouvaMetadata(res.headers);
|
|
516
|
+
if (params.stream) {
|
|
517
|
+
if (metadata) normalizedStream._rouva = metadata;
|
|
518
|
+
return normalizedStream;
|
|
519
|
+
}
|
|
520
|
+
return readChatCompletionFromSse(normalizedStream, metadata);
|
|
491
521
|
}
|
|
492
522
|
};
|
|
493
523
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.mjs
CHANGED
|
@@ -330,17 +330,30 @@ async function readChatCompletionFromSse(stream, metadata) {
|
|
|
330
330
|
}
|
|
331
331
|
function getRouvaMetadata(headers) {
|
|
332
332
|
const read = (name) => headers.get(name) || void 0;
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
333
|
+
const usd = (name) => {
|
|
334
|
+
const raw = read(name);
|
|
335
|
+
const n = raw === void 0 ? NaN : Number(raw);
|
|
336
|
+
return Number.isFinite(n) ? n : void 0;
|
|
337
|
+
};
|
|
338
|
+
const meta = {
|
|
339
|
+
// X-Rouva-Row-Id is the same id under its older name.
|
|
340
|
+
request_id: read("x-rouva-request-id") ?? read("x-rouva-row-id"),
|
|
341
|
+
model_used: read("x-rouva-model"),
|
|
342
|
+
provider_used: read("x-rouva-provider"),
|
|
343
|
+
requested_model: read("x-rouva-requested-model"),
|
|
344
|
+
// X-Rouva-Task is the older name, still sent alongside.
|
|
345
|
+
task_type: read("x-rouva-task-type") ?? read("x-rouva-task"),
|
|
346
|
+
route_reason: read("x-rouva-route-reason"),
|
|
347
|
+
cost_usd: usd("x-rouva-cost-usd"),
|
|
348
|
+
saved_usd: usd("x-rouva-saved-usd"),
|
|
349
|
+
// Duplicate-request cache hit (an identical request within 60s, not charged).
|
|
350
|
+
// (The old x-rouva-cache header went away with the semantic cache.)
|
|
351
|
+
dedup: headers.get("x-rouva-dedup") === "1" ? true : void 0
|
|
343
352
|
};
|
|
353
|
+
for (const key of Object.keys(meta)) {
|
|
354
|
+
if (meta[key] === void 0) delete meta[key];
|
|
355
|
+
}
|
|
356
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
344
357
|
}
|
|
345
358
|
|
|
346
359
|
// src/errors.ts
|
|
@@ -352,6 +365,7 @@ var RouvaError = class extends Error {
|
|
|
352
365
|
this.refusal = headers.get("x-rouva-refusal") ?? void 0;
|
|
353
366
|
const retry = Number(headers.get("retry-after"));
|
|
354
367
|
this.retryAfter = headers.get("retry-after") !== null && Number.isFinite(retry) ? retry : void 0;
|
|
368
|
+
this.requestId = headers.get("x-rouva-request-id") || headers.get("x-rouva-row-id") || void 0;
|
|
355
369
|
let parsed = rawBody;
|
|
356
370
|
try {
|
|
357
371
|
parsed = JSON.parse(rawBody);
|
|
@@ -385,6 +399,9 @@ var Rouva = class {
|
|
|
385
399
|
create: (params, options2) => this._createChatCompletion(params, options2)
|
|
386
400
|
}
|
|
387
401
|
};
|
|
402
|
+
this.requests = {
|
|
403
|
+
get: (id, options2) => this._getRequest(id, options2)
|
|
404
|
+
};
|
|
388
405
|
}
|
|
389
406
|
/**
|
|
390
407
|
* Start a new agent session. Every subsequent `chat.completions.create()`
|
|
@@ -439,16 +456,25 @@ var Rouva = class {
|
|
|
439
456
|
if (!res.ok) throw await toRouvaError("Run", res);
|
|
440
457
|
return res.json();
|
|
441
458
|
}
|
|
442
|
-
|
|
459
|
+
async _getRequest(id, options) {
|
|
460
|
+
if (typeof id !== "string" || !id) throw new Error("[Rouva] request id is required");
|
|
461
|
+
const res = await this._fetch("GET", `/api/gateway/requests/${encodeURIComponent(id)}`, void 0, {}, options);
|
|
462
|
+
if (!res.ok) throw await toRouvaError("Request", res);
|
|
463
|
+
return res.json();
|
|
464
|
+
}
|
|
443
465
|
_post(path, body, extraHeaders, options) {
|
|
466
|
+
return this._fetch("POST", path, body, extraHeaders, options);
|
|
467
|
+
}
|
|
468
|
+
// One request path for every endpoint: auth, JSON body, optional abort signal.
|
|
469
|
+
_fetch(method, path, body, extraHeaders, options) {
|
|
444
470
|
return fetch(`${this.baseURL}${path}`, {
|
|
445
|
-
method
|
|
471
|
+
method,
|
|
446
472
|
headers: {
|
|
447
|
-
"Content-Type": "application/json",
|
|
473
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {},
|
|
448
474
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
449
475
|
...extraHeaders
|
|
450
476
|
},
|
|
451
|
-
body: JSON.stringify(body),
|
|
477
|
+
...method === "POST" ? { body: JSON.stringify(body) } : {},
|
|
452
478
|
...options?.signal ? { signal: options.signal } : {}
|
|
453
479
|
});
|
|
454
480
|
}
|
|
@@ -459,8 +485,12 @@ var Rouva = class {
|
|
|
459
485
|
if (!res.ok) throw await toRouvaError("Gateway", res);
|
|
460
486
|
if (!res.body) throw new Error("[Rouva] No response body from gateway");
|
|
461
487
|
const normalizedStream = normalizeGatewayStream(res.body);
|
|
462
|
-
|
|
463
|
-
|
|
488
|
+
const metadata = getRouvaMetadata(res.headers);
|
|
489
|
+
if (params.stream) {
|
|
490
|
+
if (metadata) normalizedStream._rouva = metadata;
|
|
491
|
+
return normalizedStream;
|
|
492
|
+
}
|
|
493
|
+
return readChatCompletionFromSse(normalizedStream, metadata);
|
|
464
494
|
}
|
|
465
495
|
};
|
|
466
496
|
export {
|
package/package.json
CHANGED