theorum 0.1.15 → 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 +177 -90
  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 +437 -39
  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 -202
  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 -202
  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
@@ -1,27 +1,6 @@
1
- import * as dntShim from "../../../_dnt.shims.js";
2
1
  import { runTurn } from '../../kernel/engine/runner.js';
3
2
  import { getProfile } from '../../kernel/registry/profiles.js';
4
- function handleRunEvent(event) {
5
- if (event.type === 'thought' && event.text) {
6
- dntShim.Deno.stdout.write(new TextEncoder().encode(`\x1b[2m${event.text}\x1b[0m`));
7
- }
8
- else if (event.type === 'text' && event.text) {
9
- dntShim.Deno.stdout.write(new TextEncoder().encode(event.text));
10
- }
11
- else if (event.type === 'tool' && event.tool) {
12
- console.log(`\n\x1b[33m⚡ [Tool Call] ${event.tool.name}\x1b[0m:`, event.tool.arguments);
13
- }
14
- else if (event.type === 'structured' && event.structured) {
15
- console.log('\n\x1b[32m✓ [Structured Output]\x1b[0m:');
16
- console.log(JSON.stringify(event.structured, null, 2));
17
- }
18
- else if (event.type === 'media' && event.media) {
19
- console.log(`\n\x1b[34m🖼 [Media Output]\x1b[0m (${event.media.mimeType})`);
20
- }
21
- else if (event.type === 'error' && event.error) {
22
- console.error(`\n\x1b[31m✗ Error\x1b[0m: ${event.error}`);
23
- }
24
- }
3
+ import { createCliTraceCapture, printRunEvent, printTraceRecord } from '../event-log.js';
25
4
  export async function runCommand(options) {
26
5
  getProfile(options.profile);
27
6
  const provider = options.provider;
@@ -30,24 +9,37 @@ export async function runCommand(options) {
30
9
  return;
31
10
  }
32
11
  const prompt = options.prompt || 'Hello! Please introduce your capabilities.';
33
- const tools = {};
34
- if (options.search)
35
- tools.googleSearch = true;
36
- if (options.map)
37
- tools.googleMaps = true;
12
+ if (options.search || options.map) {
13
+ const profile = getProfile(options.profile);
14
+ const selected = options.mode
15
+ ? (profile.model.select?.[options.mode] ?? profile.model.allow[0])
16
+ : profile.model.allow[0];
17
+ const builtins = new Set(profile.model.config[selected]?.builtInTools ?? []);
18
+ if (options.search && !builtins.has('googleSearch')) {
19
+ console.error('\n\x1b[31mExecution Failed\x1b[0m: --search requires googleSearch on model.config.*.builtInTools for the selected model.\n');
20
+ return;
21
+ }
22
+ if (options.map && !builtins.has('googleMaps')) {
23
+ console.error('\n\x1b[31mExecution Failed\x1b[0m: --map requires googleMaps on model.config.*.builtInTools for the selected model.\n');
24
+ return;
25
+ }
26
+ }
38
27
  const req = {
39
28
  profile: options.profile,
40
29
  select: options.mode,
41
- tools: Object.keys(tools).length > 0 ? tools : undefined,
42
30
  input: { text: prompt },
43
31
  };
44
32
  console.log(`\n▶ [RUNNING TURN] Profile: ${options.profile} (${options.mode ?? 'default'})`);
45
33
  console.log(`Prompt: "${prompt}"\n`);
34
+ const traceCapture = options.trace ? createCliTraceCapture(options.traceDir) : undefined;
46
35
  try {
47
- for await (const event of runTurn(req, provider)) {
48
- handleRunEvent(event);
36
+ for await (const event of runTurn(req, provider, traceCapture?.sink)) {
37
+ printRunEvent(event, { verbose: options.verbose });
49
38
  }
50
39
  console.log('\n');
40
+ if (options.trace) {
41
+ printTraceRecord(traceCapture?.records.at(-1), options.verbose === true);
42
+ }
51
43
  }
52
44
  catch (err) {
53
45
  console.error(`\n\x1b[31mExecution Failed\x1b[0m: ${err instanceof Error ? err.message : String(err)}\n`);
@@ -7,9 +7,18 @@ export interface TestRunResult {
7
7
  tokensTotal?: number;
8
8
  error?: string;
9
9
  }
10
- export declare function executeSingleTest(req: TurnRequest, testName: string, provider?: ModelProvider): Promise<TestRunResult>;
10
+ interface CliTestOptions {
11
+ verbose?: boolean;
12
+ trace?: boolean;
13
+ traceDir?: string;
14
+ }
15
+ export declare function executeSingleTest(req: TurnRequest, testName: string, provider?: ModelProvider, cliOptions?: CliTestOptions): Promise<TestRunResult>;
11
16
  export declare function testProfileCommand(profileId?: string, options?: MatrixOptions & {
12
17
  all?: boolean;
13
18
  matrix?: boolean;
14
19
  provider?: ModelProvider;
20
+ verbose?: boolean;
21
+ trace?: boolean;
22
+ traceDir?: string;
15
23
  }): Promise<boolean>;
24
+ export {};
@@ -1,39 +1,30 @@
1
- import * as dntShim from "../../../_dnt.shims.js";
2
1
  import { runTurn } from '../../kernel/engine/runner.js';
3
2
  import { getProfile, listProfiles } from '../../kernel/registry/profiles.js';
3
+ import { createCliTraceCapture, printTestEvent, printTraceRecord } from '../event-log.js';
4
4
  import { buildCustomTurnRequest, synthesizeMatrixCombos, } from '../matrix/synthesizer.js';
5
5
  function printTestHeader(req, testName) {
6
- const activeTools = Object.entries(req.tools ?? {})
7
- .filter(([, v]) => Boolean(v))
8
- .map(([k]) => k)
9
- .join(', ');
6
+ const profile = getProfile(req.profile);
7
+ const modelId = req.select && profile.model.select?.[req.select]
8
+ ? profile.model.select[req.select]
9
+ : profile.model.allow[0];
10
+ const customs = profile.type === 'speech' ? 'none' : profile.tools.allow.join(', ') || 'none';
11
+ const builtins = (profile.model.config[modelId]?.builtInTools ?? []).join(', ') || 'none';
10
12
  console.log(`\n▶ [THEORUM TEST] ${testName}`);
11
13
  console.log(` Profile: ${req.profile} (Mode: ${req.select ?? 'default'})`);
12
- console.log(` Tools: ${activeTools || 'none'}`);
14
+ console.log(` Custom: ${customs}`);
15
+ console.log(` Builtins: ${builtins}`);
13
16
  console.log(` Attachments: ${req.input?.attachments?.length ?? 0} file(s) | Voice: ${req.input?.voice?.length ? 'yes' : 'no'}`);
14
17
  console.log(` ${'-'.repeat(60)}`);
15
18
  }
16
- function processTestEvent(event, acc) {
17
- if (event.type === 'thought' && event.text) {
18
- dntShim.Deno.stdout.write(new TextEncoder().encode('.'));
19
- }
20
- else if (event.type === 'tool' && event.tool) {
21
- console.log(`\n ⚡ [Tool Dispatched] ${event.tool.name}(${JSON.stringify(event.tool.arguments ?? {})})`);
22
- }
23
- else if (event.type === 'structured') {
24
- console.log(`\n ✓ [Structured Schema Validated]`);
25
- }
26
- else if (event.type === 'media') {
27
- console.log(`\n ✓ [Media Generated] (${event.media?.mimeType})`);
28
- }
29
- else if (event.type === 'tokens' && event.tokens) {
19
+ function processTestEvent(event, acc, options) {
20
+ if (event.type === 'tokens' && event.tokens) {
30
21
  acc.totalTokens = event.tokens.total;
31
22
  }
32
- else if (event.type === 'error' && event.error) {
23
+ if (event.type === 'error' && event.error) {
33
24
  acc.hasError = true;
34
25
  acc.errorMessage = event.error;
35
- console.error(`\n [Test Error Detail]: ${event.error}`);
36
26
  }
27
+ printTestEvent(event, { verbose: options.verbose });
37
28
  }
38
29
  function printTestResult(passed, durationSec, acc) {
39
30
  if (passed) {
@@ -43,7 +34,7 @@ function printTestResult(passed, durationSec, acc) {
43
34
  console.log(`\n ✗ STATUS: FAILED: ${acc.errorMessage} (${durationSec.toFixed(2)}s)`);
44
35
  }
45
36
  }
46
- export async function executeSingleTest(req, testName, provider) {
37
+ export async function executeSingleTest(req, testName, provider, cliOptions = {}) {
47
38
  const start = Date.now();
48
39
  getProfile(req.profile);
49
40
  printTestHeader(req, testName);
@@ -56,8 +47,12 @@ export async function executeSingleTest(req, testName, provider) {
56
47
  if (!provider) {
57
48
  throw new Error('Theorum CLI does not create providers or read keys. Pass an explicit ModelProvider from the host app.');
58
49
  }
59
- for await (const event of runTurn(req, provider)) {
60
- processTestEvent(event, acc);
50
+ const traceCapture = cliOptions.trace ? createCliTraceCapture(cliOptions.traceDir) : undefined;
51
+ for await (const event of runTurn(req, provider, traceCapture?.sink)) {
52
+ processTestEvent(event, acc, cliOptions);
53
+ }
54
+ if (cliOptions.trace) {
55
+ printTraceRecord(traceCapture?.records.at(-1), cliOptions.verbose === true);
61
56
  }
62
57
  }
63
58
  catch (err) {
@@ -91,19 +86,19 @@ function resolveTargetProfiles(profileId, all) {
91
86
  console.error('Error: Please specify a profile ID (e.g. `theorum test --profile your-profile`) or `--all`.');
92
87
  return null;
93
88
  }
94
- async function runProfileMatrix(profile, provider) {
89
+ async function runProfileMatrix(profile, provider, cliOptions = {}) {
95
90
  const results = [];
96
91
  const combos = synthesizeMatrixCombos(profile);
97
92
  for (const combo of combos) {
98
- const res = await executeSingleTest(combo.req, `${profile.id} [${combo.name}]`, provider);
93
+ const res = await executeSingleTest(combo.req, `${profile.id} [${combo.name}]`, provider, cliOptions);
99
94
  results.push(res);
100
95
  }
101
96
  return results;
102
97
  }
103
- async function runProfileSingle(profile, options, provider) {
98
+ async function runProfileSingle(profile, options, provider, cliOptions = {}) {
104
99
  const req = buildCustomTurnRequest(profile, options);
105
100
  const testName = options.lite ? `${profile.id} [Lite]` : `${profile.id} [Stress Combo]`;
106
- return await executeSingleTest(req, testName, provider);
101
+ return await executeSingleTest(req, testName, provider, cliOptions);
107
102
  }
108
103
  function printSummaryReport(results) {
109
104
  console.log(`\n${'='.repeat(70)}`);
@@ -127,13 +122,18 @@ export async function testProfileCommand(profileId, options = {}) {
127
122
  if (!targetProfiles || targetProfiles.length === 0) {
128
123
  return false;
129
124
  }
125
+ const cliOptions = {
126
+ verbose: options.verbose,
127
+ trace: options.trace,
128
+ traceDir: options.traceDir,
129
+ };
130
130
  const results = [];
131
131
  for (const profile of targetProfiles) {
132
132
  if (options.matrix) {
133
- results.push(...(await runProfileMatrix(profile, options.provider)));
133
+ results.push(...(await runProfileMatrix(profile, options.provider, cliOptions)));
134
134
  }
135
135
  else {
136
- results.push(await runProfileSingle(profile, options, options.provider));
136
+ results.push(await runProfileSingle(profile, options, options.provider, cliOptions));
137
137
  }
138
138
  }
139
139
  return printSummaryReport(results);
@@ -0,0 +1,19 @@
1
+ import type { TurnEvent } from '../kernel/types.js';
2
+ import { type TraceSink } from '../observability/trace.js';
3
+ import type { TraceRecord } from '../observability/trace-record.js';
4
+ export interface CliEventLogOptions {
5
+ verbose?: boolean;
6
+ }
7
+ export interface CliTraceCapture {
8
+ sink: TraceSink;
9
+ records: TraceRecord[];
10
+ }
11
+ /** Attach an in-memory trace sink; optionally mirror to a JSONL directory. */
12
+ declare function createCliTraceCapture(traceDir?: string): CliTraceCapture;
13
+ /** Print one turn event for `theorum run`. */
14
+ declare function printRunEvent(event: TurnEvent, options?: CliEventLogOptions): void;
15
+ /** Print one turn event for `theorum test`. */
16
+ declare function printTestEvent(event: TurnEvent, options?: CliEventLogOptions): void;
17
+ /** Dump the last captured trace record after a CLI turn. */
18
+ declare function printTraceRecord(record: TraceRecord | undefined, verbose: boolean): void;
19
+ export { createCliTraceCapture, printRunEvent, printTestEvent, printTraceRecord };
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Shared CLI turn-event printing and trace capture for `run` and `test`.
3
+ *
4
+ * @module
5
+ */
6
+ import * as dntShim from "../../_dnt.shims.js";
7
+ import { jsonlSink, memorySink } from '../observability/trace.js';
8
+ /** Attach an in-memory trace sink; optionally mirror to a JSONL directory. */
9
+ function createCliTraceCapture(traceDir) {
10
+ const records = [];
11
+ const sinks = [memorySink(records)];
12
+ if (traceDir?.trim()) {
13
+ sinks.push(jsonlSink(traceDir.trim()));
14
+ }
15
+ return {
16
+ records,
17
+ sink: {
18
+ write: async (record) => {
19
+ for (const sink of sinks) {
20
+ await sink.write(record);
21
+ }
22
+ },
23
+ },
24
+ };
25
+ }
26
+ function printVerboseEvidence(event) {
27
+ if (event.type !== 'evidence' || !event.evidence) {
28
+ return;
29
+ }
30
+ const e = event.evidence;
31
+ if (e.raw) {
32
+ console.log('\n\x1b[2m[verbose evidence.raw]\x1b[0m');
33
+ console.log(JSON.stringify(e.raw, null, 2));
34
+ }
35
+ }
36
+ function printVerboseError(event) {
37
+ if (event.type !== 'error' || !event.errorInternal) {
38
+ return;
39
+ }
40
+ console.error(`\n\x1b[2m[verbose errorInternal]\x1b[0m ${event.errorInternal}`);
41
+ }
42
+ function printRunEvidence(event, verbose) {
43
+ const e = event.evidence;
44
+ if (!e) {
45
+ return;
46
+ }
47
+ if (e.kind === 'code_execution_call') {
48
+ console.log(`\n\x1b[36m🐍 [code_execution_call]\x1b[0m\n${e.code ?? ''}`);
49
+ }
50
+ else if (e.kind === 'code_execution_result') {
51
+ console.log(`\n\x1b[36m🐍 [code_execution_result]\x1b[0m isError=${String(e.isError)}\n${e.result ?? ''}`);
52
+ }
53
+ else if (e.kind) {
54
+ console.log(`\n\x1b[36m📎 [evidence]\x1b[0m ${e.kind}`);
55
+ }
56
+ if (verbose) {
57
+ printVerboseEvidence(event);
58
+ }
59
+ }
60
+ /** Print one turn event for `theorum run`. */
61
+ function printRunEvent(event, options = {}) {
62
+ const verbose = options.verbose === true;
63
+ if (event.type === 'thought' && event.text) {
64
+ dntShim.Deno.stdout.write(new TextEncoder().encode(`\x1b[2m${event.text}\x1b[0m`));
65
+ }
66
+ else if (event.type === 'text' && event.text) {
67
+ dntShim.Deno.stdout.write(new TextEncoder().encode(event.text));
68
+ }
69
+ else if (event.type === 'tool' && event.tool) {
70
+ console.log(`\n\x1b[33m⚡ [Tool Call] ${event.tool.name}\x1b[0m:`, event.tool.arguments);
71
+ }
72
+ else if (event.type === 'evidence') {
73
+ printRunEvidence(event, verbose);
74
+ }
75
+ else if (event.type === 'structured' && event.structured) {
76
+ console.log('\n\x1b[32m✓ [Structured Output]\x1b[0m:');
77
+ console.log(JSON.stringify(event.structured, null, 2));
78
+ }
79
+ else if (event.type === 'media' && event.media) {
80
+ console.log(`\n\x1b[34m🖼 [Media Output]\x1b[0m (${event.media.mimeType})`);
81
+ }
82
+ else if (event.type === 'error' && event.error) {
83
+ console.error(`\n\x1b[31m✗ Error\x1b[0m: ${event.error}`);
84
+ if (verbose) {
85
+ printVerboseError(event);
86
+ }
87
+ }
88
+ }
89
+ function printTestEvidence(event, verbose) {
90
+ const e = event.evidence;
91
+ if (!e) {
92
+ return;
93
+ }
94
+ if (e.kind === 'code_execution_call') {
95
+ const preview = (e.code ?? '').replaceAll('\n', ' ').slice(0, 80);
96
+ console.log(`\n 🐍 [code_execution_call] ${preview || e.id || ''}`);
97
+ }
98
+ else if (e.kind === 'code_execution_result') {
99
+ const preview = (e.result ?? '').replaceAll('\n', ' ').slice(0, 80);
100
+ console.log(`\n 🐍 [code_execution_result] isError=${String(e.isError)} ${preview}`);
101
+ }
102
+ else if (e.kind) {
103
+ console.log(`\n 📎 [evidence] ${e.kind}`);
104
+ }
105
+ if (verbose) {
106
+ printVerboseEvidence(event);
107
+ }
108
+ }
109
+ /** Print one turn event for `theorum test`. */
110
+ function printTestEvent(event, options = {}) {
111
+ const verbose = options.verbose === true;
112
+ if (event.type === 'thought' && event.text) {
113
+ dntShim.Deno.stdout.write(new TextEncoder().encode('.'));
114
+ }
115
+ else if (event.type === 'tool' && event.tool) {
116
+ console.log(`\n ⚡ [Tool Dispatched] ${event.tool.name}(${JSON.stringify(event.tool.arguments ?? {})})`);
117
+ }
118
+ else if (event.type === 'evidence') {
119
+ printTestEvidence(event, verbose);
120
+ }
121
+ else if (event.type === 'structured') {
122
+ console.log('\n ✓ [Structured Schema Validated]');
123
+ }
124
+ else if (event.type === 'media') {
125
+ console.log(`\n ✓ [Media Generated] (${event.media?.mimeType})`);
126
+ }
127
+ else if (event.type === 'error' && event.error) {
128
+ console.error(`\n [Test Error Detail]: ${event.error}`);
129
+ if (verbose) {
130
+ printVerboseError(event);
131
+ }
132
+ }
133
+ }
134
+ /** Dump the last captured trace record after a CLI turn. */
135
+ function printTraceRecord(record, verbose) {
136
+ if (!record) {
137
+ console.error('\n\x1b[33m[trace]\x1b[0m No trace record captured for this turn.');
138
+ return;
139
+ }
140
+ console.log('\n\x1b[35m════ TRACE RECORD ════\x1b[0m');
141
+ console.log(JSON.stringify(record, null, 2));
142
+ if (verbose && record.upstreamLog) {
143
+ console.log('\n\x1b[35m════ UPSTREAM LOG ════\x1b[0m');
144
+ console.log(JSON.stringify(record.upstreamLog, null, 2));
145
+ }
146
+ }
147
+ export { createCliTraceCapture, printRunEvent, printTestEvent, printTraceRecord };
@@ -3,6 +3,7 @@ import "../../_dnt.polyfills.js";
3
3
  import "../../_dnt.polyfills.js";
4
4
  import * as dntShim from "../../_dnt.shims.js";
5
5
  import { benchCommand } from './commands/bench.js';
6
+ import { fuzzCanaryCommand } from './commands/fuzz-canary.js';
6
7
  import { fuzzGuardrailsCommand } from './commands/fuzz-guardrails.js';
7
8
  import { listProfilesCommand, showProfileCommand } from './commands/profile.js';
8
9
  import { runCommand } from './commands/run.js';
@@ -20,7 +21,18 @@ COMMANDS:
20
21
  --iterations <n> Measurement iterations (default: 50)
21
22
  --warmup <n> Warmup iterations (default: 5)
22
23
 
23
- fuzz Adversarial guardrail fuzzer
24
+ fuzz Adversarial inbound sanitization fuzzer
25
+ fuzz-canary Adversarial canary egress fuzzer (stream + Live gates)
26
+
27
+ run Execute a turn with real-time streaming output
28
+ --profile, -p <id> Target profile ID
29
+ --prompt <text> User prompt string
30
+ --mode <fast|smart> Reasoning mode
31
+ --search Requires googleSearch on the selected model's builtInTools
32
+ --map Requires googleMaps on the selected model's builtInTools
33
+ --verbose, -v Print errorInternal and evidence.raw while running
34
+ --trace Capture a TraceRecord and print it after the turn
35
+ --trace-dir <path> Also append trace JSONL under this directory
24
36
 
25
37
  test Run stress matrix or custom profile tests
26
38
  --profile, -p <id> Target profile ID registered by your host app
@@ -28,15 +40,11 @@ COMMANDS:
28
40
  --lite Minimal fast-path connectivity ping
29
41
  --matrix Run all valid permutations for the profile
30
42
  --mode <fast|smart> Reasoning mode override
31
- --search Optional: set tool id googleSearch when allowlisted
32
- --map Optional: set tool id googleMaps when allowlisted
33
-
34
- run Execute a turn with real-time streaming output
35
- --profile, -p <id> Target profile ID
36
- --prompt <text> User prompt string
37
- --mode <fast|smart> Reasoning mode
38
- --search Optional: set tool id googleSearch when allowlisted
39
- --map Optional: set tool id googleMaps when allowlisted
43
+ --search Requires googleSearch on the selected model's builtInTools
44
+ --map Requires googleMaps on the selected model's builtInTools
45
+ --verbose, -v Print errorInternal and evidence.raw while running
46
+ --trace Capture a TraceRecord and print it after each turn
47
+ --trace-dir <path> Also append trace JSONL under this directory
40
48
 
41
49
  profile Inspect registered profile blueprints
42
50
  list List all registered profiles
@@ -45,6 +53,13 @@ COMMANDS:
45
53
  help, --help, -h Show this help message
46
54
  `);
47
55
  }
56
+ function cliDiagnostics(flags) {
57
+ return {
58
+ verbose: Boolean(flags.verbose || flags.v),
59
+ trace: Boolean(flags.trace),
60
+ traceDir: typeof flags['trace-dir'] === 'string' ? flags['trace-dir'] : undefined,
61
+ };
62
+ }
48
63
  function parseLongFlag(arg, nextArg, flags) {
49
64
  const key = arg.slice(2);
50
65
  if (nextArg && !nextArg.startsWith('-')) {
@@ -64,6 +79,8 @@ function parseShortFlag(arg, nextArg, flags) {
64
79
  flags.all = true;
65
80
  else if (key === 'h')
66
81
  flags.help = true;
82
+ else if (key === 'v')
83
+ flags.verbose = true;
67
84
  else
68
85
  flags[key] = true;
69
86
  return 0;
@@ -96,6 +113,7 @@ function extractProfileId(flags) {
96
113
  }
97
114
  async function handleTest(flags) {
98
115
  const profile = extractProfileId(flags);
116
+ const diagnostics = cliDiagnostics(flags);
99
117
  const success = await testProfileCommand(profile, {
100
118
  all: Boolean(flags.all || flags.a),
101
119
  lite: Boolean(flags.lite),
@@ -103,6 +121,9 @@ async function handleTest(flags) {
103
121
  mode: typeof flags.mode === 'string' ? flags.mode : undefined,
104
122
  search: flags.search ? true : undefined,
105
123
  map: flags.map ? true : undefined,
124
+ verbose: diagnostics.verbose,
125
+ trace: diagnostics.trace,
126
+ traceDir: diagnostics.traceDir,
106
127
  });
107
128
  if (!success) {
108
129
  dntShim.Deno.exit(1);
@@ -115,12 +136,16 @@ async function handleRun(flags) {
115
136
  dntShim.Deno.exit(1);
116
137
  }
117
138
  const prompt = typeof flags.prompt === 'string' ? flags.prompt : flags._.slice(1).join(' ');
139
+ const diagnostics = cliDiagnostics(flags);
118
140
  await runCommand({
119
141
  profile,
120
142
  prompt,
121
143
  mode: typeof flags.mode === 'string' ? flags.mode : undefined,
122
144
  search: Boolean(flags.search),
123
145
  map: Boolean(flags.map),
146
+ verbose: diagnostics.verbose,
147
+ trace: diagnostics.trace,
148
+ traceDir: diagnostics.traceDir,
124
149
  });
125
150
  }
126
151
  function handleProfile(flags) {
@@ -140,7 +165,16 @@ export async function main(cliArgs = dntShim.Deno.args) {
140
165
  const flags = parseFlags(cliArgs);
141
166
  const command = flags._[0] || (flags.help ? 'help' : 'help');
142
167
  if (command === 'fuzz') {
143
- fuzzGuardrailsCommand();
168
+ const ok = fuzzGuardrailsCommand();
169
+ if (!ok) {
170
+ dntShim.Deno.exit(1);
171
+ }
172
+ }
173
+ else if (command === 'fuzz-canary') {
174
+ const ok = await fuzzCanaryCommand();
175
+ if (!ok) {
176
+ dntShim.Deno.exit(1);
177
+ }
144
178
  }
145
179
  else if (command === 'bench') {
146
180
  await benchCommand({
@@ -1,27 +1,25 @@
1
1
  /**
2
2
  * Stress / matrix turn synthesis for the THEORUM CLI.
3
3
  *
4
- * Tool enabling walks the profile allowlist and registered catalog metadata
5
- * (`conflictsWith`). Google `--search` / `--map` flags remain as convenience
6
- * aliases for `googleSearch` / `googleMaps` when those ids are allowed.
4
+ * Custom tools come from `profile.tools.allow` (visibility via loadTier).
5
+ * Provider builtins come from `model.config.*.builtInTools`. `--search` /
6
+ * `--map` only verify those ids are listed on the selected model.
7
7
  *
8
8
  * @module
9
9
  */
10
- import type { Profile, ToolId, TurnRequest } from '../../kernel/types.js';
10
+ import type { Profile, TurnRequest } from '../../kernel/types.js';
11
11
  export interface MatrixOptions {
12
12
  lite?: boolean;
13
- /** Convenience alias for enabling/disabling `googleSearch` when allowed. */
13
+ /** Requires googleSearch on the selected model's builtInTools. */
14
14
  search?: boolean;
15
- /** Convenience alias for enabling/disabling `googleMaps` when allowed. */
15
+ /** Requires googleMaps on the selected model's builtInTools. */
16
16
  map?: boolean;
17
17
  mode?: string;
18
18
  attachmentPaths?: string[];
19
19
  voicePath?: string;
20
20
  }
21
21
  export declare function synthesizeLiteCombo(profile: Profile): TurnRequest;
22
- export declare function synthesizeStressCombo(profile: Profile, options?: {
23
- preferTool?: ToolId;
24
- }): TurnRequest;
22
+ export declare function synthesizeStressCombo(profile: Profile): TurnRequest;
25
23
  export declare function synthesizeMatrixCombos(profile: Profile): Array<{
26
24
  name: string;
27
25
  req: TurnRequest;