@markbrutx/promptbook-openrouter 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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/adapter.d.ts +22 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +78 -0
- package/dist/adapter.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/src/adapter.ts +110 -0
- package/src/index.ts +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 markbrutx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @markbrutx/promptbook-openrouter
|
|
2
|
+
|
|
3
|
+
OpenRouter `ModelAdapter` for [`@markbrutx/promptbook-core`](https://www.npmjs.com/package/@markbrutx/promptbook-core)
|
|
4
|
+
eval. The network lives here so the core stays pure. Part of
|
|
5
|
+
[promptbook](https://github.com/markbrutx/promptbook).
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm i @markbrutx/promptbook-openrouter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { evaluate } from "@markbrutx/promptbook-core";
|
|
15
|
+
import { openRouterAdapter } from "@markbrutx/promptbook-openrouter";
|
|
16
|
+
|
|
17
|
+
const adapter = openRouterAdapter({
|
|
18
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
19
|
+
model: "openai/gpt-4o-mini",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const report = await evaluate({ promptsDir: "./prompts", adapter /* … */ });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`core` only ever sees the `ModelAdapter` interface; this package maps a request
|
|
26
|
+
to OpenRouter's HTTP API and back. Swap it for any provider by implementing the
|
|
27
|
+
same interface.
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ModelAdapter } from "@markbrutx/promptbook-core";
|
|
2
|
+
export interface OpenRouterOptions {
|
|
3
|
+
/** Default model id, e.g. "openai/gpt-4o-mini". Overridable per request. */
|
|
4
|
+
model: string;
|
|
5
|
+
/** API key; falls back to `process.env.OPENROUTER_API_KEY` at call time. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/** Override the API base URL (e.g. a proxy). */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** Inject a `fetch` implementation; defaults to the global `fetch`. */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build a {@link ModelAdapter} backed by OpenRouter's chat-completions API.
|
|
14
|
+
*
|
|
15
|
+
* This is the one piece that performs network IO; the eval engine in
|
|
16
|
+
* `@markbrutx/promptbook-core` only sees the adapter interface. The request maps
|
|
17
|
+
* `{ system, input }` to `system`/`user` messages and parses the first
|
|
18
|
+
* choice's content back into `{ text, usage, raw }`. A missing key (option or
|
|
19
|
+
* env) or a non-2xx response raises a clear error.
|
|
20
|
+
*/
|
|
21
|
+
export declare function openRouterAdapter(options: OpenRouterOptions): ModelAdapter;
|
|
22
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2C,MAAM,4BAA4B,CAAC;AAQxG,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CA6C1E"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** OpenRouter chat-completions base URL (no trailing slash). */
|
|
2
|
+
const DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
3
|
+
/** Environment variable consulted when no explicit `apiKey` is given. */
|
|
4
|
+
const API_KEY_ENV = "OPENROUTER_API_KEY";
|
|
5
|
+
function mapUsage(usage) {
|
|
6
|
+
if (!usage) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const mapped = {};
|
|
10
|
+
if (typeof usage.prompt_tokens === "number") {
|
|
11
|
+
mapped.promptTokens = usage.prompt_tokens;
|
|
12
|
+
}
|
|
13
|
+
if (typeof usage.completion_tokens === "number") {
|
|
14
|
+
mapped.completionTokens = usage.completion_tokens;
|
|
15
|
+
}
|
|
16
|
+
if (typeof usage.total_tokens === "number") {
|
|
17
|
+
mapped.totalTokens = usage.total_tokens;
|
|
18
|
+
}
|
|
19
|
+
return mapped;
|
|
20
|
+
}
|
|
21
|
+
async function readErrorBody(response) {
|
|
22
|
+
try {
|
|
23
|
+
const body = (await response.text()).trim();
|
|
24
|
+
return body.length > 0 ? `: ${body}` : "";
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a {@link ModelAdapter} backed by OpenRouter's chat-completions API.
|
|
32
|
+
*
|
|
33
|
+
* This is the one piece that performs network IO; the eval engine in
|
|
34
|
+
* `@markbrutx/promptbook-core` only sees the adapter interface. The request maps
|
|
35
|
+
* `{ system, input }` to `system`/`user` messages and parses the first
|
|
36
|
+
* choice's content back into `{ text, usage, raw }`. A missing key (option or
|
|
37
|
+
* env) or a non-2xx response raises a clear error.
|
|
38
|
+
*/
|
|
39
|
+
export function openRouterAdapter(options) {
|
|
40
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
41
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
42
|
+
return {
|
|
43
|
+
async complete(request) {
|
|
44
|
+
const apiKey = options.apiKey ?? process.env[API_KEY_ENV];
|
|
45
|
+
if (!apiKey) {
|
|
46
|
+
throw new Error(`OpenRouter API key missing: pass { apiKey } or set the ${API_KEY_ENV} environment variable.`);
|
|
47
|
+
}
|
|
48
|
+
const messages = [];
|
|
49
|
+
if (request.system.length > 0) {
|
|
50
|
+
messages.push({ role: "system", content: request.system });
|
|
51
|
+
}
|
|
52
|
+
messages.push({ role: "user", content: request.input });
|
|
53
|
+
const response = await doFetch(`${baseUrl}/chat/completions`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
"content-type": "application/json",
|
|
57
|
+
authorization: `Bearer ${apiKey}`,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({ model: request.model ?? options.model, messages }),
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const body = await readErrorBody(response);
|
|
63
|
+
throw new Error(`OpenRouter request failed: ${response.status} ${response.statusText}${body}`);
|
|
64
|
+
}
|
|
65
|
+
const data = (await response.json());
|
|
66
|
+
const result = {
|
|
67
|
+
text: data.choices?.[0]?.message?.content ?? "",
|
|
68
|
+
raw: data,
|
|
69
|
+
};
|
|
70
|
+
const usage = mapUsage(data.usage);
|
|
71
|
+
if (usage !== undefined) {
|
|
72
|
+
result.usage = usage;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@markbrutx/promptbook-openrouter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenRouter ModelAdapter for @markbrutx/promptbook-core eval. Network lives here; core stays pure.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
25
|
+
"typecheck": "tsgo --noEmit -p tsconfig.json",
|
|
26
|
+
"test": "vitest --run",
|
|
27
|
+
"check": "biome check ."
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@markbrutx/promptbook-core": "^0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@biomejs/biome": "latest",
|
|
34
|
+
"@types/node": "^20.14.0",
|
|
35
|
+
"@typescript/native-preview": "latest",
|
|
36
|
+
"typescript": "^5.6.0",
|
|
37
|
+
"vitest": "^2.1.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.6"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ModelAdapter, ModelRequest, ModelResponse, ModelUsage } from "@markbrutx/promptbook-core";
|
|
2
|
+
|
|
3
|
+
/** OpenRouter chat-completions base URL (no trailing slash). */
|
|
4
|
+
const DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
5
|
+
|
|
6
|
+
/** Environment variable consulted when no explicit `apiKey` is given. */
|
|
7
|
+
const API_KEY_ENV = "OPENROUTER_API_KEY";
|
|
8
|
+
|
|
9
|
+
export interface OpenRouterOptions {
|
|
10
|
+
/** Default model id, e.g. "openai/gpt-4o-mini". Overridable per request. */
|
|
11
|
+
model: string;
|
|
12
|
+
/** API key; falls back to `process.env.OPENROUTER_API_KEY` at call time. */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** Override the API base URL (e.g. a proxy). */
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/** Inject a `fetch` implementation; defaults to the global `fetch`. */
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Shape of the fields we read from an OpenRouter chat-completions reply. */
|
|
21
|
+
interface ChatCompletion {
|
|
22
|
+
choices?: { message?: { content?: string } }[];
|
|
23
|
+
usage?: {
|
|
24
|
+
prompt_tokens?: number;
|
|
25
|
+
completion_tokens?: number;
|
|
26
|
+
total_tokens?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapUsage(usage: ChatCompletion["usage"]): ModelUsage | undefined {
|
|
31
|
+
if (!usage) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const mapped: ModelUsage = {};
|
|
35
|
+
if (typeof usage.prompt_tokens === "number") {
|
|
36
|
+
mapped.promptTokens = usage.prompt_tokens;
|
|
37
|
+
}
|
|
38
|
+
if (typeof usage.completion_tokens === "number") {
|
|
39
|
+
mapped.completionTokens = usage.completion_tokens;
|
|
40
|
+
}
|
|
41
|
+
if (typeof usage.total_tokens === "number") {
|
|
42
|
+
mapped.totalTokens = usage.total_tokens;
|
|
43
|
+
}
|
|
44
|
+
return mapped;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readErrorBody(response: Response): Promise<string> {
|
|
48
|
+
try {
|
|
49
|
+
const body = (await response.text()).trim();
|
|
50
|
+
return body.length > 0 ? `: ${body}` : "";
|
|
51
|
+
} catch {
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a {@link ModelAdapter} backed by OpenRouter's chat-completions API.
|
|
58
|
+
*
|
|
59
|
+
* This is the one piece that performs network IO; the eval engine in
|
|
60
|
+
* `@markbrutx/promptbook-core` only sees the adapter interface. The request maps
|
|
61
|
+
* `{ system, input }` to `system`/`user` messages and parses the first
|
|
62
|
+
* choice's content back into `{ text, usage, raw }`. A missing key (option or
|
|
63
|
+
* env) or a non-2xx response raises a clear error.
|
|
64
|
+
*/
|
|
65
|
+
export function openRouterAdapter(options: OpenRouterOptions): ModelAdapter {
|
|
66
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
67
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
async complete(request: ModelRequest): Promise<ModelResponse> {
|
|
71
|
+
const apiKey = options.apiKey ?? process.env[API_KEY_ENV];
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`OpenRouter API key missing: pass { apiKey } or set the ${API_KEY_ENV} environment variable.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const messages: { role: string; content: string }[] = [];
|
|
79
|
+
if (request.system.length > 0) {
|
|
80
|
+
messages.push({ role: "system", content: request.system });
|
|
81
|
+
}
|
|
82
|
+
messages.push({ role: "user", content: request.input });
|
|
83
|
+
|
|
84
|
+
const response = await doFetch(`${baseUrl}/chat/completions`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"content-type": "application/json",
|
|
88
|
+
authorization: `Bearer ${apiKey}`,
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({ model: request.model ?? options.model, messages }),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const body = await readErrorBody(response);
|
|
95
|
+
throw new Error(`OpenRouter request failed: ${response.status} ${response.statusText}${body}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const data = (await response.json()) as ChatCompletion;
|
|
99
|
+
const result: ModelResponse = {
|
|
100
|
+
text: data.choices?.[0]?.message?.content ?? "",
|
|
101
|
+
raw: data,
|
|
102
|
+
};
|
|
103
|
+
const usage = mapUsage(data.usage);
|
|
104
|
+
if (usage !== undefined) {
|
|
105
|
+
result.usage = usage;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
package/src/index.ts
ADDED