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,29 @@
1
+ /**
2
+ * Turn tool visibility and wire snapshot resolution.
3
+ *
4
+ * @module
5
+ */
6
+ import type { ModelId, Profile, ToolId, TurnRequest } from '../types.js';
7
+ import type { PromoteLoadedResult, ToolFailure, TurnToolSnapshot, WireFunctionTool } from './types.js';
8
+ export declare function pathMatches(catalogPaths?: string[], turnPath?: string): boolean;
9
+ export declare function applyBuiltinMutualExclusions(requested: string[]): string[];
10
+ export declare function resolveAllowedCustomToolIds(profile: Profile, req: TurnRequest): ToolId[];
11
+ /** Provider builtins listed on the selected model — on for the turn (path-filtered). */
12
+ export declare function resolveModelBuiltinIds(profile: Profile, req: TurnRequest, modelId: ModelId): ToolId[];
13
+ export declare function wireForTool(name: string): WireFunctionTool | undefined;
14
+ export declare function buildWire(visible: ToolId[]): WireFunctionTool[];
15
+ export declare function promoteTool(state: TurnToolSnapshot, id: ToolId): void;
16
+ export declare function promoteBuiltin(state: TurnToolSnapshot, id: ToolId): void;
17
+ export declare function initialVisible(gated: ToolId[]): ToolId[];
18
+ export declare function initialBuiltins(gated: ToolId[]): ToolId[];
19
+ /** Build the initial tool snapshot for a turn (T0 wired; T1/T2 pending). */
20
+ export declare function resolveTurnTools(profile: Profile, req: TurnRequest, modelId: ModelId): TurnToolSnapshot;
21
+ /** Resolve T0 snapshot and expand T1 selections from `profile.tools.t1Policy`. */
22
+ export declare function prepareTurnToolSnapshot(profile: Profile, req: TurnRequest, modelId: ModelId): Promise<TurnToolSnapshot>;
23
+ /** Deep-clone a turn snapshot so host-side concurrent invokes do not share mutable state. */
24
+ export declare function cloneTurnToolSnapshot(state: TurnToolSnapshot): TurnToolSnapshot;
25
+ /** Wire T1 tools selected by `profile.tools.t1Policy`. */
26
+ export declare function expandT1Policy(state: TurnToolSnapshot, profile: Profile, req: TurnRequest): Promise<void>;
27
+ /** Promote T2 tools into the visible set after tools.t2Loader returns { loaded }. */
28
+ export declare function promoteLoadedTools(state: TurnToolSnapshot, loaded: string[], profile: Profile): PromoteLoadedResult;
29
+ export declare function promotionFailure(id: string, profile: Profile): ToolFailure | undefined;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Turn tool visibility and wire snapshot resolution.
3
+ *
4
+ * @module
5
+ */
6
+ import { TheorumError } from '../../guardrails/error.js';
7
+ import { getTool } from './registry.js';
8
+ export function pathMatches(catalogPaths, turnPath) {
9
+ if (!catalogPaths || catalogPaths.includes('*')) {
10
+ return true;
11
+ }
12
+ if (!turnPath) {
13
+ return false;
14
+ }
15
+ return catalogPaths.includes(turnPath);
16
+ }
17
+ export function applyBuiltinMutualExclusions(requested) {
18
+ return requested.filter((id) => {
19
+ const tool = getTool(id);
20
+ if (tool?.type !== 'builtin') {
21
+ return true;
22
+ }
23
+ const conflicts = tool.conflictsWith ?? [];
24
+ return !conflicts.some((other) => requested.includes(other));
25
+ });
26
+ }
27
+ export function resolveAllowedCustomToolIds(profile, req) {
28
+ if (profile.type === 'speech') {
29
+ return [];
30
+ }
31
+ return profile.tools.allow.filter((id) => {
32
+ const tool = getTool(id);
33
+ if (!tool || tool.type === 'builtin') {
34
+ return false;
35
+ }
36
+ return pathMatches(tool.paths, req.path);
37
+ });
38
+ }
39
+ /** Provider builtins listed on the selected model — on for the turn (path-filtered). */
40
+ export function resolveModelBuiltinIds(profile, req, modelId) {
41
+ const spec = profile.model.config[modelId];
42
+ if (!spec) {
43
+ return [];
44
+ }
45
+ return (spec.builtInTools ?? []).filter((id) => {
46
+ const tool = getTool(id);
47
+ if (tool?.type !== 'builtin') {
48
+ return false;
49
+ }
50
+ return pathMatches(tool.paths, req.path);
51
+ });
52
+ }
53
+ export function wireForTool(name) {
54
+ const tool = getTool(name);
55
+ if (!tool || tool.type === 'builtin') {
56
+ return undefined;
57
+ }
58
+ return {
59
+ type: 'function',
60
+ name: tool.name,
61
+ description: tool.description,
62
+ parameters: tool.inputSchema,
63
+ };
64
+ }
65
+ export function buildWire(visible) {
66
+ const out = [];
67
+ for (const id of visible) {
68
+ const wire = wireForTool(id);
69
+ if (wire) {
70
+ out.push(wire);
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ export function promoteTool(state, id) {
76
+ if (state.visible.includes(id)) {
77
+ return;
78
+ }
79
+ state.visible.push(id);
80
+ const wire = wireForTool(id);
81
+ if (wire && !state.wire.some((w) => w.name === id)) {
82
+ state.wire.push(wire);
83
+ }
84
+ }
85
+ export function promoteBuiltin(state, id) {
86
+ if (state.builtins.includes(id)) {
87
+ return;
88
+ }
89
+ const tool = getTool(id);
90
+ if (tool?.type !== 'builtin') {
91
+ return;
92
+ }
93
+ const conflicts = tool.conflictsWith ?? [];
94
+ state.builtins = state.builtins.filter((existing) => {
95
+ if (conflicts.includes(existing)) {
96
+ return false;
97
+ }
98
+ const existingTool = getTool(existing);
99
+ return !(existingTool?.type === 'builtin' && existingTool.conflictsWith?.includes(id));
100
+ });
101
+ state.builtins.push(id);
102
+ }
103
+ export function initialVisible(gated) {
104
+ return gated.filter((id) => getTool(id)?.loadTier === 'T0');
105
+ }
106
+ export function initialBuiltins(gated) {
107
+ return applyBuiltinMutualExclusions(gated.filter((id) => {
108
+ const tool = getTool(id);
109
+ return tool?.type === 'builtin' && tool.loadTier === 'T0';
110
+ }));
111
+ }
112
+ /** Build the initial tool snapshot for a turn (T0 wired; T1/T2 pending). */
113
+ export function resolveTurnTools(profile, req, modelId) {
114
+ const customAllowed = resolveAllowedCustomToolIds(profile, req);
115
+ const modelBuiltins = resolveModelBuiltinIds(profile, req, modelId);
116
+ const gated = [...customAllowed, ...modelBuiltins];
117
+ const builtins = initialBuiltins(gated);
118
+ const visible = initialVisible(gated);
119
+ const executable = visible.filter((id) => getTool(id)?.type !== 'builtin');
120
+ return {
121
+ builtins,
122
+ gated,
123
+ visible,
124
+ executable,
125
+ path: req.path,
126
+ sessionPermissions: req.sessionPermissions,
127
+ wire: buildWire(visible),
128
+ };
129
+ }
130
+ /** Resolve T0 snapshot and expand T1 selections from `profile.tools.t1Policy`. */
131
+ export async function prepareTurnToolSnapshot(profile, req, modelId) {
132
+ const snapshot = resolveTurnTools(profile, req, modelId);
133
+ await expandT1Policy(snapshot, profile, req);
134
+ return snapshot;
135
+ }
136
+ /** Deep-clone a turn snapshot so host-side concurrent invokes do not share mutable state. */
137
+ export function cloneTurnToolSnapshot(state) {
138
+ return {
139
+ builtins: [...state.builtins],
140
+ gated: [...state.gated],
141
+ visible: [...state.visible],
142
+ executable: [...state.executable],
143
+ path: state.path,
144
+ sessionPermissions: state.sessionPermissions ? [...state.sessionPermissions] : undefined,
145
+ wire: state.wire.map((w) => ({ ...w, parameters: structuredClone(w.parameters) })),
146
+ };
147
+ }
148
+ /** Wire T1 tools selected by `profile.tools.t1Policy`. */
149
+ export async function expandT1Policy(state, profile, req) {
150
+ if (profile.type === 'speech') {
151
+ return;
152
+ }
153
+ const t1Policy = profile.tools.t1Policy;
154
+ if (!t1Policy) {
155
+ return;
156
+ }
157
+ let selected;
158
+ try {
159
+ selected = await t1Policy({
160
+ profile,
161
+ input: req.input,
162
+ path: req.path,
163
+ sessionPermissions: req.sessionPermissions,
164
+ gated: state.gated,
165
+ });
166
+ }
167
+ catch (err) {
168
+ const msg = err instanceof Error ? err.message : String(err);
169
+ throw new TheorumError(`Profile '${profile.id}' tools.t1Policy rejected: ${msg}`, {
170
+ cause: err,
171
+ });
172
+ }
173
+ if (!Array.isArray(selected)) {
174
+ throw new TheorumError(`Profile '${profile.id}' tools.t1Policy must return ToolId[]`);
175
+ }
176
+ for (const id of selected) {
177
+ if (!state.gated.includes(id)) {
178
+ continue;
179
+ }
180
+ const tool = getTool(id);
181
+ // T0 tools are already visible; t1Policy may promote T1/T2 gated tools at turn start.
182
+ if (!tool || tool.loadTier === 'T0') {
183
+ continue;
184
+ }
185
+ if (tool.type === 'builtin') {
186
+ promoteBuiltin(state, id);
187
+ }
188
+ else {
189
+ promoteTool(state, id);
190
+ }
191
+ }
192
+ state.executable = state.visible.filter((id) => getTool(id)?.type !== 'builtin');
193
+ }
194
+ const LOADED_ID_BLOCKLIST = new Set(['__proto__', 'constructor', 'prototype']);
195
+ /** Promote T2 tools into the visible set after tools.t2Loader returns { loaded }. */
196
+ export function promoteLoadedTools(state, loaded, profile) {
197
+ const toPromote = [];
198
+ for (const id of loaded) {
199
+ if (typeof id !== 'string' || LOADED_ID_BLOCKLIST.has(id)) {
200
+ return {
201
+ promoted: [],
202
+ failure: {
203
+ code: 'invalid_output',
204
+ message: 'tools.t2Loader loaded ids must be plain strings',
205
+ },
206
+ };
207
+ }
208
+ const failure = promotionFailure(id, profile);
209
+ if (failure) {
210
+ return { promoted: [], failure };
211
+ }
212
+ const tool = getTool(id);
213
+ if (!tool) {
214
+ return {
215
+ promoted: [],
216
+ failure: {
217
+ code: 'invalid_output',
218
+ message: `Tool '${id}' is not registered`,
219
+ },
220
+ };
221
+ }
222
+ if (!pathMatches(tool.paths, state.path) || !state.gated.includes(id)) {
223
+ continue;
224
+ }
225
+ toPromote.push(id);
226
+ }
227
+ const promoted = [];
228
+ for (const id of toPromote) {
229
+ promoteTool(state, id);
230
+ promoted.push(id);
231
+ }
232
+ state.executable = state.visible.filter((tid) => getTool(tid)?.type !== 'builtin');
233
+ return { promoted };
234
+ }
235
+ export function promotionFailure(id, profile) {
236
+ if (profile.type === 'speech' || !profile.tools.allow.includes(id)) {
237
+ return {
238
+ code: 'invalid_output',
239
+ message: `tools.t2Loader attempted to promote tool '${id}' outside profile allow`,
240
+ };
241
+ }
242
+ const tool = getTool(id);
243
+ if (!tool) {
244
+ return {
245
+ code: 'invalid_output',
246
+ message: `tools.t2Loader attempted to promote unknown tool '${id}'`,
247
+ };
248
+ }
249
+ if (tool.type === 'builtin') {
250
+ return {
251
+ code: 'invalid_output',
252
+ message: `tools.t2Loader attempted to promote builtin '${id}' — only custom tools may be promoted`,
253
+ };
254
+ }
255
+ if (tool.loadTier !== 'T2') {
256
+ return {
257
+ code: 'invalid_output',
258
+ message: `tools.t2Loader attempted to promote tool '${id}' with loadTier '${tool.loadTier}' — only T2 tools may be promoted`,
259
+ };
260
+ }
261
+ return undefined;
262
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tool JSON Schema validation and Zod → wire schema conversion.
3
+ *
4
+ * @module
5
+ */
6
+ import { type ZodType } from 'zod';
7
+ export type JsonSchema = Record<string, unknown>;
8
+ /** Validate a tool parameter schema for provider compatibility. */
9
+ declare function validateToolInputSchema(schema: JsonSchema, mode?: 'gemini' | 'structural'): void;
10
+ /** Validate a tool result schema at registration. */
11
+ declare function validateToolOutputSchema(schema: JsonSchema, mode?: 'gemini' | 'structural'): void;
12
+ declare function jsonSchemaFromZod(schema: ZodType, io?: 'input' | 'output'): JsonSchema;
13
+ export { jsonSchemaFromZod, validateToolInputSchema, validateToolOutputSchema };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Tool JSON Schema validation and Zod → wire schema conversion.
3
+ *
4
+ * @module
5
+ */
6
+ import { z } from 'zod';
7
+ import { TheorumError } from '../../guardrails/error.js';
8
+ const GEMINI_SUPPORTED_SCHEMA_KEYS = [
9
+ 'type',
10
+ 'format',
11
+ 'title',
12
+ 'description',
13
+ 'nullable',
14
+ 'enum',
15
+ 'default',
16
+ 'example',
17
+ 'items',
18
+ 'minItems',
19
+ 'maxItems',
20
+ 'properties',
21
+ 'required',
22
+ 'propertyOrdering',
23
+ 'minProperties',
24
+ 'maxProperties',
25
+ 'minimum',
26
+ 'maximum',
27
+ 'pattern',
28
+ 'minLength',
29
+ 'maxLength',
30
+ 'anyOf',
31
+ 'oneOf',
32
+ ];
33
+ function validateGeminiKeys(schema, path, errors) {
34
+ for (const key of Object.keys(schema)) {
35
+ if (!GEMINI_SUPPORTED_SCHEMA_KEYS.includes(key)) {
36
+ errors.push(`${path}: unsupported Gemini schema key '${key}'`);
37
+ }
38
+ }
39
+ }
40
+ function validateSchemaShape(schema, path, mode, errors) {
41
+ if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
42
+ errors.push(`${path}: schema must be an object`);
43
+ return false;
44
+ }
45
+ const hasType = typeof schema.type === 'string' || Array.isArray(schema.type);
46
+ const hasCombinator = schema.anyOf !== undefined || schema.oneOf !== undefined;
47
+ if (!hasType && !hasCombinator) {
48
+ if (mode === 'structural' && path !== '$') {
49
+ return true;
50
+ }
51
+ errors.push(`${path}: missing type or combinator`);
52
+ }
53
+ if (mode === 'gemini') {
54
+ validateGeminiKeys(schema, path, errors);
55
+ }
56
+ if (schema.type === 'array' && schema.items === undefined) {
57
+ errors.push(`${path}: array schema must define items`);
58
+ }
59
+ return true;
60
+ }
61
+ function walkSchemaProperties(props, required, path, mode, errors) {
62
+ const reqList = Array.isArray(required) ? required : [];
63
+ for (const req of reqList) {
64
+ if (typeof req === 'string' && !(req in props)) {
65
+ errors.push(`${path}: required key '${req}' missing from properties`);
66
+ }
67
+ }
68
+ for (const [key, child] of Object.entries(props)) {
69
+ if (child && typeof child === 'object') {
70
+ walkSchema(child, `${path}.properties.${key}`, mode, errors);
71
+ }
72
+ }
73
+ }
74
+ function walkSchema(schema, path, mode, errors) {
75
+ if (!validateSchemaShape(schema, path, mode, errors))
76
+ return;
77
+ const props = schema.properties;
78
+ if (props && typeof props === 'object' && !Array.isArray(props)) {
79
+ walkSchemaProperties(props, schema.required, path, mode, errors);
80
+ }
81
+ if (schema.items && typeof schema.items === 'object' && !Array.isArray(schema.items)) {
82
+ walkSchema(schema.items, `${path}.items`, mode, errors);
83
+ }
84
+ for (const combinator of ['anyOf', 'oneOf']) {
85
+ const branch = schema[combinator];
86
+ if (Array.isArray(branch)) {
87
+ branch.forEach((entry, index) => {
88
+ if (entry && typeof entry === 'object') {
89
+ walkSchema(entry, `${path}.${combinator}[${index}]`, mode, errors);
90
+ }
91
+ });
92
+ }
93
+ }
94
+ }
95
+ /** Validate a tool wire schema for provider compatibility. */
96
+ function validateToolWireSchema(schema, mode = 'gemini', label = 'input') {
97
+ const errors = [];
98
+ walkSchema(schema, '$', mode, errors);
99
+ if (errors.length > 0) {
100
+ throw new TheorumError(`Invalid tool ${label} schema: ${errors.join('; ')}`);
101
+ }
102
+ }
103
+ /** Validate a tool parameter schema for provider compatibility. */
104
+ function validateToolInputSchema(schema, mode = 'gemini') {
105
+ validateToolWireSchema(schema, mode, 'input');
106
+ }
107
+ /** Validate a tool result schema at registration. */
108
+ function validateToolOutputSchema(schema, mode = 'structural') {
109
+ validateToolWireSchema(schema, mode, 'output');
110
+ }
111
+ function stripProperties(value) {
112
+ const props = {};
113
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
114
+ for (const [propKey, propValue] of Object.entries(value)) {
115
+ if (propValue && typeof propValue === 'object') {
116
+ props[propKey] = stripUnsupportedGeminiKeys(propValue);
117
+ }
118
+ }
119
+ }
120
+ return props;
121
+ }
122
+ function stripBranches(branches) {
123
+ return branches.map((entry) => entry && typeof entry === 'object' ? stripUnsupportedGeminiKeys(entry) : entry);
124
+ }
125
+ /** Derive provider wire JSON Schema from a Zod type. */
126
+ function stripUnsupportedGeminiKeys(schema) {
127
+ const out = {};
128
+ for (const [key, value] of Object.entries(schema)) {
129
+ if (!GEMINI_SUPPORTED_SCHEMA_KEYS.includes(key)) {
130
+ continue;
131
+ }
132
+ if (key === 'properties') {
133
+ out.properties = stripProperties(value);
134
+ continue;
135
+ }
136
+ if (key === 'items' && value && typeof value === 'object' && !Array.isArray(value)) {
137
+ out.items = stripUnsupportedGeminiKeys(value);
138
+ continue;
139
+ }
140
+ if ((key === 'anyOf' || key === 'oneOf') && Array.isArray(value)) {
141
+ out[key] = stripBranches(value);
142
+ continue;
143
+ }
144
+ out[key] = value;
145
+ }
146
+ return out;
147
+ }
148
+ function jsonSchemaFromZod(schema, io = 'output') {
149
+ const json = z.toJSONSchema(schema, {
150
+ target: 'openApi3',
151
+ io,
152
+ });
153
+ delete json.$schema;
154
+ if (!json.type) {
155
+ json.type = 'object';
156
+ }
157
+ return stripUnsupportedGeminiKeys(json);
158
+ }
159
+ export { jsonSchemaFromZod, validateToolInputSchema, validateToolOutputSchema };
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Tool registry types — single catalog, shared execution.
3
+ *
4
+ * Closed unions (`TOOL_*`) live in `../schema.ts`. This module owns the
5
+ * structural contracts built on those unions.
6
+ *
7
+ * @module
8
+ */
9
+ import type { z } from 'zod';
10
+ import type { ToolAccess, ToolLoadTier, ToolPermission } from '../schema.js';
11
+ import type { Profile, ToolId, TurnInput } from '../types.js';
12
+ export type { ToolAccess, ToolPermission };
13
+ export interface ToolLabels {
14
+ activity?: string;
15
+ activityPast?: string;
16
+ hiddenFromSettings?: boolean;
17
+ }
18
+ export interface ToolBase {
19
+ name: string;
20
+ description: string;
21
+ category: string;
22
+ access: ToolAccess;
23
+ paths: string[];
24
+ loadTier: ToolLoadTier;
25
+ permission: ToolPermission;
26
+ labels?: ToolLabels;
27
+ }
28
+ export interface BuiltinWire {
29
+ interactions?: string;
30
+ openRouter?: string;
31
+ live?: string;
32
+ }
33
+ export interface BuiltinToolDef extends ToolBase {
34
+ type: 'builtin';
35
+ wire: BuiltinWire;
36
+ conflictsWith?: string[];
37
+ /** When enabled, select the paid Vault key slot unless model.spec.key overrides. */
38
+ forcePaidKey?: boolean;
39
+ }
40
+ export interface InteractiveRender {
41
+ kind: string;
42
+ prompt: string;
43
+ options?: string[];
44
+ [key: string]: unknown;
45
+ }
46
+ export interface InteractiveConfig<TIn = unknown> {
47
+ render: (input: TIn) => InteractiveRender;
48
+ }
49
+ export interface InvokeToolResume {
50
+ value?: unknown;
51
+ granted?: boolean;
52
+ }
53
+ export interface ToolContext {
54
+ profile: Profile;
55
+ callId: string;
56
+ sessionPermissions?: string[];
57
+ path?: string;
58
+ signal?: AbortSignal;
59
+ turn?: {
60
+ step: number;
61
+ };
62
+ resume?: InvokeToolResume;
63
+ }
64
+ export interface ToolFailure {
65
+ code: string;
66
+ message: string;
67
+ details?: unknown;
68
+ }
69
+ export interface ToolPause {
70
+ kind: 'interactive' | 'confirmation' | 'permission';
71
+ tool: string;
72
+ render?: InteractiveRender;
73
+ summary?: string;
74
+ input: unknown;
75
+ permission?: ToolPermission;
76
+ }
77
+ export interface ToolWarning {
78
+ code: string;
79
+ message: string;
80
+ severity?: 'info' | 'warning' | 'error';
81
+ }
82
+ export interface ToolTraceStep {
83
+ name: string;
84
+ kind: string;
85
+ status: string;
86
+ inputs?: Record<string, unknown>;
87
+ outputs?: Record<string, unknown>;
88
+ }
89
+ export type ToolStreamEvent<TOut = unknown> = {
90
+ kind: 'progress';
91
+ data: unknown;
92
+ } | {
93
+ kind: 'trace';
94
+ step: ToolTraceStep;
95
+ } | {
96
+ kind: 'artifact';
97
+ artifact: unknown;
98
+ } | {
99
+ kind: 'warning';
100
+ warning: ToolWarning;
101
+ } | {
102
+ kind: 'complete';
103
+ output: TOut;
104
+ };
105
+ export type SyncToolHandler<TIn, TOut> = (input: TIn, ctx: ToolContext) => TOut | Promise<TOut>;
106
+ export type StreamToolHandler<TIn, TOut> = (input: TIn, ctx: ToolContext) => AsyncGenerator<ToolStreamEvent<TOut>>;
107
+ export type ToolHandler<TIn, TOut> = SyncToolHandler<TIn, TOut> | StreamToolHandler<TIn, TOut>;
108
+ export interface FunctionToolDef<TIn = unknown, TOut = unknown> extends ToolBase {
109
+ type: 'function';
110
+ input: z.ZodType<TIn>;
111
+ output: z.ZodType<TOut>;
112
+ inputSchema: Record<string, unknown>;
113
+ outputSchema: Record<string, unknown>;
114
+ handler: ToolHandler<TIn, TOut>;
115
+ interactive?: InteractiveConfig<TIn>;
116
+ canExecute?: (input: TIn, ctx: ToolContext) => boolean | Promise<boolean>;
117
+ preflight?: (input: TIn, ctx: ToolContext) => undefined | ToolFailure | ToolPause | Promise<undefined | ToolFailure | ToolPause>;
118
+ exposeToModel?: boolean;
119
+ }
120
+ export type RegisteredTool<TIn = unknown, TOut = unknown> = BuiltinToolDef | FunctionToolDef<TIn, TOut>;
121
+ export type ToolDefinitionInput<TIn = unknown, TOut = unknown> = BuiltinToolDef | (Omit<FunctionToolDef<TIn, TOut>, 'inputSchema' | 'outputSchema'> & {
122
+ input: z.ZodType<TIn>;
123
+ output: z.ZodType<TOut>;
124
+ });
125
+ export interface WireFunctionTool {
126
+ type: 'function';
127
+ name: string;
128
+ description: string;
129
+ parameters: Record<string, unknown>;
130
+ }
131
+ export interface TurnToolSnapshot {
132
+ builtins: ToolId[];
133
+ /** Tool ids eligible this turn (custom: allow + path; builtin: model builtInTools + path). */
134
+ gated: ToolId[];
135
+ /** Schemas sent to the provider (respects loadTier + t2Loader promotion). */
136
+ visible: ToolId[];
137
+ /** Kernel-executable tools: eligible, visible, and loaded (excludes builtins). */
138
+ executable: ToolId[];
139
+ path?: string;
140
+ sessionPermissions?: string[];
141
+ wire: WireFunctionTool[];
142
+ }
143
+ export interface PromoteLoadedResult {
144
+ promoted: ToolId[];
145
+ failure?: ToolFailure;
146
+ }
147
+ /** Context for profile T1 tool selection via `profile.tools.t1Policy`. */
148
+ export interface ToolLoadContext {
149
+ profile: Profile;
150
+ input?: TurnInput;
151
+ path?: string;
152
+ sessionPermissions?: string[];
153
+ /** Tool ids eligible this turn. */
154
+ gated: ToolId[];
155
+ }
156
+ /** Profile-owned T1 selection — which eligible T1 tools to wire at turn start. */
157
+ export type ToolPolicy = (ctx: ToolLoadContext) => ToolId[] | Promise<ToolId[]>;
158
+ export interface InvokeToolRequest {
159
+ profile: string;
160
+ name: string;
161
+ input: unknown;
162
+ /** Turn input context for `profile.tools.t1Policy` selection (same as `TurnRequest.input`). */
163
+ turnInput?: TurnInput;
164
+ /**
165
+ * T2 tools already promoted for this invoke (e.g. restored from pause metadata).
166
+ * Host must have run tools.t2Loader (or equivalent) before listing ids here.
167
+ */
168
+ promoted?: ToolId[];
169
+ /** Model select key — same as `TurnRequest.select` (builtins resolve from that model). */
170
+ select?: string;
171
+ /**
172
+ * Optional turn snapshot from a paused turn. Cloned before use so concurrent host
173
+ * invokes do not share mutable visibility state.
174
+ */
175
+ snapshot?: TurnToolSnapshot;
176
+ resume?: InvokeToolResume;
177
+ sessionPermissions?: string[];
178
+ path?: string;
179
+ signal?: AbortSignal;
180
+ }
181
+ export interface ProfileToolsSpec {
182
+ /** Custom function tools this profile may run. Builtins live on model specs. */
183
+ allow: ToolId[];
184
+ /**
185
+ * Optional T1 policy — returns which eligible T1 tools to wire at turn start.
186
+ * Tools must already be on `allow` (custom) or `builtInTools` (builtin) and `loadTier: 'T1'`.
187
+ */
188
+ t1Policy?: ToolPolicy;
189
+ /**
190
+ * Optional designated function tool id for T2 promotion.
191
+ * Must be in `allow`. When that tool completes with `{ loaded: string[] }`, those T2 ids are promoted.
192
+ */
193
+ t2Loader?: ToolId;
194
+ }
195
+ export interface ModelToolResult {
196
+ finding: string;
197
+ data?: unknown;
198
+ }
199
+ export type ToolCallPhase = 'running' | 'progress' | 'trace' | 'artifact' | 'warning' | 'complete' | 'pause' | 'error'
200
+ /** Provider cancelled an in-flight tool call (e.g. live barge-in). */
201
+ | 'cancel';
202
+ export interface ToolCallEvent {
203
+ name: string;
204
+ /** Provider-native id or kernel-assigned call id. */
205
+ callId?: string;
206
+ arguments?: Record<string, unknown>;
207
+ /** Absent on raw provider tool-call events; set by kernel execution. */
208
+ phase?: ToolCallPhase;
209
+ data?: unknown;
210
+ step?: ToolTraceStep;
211
+ artifact?: unknown;
212
+ warning?: ToolWarning;
213
+ output?: unknown;
214
+ pause?: ToolPause;
215
+ failure?: ToolFailure;
216
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tool registry types — single catalog, shared execution.
3
+ *
4
+ * Closed unions (`TOOL_*`) live in `../schema.ts`. This module owns the
5
+ * structural contracts built on those unions.
6
+ *
7
+ * @module
8
+ */
9
+ export {};