@x12i/openrouter-runtime 1.0.6 → 1.4.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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 x12i
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 x12i
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 CHANGED
@@ -1,140 +1,160 @@
1
- # @x12i/openrouter-runtime
2
-
3
- TypeScript runtime for executing OpenRouter calls with normalized request and response objects.
4
-
5
- It supports Chat Completions, Responses, OpenRouter server tools, local function tools, citation extraction, usage normalization, generated image extraction, patch proposal extraction, retries, and policy validation.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @x12i/openrouter-runtime
11
- ```
12
-
13
- ## Usage
14
-
15
- ```ts
16
- import { createOpenRouterRuntime } from "@x12i/openrouter-runtime";
17
-
18
- const runtime = createOpenRouterRuntime({
19
- apiKey: process.env.OPENROUTER_API_KEY!,
20
- defaults: {
21
- serverTools: {
22
- datetime: { mode: "allowed", timezone: "Asia/Jerusalem" }
23
- }
24
- }
25
- });
26
-
27
- const response = await runtime.run({
28
- model: "openai/gpt-5.2",
29
- messages: [{ role: "user", content: "What time is it?" }]
30
- });
31
-
32
- console.log(response.text);
33
- ```
34
-
35
- ## Console logging
36
-
37
- Turn on runtime console logs with:
38
-
39
- ```bash
40
- OPENROUTER_RUNTIME_LOGS=true
41
- ```
42
-
43
- When that env var is `true` / `1` / `yes` / `on`, and you do not pass a custom `logger`, the runtime logs to the console:
44
-
45
- - `runtime.request.started` — includes `streaming: false` and `entrypoint: "run"`
46
- - `runtime.request.compiled` — includes `bodyStream: false` and any streaming-related warning codes
47
- - `runtime.response.normalized`
48
- - `runtime.executeStreamingChat.called` — if someone hits the reserved streaming method
49
- - provider retry / function-tool events
50
-
51
- This package does **not** auto-load `.env`. Put the vars in your environment (shell export, process manager, or host `dotenv`) before calling `createOpenRouterRuntime()`. See `.env.example`.
52
-
53
- Example:
54
-
55
- ```bash
56
- OPEN_ROUTER_KEY=sk-or-...
57
- OPENROUTER_RUNTIME_LOGS=true
58
- ```
59
-
60
- You can still pass an explicit `logger` to override the console logger.
61
-
62
- ## Server Tools
63
-
64
- Enable OpenRouter server tools through `serverTools`:
65
-
66
- ```ts
67
- await runtime.run({
68
- model: "anthropic/claude-sonnet-4",
69
- messages: [{ role: "user", content: "Research recent OpenRouter web search changes." }],
70
- serverTools: {
71
- webSearch: { mode: "required", maxResults: 5 },
72
- webFetch: { mode: "allowed", maxContentTokens: 50000 }
73
- }
74
- });
75
- ```
76
-
77
- ### Citation Policy
78
-
79
- `defaults.requireCitationsWhenSearchUsed` controls the package-wide default for web-search citation enforcement. A request-level `serverTools.webSearch.requireCitations` value overrides that default:
80
-
81
- - `requireCitations: true`: if web search is used and no citations are extracted, the runtime emits `CITATIONS_REQUIRED_BUT_MISSING`.
82
- - `requireCitations: false`: disables citation enforcement for that request, even if the global default is `true`.
83
-
84
- When `defaults.onPolicyViolation` is `"throw"`, citation policy failures are returned as `errors[]` with `source: "policy"` and `status: "policy_violation"`. When it is `"return_error"`, they remain in `warnings[]`.
85
-
86
- `applyPatch` automatically selects the Responses API. The runtime returns patch proposals and never mutates files unless an explicit `patchApplier` is supplied.
87
-
88
- ## Function Tools
89
-
90
- ```ts
91
- const runtime = createOpenRouterRuntime({
92
- apiKey: process.env.OPENROUTER_API_KEY!,
93
- tools: {
94
- getCustomerRisk: async (args) => ({ score: 82, args })
95
- }
96
- });
97
- ```
98
-
99
- Function calls are executed locally and looped back to OpenRouter until a final response is produced or `maxToolIterations` is reached.
100
-
101
- ## Streaming
102
-
103
- ### `run()` is non-streaming — always
104
-
105
- `runtime.run(request)` never streams. It is the only supported execution path today.
106
-
107
- Use it for background tasks, graph nodes, tool calls, structured output, research jobs, classification, extraction, patch generation, and automation.
108
-
109
- ```ts
110
- const response = await runtime.run({
111
- model: "openai/gpt-5.2",
112
- prompt: "Summarize this document."
113
- });
114
- ```
115
-
116
- This sends a normal non-streaming OpenRouter request and returns a completed `RuntimeResponse`.
117
-
118
- `run()` always sends `stream: false` on the OpenRouter body. Any truthy `rawOpenRouterOverrides.stream` is overwritten and reported as `STREAMING_OVERRIDE_IGNORED_FOR_RUN`. Nested `serverTools.advisor.stream` is also forced off with `ADVISOR_STREAMING_IGNORED_FOR_RUN`.
119
-
120
- ### Streaming lives on a separate method: `executeStreamingChat()`
121
-
122
- Streaming is **not** available through `run()`, and the old `runtime.stream()` name was **removed on purpose**.
123
-
124
- Clients that still call `runtime.stream(...)` will break. That is intentional — so mistaken streaming usage is caught immediately.
125
-
126
- The reserved streaming entrypoint is:
127
-
128
- ```ts
129
- for await (const event of runtime.executeStreamingChat(request)) {
130
- // token deltas / stream events (not implemented yet)
131
- }
132
- ```
133
-
134
- Today, `executeStreamingChat()` throws `STREAMING_NOT_IMPLEMENTED`. Treat this package as a non-streaming runtime until that method is implemented.
135
-
136
- | Method | Streaming? | Status |
137
- | --- | --- | --- |
138
- | `runtime.run(request)` | No | Supported |
139
- | `runtime.executeStreamingChat(request)` | Yes (future) | Throws `STREAMING_NOT_IMPLEMENTED` |
140
- | `runtime.stream(request)` | — | **Removed** — breaks by design |
1
+ # @x12i/openrouter-runtime
2
+
3
+ TypeScript runtime for executing OpenRouter calls with normalized request and response objects.
4
+
5
+ It supports Chat Completions, Responses, OpenRouter server tools, local function tools, citation extraction, usage normalization, generated image extraction, patch proposal extraction, retries, and policy validation.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @x12i/openrouter-runtime
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createOpenRouterRuntime } from "@x12i/openrouter-runtime";
17
+
18
+ const runtime = createOpenRouterRuntime({
19
+ apiKey: process.env.OPENROUTER_API_KEY!,
20
+ defaults: {
21
+ serverTools: {
22
+ datetime: { mode: "allowed", timezone: "Asia/Jerusalem" }
23
+ }
24
+ }
25
+ });
26
+
27
+ const response = await runtime.run({
28
+ model: "openai/gpt-5.2",
29
+ messages: [{ role: "user", content: "What time is it?" }]
30
+ });
31
+
32
+ console.log(response.text);
33
+ ```
34
+
35
+ ## Console logging
36
+
37
+ Turn on runtime console logs with:
38
+
39
+ ```bash
40
+ OPENROUTER_RUNTIME_LOGS=true
41
+ ```
42
+
43
+ When that env var is `true` / `1` / `yes` / `on`, and you do not pass a custom `logger`, the runtime logs to the console:
44
+
45
+ - `runtime.request.started` — includes `streaming: false` and `entrypoint: "run"`
46
+ - `runtime.request.compiled` — includes `bodyStream: false` and any streaming-related warning codes
47
+ - `runtime.response.normalized`
48
+ - `runtime.executeStreamingChat.called` — if someone hits the reserved streaming method
49
+ - provider retry / function-tool events
50
+
51
+ This package does **not** auto-load `.env`. Put the vars in your environment (shell export, process manager, or host `dotenv`) before calling `createOpenRouterRuntime()`. See `.env.example`.
52
+
53
+ Example:
54
+
55
+ ```bash
56
+ OPEN_ROUTER_KEY=sk-or-...
57
+ OPENROUTER_RUNTIME_LOGS=true
58
+ ```
59
+
60
+ You can still pass an explicit `logger` to override the console logger.
61
+
62
+ ## App name and metadata
63
+
64
+ `agentId` is optional. When it is set, OpenRouter's activity log shows it in the **App** column. The runtime sends that value as `X-Title` and `X-OpenRouter-Title`, after `defaultHeaders`, so it wins over `appAttribution.appName` for that call. `appAttribution.siteUrl` is still sent as `HTTP-Referer` when set. Omit `agentId` when you do not have one.
65
+
66
+ `metadata` is optional. String, number, and boolean values, plus `agentId`, are stored as one record on the request `metadata` object: `m.0`, `m.1`, and so on. Read it back with `decodeProviderMetadata`. The original `metadata` object is still copied onto the normalized response. Objects and arrays are not stored in the record. The record must fit in 16 pieces of 256 characters.
67
+
68
+ ```ts
69
+ await runtime.run({
70
+ model: "openai/gpt-5.2",
71
+ prompt: "Explain the tradeoff in one paragraph.",
72
+ agentId: "agent-42",
73
+ metadata: { sessionId: "s-1" }
74
+ });
75
+ ```
76
+
77
+ ## Server Tools
78
+
79
+ Enable OpenRouter server tools through `serverTools`:
80
+
81
+ ```ts
82
+ await runtime.run({
83
+ model: "anthropic/claude-sonnet-4",
84
+ messages: [{ role: "user", content: "Research recent OpenRouter web search changes." }],
85
+ serverTools: {
86
+ webSearch: { mode: "required", maxResults: 5 },
87
+ webFetch: { mode: "allowed", maxContentTokens: 50000 }
88
+ }
89
+ });
90
+ ```
91
+
92
+ ### Citation Policy
93
+
94
+ `defaults.requireCitationsWhenSearchUsed` controls the package-wide default for web-search citation enforcement. A request-level `serverTools.webSearch.requireCitations` value overrides that default:
95
+
96
+ - `requireCitations: true`: if web search is used and no citations are extracted, the runtime emits `CITATIONS_REQUIRED_BUT_MISSING`.
97
+ - `requireCitations: false`: disables citation enforcement for that request, even if the global default is `true`.
98
+
99
+ When `defaults.onPolicyViolation` is `"throw"`, citation policy failures are returned as `errors[]` with `source: "policy"` and `status: "policy_violation"`. When it is `"return_error"`, they remain in `warnings[]`.
100
+
101
+ `applyPatch` automatically selects the Responses API. The runtime returns patch proposals and never mutates files unless an explicit `patchApplier` is supplied.
102
+
103
+ ## Function Tools
104
+
105
+ ```ts
106
+ const runtime = createOpenRouterRuntime({
107
+ apiKey: process.env.OPENROUTER_API_KEY!,
108
+ tools: {
109
+ getCustomerRisk: async (args) => ({ score: 82, args })
110
+ }
111
+ });
112
+ ```
113
+
114
+ Function calls are executed locally and looped back to OpenRouter until a final response is produced or `maxToolIterations` is reached.
115
+
116
+ ## Streaming
117
+
118
+ Streaming is a **separate API**. It is never an argument on `run()`, never the default, and never available through the removed `stream()` name.
119
+
120
+ ### `run()` — non-streaming only
121
+
122
+ ```ts
123
+ const response = await runtime.run({
124
+ model: "openai/gpt-5.2",
125
+ prompt: "Summarize this document."
126
+ });
127
+ ```
128
+
129
+ - Always sends `stream: false`
130
+ - Returns a completed `RuntimeResponse`
131
+ - Use for tools, research, extraction, patches, automation
132
+
133
+ Any truthy `rawOpenRouterOverrides.stream` is overwritten (`STREAMING_OVERRIDE_IGNORED_FOR_RUN`). Nested `advisor.stream` is forced off (`ADVISOR_STREAMING_IGNORED_FOR_RUN`).
134
+
135
+ ### `executeStreamingChat()` — streaming only
136
+
137
+ ```ts
138
+ for await (const event of runtime.executeStreamingChat({
139
+ model: "openai/gpt-5.2",
140
+ prompt: "Say hello"
141
+ })) {
142
+ if (event.type === "stream.text.delta") {
143
+ process.stdout.write(event.data.text);
144
+ }
145
+ if (event.type === "stream.done") {
146
+ console.log("\nfinal:", event.data.text);
147
+ }
148
+ }
149
+ ```
150
+
151
+ - Always sends `stream: true` (Chat Completions SSE)
152
+ - Yields typed events: `stream.start`, `stream.text.delta`, `stream.tool_call.delta`, `stream.usage`, `stream.warning`, `stream.error`, `stream.done`
153
+ - Chat Completions only — Responses / `applyPatch` must use `run()`
154
+ - Local function-tool loops are not run here; use `run()` for tool iteration
155
+
156
+ | Method | Streaming? | Notes |
157
+ | --- | --- | --- |
158
+ | `runtime.run(request)` | No | Default execution path |
159
+ | `runtime.executeStreamingChat(request)` | Yes | Explicit streaming API |
160
+ | `runtime.stream(request)` | — | **Removed** — breaks by design |