@aeye/openrouter 0.5.0 → 0.7.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 +652 -643
- package/aeye-openrouter.md +179 -175
- package/package.json +71 -71
package/aeye-openrouter.md
CHANGED
|
@@ -1,175 +1,179 @@
|
|
|
1
|
-
# @aeye/openrouter
|
|
2
|
-
|
|
3
|
-
One-line purpose: OpenRouter provider for `@aeye/ai` — one API key, hundreds of models from many upstream providers (OpenAI, Anthropic, Google, xAI, Meta, Mistral, …) with provider routing, fallback models, plugins, server tools, ZDR, reasoning, media endpoints, and real cost reporting. Built as a subclass of `@aeye/openai`'s `OpenAIProvider`.
|
|
4
|
-
|
|
5
|
-
## When to use
|
|
6
|
-
|
|
7
|
-
- You want access to many models/providers through a single endpoint and key.
|
|
8
|
-
- You need routing control (preferred providers, fallbacks, price/throughput/latency sorting) or Zero Data Retention.
|
|
9
|
-
- You want a dynamic model registry with pricing/capabilities pulled from OpenRouter.
|
|
10
|
-
|
|
11
|
-
It also serves image generation/editing (through chat with the image modality), speech, transcription, and embeddings (through OpenRouter's OpenAI-compatible endpoints). Image and transcription streaming are not offered; those fall back to the non-streaming methods.
|
|
12
|
-
|
|
13
|
-
## Install / import
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @aeye/openrouter
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
`@aeye/openai`, `@aeye/ai`, `@aeye/core`, and `openai` (v7) ship as dependencies. Requires Node >= 22.
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import {
|
|
23
|
-
OpenRouterProvider,
|
|
24
|
-
type OpenRouterConfig,
|
|
25
|
-
// dynamic model registry
|
|
26
|
-
OpenRouterModelSource,
|
|
27
|
-
type OpenRouterSourceConfig,
|
|
28
|
-
fetchModels,
|
|
29
|
-
fetchZDRModels,
|
|
30
|
-
fetchEndpointMetrics,
|
|
31
|
-
convertOpenRouterModel,
|
|
32
|
-
} from '@aeye/openrouter';
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
The provider's registry name is `openrouter`.
|
|
36
|
-
|
|
37
|
-
## Register with an `@aeye/ai` instance
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { AI } from '@aeye/ai';
|
|
41
|
-
import { OpenRouterProvider } from '@aeye/openrouter';
|
|
42
|
-
|
|
43
|
-
const openrouter = new OpenRouterProvider({
|
|
44
|
-
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const ai = AI.with().providers({ openrouter }).create({ /* ... */ });
|
|
48
|
-
|
|
49
|
-
// Model ids use provider/model form
|
|
50
|
-
await ai.chat.get(
|
|
51
|
-
{ messages: [{ role: 'user', content: 'Hi' }] },
|
|
52
|
-
{ metadata: { model: 'anthropic/claude-sonnet-4.5' } }
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
await ai.chat.get(
|
|
56
|
-
{ messages: [{ role: 'user', content: 'Hi' }] },
|
|
57
|
-
{ metadata: { model: 'google/gemini-2.5-flash' } }
|
|
58
|
-
);
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Authentication / credentials
|
|
62
|
-
|
|
63
|
-
- `apiKey` is required on the config (inherited from `OpenAIConfig`). Use `process.env.OPENROUTER_API_KEY`.
|
|
64
|
-
- `createClient` sets the base URL to `https://openrouter.ai/api/v1` (override via `baseURL`).
|
|
65
|
-
- The standalone `OpenRouterModelSource`, `fetchModels`, and `fetchZDRModels` will fall back to `process.env.OPENROUTER_API_KEY` if no key is passed.
|
|
66
|
-
- Optional attribution headers: `defaultParams.siteUrl` → `HTTP-Referer`, `defaultParams.appName` → `X-Title`.
|
|
67
|
-
|
|
68
|
-
## Configuration (`OpenRouterConfig`)
|
|
69
|
-
|
|
70
|
-
`OpenRouterConfig extends Omit<OpenAIConfig, 'api'>`, so the OpenAI fields apply (`apiKey`, `baseURL`, `organization`, `project`, `store`, `retry`, `retryEvents`, `hooks`, `defaultModels`) except `api`: requests always go through Chat Completions. It adds `defaultParams`:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
const openrouter = new OpenRouterProvider({
|
|
74
|
-
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
75
|
-
defaultParams: {
|
|
76
|
-
siteUrl: 'https://myapp.com', // -> HTTP-Referer
|
|
77
|
-
appName: 'my-app', // -> X-Title
|
|
78
|
-
providers: {
|
|
79
|
-
order: ['Anthropic', 'OpenAI'], // preferred upstream order
|
|
80
|
-
allowFallbacks: true, // -> allow_fallbacks
|
|
81
|
-
requireParameters: false, // -> require_parameters
|
|
82
|
-
dataCollection: 'deny', // 'deny' | 'allow' -> data_collection
|
|
83
|
-
zdr: true, // zero data retention
|
|
84
|
-
enforceDistillableText: false, // -> enforce_distillable_text
|
|
85
|
-
only: ['Anthropic'], // whitelist upstreams
|
|
86
|
-
ignore: ['Together'], // blacklist upstreams
|
|
87
|
-
quantizations: ['bf16', 'fp16'], // int4|int8|fp4|mxfp4|nvfp4|fp6|fp8|mxfp8|fp16|bf16|fp32|unknown
|
|
88
|
-
sort: 'price', // 'price' | 'throughput' | 'latency' | 'exacto', or { by, partition: 'model' | 'none' }
|
|
89
|
-
preferredMinThroughput: 50, // -> preferred_min_throughput (tokens/s, median)
|
|
90
|
-
preferredMaxLatency: 2, // -> preferred_max_latency (seconds, median)
|
|
91
|
-
maxPrice: { prompt: 10, completion: 30, image: 0 }, // $/M tokens (image: $/image); also audio, request
|
|
92
|
-
},
|
|
93
|
-
fallbackModels: ['openai/gpt-5.4-mini'], // -> models
|
|
94
|
-
plugins: [{ id: 'response-healing' }], // file-parser | response-healing | context-compression
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`defaultParams.providers` is mapped onto OpenRouter's `provider` request object (camelCase → snake_case is handled internally). `transforms` is deprecated: `transforms: ['middle-out']` is sent as the `context-compression` plugin. `requireParameters` left undefined is switched on automatically for strict `json_schema` requests.
|
|
100
|
-
|
|
101
|
-
## Provider routing
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
const openrouter = new OpenRouterProvider({
|
|
105
|
-
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
106
|
-
defaultParams: {
|
|
107
|
-
providers: {
|
|
108
|
-
order: ['Anthropic'], // try Anthropic first
|
|
109
|
-
allowFallbacks: true, // then fall back to others
|
|
110
|
-
sort: 'throughput', // optimize for speed
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Reasoning
|
|
117
|
-
|
|
118
|
-
If a request sets `request.reason`, the provider emits OpenRouter's `reasoning` object: `enabled: true`, either `max_tokens` (from `maxTokens`) or `effort` (clamped to the model's `reasoningEfforts`), never both, plus `exclude`, `summary`, `mode`, and `context`. No separate `reasoning_effort` is sent. Reasoning text and `reasoning_details` are parsed back onto `response.reasoning` (and streamed via chunks)
|
|
119
|
-
|
|
120
|
-
## Server tools and other request features
|
|
121
|
-
|
|
122
|
-
- `hostedTools` run as OpenRouter server tools through chat: `web_search` → `openrouter:web_search` (`maxResults`, `allowedDomains`, `blockedDomains` → `excluded_domains`), `web_fetch` → `openrouter:web_fetch`, `image_generation` → `openrouter:image_generation`. `file_search`, `code_interpreter`, and `mcp` throw `ProviderError`. Calls are counted in `usage.tools`.
|
|
123
|
-
- `cacheBreakpoint` on content goes as `cache_control: { type: 'ephemeral' }`.
|
|
124
|
-
- `modalities` with `'image'` is passed through; images come back on `response.images` (streamed or not).
|
|
125
|
-
- `userKey` goes as `user`. `moderate` is not sent (OpenRouter ignores OpenAI's inline moderation).
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
-
|
|
1
|
+
# @aeye/openrouter
|
|
2
|
+
|
|
3
|
+
One-line purpose: OpenRouter provider for `@aeye/ai` — one API key, hundreds of models from many upstream providers (OpenAI, Anthropic, Google, xAI, Meta, Mistral, …) with provider routing, fallback models, plugins, server tools, ZDR, reasoning, media endpoints, and real cost reporting. Built as a subclass of `@aeye/openai`'s `OpenAIProvider`.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- You want access to many models/providers through a single endpoint and key.
|
|
8
|
+
- You need routing control (preferred providers, fallbacks, price/throughput/latency sorting) or Zero Data Retention.
|
|
9
|
+
- You want a dynamic model registry with pricing/capabilities pulled from OpenRouter.
|
|
10
|
+
|
|
11
|
+
It also serves image generation/editing (through chat with the image modality), speech, transcription, and embeddings (through OpenRouter's OpenAI-compatible endpoints). Image and transcription streaming are not offered; those fall back to the non-streaming methods.
|
|
12
|
+
|
|
13
|
+
## Install / import
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @aeye/openrouter
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`@aeye/openai`, `@aeye/ai`, `@aeye/core`, and `openai` (v7) ship as dependencies. Requires Node >= 22.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {
|
|
23
|
+
OpenRouterProvider,
|
|
24
|
+
type OpenRouterConfig,
|
|
25
|
+
// dynamic model registry
|
|
26
|
+
OpenRouterModelSource,
|
|
27
|
+
type OpenRouterSourceConfig,
|
|
28
|
+
fetchModels,
|
|
29
|
+
fetchZDRModels,
|
|
30
|
+
fetchEndpointMetrics,
|
|
31
|
+
convertOpenRouterModel,
|
|
32
|
+
} from '@aeye/openrouter';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The provider's registry name is `openrouter`.
|
|
36
|
+
|
|
37
|
+
## Register with an `@aeye/ai` instance
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { AI } from '@aeye/ai';
|
|
41
|
+
import { OpenRouterProvider } from '@aeye/openrouter';
|
|
42
|
+
|
|
43
|
+
const openrouter = new OpenRouterProvider({
|
|
44
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const ai = AI.with().providers({ openrouter }).create({ /* ... */ });
|
|
48
|
+
|
|
49
|
+
// Model ids use provider/model form
|
|
50
|
+
await ai.chat.get(
|
|
51
|
+
{ messages: [{ role: 'user', content: 'Hi' }] },
|
|
52
|
+
{ metadata: { model: 'anthropic/claude-sonnet-4.5' } }
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
await ai.chat.get(
|
|
56
|
+
{ messages: [{ role: 'user', content: 'Hi' }] },
|
|
57
|
+
{ metadata: { model: 'google/gemini-2.5-flash' } }
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Authentication / credentials
|
|
62
|
+
|
|
63
|
+
- `apiKey` is required on the config (inherited from `OpenAIConfig`). Use `process.env.OPENROUTER_API_KEY`.
|
|
64
|
+
- `createClient` sets the base URL to `https://openrouter.ai/api/v1` (override via `baseURL`).
|
|
65
|
+
- The standalone `OpenRouterModelSource`, `fetchModels`, and `fetchZDRModels` will fall back to `process.env.OPENROUTER_API_KEY` if no key is passed.
|
|
66
|
+
- Optional attribution headers: `defaultParams.siteUrl` → `HTTP-Referer`, `defaultParams.appName` → `X-Title`.
|
|
67
|
+
|
|
68
|
+
## Configuration (`OpenRouterConfig`)
|
|
69
|
+
|
|
70
|
+
`OpenRouterConfig extends Omit<OpenAIConfig, 'api'>`, so the OpenAI fields apply (`apiKey`, `baseURL`, `organization`, `project`, `store`, `retry`, `retryEvents`, `hooks`, `defaultModels`) except `api`: requests always go through Chat Completions. It adds `defaultParams`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const openrouter = new OpenRouterProvider({
|
|
74
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
75
|
+
defaultParams: {
|
|
76
|
+
siteUrl: 'https://myapp.com', // -> HTTP-Referer
|
|
77
|
+
appName: 'my-app', // -> X-Title
|
|
78
|
+
providers: {
|
|
79
|
+
order: ['Anthropic', 'OpenAI'], // preferred upstream order
|
|
80
|
+
allowFallbacks: true, // -> allow_fallbacks
|
|
81
|
+
requireParameters: false, // -> require_parameters
|
|
82
|
+
dataCollection: 'deny', // 'deny' | 'allow' -> data_collection
|
|
83
|
+
zdr: true, // zero data retention
|
|
84
|
+
enforceDistillableText: false, // -> enforce_distillable_text
|
|
85
|
+
only: ['Anthropic'], // whitelist upstreams
|
|
86
|
+
ignore: ['Together'], // blacklist upstreams
|
|
87
|
+
quantizations: ['bf16', 'fp16'], // int4|int8|fp4|mxfp4|nvfp4|fp6|fp8|mxfp8|fp16|bf16|fp32|unknown
|
|
88
|
+
sort: 'price', // 'price' | 'throughput' | 'latency' | 'exacto', or { by, partition: 'model' | 'none' }
|
|
89
|
+
preferredMinThroughput: 50, // -> preferred_min_throughput (tokens/s, median)
|
|
90
|
+
preferredMaxLatency: 2, // -> preferred_max_latency (seconds, median)
|
|
91
|
+
maxPrice: { prompt: 10, completion: 30, image: 0 }, // $/M tokens (image: $/image); also audio, request
|
|
92
|
+
},
|
|
93
|
+
fallbackModels: ['openai/gpt-5.4-mini'], // -> models
|
|
94
|
+
plugins: [{ id: 'response-healing' }], // file-parser | response-healing | context-compression
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`defaultParams.providers` is mapped onto OpenRouter's `provider` request object (camelCase → snake_case is handled internally). `transforms` is deprecated: `transforms: ['middle-out']` is sent as the `context-compression` plugin. `requireParameters` left undefined is switched on automatically for strict `json_schema` requests.
|
|
100
|
+
|
|
101
|
+
## Provider routing
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const openrouter = new OpenRouterProvider({
|
|
105
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
106
|
+
defaultParams: {
|
|
107
|
+
providers: {
|
|
108
|
+
order: ['Anthropic'], // try Anthropic first
|
|
109
|
+
allowFallbacks: true, // then fall back to others
|
|
110
|
+
sort: 'throughput', // optimize for speed
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Reasoning
|
|
117
|
+
|
|
118
|
+
If a request sets `request.reason`, the provider emits OpenRouter's `reasoning` object: `enabled: true`, either `max_tokens` (from `maxTokens`) or `effort` (clamped to the model's `reasoningEfforts`), never both, plus `exclude`, `summary`, `mode`, and `context`. No separate `reasoning_effort` is sent. Reasoning text and `reasoning_details` are parsed back onto `response.reasoning` (and streamed via chunks) with `provider: 'openrouter'`, and replayed on the same assistant message on later turns. Reasoning that names another provider (OpenAI directly, Bedrock) is not replayed.
|
|
119
|
+
|
|
120
|
+
## Server tools and other request features
|
|
121
|
+
|
|
122
|
+
- `hostedTools` run as OpenRouter server tools through chat: `web_search` → `openrouter:web_search` (`maxResults`, `allowedDomains`, `blockedDomains` → `excluded_domains`), `web_fetch` → `openrouter:web_fetch`, `image_generation` → `openrouter:image_generation`. `file_search`, `code_interpreter`, and `mcp` throw `ProviderError`. Calls are counted in `usage.tools`.
|
|
123
|
+
- `cacheBreakpoint` on content goes as `cache_control: { type: 'ephemeral' }`.
|
|
124
|
+
- `modalities` with `'image'` is passed through; images come back on `response.images` (streamed or not).
|
|
125
|
+
- `userKey` goes as `user`. `moderate` is not sent (OpenRouter ignores OpenAI's inline moderation).
|
|
126
|
+
- A strict tool adds the `x-anthropic-beta: structured-outputs-2025-11-13` header (via `chatHeaders`), without which OpenRouter strips `strict` from tools bound for Anthropic.
|
|
127
|
+
- A JSON or schema response format adds a `Respond in JSON.` system message when no message mentions JSON (Alibaba's Qwen rejects JSON mode otherwise).
|
|
128
|
+
- Assistant turns of only tool calls are sent with no content (Qwen's upstream rejects empty text).
|
|
129
|
+
- `rerank` (`/rerank`) and `decide` (alpha decisions endpoint, System One shape) run through `hooks.rerank` / `hooks.decide`. Batches go inline to `/batches`; the whole batch's cost comes back as `job.cost` (results carry token counts only).
|
|
130
|
+
|
|
131
|
+
## Dynamic model registry (`OpenRouterModelSource`)
|
|
132
|
+
|
|
133
|
+
Use OpenRouter as a `ModelSource` to populate the registry with live pricing, capabilities, context windows, and ZDR flags. It lists every output modality (chat, image, speech, transcription, and embedding models); capabilities come from each modality (`vision`, `files`, `videoInput`, `hearing`, `image`, `audio`, `embedding`); pricing covers cache read/write, audio, image tokens, and web search per call; `reasoningEfforts` and `retiresAt` (from `expiration_date`) are filled in:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { OpenRouterModelSource } from '@aeye/openrouter';
|
|
137
|
+
|
|
138
|
+
const ai = AI.with()
|
|
139
|
+
.providers({ openrouter })
|
|
140
|
+
.create({
|
|
141
|
+
modelSources: [
|
|
142
|
+
new OpenRouterModelSource({
|
|
143
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
144
|
+
includeZDR: true, // default true; fetches the ZDR endpoint too
|
|
145
|
+
}),
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await ai.models.refresh();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`fetchModels(apiKey?)`, `fetchZDRModels(apiKey?)`, and `fetchEndpointMetrics(modelId, apiKey?)` (best median latency in ms, throughput in tokens/s, and uptime across a model's providers) are exported for direct use.
|
|
153
|
+
|
|
154
|
+
## Supported capabilities
|
|
155
|
+
|
|
156
|
+
Capabilities depend on the selected model, but the provider class supports: **chat, streaming, tools, hosted (server) tools, structured output / JSON mode, reasoning, vision, files, video input, image generation and editing, audio output, speech, transcription, embeddings** (per model). Capabilities are auto-detected from each model's input/output modalities and `supported_parameters` during model conversion.
|
|
157
|
+
|
|
158
|
+
Default models: `google/gemini-2.5-flash-image` (image generate/edit), `openai/gpt-5.4-mini` (image analysis), `openai/gpt-transcribe`, `openai/text-embedding-3-small`, `cohere/rerank-v3.5` (rerank), `typesafe/jev-1.14` (decision); speech and chat have none.
|
|
159
|
+
|
|
160
|
+
Not offered (overridden to `undefined`, so the non-streaming method is used): image generation streaming, image edit streaming, transcription streaming.
|
|
161
|
+
|
|
162
|
+
## Cost tracking
|
|
163
|
+
|
|
164
|
+
OpenRouter returns actual spend, surfaced on usage:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const response = await ai.chat.get({ messages });
|
|
168
|
+
console.log(response.usage?.cost); // real cost reported by OpenRouter, including the upstream bill for BYOK requests
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
(Available on both non-streaming responses and streamed usage chunks.)
|
|
172
|
+
|
|
173
|
+
## Gotchas
|
|
174
|
+
|
|
175
|
+
- **Images go through chat.** `generateImage` / `editImage` send a chat request with `modalities: ['image', 'text']` (running through the chat hooks); `size` becomes an `image_config.aspect_ratio`, and an edit's `image` + `references` are sent as input images. GPT Image options such as `quality` and `outputFormat` are not sent.
|
|
176
|
+
- Model ids are `provider/model` slugs (e.g. `anthropic/claude-sonnet-4.5`), not raw OpenAI ids.
|
|
177
|
+
- Strict/structured output behavior is inherited from `OpenAIProvider` (best-effort, silent fallback to the family's non-strict descriptor), but the accepted dialects are widened here: `supportedStrictFamilies = {'openai', 'anthropic', 'google', 'xai'}`, because the model id's `family/…` prefix names the upstream that actually serves the request. This applies to **tool** schemas as well as `response_format`; before 0.4.x the inherited `{'openai'}` quietly forced every non-OpenAI model's tools through LENIENT.
|
|
178
|
+
- Assistant reasoning rides on its own assistant message (with that turn's text and tool calls), once even when the message is split into parts; it is not split into a separate message.
|
|
179
|
+
- ZDR (`zdr: true` / `dataCollection: 'deny'`) only constrains upstreams that honor it; combine with `only`/`ignore` for strict guarantees.
|
package/package.json
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aeye/openrouter",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "@aeye OpenRouter - OpenRouter integration for @aeye",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"default": "./dist/index.js"
|
|
13
|
-
},
|
|
14
|
-
"require": {
|
|
15
|
-
"types": "./dist/index.d.ts",
|
|
16
|
-
"default": "./dist/index.js"
|
|
17
|
-
},
|
|
18
|
-
"default": "./dist/index.js"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"test": "jest",
|
|
24
|
-
"test:unit": "jest --testPathIgnorePatterns=__integration__",
|
|
25
|
-
"test:integration": "jest --testPathPattern=__integration__",
|
|
26
|
-
"test:coverage": "jest --coverage --testPathIgnorePatterns=__integration__",
|
|
27
|
-
"test:watch": "jest --watch",
|
|
28
|
-
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
29
|
-
"typecheck": "tsc --noEmit"
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"ai",
|
|
33
|
-
"openrouter",
|
|
34
|
-
"typescript"
|
|
35
|
-
],
|
|
36
|
-
"author": "ClickerMonkey",
|
|
37
|
-
"license": "GPL-3.0",
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "https://github.com/ClickerMonkey/aeye.git",
|
|
41
|
-
"directory": "packages/openrouter"
|
|
42
|
-
},
|
|
43
|
-
"files": [
|
|
44
|
-
"dist",
|
|
45
|
-
"README.md",
|
|
46
|
-
"LICENSE",
|
|
47
|
-
"aeye-*.md"
|
|
48
|
-
],
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"@aeye/ai": "^0.
|
|
51
|
-
"@aeye/core": "^0.
|
|
52
|
-
"@aeye/openai": "^0.
|
|
53
|
-
"openai": "^7.45.1"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/jest": "^30.0.0",
|
|
57
|
-
"jest": "^30.2.0",
|
|
58
|
-
"ts-jest": "^29.4.5",
|
|
59
|
-
"typescript": "^5.9.3"
|
|
60
|
-
},
|
|
61
|
-
"publishConfig": {
|
|
62
|
-
"access": "public"
|
|
63
|
-
},
|
|
64
|
-
"engines": {
|
|
65
|
-
"node": ">=22.0.0"
|
|
66
|
-
},
|
|
67
|
-
"homepage": "https://github.com/ClickerMonkey/aeye#readme",
|
|
68
|
-
"bugs": {
|
|
69
|
-
"url": "https://github.com/ClickerMonkey/aeye/issues"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@aeye/openrouter",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "@aeye OpenRouter - OpenRouter integration for @aeye",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"test:unit": "jest --testPathIgnorePatterns=__integration__",
|
|
25
|
+
"test:integration": "jest --testPathPattern=__integration__",
|
|
26
|
+
"test:coverage": "jest --coverage --testPathIgnorePatterns=__integration__",
|
|
27
|
+
"test:watch": "jest --watch",
|
|
28
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"ai",
|
|
33
|
+
"openrouter",
|
|
34
|
+
"typescript"
|
|
35
|
+
],
|
|
36
|
+
"author": "ClickerMonkey",
|
|
37
|
+
"license": "GPL-3.0",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/ClickerMonkey/aeye.git",
|
|
41
|
+
"directory": "packages/openrouter"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
"aeye-*.md"
|
|
48
|
+
],
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@aeye/ai": "^0.7.0",
|
|
51
|
+
"@aeye/core": "^0.7.0",
|
|
52
|
+
"@aeye/openai": "^0.7.0",
|
|
53
|
+
"openai": "^7.45.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/jest": "^30.0.0",
|
|
57
|
+
"jest": "^30.2.0",
|
|
58
|
+
"ts-jest": "^29.4.5",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=22.0.0"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/ClickerMonkey/aeye#readme",
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/ClickerMonkey/aeye/issues"
|
|
70
|
+
}
|
|
71
|
+
}
|