theorum 0.1.2 → 0.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.
Files changed (99) hide show
  1. package/README.md +62 -27
  2. package/docs/AGENT_PROFILE_CONTRACT.md +45 -18
  3. package/docs/CLI_SPEC.md +2 -2
  4. package/docs/SECRETS.md +27 -22
  5. package/esm/_dnt.polyfills.d.ts +99 -0
  6. package/esm/_dnt.polyfills.js +130 -1
  7. package/esm/mod.d.ts +19 -3
  8. package/esm/mod.js +17 -3
  9. package/esm/src/cli/commands/profile.d.ts +2 -0
  10. package/esm/src/cli/commands/profile.js +53 -0
  11. package/esm/src/cli/commands/run.d.ts +10 -0
  12. package/esm/src/cli/commands/run.js +55 -0
  13. package/esm/src/cli/commands/test.d.ts +15 -0
  14. package/esm/src/cli/commands/test.js +140 -0
  15. package/esm/src/cli/index.d.ts +4 -0
  16. package/esm/src/cli/index.js +148 -0
  17. package/esm/src/cli/matrix/fixtures.d.ts +13 -0
  18. package/esm/src/cli/matrix/fixtures.js +77 -0
  19. package/esm/src/cli/matrix/synthesizer.d.ts +29 -0
  20. package/esm/src/cli/matrix/synthesizer.js +187 -0
  21. package/esm/src/guardrails/error.d.ts +1 -3
  22. package/esm/src/guardrails/error.js +4 -6
  23. package/esm/src/guardrails/mod.d.ts +1 -1
  24. package/esm/src/guardrails/mod.js +1 -1
  25. package/esm/src/guardrails/sanitize.js +1 -1
  26. package/esm/src/host/mint-trace.d.ts +27 -0
  27. package/esm/src/host/mint-trace.js +33 -0
  28. package/esm/src/host/mod.d.ts +13 -0
  29. package/esm/src/host/mod.js +12 -0
  30. package/esm/src/host/reply.d.ts +14 -0
  31. package/esm/src/host/reply.js +27 -0
  32. package/esm/src/kernel/engine/delta.js +31 -4
  33. package/esm/src/kernel/engine/repair.d.ts +1 -3
  34. package/esm/src/kernel/engine/repair.js +1 -1
  35. package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
  36. package/esm/src/kernel/engine/runner/gates.js +206 -0
  37. package/esm/src/kernel/engine/runner/mod.d.ts +14 -0
  38. package/esm/src/kernel/engine/runner/mod.js +103 -0
  39. package/esm/src/kernel/engine/runner/state.d.ts +16 -0
  40. package/esm/src/kernel/engine/runner/state.js +8 -0
  41. package/esm/src/kernel/engine/runner/steps.d.ts +15 -0
  42. package/esm/src/kernel/engine/runner/steps.js +119 -0
  43. package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
  44. package/esm/src/kernel/engine/runner/stream.js +65 -0
  45. package/esm/src/kernel/engine/runner/tokens.d.ts +3 -0
  46. package/esm/src/kernel/engine/runner/tokens.js +38 -0
  47. package/esm/src/kernel/engine/runner/tools.d.ts +13 -0
  48. package/esm/src/kernel/engine/runner/tools.js +197 -0
  49. package/esm/src/kernel/engine/runner.d.ts +1 -9
  50. package/esm/src/kernel/engine/runner.js +1 -726
  51. package/esm/src/kernel/mod.d.ts +3 -1
  52. package/esm/src/kernel/mod.js +2 -1
  53. package/esm/src/{providers → kernel/registry}/attachments.d.ts +2 -3
  54. package/esm/src/{providers → kernel/registry}/attachments.js +6 -6
  55. package/esm/src/kernel/registry/catalog.d.ts +28 -21
  56. package/esm/src/kernel/registry/catalog.js +55 -133
  57. package/esm/src/kernel/registry/ingress.d.ts +13 -0
  58. package/esm/src/kernel/registry/ingress.js +163 -0
  59. package/esm/src/kernel/registry/profiles.d.ts +2 -4
  60. package/esm/src/kernel/registry/profiles.js +14 -5
  61. package/esm/src/kernel/registry/provider-request.d.ts +4 -0
  62. package/esm/src/kernel/registry/provider-request.js +25 -0
  63. package/esm/src/kernel/registry/resolve.d.ts +0 -1
  64. package/esm/src/kernel/registry/resolve.js +35 -53
  65. package/esm/src/kernel/registry/tools.js +2 -2
  66. package/esm/src/kernel/registry/vault.d.ts +11 -0
  67. package/esm/src/kernel/registry/vault.js +18 -0
  68. package/esm/src/kernel/types.d.ts +102 -78
  69. package/esm/src/observability/trace-attach.js +6 -20
  70. package/esm/src/presets/google.d.ts +50 -0
  71. package/esm/src/presets/google.js +96 -0
  72. package/esm/src/presets/mod.d.ts +11 -0
  73. package/esm/src/presets/mod.js +10 -0
  74. package/esm/src/providers/create-provider.d.ts +29 -0
  75. package/esm/src/providers/create-provider.js +38 -0
  76. package/esm/src/providers/gemini-tape.d.ts +1 -2
  77. package/esm/src/providers/gemini-tape.js +1 -1
  78. package/esm/src/providers/interactions.js +41 -16
  79. package/esm/src/{guardrails → providers}/keys.d.ts +10 -3
  80. package/esm/src/{guardrails → providers}/keys.js +10 -13
  81. package/esm/src/providers/mod.d.ts +4 -7
  82. package/esm/src/providers/mod.js +2 -5
  83. package/esm/src/providers/openrouter-mod.d.ts +13 -0
  84. package/esm/src/providers/openrouter-mod.js +12 -0
  85. package/esm/src/providers/openrouter-payload.d.ts +11 -3
  86. package/esm/src/providers/openrouter-payload.js +28 -26
  87. package/esm/src/providers/openrouter.d.ts +5 -7
  88. package/esm/src/providers/openrouter.js +488 -231
  89. package/esm/src/providers/pcm.d.ts +7 -0
  90. package/esm/src/providers/pcm.js +35 -0
  91. package/esm/src/providers/provider.d.ts +3 -1
  92. package/esm/src/providers/provider.js +45 -2
  93. package/esm/src/providers/speech.d.ts +23 -0
  94. package/esm/src/providers/speech.js +125 -0
  95. package/package.json +20 -3
  96. package/esm/src/providers/media.d.ts +0 -5
  97. package/esm/src/providers/media.js +0 -125
  98. package/esm/src/providers/tts.d.ts +0 -24
  99. package/esm/src/providers/tts.js +0 -144
@@ -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,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,65 @@
1
+ import { publicError } 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
+ yield { type: 'error', error: publicError(event.error) };
43
+ }
44
+ else {
45
+ yield event;
46
+ }
47
+ }
48
+ async function* yieldProviderEvents(args) {
49
+ const { profile, generation, system, provider, gemini } = args;
50
+ const { canary } = generation;
51
+ for await (const event of provider.complete({
52
+ ...providerCompleteRequest(generation, system),
53
+ tapGemini: (row) => {
54
+ gemini.push(row);
55
+ },
56
+ })) {
57
+ if (canary && eventHasCanary(event, canary)) {
58
+ yield redactCanary(event, canary);
59
+ yield { type: 'error', error: publicError('canary leaked') };
60
+ return;
61
+ }
62
+ yield* processNormalEvent(event, profile, generation);
63
+ }
64
+ }
65
+ 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 };
@@ -0,0 +1,38 @@
1
+ function calculateInputChars(safe, system) {
2
+ const input = safe.input ?? {};
3
+ return ((input.text?.length ?? 0) +
4
+ (input.repair?.previousOutput?.length ?? 0) +
5
+ (system?.length ?? 800));
6
+ }
7
+ function calculateOutputChars(events) {
8
+ let outputChars = 0;
9
+ let thinkingChars = 0;
10
+ for (const e of events) {
11
+ if (e.type === 'text' && e.text)
12
+ outputChars += e.text.length;
13
+ if (e.type === 'structured' && e.structured) {
14
+ outputChars += JSON.stringify(e.structured).length;
15
+ }
16
+ if (e.type === 'thought' && e.text)
17
+ thinkingChars += e.text.length;
18
+ }
19
+ return { outputChars, thinkingChars };
20
+ }
21
+ function* calculateFallbackTokens(safe, system, events) {
22
+ const inputChars = calculateInputChars(safe, system);
23
+ const { outputChars, thinkingChars } = calculateOutputChars(events);
24
+ const inputTokens = Math.max(1, Math.round(inputChars / 4));
25
+ const outputTokens = Math.max(1, Math.round(outputChars / 4));
26
+ const thinkingTokens = thinkingChars ? Math.round(thinkingChars / 4) : 0;
27
+ yield {
28
+ type: 'tokens',
29
+ tokens: {
30
+ input: inputTokens,
31
+ output: outputTokens,
32
+ thinking: thinkingTokens,
33
+ toolUse: 0,
34
+ total: inputTokens + outputTokens + thinkingTokens,
35
+ },
36
+ };
37
+ }
38
+ export { calculateFallbackTokens };
@@ -0,0 +1,13 @@
1
+ import type { CustomToolId, DynamicToolDeclaration, Profile, ResolvedGeneration, ToolEnvelope, TurnEvent, TurnRequest } from '../../types.js';
2
+ declare function formatToolFinding(res: ToolEnvelope): string;
3
+ declare function invokeFromUi(profile: Profile, req: TurnRequest): Generator<TurnEvent>;
4
+ declare function dispatchModelTool(profile: Profile, event: TurnEvent, gated: CustomToolId[]): TurnEvent[];
5
+ declare function findDynamicDeclaration(tools: DynamicToolDeclaration[] | undefined, name: string): DynamicToolDeclaration | undefined;
6
+ declare function isActionableDynamicDeclaration(decl: DynamicToolDeclaration | undefined): decl is DynamicToolDeclaration;
7
+ declare function executeDynamicDeclaration(args: {
8
+ decl: DynamicToolDeclaration;
9
+ toolArgs: Record<string, unknown>;
10
+ profile: Profile;
11
+ generation: ResolvedGeneration;
12
+ }): Promise<ToolEnvelope>;
13
+ export { dispatchModelTool, executeDynamicDeclaration, findDynamicDeclaration, formatToolFinding, invokeFromUi, isActionableDynamicDeclaration, };
@@ -0,0 +1,197 @@
1
+ import { publicError } from '../../../guardrails/error.js';
2
+ import { CATALOG } from '../../registry/catalog.js';
3
+ import { executeTool } from '../../registry/tools.js';
4
+ function formatToolFinding(res) {
5
+ if (res.finding) {
6
+ return res.finding;
7
+ }
8
+ if (res.data) {
9
+ return JSON.stringify(res.data);
10
+ }
11
+ return 'ok';
12
+ }
13
+ function* invokeFromUi(profile, req) {
14
+ const invoke = req.toolInvoke;
15
+ if (!invoke) {
16
+ return;
17
+ }
18
+ try {
19
+ const result = executeTool(profile, invoke.name, invoke.arguments);
20
+ yield {
21
+ type: 'tool',
22
+ tool: { name: invoke.name, arguments: invoke.arguments, result },
23
+ };
24
+ if (result.status === 'error') {
25
+ yield { type: 'error', error: publicError(formatToolFinding(result)) };
26
+ return;
27
+ }
28
+ yield { type: 'done' };
29
+ }
30
+ catch (err) {
31
+ yield { type: 'error', error: publicError(err) };
32
+ yield { type: 'done' };
33
+ }
34
+ }
35
+ function executeCustomModelTool(profile, name, args) {
36
+ try {
37
+ const result = executeTool(profile, name, args);
38
+ const events = [
39
+ {
40
+ type: 'tool',
41
+ tool: { name, arguments: args, result },
42
+ },
43
+ ];
44
+ if (result.status === 'error') {
45
+ events.push({ type: 'error', error: publicError(formatToolFinding(result)) });
46
+ }
47
+ return events;
48
+ }
49
+ catch (err) {
50
+ return [{ type: 'error', error: publicError(err) }];
51
+ }
52
+ }
53
+ function dispatchModelTool(profile, event, gated) {
54
+ const { tool } = event;
55
+ if (!tool) {
56
+ return [];
57
+ }
58
+ const name = tool.name;
59
+ if (CATALOG.tools[name]?.kind === 'builtin') {
60
+ return [event];
61
+ }
62
+ if (!gated.includes(name)) {
63
+ return [
64
+ {
65
+ type: 'error',
66
+ error: publicError(`Tool '${name}' is not gated on this turn`),
67
+ },
68
+ ];
69
+ }
70
+ const args = tool.arguments ?? {};
71
+ return executeCustomModelTool(profile, name, args);
72
+ }
73
+ function findDynamicDeclaration(tools, name) {
74
+ return tools?.find((t) => t.name === name);
75
+ }
76
+ async function checkDynamicAuthorization(decl, args, profile, sessionPermissions) {
77
+ if (!decl.canExecute) {
78
+ return null;
79
+ }
80
+ try {
81
+ const decision = await decl.canExecute({
82
+ args,
83
+ profile,
84
+ sessionPermissions,
85
+ });
86
+ if (typeof decision === 'boolean' && !decision) {
87
+ return {
88
+ status: 'error',
89
+ finding: `Tool '${decl.name}' execution not authorized.`,
90
+ };
91
+ }
92
+ if (typeof decision === 'object' && decision !== null) {
93
+ return decision;
94
+ }
95
+ return null;
96
+ }
97
+ catch (err) {
98
+ const msg = err instanceof Error ? err.message : String(err);
99
+ return {
100
+ status: 'error',
101
+ finding: `Authorization error for '${decl.name}': ${msg}`,
102
+ };
103
+ }
104
+ }
105
+ function checkDynamicPermissionTier(decl, args, sessionPermissions) {
106
+ if (decl.permissionTier === 'session_consent' || decl.permissionTier === 'always_confirm') {
107
+ const isGranted = sessionPermissions?.includes(decl.name) || sessionPermissions?.includes('*');
108
+ if (!isGranted) {
109
+ return {
110
+ status: 'pause',
111
+ finding: `Tool '${decl.name}' requires ${decl.permissionTier} authorization.`,
112
+ data: { tool: decl.name, permissionTier: decl.permissionTier, args },
113
+ };
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ async function runDynamicHandler(decl, args) {
119
+ if (!decl.handler) {
120
+ return {
121
+ status: 'ok',
122
+ finding: `${decl.name} accepted (no handler)`,
123
+ data: args,
124
+ };
125
+ }
126
+ try {
127
+ return await decl.handler(args);
128
+ }
129
+ catch (err) {
130
+ const msg = err instanceof Error ? err.message : String(err);
131
+ return { status: 'error', finding: msg };
132
+ }
133
+ }
134
+ async function executeDynamicTool(decl, args, profile, sessionPermissions) {
135
+ const authEnvelope = await checkDynamicAuthorization(decl, args, profile, sessionPermissions);
136
+ if (authEnvelope) {
137
+ return authEnvelope;
138
+ }
139
+ const permissionEnvelope = checkDynamicPermissionTier(decl, args, sessionPermissions);
140
+ if (permissionEnvelope) {
141
+ return permissionEnvelope;
142
+ }
143
+ return runDynamicHandler(decl, args);
144
+ }
145
+ function mergeDynamicTools(current, loaded) {
146
+ const merged = [...(current ?? [])];
147
+ for (const tool of loaded) {
148
+ const existing = merged.findIndex((item) => item.name === tool.name);
149
+ if (existing >= 0) {
150
+ merged[existing] = tool;
151
+ }
152
+ else {
153
+ merged.push(tool);
154
+ }
155
+ }
156
+ return merged;
157
+ }
158
+ async function executeDynamicToolLoader(args) {
159
+ const { decl, toolArgs, profile, generation } = args;
160
+ const loader = generation.dynamicToolLoader;
161
+ if (!loader) {
162
+ return {
163
+ status: 'error',
164
+ finding: `Tool '${decl.name}' is marked as a loader but no loader is configured.`,
165
+ };
166
+ }
167
+ const loaded = await loader({
168
+ name: decl.name,
169
+ args: toolArgs,
170
+ profile,
171
+ currentTools: generation.dynamicTools ?? [],
172
+ sessionPermissions: generation.sessionPermissions,
173
+ });
174
+ generation.dynamicTools = mergeDynamicTools(generation.dynamicTools, loaded);
175
+ return {
176
+ status: 'ok',
177
+ finding: `Loaded ${String(loaded.length)} dynamic tool schema(s).`,
178
+ data: { loadedTools: loaded.map((tool) => tool.name) },
179
+ };
180
+ }
181
+ function isActionableDynamicDeclaration(decl) {
182
+ return Boolean(decl?.handler || decl?.canExecute || decl?.permissionTier || decl?.loadsDynamicTools);
183
+ }
184
+ async function executeDynamicDeclaration(args) {
185
+ const { decl, toolArgs, profile, generation } = args;
186
+ const res = await executeDynamicTool(decl, toolArgs, profile, generation.sessionPermissions);
187
+ if (res.status === 'ok' && decl.loadsDynamicTools) {
188
+ return await executeDynamicToolLoader({
189
+ decl,
190
+ toolArgs,
191
+ profile,
192
+ generation,
193
+ });
194
+ }
195
+ return res;
196
+ }
197
+ export { dispatchModelTool, executeDynamicDeclaration, findDynamicDeclaration, formatToolFinding, invokeFromUi, isActionableDynamicDeclaration, };
@@ -1,14 +1,6 @@
1
1
  /**
2
2
  * Deterministic turn runner for THEORUM.
3
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
4
  * @module
9
5
  */
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 };
6
+ export { runTurn } from './runner/mod.js';