@namzu/sdk 36.0.0 → 37.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 (308) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/public-runtime.d.ts +3 -1
  57. package/dist/public-runtime.d.ts.map +1 -1
  58. package/dist/public-runtime.js +2 -1
  59. package/dist/public-runtime.js.map +1 -1
  60. package/dist/public-tools.d.ts +1 -1
  61. package/dist/public-tools.d.ts.map +1 -1
  62. package/dist/public-tools.js +1 -1
  63. package/dist/public-tools.js.map +1 -1
  64. package/dist/public-types.d.ts +1 -0
  65. package/dist/public-types.d.ts.map +1 -1
  66. package/dist/registry/tool/execute.d.ts +3 -0
  67. package/dist/registry/tool/execute.d.ts.map +1 -1
  68. package/dist/registry/tool/execute.js +11 -80
  69. package/dist/registry/tool/execute.js.map +1 -1
  70. package/dist/run/command-gate.d.ts.map +1 -1
  71. package/dist/run/command-gate.js +65 -12
  72. package/dist/run/command-gate.js.map +1 -1
  73. package/dist/run/index.d.ts +2 -0
  74. package/dist/run/index.d.ts.map +1 -1
  75. package/dist/run/index.js +1 -0
  76. package/dist/run/index.js.map +1 -1
  77. package/dist/run/memory-promoter.d.ts +3 -35
  78. package/dist/run/memory-promoter.d.ts.map +1 -1
  79. package/dist/run/memory-promoter.js +32 -40
  80. package/dist/run/memory-promoter.js.map +1 -1
  81. package/dist/run/memory-recall.d.ts +25 -0
  82. package/dist/run/memory-recall.d.ts.map +1 -0
  83. package/dist/run/memory-recall.js +228 -0
  84. package/dist/run/memory-recall.js.map +1 -0
  85. package/dist/run/reporter.d.ts.map +1 -1
  86. package/dist/run/reporter.js +1 -0
  87. package/dist/run/reporter.js.map +1 -1
  88. package/dist/run/token-budget.d.ts +2 -0
  89. package/dist/run/token-budget.d.ts.map +1 -1
  90. package/dist/run/token-budget.js +4 -0
  91. package/dist/run/token-budget.js.map +1 -1
  92. package/dist/run/workspace-fingerprint.d.ts +10 -3
  93. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  94. package/dist/run/workspace-fingerprint.js +17 -8
  95. package/dist/run/workspace-fingerprint.js.map +1 -1
  96. package/dist/runtime/query/checkpoint.d.ts +14 -0
  97. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  98. package/dist/runtime/query/checkpoint.js +82 -1
  99. package/dist/runtime/query/checkpoint.js.map +1 -1
  100. package/dist/runtime/query/executor.d.ts +5 -0
  101. package/dist/runtime/query/executor.d.ts.map +1 -1
  102. package/dist/runtime/query/executor.js +70 -14
  103. package/dist/runtime/query/executor.js.map +1 -1
  104. package/dist/runtime/query/index.d.ts +5 -8
  105. package/dist/runtime/query/index.d.ts.map +1 -1
  106. package/dist/runtime/query/index.js +54 -7
  107. package/dist/runtime/query/index.js.map +1 -1
  108. package/dist/runtime/query/iteration/index.d.ts +10 -0
  109. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  110. package/dist/runtime/query/iteration/index.js +325 -36
  111. package/dist/runtime/query/iteration/index.js.map +1 -1
  112. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  113. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  114. package/dist/runtime/query/iteration/native-output.js +32 -0
  115. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  116. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  117. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  118. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  119. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  120. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  121. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  122. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  124. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  127. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  128. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  130. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  132. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  133. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/stream-turn.js +2 -0
  135. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  136. package/dist/runtime/query/steering.d.ts +6 -2
  137. package/dist/runtime/query/steering.d.ts.map +1 -1
  138. package/dist/runtime/query/steering.js +17 -2
  139. package/dist/runtime/query/steering.js.map +1 -1
  140. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  141. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  142. package/dist/runtime/query/tool-call-budget.js +110 -0
  143. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  144. package/dist/runtime/query/tooling.d.ts +2 -0
  145. package/dist/runtime/query/tooling.d.ts.map +1 -1
  146. package/dist/runtime/query/tooling.js +4 -0
  147. package/dist/runtime/query/tooling.js.map +1 -1
  148. package/dist/scheduler/local.d.ts.map +1 -1
  149. package/dist/scheduler/local.js +1 -0
  150. package/dist/scheduler/local.js.map +1 -1
  151. package/dist/session/handoff/capacity.d.ts +2 -2
  152. package/dist/session/handoff/capacity.d.ts.map +1 -1
  153. package/dist/session/handoff/capacity.js +3 -2
  154. package/dist/session/handoff/capacity.js.map +1 -1
  155. package/dist/store/memory/disk.d.ts +6 -2
  156. package/dist/store/memory/disk.d.ts.map +1 -1
  157. package/dist/store/memory/disk.js +42 -25
  158. package/dist/store/memory/disk.js.map +1 -1
  159. package/dist/store/memory/index.d.ts +4 -0
  160. package/dist/store/memory/index.d.ts.map +1 -1
  161. package/dist/store/memory/index.js +55 -19
  162. package/dist/store/memory/index.js.map +1 -1
  163. package/dist/store/memory/memory.d.ts +3 -2
  164. package/dist/store/memory/memory.d.ts.map +1 -1
  165. package/dist/store/memory/memory.js +11 -2
  166. package/dist/store/memory/memory.js.map +1 -1
  167. package/dist/store/memory/operation-lock.d.ts +11 -0
  168. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  169. package/dist/store/memory/operation-lock.js +94 -0
  170. package/dist/store/memory/operation-lock.js.map +1 -0
  171. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  172. package/dist/tools/builtins/computer-use.js +60 -10
  173. package/dist/tools/builtins/computer-use.js.map +1 -1
  174. package/dist/tools/builtins/run-code.d.ts +6 -0
  175. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  176. package/dist/tools/builtins/run-code.js +15 -4
  177. package/dist/tools/builtins/run-code.js.map +1 -1
  178. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  179. package/dist/tools/builtins/search-tools.js +14 -2
  180. package/dist/tools/builtins/search-tools.js.map +1 -1
  181. package/dist/tools/defineTool.d.ts +2 -0
  182. package/dist/tools/defineTool.d.ts.map +1 -1
  183. package/dist/tools/defineTool.js +3 -0
  184. package/dist/tools/defineTool.js.map +1 -1
  185. package/dist/tools/memory/delete.d.ts +4 -0
  186. package/dist/tools/memory/delete.d.ts.map +1 -0
  187. package/dist/tools/memory/delete.js +26 -0
  188. package/dist/tools/memory/delete.js.map +1 -0
  189. package/dist/tools/memory/index.d.ts +2 -0
  190. package/dist/tools/memory/index.d.ts.map +1 -1
  191. package/dist/tools/memory/index.js +6 -0
  192. package/dist/tools/memory/index.js.map +1 -1
  193. package/dist/tools/memory/save.d.ts.map +1 -1
  194. package/dist/tools/memory/save.js +3 -2
  195. package/dist/tools/memory/save.js.map +1 -1
  196. package/dist/tools/memory/search.d.ts.map +1 -1
  197. package/dist/tools/memory/search.js +12 -5
  198. package/dist/tools/memory/search.js.map +1 -1
  199. package/dist/tools/memory/update.d.ts +4 -0
  200. package/dist/tools/memory/update.d.ts.map +1 -0
  201. package/dist/tools/memory/update.js +47 -0
  202. package/dist/tools/memory/update.js.map +1 -0
  203. package/dist/types/agent/scheduler.d.ts +5 -0
  204. package/dist/types/agent/scheduler.d.ts.map +1 -1
  205. package/dist/types/agent/task.d.ts +10 -0
  206. package/dist/types/agent/task.d.ts.map +1 -1
  207. package/dist/types/computer-use/index.d.ts +6 -0
  208. package/dist/types/computer-use/index.d.ts.map +1 -1
  209. package/dist/types/computer-use/index.js.map +1 -1
  210. package/dist/types/errors/catalog.js +1 -1
  211. package/dist/types/errors/catalog.js.map +1 -1
  212. package/dist/types/hitl/index.d.ts +11 -1
  213. package/dist/types/hitl/index.d.ts.map +1 -1
  214. package/dist/types/hitl/index.js.map +1 -1
  215. package/dist/types/memory/index.d.ts +14 -1
  216. package/dist/types/memory/index.d.ts.map +1 -1
  217. package/dist/types/project/entity.d.ts +1 -0
  218. package/dist/types/project/entity.d.ts.map +1 -1
  219. package/dist/types/provider/config.d.ts +2 -0
  220. package/dist/types/provider/config.d.ts.map +1 -1
  221. package/dist/types/provider/model.d.ts +4 -0
  222. package/dist/types/provider/model.d.ts.map +1 -1
  223. package/dist/types/run/answer-review.d.ts +2 -0
  224. package/dist/types/run/answer-review.d.ts.map +1 -1
  225. package/dist/types/run/events.d.ts +9 -0
  226. package/dist/types/run/events.d.ts.map +1 -1
  227. package/dist/types/run/events.js.map +1 -1
  228. package/dist/types/run/prepare-step.d.ts +19 -1
  229. package/dist/types/run/prepare-step.d.ts.map +1 -1
  230. package/dist/types/structured-output/index.d.ts +8 -2
  231. package/dist/types/structured-output/index.d.ts.map +1 -1
  232. package/dist/types/tool/index.d.ts +15 -0
  233. package/dist/types/tool/index.d.ts.map +1 -1
  234. package/dist/types/tool/index.js.map +1 -1
  235. package/dist/utils/json-snapshot.d.ts +3 -0
  236. package/dist/utils/json-snapshot.d.ts.map +1 -0
  237. package/dist/utils/json-snapshot.js +79 -0
  238. package/dist/utils/json-snapshot.js.map +1 -0
  239. package/package.json +4 -2
  240. package/src/bridge/a2a/mapper.ts +2 -0
  241. package/src/bridge/sse/mapper.ts +2 -0
  242. package/src/compaction/extractor.ts +21 -8
  243. package/src/compaction/manager.ts +11 -1
  244. package/src/compaction/verifier.ts +80 -18
  245. package/src/eval/harness-verification.ts +270 -0
  246. package/src/eval/index.ts +10 -0
  247. package/src/execution/code-runtime/json-value.ts +82 -0
  248. package/src/execution/code-runtime/worker-program.ts +239 -0
  249. package/src/execution/code-runtime/worker.ts +164 -111
  250. package/src/manager/agent/lifecycle.ts +340 -45
  251. package/src/manager/run/persistence.ts +7 -0
  252. package/src/provider/capabilities.ts +25 -1
  253. package/src/provider/fallback.ts +2 -0
  254. package/src/public-runtime.ts +4 -0
  255. package/src/public-tools.ts +5 -1
  256. package/src/public-types.ts +1 -0
  257. package/src/registry/tool/execute.ts +17 -79
  258. package/src/run/command-gate.ts +61 -13
  259. package/src/run/index.ts +2 -0
  260. package/src/run/memory-promoter.ts +32 -40
  261. package/src/run/memory-recall.ts +254 -0
  262. package/src/run/reporter.ts +1 -0
  263. package/src/run/token-budget.ts +4 -0
  264. package/src/run/workspace-fingerprint.ts +17 -8
  265. package/src/runtime/query/checkpoint.ts +96 -2
  266. package/src/runtime/query/executor.ts +77 -14
  267. package/src/runtime/query/index.ts +73 -16
  268. package/src/runtime/query/iteration/index.ts +377 -44
  269. package/src/runtime/query/iteration/native-output.ts +35 -0
  270. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  271. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  272. package/src/runtime/query/iteration/phases/context.ts +9 -0
  273. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  274. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  275. package/src/runtime/query/iteration/stream-turn.ts +2 -0
  276. package/src/runtime/query/steering.ts +19 -2
  277. package/src/runtime/query/tool-call-budget.ts +115 -0
  278. package/src/runtime/query/tooling.ts +6 -0
  279. package/src/scheduler/local.ts +1 -0
  280. package/src/session/handoff/capacity.ts +7 -3
  281. package/src/store/memory/disk.ts +52 -30
  282. package/src/store/memory/index.ts +75 -27
  283. package/src/store/memory/memory.ts +18 -6
  284. package/src/store/memory/operation-lock.ts +99 -0
  285. package/src/tools/builtins/computer-use.ts +61 -7
  286. package/src/tools/builtins/run-code.ts +24 -5
  287. package/src/tools/builtins/search-tools.ts +18 -3
  288. package/src/tools/defineTool.ts +5 -0
  289. package/src/tools/memory/delete.ts +29 -0
  290. package/src/tools/memory/index.ts +6 -0
  291. package/src/tools/memory/save.ts +4 -2
  292. package/src/tools/memory/search.ts +12 -5
  293. package/src/tools/memory/update.ts +50 -0
  294. package/src/types/agent/scheduler.ts +6 -0
  295. package/src/types/agent/task.ts +13 -0
  296. package/src/types/computer-use/index.ts +6 -0
  297. package/src/types/errors/catalog.ts +1 -1
  298. package/src/types/hitl/index.ts +11 -1
  299. package/src/types/memory/index.ts +16 -1
  300. package/src/types/project/entity.ts +1 -0
  301. package/src/types/provider/config.ts +2 -0
  302. package/src/types/provider/model.ts +4 -0
  303. package/src/types/run/answer-review.ts +2 -0
  304. package/src/types/run/events.ts +10 -0
  305. package/src/types/run/prepare-step.ts +19 -1
  306. package/src/types/structured-output/index.ts +15 -2
  307. package/src/types/tool/index.ts +16 -0
  308. package/src/utils/json-snapshot.ts +76 -0
@@ -1,6 +1,10 @@
1
1
  import { type Span, SpanStatusCode } from '@opentelemetry/api'
2
2
  import { resolveContextWindow } from '../../../compaction/context-window.js'
3
- import { extractFromAssistantMessage } from '../../../compaction/extractor.js'
3
+ import {
4
+ extractFromAssistantMessage,
5
+ extractFromUserMessage,
6
+ } from '../../../compaction/extractor.js'
7
+ import { estimateMessageTokens } from '../../../compaction/token-estimate.js'
4
8
  import { AUTO_CONTINUATION_USER_MESSAGE } from '../../../constants/continuation.js'
5
9
  import {
6
10
  DEFAULT_STRUCTURED_OUTPUT_RETRIES,
@@ -9,6 +13,7 @@ import {
9
13
  import { renderSkillsSection } from '../../../persona/assembler.js'
10
14
  import { resolveProviderCapabilities } from '../../../provider/capabilities.js'
11
15
  import { collectChatCompletion } from '../../../provider/collect-chat-completion.js'
16
+ import { renderToolSchema } from '../../../registry/tool/schema.js'
12
17
  import { formatCompletionNotification } from '../../../scheduler/completion-inbox.js'
13
18
  import {
14
19
  GENAI,
@@ -24,6 +29,7 @@ import { NamzuError } from '../../../types/errors/index.js'
24
29
  import type { MessageId } from '../../../types/ids/index.js'
25
30
  import {
26
31
  type Message,
32
+ type UserMessage,
27
33
  createAssistantMessage,
28
34
  createRuntimeContextMessage,
29
35
  createSystemMessage,
@@ -33,6 +39,7 @@ import { classifyProviderError } from '../../../types/provider/errors.js'
33
39
  import type { ChatCompletionResponse } from '../../../types/provider/index.js'
34
40
  import type { AnswerReview } from '../../../types/run/answer-review.js'
35
41
  import type {
42
+ PrepareStepContext,
36
43
  PrepareStepResult,
37
44
  RunEvent,
38
45
  StepFailure,
@@ -54,10 +61,16 @@ import {
54
61
  markProviderRejectedImage,
55
62
  projectRequestRichContent,
56
63
  } from '../request-rich-content.js'
57
- import { formatSteeringNote } from '../steering.js'
64
+ import { formatSteeringNote, isOperatorUserMessage } from '../steering.js'
65
+ import { parseNativeCandidate } from './native-output.js'
58
66
  import { runAdvisoryPhase } from './phases/advisory.js'
59
67
  import { runIterationCheckpoint } from './phases/checkpoint.js'
60
- import { measureContext, relieveOverflow, runCompactionCheck } from './phases/compaction.js'
68
+ import {
69
+ activeContextWindow,
70
+ measureContext,
71
+ relieveOverflow,
72
+ runCompactionCheck,
73
+ } from './phases/compaction.js'
61
74
  import type { IterationContext } from './phases/index.js'
62
75
  import { runPlanGate } from './phases/plan.js'
63
76
  import { runToolReview } from './phases/tool-review.js'
@@ -168,9 +181,34 @@ export class IterationOrchestrator {
168
181
  * sets it.
169
182
  */
170
183
  private stopDeferredForOutstandingWork = false
184
+ /** One current input, independent of the compactable history array. */
185
+ private latestUserMessage: UserMessage | undefined
171
186
 
172
187
  constructor(ctx: IterationContext) {
173
- this.ctx = ctx
188
+ this.ctx = {
189
+ ...ctx,
190
+ onSteeringDelivered: (text) => {
191
+ this.rememberUserMessage(createRuntimeContextMessage(text, 'steering'))
192
+ },
193
+ }
194
+ ctx.checkpointMgr.setLatestUserMessageSource(() => this.latestUserMessage)
195
+ ctx.checkpointMgr.setStructuredReviewAttemptsSource?.(() => this.structuredReviewAttempts)
196
+ ctx.checkpointMgr.setNativeStructuredAttemptsSource?.(() => this.nativeStructuredAttempts)
197
+ if (ctx.structuredOutput?.mode === 'native') {
198
+ const limit = ctx.structuredOutput.maxRetries
199
+ if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 0))
200
+ throw new RangeError(
201
+ 'Native structuredOutput.maxRetries must be a nonnegative safe integer',
202
+ )
203
+ }
204
+ if (
205
+ ctx.structuredOutput?.mode !== undefined &&
206
+ !['tool', 'native'].includes(ctx.structuredOutput.mode)
207
+ )
208
+ throw new RangeError('Unknown structuredOutput.mode')
209
+ const maxReviews = ctx.structuredOutput?.maxReviews
210
+ if (maxReviews !== undefined && (!Number.isSafeInteger(maxReviews) || maxReviews < 0))
211
+ throw new RangeError('structuredOutput.maxReviews must be a nonnegative safe integer')
174
212
  }
175
213
 
176
214
  /**
@@ -252,6 +290,17 @@ export class IterationOrchestrator {
252
290
  const { runConfig, runMgr } = this.ctx
253
291
  const { model } = runConfig
254
292
  const tracer = getTracer()
293
+ // Resume hydration happens after construction, before the loop starts.
294
+ this.latestUserMessage = this.ctx.checkpointMgr.restoredLatestUserMessage
295
+ this.structuredReviewAttempts = this.ctx.checkpointMgr.restoredStructuredReviewAttempts ?? 0
296
+ this.nativeStructuredAttempts = this.ctx.checkpointMgr.restoredNativeStructuredAttempts ?? 0
297
+ if (!this.latestUserMessage) {
298
+ for (const message of runMgr.messages) this.rememberUserMessage(message, false)
299
+ }
300
+ // The restored field can outlive the historical message it describes.
301
+ // Only known post-checkpoint arrivals may supersede it, never an old
302
+ // retained user turn encountered while scanning restored history.
303
+ for (const message of this.ctx.resumedInput ?? []) this.rememberUserMessage(message)
255
304
 
256
305
  // One context-overflow relief per *stuck point*, not per run.
257
306
  //
@@ -278,6 +327,26 @@ export class IterationOrchestrator {
278
327
  // no post-loop block reaches.
279
328
  try {
280
329
  while (true) {
330
+ if (this.ctx.abortController.signal.aborted) {
331
+ runMgr.setStopReason('cancelled')
332
+ runMgr.markCancelled()
333
+ break
334
+ }
335
+ if (
336
+ this.ctx.structuredOutput?.mode === 'native' &&
337
+ this.nativeStructuredAttempts > this.structuredOutputRetryLimit()
338
+ ) {
339
+ runMgr.setStopReason('structured_output_failed')
340
+ break
341
+ }
342
+ if (
343
+ this.ctx.structuredOutput?.review &&
344
+ this.structuredReviewAttempts >
345
+ (this.ctx.structuredOutput.maxReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT)
346
+ ) {
347
+ runMgr.setStopReason('answer_rejected')
348
+ break
349
+ }
281
350
  // Read AND clear, in that order, in this one place.
282
351
  //
283
352
  // The flag is set by the previous iteration and read by this
@@ -325,6 +394,14 @@ export class IterationOrchestrator {
325
394
  // run and been paid for; this is the seam a host with a live
326
395
  // rate limit or a revoked tenant actually needs.
327
396
  const veto = await this.beforeStep(runMgr.currentIteration + 1)
397
+ // The hook may settle because its run signal was aborted. Stop
398
+ // before interpreting that settlement as a policy refusal or
399
+ // counting an iteration that will never reach the provider.
400
+ if (this.ctx.abortController.signal.aborted) {
401
+ runMgr.setStopReason('cancelled')
402
+ runMgr.markCancelled()
403
+ break
404
+ }
328
405
  if (veto) {
329
406
  // Namespaced. The un-namespaced keys elsewhere in this file are
330
407
  // the frozen inventory LOG-22 exists to drain; a new call site
@@ -431,10 +508,6 @@ export class IterationOrchestrator {
431
508
  // compaction preserves). No-op when no provider is configured.
432
509
  await refreshWorkingMemory(this.ctx)
433
510
  await runCompactionCheck(this.ctx)
434
- // A context edit is a host-visible state transition, not a prelude
435
- // whose events may wait behind the next network call. In particular,
436
- // a slow or failing provider must not leave the host displaying the
437
- // pre-compaction context after Run.messages already shrank.
438
511
  yield* this.ctx.drainPending()
439
512
 
440
513
  // Cache discipline: keep the tools param byte-stable even on the
@@ -452,10 +525,25 @@ export class IterationOrchestrator {
452
525
  // Shape this step before calling the model. `stopWhen` decides
453
526
  // whether to keep going; this decides HOW. No-op when the host
454
527
  // supplied no hook.
528
+ const contextModelBeforePreparation = this.ctx.contextModel ?? model
455
529
  const step = await this.prepareStep(iterationNum)
530
+ stepModel = step.model ?? model
531
+ await this.selectContextModel(stepModel)
532
+ // Preserve post-compaction preparation/recall semantics. A changed
533
+ // model needs a second check against its own window; never replay
534
+ // host preparation effects merely to rebuild its request guidance.
535
+ if (stepModel !== contextModelBeforePreparation) await runCompactionCheck(this.ctx)
536
+ // Publish context edits before entering a possibly slow provider.
537
+ yield* this.ctx.drainPending()
456
538
 
457
539
  const stepAllowedTools = step.allowedTools ?? this.ctx.allowedTools
458
- const llmTools = this.ctx.tools.toLLMTools(stepAllowedTools)
540
+ const llmTools = this.ctx.tools
541
+ .toLLMTools(stepAllowedTools)
542
+ .filter(
543
+ (tool) =>
544
+ this.ctx.structuredOutput?.mode !== 'native' ||
545
+ tool.function.name !== STRUCTURED_OUTPUT_TOOL_NAME,
546
+ )
459
547
  // The same list the request was built from now also bounds what
460
548
  // may run. Narrowing only the request left the restriction
461
549
  // presentational — the model was shown fewer tools and could
@@ -632,6 +720,18 @@ export class IterationOrchestrator {
632
720
  this.ctx.provider,
633
721
  {
634
722
  model: stepModel,
723
+ ...(this.ctx.structuredOutput?.mode === 'native'
724
+ ? {
725
+ responseFormat: {
726
+ type: 'json_schema' as const,
727
+ json_schema: {
728
+ name: 'structured_output',
729
+ schema: renderToolSchema(this.ctx.structuredOutput.schema),
730
+ strict: true,
731
+ },
732
+ },
733
+ }
734
+ : {}),
635
735
  providerRoute: requestedRoute,
636
736
  messages,
637
737
  tools: llmTools.length > 0 ? llmTools : undefined,
@@ -771,11 +871,7 @@ export class IterationOrchestrator {
771
871
  const contextFigures = this.ctx.compactionConfig
772
872
  ? (() => {
773
873
  const measured = measureContext(this.ctx)
774
- const window = resolveContextWindow(
775
- this.ctx.compactionConfig?.contextWindowTokens,
776
- runConfig.model,
777
- this.ctx.providerContextWindow,
778
- )
874
+ const window = activeContextWindow(this.ctx)
779
875
  return {
780
876
  contextTokens: measured.tokens,
781
877
  contextMeasuredBy: measured.source,
@@ -932,6 +1028,70 @@ export class IterationOrchestrator {
932
1028
  costBefore,
933
1029
  })
934
1030
 
1031
+ if (this.ctx.structuredOutput?.mode === 'native') {
1032
+ const candidate = await parseNativeCandidate(
1033
+ this.ctx.structuredOutput.schema,
1034
+ response,
1035
+ this.ctx.abortController.signal,
1036
+ )
1037
+ let outcome: 'accepted' | 'retry' | 'exhausted' | 'cancelled'
1038
+ if (candidate.success) outcome = await this.reviewStructuredOutput(candidate.value)
1039
+ else {
1040
+ this.nativeStructuredAttempts++
1041
+ runMgr.pushMessage(
1042
+ createRuntimeContextMessage(
1043
+ 'Return a complete JSON value matching the supplied response schema. Do not continue a partial JSON fragment.',
1044
+ 'structured-output',
1045
+ ),
1046
+ )
1047
+ const checkpoint = await this.ctx.checkpointMgr.create(runMgr, iterationNum)
1048
+ await this.ctx.emitEvent({
1049
+ type: 'checkpoint_created',
1050
+ runId: runMgr.id,
1051
+ checkpointId: checkpoint.id,
1052
+ iteration: iterationNum,
1053
+ })
1054
+ outcome =
1055
+ this.nativeStructuredAttempts > this.structuredOutputRetryLimit()
1056
+ ? 'exhausted'
1057
+ : 'retry'
1058
+ }
1059
+ await this.ctx.emitEvent({
1060
+ type: 'iteration_completed',
1061
+ runId: runMgr.id,
1062
+ iteration: iterationNum,
1063
+ hasToolCalls: false,
1064
+ })
1065
+ yield* this.ctx.drainPending()
1066
+ if (this.ctx.abortController.signal.aborted || outcome === 'cancelled') {
1067
+ runMgr.setStopReason('cancelled')
1068
+ runMgr.markCancelled()
1069
+ break
1070
+ }
1071
+ if (outcome === 'accepted') {
1072
+ if (!forceFinalize) {
1073
+ const changed = yield* this.holdForOutstandingWork(iterationNum, false)
1074
+ const inbound = this.deliverInbound()
1075
+ if (changed || inbound > 0) continue
1076
+ }
1077
+ if (this.ctx.abortController.signal.aborted) {
1078
+ runMgr.setStopReason('cancelled')
1079
+ runMgr.markCancelled()
1080
+ break
1081
+ }
1082
+ this.publishStructuredOutput()
1083
+ runMgr.setStopReason('end_turn')
1084
+ break
1085
+ }
1086
+ if (outcome === 'exhausted') {
1087
+ runMgr.setStopReason(
1088
+ candidate.success ? 'answer_rejected' : 'structured_output_failed',
1089
+ )
1090
+ break
1091
+ }
1092
+ continue
1093
+ }
1094
+
935
1095
  const hasContent =
936
1096
  response.message.content !== null && response.message.content.length > 0
937
1097
 
@@ -1022,6 +1182,11 @@ export class IterationOrchestrator {
1022
1182
  // a loud stop rather than a loop.
1023
1183
  if (!forceFinalize && this.ctx.reviewAnswer) {
1024
1184
  const review = await this.reviewAnswer(response.message.content ?? '')
1185
+ if (this.ctx.abortController.signal.aborted) {
1186
+ runMgr.setStopReason('cancelled')
1187
+ runMgr.markCancelled()
1188
+ break
1189
+ }
1025
1190
  if (review && !review.accept) {
1026
1191
  const attempt = ++this.answerReviewAttempts
1027
1192
  const limit = this.ctx.maxAnswerReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT
@@ -1159,12 +1324,39 @@ export class IterationOrchestrator {
1159
1324
  // run ends here rather than paying for another turn whose only
1160
1325
  // job would be to restate it — unless it shared its turn with
1161
1326
  // other calls, which relays instead. See the method.
1162
- if (this.captureStructuredOutput(reviewOutcome.results, response)) {
1327
+ const structuredOutcome = await this.captureStructuredOutput(
1328
+ reviewOutcome.results,
1329
+ response,
1330
+ )
1331
+ if (
1332
+ structuredOutcome === 'retry' ||
1333
+ structuredOutcome === 'exhausted' ||
1334
+ structuredOutcome === 'cancelled'
1335
+ ) {
1336
+ await this.ctx.emitEvent({
1337
+ type: 'iteration_completed',
1338
+ runId: runMgr.id,
1339
+ iteration: iterationNum,
1340
+ hasToolCalls: true,
1341
+ })
1342
+ yield* this.ctx.drainPending()
1343
+ if (this.ctx.abortController.signal.aborted) {
1344
+ runMgr.setStopReason('cancelled')
1345
+ runMgr.markCancelled()
1346
+ break
1347
+ }
1348
+ if (structuredOutcome === 'retry') continue
1349
+ runMgr.setStopReason(
1350
+ structuredOutcome === 'cancelled' ? 'cancelled' : 'answer_rejected',
1351
+ )
1352
+ if (structuredOutcome === 'cancelled') runMgr.markCancelled()
1353
+ break
1354
+ }
1355
+ if (structuredOutcome === 'accepted') {
1163
1356
  this.ctx.log.info('Structured output produced — ending run', {
1164
1357
  [NAMZU.RUN_ID]: runMgr.id,
1165
1358
  [NAMZU.ITERATION]: iterationNum,
1166
1359
  })
1167
- runMgr.setStopReason('end_turn')
1168
1360
  await this.ctx.emitEvent({
1169
1361
  type: 'iteration_completed',
1170
1362
  runId: runMgr.id,
@@ -1172,6 +1364,13 @@ export class IterationOrchestrator {
1172
1364
  hasToolCalls: true,
1173
1365
  })
1174
1366
  yield* this.ctx.drainPending()
1367
+ if (this.ctx.abortController.signal.aborted) {
1368
+ runMgr.setStopReason('cancelled')
1369
+ runMgr.markCancelled()
1370
+ break
1371
+ }
1372
+ this.publishStructuredOutput()
1373
+ runMgr.setStopReason('end_turn')
1175
1374
  break
1176
1375
  }
1177
1376
 
@@ -1603,19 +1802,51 @@ export class IterationOrchestrator {
1603
1802
  * message becomes the reason, so an operator is not left with a run
1604
1803
  * that stopped and no account of it.
1605
1804
  */
1805
+ private stepContext(stepNumber: number, prepared: PrepareStepResult): PrepareStepContext {
1806
+ const model = prepared.model ?? this.ctx.runConfig.model
1807
+ const window = resolveContextWindow(
1808
+ this.ctx.compactionConfig?.contextWindowTokens,
1809
+ model,
1810
+ model === this.ctx.runConfig.model
1811
+ ? this.ctx.providerContextWindow
1812
+ : model === this.ctx.contextModel
1813
+ ? this.ctx.activeProviderContextWindow
1814
+ : undefined,
1815
+ )
1816
+ const skills = prepared.skills ? renderSkillsSection([...prepared.skills]) : null
1817
+ const preamble = [prepared.system, skills].filter(Boolean).join('\n\n')
1818
+ const preparedTokens = preamble ? estimateMessageTokens(createSystemMessage(preamble)) : 0
1819
+ const responseReserve = Math.min(
1820
+ prepared.maxResponseTokens ??
1821
+ this.ctx.runConfig.maxResponseTokens ??
1822
+ Math.floor(window.tokens / 4),
1823
+ Math.floor(window.tokens / 4),
1824
+ )
1825
+ return {
1826
+ runId: this.ctx.runMgr.id,
1827
+ stepNumber,
1828
+ messages: this.ctx.runMgr.messages,
1829
+ ...(this.latestUserMessage ? { latestUserMessage: this.latestUserMessage } : {}),
1830
+ signal: this.ctx.abortController.signal,
1831
+ contextBudget: {
1832
+ windowTokens: window.tokens,
1833
+ remainingTokens: Math.max(
1834
+ 0,
1835
+ Math.floor(
1836
+ window.tokens - measureContext(this.ctx).tokens - preparedTokens - responseReserve,
1837
+ ),
1838
+ ),
1839
+ },
1840
+ steps: this.steps,
1841
+ prepared,
1842
+ }
1843
+ }
1844
+
1606
1845
  private async beforeStep(stepNumber: number): Promise<StepVeto | undefined> {
1607
1846
  const configured = this.ctx.beforeStep
1608
1847
  if (!configured) return undefined
1609
1848
  try {
1610
- return (
1611
- (await configured({
1612
- runId: this.ctx.runMgr.id,
1613
- stepNumber,
1614
- messages: this.ctx.runMgr.messages,
1615
- steps: this.steps,
1616
- prepared: {},
1617
- })) ?? undefined
1618
- )
1849
+ return (await configured(this.stepContext(stepNumber, {}))) ?? undefined
1619
1850
  } catch (err) {
1620
1851
  return { reason: `beforeStep threw: ${toErrorMessage(err)}` }
1621
1852
  }
@@ -1641,14 +1872,9 @@ export class IterationOrchestrator {
1641
1872
  let result: PrepareStepResult = {}
1642
1873
  for (const stage of stages) {
1643
1874
  try {
1644
- const decided = await stage({
1645
- runId: this.ctx.runMgr.id,
1646
- stepNumber,
1647
- messages: this.ctx.runMgr.messages,
1648
- steps: this.steps,
1649
- prepared: result,
1650
- })
1875
+ const decided = await stage(this.stepContext(stepNumber, result))
1651
1876
  if (decided) result = { ...result, ...decided }
1877
+ await this.selectContextModel(result.model ?? this.ctx.runConfig.model)
1652
1878
  } catch (err) {
1653
1879
  // Skipped, and the rest still run: one broken concern must
1654
1880
  // not silently disable the others it was declared beside.
@@ -1710,6 +1936,18 @@ export class IterationOrchestrator {
1710
1936
  return prepared
1711
1937
  }
1712
1938
 
1939
+ private async selectContextModel(model: string | undefined): Promise<void> {
1940
+ if (model !== (this.ctx.contextModel ?? this.ctx.runConfig.model)) {
1941
+ // A measurement from another tokenizer cannot price the new request.
1942
+ this.ctx.runMgr.clearLastPromptTokens()
1943
+ }
1944
+ this.ctx.contextModel = model
1945
+ this.ctx.activeProviderContextWindow =
1946
+ model && model !== this.ctx.runConfig.model && !this.ctx.compactionConfig?.contextWindowTokens
1947
+ ? await this.ctx.resolveModelContextWindow?.(model)
1948
+ : undefined
1949
+ }
1950
+
1713
1951
  /** Steps completed so far, exposed on the returned `Run`. */
1714
1952
  private readonly steps: StepResult[] = []
1715
1953
 
@@ -1738,18 +1976,33 @@ export class IterationOrchestrator {
1738
1976
  * empty drain must change nothing at all: a `continue` on nothing queued
1739
1977
  * spends an iteration and a model call to say the same thing again.
1740
1978
  */
1979
+ private rememberUserMessage(message: Message, arriving = true): void {
1980
+ if (!isOperatorUserMessage(message)) return
1981
+ this.latestUserMessage = message
1982
+ // The hook field alone does not reach the model. Preserve arrivals in
1983
+ // the compaction state too, before their original message or attached
1984
+ // tool result can be shed. Initial history was already extracted at seed.
1985
+ const manager = this.ctx.workingStateManager
1986
+ if (arriving && manager) {
1987
+ extractFromUserMessage(manager, message.content, !manager.getState().task)
1988
+ }
1989
+ }
1990
+
1741
1991
  private deliverInbound(): number {
1742
1992
  const queued = this.ctx.inboundMessages?.() ?? []
1743
- for (const message of queued) this.ctx.runMgr.pushMessage(message)
1993
+ for (const message of queued) {
1994
+ this.ctx.runMgr.pushMessage(message)
1995
+ this.rememberUserMessage(message)
1996
+ }
1744
1997
 
1745
1998
  // The steering channel's remainder. `attachSteering` already took
1746
1999
  // what it could carry on a tool result; anything still pending is
1747
2000
  // guidance from a turn that had no result to attach it to.
1748
2001
  const stranded = this.ctx.steering?.drain()
1749
2002
  if (stranded) {
1750
- this.ctx.runMgr.pushMessage(
1751
- createRuntimeContextMessage(formatSteeringNote(stranded), 'steering'),
1752
- )
2003
+ const message = createRuntimeContextMessage(formatSteeringNote(stranded), 'steering')
2004
+ this.ctx.runMgr.pushMessage(message)
2005
+ this.rememberUserMessage(createRuntimeContextMessage(stranded, 'steering'))
1753
2006
  }
1754
2007
 
1755
2008
  return queued.length + (stranded ? 1 : 0)
@@ -1856,7 +2109,10 @@ export class IterationOrchestrator {
1856
2109
 
1857
2110
  /** Turns spent asking the model again for a valid structured output. */
1858
2111
  private structuredOutputAttempts = 0
2112
+ private nativeStructuredAttempts = 0
1859
2113
  private structuredOutputDone = false
2114
+ private pendingStructuredOutput: unknown
2115
+ private structuredReviewAttempts = 0
1860
2116
 
1861
2117
  private structuredOutputRetryLimit(): number {
1862
2118
  return this.ctx.structuredOutput?.maxRetries ?? DEFAULT_STRUCTURED_OUTPUT_RETRIES
@@ -1883,6 +2139,7 @@ export class IterationOrchestrator {
1883
2139
  results: readonly ToolCallOutcome[],
1884
2140
  response: ChatCompletionResponse,
1885
2141
  ): ToolCallOutcome | undefined {
2142
+ if (this.ctx.structuredOutput?.mode === 'native') return undefined
1886
2143
  const terminal = results.filter((r) => this.ctx.tools.get(r.toolName)?.terminal === true)
1887
2144
  if (terminal.length === 0) return undefined
1888
2145
 
@@ -1951,13 +2208,14 @@ export class IterationOrchestrator {
1951
2208
  * doing work, and it is the bound the neighbour relies on for the
1952
2209
  * identical pathology.
1953
2210
  */
1954
- private captureStructuredOutput(
2211
+ private async captureStructuredOutput(
1955
2212
  results: readonly ToolCallOutcome[],
1956
2213
  response: ChatCompletionResponse,
1957
- ): boolean {
1958
- if (!this.needsStructuredOutput()) return false
2214
+ ): Promise<'absent' | 'accepted' | 'retry' | 'exhausted' | 'cancelled'> {
2215
+ if (!this.needsStructuredOutput() || this.ctx.structuredOutput?.mode === 'native')
2216
+ return 'absent'
1959
2217
  const hit = results.find((r) => r.toolName === STRUCTURED_OUTPUT_TOOL_NAME && !r.isError)
1960
- if (!hit) return false
2218
+ if (!hit) return 'absent'
1961
2219
 
1962
2220
  const callCount = response.message.toolCalls?.length ?? 0
1963
2221
  if (callCount > 1) {
@@ -1965,18 +2223,90 @@ export class IterationOrchestrator {
1965
2223
  [NAMZU.RUN_ID]: this.ctx.runMgr.id,
1966
2224
  'namzu.runtime.calls_in_turn': callCount,
1967
2225
  })
1968
- return false
2226
+ return 'absent'
1969
2227
  }
1970
2228
 
2229
+ let parsed: unknown
1971
2230
  try {
1972
- this.ctx.runMgr.setStructuredOutput(JSON.parse(hit.output))
2231
+ parsed = JSON.parse(hit.output)
1973
2232
  } catch {
1974
2233
  // The tool serializes its own validated input, so this is
1975
2234
  // unreachable in practice; keep the raw text rather than losing it.
1976
- this.ctx.runMgr.setStructuredOutput(hit.output)
2235
+ if (this.ctx.structuredOutput?.review)
2236
+ throw new Error(
2237
+ 'Structured review requires an intact JSON tool result; check tool-output limits and result transformations',
2238
+ )
2239
+ parsed = hit.output
1977
2240
  }
2241
+ return this.reviewStructuredOutput(parsed)
2242
+ }
2243
+
2244
+ private publishStructuredOutput(): void {
2245
+ this.ctx.runMgr.setStructuredOutput(this.pendingStructuredOutput)
1978
2246
  this.structuredOutputDone = true
1979
- return true
2247
+ }
2248
+
2249
+ private async reviewStructuredOutput(
2250
+ parsed: unknown,
2251
+ ): Promise<'accepted' | 'retry' | 'exhausted' | 'cancelled'> {
2252
+ if (this.ctx.abortController.signal.aborted) return 'cancelled'
2253
+ const reviewer = this.ctx.structuredOutput?.review
2254
+ if (reviewer) {
2255
+ const signal = this.ctx.abortController.signal
2256
+ if (signal.aborted) return 'cancelled'
2257
+ let onAbort: () => void = () => {}
2258
+ const aborted = new Promise<never>((_resolve, reject) => {
2259
+ onAbort = () => reject(signal.reason ?? new Error('Structured review cancelled'))
2260
+ signal.addEventListener('abort', onAbort, { once: true })
2261
+ })
2262
+ let verdict: AnswerReview
2263
+ try {
2264
+ verdict = await Promise.race([
2265
+ Promise.resolve().then(() =>
2266
+ reviewer(structuredClone(parsed), {
2267
+ runId: this.ctx.runMgr.id,
2268
+ iteration: this.ctx.runMgr.currentIteration,
2269
+ signal,
2270
+ messages: this.ctx.runMgr.messages,
2271
+ }),
2272
+ ),
2273
+ aborted,
2274
+ ])
2275
+ } catch (error) {
2276
+ if (signal.aborted) return 'cancelled'
2277
+ throw error
2278
+ } finally {
2279
+ signal.removeEventListener('abort', onAbort)
2280
+ }
2281
+ if (signal.aborted) return 'cancelled'
2282
+ if (!verdict || typeof verdict.accept !== 'boolean')
2283
+ throw new Error('Structured reviewer returned an invalid verdict')
2284
+ if (!verdict.accept) {
2285
+ if (typeof verdict.feedback !== 'string' || verdict.feedback.trim().length === 0)
2286
+ throw new Error('Structured reviewer rejection requires feedback')
2287
+ this.structuredReviewAttempts++
2288
+ this.ctx.runMgr.pushMessage(createRuntimeContextMessage(verdict.feedback, 'answer-review'))
2289
+ // Persist both the feedback and its consumed allowance before the
2290
+ // next request. This checkpoint is not an approval/park boundary.
2291
+ const checkpoint = await this.ctx.checkpointMgr.create(
2292
+ this.ctx.runMgr,
2293
+ this.ctx.runMgr.currentIteration,
2294
+ )
2295
+ await this.ctx.emitEvent({
2296
+ type: 'checkpoint_created',
2297
+ runId: this.ctx.runMgr.id,
2298
+ checkpointId: checkpoint.id,
2299
+ iteration: this.ctx.runMgr.currentIteration,
2300
+ })
2301
+ if (signal.aborted) return 'cancelled'
2302
+ return this.structuredReviewAttempts >
2303
+ (this.ctx.structuredOutput?.maxReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT)
2304
+ ? 'exhausted'
2305
+ : 'retry'
2306
+ }
2307
+ }
2308
+ this.pendingStructuredOutput = parsed
2309
+ return 'accepted'
1980
2310
  }
1981
2311
 
1982
2312
  /**
@@ -1997,6 +2327,7 @@ export class IterationOrchestrator {
1997
2327
  return await this.ctx.reviewAnswer(answer, {
1998
2328
  runId: this.ctx.runMgr.id,
1999
2329
  iteration: this.ctx.runMgr.currentIteration,
2330
+ signal: this.ctx.abortController.signal,
2000
2331
  messages: this.ctx.runMgr.messages,
2001
2332
  })
2002
2333
  } catch (err) {
@@ -2059,6 +2390,8 @@ export class IterationOrchestrator {
2059
2390
  model: string,
2060
2391
  reason: StopReason,
2061
2392
  ): Promise<StopReason | undefined> {
2393
+ if (this.ctx.structuredOutput?.mode === 'native') return 'structured_output_failed'
2394
+
2062
2395
  const lastAssistant = [...this.ctx.runMgr.messages]
2063
2396
  .reverse()
2064
2397
  .find((m) => m.role === 'assistant')
@@ -0,0 +1,35 @@
1
+ import type { z } from 'zod'
2
+ import type { ChatCompletionResponse } from '../../../types/provider/index.js'
3
+ import { cloneJsonValue } from '../../../utils/json-snapshot.js'
4
+
5
+ /** Native constraints are transport assistance; local validation still owns acceptance. */
6
+ export async function parseNativeCandidate(
7
+ schema: z.ZodType,
8
+ response: ChatCompletionResponse,
9
+ signal: AbortSignal,
10
+ ): Promise<{ success: true; value: unknown } | { success: false }> {
11
+ signal.throwIfAborted()
12
+ if (response.finishReason !== 'stop' || response.message.toolCalls?.length)
13
+ return { success: false }
14
+ let input: unknown
15
+ try {
16
+ input = JSON.parse(response.message.content ?? '')
17
+ } catch {
18
+ return { success: false }
19
+ }
20
+ let abort!: () => void
21
+ const cancelled = new Promise<never>((_resolve, reject) => {
22
+ abort = () => reject(signal.reason ?? new Error('Native output validation cancelled'))
23
+ signal.addEventListener('abort', abort, { once: true })
24
+ })
25
+ try {
26
+ const parsed = await Promise.race([schema.safeParseAsync(input), cancelled])
27
+ signal.throwIfAborted()
28
+ if (!parsed.success) return { success: false }
29
+ // Match the serialized result contract of output-tool mode. Never rerun transforms.
30
+ const value = cloneJsonValue(parsed.data, false)
31
+ return { success: true, value }
32
+ } finally {
33
+ signal.removeEventListener('abort', abort)
34
+ }
35
+ }