@pensar/apex 2.6.1 → 2.7.0-canary.1572d9a4

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 (328) hide show
  1. package/build/{agent-ayreaj6z.js → agent-3g1b578v.js} +6 -5
  2. package/build/{agent-yby11zaq.js → agent-p8cezgbb.js} +7 -6
  3. package/build/{agent-runs-g37vkrjr.js → agent-runs-f86emmgg.js} +23 -20
  4. package/build/agent-vaq31je6.js +14 -0
  5. package/build/agent-vvdqga09.js +14 -0
  6. package/build/{apps-xn6km9yn.js → apps-3stwnmen.js} +16 -13
  7. package/build/{auth-d105wc64.js → auth-7md9kwda.js} +16 -13
  8. package/build/blackboxAgent-ct8bejef.js +14 -0
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-w2nr4md3.js} +21 -15
  10. package/build/{cli-c05y640n.js → cli-1j28cbhy.js} +1 -1
  11. package/build/{cli-d37a42dc.js → cli-2jz2jz06.js} +206 -90
  12. package/build/{cli-az3xq70v.js → cli-4a8dsz2k.js} +4 -4
  13. package/build/{cli-qgz1q9g5.js → cli-58781vcf.js} +1192 -1473
  14. package/build/{cli-gx71jcke.js → cli-7067zzeb.js} +1 -1
  15. package/build/{cli-3sp6nd8d.js → cli-81smmd5w.js} +5 -5
  16. package/build/{cli-k2nwxta0.js → cli-8b25dhky.js} +2 -2
  17. package/build/{cli-fy6vh2a7.js → cli-8sw5zpqz.js} +1 -1
  18. package/build/{cli-709rvekb.js → cli-cg3ypx5n.js} +14 -7
  19. package/build/{cli-dpsw2fe6.js → cli-e9v6zg6h.js} +40941 -40172
  20. package/build/cli-fjxmphg1.js +36 -0
  21. package/build/{cli-7eea4e2y.js → cli-gzb6qrzh.js} +31 -60
  22. package/build/{cli-65g6rcat.js → cli-khnf5xrf.js} +2 -1
  23. package/build/{cli-1ve18npr.js → cli-mdzt2e8j.js} +4 -2
  24. package/build/{cli-n2en7j5b.js → cli-n8mmw4c4.js} +1 -1
  25. package/build/cli-pndb7mh9.js +1193 -0
  26. package/build/cli-px6ehzqx.js +54 -0
  27. package/build/{cli-xtc2t6me.js → cli-qs4msn6x.js} +111 -47
  28. package/build/{cli-kb21qhvm.js → cli-rvybdhgn.js} +4 -3
  29. package/build/{cli-ae8jqxqr.js → cli-v739h7hb.js} +2 -2
  30. package/build/{cli-ngq9dr8n.js → cli-wm8ech0x.js} +3 -3
  31. package/build/cli.js +170 -51
  32. package/build/{config-zpqkd5cz.js → config-e5v1bkh1.js} +1 -1
  33. package/build/{fastStrike-mcjdvwez.js → fastStrike-efp0rjs3.js} +74 -17
  34. package/build/{fixes-kytx5dmj.js → fixes-926cw1ej.js} +16 -13
  35. package/build/{index-n65z6v7b.js → index-3t41p258.js} +7 -6
  36. package/build/{index-qv4cc65p.js → index-bv09af6g.js} +19 -15
  37. package/build/{index-je6hcsqr.js → index-bv6dtwse.js} +1 -1
  38. package/build/{index-4vqdgb42.js → index-jt52ffgn.js} +1657 -1400
  39. package/build/{index-ns4m4s01.js → index-np405b9e.js} +11 -7
  40. package/build/{index-r9yjkfrv.js → index-qys178kp.js} +5 -4
  41. package/build/{index-wqetp6gq.js → index-tpyzq2wq.js} +42 -11
  42. package/build/{index-fv64eb32.js → index-vrcdyebh.js} +2 -2
  43. package/build/{index-dr3tg1w3.js → index-z1g8vbtm.js} +1 -1
  44. package/build/{issues-eb3q9d3a.js → issues-sncyqh14.js} +16 -13
  45. package/build/{knowledge-fqsvtx0f.js → knowledge-esgvy5ws.js} +16 -13
  46. package/build/lance-sacfgs1z.js +232 -0
  47. package/build/lib/{chunk-9yksqgzr.js → chunk-4aevegnc.js} +77 -45
  48. package/build/lib/{chunk-9tt2021m.js → chunk-4kf4a15q.js} +13 -13
  49. package/build/lib/{chunk-fy3bbbqy.js → chunk-5avnedwm.js} +7 -1
  50. package/build/lib/{chunk-bb0ekhkw.js → chunk-5nz5tk3p.js} +76 -22
  51. package/build/lib/{chunk-4hwzqthr.js → chunk-7s8e5wrd.js} +3 -2
  52. package/build/lib/{chunk-yrnrbjes.js → chunk-85kpxwzs.js} +4 -2
  53. package/build/lib/{chunk-mg03jdmt.js → chunk-9jkzk9nc.js} +1 -1
  54. package/build/lib/{chunk-r4j8ybn9.js → chunk-9wwts6r6.js} +824 -77
  55. package/build/lib/{chunk-nhfax4gb.js → chunk-bxznzxdy.js} +5 -1
  56. package/build/lib/{chunk-r2dkp4tb.js → chunk-cqxm95md.js} +1 -1
  57. package/build/lib/{chunk-w66ce82a.js → chunk-e3agqspc.js} +12 -4
  58. package/build/lib/{chunk-ba41jh41.js → chunk-eh5yw385.js} +1137 -333
  59. package/build/lib/{chunk-1azdancq.js → chunk-ewc9r84m.js} +5 -1
  60. package/build/lib/{chunk-2njv7wz3.js → chunk-hbyxyxdc.js} +4 -0
  61. package/build/lib/{chunk-e8yy2k73.js → chunk-jhhwts6w.js} +28 -2
  62. package/build/lib/{chunk-1jvwqnem.js → chunk-kbxbd1kn.js} +108 -44
  63. package/build/lib/{chunk-7pwa92rn.js → chunk-mph5fvk7.js} +1 -1
  64. package/build/lib/chunk-psc23epq.js +107 -0
  65. package/build/lib/{chunk-acgbyf5h.js → chunk-qxad7qjd.js} +4 -2
  66. package/build/lib/{chunk-3c00n0cy.js → chunk-r0v3vvgm.js} +54 -33
  67. package/build/lib/{chunk-1yct6cra.js → chunk-rt13120a.js} +1 -1
  68. package/build/lib/{chunk-v3dk9j7k.js → chunk-sgya016v.js} +93 -22
  69. package/build/lib/{chunk-1fy8azee.js → chunk-txef92hq.js} +1 -1
  70. package/build/lib/{chunk-2qsq76xq.js → chunk-vcd90qrn.js} +189 -90
  71. package/build/lib/{chunk-7gjdygcx.js → chunk-vwr9w3ab.js} +2 -0
  72. package/build/lib/{chunk-z137x08b.js → chunk-wf6ak0m3.js} +92 -70
  73. package/build/lib/{chunk-xhdpgpkm.js → chunk-xft0ms9j.js} +2 -2
  74. package/build/lib/core/agents/agentRuntime.d.ts +3 -1
  75. package/build/lib/core/agents/defineAgent.d.ts +3 -2
  76. package/build/lib/core/agents/lance.d.ts +153 -0
  77. package/build/lib/core/agents/lanceJudge.d.ts +3 -0
  78. package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
  79. package/build/lib/core/agents/offSecAgent/index.js +16 -14
  80. package/build/lib/core/agents/offSecAgent/offensiveSecurityAgent.d.ts +4 -0
  81. package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
  82. package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +18 -2
  83. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +10 -8
  84. package/build/lib/core/agents/offSecAgent/toolExecutionContext.d.ts +3 -0
  85. package/build/lib/core/agents/offSecAgent/toolLifecycle.d.ts +1 -0
  86. package/build/lib/core/agents/offSecAgent/tools/index.js +10 -6
  87. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  88. package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
  89. package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
  90. package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
  91. package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
  92. package/build/lib/core/agents/offSecAgent/tools/types.d.ts +6 -3
  93. package/build/lib/core/agents/offSecAgent/tools/whiteboxCandidates.d.ts +2 -2
  94. package/build/lib/core/agents/offSecAgent/types.d.ts +19 -3
  95. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +13 -13
  96. package/build/lib/core/agents/specialized/attackSurface/schemas.js +12 -12
  97. package/build/lib/core/agents/specialized/authenticationAgent/index.js +14 -14
  98. package/build/lib/core/agents/specialized/codeAgent/agent.js +13 -13
  99. package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
  100. package/build/lib/core/agents/specialized/cvssScorer/index.d.ts +2 -1
  101. package/build/lib/core/agents/specialized/findingJudge/agent.js +11 -11
  102. package/build/lib/core/agents/specialized/findingJudge/index.d.ts +2 -1
  103. package/build/lib/core/agents/specialized/findingJudge/index.js +4 -4
  104. package/build/lib/core/agents/specialized/patching/index.js +14 -14
  105. package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
  106. package/build/lib/core/agents/specialized/pentest/agent.js +15 -13
  107. package/build/lib/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.d.ts +2 -1
  108. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +13 -13
  109. package/build/lib/core/ai/ai.d.ts +29 -70
  110. package/build/lib/core/ai/ai.js +5 -3
  111. package/build/lib/core/ai/index.d.ts +1 -0
  112. package/build/lib/core/ai/index.js +16 -4
  113. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  114. package/build/lib/core/ai/models/concentrate.d.ts +1 -0
  115. package/build/lib/core/ai/models/index.d.ts +1 -1
  116. package/build/lib/core/ai/providers/concentrate.d.ts +1 -0
  117. package/build/lib/core/ai/reasoning.d.ts +44 -0
  118. package/build/lib/core/ai/utils.d.ts +2 -0
  119. package/build/lib/core/ai/utils.js +5 -3
  120. package/build/lib/core/api/index.d.ts +6 -0
  121. package/build/lib/core/api/index.js +32 -23
  122. package/build/lib/core/api/lance.d.ts +6 -0
  123. package/build/lib/core/api/offesecAgent.d.ts +1 -0
  124. package/build/lib/core/api/reasoning.d.ts +12 -0
  125. package/build/lib/core/api/targetedPentest.d.ts +1 -0
  126. package/build/lib/core/api/targetedPentest.js +14 -14
  127. package/build/lib/core/cli/reasoning.d.ts +4 -0
  128. package/build/lib/core/config/config.d.ts +10 -2
  129. package/build/lib/core/config/customProviders.d.ts +11 -0
  130. package/build/lib/core/config/index.js +1 -1
  131. package/build/lib/core/eventBus.js +1 -1
  132. package/build/lib/core/findings/registry.d.ts +3 -1
  133. package/build/lib/core/findings/registry.js +4 -4
  134. package/build/lib/core/observability/index.js +1 -1
  135. package/build/lib/core/observability/telemetry.d.ts +4 -0
  136. package/build/lib/core/session/index.d.ts +47 -0
  137. package/build/lib/core/session/index.js +6 -4
  138. package/build/lib/core/session/persistence.d.ts +7 -0
  139. package/build/lib/core/session/persistence.js +8 -4
  140. package/build/lib/core/tools/backends/index.js +1 -1
  141. package/build/lib/core/tools/backends/resolve.d.ts +1 -0
  142. package/build/lib/core/workflows/fastStrike.d.ts +3 -1
  143. package/build/lib/core/workflows/pentest.d.ts +12 -2
  144. package/build/lib/core/workflows/pentest.js +19 -19
  145. package/build/lib/core/workflows/seams.js +5 -5
  146. package/build/lib/core/workflows/threatModel.d.ts +1 -0
  147. package/build/lib/core/workflows/whiteboxAttackSurface.d.ts +6 -4
  148. package/build/lib/core/workflows/whiteboxAttackSurface.js +15 -15
  149. package/build/lib/index.js +25 -25
  150. package/build/{logs-x62b7hk1.js → logs-bcbjpjp6.js} +16 -13
  151. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-rph1774m.js} +5 -5
  152. package/build/offesecAgent-81h1p6ee.js +32 -0
  153. package/build/pentest-2q20q6n1.js +23 -0
  154. package/build/{pentests-e0f32aqx.js → pentests-jgw031mc.js} +16 -13
  155. package/build/recordedRunClient-6axj6fba.js +11 -0
  156. package/build/subagentSpawner-a861gvt0.js +12 -0
  157. package/build/targetedPentest-6yxznt7s.js +91 -0
  158. package/build/{targets-xj9g3q0z.js → targets-bjah19dp.js} +16 -13
  159. package/build/threatModel-739kpexm.js +24 -0
  160. package/build/{uninstall-7s784q4a.js → uninstall-yxrn9edt.js} +1 -1
  161. package/build/{upload-4gvhwmvr.js → upload-34ewrjp5.js} +4 -4
  162. package/build/{utils-cjrz64pc.js → utils-fzm66efw.js} +5 -5
  163. package/package.json +2 -1
  164. package/src/cli/issues.test.ts +17 -1
  165. package/src/cli/lance-verifier.test.ts +57 -0
  166. package/src/cli/lance-verifier.ts +73 -0
  167. package/src/cli/lance.test.ts +71 -0
  168. package/src/cli/lance.ts +48 -0
  169. package/src/cli.ts +109 -19
  170. package/src/core/agents/agentRuntime.test.ts +42 -0
  171. package/src/core/agents/agentRuntime.ts +7 -2
  172. package/src/core/agents/defineAgent.ts +3 -1
  173. package/src/core/agents/lance.test.ts +421 -0
  174. package/src/core/agents/lance.ts +701 -0
  175. package/src/core/agents/lanceJudge.test.ts +166 -0
  176. package/src/core/agents/lanceJudge.ts +115 -0
  177. package/src/core/agents/offSecAgent/index.ts +3 -0
  178. package/src/core/agents/offSecAgent/interruptedStepFinalization.test.ts +51 -0
  179. package/src/core/agents/offSecAgent/lance.test.ts +188 -0
  180. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  181. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +244 -41
  182. package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
  183. package/src/core/agents/offSecAgent/prompt.ts +52 -8
  184. package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
  185. package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
  186. package/src/core/agents/offSecAgent/subagentSpawner.hooks.test.ts +11 -3
  187. package/src/core/agents/offSecAgent/subagentSpawner.reasoning.test.ts +167 -0
  188. package/src/core/agents/offSecAgent/subagentSpawner.ts +82 -28
  189. package/src/core/agents/offSecAgent/toolExecutionContext.test.ts +230 -0
  190. package/src/core/agents/offSecAgent/toolExecutionContext.ts +56 -0
  191. package/src/core/agents/offSecAgent/toolLifecycle.ts +2 -0
  192. package/src/core/agents/offSecAgent/tools/delegateAuth.ts +4 -2
  193. package/src/core/agents/offSecAgent/tools/documentFinding.test.ts +9 -4
  194. package/src/core/agents/offSecAgent/tools/documentFinding.ts +10 -9
  195. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  196. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  197. package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
  198. package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
  199. package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +7 -4
  200. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
  201. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +21 -1
  202. package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
  203. package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +50 -67
  204. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +79 -3
  205. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +36 -16
  206. package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +28 -5
  207. package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +4 -3
  208. package/src/core/agents/offSecAgent/tools/types.ts +12 -2
  209. package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
  210. package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
  211. package/src/core/agents/offSecAgent/types.ts +22 -1
  212. package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
  213. package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
  214. package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
  215. package/src/core/agents/specialized/cvssScorer/index.test.ts +2 -1
  216. package/src/core/agents/specialized/cvssScorer/index.ts +3 -0
  217. package/src/core/agents/specialized/findingJudge/index.ts +6 -0
  218. package/src/core/agents/specialized/pentest/agent.test.ts +155 -0
  219. package/src/core/agents/specialized/pentest/agent.ts +181 -52
  220. package/src/core/agents/specialized/pentest/results.test.ts +59 -4
  221. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.concurrency.test.ts +59 -2
  222. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.hooks.test.ts +127 -0
  223. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +74 -68
  224. package/src/core/ai/ai.durability-hooks.test.ts +25 -7
  225. package/src/core/ai/ai.lance.test.ts +581 -0
  226. package/src/core/ai/ai.operator-context.bench.test.ts +1390 -0
  227. package/src/core/ai/ai.recorded-context.test.ts +89 -8
  228. package/src/core/ai/ai.test.ts +81 -59
  229. package/src/core/ai/ai.ts +453 -327
  230. package/src/core/ai/compactionTelemetry.test.ts +69 -17
  231. package/src/core/ai/index.ts +11 -0
  232. package/src/core/ai/models/anthropic.ts +6 -0
  233. package/src/core/ai/models/bedrock.ts +18 -0
  234. package/src/core/ai/models/claude-capabilities.ts +29 -8
  235. package/src/core/ai/models/concentrate.ts +52 -0
  236. package/src/core/ai/models/index.ts +25 -4
  237. package/src/core/ai/models/models.test.ts +179 -7
  238. package/src/core/ai/models/openrouter-reasoning.json +219 -0
  239. package/src/core/ai/models/openrouter.ts +43 -1
  240. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  241. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  242. package/src/core/ai/providers/concentrate.test.ts +31 -0
  243. package/src/core/ai/providers/concentrate.ts +11 -0
  244. package/src/core/ai/providers/custom.test.ts +74 -0
  245. package/src/core/ai/providers/custom.ts +3 -1
  246. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  247. package/src/core/ai/providers/pensarFormatters.ts +8 -1
  248. package/src/core/ai/reasoning.test.ts +313 -0
  249. package/src/core/ai/reasoning.ts +500 -0
  250. package/src/core/ai/streamIdleTimeout.test.ts +21 -0
  251. package/src/core/ai/utils.native-rollout-evidence.test.ts +21 -8
  252. package/src/core/ai/utils.ts +269 -163
  253. package/src/core/api/attackSurface.ts +1 -0
  254. package/src/core/api/blackboxPentest.ts +9 -1
  255. package/src/core/api/index.ts +17 -0
  256. package/src/core/api/lance.ts +23 -0
  257. package/src/core/api/offesecAgent.test.ts +51 -0
  258. package/src/core/api/offesecAgent.ts +15 -1
  259. package/src/core/api/reasoning.test.ts +213 -0
  260. package/src/core/api/reasoning.ts +44 -0
  261. package/src/core/api/recordedRun.loop.test.ts +5 -1
  262. package/src/core/api/targetedPentest.test.ts +14 -2
  263. package/src/core/api/targetedPentest.ts +52 -5
  264. package/src/core/cli/reasoning.test.ts +149 -0
  265. package/src/core/cli/reasoning.ts +95 -0
  266. package/src/core/config/config.test.ts +35 -0
  267. package/src/core/config/config.ts +11 -0
  268. package/src/core/config/customProviders.ts +21 -2
  269. package/src/core/eventBus.ts +1 -0
  270. package/src/core/findings/registry.test.ts +9 -1
  271. package/src/core/findings/registry.ts +6 -0
  272. package/src/core/observability/telemetry.test.ts +131 -79
  273. package/src/core/observability/telemetry.ts +5 -0
  274. package/src/core/runtime/sqliteRunObservation.test.ts +1 -1
  275. package/src/core/session/index.ts +38 -0
  276. package/src/core/session/persistence.test.ts +62 -0
  277. package/src/core/session/persistence.ts +56 -0
  278. package/src/core/tools/backends/resolve.ts +7 -0
  279. package/src/core/tools/backends/whiteboxJobs.test.ts +9 -8
  280. package/src/core/workflows/fastStrike.seams.test.ts +149 -0
  281. package/src/core/workflows/fastStrike.test.ts +18 -1
  282. package/src/core/workflows/fastStrike.ts +106 -11
  283. package/src/core/workflows/pentest.test.ts +104 -0
  284. package/src/core/workflows/pentest.ts +128 -7
  285. package/src/core/workflows/seams.test.ts +13 -0
  286. package/src/core/workflows/seams.ts +5 -1
  287. package/src/core/workflows/threatModel.ts +2 -0
  288. package/src/core/workflows/whiteboxAttackSurface.test.ts +74 -2
  289. package/src/core/workflows/whiteboxAttackSurface.ts +102 -73
  290. package/src/tui/command-registry.test.ts +12 -0
  291. package/src/tui/command-registry.ts +10 -0
  292. package/src/tui/components/chat/config-view.tsx +6 -10
  293. package/src/tui/components/chat/home-view.tsx +2 -2
  294. package/src/tui/components/chat/input-area.tsx +2 -2
  295. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  296. package/src/tui/components/chat/message-list.tsx +1 -0
  297. package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +164 -0
  298. package/src/tui/components/commands/advanced-settings.test.ts +34 -0
  299. package/src/tui/components/commands/advanced-settings.tsx +220 -31
  300. package/src/tui/components/commands/models-display.tsx +7 -1
  301. package/src/tui/components/model-picker/ModelPicker.tsx +65 -103
  302. package/src/tui/components/model-picker/ModelPickerDialog.tsx +8 -14
  303. package/src/tui/components/model-picker/__tests__/reasoning-control.tsx +162 -0
  304. package/src/tui/components/model-picker/reasoning-control.test.ts +15 -0
  305. package/src/tui/components/operator-dashboard/index.tsx +166 -50
  306. package/src/tui/components/operator-dashboard/logic.test.ts +144 -2
  307. package/src/tui/components/operator-dashboard/logic.ts +54 -3
  308. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  309. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  310. package/src/tui/components/shared/action-label.test.ts +9 -0
  311. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  312. package/src/tui/components/shared/index.ts +1 -0
  313. package/src/tui/components/shared/tool-registry.ts +1 -0
  314. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  315. package/src/tui/context/agent.tsx +32 -79
  316. package/src/tui/context/route.tsx +2 -0
  317. package/src/tui/index.tsx +1 -0
  318. package/src/tui/keybindings/registry.ts +2 -0
  319. package/build/agent-6w7egcf1.js +0 -13
  320. package/build/agent-9zx75feh.js +0 -13
  321. package/build/blackboxAgent-s56sgx9b.js +0 -13
  322. package/build/cli-g06s7edq.js +0 -11
  323. package/build/lib/chunk-z96pbjhd.js +0 -56
  324. package/build/offesecAgent-5whnpeta.js +0 -22
  325. package/build/pentest-e61hc38s.js +0 -22
  326. package/build/recordedRunClient-dp032c32.js +0 -11
  327. package/build/targetedPentest-rcbs4nvp.js +0 -57
  328. package/build/threatModel-jkxfa40s.js +0 -21
package/src/core/ai/ai.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
2
2
  import { setTimeout as delay } from "node:timers/promises";
3
3
  import type { AnthropicMessagesModelId } from "@ai-sdk/anthropic/internal";
4
- import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
5
4
  import type { OpenAIChatModelId } from "@ai-sdk/openai/internal";
5
+ import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
6
6
  import {
7
7
  generateText,
8
8
  type LanguageModel,
@@ -51,6 +51,9 @@ import {
51
51
  } from "./compactionTelemetry";
52
52
  import {
53
53
  estimateMessageTokens,
54
+ estimateTokens,
55
+ estimateToolsOverheadTokens,
56
+ type FitContextResult,
54
57
  fitMessagesToContext,
55
58
  truncateWithMarker,
56
59
  } from "./contextManagement";
@@ -60,7 +63,7 @@ import {
60
63
  runWithInferenceRecorder,
61
64
  } from "./inference-attempt";
62
65
  import {
63
- getClaudeCapabilities,
66
+ type ClaudeThinkingEffort,
64
67
  getMaxOutputTokens,
65
68
  getModelInfo,
66
69
  prefersSequentialToolCalls,
@@ -71,13 +74,20 @@ import {
71
74
  } from "./native-rollout-evidence";
72
75
  import { CONCENTRATE_GLM_5_3_MODEL_ID } from "./providers/concentrate";
73
76
  import { OPENAI_PRO_TIMEOUT_MS } from "./providers/openai-pro-fetch";
77
+ import {
78
+ buildModelReasoningOptions,
79
+ getReasoningCapabilities,
80
+ type ReasoningProviderOptions,
81
+ type ReasoningSetting,
82
+ resolveReasoningSetting,
83
+ } from "./reasoning";
74
84
  import { STREAM_DEBUG } from "./streamTelemetry";
75
85
  import {
76
86
  type AIAuthConfig,
87
+ CompactionRejectedError,
77
88
  checkIfContextLengthError,
78
89
  createSummarizationStream,
79
90
  getProviderModel,
80
- isAnthropicProvider,
81
91
  } from "./utils";
82
92
 
83
93
  const log = scopedLogger(() => createLogger("ai"));
@@ -125,6 +135,7 @@ export type AIModel = AnthropicMessagesModelId | OpenAIChatModelId | string; //
125
135
 
126
136
  export type OpenAIReasoningEffort =
127
137
  | "none"
138
+ | "minimal"
128
139
  | "low"
129
140
  | "medium"
130
141
  | "high"
@@ -136,41 +147,11 @@ export const DEFAULT_OPENAI_REASONING_EFFORT: OpenAIReasoningEffort = "medium";
136
147
 
137
148
  /**
138
149
  * Anthropic adaptive-thinking effort hint. Soft guidance for how much the model
139
- * reasons (see AWS "adaptive thinking"): `low` minimizes thinking, `max`
140
- * reasons hardest (`high` is the model default). Only meaningful for models that
141
- * support adaptive thinking (Claude Opus/Sonnet 4.6+); ignored elsewhere.
150
+ * reasons (see AWS "adaptive thinking"): `low` minimizes thinking while
151
+ * `xhigh` and `max` allow progressively deeper work. Only meaningful for
152
+ * models that support adaptive thinking; ignored elsewhere.
142
153
  */
143
- export type ThinkingEffort = "low" | "medium" | "high" | "xhigh" | "max";
144
-
145
- const OPENAI_REASONING_MODEL_IDS = new Set([
146
- "gpt-6.1-sol",
147
- "gpt-6-luna",
148
- "gpt-6-sol",
149
- "gpt-6-astra",
150
- "gpt-5.5-pro",
151
- "gpt-5.5-pro-2026-04-23",
152
- "gpt-5.4-nano",
153
- "gpt-5.4-nano-2026-03-17",
154
- "gpt-5.4-mini",
155
- "gpt-5.4-mini-2026-03-17",
156
- "gpt-5",
157
- "gpt-5-2025-08-07",
158
- "gpt-5.1",
159
- "gpt-5.1-2025-11-13",
160
- "gpt-5.2",
161
- "gpt-5.2-2025-12-11",
162
- "gpt-5.2-pro",
163
- "gpt-5.2-pro-2025-12-11",
164
- "gpt-5.4",
165
- "gpt-5.4-2026-03-05",
166
- "gpt-5.4-pro",
167
- "gpt-5.4-pro-2026-03-05",
168
- "gpt-5.5",
169
- "gpt-5.5-2026-04-23",
170
- "gpt-5.6-sol",
171
- "gpt-5.6-terra",
172
- "gpt-5.6-luna",
173
- ]);
154
+ export type ThinkingEffort = ClaudeThinkingEffort;
174
155
 
175
156
  /** Per-step billing context attached to a usage report, attributing cost to a specific `(sessionId, stepSeq)`. */
176
157
  export interface UsageStepContext {
@@ -606,7 +587,11 @@ async function* withIdleTimeout<T>(
606
587
  function isStreamIdleTimeoutError(error: unknown): boolean {
607
588
  // AI SDK may rewrap the error, so walk `cause`.
608
589
  for (let e: unknown = error, depth = 0; e != null && depth < 6; depth++) {
609
- if (e instanceof StreamIdleTimeoutError) return true;
590
+ if (
591
+ e instanceof StreamIdleTimeoutError ||
592
+ (e as { name?: unknown }).name === "StreamIdleTimeoutError"
593
+ )
594
+ return true;
610
595
  e = (e as { cause?: unknown }).cause;
611
596
  }
612
597
  return false;
@@ -640,6 +625,7 @@ function createToolExecutionGate(): {
640
625
  type: string;
641
626
  toolCallId?: string;
642
627
  toolName?: string;
628
+ preliminary?: boolean;
643
629
  }) => void;
644
630
  } {
645
631
  let inFlight = 0;
@@ -648,6 +634,7 @@ function createToolExecutionGate(): {
648
634
  return {
649
635
  shouldEnforceIdleTimeout: () => inFlight === 0,
650
636
  observe: (chunk) => {
637
+ if (chunk.type === "tool-result" && chunk.preliminary) return;
651
638
  if (chunk.type === "tool-call") {
652
639
  if (chunk.toolName && NON_GATED_TOOLS.has(chunk.toolName)) return;
653
640
  const id = chunk.toolCallId;
@@ -676,6 +663,43 @@ function createToolExecutionGate(): {
676
663
  };
677
664
  }
678
665
 
666
+ class InStreamCompactionRequired extends Error {
667
+ constructor(readonly fitted: FitContextResult) {
668
+ super("In-stream compaction required before provider dispatch");
669
+ }
670
+ }
671
+
672
+ // Retry once after new tool output, then let fitting handle this turn.
673
+ const MAX_PROACTIVE_SUMMARY_FAILURES = 2;
674
+
675
+ function shouldSummarizeBeforeFitting(
676
+ messages: ModelMessage[],
677
+ contextWindow: number,
678
+ maxOutputTokens: number,
679
+ overheadTokens: number,
680
+ safetyMarginTokens: number,
681
+ system?: string,
682
+ toolChoice?: ToolChoice<ToolSet>,
683
+ ): boolean {
684
+ const historyBudget =
685
+ contextWindow -
686
+ maxOutputTokens -
687
+ overheadTokens -
688
+ safetyMarginTokens -
689
+ (system ? estimateTokens(system) : 0);
690
+ const estimatedHistoryTokens = estimateMessageTokens(messages);
691
+ // Forced-tool replays missed the summary cache; keep their fitted history path.
692
+ return (
693
+ historyBudget > 0 &&
694
+ messages.length > 2 &&
695
+ toolChoice !== "required" &&
696
+ typeof toolChoice !== "object" &&
697
+ estimatedHistoryTokens >=
698
+ Math.max(100_000, Math.floor(historyBudget * 0.8)) &&
699
+ estimatedHistoryTokens <= historyBudget
700
+ );
701
+ }
702
+
679
703
  // Helper function to wrap a stream with error handling for async errors
680
704
  function wrapStreamWithErrorHandler(
681
705
  originalStream: StreamTextResult<ToolSet, never>,
@@ -686,6 +710,7 @@ function wrapStreamWithErrorHandler(
686
710
  rateLimitRetryCount: number = 0,
687
711
  idleResumeCount: number = 0,
688
712
  generationSpans?: GenerationSpanTracker,
713
+ drainIdleStream?: (reason: unknown) => Promise<void>,
689
714
  ): StreamTextResult<ToolSet, never> {
690
715
  // Create a lazy getter for fullStream that wraps it with error handling
691
716
  let wrappedStream: AsyncIterable<TextStreamPart<ToolSet>> | null = null;
@@ -786,7 +811,15 @@ function wrapStreamWithErrorHandler(
786
811
 
787
812
  // Check context length FIRST — these should never be retried
788
813
  // as-is; the prompt must be reduced via summarization.
789
- const isCtxError = checkIfContextLengthError(error);
814
+ const isCtxError =
815
+ error instanceof InStreamCompactionRequired ||
816
+ error instanceof CompactionRejectedError ||
817
+ checkIfContextLengthError(error);
818
+ if (
819
+ opts.primaryModelOnly &&
820
+ (!opts.runContextCompaction || !isCtxError)
821
+ )
822
+ throw error;
790
823
 
791
824
  // A latched persistence failure is terminal: retrying would
792
825
  // dispatch a turn whose context was never durably committed.
@@ -798,6 +831,9 @@ function wrapStreamWithErrorHandler(
798
831
  throw error;
799
832
  }
800
833
 
834
+ if (isStreamIdleTimeoutError(error))
835
+ await drainIdleStream?.(error);
836
+
801
837
  // Handle stream idle timeout — resume from accumulated messages
802
838
  if (
803
839
  !isCtxError &&
@@ -893,42 +929,50 @@ function wrapStreamWithErrorHandler(
893
929
  }
894
930
 
895
931
  if (isCtxError) {
896
- let currentMessages: ModelMessage[] = messagesContainer.current;
897
- try {
898
- const response = await originalStream.response;
899
- if (response.messages && response.messages.length > 0) {
900
- currentMessages = response.messages as ModelMessage[];
932
+ let currentMessages: ModelMessage[] =
933
+ error instanceof InStreamCompactionRequired
934
+ ? error.fitted.messages
935
+ : messagesContainer.current;
936
+ if (!(error instanceof InStreamCompactionRequired)) {
937
+ try {
938
+ const response = await originalStream.response;
939
+ if (response.messages && response.messages.length > 0) {
940
+ currentMessages = response.messages as ModelMessage[];
941
+ }
942
+ } catch {
943
+ // Fall back to container messages.
901
944
  }
902
- } catch {
903
- // Fall back to container messages.
904
945
  }
905
946
 
906
- const fitted = fitMessagesToContext(currentMessages, {
907
- contextWindow: getContextWindow(
908
- opts.model,
909
- opts.authConfig?.customProviders,
910
- opts.authConfig?.hoonifyModels,
911
- ),
912
- maxOutputTokens: getMaxOutputTokens(
913
- opts.model,
914
- opts.authConfig?.customProviders,
915
- opts.authConfig?.hoonifyModels,
916
- ),
917
- system: applySequentialToolCallPolicy(
918
- opts.system,
919
- opts.tools,
920
- opts.model,
921
- ),
922
- tools: opts.tools,
923
- sessionPath: opts.sessionPath,
924
- telemetry: {
925
- trigger: "context_overflow",
926
- model: opts.model,
927
- sessionId: opts.sessionId,
928
- restartDepth: opts._restartDepth,
929
- previous: opts._compaction?.last,
930
- },
931
- });
947
+ const fitted =
948
+ error instanceof InStreamCompactionRequired
949
+ ? error.fitted
950
+ : fitMessagesToContext(currentMessages, {
951
+ contextWindow: getContextWindow(
952
+ opts.model,
953
+ opts.authConfig?.customProviders,
954
+ opts.authConfig?.hoonifyModels,
955
+ ),
956
+ maxOutputTokens: getMaxOutputTokens(
957
+ opts.model,
958
+ opts.authConfig?.customProviders,
959
+ opts.authConfig?.hoonifyModels,
960
+ ),
961
+ system: applySequentialToolCallPolicy(
962
+ opts.system,
963
+ opts.tools,
964
+ opts.model,
965
+ ),
966
+ tools: opts.tools,
967
+ sessionPath: opts.sessionPath,
968
+ telemetry: {
969
+ trigger: "context_overflow",
970
+ model: opts.model,
971
+ sessionId: opts.sessionId,
972
+ restartDepth: opts._restartDepth,
973
+ previous: opts._compaction?.last,
974
+ },
975
+ });
932
976
 
933
977
  if (fitted.compaction && opts._compaction)
934
978
  opts._compaction.last = fitted.compaction;
@@ -944,7 +988,11 @@ function wrapStreamWithErrorHandler(
944
988
  // `MAX_RESTART_DEPTH` finally trips.
945
989
  let postReactiveDepth = opts._restartDepth ?? 0;
946
990
 
947
- if (fitted.fitsBudget && fitted.modified) {
991
+ if (
992
+ !(error instanceof CompactionRejectedError) &&
993
+ fitted.fitsBudget &&
994
+ fitted.modified
995
+ ) {
948
996
  if (!silent) {
949
997
  log.warn(
950
998
  `Context length error — Layer 1+2 reduced to ~${fitted.estimatedInputTokens} tokens, retrying`,
@@ -1017,12 +1065,19 @@ function wrapStreamWithErrorHandler(
1017
1065
  }
1018
1066
 
1019
1067
  try {
1068
+ if (error instanceof CompactionRejectedError) throw error;
1020
1069
  const summarizationStream = createSummarizationStream(
1021
1070
  messagesForSummary,
1022
1071
  {
1023
1072
  ...opts,
1024
1073
  _restartDepth: postReactiveDepth,
1025
- _compactionTrigger: "context_overflow",
1074
+ _compactionTrigger:
1075
+ error instanceof InStreamCompactionRequired
1076
+ ? "proactive"
1077
+ : "context_overflow",
1078
+ _proactiveSummaryMayFit:
1079
+ error instanceof InStreamCompactionRequired &&
1080
+ error.fitted.fitsBudget,
1026
1081
  },
1027
1082
  model,
1028
1083
  );
@@ -1030,7 +1085,7 @@ function wrapStreamWithErrorHandler(
1030
1085
  yield chunk;
1031
1086
  }
1032
1087
  } catch (summarizeError) {
1033
- // Only fall through to minimal-restart for context-length
1088
+ // Fall through to minimal restart for context or rejected-summary
1034
1089
  // errors. For everything else — auth failures, network
1035
1090
  // errors, abort signals, the typed terminal
1036
1091
  // `ContextLengthExhaustedError`, or non-`Error` throws —
@@ -1039,11 +1094,15 @@ function wrapStreamWithErrorHandler(
1039
1094
  // auth failure would burn an extra cycle that fails
1040
1095
  // identically.
1041
1096
  if (
1097
+ opts.primaryModelOnly ||
1042
1098
  summarizeError instanceof ContextLengthExhaustedError ||
1043
1099
  (summarizeError instanceof Error &&
1044
1100
  summarizeError.name === "AbortError") ||
1045
1101
  opts.abortSignal?.aborted ||
1046
- !checkIfContextLengthError(summarizeError)
1102
+ !(
1103
+ checkIfContextLengthError(summarizeError) ||
1104
+ summarizeError instanceof CompactionRejectedError
1105
+ )
1047
1106
  ) {
1048
1107
  throw summarizeError;
1049
1108
  }
@@ -1137,56 +1196,27 @@ export interface ModelInfo {
1137
1196
  }
1138
1197
 
1139
1198
  export function modelRequiresThinking(modelId: string): boolean {
1140
- return getClaudeCapabilities(modelId)?.alwaysOnThinking ?? false;
1199
+ return getReasoningCapabilities(modelId).alwaysOn ?? false;
1141
1200
  }
1142
1201
 
1143
- /**
1144
- * Check whether a model supports extended thinking based on its ID.
1145
- *
1146
- * Thinking is available on Claude 3.7 Sonnet and all Claude 4.x+ models
1147
- * (Sonnet, Opus, Haiku 4.5). Works for Anthropic direct, Bedrock, and
1148
- * Pensar provider IDs.
1149
- */
1150
1202
  export function modelSupportsThinking(modelId: string): boolean {
1151
- if (getClaudeCapabilities(modelId)) return true;
1152
- // Normalize: strip provider prefixes so we match the base Claude model ID
1153
- const normalized = modelId
1154
- .replace(/^pensar:/, "")
1155
- .replace(/^(us\.|eu\.|global\.|ap\.)?anthropic\./, "");
1156
-
1203
+ const cap = getReasoningCapabilities(modelId);
1157
1204
  return (
1158
- /^claude-3-7-sonnet/.test(normalized) ||
1159
- /^claude-sonnet-4/.test(normalized) ||
1160
- /^claude-opus-4/.test(normalized) ||
1161
- /^claude-haiku-4-5/.test(normalized)
1205
+ cap.control !== "none" &&
1206
+ (!!cap.thinking || modelId.startsWith("anthropic/claude-"))
1162
1207
  );
1163
1208
  }
1164
1209
 
1165
- /**
1166
- * Whether a model accepts the `adaptive` thinking type — Claude Opus 4.6+ and
1167
- * Sonnet 4.6+. Pre-4.6 models (Opus 4.0–4.5, Sonnet 4.5, Haiku 4.5) support
1168
- * extended thinking but reject `adaptive`, so we don't request thinking for
1169
- * them at all.
1170
- */
1171
1210
  export function modelSupportsAdaptiveThinking(modelId: string): boolean {
1172
- if (getClaudeCapabilities(modelId)) return true;
1173
- const normalized = modelId
1174
- .replace(/^pensar:/, "")
1175
- .replace(/^(us\.|eu\.|global\.|ap\.)?anthropic\./, "");
1176
-
1177
- return (
1178
- /^claude-sonnet-4-[6-9]/.test(normalized) ||
1179
- /^claude-opus-4-[6-9]/.test(normalized)
1180
- );
1211
+ return getReasoningCapabilities(modelId).thinking === "adaptive";
1181
1212
  }
1182
1213
 
1183
1214
  export function modelSupportsOpenAIReasoning(modelId: string): boolean {
1184
- if (modelId === CONCENTRATE_GLM_5_3_MODEL_ID) return true;
1185
- const { provider } = getModelInfo(modelId);
1186
- if (!["openai", "openrouter", "concentrate"].includes(provider)) return false;
1187
- modelId = modelId.replace(/^(openai\/|concentrate:)/, "");
1215
+ const cap = getReasoningCapabilities(modelId);
1188
1216
  return (
1189
- OPENAI_REASONING_MODEL_IDS.has(modelId) || /^o[134](?:\b|-)/.test(modelId)
1217
+ cap.control === "effort" &&
1218
+ !modelSupportsThinking(modelId) &&
1219
+ cap.transport !== "google"
1190
1220
  );
1191
1221
  }
1192
1222
 
@@ -1197,99 +1227,41 @@ export function modelRequiresReasoning(modelId: string): boolean {
1197
1227
  export function getOpenAIReasoningEfforts(
1198
1228
  modelId: string,
1199
1229
  ): OpenAIReasoningEffort[] {
1200
- if (!modelSupportsOpenAIReasoning(modelId)) return [];
1201
- if (modelId === CONCENTRATE_GLM_5_3_MODEL_ID) {
1202
- return ["low", "high", "max"];
1203
- }
1204
- if (/^concentrate:gpt-5\.4-(?:mini|nano)$/.test(modelId)) {
1205
- return ["none", "low", "medium", "high"];
1206
- }
1207
- modelId = modelId.replace(/^(openai\/|concentrate:)/, "");
1208
- if (modelId === "gpt-6-sol" || modelId === "gpt-6-luna") {
1209
- return ["none", "low", "medium", "high", "xhigh", "max"];
1210
- }
1211
- if (/^gpt-6[.-]/.test(modelId)) {
1212
- return ["low", "medium", "high", "xhigh", "max"];
1213
- }
1214
- if (/^gpt-5\.5-pro(?:-|$)/.test(modelId)) {
1215
- return ["medium", "high", "xhigh"];
1216
- }
1217
- if (/^gpt-5\.4-(?:mini|nano)(?:-|$)/.test(modelId)) {
1218
- return ["none", "low", "medium", "high", "xhigh"];
1219
- }
1220
- if (/^gpt-5\.6(?:\b|-)/.test(modelId)) {
1221
- return ["low", "medium", "high", "xhigh", "max", "ultra"];
1222
- }
1223
- if (/^gpt-5\.5(?:\b|-)/.test(modelId)) {
1224
- return ["none", "low", "medium", "high", "xhigh"];
1225
- }
1226
- return ["low", "medium", "high"];
1230
+ return modelSupportsOpenAIReasoning(modelId)
1231
+ ? ([
1232
+ ...getReasoningCapabilities(modelId).efforts,
1233
+ ] as OpenAIReasoningEffort[])
1234
+ : [];
1227
1235
  }
1228
1236
 
1229
1237
  export function normalizeOpenAIReasoningEffort(
1230
1238
  modelId: string,
1231
1239
  effort?: OpenAIReasoningEffort | null,
1232
1240
  ): OpenAIReasoningEffort | undefined {
1233
- if (modelId === CONCENTRATE_GLM_5_3_MODEL_ID) {
1234
- switch (effort) {
1235
- case "none":
1236
- case "low":
1237
- return "low";
1238
- case "medium":
1239
- case "high":
1240
- return "high";
1241
- case "xhigh":
1242
- case "max":
1243
- case "ultra":
1244
- return "max";
1245
- default:
1246
- return "high";
1241
+ if (!modelSupportsOpenAIReasoning(modelId)) return undefined;
1242
+ const setting = resolveReasoningSetting(modelId, {
1243
+ openAIReasoningEffort: effort,
1244
+ });
1245
+ return setting === "default"
1246
+ ? undefined
1247
+ : (setting as OpenAIReasoningEffort | undefined);
1248
+ }
1249
+
1250
+ export type { ReasoningProviderOptions } from "./reasoning";
1251
+
1252
+ export function mergeProviderOptions(
1253
+ ...options: Array<ReasoningProviderOptions | undefined>
1254
+ ): ReasoningProviderOptions | undefined {
1255
+ if (options.every((value) => value === undefined)) return undefined;
1256
+ const merged: ReasoningProviderOptions = {};
1257
+ for (const option of options) {
1258
+ for (const [provider, value] of Object.entries(option ?? {})) {
1259
+ merged[provider] = { ...merged[provider], ...value };
1247
1260
  }
1248
1261
  }
1249
- const efforts = getOpenAIReasoningEfforts(modelId);
1250
- if (efforts.length === 0) return undefined;
1251
- if (effort && efforts.includes(effort)) {
1252
- // ponytail: OpenAI has no distinct ultra level; keep the UI choice as a max alias.
1253
- return effort === "ultra" ? "max" : effort;
1254
- }
1255
- return DEFAULT_OPENAI_REASONING_EFFORT;
1262
+ return merged;
1256
1263
  }
1257
1264
 
1258
- /**
1259
- * Provider-specific options passed to `streamText` to request reasoning /
1260
- * extended thinking. Each provider reads only its own namespace:
1261
- * - `anthropic.thinking` — direct Anthropic provider AND the Pensar gateway
1262
- * (the gateway formatter maps it to the Bedrock `thinking` field).
1263
- * - `bedrock.reasoningConfig` — the AWS Bedrock provider; it ignores the
1264
- * `anthropic` namespace entirely, so Anthropic-on-Bedrock thinking is only
1265
- * requested when this key is present. `display: 'summarized'` makes the
1266
- * model actually return reasoning text (adaptive can otherwise omit it).
1267
- * - `openai.reasoningEffort` — OpenAI/o-series reasoning models.
1268
- */
1269
- export type ReasoningProviderOptions = {
1270
- openrouter?: {
1271
- reasoning: { effort: OpenAIReasoningEffort; enabled?: boolean };
1272
- };
1273
- anthropic?: {
1274
- thinking:
1275
- | { type: "adaptive"; display?: "summarized" }
1276
- | { type: "disabled" };
1277
- // Soft effort hint for adaptive thinking; omitted when no level requested
1278
- // (model defaults to "high"). Sibling of `thinking` per the AI SDK.
1279
- effort?: ThinkingEffort;
1280
- };
1281
- bedrock?: {
1282
- reasoningConfig: {
1283
- type: "adaptive";
1284
- display: "summarized";
1285
- // Bedrock's channel for the adaptive effort hint (maps to Claude's
1286
- // output_config.effort). Omitted when no level requested.
1287
- maxReasoningEffort?: ThinkingEffort;
1288
- };
1289
- };
1290
- openai?: OpenAIResponsesProviderOptions;
1291
- };
1292
-
1293
1265
  export type OpenRouterProviderOptions = {
1294
1266
  openrouter?: {
1295
1267
  provider:
@@ -1344,99 +1316,21 @@ export function buildOpenRouterStructuredProviderOptions(
1344
1316
  };
1345
1317
  }
1346
1318
 
1347
- /**
1348
- * Build the `providerOptions` for a request based on the model and reasoning
1349
- * settings. Returns `undefined` when no reasoning/thinking is requested.
1350
- *
1351
- * When thinking is enabled for an Anthropic-family model we set BOTH the
1352
- * `anthropic` and `bedrock` keys: the resolved provider (direct Anthropic,
1353
- * Pensar gateway, or Bedrock) reads only its own namespace and ignores the
1354
- * other, so this is safe across all three and avoids brittle provider
1355
- * branching here.
1356
- */
1357
1319
  export function buildReasoningProviderOptions(
1358
1320
  model: AIModel,
1359
1321
  opts: {
1322
+ reasoning?: ReasoningSetting;
1360
1323
  enableThinking?: boolean;
1361
- /**
1362
- * Adaptive-thinking effort hint for Anthropic models that support it
1363
- * (Opus/Sonnet 4.6+). Ignored on models without adaptive support. When
1364
- * omitted the model uses its own default (`high`).
1365
- */
1366
1324
  thinkingEffort?: ThinkingEffort | null;
1367
1325
  openAIReasoningEffort?: OpenAIReasoningEffort | null;
1326
+ customProviders?: import("../config/customProviders").CustomProviders;
1327
+ maxOutputTokens?: number;
1368
1328
  },
1369
1329
  ): ReasoningProviderOptions | undefined {
1370
- const claude = getClaudeCapabilities(model);
1371
- if (claude) {
1372
- const provider = getModelInfo(model).provider;
1373
- const thinking = !!opts.enableThinking || claude.alwaysOnThinking;
1374
- if (provider === "openrouter") {
1375
- return {
1376
- openrouter: {
1377
- reasoning: {
1378
- effort: thinking ? (opts.thinkingEffort ?? "high") : "none",
1379
- enabled: thinking,
1380
- },
1381
- },
1382
- };
1383
- }
1384
- if (provider === "concentrate") {
1385
- return {
1386
- openai: {
1387
- forceReasoning: true,
1388
- reasoningEffort: thinking ? (opts.thinkingEffort ?? "high") : "none",
1389
- reasoningSummary: "auto",
1390
- },
1391
- };
1392
- }
1393
- return {
1394
- anthropic: {
1395
- thinking:
1396
- opts.enableThinking || claude.alwaysOnThinking
1397
- ? { type: "adaptive", display: "summarized" }
1398
- : { type: "disabled" },
1399
- ...(opts.thinkingEffort ? { effort: opts.thinkingEffort } : {}),
1400
- },
1401
- };
1402
- }
1403
- const useThinking =
1404
- !!opts.enableThinking &&
1405
- isAnthropicProvider(model) &&
1406
- modelSupportsAdaptiveThinking(model);
1407
- const normalizedOpenAIEffort = normalizeOpenAIReasoningEffort(
1408
- model,
1409
- opts.openAIReasoningEffort,
1410
- );
1411
- // The effort hint only rides along when adaptive thinking is actually used.
1412
- const effort = useThinking ? (opts.thinkingEffort ?? undefined) : undefined;
1413
-
1414
- if (!useThinking && !normalizedOpenAIEffort) return undefined;
1415
-
1416
- if (getModelInfo(model).provider === "openrouter" && normalizedOpenAIEffort) {
1417
- return { openrouter: { reasoning: { effort: normalizedOpenAIEffort } } };
1418
- }
1419
-
1420
- return {
1421
- ...(useThinking
1422
- ? {
1423
- anthropic: {
1424
- thinking: { type: "adaptive" as const },
1425
- ...(effort ? { effort } : {}),
1426
- },
1427
- bedrock: {
1428
- reasoningConfig: {
1429
- type: "adaptive" as const,
1430
- display: "summarized" as const,
1431
- ...(effort ? { maxReasoningEffort: effort } : {}),
1432
- },
1433
- },
1434
- }
1435
- : {}),
1436
- ...(normalizedOpenAIEffort
1437
- ? { openai: { reasoningEffort: normalizedOpenAIEffort } }
1438
- : {}),
1439
- };
1330
+ const setting = resolveReasoningSetting(model, opts);
1331
+ return setting === undefined
1332
+ ? undefined
1333
+ : buildModelReasoningOptions(model, setting, opts);
1440
1334
  }
1441
1335
 
1442
1336
  /** Cache token metrics extracted from Anthropic providerMetadata */
@@ -1446,6 +1340,12 @@ export interface CacheMetrics {
1446
1340
  }
1447
1341
 
1448
1342
  export interface StreamResponseOpts {
1343
+ /** Disables model repair and general recovery; compaction requires explicit admission. */
1344
+ primaryModelOnly?: boolean;
1345
+ runContextCompaction?: <T>(work: () => Promise<T>) => Promise<T>;
1346
+ /** Session-local working state restored after compaction. */
1347
+ getContinuationContext?: () => string;
1348
+ onInvalidToolCall?: (name: string) => void;
1449
1349
  prompt: string;
1450
1350
  system?: string;
1451
1351
  model: AIModel;
@@ -1474,6 +1374,11 @@ export interface StreamResponseOpts {
1474
1374
  * new messages (not the full pre-summarization history).
1475
1375
  */
1476
1376
  onSummarized?: (summary: string) => void;
1377
+ /** The fitted context and prior generated-message count for transcript writers. */
1378
+ onContextPrepared?: (context: {
1379
+ messages: ModelMessage[];
1380
+ responseMessageOffset: number;
1381
+ }) => void;
1477
1382
  /**
1478
1383
  * Deprecated host shim: fires only when a cache counter is `> 0`.
1479
1384
  * Apex internals read cache from {@link normalizeStepUsage} on the step.
@@ -1488,6 +1393,7 @@ export interface StreamResponseOpts {
1488
1393
  thinkingEffort?: ThinkingEffort | null;
1489
1394
  /** OpenAI reasoning effort for GPT/o-series reasoning models. */
1490
1395
  openAIReasoningEffort?: OpenAIReasoningEffort | null;
1396
+ reasoning?: ReasoningSetting;
1491
1397
  /** Session root path — used by context management layers to persist truncated tool results */
1492
1398
  sessionPath?: string;
1493
1399
  /** Session id (`ses_…`) of the agent making this call; stamped onto AI-span telemetry so traces are filterable by session. */
@@ -1506,12 +1412,15 @@ export interface StreamResponseOpts {
1506
1412
  /** Shared only by continuations of this stream, never across agents. */
1507
1413
  _compaction?: { last?: CompactionLink };
1508
1414
  _compactionTrigger?: CompactionTrigger;
1415
+ _proactiveSummaryMayFit?: boolean;
1416
+ _proactiveSummaryFailures?: number;
1417
+ _initialPrompt?: string;
1509
1418
  }
1510
1419
 
1511
1420
  const NATIVE_STREAM_RECOVERY = Symbol("native-stream-recovery");
1512
1421
 
1513
1422
  interface NativeStreamRecovery {
1514
- model: LanguageModel;
1423
+ model: ReturnType<typeof getProviderModel>;
1515
1424
  run<T>(fn: () => T): T;
1516
1425
  }
1517
1426
 
@@ -1545,6 +1454,7 @@ function streamResponseWithinOperation(
1545
1454
  // map. Re-entry is identity (`activeTools` already consumed).
1546
1455
  opts = {
1547
1456
  ...opts,
1457
+ _initialPrompt: opts._initialPrompt ?? opts.prompt,
1548
1458
  _compaction: compactionState,
1549
1459
  // `activeTools` is authoritative for advertisement and execution: a tool
1550
1460
  // (including `response`) is exposed only when the caller lists it, so the
@@ -1580,14 +1490,21 @@ function streamResponseWithinOperation(
1580
1490
  enableThinking,
1581
1491
  thinkingEffort,
1582
1492
  openAIReasoningEffort,
1493
+ reasoning,
1583
1494
  sessionId,
1495
+ sessionPath,
1584
1496
  contextRecorder,
1497
+ onContextPrepared,
1585
1498
  } = opts;
1586
1499
 
1587
- // Recorded-run base, captured once at the first prepareStep of this
1588
- // streamText call; only used to reconstruct cumulative prefixes in
1589
- // onStepFinish (response.messages excludes the input base).
1500
+ // response.messages accumulates generated steps after prepareStep rewrites
1501
+ // provider context. The offset prevents transcript writers from appending
1502
+ // older, pre-fit messages again.
1590
1503
  let recorderBase: ModelMessage[] | undefined;
1504
+ let responseMessageLength = 0;
1505
+ const estimatedOverheadTokens = estimateToolsOverheadTokens(tools) + 1_000;
1506
+ let preparedInputEstimateTokens = 0;
1507
+ let inputSafetyMarginTokens = 10_000;
1591
1508
 
1592
1509
  // Wrap onStepFinish to fire cache metrics and the usage callback for every
1593
1510
  // step. Must be async so that callers returning a Promise (persistence,
@@ -1596,6 +1513,18 @@ function streamResponseWithinOperation(
1596
1513
  // provider metadata is never parsed twice.
1597
1514
  const onStepFinish: typeof userOnStepFinish = async (step) => {
1598
1515
  const stepUsage = normalizeStepUsage(step);
1516
+ if (
1517
+ preparedInputEstimateTokens > 0 &&
1518
+ stepUsage.inputTokens > 0 &&
1519
+ step.response?.id !== "summarization" &&
1520
+ step.response?.id !== "tool-repair"
1521
+ ) {
1522
+ // Fixed system and tool overhead is already reserved by the fitter; calibrate only the remaining tokenization gap.
1523
+ inputSafetyMarginTokens = Math.max(
1524
+ inputSafetyMarginTokens,
1525
+ stepUsage.inputTokens - preparedInputEstimateTokens + 10_000,
1526
+ );
1527
+ }
1599
1528
  // Canonical commit precedes every downstream use; the recorder latches
1600
1529
  // rejections (SDK notify() swallows them) until the next turn gate or
1601
1530
  // agent-side flush turns the latch into a stream failure.
@@ -1611,10 +1540,20 @@ function streamResponseWithinOperation(
1611
1540
  throw new Error("context recorder active without a committed base");
1612
1541
  }
1613
1542
  await contextRecorder.checkpoint({
1614
- messages: [...recorderBase, ...step.response.messages],
1543
+ messages: [
1544
+ ...recorderBase,
1545
+ ...step.response.messages.slice(responseMessageLength),
1546
+ ],
1615
1547
  system: effectiveSystem ?? null,
1616
1548
  });
1617
1549
  }
1550
+ if (
1551
+ step.response?.id !== "summarization" &&
1552
+ step.response?.id !== "tool-repair" &&
1553
+ step.response?.messages
1554
+ ) {
1555
+ responseMessageLength = step.response.messages.length;
1556
+ }
1618
1557
  if (
1619
1558
  onCacheMetrics &&
1620
1559
  (stepUsage.cacheReadTokens > 0 || stepUsage.cacheWriteTokens > 0)
@@ -1661,22 +1600,55 @@ function streamResponseWithinOperation(
1661
1600
  model,
1662
1601
  );
1663
1602
 
1603
+ const contextWindow = getContextWindow(
1604
+ model,
1605
+ authConfig?.customProviders,
1606
+ authConfig?.hoonifyModels,
1607
+ );
1608
+ const maxOutputTokens = getMaxOutputTokens(
1609
+ model,
1610
+ authConfig?.customProviders,
1611
+ authConfig?.hoonifyModels,
1612
+ );
1613
+ if (
1614
+ messages &&
1615
+ (!opts.primaryModelOnly || opts.runContextCompaction) &&
1616
+ !opts._proactiveSummaryFailures &&
1617
+ shouldSummarizeBeforeFitting(
1618
+ messages,
1619
+ contextWindow,
1620
+ maxOutputTokens,
1621
+ estimatedOverheadTokens,
1622
+ inputSafetyMarginTokens,
1623
+ systemWithToolPolicy,
1624
+ toolChoice,
1625
+ )
1626
+ ) {
1627
+ return wrapStreamWithErrorHandler(
1628
+ createSummarizationStream(
1629
+ messages,
1630
+ {
1631
+ ...opts,
1632
+ _compactionTrigger: "proactive",
1633
+ _proactiveSummaryMayFit: true,
1634
+ },
1635
+ providerModel,
1636
+ ),
1637
+ { current: messages },
1638
+ opts,
1639
+ providerModel,
1640
+ silent,
1641
+ );
1642
+ }
1643
+
1664
1644
  // Proactive context fit: compact before the call. Reactive recovery
1665
1645
  // below is a safety net for tokenizer drift the estimate doesn't catch.
1666
1646
  let fittedMessages = messages;
1667
1647
  let proactiveFitFailed = false;
1668
1648
  if (messages && messages.length > 0) {
1669
1649
  const fitted = fitMessagesToContext(messages, {
1670
- contextWindow: getContextWindow(
1671
- model,
1672
- authConfig?.customProviders,
1673
- authConfig?.hoonifyModels,
1674
- ),
1675
- maxOutputTokens: getMaxOutputTokens(
1676
- model,
1677
- authConfig?.customProviders,
1678
- authConfig?.hoonifyModels,
1679
- ),
1650
+ contextWindow,
1651
+ maxOutputTokens,
1680
1652
  system: systemWithToolPolicy,
1681
1653
  tools,
1682
1654
  sessionPath: opts.sessionPath,
@@ -1707,6 +1679,8 @@ function streamResponseWithinOperation(
1707
1679
  // an overflow inside the summarization call itself escapes uncaught while
1708
1680
  // the equivalent post-streamText path (line ~880) recovers.
1709
1681
  if (proactiveFitFailed && fittedMessages) {
1682
+ if (opts.primaryModelOnly && !opts.runContextCompaction)
1683
+ throw new Error("Lance context budget exhausted");
1710
1684
  if (!silent) {
1711
1685
  log.warn(
1712
1686
  `Proactive context fit returned fitsBudget=false on ${fittedMessages.length} messages — escalating to summarization before send`,
@@ -1723,7 +1697,11 @@ function streamResponseWithinOperation(
1723
1697
  return wrapStreamWithErrorHandler(
1724
1698
  createSummarizationStream(
1725
1699
  fittedMessages,
1726
- { ...opts, _compactionTrigger: "proactive" },
1700
+ {
1701
+ ...opts,
1702
+ _compactionTrigger: "proactive",
1703
+ _proactiveSummaryMayFit: false,
1704
+ },
1727
1705
  providerModel,
1728
1706
  ),
1729
1707
  messagesContainer,
@@ -1760,12 +1738,15 @@ function streamResponseWithinOperation(
1760
1738
  enableThinking,
1761
1739
  thinkingEffort,
1762
1740
  openAIReasoningEffort,
1741
+ reasoning,
1742
+ customProviders: authConfig?.customProviders,
1743
+ maxOutputTokens,
1763
1744
  });
1764
1745
  const openRouterProviderOptions = buildOpenRouterProviderOptions(model);
1765
- const providerOptions =
1766
- reasoningProviderOptions || openRouterProviderOptions
1767
- ? { ...reasoningProviderOptions, ...openRouterProviderOptions }
1768
- : undefined;
1746
+ const providerOptions = mergeProviderOptions(
1747
+ reasoningProviderOptions,
1748
+ openRouterProviderOptions,
1749
+ );
1769
1750
 
1770
1751
  let rateLimitRetryCount = 0;
1771
1752
 
@@ -1781,8 +1762,81 @@ function streamResponseWithinOperation(
1781
1762
  // Create the appropriate provider instance. The span tracker captures
1782
1763
  // the SDK's root generation span for error marking (see below).
1783
1764
  const generationSpans = createGenerationSpanTracker();
1784
- const responseOptions: Parameters<typeof streamText>[0] = {
1765
+ const attemptAbort = new AbortController();
1766
+ const attemptSignal = abortSignal
1767
+ ? AbortSignal.any([abortSignal, attemptAbort.signal])
1768
+ : attemptAbort.signal;
1769
+ let cancelModelStream: ((reason: unknown) => Promise<void>) | undefined;
1770
+ // Cancelling the SDK fullStream only detaches its subscriber.
1771
+ const attemptModel = wrapLanguageModel({
1785
1772
  model: providerModel,
1773
+ middleware: {
1774
+ specificationVersion: "v3",
1775
+ wrapStream: async ({ doStream }) => {
1776
+ const pending = doStream();
1777
+ let reader:
1778
+ | ReadableStreamDefaultReader<LanguageModelV3StreamPart>
1779
+ | undefined;
1780
+ cancelModelStream = async (reason) => {
1781
+ try {
1782
+ const source = await pending;
1783
+ await (reader
1784
+ ? reader.cancel(reason)
1785
+ : source.stream.cancel(reason));
1786
+ } catch (error) {
1787
+ if (
1788
+ !(error instanceof Error) ||
1789
+ (error.name !== "AbortError" &&
1790
+ !isStreamIdleTimeoutError(error))
1791
+ ) {
1792
+ throw error;
1793
+ }
1794
+ }
1795
+ };
1796
+ const source = await pending;
1797
+ reader = source.stream.getReader();
1798
+ const ownedReader = reader;
1799
+ return {
1800
+ ...source,
1801
+ stream: new ReadableStream({
1802
+ async pull(controller) {
1803
+ const next = await ownedReader.read();
1804
+ if (next.done) controller.close();
1805
+ else controller.enqueue(next.value);
1806
+ },
1807
+ cancel: (reason) => ownedReader.cancel(reason),
1808
+ }),
1809
+ };
1810
+ },
1811
+ },
1812
+ });
1813
+ const drainIdleStream = async (reason: unknown) => {
1814
+ attemptAbort.abort(reason);
1815
+ let timer: ReturnType<typeof setTimeout> | undefined;
1816
+ try {
1817
+ await Promise.race([
1818
+ cancelModelStream?.(reason),
1819
+ new Promise<never>((_, reject) => {
1820
+ timer = setTimeout(
1821
+ () =>
1822
+ reject(
1823
+ Object.assign(
1824
+ new Error(
1825
+ "Stalled model stream did not drain within 30 seconds",
1826
+ ),
1827
+ { name: "ModelStreamDrainTimeoutError" },
1828
+ ),
1829
+ ),
1830
+ 30_000,
1831
+ );
1832
+ }),
1833
+ ]);
1834
+ } finally {
1835
+ clearTimeout(timer);
1836
+ }
1837
+ };
1838
+ const responseOptions: Parameters<typeof streamText>[0] = {
1839
+ model: attemptModel,
1786
1840
  system: effectiveSystem,
1787
1841
  ...(effectiveMessages ? { messages: effectiveMessages } : { prompt }),
1788
1842
  stopWhen,
@@ -1802,6 +1856,10 @@ function streamResponseWithinOperation(
1802
1856
  operation: "apex.agent.stream",
1803
1857
  sessionId,
1804
1858
  compaction: opts._compaction?.last,
1859
+ reasoning: {
1860
+ requested: reasoning ?? "legacy-default",
1861
+ parameters: JSON.stringify(reasoningProviderOptions ?? {}),
1862
+ },
1805
1863
  }),
1806
1864
  tracer: generationSpans.tracer,
1807
1865
  },
@@ -1818,30 +1876,86 @@ function streamResponseWithinOperation(
1818
1876
  // Awaiting inside prepareStep keeps streamResponse synchronous; the
1819
1877
  // SDK awaits it before provider dispatch, so every turn and every
1820
1878
  // recovery re-entry commits before use.
1821
- prepareStep: async (opts) => {
1822
- if (contextRecorder) {
1823
- if (recorderBase === undefined) {
1824
- recorderBase = structuredClone(opts.messages);
1879
+ prepareStep: async (step) => {
1880
+ let stepMessages = step.messages;
1881
+ if (step.stepNumber > 0) {
1882
+ if (
1883
+ (!opts.primaryModelOnly || opts.runContextCompaction) &&
1884
+ (opts._proactiveSummaryFailures ?? 0) <
1885
+ MAX_PROACTIVE_SUMMARY_FAILURES &&
1886
+ shouldSummarizeBeforeFitting(
1887
+ stepMessages,
1888
+ contextWindow,
1889
+ maxOutputTokens,
1890
+ estimatedOverheadTokens,
1891
+ inputSafetyMarginTokens,
1892
+ effectiveSystem,
1893
+ toolChoice,
1894
+ )
1895
+ ) {
1896
+ messagesContainer.current = stepMessages;
1897
+ throw new InStreamCompactionRequired({
1898
+ messages: stepMessages,
1899
+ estimatedInputTokens: estimateMessageTokens(stepMessages),
1900
+ modified: false,
1901
+ fitsBudget: true,
1902
+ });
1825
1903
  }
1904
+ const fitted = fitMessagesToContext(stepMessages, {
1905
+ contextWindow,
1906
+ maxOutputTokens,
1907
+ system: effectiveSystem,
1908
+ tools,
1909
+ overheadTokens: estimatedOverheadTokens,
1910
+ sessionPath,
1911
+ safetyMarginTokens: inputSafetyMarginTokens,
1912
+ telemetry: {
1913
+ trigger: "proactive",
1914
+ model,
1915
+ sessionId,
1916
+ restartDepth,
1917
+ previous: compactionState.last,
1918
+ },
1919
+ });
1920
+ if (fitted.compaction) compactionState.last = fitted.compaction;
1921
+ stepMessages = fitted.messages;
1922
+ if (!fitted.fitsBudget) {
1923
+ messagesContainer.current = stepMessages;
1924
+ throw new InStreamCompactionRequired(fitted);
1925
+ }
1926
+ }
1927
+ if (contextRecorder) {
1928
+ recorderBase = structuredClone(stepMessages);
1826
1929
  // Full logical context each turn — accepted step outputs must
1827
1930
  // survive into every later commit, and system is explicit (when
1828
1931
  // cached into messages, effectiveSystem is undefined → null).
1829
1932
  await contextRecorder.checkpoint({
1830
- messages: opts.messages,
1933
+ messages: stepMessages,
1831
1934
  system: effectiveSystem ?? null,
1832
1935
  });
1833
1936
  }
1834
1937
  // Update the container with the latest messages
1835
- messagesContainer.current = opts.messages;
1938
+ messagesContainer.current = stepMessages;
1939
+ preparedInputEstimateTokens =
1940
+ estimateMessageTokens(stepMessages) +
1941
+ estimatedOverheadTokens +
1942
+ (effectiveSystem ? estimateTokens(effectiveSystem) : 0);
1943
+ onContextPrepared?.({
1944
+ messages: stepMessages,
1945
+ responseMessageOffset: responseMessageLength,
1946
+ });
1836
1947
  // Mark the last message so the growing conversation caches incrementally
1837
1948
  if (cacheBreakpoint) {
1838
1949
  return {
1839
- messages: withCachedLastMessage(opts.messages, cacheBreakpoint),
1950
+ messages: withCachedLastMessage(stepMessages, cacheBreakpoint),
1840
1951
  };
1841
1952
  }
1842
- return undefined;
1953
+ return stepMessages === step.messages
1954
+ ? undefined
1955
+ : { messages: stepMessages };
1843
1956
  },
1844
1957
  onError: async ({ error }: { error: unknown }) => {
1958
+ if (opts.primaryModelOnly) return;
1845
1959
  // Never throw here: the SDK calls this inside its stream pipeline,
1846
1960
  // and a thrown callback kills the pipeline's terminal lifecycle —
1847
1961
  // the root generation span never ends and nothing exports. Return
@@ -1868,7 +1982,7 @@ function streamResponseWithinOperation(
1868
1982
  }
1869
1983
  },
1870
1984
  onStepFinish,
1871
- abortSignal,
1985
+ abortSignal: attemptSignal,
1872
1986
  activeTools: allowedTools,
1873
1987
  experimental_repairToolCall: async ({
1874
1988
  toolCall,
@@ -1876,6 +1990,8 @@ function streamResponseWithinOperation(
1876
1990
  tools,
1877
1991
  error,
1878
1992
  }) => {
1993
+ opts.onInvalidToolCall?.(toolCall.toolName);
1994
+ if (opts.primaryModelOnly) return null;
1879
1995
  // [response-debug] Log when repair fires for `response` (streamed args failed Zod validation), bypassing `silent`.
1880
1996
  if (RESPONSE_DEBUG && toolCall.toolName === RESPONSE_TOOL_NAME) {
1881
1997
  const raw = toolCall.input;
@@ -1953,8 +2069,9 @@ function streamResponseWithinOperation(
1953
2069
  () =>
1954
2070
  generateText({
1955
2071
  model: providerModel,
2072
+ maxOutputTokens,
1956
2073
  providerOptions: {
1957
- ...openRouterProviderOptions,
2074
+ ...providerOptions,
1958
2075
  // Durable runtime: tool-call repair fires only when a
1959
2076
  // replayed tool call fails validation — nondeterministically
1960
2077
  // vs the first run — so it must stay off the checkpoint
@@ -1982,6 +2099,10 @@ function streamResponseWithinOperation(
1982
2099
  experimental_telemetry: createAiTelemetrySettings({
1983
2100
  operation: "apex.tool.repair",
1984
2101
  sessionId,
2102
+ reasoning: {
2103
+ requested: reasoning ?? "legacy-default",
2104
+ parameters: JSON.stringify(reasoningProviderOptions ?? {}),
2105
+ },
1985
2106
  }),
1986
2107
  }),
1987
2108
  );
@@ -2081,10 +2202,16 @@ function streamResponseWithinOperation(
2081
2202
  0,
2082
2203
  0,
2083
2204
  generationSpans,
2205
+ drainIdleStream,
2084
2206
  );
2085
2207
  } catch (error) {
2086
2208
  // Check if the error is related to context length
2087
2209
  const isContextLengthError = checkIfContextLengthError(error);
2210
+ if (
2211
+ opts.primaryModelOnly &&
2212
+ (!opts.runContextCompaction || !isContextLengthError)
2213
+ )
2214
+ throw error;
2088
2215
  const outerErrorMessage =
2089
2216
  error instanceof Error ? error.message : String(error);
2090
2217
 
@@ -2136,6 +2263,7 @@ export interface GenerateObjectOpts<T extends z.ZodType> {
2136
2263
  maxTokens?: number;
2137
2264
  temperature?: number;
2138
2265
  openAIReasoningEffort?: OpenAIReasoningEffort | null;
2266
+ reasoning?: ReasoningSetting;
2139
2267
  authConfig?: AIAuthConfig;
2140
2268
  abortSignal?: AbortSignal;
2141
2269
  onTokenUsage?: (inputTokens: number, outputTokens: number) => void;
@@ -2173,6 +2301,7 @@ async function generateObjectResponseWithinRecorder<T extends z.ZodType>(
2173
2301
  maxTokens,
2174
2302
  temperature,
2175
2303
  openAIReasoningEffort,
2304
+ reasoning,
2176
2305
  authConfig,
2177
2306
  abortSignal,
2178
2307
  onTokenUsage,
@@ -2197,6 +2326,9 @@ async function generateObjectResponseWithinRecorder<T extends z.ZodType>(
2197
2326
  : baseProviderModel;
2198
2327
  const reasoningProviderOptions = buildReasoningProviderOptions(model, {
2199
2328
  openAIReasoningEffort,
2329
+ reasoning,
2330
+ customProviders: authConfig?.customProviders,
2331
+ maxOutputTokens: maxTokens,
2200
2332
  });
2201
2333
  const openRouterProviderOptions =
2202
2334
  buildOpenRouterStructuredProviderOptions(model);
@@ -2224,25 +2356,19 @@ async function generateObjectResponseWithinRecorder<T extends z.ZodType>(
2224
2356
  system,
2225
2357
  maxOutputTokens: maxTokens,
2226
2358
  temperature,
2227
- providerOptions:
2228
- reasoningProviderOptions || openRouterProviderOptions
2229
- ? {
2230
- ...reasoningProviderOptions,
2231
- ...(openRouterProviderOptions
2232
- ? {
2233
- openrouter: {
2234
- ...reasoningProviderOptions?.openrouter,
2235
- ...openRouterProviderOptions.openrouter,
2236
- },
2237
- }
2238
- : {}),
2239
- }
2240
- : undefined,
2359
+ providerOptions: mergeProviderOptions(
2360
+ reasoningProviderOptions,
2361
+ openRouterProviderOptions,
2362
+ ),
2241
2363
  maxRetries: 0,
2242
2364
  abortSignal,
2243
2365
  experimental_telemetry: createAiTelemetrySettings({
2244
2366
  operation: opts.operation ?? "apex.structured.generate",
2245
2367
  sessionId,
2368
+ reasoning: {
2369
+ requested: reasoning ?? "legacy-default",
2370
+ parameters: JSON.stringify(reasoningProviderOptions ?? {}),
2371
+ },
2246
2372
  }),
2247
2373
  });
2248
2374