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
@@ -150,9 +150,9 @@ function gateStatusToAction(status, terminalStatus = 'terminal') {
150
150
  return null;
151
151
  }
152
152
  async function* executeSingleAttemptCycle(args) {
153
- const { flow, state, profile, system, provider, gemini, maxRetries } = args;
154
- const validation = profile.outputs.validation;
155
- const egress = profile.guardrails.egress;
153
+ const { flow, state, profile, system, provider, upstream, maxRetries } = args;
154
+ const validation = profile.outputs?.validation;
155
+ const egress = profile.guardrails?.egress;
156
156
  state.attemptEvents = [];
157
157
  const { latestStructured } = yield* executeAttempt({
158
158
  safe: flow.currentReq,
@@ -160,7 +160,7 @@ async function* executeSingleAttemptCycle(args) {
160
160
  generation: flow.currentGen,
161
161
  system,
162
162
  provider,
163
- gemini,
163
+ upstream,
164
164
  state,
165
165
  });
166
166
  if (egress?.enforce) {
@@ -183,8 +183,8 @@ async function* executeSingleAttemptCycle(args) {
183
183
  }
184
184
  return { status: 'success' };
185
185
  }
186
- async function* runAttemptsWithValidation(safe, profile, generation, system, provider, gemini, state) {
187
- const maxRetries = Math.max(profile.outputs.validation?.maxRetries ?? 0, profile.guardrails.egress?.maxRetries ?? 2);
186
+ async function* runAttemptsWithValidation(safe, profile, generation, system, provider, upstream, state) {
187
+ const maxRetries = Math.max(profile.outputs?.validation?.maxRetries ?? 0, profile.guardrails?.egress?.maxRetries ?? 0);
188
188
  const flow = {
189
189
  currentAttempt: 0,
190
190
  currentGen: generation,
@@ -198,7 +198,7 @@ async function* runAttemptsWithValidation(safe, profile, generation, system, pro
198
198
  profile,
199
199
  system,
200
200
  provider,
201
- gemini,
201
+ upstream,
202
202
  maxRetries,
203
203
  });
204
204
  if (step.status === 'terminal' || step.status === 'success') {
@@ -7,18 +7,18 @@
7
7
  *
8
8
  * @module
9
9
  */
10
+ import { bindCanary } from '../../../guardrails/canary.js';
10
11
  import { throwIfAborted } from '../../../guardrails/error.js';
11
12
  import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
12
13
  import { noopSink, writeTrace } from '../../../observability/trace.js';
13
14
  import { buildRecord } from '../../../observability/trace-record.js';
14
15
  import { pickSystemRole, resolveTurn } from '../../registry/resolve.js';
15
16
  import { CONTINUE_INSTRUCTION } from '../../stop.js';
16
- import { bindCanary } from '../boundary.js';
17
+ import { expandT1Policy } from '../../tools/resolve.js';
17
18
  import { resolveCompactionTokens, shouldCompact, splitForCompaction } from '../compaction.js';
18
19
  import { runAttemptsWithValidation } from './gates.js';
19
20
  import { shouldSkipStreamEvent, systemFromProfile } from './stream.js';
20
21
  import { calculateFallbackTokens } from './tokens.js';
21
- import { invokeFromUi } from './tools.js';
22
22
  function getCompactionSpec(profile, modelId) {
23
23
  return profile.model.config[modelId]?.compaction;
24
24
  }
@@ -94,11 +94,7 @@ async function attachAfterCompaction(event, args) {
94
94
  return { ...event, compaction: signal };
95
95
  }
96
96
  async function* emitTurn(args) {
97
- const { safe, profile, generation, system, provider, gemini } = args;
98
- if (safe.toolInvoke) {
99
- yield* invokeFromUi(profile, safe);
100
- return;
101
- }
97
+ const { safe, profile, generation, system, provider, upstream } = args;
102
98
  const state = {
103
99
  currentHistory: [...(generation.history ?? [])],
104
100
  stepCount: 0,
@@ -106,7 +102,7 @@ async function* emitTurn(args) {
106
102
  allEmittedEvents: [],
107
103
  attemptEvents: [],
108
104
  };
109
- yield* runAttemptsWithValidation(safe, profile, generation, system, provider, gemini, state);
105
+ yield* runAttemptsWithValidation(safe, profile, generation, system, provider, upstream, state);
110
106
  if (!state.sawTokensEvent) {
111
107
  yield* calculateFallbackTokens(safe, system, state.allEmittedEvents);
112
108
  }
@@ -121,12 +117,13 @@ async function flushTurnTrace(sink, ctx) {
121
117
  events: ctx.seen,
122
118
  started: ctx.started,
123
119
  model: ctx.model,
124
- bucket: ctx.bucket,
120
+ keySlot: ctx.keySlot,
125
121
  thrown: ctx.thrown,
126
- gemini: ctx.gemini,
122
+ upstreamLog: ctx.upstream,
127
123
  canary: ctx.canary,
128
124
  system: ctx.system,
129
125
  generation: ctx.generation,
126
+ protocol: ctx.protocol,
130
127
  sanitizedReq: ctx.safe,
131
128
  }));
132
129
  }
@@ -137,7 +134,7 @@ async function* runTurn(req, provider, sink = noopSink()) {
137
134
  seen: [],
138
135
  started: Date.now(),
139
136
  canary: '',
140
- gemini: [],
137
+ upstream: [],
141
138
  };
142
139
  try {
143
140
  yield* runTurnBody(ctx, provider);
@@ -152,10 +149,13 @@ async function* runTurnBody(ctx, provider) {
152
149
  ctx.safe = sanitizeTurnRequest(ctx.req);
153
150
  throwIfAborted(ctx.safe.signal);
154
151
  const { profile, generation: gen } = resolveTurn(ctx.safe);
152
+ await expandT1Policy(gen.tools, profile, ctx.safe);
153
+ gen.builtins = gen.tools.builtins;
155
154
  ctx.generation = gen;
156
155
  ctx.model = gen.model;
157
- ctx.bucket = gen.geminiBucket;
156
+ ctx.keySlot = gen.keySlot;
158
157
  ctx.canary = gen.canary;
158
+ ctx.protocol = profile.model.protocol;
159
159
  const isCompacting = ctx.req.metadata?._compacting === true;
160
160
  const compactionSpec = isCompacting ? undefined : getCompactionSpec(profile, gen.model);
161
161
  await maybeCompactBefore(ctx, gen, compactionSpec, provider);
@@ -188,7 +188,7 @@ async function* streamTurnEvents(ctx, profile, gen, provider, compactionSpec, is
188
188
  generation: gen,
189
189
  system: ctx.system,
190
190
  provider,
191
- gemini: ctx.gemini,
191
+ upstream: ctx.upstream,
192
192
  })) {
193
193
  const out = await maybeAttachAfter(event, ctx, gen, compactionSpec, isCompacting);
194
194
  ctx.seen.push(out);
@@ -7,6 +7,13 @@ interface StepExecutionState {
7
7
  attemptEvents: TurnEvent[];
8
8
  /** Last provider stop from a discarded provider `done` event. */
9
9
  lastStop?: TurnStop;
10
+ /** Latest Google Interactions id observed on the current provider stream. */
11
+ lastInteractionId?: string;
12
+ /** Pending Interactions `function_result` continuation for the next provider step. */
13
+ interactionsContinuation?: {
14
+ previousInteractionId: string;
15
+ input: Record<string, unknown>[];
16
+ };
10
17
  }
11
18
  interface AttemptFlowState {
12
19
  currentAttempt: number;
@@ -6,7 +6,7 @@ declare function executeAttempt(args: {
6
6
  generation: ResolvedGeneration;
7
7
  system: string;
8
8
  provider: ModelProvider;
9
- gemini: Record<string, unknown>[];
9
+ upstream: Record<string, unknown>[];
10
10
  state: StepExecutionState;
11
11
  }): AsyncGenerator<TurnEvent, {
12
12
  pendingTools: TurnEvent[];
@@ -1,29 +1,48 @@
1
1
  import { throwIfAborted } from '../../../guardrails/error.js';
2
+ import { executeRegisteredTool, formatToolFailureForModel, formatToolResult, newCallId, } from '../../tools/execute.js';
2
3
  import { recordStepEvent } from './state.js';
3
4
  import { yieldProviderEvents } from './stream.js';
4
- import { executeDynamicDeclaration, findDynamicDeclaration, formatToolFinding, isActionableDynamicDeclaration, } from './tools.js';
5
5
  function isStepLimitReached(step, maxSteps) {
6
- if (maxSteps <= 0) {
6
+ if (maxSteps === undefined || maxSteps <= 0) {
7
7
  return false;
8
8
  }
9
9
  return step >= maxSteps;
10
10
  }
11
+ function generationForProviderStep(generation, state) {
12
+ if (state.interactionsContinuation) {
13
+ const { previousInteractionId, input } = state.interactionsContinuation;
14
+ state.interactionsContinuation = undefined;
15
+ return {
16
+ ...generation,
17
+ history: [],
18
+ input: [],
19
+ previousInteractionId,
20
+ interactionOnlyInput: input,
21
+ };
22
+ }
23
+ return { ...generation, history: state.currentHistory };
24
+ }
25
+ function captureInteractionId(event, state) {
26
+ if (event.interactionId) {
27
+ state.lastInteractionId = event.interactionId;
28
+ }
29
+ }
11
30
  async function* executeAutonomousStep(args, state, buffer = {
12
31
  holdLate: false,
13
32
  holdUserVisible: false,
14
33
  }) {
15
- const { profile, generation, system, provider, gemini, signal } = args;
16
- const genForStep = { ...generation, history: state.currentHistory };
34
+ const { generation, system, provider, upstream, signal } = args;
35
+ const genForStep = generationForProviderStep(generation, state);
17
36
  const pendingTools = [];
18
37
  let latestStructured;
19
38
  for await (const event of yieldProviderEvents({
20
- profile,
21
39
  generation: genForStep,
22
40
  system,
23
41
  provider,
24
- gemini,
42
+ upstream,
25
43
  signal,
26
44
  })) {
45
+ captureInteractionId(event, state);
27
46
  if (event.type === 'structured') {
28
47
  latestStructured = event.structured;
29
48
  }
@@ -39,8 +58,6 @@ async function* executeAutonomousStep(args, state, buffer = {
39
58
  }
40
59
  recordStepEvent(event, state);
41
60
  const isUserVisible = event.type === 'thought' || event.type === 'text';
42
- // Egress must not stream user-visible text before the gate runs.
43
- // Validation-only may stream thought/text live and only hold structured.
44
61
  const streamNow = !buffer.holdLate || event.type === 'tokens' || (isUserVisible && !buffer.holdUserVisible);
45
62
  if (streamNow) {
46
63
  yield event;
@@ -48,12 +65,24 @@ async function* executeAutonomousStep(args, state, buffer = {
48
65
  }
49
66
  return { pendingTools, latestStructured };
50
67
  }
51
- function appendToolTurnToHistory(history, toolEv, res) {
68
+ function appendInteractionsToolResultToHistory(history, toolEv, result) {
52
69
  const tool = toolEv.tool;
53
70
  if (!tool) {
54
71
  return;
55
72
  }
56
- const callId = tool.id ?? `call_${tool.name}_${String(Date.now())}`;
73
+ history.push({
74
+ role: 'tool',
75
+ tool_call_id: tool.id ?? tool.callId ?? `call_${tool.name}`,
76
+ name: tool.name,
77
+ content: formatToolResult(result),
78
+ });
79
+ }
80
+ function appendToolTurnToHistory(history, toolEv, result) {
81
+ const tool = toolEv.tool;
82
+ if (!tool) {
83
+ return;
84
+ }
85
+ const callId = tool.id ?? tool.callId ?? newCallId(tool.name);
57
86
  history.push({
58
87
  role: 'assistant',
59
88
  tool_calls: [
@@ -71,51 +100,156 @@ function appendToolTurnToHistory(history, toolEv, res) {
71
100
  role: 'tool',
72
101
  tool_call_id: callId,
73
102
  name: tool.name,
74
- content: formatToolFinding(res),
103
+ content: formatToolResult(result),
75
104
  });
76
105
  }
77
- async function* handlePendingDynamicTools(pendingTools, generation, profile, state) {
78
- let hasRunnableHandler = false;
106
+ function queueInteractionsToolContinuation(state, toolEv, result, fallbackInteractionId) {
107
+ const tool = toolEv.tool;
108
+ if (!tool) {
109
+ return;
110
+ }
111
+ const previousInteractionId = state.lastInteractionId ?? fallbackInteractionId;
112
+ if (!previousInteractionId) {
113
+ return;
114
+ }
115
+ const step = {
116
+ type: 'function_result',
117
+ name: tool.name,
118
+ call_id: tool.id ?? tool.callId ?? `call_${tool.name}`,
119
+ result: [{ type: 'text', text: formatToolResult(result) }],
120
+ };
121
+ if (state.interactionsContinuation &&
122
+ state.interactionsContinuation.previousInteractionId === previousInteractionId) {
123
+ state.interactionsContinuation.input.push(step);
124
+ return;
125
+ }
126
+ state.interactionsContinuation = {
127
+ previousInteractionId,
128
+ input: [step],
129
+ };
130
+ }
131
+ function recordToolModelResult(state, toolEv, modelResult, generation, useInteractionsContinuation) {
132
+ if (useInteractionsContinuation) {
133
+ queueInteractionsToolContinuation(state, toolEv, modelResult, generation.previousInteractionId);
134
+ if (!state.interactionsContinuation) {
135
+ appendInteractionsToolResultToHistory(state.currentHistory, toolEv, modelResult);
136
+ }
137
+ return;
138
+ }
139
+ appendToolTurnToHistory(state.currentHistory, toolEv, modelResult);
140
+ }
141
+ function enrichToolEvent(tool, callId, patch) {
142
+ return {
143
+ type: 'tool',
144
+ tool: {
145
+ ...tool,
146
+ ...patch,
147
+ callId,
148
+ id: tool.id ?? callId,
149
+ },
150
+ };
151
+ }
152
+ async function* handlePendingTools(pendingTools, generation, profile, state) {
153
+ let executed = false;
154
+ let sawPause = false;
155
+ const useInteractionsContinuation = generation.transport === 'interactions';
79
156
  for (const toolEv of pendingTools) {
80
157
  const tool = toolEv.tool;
81
158
  if (!tool) {
82
159
  continue;
83
160
  }
84
- const decl = findDynamicDeclaration(generation.dynamicTools, tool.name);
85
- if (!isActionableDynamicDeclaration(decl)) {
86
- state.allEmittedEvents.push(toolEv);
87
- yield toolEv;
161
+ executed = true;
162
+ const callId = tool.id ?? tool.callId ?? newCallId(tool.name || 'unknown');
163
+ // Provider cancelled an in-flight call (e.g. live barge-in). Do not execute.
164
+ if (tool.phase === 'cancel') {
165
+ const enriched = enrichToolEvent(tool, callId);
166
+ state.allEmittedEvents.push(enriched);
167
+ yield enriched;
88
168
  continue;
89
169
  }
90
- hasRunnableHandler = true;
91
- const finalResult = await executeDynamicDeclaration({
92
- decl,
93
- toolArgs: tool.arguments ?? {},
170
+ // Provider already failed this call (e.g. malformed arguments JSON).
171
+ if (tool.phase === 'error' && tool.failure) {
172
+ const enriched = enrichToolEvent(tool, callId);
173
+ state.allEmittedEvents.push(enriched);
174
+ yield enriched;
175
+ recordToolModelResult(state, toolEv, formatToolFailureForModel(tool.failure), generation, useInteractionsContinuation);
176
+ continue;
177
+ }
178
+ // Empty name is a protocol defect — never route through the registry as unknown_tool.
179
+ if (!tool.name) {
180
+ const failure = {
181
+ code: 'malformed_arguments',
182
+ message: 'Provider tool call is missing a function name',
183
+ };
184
+ const enriched = enrichToolEvent(tool, callId, {
185
+ phase: 'error',
186
+ failure,
187
+ name: '',
188
+ });
189
+ state.allEmittedEvents.push(enriched);
190
+ yield enriched;
191
+ recordToolModelResult(state, toolEv, formatToolFailureForModel(failure), generation, useInteractionsContinuation);
192
+ continue;
193
+ }
194
+ let modelResult;
195
+ let paused = false;
196
+ const exec = executeRegisteredTool({
94
197
  profile,
95
- generation,
198
+ name: tool.name,
199
+ input: tool.arguments ?? {},
200
+ callId,
201
+ ctx: {
202
+ sessionPermissions: generation.sessionPermissions,
203
+ path: generation.tools.path,
204
+ turn: { step: state.stepCount },
205
+ },
206
+ snapshot: generation.tools,
96
207
  });
97
- const enrichedEvent = {
98
- type: 'tool',
99
- tool: { ...tool, result: finalResult },
100
- };
101
- state.allEmittedEvents.push(enrichedEvent);
102
- yield enrichedEvent;
103
- appendToolTurnToHistory(state.currentHistory, toolEv, finalResult);
208
+ let next = await exec.next();
209
+ while (!next.done) {
210
+ const event = next.value;
211
+ const enriched = enrichToolEvent(tool, callId, event.tool);
212
+ state.allEmittedEvents.push(enriched);
213
+ yield enriched;
214
+ if (event.tool?.phase === 'error' && event.tool.failure) {
215
+ modelResult = formatToolFailureForModel(event.tool.failure);
216
+ }
217
+ if (event.tool?.phase === 'pause') {
218
+ modelResult = undefined;
219
+ paused = true;
220
+ }
221
+ next = await exec.next();
222
+ }
223
+ if (next.value !== undefined) {
224
+ modelResult = next.value;
225
+ }
226
+ if (paused) {
227
+ sawPause = true;
228
+ continue;
229
+ }
230
+ if (!modelResult) {
231
+ continue;
232
+ }
233
+ recordToolModelResult(state, toolEv, modelResult, generation, useInteractionsContinuation);
234
+ }
235
+ if (sawPause) {
236
+ state.lastStop = { kind: 'tool' };
237
+ return false;
104
238
  }
105
- return hasRunnableHandler;
239
+ return executed;
106
240
  }
107
241
  async function* executeAttempt(args) {
108
- const { profile, generation, system, provider, gemini, state } = args;
242
+ const { profile, generation, system, provider, upstream, state } = args;
109
243
  let latestStructured;
110
244
  let pendingTools = [];
111
245
  let stepInAttempt = 0;
112
- const holdUserVisible = Boolean(profile.guardrails.egress?.enforce);
113
- const holdLate = Boolean(profile.outputs.validation) || holdUserVisible;
114
- while (!isStepLimitReached(stepInAttempt, generation.maxSteps)) {
246
+ const holdUserVisible = Boolean(profile.guardrails?.egress?.enforce);
247
+ const holdLate = Boolean(profile.outputs?.validation) || holdUserVisible;
248
+ while (!isStepLimitReached(stepInAttempt, generation.maxSteps ?? 0)) {
115
249
  throwIfAborted(args.safe.signal);
116
250
  stepInAttempt++;
117
251
  state.stepCount++;
118
- const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, gemini, signal: args.safe.signal }, state, { holdLate, holdUserVisible });
252
+ const stepResult = yield* executeAutonomousStep({ profile, generation, system, provider, upstream, signal: args.safe.signal }, state, { holdLate, holdUserVisible });
119
253
  if (stepResult.latestStructured !== undefined) {
120
254
  latestStructured = stepResult.latestStructured;
121
255
  }
@@ -123,8 +257,8 @@ async function* executeAttempt(args) {
123
257
  if (pendingTools.length === 0) {
124
258
  break;
125
259
  }
126
- const hasRunnableHandler = yield* handlePendingDynamicTools(pendingTools, generation, profile, state);
127
- if (!hasRunnableHandler) {
260
+ const executed = yield* handlePendingTools(pendingTools, generation, profile, state);
261
+ if (!executed) {
128
262
  break;
129
263
  }
130
264
  }
@@ -2,11 +2,10 @@ import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent } from '../.
2
2
  declare function systemFromProfile(profile: Profile, role: string): string;
3
3
  declare function shouldSkipStreamEvent(event: TurnEvent, profile: Profile): boolean;
4
4
  declare function yieldProviderEvents(args: {
5
- profile: Profile;
6
5
  generation: ResolvedGeneration;
7
6
  system: string;
8
7
  provider: ModelProvider;
9
- gemini: Record<string, unknown>[];
8
+ upstream: Record<string, unknown>[];
10
9
  signal?: AbortSignal;
11
10
  }): AsyncGenerator<TurnEvent>;
12
11
  export { shouldSkipStreamEvent, systemFromProfile, yieldProviderEvents };
@@ -1,7 +1,6 @@
1
+ import { createCanaryStreamGate, eventHasCanary, isStreamedCanaryEvent, redactCanary, } from '../../../guardrails/canary.js';
1
2
  import { publicError, throwIfAborted, toErrorEvent } from '../../../guardrails/error.js';
2
3
  import { providerCompleteRequest } from '../../registry/provider-request.js';
3
- import { eventHasCanary, redactCanary } from '../boundary.js';
4
- import { dispatchModelTool } from './tools.js';
5
4
  function systemFromProfile(profile, role) {
6
5
  const { identity } = profile;
7
6
  const { systemByRole, system } = identity;
@@ -16,29 +15,11 @@ function systemFromProfile(profile, role) {
16
15
  }
17
16
  return '';
18
17
  }
19
- function* interceptProviderTool(event, profile, generation) {
20
- const tool = event.tool;
21
- if (!tool) {
22
- return;
23
- }
24
- const isDynamic = generation.dynamicTools?.some((d) => d.name === tool.name);
25
- if (isDynamic) {
26
- yield event;
27
- }
28
- else {
29
- for (const item of dispatchModelTool(profile, event, generation.custom)) {
30
- yield item;
31
- }
32
- }
33
- }
34
18
  function shouldSkipStreamEvent(event, profile) {
35
- return event.type === 'thought' && profile.outputs.streaming?.streamThoughts === false;
19
+ return event.type === 'thought' && profile.outputs?.streaming?.streamThoughts === false;
36
20
  }
37
- function* processNormalEvent(event, profile, generation) {
38
- if (event.type === 'tool') {
39
- yield* interceptProviderTool(event, profile, generation);
40
- }
41
- else if (event.type === 'error') {
21
+ function* processNormalEvent(event) {
22
+ if (event.type === 'error') {
42
23
  const internal = event.errorInternal ?? event.error ?? '';
43
24
  yield {
44
25
  type: 'error',
@@ -50,24 +31,80 @@ function* processNormalEvent(event, profile, generation) {
50
31
  yield event;
51
32
  }
52
33
  }
34
+ function* yieldCanaryLeak(canary, event) {
35
+ yield redactCanary(event, canary);
36
+ yield toErrorEvent('canary leaked');
37
+ }
38
+ function* yieldGatedStreamEvent(event, gate, canary) {
39
+ const fragment = event.text ?? '';
40
+ const result = gate.process(fragment);
41
+ if (result.leak) {
42
+ yield* yieldCanaryLeak(canary, event);
43
+ return 'stop';
44
+ }
45
+ if (result.emit) {
46
+ yield* processNormalEvent({ ...event, text: result.emit });
47
+ }
48
+ return 'continue';
49
+ }
50
+ function* flushCanaryGate(gate, _canary, lastType) {
51
+ const result = gate.flush();
52
+ if (result.leak) {
53
+ yield toErrorEvent('canary leaked');
54
+ return 'stop';
55
+ }
56
+ if (result.emit) {
57
+ const emitType = lastType ?? 'text';
58
+ yield* processNormalEvent({ type: emitType, text: result.emit });
59
+ }
60
+ return 'pass';
61
+ }
53
62
  async function* yieldProviderEvents(args) {
54
- const { profile, generation, system, provider, gemini, signal } = args;
63
+ const { generation, system, provider, upstream, signal } = args;
55
64
  const { canary } = generation;
65
+ const gate = canary ? createCanaryStreamGate(canary) : null;
66
+ let lastStreamType;
56
67
  throwIfAborted(signal);
57
68
  for await (const event of provider.complete({
58
69
  ...providerCompleteRequest(generation, system),
59
70
  signal,
60
- tapGemini: (row) => {
61
- gemini.push(row);
71
+ tapUpstream: (row) => {
72
+ upstream.push(row);
62
73
  },
63
74
  })) {
64
75
  throwIfAborted(signal);
76
+ if (gate && isStreamedCanaryEvent(event)) {
77
+ if (lastStreamType && lastStreamType !== event.type) {
78
+ const status = yield* flushCanaryGate(gate, canary, lastStreamType);
79
+ if (status === 'stop') {
80
+ return;
81
+ }
82
+ }
83
+ lastStreamType = event.type;
84
+ const status = yield* yieldGatedStreamEvent(event, gate, canary);
85
+ if (status === 'stop') {
86
+ return;
87
+ }
88
+ continue;
89
+ }
90
+ if (gate && lastStreamType) {
91
+ const status = yield* flushCanaryGate(gate, canary, lastStreamType);
92
+ if (status === 'stop') {
93
+ return;
94
+ }
95
+ lastStreamType = undefined;
96
+ }
65
97
  if (canary && eventHasCanary(event, canary)) {
66
- yield redactCanary(event, canary);
67
- yield toErrorEvent('canary leaked');
98
+ yield* yieldCanaryLeak(canary, event);
99
+ return;
100
+ }
101
+ yield* processNormalEvent(event);
102
+ }
103
+ if (gate) {
104
+ const status = yield* flushCanaryGate(gate, canary, lastStreamType);
105
+ if (status === 'stop') {
68
106
  return;
69
107
  }
70
- yield* processNormalEvent(event, profile, generation);
71
108
  }
72
109
  }
73
110
  export { shouldSkipStreamEvent, systemFromProfile, yieldProviderEvents };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `runSession` — long-lived live profile execution.
3
+ *
4
+ * Shares resolve / tools / canary / system / outbound gate with `runTurn`.
5
+ * Does not use `ModelProvider.complete()` — live is a session, not one turn.
6
+ *
7
+ * @module
8
+ */
9
+ import type { GeminiTransport } from '../../../providers/google/keys.js';
10
+ import type { LiveSession, SessionRequest } from '../../types.js';
11
+ export type { LiveSession, SessionRequest };
12
+ export interface RunSessionOptions {
13
+ gemini: GeminiTransport;
14
+ /** Override socket open (Cloudflare fetch-upgrade, tests). Default: `new WebSocket(url)`. */
15
+ openWebSocket?: (url: string) => Promise<WebSocket>;
16
+ }
17
+ /**
18
+ * Open a gated Gemini Live session for a `type: 'live'` profile.
19
+ *
20
+ * Hosts bridge browser sockets and tool dispatch; THEORUM owns Gemini WS,
21
+ * framing, inbound prep, and outbound canary/egress gates.
22
+ */
23
+ export declare function runSession(req: SessionRequest, options: RunSessionOptions): Promise<LiveSession>;