theorum 0.1.14 → 1.0.0

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 (229) hide show
  1. package/README.md +178 -89
  2. package/esm/mod.d.ts +21 -11
  3. package/esm/mod.js +15 -8
  4. package/esm/src/cli/commands/bench.js +6 -3
  5. package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
  6. package/esm/src/cli/commands/fuzz-canary.js +195 -0
  7. package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
  8. package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
  9. package/esm/src/cli/commands/profile.js +25 -14
  10. package/esm/src/cli/commands/run.d.ts +3 -0
  11. package/esm/src/cli/commands/run.js +22 -30
  12. package/esm/src/cli/commands/test.d.ts +10 -1
  13. package/esm/src/cli/commands/test.js +31 -31
  14. package/esm/src/cli/event-log.d.ts +19 -0
  15. package/esm/src/cli/event-log.js +147 -0
  16. package/esm/src/cli/index.js +45 -11
  17. package/esm/src/cli/matrix/synthesizer.d.ts +7 -9
  18. package/esm/src/cli/matrix/synthesizer.js +32 -106
  19. package/esm/src/guardrails/canary-gate.d.ts +20 -0
  20. package/esm/src/guardrails/canary-gate.js +31 -0
  21. package/esm/src/guardrails/canary.d.ts +26 -0
  22. package/esm/src/guardrails/canary.js +139 -0
  23. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +16 -0
  24. package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
  25. package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
  26. package/esm/src/guardrails/corpus/fuzz-inbound.js +216 -0
  27. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
  28. package/esm/src/guardrails/corpus/inbound-payloads.js +124 -0
  29. package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
  30. package/esm/src/guardrails/corpus/live-attacks.js +232 -0
  31. package/esm/src/guardrails/corpus/mod.d.ts +13 -0
  32. package/esm/src/guardrails/corpus/mod.js +11 -0
  33. package/esm/src/guardrails/corpus/secrets.d.ts +16 -0
  34. package/esm/src/guardrails/corpus/secrets.js +16 -0
  35. package/esm/src/guardrails/corpus/strings.d.ts +27 -0
  36. package/esm/src/guardrails/corpus/strings.js +33 -0
  37. package/esm/src/guardrails/corpus/types.d.ts +37 -0
  38. package/esm/src/guardrails/corpus/types.js +6 -0
  39. package/esm/src/guardrails/egress.d.ts +9 -0
  40. package/esm/src/guardrails/egress.js +36 -0
  41. package/esm/src/guardrails/error.js +6 -4
  42. package/esm/src/guardrails/injection.js +28 -5
  43. package/esm/src/guardrails/live-outbound-gate.d.ts +39 -0
  44. package/esm/src/guardrails/live-outbound-gate.js +180 -0
  45. package/esm/src/guardrails/mod.d.ts +13 -4
  46. package/esm/src/guardrails/mod.js +10 -4
  47. package/esm/src/guardrails/quota.js +1 -1
  48. package/esm/src/guardrails/sanitize.d.ts +11 -15
  49. package/esm/src/guardrails/sanitize.js +40 -49
  50. package/esm/src/guardrails/sensitive.js +2 -1
  51. package/esm/src/guardrails/testing.d.ts +10 -0
  52. package/esm/src/guardrails/testing.js +9 -0
  53. package/esm/src/host/client-turn.d.ts +19 -0
  54. package/esm/src/host/client-turn.js +32 -0
  55. package/esm/src/host/mod.d.ts +5 -3
  56. package/esm/src/host/mod.js +4 -3
  57. package/esm/src/interface/blocks.d.ts +20 -0
  58. package/esm/src/interface/blocks.js +180 -0
  59. package/esm/src/interface/from-profile.d.ts +14 -0
  60. package/esm/src/interface/from-profile.js +104 -0
  61. package/esm/src/interface/inputs.d.ts +8 -0
  62. package/esm/src/interface/inputs.js +127 -0
  63. package/esm/src/interface/mod.d.ts +10 -0
  64. package/esm/src/interface/mod.js +9 -0
  65. package/esm/src/interface/types.d.ts +153 -0
  66. package/esm/src/interface/types.js +13 -0
  67. package/esm/src/kernel/engine/delta.d.ts +24 -2
  68. package/esm/src/kernel/engine/delta.js +519 -29
  69. package/esm/src/kernel/engine/live-inbound.d.ts +9 -0
  70. package/esm/src/kernel/engine/live-inbound.js +17 -0
  71. package/esm/src/kernel/engine/repair.js +1 -1
  72. package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
  73. package/esm/src/kernel/engine/runner/gates.js +7 -7
  74. package/esm/src/kernel/engine/runner/mod.js +13 -13
  75. package/esm/src/kernel/engine/runner/state.d.ts +7 -0
  76. package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
  77. package/esm/src/kernel/engine/runner/steps.js +171 -37
  78. package/esm/src/kernel/engine/runner/stream.d.ts +1 -2
  79. package/esm/src/kernel/engine/runner/stream.js +66 -29
  80. package/esm/src/kernel/engine/session/mod.d.ts +23 -0
  81. package/esm/src/kernel/engine/session/mod.js +205 -0
  82. package/esm/src/kernel/mod.d.ts +9 -10
  83. package/esm/src/kernel/mod.js +6 -8
  84. package/esm/src/kernel/registry/attachments.d.ts +4 -2
  85. package/esm/src/kernel/registry/attachments.js +6 -3
  86. package/esm/src/kernel/registry/catalog.d.ts +3 -16
  87. package/esm/src/kernel/registry/catalog.js +5 -81
  88. package/esm/src/kernel/registry/ingress.d.ts +9 -4
  89. package/esm/src/kernel/registry/ingress.js +73 -63
  90. package/esm/src/kernel/registry/profiles.d.ts +35 -12
  91. package/esm/src/kernel/registry/profiles.js +159 -66
  92. package/esm/src/kernel/registry/provider-request.js +11 -7
  93. package/esm/src/kernel/registry/resolve.d.ts +5 -6
  94. package/esm/src/kernel/registry/resolve.js +100 -70
  95. package/esm/src/kernel/registry/vault.d.ts +12 -7
  96. package/esm/src/kernel/registry/vault.js +30 -8
  97. package/esm/src/kernel/schema.d.ts +145 -0
  98. package/esm/src/kernel/schema.js +462 -0
  99. package/esm/src/kernel/stop.d.ts +15 -10
  100. package/esm/src/kernel/stop.js +5 -5
  101. package/esm/src/kernel/tools/execute.d.ts +45 -0
  102. package/esm/src/kernel/tools/execute.js +363 -0
  103. package/esm/src/kernel/tools/harness.d.ts +8 -0
  104. package/esm/src/kernel/tools/harness.js +44 -0
  105. package/esm/src/kernel/tools/invoke.d.ts +10 -0
  106. package/esm/src/kernel/tools/invoke.js +83 -0
  107. package/esm/src/kernel/tools/mod.d.ts +11 -0
  108. package/esm/src/kernel/tools/mod.js +10 -0
  109. package/esm/src/kernel/tools/project.d.ts +12 -0
  110. package/esm/src/kernel/tools/project.js +34 -0
  111. package/esm/src/kernel/tools/registry.d.ts +23 -0
  112. package/esm/src/kernel/tools/registry.js +71 -0
  113. package/esm/src/kernel/tools/resolve.d.ts +29 -0
  114. package/esm/src/kernel/tools/resolve.js +262 -0
  115. package/esm/src/kernel/tools/schema.d.ts +13 -0
  116. package/esm/src/kernel/tools/schema.js +159 -0
  117. package/esm/src/kernel/tools/types.d.ts +216 -0
  118. package/esm/src/kernel/tools/types.js +9 -0
  119. package/esm/src/kernel/types.d.ts +338 -193
  120. package/esm/src/observability/trace-attach.d.ts +6 -4
  121. package/esm/src/observability/trace-attach.js +39 -25
  122. package/esm/src/observability/trace-record.d.ts +11 -9
  123. package/esm/src/observability/trace-record.js +36 -23
  124. package/esm/src/observability/trace-usage.d.ts +10 -3
  125. package/esm/src/observability/trace-usage.js +70 -17
  126. package/esm/src/observability/trace.d.ts +9 -1
  127. package/esm/src/observability/trace.js +11 -3
  128. package/esm/src/presets/google/speech-voices.d.ts +11 -0
  129. package/esm/src/presets/google/speech-voices.js +41 -0
  130. package/esm/src/presets/google.d.ts +36 -24
  131. package/esm/src/presets/google.js +50 -63
  132. package/esm/src/presets/mod.d.ts +2 -2
  133. package/esm/src/presets/mod.js +1 -1
  134. package/esm/src/providers/create-provider.d.ts +14 -14
  135. package/esm/src/providers/create-provider.js +55 -23
  136. package/esm/src/providers/google/interactions/framing.d.ts +15 -0
  137. package/esm/src/providers/google/interactions/framing.js +208 -0
  138. package/esm/src/providers/google/interactions/mod.d.ts +7 -0
  139. package/esm/src/providers/google/interactions/mod.js +7 -0
  140. package/esm/src/providers/google/interactions/stream.d.ts +83 -0
  141. package/esm/src/providers/google/interactions/stream.js +588 -0
  142. package/esm/src/providers/google/keys.d.ts +26 -0
  143. package/esm/src/providers/{keys.js → google/keys.js} +19 -31
  144. package/esm/src/providers/google/live/framing.d.ts +46 -0
  145. package/esm/src/providers/google/live/framing.js +515 -0
  146. package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
  147. package/esm/src/providers/google/live/openapi-schema.js +45 -0
  148. package/esm/src/providers/google/live/session.d.ts +25 -0
  149. package/esm/src/providers/google/live/session.js +134 -0
  150. package/esm/src/providers/google/live/stream.d.ts +36 -0
  151. package/esm/src/providers/google/live/stream.js +201 -0
  152. package/esm/src/providers/google/urls.d.ts +6 -0
  153. package/esm/src/providers/google/urls.js +6 -0
  154. package/esm/src/providers/local/local.d.ts +30 -0
  155. package/esm/src/providers/{local.js → local/local.js} +66 -126
  156. package/esm/src/providers/local/mod.d.ts +9 -0
  157. package/esm/src/providers/local/mod.js +9 -0
  158. package/esm/src/providers/mod.d.ts +6 -3
  159. package/esm/src/providers/mod.js +3 -1
  160. package/esm/src/providers/openrouter/chat.d.ts +87 -0
  161. package/esm/src/providers/{openrouter.js → openrouter/chat.js} +70 -229
  162. package/esm/src/providers/openrouter/image.d.ts +34 -0
  163. package/esm/src/providers/openrouter/image.js +286 -0
  164. package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
  165. package/esm/src/providers/openrouter/openai/chat-payload.js +60 -0
  166. package/esm/src/providers/openrouter/openai/compat.d.ts +47 -0
  167. package/esm/src/providers/openrouter/openai/compat.js +194 -0
  168. package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
  169. package/esm/src/providers/openrouter/openai/image-payload.js +84 -0
  170. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
  171. package/esm/src/providers/openrouter/openai/sdk-messages.js +95 -0
  172. package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
  173. package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
  174. package/esm/src/providers/openrouter/speech.d.ts +23 -0
  175. package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
  176. package/esm/src/providers/probe.d.ts +1 -0
  177. package/esm/src/providers/probe.js +22 -0
  178. package/esm/src/providers/shared/pcm.d.ts +12 -0
  179. package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
  180. package/esm/src/providers/shared/sse.d.ts +18 -0
  181. package/esm/src/providers/shared/sse.js +87 -0
  182. package/esm/src/providers/shared/tool-args.d.ts +17 -0
  183. package/esm/src/providers/shared/tool-args.js +45 -0
  184. package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
  185. package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
  186. package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
  187. package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
  188. package/esm/src/providers/types.d.ts +27 -0
  189. package/esm/src/providers/types.js +1 -0
  190. package/package.json +11 -7
  191. package/docs/cli.md +0 -97
  192. package/docs/guardrails.md +0 -178
  193. package/docs/host.md +0 -97
  194. package/docs/kernel.md +0 -404
  195. package/docs/observability.md +0 -105
  196. package/docs/openrouter.md +0 -125
  197. package/docs/presets-google.md +0 -91
  198. package/docs/presets.md +0 -88
  199. package/docs/providers.md +0 -201
  200. package/docs/streaming.md +0 -96
  201. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  202. package/esm/src/kernel/engine/boundary.js +0 -55
  203. package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
  204. package/esm/src/kernel/engine/runner/tools.js +0 -198
  205. package/esm/src/kernel/registry/tools.d.ts +0 -12
  206. package/esm/src/kernel/registry/tools.js +0 -36
  207. package/esm/src/providers/expose-for-tests.d.ts +0 -1
  208. package/esm/src/providers/expose-for-tests.js +0 -25
  209. package/esm/src/providers/gemini-tape.d.ts +0 -2
  210. package/esm/src/providers/google-tap.d.ts +0 -3
  211. package/esm/src/providers/interactions.d.ts +0 -5
  212. package/esm/src/providers/interactions.js +0 -169
  213. package/esm/src/providers/keys.d.ts +0 -19
  214. package/esm/src/providers/local.d.ts +0 -29
  215. package/esm/src/providers/openrouter-mod.d.ts +0 -13
  216. package/esm/src/providers/openrouter-mod.js +0 -12
  217. package/esm/src/providers/openrouter-payload.d.ts +0 -39
  218. package/esm/src/providers/openrouter-payload.js +0 -195
  219. package/esm/src/providers/openrouter.d.ts +0 -15
  220. package/esm/src/providers/pcm.d.ts +0 -7
  221. package/esm/src/providers/provider.d.ts +0 -15
  222. package/esm/src/providers/provider.js +0 -176
  223. package/esm/src/providers/speech.d.ts +0 -23
  224. package/esm/src/providers/sse.d.ts +0 -7
  225. package/esm/src/providers/sse.js +0 -55
  226. package/esm/src/streaming/mod.d.ts +0 -9
  227. package/esm/src/streaming/mod.js +0 -8
  228. /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
  229. /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Shared tool execution core for model-initiated and host-initiated calls.
3
+ *
4
+ * @module
5
+ */
6
+ import { throwIfAborted } from '../../guardrails/error.js';
7
+ import { sanitizeText } from '../../guardrails/sanitize.js';
8
+ import { getTool } from './registry.js';
9
+ import { promoteLoadedTools } from './resolve.js';
10
+ function isStreamHandler(handler) {
11
+ return (typeof handler === 'function' &&
12
+ Object.prototype.toString.call(handler) === '[object AsyncGeneratorFunction]');
13
+ }
14
+ export function isResumeContinuation(resume) {
15
+ return resume?.value !== undefined || resume?.granted === true;
16
+ }
17
+ export function isToolPause(value) {
18
+ return ('kind' in value &&
19
+ (value.kind === 'interactive' || value.kind === 'confirmation' || value.kind === 'permission'));
20
+ }
21
+ export function* yieldHandlerSideEvent(base, event) {
22
+ if (event.kind === 'progress') {
23
+ yield toolEvent(base, { phase: 'progress', data: event.data });
24
+ }
25
+ else if (event.kind === 'trace') {
26
+ yield toolEvent(base, { phase: 'trace', step: event.step });
27
+ }
28
+ else if (event.kind === 'artifact') {
29
+ yield toolEvent(base, { phase: 'artifact', artifact: event.artifact });
30
+ }
31
+ else if (event.kind === 'warning') {
32
+ yield toolEvent(base, { phase: 'warning', warning: event.warning });
33
+ }
34
+ }
35
+ /** Run the handler, yielding stream side-events live; returns terminal output. */
36
+ async function* runHandler(handler, input, ctx, base) {
37
+ if (isStreamHandler(handler)) {
38
+ let output;
39
+ const gen = handler(input, ctx);
40
+ for await (const event of gen) {
41
+ throwIfAborted(ctx.signal);
42
+ if (event.kind === 'complete') {
43
+ output = event.output;
44
+ continue;
45
+ }
46
+ yield* yieldHandlerSideEvent(base, event);
47
+ }
48
+ return output;
49
+ }
50
+ const output = await handler(input, ctx);
51
+ return output;
52
+ }
53
+ export function permissionGranted(toolName, sessionPermissions) {
54
+ if (!sessionPermissions) {
55
+ return false;
56
+ }
57
+ return sessionPermissions.includes('*') || sessionPermissions.includes(toolName);
58
+ }
59
+ export function checkPermission(toolName, permission, sessionPermissions, resume) {
60
+ if (permission === 'auto') {
61
+ return null;
62
+ }
63
+ if (permission === 'always_confirm') {
64
+ if (resume?.granted === true) {
65
+ return null;
66
+ }
67
+ return {
68
+ kind: 'permission',
69
+ tool: toolName,
70
+ permission,
71
+ input: {},
72
+ };
73
+ }
74
+ if (permissionGranted(toolName, sessionPermissions)) {
75
+ return null;
76
+ }
77
+ return {
78
+ kind: 'permission',
79
+ tool: toolName,
80
+ permission,
81
+ input: {},
82
+ };
83
+ }
84
+ function toolEvent(base, patch) {
85
+ return {
86
+ type: 'tool',
87
+ tool: { ...base, ...patch },
88
+ };
89
+ }
90
+ function failureEvent(base, failure) {
91
+ return toolEvent(base, { phase: 'error', failure });
92
+ }
93
+ export function projectForModel(tool, output) {
94
+ if (tool.exposeToModel === false) {
95
+ return { finding: 'Completed.' };
96
+ }
97
+ const finding = typeof output === 'object' && output !== null && 'finding' in output
98
+ ? String(output.finding)
99
+ : JSON.stringify(output);
100
+ return {
101
+ finding: sanitizeText(finding),
102
+ data: output,
103
+ };
104
+ }
105
+ /** Format model-facing tool output for provider history continuation. */
106
+ export function formatToolResult(result) {
107
+ if (result.data !== undefined) {
108
+ return sanitizeText(`${result.finding}\n${JSON.stringify(result.data)}`);
109
+ }
110
+ return sanitizeText(result.finding);
111
+ }
112
+ /** Format a tool failure for provider history — structured so the model (or host) sees the code. */
113
+ export function formatToolFailureForModel(failure) {
114
+ return {
115
+ finding: `Tool error (${failure.code}): ${failure.message}`,
116
+ data: {
117
+ ok: false,
118
+ code: failure.code,
119
+ message: failure.message,
120
+ ...(failure.details !== undefined ? { details: failure.details } : {}),
121
+ },
122
+ };
123
+ }
124
+ export function* startToolExecution(tool, rawInput, ctx, base) {
125
+ yield toolEvent(base, { phase: 'running' });
126
+ throwIfAborted(ctx.signal);
127
+ const parsed = tool.input.safeParse(rawInput);
128
+ if (!parsed.success) {
129
+ yield failureEvent(base, {
130
+ code: 'invalid_input',
131
+ message: 'Tool input validation failed',
132
+ details: parsed.error.flatten(),
133
+ });
134
+ return { ok: false };
135
+ }
136
+ return { ok: true, data: parsed.data };
137
+ }
138
+ export async function* executeFunction(tool, rawInput, ctx, base, snapshot) {
139
+ const parsed = yield* startToolExecution(tool, rawInput, ctx, base);
140
+ if (!parsed.ok)
141
+ return undefined;
142
+ const input = parsed.data;
143
+ const permissionPause = checkPermission(tool.name, tool.permission, ctx.sessionPermissions, ctx.resume);
144
+ if (permissionPause) {
145
+ permissionPause.input = input;
146
+ yield toolEvent(base, { phase: 'pause', pause: permissionPause });
147
+ return undefined;
148
+ }
149
+ throwIfAborted(ctx.signal);
150
+ if (tool.canExecute) {
151
+ try {
152
+ const allowed = await tool.canExecute(input, ctx);
153
+ if (!allowed) {
154
+ yield failureEvent(base, {
155
+ code: 'not_authorized',
156
+ message: 'Tool execution not authorized',
157
+ });
158
+ return undefined;
159
+ }
160
+ }
161
+ catch (err) {
162
+ const msg = err instanceof Error ? err.message : String(err);
163
+ yield failureEvent(base, {
164
+ code: 'not_authorized',
165
+ message: `Authorization failed for '${tool.name}': ${msg}`,
166
+ });
167
+ return undefined;
168
+ }
169
+ }
170
+ if (tool.preflight) {
171
+ const pre = await tool.preflight(input, ctx);
172
+ if (pre) {
173
+ if (isToolPause(pre)) {
174
+ const pausePayload = { ...pre, input: pre.input ?? input };
175
+ yield toolEvent(base, { phase: 'pause', pause: pausePayload });
176
+ return undefined;
177
+ }
178
+ yield failureEvent(base, pre);
179
+ return undefined;
180
+ }
181
+ }
182
+ if (tool.interactive && ctx.resume?.value === undefined) {
183
+ const render = tool.interactive.render(input);
184
+ const interactivePause = {
185
+ kind: 'interactive',
186
+ tool: tool.name,
187
+ render,
188
+ input,
189
+ };
190
+ yield toolEvent(base, { phase: 'pause', pause: interactivePause });
191
+ return undefined;
192
+ }
193
+ throwIfAborted(ctx.signal);
194
+ try {
195
+ const output = yield* runHandler(tool.handler, input, ctx, base);
196
+ if (output === undefined) {
197
+ yield failureEvent(base, { code: 'invalid_output', message: 'Handler returned no output' });
198
+ return undefined;
199
+ }
200
+ const checked = tool.output.safeParse(output);
201
+ if (!checked.success) {
202
+ yield failureEvent(base, {
203
+ code: 'invalid_output',
204
+ message: 'Tool output validation failed',
205
+ details: checked.error.flatten(),
206
+ });
207
+ return undefined;
208
+ }
209
+ let finalOutput = checked.data;
210
+ if (ctx.profile.type !== 'speech' && ctx.profile.tools.t2Loader === tool.name) {
211
+ if (!snapshot) {
212
+ yield failureEvent(base, {
213
+ code: 'invalid_output',
214
+ message: `tools.t2Loader '${tool.name}' requires a turn tool snapshot`,
215
+ });
216
+ return undefined;
217
+ }
218
+ const loaded = extractLoadedIds(checked.data);
219
+ if (!loaded) {
220
+ yield failureEvent(base, {
221
+ code: 'invalid_output',
222
+ message: `T2 loader '${tool.name}' must return { loaded: string[] }`,
223
+ });
224
+ return undefined;
225
+ }
226
+ const { promoted, failure: promoteFailure } = promoteLoadedTools(snapshot, loaded, ctx.profile);
227
+ if (promoteFailure) {
228
+ yield failureEvent(base, promoteFailure);
229
+ return undefined;
230
+ }
231
+ finalOutput = { ...checked.data, loaded: promoted };
232
+ const rechecked = tool.output.safeParse(finalOutput);
233
+ if (!rechecked.success) {
234
+ yield failureEvent(base, {
235
+ code: 'invalid_output',
236
+ message: 'T2 loader output validation failed after promotion',
237
+ details: rechecked.error.flatten(),
238
+ });
239
+ return undefined;
240
+ }
241
+ finalOutput = rechecked.data;
242
+ }
243
+ const modelResult = projectForModel(tool, finalOutput);
244
+ yield toolEvent(base, { phase: 'complete', output: finalOutput });
245
+ return modelResult;
246
+ }
247
+ catch (err) {
248
+ const msg = err instanceof Error ? err.message : String(err);
249
+ yield failureEvent(base, { code: 'handler_error', message: msg });
250
+ return undefined;
251
+ }
252
+ }
253
+ export function notLoadedMessage(tool) {
254
+ if (tool.loadTier === 'T1') {
255
+ return `Tool '${tool.name}' is not wired — profile.tools.t1Policy must select it`;
256
+ }
257
+ if (tool.loadTier === 'T2') {
258
+ return `Tool '${tool.name}' is not loaded — run profile.tools.t2Loader first`;
259
+ }
260
+ return `Tool '${tool.name}' is not visible this turn`;
261
+ }
262
+ export function extractLoadedIds(output) {
263
+ if (!output || typeof output !== 'object' || Array.isArray(output)) {
264
+ return undefined;
265
+ }
266
+ const loaded = output.loaded;
267
+ if (!Array.isArray(loaded) || !loaded.every((id) => typeof id === 'string')) {
268
+ return undefined;
269
+ }
270
+ return loaded;
271
+ }
272
+ /** Strip prototype-pollution keys from provider/host tool args before validation. */
273
+ export function plainToolInput(input) {
274
+ if (input === null || typeof input !== 'object') {
275
+ return input;
276
+ }
277
+ if (Array.isArray(input)) {
278
+ return input.map(plainToolInput);
279
+ }
280
+ const out = {};
281
+ for (const key of Object.keys(input)) {
282
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
283
+ continue;
284
+ }
285
+ out[key] = plainToolInput(input[key]);
286
+ }
287
+ return out;
288
+ }
289
+ export async function* executeBuiltin(tool, ctx, base, snapshot) {
290
+ yield toolEvent(base, { phase: 'running' });
291
+ throwIfAborted(ctx.signal);
292
+ if (!snapshot.builtins.includes(tool.name)) {
293
+ yield failureEvent(base, {
294
+ code: 'not_loaded',
295
+ message: `Builtin '${tool.name}' is not enabled this turn`,
296
+ });
297
+ return undefined;
298
+ }
299
+ yield failureEvent(base, {
300
+ code: 'provider_native',
301
+ message: `Tool '${tool.name}' is a provider builtin — execution is handled by the model provider, not the kernel`,
302
+ });
303
+ return undefined;
304
+ }
305
+ export async function* executeRegisteredTool(args) {
306
+ const { profile, name, input, callId, ctx, snapshot } = args;
307
+ const tool = getTool(name);
308
+ const safeInput = plainToolInput(input);
309
+ const base = {
310
+ name,
311
+ callId,
312
+ arguments: typeof safeInput === 'object' && safeInput !== null && !Array.isArray(safeInput)
313
+ ? safeInput
314
+ : { value: safeInput },
315
+ };
316
+ if (!tool) {
317
+ yield failureEvent(base, { code: 'unknown_tool', message: `Tool '${name}' is not registered` });
318
+ return undefined;
319
+ }
320
+ if (tool.type === 'builtin') {
321
+ if (!snapshot) {
322
+ yield failureEvent(base, {
323
+ code: 'provider_native',
324
+ message: `Tool '${name}' is a provider builtin and requires a turn tool snapshot`,
325
+ });
326
+ return undefined;
327
+ }
328
+ const fullCtx = { ...ctx, callId, profile };
329
+ return yield* executeBuiltin(tool, fullCtx, base, snapshot);
330
+ }
331
+ if (profile.type === 'speech' || !profile.tools.allow.includes(name)) {
332
+ yield failureEvent(base, {
333
+ code: 'not_allowed',
334
+ message: `Tool '${name}' is not allowed on ${profile.id}`,
335
+ });
336
+ return undefined;
337
+ }
338
+ if (snapshot) {
339
+ const continuing = isResumeContinuation(ctx.resume);
340
+ if (!continuing && !snapshot.gated.includes(name)) {
341
+ yield failureEvent(base, {
342
+ code: 'not_gated',
343
+ message: `Tool '${name}' is not eligible on this turn (allow/path)`,
344
+ });
345
+ return undefined;
346
+ }
347
+ if (!snapshot.visible.includes(name)) {
348
+ const skipLoadCheck = continuing && tool.loadTier === 'T0';
349
+ if (!skipLoadCheck) {
350
+ yield failureEvent(base, {
351
+ code: 'not_loaded',
352
+ message: notLoadedMessage(tool),
353
+ });
354
+ return undefined;
355
+ }
356
+ }
357
+ }
358
+ const fullCtx = { ...ctx, callId, profile };
359
+ return yield* executeFunction(tool, safeInput, fullCtx, base, snapshot);
360
+ }
361
+ export function newCallId(name) {
362
+ return `call_${name}_${Date.now()}`;
363
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Built-in harness tools registered with THEORUM.
3
+ *
4
+ * @module
5
+ */
6
+ /** Register harness tools shipped with THEORUM. */
7
+ declare function registerHarnessTools(): void;
8
+ export { registerHarnessTools };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Built-in harness tools registered with THEORUM.
3
+ *
4
+ * @module
5
+ */
6
+ import { z } from 'zod';
7
+ import { registerTool } from './registry.js';
8
+ const AskInputSchema = z.object({
9
+ kind: z.enum(['confirm', 'choice', 'text']),
10
+ prompt: z.string().trim().min(1),
11
+ options: z.array(z.string()).optional(),
12
+ });
13
+ const AskOutputSchema = z.object({
14
+ answer: z.unknown(),
15
+ });
16
+ /** Register harness tools shipped with THEORUM. */
17
+ function registerHarnessTools() {
18
+ registerTool({
19
+ type: 'function',
20
+ name: 'ask_user',
21
+ description: 'Ask the user a question and wait for a response',
22
+ category: 'conversation',
23
+ access: 'read-only',
24
+ paths: ['*'],
25
+ loadTier: 'T0',
26
+ permission: 'auto',
27
+ input: AskInputSchema,
28
+ output: AskOutputSchema,
29
+ interactive: {
30
+ render: (input) => {
31
+ const ask = input;
32
+ return {
33
+ kind: ask.kind,
34
+ prompt: ask.prompt,
35
+ options: ask.options,
36
+ };
37
+ },
38
+ },
39
+ handler: (_input, ctx) => ({
40
+ answer: ctx.resume?.value ?? ctx.resume?.granted,
41
+ }),
42
+ });
43
+ }
44
+ export { registerHarnessTools };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Host-initiated tool execution entrypoint.
3
+ *
4
+ * @module
5
+ */
6
+ import type { TurnEvent } from '../types.js';
7
+ import type { InvokeToolRequest } from './types.js';
8
+ /** Execute a registered tool without calling a model provider. */
9
+ declare function invokeTool(request: InvokeToolRequest): AsyncGenerator<TurnEvent>;
10
+ export { invokeTool };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Host-initiated tool execution entrypoint.
3
+ *
4
+ * @module
5
+ */
6
+ import { toErrorEvent } from '../../guardrails/error.js';
7
+ import { getProfile } from '../registry/profiles.js';
8
+ import { pickModel } from '../registry/resolve.js';
9
+ import { executeRegisteredTool, newCallId } from './execute.js';
10
+ import { cloneTurnToolSnapshot, prepareTurnToolSnapshot, promoteLoadedTools } from './resolve.js';
11
+ function turnRequestFromInvoke(request) {
12
+ return {
13
+ profile: request.profile,
14
+ path: request.path,
15
+ sessionPermissions: request.sessionPermissions,
16
+ input: request.turnInput,
17
+ select: request.select,
18
+ };
19
+ }
20
+ async function prepareInvokeSnapshot(request, profile) {
21
+ const req = turnRequestFromInvoke(request);
22
+ const model = pickModel(profile, request.select);
23
+ return await prepareTurnToolSnapshot(profile, req, model);
24
+ }
25
+ /** Execute a registered tool without calling a model provider. */
26
+ async function* invokeTool(request) {
27
+ const profile = getProfile(request.profile);
28
+ const callId = newCallId(request.name);
29
+ const snapshot = request.snapshot
30
+ ? cloneTurnToolSnapshot(request.snapshot)
31
+ : await prepareInvokeSnapshot(request, profile);
32
+ if (request.promoted?.length) {
33
+ const { failure } = promoteLoadedTools(snapshot, request.promoted, profile);
34
+ if (failure) {
35
+ yield {
36
+ type: 'tool',
37
+ tool: {
38
+ name: request.name,
39
+ callId,
40
+ phase: 'error',
41
+ failure,
42
+ },
43
+ };
44
+ yield { type: 'done', stop: { kind: 'tool' } };
45
+ return;
46
+ }
47
+ }
48
+ let sawPause = false;
49
+ let sawError = false;
50
+ try {
51
+ for await (const event of executeRegisteredTool({
52
+ profile,
53
+ name: request.name,
54
+ input: request.input,
55
+ callId,
56
+ ctx: {
57
+ sessionPermissions: request.sessionPermissions,
58
+ path: request.path,
59
+ signal: request.signal,
60
+ resume: request.resume,
61
+ },
62
+ snapshot,
63
+ })) {
64
+ yield event;
65
+ if (event.type !== 'tool') {
66
+ continue;
67
+ }
68
+ if (event.tool?.phase === 'pause') {
69
+ sawPause = true;
70
+ }
71
+ if (event.tool?.phase === 'error') {
72
+ sawError = true;
73
+ }
74
+ }
75
+ }
76
+ catch (err) {
77
+ yield toErrorEvent(err);
78
+ sawError = true;
79
+ }
80
+ const stopKind = sawError || sawPause ? 'tool' : 'completed';
81
+ yield { type: 'done', stop: { kind: stopKind } };
82
+ }
83
+ export { invokeTool };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * THEORUM tool registry and execution.
3
+ *
4
+ * @module
5
+ */
6
+ export { formatToolResult } from './execute.js';
7
+ export { registerHarnessTools } from './harness.js';
8
+ export { invokeTool } from './invoke.js';
9
+ export { getTool, hasTool, listBuiltinIds, listFunctionIds, listTools, registerTool, registerTools, requireTool, resetTools, } from './registry.js';
10
+ export { cloneTurnToolSnapshot, prepareTurnToolSnapshot } from './resolve.js';
11
+ export type * from './types.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * THEORUM tool registry and execution.
3
+ *
4
+ * @module
5
+ */
6
+ export { formatToolResult } from './execute.js';
7
+ export { registerHarnessTools } from './harness.js';
8
+ export { invokeTool } from './invoke.js';
9
+ export { getTool, hasTool, listBuiltinIds, listFunctionIds, listTools, registerTool, registerTools, requireTool, resetTools, } from './registry.js';
10
+ export { cloneTurnToolSnapshot, prepareTurnToolSnapshot } from './resolve.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tool projection for host/UI inspection.
3
+ *
4
+ * @module
5
+ */
6
+ import type { Profile, ToolId } from '../types.js';
7
+ import type { RegisteredTool } from './types.js';
8
+ declare function projectTools(profile: Profile): Array<RegisteredTool | {
9
+ name: ToolId;
10
+ missing: true;
11
+ }>;
12
+ export { projectTools };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tool projection for host/UI inspection.
3
+ *
4
+ * @module
5
+ */
6
+ import { getTool } from './registry.js';
7
+ function projectTool(name) {
8
+ const tool = getTool(name);
9
+ if (!tool) {
10
+ return { name, missing: true };
11
+ }
12
+ return tool;
13
+ }
14
+ function builtInToolIds(profile) {
15
+ const seen = new Set();
16
+ for (const modelId of profile.model.allow) {
17
+ const spec = profile.model.config[modelId];
18
+ for (const id of spec?.builtInTools ?? []) {
19
+ seen.add(id);
20
+ }
21
+ }
22
+ return [...seen];
23
+ }
24
+ function profileAllow(profile) {
25
+ if (profile.type === 'speech') {
26
+ return [];
27
+ }
28
+ return profile.tools.allow;
29
+ }
30
+ function projectTools(profile) {
31
+ const ids = [...profileAllow(profile), ...builtInToolIds(profile)];
32
+ return ids.map((name) => projectTool(name));
33
+ }
34
+ export { projectTools };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Process-local tool registry.
3
+ *
4
+ * Registration is not synchronized — hosts must register tools at startup before
5
+ * concurrent turns or invokeTool calls. Reads during execution are safe under Deno's
6
+ * single-threaded event loop; concurrent mutation of a shared TurnToolSnapshot is
7
+ * avoided by cloneTurnToolSnapshot on invokeTool entry.
8
+ *
9
+ * @module
10
+ */
11
+ import type { RegisteredTool, ToolDefinitionInput } from './types.js';
12
+ /** Register or replace a tool definition. */
13
+ declare function registerTool<TIn, TOut>(def: ToolDefinitionInput<TIn, TOut>): RegisteredTool<TIn, TOut>;
14
+ /** Register several tools in order. */
15
+ declare function registerTools(defs: ToolDefinitionInput[]): RegisteredTool[];
16
+ declare function getTool(name: string): RegisteredTool | undefined;
17
+ declare function requireTool(name: string): RegisteredTool;
18
+ declare function hasTool(name: string): boolean;
19
+ declare function listTools(): RegisteredTool[];
20
+ declare function listBuiltinIds(): string[];
21
+ declare function listFunctionIds(): string[];
22
+ declare function resetTools(): void;
23
+ export { getTool, hasTool, listBuiltinIds, listFunctionIds, listTools, registerTool, registerTools, requireTool, resetTools, };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Process-local tool registry.
3
+ *
4
+ * Registration is not synchronized — hosts must register tools at startup before
5
+ * concurrent turns or invokeTool calls. Reads during execution are safe under Deno's
6
+ * single-threaded event loop; concurrent mutation of a shared TurnToolSnapshot is
7
+ * avoided by cloneTurnToolSnapshot on invokeTool entry.
8
+ *
9
+ * @module
10
+ */
11
+ import { TheorumError } from '../../guardrails/error.js';
12
+ import { jsonSchemaFromZod, validateToolInputSchema, validateToolOutputSchema } from './schema.js';
13
+ const tools = new Map();
14
+ function normalizeFunction(def) {
15
+ const inputSchema = jsonSchemaFromZod(def.input, 'input');
16
+ validateToolInputSchema(inputSchema);
17
+ const outputSchema = jsonSchemaFromZod(def.output, 'output');
18
+ validateToolOutputSchema(outputSchema);
19
+ return {
20
+ ...def,
21
+ type: 'function',
22
+ inputSchema,
23
+ outputSchema,
24
+ };
25
+ }
26
+ function normalizeToolDefinition(def) {
27
+ if (def.type === 'builtin') {
28
+ return def;
29
+ }
30
+ return normalizeFunction(def);
31
+ }
32
+ /** Register or replace a tool definition. */
33
+ function registerTool(def) {
34
+ const normalized = normalizeToolDefinition(def);
35
+ tools.set(normalized.name, normalized);
36
+ return normalized;
37
+ }
38
+ /** Register several tools in order. */
39
+ function registerTools(defs) {
40
+ return defs.map((def) => registerTool(def));
41
+ }
42
+ function getTool(name) {
43
+ return tools.get(name);
44
+ }
45
+ function requireTool(name) {
46
+ const tool = getTool(name);
47
+ if (!tool) {
48
+ throw new TheorumError(`Tool '${name}' is not registered`);
49
+ }
50
+ return tool;
51
+ }
52
+ function hasTool(name) {
53
+ return tools.has(name);
54
+ }
55
+ function listTools() {
56
+ return [...tools.values()];
57
+ }
58
+ function listBuiltinIds() {
59
+ return listTools()
60
+ .filter((t) => t.type === 'builtin')
61
+ .map((t) => t.name);
62
+ }
63
+ function listFunctionIds() {
64
+ return listTools()
65
+ .filter((t) => t.type === 'function')
66
+ .map((t) => t.name);
67
+ }
68
+ function resetTools() {
69
+ tools.clear();
70
+ }
71
+ export { getTool, hasTool, listBuiltinIds, listFunctionIds, listTools, registerTool, registerTools, requireTool, resetTools, };