@rouvanpm/rouva 0.1.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 +91 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +67 -0
- package/dist/index.mjs +40 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# rouva
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [Rouva](https://rouva.io) — managed AI gateway with intelligent routing and spend tracking.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install rouva
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Rouva } from 'rouva'
|
|
15
|
+
|
|
16
|
+
const rouva = new Rouva({ apiKey: 'rva_...' })
|
|
17
|
+
|
|
18
|
+
const response = await rouva.chat.completions.create({
|
|
19
|
+
messages: [{ role: 'user', content: 'Summarize the benefits of AI routing.' }],
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
console.log(response.choices[0].message.content)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Drop-in replacement for OpenAI
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Before
|
|
29
|
+
import OpenAI from 'openai'
|
|
30
|
+
const openai = new OpenAI({ apiKey: '...' })
|
|
31
|
+
const res = await openai.chat.completions.create({ messages, model: 'gpt-4o' })
|
|
32
|
+
|
|
33
|
+
// After — Rouva routes to the cheapest capable model automatically
|
|
34
|
+
import { Rouva } from 'rouva'
|
|
35
|
+
const rouva = new Rouva({ apiKey: 'rva_...' })
|
|
36
|
+
const res = await rouva.chat.completions.create({ messages })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Streaming
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const stream = await rouva.chat.completions.create({
|
|
43
|
+
messages: [{ role: 'user', content: 'Write a short story.' }],
|
|
44
|
+
stream: true,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const reader = (stream as ReadableStream).getReader()
|
|
48
|
+
const decoder = new TextDecoder()
|
|
49
|
+
|
|
50
|
+
while (true) {
|
|
51
|
+
const { done, value } = await reader.read()
|
|
52
|
+
if (done) break
|
|
53
|
+
process.stdout.write(decoder.decode(value))
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const rouva = new Rouva({
|
|
61
|
+
apiKey: 'rva_...', // Required — get this from your Rouva dashboard
|
|
62
|
+
baseURL: 'https://...', // Optional — override the gateway URL
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Response metadata
|
|
67
|
+
|
|
68
|
+
Every response includes a `_rouva` field with routing and cost details:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const res = await rouva.chat.completions.create({ messages })
|
|
72
|
+
|
|
73
|
+
console.log(res._rouva)
|
|
74
|
+
// {
|
|
75
|
+
// model_used: 'gpt-4o-mini',
|
|
76
|
+
// cost: 0.000012,
|
|
77
|
+
// savings: 0.000088,
|
|
78
|
+
// intelligently_routed: true,
|
|
79
|
+
// task_type: 'summarize'
|
|
80
|
+
// }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Getting your API key
|
|
84
|
+
|
|
85
|
+
1. Sign in to [app.rouva.io](https://app.rouva.io)
|
|
86
|
+
2. Go to **Settings → Gateway Key**
|
|
87
|
+
3. Generate a key — it starts with `rva_`
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
interface RouvaOptions {
|
|
2
|
+
/** Your Rouva gateway API key (rva_...) */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Override the default gateway URL — useful for self-hosted or testing */
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Message {
|
|
8
|
+
role: 'system' | 'user' | 'assistant';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
interface ChatCompletionParams {
|
|
12
|
+
messages: Message[];
|
|
13
|
+
/** Target model — omit to let Rouva route intelligently */
|
|
14
|
+
model?: string;
|
|
15
|
+
/** Maximum tokens to generate */
|
|
16
|
+
max_tokens?: number;
|
|
17
|
+
/** Sampling temperature 0–1 */
|
|
18
|
+
temperature?: number;
|
|
19
|
+
/** Stream the response */
|
|
20
|
+
stream?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ChatCompletionChoice {
|
|
23
|
+
index: number;
|
|
24
|
+
message: Message;
|
|
25
|
+
finish_reason: string | null;
|
|
26
|
+
}
|
|
27
|
+
interface ChatCompletionUsage {
|
|
28
|
+
prompt_tokens: number;
|
|
29
|
+
completion_tokens: number;
|
|
30
|
+
total_tokens: number;
|
|
31
|
+
}
|
|
32
|
+
interface ChatCompletion {
|
|
33
|
+
id: string;
|
|
34
|
+
object: 'chat.completion';
|
|
35
|
+
created: number;
|
|
36
|
+
model: string;
|
|
37
|
+
choices: ChatCompletionChoice[];
|
|
38
|
+
usage: ChatCompletionUsage;
|
|
39
|
+
/** Rouva metadata — cost, savings, routing decision */
|
|
40
|
+
_rouva?: RouvaResponseMeta;
|
|
41
|
+
}
|
|
42
|
+
interface RouvaResponseMeta {
|
|
43
|
+
/** Actual model used (may differ from requested when intelligently routed) */
|
|
44
|
+
model_used: string;
|
|
45
|
+
/** USD cost for this request */
|
|
46
|
+
cost: number;
|
|
47
|
+
/** USD saved vs your baseline model */
|
|
48
|
+
savings: number;
|
|
49
|
+
/** Whether intelligent routing selected the model */
|
|
50
|
+
intelligently_routed: boolean;
|
|
51
|
+
/** Task type classified by Rouva */
|
|
52
|
+
task_type: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare class Rouva {
|
|
56
|
+
private apiKey;
|
|
57
|
+
private baseURL;
|
|
58
|
+
readonly chat: {
|
|
59
|
+
completions: {
|
|
60
|
+
create(params: ChatCompletionParams): Promise<ChatCompletion | ReadableStream>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
constructor(options: RouvaOptions);
|
|
64
|
+
private _createChatCompletion;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type Message, Rouva, type RouvaOptions, type RouvaResponseMeta };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
interface RouvaOptions {
|
|
2
|
+
/** Your Rouva gateway API key (rva_...) */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Override the default gateway URL — useful for self-hosted or testing */
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Message {
|
|
8
|
+
role: 'system' | 'user' | 'assistant';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
interface ChatCompletionParams {
|
|
12
|
+
messages: Message[];
|
|
13
|
+
/** Target model — omit to let Rouva route intelligently */
|
|
14
|
+
model?: string;
|
|
15
|
+
/** Maximum tokens to generate */
|
|
16
|
+
max_tokens?: number;
|
|
17
|
+
/** Sampling temperature 0–1 */
|
|
18
|
+
temperature?: number;
|
|
19
|
+
/** Stream the response */
|
|
20
|
+
stream?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ChatCompletionChoice {
|
|
23
|
+
index: number;
|
|
24
|
+
message: Message;
|
|
25
|
+
finish_reason: string | null;
|
|
26
|
+
}
|
|
27
|
+
interface ChatCompletionUsage {
|
|
28
|
+
prompt_tokens: number;
|
|
29
|
+
completion_tokens: number;
|
|
30
|
+
total_tokens: number;
|
|
31
|
+
}
|
|
32
|
+
interface ChatCompletion {
|
|
33
|
+
id: string;
|
|
34
|
+
object: 'chat.completion';
|
|
35
|
+
created: number;
|
|
36
|
+
model: string;
|
|
37
|
+
choices: ChatCompletionChoice[];
|
|
38
|
+
usage: ChatCompletionUsage;
|
|
39
|
+
/** Rouva metadata — cost, savings, routing decision */
|
|
40
|
+
_rouva?: RouvaResponseMeta;
|
|
41
|
+
}
|
|
42
|
+
interface RouvaResponseMeta {
|
|
43
|
+
/** Actual model used (may differ from requested when intelligently routed) */
|
|
44
|
+
model_used: string;
|
|
45
|
+
/** USD cost for this request */
|
|
46
|
+
cost: number;
|
|
47
|
+
/** USD saved vs your baseline model */
|
|
48
|
+
savings: number;
|
|
49
|
+
/** Whether intelligent routing selected the model */
|
|
50
|
+
intelligently_routed: boolean;
|
|
51
|
+
/** Task type classified by Rouva */
|
|
52
|
+
task_type: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare class Rouva {
|
|
56
|
+
private apiKey;
|
|
57
|
+
private baseURL;
|
|
58
|
+
readonly chat: {
|
|
59
|
+
completions: {
|
|
60
|
+
create(params: ChatCompletionParams): Promise<ChatCompletion | ReadableStream>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
constructor(options: RouvaOptions);
|
|
64
|
+
private _createChatCompletion;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { type ChatCompletion, type ChatCompletionChoice, type ChatCompletionParams, type ChatCompletionUsage, type Message, Rouva, type RouvaOptions, type RouvaResponseMeta };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Rouva: () => Rouva
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/client.ts
|
|
28
|
+
var DEFAULT_BASE_URL = "https://app.rouva.io";
|
|
29
|
+
var Rouva = class {
|
|
30
|
+
constructor(options) {
|
|
31
|
+
if (!options.apiKey) throw new Error("[Rouva] apiKey is required");
|
|
32
|
+
if (!options.apiKey.startsWith("rva_")) {
|
|
33
|
+
throw new Error("[Rouva] apiKey must start with rva_");
|
|
34
|
+
}
|
|
35
|
+
this.apiKey = options.apiKey;
|
|
36
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
37
|
+
this.chat = {
|
|
38
|
+
completions: {
|
|
39
|
+
create: (params) => this._createChatCompletion(params)
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async _createChatCompletion(params) {
|
|
44
|
+
const url = `${this.baseURL}/api/gateway/messages`;
|
|
45
|
+
const res = await fetch(url, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"x-api-key": this.apiKey
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify(params)
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const body = await res.text().catch(() => res.statusText);
|
|
55
|
+
throw new Error(`[Rouva] Gateway error ${res.status}: ${body}`);
|
|
56
|
+
}
|
|
57
|
+
if (params.stream) {
|
|
58
|
+
if (!res.body) throw new Error("[Rouva] No response body for streaming request");
|
|
59
|
+
return res.body;
|
|
60
|
+
}
|
|
61
|
+
return res.json();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
65
|
+
0 && (module.exports = {
|
|
66
|
+
Rouva
|
|
67
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://app.rouva.io";
|
|
3
|
+
var Rouva = class {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
if (!options.apiKey) throw new Error("[Rouva] apiKey is required");
|
|
6
|
+
if (!options.apiKey.startsWith("rva_")) {
|
|
7
|
+
throw new Error("[Rouva] apiKey must start with rva_");
|
|
8
|
+
}
|
|
9
|
+
this.apiKey = options.apiKey;
|
|
10
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
11
|
+
this.chat = {
|
|
12
|
+
completions: {
|
|
13
|
+
create: (params) => this._createChatCompletion(params)
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async _createChatCompletion(params) {
|
|
18
|
+
const url = `${this.baseURL}/api/gateway/messages`;
|
|
19
|
+
const res = await fetch(url, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"x-api-key": this.apiKey
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(params)
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const body = await res.text().catch(() => res.statusText);
|
|
29
|
+
throw new Error(`[Rouva] Gateway error ${res.status}: ${body}`);
|
|
30
|
+
}
|
|
31
|
+
if (params.stream) {
|
|
32
|
+
if (!res.body) throw new Error("[Rouva] No response body for streaming request");
|
|
33
|
+
return res.body;
|
|
34
|
+
}
|
|
35
|
+
return res.json();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
Rouva
|
|
40
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rouvanpm/rouva",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Node.js SDK for Rouva — managed AI gateway with intelligent routing and spend tracking",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"lint": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"openai",
|
|
29
|
+
"anthropic",
|
|
30
|
+
"routing",
|
|
31
|
+
"gateway",
|
|
32
|
+
"rouva"
|
|
33
|
+
],
|
|
34
|
+
"author": "Rouva <hello@rouva.io>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"vitest": "^1.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|