theorum 1.0.0 → 1.1.3
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 +81 -25
- package/esm/mod.d.ts +48 -29
- package/esm/mod.js +37 -24
- package/esm/src/cli/commands/bench.js +13 -16
- package/esm/src/cli/commands/fuzz-canary.js +25 -29
- package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
- package/esm/src/cli/commands/guardrails-eval.js +15 -0
- package/esm/src/cli/commands/profile.js +12 -3
- package/esm/src/cli/commands/run.js +8 -9
- package/esm/src/cli/commands/test.js +8 -8
- package/esm/src/cli/event-log.d.ts +1 -1
- package/esm/src/cli/index.js +12 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
- package/esm/src/cli/matrix/synthesizer.js +23 -22
- package/esm/src/guardrails/canary-gate.d.ts +2 -1
- package/esm/src/guardrails/canary-gate.js +2 -1
- package/esm/src/guardrails/canary.d.ts +9 -1
- package/esm/src/guardrails/canary.js +21 -10
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
- package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
- package/esm/src/guardrails/corpus/live-attacks.js +0 -1
- package/esm/src/guardrails/corpus/mod.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.js +1 -0
- package/esm/src/guardrails/corpus/strings.d.ts +1 -0
- package/esm/src/guardrails/corpus/strings.js +1 -0
- package/esm/src/guardrails/corpus/types.d.ts +1 -0
- package/esm/src/guardrails/egress.d.ts +26 -3
- package/esm/src/guardrails/egress.js +69 -18
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +86 -77
- package/esm/src/guardrails/eval/corpus.d.ts +108 -0
- package/esm/src/guardrails/eval/corpus.js +978 -0
- package/esm/src/guardrails/eval/mod.d.ts +51 -0
- package/esm/src/guardrails/eval/mod.js +133 -0
- package/esm/src/guardrails/eval/score.d.ts +66 -0
- package/esm/src/guardrails/eval/score.js +114 -0
- package/esm/src/guardrails/events.d.ts +25 -0
- package/esm/src/guardrails/events.js +56 -0
- package/esm/src/guardrails/hits.d.ts +24 -0
- package/esm/src/guardrails/hits.js +45 -0
- package/esm/src/guardrails/lexicon.d.ts +39 -0
- package/esm/src/guardrails/lexicon.js +200 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
- package/esm/src/guardrails/live-outbound-gate.js +131 -89
- package/esm/src/guardrails/mod.d.ts +19 -4
- package/esm/src/guardrails/mod.js +12 -3
- package/esm/src/guardrails/network.d.ts +19 -0
- package/esm/src/guardrails/network.js +234 -0
- package/esm/src/guardrails/policy.d.ts +35 -0
- package/esm/src/guardrails/policy.js +50 -0
- package/esm/src/guardrails/progressive-yield.d.ts +51 -0
- package/esm/src/guardrails/progressive-yield.js +98 -0
- package/esm/src/guardrails/quota.d.ts +17 -3
- package/esm/src/guardrails/quota.js +18 -4
- package/esm/src/guardrails/sanitize.d.ts +36 -6
- package/esm/src/guardrails/sanitize.js +152 -60
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +7 -0
- package/esm/src/guardrails/testing.js +4 -0
- package/esm/src/guardrails/theorum-error.d.ts +12 -0
- package/esm/src/guardrails/theorum-error.js +15 -0
- package/esm/src/guardrails/tool-directives.d.ts +48 -0
- package/esm/src/guardrails/tool-directives.js +124 -0
- package/esm/src/guardrails/tool-result.d.ts +93 -0
- package/esm/src/guardrails/tool-result.js +276 -0
- package/esm/src/guardrails/types.d.ts +291 -0
- package/esm/src/guardrails/types.js +72 -0
- package/esm/src/host/client-turn.js +4 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/kernel/auth/crypto.d.ts +42 -0
- package/esm/src/kernel/auth/crypto.js +106 -0
- package/esm/src/kernel/auth/mod.d.ts +11 -0
- package/esm/src/kernel/auth/mod.js +11 -0
- package/esm/src/kernel/auth/oauth.d.ts +47 -0
- package/esm/src/kernel/auth/oauth.js +278 -0
- package/esm/src/kernel/auth/types.d.ts +133 -0
- package/esm/src/kernel/auth/types.js +13 -0
- package/esm/src/kernel/engine/delta.js +43 -2
- package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
- package/esm/src/kernel/engine/live-inbound.js +22 -8
- package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
- package/esm/src/kernel/engine/live-ingress.js +47 -0
- package/esm/src/kernel/engine/repair.js +13 -12
- package/esm/src/kernel/engine/runner/gates.js +127 -40
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +182 -43
- package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
- package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
- package/esm/src/kernel/engine/runner/stages.js +89 -0
- package/esm/src/kernel/engine/runner/state.d.ts +24 -0
- package/esm/src/kernel/engine/runner/steps.js +128 -61
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
- package/esm/src/kernel/engine/runner/stream.js +114 -55
- package/esm/src/kernel/engine/session/mod.d.ts +3 -1
- package/esm/src/kernel/engine/session/mod.js +386 -34
- package/esm/src/kernel/interaction-parts.d.ts +14 -0
- package/esm/src/kernel/interaction-parts.js +23 -0
- package/esm/src/kernel/mod.d.ts +19 -7
- package/esm/src/kernel/mod.js +10 -5
- package/esm/src/kernel/profile-graph.d.ts +159 -0
- package/esm/src/kernel/profile-graph.js +156 -0
- package/esm/src/kernel/registry/attachments.d.ts +10 -10
- package/esm/src/kernel/registry/attachments.js +29 -26
- package/esm/src/kernel/registry/catalog.d.ts +25 -11
- package/esm/src/kernel/registry/catalog.js +59 -24
- package/esm/src/kernel/registry/ingress.js +34 -22
- package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
- package/esm/src/kernel/registry/profile-outputs.js +8 -0
- package/esm/src/kernel/registry/profiles.d.ts +34 -14
- package/esm/src/kernel/registry/profiles.js +306 -59
- package/esm/src/kernel/registry/provider-request.js +2 -0
- package/esm/src/kernel/registry/resolve.d.ts +7 -6
- package/esm/src/kernel/registry/resolve.js +102 -117
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/registry/sole-model.d.ts +8 -0
- package/esm/src/kernel/registry/sole-model.js +10 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
- package/esm/src/kernel/registry/system-prompt.js +40 -0
- package/esm/src/kernel/registry/system-role.d.ts +8 -0
- package/esm/src/kernel/registry/system-role.js +14 -0
- package/esm/src/kernel/registry/vault.d.ts +2 -2
- package/esm/src/kernel/registry/vault.js +4 -4
- package/esm/src/kernel/schema.d.ts +97 -11
- package/esm/src/kernel/schema.js +256 -111
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +65 -11
- package/esm/src/kernel/stop.js +46 -11
- package/esm/src/kernel/tools/events.d.ts +41 -0
- package/esm/src/kernel/tools/events.js +71 -0
- package/esm/src/kernel/tools/execute.d.ts +63 -24
- package/esm/src/kernel/tools/execute.js +475 -224
- package/esm/src/kernel/tools/harness.js +16 -14
- package/esm/src/kernel/tools/invoke.js +26 -8
- package/esm/src/kernel/tools/mod.d.ts +3 -1
- package/esm/src/kernel/tools/mod.js +2 -1
- package/esm/src/kernel/tools/permission.d.ts +15 -0
- package/esm/src/kernel/tools/permission.js +47 -0
- package/esm/src/kernel/tools/project.js +5 -3
- package/esm/src/kernel/tools/registry.js +20 -10
- package/esm/src/kernel/tools/remote.d.ts +94 -0
- package/esm/src/kernel/tools/remote.js +577 -0
- package/esm/src/kernel/tools/resolve.d.ts +17 -7
- package/esm/src/kernel/tools/resolve.js +38 -17
- package/esm/src/kernel/tools/schema.d.ts +2 -0
- package/esm/src/kernel/tools/schema.js +23 -6
- package/esm/src/kernel/tools/stage-run.d.ts +105 -0
- package/esm/src/kernel/tools/stage-run.js +155 -0
- package/esm/src/kernel/tools/types.d.ts +198 -20
- package/esm/src/kernel/types.d.ts +253 -114
- package/esm/src/kernel/util/find-last.d.ts +2 -0
- package/esm/src/kernel/util/find-last.js +10 -0
- package/esm/src/observability/destinations.d.ts +31 -0
- package/esm/src/observability/destinations.js +67 -0
- package/esm/src/observability/mod.d.ts +10 -3
- package/esm/src/observability/mod.js +6 -2
- package/esm/src/observability/policy.d.ts +27 -0
- package/esm/src/observability/policy.js +80 -0
- package/esm/src/observability/resolve-policy.d.ts +16 -0
- package/esm/src/observability/resolve-policy.js +64 -0
- package/esm/src/observability/trace-attach.d.ts +3 -1
- package/esm/src/observability/trace-attach.js +17 -10
- package/esm/src/observability/trace-record.d.ts +12 -4
- package/esm/src/observability/trace-record.js +63 -19
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- package/esm/src/observability/trace.d.ts +14 -11
- package/esm/src/observability/trace.js +23 -14
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- package/esm/src/providers/create-provider.d.ts +7 -4
- package/esm/src/providers/create-provider.js +21 -7
- package/esm/src/providers/google/interactions/framing.d.ts +8 -0
- package/esm/src/providers/google/interactions/framing.js +74 -13
- package/esm/src/providers/google/live/framing.d.ts +5 -2
- package/esm/src/providers/google/live/framing.js +49 -12
- package/esm/src/providers/google/live/openapi-schema.js +3 -2
- package/esm/src/providers/google/live/stream.d.ts +9 -0
- package/esm/src/providers/google/live/stream.js +16 -3
- package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
- package/esm/src/providers/openrouter/cache-control.js +23 -0
- package/esm/src/providers/openrouter/chat.d.ts +21 -1
- package/esm/src/providers/openrouter/chat.js +51 -6
- package/esm/src/providers/openrouter/image.js +8 -19
- package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
- package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
- package/esm/src/providers/openrouter/openai/compat.js +23 -4
- package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
- package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
- package/package.json +1 -1
- package/esm/src/interface/blocks.d.ts +0 -20
- package/esm/src/interface/blocks.js +0 -180
- package/esm/src/interface/from-profile.d.ts +0 -14
- package/esm/src/interface/from-profile.js +0 -104
- package/esm/src/interface/inputs.d.ts +0 -8
- package/esm/src/interface/inputs.js +0 -127
- package/esm/src/interface/mod.d.ts +0 -10
- package/esm/src/interface/mod.js +0 -9
- package/esm/src/interface/types.d.ts +0 -153
- package/esm/src/interface/types.js +0 -13
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { throwIfAborted } from '../../../guardrails/error.js';
|
|
2
|
+
import { resolveGuardrailPolicy } from '../../../guardrails/policy.js';
|
|
3
|
+
import { recordTaint } from '../../../guardrails/tool-result.js';
|
|
4
|
+
import { wireInteractionPart } from '../../interaction-parts.js';
|
|
5
|
+
import { profileTurnOutputs } from '../../registry/profile-outputs.js';
|
|
6
|
+
import { injectWouldExceedMaxSteps } from '../../stages.js';
|
|
7
|
+
import { profileAllowsInject } from '../../stop.js';
|
|
2
8
|
import { executeRegisteredTool, formatToolFailureForModel, formatToolResult, newCallId, } from '../../tools/execute.js';
|
|
9
|
+
import { applyStageInjects } from './stages.js';
|
|
3
10
|
import { recordStepEvent } from './state.js';
|
|
4
11
|
import { yieldProviderEvents } from './stream.js';
|
|
5
12
|
function isStepLimitReached(step, maxSteps) {
|
|
@@ -29,18 +36,20 @@ function captureInteractionId(event, state) {
|
|
|
29
36
|
}
|
|
30
37
|
async function* executeAutonomousStep(args, state, buffer = {
|
|
31
38
|
holdLate: false,
|
|
32
|
-
holdUserVisible: false,
|
|
33
39
|
}) {
|
|
34
40
|
const { generation, system, provider, upstream, signal } = args;
|
|
35
41
|
const genForStep = generationForProviderStep(generation, state);
|
|
36
42
|
const pendingTools = [];
|
|
37
43
|
let latestStructured;
|
|
44
|
+
const control = { withholdVisible: false };
|
|
38
45
|
for await (const event of yieldProviderEvents({
|
|
46
|
+
profile: args.profile,
|
|
39
47
|
generation: genForStep,
|
|
40
48
|
system,
|
|
41
49
|
provider,
|
|
42
50
|
upstream,
|
|
43
51
|
signal,
|
|
52
|
+
control,
|
|
44
53
|
})) {
|
|
45
54
|
captureInteractionId(event, state);
|
|
46
55
|
if (event.type === 'structured') {
|
|
@@ -57,8 +66,16 @@ async function* executeAutonomousStep(args, state, buffer = {
|
|
|
57
66
|
continue;
|
|
58
67
|
}
|
|
59
68
|
recordStepEvent(event, state);
|
|
60
|
-
const isUserVisible = event.type === 'thought' || event.type === 'text';
|
|
61
|
-
|
|
69
|
+
const isUserVisible = event.type === 'thought' || event.type === 'text' || event.type === 'media';
|
|
70
|
+
if (control.withholdVisible && isUserVisible) {
|
|
71
|
+
// Progressive-yield blocked this attempt — keep events for egress/repair only.
|
|
72
|
+
// Record the decision so the attempt gate knows nothing reached the host.
|
|
73
|
+
state.withheldVisible = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Progressive-yield streams text/thought live under egress; holdLate only
|
|
77
|
+
// buffers non-visible events (e.g. structured) for validation.
|
|
78
|
+
const streamNow = !buffer.holdLate || event.type === 'tokens' || isUserVisible;
|
|
62
79
|
if (streamNow) {
|
|
63
80
|
yield event;
|
|
64
81
|
}
|
|
@@ -75,6 +92,7 @@ function appendInteractionsToolResultToHistory(history, toolEv, result) {
|
|
|
75
92
|
tool_call_id: tool.id ?? tool.callId ?? `call_${tool.name}`,
|
|
76
93
|
name: tool.name,
|
|
77
94
|
content: formatToolResult(result),
|
|
95
|
+
...(result.parts && result.parts.length > 0 ? { parts: result.parts } : {}),
|
|
78
96
|
});
|
|
79
97
|
}
|
|
80
98
|
function appendToolTurnToHistory(history, toolEv, result) {
|
|
@@ -101,6 +119,7 @@ function appendToolTurnToHistory(history, toolEv, result) {
|
|
|
101
119
|
tool_call_id: callId,
|
|
102
120
|
name: tool.name,
|
|
103
121
|
content: formatToolResult(result),
|
|
122
|
+
...(result.parts && result.parts.length > 0 ? { parts: result.parts } : {}),
|
|
104
123
|
});
|
|
105
124
|
}
|
|
106
125
|
function queueInteractionsToolContinuation(state, toolEv, result, fallbackInteractionId) {
|
|
@@ -116,7 +135,9 @@ function queueInteractionsToolContinuation(state, toolEv, result, fallbackIntera
|
|
|
116
135
|
type: 'function_result',
|
|
117
136
|
name: tool.name,
|
|
118
137
|
call_id: tool.id ?? tool.callId ?? `call_${tool.name}`,
|
|
119
|
-
result:
|
|
138
|
+
result: result.parts && result.parts.length > 0
|
|
139
|
+
? result.parts.map(wireInteractionPart)
|
|
140
|
+
: [{ type: 'text', text: formatToolResult(result) }],
|
|
120
141
|
};
|
|
121
142
|
if (state.interactionsContinuation &&
|
|
122
143
|
state.interactionsContinuation.previousInteractionId === previousInteractionId) {
|
|
@@ -149,25 +170,79 @@ function enrichToolEvent(tool, callId, patch) {
|
|
|
149
170
|
},
|
|
150
171
|
};
|
|
151
172
|
}
|
|
152
|
-
async function*
|
|
173
|
+
async function* drainToolExecEvents(exec, tool, callId, state) {
|
|
174
|
+
let next = await exec.next();
|
|
175
|
+
let sawGate = false;
|
|
176
|
+
while (!next.done) {
|
|
177
|
+
const event = next.value;
|
|
178
|
+
if (event.type === 'tool') {
|
|
179
|
+
const enriched = enrichToolEvent(tool, callId, event.tool);
|
|
180
|
+
state.allEmittedEvents.push(enriched);
|
|
181
|
+
yield enriched;
|
|
182
|
+
if (event.tool?.phase === 'gate')
|
|
183
|
+
sawGate = true;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
state.allEmittedEvents.push(event);
|
|
187
|
+
yield event;
|
|
188
|
+
}
|
|
189
|
+
next = await exec.next();
|
|
190
|
+
}
|
|
191
|
+
return { settlement: next.value, sawGate };
|
|
192
|
+
}
|
|
193
|
+
function recordProviderToolFailure(state, toolEv, tool, callId, failure, generation, useInteractionsContinuation, patch) {
|
|
194
|
+
const enriched = enrichToolEvent(tool, callId, {
|
|
195
|
+
phase: 'error',
|
|
196
|
+
failure,
|
|
197
|
+
...patch,
|
|
198
|
+
});
|
|
199
|
+
state.allEmittedEvents.push(enriched);
|
|
200
|
+
recordToolModelResult(state, toolEv, formatToolFailureForModel(failure), generation, useInteractionsContinuation);
|
|
201
|
+
return enriched;
|
|
202
|
+
}
|
|
203
|
+
function applyToolSettlement(settlement, state, toolEv, generation, useInteractionsContinuation) {
|
|
204
|
+
if (settlement.aborted) {
|
|
205
|
+
state.lastStop = {
|
|
206
|
+
kind: 'cancelled',
|
|
207
|
+
...(typeof settlement.aborted === 'object' && settlement.aborted.reason
|
|
208
|
+
? { native: settlement.aborted.reason }
|
|
209
|
+
: {}),
|
|
210
|
+
};
|
|
211
|
+
return 'stop_cancelled';
|
|
212
|
+
}
|
|
213
|
+
if (settlement.gated) {
|
|
214
|
+
state.toolSnapshot = generation.tools;
|
|
215
|
+
return 'gated';
|
|
216
|
+
}
|
|
217
|
+
if (settlement.modelResult?.provenance) {
|
|
218
|
+
state.taint = recordTaint(state.taint, settlement.modelResult.provenance, settlement.modelResult.suspicious);
|
|
219
|
+
}
|
|
220
|
+
if (!settlement.modelResult)
|
|
221
|
+
return 'continue';
|
|
222
|
+
recordToolModelResult(state, toolEv, settlement.modelResult, generation, useInteractionsContinuation);
|
|
223
|
+
if (settlement.pendingInject?.length) {
|
|
224
|
+
applyStageInjects(state, settlement.pendingInject);
|
|
225
|
+
}
|
|
226
|
+
return 'continue';
|
|
227
|
+
}
|
|
228
|
+
async function* handlePendingTools(pendingTools, generation, profile, state, safe) {
|
|
153
229
|
let executed = false;
|
|
154
|
-
let
|
|
230
|
+
let sawGate = false;
|
|
155
231
|
const useInteractionsContinuation = generation.transport === 'interactions';
|
|
156
232
|
for (const toolEv of pendingTools) {
|
|
233
|
+
if (sawGate)
|
|
234
|
+
break;
|
|
157
235
|
const tool = toolEv.tool;
|
|
158
|
-
if (!tool)
|
|
236
|
+
if (!tool)
|
|
159
237
|
continue;
|
|
160
|
-
}
|
|
161
238
|
executed = true;
|
|
162
239
|
const callId = tool.id ?? tool.callId ?? newCallId(tool.name || 'unknown');
|
|
163
|
-
// Provider cancelled an in-flight call (e.g. live barge-in). Do not execute.
|
|
164
240
|
if (tool.phase === 'cancel') {
|
|
165
241
|
const enriched = enrichToolEvent(tool, callId);
|
|
166
242
|
state.allEmittedEvents.push(enriched);
|
|
167
243
|
yield enriched;
|
|
168
244
|
continue;
|
|
169
245
|
}
|
|
170
|
-
// Provider already failed this call (e.g. malformed arguments JSON).
|
|
171
246
|
if (tool.phase === 'error' && tool.failure) {
|
|
172
247
|
const enriched = enrichToolEvent(tool, callId);
|
|
173
248
|
state.allEmittedEvents.push(enriched);
|
|
@@ -175,25 +250,24 @@ async function* handlePendingTools(pendingTools, generation, profile, state) {
|
|
|
175
250
|
recordToolModelResult(state, toolEv, formatToolFailureForModel(tool.failure), generation, useInteractionsContinuation);
|
|
176
251
|
continue;
|
|
177
252
|
}
|
|
178
|
-
// Empty name is a protocol defect — never route through the registry as unknown_tool.
|
|
179
253
|
if (!tool.name) {
|
|
180
|
-
|
|
254
|
+
yield recordProviderToolFailure(state, toolEv, tool, callId, {
|
|
181
255
|
code: 'malformed_arguments',
|
|
182
|
-
message: 'Provider tool call is missing a function name',
|
|
183
|
-
};
|
|
184
|
-
const enriched = enrichToolEvent(tool, callId, {
|
|
185
|
-
phase: 'error',
|
|
186
|
-
failure,
|
|
187
|
-
name: '',
|
|
188
|
-
});
|
|
189
|
-
state.allEmittedEvents.push(enriched);
|
|
190
|
-
yield enriched;
|
|
191
|
-
recordToolModelResult(state, toolEv, formatToolFailureForModel(failure), generation, useInteractionsContinuation);
|
|
256
|
+
message: 'Provider tool call is missing a function name', // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
257
|
+
}, generation, useInteractionsContinuation, { name: '' });
|
|
192
258
|
continue;
|
|
193
259
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
260
|
+
const stages = {
|
|
261
|
+
handlers: safe?.onStage ? [safe.onStage] : [],
|
|
262
|
+
profile,
|
|
263
|
+
step: state.stepCount,
|
|
264
|
+
history: () => state.currentHistory,
|
|
265
|
+
injectAllowed: profileAllowsInject(profile),
|
|
266
|
+
injectWouldExceedMaxSteps: injectWouldExceedMaxSteps(state.stepCount, generation.maxSteps),
|
|
267
|
+
host: generation.host,
|
|
268
|
+
signal: safe?.signal,
|
|
269
|
+
};
|
|
270
|
+
const drained = yield* drainToolExecEvents(executeRegisteredTool({
|
|
197
271
|
profile,
|
|
198
272
|
name: tool.name,
|
|
199
273
|
input: tool.arguments ?? {},
|
|
@@ -201,39 +275,26 @@ async function* handlePendingTools(pendingTools, generation, profile, state) {
|
|
|
201
275
|
ctx: {
|
|
202
276
|
sessionPermissions: generation.sessionPermissions,
|
|
203
277
|
path: generation.tools.path,
|
|
204
|
-
turn: { step: state.stepCount },
|
|
278
|
+
turn: { step: state.stepCount, taint: state.taint },
|
|
279
|
+
credentials: safe?.credentials,
|
|
280
|
+
host: generation.host,
|
|
281
|
+
resume: undefined,
|
|
282
|
+
signal: safe?.signal,
|
|
205
283
|
},
|
|
206
284
|
snapshot: generation.tools,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
if (event.tool?.phase === 'pause') {
|
|
218
|
-
modelResult = undefined;
|
|
219
|
-
paused = true;
|
|
220
|
-
}
|
|
221
|
-
next = await exec.next();
|
|
222
|
-
}
|
|
223
|
-
if (next.value !== undefined) {
|
|
224
|
-
modelResult = next.value;
|
|
225
|
-
}
|
|
226
|
-
if (paused) {
|
|
227
|
-
sawPause = true;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (!modelResult) {
|
|
231
|
-
continue;
|
|
285
|
+
stages,
|
|
286
|
+
}), tool, callId, state);
|
|
287
|
+
if (drained.sawGate)
|
|
288
|
+
sawGate = true;
|
|
289
|
+
const outcome = applyToolSettlement(drained.settlement, state, toolEv, generation, useInteractionsContinuation);
|
|
290
|
+
if (outcome === 'stop_cancelled')
|
|
291
|
+
return false;
|
|
292
|
+
if (outcome === 'gated' || sawGate) {
|
|
293
|
+
sawGate = true;
|
|
232
294
|
}
|
|
233
|
-
recordToolModelResult(state, toolEv, modelResult, generation, useInteractionsContinuation);
|
|
234
295
|
}
|
|
235
|
-
if (
|
|
236
|
-
state.lastStop = { kind: '
|
|
296
|
+
if (sawGate) {
|
|
297
|
+
state.lastStop = { kind: 'gate' };
|
|
237
298
|
return false;
|
|
238
299
|
}
|
|
239
300
|
return executed;
|
|
@@ -242,14 +303,20 @@ async function* executeAttempt(args) {
|
|
|
242
303
|
const { profile, generation, system, provider, upstream, state } = args;
|
|
243
304
|
let latestStructured;
|
|
244
305
|
let pendingTools = [];
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
306
|
+
// Text/thought stream via progressive-yield under egress; validation and egress
|
|
307
|
+
// both hold non-visible events (structured) until the attempt gate, so a policy
|
|
308
|
+
// sees the structured payload before any of it reaches the host.
|
|
309
|
+
const holdLate = Boolean(profileTurnOutputs(profile)?.validation ||
|
|
310
|
+
resolveGuardrailPolicy(profile.guardrails).egress?.enforce);
|
|
311
|
+
// Ceiling is cumulative `state.stepCount` across before_end inject re-entries
|
|
312
|
+
// within this attempt. Validation/egress repair resets stepCount at the start
|
|
313
|
+
// of each attempt cycle (see gates.ts).
|
|
314
|
+
while (!isStepLimitReached(state.stepCount, generation.maxSteps ?? 0)) {
|
|
249
315
|
throwIfAborted(args.safe.signal);
|
|
250
|
-
stepInAttempt++;
|
|
251
316
|
state.stepCount++;
|
|
252
|
-
|
|
317
|
+
// Mid-loop inject lives on `post_tool` (after tools). Opening inject is `pre_turn`
|
|
318
|
+
// outside this loop.
|
|
319
|
+
const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, upstream, signal: args.safe.signal }, state, { holdLate });
|
|
253
320
|
if (stepResult.latestStructured !== undefined) {
|
|
254
321
|
latestStructured = stepResult.latestStructured;
|
|
255
322
|
}
|
|
@@ -257,7 +324,7 @@ async function* executeAttempt(args) {
|
|
|
257
324
|
if (pendingTools.length === 0) {
|
|
258
325
|
break;
|
|
259
326
|
}
|
|
260
|
-
const executed = yield* handlePendingTools(pendingTools, generation, profile, state);
|
|
327
|
+
const executed = yield* handlePendingTools(pendingTools, generation, profile, state, args.safe);
|
|
261
328
|
if (!executed) {
|
|
262
329
|
break;
|
|
263
330
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent } from '../../types.js';
|
|
2
|
-
|
|
2
|
+
/** Mutable control flags shared with the step runner during one provider stream. */
|
|
3
|
+
interface OutboundStreamControl {
|
|
4
|
+
/** Stop releasing text/thought/media to the host; keep recording for egress. */
|
|
5
|
+
withholdVisible: boolean;
|
|
6
|
+
}
|
|
3
7
|
declare function shouldSkipStreamEvent(event: TurnEvent, profile: Profile): boolean;
|
|
4
8
|
declare function yieldProviderEvents(args: {
|
|
9
|
+
profile: Profile;
|
|
5
10
|
generation: ResolvedGeneration;
|
|
6
11
|
system: string;
|
|
7
12
|
provider: ModelProvider;
|
|
8
13
|
upstream: Record<string, unknown>[];
|
|
9
14
|
signal?: AbortSignal;
|
|
15
|
+
control?: OutboundStreamControl;
|
|
10
16
|
}): AsyncGenerator<TurnEvent>;
|
|
11
|
-
export {
|
|
17
|
+
export type { OutboundStreamControl };
|
|
18
|
+
export { shouldSkipStreamEvent, yieldProviderEvents };
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { eventHasCanary, isStreamedCanaryEvent, redactCanary } from '../../../guardrails/canary.js';
|
|
2
|
+
import { EGRESS_RULES } from '../../../guardrails/egress.js';
|
|
2
3
|
import { publicError, throwIfAborted, toErrorEvent } from '../../../guardrails/error.js';
|
|
4
|
+
import { guardrailFromHits } from '../../../guardrails/events.js';
|
|
5
|
+
import { resolveGuardrailPolicy } from '../../../guardrails/policy.js';
|
|
6
|
+
import { createOutboundProgressiveGate, } from '../../../guardrails/progressive-yield.js';
|
|
7
|
+
import { profileTurnOutputs } from '../../registry/profile-outputs.js';
|
|
3
8
|
import { providerCompleteRequest } from '../../registry/provider-request.js';
|
|
4
|
-
function systemFromProfile(profile, role) {
|
|
5
|
-
const { identity } = profile;
|
|
6
|
-
const { systemByRole, system } = identity;
|
|
7
|
-
if (systemByRole) {
|
|
8
|
-
const byRole = systemByRole[role];
|
|
9
|
-
if (byRole) {
|
|
10
|
-
return byRole;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
if (system) {
|
|
14
|
-
return system;
|
|
15
|
-
}
|
|
16
|
-
return '';
|
|
17
|
-
}
|
|
18
9
|
function shouldSkipStreamEvent(event, profile) {
|
|
19
|
-
return event.type === 'thought' && profile
|
|
10
|
+
return (event.type === 'thought' && profileTurnOutputs(profile)?.streaming?.streamThoughts === false);
|
|
20
11
|
}
|
|
21
12
|
function* processNormalEvent(event) {
|
|
22
13
|
if (event.type === 'error') {
|
|
@@ -32,38 +23,109 @@ function* processNormalEvent(event) {
|
|
|
32
23
|
}
|
|
33
24
|
}
|
|
34
25
|
function* yieldCanaryLeak(canary, event) {
|
|
26
|
+
const guardrail = guardrailFromHits('output_delta', 'untrusted', [{ rule: EGRESS_RULES.canary, severity: 'high', match: '[canary]' }], 'block');
|
|
27
|
+
if (guardrail) {
|
|
28
|
+
yield guardrail;
|
|
29
|
+
}
|
|
35
30
|
yield redactCanary(event, canary);
|
|
36
31
|
yield toErrorEvent('canary leaked');
|
|
37
32
|
}
|
|
38
|
-
function*
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
yield* yieldCanaryLeak(canary, event);
|
|
43
|
-
return 'stop';
|
|
44
|
-
}
|
|
45
|
-
if (result.emit) {
|
|
46
|
-
yield* processNormalEvent({ ...event, text: result.emit });
|
|
33
|
+
function* yieldDeltaBlock(hits) {
|
|
34
|
+
const guardrail = guardrailFromHits('output_delta', 'untrusted', hits, 'block');
|
|
35
|
+
if (guardrail) {
|
|
36
|
+
yield guardrail;
|
|
47
37
|
}
|
|
48
|
-
return 'continue';
|
|
49
38
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
yield* processNormalEvent({ type: emitType, text: result.emit });
|
|
59
|
-
}
|
|
60
|
-
return 'pass';
|
|
39
|
+
function isHostVisible(event) {
|
|
40
|
+
return event.type === 'text' || event.type === 'thought' || event.type === 'media';
|
|
41
|
+
}
|
|
42
|
+
function canaryOnlyImmediateStop(policy) {
|
|
43
|
+
return !policy.egress?.enforce;
|
|
44
|
+
}
|
|
45
|
+
function hasCanaryHit(hits) {
|
|
46
|
+
return hits.some((hit) => hit.rule === EGRESS_RULES.canary);
|
|
61
47
|
}
|
|
62
48
|
async function* yieldProviderEvents(args) {
|
|
63
|
-
const { generation, system, provider, upstream, signal } = args;
|
|
49
|
+
const { profile, generation, system, provider, upstream, signal, control } = args;
|
|
64
50
|
const { canary } = generation;
|
|
65
|
-
const
|
|
51
|
+
const policy = resolveGuardrailPolicy(profile.guardrails);
|
|
52
|
+
const context = {
|
|
53
|
+
stage: 'output_final',
|
|
54
|
+
trust: 'untrusted',
|
|
55
|
+
profileId: profile.id,
|
|
56
|
+
...(canary ? { canary } : {}),
|
|
57
|
+
};
|
|
58
|
+
const gate = createOutboundProgressiveGate(policy, context);
|
|
66
59
|
let lastStreamType;
|
|
60
|
+
let withholdVisible = false;
|
|
61
|
+
function armWithhold() {
|
|
62
|
+
withholdVisible = true;
|
|
63
|
+
if (control)
|
|
64
|
+
control.withholdVisible = true;
|
|
65
|
+
}
|
|
66
|
+
function withholding() {
|
|
67
|
+
return withholdVisible;
|
|
68
|
+
}
|
|
69
|
+
async function* drainBlockedDelta(hits) {
|
|
70
|
+
yield* yieldDeltaBlock(hits);
|
|
71
|
+
// Arm withhold before recording the unreleased tail so the step runner
|
|
72
|
+
// does not forward that text to the host.
|
|
73
|
+
armWithhold();
|
|
74
|
+
const tail = gate?.drainUnreleased();
|
|
75
|
+
if (tail)
|
|
76
|
+
yield { type: 'text', text: tail };
|
|
77
|
+
}
|
|
78
|
+
async function* flushGate() {
|
|
79
|
+
if (!gate || !lastStreamType) {
|
|
80
|
+
return 'pass';
|
|
81
|
+
}
|
|
82
|
+
const emitType = lastStreamType;
|
|
83
|
+
const result = await gate.flush();
|
|
84
|
+
lastStreamType = undefined;
|
|
85
|
+
if (result.blocked) {
|
|
86
|
+
if (hasCanaryHit(result.hits) && canary && canaryOnlyImmediateStop(policy)) {
|
|
87
|
+
yield* yieldCanaryLeak(canary, { type: emitType, text: gate.accumulated() });
|
|
88
|
+
return 'stop';
|
|
89
|
+
}
|
|
90
|
+
yield* drainBlockedDelta(result.hits);
|
|
91
|
+
return 'pass';
|
|
92
|
+
}
|
|
93
|
+
if (result.emit) {
|
|
94
|
+
yield* processNormalEvent({ type: emitType, text: result.emit });
|
|
95
|
+
}
|
|
96
|
+
return 'pass';
|
|
97
|
+
}
|
|
98
|
+
async function* gateStreamEvent(event) {
|
|
99
|
+
if (!gate) {
|
|
100
|
+
yield* processNormalEvent(event);
|
|
101
|
+
return 'continue';
|
|
102
|
+
}
|
|
103
|
+
if (withholding()) {
|
|
104
|
+
// Keep recording ungated fragments for egress context; host will not see them.
|
|
105
|
+
yield { type: event.type, text: event.text ?? '' };
|
|
106
|
+
return 'continue';
|
|
107
|
+
}
|
|
108
|
+
if (lastStreamType && lastStreamType !== event.type) {
|
|
109
|
+
const flushed = yield* flushGate();
|
|
110
|
+
if (flushed === 'stop') {
|
|
111
|
+
return 'stop';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
lastStreamType = event.type;
|
|
115
|
+
const result = await gate.process(event.text ?? '');
|
|
116
|
+
if (result.blocked) {
|
|
117
|
+
if (hasCanaryHit(result.hits) && canary && canaryOnlyImmediateStop(policy)) {
|
|
118
|
+
yield* yieldCanaryLeak(canary, event);
|
|
119
|
+
return 'stop';
|
|
120
|
+
}
|
|
121
|
+
yield* drainBlockedDelta(result.hits);
|
|
122
|
+
return 'continue';
|
|
123
|
+
}
|
|
124
|
+
if (result.emit) {
|
|
125
|
+
yield* processNormalEvent({ ...event, text: result.emit });
|
|
126
|
+
}
|
|
127
|
+
return 'continue';
|
|
128
|
+
}
|
|
67
129
|
throwIfAborted(signal);
|
|
68
130
|
for await (const event of provider.complete({
|
|
69
131
|
...providerCompleteRequest(generation, system),
|
|
@@ -73,38 +135,35 @@ async function* yieldProviderEvents(args) {
|
|
|
73
135
|
},
|
|
74
136
|
})) {
|
|
75
137
|
throwIfAborted(signal);
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
const status = yield* flushCanaryGate(gate, canary, lastStreamType);
|
|
79
|
-
if (status === 'stop') {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
lastStreamType = event.type;
|
|
84
|
-
const status = yield* yieldGatedStreamEvent(event, gate, canary);
|
|
138
|
+
if (isStreamedCanaryEvent(event)) {
|
|
139
|
+
const status = yield* gateStreamEvent(event);
|
|
85
140
|
if (status === 'stop') {
|
|
86
141
|
return;
|
|
87
142
|
}
|
|
88
143
|
continue;
|
|
89
144
|
}
|
|
90
145
|
if (gate && lastStreamType) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
146
|
+
const flushed = yield* flushGate();
|
|
147
|
+
if (flushed === 'stop') {
|
|
93
148
|
return;
|
|
94
149
|
}
|
|
95
|
-
lastStreamType = undefined;
|
|
96
150
|
}
|
|
97
151
|
if (canary && eventHasCanary(event, canary)) {
|
|
98
152
|
yield* yieldCanaryLeak(canary, event);
|
|
99
153
|
return;
|
|
100
154
|
}
|
|
155
|
+
if (withholding() && isHostVisible(event)) {
|
|
156
|
+
// Record for attempt egress / repair; step runner withholds from host.
|
|
157
|
+
yield event;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
101
160
|
yield* processNormalEvent(event);
|
|
102
161
|
}
|
|
103
|
-
if (gate) {
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
162
|
+
if (gate && lastStreamType) {
|
|
163
|
+
const flushed = yield* flushGate();
|
|
164
|
+
if (flushed === 'stop') {
|
|
106
165
|
return;
|
|
107
166
|
}
|
|
108
167
|
}
|
|
109
168
|
}
|
|
110
|
-
export { shouldSkipStreamEvent,
|
|
169
|
+
export { shouldSkipStreamEvent, yieldProviderEvents };
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Shares resolve / tools / canary / system / outbound gate with `runTurn`.
|
|
5
5
|
* Does not use `ModelProvider.complete()` — live is a session, not one turn.
|
|
6
6
|
*
|
|
7
|
+
* Stages: `docs/contracts/stages.md` (cycle map + `executeTool`).
|
|
8
|
+
*
|
|
7
9
|
* @module
|
|
8
10
|
*/
|
|
9
11
|
import type { GeminiTransport } from '../../../providers/google/keys.js';
|
|
@@ -18,6 +20,6 @@ export interface RunSessionOptions {
|
|
|
18
20
|
* Open a gated Gemini Live session for a `type: 'live'` profile.
|
|
19
21
|
*
|
|
20
22
|
* Hosts bridge browser sockets and tool dispatch; THEORUM owns Gemini WS,
|
|
21
|
-
* framing, inbound prep,
|
|
23
|
+
* framing, inbound prep, outbound canary/egress gates, and live stages.
|
|
22
24
|
*/
|
|
23
25
|
export declare function runSession(req: SessionRequest, options: RunSessionOptions): Promise<LiveSession>;
|