@rouvanpm/rouva 0.2.9 → 0.2.11

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 CHANGED
@@ -119,13 +119,13 @@ Models not recognised by the gateway return a `400` immediately:
119
119
 
120
120
  Using the OpenAI SDK (or LangChain, the Vercel AI SDK, …) instead of this one? The gateway is also served at `POST /v1/chat/completions` — point `baseURL` at `https://app.rouva.io/v1` with your `rva_` key and it behaves exactly like OpenAI: `model` is required, responses are buffered JSON unless `stream: true`, and only OpenAI-format providers are available (use this SDK or the native endpoint for Anthropic models).
121
121
 
122
- Intelligent routing applies to `/v1` too — Rouva may substitute a cheaper model within the same provider using your requested model as a cost ceiling. For tool call requests specifically, routing only considers non-reasoning models (reasoning models add unpredictable hidden token costs on tool dispatch). To pin a model exactly, disable Intelligent Routing in your dashboard under **Settings**.
122
+ Non-tools requests on `/v1` always honor the named model exactly. **Tools requests on `/v1` benefit from within-provider intelligent routing** when Intelligent Routing is enabled in your dashboard — Rouva picks the cheapest capable model within the same provider, using the named `model` as a cost ceiling. The provider never changes (tool schemas are provider-specific), and `response.model` reflects the model that actually served the request. Turn Intelligent Routing off in dashboard Settings → Gateway to always pin the exact model.
123
123
 
124
124
  ## Tool use
125
125
 
126
126
  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.
127
127
 
128
- On the native endpoint (`/api/gateway/messages`), the pinned model is always honored exactly — no substitution. On the `/v1` endpoint with Intelligent Routing enabled, Rouva may route to a cheaper non-reasoning model within the same provider. Disable Intelligent Routing in **Settings** to pin the exact model on `/v1`.
128
+ When using the `/v1` endpoint (OpenAI SDK, LangChain, Vercel AI SDK, …) with Intelligent Routing enabled, tools requests are routed to the cheapest capable model within the same provider — see [OpenAI-compatible endpoint](#openai-compatible-endpoint) for details. On the native SDK endpoint (`/api/gateway/messages`), the named model is always honored exactly.
129
129
 
130
130
  ```typescript
131
131
  const res = await rouva.chat.completions.create({
@@ -167,7 +167,7 @@ Responses are normalized to the OpenAI shape regardless of provider: Anthropic `
167
167
 
168
168
  **Don't branch on `finish_reason` to detect tool calls** — check for the presence of `message.tool_calls` instead. `finish_reason` follows each provider's own semantics, and OpenAI notably returns `"stop"` (not `"tool_calls"`) when `tool_choice` forces a specific function. The SDK passes OpenAI's values through unchanged so behavior matches calling OpenAI directly.
169
169
 
170
- Tools requests record usage and cost but no savings, and are not quality-scored or served from the semantic cache.
170
+ Tools requests record usage and cost. Savings are recorded when within-provider routing substitutes a cheaper model on `/v1`. Tools responses are not quality-scored or served from the semantic cache.
171
171
 
172
172
  ## Cost optimisations
173
173
 
@@ -190,6 +190,33 @@ const rouva = new Rouva({
190
190
  })
191
191
  ```
192
192
 
193
+ ## Agent session tracking
194
+
195
+ Group all turns of an agent run under a single session ID so the Rouva dashboard can show per-session cost, token usage, and quality.
196
+
197
+ ```typescript
198
+ const rouva = new Rouva({ apiKey: 'rva_...' })
199
+
200
+ // Start a session — auto-generates an ID and attaches it to every request
201
+ const sessionId = rouva.startSession()
202
+
203
+ // All turns carry the same session ID automatically
204
+ await rouva.chat.completions.create({ messages: [...], tools: [...] })
205
+ await rouva.chat.completions.create({ messages: [...], tools: [...] })
206
+ await rouva.chat.completions.create({ messages: [...], tools: [...] })
207
+
208
+ // End the session when the agent run is complete
209
+ rouva.endSession()
210
+ ```
211
+
212
+ You can also read the active session ID at any time:
213
+
214
+ ```typescript
215
+ console.log(rouva.sessionId) // 'rva-sess-abc123' or undefined
216
+ ```
217
+
218
+ Sessions are optional — requests without an active session are tracked individually as before.
219
+
193
220
  ## Response metadata
194
221
 
195
222
  Parsed non-stream responses may include a `_rouva` field with gateway header metadata when available:
package/dist/index.d.mts CHANGED
@@ -56,7 +56,7 @@ type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral'
56
56
  * as-is. Until the gateway's registry knows their pricing, such requests are
57
57
  * logged with token counts but zero cost.
58
58
  */
59
- type RouvaModel = 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-fable-5' | 'gpt-5-nano' | 'gpt-5-mini' | 'gpt-5.4-mini' | 'gpt-5' | 'gpt-5.6' | 'gpt-4.1-nano' | 'gpt-4.1-mini' | 'gpt-4.1' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-2.5-flash' | 'gemini-2.5-pro' | 'deepseek-v4-flash' | 'deepseek-v4-flash-think' | 'deepseek-v4-pro' | 'mistral-small-latest' | 'mistral-large-latest' | 'kimi-k2.6' | 'kimi-k3' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.5' | 'glm-4.7-flash' | 'glm-5.2' | (string & {});
59
+ type RouvaModel = 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-fable-5' | 'gpt-5-nano' | 'gpt-5-mini' | 'gpt-5.4-mini' | 'gpt-5' | 'gpt-5.6' | 'gpt-4.1-nano' | 'gpt-4.1-mini' | 'gpt-4.1' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-2.5-flash' | 'gemini-2.5-pro' | 'deepseek-v4-flash' | 'deepseek-v4-flash-think' | 'deepseek-v4-pro' | 'mistral-small-latest' | 'mistral-large-latest' | 'kimi-k2.6' | 'kimi-k3' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-4.7-flash' | 'glm-5.2' | (string & {});
60
60
  /** Anthropic-style text block — the array form of the `system` param */
61
61
  interface SystemTextBlock {
62
62
  type: 'text';
@@ -168,12 +168,27 @@ interface RouvaResponseMeta {
168
168
  declare class Rouva {
169
169
  private apiKey;
170
170
  private baseURL;
171
+ private _sessionId;
171
172
  readonly chat: {
172
173
  completions: {
173
174
  create(params: ChatCompletionParams): Promise<ChatCompletion | ReadableStream>;
174
175
  };
175
176
  };
176
177
  constructor(options: RouvaOptions);
178
+ /**
179
+ * Start a new agent session. Every subsequent `chat.completions.create()`
180
+ * call will include `x-rouva-session-id` so all turns are grouped in the
181
+ * Rouva dashboard. Call `endSession()` when the agent run is complete.
182
+ * Returns the generated session ID.
183
+ */
184
+ startSession(): string;
185
+ /**
186
+ * End the current session. Subsequent requests will not carry a session ID
187
+ * until `startSession()` is called again.
188
+ */
189
+ endSession(): void;
190
+ /** The active session ID, or undefined if no session is in progress. */
191
+ get sessionId(): string | undefined;
177
192
  private _createChatCompletion;
178
193
  }
179
194
 
package/dist/index.d.ts CHANGED
@@ -56,7 +56,7 @@ type RouvaProvider = 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'mistral'
56
56
  * as-is. Until the gateway's registry knows their pricing, such requests are
57
57
  * logged with token counts but zero cost.
58
58
  */
59
- type RouvaModel = 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-fable-5' | 'gpt-5-nano' | 'gpt-5-mini' | 'gpt-5.4-mini' | 'gpt-5' | 'gpt-5.6' | 'gpt-4.1-nano' | 'gpt-4.1-mini' | 'gpt-4.1' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-2.5-flash' | 'gemini-2.5-pro' | 'deepseek-v4-flash' | 'deepseek-v4-flash-think' | 'deepseek-v4-pro' | 'mistral-small-latest' | 'mistral-large-latest' | 'kimi-k2.6' | 'kimi-k3' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.5' | 'glm-4.7-flash' | 'glm-5.2' | (string & {});
59
+ type RouvaModel = 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-fable-5' | 'gpt-5-nano' | 'gpt-5-mini' | 'gpt-5.4-mini' | 'gpt-5' | 'gpt-5.6' | 'gpt-4.1-nano' | 'gpt-4.1-mini' | 'gpt-4.1' | 'gpt-4o' | 'gpt-4o-mini' | 'gemini-3.7-flash' | 'gemini-3.6-flash' | 'gemini-2.5-flash' | 'gemini-2.5-pro' | 'deepseek-v4-flash' | 'deepseek-v4-flash-think' | 'deepseek-v4-pro' | 'mistral-small-latest' | 'mistral-large-latest' | 'kimi-k2.6' | 'kimi-k3' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'glm-4.7-flash' | 'glm-5.2' | (string & {});
60
60
  /** Anthropic-style text block — the array form of the `system` param */
61
61
  interface SystemTextBlock {
62
62
  type: 'text';
@@ -168,12 +168,27 @@ interface RouvaResponseMeta {
168
168
  declare class Rouva {
169
169
  private apiKey;
170
170
  private baseURL;
171
+ private _sessionId;
171
172
  readonly chat: {
172
173
  completions: {
173
174
  create(params: ChatCompletionParams): Promise<ChatCompletion | ReadableStream>;
174
175
  };
175
176
  };
176
177
  constructor(options: RouvaOptions);
178
+ /**
179
+ * Start a new agent session. Every subsequent `chat.completions.create()`
180
+ * call will include `x-rouva-session-id` so all turns are grouped in the
181
+ * Rouva dashboard. Call `endSession()` when the agent run is complete.
182
+ * Returns the generated session ID.
183
+ */
184
+ startSession(): string;
185
+ /**
186
+ * End the current session. Subsequent requests will not carry a session ID
187
+ * until `startSession()` is called again.
188
+ */
189
+ endSession(): void;
190
+ /** The active session ID, or undefined if no session is in progress. */
191
+ get sessionId(): string | undefined;
177
192
  private _createChatCompletion;
178
193
  }
179
194
 
package/dist/index.js CHANGED
@@ -383,15 +383,38 @@ var Rouva = class {
383
383
  }
384
384
  };
385
385
  }
386
+ /**
387
+ * Start a new agent session. Every subsequent `chat.completions.create()`
388
+ * call will include `x-rouva-session-id` so all turns are grouped in the
389
+ * Rouva dashboard. Call `endSession()` when the agent run is complete.
390
+ * Returns the generated session ID.
391
+ */
392
+ startSession() {
393
+ this._sessionId = `rva-sess-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
394
+ return this._sessionId;
395
+ }
396
+ /**
397
+ * End the current session. Subsequent requests will not carry a session ID
398
+ * until `startSession()` is called again.
399
+ */
400
+ endSession() {
401
+ this._sessionId = void 0;
402
+ }
403
+ /** The active session ID, or undefined if no session is in progress. */
404
+ get sessionId() {
405
+ return this._sessionId;
406
+ }
386
407
  async _createChatCompletion(params) {
387
408
  const url = `${this.baseURL}/api/gateway/messages`;
388
409
  const { stream: _stream, ...body } = params;
410
+ const headers = {
411
+ "Content-Type": "application/json",
412
+ "Authorization": `Bearer ${this.apiKey}`
413
+ };
414
+ if (this._sessionId) headers["x-rouva-session-id"] = this._sessionId;
389
415
  const res = await fetch(url, {
390
416
  method: "POST",
391
- headers: {
392
- "Content-Type": "application/json",
393
- "Authorization": `Bearer ${this.apiKey}`
394
- },
417
+ headers,
395
418
  body: JSON.stringify(body)
396
419
  });
397
420
  if (!res.ok) {
package/dist/index.mjs CHANGED
@@ -357,15 +357,38 @@ var Rouva = class {
357
357
  }
358
358
  };
359
359
  }
360
+ /**
361
+ * Start a new agent session. Every subsequent `chat.completions.create()`
362
+ * call will include `x-rouva-session-id` so all turns are grouped in the
363
+ * Rouva dashboard. Call `endSession()` when the agent run is complete.
364
+ * Returns the generated session ID.
365
+ */
366
+ startSession() {
367
+ this._sessionId = `rva-sess-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
368
+ return this._sessionId;
369
+ }
370
+ /**
371
+ * End the current session. Subsequent requests will not carry a session ID
372
+ * until `startSession()` is called again.
373
+ */
374
+ endSession() {
375
+ this._sessionId = void 0;
376
+ }
377
+ /** The active session ID, or undefined if no session is in progress. */
378
+ get sessionId() {
379
+ return this._sessionId;
380
+ }
360
381
  async _createChatCompletion(params) {
361
382
  const url = `${this.baseURL}/api/gateway/messages`;
362
383
  const { stream: _stream, ...body } = params;
384
+ const headers = {
385
+ "Content-Type": "application/json",
386
+ "Authorization": `Bearer ${this.apiKey}`
387
+ };
388
+ if (this._sessionId) headers["x-rouva-session-id"] = this._sessionId;
363
389
  const res = await fetch(url, {
364
390
  method: "POST",
365
- headers: {
366
- "Content-Type": "application/json",
367
- "Authorization": `Bearer ${this.apiKey}`
368
- },
391
+ headers,
369
392
  body: JSON.stringify(body)
370
393
  });
371
394
  if (!res.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rouvanpm/rouva",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Official Node.js SDK for Rouva — managed AI gateway with intelligent routing and spend tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",