theorum 1.0.0 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +81 -25
  2. package/esm/mod.d.ts +48 -29
  3. package/esm/mod.js +37 -24
  4. package/esm/src/cli/commands/bench.js +13 -16
  5. package/esm/src/cli/commands/fuzz-canary.js +25 -29
  6. package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
  7. package/esm/src/cli/commands/guardrails-eval.js +15 -0
  8. package/esm/src/cli/commands/profile.js +12 -3
  9. package/esm/src/cli/commands/run.js +8 -9
  10. package/esm/src/cli/commands/test.js +8 -8
  11. package/esm/src/cli/event-log.d.ts +1 -1
  12. package/esm/src/cli/index.js +12 -0
  13. package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
  14. package/esm/src/cli/matrix/synthesizer.js +23 -22
  15. package/esm/src/guardrails/canary-gate.d.ts +2 -1
  16. package/esm/src/guardrails/canary-gate.js +2 -1
  17. package/esm/src/guardrails/canary.d.ts +9 -1
  18. package/esm/src/guardrails/canary.js +21 -10
  19. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
  20. package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
  21. package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
  22. package/esm/src/guardrails/corpus/live-attacks.js +0 -1
  23. package/esm/src/guardrails/corpus/mod.d.ts +1 -0
  24. package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
  25. package/esm/src/guardrails/corpus/secrets.js +1 -0
  26. package/esm/src/guardrails/corpus/strings.d.ts +1 -0
  27. package/esm/src/guardrails/corpus/strings.js +1 -0
  28. package/esm/src/guardrails/corpus/types.d.ts +1 -0
  29. package/esm/src/guardrails/egress.d.ts +26 -3
  30. package/esm/src/guardrails/egress.js +69 -18
  31. package/esm/src/guardrails/error.d.ts +14 -23
  32. package/esm/src/guardrails/error.js +86 -77
  33. package/esm/src/guardrails/eval/corpus.d.ts +108 -0
  34. package/esm/src/guardrails/eval/corpus.js +978 -0
  35. package/esm/src/guardrails/eval/mod.d.ts +51 -0
  36. package/esm/src/guardrails/eval/mod.js +133 -0
  37. package/esm/src/guardrails/eval/score.d.ts +66 -0
  38. package/esm/src/guardrails/eval/score.js +114 -0
  39. package/esm/src/guardrails/events.d.ts +25 -0
  40. package/esm/src/guardrails/events.js +56 -0
  41. package/esm/src/guardrails/hits.d.ts +24 -0
  42. package/esm/src/guardrails/hits.js +45 -0
  43. package/esm/src/guardrails/lexicon.d.ts +39 -0
  44. package/esm/src/guardrails/lexicon.js +200 -0
  45. package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
  46. package/esm/src/guardrails/live-outbound-gate.js +131 -89
  47. package/esm/src/guardrails/mod.d.ts +19 -4
  48. package/esm/src/guardrails/mod.js +12 -3
  49. package/esm/src/guardrails/network.d.ts +19 -0
  50. package/esm/src/guardrails/network.js +234 -0
  51. package/esm/src/guardrails/policy.d.ts +35 -0
  52. package/esm/src/guardrails/policy.js +50 -0
  53. package/esm/src/guardrails/progressive-yield.d.ts +51 -0
  54. package/esm/src/guardrails/progressive-yield.js +98 -0
  55. package/esm/src/guardrails/quota.d.ts +17 -3
  56. package/esm/src/guardrails/quota.js +18 -4
  57. package/esm/src/guardrails/sanitize.d.ts +36 -6
  58. package/esm/src/guardrails/sanitize.js +152 -60
  59. package/esm/src/guardrails/serialize.d.ts +35 -0
  60. package/esm/src/guardrails/serialize.js +58 -0
  61. package/esm/src/guardrails/testing.d.ts +7 -0
  62. package/esm/src/guardrails/testing.js +4 -0
  63. package/esm/src/guardrails/theorum-error.d.ts +12 -0
  64. package/esm/src/guardrails/theorum-error.js +15 -0
  65. package/esm/src/guardrails/tool-directives.d.ts +48 -0
  66. package/esm/src/guardrails/tool-directives.js +124 -0
  67. package/esm/src/guardrails/tool-result.d.ts +93 -0
  68. package/esm/src/guardrails/tool-result.js +276 -0
  69. package/esm/src/guardrails/types.d.ts +291 -0
  70. package/esm/src/guardrails/types.js +72 -0
  71. package/esm/src/host/client-turn.js +4 -0
  72. package/esm/src/host/mint-trace.d.ts +1 -1
  73. package/esm/src/kernel/auth/crypto.d.ts +42 -0
  74. package/esm/src/kernel/auth/crypto.js +106 -0
  75. package/esm/src/kernel/auth/mod.d.ts +11 -0
  76. package/esm/src/kernel/auth/mod.js +11 -0
  77. package/esm/src/kernel/auth/oauth.d.ts +47 -0
  78. package/esm/src/kernel/auth/oauth.js +278 -0
  79. package/esm/src/kernel/auth/types.d.ts +133 -0
  80. package/esm/src/kernel/auth/types.js +13 -0
  81. package/esm/src/kernel/engine/delta.js +43 -2
  82. package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
  83. package/esm/src/kernel/engine/live-inbound.js +22 -8
  84. package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
  85. package/esm/src/kernel/engine/live-ingress.js +47 -0
  86. package/esm/src/kernel/engine/repair.js +13 -12
  87. package/esm/src/kernel/engine/runner/gates.js +127 -40
  88. package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
  89. package/esm/src/kernel/engine/runner/mod.js +182 -43
  90. package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
  91. package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
  92. package/esm/src/kernel/engine/runner/stages.js +89 -0
  93. package/esm/src/kernel/engine/runner/state.d.ts +24 -0
  94. package/esm/src/kernel/engine/runner/steps.js +128 -61
  95. package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
  96. package/esm/src/kernel/engine/runner/stream.js +114 -55
  97. package/esm/src/kernel/engine/session/mod.d.ts +3 -1
  98. package/esm/src/kernel/engine/session/mod.js +386 -34
  99. package/esm/src/kernel/interaction-parts.d.ts +14 -0
  100. package/esm/src/kernel/interaction-parts.js +23 -0
  101. package/esm/src/kernel/mod.d.ts +19 -7
  102. package/esm/src/kernel/mod.js +10 -5
  103. package/esm/src/kernel/profile-graph.d.ts +159 -0
  104. package/esm/src/kernel/profile-graph.js +156 -0
  105. package/esm/src/kernel/registry/attachments.d.ts +10 -10
  106. package/esm/src/kernel/registry/attachments.js +29 -26
  107. package/esm/src/kernel/registry/catalog.d.ts +25 -11
  108. package/esm/src/kernel/registry/catalog.js +59 -24
  109. package/esm/src/kernel/registry/ingress.js +34 -22
  110. package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
  111. package/esm/src/kernel/registry/profile-outputs.js +8 -0
  112. package/esm/src/kernel/registry/profiles.d.ts +34 -14
  113. package/esm/src/kernel/registry/profiles.js +306 -59
  114. package/esm/src/kernel/registry/provider-request.js +2 -0
  115. package/esm/src/kernel/registry/resolve.d.ts +7 -6
  116. package/esm/src/kernel/registry/resolve.js +102 -117
  117. package/esm/src/kernel/registry/schemas.js +1 -1
  118. package/esm/src/kernel/registry/sole-model.d.ts +8 -0
  119. package/esm/src/kernel/registry/sole-model.js +10 -0
  120. package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
  121. package/esm/src/kernel/registry/system-prompt.js +40 -0
  122. package/esm/src/kernel/registry/system-role.d.ts +8 -0
  123. package/esm/src/kernel/registry/system-role.js +14 -0
  124. package/esm/src/kernel/registry/vault.d.ts +2 -2
  125. package/esm/src/kernel/registry/vault.js +4 -4
  126. package/esm/src/kernel/schema.d.ts +97 -11
  127. package/esm/src/kernel/schema.js +256 -111
  128. package/esm/src/kernel/stages.d.ts +175 -0
  129. package/esm/src/kernel/stages.js +476 -0
  130. package/esm/src/kernel/stop.d.ts +65 -11
  131. package/esm/src/kernel/stop.js +46 -11
  132. package/esm/src/kernel/tools/events.d.ts +41 -0
  133. package/esm/src/kernel/tools/events.js +71 -0
  134. package/esm/src/kernel/tools/execute.d.ts +63 -24
  135. package/esm/src/kernel/tools/execute.js +475 -224
  136. package/esm/src/kernel/tools/harness.js +16 -14
  137. package/esm/src/kernel/tools/invoke.js +26 -8
  138. package/esm/src/kernel/tools/mod.d.ts +3 -1
  139. package/esm/src/kernel/tools/mod.js +2 -1
  140. package/esm/src/kernel/tools/permission.d.ts +15 -0
  141. package/esm/src/kernel/tools/permission.js +47 -0
  142. package/esm/src/kernel/tools/project.js +5 -3
  143. package/esm/src/kernel/tools/registry.js +20 -10
  144. package/esm/src/kernel/tools/remote.d.ts +94 -0
  145. package/esm/src/kernel/tools/remote.js +577 -0
  146. package/esm/src/kernel/tools/resolve.d.ts +17 -7
  147. package/esm/src/kernel/tools/resolve.js +38 -17
  148. package/esm/src/kernel/tools/schema.d.ts +2 -0
  149. package/esm/src/kernel/tools/schema.js +23 -6
  150. package/esm/src/kernel/tools/stage-run.d.ts +105 -0
  151. package/esm/src/kernel/tools/stage-run.js +155 -0
  152. package/esm/src/kernel/tools/types.d.ts +198 -20
  153. package/esm/src/kernel/types.d.ts +253 -114
  154. package/esm/src/kernel/util/find-last.d.ts +2 -0
  155. package/esm/src/kernel/util/find-last.js +10 -0
  156. package/esm/src/observability/destinations.d.ts +31 -0
  157. package/esm/src/observability/destinations.js +67 -0
  158. package/esm/src/observability/mod.d.ts +10 -3
  159. package/esm/src/observability/mod.js +6 -2
  160. package/esm/src/observability/policy.d.ts +27 -0
  161. package/esm/src/observability/policy.js +80 -0
  162. package/esm/src/observability/resolve-policy.d.ts +16 -0
  163. package/esm/src/observability/resolve-policy.js +64 -0
  164. package/esm/src/observability/trace-attach.d.ts +3 -1
  165. package/esm/src/observability/trace-attach.js +17 -10
  166. package/esm/src/observability/trace-record.d.ts +12 -4
  167. package/esm/src/observability/trace-record.js +63 -19
  168. package/esm/src/observability/trace-sink.d.ts +19 -0
  169. package/esm/src/observability/trace-sink.js +10 -0
  170. package/esm/src/observability/trace.d.ts +14 -11
  171. package/esm/src/observability/trace.js +23 -14
  172. package/esm/src/observability/types.d.ts +113 -0
  173. package/esm/src/observability/types.js +11 -0
  174. package/esm/src/providers/create-provider.d.ts +7 -4
  175. package/esm/src/providers/create-provider.js +21 -7
  176. package/esm/src/providers/google/interactions/framing.d.ts +8 -0
  177. package/esm/src/providers/google/interactions/framing.js +74 -13
  178. package/esm/src/providers/google/live/framing.d.ts +5 -2
  179. package/esm/src/providers/google/live/framing.js +49 -12
  180. package/esm/src/providers/google/live/openapi-schema.js +3 -2
  181. package/esm/src/providers/google/live/stream.d.ts +9 -0
  182. package/esm/src/providers/google/live/stream.js +16 -3
  183. package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
  184. package/esm/src/providers/openrouter/cache-control.js +23 -0
  185. package/esm/src/providers/openrouter/chat.d.ts +21 -1
  186. package/esm/src/providers/openrouter/chat.js +51 -6
  187. package/esm/src/providers/openrouter/image.js +8 -19
  188. package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
  189. package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
  190. package/esm/src/providers/openrouter/openai/compat.js +23 -4
  191. package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
  192. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
  193. package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
  194. package/package.json +1 -1
  195. package/esm/src/interface/blocks.d.ts +0 -20
  196. package/esm/src/interface/blocks.js +0 -180
  197. package/esm/src/interface/from-profile.d.ts +0 -14
  198. package/esm/src/interface/from-profile.js +0 -104
  199. package/esm/src/interface/inputs.d.ts +0 -8
  200. package/esm/src/interface/inputs.js +0 -127
  201. package/esm/src/interface/mod.d.ts +0 -10
  202. package/esm/src/interface/mod.js +0 -9
  203. package/esm/src/interface/types.d.ts +0 -153
  204. package/esm/src/interface/types.js +0 -13
@@ -8,27 +8,51 @@
8
8
  * @module
9
9
  */
10
10
  import { bindCanary } from '../../../guardrails/canary.js';
11
- import { throwIfAborted } from '../../../guardrails/error.js';
12
- import { sanitizeTurnRequest } from '../../../guardrails/sanitize.js';
11
+ import { isAbortError, throwIfAborted } from '../../../guardrails/error.js';
12
+ import { projectGuardrailTurnEvent } from '../../../guardrails/events.js';
13
+ import { resolveGuardrailPolicy } from '../../../guardrails/policy.js';
14
+ import { sanitizeTurnRequestWithEvents } from '../../../guardrails/sanitize.js';
15
+ import { resolveTraceWriter } from '../../../observability/policy.js';
13
16
  import { noopSink, writeTrace } from '../../../observability/trace.js';
14
17
  import { buildRecord } from '../../../observability/trace-record.js';
15
- import { pickSystemRole, resolveTurn } from '../../registry/resolve.js';
16
- import { CONTINUE_INSTRUCTION } from '../../stop.js';
17
- import { expandT1Policy } from '../../tools/resolve.js';
18
+ import { getProfile } from '../../registry/profiles.js';
19
+ import { resolveTurn } from '../../registry/resolve.js';
20
+ import { cloneTurnToolSnapshot, expandT1Policy } from '../../tools/resolve.js';
18
21
  import { resolveCompactionTokens, shouldCompact, splitForCompaction } from '../compaction.js';
19
22
  import { runAttemptsWithValidation } from './gates.js';
20
- import { shouldSkipStreamEvent, systemFromProfile } from './stream.js';
23
+ import { applyTurnStage } from './stages.js';
24
+ import { shouldSkipStreamEvent } from './stream.js';
21
25
  import { calculateFallbackTokens } from './tokens.js';
26
+ function projectForObs(event, policy) {
27
+ return projectGuardrailTurnEvent(event, policy?.include.guardrailMatchPreview ?? false);
28
+ }
29
+ function* emitObservedStage(ctx, stageEv, profile) {
30
+ const stageOut = projectForObs(stageEv, ctx.observability);
31
+ ctx.seen.push(stageOut);
32
+ if (!shouldSkipStreamEvent(stageOut, profile))
33
+ yield stageOut;
34
+ }
22
35
  function getCompactionSpec(profile, modelId) {
23
- return profile.model.config[modelId]?.compaction;
36
+ return profile.models[modelId]?.compaction;
37
+ }
38
+ /** Fold one history message to a compaction transcript line (media → markers). */
39
+ function compactionTranscriptLine(m) {
40
+ const text = m.content ?? '';
41
+ const mediaMarkers = m.parts
42
+ ?.filter((p) => p.type !== 'text')
43
+ .map((p) => `[${p.type}]`)
44
+ .join('') ?? '';
45
+ const fromTextParts = !text && m.parts
46
+ ? m.parts
47
+ .filter((p) => p.type === 'text')
48
+ .map((p) => p.text)
49
+ .join('')
50
+ : '';
51
+ const content = `${text || fromTextParts}${mediaMarkers}`;
52
+ return `[${m.role}]: ${content}`;
24
53
  }
25
54
  async function runCompactionTurn(toCompact, spec, provider, signal) {
26
- const compactText = toCompact
27
- .map((m) => {
28
- const content = m.content ?? m.parts?.map((p) => ('text' in p ? p.text : '')).join('') ?? '';
29
- return `[${m.role}]: ${content}`;
30
- })
31
- .join('\n');
55
+ const compactText = toCompact.map(compactionTranscriptLine).join('\n');
32
56
  const events = [];
33
57
  for await (const event of runTurn({
34
58
  profile: spec.profile,
@@ -102,14 +126,95 @@ async function* emitTurn(args) {
102
126
  allEmittedEvents: [],
103
127
  attemptEvents: [],
104
128
  };
129
+ args.outState.state = state;
130
+ const pre = yield* applyTurnStage({
131
+ profile,
132
+ generation,
133
+ state,
134
+ stage: 'pre_turn',
135
+ step: 1,
136
+ onStage: safe.onStage,
137
+ signal: safe.signal,
138
+ foldInput: true,
139
+ host: generation.host,
140
+ });
141
+ if (pre.abort) {
142
+ const stop = {
143
+ kind: 'cancelled',
144
+ ...(typeof pre.abort === 'object' && pre.abort.reason ? { native: pre.abort.reason } : {}),
145
+ };
146
+ const done = { type: 'done', stop };
147
+ state.allEmittedEvents.push(done);
148
+ yield done;
149
+ yield* applyTurnStage({
150
+ profile,
151
+ generation,
152
+ state,
153
+ stage: 'post_turn',
154
+ step: 1,
155
+ onStage: safe.onStage,
156
+ signal: safe.signal,
157
+ stop,
158
+ host: generation.host,
159
+ });
160
+ return;
161
+ }
105
162
  yield* runAttemptsWithValidation(safe, profile, generation, system, provider, upstream, state);
106
163
  if (!state.sawTokensEvent) {
107
164
  yield* calculateFallbackTokens(safe, system, state.allEmittedEvents);
108
165
  }
109
- yield {
166
+ const stop = state.lastStop ?? { kind: 'completed' };
167
+ const done = {
110
168
  type: 'done',
111
- stop: state.lastStop ?? { kind: 'completed' },
169
+ stop,
170
+ ...(stop.kind === 'tool' && state.toolSnapshot
171
+ ? { tools: cloneTurnToolSnapshot(state.toolSnapshot) }
172
+ : {}),
173
+ ...(stop.kind === 'gate' && state.toolSnapshot
174
+ ? { tools: cloneTurnToolSnapshot(state.toolSnapshot) }
175
+ : {}),
112
176
  };
177
+ state.allEmittedEvents.push(done);
178
+ yield done;
179
+ }
180
+ async function* streamTurnEvents(ctx, profile, gen, provider, compactionSpec, isCompacting) {
181
+ if (!ctx.safe || ctx.system === undefined)
182
+ return;
183
+ const outState = {};
184
+ for await (const event of emitTurn({
185
+ safe: ctx.safe,
186
+ profile,
187
+ generation: gen,
188
+ system: ctx.system,
189
+ provider,
190
+ upstream: ctx.upstream,
191
+ outState,
192
+ })) {
193
+ const attached = await maybeAttachAfter(event, ctx, gen, compactionSpec, isCompacting);
194
+ const out = projectForObs(attached, ctx.observability);
195
+ ctx.seen.push(out);
196
+ if (!shouldSkipStreamEvent(out, profile))
197
+ yield out;
198
+ if (out.type === 'done' && outState.state) {
199
+ // Skip duplicate post_turn when pre_turn abort already emitted it inside emitTurn.
200
+ const alreadyPost = outState.state.allEmittedEvents.some((e) => e.type === 'stage' && e.stage === 'post_turn');
201
+ if (!alreadyPost) {
202
+ for await (const stageEv of applyTurnStage({
203
+ profile,
204
+ generation: gen,
205
+ state: outState.state,
206
+ stage: 'post_turn',
207
+ step: Math.max(outState.state.stepCount, 1),
208
+ onStage: ctx.safe.onStage,
209
+ signal: ctx.safe.signal,
210
+ stop: out.stop,
211
+ host: gen.host,
212
+ })) {
213
+ yield* emitObservedStage(ctx, stageEv, profile);
214
+ }
215
+ }
216
+ }
217
+ }
113
218
  }
114
219
  async function flushTurnTrace(sink, ctx) {
115
220
  await writeTrace(sink, buildRecord({
@@ -125,10 +230,11 @@ async function flushTurnTrace(sink, ctx) {
125
230
  generation: ctx.generation,
126
231
  protocol: ctx.protocol,
127
232
  sanitizedReq: ctx.safe,
233
+ observability: ctx.observability,
128
234
  }));
129
235
  }
130
236
  /** Execute one host turn against a provider adapter. */
131
- async function* runTurn(req, provider, sink = noopSink()) {
237
+ async function* runTurn(req, provider, sinkOverride) {
132
238
  const ctx = {
133
239
  req,
134
240
  seen: [],
@@ -136,18 +242,72 @@ async function* runTurn(req, provider, sink = noopSink()) {
136
242
  canary: '',
137
243
  upstream: [],
138
244
  };
245
+ let sink = sinkOverride ?? noopSink();
139
246
  try {
247
+ const profile = getProfile(req.profile);
248
+ const resolved = resolveTraceWriter({
249
+ override: sinkOverride,
250
+ observability: profile.observability,
251
+ });
252
+ sink = resolved.sink;
253
+ ctx.observability = resolved.policy;
140
254
  yield* runTurnBody(ctx, provider);
141
255
  }
142
256
  catch (err) {
257
+ if (isAbortError(err)) {
258
+ yield* emitCancelledDoneAfterAbort(ctx);
259
+ await flushTurnTrace(sink, { ...ctx, thrown: err });
260
+ return;
261
+ }
143
262
  await flushTurnTrace(sink, { ...ctx, thrown: err });
144
263
  throw err;
145
264
  }
146
265
  await flushTurnTrace(sink, ctx);
147
266
  }
267
+ /**
268
+ * Slice 1: host AbortSignal ends with cancelled `done` + `post_turn`, not a bare throw.
269
+ * Skip when a terminal `done` already reached the host stream.
270
+ */
271
+ async function* emitCancelledDoneAfterAbort(ctx) {
272
+ if (ctx.seen.some((e) => e.type === 'done'))
273
+ return;
274
+ const stop = { kind: 'cancelled' };
275
+ const done = { type: 'done', stop };
276
+ const outDone = projectForObs(done, ctx.observability);
277
+ ctx.seen.push(outDone);
278
+ yield outDone;
279
+ const profile = getProfile(ctx.req.profile);
280
+ const gen = ctx.generation;
281
+ if (!gen || !ctx.safe)
282
+ return;
283
+ const state = {
284
+ currentHistory: [...(gen.history ?? [])],
285
+ stepCount: 0,
286
+ sawTokensEvent: false,
287
+ allEmittedEvents: [...ctx.seen],
288
+ attemptEvents: [],
289
+ };
290
+ for await (const stageEv of applyTurnStage({
291
+ profile,
292
+ generation: gen,
293
+ state,
294
+ stage: 'post_turn',
295
+ step: 1,
296
+ onStage: ctx.safe.onStage,
297
+ stop,
298
+ host: gen.host,
299
+ })) {
300
+ yield* emitObservedStage(ctx, stageEv, profile);
301
+ }
302
+ }
148
303
  async function* runTurnBody(ctx, provider) {
149
- ctx.safe = sanitizeTurnRequest(ctx.req);
150
- throwIfAborted(ctx.safe.signal);
304
+ const sanitized = sanitizeTurnRequestWithEvents(ctx.req);
305
+ ctx.safe = sanitized.request;
306
+ for (const event of sanitized.events) {
307
+ const out = projectForObs(event, ctx.observability);
308
+ ctx.seen.push(out);
309
+ yield out;
310
+ }
151
311
  const { profile, generation: gen } = resolveTurn(ctx.safe);
152
312
  await expandT1Policy(gen.tools, profile, ctx.safe);
153
313
  gen.builtins = gen.tools.builtins;
@@ -155,16 +315,12 @@ async function* runTurnBody(ctx, provider) {
155
315
  ctx.model = gen.model;
156
316
  ctx.keySlot = gen.keySlot;
157
317
  ctx.canary = gen.canary;
158
- ctx.protocol = profile.model.protocol;
318
+ ctx.protocol = profile.models[gen.model]?.protocol;
319
+ throwIfAborted(ctx.safe.signal);
159
320
  const isCompacting = ctx.req.metadata?._compacting === true;
160
321
  const compactionSpec = isCompacting ? undefined : getCompactionSpec(profile, gen.model);
161
322
  await maybeCompactBefore(ctx, gen, compactionSpec, provider);
162
- const role = pickSystemRole(profile, ctx.safe.input?.role);
163
- const continueSys = ctx.safe.continueFrom ? CONTINUE_INSTRUCTION : '';
164
- const combinedSys = [systemFromProfile(profile, role), ctx.safe.system, continueSys]
165
- .filter(Boolean)
166
- .join('\n\n');
167
- ctx.system = bindCanary(combinedSys, ctx.canary);
323
+ ctx.system = bindCanary(gen.resolvedSystem, ctx.canary, resolveGuardrailPolicy(profile.guardrails).canaryBindNote);
168
324
  yield* streamTurnEvents(ctx, profile, gen, provider, compactionSpec, isCompacting);
169
325
  }
170
326
  async function maybeCompactBefore(ctx, gen, compactionSpec, provider) {
@@ -179,23 +335,6 @@ async function maybeCompactBefore(ctx, gen, compactionSpec, provider) {
179
335
  signal: ctx.safe.signal,
180
336
  });
181
337
  }
182
- async function* streamTurnEvents(ctx, profile, gen, provider, compactionSpec, isCompacting) {
183
- if (!ctx.safe || ctx.system === undefined)
184
- return;
185
- for await (const event of emitTurn({
186
- safe: ctx.safe,
187
- profile,
188
- generation: gen,
189
- system: ctx.system,
190
- provider,
191
- upstream: ctx.upstream,
192
- })) {
193
- const out = await maybeAttachAfter(event, ctx, gen, compactionSpec, isCompacting);
194
- ctx.seen.push(out);
195
- if (!shouldSkipStreamEvent(out, profile))
196
- yield out;
197
- }
198
- }
199
338
  async function maybeAttachAfter(event, ctx, gen, compactionSpec, isCompacting) {
200
339
  if (!(event.type === 'done' && compactionSpec?.timing === 'after' && !isCompacting && ctx.safe)) {
201
340
  return event;
@@ -207,4 +346,4 @@ async function maybeAttachAfter(event, ctx, gen, compactionSpec, isCompacting) {
207
346
  seen: ctx.seen,
208
347
  });
209
348
  }
210
- export { runTurn };
349
+ export { compactionTranscriptLine, runTurn };
@@ -41,7 +41,7 @@ function joinPath(prefix, key) {
41
41
  return prefix ? `${prefix}.${key}` : key;
42
42
  }
43
43
  function pushMissing(path, failures) {
44
- failures.push({ path, error: `required field '${path}' is missing` });
44
+ failures.push({ path, error: `required field '${path}' is missing` }); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
45
45
  }
46
46
  async function runFieldValidator(path, value, validator, slots, failures) {
47
47
  if (!validator) {
@@ -53,7 +53,7 @@ async function runFieldValidator(path, value, validator, slots, failures) {
53
53
  }
54
54
  failures.push({
55
55
  path,
56
- error: check.error || check.finding || `Validation failed for '${path}'`,
56
+ error: check.error || check.finding || `Validation failed for '${path}'`, // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
57
57
  });
58
58
  }
59
59
  async function visitProperty(args) {
@@ -95,7 +95,7 @@ async function walkObject(schema, value, pathPrefix, fields, slots, failures) {
95
95
  async function collectValidationFailures(jsonSchema, structured, fields, slots) {
96
96
  const root = asObjectSchema(jsonSchema);
97
97
  if (!root) {
98
- throw new TheorumError('structured validation requires a JSON Schema object root');
98
+ throw new TheorumError('structured validation requires a JSON Schema object root'); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
99
99
  }
100
100
  const failures = [];
101
101
  if (!asRecord(structured)) {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Text `runTurn` wiring around the stage spine: record stage events in step
3
+ * state, fold the opening input, and append sanitized injects to turn history.
4
+ *
5
+ * Contract: `docs/contracts/stages.md`. The stage itself runs in `runStage`.
6
+ *
7
+ * @module
8
+ */
9
+ import { type StageApplyWarning, type StageCallBag, type StageContext, type StageHandler } from '../../stages.js';
10
+ import type { Profile, ResolvedGeneration, TurnEvent, TurnHistoryMessage } from '../../types.js';
11
+ import type { StepExecutionState } from './state.js';
12
+ export interface ApplyTurnStageArgs extends StageCallBag {
13
+ profile: Profile;
14
+ generation: ResolvedGeneration;
15
+ state: StepExecutionState;
16
+ stage: StageContext['stage'];
17
+ step: number;
18
+ onStage?: StageHandler;
19
+ signal?: AbortSignal;
20
+ /** Fold generation.input into history when true (pre_turn + handler present). */
21
+ foldInput?: boolean;
22
+ /** Another provider step would exceed maxSteps — reject inject. */
23
+ injectWouldExceedMaxSteps?: boolean;
24
+ host?: unknown;
25
+ }
26
+ export interface ApplyTurnStageResult {
27
+ abort?: boolean | {
28
+ reason?: string;
29
+ };
30
+ injectCount: number;
31
+ warnings: StageApplyWarning[];
32
+ }
33
+ /**
34
+ * Append already-sanitized inject messages to turn history. Mirrors them into
35
+ * the Interactions continuation when one is active. Returns how many landed.
36
+ */
37
+ export declare function applyStageInjects(state: StepExecutionState, inject: readonly TurnHistoryMessage[]): number;
38
+ /** Run a turn stage, recording every stage event on step state. */
39
+ export declare function applyTurnStage(args: ApplyTurnStageArgs): AsyncGenerator<TurnEvent, ApplyTurnStageResult>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Text `runTurn` wiring around the stage spine: record stage events in step
3
+ * state, fold the opening input, and append sanitized injects to turn history.
4
+ *
5
+ * Contract: `docs/contracts/stages.md`. The stage itself runs in `runStage`.
6
+ *
7
+ * @module
8
+ */
9
+ import { throwIfAborted } from '../../../guardrails/error.js';
10
+ import { wireInteractionPart } from '../../interaction-parts.js';
11
+ import { runStage, } from '../../stages.js';
12
+ import { profileAllowsInject } from '../../stop.js';
13
+ function stageMessageToInteractionStep(msg) {
14
+ const type = msg.role === 'assistant' ? 'model_output' : 'user_input';
15
+ if (msg.parts && msg.parts.length > 0) {
16
+ return {
17
+ type,
18
+ content: msg.parts.map(wireInteractionPart),
19
+ };
20
+ }
21
+ return { type, content: [{ type: 'text', text: msg.content ?? '' }] };
22
+ }
23
+ /** Move opening user `generation.input` into history (Interactions-safe fold). */
24
+ function foldGenerationInputIntoHistory(generation, state) {
25
+ if (state.foldedForGeneration === generation)
26
+ return;
27
+ state.foldedForGeneration = generation;
28
+ if (generation.input.length === 0)
29
+ return;
30
+ const textOnly = generation.input.every((p) => p.type === 'text');
31
+ if (textOnly) {
32
+ state.currentHistory.push({
33
+ role: 'user',
34
+ content: generation.input.map((p) => (p.type === 'text' ? p.text : '')).join(''),
35
+ });
36
+ }
37
+ else {
38
+ state.currentHistory.push({
39
+ role: 'user',
40
+ parts: [...generation.input],
41
+ });
42
+ }
43
+ generation.input = [];
44
+ }
45
+ /**
46
+ * Append already-sanitized inject messages to turn history. Mirrors them into
47
+ * the Interactions continuation when one is active. Returns how many landed.
48
+ */
49
+ export function applyStageInjects(state, inject) {
50
+ if (inject.length === 0)
51
+ return 0;
52
+ state.currentHistory.push(...inject);
53
+ if (state.interactionsContinuation) {
54
+ for (const msg of inject) {
55
+ if (msg.role === 'tool')
56
+ continue;
57
+ state.interactionsContinuation.input.push(stageMessageToInteractionStep(msg));
58
+ }
59
+ }
60
+ return inject.length;
61
+ }
62
+ /** Run a turn stage, recording every stage event on step state. */
63
+ export async function* applyTurnStage(args) {
64
+ const { profile, generation, state, onStage, foldInput, host, ...call } = args;
65
+ throwIfAborted(call.signal);
66
+ if (foldInput && onStage) {
67
+ foldGenerationInputIntoHistory(generation, state);
68
+ }
69
+ const run = runStage({
70
+ ...call,
71
+ history: state.currentHistory,
72
+ handlers: onStage ? [onStage] : [],
73
+ guardrails: profile.guardrails,
74
+ injectAllowed: profileAllowsInject(profile),
75
+ host: host ?? generation.host,
76
+ });
77
+ let next = await run.next();
78
+ while (!next.done) {
79
+ state.allEmittedEvents.push(next.value);
80
+ yield next.value;
81
+ next = await run.next();
82
+ }
83
+ const out = next.value;
84
+ return {
85
+ abort: out.abort,
86
+ injectCount: applyStageInjects(state, out.inject),
87
+ warnings: out.warnings,
88
+ };
89
+ }
@@ -1,3 +1,5 @@
1
+ import type { TurnTaint } from '../../../guardrails/types.js';
2
+ import type { TurnToolSnapshot } from '../../tools/types.js';
1
3
  import type { ResolvedGeneration, TurnEvent, TurnHistoryMessage, TurnRequest, TurnStop } from '../../types.js';
2
4
  interface StepExecutionState {
3
5
  currentHistory: TurnHistoryMessage[];
@@ -5,8 +7,25 @@ interface StepExecutionState {
5
7
  sawTokensEvent: boolean;
6
8
  allEmittedEvents: TurnEvent[];
7
9
  attemptEvents: TurnEvent[];
10
+ /**
11
+ * True when progressive yield withheld user-visible events during this attempt.
12
+ *
13
+ * The attempt gate needs it: if the mid-stream window tripped but the final
14
+ * verdict on the whole text passes, nothing was streamed, so the buffered
15
+ * text must be released rather than silently dropped.
16
+ */
17
+ withheldVisible?: boolean;
18
+ /**
19
+ * Untrusted remote content this turn has already read.
20
+ *
21
+ * Accumulates across tool calls so a later call can be judged against what the
22
+ * turn has ingested, not just its own arguments.
23
+ */
24
+ taint?: TurnTaint;
8
25
  /** Last provider stop from a discarded provider `done` event. */
9
26
  lastStop?: TurnStop;
27
+ /** Tool snapshot at tool pause — emitted on terminal `done` when `stop.kind === 'tool'`. */
28
+ toolSnapshot?: TurnToolSnapshot;
10
29
  /** Latest Google Interactions id observed on the current provider stream. */
11
30
  lastInteractionId?: string;
12
31
  /** Pending Interactions `function_result` continuation for the next provider step. */
@@ -14,6 +33,11 @@ interface StepExecutionState {
14
33
  previousInteractionId: string;
15
34
  input: Record<string, unknown>[];
16
35
  };
36
+ /**
37
+ * Generation whose opening `input` was folded into `currentHistory` for steering.
38
+ * Compared by identity so repair retries (new generation) fold again.
39
+ */
40
+ foldedForGeneration?: ResolvedGeneration;
17
41
  }
18
42
  interface AttemptFlowState {
19
43
  currentAttempt: number;