theorum 0.1.4 → 0.1.5

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.
Files changed (188) hide show
  1. package/esm/_dnt.polyfills.d.ts +110 -0
  2. package/esm/_dnt.polyfills.js +144 -0
  3. package/esm/_dnt.shims.d.ts +5 -0
  4. package/esm/_dnt.shims.js +61 -0
  5. package/{mod.ts → esm/mod.d.ts} +17 -57
  6. package/esm/mod.js +50 -0
  7. package/esm/package.json +3 -0
  8. package/esm/src/cli/commands/profile.d.ts +2 -0
  9. package/esm/src/cli/commands/profile.js +53 -0
  10. package/esm/src/cli/commands/run.d.ts +10 -0
  11. package/esm/src/cli/commands/run.js +55 -0
  12. package/esm/src/cli/commands/test.d.ts +15 -0
  13. package/esm/src/cli/commands/test.js +140 -0
  14. package/esm/src/cli/index.d.ts +4 -0
  15. package/esm/src/cli/index.js +148 -0
  16. package/esm/src/cli/matrix/fixtures.d.ts +13 -0
  17. package/esm/src/cli/matrix/fixtures.js +77 -0
  18. package/esm/src/cli/matrix/synthesizer.d.ts +29 -0
  19. package/esm/src/cli/matrix/synthesizer.js +187 -0
  20. package/esm/src/guardrails/error.d.ts +45 -0
  21. package/esm/src/guardrails/error.js +149 -0
  22. package/esm/src/guardrails/injection.d.ts +12 -0
  23. package/esm/src/guardrails/injection.js +220 -0
  24. package/esm/src/guardrails/mod.d.ts +16 -0
  25. package/esm/src/guardrails/mod.js +15 -0
  26. package/esm/src/guardrails/quota.d.ts +10 -0
  27. package/esm/src/guardrails/quota.js +64 -0
  28. package/esm/src/guardrails/sanitize.d.ts +22 -0
  29. package/esm/src/guardrails/sanitize.js +133 -0
  30. package/esm/src/guardrails/sensitive.d.ts +12 -0
  31. package/esm/src/guardrails/sensitive.js +88 -0
  32. package/esm/src/host/mint-trace.d.ts +27 -0
  33. package/esm/src/host/mint-trace.js +33 -0
  34. package/esm/src/host/mod.d.ts +13 -0
  35. package/{src/host/mod.ts → esm/src/host/mod.js} +3 -11
  36. package/esm/src/host/reply.d.ts +14 -0
  37. package/esm/src/host/reply.js +27 -0
  38. package/esm/src/kernel/engine/boundary.d.ts +10 -0
  39. package/esm/src/kernel/engine/boundary.js +55 -0
  40. package/esm/src/kernel/engine/delta.d.ts +8 -0
  41. package/esm/src/kernel/engine/delta.js +389 -0
  42. package/esm/src/kernel/engine/hash.d.ts +1 -0
  43. package/esm/src/kernel/engine/hash.js +9 -0
  44. package/esm/src/kernel/engine/record.d.ts +2 -0
  45. package/esm/src/kernel/engine/record.js +7 -0
  46. package/esm/src/kernel/engine/repair.d.ts +7 -0
  47. package/esm/src/kernel/engine/repair.js +38 -0
  48. package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
  49. package/esm/src/kernel/engine/runner/gates.js +203 -0
  50. package/esm/src/kernel/engine/runner/mod.d.ts +14 -0
  51. package/esm/src/kernel/engine/runner/mod.js +109 -0
  52. package/esm/src/kernel/engine/runner/state.d.ts +16 -0
  53. package/esm/src/kernel/engine/runner/state.js +8 -0
  54. package/esm/src/kernel/engine/runner/steps.d.ts +15 -0
  55. package/esm/src/kernel/engine/runner/steps.js +119 -0
  56. package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
  57. package/esm/src/kernel/engine/runner/stream.js +70 -0
  58. package/esm/src/kernel/engine/runner/tokens.d.ts +3 -0
  59. package/esm/src/kernel/engine/runner/tokens.js +38 -0
  60. package/esm/src/kernel/engine/runner/tools.d.ts +13 -0
  61. package/esm/src/kernel/engine/runner/tools.js +192 -0
  62. package/{src/kernel/engine/runner.ts → esm/src/kernel/engine/runner.d.ts} +1 -2
  63. package/esm/src/kernel/engine/runner.js +6 -0
  64. package/esm/src/kernel/engine/tree.d.ts +2 -0
  65. package/esm/src/kernel/engine/tree.js +17 -0
  66. package/esm/src/kernel/mod.d.ts +18 -0
  67. package/esm/src/kernel/mod.js +16 -0
  68. package/esm/src/kernel/registry/attachments.d.ts +16 -0
  69. package/esm/src/kernel/registry/attachments.js +156 -0
  70. package/esm/src/kernel/registry/catalog.d.ts +31 -0
  71. package/esm/src/kernel/registry/catalog.js +135 -0
  72. package/esm/src/kernel/registry/ingress.d.ts +13 -0
  73. package/esm/src/kernel/registry/ingress.js +163 -0
  74. package/esm/src/kernel/registry/profiles.d.ts +34 -0
  75. package/esm/src/kernel/registry/profiles.js +120 -0
  76. package/esm/src/kernel/registry/provider-request.d.ts +4 -0
  77. package/esm/src/kernel/registry/provider-request.js +25 -0
  78. package/esm/src/kernel/registry/resolve.d.ts +19 -0
  79. package/esm/src/kernel/registry/resolve.js +217 -0
  80. package/esm/src/kernel/registry/schemas.d.ts +14 -0
  81. package/esm/src/kernel/registry/schemas.js +23 -0
  82. package/esm/src/kernel/registry/tools.d.ts +12 -0
  83. package/esm/src/kernel/registry/tools.js +36 -0
  84. package/{src/kernel/registry/vault.ts → esm/src/kernel/registry/vault.d.ts} +2 -17
  85. package/esm/src/kernel/registry/vault.js +18 -0
  86. package/esm/src/kernel/types.d.ts +524 -0
  87. package/esm/src/kernel/types.js +10 -0
  88. package/esm/src/observability/mod.d.ts +12 -0
  89. package/{src/observability/mod.ts → esm/src/observability/mod.js} +2 -11
  90. package/esm/src/observability/spans.d.ts +16 -0
  91. package/esm/src/observability/spans.js +56 -0
  92. package/esm/src/observability/trace-attach.d.ts +16 -0
  93. package/esm/src/observability/trace-attach.js +67 -0
  94. package/esm/src/observability/trace-record.d.ts +113 -0
  95. package/esm/src/observability/trace-record.js +143 -0
  96. package/esm/src/observability/trace-usage.d.ts +3 -0
  97. package/esm/src/observability/trace-usage.js +32 -0
  98. package/esm/src/observability/trace.d.ts +23 -0
  99. package/esm/src/observability/trace.js +121 -0
  100. package/esm/src/presets/google.d.ts +50 -0
  101. package/esm/src/presets/google.js +96 -0
  102. package/esm/src/presets/mod.d.ts +11 -0
  103. package/esm/src/presets/mod.js +10 -0
  104. package/esm/src/providers/create-provider.d.ts +29 -0
  105. package/esm/src/providers/create-provider.js +38 -0
  106. package/esm/src/providers/gemini-tape.d.ts +2 -0
  107. package/esm/src/providers/gemini-tape.js +46 -0
  108. package/esm/src/providers/google-tap.d.ts +3 -0
  109. package/esm/src/providers/google-tap.js +48 -0
  110. package/esm/src/providers/interactions.d.ts +5 -0
  111. package/esm/src/providers/interactions.js +153 -0
  112. package/esm/src/providers/keys.d.ts +19 -0
  113. package/esm/src/providers/keys.js +129 -0
  114. package/esm/src/providers/mod.d.ts +12 -0
  115. package/{src/providers/mod.ts → esm/src/providers/mod.js} +2 -4
  116. package/esm/src/providers/openrouter-mod.d.ts +13 -0
  117. package/{src/providers/openrouter-mod.ts → esm/src/providers/openrouter-mod.js} +3 -7
  118. package/esm/src/providers/openrouter-payload.d.ts +32 -0
  119. package/esm/src/providers/openrouter-payload.js +179 -0
  120. package/esm/src/providers/openrouter.d.ts +15 -0
  121. package/esm/src/providers/openrouter.js +589 -0
  122. package/esm/src/providers/pcm.d.ts +7 -0
  123. package/esm/src/providers/pcm.js +35 -0
  124. package/esm/src/providers/provider.d.ts +15 -0
  125. package/esm/src/providers/provider.js +160 -0
  126. package/esm/src/providers/speech.d.ts +23 -0
  127. package/esm/src/providers/speech.js +125 -0
  128. package/esm/src/providers/sse.d.ts +7 -0
  129. package/esm/src/providers/sse.js +53 -0
  130. package/package.json +45 -36
  131. package/deno.json +0 -57
  132. package/deno.lock +0 -469
  133. package/src/cli/commands/profile.ts +0 -57
  134. package/src/cli/commands/run.ts +0 -66
  135. package/src/cli/commands/test.ts +0 -195
  136. package/src/cli/index.ts +0 -161
  137. package/src/cli/matrix/fixtures.ts +0 -96
  138. package/src/cli/matrix/synthesizer.ts +0 -240
  139. package/src/guardrails/error.ts +0 -142
  140. package/src/guardrails/injection.ts +0 -254
  141. package/src/guardrails/mod.ts +0 -40
  142. package/src/guardrails/quota.ts +0 -87
  143. package/src/guardrails/sanitize.ts +0 -165
  144. package/src/guardrails/sensitive.ts +0 -96
  145. package/src/host/mint-trace.ts +0 -54
  146. package/src/host/reply.ts +0 -32
  147. package/src/kernel/engine/assert.ts +0 -29
  148. package/src/kernel/engine/boundary.ts +0 -75
  149. package/src/kernel/engine/delta.ts +0 -440
  150. package/src/kernel/engine/hash.ts +0 -11
  151. package/src/kernel/engine/record.ts +0 -8
  152. package/src/kernel/engine/repair.ts +0 -56
  153. package/src/kernel/engine/runner/gates.ts +0 -338
  154. package/src/kernel/engine/runner/mod.ts +0 -135
  155. package/src/kernel/engine/runner/state.ts +0 -31
  156. package/src/kernel/engine/runner/steps.ts +0 -183
  157. package/src/kernel/engine/runner/stream.ts +0 -83
  158. package/src/kernel/engine/runner/tokens.ts +0 -47
  159. package/src/kernel/engine/runner/tools.ts +0 -266
  160. package/src/kernel/engine/tree.ts +0 -18
  161. package/src/kernel/mod.ts +0 -39
  162. package/src/kernel/registry/attachments.ts +0 -195
  163. package/src/kernel/registry/catalog.ts +0 -181
  164. package/src/kernel/registry/ingress.ts +0 -223
  165. package/src/kernel/registry/profiles.ts +0 -165
  166. package/src/kernel/registry/provider-request.ts +0 -31
  167. package/src/kernel/registry/resolve.ts +0 -290
  168. package/src/kernel/registry/schemas.ts +0 -29
  169. package/src/kernel/registry/tools.ts +0 -45
  170. package/src/kernel/types.ts +0 -576
  171. package/src/observability/spans.ts +0 -70
  172. package/src/observability/trace-attach.ts +0 -96
  173. package/src/observability/trace-record.ts +0 -254
  174. package/src/observability/trace-usage.ts +0 -36
  175. package/src/observability/trace.ts +0 -143
  176. package/src/presets/google.ts +0 -147
  177. package/src/presets/mod.ts +0 -24
  178. package/src/providers/create-provider.ts +0 -64
  179. package/src/providers/gemini-tape.ts +0 -59
  180. package/src/providers/google-tap.ts +0 -57
  181. package/src/providers/interactions.ts +0 -182
  182. package/src/providers/keys.ts +0 -182
  183. package/src/providers/openrouter-payload.ts +0 -232
  184. package/src/providers/openrouter.ts +0 -758
  185. package/src/providers/pcm.ts +0 -37
  186. package/src/providers/provider.ts +0 -206
  187. package/src/providers/speech.ts +0 -180
  188. 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,2 @@
1
+ declare function asRecord(value: unknown): Record<string, unknown> | undefined;
2
+ export { asRecord };
@@ -0,0 +1,7 @@
1
+ function asRecord(value) {
2
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
3
+ return value;
4
+ }
5
+ return undefined;
6
+ }
7
+ export { asRecord };
@@ -0,0 +1,7 @@
1
+ import type { Profile, TurnHistoryMessage, TurnRepairRequest } from '../types.js';
2
+ declare function synthesizeRepairPrompt(args: {
3
+ profile: Profile;
4
+ repair: TurnRepairRequest;
5
+ history?: TurnHistoryMessage[];
6
+ }): string;
7
+ export { synthesizeRepairPrompt };
@@ -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,203 @@
1
+ import { toErrorEvent } from '../../../guardrails/error.js';
2
+ import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
3
+ import { resolveTurn } from '../../registry/resolve.js';
4
+ import { executeAttempt } from './steps.js';
5
+ function collectAttemptText(events) {
6
+ return events
7
+ .filter((e) => e.type === 'text' && e.text)
8
+ .map((e) => e.text)
9
+ .join('');
10
+ }
11
+ function buildRepairRequest(safe, previousOutput, rejection, repairGuidance) {
12
+ return {
13
+ ...safe,
14
+ input: {
15
+ ...safe.input,
16
+ repair: {
17
+ previousOutput: typeof previousOutput === 'string' ? previousOutput : JSON.stringify(previousOutput),
18
+ rejection,
19
+ guidance: repairGuidance,
20
+ },
21
+ },
22
+ };
23
+ }
24
+ function hasValidatableOutput(validation, latestStructured) {
25
+ if (!validation || latestStructured === undefined) {
26
+ return false;
27
+ }
28
+ const candidateOutput = validation.extract?.(latestStructured) ?? latestStructured;
29
+ return candidateOutput !== undefined && candidateOutput !== null;
30
+ }
31
+ async function evaluateValidationAttempt(validation, latestStructured, slots) {
32
+ const candidateOutput = validation.extract?.(latestStructured) ?? latestStructured;
33
+ const check = await validation.validate(candidateOutput, slots);
34
+ const error = check.error || check.finding || 'Validation failed';
35
+ return { candidateOutput, isValid: Boolean(check.isValid), error };
36
+ }
37
+ async function evaluateEgressOutcome(args) {
38
+ const { egress, attemptEvents, generation, request, profile, canRetry } = args;
39
+ const attemptText = collectAttemptText(attemptEvents);
40
+ const result = await egress.enforce({
41
+ text: attemptText,
42
+ canary: generation.canary,
43
+ slots: request.input?.slots,
44
+ profile,
45
+ role: request.input?.role,
46
+ });
47
+ if (!result.blocked) {
48
+ return { action: 'pass' };
49
+ }
50
+ if (egress.onBlock === 'refuse_to_user') {
51
+ return { action: 'refusal', event: { type: 'text', text: result.text } };
52
+ }
53
+ if (canRetry) {
54
+ const rejectionMsg = result.rejectionMessage || 'Egress disclosure violation detected.';
55
+ const repairGuidance = egress.repairGuidance ||
56
+ 'Rewrite the message as corrected user-visible prose only. Keep the same helpful substance; scrub all internal tool names, leak phrases, and disclosure markers.';
57
+ const nextRequest = buildRepairRequest(request, attemptText, rejectionMsg, repairGuidance);
58
+ return { action: 'retry', nextRequest };
59
+ }
60
+ return {
61
+ action: 'withhold',
62
+ event: toErrorEvent('Turn withheld: egress disclosure violation'),
63
+ };
64
+ }
65
+ async function evaluateValidationOutcome(args) {
66
+ const { validation, latestStructured, request, canRetry } = args;
67
+ if (!hasValidatableOutput(validation, latestStructured)) {
68
+ return { action: 'pass' };
69
+ }
70
+ const { candidateOutput, isValid, error } = await evaluateValidationAttempt(validation, latestStructured, request.input?.slots);
71
+ if (isValid) {
72
+ return { action: 'pass' };
73
+ }
74
+ if (canRetry) {
75
+ const nextRequest = buildRepairRequest(request, candidateOutput, error, validation.repairGuidance);
76
+ return { action: 'retry', nextRequest };
77
+ }
78
+ return {
79
+ action: 'accept',
80
+ event: { type: 'structured', structured: latestStructured },
81
+ };
82
+ }
83
+ function* yieldBufferedAttemptEvents(events) {
84
+ for (const ev of events) {
85
+ if (ev.type !== 'tokens') {
86
+ yield ev;
87
+ }
88
+ }
89
+ }
90
+ function updateFlowForRetry(flow, nextReq) {
91
+ flow.currentAttempt++;
92
+ flow.currentReq = nextReq;
93
+ flow.currentGen = resolveTurn(sanitizeTurnRequest(nextReq)).generation;
94
+ }
95
+ async function* handleEgressGate(egress, flow, state, profile, maxRetries) {
96
+ const canRetry = flow.currentAttempt < maxRetries;
97
+ const outcome = await evaluateEgressOutcome({
98
+ egress,
99
+ attemptEvents: state.attemptEvents,
100
+ generation: flow.currentGen,
101
+ request: flow.currentReq,
102
+ profile,
103
+ canRetry,
104
+ });
105
+ if (outcome.action === 'refusal') {
106
+ state.allEmittedEvents.push(outcome.event);
107
+ yield outcome.event;
108
+ return 'terminal';
109
+ }
110
+ if (outcome.action === 'withhold') {
111
+ yield outcome.event;
112
+ return 'terminal';
113
+ }
114
+ if (outcome.action === 'retry') {
115
+ updateFlowForRetry(flow, outcome.nextRequest);
116
+ return 'continue';
117
+ }
118
+ return 'pass';
119
+ }
120
+ async function* handleValidationGate(validation, flow, state, latestStructured, maxRetries) {
121
+ const canRetry = flow.currentAttempt < maxRetries;
122
+ const outcome = await evaluateValidationOutcome({
123
+ validation,
124
+ latestStructured,
125
+ request: flow.currentReq,
126
+ canRetry,
127
+ });
128
+ if (outcome.action === 'retry') {
129
+ updateFlowForRetry(flow, outcome.nextRequest);
130
+ return 'continue';
131
+ }
132
+ if (outcome.action === 'accept') {
133
+ state.allEmittedEvents.push(outcome.event);
134
+ yield outcome.event;
135
+ return 'terminal';
136
+ }
137
+ return 'pass';
138
+ }
139
+ function gateStatusToAction(status, terminalStatus = 'terminal') {
140
+ if (status === 'terminal') {
141
+ return { status: terminalStatus };
142
+ }
143
+ if (status === 'continue') {
144
+ return { status: 'continue' };
145
+ }
146
+ return null;
147
+ }
148
+ async function* executeSingleAttemptCycle(args) {
149
+ const { flow, state, profile, system, provider, gemini, maxRetries } = args;
150
+ const validation = profile.outputs.validation;
151
+ const egress = profile.guardrails.egress;
152
+ state.attemptEvents = [];
153
+ const { latestStructured } = yield* executeAttempt({
154
+ safe: flow.currentReq,
155
+ profile,
156
+ generation: flow.currentGen,
157
+ system,
158
+ provider,
159
+ gemini,
160
+ state,
161
+ });
162
+ if (egress?.enforce) {
163
+ const status = yield* handleEgressGate(egress, flow, state, profile, maxRetries);
164
+ const action = gateStatusToAction(status, 'terminal');
165
+ if (action) {
166
+ return action;
167
+ }
168
+ }
169
+ if (validation) {
170
+ const status = yield* handleValidationGate(validation, flow, state, latestStructured, maxRetries);
171
+ const action = gateStatusToAction(status, 'success');
172
+ if (action) {
173
+ return action;
174
+ }
175
+ }
176
+ if (validation || egress?.enforce) {
177
+ yield* yieldBufferedAttemptEvents(state.attemptEvents);
178
+ }
179
+ return { status: 'success' };
180
+ }
181
+ async function* runAttemptsWithValidation(safe, profile, generation, system, provider, gemini, state) {
182
+ const maxRetries = Math.max(profile.outputs.validation?.maxRetries ?? 0, profile.guardrails.egress?.maxRetries ?? 2);
183
+ const flow = {
184
+ currentAttempt: 0,
185
+ currentGen: generation,
186
+ currentReq: safe,
187
+ };
188
+ while (flow.currentAttempt <= maxRetries) {
189
+ const step = yield* executeSingleAttemptCycle({
190
+ flow,
191
+ state,
192
+ profile,
193
+ system,
194
+ provider,
195
+ gemini,
196
+ maxRetries,
197
+ });
198
+ if (step.status === 'terminal' || step.status === 'success') {
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ 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,109 @@
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 (event.type === 'error') {
71
+ const detail = event.errorInternal ?? event.error;
72
+ if (detail) {
73
+ console.error(`[theorum] turn error (${req.profile}): ${detail}`);
74
+ }
75
+ }
76
+ if (shouldSkipStreamEvent(event, profile)) {
77
+ continue;
78
+ }
79
+ yield event;
80
+ }
81
+ }
82
+ catch (err) {
83
+ await writeTrace(sink, buildRecord({
84
+ req,
85
+ events: seen,
86
+ started,
87
+ model,
88
+ bucket,
89
+ thrown: err,
90
+ gemini,
91
+ canary,
92
+ system,
93
+ generation,
94
+ }));
95
+ throw err;
96
+ }
97
+ await writeTrace(sink, buildRecord({
98
+ req,
99
+ events: seen,
100
+ started,
101
+ model,
102
+ bucket,
103
+ gemini,
104
+ canary,
105
+ system,
106
+ generation,
107
+ }));
108
+ }
109
+ export { runTurn };
@@ -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,8 @@
1
+ function recordStepEvent(event, state) {
2
+ if (event.type === 'tokens') {
3
+ state.sawTokensEvent = true;
4
+ }
5
+ state.allEmittedEvents.push(event);
6
+ state.attemptEvents.push(event);
7
+ }
8
+ 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 };
@@ -0,0 +1,119 @@
1
+ import { recordStepEvent } from './state.js';
2
+ import { yieldProviderEvents } from './stream.js';
3
+ import { executeDynamicDeclaration, findDynamicDeclaration, formatToolFinding, isActionableDynamicDeclaration, } from './tools.js';
4
+ function isStepLimitReached(step, maxSteps) {
5
+ if (maxSteps <= 0) {
6
+ return false;
7
+ }
8
+ return step >= maxSteps;
9
+ }
10
+ async function* executeAutonomousStep(args, state, bufferOutputs = false) {
11
+ const { profile, generation, system, provider, gemini } = args;
12
+ const genForStep = { ...generation, history: state.currentHistory };
13
+ const pendingTools = [];
14
+ let latestStructured;
15
+ for await (const event of yieldProviderEvents({
16
+ profile,
17
+ generation: genForStep,
18
+ system,
19
+ provider,
20
+ gemini,
21
+ })) {
22
+ if (event.type === 'structured') {
23
+ latestStructured = event.structured;
24
+ }
25
+ if (event.type === 'done') {
26
+ continue;
27
+ }
28
+ if (event.type === 'tool' && event.tool) {
29
+ pendingTools.push(event);
30
+ continue;
31
+ }
32
+ recordStepEvent(event, state);
33
+ if (!bufferOutputs || event.type === 'tokens') {
34
+ yield event;
35
+ }
36
+ }
37
+ return { pendingTools, latestStructured };
38
+ }
39
+ function appendToolTurnToHistory(history, toolEv, res) {
40
+ const tool = toolEv.tool;
41
+ if (!tool) {
42
+ return;
43
+ }
44
+ const callId = tool.id ?? `call_${tool.name}_${String(Date.now())}`;
45
+ history.push({
46
+ role: 'assistant',
47
+ tool_calls: [
48
+ {
49
+ id: callId,
50
+ type: 'function',
51
+ function: {
52
+ name: tool.name,
53
+ arguments: JSON.stringify(tool.arguments ?? {}),
54
+ },
55
+ },
56
+ ],
57
+ });
58
+ history.push({
59
+ role: 'tool',
60
+ tool_call_id: callId,
61
+ name: tool.name,
62
+ content: formatToolFinding(res),
63
+ });
64
+ }
65
+ async function* handlePendingDynamicTools(pendingTools, generation, profile, state) {
66
+ let hasRunnableHandler = false;
67
+ for (const toolEv of pendingTools) {
68
+ const tool = toolEv.tool;
69
+ if (!tool) {
70
+ continue;
71
+ }
72
+ const decl = findDynamicDeclaration(generation.dynamicTools, tool.name);
73
+ if (!isActionableDynamicDeclaration(decl)) {
74
+ state.allEmittedEvents.push(toolEv);
75
+ yield toolEv;
76
+ continue;
77
+ }
78
+ hasRunnableHandler = true;
79
+ const finalResult = await executeDynamicDeclaration({
80
+ decl,
81
+ toolArgs: tool.arguments ?? {},
82
+ profile,
83
+ generation,
84
+ });
85
+ const enrichedEvent = {
86
+ type: 'tool',
87
+ tool: { ...tool, result: finalResult },
88
+ };
89
+ state.allEmittedEvents.push(enrichedEvent);
90
+ yield enrichedEvent;
91
+ appendToolTurnToHistory(state.currentHistory, toolEv, finalResult);
92
+ }
93
+ return hasRunnableHandler;
94
+ }
95
+ async function* executeAttempt(args) {
96
+ const { profile, generation, system, provider, gemini, state } = args;
97
+ let latestStructured;
98
+ let pendingTools = [];
99
+ let stepInAttempt = 0;
100
+ const shouldBuffer = Boolean(profile.outputs.validation) || Boolean(profile.guardrails.egress?.enforce);
101
+ while (!isStepLimitReached(stepInAttempt, generation.maxSteps)) {
102
+ stepInAttempt++;
103
+ state.stepCount++;
104
+ const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, gemini }, state, shouldBuffer);
105
+ if (stepResult.latestStructured !== undefined) {
106
+ latestStructured = stepResult.latestStructured;
107
+ }
108
+ pendingTools = stepResult.pendingTools;
109
+ if (pendingTools.length === 0) {
110
+ break;
111
+ }
112
+ const hasRunnableHandler = yield* handlePendingDynamicTools(pendingTools, generation, profile, state);
113
+ if (!hasRunnableHandler) {
114
+ break;
115
+ }
116
+ }
117
+ return { pendingTools, latestStructured };
118
+ }
119
+ export { executeAttempt };
@@ -0,0 +1,11 @@
1
+ import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent } from '../../types.js';
2
+ declare function systemFromProfile(profile: Profile, role: string): string;
3
+ declare function shouldSkipStreamEvent(event: TurnEvent, profile: Profile): boolean;
4
+ declare function yieldProviderEvents(args: {
5
+ profile: Profile;
6
+ generation: ResolvedGeneration;
7
+ system: string;
8
+ provider: ModelProvider;
9
+ gemini: Record<string, unknown>[];
10
+ }): AsyncGenerator<TurnEvent>;
11
+ export { shouldSkipStreamEvent, systemFromProfile, yieldProviderEvents };
@@ -0,0 +1,70 @@
1
+ import { publicError, toErrorEvent } from '../../../guardrails/error.js';
2
+ import { providerCompleteRequest } from '../../registry/provider-request.js';
3
+ import { eventHasCanary, redactCanary } from '../boundary.js';
4
+ import { dispatchModelTool } from './tools.js';
5
+ function systemFromProfile(profile, role) {
6
+ const { identity } = profile;
7
+ const { systemByRole, system } = identity;
8
+ if (systemByRole) {
9
+ const byRole = systemByRole[role];
10
+ if (byRole) {
11
+ return byRole;
12
+ }
13
+ }
14
+ if (system) {
15
+ return system;
16
+ }
17
+ return '';
18
+ }
19
+ function* interceptProviderTool(event, profile, generation) {
20
+ const tool = event.tool;
21
+ if (!tool) {
22
+ return;
23
+ }
24
+ const isDynamic = generation.dynamicTools?.some((d) => d.name === tool.name);
25
+ if (isDynamic) {
26
+ yield event;
27
+ }
28
+ else {
29
+ for (const item of dispatchModelTool(profile, event, generation.custom)) {
30
+ yield item;
31
+ }
32
+ }
33
+ }
34
+ function shouldSkipStreamEvent(event, profile) {
35
+ return event.type === 'thought' && profile.outputs.streaming?.streamThoughts === false;
36
+ }
37
+ function* processNormalEvent(event, profile, generation) {
38
+ if (event.type === 'tool') {
39
+ yield* interceptProviderTool(event, profile, generation);
40
+ }
41
+ else if (event.type === 'error') {
42
+ const internal = event.errorInternal ?? event.error ?? '';
43
+ yield {
44
+ type: 'error',
45
+ error: publicError(event.error ?? internal),
46
+ ...(internal ? { errorInternal: internal } : {}),
47
+ };
48
+ }
49
+ else {
50
+ yield event;
51
+ }
52
+ }
53
+ async function* yieldProviderEvents(args) {
54
+ const { profile, generation, system, provider, gemini } = args;
55
+ const { canary } = generation;
56
+ for await (const event of provider.complete({
57
+ ...providerCompleteRequest(generation, system),
58
+ tapGemini: (row) => {
59
+ gemini.push(row);
60
+ },
61
+ })) {
62
+ if (canary && eventHasCanary(event, canary)) {
63
+ yield redactCanary(event, canary);
64
+ yield toErrorEvent('canary leaked');
65
+ return;
66
+ }
67
+ yield* processNormalEvent(event, profile, generation);
68
+ }
69
+ }
70
+ export { shouldSkipStreamEvent, systemFromProfile, yieldProviderEvents };
@@ -0,0 +1,3 @@
1
+ import type { TurnEvent, TurnRequest } from '../../types.js';
2
+ declare function calculateFallbackTokens(safe: TurnRequest, system: string, events: TurnEvent[]): Generator<TurnEvent>;
3
+ export { calculateFallbackTokens };