@theoremjs/agents 0.3.4-next.482.g7e608f2 → 0.3.4-next.508.geda6db0
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 +61 -51
- package/esm/mod.d.ts +2 -2
- package/esm/mod.js +1 -1
- package/esm/src/guardrails/credential-scan.d.ts +1 -1
- package/esm/src/guardrails/credential-scan.js +41 -18
- package/esm/src/guardrails/destinations.js +6 -2
- package/esm/src/guardrails/detect-at.d.ts +1 -3
- package/esm/src/guardrails/detect-at.js +6 -22
- package/esm/src/guardrails/egress-automata.js +203 -202
- package/esm/src/guardrails/egress-stream.js +10 -3
- package/esm/src/guardrails/egress-urls.js +4 -2
- package/esm/src/guardrails/injection-patterns.d.ts +3 -1
- package/esm/src/guardrails/injection-patterns.js +87 -4
- package/esm/src/guardrails/labels.d.ts +2 -2
- package/esm/src/guardrails/labels.js +7 -5
- package/esm/src/guardrails/mod.d.ts +1 -1
- package/esm/src/guardrails/normalize.js +4 -2
- package/esm/src/guardrails/prompt-echo.js +4 -2
- package/esm/src/guardrails/sensitive.d.ts +2 -2
- package/esm/src/guardrails/sensitive.js +1 -1
- package/esm/src/interface/profile-interface.js +13 -0
- package/esm/src/kernel/answer-tool.d.ts +14 -0
- package/esm/src/kernel/answer-tool.js +26 -0
- package/esm/src/kernel/engine/decision-references.d.ts +14 -0
- package/esm/src/kernel/engine/decision-references.js +55 -0
- package/esm/src/kernel/engine/decision.d.ts +3 -3
- package/esm/src/kernel/engine/decision.js +38 -22
- package/esm/src/kernel/engine/embedding.js +0 -2
- package/esm/src/kernel/engine/runner/gates.js +38 -4
- package/esm/src/kernel/engine/runner/steps.js +47 -10
- package/esm/src/kernel/engine/runner/stream.d.ts +14 -8
- package/esm/src/kernel/engine/runner/stream.js +25 -13
- package/esm/src/kernel/mod.d.ts +1 -1
- package/esm/src/kernel/mod.js +1 -1
- package/esm/src/kernel/profile-presence.js +12 -0
- package/esm/src/kernel/profile-scope.js +14 -14
- package/esm/src/kernel/protocols/binding.d.ts +3 -1
- package/esm/src/kernel/protocols/binding.js +6 -0
- package/esm/src/kernel/protocols/limits.d.ts +10 -11
- package/esm/src/kernel/protocols/limits.js +9 -23
- package/esm/src/kernel/protocols/runtime.d.ts +3 -2
- package/esm/src/kernel/protocols/runtime.js +1 -2
- package/esm/src/kernel/provider-budget.d.ts +2 -2
- package/esm/src/kernel/provider-budget.js +22 -12
- package/esm/src/kernel/provider-runtime.js +2 -2
- package/esm/src/kernel/registry/profiles.js +1 -1
- package/esm/src/kernel/registry/resolve.js +4 -0
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/schema.js +12 -2
- package/esm/src/kernel/types.d.ts +4 -2
- package/esm/src/observability/spans.js +7 -8
- package/esm/src/providers/embedding-limits.js +4 -4
- package/esm/src/providers/gemini/{embedding-protocol.d.ts → embeddings-protocol.d.ts} +3 -3
- package/esm/src/providers/gemini/{embedding-protocol.js → embeddings-protocol.js} +3 -3
- package/esm/src/providers/gemini/live-protocol.js +4 -1
- package/esm/src/providers/gemini/provider.js +2 -2
- package/esm/src/providers/local/embeddings-protocol.d.ts +7 -0
- package/esm/src/providers/local/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/local/provider.js +2 -2
- package/esm/src/providers/mod.d.ts +3 -3
- package/esm/src/providers/mod.js +3 -3
- package/esm/src/providers/openrouter/decisions-protocol.d.ts +5 -3
- package/esm/src/providers/openrouter/decisions-protocol.js +55 -4
- package/esm/src/providers/openrouter/embeddings-protocol.d.ts +6 -0
- package/esm/src/providers/openrouter/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/openrouter/provider.js +2 -2
- package/esm/src/providers/system-one/protocol.d.ts +4 -0
- package/esm/src/providers/system-one/protocol.js +25 -24
- package/package.json +1 -1
- package/esm/src/providers/local/embedding-protocol.d.ts +0 -7
- package/esm/src/providers/openrouter/embedding-protocol.d.ts +0 -6
|
@@ -21,6 +21,9 @@ function combinedSignal(signals) {
|
|
|
21
21
|
}
|
|
22
22
|
return { signal: controller.signal, dispose };
|
|
23
23
|
}
|
|
24
|
+
function over(value, limit) {
|
|
25
|
+
return limit !== undefined && value > limit;
|
|
26
|
+
}
|
|
24
27
|
export class ProviderBudget {
|
|
25
28
|
limits;
|
|
26
29
|
controller = new AbortController();
|
|
@@ -38,10 +41,14 @@ export class ProviderBudget {
|
|
|
38
41
|
const effective = combinedSignal(caller ? [caller, this.controller.signal] : [this.controller.signal]);
|
|
39
42
|
this.signal = effective.signal;
|
|
40
43
|
this.releaseSignal = effective.dispose;
|
|
44
|
+
if (timeoutMs === undefined)
|
|
45
|
+
return;
|
|
41
46
|
this.startTimer(timeoutMs);
|
|
42
47
|
this.deadline = performance.now() + timeoutMs;
|
|
43
48
|
}
|
|
44
49
|
resumeOperation() {
|
|
50
|
+
if (this.deadline === undefined)
|
|
51
|
+
return;
|
|
45
52
|
const remaining = this.deadline - performance.now();
|
|
46
53
|
if (remaining <= 0) {
|
|
47
54
|
this.controller.abort(new TheoremError('timeout', labelText('error.timeout')));
|
|
@@ -51,7 +58,8 @@ export class ProviderBudget {
|
|
|
51
58
|
}
|
|
52
59
|
/** Milliseconds before the running timer ends the call. */
|
|
53
60
|
timeLeft() {
|
|
54
|
-
|
|
61
|
+
const end = this.timerDeadline ?? this.deadline;
|
|
62
|
+
return end === undefined ? Number.POSITIVE_INFINITY : end - performance.now();
|
|
55
63
|
}
|
|
56
64
|
startTimer(ms) {
|
|
57
65
|
this.stopTimer();
|
|
@@ -73,8 +81,9 @@ export class ProviderBudget {
|
|
|
73
81
|
this.releaseSignal();
|
|
74
82
|
}
|
|
75
83
|
startLiveStep() {
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const ms = this.limits.liveStepTimeoutMs;
|
|
85
|
+
if (this.timer === undefined && ms !== undefined)
|
|
86
|
+
this.startTimer(ms);
|
|
78
87
|
}
|
|
79
88
|
fail() {
|
|
80
89
|
const error = new TheoremError('bad_response', labelText('provider.budget_exhausted'), {
|
|
@@ -95,9 +104,9 @@ export class ProviderBudget {
|
|
|
95
104
|
this.checkDeadline();
|
|
96
105
|
this.stepBytes += bytes;
|
|
97
106
|
this.stepEvents++;
|
|
98
|
-
if (bytes
|
|
99
|
-
this.stepBytes
|
|
100
|
-
this.stepEvents
|
|
107
|
+
if (over(bytes, this.limits.maxEventBytes) ||
|
|
108
|
+
over(this.stepBytes, this.limits.maxStepBytes) ||
|
|
109
|
+
over(this.stepEvents, this.limits.maxStepEvents))
|
|
101
110
|
this.fail();
|
|
102
111
|
}
|
|
103
112
|
event(event) {
|
|
@@ -106,7 +115,7 @@ export class ProviderBudget {
|
|
|
106
115
|
this.calls.add(event.call.callId);
|
|
107
116
|
if (event.type === 'tool_cancel')
|
|
108
117
|
this.calls.delete(event.callId);
|
|
109
|
-
if (this.calls.size
|
|
118
|
+
if (over(this.calls.size, this.limits.maxPendingToolCalls))
|
|
110
119
|
this.fail();
|
|
111
120
|
}
|
|
112
121
|
resetStep() {
|
|
@@ -138,7 +147,7 @@ export class ProviderBudget {
|
|
|
138
147
|
: data instanceof ArrayBuffer || ArrayBuffer.isView(data)
|
|
139
148
|
? data.byteLength
|
|
140
149
|
: 0;
|
|
141
|
-
if (bytes
|
|
150
|
+
if (!over(bytes, this.limits.maxWireRecordBytes))
|
|
142
151
|
return;
|
|
143
152
|
event.stopImmediatePropagation();
|
|
144
153
|
try {
|
|
@@ -168,7 +177,8 @@ export class ProviderBudget {
|
|
|
168
177
|
}
|
|
169
178
|
const reader = response.body.getReader();
|
|
170
179
|
let responseBytes = 0;
|
|
171
|
-
const record =
|
|
180
|
+
const record = this.limits.maxWireRecordBytes !== undefined &&
|
|
181
|
+
response.headers.get('content-type')?.includes('text/event-stream')
|
|
172
182
|
? new SseRecordBudget(this)
|
|
173
183
|
: undefined;
|
|
174
184
|
const abort = () => {
|
|
@@ -190,8 +200,8 @@ export class ProviderBudget {
|
|
|
190
200
|
}
|
|
191
201
|
responseBytes += next.value.byteLength;
|
|
192
202
|
this.wireBytes += next.value.byteLength;
|
|
193
|
-
if (responseBytes
|
|
194
|
-
this.wireBytes
|
|
203
|
+
if (over(responseBytes, this.limits.maxWireResponseBytes) ||
|
|
204
|
+
over(this.wireBytes, this.limits.maxWireResponseBytes))
|
|
195
205
|
this.fail();
|
|
196
206
|
record?.accept(next.value);
|
|
197
207
|
controller.enqueue(next.value);
|
|
@@ -233,7 +243,7 @@ class SseRecordBudget {
|
|
|
233
243
|
this.emptyCR = false;
|
|
234
244
|
}
|
|
235
245
|
this.recordBytes++;
|
|
236
|
-
if (this.recordBytes
|
|
246
|
+
if (over(this.recordBytes, this.budget.limits.maxWireRecordBytes))
|
|
237
247
|
this.budget.fail();
|
|
238
248
|
if (this.emptyCR) {
|
|
239
249
|
this.recordBytes = 0;
|
|
@@ -5,7 +5,7 @@ import { historyHash, providerCheckpointSchema, } from './checkpoint.js';
|
|
|
5
5
|
import { stepReplyMessages } from './interaction-parts.js';
|
|
6
6
|
import { jsonObjectSchema, jsonValueSchema } from './json.js';
|
|
7
7
|
import { keySlotsOf, protocolOf, } from './protocols/binding.js';
|
|
8
|
-
import {
|
|
8
|
+
import { providerLimitsSchema } from './protocols/limits.js';
|
|
9
9
|
import { carriesTo, } from './protocols/protocol.js';
|
|
10
10
|
import { callProtocol, plainWebSocket } from './protocols/runtime.js';
|
|
11
11
|
import { ProviderBudget } from './provider-budget.js';
|
|
@@ -188,7 +188,7 @@ const SECRET_FIELD = /^(?:authorization|proxy-authorization|cookie|set-cookie|ap
|
|
|
188
188
|
* the host's budgets, with every key it signs with kept out of errors and traces.
|
|
189
189
|
*/
|
|
190
190
|
export function guardedCall(provider, binding, profileType, host, req) {
|
|
191
|
-
const limits =
|
|
191
|
+
const limits = Object.freeze(providerLimitsSchema.parse({ ...binding.limits, ...host.limits }));
|
|
192
192
|
const budget = new ProviderBudget(limits, req.signal, req.timeoutMs ?? (req.liveConnection ? limits.connectTimeoutMs : limits.operationTimeoutMs));
|
|
193
193
|
const secrets = new Set();
|
|
194
194
|
const remember = (value) => {
|
|
@@ -50,7 +50,7 @@ function validateDecisionBinding(profileId, modelId, binding) {
|
|
|
50
50
|
if ('retry' in binding) {
|
|
51
51
|
throw new TheoremError('config',
|
|
52
52
|
// labels-exempt: developer contract / internal diagnostic
|
|
53
|
-
`Profile ${profileId} model '${modelId}': decision
|
|
53
|
+
`Profile ${profileId} model '${modelId}': a decision binding takes no retry; a decision retries only a transient failure, by the kernel's rule`);
|
|
54
54
|
}
|
|
55
55
|
if (binding.timeoutMs !== undefined &&
|
|
56
56
|
(!Number.isFinite(binding.timeoutMs) || binding.timeoutMs <= 0)) {
|
|
@@ -4,6 +4,7 @@ import { TheoremError } from '../../guardrails/error.js';
|
|
|
4
4
|
import { resolveGuardrailPolicy } from '../../guardrails/policy.js';
|
|
5
5
|
import { replyIsJudged } from '../../guardrails/progressive-yield.js';
|
|
6
6
|
import { sanitizeTurnRequest } from '../../guardrails/sanitize.js';
|
|
7
|
+
import { ANSWER_TOOL } from '../answer-tool.js';
|
|
7
8
|
import { DEFAULT_MAX_STEPS } from '../profile-presence.js';
|
|
8
9
|
import { profileTurnResumption } from '../stop.js';
|
|
9
10
|
import { projectTools } from '../tools/project.js';
|
|
@@ -171,6 +172,9 @@ function resolveTurnInRegistry(registry, req, model) {
|
|
|
171
172
|
const builtins = toolSnapshot.builtins;
|
|
172
173
|
const structuredId = resolveStructured(profile, input.slots);
|
|
173
174
|
assertOutputMode(profile, structuredId);
|
|
175
|
+
if (structuredId && toolSnapshot.wire.some((tool) => tool.name === ANSWER_TOOL)) {
|
|
176
|
+
throw new TheoremError('config', `Profile ${profile.id}: a structured turn answers through '${ANSWER_TOOL}', so no tool of its own may take that name`);
|
|
177
|
+
}
|
|
174
178
|
assertSpeechRole(profile, safe);
|
|
175
179
|
const builtInArgs = resolveBuiltInArgs(profile, picked, binding, safe.builtInArgs);
|
|
176
180
|
const transport = resolveTransport(profile, binding);
|
|
@@ -5,7 +5,7 @@ function createSchemaRegistry() {
|
|
|
5
5
|
return {
|
|
6
6
|
register(id, spec) {
|
|
7
7
|
if ('enforced' in spec) {
|
|
8
|
-
throw new TheoremError('config', `registerStructured '${id}': enforced was removed; every structured schema is sent to the model as its response format`);
|
|
8
|
+
throw new TheoremError('config', `registerStructured '${id}': enforced was removed; every structured schema is sent to the model, as its response format or as its answer tool's input`);
|
|
9
9
|
}
|
|
10
10
|
if (!spec.jsonSchema ||
|
|
11
11
|
typeof spec.jsonSchema !== 'object' ||
|
package/esm/src/kernel/schema.js
CHANGED
|
@@ -520,6 +520,16 @@ export const PROFILE_FIELDS = withScopeAndPresence({
|
|
|
520
520
|
'providerContinuation.history': field("'host' | 'stateless' | 'provider'", 'Who holds the conversation a checkpoint continues: the host passes history back, the provider keeps it and the checkpoint travels alone, or the profile is stateless and every request stands alone.', ['host', 'stateless', 'provider']),
|
|
521
521
|
'models.*.apiId': field('string', "The model's id as the provider lists it, such as gemini-2.5-flash; sent on every call. The name this model has in the profile is its key in models."),
|
|
522
522
|
'models.*.timeoutMs': field('number', 'How long a decision waits for its provider, in milliseconds. Omit for no timeout.'),
|
|
523
|
+
'models.*.limits': field('ProviderLimits', "The points at which Theorem stops this model's calls. Each one left out is no limit, so the provider's own limits apply. A runTurn limits value overrides each field it sets."),
|
|
524
|
+
'models.*.limits.operationTimeoutMs': field('number', 'How long one call may take, in milliseconds, retries included.'),
|
|
525
|
+
'models.*.limits.connectTimeoutMs': field('number', 'How long a live session may take to open, in milliseconds.'),
|
|
526
|
+
'models.*.limits.liveStepTimeoutMs': field('number', 'How long a live session may wait for the model to answer, in milliseconds.'),
|
|
527
|
+
'models.*.limits.maxWireRecordBytes': field('number', 'The most bytes in one record the provider sends: a stream event or a socket message.'),
|
|
528
|
+
'models.*.limits.maxWireResponseBytes': field('number', 'The most bytes the provider sends for one step.'),
|
|
529
|
+
'models.*.limits.maxEventBytes': field('number', 'The most bytes in one event of a step.'),
|
|
530
|
+
'models.*.limits.maxStepBytes': field('number', 'The most bytes in all the events of one step.'),
|
|
531
|
+
'models.*.limits.maxStepEvents': field('number', 'The most events in one step.'),
|
|
532
|
+
'models.*.limits.maxPendingToolCalls': field('number', 'The most tool calls one step may ask for.'),
|
|
523
533
|
decision: field('{ contract: DecisionContractId }', 'Names the host decision this profile makes.'),
|
|
524
534
|
'decision.contract': field('string', 'Your name for this decision, on its traces. Not sent to the model. Not checked against the questions.'),
|
|
525
535
|
'models.*.efforts': field('Record<string, ThinkingLevel>', 'Named thinking levels. A turn picks one when allowEffortSelect is on. A provider refuses a level its model does not take. Speech ignores them, and so may an image profile without includeText.'),
|
|
@@ -762,12 +772,12 @@ export const EXTRA_FIELDS = {
|
|
|
762
772
|
noul: 'One number, which may weigh named criteria.',
|
|
763
773
|
box: 'Where something is in an image: its top, bottom, left and right edges, with a confidence.',
|
|
764
774
|
}),
|
|
765
|
-
'decision.questions.instructions': field('DecisionEntry', "What to ask about the state. Name a state field in backticks, such as `photo`, and one of the decision's images by its reference, such as <ginger>. A box
|
|
775
|
+
'decision.questions.instructions': field('DecisionEntry', "What to ask about the state. Name a state field in backticks, such as `photo`, and one of the decision's images by its reference, such as <ginger>. A box names what to find and the image it is in, such as the ginger in `photo`."),
|
|
766
776
|
'decision.questions.image': {
|
|
767
777
|
...field('string', "The image a box is in: one of the decision's images, such as <ghs02>, or a state field its instructions name, such as `photo`. The model is told it in the instructions."),
|
|
768
778
|
unset: 'The image the instructions name',
|
|
769
779
|
},
|
|
770
|
-
'decision.images': field('Record<string, DecisionAsset>',
|
|
780
|
+
'decision.images': field('Record<string, DecisionAsset>', "Pictures a decision can send. Write one's reference, such as <ghs02>, in the state or a question, and the model reads the picture there; a picture nothing references is not sent. A PNG or JPEG."),
|
|
771
781
|
'decision.images.name': field('string', 'The reference a question writes, without its angle brackets: ghs02 is written <ghs02>. Lowercase letters, digits and _.'),
|
|
772
782
|
'registerTool.name': field('string', "The tool's id, which profiles list in tools.allow (or a model's builtInTools) and the model calls a custom tool by."),
|
|
773
783
|
'registerTool.description': field('string', 'Tells the model what the tool does (custom tools only).'),
|
|
@@ -458,8 +458,10 @@ export interface DecisionRequest {
|
|
|
458
458
|
state: Exclude<DecisionJson, null>;
|
|
459
459
|
questions: Record<string, DecisionQuestion>;
|
|
460
460
|
/**
|
|
461
|
-
* Images by reference. Write a reference
|
|
462
|
-
* question's instructions, or in its criteria
|
|
461
|
+
* Images by reference. Write a reference in a string where its image belongs, in `state`, in a
|
|
462
|
+
* question's instructions, or in its criteria, as the whole string or inside it; the model reads
|
|
463
|
+
* the image in its place, once for each use. Keys are never read. Only the images some string
|
|
464
|
+
* references are sent, so pick references that cannot occur in your text by accident.
|
|
463
465
|
*/
|
|
464
466
|
assets?: Record<string, DecisionAsset>;
|
|
465
467
|
signal?: AbortSignal;
|
|
@@ -47,15 +47,14 @@ function mergeSpans(spans) {
|
|
|
47
47
|
return out;
|
|
48
48
|
}
|
|
49
49
|
function applySpans(text, spans) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
out = out.slice(0, span.start) + tokenFor(span.kind) + out.slice(span.end);
|
|
56
|
-
}
|
|
50
|
+
const pieces = [];
|
|
51
|
+
let at = 0;
|
|
52
|
+
for (const span of mergeSpans(spans)) {
|
|
53
|
+
pieces.push(text.slice(at, span.start), tokenFor(span.kind));
|
|
54
|
+
at = span.end;
|
|
57
55
|
}
|
|
58
|
-
|
|
56
|
+
pieces.push(text.slice(at));
|
|
57
|
+
return pieces.join('');
|
|
59
58
|
}
|
|
60
59
|
function spansFromPatterns(text, patterns, kind) {
|
|
61
60
|
const spans = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
const SHIPPED = [
|
|
1
|
+
import { geminiEmbeddings } from './gemini/embeddings-protocol.js';
|
|
2
|
+
import { ollamaEmbeddings } from './local/embeddings-protocol.js';
|
|
3
|
+
import { openRouterEmbeddings } from './openrouter/embeddings-protocol.js';
|
|
4
|
+
const SHIPPED = [geminiEmbeddings, openRouterEmbeddings, ollamaEmbeddings];
|
|
5
5
|
/**
|
|
6
6
|
* What a binding's embedding model takes, read from its protocol: one Theorem ships, or one of
|
|
7
7
|
* `protocols`. Undefined when neither has it, and the kernel then holds requests to the profile alone.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Protocol } from '../../kernel/protocols/protocol.js';
|
|
2
|
-
/** What a `
|
|
3
|
-
export interface
|
|
2
|
+
/** What a `geminiEmbeddings` binding sets in `options`. */
|
|
3
|
+
export interface GeminiEmbeddingsOptions {
|
|
4
4
|
/** For `gemini-embedding-001`; Gemini Embedding 2 refuses it and reads the request's `task` from the text. */
|
|
5
5
|
taskType?: 'RETRIEVAL_QUERY' | 'RETRIEVAL_DOCUMENT' | 'SEMANTIC_SIMILARITY' | 'CLASSIFICATION' | 'CLUSTERING' | 'QUESTION_ANSWERING' | 'FACT_VERIFICATION' | 'CODE_RETRIEVAL_QUERY';
|
|
6
6
|
outputDimensionality?: 768 | 1536 | 3072;
|
|
7
7
|
}
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const geminiEmbeddings: Protocol<GeminiEmbeddingsOptions>;
|
|
@@ -76,8 +76,8 @@ function requestBody(input, parsed, shape) {
|
|
|
76
76
|
body.outputDimensionality = parsed.outputDimensionality;
|
|
77
77
|
return body;
|
|
78
78
|
}
|
|
79
|
-
export const
|
|
80
|
-
id: '
|
|
79
|
+
export const geminiEmbeddings = defineProtocol({
|
|
80
|
+
id: 'geminiEmbeddings',
|
|
81
81
|
name: 'Gemini Embeddings',
|
|
82
82
|
description: "Google's embedding API for text and supported media.",
|
|
83
83
|
endpoints: { embedding: '/models/{apiId}:embedContent' },
|
|
@@ -88,7 +88,7 @@ export const geminiEmbedding = defineProtocol({
|
|
|
88
88
|
create(context) {
|
|
89
89
|
return {
|
|
90
90
|
async embed(request) {
|
|
91
|
-
const parsed = modelOptions(
|
|
91
|
+
const parsed = modelOptions(geminiEmbeddings, context.apiId, request.options);
|
|
92
92
|
const taskField = TASK_FIELD_MODELS.test(context.apiId);
|
|
93
93
|
const shape = { taskField, ...(request.task ? { task: request.task } : {}) };
|
|
94
94
|
const inputs = Array.isArray(request.input) ? request.input : [request.input];
|
|
@@ -20,8 +20,11 @@ function socketTrouble(frame, during) {
|
|
|
20
20
|
/** Waits for `setupComplete`; a refusal here comes before any event, so the kernel can try the other key. */
|
|
21
21
|
async function setupComplete(frames, context) {
|
|
22
22
|
let timer;
|
|
23
|
+
const limit = context.limits.connectTimeoutMs;
|
|
23
24
|
const late = new Promise((_, reject) => {
|
|
24
|
-
|
|
25
|
+
if (limit === undefined)
|
|
26
|
+
return;
|
|
27
|
+
timer = setTimeout(() => reject(new TheoremError('timeout', `Gemini Live setup timed out after ${limit}ms`)), limit);
|
|
25
28
|
});
|
|
26
29
|
try {
|
|
27
30
|
for (;;) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { labelText } from '../../guardrails/labels.js';
|
|
3
3
|
import { defineProvider, endpointUrlSchema, header, query, } from '../../kernel/protocols/provider.js';
|
|
4
|
-
import {
|
|
4
|
+
import { geminiEmbeddings } from './embeddings-protocol.js';
|
|
5
5
|
import { geminiInteractions } from './interactions-protocol.js';
|
|
6
6
|
import { geminiLive } from './live-protocol.js';
|
|
7
7
|
import { geminiPrices } from './prices.js';
|
|
@@ -31,7 +31,7 @@ export const geminiProvider = defineProvider({
|
|
|
31
31
|
const liveOrigin = `${host.protocol === 'http:' ? 'ws:' : 'wss:'}//${host.host}`;
|
|
32
32
|
return [
|
|
33
33
|
{ protocol: geminiInteractions, baseUrl: `${origin}/v1beta` },
|
|
34
|
-
{ protocol:
|
|
34
|
+
{ protocol: geminiEmbeddings, baseUrl: `${origin}/v1beta` },
|
|
35
35
|
// probed 2026-09-23: Live's WebSocket takes the key only in the URL's query.
|
|
36
36
|
{ protocol: geminiLive, baseUrl: `${liveOrigin}/ws`, authorize: query('key') },
|
|
37
37
|
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
2
|
+
/** What an `ollamaEmbeddings` binding sets in `options`. */
|
|
3
|
+
export interface OllamaEmbeddingsOptions {
|
|
4
|
+
truncate?: boolean;
|
|
5
|
+
dimensions?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const ollamaEmbeddings: Protocol<OllamaEmbeddingsOptions>;
|
|
@@ -23,8 +23,8 @@ function strings(input) {
|
|
|
23
23
|
return item.parts[0].text;
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
export const
|
|
27
|
-
id: '
|
|
26
|
+
export const ollamaEmbeddings = defineProtocol({
|
|
27
|
+
id: 'ollamaEmbeddings',
|
|
28
28
|
name: 'Ollama Embeddings',
|
|
29
29
|
description: 'Ollama’s local embedding endpoint.',
|
|
30
30
|
endpoints: { embedding: '/api/embed' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { bearer, defineProvider, endpointUrlSchema, } from '../../kernel/protocols/provider.js';
|
|
3
3
|
import { openAi } from '../open-ai/protocol.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ollamaEmbeddings } from './embeddings-protocol.js';
|
|
5
5
|
/** A server on your machine or network that speaks OpenAI's chat API: Ollama, llama.cpp, vLLM, LM Studio. */
|
|
6
6
|
export const localProvider = defineProvider({
|
|
7
7
|
id: 'local',
|
|
@@ -16,7 +16,7 @@ export const localProvider = defineProvider({
|
|
|
16
16
|
serves: (at) => [
|
|
17
17
|
{ protocol: openAi, baseUrl: at.baseUrl },
|
|
18
18
|
{
|
|
19
|
-
protocol:
|
|
19
|
+
protocol: ollamaEmbeddings,
|
|
20
20
|
baseUrl: at.baseUrl,
|
|
21
21
|
url: ({ path }) => {
|
|
22
22
|
const base = new URL(at.baseUrl);
|
|
@@ -17,15 +17,15 @@ export { bearer, createProviderRegistry, defineProvider, endpointUrlSchema, head
|
|
|
17
17
|
export type { OpenWebSocket, ProviderHostOptions } from '../kernel/protocols/runtime.js';
|
|
18
18
|
export type { CredentialContext, CredentialResolver, KeyCredential, KeyVault, } from '../kernel/registry/vault.js';
|
|
19
19
|
export { embeddingModelLimits } from './embedding-limits.js';
|
|
20
|
-
export {
|
|
20
|
+
export { geminiEmbeddings } from './gemini/embeddings-protocol.js';
|
|
21
21
|
export { geminiInteractions } from './gemini/interactions-protocol.js';
|
|
22
22
|
export { geminiLive } from './gemini/live-protocol.js';
|
|
23
23
|
export { geminiProvider } from './gemini/provider.js';
|
|
24
|
-
export {
|
|
24
|
+
export { ollamaEmbeddings } from './local/embeddings-protocol.js';
|
|
25
25
|
export { localProvider } from './local/provider.js';
|
|
26
26
|
export { openAi } from './open-ai/protocol.js';
|
|
27
27
|
export { openRouterDecisions } from './openrouter/decisions-protocol.js';
|
|
28
|
-
export {
|
|
28
|
+
export { openRouterEmbeddings } from './openrouter/embeddings-protocol.js';
|
|
29
29
|
export { openRouterImages } from './openrouter/images-protocol.js';
|
|
30
30
|
export { openRouterOpenAi } from './openrouter/protocol.js';
|
|
31
31
|
export { openRouterProvider } from './openrouter/provider.js';
|
package/esm/src/providers/mod.js
CHANGED
|
@@ -8,15 +8,15 @@ export { jsonObjectSchema, jsonValueSchema } from '../kernel/json.js';
|
|
|
8
8
|
export { carriesTo, defineProtocol, extendProtocol, modelOptions, } from '../kernel/protocols/protocol.js';
|
|
9
9
|
export { bearer, createProviderRegistry, defineProvider, endpointUrlSchema, header, query, } from '../kernel/protocols/provider.js';
|
|
10
10
|
export { embeddingModelLimits } from './embedding-limits.js';
|
|
11
|
-
export {
|
|
11
|
+
export { geminiEmbeddings } from './gemini/embeddings-protocol.js';
|
|
12
12
|
export { geminiInteractions } from './gemini/interactions-protocol.js';
|
|
13
13
|
export { geminiLive } from './gemini/live-protocol.js';
|
|
14
14
|
export { geminiProvider } from './gemini/provider.js';
|
|
15
|
-
export {
|
|
15
|
+
export { ollamaEmbeddings } from './local/embeddings-protocol.js';
|
|
16
16
|
export { localProvider } from './local/provider.js';
|
|
17
17
|
export { openAi } from './open-ai/protocol.js';
|
|
18
18
|
export { openRouterDecisions } from './openrouter/decisions-protocol.js';
|
|
19
|
-
export {
|
|
19
|
+
export { openRouterEmbeddings } from './openrouter/embeddings-protocol.js';
|
|
20
20
|
export { openRouterImages } from './openrouter/images-protocol.js';
|
|
21
21
|
export { openRouterOpenAi } from './openrouter/protocol.js';
|
|
22
22
|
export { openRouterProvider } from './openrouter/provider.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* OpenRouter's decision API: questions about a state, answered in one call. It takes no options:
|
|
4
|
+
* images and box questions need no opt-in, and a request with an image runs in long context.
|
|
5
|
+
*/
|
|
6
|
+
export declare const openRouterDecisions: Protocol<Record<string, never>>;
|
|
@@ -1,9 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { splitReferences } from '../../kernel/engine/decision-references.js';
|
|
3
|
+
import { jsonObjectSchema } from '../../kernel/json.js';
|
|
4
|
+
import { defineProtocol } from '../../kernel/protocols/mod.js';
|
|
5
|
+
import { decisionAnswered } from '../system-one/protocol.js';
|
|
6
|
+
/**
|
|
7
|
+
* OpenRouter's decision API: questions about a state, answered in one call. It takes no options:
|
|
8
|
+
* images and box questions need no opt-in, and a request with an image runs in long context.
|
|
9
|
+
*/
|
|
10
|
+
export const openRouterDecisions = defineProtocol({
|
|
5
11
|
id: 'openRouterDecisions',
|
|
6
12
|
name: 'OpenRouter Decisions',
|
|
7
13
|
description: "OpenRouter's decision API: questions about a state, answered in one call.", // labels-exempt: builder field description, read by the studio and docs
|
|
8
14
|
endpoints: { decision: '/alpha/decisions' },
|
|
15
|
+
options: z.strictObject({}),
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
decide(request) {
|
|
19
|
+
const assets = request.assets ?? {};
|
|
20
|
+
const body = jsonObjectSchema.parse({
|
|
21
|
+
model: context.apiId,
|
|
22
|
+
state: withImages(request.state, assets),
|
|
23
|
+
questions: Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [
|
|
24
|
+
id,
|
|
25
|
+
{
|
|
26
|
+
...question,
|
|
27
|
+
instructions: withImages(question.instructions, assets),
|
|
28
|
+
...('criteria' in question && question.criteria !== undefined
|
|
29
|
+
? { criteria: withImages(question.criteria, assets) }
|
|
30
|
+
: {}),
|
|
31
|
+
},
|
|
32
|
+
])),
|
|
33
|
+
});
|
|
34
|
+
return decisionAnswered('openRouterDecisions', context.send(body, { stream: false }));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
9
38
|
});
|
|
39
|
+
/**
|
|
40
|
+
* Each reference becomes its image, where the reference was, as TypeSafe reads references. A string
|
|
41
|
+
* that is one reference becomes the image part; a longer one becomes its text and images in order.
|
|
42
|
+
* why: OpenRouter ignores an `assets` map; it reads image parts in place.
|
|
43
|
+
*/
|
|
44
|
+
function withImages(value, assets) {
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
const pieces = splitReferences(value, assets);
|
|
47
|
+
if (pieces.every((piece) => typeof piece === 'string'))
|
|
48
|
+
return value;
|
|
49
|
+
const parts = pieces.map((piece) => typeof piece === 'string' ? piece : imagePart(piece.asset));
|
|
50
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value))
|
|
53
|
+
return value.map((item) => withImages(item, assets));
|
|
54
|
+
if (value !== null && typeof value === 'object')
|
|
55
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, withImages(item, assets)]));
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function imagePart({ mimeType, data }) {
|
|
59
|
+
return { type: 'image_url', image_url: { url: `data:${mimeType};base64,${data}` } };
|
|
60
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
2
|
+
/** What an `openRouterEmbeddings` binding sets in `options`. */
|
|
3
|
+
export interface OpenRouterEmbeddingsOptions {
|
|
4
|
+
encodingFormat?: 'float' | 'base64';
|
|
5
|
+
}
|
|
6
|
+
export declare const openRouterEmbeddings: Protocol<OpenRouterEmbeddingsOptions>;
|
|
@@ -38,8 +38,8 @@ function embeddingValues(value) {
|
|
|
38
38
|
}
|
|
39
39
|
return Array.from(new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / Float32Array.BYTES_PER_ELEMENT));
|
|
40
40
|
}
|
|
41
|
-
export const
|
|
42
|
-
id: '
|
|
41
|
+
export const openRouterEmbeddings = defineProtocol({
|
|
42
|
+
id: 'openRouterEmbeddings',
|
|
43
43
|
name: 'OpenRouter Embeddings',
|
|
44
44
|
description: 'OpenRouter embedding models, including supported multimodal models.',
|
|
45
45
|
endpoints: { embedding: '/embeddings' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { bearer, defineProvider, endpointUrlSchema, } from '../../kernel/protocols/provider.js';
|
|
3
3
|
import { openRouterDecisions } from './decisions-protocol.js';
|
|
4
|
-
import {
|
|
4
|
+
import { openRouterEmbeddings } from './embeddings-protocol.js';
|
|
5
5
|
import { openRouterImages } from './images-protocol.js';
|
|
6
6
|
import { openRouterOpenAi } from './protocol.js';
|
|
7
7
|
const connection = z.strictObject({
|
|
@@ -42,7 +42,7 @@ export const openRouterProvider = defineProvider({
|
|
|
42
42
|
const decisionUrl = at.decisionUrl ?? 'https://openrouter.ai/api/alpha/decisions';
|
|
43
43
|
return [
|
|
44
44
|
{ protocol: openRouterOpenAi, baseUrl, headers },
|
|
45
|
-
{ protocol:
|
|
45
|
+
{ protocol: openRouterEmbeddings, baseUrl, headers },
|
|
46
46
|
{ protocol: openRouterImages, baseUrl, headers },
|
|
47
47
|
{ protocol: openRouterDecisions, baseUrl: decisionUrl, url: () => decisionUrl, headers },
|
|
48
48
|
];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type JsonObject } from '../../kernel/json.js';
|
|
1
2
|
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
3
|
+
import type { DecisionResult } from '../../kernel/types.js';
|
|
2
4
|
/** What a System One binding may set. Both are TypeSafe's opt-in "danger zone" headers. */
|
|
3
5
|
export interface SystemOneOptions {
|
|
4
6
|
images?: boolean;
|
|
@@ -6,3 +8,5 @@ export interface SystemOneOptions {
|
|
|
6
8
|
}
|
|
7
9
|
/** TypeSafe's decision API: questions about a state, answered in one call. */
|
|
8
10
|
export declare const systemOne: Protocol<SystemOneOptions>;
|
|
11
|
+
/** The first decision reply, read into the kernel's result. Shared by every decision API that answers as System One does. */
|
|
12
|
+
export declare function decisionAnswered(protocol: string, rows: AsyncIterable<JsonObject>): Promise<DecisionResult>;
|
|
@@ -56,34 +56,35 @@ export const systemOne = defineProtocol({
|
|
|
56
56
|
? { assets: assetsOnWire(request.assets) }
|
|
57
57
|
: {}),
|
|
58
58
|
});
|
|
59
|
-
|
|
60
|
-
const parsed = reply.safeParse(row);
|
|
61
|
-
if (!parsed.success)
|
|
62
|
-
throw new TheoremError('bad_response', labelText('protocol.reply_shape', {
|
|
63
|
-
protocol: 'systemOne',
|
|
64
|
-
issues: z.prettifyError(parsed.error),
|
|
65
|
-
}));
|
|
66
|
-
const { model, answers, usage } = parsed.data;
|
|
67
|
-
// invariant: The kernel validates every answer against its question before exposing it.
|
|
68
|
-
return {
|
|
69
|
-
model,
|
|
70
|
-
answers: answers,
|
|
71
|
-
...(usage
|
|
72
|
-
? {
|
|
73
|
-
usage: {
|
|
74
|
-
inputTokens: usage.input_tokens,
|
|
75
|
-
outputTokens: usage.output_tokens,
|
|
76
|
-
...(usage.cost === undefined ? {} : { costUsd: usage.cost }),
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
: {}),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
throw new TheoremError('bad_response', labelText('provider.decision_failed'));
|
|
59
|
+
return await decisionAnswered('systemOne', context.send(body, { stream: false, headers }));
|
|
83
60
|
},
|
|
84
61
|
};
|
|
85
62
|
},
|
|
86
63
|
});
|
|
64
|
+
/** The first decision reply, read into the kernel's result. Shared by every decision API that answers as System One does. */
|
|
65
|
+
export async function decisionAnswered(protocol, rows) {
|
|
66
|
+
for await (const row of rows) {
|
|
67
|
+
const parsed = reply.safeParse(row);
|
|
68
|
+
if (!parsed.success)
|
|
69
|
+
throw new TheoremError('bad_response', labelText('protocol.reply_shape', { protocol, issues: z.prettifyError(parsed.error) }));
|
|
70
|
+
const { model, answers, usage } = parsed.data;
|
|
71
|
+
// invariant: The kernel validates every answer against its question before exposing it.
|
|
72
|
+
return {
|
|
73
|
+
model,
|
|
74
|
+
answers: answers,
|
|
75
|
+
...(usage
|
|
76
|
+
? {
|
|
77
|
+
usage: {
|
|
78
|
+
inputTokens: usage.input_tokens,
|
|
79
|
+
outputTokens: usage.output_tokens,
|
|
80
|
+
...(usage.cost === undefined ? {} : { costUsd: usage.cost }),
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
throw new TheoremError('bad_response', labelText('provider.decision_failed'));
|
|
87
|
+
}
|
|
87
88
|
const FORMATS = { 'image/png': 'png', 'image/jpeg': 'jpeg' };
|
|
88
89
|
function assetsOnWire(assets) {
|
|
89
90
|
return Object.fromEntries(Object.entries(assets).map(([reference, { mimeType, data }]) => [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/agents",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.508.geda6db0",
|
|
4
4
|
"description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
2
|
-
/** What an `ollamaEmbedding` binding sets in `options`. */
|
|
3
|
-
export interface OllamaEmbeddingOptions {
|
|
4
|
-
truncate?: boolean;
|
|
5
|
-
dimensions?: number;
|
|
6
|
-
}
|
|
7
|
-
export declare const ollamaEmbedding: Protocol<OllamaEmbeddingOptions>;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type Protocol } from '../../kernel/protocols/mod.js';
|
|
2
|
-
/** What an `openRouterEmbedding` binding sets in `options`. */
|
|
3
|
-
export interface OpenRouterEmbeddingOptions {
|
|
4
|
-
encodingFormat?: 'float' | 'base64';
|
|
5
|
-
}
|
|
6
|
-
export declare const openRouterEmbedding: Protocol<OpenRouterEmbeddingOptions>;
|