@zenera/neo 1.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 +178 -0
- package/dist/agent.d.ts +66 -0
- package/dist/agent.js +97 -0
- package/dist/architecture.d.ts +74 -0
- package/dist/architecture.js +105 -0
- package/dist/embedding.d.ts +60 -0
- package/dist/embedding.js +21 -0
- package/dist/embeddings/gemini.d.ts +24 -0
- package/dist/embeddings/gemini.js +71 -0
- package/dist/embeddings/index.d.ts +4 -0
- package/dist/embeddings/index.js +4 -0
- package/dist/embeddings/openai.d.ts +18 -0
- package/dist/embeddings/openai.js +27 -0
- package/dist/embeddings/openrouter.d.ts +21 -0
- package/dist/embeddings/openrouter.js +54 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +110 -0
- package/dist/ids.d.ts +14 -0
- package/dist/ids.js +33 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/inspect/client.d.ts +2 -0
- package/dist/inspect/client.js +1326 -0
- package/dist/inspect/css.d.ts +2 -0
- package/dist/inspect/css.js +164 -0
- package/dist/inspect/index.d.ts +9 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/page.d.ts +3 -0
- package/dist/inspect/page.js +93 -0
- package/dist/inspect/report.d.ts +54 -0
- package/dist/inspect/report.js +62 -0
- package/dist/kernel.d.ts +178 -0
- package/dist/kernel.js +929 -0
- package/dist/memory-stores/factory.d.ts +15 -0
- package/dist/memory-stores/factory.js +29 -0
- package/dist/memory-stores/file.d.ts +37 -0
- package/dist/memory-stores/file.js +174 -0
- package/dist/memory-stores/in-memory.d.ts +17 -0
- package/dist/memory-stores/in-memory.js +80 -0
- package/dist/memory-stores/index.d.ts +4 -0
- package/dist/memory-stores/index.js +5 -0
- package/dist/memory.d.ts +115 -0
- package/dist/memory.js +213 -0
- package/dist/model.d.ts +25 -0
- package/dist/model.js +2 -0
- package/dist/models/anthropic.d.ts +36 -0
- package/dist/models/anthropic.js +293 -0
- package/dist/models/factory.d.ts +202 -0
- package/dist/models/factory.js +533 -0
- package/dist/models/gemini.d.ts +45 -0
- package/dist/models/gemini.js +368 -0
- package/dist/models/index.d.ts +7 -0
- package/dist/models/index.js +7 -0
- package/dist/models/openai-chat.d.ts +19 -0
- package/dist/models/openai-chat.js +203 -0
- package/dist/models/openai-responses.d.ts +31 -0
- package/dist/models/openai-responses.js +258 -0
- package/dist/models/openrouter.d.ts +44 -0
- package/dist/models/openrouter.js +230 -0
- package/dist/payload-stores/factory.d.ts +15 -0
- package/dist/payload-stores/factory.js +30 -0
- package/dist/payload-stores/file.d.ts +29 -0
- package/dist/payload-stores/file.js +112 -0
- package/dist/payload-stores/in-memory.d.ts +17 -0
- package/dist/payload-stores/in-memory.js +40 -0
- package/dist/payload-stores/index.d.ts +4 -0
- package/dist/payload-stores/index.js +5 -0
- package/dist/payload.d.ts +89 -0
- package/dist/payload.js +181 -0
- package/dist/project/config.d.ts +302 -0
- package/dist/project/config.js +290 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +4 -0
- package/dist/project/load.d.ts +142 -0
- package/dist/project/load.js +430 -0
- package/dist/project/refs.d.ts +23 -0
- package/dist/project/refs.js +70 -0
- package/dist/prompt.d.ts +66 -0
- package/dist/prompt.js +62 -0
- package/dist/runner.d.ts +103 -0
- package/dist/runner.js +537 -0
- package/dist/services.d.ts +23 -0
- package/dist/services.js +44 -0
- package/dist/skill-providers/factory.d.ts +16 -0
- package/dist/skill-providers/factory.js +26 -0
- package/dist/skill-providers/file.d.ts +44 -0
- package/dist/skill-providers/file.js +209 -0
- package/dist/skill-providers/index.d.ts +4 -0
- package/dist/skill-providers/index.js +5 -0
- package/dist/skill-providers/static.d.ts +13 -0
- package/dist/skill-providers/static.js +47 -0
- package/dist/skills.d.ts +102 -0
- package/dist/skills.js +232 -0
- package/dist/state.d.ts +86 -0
- package/dist/state.js +49 -0
- package/dist/tools/exa.d.ts +23 -0
- package/dist/tools/exa.js +389 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/sandbox.d.ts +172 -0
- package/dist/tools/sandbox.js +602 -0
- package/dist/tools/workspace.d.ts +53 -0
- package/dist/tools/workspace.js +1034 -0
- package/dist/trajectory.d.ts +213 -0
- package/dist/trajectory.js +232 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.js +134 -0
- package/package.json +98 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { zeroUsage } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Same `Model` contract as `OpenAIModel`, over `/v1/responses` instead of
|
|
5
|
+
* `/v1/chat/completions`. Reasoning models are the reason to prefer it: effort
|
|
6
|
+
* and summaries are first-class there.
|
|
7
|
+
*/
|
|
8
|
+
export class OpenAIResponsesModel {
|
|
9
|
+
id;
|
|
10
|
+
#client;
|
|
11
|
+
#reasoningEffort;
|
|
12
|
+
#reasoningSummary;
|
|
13
|
+
#store;
|
|
14
|
+
constructor(id, client, options = {}) {
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.#client = client;
|
|
17
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
18
|
+
this.#reasoningSummary = options.reasoningSummary;
|
|
19
|
+
this.#store = options.store ?? false;
|
|
20
|
+
}
|
|
21
|
+
async generate(req) {
|
|
22
|
+
const res = await this.#client.responses.create(this.#params(req), {
|
|
23
|
+
signal: req.signal,
|
|
24
|
+
});
|
|
25
|
+
const toolCalls = readToolCalls(res.output);
|
|
26
|
+
return {
|
|
27
|
+
text: readText(res.output),
|
|
28
|
+
thinking: readThinking(res.output),
|
|
29
|
+
toolCalls,
|
|
30
|
+
usage: toUsage(res.usage),
|
|
31
|
+
stopReason: toStopReason(res, toolCalls.length > 0),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async stream(req, onDelta) {
|
|
35
|
+
const stream = await this.#client.responses.create({ ...this.#params(req), stream: true }, { signal: req.signal });
|
|
36
|
+
let text = '';
|
|
37
|
+
let thinking = '';
|
|
38
|
+
let final;
|
|
39
|
+
// Output items are addressed by their index in `response.output`, which
|
|
40
|
+
// every delta event carries — that is the only stable key while the
|
|
41
|
+
// items are still being assembled.
|
|
42
|
+
const calls = new Map();
|
|
43
|
+
for await (const event of stream) {
|
|
44
|
+
// Some models/gateways emit `response.reasoning.delta`, which the
|
|
45
|
+
// SDK does not carry in its event union, so it is matched before
|
|
46
|
+
// the typed switch.
|
|
47
|
+
if (event.type === 'response.reasoning.delta') {
|
|
48
|
+
const delta = readReasoningDelta(event.delta);
|
|
49
|
+
if (delta) {
|
|
50
|
+
thinking += delta;
|
|
51
|
+
onDelta({ type: 'thinking_delta', delta });
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
switch (event.type) {
|
|
56
|
+
case 'response.output_text.delta': {
|
|
57
|
+
text += event.delta;
|
|
58
|
+
onDelta({ type: 'text_delta', delta: event.delta });
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'response.reasoning_summary_part.added': {
|
|
62
|
+
// A response carries several summary parts, each its own
|
|
63
|
+
// paragraph. Nothing separates them in the deltas, so a
|
|
64
|
+
// blank line is inserted between them here.
|
|
65
|
+
if (thinking) {
|
|
66
|
+
thinking += '\n\n';
|
|
67
|
+
onDelta({ type: 'thinking_delta', delta: '\n\n' });
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'response.reasoning_summary_text.delta': {
|
|
72
|
+
thinking += event.delta;
|
|
73
|
+
onDelta({ type: 'thinking_delta', delta: event.delta });
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case 'response.output_item.added': {
|
|
77
|
+
if (event.item.type === 'function_call') {
|
|
78
|
+
const call = {
|
|
79
|
+
id: event.item.call_id,
|
|
80
|
+
name: event.item.name,
|
|
81
|
+
args: '',
|
|
82
|
+
};
|
|
83
|
+
calls.set(event.output_index, call);
|
|
84
|
+
onDelta({
|
|
85
|
+
type: 'tool_call_detected',
|
|
86
|
+
callId: call.id,
|
|
87
|
+
name: call.name,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'response.function_call_arguments.delta': {
|
|
93
|
+
const call = calls.get(event.output_index);
|
|
94
|
+
if (call) {
|
|
95
|
+
call.args += event.delta;
|
|
96
|
+
onDelta({
|
|
97
|
+
type: 'tool_args_delta',
|
|
98
|
+
callId: call.id,
|
|
99
|
+
name: call.name,
|
|
100
|
+
delta: event.delta,
|
|
101
|
+
argsSoFar: call.args,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case 'response.completed':
|
|
107
|
+
case 'response.incomplete':
|
|
108
|
+
case 'response.failed': {
|
|
109
|
+
final = event.response;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const toolCalls = [...calls.entries()].sort((a, b) => a[0] - b[0]).map(([, c]) => c);
|
|
117
|
+
return {
|
|
118
|
+
text,
|
|
119
|
+
thinking: thinking || undefined,
|
|
120
|
+
toolCalls,
|
|
121
|
+
usage: toUsage(final?.usage),
|
|
122
|
+
stopReason: final
|
|
123
|
+
? toStopReason(final, toolCalls.length > 0)
|
|
124
|
+
: toolCalls.length
|
|
125
|
+
? 'tool_calls'
|
|
126
|
+
: 'stop',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
#params(req) {
|
|
130
|
+
return {
|
|
131
|
+
model: this.id,
|
|
132
|
+
// The responses API has a dedicated slot for the system prompt
|
|
133
|
+
// instead of a leading message.
|
|
134
|
+
instructions: req.system,
|
|
135
|
+
input: req.messages.flatMap(_a.#toResponses),
|
|
136
|
+
store: this.#store,
|
|
137
|
+
reasoning: this.#reasoningEffort
|
|
138
|
+
? { effort: this.#reasoningEffort, summary: this.#reasoningSummary }
|
|
139
|
+
: undefined,
|
|
140
|
+
tools: req.tools.length
|
|
141
|
+
? req.tools.map((t) => ({
|
|
142
|
+
// Tools are flat here — no nested `function` object.
|
|
143
|
+
type: 'function',
|
|
144
|
+
name: t.name,
|
|
145
|
+
description: t.description,
|
|
146
|
+
parameters: t.parameters,
|
|
147
|
+
strict: false,
|
|
148
|
+
}))
|
|
149
|
+
: undefined,
|
|
150
|
+
tool_choice: req.tools.length ? (req.toolChoice ?? 'auto') : undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Translates one internal message into responses-API input items. Returns a
|
|
155
|
+
* list because an assistant turn with tool calls becomes several items: the
|
|
156
|
+
* prose message plus one `function_call` per call.
|
|
157
|
+
*/
|
|
158
|
+
static #toResponses(m) {
|
|
159
|
+
switch (m.role) {
|
|
160
|
+
case 'system':
|
|
161
|
+
return [{ role: 'system', content: m.content }];
|
|
162
|
+
case 'user':
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
role: 'user',
|
|
166
|
+
// Only images have a first-class multimodal representation
|
|
167
|
+
// by url; other media degrade to a text reference so the
|
|
168
|
+
// thread stays valid instead of throwing.
|
|
169
|
+
content: m.content.map((p) => p.type === 'text'
|
|
170
|
+
? { type: 'input_text', text: p.text }
|
|
171
|
+
: p.type === 'image'
|
|
172
|
+
? {
|
|
173
|
+
type: 'input_image',
|
|
174
|
+
image_url: p.url,
|
|
175
|
+
detail: 'auto',
|
|
176
|
+
}
|
|
177
|
+
: { type: 'input_text', text: `[${p.type}] ${p.url}` }),
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
case 'assistant': {
|
|
181
|
+
const items = [];
|
|
182
|
+
if (m.content) {
|
|
183
|
+
items.push({ role: 'assistant', content: m.content });
|
|
184
|
+
}
|
|
185
|
+
for (const c of m.toolCalls ?? []) {
|
|
186
|
+
items.push({
|
|
187
|
+
type: 'function_call',
|
|
188
|
+
call_id: c.id,
|
|
189
|
+
name: c.name,
|
|
190
|
+
arguments: c.args,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return items;
|
|
194
|
+
}
|
|
195
|
+
case 'tool':
|
|
196
|
+
return [{ type: 'function_call_output', call_id: m.callId, output: m.content }];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
_a = OpenAIResponsesModel;
|
|
201
|
+
function readText(output) {
|
|
202
|
+
return output
|
|
203
|
+
.flatMap((item) => item.type === 'message'
|
|
204
|
+
? item.content.flatMap((p) => (p.type === 'output_text' ? [p.text] : []))
|
|
205
|
+
: [])
|
|
206
|
+
.join('');
|
|
207
|
+
}
|
|
208
|
+
function readThinking(output) {
|
|
209
|
+
const parts = output.flatMap((item) => item.type === 'reasoning' ? item.summary.map((s) => s.text) : []);
|
|
210
|
+
return parts.join('\n\n') || undefined;
|
|
211
|
+
}
|
|
212
|
+
function readToolCalls(output) {
|
|
213
|
+
// `call_id` — not `id` — is what a later `function_call_output` must
|
|
214
|
+
// reference, so it is the identity we carry through the trajectory.
|
|
215
|
+
return output.flatMap((item) => item.type === 'function_call'
|
|
216
|
+
? [{ id: item.call_id, name: item.name, args: item.arguments }]
|
|
217
|
+
: []);
|
|
218
|
+
}
|
|
219
|
+
function toStopReason(res, hasToolCalls) {
|
|
220
|
+
if (hasToolCalls) {
|
|
221
|
+
return 'tool_calls';
|
|
222
|
+
}
|
|
223
|
+
switch (res.incomplete_details?.reason) {
|
|
224
|
+
case 'max_output_tokens':
|
|
225
|
+
return 'length';
|
|
226
|
+
case 'content_filter':
|
|
227
|
+
return 'content_filter';
|
|
228
|
+
default:
|
|
229
|
+
return 'stop';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function toUsage(u) {
|
|
233
|
+
if (!u) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
...zeroUsage(),
|
|
238
|
+
inputTokens: u.input_tokens,
|
|
239
|
+
cachedInputTokens: u.input_tokens_details?.cached_tokens ?? 0,
|
|
240
|
+
outputTokens: u.output_tokens,
|
|
241
|
+
reasoningTokens: u.output_tokens_details?.reasoning_tokens ?? 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* `response.reasoning.delta` carries an untyped payload: providers send either a
|
|
246
|
+
* bare string or `{ text }`.
|
|
247
|
+
*/
|
|
248
|
+
function readReasoningDelta(delta) {
|
|
249
|
+
if (typeof delta === 'string') {
|
|
250
|
+
return delta || undefined;
|
|
251
|
+
}
|
|
252
|
+
if (delta === null || typeof delta !== 'object') {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
const text = delta.text;
|
|
256
|
+
return typeof text === 'string' && text ? text : undefined;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=openai-responses.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { OpenRouter } from '@openrouter/sdk';
|
|
2
|
+
import type { ChatRequestPlugin, ProviderPreferences } from '@openrouter/sdk/models';
|
|
3
|
+
import type { StreamDelta } from '../events.ts';
|
|
4
|
+
import type { Model, ModelRequest, ModelResponse } from '../model.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Effort levels, spelled out rather than imported: the SDK's is an *open* enum
|
|
7
|
+
* (its literals plus any string), which would let a typo through, and naming
|
|
8
|
+
* its type here would tie this interface to a generated alias. The set is the
|
|
9
|
+
* same one the OpenAI adapters accept, which is what lets `ModelSpec` extend
|
|
10
|
+
* both without the two declarations disagreeing.
|
|
11
|
+
*/
|
|
12
|
+
export type OpenRouterEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;
|
|
13
|
+
/** Provider-specific knobs. */
|
|
14
|
+
export interface OpenRouterModelOptions {
|
|
15
|
+
reasoningEffort?: OpenRouterEffort;
|
|
16
|
+
/** ask for a reasoning summary from models that expose one */
|
|
17
|
+
reasoningSummary?: 'auto' | 'concise' | 'detailed';
|
|
18
|
+
/** hard cap on output tokens; the model's own default applies when unset */
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Which upstream providers may serve this model, and in what order. Named
|
|
22
|
+
* `routing` rather than the SDK's `provider` because in this runtime a
|
|
23
|
+
* provider is already the *connection* — OpenRouter itself — and reusing
|
|
24
|
+
* the word for "who OpenRouter hands the request to" would collide.
|
|
25
|
+
*/
|
|
26
|
+
routing?: ProviderPreferences;
|
|
27
|
+
/**
|
|
28
|
+
* Models to try, in order, when the primary one is unavailable. The SDK
|
|
29
|
+
* calls this `models`; `fallbacks` says which of the two lists it is.
|
|
30
|
+
*/
|
|
31
|
+
fallbacks?: string[];
|
|
32
|
+
/** OpenRouter-side plugins: web search, file parsing, moderation */
|
|
33
|
+
plugins?: ChatRequestPlugin[];
|
|
34
|
+
/** provider-dependent latency/price tier, ignored where unsupported */
|
|
35
|
+
serviceTier?: 'auto' | 'default' | 'fast' | 'flex' | 'priority' | 'scale';
|
|
36
|
+
}
|
|
37
|
+
export declare class OpenRouterModel implements Model {
|
|
38
|
+
#private;
|
|
39
|
+
readonly id: string;
|
|
40
|
+
constructor(id: string, client: OpenRouter, options?: OpenRouterModelOptions);
|
|
41
|
+
generate(req: ModelRequest): Promise<ModelResponse>;
|
|
42
|
+
stream(req: ModelRequest, onDelta: (d: StreamDelta) => void): Promise<ModelResponse>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=openrouter.d.ts.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { zeroUsage } from "../types.js";
|
|
3
|
+
function toStopReason(finish) {
|
|
4
|
+
switch (finish) {
|
|
5
|
+
case 'tool_calls':
|
|
6
|
+
return 'tool_calls';
|
|
7
|
+
case 'length':
|
|
8
|
+
return 'length';
|
|
9
|
+
case 'content_filter':
|
|
10
|
+
return 'content_filter';
|
|
11
|
+
default:
|
|
12
|
+
return 'stop';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Assistant content is `string | ContentItems[] | null` here, unlike chat
|
|
17
|
+
* completions where it is a string. The array form appears when a provider
|
|
18
|
+
* returns text alongside images, so the text parts are what this runtime's
|
|
19
|
+
* `text` channel means; anything else is carried by other fields.
|
|
20
|
+
*/
|
|
21
|
+
function toText(content) {
|
|
22
|
+
if (typeof content === 'string') {
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
if (!content) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
return content
|
|
29
|
+
.filter((p) => p.type === 'text')
|
|
30
|
+
.map((p) => p.text)
|
|
31
|
+
.join('');
|
|
32
|
+
}
|
|
33
|
+
function toUsage(u) {
|
|
34
|
+
if (!u) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...zeroUsage(),
|
|
39
|
+
inputTokens: u.promptTokens,
|
|
40
|
+
cachedInputTokens: u.promptTokensDetails?.cachedTokens ?? 0,
|
|
41
|
+
outputTokens: u.completionTokens,
|
|
42
|
+
reasoningTokens: u.completionTokensDetails?.reasoningTokens ?? 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export class OpenRouterModel {
|
|
46
|
+
id;
|
|
47
|
+
#client;
|
|
48
|
+
#options;
|
|
49
|
+
constructor(id, client, options = {}) {
|
|
50
|
+
this.id = id;
|
|
51
|
+
this.#client = client;
|
|
52
|
+
this.#options = options;
|
|
53
|
+
}
|
|
54
|
+
async generate(req) {
|
|
55
|
+
const res = await this.#send(req, false);
|
|
56
|
+
// Both overloads are declared to return the same union, so the narrowing
|
|
57
|
+
// the `stream` flag implies has to be done here.
|
|
58
|
+
if (Symbol.asyncIterator in res) {
|
|
59
|
+
throw new Error(`model "${this.id}": asked for one completion, got a stream`);
|
|
60
|
+
}
|
|
61
|
+
const choice = res.choices[0];
|
|
62
|
+
const message = choice?.message;
|
|
63
|
+
return {
|
|
64
|
+
text: toText(message?.content),
|
|
65
|
+
thinking: message?.reasoning ?? undefined,
|
|
66
|
+
toolCalls: (message?.toolCalls ?? []).map((c) => ({
|
|
67
|
+
id: c.id,
|
|
68
|
+
name: c.function.name,
|
|
69
|
+
args: c.function.arguments,
|
|
70
|
+
})),
|
|
71
|
+
usage: toUsage(res.usage),
|
|
72
|
+
stopReason: toStopReason(choice?.finishReason),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async stream(req, onDelta) {
|
|
76
|
+
const res = await this.#send(req, true);
|
|
77
|
+
if (!(Symbol.asyncIterator in res)) {
|
|
78
|
+
throw new Error(`model "${this.id}": asked for a stream, got one completion`);
|
|
79
|
+
}
|
|
80
|
+
let text = '';
|
|
81
|
+
let thinking = '';
|
|
82
|
+
let usage;
|
|
83
|
+
let stopReason;
|
|
84
|
+
// Tool calls arrive as deltas keyed by index, with the id and name in the
|
|
85
|
+
// first fragment and arguments trickling in afterwards.
|
|
86
|
+
const calls = new Map();
|
|
87
|
+
for await (const chunk of res) {
|
|
88
|
+
// An upstream failure mid-stream arrives as a chunk on a 200, so it
|
|
89
|
+
// has to be raised here or the turn ends early and looks complete.
|
|
90
|
+
if (chunk.error) {
|
|
91
|
+
throw new Error(`model "${this.id}": ${chunk.error.message} (${chunk.error.code})`);
|
|
92
|
+
}
|
|
93
|
+
if (chunk.usage) {
|
|
94
|
+
usage = toUsage(chunk.usage);
|
|
95
|
+
}
|
|
96
|
+
const choice = chunk.choices[0];
|
|
97
|
+
if (!choice) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (choice.finishReason) {
|
|
101
|
+
stopReason = toStopReason(choice.finishReason);
|
|
102
|
+
}
|
|
103
|
+
const delta = choice.delta;
|
|
104
|
+
if (delta.reasoning) {
|
|
105
|
+
thinking += delta.reasoning;
|
|
106
|
+
onDelta({ type: 'thinking_delta', delta: delta.reasoning });
|
|
107
|
+
}
|
|
108
|
+
if (delta.content) {
|
|
109
|
+
text += delta.content;
|
|
110
|
+
onDelta({ type: 'text_delta', delta: delta.content });
|
|
111
|
+
}
|
|
112
|
+
for (const part of delta.toolCalls ?? []) {
|
|
113
|
+
const known = calls.get(part.index);
|
|
114
|
+
const call = known ?? {
|
|
115
|
+
id: part.id ?? `call_${part.index}`,
|
|
116
|
+
name: part.function?.name ?? '',
|
|
117
|
+
args: '',
|
|
118
|
+
};
|
|
119
|
+
if (!known) {
|
|
120
|
+
calls.set(part.index, call);
|
|
121
|
+
}
|
|
122
|
+
if (part.id) {
|
|
123
|
+
call.id = part.id;
|
|
124
|
+
}
|
|
125
|
+
if (part.function?.name) {
|
|
126
|
+
call.name = part.function.name;
|
|
127
|
+
}
|
|
128
|
+
if (!known || (part.function?.name && !known.name)) {
|
|
129
|
+
onDelta({ type: 'tool_call_detected', callId: call.id, name: call.name });
|
|
130
|
+
}
|
|
131
|
+
const args = part.function?.arguments;
|
|
132
|
+
if (args) {
|
|
133
|
+
call.args += args;
|
|
134
|
+
onDelta({
|
|
135
|
+
type: 'tool_args_delta',
|
|
136
|
+
callId: call.id,
|
|
137
|
+
name: call.name,
|
|
138
|
+
delta: args,
|
|
139
|
+
argsSoFar: call.args,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
text,
|
|
146
|
+
thinking: thinking || undefined,
|
|
147
|
+
toolCalls: [...calls.entries()].sort((a, b) => a[0] - b[0]).map(([, c]) => c),
|
|
148
|
+
usage,
|
|
149
|
+
stopReason: stopReason ?? (calls.size ? 'tool_calls' : 'stop'),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The abort signal rides in `fetchOptions` rather than on the request: this
|
|
154
|
+
* SDK keeps transport concerns out of the body it validates.
|
|
155
|
+
*/
|
|
156
|
+
#send(req, stream) {
|
|
157
|
+
return this.#client.chat.send({ chatRequest: this.#request(req, stream) }, { fetchOptions: { signal: req.signal } });
|
|
158
|
+
}
|
|
159
|
+
#request(req, stream) {
|
|
160
|
+
const o = this.#options;
|
|
161
|
+
const messages = [];
|
|
162
|
+
if (req.system) {
|
|
163
|
+
messages.push({ role: 'system', content: req.system });
|
|
164
|
+
}
|
|
165
|
+
for (const m of req.messages) {
|
|
166
|
+
messages.push(_a.#toOpenRouter(m));
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
model: this.id,
|
|
170
|
+
messages,
|
|
171
|
+
stream,
|
|
172
|
+
maxCompletionTokens: o.maxTokens,
|
|
173
|
+
reasoning: o.reasoningEffort || o.reasoningSummary
|
|
174
|
+
? { effort: o.reasoningEffort, summary: o.reasoningSummary }
|
|
175
|
+
: undefined,
|
|
176
|
+
provider: o.routing,
|
|
177
|
+
models: o.fallbacks,
|
|
178
|
+
plugins: o.plugins,
|
|
179
|
+
serviceTier: o.serviceTier,
|
|
180
|
+
tools: req.tools.length
|
|
181
|
+
? req.tools.map((t) => ({
|
|
182
|
+
type: 'function',
|
|
183
|
+
function: {
|
|
184
|
+
name: t.name,
|
|
185
|
+
description: t.description,
|
|
186
|
+
parameters: t.parameters,
|
|
187
|
+
},
|
|
188
|
+
}))
|
|
189
|
+
: undefined,
|
|
190
|
+
toolChoice: req.tools.length ? (req.toolChoice ?? 'auto') : undefined,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Translates one internal message into the provider wire format. */
|
|
194
|
+
static #toOpenRouter(m) {
|
|
195
|
+
switch (m.role) {
|
|
196
|
+
case 'system':
|
|
197
|
+
return { role: 'system', content: m.content };
|
|
198
|
+
case 'user':
|
|
199
|
+
return {
|
|
200
|
+
role: 'user',
|
|
201
|
+
// Only images have a first-class multimodal representation
|
|
202
|
+
// here; other media degrade to a text reference so the
|
|
203
|
+
// thread stays valid instead of throwing.
|
|
204
|
+
content: m.content.map((p) => p.type === 'text'
|
|
205
|
+
? { type: 'text', text: p.text }
|
|
206
|
+
: p.type === 'image'
|
|
207
|
+
? { type: 'image_url', imageUrl: { url: p.url } }
|
|
208
|
+
: { type: 'text', text: `[${p.type}] ${p.url}` }),
|
|
209
|
+
};
|
|
210
|
+
case 'assistant':
|
|
211
|
+
return {
|
|
212
|
+
role: 'assistant',
|
|
213
|
+
// A tool-calling turn usually has no prose; the API wants an
|
|
214
|
+
// explicit null there rather than an empty string.
|
|
215
|
+
content: m.content || null,
|
|
216
|
+
toolCalls: m.toolCalls?.length
|
|
217
|
+
? m.toolCalls.map((c) => ({
|
|
218
|
+
id: c.id,
|
|
219
|
+
type: 'function',
|
|
220
|
+
function: { name: c.name, arguments: c.args },
|
|
221
|
+
}))
|
|
222
|
+
: undefined,
|
|
223
|
+
};
|
|
224
|
+
case 'tool':
|
|
225
|
+
return { role: 'tool', toolCallId: m.callId, content: m.content };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
_a = OpenRouterModel;
|
|
230
|
+
//# sourceMappingURL=openrouter.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PayloadStore } from '../payload.ts';
|
|
2
|
+
import { type FilePayloadStoreOptions } from './file.ts';
|
|
3
|
+
export interface MemoryPayloadStoreSpec {
|
|
4
|
+
kind: 'memory';
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface FilePayloadStoreSpec extends FilePayloadStoreOptions {
|
|
8
|
+
kind: 'file';
|
|
9
|
+
}
|
|
10
|
+
/** Grows a member per backend (`s3`, …); `kind` is the discriminator. */
|
|
11
|
+
export type PayloadStoreSpec = MemoryPayloadStoreSpec | FilePayloadStoreSpec;
|
|
12
|
+
/** Shorthand: `mem`, `memory`, `file:./.data/blobs`. */
|
|
13
|
+
export type PayloadStoreRef = PayloadStoreSpec | string;
|
|
14
|
+
export declare function createPayloadStore(ref: PayloadStoreRef): PayloadStore;
|
|
15
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FilePayloadStore } from "./file.js";
|
|
2
|
+
import { InMemoryPayloadStore } from "./in-memory.js";
|
|
3
|
+
export function createPayloadStore(ref) {
|
|
4
|
+
const spec = typeof ref === 'string' ? parseRef(ref) : ref;
|
|
5
|
+
switch (spec.kind) {
|
|
6
|
+
case 'memory':
|
|
7
|
+
return new InMemoryPayloadStore(spec.id);
|
|
8
|
+
case 'file':
|
|
9
|
+
return new FilePayloadStore(spec);
|
|
10
|
+
default:
|
|
11
|
+
throw new TypeError(`unknown payload store kind: ${spec.kind}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function parseRef(ref) {
|
|
15
|
+
if (ref === 'mem' || ref === 'memory') {
|
|
16
|
+
return { kind: 'memory' };
|
|
17
|
+
}
|
|
18
|
+
// Only the first colon separates the scheme: a path may contain colons.
|
|
19
|
+
const colon = ref.indexOf(':');
|
|
20
|
+
const scheme = colon < 0 ? ref : ref.slice(0, colon);
|
|
21
|
+
const rest = colon < 0 ? '' : ref.slice(colon + 1);
|
|
22
|
+
if (scheme === 'file') {
|
|
23
|
+
if (!rest) {
|
|
24
|
+
throw new TypeError(`missing directory in "${ref}" (expected file:<dir>)`);
|
|
25
|
+
}
|
|
26
|
+
return { kind: 'file', dir: rest };
|
|
27
|
+
}
|
|
28
|
+
throw new TypeError(`unknown payload store ref: "${ref}"`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Payload, type PayloadStore } from '../payload.ts';
|
|
2
|
+
export interface FilePayloadStoreOptions {
|
|
3
|
+
/** root directory; created on demand */
|
|
4
|
+
dir: string;
|
|
5
|
+
/**
|
|
6
|
+
* Store id embedded in every `Payload` this store hands out. Keep it a
|
|
7
|
+
* logical name (not the path) so a run stays portable between machines.
|
|
8
|
+
*/
|
|
9
|
+
id?: string;
|
|
10
|
+
/** parallel reads in `getMany` — guards against EMFILE on big projections */
|
|
11
|
+
concurrency?: number;
|
|
12
|
+
/** re-hash on read; turns silent corruption into a loud error */
|
|
13
|
+
verify?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Same contract as the in-memory store, on disk. Content addressing does the
|
|
17
|
+
* hard part: writes are write-once and therefore idempotent under retry and
|
|
18
|
+
* replay, and two processes racing on the same blob produce identical bytes.
|
|
19
|
+
*/
|
|
20
|
+
export declare class FilePayloadStore implements PayloadStore {
|
|
21
|
+
#private;
|
|
22
|
+
readonly id: string;
|
|
23
|
+
constructor(opts: FilePayloadStoreOptions | string);
|
|
24
|
+
get dir(): string;
|
|
25
|
+
put(value: string): Promise<Payload>;
|
|
26
|
+
get(p: Payload): Promise<string>;
|
|
27
|
+
getMany(ps: Payload[]): Promise<string[]>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=file.d.ts.map
|