@cjhyy/code-shell-core 0.5.0-rc.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 (484) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/agent/coordinator.d.ts +49 -0
  4. package/dist/agent/coordinator.js +77 -0
  5. package/dist/arena/arena.d.ts +43 -0
  6. package/dist/arena/arena.js +334 -0
  7. package/dist/arena/context/context-tools.d.ts +16 -0
  8. package/dist/arena/context/context-tools.js +231 -0
  9. package/dist/arena/detect-mode.d.ts +20 -0
  10. package/dist/arena/detect-mode.js +78 -0
  11. package/dist/arena/digest-builder.d.ts +25 -0
  12. package/dist/arena/digest-builder.js +120 -0
  13. package/dist/arena/index.d.ts +29 -0
  14. package/dist/arena/index.js +29 -0
  15. package/dist/arena/iterate/convergence.d.ts +25 -0
  16. package/dist/arena/iterate/convergence.js +103 -0
  17. package/dist/arena/iterate/formats/index.d.ts +22 -0
  18. package/dist/arena/iterate/formats/index.js +283 -0
  19. package/dist/arena/iterate/index.d.ts +11 -0
  20. package/dist/arena/iterate/index.js +9 -0
  21. package/dist/arena/iterate/iterative-arena.d.ts +23 -0
  22. package/dist/arena/iterate/iterative-arena.js +237 -0
  23. package/dist/arena/iterate/parse.d.ts +42 -0
  24. package/dist/arena/iterate/parse.js +123 -0
  25. package/dist/arena/iterate/phases/argue.d.ts +22 -0
  26. package/dist/arena/iterate/phases/argue.js +159 -0
  27. package/dist/arena/iterate/phases/revise.d.ts +16 -0
  28. package/dist/arena/iterate/phases/revise.js +62 -0
  29. package/dist/arena/iterate/phases/tournament.d.ts +34 -0
  30. package/dist/arena/iterate/phases/tournament.js +113 -0
  31. package/dist/arena/iterate/tools/web-tools.d.ts +13 -0
  32. package/dist/arena/iterate/tools/web-tools.js +54 -0
  33. package/dist/arena/iterate/types.d.ts +152 -0
  34. package/dist/arena/iterate/types.js +8 -0
  35. package/dist/arena/ledger.d.ts +47 -0
  36. package/dist/arena/ledger.js +151 -0
  37. package/dist/arena/lenses/architecture.d.ts +5 -0
  38. package/dist/arena/lenses/architecture.js +22 -0
  39. package/dist/arena/lenses/engineering.d.ts +5 -0
  40. package/dist/arena/lenses/engineering.js +22 -0
  41. package/dist/arena/lenses/general.d.ts +5 -0
  42. package/dist/arena/lenses/general.js +20 -0
  43. package/dist/arena/lenses/index.d.ts +16 -0
  44. package/dist/arena/lenses/index.js +47 -0
  45. package/dist/arena/lenses/product.d.ts +5 -0
  46. package/dist/arena/lenses/product.js +22 -0
  47. package/dist/arena/model-presets.d.ts +23 -0
  48. package/dist/arena/model-presets.js +44 -0
  49. package/dist/arena/phases/adjudication.d.ts +24 -0
  50. package/dist/arena/phases/adjudication.js +144 -0
  51. package/dist/arena/phases/build-consensus.d.ts +29 -0
  52. package/dist/arena/phases/build-consensus.js +86 -0
  53. package/dist/arena/phases/claim-registry.d.ts +26 -0
  54. package/dist/arena/phases/claim-registry.js +60 -0
  55. package/dist/arena/phases/cross-review.d.ts +45 -0
  56. package/dist/arena/phases/cross-review.js +224 -0
  57. package/dist/arena/phases/debate-rounds.d.ts +27 -0
  58. package/dist/arena/phases/debate-rounds.js +162 -0
  59. package/dist/arena/phases/participant-research.d.ts +38 -0
  60. package/dist/arena/phases/participant-research.js +317 -0
  61. package/dist/arena/phases/planning-detail-expansion.d.ts +38 -0
  62. package/dist/arena/phases/planning-detail-expansion.js +121 -0
  63. package/dist/arena/planner.d.ts +27 -0
  64. package/dist/arena/planner.js +312 -0
  65. package/dist/arena/providers/docs.d.ts +6 -0
  66. package/dist/arena/providers/docs.js +108 -0
  67. package/dist/arena/providers/git.d.ts +8 -0
  68. package/dist/arena/providers/git.js +174 -0
  69. package/dist/arena/providers/index.d.ts +32 -0
  70. package/dist/arena/providers/index.js +132 -0
  71. package/dist/arena/providers/none.d.ts +7 -0
  72. package/dist/arena/providers/none.js +11 -0
  73. package/dist/arena/providers/repo.d.ts +6 -0
  74. package/dist/arena/providers/repo.js +255 -0
  75. package/dist/arena/render/session.d.ts +17 -0
  76. package/dist/arena/render/session.js +190 -0
  77. package/dist/arena/render/terminal.d.ts +34 -0
  78. package/dist/arena/render/terminal.js +286 -0
  79. package/dist/arena/strategies/discussion.d.ts +25 -0
  80. package/dist/arena/strategies/discussion.js +143 -0
  81. package/dist/arena/strategies/index.d.ts +15 -0
  82. package/dist/arena/strategies/index.js +28 -0
  83. package/dist/arena/strategies/language-wrapper.d.ts +17 -0
  84. package/dist/arena/strategies/language-wrapper.js +102 -0
  85. package/dist/arena/strategies/lens-wrapper.d.ts +16 -0
  86. package/dist/arena/strategies/lens-wrapper.js +236 -0
  87. package/dist/arena/strategies/planning.d.ts +30 -0
  88. package/dist/arena/strategies/planning.js +225 -0
  89. package/dist/arena/strategies/review.d.ts +26 -0
  90. package/dist/arena/strategies/review.js +168 -0
  91. package/dist/arena/strategies/utils.d.ts +39 -0
  92. package/dist/arena/strategies/utils.js +647 -0
  93. package/dist/arena/tools/selector.d.ts +17 -0
  94. package/dist/arena/tools/selector.js +61 -0
  95. package/dist/arena/transitions.d.ts +48 -0
  96. package/dist/arena/transitions.js +92 -0
  97. package/dist/arena/types.d.ts +508 -0
  98. package/dist/arena/types.js +27 -0
  99. package/dist/cli/agent-server-stdio.d.ts +32 -0
  100. package/dist/cli/agent-server-stdio.js +116 -0
  101. package/dist/colorizer.d.ts +23 -0
  102. package/dist/colorizer.js +12 -0
  103. package/dist/context/compaction.d.ts +116 -0
  104. package/dist/context/compaction.js +534 -0
  105. package/dist/context/manager.d.ts +145 -0
  106. package/dist/context/manager.js +423 -0
  107. package/dist/context/token-counter.d.ts +27 -0
  108. package/dist/context/token-counter.js +91 -0
  109. package/dist/context/tool-result-storage.d.ts +96 -0
  110. package/dist/context/tool-result-storage.js +296 -0
  111. package/dist/cost-tracker.d.ts +64 -0
  112. package/dist/cost-tracker.js +314 -0
  113. package/dist/cron/scheduler.d.ts +34 -0
  114. package/dist/cron/scheduler.js +115 -0
  115. package/dist/data/deepseek-models.json +46 -0
  116. package/dist/data/gemini-models.json +67 -0
  117. package/dist/data/openai-models.json +175 -0
  118. package/dist/data/openrouter-models.d.ts +35 -0
  119. package/dist/data/openrouter-models.js +36 -0
  120. package/dist/data/openrouter-models.json +4626 -0
  121. package/dist/data/openrouter-sync.d.ts +16 -0
  122. package/dist/data/openrouter-sync.js +53 -0
  123. package/dist/data/static-catalogs.d.ts +25 -0
  124. package/dist/data/static-catalogs.js +26 -0
  125. package/dist/data/zai-models.json +56 -0
  126. package/dist/engine/cost-store.d.ts +22 -0
  127. package/dist/engine/cost-store.js +15 -0
  128. package/dist/engine/engine.d.ts +295 -0
  129. package/dist/engine/engine.js +1467 -0
  130. package/dist/engine/model-facade.d.ts +24 -0
  131. package/dist/engine/model-facade.js +194 -0
  132. package/dist/engine/parse-task.d.ts +59 -0
  133. package/dist/engine/parse-task.js +135 -0
  134. package/dist/engine/patch-orphaned-tools.d.ts +52 -0
  135. package/dist/engine/patch-orphaned-tools.js +95 -0
  136. package/dist/engine/query.d.ts +50 -0
  137. package/dist/engine/query.js +107 -0
  138. package/dist/engine/runtime.d.ts +50 -0
  139. package/dist/engine/runtime.js +59 -0
  140. package/dist/engine/streaming-tool-queue.d.ts +34 -0
  141. package/dist/engine/streaming-tool-queue.js +61 -0
  142. package/dist/engine/token-budget.d.ts +22 -0
  143. package/dist/engine/token-budget.js +40 -0
  144. package/dist/engine/tool-summary.d.ts +13 -0
  145. package/dist/engine/tool-summary.js +35 -0
  146. package/dist/engine/turn-loop.d.ts +122 -0
  147. package/dist/engine/turn-loop.js +602 -0
  148. package/dist/engine/turn-state.d.ts +20 -0
  149. package/dist/engine/turn-state.js +17 -0
  150. package/dist/exceptions.d.ts +61 -0
  151. package/dist/exceptions.js +116 -0
  152. package/dist/git/utils.d.ts +35 -0
  153. package/dist/git/utils.js +98 -0
  154. package/dist/git/worktree.d.ts +36 -0
  155. package/dist/git/worktree.js +139 -0
  156. package/dist/hooks/events.d.ts +114 -0
  157. package/dist/hooks/events.js +4 -0
  158. package/dist/hooks/inject.d.ts +19 -0
  159. package/dist/hooks/inject.js +29 -0
  160. package/dist/hooks/registry.d.ts +15 -0
  161. package/dist/hooks/registry.js +84 -0
  162. package/dist/hooks/shell-runner.d.ts +45 -0
  163. package/dist/hooks/shell-runner.js +173 -0
  164. package/dist/index.d.ts +117 -0
  165. package/dist/index.js +145 -0
  166. package/dist/llm/api-key-sanitize.d.ts +22 -0
  167. package/dist/llm/api-key-sanitize.js +81 -0
  168. package/dist/llm/capabilities/index.d.ts +20 -0
  169. package/dist/llm/capabilities/index.js +39 -0
  170. package/dist/llm/capabilities/rules.d.ts +26 -0
  171. package/dist/llm/capabilities/rules.js +215 -0
  172. package/dist/llm/capabilities/types.d.ts +114 -0
  173. package/dist/llm/capabilities/types.js +24 -0
  174. package/dist/llm/client-base.d.ts +29 -0
  175. package/dist/llm/client-base.js +137 -0
  176. package/dist/llm/client-factory.d.ts +10 -0
  177. package/dist/llm/client-factory.js +29 -0
  178. package/dist/llm/model-cache.d.ts +21 -0
  179. package/dist/llm/model-cache.js +46 -0
  180. package/dist/llm/model-fetcher.d.ts +29 -0
  181. package/dist/llm/model-fetcher.js +282 -0
  182. package/dist/llm/model-pool.d.ts +95 -0
  183. package/dist/llm/model-pool.js +246 -0
  184. package/dist/llm/provider-catalog.d.ts +32 -0
  185. package/dist/llm/provider-catalog.js +50 -0
  186. package/dist/llm/provider-kinds.d.ts +21 -0
  187. package/dist/llm/provider-kinds.js +132 -0
  188. package/dist/llm/providers/anthropic.d.ts +19 -0
  189. package/dist/llm/providers/anthropic.js +267 -0
  190. package/dist/llm/providers/openai.d.ts +64 -0
  191. package/dist/llm/providers/openai.js +606 -0
  192. package/dist/llm/retry.d.ts +12 -0
  193. package/dist/llm/retry.js +17 -0
  194. package/dist/llm/stream-watchdog.d.ts +42 -0
  195. package/dist/llm/stream-watchdog.js +70 -0
  196. package/dist/llm/token-counter.d.ts +17 -0
  197. package/dist/llm/token-counter.js +36 -0
  198. package/dist/llm/types.d.ts +37 -0
  199. package/dist/llm/types.js +4 -0
  200. package/dist/logging/logger.d.ts +109 -0
  201. package/dist/logging/logger.js +390 -0
  202. package/dist/logging/sanitize-messages.d.ts +42 -0
  203. package/dist/logging/sanitize-messages.js +150 -0
  204. package/dist/logging/session-recorder.d.ts +90 -0
  205. package/dist/logging/session-recorder.js +304 -0
  206. package/dist/lsp/client.d.ts +41 -0
  207. package/dist/lsp/client.js +166 -0
  208. package/dist/lsp/manager.d.ts +40 -0
  209. package/dist/lsp/manager.js +124 -0
  210. package/dist/lsp/servers.d.ts +16 -0
  211. package/dist/lsp/servers.js +60 -0
  212. package/dist/migrate-models.d.ts +45 -0
  213. package/dist/migrate-models.js +179 -0
  214. package/dist/onboarding.d.ts +189 -0
  215. package/dist/onboarding.js +625 -0
  216. package/dist/plugins/gitOps.d.ts +32 -0
  217. package/dist/plugins/gitOps.js +62 -0
  218. package/dist/plugins/installedPlugins.d.ts +15 -0
  219. package/dist/plugins/installedPlugins.js +56 -0
  220. package/dist/plugins/knownMarketplaces.d.ts +10 -0
  221. package/dist/plugins/knownMarketplaces.js +46 -0
  222. package/dist/plugins/loadPluginHooks.d.ts +52 -0
  223. package/dist/plugins/loadPluginHooks.js +162 -0
  224. package/dist/plugins/marketplaceManager.d.ts +49 -0
  225. package/dist/plugins/marketplaceManager.js +142 -0
  226. package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
  227. package/dist/plugins/parseMarketplaceInput.js +73 -0
  228. package/dist/plugins/pluginCommandHook.d.ts +54 -0
  229. package/dist/plugins/pluginCommandHook.js +180 -0
  230. package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
  231. package/dist/plugins/pluginCommandsLoader.js +114 -0
  232. package/dist/plugins/pluginInstaller.d.ts +36 -0
  233. package/dist/plugins/pluginInstaller.js +228 -0
  234. package/dist/plugins/schemas.d.ts +9 -0
  235. package/dist/plugins/schemas.js +136 -0
  236. package/dist/plugins/types.d.ts +75 -0
  237. package/dist/plugins/types.js +7 -0
  238. package/dist/plugins/varRewrite.d.ts +42 -0
  239. package/dist/plugins/varRewrite.js +129 -0
  240. package/dist/preset/index.d.ts +60 -0
  241. package/dist/preset/index.js +159 -0
  242. package/dist/product/define.d.ts +60 -0
  243. package/dist/product/define.js +123 -0
  244. package/dist/product/index.d.ts +5 -0
  245. package/dist/product/index.js +4 -0
  246. package/dist/product/types.d.ts +111 -0
  247. package/dist/product/types.js +21 -0
  248. package/dist/prompt/composer.d.ts +49 -0
  249. package/dist/prompt/composer.js +168 -0
  250. package/dist/prompt/instruction-scanner.d.ts +54 -0
  251. package/dist/prompt/instruction-scanner.js +187 -0
  252. package/dist/prompt/section-cache.d.ts +15 -0
  253. package/dist/prompt/section-cache.js +31 -0
  254. package/dist/prompt/section-loader.d.ts +26 -0
  255. package/dist/prompt/section-loader.js +53 -0
  256. package/dist/prompt/sections/base.md +35 -0
  257. package/dist/prompt/sections/coding.md +31 -0
  258. package/dist/prompt/sections/orchestration.md +16 -0
  259. package/dist/prompt/sections/tone.md +9 -0
  260. package/dist/protocol/chat-session-manager.d.ts +33 -0
  261. package/dist/protocol/chat-session-manager.js +65 -0
  262. package/dist/protocol/chat-session.d.ts +49 -0
  263. package/dist/protocol/chat-session.js +86 -0
  264. package/dist/protocol/client.d.ts +104 -0
  265. package/dist/protocol/client.js +260 -0
  266. package/dist/protocol/factories.d.ts +93 -0
  267. package/dist/protocol/factories.js +64 -0
  268. package/dist/protocol/helpers.d.ts +42 -0
  269. package/dist/protocol/helpers.js +55 -0
  270. package/dist/protocol/index.d.ts +4 -0
  271. package/dist/protocol/index.js +4 -0
  272. package/dist/protocol/server.d.ts +80 -0
  273. package/dist/protocol/server.js +855 -0
  274. package/dist/protocol/transport.d.ts +37 -0
  275. package/dist/protocol/transport.js +85 -0
  276. package/dist/protocol/types.d.ts +221 -0
  277. package/dist/protocol/types.js +61 -0
  278. package/dist/remote/bridge.d.ts +37 -0
  279. package/dist/remote/bridge.js +103 -0
  280. package/dist/run/ArtifactTracker.d.ts +38 -0
  281. package/dist/run/ArtifactTracker.js +132 -0
  282. package/dist/run/CheckpointWriter.d.ts +53 -0
  283. package/dist/run/CheckpointWriter.js +135 -0
  284. package/dist/run/EngineRunner.d.ts +72 -0
  285. package/dist/run/EngineRunner.js +119 -0
  286. package/dist/run/Evaluator.d.ts +56 -0
  287. package/dist/run/Evaluator.js +62 -0
  288. package/dist/run/FileRunStore.d.ts +39 -0
  289. package/dist/run/FileRunStore.js +176 -0
  290. package/dist/run/Heartbeat.d.ts +54 -0
  291. package/dist/run/Heartbeat.js +123 -0
  292. package/dist/run/RunApprovalBackend.d.ts +53 -0
  293. package/dist/run/RunApprovalBackend.js +85 -0
  294. package/dist/run/RunLock.d.ts +40 -0
  295. package/dist/run/RunLock.js +98 -0
  296. package/dist/run/RunManager.d.ts +82 -0
  297. package/dist/run/RunManager.js +587 -0
  298. package/dist/run/RunQueue.d.ts +27 -0
  299. package/dist/run/RunQueue.js +73 -0
  300. package/dist/run/RunStore.d.ts +25 -0
  301. package/dist/run/RunStore.js +9 -0
  302. package/dist/run/factory.d.ts +63 -0
  303. package/dist/run/factory.js +51 -0
  304. package/dist/run/index.d.ts +17 -0
  305. package/dist/run/index.js +23 -0
  306. package/dist/run/types.d.ts +136 -0
  307. package/dist/run/types.js +28 -0
  308. package/dist/runtime/safe-spawn.d.ts +118 -0
  309. package/dist/runtime/safe-spawn.js +268 -0
  310. package/dist/services/analytics.d.ts +29 -0
  311. package/dist/services/analytics.js +84 -0
  312. package/dist/services/auto-dream.d.ts +49 -0
  313. package/dist/services/auto-dream.js +124 -0
  314. package/dist/services/diagnostics.d.ts +41 -0
  315. package/dist/services/diagnostics.js +99 -0
  316. package/dist/services/extract-memories.d.ts +31 -0
  317. package/dist/services/extract-memories.js +71 -0
  318. package/dist/services/index.d.ts +11 -0
  319. package/dist/services/index.js +17 -0
  320. package/dist/services/memory-orchestrator.d.ts +60 -0
  321. package/dist/services/memory-orchestrator.js +133 -0
  322. package/dist/services/notifier.d.ts +25 -0
  323. package/dist/services/notifier.js +69 -0
  324. package/dist/services/oauth.d.ts +27 -0
  325. package/dist/services/oauth.js +153 -0
  326. package/dist/services/session-memory.d.ts +37 -0
  327. package/dist/services/session-memory.js +81 -0
  328. package/dist/session/file-history.d.ts +41 -0
  329. package/dist/session/file-history.js +115 -0
  330. package/dist/session/memory.d.ts +104 -0
  331. package/dist/session/memory.js +310 -0
  332. package/dist/session/session-manager.d.ts +41 -0
  333. package/dist/session/session-manager.js +261 -0
  334. package/dist/session/transcript.d.ts +39 -0
  335. package/dist/session/transcript.js +186 -0
  336. package/dist/settings/manager.d.ts +36 -0
  337. package/dist/settings/manager.js +187 -0
  338. package/dist/settings/schema.d.ts +1219 -0
  339. package/dist/settings/schema.js +246 -0
  340. package/dist/skills/frontmatter.d.ts +13 -0
  341. package/dist/skills/frontmatter.js +70 -0
  342. package/dist/skills/index.d.ts +7 -0
  343. package/dist/skills/index.js +6 -0
  344. package/dist/skills/scanner.d.ts +31 -0
  345. package/dist/skills/scanner.js +171 -0
  346. package/dist/state.d.ts +160 -0
  347. package/dist/state.js +267 -0
  348. package/dist/tool-system/builtin/agent-notifications.d.ts +84 -0
  349. package/dist/tool-system/builtin/agent-notifications.js +197 -0
  350. package/dist/tool-system/builtin/agent-registry.d.ts +67 -0
  351. package/dist/tool-system/builtin/agent-registry.js +126 -0
  352. package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
  353. package/dist/tool-system/builtin/agent-transcript-translator.js +160 -0
  354. package/dist/tool-system/builtin/agent.d.ts +16 -0
  355. package/dist/tool-system/builtin/agent.js +364 -0
  356. package/dist/tool-system/builtin/apply-patch/applier.d.ts +26 -0
  357. package/dist/tool-system/builtin/apply-patch/applier.js +250 -0
  358. package/dist/tool-system/builtin/apply-patch/index.d.ts +20 -0
  359. package/dist/tool-system/builtin/apply-patch/index.js +101 -0
  360. package/dist/tool-system/builtin/apply-patch/parser.d.ts +17 -0
  361. package/dist/tool-system/builtin/apply-patch/parser.js +212 -0
  362. package/dist/tool-system/builtin/apply-patch/seek-sequence.d.ts +18 -0
  363. package/dist/tool-system/builtin/apply-patch/seek-sequence.js +123 -0
  364. package/dist/tool-system/builtin/apply-patch/types.d.ts +49 -0
  365. package/dist/tool-system/builtin/apply-patch/types.js +13 -0
  366. package/dist/tool-system/builtin/arena.d.ts +31 -0
  367. package/dist/tool-system/builtin/arena.js +416 -0
  368. package/dist/tool-system/builtin/ask-user.d.ts +20 -0
  369. package/dist/tool-system/builtin/ask-user.js +99 -0
  370. package/dist/tool-system/builtin/bash.d.ts +21 -0
  371. package/dist/tool-system/builtin/bash.js +149 -0
  372. package/dist/tool-system/builtin/brief.d.ts +6 -0
  373. package/dist/tool-system/builtin/brief.js +41 -0
  374. package/dist/tool-system/builtin/config.d.ts +6 -0
  375. package/dist/tool-system/builtin/config.js +67 -0
  376. package/dist/tool-system/builtin/cron.d.ts +10 -0
  377. package/dist/tool-system/builtin/cron.js +61 -0
  378. package/dist/tool-system/builtin/edit.d.ts +6 -0
  379. package/dist/tool-system/builtin/edit.js +100 -0
  380. package/dist/tool-system/builtin/file-cache.d.ts +11 -0
  381. package/dist/tool-system/builtin/file-cache.js +39 -0
  382. package/dist/tool-system/builtin/glob.d.ts +7 -0
  383. package/dist/tool-system/builtin/glob.js +76 -0
  384. package/dist/tool-system/builtin/grep.d.ts +7 -0
  385. package/dist/tool-system/builtin/grep.js +150 -0
  386. package/dist/tool-system/builtin/index.d.ts +20 -0
  387. package/dist/tool-system/builtin/index.js +449 -0
  388. package/dist/tool-system/builtin/lsp.d.ts +6 -0
  389. package/dist/tool-system/builtin/lsp.js +141 -0
  390. package/dist/tool-system/builtin/mcp-tools.d.ts +10 -0
  391. package/dist/tool-system/builtin/mcp-tools.js +102 -0
  392. package/dist/tool-system/builtin/memory.d.ts +26 -0
  393. package/dist/tool-system/builtin/memory.js +226 -0
  394. package/dist/tool-system/builtin/notebook-edit.d.ts +6 -0
  395. package/dist/tool-system/builtin/notebook-edit.js +124 -0
  396. package/dist/tool-system/builtin/plan.d.ts +15 -0
  397. package/dist/tool-system/builtin/plan.js +55 -0
  398. package/dist/tool-system/builtin/powershell.d.ts +11 -0
  399. package/dist/tool-system/builtin/powershell.js +64 -0
  400. package/dist/tool-system/builtin/read.d.ts +6 -0
  401. package/dist/tool-system/builtin/read.js +70 -0
  402. package/dist/tool-system/builtin/remote-trigger.d.ts +6 -0
  403. package/dist/tool-system/builtin/remote-trigger.js +54 -0
  404. package/dist/tool-system/builtin/repl.d.ts +11 -0
  405. package/dist/tool-system/builtin/repl.js +82 -0
  406. package/dist/tool-system/builtin/send-message.d.ts +6 -0
  407. package/dist/tool-system/builtin/send-message.js +47 -0
  408. package/dist/tool-system/builtin/skill-prompt.d.ts +20 -0
  409. package/dist/tool-system/builtin/skill-prompt.js +52 -0
  410. package/dist/tool-system/builtin/skill.d.ts +9 -0
  411. package/dist/tool-system/builtin/skill.js +55 -0
  412. package/dist/tool-system/builtin/sleep.d.ts +6 -0
  413. package/dist/tool-system/builtin/sleep.js +33 -0
  414. package/dist/tool-system/builtin/task.d.ts +48 -0
  415. package/dist/tool-system/builtin/task.js +139 -0
  416. package/dist/tool-system/builtin/tool-search.d.ts +11 -0
  417. package/dist/tool-system/builtin/tool-search.js +90 -0
  418. package/dist/tool-system/builtin/web-fetch.d.ts +10 -0
  419. package/dist/tool-system/builtin/web-fetch.js +328 -0
  420. package/dist/tool-system/builtin/web-search.d.ts +25 -0
  421. package/dist/tool-system/builtin/web-search.js +174 -0
  422. package/dist/tool-system/builtin/worktree.d.ts +10 -0
  423. package/dist/tool-system/builtin/worktree.js +95 -0
  424. package/dist/tool-system/builtin/write.d.ts +6 -0
  425. package/dist/tool-system/builtin/write.js +36 -0
  426. package/dist/tool-system/context.d.ts +165 -0
  427. package/dist/tool-system/context.js +18 -0
  428. package/dist/tool-system/executor.d.ts +49 -0
  429. package/dist/tool-system/executor.js +495 -0
  430. package/dist/tool-system/investigation-guard.d.ts +46 -0
  431. package/dist/tool-system/investigation-guard.js +155 -0
  432. package/dist/tool-system/mcp-manager.d.ts +67 -0
  433. package/dist/tool-system/mcp-manager.js +249 -0
  434. package/dist/tool-system/permission.d.ts +111 -0
  435. package/dist/tool-system/permission.js +774 -0
  436. package/dist/tool-system/registry.d.ts +32 -0
  437. package/dist/tool-system/registry.js +131 -0
  438. package/dist/tool-system/sandbox/bwrap.d.ts +13 -0
  439. package/dist/tool-system/sandbox/bwrap.js +63 -0
  440. package/dist/tool-system/sandbox/index.d.ts +78 -0
  441. package/dist/tool-system/sandbox/index.js +222 -0
  442. package/dist/tool-system/sandbox/off.d.ts +2 -0
  443. package/dist/tool-system/sandbox/off.js +8 -0
  444. package/dist/tool-system/sandbox/seatbelt.d.ts +18 -0
  445. package/dist/tool-system/sandbox/seatbelt.js +109 -0
  446. package/dist/tool-system/task-guard.d.ts +29 -0
  447. package/dist/tool-system/task-guard.js +70 -0
  448. package/dist/tool-system/validation.d.ts +5 -0
  449. package/dist/tool-system/validation.js +39 -0
  450. package/dist/types.d.ts +376 -0
  451. package/dist/types.js +4 -0
  452. package/dist/updater.d.ts +65 -0
  453. package/dist/updater.js +405 -0
  454. package/dist/utils/debug.d.ts +41 -0
  455. package/dist/utils/debug.js +110 -0
  456. package/dist/utils/earlyInput.d.ts +43 -0
  457. package/dist/utils/earlyInput.js +166 -0
  458. package/dist/utils/env.d.ts +24 -0
  459. package/dist/utils/env.js +104 -0
  460. package/dist/utils/envUtils.d.ts +51 -0
  461. package/dist/utils/envUtils.js +135 -0
  462. package/dist/utils/execFileNoThrow.d.ts +39 -0
  463. package/dist/utils/execFileNoThrow.js +74 -0
  464. package/dist/utils/format.d.ts +44 -0
  465. package/dist/utils/format.js +236 -0
  466. package/dist/utils/intl.d.ts +22 -0
  467. package/dist/utils/intl.js +83 -0
  468. package/dist/utils/lockfile.d.ts +15 -0
  469. package/dist/utils/lockfile.js +30 -0
  470. package/dist/utils/memoize.d.ts +19 -0
  471. package/dist/utils/memoize.js +14 -0
  472. package/dist/utils/semver.d.ts +6 -0
  473. package/dist/utils/semver.js +13 -0
  474. package/dist/utils/sliceAnsi.d.ts +12 -0
  475. package/dist/utils/sliceAnsi.js +62 -0
  476. package/dist/utils/systemTheme.d.ts +40 -0
  477. package/dist/utils/systemTheme.js +108 -0
  478. package/dist/utils/task-sanitizer.d.ts +6 -0
  479. package/dist/utils/task-sanitizer.js +24 -0
  480. package/dist/utils/theme.d.ts +93 -0
  481. package/dist/utils/theme.js +544 -0
  482. package/dist/utils/toolDisplay.d.ts +31 -0
  483. package/dist/utils/toolDisplay.js +188 -0
  484. package/package.json +72 -0
@@ -0,0 +1,606 @@
1
+ /**
2
+ * OpenAI GPT provider using openai SDK.
3
+ *
4
+ * Serves every OpenAI-compatible endpoint we support (OpenAI, DeepSeek,
5
+ * OpenRouter, Z.AI, xAI, Mistral, Groq, Gemini-OpenAI-compat, Ollama,
6
+ * custom). Per-(provider, model) request-shape divergence — token-limit
7
+ * field name, rejected sampling params, thinking knob, reasoning echo —
8
+ * is resolved through `capabilitiesFor()`, not hardcoded.
9
+ */
10
+ import OpenAI from "openai";
11
+ import { LLMClientBase } from "../client-base.js";
12
+ import { ContextLimitError, LLMError, LLMRateLimitError } from "../../exceptions.js";
13
+ import { logger } from "../../logging/logger.js";
14
+ import { countTokens } from "../token-counter.js";
15
+ import { capabilitiesFor } from "../capabilities/index.js";
16
+ import { STREAM_WATCHDOG_CONFIG, StreamIdleTimeoutError, } from "../stream-watchdog.js";
17
+ /**
18
+ * Consume an async iterable of stream chunks with an idle watchdog.
19
+ * Returns the accumulated text — either from onChunk return values, or
20
+ * by extracting choices[0].delta.content from raw chunks (useful in tests
21
+ * that call runStreamWithWatchdog directly without an onChunk handler).
22
+ *
23
+ * The watchdog is active whenever opts.idleTimeoutMs is explicitly provided,
24
+ * or when STREAM_WATCHDOG_CONFIG.enabled is true. When active, each call to
25
+ * iterator.next() races against an idle deadline; if the deadline fires first
26
+ * the function rejects with StreamIdleTimeoutError.
27
+ */
28
+ export async function runStreamWithWatchdog(stream, opts = {}) {
29
+ const watchdogActive = STREAM_WATCHDOG_CONFIG.enabled || opts.idleTimeoutMs !== undefined;
30
+ const idleTimeoutMs = opts.idleTimeoutMs ?? STREAM_WATCHDOG_CONFIG.idleTimeoutMs;
31
+ let text = "";
32
+ // Fast path: watchdog disabled AND caller did not override → no overhead.
33
+ if (!watchdogActive) {
34
+ for await (const chunk of stream) {
35
+ if (opts.onChunk) {
36
+ text += opts.onChunk(chunk) ?? "";
37
+ }
38
+ else {
39
+ text += chunk?.choices?.[0]?.delta?.content ?? "";
40
+ }
41
+ }
42
+ return text;
43
+ }
44
+ // Watchdog path: race each iterator.next() against a per-chunk idle timer.
45
+ // This ensures the for-await cannot block forever between chunks.
46
+ const iterator = stream[Symbol.asyncIterator]();
47
+ try {
48
+ while (true) {
49
+ const nextPromise = iterator.next();
50
+ // Build a timeout promise that rejects if no chunk arrives in time.
51
+ let timeoutHandle = null;
52
+ const timeoutPromise = new Promise((_, reject) => {
53
+ timeoutHandle = setTimeout(() => {
54
+ reject(new StreamIdleTimeoutError(idleTimeoutMs, opts.requestId));
55
+ }, idleTimeoutMs);
56
+ });
57
+ let result;
58
+ try {
59
+ result = await Promise.race([nextPromise, timeoutPromise]);
60
+ }
61
+ finally {
62
+ if (timeoutHandle !== null)
63
+ clearTimeout(timeoutHandle);
64
+ }
65
+ if (result.done)
66
+ break;
67
+ const chunk = result.value;
68
+ if (opts.onChunk) {
69
+ text += opts.onChunk(chunk) ?? "";
70
+ }
71
+ else {
72
+ text += chunk?.choices?.[0]?.delta?.content ?? "";
73
+ }
74
+ }
75
+ }
76
+ finally {
77
+ // Best-effort cleanup: signal iterator to stop. Do NOT await — if the
78
+ // generator is stuck (e.g., wedged HTTP stream), awaiting return() would
79
+ // itself hang. Fire-and-forget is intentional here.
80
+ iterator.return?.();
81
+ }
82
+ return text;
83
+ }
84
+ export class OpenAIClient extends LLMClientBase {
85
+ _client = null;
86
+ // Sticky override: once the endpoint tells us `max_tokens` is rejected for
87
+ // this model, switch to `max_completion_tokens` for the lifetime of the
88
+ // client. Cheaper and more reliable than re-deriving from the model id when
89
+ // a new variant ships before our regex knows about it.
90
+ _forceMaxCompletionTokens = false;
91
+ constructor(config) {
92
+ super(config);
93
+ }
94
+ initClient() {
95
+ // Lazy init — client created on first use
96
+ }
97
+ get client() {
98
+ if (!this._client) {
99
+ this._client = new OpenAI({
100
+ apiKey: this.config.apiKey ?? process.env.OPENAI_API_KEY,
101
+ ...(this.config.baseUrl ? { baseURL: this.config.baseUrl } : {}),
102
+ timeout: this.timeout,
103
+ });
104
+ }
105
+ return this._client;
106
+ }
107
+ /**
108
+ * Resolve the capability descriptor for the current (provider kind, model)
109
+ * pair. Cached for the lifetime of the client — model never changes
110
+ * mid-client. `providerKind` defaults to "openai" because legacy LLMConfig
111
+ * paths (arena, env-derived) don't always populate it; for those, the
112
+ * built-in rules table treats them as plain OpenAI.
113
+ */
114
+ _capability = null;
115
+ get capability() {
116
+ if (!this._capability) {
117
+ const kind = (this.config.providerKind ?? "openai");
118
+ this._capability = capabilitiesFor(kind, this.model);
119
+ }
120
+ return this._capability;
121
+ }
122
+ async createMessage(options) {
123
+ return this.withRetry(async () => {
124
+ // Per-call thinking wins; otherwise fall back to provider default
125
+ // (settings.providers[].thinking, threaded through LLMConfig).
126
+ const thinking = options.thinking ?? this.config.thinking;
127
+ const messages = this.buildMessages(options.systemPrompt, options.messages, thinking);
128
+ const tools = options.tools?.length ? this.convertTools(options.tools) : undefined;
129
+ const span = logger.span("llm.request", {
130
+ cat: "llm",
131
+ provider: this.provider,
132
+ model: this.model,
133
+ stream: !!(options.stream && options.onChunk),
134
+ messageCount: messages.length,
135
+ toolCount: tools?.length ?? 0,
136
+ });
137
+ try {
138
+ const response = options.stream && options.onChunk
139
+ ? await this.streamMessage(options, messages, tools, thinking)
140
+ : await this.nonStreamMessage(options, messages, tools, thinking);
141
+ span.end({
142
+ stopReason: response.stopReason,
143
+ promptTokens: response.usage?.promptTokens,
144
+ completionTokens: response.usage?.completionTokens,
145
+ cacheReadTokens: response.usage?.cacheReadTokens,
146
+ cacheCreationTokens: response.usage?.cacheCreationTokens,
147
+ });
148
+ return response;
149
+ }
150
+ catch (err) {
151
+ span.fail(err);
152
+ throw err;
153
+ }
154
+ });
155
+ }
156
+ /**
157
+ * Build the request body honoring the model's capability descriptor.
158
+ * Centralized so both streaming and non-streaming paths agree on the
159
+ * exact shape.
160
+ */
161
+ buildRequestBody(options, messages, tools, thinking, stream) {
162
+ const cap = this.capability;
163
+ const maxTokens = options.maxTokens ?? this.maxTokens;
164
+ // Token-limit field — capability picks `max_tokens` vs `max_completion_tokens`.
165
+ // Sticky fallback (set by handleApiError on a 400) overrides the rule for
166
+ // ids the regex hasn't learned about yet.
167
+ const useCompletion = this._forceMaxCompletionTokens || cap.tokenLimitField === "max_completion_tokens";
168
+ const tokenLimit = useCompletion
169
+ ? { max_completion_tokens: maxTokens }
170
+ : { max_tokens: maxTokens };
171
+ // Sampling params — only include if the model accepts them.
172
+ const sampling = {};
173
+ if (!cap.rejectedParams.has("temperature")) {
174
+ sampling.temperature =
175
+ options.temperature !== undefined ? options.temperature : this.temperature;
176
+ }
177
+ // Reasoning shape — different vendors, different fields, never combine.
178
+ // We treat `options.thinking` as the user's intent ("enabled"/"disabled")
179
+ // and translate to whichever wire shape the model expects.
180
+ const reasoning = {};
181
+ if (thinking) {
182
+ switch (cap.reasoning.kind) {
183
+ case "deepseek-thinking":
184
+ // DeepSeek V4, Z.AI GLM-4.5+ — top-level {thinking: {type}}.
185
+ reasoning.thinking = { type: thinking };
186
+ break;
187
+ case "openai-effort":
188
+ // OpenAI o-series, gpt-5+, Gemini OpenAI-compat, xAI grok-4.3,
189
+ // Mistral magistral, Groq reasoning models — `reasoning_effort`.
190
+ // "enabled" → "medium" (safe middle). "disabled" → the capability's
191
+ // `disabledEffort` value; defaults to "minimal" (OpenAI), but xAI
192
+ // uses "low" (no minimal) and Mistral uses "none" (only high|none).
193
+ reasoning.reasoning_effort =
194
+ thinking === "disabled"
195
+ ? (cap.reasoning.disabledEffort ?? "minimal")
196
+ : "medium";
197
+ break;
198
+ case "openrouter-reasoning":
199
+ // OpenRouter normalized shape — {reasoning: {effort, exclude}}.
200
+ reasoning.reasoning =
201
+ thinking === "disabled"
202
+ ? { effort: "minimal", exclude: true }
203
+ : { effort: "medium" };
204
+ break;
205
+ case "anthropic-budget":
206
+ case "anthropic-adaptive":
207
+ case "none":
208
+ // OpenAI client doesn't serve Anthropic-direct; nothing to do.
209
+ // `none` means the model doesn't expose a knob — skip silently.
210
+ break;
211
+ }
212
+ }
213
+ return {
214
+ model: this.model,
215
+ messages,
216
+ ...tokenLimit,
217
+ ...sampling,
218
+ ...reasoning,
219
+ ...(tools ? { tools } : {}),
220
+ ...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),
221
+ };
222
+ }
223
+ async nonStreamMessage(options, messages, tools, thinking) {
224
+ try {
225
+ const response = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, thinking, false), { signal: options.signal });
226
+ const choice = response.choices[0];
227
+ if (!choice)
228
+ throw new LLMError("No response from OpenAI", "openai");
229
+ const usage = {
230
+ promptTokens: response.usage?.prompt_tokens ?? 0,
231
+ completionTokens: response.usage?.completion_tokens ?? 0,
232
+ totalTokens: response.usage?.total_tokens ?? 0,
233
+ };
234
+ this.recordUsage(usage, options);
235
+ return this.processChoice(choice, usage);
236
+ }
237
+ catch (err) {
238
+ this.handleApiError(err);
239
+ throw err;
240
+ }
241
+ }
242
+ async streamMessage(options, messages, tools, thinking) {
243
+ try {
244
+ const stream = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, thinking, true), { signal: options.signal });
245
+ let text = "";
246
+ let reasoningContent = "";
247
+ const toolCallsMap = new Map();
248
+ let streamUsage;
249
+ // TTFT — first chunk that actually carried text. Tool-call-only chunks
250
+ // earlier in the stream don't count: the user-visible "text starts now"
251
+ // moment is what we want to compare across providers.
252
+ const streamStartedAt = Date.now();
253
+ let firstByteLogged = false;
254
+ const requestId = stream.request_id;
255
+ const handleChunk = (chunk) => {
256
+ // Capture usage from the final chunk
257
+ if (chunk.usage) {
258
+ streamUsage = chunk.usage;
259
+ }
260
+ const delta = chunk.choices[0]?.delta;
261
+ if (!delta)
262
+ return "";
263
+ // DeepSeek thinking-mode streams reasoning as a separate delta
264
+ // field. Accumulate it so we can echo it back next turn.
265
+ const reasoningDelta = delta.reasoning_content;
266
+ if (typeof reasoningDelta === "string" && reasoningDelta.length > 0) {
267
+ reasoningContent += reasoningDelta;
268
+ }
269
+ if (delta.content) {
270
+ if (!firstByteLogged) {
271
+ firstByteLogged = true;
272
+ logger.debug("llm.first_byte", {
273
+ cat: "llm",
274
+ provider: this.provider,
275
+ model: this.model,
276
+ ttft_ms: Date.now() - streamStartedAt,
277
+ });
278
+ }
279
+ text += delta.content;
280
+ options.onChunk?.({
281
+ type: "text",
282
+ text: delta.content,
283
+ tokens: countTokens(delta.content),
284
+ });
285
+ }
286
+ if (delta.tool_calls) {
287
+ for (const tc of delta.tool_calls) {
288
+ const idx = tc.index;
289
+ if (!toolCallsMap.has(idx)) {
290
+ toolCallsMap.set(idx, { id: tc.id ?? "", name: tc.function?.name ?? "", args: "" });
291
+ options.onChunk?.({
292
+ type: "tool_use_start",
293
+ toolCall: { id: tc.id, toolName: tc.function?.name },
294
+ });
295
+ }
296
+ const existing = toolCallsMap.get(idx);
297
+ if (tc.id)
298
+ existing.id = tc.id;
299
+ if (tc.function?.name)
300
+ existing.name = tc.function.name;
301
+ if (tc.function?.arguments) {
302
+ existing.args += tc.function.arguments;
303
+ // Best-effort partial-JSON parse: succeeds once a complete
304
+ // top-level object has streamed in (typical for short args)
305
+ // and silently waits otherwise.
306
+ if (existing.id) {
307
+ try {
308
+ const parsed = JSON.parse(existing.args || "{}");
309
+ options.onChunk?.({
310
+ type: "tool_use_delta",
311
+ toolCall: { id: existing.id, toolName: existing.name, args: parsed },
312
+ });
313
+ }
314
+ catch {
315
+ // partial JSON — wait for more deltas
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+ if (chunk.choices[0]?.finish_reason) {
322
+ options.onChunk?.({ type: "stop", stopReason: chunk.choices[0].finish_reason });
323
+ }
324
+ return delta.content ?? "";
325
+ };
326
+ await runStreamWithWatchdog(stream, {
327
+ idleTimeoutMs: STREAM_WATCHDOG_CONFIG.enabled
328
+ ? STREAM_WATCHDOG_CONFIG.idleTimeoutMs
329
+ : undefined,
330
+ requestId,
331
+ onChunk: handleChunk,
332
+ });
333
+ const toolCalls = [];
334
+ for (const [, tc] of toolCallsMap) {
335
+ let args = {};
336
+ try {
337
+ args = JSON.parse(tc.args || "{}");
338
+ }
339
+ catch { }
340
+ toolCalls.push({ id: tc.id, toolName: tc.name, args });
341
+ }
342
+ const usage = {
343
+ promptTokens: streamUsage?.prompt_tokens ?? 0,
344
+ completionTokens: streamUsage?.completion_tokens ?? 0,
345
+ totalTokens: streamUsage?.total_tokens ?? 0,
346
+ };
347
+ this.recordUsage(usage, options);
348
+ return {
349
+ text,
350
+ toolCalls,
351
+ usage,
352
+ stopReason: "stop",
353
+ ...(reasoningContent ? { reasoningContent } : {}),
354
+ };
355
+ }
356
+ catch (err) {
357
+ this.handleApiError(err);
358
+ throw err;
359
+ }
360
+ }
361
+ processChoice(choice, usage) {
362
+ const text = choice.message.content ?? "";
363
+ const toolCalls = [];
364
+ if (choice.message.tool_calls) {
365
+ for (const tc of choice.message.tool_calls) {
366
+ let args = {};
367
+ try {
368
+ args = JSON.parse(tc.function.arguments || "{}");
369
+ }
370
+ catch { }
371
+ toolCalls.push({
372
+ id: tc.id,
373
+ toolName: tc.function.name,
374
+ args,
375
+ });
376
+ }
377
+ }
378
+ const reasoningContent = extractReasoningContent(choice.message);
379
+ return {
380
+ text,
381
+ toolCalls,
382
+ usage,
383
+ stopReason: choice.finish_reason ?? undefined,
384
+ ...(reasoningContent ? { reasoningContent } : {}),
385
+ };
386
+ }
387
+ buildMessages(systemPrompt, messages, thinking) {
388
+ const result = [{ role: "system", content: systemPrompt }];
389
+ // Reasoning-content echo-back contract — driven by capability:
390
+ // "when-tools" : backfill an empty placeholder if the prior assistant
391
+ // turn doesn't carry one (DeepSeek V4 + tools 400s
392
+ // otherwise). Skip when thinking is explicitly disabled,
393
+ // because empty-string in that mode can degenerate into
394
+ // an empty reply on the *next* turn.
395
+ // "never" : drop any reasoning_content (deepseek-reasoner 400s
396
+ // if input messages contain it).
397
+ // "optional" : pass through when we have it, never synthesize.
398
+ const cap = this.capability;
399
+ const hasTools = messages.some((m) => Array.isArray(m.content) &&
400
+ m.content.some((b) => b.type === "tool_use" || b.type === "tool_result"));
401
+ const needsReasoningBackfill = thinking !== "disabled" &&
402
+ cap.echoReasoning === "when-tools" &&
403
+ hasTools;
404
+ const stripReasoning = cap.echoReasoning === "never";
405
+ for (const msg of messages) {
406
+ if (msg.role === "system")
407
+ continue;
408
+ if (msg.role === "assistant") {
409
+ const param = { role: "assistant" };
410
+ let reasoningContent;
411
+ if (typeof msg.content === "string") {
412
+ param.content = msg.content;
413
+ }
414
+ else {
415
+ const textParts = [];
416
+ const toolCalls = [];
417
+ for (const block of msg.content) {
418
+ if (block.type === "text" && block.text) {
419
+ textParts.push(block.text);
420
+ }
421
+ else if (block.type === "tool_use" && block.id && block.name) {
422
+ toolCalls.push({
423
+ id: block.id,
424
+ type: "function",
425
+ function: {
426
+ name: block.name,
427
+ arguments: JSON.stringify(block.input ?? {}),
428
+ },
429
+ });
430
+ }
431
+ else if (block.type === "reasoning" && block.reasoningContent) {
432
+ reasoningContent = block.reasoningContent;
433
+ }
434
+ // Reasoning may also ride alongside another block (e.g. on a
435
+ // text block) — pick it up wherever it appears so we never
436
+ // silently drop it.
437
+ if (!reasoningContent && block.reasoningContent) {
438
+ reasoningContent = block.reasoningContent;
439
+ }
440
+ }
441
+ if (textParts.length)
442
+ param.content = textParts.join("\n");
443
+ if (toolCalls.length)
444
+ param.tool_calls = toolCalls;
445
+ }
446
+ if (stripReasoning) {
447
+ // Some endpoints (deepseek-reasoner) 400 if reasoning_content
448
+ // appears in input history. Don't attach the field at all.
449
+ reasoningContent = undefined;
450
+ }
451
+ else if (!reasoningContent && needsReasoningBackfill) {
452
+ reasoningContent = "";
453
+ }
454
+ if (reasoningContent !== undefined) {
455
+ param.reasoning_content = reasoningContent;
456
+ }
457
+ // OpenAI rejects { role: "assistant" } with no content AND no
458
+ // tool_calls ("Invalid assistant message: content or tool_calls
459
+ // must be set"). This shape can arise from a transcript where
460
+ // the assistant produced only reasoning blocks (no text, no tool
461
+ // calls) before the stream was cut off, or from a synthetic
462
+ // message left behind by an interrupted retry. Drop these so
463
+ // the API call doesn't 400 on a structurally-empty turn.
464
+ // reasoning_content alone does NOT satisfy OpenAI's requirement —
465
+ // it's an extension field and not counted as "content".
466
+ if (param.content === undefined && param.tool_calls === undefined) {
467
+ continue;
468
+ }
469
+ result.push(param);
470
+ }
471
+ else if (msg.role === "tool") {
472
+ result.push({
473
+ role: "tool",
474
+ tool_call_id: msg.tool_call_id ?? "",
475
+ content: typeof msg.content === "string" ? msg.content : "",
476
+ });
477
+ }
478
+ else {
479
+ // user messages — may contain tool_result blocks mixed with text
480
+ // and image blocks (P2-6: image input).
481
+ if (typeof msg.content === "string") {
482
+ result.push({ role: "user", content: msg.content });
483
+ }
484
+ else {
485
+ // Separate tool_result blocks from text/image blocks. tool_result
486
+ // must become its own `role: "tool"` message (OpenAI's wire format
487
+ // doesn't allow tool_result inside a user content array); text and
488
+ // image stay together so the model sees "this text refers to this
489
+ // image" without an intervening tool turn.
490
+ const textParts = [];
491
+ const imageParts = [];
492
+ const toolResults = [];
493
+ for (const block of msg.content) {
494
+ if (block.type === "tool_result" && block.tool_use_id) {
495
+ toolResults.push({
496
+ tool_use_id: block.tool_use_id,
497
+ content: typeof block.content === "string" ? block.content : "",
498
+ });
499
+ }
500
+ else if (block.type === "text" && block.text) {
501
+ textParts.push(block.text);
502
+ }
503
+ else if (block.type === "image" && block.source) {
504
+ // OpenAI-compat image_url: every supported provider (OpenAI,
505
+ // OpenRouter, OpenAI-compatible proxies for Gemini/xAI/etc)
506
+ // accepts a base64 data URL as the URL. Per-(provider, model)
507
+ // vision-capability gating happens earlier in Engine.run —
508
+ // by the time we reach here, the model supports vision.
509
+ imageParts.push({
510
+ type: "image_url",
511
+ image_url: {
512
+ url: `data:${block.source.media_type};base64,${block.source.data}`,
513
+ },
514
+ });
515
+ }
516
+ }
517
+ // Emit tool_result blocks as separate tool messages
518
+ for (const tr of toolResults) {
519
+ result.push({
520
+ role: "tool",
521
+ tool_call_id: tr.tool_use_id,
522
+ content: tr.content,
523
+ });
524
+ }
525
+ // Emit the user-visible turn. With images present, content MUST be
526
+ // an array — putting a stringified version into `content: "..."`
527
+ // strips the images. Without images we keep the legacy string form
528
+ // so requests for vanilla text turns are byte-identical to before.
529
+ if (imageParts.length > 0) {
530
+ const parts = [];
531
+ if (textParts.length > 0) {
532
+ parts.push({ type: "text", text: textParts.join("\n") });
533
+ }
534
+ parts.push(...imageParts);
535
+ result.push({ role: "user", content: parts });
536
+ }
537
+ else if (textParts.length > 0) {
538
+ result.push({ role: "user", content: textParts.join("\n") });
539
+ }
540
+ }
541
+ }
542
+ }
543
+ return result;
544
+ }
545
+ convertTools(tools) {
546
+ return tools.map((t) => ({
547
+ type: "function",
548
+ function: {
549
+ name: t.name,
550
+ description: t.description,
551
+ parameters: t.inputSchema,
552
+ },
553
+ }));
554
+ }
555
+ handleApiError(err) {
556
+ if (err instanceof OpenAI.APIError) {
557
+ if (err.status === 429) {
558
+ throw new LLMRateLimitError("openai");
559
+ }
560
+ const msg = (err.message ?? "").toLowerCase();
561
+ // o-series / gpt-5+ reject `max_tokens` and demand
562
+ // `max_completion_tokens`. The id-based regex catches the common
563
+ // cases; this is the belt-and-suspenders path for ids that ship
564
+ // before the regex knows about them (e.g. new `gpt-5.x` variants
565
+ // routed via OpenAI-compatible proxies). Flip a sticky flag so the
566
+ // next request — including `withRetry`'s next attempt — sends the
567
+ // right field, instead of looping on the same 400.
568
+ if (err.status === 400 &&
569
+ msg.includes("max_tokens") &&
570
+ msg.includes("max_completion_tokens")) {
571
+ this._forceMaxCompletionTokens = true;
572
+ }
573
+ if (msg.includes("context_length_exceeded") ||
574
+ msg.includes("maximum context length") ||
575
+ msg.includes("too many tokens") ||
576
+ msg.includes("prompt is too long")) {
577
+ throw new ContextLimitError("openai");
578
+ }
579
+ // OpenRouter and other compatible endpoints often return 401 "Provider
580
+ // returned error" when the routed backend disables function calling for
581
+ // the selected model. A real auth failure is also possible — surface
582
+ // both possibilities so users don't blindly swap models.
583
+ if (err.status === 401 && msg.includes("provider returned error")) {
584
+ throw new LLMError(`OpenAI-compatible endpoint returned 401 "Provider returned error". ` +
585
+ `Two likely causes: (1) the API key is invalid/revoked, or ` +
586
+ `(2) the upstream provider rejects tool calls for this model ` +
587
+ `(common on OpenRouter's gpt-4o-mini route). Verify the key, ` +
588
+ `then try a tool-capable model such as openai/gpt-4.1-mini or ` +
589
+ `anthropic/claude-3.5-haiku.`, "openai", { status: err.status });
590
+ }
591
+ throw new LLMError(`OpenAI API error: ${err.message}`, "openai", { status: err.status });
592
+ }
593
+ throw err;
594
+ }
595
+ }
596
+ /**
597
+ * Pull a non-empty reasoning payload out of a provider message.
598
+ *
599
+ * DeepSeek thinking mode emits this on the assistant message; the
600
+ * spec requires that the same value be echoed back on the next
601
+ * request. Some routers also surface it under `reasoning`.
602
+ */
603
+ function extractReasoningContent(msg) {
604
+ const candidate = msg.reasoning_content ?? msg.reasoning;
605
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
606
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Retry classification — which errors trigger an automatic retry of the
3
+ * streaming LLM call?
4
+ *
5
+ * Stream-idle timeouts are retried — the upstream may have just recovered.
6
+ * User-initiated aborts (AbortError) are never retried — the user explicitly
7
+ * asked for the work to stop.
8
+ *
9
+ * Other transient errors (5xx, 429, ECONNRESET) are out of scope for this
10
+ * iteration; add them here as they become observed.
11
+ */
12
+ export declare function isRetryable(err: unknown): boolean;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Retry classification — which errors trigger an automatic retry of the
3
+ * streaming LLM call?
4
+ *
5
+ * Stream-idle timeouts are retried — the upstream may have just recovered.
6
+ * User-initiated aborts (AbortError) are never retried — the user explicitly
7
+ * asked for the work to stop.
8
+ *
9
+ * Other transient errors (5xx, 429, ECONNRESET) are out of scope for this
10
+ * iteration; add them here as they become observed.
11
+ */
12
+ import { StreamIdleTimeoutError } from "./stream-watchdog.js";
13
+ export function isRetryable(err) {
14
+ if (err instanceof StreamIdleTimeoutError)
15
+ return true;
16
+ return false;
17
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Stream Idle Watchdog — guarantees an upper bound on the time a streaming
3
+ * LLM call can spend with no bytes arriving. Without this, a wedged HTTP/2
4
+ * connection can hang the session indefinitely; the SDK's request timeout
5
+ * only covers the initial fetch, not the streaming body.
6
+ *
7
+ * Disabled by default. Enabled via env CODESHELL_ENABLE_STREAM_WATCHDOG=1.
8
+ */
9
+ export interface StreamWatchdogOptions {
10
+ /** Abort the stream when no chunks arrive within this window. */
11
+ idleTimeoutMs: number;
12
+ /** Called when the watchdog decides to abort. */
13
+ onTimeout: () => void;
14
+ /**
15
+ * Called at idleTimeoutMs/2 (or warningMs if provided). Logging only —
16
+ * the watchdog does not abort on warning. The argument is the configured
17
+ * warning threshold (NOT real-time elapsed) so callers can format the log
18
+ * without re-deriving it.
19
+ */
20
+ onWarning?: (warnThresholdMs: number) => void;
21
+ /** Override the warning trigger; defaults to idleTimeoutMs / 2. */
22
+ warningMs?: number;
23
+ }
24
+ export interface StreamWatchdog {
25
+ /** Re-arm both timers — call after every chunk. */
26
+ reset(): void;
27
+ /** Clear all timers permanently — call in finally. */
28
+ dispose(): void;
29
+ }
30
+ export declare class StreamIdleTimeoutError extends Error {
31
+ idleMs: number;
32
+ requestId?: string | undefined;
33
+ readonly kind = "stream-idle-timeout";
34
+ constructor(idleMs: number, requestId?: string | undefined);
35
+ }
36
+ export declare function createStreamWatchdog(opts: StreamWatchdogOptions): StreamWatchdog;
37
+ /** Environment-driven defaults. Read once at import time is fine — these never change inside a process. */
38
+ export declare const STREAM_WATCHDOG_CONFIG: {
39
+ enabled: boolean;
40
+ idleTimeoutMs: number;
41
+ retries: number;
42
+ };