theorum 0.1.6 → 0.1.9
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/docs/AGENT_PROFILE_CONTRACT.md +1 -0
- package/esm/mod.d.ts +1 -1
- package/esm/mod.js +1 -1
- package/esm/src/guardrails/error.d.ts +7 -1
- package/esm/src/guardrails/error.js +31 -1
- package/esm/src/guardrails/mod.d.ts +1 -1
- package/esm/src/guardrails/mod.js +1 -1
- package/esm/src/kernel/engine/runner/gates.js +2 -1
- package/esm/src/kernel/engine/runner/mod.js +2 -0
- package/esm/src/kernel/engine/runner/steps.js +5 -2
- package/esm/src/kernel/engine/runner/stream.d.ts +1 -0
- package/esm/src/kernel/engine/runner/stream.js +5 -2
- package/esm/src/kernel/types.d.ts +7 -0
- package/esm/src/observability/trace-record.js +4 -3
- package/esm/src/providers/create-provider.d.ts +7 -0
- package/esm/src/providers/create-provider.js +2 -0
- package/esm/src/providers/gemini-tape.d.ts +14 -0
- package/esm/src/providers/gemini-tape.js +9 -0
- package/esm/src/providers/google-tap.d.ts +10 -0
- package/esm/src/providers/google-tap.js +2 -0
- package/esm/src/providers/interactions.d.ts +37 -1
- package/esm/src/providers/interactions.js +16 -0
- package/esm/src/providers/keys.d.ts +19 -0
- package/esm/src/providers/keys.js +24 -3
- package/esm/src/providers/openrouter-payload.d.ts +9 -2
- package/esm/src/providers/openrouter-payload.js +23 -7
- package/esm/src/providers/openrouter.d.ts +118 -1
- package/esm/src/providers/openrouter.js +106 -141
- package/esm/src/providers/pcm.d.ts +7 -0
- package/esm/src/providers/pcm.js +2 -0
- package/esm/src/providers/provider.d.ts +29 -1
- package/esm/src/providers/provider.js +18 -2
- package/esm/src/providers/speech.d.ts +18 -1
- package/esm/src/providers/speech.js +11 -0
- package/esm/src/providers/sse.d.ts +8 -0
- package/esm/src/providers/sse.js +2 -0
- package/esm/src/streaming/mod.d.ts +7 -0
- package/esm/src/streaming/mod.js +7 -0
- package/esm/src/streaming/readStreamingJsonStringField.d.ts +6 -0
- package/esm/src/streaming/readStreamingJsonStringField.js +55 -0
- package/package.json +6 -3
|
@@ -7,9 +7,126 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
import type
|
|
10
|
+
import { createOpenRouter, type OpenRouterChatSettings } from '@openrouter/ai-sdk-provider';
|
|
11
|
+
import { type LanguageModelUsage, type ModelMessage, streamText, type TextStreamPart, type ToolSet } from 'ai';
|
|
12
|
+
import type { DynamicToolDeclaration, InteractionPart, ModelProvider, ProviderCompleteRequest, TurnEvent, TurnHistoryMessage, TurnTokens } from '../kernel/types.js';
|
|
11
13
|
import { type OpenRouterConfig, resolveOpenRouterModel, toOpenRouterPayload } from './openrouter-payload.js';
|
|
14
|
+
interface StreamAccumulator {
|
|
15
|
+
text: string;
|
|
16
|
+
evidenceSeen: boolean;
|
|
17
|
+
emittedTokens: boolean;
|
|
18
|
+
errored: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface OpenRouterStreamContext {
|
|
21
|
+
openrouter: ReturnType<typeof createOpenRouter>;
|
|
22
|
+
modelName: string;
|
|
23
|
+
}
|
|
24
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
25
|
+
[key: string]: JsonValue;
|
|
26
|
+
};
|
|
27
|
+
type ProviderOptions = Record<string, {
|
|
28
|
+
[key: string]: JsonValue;
|
|
29
|
+
}>;
|
|
30
|
+
declare function trimApiKey(explicitKey?: string): string | undefined;
|
|
31
|
+
declare function createAccumulator(): StreamAccumulator;
|
|
32
|
+
declare function mediaPart(part: InteractionPart): Record<string, unknown>;
|
|
33
|
+
declare function contentFromParts(parts: InteractionPart[]): string | Array<Record<string, unknown>>;
|
|
34
|
+
declare function parseToolInput(raw: string): unknown;
|
|
35
|
+
declare function stringDefault(value: string | undefined, fallback: string): string;
|
|
36
|
+
declare function fallbackToolCallId(name?: string): string;
|
|
37
|
+
declare function toolResultContent(msg: TurnHistoryMessage): ModelMessage;
|
|
38
|
+
declare function assistantToolCallContent(msg: TurnHistoryMessage): ModelMessage | null;
|
|
39
|
+
declare function historyMessage(msg: TurnHistoryMessage): ModelMessage | null;
|
|
40
|
+
declare function contentFromOptionalParts(parts: InteractionPart[] | undefined, text: string | undefined): string | Array<Record<string, unknown>>;
|
|
41
|
+
declare function contentHistoryMessage(msg: TurnHistoryMessage): ModelMessage;
|
|
42
|
+
declare function sourceEvent(part: Extract<TextStreamPart<ToolSet>, {
|
|
43
|
+
type: 'source';
|
|
44
|
+
}>): TurnEvent;
|
|
45
|
+
declare function buildMessages(req: ProviderCompleteRequest): ModelMessage[];
|
|
46
|
+
declare function schemaForTool(decl: DynamicToolDeclaration): Record<string, unknown>;
|
|
47
|
+
declare function buildTools(dynamicTools?: DynamicToolDeclaration[]): ToolSet | undefined;
|
|
48
|
+
declare function openRouterSettings(req: ProviderCompleteRequest): OpenRouterChatSettings | undefined;
|
|
49
|
+
declare function tokensFromUsage(usage: LanguageModelUsage): TurnTokens | undefined;
|
|
50
|
+
declare function rawRecord(value: unknown): Record<string, unknown> | undefined;
|
|
51
|
+
declare function stringArray(value: unknown): string[] | undefined;
|
|
52
|
+
declare function metadataRecord(raw: Record<string, unknown>, key: string): Record<string, unknown> | undefined;
|
|
53
|
+
declare function citationCandidates(raw: Record<string, unknown>): unknown[];
|
|
54
|
+
declare function nestedCitations(raw: Record<string, unknown>): string[] | undefined;
|
|
55
|
+
declare function metadataAnnotations(raw: Record<string, unknown>): unknown[] | undefined;
|
|
56
|
+
declare function evidenceFromMetadata(metadata: unknown, acc: StreamAccumulator): TurnEvent | undefined;
|
|
57
|
+
declare function toolArguments(input: unknown): Record<string, unknown> | undefined;
|
|
58
|
+
declare function toolResultData(output: unknown): Record<string, unknown> | undefined;
|
|
59
|
+
declare function rawThoughtEvent(raw: Record<string, unknown>): TurnEvent | undefined;
|
|
60
|
+
declare function rawChoiceMessageEvidence(raw: Record<string, unknown>, acc: StreamAccumulator): TurnEvent | undefined;
|
|
61
|
+
declare function rawEvents(raw: unknown, acc: StreamAccumulator): TurnEvent[];
|
|
62
|
+
declare function toolCallEvent(part: Extract<TextStreamPart<ToolSet>, {
|
|
63
|
+
type: 'tool-call';
|
|
64
|
+
}>): TurnEvent;
|
|
65
|
+
declare function toolResultEvent(part: Extract<TextStreamPart<ToolSet>, {
|
|
66
|
+
type: 'tool-result';
|
|
67
|
+
}>): TurnEvent;
|
|
68
|
+
declare function tokenEvent(part: Extract<TextStreamPart<ToolSet>, {
|
|
69
|
+
type: 'finish';
|
|
70
|
+
}>): TurnEvent | undefined;
|
|
71
|
+
declare function providerMetadataEvent(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): TurnEvent | undefined;
|
|
72
|
+
declare function eventFromPart(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): TurnEvent[];
|
|
73
|
+
declare function primaryEventFromPart(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): TurnEvent | undefined;
|
|
74
|
+
declare function finishEvent(part: Extract<TextStreamPart<ToolSet>, {
|
|
75
|
+
type: 'finish';
|
|
76
|
+
}>, acc: StreamAccumulator): TurnEvent | undefined;
|
|
77
|
+
declare function streamTextOptions(req: ProviderCompleteRequest, context: OpenRouterStreamContext): Parameters<typeof streamText>[0];
|
|
78
|
+
declare function missingOpenRouterKey(): TurnEvent;
|
|
79
|
+
declare function finalEvents(req: ProviderCompleteRequest, acc: StreamAccumulator): Generator<TurnEvent>;
|
|
80
|
+
declare function responseFormatFor(req: ProviderCompleteRequest): Record<string, JsonValue> | undefined;
|
|
81
|
+
declare function providerOptionsFor(req: ProviderCompleteRequest): ProviderOptions | undefined;
|
|
82
|
+
declare function openRouterHeaders(config: OpenRouterConfig): Record<string, string> | undefined;
|
|
12
83
|
/** Create a `ModelProvider` backed by OpenRouter through AI SDK Core. */
|
|
13
84
|
declare function createOpenRouterProvider(config?: OpenRouterConfig): ModelProvider;
|
|
14
85
|
export type { OpenRouterConfig };
|
|
15
86
|
export { createOpenRouterProvider, resolveOpenRouterModel, toOpenRouterPayload };
|
|
87
|
+
/** @internal Exported for direct unit testing only. */
|
|
88
|
+
export declare const _internals: {
|
|
89
|
+
trimApiKey: typeof trimApiKey;
|
|
90
|
+
createAccumulator: typeof createAccumulator;
|
|
91
|
+
mediaPart: typeof mediaPart;
|
|
92
|
+
contentFromParts: typeof contentFromParts;
|
|
93
|
+
parseToolInput: typeof parseToolInput;
|
|
94
|
+
stringDefault: typeof stringDefault;
|
|
95
|
+
fallbackToolCallId: typeof fallbackToolCallId;
|
|
96
|
+
toolResultContent: typeof toolResultContent;
|
|
97
|
+
assistantToolCallContent: typeof assistantToolCallContent;
|
|
98
|
+
historyMessage: typeof historyMessage;
|
|
99
|
+
contentFromOptionalParts: typeof contentFromOptionalParts;
|
|
100
|
+
contentHistoryMessage: typeof contentHistoryMessage;
|
|
101
|
+
buildMessages: typeof buildMessages;
|
|
102
|
+
schemaForTool: typeof schemaForTool;
|
|
103
|
+
buildTools: typeof buildTools;
|
|
104
|
+
openRouterSettings: typeof openRouterSettings;
|
|
105
|
+
tokensFromUsage: typeof tokensFromUsage;
|
|
106
|
+
rawRecord: typeof rawRecord;
|
|
107
|
+
stringArray: typeof stringArray;
|
|
108
|
+
metadataRecord: typeof metadataRecord;
|
|
109
|
+
citationCandidates: typeof citationCandidates;
|
|
110
|
+
nestedCitations: typeof nestedCitations;
|
|
111
|
+
metadataAnnotations: typeof metadataAnnotations;
|
|
112
|
+
evidenceFromMetadata: typeof evidenceFromMetadata;
|
|
113
|
+
toolArguments: typeof toolArguments;
|
|
114
|
+
toolResultData: typeof toolResultData;
|
|
115
|
+
rawThoughtEvent: typeof rawThoughtEvent;
|
|
116
|
+
rawChoiceMessageEvidence: typeof rawChoiceMessageEvidence;
|
|
117
|
+
rawEvents: typeof rawEvents;
|
|
118
|
+
toolCallEvent: typeof toolCallEvent;
|
|
119
|
+
toolResultEvent: typeof toolResultEvent;
|
|
120
|
+
tokenEvent: typeof tokenEvent;
|
|
121
|
+
providerMetadataEvent: typeof providerMetadataEvent;
|
|
122
|
+
eventFromPart: typeof eventFromPart;
|
|
123
|
+
primaryEventFromPart: typeof primaryEventFromPart;
|
|
124
|
+
finishEvent: typeof finishEvent;
|
|
125
|
+
sourceEvent: typeof sourceEvent;
|
|
126
|
+
finalEvents: typeof finalEvents;
|
|
127
|
+
responseFormatFor: typeof responseFormatFor;
|
|
128
|
+
providerOptionsFor: typeof providerOptionsFor;
|
|
129
|
+
openRouterHeaders: typeof openRouterHeaders;
|
|
130
|
+
missingOpenRouterKey: typeof missingOpenRouterKey;
|
|
131
|
+
streamTextOptions: typeof streamTextOptions;
|
|
132
|
+
};
|
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
11
11
|
import { jsonSchema, streamText, tool, } from 'ai';
|
|
12
|
-
import { toErrorEvent } from '../guardrails/error.js';
|
|
12
|
+
import { isAbortError, toErrorEvent } from '../guardrails/error.js';
|
|
13
13
|
import { tryStructured } from '../kernel/engine/delta.js';
|
|
14
|
-
import {
|
|
15
|
-
import { resolveOpenRouterModel, toOpenRouterPayload, } from './openrouter-payload.js';
|
|
16
|
-
import { takeSsePayloads } from './sse.js';
|
|
14
|
+
import { getStructured } from '../kernel/registry/schemas.js';
|
|
15
|
+
import { resolveOpenRouterModel, resolveOpenRouterPlugins, toOpenRouterPayload, } from './openrouter-payload.js';
|
|
17
16
|
function trimApiKey(explicitKey) {
|
|
18
17
|
if (explicitKey?.trim()) {
|
|
19
18
|
return explicitKey.trim();
|
|
@@ -26,7 +25,6 @@ function createAccumulator() {
|
|
|
26
25
|
evidenceSeen: false,
|
|
27
26
|
emittedTokens: false,
|
|
28
27
|
errored: false,
|
|
29
|
-
toolInputs: new Map(),
|
|
30
28
|
};
|
|
31
29
|
}
|
|
32
30
|
function mediaPart(part) {
|
|
@@ -61,9 +59,6 @@ function parseToolInput(raw) {
|
|
|
61
59
|
function stringDefault(value, fallback) {
|
|
62
60
|
return value === undefined ? fallback : value;
|
|
63
61
|
}
|
|
64
|
-
function optionalSingle(value) {
|
|
65
|
-
return value === undefined ? undefined : [value];
|
|
66
|
-
}
|
|
67
62
|
function fallbackToolCallId(name) {
|
|
68
63
|
return `call_${stringDefault(name, 'tool')}`;
|
|
69
64
|
}
|
|
@@ -111,34 +106,21 @@ function contentHistoryMessage(msg) {
|
|
|
111
106
|
const content = contentFromOptionalParts(msg.parts, msg.content);
|
|
112
107
|
return { role: msg.role, content };
|
|
113
108
|
}
|
|
114
|
-
function sourceValue(part, key) {
|
|
115
|
-
const source = part;
|
|
116
|
-
const value = source[key];
|
|
117
|
-
return typeof value === 'string' ? value : undefined;
|
|
118
|
-
}
|
|
119
|
-
function sourceUrl(part) {
|
|
120
|
-
return sourceValue(part, 'url');
|
|
121
|
-
}
|
|
122
|
-
function sourceTitle(part) {
|
|
123
|
-
return sourceValue(part, 'title') ?? sourceUrl(part);
|
|
124
|
-
}
|
|
125
|
-
function sourceList(title, url) {
|
|
126
|
-
if (title === undefined || url === undefined) {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
return [{ title, uri: url, type: 'web' }];
|
|
130
|
-
}
|
|
131
109
|
function sourceEvent(part) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
110
|
+
if (part.sourceType !== 'url') {
|
|
111
|
+
return {
|
|
112
|
+
type: 'evidence',
|
|
113
|
+
evidence: { provider: 'openrouter', raw: part },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const title = part.title ?? part.url;
|
|
135
117
|
return {
|
|
136
118
|
type: 'evidence',
|
|
137
119
|
evidence: {
|
|
138
120
|
provider: 'openrouter',
|
|
139
|
-
raw,
|
|
140
|
-
citations:
|
|
141
|
-
sources:
|
|
121
|
+
raw: part,
|
|
122
|
+
citations: [part.url],
|
|
123
|
+
sources: [{ title, uri: part.url, type: 'web' }],
|
|
142
124
|
},
|
|
143
125
|
};
|
|
144
126
|
}
|
|
@@ -171,19 +153,17 @@ function buildTools(dynamicTools) {
|
|
|
171
153
|
}
|
|
172
154
|
return tools;
|
|
173
155
|
}
|
|
174
|
-
function openRouterPlugins(req) {
|
|
175
|
-
const plugins = req.builtins
|
|
176
|
-
.map((id) => getTool(id)?.openRouterPlugin)
|
|
177
|
-
.filter((id) => Boolean(id))
|
|
178
|
-
.map((id) => ({ id }));
|
|
179
|
-
return plugins.length > 0 ? plugins : undefined;
|
|
180
|
-
}
|
|
181
156
|
function openRouterSettings(req) {
|
|
182
|
-
const plugins =
|
|
183
|
-
if (!
|
|
157
|
+
const { plugins, webSearch } = resolveOpenRouterPlugins(req.builtins);
|
|
158
|
+
if (plugins.length === 0 && !webSearch) {
|
|
184
159
|
return undefined;
|
|
185
160
|
}
|
|
186
|
-
|
|
161
|
+
const settings = {};
|
|
162
|
+
if (plugins.length > 0)
|
|
163
|
+
settings.plugins = plugins;
|
|
164
|
+
if (webSearch)
|
|
165
|
+
settings.web_search_options = {};
|
|
166
|
+
return settings;
|
|
187
167
|
}
|
|
188
168
|
function tokensFromUsage(usage) {
|
|
189
169
|
const input = usage.inputTokens ?? 0;
|
|
@@ -269,17 +249,6 @@ function toolArguments(input) {
|
|
|
269
249
|
}
|
|
270
250
|
return { value: input };
|
|
271
251
|
}
|
|
272
|
-
function isEmptyRecord(input) {
|
|
273
|
-
return Boolean(input) && Object.keys(input ?? {}).length === 0;
|
|
274
|
-
}
|
|
275
|
-
function toolCallArguments(part, acc) {
|
|
276
|
-
const fromInput = toolArguments(part.input);
|
|
277
|
-
const rawInput = acc.toolInputs.get(part.toolCallId);
|
|
278
|
-
if ((!fromInput || isEmptyRecord(fromInput)) && rawInput) {
|
|
279
|
-
return toolArguments(parseToolInput(rawInput));
|
|
280
|
-
}
|
|
281
|
-
return fromInput;
|
|
282
|
-
}
|
|
283
252
|
function toolResultData(output) {
|
|
284
253
|
return rawRecord(output);
|
|
285
254
|
}
|
|
@@ -333,45 +302,12 @@ function rawEvents(raw, acc) {
|
|
|
333
302
|
}
|
|
334
303
|
return events;
|
|
335
304
|
}
|
|
336
|
-
|
|
337
|
-
if (!res.body) {
|
|
338
|
-
return [];
|
|
339
|
-
}
|
|
340
|
-
const reader = res.body.getReader();
|
|
341
|
-
const decoder = new TextDecoder();
|
|
342
|
-
const events = [];
|
|
343
|
-
const acc = createAccumulator();
|
|
344
|
-
let buffer = '';
|
|
345
|
-
let pendingEvent = '';
|
|
346
|
-
while (true) {
|
|
347
|
-
const { done, value } = await reader.read();
|
|
348
|
-
if (done) {
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
buffer += decoder.decode(value, { stream: true });
|
|
352
|
-
const taken = takeSsePayloads(buffer, pendingEvent);
|
|
353
|
-
buffer = taken.rest;
|
|
354
|
-
pendingEvent = taken.pendingEvent;
|
|
355
|
-
for (const payload of taken.payloads) {
|
|
356
|
-
events.push(...rawEvents(payload, acc));
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return events;
|
|
360
|
-
}
|
|
361
|
-
function captureFetch(config, setCapture) {
|
|
362
|
-
const fetchFn = config.fetch ?? fetch;
|
|
363
|
-
return async (input, init) => {
|
|
364
|
-
const res = await fetchFn(input, init);
|
|
365
|
-
setCapture(collectRawEvents(res.clone()).catch(() => []));
|
|
366
|
-
return res;
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
function toolCallEvent(part, acc) {
|
|
305
|
+
function toolCallEvent(part) {
|
|
370
306
|
return {
|
|
371
307
|
type: 'tool',
|
|
372
308
|
tool: {
|
|
373
309
|
name: part.toolName,
|
|
374
|
-
arguments:
|
|
310
|
+
arguments: toolArguments(part.input),
|
|
375
311
|
id: part.toolCallId,
|
|
376
312
|
},
|
|
377
313
|
};
|
|
@@ -401,22 +337,7 @@ function providerMetadataEvent(part, acc) {
|
|
|
401
337
|
}
|
|
402
338
|
return evidenceFromMetadata(part.providerMetadata, acc);
|
|
403
339
|
}
|
|
404
|
-
function appendToolInput(part, acc) {
|
|
405
|
-
if (part.type === 'tool-input-start') {
|
|
406
|
-
acc.toolInputs.set(part.id, '');
|
|
407
|
-
return true;
|
|
408
|
-
}
|
|
409
|
-
if (part.type === 'tool-input-delta') {
|
|
410
|
-
const current = acc.toolInputs.get(part.id) ?? '';
|
|
411
|
-
acc.toolInputs.set(part.id, current + part.delta);
|
|
412
|
-
return true;
|
|
413
|
-
}
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
340
|
function eventFromPart(part, acc) {
|
|
417
|
-
if (appendToolInput(part, acc)) {
|
|
418
|
-
return [];
|
|
419
|
-
}
|
|
420
341
|
const mapped = primaryEventFromPart(part, acc);
|
|
421
342
|
if (mapped) {
|
|
422
343
|
return [mapped];
|
|
@@ -432,11 +353,15 @@ function primaryEventFromPart(part, acc) {
|
|
|
432
353
|
case 'reasoning-delta':
|
|
433
354
|
return { type: 'thought', text: part.text };
|
|
434
355
|
case 'tool-call':
|
|
435
|
-
return toolCallEvent(part
|
|
356
|
+
return toolCallEvent(part);
|
|
436
357
|
case 'tool-result':
|
|
437
358
|
return toolResultEvent(part);
|
|
438
|
-
case 'source':
|
|
359
|
+
case 'source': {
|
|
360
|
+
if (acc.evidenceSeen)
|
|
361
|
+
return undefined;
|
|
362
|
+
acc.evidenceSeen = true;
|
|
439
363
|
return sourceEvent(part);
|
|
364
|
+
}
|
|
440
365
|
case 'finish':
|
|
441
366
|
return finishEvent(part, acc);
|
|
442
367
|
case 'error':
|
|
@@ -456,27 +381,12 @@ function finishEvent(part, acc) {
|
|
|
456
381
|
}
|
|
457
382
|
return event;
|
|
458
383
|
}
|
|
459
|
-
function emitRawEvents(rawCapture, acc) {
|
|
460
|
-
return (rawCapture ?? Promise.resolve([])).then((events) => events.filter((event) => {
|
|
461
|
-
if (event.type !== 'evidence') {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
if (acc.evidenceSeen) {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
acc.evidenceSeen = true;
|
|
468
|
-
return true;
|
|
469
|
-
}));
|
|
470
|
-
}
|
|
471
384
|
function createStreamContext(req, config, apiKey) {
|
|
472
|
-
let rawCapture;
|
|
473
385
|
const openrouter = createOpenRouter({
|
|
474
386
|
apiKey,
|
|
475
387
|
baseURL: config.baseUrl,
|
|
476
388
|
headers: openRouterHeaders(config),
|
|
477
|
-
fetch:
|
|
478
|
-
rawCapture = capture;
|
|
479
|
-
}),
|
|
389
|
+
fetch: config.fetch,
|
|
480
390
|
compatibility: 'strict',
|
|
481
391
|
});
|
|
482
392
|
return {
|
|
@@ -485,26 +395,26 @@ function createStreamContext(req, config, apiKey) {
|
|
|
485
395
|
apiId: req.apiId,
|
|
486
396
|
openRouterId: req.openRouterId,
|
|
487
397
|
}),
|
|
488
|
-
rawCapture: () => rawCapture,
|
|
489
398
|
};
|
|
490
399
|
}
|
|
491
400
|
function streamTextOptions(req, context) {
|
|
492
401
|
return {
|
|
493
402
|
model: context.openrouter.chat(context.modelName, openRouterSettings(req)),
|
|
494
|
-
|
|
403
|
+
instructions: req.system,
|
|
495
404
|
messages: buildMessages(req),
|
|
496
405
|
allowSystemInMessages: true,
|
|
497
406
|
temperature: req.temperature,
|
|
498
407
|
maxOutputTokens: req.maxOutputTokens,
|
|
499
408
|
tools: buildTools(req.dynamicTools),
|
|
500
409
|
providerOptions: providerOptionsFor(req),
|
|
501
|
-
|
|
410
|
+
include: { rawChunks: true },
|
|
411
|
+
abortSignal: req.signal,
|
|
502
412
|
onError: () => undefined,
|
|
503
413
|
};
|
|
504
414
|
}
|
|
505
415
|
async function* yieldAiSdkStream(req, acc, context) {
|
|
506
416
|
const result = streamText(streamTextOptions(req, context));
|
|
507
|
-
for await (const part of result.
|
|
417
|
+
for await (const part of result.stream) {
|
|
508
418
|
if (part.type === 'raw') {
|
|
509
419
|
req.tapGemini?.(rawRecord(part.rawValue) ?? { rawValue: part.rawValue });
|
|
510
420
|
for (const event of rawEvents(part.rawValue, acc)) {
|
|
@@ -517,16 +427,6 @@ async function* yieldAiSdkStream(req, acc, context) {
|
|
|
517
427
|
}
|
|
518
428
|
}
|
|
519
429
|
}
|
|
520
|
-
async function* yieldCapturedRawEvents(context, acc) {
|
|
521
|
-
for (const event of await emitRawEvents(context.rawCapture(), acc)) {
|
|
522
|
-
yield event;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
async function* yieldCapturedRawEventsUnchecked(context) {
|
|
526
|
-
for (const event of await (context.rawCapture() ?? Promise.resolve([]))) {
|
|
527
|
-
yield event;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
430
|
function missingOpenRouterKey() {
|
|
531
431
|
return toErrorEvent('missing OpenRouter API key');
|
|
532
432
|
}
|
|
@@ -540,11 +440,12 @@ async function* streamOpenRouter(req, config) {
|
|
|
540
440
|
const context = createStreamContext(req, config, apiKey);
|
|
541
441
|
try {
|
|
542
442
|
yield* yieldAiSdkStream(req, acc, context);
|
|
543
|
-
yield* yieldCapturedRawEvents(context, acc);
|
|
544
443
|
yield* finalEvents(req, acc);
|
|
545
444
|
}
|
|
546
445
|
catch (err) {
|
|
547
|
-
|
|
446
|
+
if (isAbortError(err)) {
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
548
449
|
yield toErrorEvent(err);
|
|
549
450
|
}
|
|
550
451
|
}
|
|
@@ -560,16 +461,34 @@ function* finalEvents(req, acc) {
|
|
|
560
461
|
}
|
|
561
462
|
yield { type: 'done' };
|
|
562
463
|
}
|
|
563
|
-
function
|
|
564
|
-
if (req.
|
|
464
|
+
function responseFormatFor(req) {
|
|
465
|
+
if (!req.structured)
|
|
466
|
+
return undefined;
|
|
467
|
+
const spec = getStructured(req.structured);
|
|
468
|
+
if (!spec.jsonSchema)
|
|
565
469
|
return undefined;
|
|
566
|
-
}
|
|
567
470
|
return {
|
|
568
|
-
|
|
569
|
-
|
|
471
|
+
type: 'json_schema',
|
|
472
|
+
json_schema: {
|
|
473
|
+
name: String(req.structured),
|
|
474
|
+
strict: true,
|
|
475
|
+
schema: spec.jsonSchema,
|
|
570
476
|
},
|
|
571
477
|
};
|
|
572
478
|
}
|
|
479
|
+
function providerOptionsFor(req) {
|
|
480
|
+
const openrouter = {};
|
|
481
|
+
if (req.thinking !== 'none') {
|
|
482
|
+
openrouter.reasoning = { effort: req.thinking };
|
|
483
|
+
}
|
|
484
|
+
const responseFormat = responseFormatFor(req);
|
|
485
|
+
if (responseFormat) {
|
|
486
|
+
openrouter.response_format = responseFormat;
|
|
487
|
+
}
|
|
488
|
+
if (Object.keys(openrouter).length === 0)
|
|
489
|
+
return undefined;
|
|
490
|
+
return { openrouter };
|
|
491
|
+
}
|
|
573
492
|
function openRouterHeaders(config) {
|
|
574
493
|
const headers = {};
|
|
575
494
|
if (config.siteUrl) {
|
|
@@ -587,3 +506,49 @@ function createOpenRouterProvider(config = {}) {
|
|
|
587
506
|
};
|
|
588
507
|
}
|
|
589
508
|
export { createOpenRouterProvider, resolveOpenRouterModel, toOpenRouterPayload };
|
|
509
|
+
/** @internal Exported for direct unit testing only. */
|
|
510
|
+
export const _internals = {
|
|
511
|
+
trimApiKey,
|
|
512
|
+
createAccumulator,
|
|
513
|
+
mediaPart,
|
|
514
|
+
contentFromParts,
|
|
515
|
+
parseToolInput,
|
|
516
|
+
stringDefault,
|
|
517
|
+
fallbackToolCallId,
|
|
518
|
+
toolResultContent,
|
|
519
|
+
assistantToolCallContent,
|
|
520
|
+
historyMessage,
|
|
521
|
+
contentFromOptionalParts,
|
|
522
|
+
contentHistoryMessage,
|
|
523
|
+
buildMessages,
|
|
524
|
+
schemaForTool,
|
|
525
|
+
buildTools,
|
|
526
|
+
openRouterSettings,
|
|
527
|
+
tokensFromUsage,
|
|
528
|
+
rawRecord,
|
|
529
|
+
stringArray,
|
|
530
|
+
metadataRecord,
|
|
531
|
+
citationCandidates,
|
|
532
|
+
nestedCitations,
|
|
533
|
+
metadataAnnotations,
|
|
534
|
+
evidenceFromMetadata,
|
|
535
|
+
toolArguments,
|
|
536
|
+
toolResultData,
|
|
537
|
+
rawThoughtEvent,
|
|
538
|
+
rawChoiceMessageEvidence,
|
|
539
|
+
rawEvents,
|
|
540
|
+
toolCallEvent,
|
|
541
|
+
toolResultEvent,
|
|
542
|
+
tokenEvent,
|
|
543
|
+
providerMetadataEvent,
|
|
544
|
+
eventFromPart,
|
|
545
|
+
primaryEventFromPart,
|
|
546
|
+
finishEvent,
|
|
547
|
+
sourceEvent,
|
|
548
|
+
finalEvents,
|
|
549
|
+
responseFormatFor,
|
|
550
|
+
providerOptionsFor,
|
|
551
|
+
openRouterHeaders,
|
|
552
|
+
missingOpenRouterKey,
|
|
553
|
+
streamTextOptions,
|
|
554
|
+
};
|
|
@@ -3,5 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
+
declare function writeAscii(view: DataView, offset: number, str: string): void;
|
|
6
7
|
/** Wrap raw PCM bytes in a RIFF/WAVE container. */
|
|
7
8
|
export declare function wrapPcmAsWav(pcm: Uint8Array, sampleRate?: number): Uint8Array;
|
|
9
|
+
/** @internal Exported for direct unit testing only. */
|
|
10
|
+
export declare const _internals: {
|
|
11
|
+
writeAscii: typeof writeAscii;
|
|
12
|
+
wrapPcmAsWav: typeof wrapPcmAsWav;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
package/esm/src/providers/pcm.js
CHANGED
|
@@ -8,8 +8,36 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
-
import type { ModelProvider } from '../kernel/types.js';
|
|
11
|
+
import type { ModelProvider, ProviderCompleteRequest, TurnEvent } from '../kernel/types.js';
|
|
12
12
|
import { type GeminiTransport } from './keys.js';
|
|
13
|
+
declare function base64ToBytes(data: string): Uint8Array;
|
|
14
|
+
declare function bytesToBase64(bytes: Uint8Array): string;
|
|
15
|
+
declare function isRawPcmMime(mime: string): boolean;
|
|
16
|
+
/** Google TTS returns raw PCM; wrap as WAV for hosts (matches OpenRouter pcm path). */
|
|
17
|
+
declare function normalizeSpeechMedia(event: TurnEvent, speech: boolean): TurnEvent;
|
|
18
|
+
declare function eventType(event: Record<string, unknown>): string;
|
|
19
|
+
declare function isDeltaEvent(kind: string): boolean;
|
|
20
|
+
declare function isCompleteEvent(kind: string): boolean;
|
|
21
|
+
declare function foldDeltaPayload(event: Record<string, unknown>, acc: {
|
|
22
|
+
text: string;
|
|
23
|
+
}): TurnEvent[];
|
|
24
|
+
declare function foldPayload(event: Record<string, unknown>, acc: {
|
|
25
|
+
text: string;
|
|
26
|
+
}): TurnEvent[];
|
|
27
|
+
declare function withTap(req: ProviderCompleteRequest, transport: GeminiTransport): GeminiTransport;
|
|
13
28
|
/** Create a `ModelProvider` backed by Google Interactions streaming. */
|
|
14
29
|
declare function createInteractionsProvider(transport: GeminiTransport): ModelProvider;
|
|
15
30
|
export { createInteractionsProvider };
|
|
31
|
+
/** @internal Exported for direct unit testing only. */
|
|
32
|
+
export declare const _internals: {
|
|
33
|
+
base64ToBytes: typeof base64ToBytes;
|
|
34
|
+
bytesToBase64: typeof bytesToBase64;
|
|
35
|
+
isRawPcmMime: typeof isRawPcmMime;
|
|
36
|
+
normalizeSpeechMedia: typeof normalizeSpeechMedia;
|
|
37
|
+
eventType: typeof eventType;
|
|
38
|
+
isDeltaEvent: typeof isDeltaEvent;
|
|
39
|
+
isCompleteEvent: typeof isCompleteEvent;
|
|
40
|
+
foldDeltaPayload: typeof foldDeltaPayload;
|
|
41
|
+
foldPayload: typeof foldPayload;
|
|
42
|
+
withTap: typeof withTap;
|
|
43
|
+
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
-
import { TheorumError, toErrorEvent } from '../guardrails/error.js';
|
|
11
|
+
import { isAbortError, TheorumError, toErrorEvent } from '../guardrails/error.js';
|
|
12
12
|
import { eventsFromComplete, eventsFromDelta, extractTokenEvent, groundingFromEvent, tryStructured, } from '../kernel/engine/delta.js';
|
|
13
13
|
import { tapFetch } from './google-tap.js';
|
|
14
14
|
import { toInteractionsBody } from './interactions.js';
|
|
@@ -121,7 +121,7 @@ async function* streamComplete(req, transport) {
|
|
|
121
121
|
yield toErrorEvent('missing Gemini vault bucket for Interactions');
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
const res = await fetchGemini(INTERACTIONS_URL, { method: 'POST', body: JSON.stringify(toInteractionsBody(req)) }, req.geminiBucket, withTap(req, transport));
|
|
124
|
+
const res = await fetchGemini(INTERACTIONS_URL, { method: 'POST', body: JSON.stringify(toInteractionsBody(req)), signal: req.signal }, req.geminiBucket, withTap(req, transport));
|
|
125
125
|
if (res.status !== HTTP_OK) {
|
|
126
126
|
const errorBody = await res.text().catch(() => '');
|
|
127
127
|
yield toErrorEvent(`Gemini HTTP ${String(res.status)}: ${errorBody}`);
|
|
@@ -148,6 +148,9 @@ async function* streamGuarded(req, transport) {
|
|
|
148
148
|
yield* streamComplete(req, transport);
|
|
149
149
|
}
|
|
150
150
|
catch (err) {
|
|
151
|
+
if (isAbortError(err)) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
151
154
|
yield toErrorEvent(err);
|
|
152
155
|
}
|
|
153
156
|
}
|
|
@@ -158,3 +161,16 @@ function createInteractionsProvider(transport) {
|
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
export { createInteractionsProvider };
|
|
164
|
+
/** @internal Exported for direct unit testing only. */
|
|
165
|
+
export const _internals = {
|
|
166
|
+
base64ToBytes,
|
|
167
|
+
bytesToBase64,
|
|
168
|
+
isRawPcmMime,
|
|
169
|
+
normalizeSpeechMedia,
|
|
170
|
+
eventType,
|
|
171
|
+
isDeltaEvent,
|
|
172
|
+
isCompleteEvent,
|
|
173
|
+
foldDeltaPayload,
|
|
174
|
+
foldPayload,
|
|
175
|
+
withTap,
|
|
176
|
+
};
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { ModelProvider, ProviderCompleteRequest, TurnEvent } from '../kernel/types.js';
|
|
9
|
+
import type { InteractionPart, ModelProvider, ProfileSpeechSpec, ProviderCompleteRequest, SpeechAudioFormat, TurnEvent } from '../kernel/types.js';
|
|
10
|
+
declare function bytesToBase64(bytes: Uint8Array): string;
|
|
10
11
|
/** Credentials for the openAi speech path (same shape as OpenRouter chat config + voice). */
|
|
11
12
|
export interface SpeechProviderConfig {
|
|
12
13
|
apiKey?: string;
|
|
@@ -17,7 +18,23 @@ export interface SpeechProviderConfig {
|
|
|
17
18
|
siteName?: string;
|
|
18
19
|
fetch?: typeof fetch;
|
|
19
20
|
}
|
|
21
|
+
declare function extractInputText(input: InteractionPart[]): string;
|
|
22
|
+
declare function resolveSpeechWireModel(req: ProviderCompleteRequest): string;
|
|
23
|
+
declare function buildHeaders(apiKey: string, config: SpeechProviderConfig): Record<string, string>;
|
|
24
|
+
declare function buildPayload(req: ProviderCompleteRequest, text: string, speech: ProfileSpeechSpec | undefined, configVoice?: string): Record<string, unknown>;
|
|
25
|
+
declare function requestSpeech(apiKey: string, text: string, req: ProviderCompleteRequest, config: SpeechProviderConfig): Promise<Response>;
|
|
26
|
+
declare function yieldSpeechSuccess(rawBytes: Uint8Array, text: string, format: SpeechAudioFormat): Generator<TurnEvent>;
|
|
20
27
|
declare function streamSpeech(req: ProviderCompleteRequest, config?: SpeechProviderConfig): AsyncGenerator<TurnEvent>;
|
|
21
28
|
/** Internal ModelProvider for openAi speech roles. */
|
|
22
29
|
declare function createSpeechProvider(config?: SpeechProviderConfig): ModelProvider;
|
|
23
30
|
export { createSpeechProvider, streamSpeech };
|
|
31
|
+
/** @internal Exported for direct unit testing only. */
|
|
32
|
+
export declare const _internals: {
|
|
33
|
+
bytesToBase64: typeof bytesToBase64;
|
|
34
|
+
extractInputText: typeof extractInputText;
|
|
35
|
+
resolveSpeechWireModel: typeof resolveSpeechWireModel;
|
|
36
|
+
buildHeaders: typeof buildHeaders;
|
|
37
|
+
buildPayload: typeof buildPayload;
|
|
38
|
+
requestSpeech: typeof requestSpeech;
|
|
39
|
+
yieldSpeechSuccess: typeof yieldSpeechSuccess;
|
|
40
|
+
};
|
|
@@ -68,6 +68,7 @@ async function requestSpeech(apiKey, text, req, config) {
|
|
|
68
68
|
method: 'POST',
|
|
69
69
|
headers: buildHeaders(apiKey, config),
|
|
70
70
|
body: JSON.stringify(buildPayload(req, text, req.speech, config.voice)),
|
|
71
|
+
signal: req.signal,
|
|
71
72
|
});
|
|
72
73
|
}
|
|
73
74
|
function* yieldSpeechSuccess(rawBytes, text, format) {
|
|
@@ -123,3 +124,13 @@ function createSpeechProvider(config = {}) {
|
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
export { createSpeechProvider, streamSpeech };
|
|
127
|
+
/** @internal Exported for direct unit testing only. */
|
|
128
|
+
export const _internals = {
|
|
129
|
+
bytesToBase64,
|
|
130
|
+
extractInputText,
|
|
131
|
+
resolveSpeechWireModel,
|
|
132
|
+
buildHeaders,
|
|
133
|
+
buildPayload,
|
|
134
|
+
requestSpeech,
|
|
135
|
+
yieldSpeechSuccess,
|
|
136
|
+
};
|