theorum 0.1.3 → 0.1.4
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 +30 -9
- package/deno.json +57 -0
- package/deno.lock +469 -0
- package/{esm/mod.d.ts → mod.ts} +57 -15
- package/package.json +35 -44
- package/src/cli/commands/profile.ts +57 -0
- package/src/cli/commands/run.ts +66 -0
- package/src/cli/commands/test.ts +195 -0
- package/src/cli/index.ts +161 -0
- package/src/cli/matrix/fixtures.ts +96 -0
- package/src/cli/matrix/synthesizer.ts +240 -0
- package/src/guardrails/error.ts +142 -0
- package/src/guardrails/injection.ts +254 -0
- package/src/guardrails/mod.ts +40 -0
- package/src/guardrails/quota.ts +87 -0
- package/src/guardrails/sanitize.ts +165 -0
- package/src/guardrails/sensitive.ts +96 -0
- package/src/host/mint-trace.ts +54 -0
- package/{esm/src/host/mod.js → src/host/mod.ts} +11 -3
- package/src/host/reply.ts +32 -0
- package/src/kernel/engine/assert.ts +29 -0
- package/src/kernel/engine/boundary.ts +75 -0
- package/src/kernel/engine/delta.ts +440 -0
- package/src/kernel/engine/hash.ts +11 -0
- package/src/kernel/engine/record.ts +8 -0
- package/src/kernel/engine/repair.ts +56 -0
- package/src/kernel/engine/runner/gates.ts +338 -0
- package/src/kernel/engine/runner/mod.ts +135 -0
- package/src/kernel/engine/runner/state.ts +31 -0
- package/src/kernel/engine/runner/steps.ts +183 -0
- package/src/kernel/engine/runner/stream.ts +83 -0
- package/src/kernel/engine/runner/tokens.ts +47 -0
- package/src/kernel/engine/runner/tools.ts +266 -0
- package/{esm/src/kernel/engine/runner.d.ts → src/kernel/engine/runner.ts} +2 -1
- package/src/kernel/engine/tree.ts +18 -0
- package/src/kernel/mod.ts +39 -0
- package/src/kernel/registry/attachments.ts +195 -0
- package/src/kernel/registry/catalog.ts +181 -0
- package/src/kernel/registry/ingress.ts +223 -0
- package/src/kernel/registry/profiles.ts +165 -0
- package/src/kernel/registry/provider-request.ts +31 -0
- package/src/kernel/registry/resolve.ts +290 -0
- package/src/kernel/registry/schemas.ts +29 -0
- package/src/kernel/registry/tools.ts +45 -0
- package/{esm/src/kernel/registry/vault.d.ts → src/kernel/registry/vault.ts} +17 -2
- package/src/kernel/types.ts +576 -0
- package/{esm/src/observability/mod.js → src/observability/mod.ts} +11 -2
- package/src/observability/spans.ts +70 -0
- package/src/observability/trace-attach.ts +96 -0
- package/src/observability/trace-record.ts +254 -0
- package/src/observability/trace-usage.ts +36 -0
- package/src/observability/trace.ts +143 -0
- package/src/presets/google.ts +147 -0
- package/src/presets/mod.ts +24 -0
- package/src/providers/create-provider.ts +64 -0
- package/src/providers/gemini-tape.ts +59 -0
- package/src/providers/google-tap.ts +57 -0
- package/src/providers/interactions.ts +182 -0
- package/src/providers/keys.ts +182 -0
- package/{esm/src/providers/mod.js → src/providers/mod.ts} +4 -2
- package/{esm/src/providers/openrouter-mod.js → src/providers/openrouter-mod.ts} +7 -3
- package/src/providers/openrouter-payload.ts +232 -0
- package/src/providers/openrouter.ts +758 -0
- package/src/providers/pcm.ts +37 -0
- package/src/providers/provider.ts +206 -0
- package/src/providers/speech.ts +180 -0
- package/src/providers/sse.ts +59 -0
- package/esm/_dnt.polyfills.d.ts +0 -110
- package/esm/_dnt.polyfills.js +0 -144
- package/esm/_dnt.shims.d.ts +0 -5
- package/esm/_dnt.shims.js +0 -61
- package/esm/mod.js +0 -49
- package/esm/package.json +0 -3
- package/esm/src/cli/commands/profile.d.ts +0 -2
- package/esm/src/cli/commands/profile.js +0 -53
- package/esm/src/cli/commands/run.d.ts +0 -10
- package/esm/src/cli/commands/run.js +0 -55
- package/esm/src/cli/commands/test.d.ts +0 -15
- package/esm/src/cli/commands/test.js +0 -140
- package/esm/src/cli/index.d.ts +0 -4
- package/esm/src/cli/index.js +0 -148
- package/esm/src/cli/matrix/fixtures.d.ts +0 -13
- package/esm/src/cli/matrix/fixtures.js +0 -77
- package/esm/src/cli/matrix/synthesizer.d.ts +0 -29
- package/esm/src/cli/matrix/synthesizer.js +0 -187
- package/esm/src/guardrails/error.d.ts +0 -33
- package/esm/src/guardrails/error.js +0 -114
- package/esm/src/guardrails/injection.d.ts +0 -12
- package/esm/src/guardrails/injection.js +0 -220
- package/esm/src/guardrails/mod.d.ts +0 -14
- package/esm/src/guardrails/mod.js +0 -14
- package/esm/src/guardrails/sanitize.d.ts +0 -22
- package/esm/src/guardrails/sanitize.js +0 -133
- package/esm/src/guardrails/sensitive.d.ts +0 -12
- package/esm/src/guardrails/sensitive.js +0 -88
- package/esm/src/host/mint-trace.d.ts +0 -27
- package/esm/src/host/mint-trace.js +0 -33
- package/esm/src/host/mod.d.ts +0 -13
- package/esm/src/host/reply.d.ts +0 -14
- package/esm/src/host/reply.js +0 -27
- package/esm/src/kernel/engine/boundary.d.ts +0 -10
- package/esm/src/kernel/engine/boundary.js +0 -55
- package/esm/src/kernel/engine/delta.d.ts +0 -8
- package/esm/src/kernel/engine/delta.js +0 -389
- package/esm/src/kernel/engine/hash.d.ts +0 -1
- package/esm/src/kernel/engine/hash.js +0 -9
- package/esm/src/kernel/engine/record.d.ts +0 -2
- package/esm/src/kernel/engine/record.js +0 -7
- package/esm/src/kernel/engine/repair.d.ts +0 -7
- package/esm/src/kernel/engine/repair.js +0 -38
- package/esm/src/kernel/engine/runner/gates.d.ts +0 -4
- package/esm/src/kernel/engine/runner/gates.js +0 -206
- package/esm/src/kernel/engine/runner/mod.d.ts +0 -14
- package/esm/src/kernel/engine/runner/mod.js +0 -103
- package/esm/src/kernel/engine/runner/state.d.ts +0 -16
- package/esm/src/kernel/engine/runner/state.js +0 -8
- package/esm/src/kernel/engine/runner/steps.d.ts +0 -15
- package/esm/src/kernel/engine/runner/steps.js +0 -119
- package/esm/src/kernel/engine/runner/stream.d.ts +0 -11
- package/esm/src/kernel/engine/runner/stream.js +0 -65
- package/esm/src/kernel/engine/runner/tokens.d.ts +0 -3
- package/esm/src/kernel/engine/runner/tokens.js +0 -38
- package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
- package/esm/src/kernel/engine/runner/tools.js +0 -197
- package/esm/src/kernel/engine/runner.js +0 -6
- package/esm/src/kernel/engine/tree.d.ts +0 -2
- package/esm/src/kernel/engine/tree.js +0 -17
- package/esm/src/kernel/mod.d.ts +0 -18
- package/esm/src/kernel/mod.js +0 -16
- package/esm/src/kernel/registry/attachments.d.ts +0 -16
- package/esm/src/kernel/registry/attachments.js +0 -156
- package/esm/src/kernel/registry/catalog.d.ts +0 -31
- package/esm/src/kernel/registry/catalog.js +0 -135
- package/esm/src/kernel/registry/ingress.d.ts +0 -13
- package/esm/src/kernel/registry/ingress.js +0 -163
- package/esm/src/kernel/registry/profiles.d.ts +0 -34
- package/esm/src/kernel/registry/profiles.js +0 -120
- package/esm/src/kernel/registry/provider-request.d.ts +0 -4
- package/esm/src/kernel/registry/provider-request.js +0 -25
- package/esm/src/kernel/registry/resolve.d.ts +0 -19
- package/esm/src/kernel/registry/resolve.js +0 -217
- package/esm/src/kernel/registry/schemas.d.ts +0 -14
- package/esm/src/kernel/registry/schemas.js +0 -23
- package/esm/src/kernel/registry/tools.d.ts +0 -12
- package/esm/src/kernel/registry/tools.js +0 -36
- package/esm/src/kernel/registry/vault.js +0 -18
- package/esm/src/kernel/types.d.ts +0 -521
- package/esm/src/kernel/types.js +0 -10
- package/esm/src/observability/mod.d.ts +0 -12
- package/esm/src/observability/spans.d.ts +0 -16
- package/esm/src/observability/spans.js +0 -56
- package/esm/src/observability/trace-attach.d.ts +0 -16
- package/esm/src/observability/trace-attach.js +0 -67
- package/esm/src/observability/trace-record.d.ts +0 -112
- package/esm/src/observability/trace-record.js +0 -140
- package/esm/src/observability/trace-usage.d.ts +0 -3
- package/esm/src/observability/trace-usage.js +0 -32
- package/esm/src/observability/trace.d.ts +0 -23
- package/esm/src/observability/trace.js +0 -121
- package/esm/src/presets/google.d.ts +0 -50
- package/esm/src/presets/google.js +0 -96
- package/esm/src/presets/mod.d.ts +0 -11
- package/esm/src/presets/mod.js +0 -10
- package/esm/src/providers/create-provider.d.ts +0 -29
- package/esm/src/providers/create-provider.js +0 -38
- package/esm/src/providers/gemini-tape.d.ts +0 -2
- package/esm/src/providers/gemini-tape.js +0 -46
- package/esm/src/providers/google-tap.d.ts +0 -3
- package/esm/src/providers/google-tap.js +0 -48
- package/esm/src/providers/interactions.d.ts +0 -5
- package/esm/src/providers/interactions.js +0 -152
- package/esm/src/providers/keys.d.ts +0 -19
- package/esm/src/providers/keys.js +0 -129
- package/esm/src/providers/mod.d.ts +0 -12
- package/esm/src/providers/openrouter-mod.d.ts +0 -13
- package/esm/src/providers/openrouter-payload.d.ts +0 -32
- package/esm/src/providers/openrouter-payload.js +0 -179
- package/esm/src/providers/openrouter.d.ts +0 -15
- package/esm/src/providers/openrouter.js +0 -589
- package/esm/src/providers/pcm.d.ts +0 -7
- package/esm/src/providers/pcm.js +0 -35
- package/esm/src/providers/provider.d.ts +0 -15
- package/esm/src/providers/provider.js +0 -166
- package/esm/src/providers/speech.d.ts +0 -23
- package/esm/src/providers/speech.js +0 -125
- package/esm/src/providers/sse.d.ts +0 -7
- package/esm/src/providers/sse.js +0 -53
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter provider adapter powered by Vercel AI SDK Core.
|
|
3
|
+
*
|
|
4
|
+
* THEORUM keeps the public `ModelProvider` and `TurnEvent` contract; AI SDK
|
|
5
|
+
* owns the OpenRouter call, stream parsing, provider compatibility, and tool
|
|
6
|
+
* call normalization.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createOpenRouter, type OpenRouterChatSettings } from '@openrouter/ai-sdk-provider';
|
|
12
|
+
import {
|
|
13
|
+
jsonSchema,
|
|
14
|
+
type LanguageModelUsage,
|
|
15
|
+
type ModelMessage,
|
|
16
|
+
streamText,
|
|
17
|
+
type TextStreamPart,
|
|
18
|
+
type ToolSet,
|
|
19
|
+
tool,
|
|
20
|
+
} from 'ai';
|
|
21
|
+
import { publicError } from '../guardrails/error.ts';
|
|
22
|
+
import { tryStructured } from '../kernel/engine/delta.ts';
|
|
23
|
+
import { getTool } from '../kernel/registry/catalog.ts';
|
|
24
|
+
import type {
|
|
25
|
+
DynamicToolDeclaration,
|
|
26
|
+
InteractionPart,
|
|
27
|
+
ModelProvider,
|
|
28
|
+
ProviderCompleteRequest,
|
|
29
|
+
TurnEvent,
|
|
30
|
+
TurnHistoryMessage,
|
|
31
|
+
TurnTokens,
|
|
32
|
+
} from '../kernel/types.ts';
|
|
33
|
+
import {
|
|
34
|
+
type OpenRouterConfig,
|
|
35
|
+
resolveOpenRouterModel,
|
|
36
|
+
toOpenRouterPayload,
|
|
37
|
+
} from './openrouter-payload.ts';
|
|
38
|
+
import { takeSsePayloads } from './sse.ts';
|
|
39
|
+
|
|
40
|
+
interface StreamAccumulator {
|
|
41
|
+
text: string;
|
|
42
|
+
evidenceSeen: boolean;
|
|
43
|
+
emittedTokens: boolean;
|
|
44
|
+
errored: boolean;
|
|
45
|
+
toolInputs: Map<string, string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface OpenRouterStreamContext {
|
|
49
|
+
openrouter: ReturnType<typeof createOpenRouter>;
|
|
50
|
+
modelName: string;
|
|
51
|
+
rawCapture: () => Promise<TurnEvent[]> | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type JsonValue =
|
|
55
|
+
| string
|
|
56
|
+
| number
|
|
57
|
+
| boolean
|
|
58
|
+
| null
|
|
59
|
+
| JsonValue[]
|
|
60
|
+
| {
|
|
61
|
+
[key: string]: JsonValue;
|
|
62
|
+
};
|
|
63
|
+
type ProviderOptions = Record<string, { [key: string]: JsonValue }>;
|
|
64
|
+
|
|
65
|
+
function trimApiKey(explicitKey?: string): string | undefined {
|
|
66
|
+
if (explicitKey?.trim()) {
|
|
67
|
+
return explicitKey.trim();
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createAccumulator(): StreamAccumulator {
|
|
73
|
+
return {
|
|
74
|
+
text: '',
|
|
75
|
+
evidenceSeen: false,
|
|
76
|
+
emittedTokens: false,
|
|
77
|
+
errored: false,
|
|
78
|
+
toolInputs: new Map(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mediaPart(part: InteractionPart): Record<string, unknown> {
|
|
83
|
+
if (part.type === 'text') {
|
|
84
|
+
return { type: 'text', text: part.text };
|
|
85
|
+
}
|
|
86
|
+
if (part.type === 'image') {
|
|
87
|
+
return {
|
|
88
|
+
type: 'image',
|
|
89
|
+
image: `data:${part.mimeType};base64,${part.data}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return { type: 'file', mediaType: part.mimeType, data: part.data };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function contentFromParts(parts: InteractionPart[]): string | Array<Record<string, unknown>> {
|
|
96
|
+
if (parts.every((part) => part.type === 'text')) {
|
|
97
|
+
return parts
|
|
98
|
+
.map((part) => (part.type === 'text' ? part.text : ''))
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join('\n');
|
|
101
|
+
}
|
|
102
|
+
return parts.map(mediaPart);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseToolInput(raw: string): unknown {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(raw);
|
|
108
|
+
} catch {
|
|
109
|
+
return { _raw: raw };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function stringDefault(value: string | undefined, fallback: string): string {
|
|
114
|
+
return value === undefined ? fallback : value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function optionalSingle(value: string | undefined): string[] | undefined {
|
|
118
|
+
return value === undefined ? undefined : [value];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function fallbackToolCallId(name?: string): string {
|
|
122
|
+
return `call_${stringDefault(name, 'tool')}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function toolResultContent(msg: TurnHistoryMessage): ModelMessage {
|
|
126
|
+
const toolName = stringDefault(msg.name, 'tool');
|
|
127
|
+
return {
|
|
128
|
+
role: 'tool',
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'tool-result',
|
|
132
|
+
toolCallId: stringDefault(msg.tool_call_id, fallbackToolCallId(msg.name)),
|
|
133
|
+
toolName,
|
|
134
|
+
output: { type: 'text', value: stringDefault(msg.content, '') },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
} as unknown as ModelMessage;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function assistantToolCallContent(msg: TurnHistoryMessage): ModelMessage | null {
|
|
141
|
+
if (!msg.tool_calls || msg.tool_calls.length === 0) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
role: 'assistant',
|
|
146
|
+
content: msg.tool_calls.map((call) => ({
|
|
147
|
+
type: 'tool-call',
|
|
148
|
+
toolCallId: call.id,
|
|
149
|
+
toolName: call.function.name,
|
|
150
|
+
input: parseToolInput(call.function.arguments),
|
|
151
|
+
})),
|
|
152
|
+
} as ModelMessage;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function historyMessage(msg: TurnHistoryMessage): ModelMessage | null {
|
|
156
|
+
if (msg.role === 'tool') {
|
|
157
|
+
return toolResultContent(msg);
|
|
158
|
+
}
|
|
159
|
+
return assistantToolCallContent(msg) || contentHistoryMessage(msg);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function contentFromOptionalParts(
|
|
163
|
+
parts: InteractionPart[] | undefined,
|
|
164
|
+
text: string | undefined,
|
|
165
|
+
): string | Array<Record<string, unknown>> {
|
|
166
|
+
if (!parts || parts.length === 0) {
|
|
167
|
+
return stringDefault(text, '');
|
|
168
|
+
}
|
|
169
|
+
return contentFromParts(parts);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function contentHistoryMessage(msg: TurnHistoryMessage): ModelMessage {
|
|
173
|
+
const content = contentFromOptionalParts(msg.parts, msg.content);
|
|
174
|
+
return { role: msg.role, content } as ModelMessage;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sourceValue(
|
|
178
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'source' }>,
|
|
179
|
+
key: string,
|
|
180
|
+
): string | undefined {
|
|
181
|
+
const source = part as Record<string, unknown>;
|
|
182
|
+
const value = source[key];
|
|
183
|
+
return typeof value === 'string' ? value : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function sourceUrl(part: Extract<TextStreamPart<ToolSet>, { type: 'source' }>): string | undefined {
|
|
187
|
+
return sourceValue(part, 'url');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function sourceTitle(
|
|
191
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'source' }>,
|
|
192
|
+
): string | undefined {
|
|
193
|
+
return sourceValue(part, 'title') ?? sourceUrl(part);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function sourceList(title: string | undefined, url: string | undefined) {
|
|
197
|
+
if (title === undefined || url === undefined) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
return [{ title, uri: url, type: 'web' as const }];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function sourceEvent(part: Extract<TextStreamPart<ToolSet>, { type: 'source' }>): TurnEvent {
|
|
204
|
+
const raw = part as Record<string, unknown>;
|
|
205
|
+
const url = sourceUrl(part);
|
|
206
|
+
const title = sourceTitle(part);
|
|
207
|
+
return {
|
|
208
|
+
type: 'evidence',
|
|
209
|
+
evidence: {
|
|
210
|
+
provider: 'openrouter',
|
|
211
|
+
raw,
|
|
212
|
+
citations: optionalSingle(url),
|
|
213
|
+
sources: sourceList(title, url),
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildMessages(req: ProviderCompleteRequest): ModelMessage[] {
|
|
219
|
+
const messages: ModelMessage[] = [];
|
|
220
|
+
for (const msg of req.history ?? []) {
|
|
221
|
+
const wired = historyMessage(msg);
|
|
222
|
+
if (wired) {
|
|
223
|
+
messages.push(wired);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (req.input.length > 0) {
|
|
227
|
+
messages.push({ role: 'user', content: contentFromParts(req.input) } as ModelMessage);
|
|
228
|
+
}
|
|
229
|
+
return messages;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function schemaForTool(decl: DynamicToolDeclaration): Record<string, unknown> {
|
|
233
|
+
return decl.parameters ?? { type: 'object', properties: {}, additionalProperties: true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildTools(dynamicTools?: DynamicToolDeclaration[]): ToolSet | undefined {
|
|
237
|
+
if (!dynamicTools || dynamicTools.length === 0) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
const tools: ToolSet = {};
|
|
241
|
+
for (const decl of dynamicTools) {
|
|
242
|
+
tools[decl.name] = tool({
|
|
243
|
+
description: decl.description ?? '',
|
|
244
|
+
inputSchema: jsonSchema(schemaForTool(decl)),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return tools;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function openRouterPlugins(req: ProviderCompleteRequest): OpenRouterChatSettings['plugins'] {
|
|
251
|
+
const plugins = req.builtins
|
|
252
|
+
.map((id) => getTool(id)?.openRouterPlugin)
|
|
253
|
+
.filter((id): id is string => Boolean(id))
|
|
254
|
+
.map((id) => ({ id }));
|
|
255
|
+
return plugins.length > 0 ? (plugins as OpenRouterChatSettings['plugins']) : undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function openRouterSettings(req: ProviderCompleteRequest): OpenRouterChatSettings | undefined {
|
|
259
|
+
const plugins = openRouterPlugins(req);
|
|
260
|
+
if (!plugins) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return { plugins };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function tokensFromUsage(usage: LanguageModelUsage): TurnTokens | undefined {
|
|
267
|
+
const input = usage.inputTokens ?? 0;
|
|
268
|
+
const output = usage.outputTokens ?? 0;
|
|
269
|
+
const total = usage.totalTokens ?? input + output;
|
|
270
|
+
if (input === 0 && output === 0 && total === 0) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
input,
|
|
275
|
+
output,
|
|
276
|
+
total,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function rawRecord(value: unknown): Record<string, unknown> | undefined {
|
|
281
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
282
|
+
return value as Record<string, unknown>;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function stringArray(value: unknown): string[] | undefined {
|
|
288
|
+
if (!Array.isArray(value)) {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
const out = value.filter((item): item is string => typeof item === 'string');
|
|
292
|
+
return out.length > 0 ? out : undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function metadataRecord(
|
|
296
|
+
raw: Record<string, unknown>,
|
|
297
|
+
key: string,
|
|
298
|
+
): Record<string, unknown> | undefined {
|
|
299
|
+
return rawRecord(raw[key]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function citationCandidates(raw: Record<string, unknown>): unknown[] {
|
|
303
|
+
const openrouter = metadataRecord(raw, 'openrouter') ?? {};
|
|
304
|
+
return [
|
|
305
|
+
raw.citations,
|
|
306
|
+
metadataRecord(raw, 'providerMetadata')?.citations,
|
|
307
|
+
metadataRecord(raw, 'provider_metadata')?.citations,
|
|
308
|
+
openrouter.citations,
|
|
309
|
+
metadataRecord(openrouter, 'providerMetadata')?.citations,
|
|
310
|
+
metadataRecord(openrouter, 'provider_metadata')?.citations,
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function nestedCitations(raw: Record<string, unknown>): string[] | undefined {
|
|
315
|
+
for (const candidate of citationCandidates(raw)) {
|
|
316
|
+
const citations = stringArray(candidate);
|
|
317
|
+
if (citations) {
|
|
318
|
+
return citations;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function metadataAnnotations(raw: Record<string, unknown>): unknown[] | undefined {
|
|
325
|
+
const openrouter = metadataRecord(raw, 'openrouter');
|
|
326
|
+
if (Array.isArray(raw.annotations)) {
|
|
327
|
+
return raw.annotations;
|
|
328
|
+
}
|
|
329
|
+
if (Array.isArray(openrouter?.annotations)) {
|
|
330
|
+
return openrouter.annotations;
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function evidenceFromMetadata(metadata: unknown, acc: StreamAccumulator): TurnEvent | undefined {
|
|
336
|
+
const raw = rawRecord(metadata);
|
|
337
|
+
if (!raw || acc.evidenceSeen) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
const citations = nestedCitations(raw);
|
|
341
|
+
const annotations = metadataAnnotations(raw);
|
|
342
|
+
if (!citations && !annotations) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
acc.evidenceSeen = true;
|
|
346
|
+
return {
|
|
347
|
+
type: 'evidence',
|
|
348
|
+
evidence: { provider: 'openrouter', raw, citations, annotations },
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function toolArguments(input: unknown): Record<string, unknown> | undefined {
|
|
353
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
354
|
+
return input as Record<string, unknown>;
|
|
355
|
+
}
|
|
356
|
+
if (input === undefined) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
return { value: input };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function isEmptyRecord(input: Record<string, unknown> | undefined): boolean {
|
|
363
|
+
return Boolean(input) && Object.keys(input ?? {}).length === 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function toolCallArguments(
|
|
367
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'tool-call' }>,
|
|
368
|
+
acc: StreamAccumulator,
|
|
369
|
+
): Record<string, unknown> | undefined {
|
|
370
|
+
const fromInput = toolArguments(part.input);
|
|
371
|
+
const rawInput = acc.toolInputs.get(part.toolCallId);
|
|
372
|
+
if ((!fromInput || isEmptyRecord(fromInput)) && rawInput) {
|
|
373
|
+
return toolArguments(parseToolInput(rawInput));
|
|
374
|
+
}
|
|
375
|
+
return fromInput;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function toolResultData(output: unknown): Record<string, unknown> | undefined {
|
|
379
|
+
return rawRecord(output);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function rawThoughtEvent(raw: Record<string, unknown>): TurnEvent | undefined {
|
|
383
|
+
const choices = raw.choices;
|
|
384
|
+
if (!Array.isArray(choices)) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
for (const choice of choices) {
|
|
388
|
+
const delta = rawRecord(rawRecord(choice)?.delta);
|
|
389
|
+
if (typeof delta?.thinking === 'string') {
|
|
390
|
+
return { type: 'thought', text: delta.thinking };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function rawChoiceMessageEvidence(
|
|
397
|
+
raw: Record<string, unknown>,
|
|
398
|
+
acc: StreamAccumulator,
|
|
399
|
+
): TurnEvent | undefined {
|
|
400
|
+
const choices = raw.choices;
|
|
401
|
+
if (!Array.isArray(choices)) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
for (const choice of choices) {
|
|
405
|
+
const message = rawRecord(rawRecord(choice)?.message);
|
|
406
|
+
if (!message) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const evidence = evidenceFromMetadata(message, acc);
|
|
410
|
+
if (evidence) {
|
|
411
|
+
return evidence;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function rawEvents(raw: unknown, acc: StreamAccumulator): TurnEvent[] {
|
|
418
|
+
const record = rawRecord(raw);
|
|
419
|
+
if (!record) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
const events: TurnEvent[] = [];
|
|
423
|
+
const thought = rawThoughtEvent(record);
|
|
424
|
+
if (thought) {
|
|
425
|
+
events.push(thought);
|
|
426
|
+
}
|
|
427
|
+
const evidence = evidenceFromMetadata(record, acc);
|
|
428
|
+
if (evidence) {
|
|
429
|
+
events.push(evidence);
|
|
430
|
+
}
|
|
431
|
+
const messageEvidence = rawChoiceMessageEvidence(record, acc);
|
|
432
|
+
if (messageEvidence) {
|
|
433
|
+
events.push(messageEvidence);
|
|
434
|
+
}
|
|
435
|
+
return events;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function collectRawEvents(res: Response): Promise<TurnEvent[]> {
|
|
439
|
+
if (!res.body) {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
const reader = res.body.getReader();
|
|
443
|
+
const decoder = new TextDecoder();
|
|
444
|
+
const events: TurnEvent[] = [];
|
|
445
|
+
const acc = createAccumulator();
|
|
446
|
+
let buffer = '';
|
|
447
|
+
let pendingEvent = '';
|
|
448
|
+
|
|
449
|
+
while (true) {
|
|
450
|
+
const { done, value } = await reader.read();
|
|
451
|
+
if (done) {
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
buffer += decoder.decode(value, { stream: true });
|
|
455
|
+
const taken = takeSsePayloads(buffer, pendingEvent);
|
|
456
|
+
buffer = taken.rest;
|
|
457
|
+
pendingEvent = taken.pendingEvent;
|
|
458
|
+
for (const payload of taken.payloads) {
|
|
459
|
+
events.push(...rawEvents(payload, acc));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return events;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function captureFetch(
|
|
466
|
+
config: OpenRouterConfig,
|
|
467
|
+
setCapture: (capture: Promise<TurnEvent[]>) => void,
|
|
468
|
+
): typeof fetch {
|
|
469
|
+
const fetchFn = config.fetch ?? fetch;
|
|
470
|
+
return async (input, init) => {
|
|
471
|
+
const res = await fetchFn(input, init);
|
|
472
|
+
setCapture(collectRawEvents(res.clone()).catch(() => []));
|
|
473
|
+
return res;
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function toolCallEvent(
|
|
478
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'tool-call' }>,
|
|
479
|
+
acc: StreamAccumulator,
|
|
480
|
+
): TurnEvent {
|
|
481
|
+
return {
|
|
482
|
+
type: 'tool',
|
|
483
|
+
tool: {
|
|
484
|
+
name: part.toolName,
|
|
485
|
+
arguments: toolCallArguments(part, acc),
|
|
486
|
+
id: part.toolCallId,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function toolResultEvent(
|
|
492
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'tool-result' }>,
|
|
493
|
+
): TurnEvent {
|
|
494
|
+
return {
|
|
495
|
+
type: 'tool',
|
|
496
|
+
tool: {
|
|
497
|
+
name: part.toolName,
|
|
498
|
+
arguments: toolArguments(part.input),
|
|
499
|
+
result: {
|
|
500
|
+
status: 'ok',
|
|
501
|
+
data: toolResultData(part.output),
|
|
502
|
+
finding: typeof part.output === 'string' ? part.output : undefined,
|
|
503
|
+
},
|
|
504
|
+
id: part.toolCallId,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function tokenEvent(
|
|
510
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'finish' }>,
|
|
511
|
+
): TurnEvent | undefined {
|
|
512
|
+
const tokens = tokensFromUsage(part.totalUsage);
|
|
513
|
+
return tokens ? { type: 'tokens', tokens } : undefined;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function providerMetadataEvent(
|
|
517
|
+
part: TextStreamPart<ToolSet>,
|
|
518
|
+
acc: StreamAccumulator,
|
|
519
|
+
): TurnEvent | undefined {
|
|
520
|
+
if (!('providerMetadata' in part)) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
return evidenceFromMetadata(part.providerMetadata, acc);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function appendToolInput(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): boolean {
|
|
527
|
+
if (part.type === 'tool-input-start') {
|
|
528
|
+
acc.toolInputs.set(part.id, '');
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
if (part.type === 'tool-input-delta') {
|
|
532
|
+
const current = acc.toolInputs.get(part.id) ?? '';
|
|
533
|
+
acc.toolInputs.set(part.id, current + part.delta);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function eventFromPart(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): TurnEvent[] {
|
|
540
|
+
if (appendToolInput(part, acc)) {
|
|
541
|
+
return [];
|
|
542
|
+
}
|
|
543
|
+
const mapped = primaryEventFromPart(part, acc);
|
|
544
|
+
if (mapped) {
|
|
545
|
+
return [mapped];
|
|
546
|
+
}
|
|
547
|
+
const evidence = providerMetadataEvent(part, acc);
|
|
548
|
+
return evidence ? [evidence] : [];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function primaryEventFromPart(
|
|
552
|
+
part: TextStreamPart<ToolSet>,
|
|
553
|
+
acc: StreamAccumulator,
|
|
554
|
+
): TurnEvent | undefined {
|
|
555
|
+
switch (part.type) {
|
|
556
|
+
case 'text-delta':
|
|
557
|
+
acc.text += part.text;
|
|
558
|
+
return { type: 'text', text: part.text };
|
|
559
|
+
case 'reasoning-delta':
|
|
560
|
+
return { type: 'thought', text: part.text };
|
|
561
|
+
case 'tool-call':
|
|
562
|
+
return toolCallEvent(part, acc);
|
|
563
|
+
case 'tool-result':
|
|
564
|
+
return toolResultEvent(part);
|
|
565
|
+
case 'source':
|
|
566
|
+
return sourceEvent(part);
|
|
567
|
+
case 'finish':
|
|
568
|
+
return finishEvent(part, acc);
|
|
569
|
+
case 'error':
|
|
570
|
+
acc.errored = true;
|
|
571
|
+
return { type: 'error', error: publicError(part.error) };
|
|
572
|
+
default:
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function finishEvent(
|
|
578
|
+
part: Extract<TextStreamPart<ToolSet>, { type: 'finish' }>,
|
|
579
|
+
acc: StreamAccumulator,
|
|
580
|
+
): TurnEvent | undefined {
|
|
581
|
+
if (acc.emittedTokens) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
const event = tokenEvent(part);
|
|
585
|
+
if (event) {
|
|
586
|
+
acc.emittedTokens = true;
|
|
587
|
+
}
|
|
588
|
+
return event;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function emitRawEvents(
|
|
592
|
+
rawCapture: Promise<TurnEvent[]> | undefined,
|
|
593
|
+
acc: StreamAccumulator,
|
|
594
|
+
): Promise<TurnEvent[]> {
|
|
595
|
+
return (rawCapture ?? Promise.resolve([])).then((events) =>
|
|
596
|
+
events.filter((event) => {
|
|
597
|
+
if (event.type !== 'evidence') {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
if (acc.evidenceSeen) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
acc.evidenceSeen = true;
|
|
604
|
+
return true;
|
|
605
|
+
}),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function createStreamContext(
|
|
610
|
+
req: ProviderCompleteRequest,
|
|
611
|
+
config: OpenRouterConfig,
|
|
612
|
+
apiKey: string,
|
|
613
|
+
): OpenRouterStreamContext {
|
|
614
|
+
let rawCapture: Promise<TurnEvent[]> | undefined;
|
|
615
|
+
const openrouter = createOpenRouter({
|
|
616
|
+
apiKey,
|
|
617
|
+
baseURL: config.baseUrl,
|
|
618
|
+
headers: openRouterHeaders(config),
|
|
619
|
+
fetch: captureFetch(config, (capture) => {
|
|
620
|
+
rawCapture = capture;
|
|
621
|
+
}),
|
|
622
|
+
compatibility: 'strict',
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
openrouter,
|
|
626
|
+
modelName: resolveOpenRouterModel(req.model, config.modelMap, {
|
|
627
|
+
apiId: req.apiId,
|
|
628
|
+
openRouterId: req.openRouterId,
|
|
629
|
+
}),
|
|
630
|
+
rawCapture: () => rawCapture,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function streamTextOptions(
|
|
635
|
+
req: ProviderCompleteRequest,
|
|
636
|
+
context: OpenRouterStreamContext,
|
|
637
|
+
): Parameters<typeof streamText>[0] {
|
|
638
|
+
return {
|
|
639
|
+
model: context.openrouter.chat(context.modelName, openRouterSettings(req)),
|
|
640
|
+
system: req.system,
|
|
641
|
+
messages: buildMessages(req),
|
|
642
|
+
allowSystemInMessages: true,
|
|
643
|
+
temperature: req.temperature,
|
|
644
|
+
maxOutputTokens: req.maxOutputTokens,
|
|
645
|
+
tools: buildTools(req.dynamicTools),
|
|
646
|
+
providerOptions: providerOptionsFor(req),
|
|
647
|
+
includeRawChunks: true,
|
|
648
|
+
onError: () => undefined,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function* yieldAiSdkStream(
|
|
653
|
+
req: ProviderCompleteRequest,
|
|
654
|
+
acc: StreamAccumulator,
|
|
655
|
+
context: OpenRouterStreamContext,
|
|
656
|
+
): AsyncGenerator<TurnEvent> {
|
|
657
|
+
const result = streamText(streamTextOptions(req, context));
|
|
658
|
+
for await (const part of result.fullStream) {
|
|
659
|
+
if (part.type === 'raw') {
|
|
660
|
+
req.tapGemini?.(rawRecord(part.rawValue) ?? { rawValue: part.rawValue });
|
|
661
|
+
for (const event of rawEvents(part.rawValue, acc)) {
|
|
662
|
+
yield event;
|
|
663
|
+
}
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
for (const event of eventFromPart(part, acc)) {
|
|
667
|
+
yield event;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function* yieldCapturedRawEvents(
|
|
673
|
+
context: OpenRouterStreamContext,
|
|
674
|
+
acc: StreamAccumulator,
|
|
675
|
+
): AsyncGenerator<TurnEvent> {
|
|
676
|
+
for (const event of await emitRawEvents(context.rawCapture(), acc)) {
|
|
677
|
+
yield event;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function* yieldCapturedRawEventsUnchecked(
|
|
682
|
+
context: OpenRouterStreamContext,
|
|
683
|
+
): AsyncGenerator<TurnEvent> {
|
|
684
|
+
for (const event of await (context.rawCapture() ?? Promise.resolve([]))) {
|
|
685
|
+
yield event;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function missingOpenRouterKey(): TurnEvent {
|
|
690
|
+
return { type: 'error', error: publicError('missing OpenRouter API key') };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function* streamOpenRouter(
|
|
694
|
+
req: ProviderCompleteRequest,
|
|
695
|
+
config: OpenRouterConfig,
|
|
696
|
+
): AsyncGenerator<TurnEvent> {
|
|
697
|
+
const apiKey = trimApiKey(config.apiKey);
|
|
698
|
+
if (!apiKey) {
|
|
699
|
+
yield missingOpenRouterKey();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const acc = createAccumulator();
|
|
704
|
+
const context = createStreamContext(req, config, apiKey);
|
|
705
|
+
try {
|
|
706
|
+
yield* yieldAiSdkStream(req, acc, context);
|
|
707
|
+
yield* yieldCapturedRawEvents(context, acc);
|
|
708
|
+
yield* finalEvents(req, acc);
|
|
709
|
+
} catch (err) {
|
|
710
|
+
yield* yieldCapturedRawEventsUnchecked(context);
|
|
711
|
+
yield { type: 'error', error: publicError(err) };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function* finalEvents(req: ProviderCompleteRequest, acc: StreamAccumulator): Generator<TurnEvent> {
|
|
716
|
+
if (acc.errored) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (req.structured && acc.text) {
|
|
720
|
+
const structured = tryStructured(acc.text);
|
|
721
|
+
if (structured) {
|
|
722
|
+
yield structured;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
yield { type: 'done' };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function providerOptionsFor(req: ProviderCompleteRequest): ProviderOptions | undefined {
|
|
729
|
+
if (req.thinking === 'none') {
|
|
730
|
+
return undefined;
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
openrouter: {
|
|
734
|
+
reasoning: { effort: req.thinking },
|
|
735
|
+
},
|
|
736
|
+
} as ProviderOptions;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function openRouterHeaders(config: OpenRouterConfig): Record<string, string> | undefined {
|
|
740
|
+
const headers: Record<string, string> = {};
|
|
741
|
+
if (config.siteUrl) {
|
|
742
|
+
headers['HTTP-Referer'] = config.siteUrl;
|
|
743
|
+
}
|
|
744
|
+
if (config.siteName) {
|
|
745
|
+
headers['X-Title'] = config.siteName;
|
|
746
|
+
}
|
|
747
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** Create a `ModelProvider` backed by OpenRouter through AI SDK Core. */
|
|
751
|
+
function createOpenRouterProvider(config: OpenRouterConfig = {}): ModelProvider {
|
|
752
|
+
return {
|
|
753
|
+
complete: (req: ProviderCompleteRequest) => streamOpenRouter(req, config),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export type { OpenRouterConfig };
|
|
758
|
+
export { createOpenRouterProvider, resolveOpenRouterModel, toOpenRouterPayload };
|