@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
|
@@ -7,6 +7,7 @@ import { resolveGuardrailPolicy } from '../../../guardrails/policy.js';
|
|
|
7
7
|
import { replyIsJudged } from '../../../guardrails/progressive-yield.js';
|
|
8
8
|
import { recordTaint } from '../../../guardrails/tool-result.js';
|
|
9
9
|
import { waitDefault } from '../../../providers/shared/retry.js';
|
|
10
|
+
import { ANSWER_TOOL, answersByTool, withAnswerTool } from '../../answer-tool.js';
|
|
10
11
|
import { stepReplyMessages } from '../../interaction-parts.js';
|
|
11
12
|
import { profileTurnOutputs } from '../../registry/profile-outputs.js';
|
|
12
13
|
import { providerCompleteRequest } from '../../registry/provider-request.js';
|
|
@@ -60,14 +61,14 @@ function holdModelCall(calls, tool) {
|
|
|
60
61
|
return true;
|
|
61
62
|
}
|
|
62
63
|
/** The step's request, with what it gives the model noted on `state`, and its call's usage and trace. */
|
|
63
|
-
function startProviderCall(generation, system, state, answerOnly) {
|
|
64
|
+
function startProviderCall(generation, system, state, answerOnly, labels) {
|
|
64
65
|
const usage = startCallUsage(system.text, {
|
|
65
66
|
history: state.currentHistory,
|
|
66
67
|
input: generation.input,
|
|
67
68
|
});
|
|
68
69
|
state.lastCall = usage;
|
|
69
70
|
const genForStep = generationForProviderStep(generation, state, answerOnly);
|
|
70
|
-
const request = providerCompleteRequest(state.tools, genForStep, system.text);
|
|
71
|
+
const request = withAnswerTool(providerCompleteRequest(state.tools, genForStep, system.text), generation, labels);
|
|
71
72
|
addRequestUrls(state.givenUrls, request);
|
|
72
73
|
addRequestDestinations(state.destinations, request, state.givenUrls.own);
|
|
73
74
|
if (generation.canary && !state.canaryGiven) {
|
|
@@ -118,6 +119,42 @@ function tallyCallEvent(event, tally, call) {
|
|
|
118
119
|
tally.replied = true;
|
|
119
120
|
return false;
|
|
120
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The structured output the call gave through the answer tool, shown as the step's, with the answer
|
|
124
|
+
* tool's calls taken out of the step's calls. An answer ends the turn, so the step's other calls do
|
|
125
|
+
* not run, and the call that gave it completed.
|
|
126
|
+
*/
|
|
127
|
+
async function* showAnswer(shown, tally, step) {
|
|
128
|
+
if (!answersByTool(step.generation))
|
|
129
|
+
return;
|
|
130
|
+
const answer = tally.calls.find((call) => call.name === ANSWER_TOOL && !call.failure);
|
|
131
|
+
tally.calls = answer ? [] : tally.calls.filter((call) => call.name !== ANSWER_TOOL);
|
|
132
|
+
if (!answer)
|
|
133
|
+
return;
|
|
134
|
+
if (tally.stop?.kind === 'tool') {
|
|
135
|
+
tally.stop = { kind: 'completed' };
|
|
136
|
+
step.state.lastStop = tally.stop;
|
|
137
|
+
}
|
|
138
|
+
const event = { type: 'structured', structured: answer.arguments };
|
|
139
|
+
if (tallyCallEvent(event, tally, step.call))
|
|
140
|
+
return;
|
|
141
|
+
if (releasesNow(event, step.control, step.state, step.buffer))
|
|
142
|
+
yield* shown.show(event);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A tool call's preview. The answer tool's streams as the structured reply's draft.
|
|
146
|
+
*
|
|
147
|
+
* why: A preview of the call that follows, which owns the arguments: not the turn's record. It streams
|
|
148
|
+
* as the reply's text does, through its own gate, and like that text is held only when delivery is buffered.
|
|
149
|
+
*/
|
|
150
|
+
async function* showToolDraft(event, shown, generation, buffer) {
|
|
151
|
+
if (buffer.holdVisible)
|
|
152
|
+
return;
|
|
153
|
+
if (event.name === ANSWER_TOOL && answersByTool(generation))
|
|
154
|
+
yield* shown.draft(event.text);
|
|
155
|
+
else
|
|
156
|
+
yield event;
|
|
157
|
+
}
|
|
121
158
|
/** What sets one model call's text apart from the reply text before it: a blank line. */
|
|
122
159
|
const CALL_BREAK = '\n\n';
|
|
123
160
|
/**
|
|
@@ -130,7 +167,8 @@ function withCallBreak(event, tally, state, generation) {
|
|
|
130
167
|
if (tally.text)
|
|
131
168
|
return event;
|
|
132
169
|
const before = state.replyText;
|
|
133
|
-
const
|
|
170
|
+
const prose = generation.structured === null || answersByTool(generation);
|
|
171
|
+
const breaks = before && prose && !/\s$/.test(before) && !/^\s/.test(event.text);
|
|
134
172
|
tally.replyAt = before.length + (breaks ? CALL_BREAK.length : 0);
|
|
135
173
|
return breaks ? { ...event, text: `${CALL_BREAK}${event.text}` } : event;
|
|
136
174
|
}
|
|
@@ -194,7 +232,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
194
232
|
holdVisible: false,
|
|
195
233
|
}) {
|
|
196
234
|
const { profile, generation, system, provider, signal } = args;
|
|
197
|
-
const { usage, genForStep, request, call } = startProviderCall(generation, system, state, isAnswerOnlyStep(profile, generation, state.stepCount));
|
|
235
|
+
const { usage, genForStep, request, call } = startProviderCall(generation, system, state, isAnswerOnlyStep(profile, generation, state.stepCount), profile.labels);
|
|
198
236
|
const tally = { calls: [], reasoning: [], text: '', failures: [], replied: false };
|
|
199
237
|
const control = {
|
|
200
238
|
withholdVisible: false,
|
|
@@ -209,7 +247,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
209
247
|
canaryGiven: state.canaryGiven,
|
|
210
248
|
...(state.ownTools ? { ownTools: state.ownTools } : {}),
|
|
211
249
|
};
|
|
212
|
-
const shown = new StructuredDrafts(seen);
|
|
250
|
+
const shown = new StructuredDrafts({ ...seen, generation });
|
|
213
251
|
try {
|
|
214
252
|
for await (const event of yieldProviderEvents({
|
|
215
253
|
...seen,
|
|
@@ -219,6 +257,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
219
257
|
signal,
|
|
220
258
|
control,
|
|
221
259
|
tools: state.tools,
|
|
260
|
+
answerTool: answersByTool(generation),
|
|
222
261
|
})) {
|
|
223
262
|
if (observeCallEvent(usage, event))
|
|
224
263
|
continue;
|
|
@@ -234,14 +273,11 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
234
273
|
continue;
|
|
235
274
|
}
|
|
236
275
|
if (event.type === 'tool_draft') {
|
|
237
|
-
|
|
238
|
-
// as the reply's text does, through its own gate, and like that text is held only when delivery is buffered.
|
|
239
|
-
if (!buffer.holdVisible)
|
|
240
|
-
yield event;
|
|
276
|
+
yield* showToolDraft(event, shown, generation, buffer);
|
|
241
277
|
continue;
|
|
242
278
|
}
|
|
243
279
|
// why: The model's history keeps what it wrote; only the reply the host gets is set apart.
|
|
244
|
-
const next = event.type === 'text' ? withCallBreak(event, tally, state,
|
|
280
|
+
const next = event.type === 'text' ? withCallBreak(event, tally, state, generation) : event;
|
|
245
281
|
if (tallyCallEvent(event, tally, call))
|
|
246
282
|
continue;
|
|
247
283
|
if (!releasesNow(next, control, state, buffer))
|
|
@@ -254,6 +290,7 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
254
290
|
throw err;
|
|
255
291
|
}
|
|
256
292
|
yield* shown.end();
|
|
293
|
+
yield* showAnswer(shown, tally, { generation, state, control, buffer, call });
|
|
257
294
|
keepStreamCarry(state, control);
|
|
258
295
|
const resume = resumeOf(args.mayResume, profile, tally);
|
|
259
296
|
if (!resume) {
|
|
@@ -24,18 +24,20 @@ interface OutboundStreamControl {
|
|
|
24
24
|
declare function shouldSkipStreamEvent(event: ModelEvent, profile: Profile): boolean;
|
|
25
25
|
/** Host-visible output a mid-stream block withholds. A thought is never withheld, only omitted from. */
|
|
26
26
|
declare function isWithheldOnBlock(event: ModelEvent): boolean;
|
|
27
|
+
export type ShownGeneration = Pick<ResolvedGeneration, 'structured' | 'stream' | 'tools'>;
|
|
27
28
|
/**
|
|
28
|
-
* An event as the host gets it, or nothing.
|
|
29
|
-
* that output's JSON: under `sse` it goes out as `structured_draft`, and under `buffered`
|
|
30
|
-
* since the `structured` event is the reply whole. The turn keeps it as text, which its
|
|
31
|
-
* repairs and history read.
|
|
29
|
+
* An event as the host gets it, or nothing. On a turn that answers through a response format, the
|
|
30
|
+
* text is that output's JSON: under `sse` it goes out as `structured_draft`, and under `buffered`
|
|
31
|
+
* not at all, since the `structured` event is the reply whole. The turn keeps it as text, which its
|
|
32
|
+
* checks, repairs and history read. On a turn that answers by tool, the text is progress.
|
|
32
33
|
*/
|
|
33
|
-
declare function shownToHost(event: TurnEvent, generation:
|
|
34
|
+
declare function shownToHost(event: TurnEvent, generation: ShownGeneration): TurnEvent | undefined;
|
|
34
35
|
/**
|
|
35
36
|
* A step's events as the host gets them (`shownToHost`), with a structured reply's drafts read
|
|
36
|
-
* first at `reply_structured`, where the whole is read. The reply's text
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* first at `reply_structured`, where the whole is read. The drafts are the reply's text, or the
|
|
38
|
+
* answer tool's arguments as the model writes them. Text has already passed the `reply` gate; a
|
|
39
|
+
* match at `reply_structured` stops the drafts, and the `structured` event still arrives as that
|
|
40
|
+
* check leaves it. The held tail is read and released when the step's stream ends.
|
|
39
41
|
*/
|
|
40
42
|
declare class StructuredDrafts {
|
|
41
43
|
private readonly drafts;
|
|
@@ -43,6 +45,8 @@ declare class StructuredDrafts {
|
|
|
43
45
|
/** Reads the stream's own checks off what the step knows of the turn. */
|
|
44
46
|
constructor(seen: Pick<StreamArgs, 'profile' | 'generation' | 'privateSystem' | 'givenUrls' | 'canaryGiven' | 'ownTools'>);
|
|
45
47
|
show(event: TurnEvent): AsyncGenerator<TurnEvent>;
|
|
48
|
+
/** Part of the structured output's JSON, as the model writes it. */
|
|
49
|
+
draft(json: string): AsyncGenerator<TurnEvent>;
|
|
46
50
|
/** The drafts' tail the gate still holds, once the step's stream has ended. */
|
|
47
51
|
end(): Generator<TurnEvent>;
|
|
48
52
|
}
|
|
@@ -65,6 +69,8 @@ interface StreamArgs {
|
|
|
65
69
|
ownTools?: OwnTools;
|
|
66
70
|
/** The turn's tools: a draft is read at the boundary its tool's calls are. */
|
|
67
71
|
tools: ToolRegistry;
|
|
72
|
+
/** True when the turn answers through the answer tool (`answersByTool`). */
|
|
73
|
+
answerTool?: boolean;
|
|
68
74
|
}
|
|
69
75
|
declare function yieldProviderEvents(args: StreamArgs): AsyncGenerator<StreamEvent>;
|
|
70
76
|
export type { OutboundStreamControl, StreamArgs };
|
|
@@ -7,6 +7,7 @@ import { resolveGuardrailPolicy } from '../../../guardrails/policy.js';
|
|
|
7
7
|
import { createDraftGate, createOutboundProgressiveGate, DraftReader, } from '../../../guardrails/progressive-yield.js';
|
|
8
8
|
import { EGRESS_RULES } from '../../../guardrails/rules.js';
|
|
9
9
|
import { thoughtGuardFor, } from '../../../guardrails/thought-guard.js';
|
|
10
|
+
import { ANSWER_TOOL, answersByTool } from '../../answer-tool.js';
|
|
10
11
|
import { profileTurnOutputs } from '../../registry/profile-outputs.js';
|
|
11
12
|
import { toolArgumentsBoundary } from '../../tools/execute.js';
|
|
12
13
|
function shouldSkipStreamEvent(event, profile) {
|
|
@@ -69,21 +70,22 @@ function isWithheldOnBlock(event) {
|
|
|
69
70
|
return event.type === 'text' || event.type === 'media';
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
|
-
* An event as the host gets it, or nothing.
|
|
73
|
-
* that output's JSON: under `sse` it goes out as `structured_draft`, and under `buffered`
|
|
74
|
-
* since the `structured` event is the reply whole. The turn keeps it as text, which its
|
|
75
|
-
* repairs and history read.
|
|
73
|
+
* An event as the host gets it, or nothing. On a turn that answers through a response format, the
|
|
74
|
+
* text is that output's JSON: under `sse` it goes out as `structured_draft`, and under `buffered`
|
|
75
|
+
* not at all, since the `structured` event is the reply whole. The turn keeps it as text, which its
|
|
76
|
+
* checks, repairs and history read. On a turn that answers by tool, the text is progress.
|
|
76
77
|
*/
|
|
77
78
|
function shownToHost(event, generation) {
|
|
78
|
-
if (!generation.structured || event.type !== 'text')
|
|
79
|
+
if (!generation.structured || answersByTool(generation) || event.type !== 'text')
|
|
79
80
|
return event;
|
|
80
81
|
return generation.stream === false ? undefined : { type: 'structured_draft', text: event.text };
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
84
|
* A step's events as the host gets them (`shownToHost`), with a structured reply's drafts read
|
|
84
|
-
* first at `reply_structured`, where the whole is read. The reply's text
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* first at `reply_structured`, where the whole is read. The drafts are the reply's text, or the
|
|
86
|
+
* answer tool's arguments as the model writes them. Text has already passed the `reply` gate; a
|
|
87
|
+
* match at `reply_structured` stops the drafts, and the `structured` event still arrives as that
|
|
88
|
+
* check leaves it. The held tail is read and released when the step's stream ends.
|
|
87
89
|
*/
|
|
88
90
|
class StructuredDrafts {
|
|
89
91
|
drafts;
|
|
@@ -102,9 +104,15 @@ class StructuredDrafts {
|
|
|
102
104
|
yield shown;
|
|
103
105
|
return;
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
yield* this.draft(shown.text);
|
|
108
|
+
}
|
|
109
|
+
/** Part of the structured output's JSON, as the model writes it. */
|
|
110
|
+
async *draft(json) {
|
|
111
|
+
if (this.generation.stream === false)
|
|
112
|
+
return;
|
|
113
|
+
const text = await this.drafts.read(json);
|
|
106
114
|
if (text)
|
|
107
|
-
yield {
|
|
115
|
+
yield { type: 'structured_draft', text };
|
|
108
116
|
}
|
|
109
117
|
/** The drafts' tail the gate still holds, once the step's stream has ended. */
|
|
110
118
|
*end() {
|
|
@@ -131,11 +139,13 @@ class ToolDrafts {
|
|
|
131
139
|
policy;
|
|
132
140
|
context;
|
|
133
141
|
tools;
|
|
142
|
+
answerTool;
|
|
134
143
|
drafts = new Map();
|
|
135
|
-
constructor(policy, context, tools) {
|
|
144
|
+
constructor(policy, context, tools, answerTool) {
|
|
136
145
|
this.policy = policy;
|
|
137
146
|
this.context = context;
|
|
138
147
|
this.tools = tools;
|
|
148
|
+
this.answerTool = answerTool;
|
|
139
149
|
}
|
|
140
150
|
/**
|
|
141
151
|
* Shows `event` when it is a draft and says whether it was; any other event closes its call's
|
|
@@ -178,7 +188,9 @@ class ToolDrafts {
|
|
|
178
188
|
this.drafts.delete(event.tool.callId);
|
|
179
189
|
}
|
|
180
190
|
async open(event) {
|
|
181
|
-
|
|
191
|
+
// why: The answer's drafts are the structured output's, read at `reply_structured` by `StructuredDrafts`.
|
|
192
|
+
const answer = this.answerTool && event.name === ANSWER_TOOL;
|
|
193
|
+
const boundary = answer ? undefined : toolArgumentsBoundary(this.tools, event.name);
|
|
182
194
|
const gate = boundary ? await createDraftGate(this.policy, this.context, boundary) : null;
|
|
183
195
|
const draft = { name: event.name, gate };
|
|
184
196
|
this.drafts.set(event.callId, draft);
|
|
@@ -216,7 +228,7 @@ async function* yieldProviderEvents(args) {
|
|
|
216
228
|
const { canaryCarry, thoughtCarry } = control ?? {};
|
|
217
229
|
const gate = await createOutboundProgressiveGate(policy, context, 'reply', canaryCarry);
|
|
218
230
|
const thoughts = await thoughtGuardFor(policy, context, thoughtCarry);
|
|
219
|
-
const drafts = new ToolDrafts(policy, context, args.tools);
|
|
231
|
+
const drafts = new ToolDrafts(policy, context, args.tools, args.answerTool === true);
|
|
220
232
|
/** The streamed event whose reply sits in the gate's lookback; released tails keep its shape. */
|
|
221
233
|
let pendingStream = null;
|
|
222
234
|
let withholdVisible = false;
|
package/esm/src/kernel/mod.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { outOfScopeFields, profileTypesForField } from './profile-scope.js';
|
|
|
29
29
|
export type { DecisionModelBinding, ModelBinding, RuntimeAbilities } from './protocols/binding.js';
|
|
30
30
|
export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './protocols/binding.js';
|
|
31
31
|
export type { ProviderLimits } from './protocols/limits.js';
|
|
32
|
-
export { providerLimitsSchema
|
|
32
|
+
export { providerLimitsSchema } from './protocols/limits.js';
|
|
33
33
|
export type { EstimatedCost, ModelPrice, PriceTable, Rates } from './protocols/prices.js';
|
|
34
34
|
export { costFromPrices } from './protocols/prices.js';
|
|
35
35
|
export type { Continuation, DecisionCall, EmbeddingCall, EmbeddingMediaKind, EmbeddingModelLimits, ExtensionBodyAt, ExtensionReader, LiveToolResult, ModelProfileType, Protocol, ProtocolContentEvent, ProtocolContext, ProtocolDefinition, ProtocolEvent, ProtocolExtension, ProtocolOperations, ProtocolRequest, ProtocolSession, ProtocolSocket, SendInit, UpstreamFailure, } from './protocols/protocol.js';
|
package/esm/src/kernel/mod.js
CHANGED
|
@@ -18,7 +18,7 @@ export { PROFILE_GRAPH, profileGraphFacet, spineFacetsForProfileType, } from './
|
|
|
18
18
|
export { unsetValue } from './profile-presence.js';
|
|
19
19
|
export { outOfScopeFields, profileTypesForField } from './profile-scope.js';
|
|
20
20
|
export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './protocols/binding.js';
|
|
21
|
-
export { providerLimitsSchema
|
|
21
|
+
export { providerLimitsSchema } from './protocols/limits.js';
|
|
22
22
|
export { costFromPrices } from './protocols/prices.js';
|
|
23
23
|
export { carriesTo, defineProtocol, extendProtocol, modelOptions } from './protocols/protocol.js';
|
|
24
24
|
export { bearer, createProviderRegistry, defineProvider, endpointUrlSchema, header, query, } from './protocols/provider.js';
|
|
@@ -40,6 +40,18 @@ export const PROFILE_FIELD_PRESENCE = {
|
|
|
40
40
|
},
|
|
41
41
|
'models.*.allowEffortSelect': { unset: 'Off', value: false },
|
|
42
42
|
'models.*.timeoutMs': { unset: 'No timeout' },
|
|
43
|
+
'models.*.limits': { unset: 'No limits' },
|
|
44
|
+
...Object.fromEntries([
|
|
45
|
+
'operationTimeoutMs',
|
|
46
|
+
'connectTimeoutMs',
|
|
47
|
+
'liveStepTimeoutMs',
|
|
48
|
+
'maxWireRecordBytes',
|
|
49
|
+
'maxWireResponseBytes',
|
|
50
|
+
'maxEventBytes',
|
|
51
|
+
'maxStepBytes',
|
|
52
|
+
'maxStepEvents',
|
|
53
|
+
'maxPendingToolCalls',
|
|
54
|
+
].map((field) => [`models.*.limits.${field}`, { unset: 'No limit' }])),
|
|
43
55
|
'models.*.compaction': { unset: 'Off' },
|
|
44
56
|
'models.*.compaction.maxTokens': { required: true },
|
|
45
57
|
'models.*.compaction.compactAt': { required: true },
|
|
@@ -25,10 +25,10 @@ const MODEL_PROFILE_TYPES = [
|
|
|
25
25
|
const TURN_TYPES = ['text', 'image', 'speech', 'live'];
|
|
26
26
|
/** Types whose `inputs` are turn inputs (text, files, slots) rather than decision state. */
|
|
27
27
|
const TURN_INPUT_TYPES = ['text', 'image'];
|
|
28
|
-
const TURN_INPUT_REASON = 'a decision takes JSON state,
|
|
28
|
+
const TURN_INPUT_REASON = 'a decision takes JSON state, and live takes its input through live.ingress';
|
|
29
29
|
/** Types that take slots and context: the turn types, and a call. */
|
|
30
30
|
const SLOT_TYPES = ['text', 'image', 'live'];
|
|
31
|
-
const SLOT_REASON = 'a decision takes JSON state, and
|
|
31
|
+
const SLOT_REASON = 'a decision takes JSON state, and an embedding call embeds its input alone';
|
|
32
32
|
/** Guardrails a host profile may set keep `host`; the rest guard a model turn. */
|
|
33
33
|
function turnGuardrailTypes(key) {
|
|
34
34
|
return HOST_GUARDRAIL_FIELDS.includes(key)
|
|
@@ -79,11 +79,11 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
79
79
|
},
|
|
80
80
|
'identity.system': {
|
|
81
81
|
profileTypes: ['text', 'image', 'live'],
|
|
82
|
-
reason: 'speech has no system channel (the input text is the transcript)
|
|
82
|
+
reason: 'speech has no system channel (the input text is the transcript), a decision is asked only through its questions, and an embedding call embeds its input alone',
|
|
83
83
|
},
|
|
84
84
|
'identity.systemByRole': {
|
|
85
85
|
profileTypes: ['text', 'image', 'live'],
|
|
86
|
-
reason: 'speech has no system channel (the input text is the transcript)
|
|
86
|
+
reason: 'speech has no system channel (the input text is the transcript), a decision is asked only through its questions, and an embedding call embeds its input alone',
|
|
87
87
|
},
|
|
88
88
|
models: { profileTypes: MODEL_PROFILE_TYPES, reason: 'a host profile runs no model' },
|
|
89
89
|
'models.*.protocol': {
|
|
@@ -96,7 +96,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
96
96
|
},
|
|
97
97
|
'models.*.compaction': {
|
|
98
98
|
profileTypes: ['text', 'image', 'speech'],
|
|
99
|
-
reason: 'live compacts with live.contextCompression',
|
|
99
|
+
reason: 'live compacts with live.contextCompression, and decision and embedding calls keep no conversation to compact',
|
|
100
100
|
},
|
|
101
101
|
'profile.defaultModel': {
|
|
102
102
|
profileTypes: [...TURN_TYPES, 'embedding'],
|
|
@@ -116,7 +116,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
116
116
|
live: { profileTypes: ['live'], reason: 'only live profiles open a realtime session' },
|
|
117
117
|
tools: {
|
|
118
118
|
profileTypes: ['text', 'image', 'live', 'host'],
|
|
119
|
-
reason: 'speech and
|
|
119
|
+
reason: 'speech, decision, and embedding profiles call no tools',
|
|
120
120
|
},
|
|
121
121
|
'tools.t1Policy': {
|
|
122
122
|
profileTypes: ['text', 'image'],
|
|
@@ -132,7 +132,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
132
132
|
},
|
|
133
133
|
'inputs.voice': {
|
|
134
134
|
profileTypes: ['text'],
|
|
135
|
-
reason: 'no image model reads audio,
|
|
135
|
+
reason: 'no image model reads audio, live takes audio through live.ingress, a decision takes JSON state, and an embedding call takes text and files',
|
|
136
136
|
},
|
|
137
137
|
'inputs.text': {
|
|
138
138
|
profileTypes: [...TURN_INPUT_TYPES, 'embedding'],
|
|
@@ -176,7 +176,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
176
176
|
},
|
|
177
177
|
outputs: {
|
|
178
178
|
profileTypes: ['text', 'image', 'speech'],
|
|
179
|
-
reason: 'live output is the realtime session, and decision and host profiles produce no turn output',
|
|
179
|
+
reason: 'live output is the realtime session, and decision, embedding, and host profiles produce no turn output',
|
|
180
180
|
},
|
|
181
181
|
'outputs.structured': {
|
|
182
182
|
profileTypes: ['text'],
|
|
@@ -205,27 +205,27 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
205
205
|
},
|
|
206
206
|
'guardrails.quota': {
|
|
207
207
|
profileTypes: turnGuardrailTypes('quota'),
|
|
208
|
-
reason: 'quota counts model turns',
|
|
208
|
+
reason: 'quota counts model turns, and decision, embedding, and host profiles run none',
|
|
209
209
|
},
|
|
210
210
|
'guardrails.detect': {
|
|
211
211
|
profileTypes: turnGuardrailTypes('detect'),
|
|
212
|
-
reason: '
|
|
212
|
+
reason: 'decision and embedding calls run none of the turn guardrails',
|
|
213
213
|
},
|
|
214
214
|
'guardrails.blockedReply': {
|
|
215
215
|
profileTypes: ['text', 'image', 'live'],
|
|
216
|
-
reason: 'only a reply can be blocked, and speech, decision, and host profiles write none',
|
|
216
|
+
reason: 'only a reply can be blocked, and speech, decision, embedding, and host profiles write none',
|
|
217
217
|
},
|
|
218
218
|
'guardrails.network': {
|
|
219
219
|
profileTypes: ['text', 'image', 'live', 'host'],
|
|
220
|
-
reason: 'speech and
|
|
220
|
+
reason: 'speech, decision, and embedding profiles call no tools',
|
|
221
221
|
},
|
|
222
222
|
'guardrails.taint': {
|
|
223
223
|
profileTypes: ['text', 'image', 'live'],
|
|
224
|
-
reason: 'speech and
|
|
224
|
+
reason: 'speech, decision, and embedding profiles call no tools, and a host profile runs no turn to taint',
|
|
225
225
|
},
|
|
226
226
|
'guardrails.disclosure': {
|
|
227
227
|
profileTypes: ['decision'],
|
|
228
|
-
reason: '
|
|
228
|
+
reason: 'only a decision profile sends state to its model, and disclosure gates that state',
|
|
229
229
|
},
|
|
230
230
|
};
|
|
231
231
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { type JsonObject } from '../json.js';
|
|
3
3
|
import { type KeySlot } from '../schema.js';
|
|
4
|
+
import { type ProviderLimits } from './limits.js';
|
|
4
5
|
import { type ModelProfileType, type Protocol } from './protocol.js';
|
|
5
6
|
import type { RegisteredProvider } from './provider.js';
|
|
6
7
|
/** A vault slot name. */
|
|
@@ -31,9 +32,10 @@ export interface ModelBinding extends z.infer<typeof commonModelSettingsSchema>
|
|
|
31
32
|
keySlot?: string;
|
|
32
33
|
fallbackKeySlot?: string;
|
|
33
34
|
options?: JsonObject;
|
|
35
|
+
limits?: ProviderLimits;
|
|
34
36
|
}
|
|
35
37
|
/** A decision profile's model: no generation settings, and a time limit. */
|
|
36
|
-
export interface DecisionModelBinding extends Pick<ModelBinding, 'protocol' | 'provider' | 'apiId' | 'keySlot' | 'fallbackKeySlot' | 'options'> {
|
|
38
|
+
export interface DecisionModelBinding extends Pick<ModelBinding, 'protocol' | 'provider' | 'apiId' | 'keySlot' | 'fallbackKeySlot' | 'options' | 'limits'> {
|
|
37
39
|
timeoutMs?: number;
|
|
38
40
|
}
|
|
39
41
|
export declare const modelBindingSchema: z.ZodType<ModelBinding>;
|
|
@@ -3,6 +3,7 @@ import { TheoremError } from '../../guardrails/error.js';
|
|
|
3
3
|
import { labelText } from '../../guardrails/labels.js';
|
|
4
4
|
import { jsonObjectSchema } from '../json.js';
|
|
5
5
|
import { KEY_SLOT_NAME } from '../schema.js';
|
|
6
|
+
import { providerLimitsSchema } from './limits.js';
|
|
6
7
|
import { modelOptions } from './protocol.js';
|
|
7
8
|
/** A vault slot name. */
|
|
8
9
|
export const keySlotSchema = z.string().regex(KEY_SLOT_NAME);
|
|
@@ -61,6 +62,10 @@ const modelBinding = z.strictObject({
|
|
|
61
62
|
.optional()
|
|
62
63
|
// labels-exempt: builder field description, read by the studio and docs
|
|
63
64
|
.describe("What this model's protocol takes beyond the shared settings."),
|
|
65
|
+
limits: providerLimitsSchema
|
|
66
|
+
.optional()
|
|
67
|
+
// labels-exempt: builder field description, read by the studio and docs
|
|
68
|
+
.describe("Theorem's cut-offs for this model's calls; each one left out is no limit."),
|
|
64
69
|
});
|
|
65
70
|
true;
|
|
66
71
|
export const modelBindingSchema = modelBinding;
|
|
@@ -72,6 +77,7 @@ const decisionModelBinding = modelBinding
|
|
|
72
77
|
keySlot: true,
|
|
73
78
|
fallbackKeySlot: true,
|
|
74
79
|
options: true,
|
|
80
|
+
limits: true,
|
|
75
81
|
})
|
|
76
82
|
.extend({
|
|
77
83
|
timeoutMs: z
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
/** The budgets
|
|
2
|
+
/** The budgets a provider call runs under. Each one left out is no limit: the provider's own applies. */
|
|
3
3
|
export interface ProviderLimits {
|
|
4
|
-
operationTimeoutMs
|
|
5
|
-
connectTimeoutMs
|
|
6
|
-
liveStepTimeoutMs
|
|
7
|
-
maxWireRecordBytes
|
|
8
|
-
maxWireResponseBytes
|
|
9
|
-
maxEventBytes
|
|
10
|
-
maxStepBytes
|
|
11
|
-
maxStepEvents
|
|
12
|
-
maxPendingToolCalls
|
|
4
|
+
operationTimeoutMs?: number;
|
|
5
|
+
connectTimeoutMs?: number;
|
|
6
|
+
liveStepTimeoutMs?: number;
|
|
7
|
+
maxWireRecordBytes?: number;
|
|
8
|
+
maxWireResponseBytes?: number;
|
|
9
|
+
maxEventBytes?: number;
|
|
10
|
+
maxStepBytes?: number;
|
|
11
|
+
maxStepEvents?: number;
|
|
12
|
+
maxPendingToolCalls?: number;
|
|
13
13
|
}
|
|
14
14
|
export declare const providerLimitsSchema: z.ZodType<ProviderLimits>;
|
|
15
|
-
export declare function resolveProviderLimits(overrides?: Partial<ProviderLimits>): ProviderLimits;
|
|
@@ -2,27 +2,13 @@ import { z } from 'zod';
|
|
|
2
2
|
const positiveBudget = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
|
|
3
3
|
const timerBudget = positiveBudget.max(2147483647);
|
|
4
4
|
export const providerLimitsSchema = z.strictObject({
|
|
5
|
-
operationTimeoutMs: timerBudget,
|
|
6
|
-
connectTimeoutMs: timerBudget,
|
|
7
|
-
liveStepTimeoutMs: timerBudget,
|
|
8
|
-
maxWireRecordBytes: positiveBudget,
|
|
9
|
-
maxWireResponseBytes: positiveBudget,
|
|
10
|
-
maxEventBytes: positiveBudget,
|
|
11
|
-
maxStepBytes: positiveBudget,
|
|
12
|
-
maxStepEvents: positiveBudget,
|
|
13
|
-
maxPendingToolCalls: positiveBudget,
|
|
5
|
+
operationTimeoutMs: timerBudget.optional(),
|
|
6
|
+
connectTimeoutMs: timerBudget.optional(),
|
|
7
|
+
liveStepTimeoutMs: timerBudget.optional(),
|
|
8
|
+
maxWireRecordBytes: positiveBudget.optional(),
|
|
9
|
+
maxWireResponseBytes: positiveBudget.optional(),
|
|
10
|
+
maxEventBytes: positiveBudget.optional(),
|
|
11
|
+
maxStepBytes: positiveBudget.optional(),
|
|
12
|
+
maxStepEvents: positiveBudget.optional(),
|
|
13
|
+
maxPendingToolCalls: positiveBudget.optional(),
|
|
14
14
|
});
|
|
15
|
-
export function resolveProviderLimits(overrides = {}) {
|
|
16
|
-
return Object.freeze(providerLimitsSchema.parse({
|
|
17
|
-
operationTimeoutMs: 120000,
|
|
18
|
-
connectTimeoutMs: 30000,
|
|
19
|
-
liveStepTimeoutMs: 120000,
|
|
20
|
-
maxWireRecordBytes: 16 * 1024 * 1024,
|
|
21
|
-
maxWireResponseBytes: 64 * 1024 * 1024,
|
|
22
|
-
maxEventBytes: 16 * 1024 * 1024,
|
|
23
|
-
maxStepBytes: 64 * 1024 * 1024,
|
|
24
|
-
maxStepEvents: 100000,
|
|
25
|
-
maxPendingToolCalls: 32,
|
|
26
|
-
...overrides,
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
@@ -3,7 +3,7 @@ import { type JsonObject } from '../json.js';
|
|
|
3
3
|
import type { KeyVault } from '../registry/vault.js';
|
|
4
4
|
import type { DecisionResult, KeySlot } from '../types.js';
|
|
5
5
|
import { type DecisionModelBinding, type ModelBinding } from './binding.js';
|
|
6
|
-
import {
|
|
6
|
+
import type { ProviderLimits } from './limits.js';
|
|
7
7
|
import type { DecisionCall, EmbeddingCall, EmbeddingResult, ModelProfileType, ProtocolOperations } from './protocol.js';
|
|
8
8
|
import type { RegisteredProvider } from './provider.js';
|
|
9
9
|
/** Opens a WebSocket with the headers and subprotocols a sign-in put on it. */
|
|
@@ -18,7 +18,8 @@ export type OpenWebSocket = (url: string, init: {
|
|
|
18
18
|
export declare function plainWebSocket(provider: string): OpenWebSocket;
|
|
19
19
|
/** What a host lends every provider call: its keys, its network, its clock, and its budgets. */
|
|
20
20
|
export interface ProviderHostOptions {
|
|
21
|
-
limits
|
|
21
|
+
/** Overrides the model's own `limits`, one field at a time. */
|
|
22
|
+
limits?: ProviderLimits;
|
|
22
23
|
vault?: KeyVault;
|
|
23
24
|
fetch?: typeof fetch;
|
|
24
25
|
wait?: Wait;
|
|
@@ -7,7 +7,6 @@ import { KEY_FALLBACK_ROW } from '../../providers/types.js';
|
|
|
7
7
|
import { jsonValueSchema } from '../json.js';
|
|
8
8
|
import { bytesToBase64 } from '../util/base64.js';
|
|
9
9
|
import { bindingServe, keySlotsOf, } from './binding.js';
|
|
10
|
-
import { resolveProviderLimits } from './limits.js';
|
|
11
10
|
import { costFromPrices } from './prices.js';
|
|
12
11
|
/**
|
|
13
12
|
* The runtime's own WebSocket. It takes no headers, so a sign-in that needs them is refused; a host
|
|
@@ -249,7 +248,7 @@ function protocolContext(setup, slot, raw, wire = { sent: false }, asked = []) {
|
|
|
249
248
|
apiId,
|
|
250
249
|
options: setup.binding.options ?? {},
|
|
251
250
|
builtInTools,
|
|
252
|
-
limits: setup.limits ??
|
|
251
|
+
limits: setup.limits ?? setup.binding.limits ?? {},
|
|
253
252
|
...(setup.signal ? { signal: setup.signal } : {}),
|
|
254
253
|
tapUpstream: tap,
|
|
255
254
|
async *send(body, init) {
|
|
@@ -11,9 +11,9 @@ export declare class ProviderBudget {
|
|
|
11
11
|
private stepBytes;
|
|
12
12
|
private stepEvents;
|
|
13
13
|
private calls;
|
|
14
|
-
private deadline
|
|
14
|
+
private deadline?;
|
|
15
15
|
private releaseSignal;
|
|
16
|
-
constructor(limits: ProviderLimits, caller?: AbortSignal, timeoutMs?: number);
|
|
16
|
+
constructor(limits: ProviderLimits, caller?: AbortSignal, timeoutMs?: number | undefined);
|
|
17
17
|
resumeOperation(): void;
|
|
18
18
|
/** Milliseconds before the running timer ends the call. */
|
|
19
19
|
timeLeft(): number;
|