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
|
@@ -181,6 +181,7 @@ export interface Profile {
|
|
|
181
181
|
|
|
182
182
|
### Per-turn Interactions state
|
|
183
183
|
- `TurnRequest.input`: Optional turn input object. If omitted, Theorum normalizes it to an empty input and still runs the profile/provider turn.
|
|
184
|
+
- `TurnRequest.signal`: Optional `AbortSignal`. When aborted, THEORUM stops the turn and cancels in-flight provider HTTP (Gemini fetch, OpenRouter `abortSignal`, speech fetch). Traces mark `cancelled: true`.
|
|
184
185
|
- `TurnRequest.previousInteractionId`: Optional Google Interactions server-side conversation pointer. Theorum passes it through as `previous_interaction_id` for profiles using `geminiInteractions`.
|
|
185
186
|
- `TurnRequest.store`: Optional Google Interactions storage override. If omitted, Theorum does not send `store`; provider/project policy remains the authority. If supplied, Theorum serializes the explicit boolean.
|
|
186
187
|
|
package/esm/mod.d.ts
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* @module
|
|
38
38
|
*/
|
|
39
39
|
import "./_dnt.polyfills.js";
|
|
40
|
-
export { describeError, publicError, TheorumError, toErrorEvent } from './src/guardrails/error.js';
|
|
40
|
+
export { describeError, isAbortError, publicError, TheorumError, throwIfAborted, toErrorEvent, } from './src/guardrails/error.js';
|
|
41
41
|
export type { QuotaSlotStatus } from './src/guardrails/quota.js';
|
|
42
42
|
export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
|
|
43
43
|
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
|
package/esm/mod.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* @module
|
|
38
38
|
*/
|
|
39
39
|
import "./_dnt.polyfills.js";
|
|
40
|
-
export { describeError, publicError, TheorumError, toErrorEvent } from './src/guardrails/error.js';
|
|
40
|
+
export { describeError, isAbortError, publicError, TheorumError, throwIfAborted, toErrorEvent, } from './src/guardrails/error.js';
|
|
41
41
|
export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
|
|
42
42
|
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
|
|
43
43
|
export { runTurn } from './src/kernel/engine/runner.js';
|
|
@@ -28,6 +28,12 @@ declare const PUBLIC_FILE_SIZE = "That file is too large.";
|
|
|
28
28
|
declare const PUBLIC_FILE_COUNT = "Too many files for one message.";
|
|
29
29
|
/** Safe copy for unsupported generated image dimensions. */
|
|
30
30
|
declare const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
31
|
+
/** Safe copy when the host aborts a turn. */
|
|
32
|
+
declare const PUBLIC_CANCELLED = "Cancelled.";
|
|
33
|
+
/** True when `err` is an abort (DOMException or Error named AbortError). */
|
|
34
|
+
declare function isAbortError(err: unknown): boolean;
|
|
35
|
+
/** Throw if `signal` is already aborted. */
|
|
36
|
+
declare function throwIfAborted(signal?: AbortSignal): void;
|
|
31
37
|
/** Convert an unknown thrown value or internal message to user-safe text. */
|
|
32
38
|
declare function publicError(err: unknown): string;
|
|
33
39
|
/** Raw diagnostic text for hosts, traces, and logs (never shown to end users). */
|
|
@@ -42,4 +48,4 @@ declare function toErrorEvent(err: unknown): {
|
|
|
42
48
|
error: string;
|
|
43
49
|
errorInternal: string;
|
|
44
50
|
};
|
|
45
|
-
export { describeError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, toErrorEvent, UPSTREAM_FAILED, };
|
|
51
|
+
export { describeError, isAbortError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_CANCELLED, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, throwIfAborted, toErrorEvent, UPSTREAM_FAILED, };
|
|
@@ -31,10 +31,33 @@ const PUBLIC_FILE_SIZE = 'That file is too large.';
|
|
|
31
31
|
const PUBLIC_FILE_COUNT = 'Too many files for one message.';
|
|
32
32
|
/** Safe copy for unsupported generated image dimensions. */
|
|
33
33
|
const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
34
|
+
/** Safe copy when the host aborts a turn. */
|
|
35
|
+
const PUBLIC_CANCELLED = 'Cancelled.';
|
|
36
|
+
/** True when `err` is an abort (DOMException or Error named AbortError). */
|
|
37
|
+
function isAbortError(err) {
|
|
38
|
+
if (!err || typeof err !== 'object') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const name = err.name;
|
|
42
|
+
return name === 'AbortError';
|
|
43
|
+
}
|
|
44
|
+
/** Throw if `signal` is already aborted. */
|
|
45
|
+
function throwIfAborted(signal) {
|
|
46
|
+
if (!signal?.aborted) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { reason } = signal;
|
|
50
|
+
if (isAbortError(reason)) {
|
|
51
|
+
throw reason;
|
|
52
|
+
}
|
|
53
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
54
|
+
}
|
|
34
55
|
const EXACT = {
|
|
35
56
|
[UPSTREAM_FAILED]: PUBLIC_UNAVAILABLE,
|
|
36
57
|
'empty Gemini stream': PUBLIC_UNAVAILABLE,
|
|
37
58
|
'canary leaked': PUBLIC_CANARY,
|
|
59
|
+
'The operation was aborted.': PUBLIC_CANCELLED,
|
|
60
|
+
'This operation was aborted': PUBLIC_CANCELLED,
|
|
38
61
|
'Turn withheld: egress disclosure violation': PUBLIC_CANARY,
|
|
39
62
|
'expected JSON object': 'Something was wrong with that request.',
|
|
40
63
|
'user input cannot be placed in the system block': PUBLIC_GENERIC,
|
|
@@ -98,8 +121,12 @@ const ALREADY_PUBLIC = new Set([
|
|
|
98
121
|
PUBLIC_FILE_SIZE,
|
|
99
122
|
PUBLIC_FILE_COUNT,
|
|
100
123
|
PUBLIC_IMAGE_SIZE,
|
|
124
|
+
PUBLIC_CANCELLED,
|
|
101
125
|
]);
|
|
102
126
|
function publicText(text) {
|
|
127
|
+
if (/aborted/i.test(text)) {
|
|
128
|
+
return PUBLIC_CANCELLED;
|
|
129
|
+
}
|
|
103
130
|
if (ALREADY_PUBLIC.has(text)) {
|
|
104
131
|
return text;
|
|
105
132
|
}
|
|
@@ -116,6 +143,9 @@ function publicText(text) {
|
|
|
116
143
|
}
|
|
117
144
|
/** Convert an unknown thrown value or internal message to user-safe text. */
|
|
118
145
|
function publicError(err) {
|
|
146
|
+
if (isAbortError(err)) {
|
|
147
|
+
return PUBLIC_CANCELLED;
|
|
148
|
+
}
|
|
119
149
|
if (typeof err === 'string') {
|
|
120
150
|
return publicText(err);
|
|
121
151
|
}
|
|
@@ -146,4 +176,4 @@ function toErrorEvent(err) {
|
|
|
146
176
|
errorInternal: describeError(err),
|
|
147
177
|
};
|
|
148
178
|
}
|
|
149
|
-
export { describeError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, toErrorEvent, UPSTREAM_FAILED, };
|
|
179
|
+
export { describeError, isAbortError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_CANCELLED, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, throwIfAborted, toErrorEvent, UPSTREAM_FAILED, };
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import "../../_dnt.polyfills.js";
|
|
11
|
-
export { describeError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, toErrorEvent, UPSTREAM_FAILED, } from './error.js';
|
|
11
|
+
export { describeError, isAbortError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_CANCELLED, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, throwIfAborted, toErrorEvent, UPSTREAM_FAILED, } from './error.js';
|
|
12
12
|
export { injectionSpans } from './injection.js';
|
|
13
13
|
export type { QuotaSlotStatus } from './quota.js';
|
|
14
14
|
export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import "../../_dnt.polyfills.js";
|
|
11
|
-
export { describeError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, toErrorEvent, UPSTREAM_FAILED, } from './error.js';
|
|
11
|
+
export { describeError, isAbortError, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_CANCELLED, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, throwIfAborted, toErrorEvent, UPSTREAM_FAILED, } from './error.js';
|
|
12
12
|
export { injectionSpans } from './injection.js';
|
|
13
13
|
export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
|
|
14
14
|
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './sanitize.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TheorumError, toErrorEvent } from '../../../guardrails/error.js';
|
|
1
|
+
import { TheorumError, throwIfAborted, toErrorEvent } from '../../../guardrails/error.js';
|
|
2
2
|
import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
|
|
3
3
|
import { resolveTurn } from '../../registry/resolve.js';
|
|
4
4
|
import { getStructured } from '../../registry/schemas.js';
|
|
@@ -191,6 +191,7 @@ async function* runAttemptsWithValidation(safe, profile, generation, system, pro
|
|
|
191
191
|
currentReq: safe,
|
|
192
192
|
};
|
|
193
193
|
while (flow.currentAttempt <= maxRetries) {
|
|
194
|
+
throwIfAborted(safe.signal);
|
|
194
195
|
const step = yield* executeSingleAttemptCycle({
|
|
195
196
|
flow,
|
|
196
197
|
state,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
+
import { throwIfAborted } from '../../../guardrails/error.js';
|
|
10
11
|
import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
|
|
11
12
|
import { noopSink, writeTrace } from '../../../observability/trace.js';
|
|
12
13
|
import { buildRecord } from '../../../observability/trace-record.js';
|
|
@@ -47,6 +48,7 @@ async function* runTurn(req, provider, sink = noopSink()) {
|
|
|
47
48
|
let generation;
|
|
48
49
|
try {
|
|
49
50
|
const safe = sanitizeTurnRequest(req);
|
|
51
|
+
throwIfAborted(safe.signal);
|
|
50
52
|
const { profile, generation: gen } = resolveTurn(safe);
|
|
51
53
|
generation = gen;
|
|
52
54
|
const { model: resolvedModel, geminiBucket, canary: turnCanary } = gen;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { throwIfAborted } from '../../../guardrails/error.js';
|
|
1
2
|
import { recordStepEvent } from './state.js';
|
|
2
3
|
import { yieldProviderEvents } from './stream.js';
|
|
3
4
|
import { executeDynamicDeclaration, findDynamicDeclaration, formatToolFinding, isActionableDynamicDeclaration, } from './tools.js';
|
|
@@ -11,7 +12,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
11
12
|
holdLate: false,
|
|
12
13
|
holdUserVisible: false,
|
|
13
14
|
}) {
|
|
14
|
-
const { profile, generation, system, provider, gemini } = args;
|
|
15
|
+
const { profile, generation, system, provider, gemini, signal } = args;
|
|
15
16
|
const genForStep = { ...generation, history: state.currentHistory };
|
|
16
17
|
const pendingTools = [];
|
|
17
18
|
let latestStructured;
|
|
@@ -21,6 +22,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
21
22
|
system,
|
|
22
23
|
provider,
|
|
23
24
|
gemini,
|
|
25
|
+
signal,
|
|
24
26
|
})) {
|
|
25
27
|
if (event.type === 'structured') {
|
|
26
28
|
latestStructured = event.structured;
|
|
@@ -107,9 +109,10 @@ async function* executeAttempt(args) {
|
|
|
107
109
|
const holdUserVisible = Boolean(profile.guardrails.egress?.enforce);
|
|
108
110
|
const holdLate = Boolean(profile.outputs.validation) || holdUserVisible;
|
|
109
111
|
while (!isStepLimitReached(stepInAttempt, generation.maxSteps)) {
|
|
112
|
+
throwIfAborted(args.safe.signal);
|
|
110
113
|
stepInAttempt++;
|
|
111
114
|
state.stepCount++;
|
|
112
|
-
const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, gemini }, state, { holdLate, holdUserVisible });
|
|
115
|
+
const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, gemini, signal: args.safe.signal }, state, { holdLate, holdUserVisible });
|
|
113
116
|
if (stepResult.latestStructured !== undefined) {
|
|
114
117
|
latestStructured = stepResult.latestStructured;
|
|
115
118
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { publicError, toErrorEvent } from '../../../guardrails/error.js';
|
|
1
|
+
import { publicError, throwIfAborted, toErrorEvent } from '../../../guardrails/error.js';
|
|
2
2
|
import { providerCompleteRequest } from '../../registry/provider-request.js';
|
|
3
3
|
import { eventHasCanary, redactCanary } from '../boundary.js';
|
|
4
4
|
import { dispatchModelTool } from './tools.js';
|
|
@@ -51,14 +51,17 @@ function* processNormalEvent(event, profile, generation) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
async function* yieldProviderEvents(args) {
|
|
54
|
-
const { profile, generation, system, provider, gemini } = args;
|
|
54
|
+
const { profile, generation, system, provider, gemini, signal } = args;
|
|
55
55
|
const { canary } = generation;
|
|
56
|
+
throwIfAborted(signal);
|
|
56
57
|
for await (const event of provider.complete({
|
|
57
58
|
...providerCompleteRequest(generation, system),
|
|
59
|
+
signal,
|
|
58
60
|
tapGemini: (row) => {
|
|
59
61
|
gemini.push(row);
|
|
60
62
|
},
|
|
61
63
|
})) {
|
|
64
|
+
throwIfAborted(signal);
|
|
62
65
|
if (canary && eventHasCanary(event, canary)) {
|
|
63
66
|
yield redactCanary(event, canary);
|
|
64
67
|
yield toErrorEvent('canary leaked');
|
|
@@ -387,6 +387,11 @@ export interface TurnRequest {
|
|
|
387
387
|
dynamicToolLoader?: DynamicToolLoader;
|
|
388
388
|
/** Host-owned metadata preserved for traces; the kernel does not interpret it. */
|
|
389
389
|
metadata?: Record<string, unknown>;
|
|
390
|
+
/**
|
|
391
|
+
* Optional abort signal. When aborted, THEORUM stops the turn and cancels
|
|
392
|
+
* in-flight provider HTTP where the adapter supports it.
|
|
393
|
+
*/
|
|
394
|
+
signal?: AbortSignal;
|
|
390
395
|
input?: TurnInput;
|
|
391
396
|
toolInvoke?: {
|
|
392
397
|
name: CustomToolId;
|
|
@@ -521,6 +526,8 @@ export interface ProviderCompleteRequest extends ProviderGenerationConfig {
|
|
|
521
526
|
geminiBucket?: GeminiBucket;
|
|
522
527
|
/** Scrubbed SSE / HTTP rows for traces. */
|
|
523
528
|
tapGemini?: (row: Record<string, unknown>) => void;
|
|
529
|
+
/** Host abort signal — adapters should pass this into fetch / SDK calls. */
|
|
530
|
+
signal?: AbortSignal;
|
|
524
531
|
}
|
|
525
532
|
/** Minimal adapter contract every model provider must implement. */
|
|
526
533
|
export interface ModelProvider {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { publicError } from '../guardrails/error.js';
|
|
9
|
+
import { isAbortError, publicError } from '../guardrails/error.js';
|
|
10
10
|
import { sanitizeText, sanitizeTurnRequest } from '../guardrails/sanitize.js';
|
|
11
11
|
import { OMIT_CANARY } from '../kernel/engine/boundary.js';
|
|
12
12
|
import { sha256 } from '../kernel/engine/hash.js';
|
|
@@ -109,14 +109,15 @@ async function buildRecord(args) {
|
|
|
109
109
|
const lastErr = [...snapped].reverse().find((row) => row.type === 'error');
|
|
110
110
|
const done = completedInteraction(gemini);
|
|
111
111
|
const status = done?.status;
|
|
112
|
-
const
|
|
112
|
+
const aborted = isAbortError(thrown);
|
|
113
|
+
const ok = !(thrown || lastErr) && status !== 'cancelled' && !aborted;
|
|
113
114
|
const record = {
|
|
114
115
|
v: TRACE_VERSION,
|
|
115
116
|
id: crypto.randomUUID(),
|
|
116
117
|
ts: started,
|
|
117
118
|
ms: Date.now() - started,
|
|
118
119
|
streamed: true,
|
|
119
|
-
cancelled: status === 'cancelled',
|
|
120
|
+
cancelled: status === 'cancelled' || aborted,
|
|
120
121
|
previousInteractionId: safe.previousInteractionId ?? null,
|
|
121
122
|
store: safe.store ?? null,
|
|
122
123
|
profile: safe.profile,
|
|
@@ -22,8 +22,15 @@ export interface CreateProviderOptions {
|
|
|
22
22
|
voice?: string;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
declare function isSpeechRole(profile: Profile): boolean;
|
|
25
26
|
/**
|
|
26
27
|
* Create a `ModelProvider` for a profile.
|
|
27
28
|
* One call: protocol/provider (and speech role) pick the transport.
|
|
28
29
|
*/
|
|
29
30
|
export declare function createProvider(profile: Profile, options?: CreateProviderOptions): ModelProvider;
|
|
31
|
+
/** @internal Exported for direct unit testing only. */
|
|
32
|
+
export declare const _internals: {
|
|
33
|
+
isSpeechRole: typeof isSpeechRole;
|
|
34
|
+
createProvider: typeof createProvider;
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
@@ -36,3 +36,5 @@ export function createProvider(profile, options = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
|
|
38
38
|
}
|
|
39
|
+
/** @internal Exported for direct unit testing only. */
|
|
40
|
+
export const _internals = { isSpeechRole, createProvider };
|
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
declare function isImageBlob(rec: Record<string, unknown>): boolean;
|
|
2
|
+
declare function scrubEntry(rec: Record<string, unknown>, key: string, nested: unknown): Promise<[string, unknown]>;
|
|
3
|
+
declare function scrubRecord(rec: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
4
|
+
declare function scrubGemini(value: unknown): Promise<unknown>;
|
|
5
|
+
declare function redactCanaryInTree(value: unknown, canary: string): unknown;
|
|
1
6
|
declare function tapeGemini(value: unknown, canary: string): Promise<unknown>;
|
|
2
7
|
export { tapeGemini };
|
|
8
|
+
/** @internal Exported for direct unit testing only. */
|
|
9
|
+
export declare const _internals: {
|
|
10
|
+
isImageBlob: typeof isImageBlob;
|
|
11
|
+
scrubEntry: typeof scrubEntry;
|
|
12
|
+
scrubRecord: typeof scrubRecord;
|
|
13
|
+
scrubGemini: typeof scrubGemini;
|
|
14
|
+
redactCanaryInTree: typeof redactCanaryInTree;
|
|
15
|
+
tapeGemini: typeof tapeGemini;
|
|
16
|
+
};
|
|
@@ -44,3 +44,12 @@ async function tapeGemini(value, canary) {
|
|
|
44
44
|
return redactCanaryInTree(await scrubGemini(value), canary);
|
|
45
45
|
}
|
|
46
46
|
export { tapeGemini };
|
|
47
|
+
/** @internal Exported for direct unit testing only. */
|
|
48
|
+
export const _internals = {
|
|
49
|
+
isImageBlob,
|
|
50
|
+
scrubEntry,
|
|
51
|
+
scrubRecord,
|
|
52
|
+
scrubGemini,
|
|
53
|
+
redactCanaryInTree,
|
|
54
|
+
tapeGemini,
|
|
55
|
+
};
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import type { ProviderCompleteRequest } from '../kernel/types.js';
|
|
2
|
+
declare function tapeHeaderValue(key: string, value: string): string;
|
|
3
|
+
declare function tapeHeaders(headers?: HeadersInit): Record<string, string>;
|
|
4
|
+
declare function throwRow(err: unknown): Record<string, unknown>;
|
|
2
5
|
declare function tapFetch(tap: ProviderCompleteRequest['tapGemini'], send?: typeof fetch): typeof fetch;
|
|
3
6
|
export { tapFetch };
|
|
7
|
+
/** @internal Exported for direct unit testing only. */
|
|
8
|
+
export declare const _internals: {
|
|
9
|
+
tapeHeaderValue: typeof tapeHeaderValue;
|
|
10
|
+
tapeHeaders: typeof tapeHeaders;
|
|
11
|
+
throwRow: typeof throwRow;
|
|
12
|
+
tapFetch: typeof tapFetch;
|
|
13
|
+
};
|
|
@@ -1,5 +1,41 @@
|
|
|
1
|
-
import type { ProviderCompleteRequest } from '../kernel/types.js';
|
|
1
|
+
import type { InteractionPart, ProviderCompleteRequest } from '../kernel/types.js';
|
|
2
2
|
declare function camelToSnake(key: string): string;
|
|
3
|
+
declare function toGoogleValue(value: unknown): unknown;
|
|
4
|
+
declare function wirePart(part: InteractionPart): Record<string, string>;
|
|
5
|
+
declare function userInputStep(parts: InteractionPart[]): {
|
|
6
|
+
type: string;
|
|
7
|
+
content: Record<string, string>[];
|
|
8
|
+
};
|
|
9
|
+
declare function historyStep(msg: import('../kernel/types.js').TurnHistoryMessage): {
|
|
10
|
+
type: string;
|
|
11
|
+
content: Record<string, string>[];
|
|
12
|
+
};
|
|
13
|
+
declare function systemHoldsUserInput(system: string, parts: InteractionPart[]): boolean;
|
|
14
|
+
declare function jsonResponseFormat(schema: Record<string, unknown>): unknown[];
|
|
15
|
+
declare function attachResponseFormat(req: ProviderCompleteRequest, camel: Record<string, unknown>): void;
|
|
16
|
+
declare function attachSpeechConfig(req: ProviderCompleteRequest, generationConfig: Record<string, unknown>): void;
|
|
17
|
+
declare function inputStepsFromRequest(req: ProviderCompleteRequest): {
|
|
18
|
+
type: string;
|
|
19
|
+
content: Record<string, string>[];
|
|
20
|
+
}[];
|
|
21
|
+
declare function applyOptionalRequestFields(req: ProviderCompleteRequest, camel: Record<string, unknown>): void;
|
|
22
|
+
declare function baseInteractionsBody(req: ProviderCompleteRequest): Record<string, unknown>;
|
|
3
23
|
/** Interactions REST body for one complete() call (Google snake_case keys). */
|
|
4
24
|
declare function toInteractionsBody(req: ProviderCompleteRequest): Record<string, unknown>;
|
|
5
25
|
export { camelToSnake, toInteractionsBody };
|
|
26
|
+
/** @internal Exported for direct unit testing only. */
|
|
27
|
+
export declare const _internals: {
|
|
28
|
+
camelToSnake: typeof camelToSnake;
|
|
29
|
+
toGoogleValue: typeof toGoogleValue;
|
|
30
|
+
wirePart: typeof wirePart;
|
|
31
|
+
userInputStep: typeof userInputStep;
|
|
32
|
+
historyStep: typeof historyStep;
|
|
33
|
+
systemHoldsUserInput: typeof systemHoldsUserInput;
|
|
34
|
+
jsonResponseFormat: typeof jsonResponseFormat;
|
|
35
|
+
attachResponseFormat: typeof attachResponseFormat;
|
|
36
|
+
attachSpeechConfig: typeof attachSpeechConfig;
|
|
37
|
+
inputStepsFromRequest: typeof inputStepsFromRequest;
|
|
38
|
+
applyOptionalRequestFields: typeof applyOptionalRequestFields;
|
|
39
|
+
baseInteractionsBody: typeof baseInteractionsBody;
|
|
40
|
+
toInteractionsBody: typeof toInteractionsBody;
|
|
41
|
+
};
|
|
@@ -151,3 +151,19 @@ function toInteractionsBody(req) {
|
|
|
151
151
|
return toGoogleValue(camel);
|
|
152
152
|
}
|
|
153
153
|
export { camelToSnake, toInteractionsBody };
|
|
154
|
+
/** @internal Exported for direct unit testing only. */
|
|
155
|
+
export const _internals = {
|
|
156
|
+
camelToSnake,
|
|
157
|
+
toGoogleValue,
|
|
158
|
+
wirePart,
|
|
159
|
+
userInputStep,
|
|
160
|
+
historyStep,
|
|
161
|
+
systemHoldsUserInput,
|
|
162
|
+
jsonResponseFormat,
|
|
163
|
+
attachResponseFormat,
|
|
164
|
+
attachSpeechConfig,
|
|
165
|
+
inputStepsFromRequest,
|
|
166
|
+
applyOptionalRequestFields,
|
|
167
|
+
baseInteractionsBody,
|
|
168
|
+
toInteractionsBody,
|
|
169
|
+
};
|
|
@@ -13,7 +13,26 @@ interface GeminiTransport {
|
|
|
13
13
|
wait?: (ms: number) => Promise<void>;
|
|
14
14
|
fetch?: typeof fetch;
|
|
15
15
|
}
|
|
16
|
+
declare function waitDefault(ms: number): Promise<void>;
|
|
17
|
+
declare function isQuota(err: unknown): boolean;
|
|
18
|
+
declare function isTransientHttp(status: number): boolean;
|
|
19
|
+
declare function isTransientThrown(err: unknown): boolean;
|
|
20
|
+
declare function requireKey(vault: GeminiVault, bucket: GeminiBucket): string;
|
|
21
|
+
declare function backoffMs(attempt: number): number;
|
|
22
|
+
declare function canOverflow(bucket: GeminiBucket, vault: GeminiVault, primary: string): string | undefined;
|
|
16
23
|
declare function withGeminiKey<T>(bucket: GeminiBucket, run: (apiKey: string) => Promise<T>, transport: GeminiTransport): Promise<T>;
|
|
24
|
+
declare function withApiKey(init: RequestInit, apiKey: string): RequestInit;
|
|
17
25
|
declare function fetchGemini(url: string, init: RequestInit, bucket: GeminiBucket, transport: GeminiTransport): Promise<Response>;
|
|
18
26
|
export type { GeminiTransport, GeminiVault };
|
|
19
27
|
export { fetchGemini, withGeminiKey };
|
|
28
|
+
/** @internal Exported for direct unit testing only. */
|
|
29
|
+
export declare const _internals: {
|
|
30
|
+
waitDefault: typeof waitDefault;
|
|
31
|
+
isQuota: typeof isQuota;
|
|
32
|
+
isTransientHttp: typeof isTransientHttp;
|
|
33
|
+
isTransientThrown: typeof isTransientThrown;
|
|
34
|
+
requireKey: typeof requireKey;
|
|
35
|
+
backoffMs: typeof backoffMs;
|
|
36
|
+
canOverflow: typeof canOverflow;
|
|
37
|
+
withApiKey: typeof withApiKey;
|
|
38
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { TheorumError, UPSTREAM_FAILED } from '../guardrails/error.js';
|
|
9
|
+
import { isAbortError, TheorumError, UPSTREAM_FAILED } from '../guardrails/error.js';
|
|
10
10
|
const ATTEMPTS = 3;
|
|
11
11
|
const LAST_ATTEMPT = ATTEMPTS - 1;
|
|
12
12
|
const BACKOFF_FIRST_MS = 1000;
|
|
@@ -39,6 +39,9 @@ function isTransientHttp(status) {
|
|
|
39
39
|
status === HTTP_GATEWAY_TIMEOUT);
|
|
40
40
|
}
|
|
41
41
|
function isTransientThrown(err) {
|
|
42
|
+
if (isAbortError(err)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
42
45
|
return TRANSIENT_THROWN_RE.test(String(err));
|
|
43
46
|
}
|
|
44
47
|
function requireKey(vault, bucket) {
|
|
@@ -56,7 +59,9 @@ async function runWithBackoff(apiKey, run, wait, attempt) {
|
|
|
56
59
|
return await run(apiKey);
|
|
57
60
|
}
|
|
58
61
|
catch (err) {
|
|
59
|
-
if (
|
|
62
|
+
if (isAbortError(err) ||
|
|
63
|
+
!(isQuota(err) || isTransientThrown(err)) ||
|
|
64
|
+
attempt === LAST_ATTEMPT) {
|
|
60
65
|
throw err;
|
|
61
66
|
}
|
|
62
67
|
await wait(backoffMs(attempt));
|
|
@@ -103,11 +108,16 @@ async function fetchWithBackoff(args) {
|
|
|
103
108
|
if (!isTransientHttp(last.status) || args.attempt === LAST_ATTEMPT) {
|
|
104
109
|
return last;
|
|
105
110
|
}
|
|
111
|
+
if (args.init.signal?.aborted) {
|
|
112
|
+
throw args.init.signal.reason instanceof Error
|
|
113
|
+
? args.init.signal.reason
|
|
114
|
+
: new DOMException('The operation was aborted.', 'AbortError');
|
|
115
|
+
}
|
|
106
116
|
await wait(backoffMs(args.attempt));
|
|
107
117
|
return fetchWithBackoff({ ...args, attempt: args.attempt + 1 });
|
|
108
118
|
}
|
|
109
119
|
catch (err) {
|
|
110
|
-
if (!isTransientThrown(err) || args.attempt === LAST_ATTEMPT) {
|
|
120
|
+
if (isAbortError(err) || !isTransientThrown(err) || args.attempt === LAST_ATTEMPT) {
|
|
111
121
|
throw err;
|
|
112
122
|
}
|
|
113
123
|
await wait(backoffMs(args.attempt));
|
|
@@ -127,3 +137,14 @@ async function fetchGemini(url, init, bucket, transport) {
|
|
|
127
137
|
return last;
|
|
128
138
|
}
|
|
129
139
|
export { fetchGemini, withGeminiKey };
|
|
140
|
+
/** @internal Exported for direct unit testing only. */
|
|
141
|
+
export const _internals = {
|
|
142
|
+
waitDefault,
|
|
143
|
+
isQuota,
|
|
144
|
+
isTransientHttp,
|
|
145
|
+
isTransientThrown,
|
|
146
|
+
requireKey,
|
|
147
|
+
backoffMs,
|
|
148
|
+
canOverflow,
|
|
149
|
+
withApiKey,
|
|
150
|
+
};
|
|
@@ -28,5 +28,12 @@ interface OpenRouterWireIds {
|
|
|
28
28
|
declare function resolveOpenRouterModel(modelId: ModelId | string, customMap?: Record<string, string>, wire?: OpenRouterWireIds): string;
|
|
29
29
|
/** Convert a provider-neutral request into an OpenRouter chat completion payload. */
|
|
30
30
|
declare function toOpenRouterPayload(req: ProviderCompleteRequest, config: OpenRouterConfig): Record<string, unknown>;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
interface ResolvedPlugins {
|
|
32
|
+
plugins: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
}>;
|
|
35
|
+
webSearch: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function resolveOpenRouterPlugins(builtins: readonly string[]): ResolvedPlugins;
|
|
38
|
+
export type { OpenRouterConfig, OpenRouterWireIds, ResolvedPlugins };
|
|
39
|
+
export { resolveOpenRouterModel, resolveOpenRouterPlugins, toOpenRouterPayload };
|
|
@@ -167,13 +167,29 @@ function toOpenRouterPayload(req, config) {
|
|
|
167
167
|
if (tools.length > 0) {
|
|
168
168
|
payload.tools = tools;
|
|
169
169
|
}
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
if (plugins.length > 0) {
|
|
175
|
-
payload.plugins = plugins;
|
|
170
|
+
const resolved = resolveOpenRouterPlugins(req.builtins);
|
|
171
|
+
if (resolved.webSearch) {
|
|
172
|
+
payload.web_search_options = {};
|
|
173
|
+
}
|
|
174
|
+
if (resolved.plugins.length > 0) {
|
|
175
|
+
payload.plugins = resolved.plugins;
|
|
176
176
|
}
|
|
177
177
|
return payload;
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
function resolveOpenRouterPlugins(builtins) {
|
|
180
|
+
let webSearch = false;
|
|
181
|
+
const plugins = [];
|
|
182
|
+
for (const id of builtins) {
|
|
183
|
+
const pluginId = getTool(id)?.openRouterPlugin;
|
|
184
|
+
if (!pluginId)
|
|
185
|
+
continue;
|
|
186
|
+
if (pluginId === 'web') {
|
|
187
|
+
webSearch = true;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
plugins.push({ id: pluginId });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { plugins, webSearch };
|
|
194
|
+
}
|
|
195
|
+
export { resolveOpenRouterModel, resolveOpenRouterPlugins, toOpenRouterPayload };
|