theorum 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/AGENT_PROFILE_CONTRACT.md +2 -2
- package/esm/_dnt.polyfills.d.ts +110 -0
- package/esm/_dnt.polyfills.js +144 -0
- package/esm/_dnt.shims.d.ts +5 -0
- package/esm/_dnt.shims.js +61 -0
- package/{mod.ts → esm/mod.d.ts} +17 -57
- package/esm/mod.js +50 -0
- package/esm/package.json +3 -0
- package/esm/src/cli/commands/profile.d.ts +2 -0
- package/esm/src/cli/commands/profile.js +53 -0
- package/esm/src/cli/commands/run.d.ts +10 -0
- package/esm/src/cli/commands/run.js +55 -0
- package/esm/src/cli/commands/test.d.ts +15 -0
- package/esm/src/cli/commands/test.js +140 -0
- package/esm/src/cli/index.d.ts +4 -0
- package/esm/src/cli/index.js +148 -0
- package/esm/src/cli/matrix/fixtures.d.ts +13 -0
- package/esm/src/cli/matrix/fixtures.js +77 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +29 -0
- package/esm/src/cli/matrix/synthesizer.js +187 -0
- package/esm/src/guardrails/error.d.ts +45 -0
- package/esm/src/guardrails/error.js +149 -0
- package/esm/src/guardrails/injection.d.ts +12 -0
- package/esm/src/guardrails/injection.js +220 -0
- package/esm/src/guardrails/mod.d.ts +16 -0
- package/esm/src/guardrails/mod.js +15 -0
- package/esm/src/guardrails/quota.d.ts +10 -0
- package/esm/src/guardrails/quota.js +64 -0
- package/esm/src/guardrails/sanitize.d.ts +22 -0
- package/esm/src/guardrails/sanitize.js +133 -0
- package/esm/src/guardrails/sensitive.d.ts +12 -0
- package/esm/src/guardrails/sensitive.js +88 -0
- package/esm/src/host/mint-trace.d.ts +27 -0
- package/esm/src/host/mint-trace.js +33 -0
- package/esm/src/host/mod.d.ts +13 -0
- package/{src/host/mod.ts → esm/src/host/mod.js} +3 -11
- package/esm/src/host/reply.d.ts +14 -0
- package/esm/src/host/reply.js +27 -0
- package/esm/src/kernel/engine/boundary.d.ts +10 -0
- package/esm/src/kernel/engine/boundary.js +55 -0
- package/esm/src/kernel/engine/delta.d.ts +8 -0
- package/esm/src/kernel/engine/delta.js +389 -0
- package/esm/src/kernel/engine/hash.d.ts +1 -0
- package/esm/src/kernel/engine/hash.js +9 -0
- package/esm/src/kernel/engine/record.d.ts +2 -0
- package/esm/src/kernel/engine/record.js +7 -0
- package/esm/src/kernel/engine/repair.d.ts +7 -0
- package/esm/src/kernel/engine/repair.js +38 -0
- package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
- package/esm/src/kernel/engine/runner/gates.js +208 -0
- package/esm/src/kernel/engine/runner/mod.d.ts +14 -0
- package/esm/src/kernel/engine/runner/mod.js +103 -0
- package/esm/src/kernel/engine/runner/schema-validation.d.ts +22 -0
- package/esm/src/kernel/engine/runner/schema-validation.js +113 -0
- package/esm/src/kernel/engine/runner/state.d.ts +16 -0
- package/esm/src/kernel/engine/runner/state.js +8 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +15 -0
- package/esm/src/kernel/engine/runner/steps.js +127 -0
- package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
- package/esm/src/kernel/engine/runner/stream.js +70 -0
- package/esm/src/kernel/engine/runner/tokens.d.ts +3 -0
- package/esm/src/kernel/engine/runner/tokens.js +38 -0
- package/esm/src/kernel/engine/runner/tools.d.ts +13 -0
- package/esm/src/kernel/engine/runner/tools.js +192 -0
- package/{src/kernel/engine/runner.ts → esm/src/kernel/engine/runner.d.ts} +1 -2
- package/esm/src/kernel/engine/runner.js +6 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/mod.d.ts +18 -0
- package/esm/src/kernel/mod.js +16 -0
- package/esm/src/kernel/registry/attachments.d.ts +16 -0
- package/esm/src/kernel/registry/attachments.js +156 -0
- package/esm/src/kernel/registry/catalog.d.ts +31 -0
- package/esm/src/kernel/registry/catalog.js +135 -0
- package/esm/src/kernel/registry/ingress.d.ts +13 -0
- package/esm/src/kernel/registry/ingress.js +163 -0
- package/esm/src/kernel/registry/profiles.d.ts +34 -0
- package/esm/src/kernel/registry/profiles.js +120 -0
- package/esm/src/kernel/registry/provider-request.d.ts +4 -0
- package/esm/src/kernel/registry/provider-request.js +25 -0
- package/esm/src/kernel/registry/resolve.d.ts +19 -0
- package/esm/src/kernel/registry/resolve.js +217 -0
- package/esm/src/kernel/registry/schemas.d.ts +14 -0
- package/esm/src/kernel/registry/schemas.js +23 -0
- package/esm/src/kernel/registry/tools.d.ts +12 -0
- package/esm/src/kernel/registry/tools.js +36 -0
- package/{src/kernel/registry/vault.ts → esm/src/kernel/registry/vault.d.ts} +2 -17
- package/esm/src/kernel/registry/vault.js +18 -0
- package/esm/src/kernel/types.d.ts +528 -0
- package/esm/src/kernel/types.js +10 -0
- package/esm/src/observability/mod.d.ts +12 -0
- package/{src/observability/mod.ts → esm/src/observability/mod.js} +2 -11
- package/esm/src/observability/spans.d.ts +16 -0
- package/esm/src/observability/spans.js +56 -0
- package/esm/src/observability/trace-attach.d.ts +16 -0
- package/esm/src/observability/trace-attach.js +67 -0
- package/esm/src/observability/trace-record.d.ts +113 -0
- package/esm/src/observability/trace-record.js +143 -0
- package/esm/src/observability/trace-usage.d.ts +3 -0
- package/esm/src/observability/trace-usage.js +32 -0
- package/esm/src/observability/trace.d.ts +23 -0
- package/esm/src/observability/trace.js +121 -0
- package/esm/src/presets/google.d.ts +50 -0
- package/esm/src/presets/google.js +96 -0
- package/esm/src/presets/mod.d.ts +11 -0
- package/esm/src/presets/mod.js +10 -0
- package/esm/src/providers/create-provider.d.ts +29 -0
- package/esm/src/providers/create-provider.js +38 -0
- package/esm/src/providers/gemini-tape.d.ts +2 -0
- package/esm/src/providers/gemini-tape.js +46 -0
- package/esm/src/providers/google-tap.d.ts +3 -0
- package/esm/src/providers/google-tap.js +48 -0
- package/esm/src/providers/interactions.d.ts +5 -0
- package/esm/src/providers/interactions.js +153 -0
- package/esm/src/providers/keys.d.ts +19 -0
- package/esm/src/providers/keys.js +129 -0
- package/esm/src/providers/mod.d.ts +12 -0
- package/{src/providers/mod.ts → esm/src/providers/mod.js} +2 -4
- package/esm/src/providers/openrouter-mod.d.ts +13 -0
- package/{src/providers/openrouter-mod.ts → esm/src/providers/openrouter-mod.js} +3 -7
- package/esm/src/providers/openrouter-payload.d.ts +32 -0
- package/esm/src/providers/openrouter-payload.js +179 -0
- package/esm/src/providers/openrouter.d.ts +15 -0
- package/esm/src/providers/openrouter.js +589 -0
- package/esm/src/providers/pcm.d.ts +7 -0
- package/esm/src/providers/pcm.js +35 -0
- package/esm/src/providers/provider.d.ts +15 -0
- package/esm/src/providers/provider.js +160 -0
- package/esm/src/providers/speech.d.ts +23 -0
- package/esm/src/providers/speech.js +125 -0
- package/esm/src/providers/sse.d.ts +7 -0
- package/esm/src/providers/sse.js +53 -0
- package/package.json +45 -36
- package/deno.json +0 -57
- package/deno.lock +0 -469
- package/src/cli/commands/profile.ts +0 -57
- package/src/cli/commands/run.ts +0 -66
- package/src/cli/commands/test.ts +0 -195
- package/src/cli/index.ts +0 -161
- package/src/cli/matrix/fixtures.ts +0 -96
- package/src/cli/matrix/synthesizer.ts +0 -240
- package/src/guardrails/error.ts +0 -142
- package/src/guardrails/injection.ts +0 -254
- package/src/guardrails/mod.ts +0 -40
- package/src/guardrails/quota.ts +0 -87
- package/src/guardrails/sanitize.ts +0 -165
- package/src/guardrails/sensitive.ts +0 -96
- package/src/host/mint-trace.ts +0 -54
- package/src/host/reply.ts +0 -32
- package/src/kernel/engine/assert.ts +0 -29
- package/src/kernel/engine/boundary.ts +0 -75
- package/src/kernel/engine/delta.ts +0 -440
- package/src/kernel/engine/hash.ts +0 -11
- package/src/kernel/engine/record.ts +0 -8
- package/src/kernel/engine/repair.ts +0 -56
- package/src/kernel/engine/runner/gates.ts +0 -338
- package/src/kernel/engine/runner/mod.ts +0 -135
- package/src/kernel/engine/runner/state.ts +0 -31
- package/src/kernel/engine/runner/steps.ts +0 -183
- package/src/kernel/engine/runner/stream.ts +0 -83
- package/src/kernel/engine/runner/tokens.ts +0 -47
- package/src/kernel/engine/runner/tools.ts +0 -266
- package/src/kernel/engine/tree.ts +0 -18
- package/src/kernel/mod.ts +0 -39
- package/src/kernel/registry/attachments.ts +0 -195
- package/src/kernel/registry/catalog.ts +0 -181
- package/src/kernel/registry/ingress.ts +0 -223
- package/src/kernel/registry/profiles.ts +0 -165
- package/src/kernel/registry/provider-request.ts +0 -31
- package/src/kernel/registry/resolve.ts +0 -290
- package/src/kernel/registry/schemas.ts +0 -29
- package/src/kernel/registry/tools.ts +0 -45
- package/src/kernel/types.ts +0 -576
- package/src/observability/spans.ts +0 -70
- package/src/observability/trace-attach.ts +0 -96
- package/src/observability/trace-record.ts +0 -254
- package/src/observability/trace-usage.ts +0 -36
- package/src/observability/trace.ts +0 -143
- package/src/presets/google.ts +0 -147
- package/src/presets/mod.ts +0 -24
- package/src/providers/create-provider.ts +0 -64
- package/src/providers/gemini-tape.ts +0 -59
- package/src/providers/google-tap.ts +0 -57
- package/src/providers/interactions.ts +0 -182
- package/src/providers/keys.ts +0 -182
- package/src/providers/openrouter-payload.ts +0 -232
- package/src/providers/openrouter.ts +0 -758
- package/src/providers/pcm.ts +0 -37
- package/src/providers/provider.ts +0 -206
- package/src/providers/speech.ts +0 -180
- package/src/providers/sse.ts +0 -59
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const HEX_PAD = 2;
|
|
2
|
+
const HEX_RADIX = 16;
|
|
3
|
+
function hexSha256(bytes) {
|
|
4
|
+
return [...bytes].map((b) => b.toString(HEX_RADIX).padStart(HEX_PAD, '0')).join('');
|
|
5
|
+
}
|
|
6
|
+
export async function sha256(text) {
|
|
7
|
+
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
|
|
8
|
+
return hexSha256(new Uint8Array(buf));
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const MAX_REPAIR_HISTORY_EXCHANGES = 2;
|
|
2
|
+
const MAX_REPAIR_HISTORY_MESSAGES = MAX_REPAIR_HISTORY_EXCHANGES * 2;
|
|
3
|
+
function scopeHistory(history) {
|
|
4
|
+
if (!history || history.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
return history.slice(-MAX_REPAIR_HISTORY_MESSAGES);
|
|
8
|
+
}
|
|
9
|
+
function formatHistoryBlock(messages) {
|
|
10
|
+
if (messages.length === 0) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
const lines = messages.map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`);
|
|
14
|
+
return `### RECENT CONVERSATION CONTEXT (LAST ${messages.length} TURNS)\n${lines.join('\n')}\n\n`;
|
|
15
|
+
}
|
|
16
|
+
function synthesizeRepairPrompt(args) {
|
|
17
|
+
const { profile, repair, history } = args;
|
|
18
|
+
const guidance = repair.guidance ||
|
|
19
|
+
profile.outputs.validation?.repairGuidance ||
|
|
20
|
+
'Revise the previous output so it satisfies the validator rejection. Preserve the intended user-facing substance unless the guidance says otherwise.';
|
|
21
|
+
const historyBlock = formatHistoryBlock(scopeHistory(history));
|
|
22
|
+
let prompt = `## OUTPUT REPAIR REQUEST\n\n`;
|
|
23
|
+
prompt += `The previous assistant output was rejected by a host validator and must be revised.\n\n`;
|
|
24
|
+
prompt += `### PREVIOUS OUTPUT\n\`\`\`\n${repair.previousOutput.trim()}\n\`\`\`\n\n`;
|
|
25
|
+
prompt += `### VALIDATOR REJECTION\n${repair.rejection.trim()}\n\n`;
|
|
26
|
+
if (guidance.trim()) {
|
|
27
|
+
prompt += `### REPAIR GUIDANCE\n${guidance.trim()}\n\n`;
|
|
28
|
+
}
|
|
29
|
+
if (historyBlock) {
|
|
30
|
+
prompt += historyBlock;
|
|
31
|
+
}
|
|
32
|
+
prompt += `### INSTRUCTIONS\n`;
|
|
33
|
+
prompt += `1. Inspect the previous output and validator rejection.\n`;
|
|
34
|
+
prompt += `2. Apply the repair guidance without inventing unsupported facts.\n`;
|
|
35
|
+
prompt += `3. Return only the corrected assistant output required by the active profile.`;
|
|
36
|
+
return prompt;
|
|
37
|
+
}
|
|
38
|
+
export { synthesizeRepairPrompt };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent, TurnRequest } from '../../types.js';
|
|
2
|
+
import type { StepExecutionState } from './state.js';
|
|
3
|
+
declare function runAttemptsWithValidation(safe: TurnRequest, profile: Profile, generation: ResolvedGeneration, system: string, provider: ModelProvider, gemini: Record<string, unknown>[], state: StepExecutionState): AsyncGenerator<TurnEvent>;
|
|
4
|
+
export { runAttemptsWithValidation };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { TheorumError, toErrorEvent } from '../../../guardrails/error.js';
|
|
2
|
+
import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
|
|
3
|
+
import { resolveTurn } from '../../registry/resolve.js';
|
|
4
|
+
import { getStructured } from '../../registry/schemas.js';
|
|
5
|
+
import { collectValidationFailures, formatValidationFailures } from './schema-validation.js';
|
|
6
|
+
import { executeAttempt } from './steps.js';
|
|
7
|
+
function collectAttemptText(events) {
|
|
8
|
+
return events
|
|
9
|
+
.filter((e) => e.type === 'text' && e.text)
|
|
10
|
+
.map((e) => e.text)
|
|
11
|
+
.join('');
|
|
12
|
+
}
|
|
13
|
+
function buildRepairRequest(safe, previousOutput, rejection, repairGuidance) {
|
|
14
|
+
return {
|
|
15
|
+
...safe,
|
|
16
|
+
input: {
|
|
17
|
+
...safe.input,
|
|
18
|
+
repair: {
|
|
19
|
+
previousOutput: typeof previousOutput === 'string' ? previousOutput : JSON.stringify(previousOutput),
|
|
20
|
+
rejection,
|
|
21
|
+
guidance: repairGuidance,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function evaluateEgressOutcome(args) {
|
|
27
|
+
const { egress, attemptEvents, generation, request, profile, canRetry } = args;
|
|
28
|
+
const attemptText = collectAttemptText(attemptEvents);
|
|
29
|
+
const result = await egress.enforce({
|
|
30
|
+
text: attemptText,
|
|
31
|
+
canary: generation.canary,
|
|
32
|
+
slots: request.input?.slots,
|
|
33
|
+
profile,
|
|
34
|
+
role: request.input?.role,
|
|
35
|
+
});
|
|
36
|
+
if (!result.blocked) {
|
|
37
|
+
return { action: 'pass' };
|
|
38
|
+
}
|
|
39
|
+
if (egress.onBlock === 'refuse_to_user') {
|
|
40
|
+
return { action: 'refusal', event: { type: 'text', text: result.text } };
|
|
41
|
+
}
|
|
42
|
+
if (canRetry) {
|
|
43
|
+
const rejectionMsg = result.rejectionMessage || 'Egress disclosure violation detected.';
|
|
44
|
+
const repairGuidance = egress.repairGuidance ||
|
|
45
|
+
'Rewrite the message as corrected user-visible prose only. Keep the same helpful substance; scrub all internal tool names, leak phrases, and disclosure markers.';
|
|
46
|
+
const nextRequest = buildRepairRequest(request, attemptText, rejectionMsg, repairGuidance);
|
|
47
|
+
return { action: 'retry', nextRequest };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
action: 'withhold',
|
|
51
|
+
event: toErrorEvent('Turn withheld: egress disclosure violation'),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function evaluateValidationOutcome(args) {
|
|
55
|
+
const { validation, generation, latestStructured, request, canRetry } = args;
|
|
56
|
+
if (latestStructured === undefined) {
|
|
57
|
+
return { action: 'pass' };
|
|
58
|
+
}
|
|
59
|
+
const structuredId = generation.structured;
|
|
60
|
+
if (!structuredId) {
|
|
61
|
+
throw new TheorumError('outputs.validation requires outputs.structured with a JSON Schema');
|
|
62
|
+
}
|
|
63
|
+
const spec = getStructured(structuredId);
|
|
64
|
+
if (!spec.jsonSchema) {
|
|
65
|
+
throw new TheorumError(`structured schema '${structuredId}' has no jsonSchema for validation`);
|
|
66
|
+
}
|
|
67
|
+
const failures = await collectValidationFailures(spec.jsonSchema, latestStructured, validation.fields, request.input?.slots);
|
|
68
|
+
if (failures.length === 0) {
|
|
69
|
+
return { action: 'pass' };
|
|
70
|
+
}
|
|
71
|
+
const error = formatValidationFailures(failures);
|
|
72
|
+
if (canRetry) {
|
|
73
|
+
const nextRequest = buildRepairRequest(request, latestStructured, error, validation.repairGuidance);
|
|
74
|
+
return { action: 'retry', nextRequest };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
action: 'accept',
|
|
78
|
+
event: { type: 'structured', structured: latestStructured },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function* yieldBufferedAttemptEvents(events, alreadyStreamedUserVisible) {
|
|
82
|
+
for (const ev of events) {
|
|
83
|
+
if (ev.type === 'tokens') {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// When validation-only, thought/text already streamed live.
|
|
87
|
+
if (alreadyStreamedUserVisible && (ev.type === 'thought' || ev.type === 'text')) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
yield ev;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function updateFlowForRetry(flow, nextReq) {
|
|
94
|
+
flow.currentAttempt++;
|
|
95
|
+
flow.currentReq = nextReq;
|
|
96
|
+
flow.currentGen = resolveTurn(sanitizeTurnRequest(nextReq)).generation;
|
|
97
|
+
}
|
|
98
|
+
async function* handleEgressGate(egress, flow, state, profile, maxRetries) {
|
|
99
|
+
const canRetry = flow.currentAttempt < maxRetries;
|
|
100
|
+
const outcome = await evaluateEgressOutcome({
|
|
101
|
+
egress,
|
|
102
|
+
attemptEvents: state.attemptEvents,
|
|
103
|
+
generation: flow.currentGen,
|
|
104
|
+
request: flow.currentReq,
|
|
105
|
+
profile,
|
|
106
|
+
canRetry,
|
|
107
|
+
});
|
|
108
|
+
if (outcome.action === 'refusal') {
|
|
109
|
+
state.allEmittedEvents.push(outcome.event);
|
|
110
|
+
yield outcome.event;
|
|
111
|
+
return 'terminal';
|
|
112
|
+
}
|
|
113
|
+
if (outcome.action === 'withhold') {
|
|
114
|
+
yield outcome.event;
|
|
115
|
+
return 'terminal';
|
|
116
|
+
}
|
|
117
|
+
if (outcome.action === 'retry') {
|
|
118
|
+
updateFlowForRetry(flow, outcome.nextRequest);
|
|
119
|
+
return 'continue';
|
|
120
|
+
}
|
|
121
|
+
return 'pass';
|
|
122
|
+
}
|
|
123
|
+
async function* handleValidationGate(validation, flow, state, latestStructured, maxRetries) {
|
|
124
|
+
const canRetry = flow.currentAttempt < maxRetries;
|
|
125
|
+
const outcome = await evaluateValidationOutcome({
|
|
126
|
+
validation,
|
|
127
|
+
generation: flow.currentGen,
|
|
128
|
+
latestStructured,
|
|
129
|
+
request: flow.currentReq,
|
|
130
|
+
canRetry,
|
|
131
|
+
});
|
|
132
|
+
if (outcome.action === 'retry') {
|
|
133
|
+
updateFlowForRetry(flow, outcome.nextRequest);
|
|
134
|
+
return 'continue';
|
|
135
|
+
}
|
|
136
|
+
if (outcome.action === 'accept') {
|
|
137
|
+
state.allEmittedEvents.push(outcome.event);
|
|
138
|
+
yield outcome.event;
|
|
139
|
+
return 'terminal';
|
|
140
|
+
}
|
|
141
|
+
return 'pass';
|
|
142
|
+
}
|
|
143
|
+
function gateStatusToAction(status, terminalStatus = 'terminal') {
|
|
144
|
+
if (status === 'terminal') {
|
|
145
|
+
return { status: terminalStatus };
|
|
146
|
+
}
|
|
147
|
+
if (status === 'continue') {
|
|
148
|
+
return { status: 'continue' };
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
async function* executeSingleAttemptCycle(args) {
|
|
153
|
+
const { flow, state, profile, system, provider, gemini, maxRetries } = args;
|
|
154
|
+
const validation = profile.outputs.validation;
|
|
155
|
+
const egress = profile.guardrails.egress;
|
|
156
|
+
state.attemptEvents = [];
|
|
157
|
+
const { latestStructured } = yield* executeAttempt({
|
|
158
|
+
safe: flow.currentReq,
|
|
159
|
+
profile,
|
|
160
|
+
generation: flow.currentGen,
|
|
161
|
+
system,
|
|
162
|
+
provider,
|
|
163
|
+
gemini,
|
|
164
|
+
state,
|
|
165
|
+
});
|
|
166
|
+
if (egress?.enforce) {
|
|
167
|
+
const status = yield* handleEgressGate(egress, flow, state, profile, maxRetries);
|
|
168
|
+
const action = gateStatusToAction(status, 'terminal');
|
|
169
|
+
if (action) {
|
|
170
|
+
return action;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (validation) {
|
|
174
|
+
const status = yield* handleValidationGate(validation, flow, state, latestStructured, maxRetries);
|
|
175
|
+
const action = gateStatusToAction(status, 'success');
|
|
176
|
+
if (action) {
|
|
177
|
+
return action;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (validation || egress?.enforce) {
|
|
181
|
+
const alreadyStreamedUserVisible = !egress?.enforce;
|
|
182
|
+
yield* yieldBufferedAttemptEvents(state.attemptEvents, alreadyStreamedUserVisible);
|
|
183
|
+
}
|
|
184
|
+
return { status: 'success' };
|
|
185
|
+
}
|
|
186
|
+
async function* runAttemptsWithValidation(safe, profile, generation, system, provider, gemini, state) {
|
|
187
|
+
const maxRetries = Math.max(profile.outputs.validation?.maxRetries ?? 0, profile.guardrails.egress?.maxRetries ?? 2);
|
|
188
|
+
const flow = {
|
|
189
|
+
currentAttempt: 0,
|
|
190
|
+
currentGen: generation,
|
|
191
|
+
currentReq: safe,
|
|
192
|
+
};
|
|
193
|
+
while (flow.currentAttempt <= maxRetries) {
|
|
194
|
+
const step = yield* executeSingleAttemptCycle({
|
|
195
|
+
flow,
|
|
196
|
+
state,
|
|
197
|
+
profile,
|
|
198
|
+
system,
|
|
199
|
+
provider,
|
|
200
|
+
gemini,
|
|
201
|
+
maxRetries,
|
|
202
|
+
});
|
|
203
|
+
if (step.status === 'terminal' || step.status === 'success') {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export { runAttemptsWithValidation };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic turn runner for THEORUM.
|
|
3
|
+
*
|
|
4
|
+
* `runTurn` resolves a profile, sanitizes input, binds canary boundaries,
|
|
5
|
+
* streams provider events, executes allowed tools, applies validation and
|
|
6
|
+
* egress repair loops, writes traces, and emits one terminal `done` event.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { type TraceSink } from '../../../observability/trace.js';
|
|
11
|
+
import type { ModelProvider, TurnEvent, TurnRequest } from '../../types.js';
|
|
12
|
+
/** Execute one host turn against a provider adapter. */
|
|
13
|
+
declare function runTurn(req: TurnRequest, provider: ModelProvider, sink?: TraceSink): AsyncGenerator<TurnEvent>;
|
|
14
|
+
export { runTurn };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic turn runner for THEORUM.
|
|
3
|
+
*
|
|
4
|
+
* `runTurn` resolves a profile, sanitizes input, binds canary boundaries,
|
|
5
|
+
* streams provider events, executes allowed tools, applies validation and
|
|
6
|
+
* egress repair loops, writes traces, and emits one terminal `done` event.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
|
|
11
|
+
import { noopSink, writeTrace } from '../../../observability/trace.js';
|
|
12
|
+
import { buildRecord } from '../../../observability/trace-record.js';
|
|
13
|
+
import { pickSystemRole, resolveTurn } from '../../registry/resolve.js';
|
|
14
|
+
import { bindCanary } from '../boundary.js';
|
|
15
|
+
import { runAttemptsWithValidation } from './gates.js';
|
|
16
|
+
import { shouldSkipStreamEvent, systemFromProfile } from './stream.js';
|
|
17
|
+
import { calculateFallbackTokens } from './tokens.js';
|
|
18
|
+
import { invokeFromUi } from './tools.js';
|
|
19
|
+
async function* emitTurn(args) {
|
|
20
|
+
const { safe, profile, generation, system, provider, gemini } = args;
|
|
21
|
+
if (safe.toolInvoke) {
|
|
22
|
+
yield* invokeFromUi(profile, safe);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const state = {
|
|
26
|
+
currentHistory: [...(generation.history ?? [])],
|
|
27
|
+
stepCount: 0,
|
|
28
|
+
sawTokensEvent: false,
|
|
29
|
+
allEmittedEvents: [],
|
|
30
|
+
attemptEvents: [],
|
|
31
|
+
};
|
|
32
|
+
yield* runAttemptsWithValidation(safe, profile, generation, system, provider, gemini, state);
|
|
33
|
+
if (!state.sawTokensEvent) {
|
|
34
|
+
yield* calculateFallbackTokens(safe, system, state.allEmittedEvents);
|
|
35
|
+
}
|
|
36
|
+
yield { type: 'done' };
|
|
37
|
+
}
|
|
38
|
+
/** Execute one host turn against a provider adapter. */
|
|
39
|
+
async function* runTurn(req, provider, sink = noopSink()) {
|
|
40
|
+
const started = Date.now();
|
|
41
|
+
const seen = [];
|
|
42
|
+
const gemini = [];
|
|
43
|
+
let model;
|
|
44
|
+
let bucket;
|
|
45
|
+
let canary = '';
|
|
46
|
+
let system;
|
|
47
|
+
let generation;
|
|
48
|
+
try {
|
|
49
|
+
const safe = sanitizeTurnRequest(req);
|
|
50
|
+
const { profile, generation: gen } = resolveTurn(safe);
|
|
51
|
+
generation = gen;
|
|
52
|
+
const { model: resolvedModel, geminiBucket, canary: turnCanary } = gen;
|
|
53
|
+
model = resolvedModel;
|
|
54
|
+
bucket = geminiBucket;
|
|
55
|
+
canary = turnCanary;
|
|
56
|
+
const role = pickSystemRole(profile, safe.input?.role);
|
|
57
|
+
const profileSys = systemFromProfile(profile, role);
|
|
58
|
+
const combinedSys = [profileSys, safe.system].filter(Boolean).join('\n\n');
|
|
59
|
+
const bound = bindCanary(combinedSys, turnCanary);
|
|
60
|
+
system = bound;
|
|
61
|
+
for await (const event of emitTurn({
|
|
62
|
+
safe,
|
|
63
|
+
profile,
|
|
64
|
+
generation: gen,
|
|
65
|
+
system: bound,
|
|
66
|
+
provider,
|
|
67
|
+
gemini,
|
|
68
|
+
})) {
|
|
69
|
+
seen.push(event);
|
|
70
|
+
if (shouldSkipStreamEvent(event, profile)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
yield event;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
await writeTrace(sink, buildRecord({
|
|
78
|
+
req,
|
|
79
|
+
events: seen,
|
|
80
|
+
started,
|
|
81
|
+
model,
|
|
82
|
+
bucket,
|
|
83
|
+
thrown: err,
|
|
84
|
+
gemini,
|
|
85
|
+
canary,
|
|
86
|
+
system,
|
|
87
|
+
generation,
|
|
88
|
+
}));
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
await writeTrace(sink, buildRecord({
|
|
92
|
+
req,
|
|
93
|
+
events: seen,
|
|
94
|
+
started,
|
|
95
|
+
model,
|
|
96
|
+
bucket,
|
|
97
|
+
gemini,
|
|
98
|
+
canary,
|
|
99
|
+
system,
|
|
100
|
+
generation,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
export { runTurn };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-driven structured-output validation.
|
|
3
|
+
*
|
|
4
|
+
* Required vs optional comes only from the JSON Schema. Host field validators
|
|
5
|
+
* run for required paths and for optional paths that are present.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ProfileValidator } from '../../types.js';
|
|
10
|
+
interface ValidationFailure {
|
|
11
|
+
path: string;
|
|
12
|
+
error: string;
|
|
13
|
+
}
|
|
14
|
+
declare function isAbsent(value: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Collect schema presence failures and host field-validator failures.
|
|
17
|
+
* Throws when the root schema is not an object schema.
|
|
18
|
+
*/
|
|
19
|
+
declare function collectValidationFailures(jsonSchema: Record<string, unknown>, structured: unknown, fields: Record<string, ProfileValidator> | undefined, slots?: Record<string, string>): Promise<ValidationFailure[]>;
|
|
20
|
+
declare function formatValidationFailures(failures: ValidationFailure[]): string;
|
|
21
|
+
export type { ValidationFailure };
|
|
22
|
+
export { collectValidationFailures, formatValidationFailures, isAbsent };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-driven structured-output validation.
|
|
3
|
+
*
|
|
4
|
+
* Required vs optional comes only from the JSON Schema. Host field validators
|
|
5
|
+
* run for required paths and for optional paths that are present.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
10
|
+
function isAbsent(value) {
|
|
11
|
+
return value === undefined || value === null;
|
|
12
|
+
}
|
|
13
|
+
function asRecord(value) {
|
|
14
|
+
if (isAbsent(value) || typeof value !== 'object' || Array.isArray(value)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function asObjectSchema(schema) {
|
|
20
|
+
const rec = asRecord(schema);
|
|
21
|
+
if (!rec) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (rec.type !== undefined && rec.type !== 'object') {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return rec;
|
|
28
|
+
}
|
|
29
|
+
function requiredKeys(schema) {
|
|
30
|
+
const raw = schema.required;
|
|
31
|
+
if (!Array.isArray(raw)) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return raw.filter((k) => typeof k === 'string');
|
|
35
|
+
}
|
|
36
|
+
function propertySchemas(schema) {
|
|
37
|
+
const props = schema.properties;
|
|
38
|
+
return asRecord(props) ?? {};
|
|
39
|
+
}
|
|
40
|
+
function joinPath(prefix, key) {
|
|
41
|
+
return prefix ? `${prefix}.${key}` : key;
|
|
42
|
+
}
|
|
43
|
+
function pushMissing(path, failures) {
|
|
44
|
+
failures.push({ path, error: `required field '${path}' is missing` });
|
|
45
|
+
}
|
|
46
|
+
async function runFieldValidator(path, value, validator, slots, failures) {
|
|
47
|
+
if (!validator) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const check = await validator(value, slots);
|
|
51
|
+
if (check.isValid) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
failures.push({
|
|
55
|
+
path,
|
|
56
|
+
error: check.error || check.finding || `Validation failed for '${path}'`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function visitProperty(args) {
|
|
60
|
+
const { path, propSchema, child, isRequired, fields, slots, failures } = args;
|
|
61
|
+
if (isAbsent(child)) {
|
|
62
|
+
if (isRequired) {
|
|
63
|
+
pushMissing(path, failures);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await runFieldValidator(path, child, fields?.[path], slots, failures);
|
|
68
|
+
const nested = asObjectSchema(propSchema);
|
|
69
|
+
const nestedValue = nested ? asRecord(child) : null;
|
|
70
|
+
if (nested && nestedValue) {
|
|
71
|
+
await walkObject(nested, nestedValue, path, fields, slots, failures);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function walkObject(schema, value, pathPrefix, fields, slots, failures) {
|
|
75
|
+
const props = propertySchemas(schema);
|
|
76
|
+
const required = new Set(requiredKeys(schema));
|
|
77
|
+
const keys = new Set([...Object.keys(props), ...required]);
|
|
78
|
+
const record = asRecord(value);
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
await visitProperty({
|
|
81
|
+
path: joinPath(pathPrefix, key),
|
|
82
|
+
propSchema: props[key],
|
|
83
|
+
child: record?.[key],
|
|
84
|
+
isRequired: required.has(key),
|
|
85
|
+
fields,
|
|
86
|
+
slots,
|
|
87
|
+
failures,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Collect schema presence failures and host field-validator failures.
|
|
93
|
+
* Throws when the root schema is not an object schema.
|
|
94
|
+
*/
|
|
95
|
+
async function collectValidationFailures(jsonSchema, structured, fields, slots) {
|
|
96
|
+
const root = asObjectSchema(jsonSchema);
|
|
97
|
+
if (!root) {
|
|
98
|
+
throw new TheorumError('structured validation requires a JSON Schema object root');
|
|
99
|
+
}
|
|
100
|
+
const failures = [];
|
|
101
|
+
if (!asRecord(structured)) {
|
|
102
|
+
for (const key of requiredKeys(root)) {
|
|
103
|
+
pushMissing(key, failures);
|
|
104
|
+
}
|
|
105
|
+
return failures;
|
|
106
|
+
}
|
|
107
|
+
await walkObject(root, structured, '', fields, slots, failures);
|
|
108
|
+
return failures;
|
|
109
|
+
}
|
|
110
|
+
function formatValidationFailures(failures) {
|
|
111
|
+
return failures.map((f) => f.error).join('; ');
|
|
112
|
+
}
|
|
113
|
+
export { collectValidationFailures, formatValidationFailures, isAbsent };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ResolvedGeneration, TurnEvent, TurnHistoryMessage, TurnRequest } from '../../types.js';
|
|
2
|
+
interface StepExecutionState {
|
|
3
|
+
currentHistory: TurnHistoryMessage[];
|
|
4
|
+
stepCount: number;
|
|
5
|
+
sawTokensEvent: boolean;
|
|
6
|
+
allEmittedEvents: TurnEvent[];
|
|
7
|
+
attemptEvents: TurnEvent[];
|
|
8
|
+
}
|
|
9
|
+
interface AttemptFlowState {
|
|
10
|
+
currentAttempt: number;
|
|
11
|
+
currentReq: TurnRequest;
|
|
12
|
+
currentGen: ResolvedGeneration;
|
|
13
|
+
}
|
|
14
|
+
declare function recordStepEvent(event: TurnEvent, state: StepExecutionState): void;
|
|
15
|
+
export type { AttemptFlowState, StepExecutionState };
|
|
16
|
+
export { recordStepEvent };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent, TurnRequest } from '../../types.js';
|
|
2
|
+
import { type StepExecutionState } from './state.js';
|
|
3
|
+
declare function executeAttempt(args: {
|
|
4
|
+
safe: TurnRequest;
|
|
5
|
+
profile: Profile;
|
|
6
|
+
generation: ResolvedGeneration;
|
|
7
|
+
system: string;
|
|
8
|
+
provider: ModelProvider;
|
|
9
|
+
gemini: Record<string, unknown>[];
|
|
10
|
+
state: StepExecutionState;
|
|
11
|
+
}): AsyncGenerator<TurnEvent, {
|
|
12
|
+
pendingTools: TurnEvent[];
|
|
13
|
+
latestStructured?: unknown;
|
|
14
|
+
}>;
|
|
15
|
+
export { executeAttempt };
|