@lamplitisles/dsh-nanocodex 0.1.0-beta.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 (276) hide show
  1. package/LICENSE +65 -0
  2. package/NOTICE +2 -0
  3. package/README.md +182 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/cordis.patch.yml +52 -0
  6. package/dist/index.d.ts +435 -0
  7. package/dist/index.js +3405 -0
  8. package/dist/standard-preset.d.ts +7 -0
  9. package/dist/standard-preset.js +25 -0
  10. package/engine-release.json +14 -0
  11. package/package.json +119 -0
  12. package/presets/standard/agent.cordis.yml +6 -0
  13. package/presets/standard/preset.yml +2 -0
  14. package/vendor/nanocodex/README.md +1344 -0
  15. package/vendor/nanocodex/actions/events.d.mts +10 -0
  16. package/vendor/nanocodex/actions/events.mjs +163 -0
  17. package/vendor/nanocodex/actions/index.d.mts +13 -0
  18. package/vendor/nanocodex/actions/index.mjs +35 -0
  19. package/vendor/nanocodex/actions/session.d.mts +66 -0
  20. package/vendor/nanocodex/actions/session.mjs +59 -0
  21. package/vendor/nanocodex/actions/turn.d.mts +59 -0
  22. package/vendor/nanocodex/actions/turn.mjs +37 -0
  23. package/vendor/nanocodex/actions/voice.d.mts +22 -0
  24. package/vendor/nanocodex/actions/voice.mjs +50 -0
  25. package/vendor/nanocodex/browser/Agent.d.mts +55 -0
  26. package/vendor/nanocodex/browser/Agent.mjs +11 -0
  27. package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
  28. package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
  29. package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
  30. package/vendor/nanocodex/browser/Transport.d.mts +74 -0
  31. package/vendor/nanocodex/browser/Transport.mjs +61 -0
  32. package/vendor/nanocodex/browser/Voice.d.mts +71 -0
  33. package/vendor/nanocodex/browser/Voice.mjs +291 -0
  34. package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
  35. package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
  36. package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
  37. package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
  38. package/vendor/nanocodex/browser/config.d.mts +36 -0
  39. package/vendor/nanocodex/browser/config.mjs +439 -0
  40. package/vendor/nanocodex/browser/engine.mjs +13 -0
  41. package/vendor/nanocodex/browser/harness.mjs +55 -0
  42. package/vendor/nanocodex/browser/host.d.mts +83 -0
  43. package/vendor/nanocodex/browser/host.mjs +555 -0
  44. package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
  45. package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
  46. package/vendor/nanocodex/browser/index.d.mts +92 -0
  47. package/vendor/nanocodex/browser/index.mjs +23 -0
  48. package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
  49. package/vendor/nanocodex/browser/workspace.d.mts +16 -0
  50. package/vendor/nanocodex/browser/workspace.mjs +107 -0
  51. package/vendor/nanocodex/cloud/Client.d.mts +83 -0
  52. package/vendor/nanocodex/cloud/Client.mjs +231 -0
  53. package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
  54. package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
  55. package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
  56. package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
  57. package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
  58. package/vendor/nanocodex/cloud/Errors.mjs +39 -0
  59. package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
  60. package/vendor/nanocodex/cloud/Principal.mjs +88 -0
  61. package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
  62. package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
  63. package/vendor/nanocodex/cloud/Transport.mjs +454 -0
  64. package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
  65. package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
  66. package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
  67. package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
  68. package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
  69. package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
  70. package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
  71. package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
  72. package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
  73. package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
  74. package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
  75. package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
  76. package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
  77. package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
  78. package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
  79. package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
  80. package/vendor/nanocodex/cloud/index.d.mts +30 -0
  81. package/vendor/nanocodex/cloud/index.mjs +9 -0
  82. package/vendor/nanocodex/cloud/internal.mjs +533 -0
  83. package/vendor/nanocodex/cloud/mercator.mjs +108 -0
  84. package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
  85. package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
  86. package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
  87. package/vendor/nanocodex/cloud/server/index.mjs +1 -0
  88. package/vendor/nanocodex/cloud/types.d.mts +211 -0
  89. package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
  90. package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
  91. package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
  92. package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
  93. package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
  94. package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
  95. package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
  96. package/vendor/nanocodex/cloudflare/index.mjs +6 -0
  97. package/vendor/nanocodex/host/Agent.d.mts +55 -0
  98. package/vendor/nanocodex/host/Agent.mjs +1 -0
  99. package/vendor/nanocodex/host/index.d.mts +82 -0
  100. package/vendor/nanocodex/host/index.mjs +10 -0
  101. package/vendor/nanocodex/index.d.mts +81 -0
  102. package/vendor/nanocodex/index.mjs +12 -0
  103. package/vendor/nanocodex/internal.mjs +1275 -0
  104. package/vendor/nanocodex/managed/Agent.d.mts +380 -0
  105. package/vendor/nanocodex/managed/Agent.mjs +1694 -0
  106. package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
  107. package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
  108. package/vendor/nanocodex/managed/README.md +241 -0
  109. package/vendor/nanocodex/managed/Voice.mjs +202 -0
  110. package/vendor/nanocodex/managed/index.d.mts +20 -0
  111. package/vendor/nanocodex/managed/index.mjs +2 -0
  112. package/vendor/nanocodex/managed/internal.mjs +119 -0
  113. package/vendor/nanocodex/node/Agent.d.mts +50 -0
  114. package/vendor/nanocodex/node/Agent.mjs +203 -0
  115. package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
  116. package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
  117. package/vendor/nanocodex/node/Transport.d.mts +44 -0
  118. package/vendor/nanocodex/node/Transport.mjs +46 -0
  119. package/vendor/nanocodex/node/host.mjs +731 -0
  120. package/vendor/nanocodex/node/index.d.mts +73 -0
  121. package/vendor/nanocodex/node/index.mjs +17 -0
  122. package/vendor/nanocodex/node/workspace.d.mts +10 -0
  123. package/vendor/nanocodex/node/workspace.mjs +198 -0
  124. package/vendor/nanocodex/package.json +200 -0
  125. package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
  126. package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
  127. package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
  128. package/vendor/nanocodex/pkg-node/package.json +1 -0
  129. package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
  130. package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
  131. package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
  132. package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
  133. package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
  134. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
  135. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
  136. package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
  137. package/vendor/nanocodex/pkg-web/package.json +1 -0
  138. package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
  139. package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
  140. package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
  141. package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
  142. package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
  143. package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
  144. package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
  145. package/vendor/nanocodex/runtime/durability.mjs +153 -0
  146. package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
  147. package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
  148. package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
  149. package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
  150. package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
  151. package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
  152. package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
  153. package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
  154. package/vendor/nanocodex/runtime/subagents.mjs +54 -0
  155. package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
  156. package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
  157. package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
  158. package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
  159. package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
  160. package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
  161. package/vendor/nanocodex/runtime/utf8.mjs +1 -0
  162. package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
  163. package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
  164. package/vendor/nanocodex/runtime/workspace.mjs +1 -0
  165. package/vendor/nanocodex/tools/Tools.d.mts +56 -0
  166. package/vendor/nanocodex/tools/Tools.mjs +136 -0
  167. package/vendor/nanocodex/tools/artifact.d.mts +1 -0
  168. package/vendor/nanocodex/tools/artifact.mjs +1 -0
  169. package/vendor/nanocodex/tools/attachment.mjs +1 -0
  170. package/vendor/nanocodex/tools/bash.d.mts +1 -0
  171. package/vendor/nanocodex/tools/bash.mjs +1 -0
  172. package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
  173. package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
  174. package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
  175. package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
  176. package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
  177. package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
  178. package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
  179. package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
  180. package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
  181. package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
  182. package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  183. package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
  184. package/vendor/nanocodex/tools/browser/index.mjs +174 -0
  185. package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
  186. package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
  187. package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
  188. package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
  189. package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
  190. package/vendor/nanocodex/tools/dataset.d.mts +1 -0
  191. package/vendor/nanocodex/tools/dataset.mjs +1 -0
  192. package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
  193. package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
  194. package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
  195. package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
  196. package/vendor/nanocodex/tools/index.d.mts +49 -0
  197. package/vendor/nanocodex/tools/index.mjs +19 -0
  198. package/vendor/nanocodex/tools/namedTool.mjs +1 -0
  199. package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
  200. package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
  201. package/vendor/nanocodex/tools/ssh.d.mts +1 -0
  202. package/vendor/nanocodex/tools/ssh.mjs +1 -0
  203. package/vendor/nanocodex/tools/standard.mjs +1 -0
  204. package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
  205. package/vendor/nanocodex/types.d.mts +663 -0
  206. package/vendor/nanocodex/wasm.d.mts +4 -0
  207. package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
  208. package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
  209. package/vendor/nanocodex/worker/index.d.mts +18 -0
  210. package/vendor/nanocodex/worker/index.mjs +5 -0
  211. package/vendor/nanocodex-tools/README.md +65 -0
  212. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
  213. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
  214. package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
  215. package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
  216. package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
  217. package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
  218. package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
  219. package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
  220. package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
  221. package/vendor/nanocodex-tools/dist/index.js +14 -0
  222. package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
  223. package/vendor/nanocodex-tools/dist/memory.js +411 -0
  224. package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
  225. package/vendor/nanocodex-tools/dist/namespace.js +338 -0
  226. package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
  227. package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
  228. package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
  229. package/vendor/nanocodex-tools/dist/runtime.js +37 -0
  230. package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
  231. package/vendor/nanocodex-tools/dist/session.js +286 -0
  232. package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
  233. package/vendor/nanocodex-tools/dist/shell.js +553 -0
  234. package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
  235. package/vendor/nanocodex-tools/dist/ssh.js +66 -0
  236. package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
  237. package/vendor/nanocodex-tools/dist/workspace.js +237 -0
  238. package/vendor/nanocodex-tools/package.json +122 -0
  239. package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
  240. package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
  241. package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
  242. package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
  243. package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
  244. package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
  245. package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
  246. package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
  247. package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
  248. package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
  249. package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
  250. package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
  251. package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
  252. package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
  253. package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
  254. package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
  255. package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
  256. package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
  257. package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
  258. package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
  259. package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
  260. package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
  261. package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
  262. package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
  263. package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
  264. package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
  265. package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
  266. package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
  267. package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
  268. package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
  269. package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
  270. package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
  271. package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
  272. package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
  273. package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
  274. package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
  275. package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
  276. package/vendor/nanocodex-tools/tools/x.mjs +91 -0
package/dist/index.js ADDED
@@ -0,0 +1,3405 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { CompactionEngine, CompactionId, ManualCompactionError, compactCheckpointSource, isCompactCheckpointSource, toolPairingBalancedAfter, toolPairingBalancedBefore } from "@deepseek-ai/dsh-compaction";
3
+ import { LlmAdapter, ReasoningEffortId, ToolCallId, createAssistantMessage, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
4
+ import { SessionPreparation, canonicalHeader, headerEquals } from "@deepseek-ai/dsh-session";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
7
+ import { z } from "zod";
8
+ import { joinContextSections, renderContextSections, renderPrompt } from "@deepseek-ai/dsh-system-prompt";
9
+ import { Agent, Transport, createQuickJsEvaluator } from "../vendor/nanocodex/node/index.mjs";
10
+ import { newQuickJSAsyncWASMModuleFromVariant } from "quickjs-emscripten-core";
11
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
12
+ import z$1 from "@deepseek-ai/schemastery";
13
+ import { createScope, scopeOf, scopeTarget } from "@deepseek-ai/dsh-scope";
14
+ import { admitPromptContent } from "@deepseek-ai/dsh-attachment";
15
+ import { Inbox, agentEvents, assembleContextFor, emitAgentEvent } from "@deepseek-ai/dsh-agent";
16
+ import { apply as apply$1 } from "@deepseek-ai/dsh-command-compact";
17
+ import { FsError } from "@deepseek-ai/dsh-fs";
18
+ import "@deepseek-ai/dsh-tools";
19
+ //#region src/constants.ts
20
+ const INTERRUPTED_TOOL_OUTPUT = "Tool execution was interrupted before a complete result was recorded. It may have had side effects; inspect the current state before retrying.";
21
+ const PLUGIN_NAME = "dsh-nanocodex";
22
+ const APPLY_PATCH_NAME = "apply_patch";
23
+ const APPLY_PATCH_DESCRIPTION = "Apply a raw Codex patch to text files in the active workspace. Call apply_patch(input: string) with a complete Add File/Update File patch. Supports Add File and Update File with contextual hunks; Delete File and Move to are not supported.";
24
+ const APPLY_PATCH_GRAMMAR = String.raw`start: begin_patch hunk+ end_patch
25
+ begin_patch: "*** Begin Patch" LF
26
+ end_patch: "*** End Patch" LF?
27
+ hunk: add_hunk | update_hunk
28
+ add_hunk: "*** Add File: " filename LF add_line+
29
+ update_hunk: "*** Update File: " filename LF change+
30
+ filename: /(.+)/
31
+ add_line: "+" /(.*)/ LF
32
+ change: (change_context | change_line)+ eof_line?
33
+ change_context: ("@@" | "@@ " /(.+)/) LF
34
+ change_line: ("+" | "-" | " ") /(.*)/ LF
35
+ eof_line: "*** End of File" LF
36
+ %import common.LF`;
37
+ const DEFAULT_MAX_PATCH_CHARS = 4e6;
38
+ const DEFAULT_MAX_PATCH_FILES = 64;
39
+ const DEFAULT_MAX_PATCH_FILE_BYTES = 4e6;
40
+ const MODEL_CONTEXT_WINDOW = 2e5;
41
+ const SUPPORTED_MODELS = [
42
+ "gpt-5.6-sol",
43
+ "gpt-5.6-terra",
44
+ "gpt-5.6-luna",
45
+ "gpt-6-astra"
46
+ ];
47
+ function isSupportedModel(value) {
48
+ return value !== void 0 && SUPPORTED_MODELS.includes(value);
49
+ }
50
+ function isSupportedRoute(provider, api) {
51
+ return (provider === "openai" || provider === "openai-codex-responses") && (api === "openai-responses" || api === "openai");
52
+ }
53
+ //#endregion
54
+ //#region src/history.ts
55
+ function imageDataUrl(ref, bytes) {
56
+ return `data:${ref.mediaType};base64,${Buffer.from(bytes).toString("base64")}`;
57
+ }
58
+ async function imageItem(ctx, ref, signal) {
59
+ const stored = await ctx.attachments.readImage(ref, signal);
60
+ return {
61
+ type: "input_image",
62
+ image_url: imageDataUrl(stored.ref, stored.data),
63
+ detail: "auto"
64
+ };
65
+ }
66
+ function outputText(block) {
67
+ if (block.type !== "text") throw new Error(`Nanocodex cannot hydrate assistant content block ${JSON.stringify(block.type)}`);
68
+ return {
69
+ type: "output_text",
70
+ text: block.text
71
+ };
72
+ }
73
+ function inputText(block) {
74
+ if (block.type !== "text") throw new Error(`Nanocodex cannot hydrate user content block ${JSON.stringify(block.type)}`);
75
+ return {
76
+ type: "input_text",
77
+ text: block.text
78
+ };
79
+ }
80
+ async function toolOutput(ctx, blocks, signal) {
81
+ if (blocks.length === 1 && blocks[0]?.type === "text") return blocks[0].text;
82
+ const output = [];
83
+ for (const block of blocks) {
84
+ if (block.type !== "text") {
85
+ if (block.type === "image") {
86
+ output.push(await imageItem(ctx, block.attachment, signal));
87
+ continue;
88
+ }
89
+ throw new Error(`Nanocodex cannot hydrate tool-result content block ${JSON.stringify(block.type)}`);
90
+ }
91
+ output.push({
92
+ type: "input_text",
93
+ text: block.text
94
+ });
95
+ }
96
+ return output;
97
+ }
98
+ function textOfCheckpoint(message) {
99
+ return message.content.map((block) => {
100
+ if (block.type !== "text") throw new Error(`Nanocodex cannot hydrate compaction checkpoint block ${JSON.stringify(block.type)}`);
101
+ return block.text;
102
+ }).join("\n");
103
+ }
104
+ /** Pi Responses IDs store call_id|item_id; only the call part pairs results. */
105
+ function historyToolCallId(value) {
106
+ return value.split("|", 1)[0].replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64).replace(/_+$/u, "");
107
+ }
108
+ function rawCustomInput(name, argumentsText) {
109
+ const key = name === "apply_patch" ? "patch" : "code";
110
+ let value;
111
+ try {
112
+ value = JSON.parse(argumentsText);
113
+ } catch (error) {
114
+ throw new Error(`Nanocodex cannot hydrate an invalid ${name} call`, { cause: error });
115
+ }
116
+ if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value[key] !== "string" || Object.keys(value).length !== 1) throw new Error(`Nanocodex cannot hydrate ${name} without canonical {${key}} arguments`);
117
+ return value[key];
118
+ }
119
+ function validateToolPairs(messages) {
120
+ const toolNames = /* @__PURE__ */ new Map();
121
+ for (const message of messages) {
122
+ if (message.role !== "assistant") continue;
123
+ for (const block of message.content) {
124
+ if (block.type !== "tool-call") continue;
125
+ const id = historyToolCallId(block.id);
126
+ if (toolNames.has(id)) throw new Error(`Nanocodex cannot hydrate duplicate tool call ${JSON.stringify(id)}`);
127
+ toolNames.set(id, block.name);
128
+ }
129
+ }
130
+ const seenCalls = /* @__PURE__ */ new Set();
131
+ const resultIds = /* @__PURE__ */ new Set();
132
+ for (const message of messages) for (const block of message.content) {
133
+ if (message.role === "assistant" && block.type === "tool-call") {
134
+ seenCalls.add(historyToolCallId(block.id));
135
+ continue;
136
+ }
137
+ if (block.type !== "tool-result") continue;
138
+ const id = historyToolCallId(block.toolCallId);
139
+ const name = toolNames.get(id);
140
+ if (name === void 0) throw new Error(`Nanocodex cannot hydrate a tool result for foreign call ${JSON.stringify(id)}`);
141
+ if (name !== "apply_patch" && name !== "exec") continue;
142
+ if (!seenCalls.has(id)) throw new Error(`Nanocodex cannot hydrate ${name} output before call ${JSON.stringify(id)}`);
143
+ if (resultIds.has(id)) throw new Error(`Nanocodex cannot hydrate duplicate ${name} output ${JSON.stringify(id)}`);
144
+ resultIds.add(id);
145
+ }
146
+ for (const [id, name] of toolNames) if ((name === "apply_patch" || name === "exec") && !resultIds.has(id)) throw new Error(`Nanocodex cannot hydrate ${name} call without a result ${JSON.stringify(id)}`);
147
+ return toolNames;
148
+ }
149
+ /**
150
+ * Convert the authoritative active DSH surface to Nanocodex's typed history.
151
+ * Tool calls are represented as history items only; no handler is invoked while
152
+ * hydrating a session.
153
+ */
154
+ async function buildHistoryProjection(messages, ctx, signal) {
155
+ const history = [];
156
+ const items = [];
157
+ let continuitySummary;
158
+ const toolNames = validateToolPairs(messages);
159
+ const push = (message, item) => {
160
+ history.push(item);
161
+ items.push({
162
+ message,
163
+ item
164
+ });
165
+ };
166
+ for (const message of messages) {
167
+ signal?.throwIfAborted();
168
+ if (message.role === "user" && isCompactCheckpointSource(message.source)) {
169
+ continuitySummary = textOfCheckpoint(message);
170
+ continue;
171
+ }
172
+ const toolResult = message.content[0];
173
+ if (message.role === "user" && message.content.length === 1 && toolResult?.type === "tool-result") {
174
+ const id = historyToolCallId(toolResult.toolCallId);
175
+ const name = toolNames.get(id);
176
+ const output = await toolOutput(ctx, toolResult.content, signal);
177
+ push(message, name === "apply_patch" || name === "exec" ? {
178
+ type: "custom_tool_call_output",
179
+ call_id: id,
180
+ output
181
+ } : {
182
+ type: "function_call_output",
183
+ call_id: id,
184
+ output
185
+ });
186
+ continue;
187
+ }
188
+ if (message.role === "assistant") {
189
+ const output = [];
190
+ for (const block of message.content) {
191
+ if (block.type === "reasoning") continue;
192
+ if (block.type === "tool-call") {
193
+ if (output.length > 0) push(message, {
194
+ type: "message",
195
+ role: "assistant",
196
+ content: output.splice(0),
197
+ id: String(message.id),
198
+ status: "completed"
199
+ });
200
+ const id = historyToolCallId(block.id);
201
+ if (block.name === "apply_patch" || block.name === "exec") push(message, {
202
+ type: "custom_tool_call",
203
+ name: block.name,
204
+ input: rawCustomInput(block.name, block.arguments),
205
+ call_id: id
206
+ });
207
+ else push(message, {
208
+ type: "function_call",
209
+ name: block.name,
210
+ arguments: block.arguments,
211
+ call_id: id
212
+ });
213
+ continue;
214
+ }
215
+ output.push(outputText(block));
216
+ }
217
+ if (output.length > 0) push(message, {
218
+ type: "message",
219
+ role: "assistant",
220
+ content: output,
221
+ id: String(message.id),
222
+ status: "completed"
223
+ });
224
+ continue;
225
+ }
226
+ const content = [];
227
+ for (const block of message.content) if (block.type === "image") content.push(await imageItem(ctx, block.attachment, signal));
228
+ else if (block.type === "text") content.push(inputText(block));
229
+ else throw new Error(`Nanocodex cannot hydrate user content block ${JSON.stringify(block.type)}`);
230
+ if (content.length === 0) throw new Error(`Nanocodex cannot hydrate empty DSH message ${String(message.id)}`);
231
+ push(message, {
232
+ type: "message",
233
+ role: message.role === "system" ? "developer" : "user",
234
+ content,
235
+ id: String(message.id),
236
+ status: "completed"
237
+ });
238
+ }
239
+ return {
240
+ history,
241
+ items,
242
+ ...continuitySummary !== void 0 ? { continuitySummary } : {}
243
+ };
244
+ }
245
+ async function buildHistorySeed(messages, ctx, signal) {
246
+ const { items: _items, ...seed } = await buildHistoryProjection(messages, ctx, signal);
247
+ return seed;
248
+ }
249
+ /** Convert newly admitted DSH user content into one Nanocodex prompt. */
250
+ async function buildPromptInput(messages, ctx, signal) {
251
+ const items = [];
252
+ for (const message of messages) {
253
+ signal?.throwIfAborted();
254
+ for (const block of message.content) if (block.type === "text") items.push({
255
+ type: "text",
256
+ text: block.text
257
+ });
258
+ else if (block.type === "image") {
259
+ const stored = await ctx.attachments.readImage(block.attachment, signal);
260
+ items.push({
261
+ type: "image",
262
+ image_url: imageDataUrl(stored.ref, stored.data),
263
+ detail: "auto"
264
+ });
265
+ } else if (block.type === "tool-result") continue;
266
+ else throw new Error(`Nanocodex cannot use admitted DSH content block ${JSON.stringify(block.type)} as prompt input`);
267
+ }
268
+ if (items.length === 0) return "Continue from the current conversation.";
269
+ if (items.length === 1 && items[0]?.type === "text") return items[0].text;
270
+ return items;
271
+ }
272
+ function plainText(messages) {
273
+ return messages.flatMap((message) => message.content.flatMap((block) => block.type === "text" ? [block.text] : [])).join("\n");
274
+ }
275
+ //#endregion
276
+ //#region src/session-id.ts
277
+ const DSH_SESSION_ID = /^session-([0-9a-f]{8}-[0-9a-f]{4}-[47][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/iu;
278
+ /** Convert the DSH `session-<UUID>` identity to Nanocodex's UUID identity. */
279
+ function normalizeNanocodexSessionId(value) {
280
+ const match = DSH_SESSION_ID.exec(value);
281
+ if (match?.[1] === void 0) throw new Error(`Nanocodex requires a DSH session ID in the form session-<UUIDv4|UUIDv7>, got ${JSON.stringify(value)}`);
282
+ return match[1].toLowerCase();
283
+ }
284
+ //#endregion
285
+ //#region src/settings.ts
286
+ const SETTINGS_NAMESPACE = "llm-pi-ai";
287
+ const SUPPORTED_PROVIDERS = ["openai", "openai-codex-responses"];
288
+ const DEFAULT_NATIVE_API = "openai-responses";
289
+ const profile = z$1.object({
290
+ apiKeyEnv: z$1.string(),
291
+ api: z$1.string(),
292
+ baseURL: z$1.string(),
293
+ websocketURL: z$1.string(),
294
+ reasoning: z$1.string()
295
+ }).loose(true);
296
+ /**
297
+ * Keep the existing settings namespace and profile shape readable by DSH
298
+ * configuration surfaces while accepting fields owned by older pi-ai
299
+ * profiles. Nanocodex intentionally validates only the route fields it can
300
+ * act on at request admission.
301
+ */
302
+ const NanocodexSettingsSchema = z$1.object({ providers: z$1.dict(profile).default({}) });
303
+ function normalizedApi(provider, api) {
304
+ if (api !== void 0) return api;
305
+ return SUPPORTED_PROVIDERS.includes(provider) ? DEFAULT_NATIVE_API : void 0;
306
+ }
307
+ function configuredNanocodexProviders(value) {
308
+ return SUPPORTED_PROVIDERS.filter((provider) => {
309
+ const configured = value.providers[provider];
310
+ return configured !== void 0 && isSupportedRoute(provider, normalizedApi(provider, configured.api));
311
+ });
312
+ }
313
+ function settingsValue(ctx) {
314
+ const value = ctx.settings.get(SETTINGS_NAMESPACE);
315
+ if (!value || typeof value !== "object") throw new Error(`Nanocodex route settings are unavailable: register ${SETTINGS_NAMESPACE} before creating an agent`);
316
+ const providers = value.providers;
317
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) throw new Error(`Nanocodex route settings ${SETTINGS_NAMESPACE}.providers is invalid`);
318
+ return { providers };
319
+ }
320
+ function thinking(value) {
321
+ if (value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max") return value;
322
+ }
323
+ function supportedThinking(value) {
324
+ if (value === "pro") return value;
325
+ return thinking(value);
326
+ }
327
+ /** Resolve one explicit DSH route without provider or credential fallbacks. */
328
+ async function resolveNanocodexRoute(ctx, options) {
329
+ const provider = options.provider;
330
+ const model = options.model;
331
+ if (!provider || !model) throw new Error("Nanocodex requires an explicit AgentOptions.provider and AgentOptions.model");
332
+ if (!isSupportedModel(model)) throw new Error(`Nanocodex does not support model ${JSON.stringify(model)}`);
333
+ const configured = settingsValue(ctx).providers[provider];
334
+ if (!configured) throw new Error(`Nanocodex route ${JSON.stringify(provider)} is not configured`);
335
+ if (!isSupportedRoute(provider, normalizedApi(provider, configured.api))) throw new Error(`Nanocodex does not support route ${JSON.stringify(provider)} with api ${JSON.stringify(configured.api)}`);
336
+ if (!configured.apiKeyEnv) throw new Error(`Nanocodex route ${JSON.stringify(provider)} must declare apiKeyEnv in ${SETTINGS_NAMESPACE}`);
337
+ let ref;
338
+ try {
339
+ ref = credentialRef(configured.apiKeyEnv);
340
+ } catch (error) {
341
+ throw new Error(`Nanocodex route ${JSON.stringify(provider)} has an invalid credential reference`, { cause: error });
342
+ }
343
+ const resolved = await ctx.credentials.resolve(ref);
344
+ if (!resolved?.value) throw new Error(`Nanocodex credential ${JSON.stringify(configured.apiKeyEnv)} is not configured for route ${JSON.stringify(provider)}`);
345
+ const selectedThinking = thinking(options.reasoningEffort ?? configured.reasoning);
346
+ return {
347
+ provider,
348
+ model,
349
+ apiKey: resolved.value,
350
+ ...configured.baseURL ? { apiBaseUrl: configured.baseURL } : {},
351
+ ...configured.websocketURL ? { websocketUrl: configured.websocketURL } : {},
352
+ ...selectedThinking === void 0 ? {} : { thinking: selectedThinking }
353
+ };
354
+ }
355
+ //#endregion
356
+ //#region src/tool-bridge.ts
357
+ function jsonArguments(input) {
358
+ try {
359
+ return JSON.stringify(input ?? {}) ?? "{}";
360
+ } catch (error) {
361
+ throw new Error("Nanocodex produced non-serializable tool arguments", { cause: error });
362
+ }
363
+ }
364
+ function normalizedArguments(input) {
365
+ const text = jsonArguments(input);
366
+ return {
367
+ text,
368
+ value: JSON.parse(text)
369
+ };
370
+ }
371
+ function contentText(content) {
372
+ return content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n") || "The DSH tool returned no text content.";
373
+ }
374
+ function modelValue(result, custom) {
375
+ if (custom) return contentText(result.content);
376
+ if (!result.isError) return result.value;
377
+ return {
378
+ error: result.error.message,
379
+ content: contentText(result.content)
380
+ };
381
+ }
382
+ function executionFailure(error) {
383
+ const message = error instanceof Error ? error.message : String(error);
384
+ return {
385
+ isError: true,
386
+ error: { message },
387
+ content: [{
388
+ type: "text",
389
+ text: message
390
+ }]
391
+ };
392
+ }
393
+ function makeTool(definition, context, customDefinition) {
394
+ const custom = customDefinition?.type === "custom";
395
+ let outputSchema = structuredClone(definition.output.schema);
396
+ let description = definition.description;
397
+ if (definition.name === "read_image") {
398
+ description += "\nIn Code Mode, display the loaded image with image(await tools.read_image({file_path: \"picture.png\"})). The result contains path and image_url (a data URL); image(result) emits the image to the model. A bare return does not emit output.";
399
+ outputSchema = {
400
+ type: "object",
401
+ properties: {
402
+ path: {
403
+ type: "string",
404
+ description: "Resolved image file path."
405
+ },
406
+ image_url: {
407
+ type: "string",
408
+ description: "Loaded image data URL."
409
+ }
410
+ },
411
+ required: ["path", "image_url"],
412
+ additionalProperties: false
413
+ };
414
+ }
415
+ return {
416
+ name: definition.name,
417
+ description,
418
+ parameters: structuredClone(definition.parameters),
419
+ outputSchema,
420
+ ...customDefinition === void 0 ? {} : { definition: structuredClone(customDefinition) },
421
+ async handler(input, call) {
422
+ const id = call.callId;
423
+ const argumentsValue = normalizedArguments(custom && definition.name === "apply_patch" ? (() => {
424
+ if (typeof input !== "string" || input.length === 0) throw new TypeError("apply_patch expects one non-empty raw patch string");
425
+ return { patch: input };
426
+ })() : input);
427
+ const callSeq = await context.callbacks.onCall({
428
+ id,
429
+ name: definition.name,
430
+ arguments: argumentsValue.text,
431
+ ...call.parentCallId ? { parentCallId: call.parentCallId } : {}
432
+ });
433
+ const parentCallId = call.parentCallId;
434
+ if (parentCallId) context.agent.session.append("tool/code-dispatch-start", {
435
+ rootCallId: ToolCallId(parentCallId),
436
+ parentCallId: ToolCallId(parentCallId),
437
+ subCallId: ToolCallId(id),
438
+ name: definition.name,
439
+ arguments: argumentsValue.value
440
+ });
441
+ let result;
442
+ try {
443
+ result = await context.tools.execute({
444
+ callId: ToolCallId(id),
445
+ ...call.parentCallId ? { rootCallId: ToolCallId(call.parentCallId) } : {},
446
+ name: definition.name,
447
+ arguments: argumentsValue.value,
448
+ agent: context.agent,
449
+ signal: call.signal
450
+ });
451
+ } catch (error) {
452
+ result = executionFailure(error);
453
+ }
454
+ try {
455
+ await context.callbacks.onResult({
456
+ id,
457
+ callSeq,
458
+ result,
459
+ ...parentCallId ? { parentCallId } : {}
460
+ });
461
+ } finally {
462
+ if (parentCallId) context.agent.session.append("tool/code-dispatch", {
463
+ rootCallId: ToolCallId(parentCallId),
464
+ parentCallId: ToolCallId(parentCallId),
465
+ subCallId: ToolCallId(id),
466
+ name: definition.name,
467
+ arguments: argumentsValue.value,
468
+ isError: result.isError,
469
+ content: result.content
470
+ });
471
+ }
472
+ if (!result.isError && definition.name === "read_image") {
473
+ const image = result.content.find((block) => block.type === "image");
474
+ if (image?.type !== "image") throw new Error("read_image returned no image attachment");
475
+ const stored = await context.agent.ctx.attachments.readImage(image.attachment, call.signal);
476
+ return {
477
+ path: result.value.path,
478
+ image_url: `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString("base64")}`
479
+ };
480
+ }
481
+ return modelValue(result, custom);
482
+ }
483
+ };
484
+ }
485
+ /** Build one model-facing tool set from the active DSH scope. */
486
+ function createToolBridge(context) {
487
+ const scope = scopeOf(context.agent.ctx);
488
+ return context.tools.schemas(scope).map((schema) => context.tools.get(schema.name, scope)).filter((definition) => definition !== void 0).map((definition) => makeTool(definition, context, context.customDefinitions?.get(definition.name)));
489
+ }
490
+ //#endregion
491
+ //#region src/interrupted-tools.ts
492
+ /** Close admitted calls at a stopped driver boundary without replaying them. */
493
+ function closeInterruptedToolCalls(session, failure) {
494
+ const calls = /* @__PURE__ */ new Map();
495
+ const results = /* @__PURE__ */ new Set();
496
+ for (const seq of session.surface.nodes) {
497
+ const event = session.eventAt(seq);
498
+ if (event?.type === "assistant/message") for (const block of event.data.message.content) {
499
+ if (block.type !== "tool-call") continue;
500
+ const id = String(block.id);
501
+ if (calls.has(id)) throw new Error(`Cannot recover duplicate tool call ${JSON.stringify(id)}`);
502
+ calls.set(id, {
503
+ block,
504
+ turn: event.data.turn,
505
+ step: event.data.step
506
+ });
507
+ }
508
+ else if (event?.type === "tool/result") results.add(String(event.data.message.source.callId));
509
+ }
510
+ for (const id of results) calls.delete(id);
511
+ if (calls.size === 0) return;
512
+ const callSeqs = /* @__PURE__ */ new Map();
513
+ for (const event of session.snapshotEvents()) {
514
+ if (event.type !== "tool/call") continue;
515
+ const pending = calls.get(String(event.data.callId));
516
+ if (pending?.turn === event.data.turn && pending.step === event.data.step) callSeqs.set(String(event.data.callId), event.seq);
517
+ }
518
+ const cause = failure instanceof Error ? failure.message : String(failure);
519
+ const text = failure === void 0 ? INTERRUPTED_TOOL_OUTPUT : `${INTERRUPTED_TOOL_OUTPUT}\nCause: ${cause.slice(0, 1024)}`;
520
+ for (const [id, { block, turn, step }] of calls) {
521
+ const callSeq = callSeqs.get(id) ?? session.append("tool/call", {
522
+ turn,
523
+ step,
524
+ callId: block.id,
525
+ name: block.name,
526
+ arguments: block.arguments
527
+ }).seq;
528
+ session.append("tool/result", {
529
+ turn,
530
+ step,
531
+ message: createToolResultMessage({
532
+ callId: block.id,
533
+ content: [{
534
+ type: "text",
535
+ text
536
+ }],
537
+ isError: true
538
+ })
539
+ }, {
540
+ surfaceOp: "append",
541
+ sourceEventSeqs: [callSeq]
542
+ });
543
+ }
544
+ }
545
+ //#endregion
546
+ //#region src/output.ts
547
+ function record$1(value) {
548
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
549
+ }
550
+ function count(value) {
551
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
552
+ }
553
+ /** Responses input includes cache buckets; DSH input excludes them. */
554
+ function modelCallUsage(event) {
555
+ if (event.type !== "model.call.completed") return void 0;
556
+ const value = record$1(event.payload.usage);
557
+ const input = count(value?.input_tokens);
558
+ const output = count(value?.output_tokens);
559
+ if (value === void 0 || input === void 0 || output === void 0) return void 0;
560
+ const details = record$1(value.input_tokens_details);
561
+ const cacheReadTokens = count(details?.cached_tokens);
562
+ const cacheWriteTokens = count(details?.cache_write_tokens);
563
+ const reasoningTokens = count(record$1(value.output_tokens_details)?.reasoning_tokens);
564
+ const totalTokens = count(value.total_tokens);
565
+ return {
566
+ inputTokens: Math.max(0, input - (cacheReadTokens ?? 0) - (cacheWriteTokens ?? 0)),
567
+ outputTokens: output,
568
+ ...totalTokens === void 0 ? {} : { totalTokens },
569
+ ...cacheReadTokens === void 0 ? {} : { cacheReadTokens },
570
+ ...cacheWriteTokens === void 0 ? {} : { cacheWriteTokens },
571
+ ...reasoningTokens === void 0 ? {} : { reasoningTokens }
572
+ };
573
+ }
574
+ function outputBlock(item) {
575
+ if (item.type === "message" && item.role === "assistant" && Array.isArray(item.content)) {
576
+ const text = item.content.map(record$1).flatMap((part) => part?.type === "output_text" && typeof part.text === "string" ? [part.text] : []).join("");
577
+ return text ? {
578
+ type: "text",
579
+ text
580
+ } : void 0;
581
+ }
582
+ if ((item.type === "custom_tool_call" || item.type === "function_call") && typeof item.call_id === "string" && typeof item.name === "string") {
583
+ let args;
584
+ if (item.type === "custom_tool_call") {
585
+ if (typeof item.input !== "string") throw new Error("Nanocodex custom call has no raw input");
586
+ if (item.name === "exec") args = JSON.stringify({ code: item.input });
587
+ else if (item.name === "apply_patch") args = JSON.stringify({ patch: item.input });
588
+ else throw new Error(`Nanocodex cannot project custom tool ${JSON.stringify(item.name)}`);
589
+ } else {
590
+ if (typeof item.arguments !== "string") throw new Error("Nanocodex function call has no arguments");
591
+ args = item.arguments;
592
+ }
593
+ return {
594
+ type: "tool-call",
595
+ id: ToolCallId(item.call_id),
596
+ name: item.name,
597
+ arguments: args
598
+ };
599
+ }
600
+ }
601
+ async function resultContent(agent, value) {
602
+ if (typeof value === "string") return [{
603
+ type: "text",
604
+ text: value
605
+ }];
606
+ if (!Array.isArray(value)) throw new Error("Nanocodex tool result has no supported output body");
607
+ const content = value.map((value) => {
608
+ const part = record$1(value);
609
+ if (part?.type === "input_text" && typeof part.text === "string") return {
610
+ type: "text",
611
+ text: part.text
612
+ };
613
+ if (part?.type === "input_image" && typeof part.image_url === "string") {
614
+ const match = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/=]+)$/u.exec(part.image_url);
615
+ if (match) return {
616
+ type: "image",
617
+ mediaType: match[1],
618
+ data: match[2]
619
+ };
620
+ }
621
+ throw new Error("Nanocodex tool result contains an unsupported content part");
622
+ });
623
+ if (content.every((part) => part.type === "text")) return content;
624
+ return admitPromptContent(agent.ctx.attachments, content);
625
+ }
626
+ /** One DSH step and one durable Assistant per Nanocodex model request. */
627
+ var NanocodexOutput = class {
628
+ agent;
629
+ provider;
630
+ model;
631
+ turn;
632
+ advanceStep;
633
+ step;
634
+ started = false;
635
+ committed = false;
636
+ blocks = [];
637
+ chunkSeqs = [];
638
+ textIndices = /* @__PURE__ */ new Map();
639
+ openText;
640
+ outputItems = /* @__PURE__ */ new Map();
641
+ parents = /* @__PURE__ */ new Map();
642
+ constructor(agent, provider, model, turn, step, advanceStep) {
643
+ this.agent = agent;
644
+ this.provider = provider;
645
+ this.model = model;
646
+ this.turn = turn;
647
+ this.advanceStep = advanceStep;
648
+ this.step = step;
649
+ }
650
+ get coordinates() {
651
+ return {
652
+ turn: this.turn,
653
+ step: this.step
654
+ };
655
+ }
656
+ chunk(chunk) {
657
+ this.chunkSeqs.push(this.agent.session.append("assistant/chunk", {
658
+ ...this.coordinates,
659
+ chunk
660
+ }).seq);
661
+ }
662
+ text(text, id, complete) {
663
+ if (!text) return;
664
+ let index = typeof id === "string" ? this.textIndices.get(id) : this.openText;
665
+ if (index === void 0) {
666
+ index = this.blocks.length;
667
+ this.blocks.push({
668
+ type: "text",
669
+ text: ""
670
+ });
671
+ if (typeof id === "string") this.textIndices.set(id, index);
672
+ this.chunk({
673
+ type: "block-start",
674
+ index,
675
+ blockType: "text"
676
+ });
677
+ }
678
+ const previous = this.blocks[index];
679
+ if (previous?.type !== "text") throw new Error("Nanocodex text stream lost its block");
680
+ if (complete) {
681
+ this.blocks[index] = {
682
+ type: "text",
683
+ text
684
+ };
685
+ this.chunk({
686
+ type: "block-end",
687
+ index,
688
+ block: this.blocks[index]
689
+ });
690
+ this.openText = void 0;
691
+ } else {
692
+ this.blocks[index] = {
693
+ type: "text",
694
+ text: previous.text + text
695
+ };
696
+ this.openText = index;
697
+ this.chunk({
698
+ type: "text-delta",
699
+ index,
700
+ text
701
+ });
702
+ }
703
+ }
704
+ installOutput(items) {
705
+ this.blocks = items.flatMap((item) => {
706
+ const block = outputBlock(item);
707
+ return block === void 0 ? [] : [block];
708
+ });
709
+ for (const [index, block] of this.blocks.entries()) this.chunk({
710
+ type: "block-end",
711
+ index,
712
+ block
713
+ });
714
+ }
715
+ commit(usage, interrupted = false) {
716
+ if (this.committed) return;
717
+ if (usage !== void 0) this.chunk({
718
+ type: "usage",
719
+ usage
720
+ });
721
+ for (const block of this.blocks) if (block.type === "tool-call") this.parents.set(String(block.id), {
722
+ block,
723
+ step: this.step
724
+ });
725
+ this.agent.session.append("assistant/message", {
726
+ ...this.coordinates,
727
+ message: createAssistantMessage({
728
+ content: this.blocks,
729
+ source: {
730
+ provider: this.provider,
731
+ model: this.model
732
+ }
733
+ }),
734
+ ...usage === void 0 ? {} : { usage },
735
+ ...interrupted ? { interrupted: true } : {}
736
+ }, {
737
+ surfaceOp: "append",
738
+ sourceEventSeqs: this.chunkSeqs
739
+ });
740
+ this.committed = true;
741
+ }
742
+ async accept(event) {
743
+ const payload = event.payload;
744
+ if (event.type === "model.call.started") {
745
+ if (this.started) {
746
+ if (!this.committed && this.blocks.length > 0) this.commit(void 0, true);
747
+ this.step = this.advanceStep();
748
+ }
749
+ this.started = true;
750
+ this.committed = false;
751
+ this.blocks = [];
752
+ this.chunkSeqs = [];
753
+ this.textIndices.clear();
754
+ this.outputItems.clear();
755
+ this.openText = void 0;
756
+ } else if (event.type === "assistant.delta" && typeof payload.text === "string") this.text(payload.text, payload.item_id, false);
757
+ else if (event.type === "assistant.message" && typeof payload.text === "string") this.text(payload.text, payload.item_id, true);
758
+ else if (event.type === "api.event" && payload.phase === "generation") {
759
+ const api = record$1(payload.event);
760
+ if (api?.type === "response.output_item.done") {
761
+ const item = record$1(api.item);
762
+ if (item !== void 0) this.outputItems.set(String(item.id ?? api.output_index), item);
763
+ } else if (api?.type === "response.completed") {
764
+ const output = record$1(api.response)?.output;
765
+ if (Array.isArray(output) && output.length > 0) this.installOutput(output.flatMap((value) => {
766
+ const item = record$1(value);
767
+ return item === void 0 ? [] : [item];
768
+ }));
769
+ else if (this.outputItems.size > 0) this.installOutput([...this.outputItems.values()]);
770
+ }
771
+ } else if (event.type === "model.call.completed") this.commit(modelCallUsage(event));
772
+ else if (event.type === "tool.call" || event.type === "tool.result") {
773
+ if (typeof payload.call_id !== "string") return;
774
+ const parent = this.parents.get(payload.call_id);
775
+ if (parent === void 0 || parent.block.name !== "exec" && parent.block.name !== "wait") return;
776
+ if (event.type === "tool.call") parent.callSeq = this.agent.session.append("tool/call", {
777
+ turn: this.turn,
778
+ step: parent.step,
779
+ callId: parent.block.id,
780
+ name: parent.block.name,
781
+ arguments: parent.block.arguments
782
+ }).seq;
783
+ else {
784
+ if (parent.callSeq === void 0) throw new Error("Nanocodex parent result has no durable call");
785
+ this.agent.session.append("tool/result", {
786
+ turn: this.turn,
787
+ step: parent.step,
788
+ message: createToolResultMessage({
789
+ callId: parent.block.id,
790
+ content: await resultContent(this.agent, payload.result),
791
+ isError: payload.status !== "completed"
792
+ })
793
+ }, {
794
+ surfaceOp: "append",
795
+ sourceEventSeqs: [parent.callSeq]
796
+ });
797
+ this.parents.delete(payload.call_id);
798
+ }
799
+ }
800
+ }
801
+ finish(finalText) {
802
+ if (this.committed) return;
803
+ if (this.blocks.length === 0 && finalText) this.text(finalText, void 0, true);
804
+ if (this.blocks.length > 0) this.commit();
805
+ }
806
+ interrupt(error) {
807
+ if (!this.committed && this.blocks.length > 0) this.commit(void 0, true);
808
+ closeInterruptedToolCalls(this.agent.session, error);
809
+ }
810
+ };
811
+ //#endregion
812
+ //#region src/transport-diagnostic.ts
813
+ const FALLBACK_EVENT = "model.attempt.retrying";
814
+ const FALLBACK_ERROR_CLASS = "websocket_fallback";
815
+ const PREVIOUS_TRANSPORT = "responses_websocket_v2";
816
+ const NEXT_TRANSPORT = "responses_https_sse";
817
+ const REASONS = /* @__PURE__ */ new Set([
818
+ "upgrade_required",
819
+ "transport_unavailable",
820
+ "retry_exhausted"
821
+ ]);
822
+ const MAX_CORRELATION_LENGTH = 128;
823
+ function boundedCorrelation(value) {
824
+ if (typeof value !== "string" || value.length === 0) return void 0;
825
+ return value.slice(0, MAX_CORRELATION_LENGTH);
826
+ }
827
+ function transportFallbackDiagnostic(sessionId, event) {
828
+ if (event.type !== FALLBACK_EVENT) return void 0;
829
+ const payload = event.payload;
830
+ if (payload.error_class !== FALLBACK_ERROR_CLASS || payload.previous_transport !== PREVIOUS_TRANSPORT || payload.next_transport !== NEXT_TRANSPORT || typeof payload.reason !== "string" || !REASONS.has(payload.reason)) return;
831
+ const reason = payload.reason;
832
+ const requestId = boundedCorrelation(event.request_id);
833
+ return {
834
+ kind: "nanocodex.transport_fallback",
835
+ session_id: boundedCorrelation(sessionId) ?? "unknown",
836
+ ...requestId === void 0 ? {} : { request_id: requestId },
837
+ error_class: FALLBACK_ERROR_CLASS,
838
+ previous_transport: PREVIOUS_TRANSPORT,
839
+ next_transport: NEXT_TRANSPORT,
840
+ reason
841
+ };
842
+ }
843
+ /** Observe public Nanocodex fallback events and project only safe fields. */
844
+ function observeTransportFallback(ctx, sessionId, source) {
845
+ const watcher = source.events.watch();
846
+ const removeListener = watcher.onEvent((event) => {
847
+ const diagnostic = transportFallbackDiagnostic(sessionId, event);
848
+ if (diagnostic === void 0) return;
849
+ try {
850
+ ctx.logger("dsh-nanocodex.transport").info(diagnostic);
851
+ } catch {}
852
+ });
853
+ return () => {
854
+ removeListener();
855
+ watcher.off();
856
+ };
857
+ }
858
+ //#endregion
859
+ //#region src/engine.ts
860
+ const nanocodexCheckpointDomain = defineDomain({
861
+ name: "nanocodex_checkpoints",
862
+ version: 1,
863
+ layout: "per-record",
864
+ tables: { sessions: domainTable(z.custom((value) => checkpoint(value) !== void 0)) }
865
+ });
866
+ const RAW_APPLICATION_DEFINITIONS = /* @__PURE__ */ new Map([[APPLY_PATCH_NAME, {
867
+ type: "custom",
868
+ description: APPLY_PATCH_DESCRIPTION,
869
+ format: {
870
+ type: "grammar",
871
+ syntax: "lark",
872
+ definition: APPLY_PATCH_GRAMMAR
873
+ }
874
+ }]]);
875
+ function runtimeKey(agent) {
876
+ return String(agent.session.id);
877
+ }
878
+ function messageText(message) {
879
+ return plainText([message]);
880
+ }
881
+ function supplementaryContext(messages) {
882
+ return messages.filter((message) => message.source.kind === "plugin").map(messageText).filter(Boolean).join("\n\n") || void 0;
883
+ }
884
+ function routeKey(route, agent) {
885
+ return JSON.stringify({
886
+ provider: route.provider,
887
+ model: route.model,
888
+ apiKeyDigest: createHash("sha256").update(route.apiKey).digest("hex"),
889
+ apiBaseUrl: route.apiBaseUrl,
890
+ websocketUrl: route.websocketUrl,
891
+ thinking: route.thinking,
892
+ reasoningEffort: agent.options.reasoningEffort
893
+ });
894
+ }
895
+ function fingerprint(messages) {
896
+ return createHash("sha256").update(JSON.stringify(messages)).digest("hex");
897
+ }
898
+ function makeBoundary(session, messages) {
899
+ return {
900
+ replaceGeneration: session.surface.replaceGeneration,
901
+ surfaceSeqs: [...session.surface.nodes],
902
+ messageCount: messages.length,
903
+ fingerprint: fingerprint(messages)
904
+ };
905
+ }
906
+ function boundaryMatches(boundary, session, messages) {
907
+ const nodes = session.surface.nodes;
908
+ return boundary.replaceGeneration === session.surface.replaceGeneration && boundary.messageCount === messages.length && boundary.surfaceSeqs.length <= nodes.length && boundary.surfaceSeqs.every((seq, index) => nodes[index] === seq) && boundary.fingerprint === fingerprint(messages);
909
+ }
910
+ function surfacePrefix(session, messageCount) {
911
+ const nodes = [...session.surface.nodes];
912
+ if (messageCount > nodes.length) throw new Error("Nanocodex compaction cannot capture a DSH admission boundary");
913
+ return nodes.slice(0, messageCount);
914
+ }
915
+ function record(value) {
916
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
917
+ }
918
+ function supportedModel(value) {
919
+ return typeof value === "string" && isSupportedModel(value) ? value : void 0;
920
+ }
921
+ function sessionSnapshot(value) {
922
+ const candidate = record(value);
923
+ if (!candidate || !Array.isArray(candidate.history)) return void 0;
924
+ if (supportedModel(candidate.model) === void 0) return void 0;
925
+ return candidate;
926
+ }
927
+ function checkpoint(value) {
928
+ const candidate = record(value);
929
+ const boundary = record(candidate?.boundary);
930
+ const snapshot = sessionSnapshot(candidate?.snapshot);
931
+ if (candidate?.version !== 1 || typeof candidate.provider !== "string" || supportedModel(candidate.model) === void 0 || !boundary || snapshot === void 0 || snapshot.model !== candidate.model || !Array.isArray(boundary.surfaceSeqs) || !boundary.surfaceSeqs.every((seq) => typeof seq === "number" && Number.isSafeInteger(seq) && seq >= 0) || typeof boundary.replaceGeneration !== "number" || !Number.isSafeInteger(boundary.replaceGeneration) || boundary.replaceGeneration < 0 || typeof boundary.messageCount !== "number" || !Number.isSafeInteger(boundary.messageCount) || boundary.messageCount < 0 || typeof boundary.fingerprint !== "string") return;
932
+ return {
933
+ version: 1,
934
+ provider: candidate.provider,
935
+ model: supportedModel(candidate.model),
936
+ boundary: {
937
+ replaceGeneration: boundary.replaceGeneration,
938
+ surfaceSeqs: boundary.surfaceSeqs,
939
+ messageCount: boundary.messageCount,
940
+ fingerprint: boundary.fingerprint
941
+ },
942
+ snapshot
943
+ };
944
+ }
945
+ function findMatchingCheckpoint(store, session, messages, provider, model) {
946
+ const candidate = checkpoint(store.get(session.id));
947
+ if (candidate !== void 0 && candidate.provider === provider && candidate.model === model && boundaryMatches(candidate.boundary, session, messages)) return candidate;
948
+ }
949
+ const DEFAULT_COMPACTION_INSTRUCTION = [
950
+ "Create a concise private continuity summary of the conversation above.",
951
+ "Preserve important facts, preferences, commitments, unresolved questions, and the immediate context.",
952
+ "Return summary text only. Do not mention this instruction or compaction."
953
+ ].join("\n");
954
+ async function* emptyLlmStream() {}
955
+ function integer(value) {
956
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
957
+ }
958
+ function nullableString(value) {
959
+ return value === null || typeof value === "string";
960
+ }
961
+ function parseCompactionIdentity(value) {
962
+ const candidate = record(value);
963
+ if (!candidate || !integer(candidate.index) || typeof candidate.kind !== "string" || candidate.kind.length === 0 || !nullableString(candidate.id) || !nullableString(candidate.call_id)) return;
964
+ return candidate;
965
+ }
966
+ function parseCompactionOutcome(value) {
967
+ const candidate = record(value);
968
+ const range = record(candidate?.replaced_history);
969
+ const context = record(candidate?.context);
970
+ const retained = Array.isArray(candidate?.retained_tail) ? candidate.retained_tail.map(parseCompactionIdentity) : void 0;
971
+ if (!candidate || typeof candidate.revision !== "string" || candidate.revision.length === 0 || candidate.trigger !== "manual" && candidate.trigger !== "automatic" || !nullableString(candidate.summary) || !range || !integer(range.start) || !integer(range.end) || range.start > range.end || !retained || retained.some((item) => item === void 0) || !context || typeof context.workspace !== "string" || !Array.isArray(context.history) || !context.history.every((item) => record(item) !== void 0)) return;
972
+ return candidate;
973
+ }
974
+ function parseCompactionReplacedEvent(event) {
975
+ if (event.type !== "model.compaction.replaced") return void 0;
976
+ if (parseCompactionOutcome(event.payload) === void 0 || !integer(event.payload.after_model_call_index) || event.payload.phase !== "pre_turn" && event.payload.phase !== "mid_turn") return;
977
+ return event.payload;
978
+ }
979
+ function projectedIdentityMatches(identity, item) {
980
+ const identityKind = identity.kind === "custom_tool_call" ? "function_call" : identity.kind === "custom_tool_call_output" ? "function_call_output" : identity.kind;
981
+ if (identityKind !== (item.type === "custom_tool_call" ? "function_call" : item.type === "custom_tool_call_output" ? "function_call_output" : item.type)) return false;
982
+ const callId = "call_id" in item ? item.call_id : null;
983
+ if (identity.call_id !== callId) return false;
984
+ return identityKind === "message" || identityKind === "function_call" || identityKind === "function_call_output" || identity.id === (item.id ?? null);
985
+ }
986
+ function historyImageDetail(value) {
987
+ return value === void 0 || value === "auto" || value === "low" || value === "high" || value === "original";
988
+ }
989
+ function historyInputItemMatches(left, right) {
990
+ const leftObject = record(left);
991
+ const rightObject = record(right);
992
+ if (leftObject === void 0 || rightObject === void 0) return false;
993
+ if (leftObject.type !== rightObject.type) return false;
994
+ switch (leftObject.type) {
995
+ case "input_text": return typeof leftObject.text === "string" && leftObject.text === rightObject.text;
996
+ case "input_image": return typeof leftObject.image_url === "string" && leftObject.image_url === rightObject.image_url && historyImageDetail(leftObject.detail) && historyImageDetail(rightObject.detail) && leftObject.detail === rightObject.detail;
997
+ case "input_audio": return typeof leftObject.audio_url === "string" && leftObject.audio_url === rightObject.audio_url;
998
+ case "encrypted_content": return typeof leftObject.encrypted_content === "string" && leftObject.encrypted_content === rightObject.encrypted_content;
999
+ default: return false;
1000
+ }
1001
+ }
1002
+ function historyContentItemMatches(left, right) {
1003
+ const leftObject = record(left);
1004
+ const rightObject = record(right);
1005
+ if (leftObject === void 0 || rightObject === void 0) return false;
1006
+ if (leftObject.type !== rightObject.type) return false;
1007
+ if (leftObject.type === "output_text") return typeof leftObject.text === "string" && leftObject.text === rightObject.text;
1008
+ if (leftObject.type === "input_text" || leftObject.type === "input_image" || leftObject.type === "input_audio") return historyInputItemMatches(left, right);
1009
+ return false;
1010
+ }
1011
+ function historyContentMatches(left, right) {
1012
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((item, index) => historyContentItemMatches(item, right[index]));
1013
+ }
1014
+ function historyToolOutputMatches(left, right) {
1015
+ if (typeof left === "string" || typeof right === "string") return typeof left === "string" && left === right;
1016
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((item, index) => historyInputItemMatches(item, right[index]));
1017
+ }
1018
+ function historyMessageMatches(left, right) {
1019
+ return left?.type === "message" && right.type === "message" && left.role === right.role && historyContentMatches(left.content, right.content);
1020
+ }
1021
+ function historyItemsMatch(left, right) {
1022
+ if (left === void 0) return false;
1023
+ const leftType = historyKind(left);
1024
+ if (leftType !== historyKind(right)) return false;
1025
+ switch (leftType) {
1026
+ case "message": return left.type === "message" && right.type === "message" && left.role === right.role && historyContentMatches(left.content, right.content);
1027
+ case "function_call": {
1028
+ const leftCall = functionCallFields(left);
1029
+ const rightCall = functionCallFields(right);
1030
+ return leftCall !== void 0 && rightCall !== void 0 && leftCall.name === rightCall.name && leftCall.input === rightCall.input && leftCall.call_id === rightCall.call_id;
1031
+ }
1032
+ case "function_call_output": {
1033
+ const leftOutput = functionCallOutputFields(left);
1034
+ const rightOutput = functionCallOutputFields(right);
1035
+ return leftOutput !== void 0 && rightOutput !== void 0 && leftOutput.call_id === rightOutput.call_id && historyToolOutputMatches(leftOutput.output, rightOutput.output);
1036
+ }
1037
+ case "compaction": return left.type === "compaction" && right.type === "compaction" && left.encrypted_content === right.encrypted_content;
1038
+ default: return false;
1039
+ }
1040
+ }
1041
+ function historyKind(item) {
1042
+ if (item.type === "custom_tool_call") return "function_call";
1043
+ if (item.type === "custom_tool_call_output") return "function_call_output";
1044
+ return item.type;
1045
+ }
1046
+ function functionCallFields(item) {
1047
+ if (item.type === "function_call") return {
1048
+ name: item.name,
1049
+ input: item.arguments,
1050
+ call_id: item.call_id
1051
+ };
1052
+ if (item.type === "custom_tool_call") return {
1053
+ name: item.name,
1054
+ input: item.input,
1055
+ call_id: item.call_id
1056
+ };
1057
+ }
1058
+ function functionCallOutputFields(item) {
1059
+ if (item.type === "function_call_output" || item.type === "custom_tool_call_output") return {
1060
+ call_id: item.call_id,
1061
+ output: item.output
1062
+ };
1063
+ }
1064
+ function validateRetainedApplyPatchPairs(items) {
1065
+ const callIds = /* @__PURE__ */ new Set();
1066
+ const resultIds = /* @__PURE__ */ new Set();
1067
+ for (const item of items) {
1068
+ if (item.type === "custom_tool_call" && item.name === "apply_patch") {
1069
+ if (callIds.has(item.call_id)) throw new Error(`Nanocodex compaction retained duplicate apply_patch call ${JSON.stringify(item.call_id)}`);
1070
+ callIds.add(item.call_id);
1071
+ continue;
1072
+ }
1073
+ if (item.type !== "custom_tool_call_output") continue;
1074
+ const namedPatch = item.name === APPLY_PATCH_NAME;
1075
+ const matchesPatchCall = callIds.has(item.call_id);
1076
+ if (!namedPatch && !matchesPatchCall) continue;
1077
+ if (!matchesPatchCall) throw new Error(`Nanocodex compaction retained apply_patch output has no matching call ${JSON.stringify(item.call_id)}`);
1078
+ if (item.name !== void 0 && item.name !== "apply_patch") throw new Error(`Nanocodex compaction retained apply_patch output has a mismatched name ${JSON.stringify(item.call_id)}`);
1079
+ if (resultIds.has(item.call_id)) throw new Error(`Nanocodex compaction retained duplicate apply_patch output ${JSON.stringify(item.call_id)}`);
1080
+ resultIds.add(item.call_id);
1081
+ }
1082
+ for (const callId of callIds) if (!resultIds.has(callId)) throw new Error(`Nanocodex compaction retained apply_patch call without a result ${JSON.stringify(callId)}`);
1083
+ return callIds;
1084
+ }
1085
+ function codeDispatchAssociations(session) {
1086
+ const bySubCallId = /* @__PURE__ */ new Map();
1087
+ for (const event of session.snapshotEvents()) {
1088
+ if (event.type !== "tool/code-dispatch-start" && event.type !== "tool/code-dispatch") continue;
1089
+ const subCallId = String(event.data.subCallId);
1090
+ const state = bySubCallId.get(subCallId) ?? {
1091
+ starts: [],
1092
+ settles: []
1093
+ };
1094
+ if (event.type === "tool/code-dispatch-start") state.starts.push({
1095
+ seq: event.seq,
1096
+ data: event.data
1097
+ });
1098
+ else state.settles.push({
1099
+ seq: event.seq,
1100
+ data: event.data
1101
+ });
1102
+ bySubCallId.set(subCallId, state);
1103
+ }
1104
+ const associations = /* @__PURE__ */ new Map();
1105
+ const associationFor = (parentCallId) => {
1106
+ const existing = associations.get(parentCallId);
1107
+ if (existing !== void 0) return existing;
1108
+ const created = {
1109
+ children: [],
1110
+ invalid: false
1111
+ };
1112
+ associations.set(parentCallId, created);
1113
+ return created;
1114
+ };
1115
+ for (const state of bySubCallId.values()) {
1116
+ const parentCallIds = /* @__PURE__ */ new Set([...state.starts.map((event) => String(event.data.parentCallId)), ...state.settles.map((event) => String(event.data.parentCallId))]);
1117
+ for (const parentCallId of parentCallIds) {
1118
+ const association = associationFor(parentCallId);
1119
+ const start = state.starts[0];
1120
+ const settle = state.settles[0];
1121
+ if (state.starts.length !== 1 || state.settles.length !== 1 || start === void 0 || settle === void 0 || String(start.data.parentCallId) !== parentCallId || String(settle.data.parentCallId) !== parentCallId || String(start.data.rootCallId) !== String(settle.data.rootCallId) || settle.seq <= start.seq || start.data.name !== settle.data.name || !isDeepStrictEqual(start.data.arguments, settle.data.arguments)) {
1122
+ association.invalid = true;
1123
+ continue;
1124
+ }
1125
+ association.children.push({
1126
+ startSeq: start.seq,
1127
+ settleSeq: settle.seq,
1128
+ subCallId: String(settle.data.subCallId),
1129
+ name: settle.data.name,
1130
+ arguments: settle.data.arguments,
1131
+ isError: settle.data.isError,
1132
+ content: settle.data.content
1133
+ });
1134
+ }
1135
+ }
1136
+ for (const association of associations.values()) association.children.sort((left, right) => left.startSeq - right.startSeq);
1137
+ return associations;
1138
+ }
1139
+ function dshToolEvidence(session) {
1140
+ const evidence = /* @__PURE__ */ new Map();
1141
+ const forCall = (callId) => {
1142
+ const existing = evidence.get(callId);
1143
+ if (existing !== void 0) return existing;
1144
+ const created = {
1145
+ calls: [],
1146
+ results: []
1147
+ };
1148
+ evidence.set(callId, created);
1149
+ return created;
1150
+ };
1151
+ for (const event of session.snapshotEvents()) if (event.type === "tool/call") forCall(String(event.data.callId)).calls.push({
1152
+ name: event.data.name,
1153
+ arguments: event.data.arguments
1154
+ });
1155
+ else if (event.type === "tool/result") {
1156
+ const block = event.data.message.content[0];
1157
+ forCall(String(event.data.message.source.callId)).results.push({
1158
+ isError: block?.isError === true,
1159
+ content: block?.content ?? []
1160
+ });
1161
+ }
1162
+ return evidence;
1163
+ }
1164
+ function serializedToolArguments(value) {
1165
+ try {
1166
+ return JSON.stringify(value ?? {}) ?? "{}";
1167
+ } catch {
1168
+ return;
1169
+ }
1170
+ }
1171
+ function codeDispatchCallMatches(projected, projectedIndex, child, evidence) {
1172
+ const call = projected[projectedIndex]?.item;
1173
+ const callFields = call === void 0 ? void 0 : functionCallFields(call);
1174
+ const tool = evidence.get(child.subCallId);
1175
+ const expectedArguments = serializedToolArguments(child.arguments);
1176
+ const expectedInput = child.name === "apply_patch" ? (() => {
1177
+ try {
1178
+ const value = JSON.parse(serializedToolArguments(child.arguments));
1179
+ return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.patch === "string" && Object.keys(value).length === 1 ? value.patch : void 0;
1180
+ } catch {
1181
+ return;
1182
+ }
1183
+ })() : serializedToolArguments(child.arguments);
1184
+ return (call?.type === "function_call" || call?.type === "custom_tool_call") && callFields !== void 0 && callFields.call_id === historyToolCallId(child.subCallId) && callFields.name === child.name && expectedInput !== void 0 && callFields.input === expectedInput && expectedArguments !== void 0 && tool !== void 0 && tool.calls.length === 1 && tool.results.length === 1 && tool.calls[0].name === child.name && tool.calls[0].arguments === expectedArguments;
1185
+ }
1186
+ function codeDispatchResultMatches(projected, projectedIndex, child, evidence) {
1187
+ const output = projected[projectedIndex]?.item;
1188
+ const outputFields = output === void 0 ? void 0 : functionCallOutputFields(output);
1189
+ const tool = evidence.get(child.subCallId);
1190
+ return (output?.type === "function_call_output" || output?.type === "custom_tool_call_output") && outputFields !== void 0 && outputFields.call_id === historyToolCallId(child.subCallId) && tool !== void 0 && tool.calls.length === 1 && tool.results.length === 1 && tool.results[0].isError === child.isError && isDeepStrictEqual(tool.results[0].content, child.content);
1191
+ }
1192
+ function codeDispatchProjectedCount(projected, projectedIndex, association, evidence) {
1193
+ if (association.invalid || association.children.length === 0) throw new Error("Nanocodex compaction retained Code Mode child tool association is incomplete");
1194
+ const expectations = association.children.flatMap((child) => [{
1195
+ seq: child.startSeq,
1196
+ kind: "call",
1197
+ child
1198
+ }, {
1199
+ seq: child.settleSeq,
1200
+ kind: "result",
1201
+ child
1202
+ }]).sort((left, right) => left.seq - right.seq);
1203
+ let index = projectedIndex;
1204
+ for (const expectation of expectations) {
1205
+ if (!(expectation.kind === "call" ? codeDispatchCallMatches(projected, index, expectation.child, evidence) : codeDispatchResultMatches(projected, index, expectation.child, evidence))) throw new Error("Nanocodex compaction retained Code Mode child tool does not match the DSH pair");
1206
+ index += 1;
1207
+ }
1208
+ return index - projectedIndex;
1209
+ }
1210
+ function mergeSupplementaryContext(entries) {
1211
+ const first = entries[0];
1212
+ if (first === void 0 || first.item.type !== "message" || first.item.role !== "user" || first.message.role !== "user" || first.message.source.kind !== "user") return;
1213
+ const pluginMessages = entries.slice(1).map((entry) => entry.message);
1214
+ if (pluginMessages.some((message) => message.role !== "user" || message.source.kind !== "plugin")) return;
1215
+ const text = pluginMessages.map((message) => plainText([message])).filter(Boolean).join("\n\n");
1216
+ if (!text) return void 0;
1217
+ return {
1218
+ ...first.item,
1219
+ content: [...first.item.content, {
1220
+ type: "input_text",
1221
+ text
1222
+ }]
1223
+ };
1224
+ }
1225
+ function isRealUserHistoryItem(entry) {
1226
+ return entry !== void 0 && entry.item.type === "message" && entry.item.role === "user" && entry.message.role === "user" && entry.message.source.kind === "user";
1227
+ }
1228
+ function isPluginUserHistoryItem(entry) {
1229
+ return entry !== void 0 && entry.item.type === "message" && entry.item.role === "user" && entry.message.role === "user" && entry.message.source.kind === "plugin";
1230
+ }
1231
+ function supplementaryGroupEnd(projected, start) {
1232
+ let end = start + 1;
1233
+ while (isPluginUserHistoryItem(projected[end])) end += 1;
1234
+ return end;
1235
+ }
1236
+ function projectedCountForContext(projected, projectedIndex, contextItem) {
1237
+ const candidate = projected[projectedIndex];
1238
+ if (candidate === void 0 || contextItem === void 0) return void 0;
1239
+ if (!isRealUserHistoryItem(candidate)) return historyItemsMatch(contextItem, candidate.item) ? 1 : void 0;
1240
+ const groupEnd = supplementaryGroupEnd(projected, projectedIndex);
1241
+ const merged = mergeSupplementaryContext(projected.slice(projectedIndex, groupEnd));
1242
+ if (merged !== void 0 && historyMessageMatches(contextItem, merged)) return groupEnd - projectedIndex;
1243
+ return historyItemsMatch(contextItem, candidate.item) ? 1 : void 0;
1244
+ }
1245
+ /** The only provider-facing owner in the DSH Nanocodex package. */
1246
+ var NanocodexEngine = class {
1247
+ ctx;
1248
+ checkpoints;
1249
+ quickJsPromise;
1250
+ requestSurfaceGeneration = /* @__PURE__ */ new WeakMap();
1251
+ runtimes = /* @__PURE__ */ new Map();
1252
+ constructor(ctx, checkpoints) {
1253
+ this.ctx = ctx;
1254
+ this.checkpoints = checkpoints;
1255
+ }
1256
+ quickJs() {
1257
+ this.quickJsPromise ??= (async () => {
1258
+ const asyncQuickJsVariant = (await import("@jitl/quickjs-wasmfile-release-asyncify")).default;
1259
+ const quickJs = await newQuickJSAsyncWASMModuleFromVariant(asyncQuickJsVariant);
1260
+ return createQuickJsEvaluator(quickJs);
1261
+ })();
1262
+ return this.quickJsPromise;
1263
+ }
1264
+ async run(agent, previousMessages, messages, assembly, turn, step, signal, advanceStep) {
1265
+ signal.throwIfAborted();
1266
+ const route = await resolveNanocodexRoute(this.ctx, agent.options);
1267
+ signal.throwIfAborted();
1268
+ const historySeed = await buildHistorySeed(previousMessages, this.ctx, signal);
1269
+ const preTurnSurfaceSeqs = surfacePrefix(agent.session, previousMessages.length);
1270
+ const input = await buildPromptInput(messages.filter((message) => message.source.kind !== "plugin"), this.ctx, signal);
1271
+ const system = renderPrompt(assembly);
1272
+ const quickJs = await this.quickJs();
1273
+ signal.throwIfAborted();
1274
+ const output = new NanocodexOutput(agent, route.provider, route.model, turn, step, advanceStep);
1275
+ let projection = Promise.resolve();
1276
+ const active = {
1277
+ output,
1278
+ drain: () => projection
1279
+ };
1280
+ const visibleToolNames = new Set(assembly.tools.map((tool) => tool.name));
1281
+ const toolKey = JSON.stringify(assembly.tools);
1282
+ const key = runtimeKey(agent);
1283
+ let runtime = this.runtimes.get(key);
1284
+ if (runtime !== void 0 && (runtime.agent !== agent || runtime.routeKey !== routeKey(route, agent) || runtime.system !== system || runtime.toolKey !== toolKey || runtime.boundary === void 0 || !boundaryMatches(runtime.boundary, agent.session, previousMessages))) {
1285
+ await this.closeRuntime(runtime);
1286
+ this.runtimes.delete(key);
1287
+ runtime = void 0;
1288
+ }
1289
+ if (runtime === void 0) {
1290
+ const bridge = createToolBridge({
1291
+ tools: this.ctx.tools,
1292
+ agent,
1293
+ signal,
1294
+ customDefinitions: RAW_APPLICATION_DEFINITIONS,
1295
+ callbacks: {
1296
+ onCall: async ({ id, name, arguments: argumentsText, parentCallId }) => {
1297
+ const current = runtime?.active;
1298
+ if (current === void 0) throw new Error("Nanocodex tool call has no active DSH step");
1299
+ await current.drain();
1300
+ if (parentCallId !== void 0) return void 0;
1301
+ return agent.session.append("tool/call", {
1302
+ ...current.output.coordinates,
1303
+ callId: ToolCallId(id),
1304
+ name,
1305
+ arguments: argumentsText
1306
+ }).seq;
1307
+ },
1308
+ onResult: async ({ id, callSeq, result }) => {
1309
+ const current = runtime?.active;
1310
+ if (current !== void 0) await current.drain();
1311
+ if (callSeq !== void 0) {
1312
+ const call = agent.session.eventAt(callSeq);
1313
+ if (call?.type !== "tool/call") throw new Error("Nanocodex tool result has no durable call");
1314
+ this.appendToolResult(agent, call.data.turn, call.data.step, id, callSeq, result);
1315
+ }
1316
+ for (const context of result.additionalContexts ?? []) agent.inject(context);
1317
+ }
1318
+ }
1319
+ }).filter((tool) => visibleToolNames.has(tool.name));
1320
+ const checkpoint = findMatchingCheckpoint(this.checkpoints, agent.session, previousMessages, route.provider, route.model);
1321
+ runtime = {
1322
+ agent,
1323
+ nodeAgent: await this.createNodeAgent(agent, route, historySeed, system, bridge, quickJs, checkpoint?.snapshot),
1324
+ provider: route.provider,
1325
+ model: route.model,
1326
+ routeKey: routeKey(route, agent),
1327
+ system,
1328
+ toolKey,
1329
+ bridge,
1330
+ consumedCompactionRevisions: /* @__PURE__ */ new Set()
1331
+ };
1332
+ this.runtimes.set(key, runtime);
1333
+ }
1334
+ runtime.active = active;
1335
+ const automaticCompactions = [];
1336
+ this.appendRequestFacts(agent, route.provider, route.model, agent.options.reasoningEffort, system, runtime.bridge);
1337
+ const nodeAgent = runtime.nodeAgent;
1338
+ const watcher = nodeAgent.events.watch();
1339
+ const removeListener = watcher.onEvent((event) => {
1340
+ projection = projection.then(async () => {
1341
+ await output.accept(event);
1342
+ const replaced = parseCompactionReplacedEvent(event);
1343
+ if (replaced !== void 0 && replaced.trigger === "automatic" && this.consumeCompactionOutcome(runtime, replaced)) automaticCompactions.push({
1344
+ outcome: replaced,
1345
+ phase: replaced.phase,
1346
+ afterModelCallIndex: replaced.after_model_call_index,
1347
+ admittedSurfaceSeqs: replaced.phase === "pre_turn" ? preTurnSurfaceSeqs : [...agent.session.surface.nodes]
1348
+ });
1349
+ });
1350
+ projection.catch(() => modelTurn.cancel().catch(() => void 0));
1351
+ });
1352
+ const removeTransportFallback = observeTransportFallback(this.ctx, String(agent.id), nodeAgent);
1353
+ const modelTurn = nodeAgent.turn.prompt({
1354
+ input,
1355
+ ...supplementaryContext(messages) ? { supplementaryContext: supplementaryContext(messages) } : {}
1356
+ });
1357
+ const abort = () => {
1358
+ modelTurn.cancel().catch(() => void 0);
1359
+ };
1360
+ signal.addEventListener("abort", abort, { once: true });
1361
+ let discardRuntime = false;
1362
+ try {
1363
+ await modelTurn.accepted();
1364
+ const result = await modelTurn.result();
1365
+ try {
1366
+ await projection;
1367
+ signal.throwIfAborted();
1368
+ output.finish(result.finalMessage);
1369
+ const snapshot = await result.snapshot();
1370
+ return {
1371
+ provider: route.provider,
1372
+ model: route.model,
1373
+ snapshot,
1374
+ automaticCompactions
1375
+ };
1376
+ } finally {
1377
+ result.dispose();
1378
+ }
1379
+ } catch (error) {
1380
+ discardRuntime = true;
1381
+ let failure = error;
1382
+ try {
1383
+ await projection;
1384
+ } catch (projectionError) {
1385
+ failure = projectionError;
1386
+ }
1387
+ output.interrupt(failure);
1388
+ throw failure;
1389
+ } finally {
1390
+ signal.removeEventListener("abort", abort);
1391
+ removeListener();
1392
+ watcher.off();
1393
+ removeTransportFallback();
1394
+ modelTurn.dispose();
1395
+ runtime.active = void 0;
1396
+ if (discardRuntime) {
1397
+ this.runtimes.delete(key);
1398
+ await this.closeRuntime(runtime).catch(() => void 0);
1399
+ }
1400
+ }
1401
+ }
1402
+ /** Release the retained Nanocodex host after DSH has drained the Agent. */
1403
+ async dispose(agent) {
1404
+ const key = runtimeKey(agent);
1405
+ const runtime = this.runtimes.get(key);
1406
+ if (runtime === void 0) return;
1407
+ this.runtimes.delete(key);
1408
+ await this.closeRuntime(runtime);
1409
+ }
1410
+ /** Invalidate a runtime whose private context no longer matches DSH. */
1411
+ async invalidate(agent) {
1412
+ await this.dispose(agent);
1413
+ }
1414
+ /** Mark the retained runtime at the current authoritative DSH surface. */
1415
+ markSurfaceBoundary(agent) {
1416
+ const runtime = this.runtimes.get(runtimeKey(agent));
1417
+ if (runtime !== void 0) runtime.boundary = makeBoundary(agent.session, agent.session.deriveMessages());
1418
+ }
1419
+ consumeCompactionOutcome(runtime, outcome) {
1420
+ if (runtime.consumedCompactionRevisions.has(outcome.revision)) return false;
1421
+ runtime.consumedCompactionRevisions.add(outcome.revision);
1422
+ return true;
1423
+ }
1424
+ runtimeFor(agent) {
1425
+ const runtime = this.runtimes.get(runtimeKey(agent));
1426
+ if (runtime === void 0 || runtime.agent !== agent) throw new Error("Nanocodex compaction requires the agent's current live runtime");
1427
+ return runtime;
1428
+ }
1429
+ /**
1430
+ * Map Nanocodex's exact retained-tail identities to the current DSH surface.
1431
+ * The engine owns the selection; DSH never guesses from a fixed message count.
1432
+ */
1433
+ async mapCompactionOutcome(agent, outcome, signal, mapping) {
1434
+ signal.throwIfAborted();
1435
+ const range = outcome.replaced_history;
1436
+ const retained = outcome.retained_tail;
1437
+ if (range.start !== 0 || range.end <= 0 || retained.length === 0 || retained[0]?.index !== range.end || retained.some((item, index) => item.index !== range.end + index)) throw new Error("Nanocodex compaction returned a non-contiguous retained history boundary");
1438
+ const nodes = [...agent.session.surface.nodes];
1439
+ const messages = agent.session.deriveMessages();
1440
+ if (nodes.length !== messages.length) throw new Error("Nanocodex compaction cannot map a DSH surface with mismatched messages");
1441
+ const admittedSurfaceSeqs = mapping === void 0 ? void 0 : new Set(mapping.admittedSurfaceSeqs);
1442
+ const historyProjection = await buildHistoryProjection(messages, this.ctx, signal);
1443
+ const surfaceIndexByMessageId = new Map(messages.map((message, index) => [String(message.id), index]));
1444
+ const projected = [];
1445
+ for (const projectedItem of historyProjection.items) {
1446
+ signal.throwIfAborted();
1447
+ const surfaceIndex = surfaceIndexByMessageId.get(String(projectedItem.message.id));
1448
+ if (surfaceIndex === void 0) continue;
1449
+ const surfaceSeq = nodes[surfaceIndex];
1450
+ if (surfaceSeq === void 0 || admittedSurfaceSeqs !== void 0 && !admittedSurfaceSeqs.has(surfaceSeq)) continue;
1451
+ projected.push({
1452
+ surfaceIndex,
1453
+ message: projectedItem.message,
1454
+ item: projectedItem.item
1455
+ });
1456
+ }
1457
+ const contextHistory = outcome.context.history;
1458
+ const retainedContextStart = contextHistory.length - retained.length;
1459
+ if (retainedContextStart < 0) throw new Error("Nanocodex compaction context does not contain the retained tail");
1460
+ const retainedContext = contextHistory.slice(retainedContextStart);
1461
+ if (retainedContext.length !== retained.length) throw new Error("Nanocodex compaction context does not contain the retained tail");
1462
+ const directPatchCallIds = validateRetainedApplyPatchPairs(retainedContext);
1463
+ const surfaceCustomCallIds = new Set(projected.flatMap(({ item }) => item.type === "custom_tool_call" ? [item.call_id] : []));
1464
+ let firstIndex = -1;
1465
+ const firstIdentityPosition = retained.findIndex((identity, index) => {
1466
+ const contextItem = retainedContext[index];
1467
+ return identity.kind === "message" && contextItem?.type === "message" && contextItem.role === "user";
1468
+ });
1469
+ const firstIdentity = firstIdentityPosition < 0 ? void 0 : retained[firstIdentityPosition];
1470
+ const firstContext = firstIdentityPosition < 0 ? void 0 : retainedContext[firstIdentityPosition];
1471
+ if (firstIdentity === void 0 || firstContext === void 0 || !projectedIdentityMatches(firstIdentity, firstContext)) throw new Error("Nanocodex compaction retained tail does not begin at a real DSH user message");
1472
+ for (let index = projected.length - 1; index >= 0; index -= 1) {
1473
+ const candidate = projected[index];
1474
+ if (!isRealUserHistoryItem(candidate) || !projectedIdentityMatches(firstIdentity, candidate.item)) continue;
1475
+ if (projectedCountForContext(projected, index, firstContext) !== void 0) {
1476
+ firstIndex = index;
1477
+ break;
1478
+ }
1479
+ }
1480
+ const first = firstIndex < 0 ? void 0 : projected[firstIndex];
1481
+ if (first === void 0 || !projectedIdentityMatches(firstIdentity, first.item) || first.surfaceIndex === 0 || !isRealUserHistoryItem(first)) throw new Error("Nanocodex compaction retained tail does not begin at a real DSH user message");
1482
+ const codeDispatches = codeDispatchAssociations(agent.session);
1483
+ const toolEvidence = dshToolEvidence(agent.session);
1484
+ let projectedIndex = firstIndex;
1485
+ let pendingCustomCallId;
1486
+ for (let retainedPosition = 0; retainedPosition < retained.length; retainedPosition += 1) {
1487
+ const identity = retained[retainedPosition];
1488
+ const contextItem = retainedContext[retainedPosition];
1489
+ const contextCallId = "call_id" in contextItem ? contextItem.call_id : void 0;
1490
+ const surfaceCustom = contextCallId !== void 0 && (directPatchCallIds.has(contextCallId) || surfaceCustomCallIds.has(contextCallId));
1491
+ const dshBacked = identity.kind === "message" || identity.kind === "function_call" || identity.kind === "function_call_output" || surfaceCustom;
1492
+ const engineTool = !surfaceCustom && (identity.kind === "custom_tool_call" || identity.kind === "custom_tool_call_output");
1493
+ if (!dshBacked && !engineTool) {
1494
+ if (pendingCustomCallId !== void 0) throw new Error("Nanocodex compaction retained Code Mode tool pair is incomplete");
1495
+ continue;
1496
+ }
1497
+ if (!projectedIdentityMatches(identity, retainedContext[retainedPosition])) throw new Error("Nanocodex compaction retained history does not match its public context");
1498
+ if (engineTool) {
1499
+ if (identity.kind === "custom_tool_call") {
1500
+ if (pendingCustomCallId !== void 0) throw new Error("Nanocodex compaction retained Code Mode tool pair is incomplete");
1501
+ if (typeof identity.call_id === "string") {
1502
+ const association = codeDispatches.get(identity.call_id);
1503
+ if (association !== void 0) projectedIndex += codeDispatchProjectedCount(projected, projectedIndex, association, toolEvidence);
1504
+ }
1505
+ pendingCustomCallId = identity.call_id;
1506
+ continue;
1507
+ }
1508
+ if (pendingCustomCallId !== identity.call_id) throw new Error("Nanocodex compaction retained Code Mode tool pair is incomplete");
1509
+ pendingCustomCallId = void 0;
1510
+ continue;
1511
+ }
1512
+ if (pendingCustomCallId !== void 0) throw new Error("Nanocodex compaction retained Code Mode tool pair is incomplete");
1513
+ const candidate = projected[projectedIndex];
1514
+ const projectedCount = candidate === void 0 ? void 0 : projectedCountForContext(projected, projectedIndex, retainedContext[retainedPosition]);
1515
+ if (candidate === void 0 || !projectedIdentityMatches(identity, candidate.item)) throw new Error("Nanocodex compaction retained history order does not match the active DSH surface");
1516
+ if (candidate.surfaceIndex < first.surfaceIndex) throw new Error("Nanocodex compaction retained tail reaches before its DSH user boundary");
1517
+ if (projectedCount === void 0) throw new Error("Nanocodex compaction retained history order does not match the active DSH surface");
1518
+ projectedIndex += projectedCount;
1519
+ }
1520
+ if (pendingCustomCallId !== void 0) throw new Error("Nanocodex compaction retained Code Mode tool pair is incomplete");
1521
+ if (projectedIndex !== projected.length) throw new Error("Nanocodex compaction retained tail omits an active DSH history item");
1522
+ const start = nodes[0];
1523
+ const end = nodes[first.surfaceIndex - 1];
1524
+ if (start === void 0 || end === void 0 || !toolPairingBalancedBefore(agent.session, start) || !toolPairingBalancedAfter(agent.session, end)) throw new Error("Nanocodex compaction retained boundary splits a DSH tool exchange");
1525
+ return {
1526
+ shadowedRange: {
1527
+ start,
1528
+ end
1529
+ },
1530
+ shadowedSeqs: nodes.slice(0, first.surfaceIndex)
1531
+ };
1532
+ }
1533
+ async resolveCompactionInstruction(agent, route, _context, signal) {
1534
+ signal.throwIfAborted();
1535
+ const sessionId = String(agent.session.id);
1536
+ const options = {
1537
+ provider: route.provider,
1538
+ model: route.model,
1539
+ messages: [createUserMessage({
1540
+ content: [{
1541
+ type: "text",
1542
+ text: DEFAULT_COMPACTION_INSTRUCTION
1543
+ }],
1544
+ source: {
1545
+ kind: "plugin",
1546
+ plugin: "dsh-compaction-basic"
1547
+ }
1548
+ })],
1549
+ ...agent.options.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.options.reasoningEffort },
1550
+ sessionId,
1551
+ purpose: "compaction",
1552
+ signal
1553
+ };
1554
+ const stream = agent.ctx.waterfall(agent.ctx.llm, "llm/stream", options, emptyLlmStream);
1555
+ for await (const _chunk of stream) signal.throwIfAborted();
1556
+ signal.throwIfAborted();
1557
+ const instruction = plainText(options.messages).trim();
1558
+ if (!instruction) throw new Error("Nanocodex compaction instruction is empty");
1559
+ return instruction;
1560
+ }
1561
+ /** Persist the exact successful engine snapshot at the current DSH surface. */
1562
+ async persistCheckpoint(agent, provider, model, snapshot, signal) {
1563
+ signal.throwIfAborted();
1564
+ const session = agent.session;
1565
+ const boundary = makeBoundary(session, session.deriveMessages());
1566
+ const checkpoint = {
1567
+ version: 1,
1568
+ provider,
1569
+ model,
1570
+ boundary,
1571
+ snapshot
1572
+ };
1573
+ await this.checkpoints.put(session.id, checkpoint);
1574
+ const context = {
1575
+ provider,
1576
+ model,
1577
+ contextWindow: MODEL_CONTEXT_WINDOW
1578
+ };
1579
+ session.append("request/context", context);
1580
+ await this.ctx.sessions.flush(session);
1581
+ const runtime = this.runtimes.get(runtimeKey(agent));
1582
+ if (runtime !== void 0) runtime.boundary = boundary;
1583
+ }
1584
+ async createNodeAgent(agent, route, historySeed, system, bridge, quickJs, resume) {
1585
+ const base = {
1586
+ transport: Transport.openAi({
1587
+ apiKey: route.apiKey,
1588
+ ...route.apiBaseUrl ? { apiBaseUrl: route.apiBaseUrl } : {},
1589
+ ...route.websocketUrl ? { websocketUrl: route.websocketUrl } : {},
1590
+ websocketWarmup: true
1591
+ }),
1592
+ model: route.model,
1593
+ ...route.thinking ? { thinking: route.thinking } : {},
1594
+ ...agent.options.reasoningEffort === "pro" ? { reasoningMode: "pro" } : {},
1595
+ sessionId: normalizeNanocodexSessionId(String(agent.id)),
1596
+ ...system ? { additionalInstructions: system } : {},
1597
+ resolveCompactionInstruction: (context, signal) => this.resolveCompactionInstruction(agent, route, context, signal),
1598
+ codeEvaluator: quickJs,
1599
+ tools: bridge,
1600
+ subagents: false,
1601
+ toolMode: "code"
1602
+ };
1603
+ if (resume !== void 0) try {
1604
+ return await Agent.create({
1605
+ ...base,
1606
+ resume
1607
+ });
1608
+ } catch {}
1609
+ return Agent.create({
1610
+ ...base,
1611
+ ...historySeed.history.length > 0 || historySeed.continuitySummary !== void 0 ? { historySeed } : {}
1612
+ });
1613
+ }
1614
+ async closeRuntime(runtime) {
1615
+ await runtime.nodeAgent.session.shutdown().catch(() => void 0);
1616
+ runtime.nodeAgent.dispose();
1617
+ }
1618
+ /** Run custom compaction on the already-live Nanocodex runtime. */
1619
+ async compact(agent, signal) {
1620
+ signal.throwIfAborted();
1621
+ const runtime = this.runtimeFor(agent);
1622
+ const watcher = runtime.nodeAgent.events.watch();
1623
+ let eventOutcome;
1624
+ let installed = false;
1625
+ let succeeded = false;
1626
+ const removeListener = watcher.onEvent((event) => {
1627
+ const replaced = parseCompactionReplacedEvent(event);
1628
+ if (replaced === void 0 || replaced.trigger !== "manual") return;
1629
+ installed = true;
1630
+ if (this.consumeCompactionOutcome(runtime, replaced)) eventOutcome = replaced;
1631
+ });
1632
+ const removeTransportFallback = observeTransportFallback(this.ctx, String(agent.id), runtime.nodeAgent);
1633
+ let shutdownRequested = false;
1634
+ const abort = () => {
1635
+ shutdownRequested = true;
1636
+ runtime.nodeAgent.session.shutdown().catch(() => void 0);
1637
+ };
1638
+ signal.addEventListener("abort", abort, { once: true });
1639
+ try {
1640
+ signal.throwIfAborted();
1641
+ const returned = await runtime.nodeAgent.session.compact();
1642
+ signal.throwIfAborted();
1643
+ if (returned === null) throw new Error("Nanocodex manual compaction did not produce one matching replacement event");
1644
+ const snapshot = await runtime.nodeAgent.session.snapshot();
1645
+ signal.throwIfAborted();
1646
+ if (eventOutcome === void 0 || returned.revision !== eventOutcome.revision) throw new Error("Nanocodex manual compaction did not produce one matching replacement event");
1647
+ const summary = returned.summary?.trim();
1648
+ if (!summary) throw new Error("Nanocodex compaction returned no summary text");
1649
+ const selection = await this.mapCompactionOutcome(agent, returned, signal);
1650
+ succeeded = true;
1651
+ return {
1652
+ summary,
1653
+ outcome: returned,
1654
+ selection,
1655
+ snapshot,
1656
+ provider: runtime.provider,
1657
+ model: runtime.model
1658
+ };
1659
+ } finally {
1660
+ signal.removeEventListener("abort", abort);
1661
+ removeListener();
1662
+ watcher.off();
1663
+ removeTransportFallback();
1664
+ if (shutdownRequested || installed && !succeeded) await this.invalidate(agent).catch(() => void 0);
1665
+ }
1666
+ }
1667
+ appendRequestFacts(agent, provider, model, reasoningEffort, system, bridge) {
1668
+ const tools = bridge.map((tool) => ({
1669
+ name: tool.name,
1670
+ description: tool.description,
1671
+ parameters: tool.parameters ?? {
1672
+ type: "object",
1673
+ properties: {},
1674
+ additionalProperties: false
1675
+ }
1676
+ }));
1677
+ const header = canonicalHeader({
1678
+ config: {
1679
+ provider,
1680
+ model,
1681
+ ...reasoningEffort === void 0 ? {} : { reasoningEffort }
1682
+ },
1683
+ ...system ? { system } : {},
1684
+ ...tools.length > 0 ? { tools } : {}
1685
+ });
1686
+ const session = agent.session;
1687
+ const baseline = session.requestHeader();
1688
+ const hasLiveHeader = session.snapshotEvents(session.firstLiveSeq).some((event) => event.type === "request/header");
1689
+ const previousGeneration = this.requestSurfaceGeneration.get(session);
1690
+ const startsSeries = previousGeneration !== void 0 && previousGeneration !== session.surface.replaceGeneration;
1691
+ if (!hasLiveHeader) session.append("request/header", {
1692
+ header,
1693
+ reason: baseline === void 0 ? "initial" : "resume"
1694
+ });
1695
+ else if (baseline === void 0 || !headerEquals(baseline, header)) session.append("request/header", {
1696
+ header,
1697
+ reason: "change",
1698
+ ...startsSeries ? { startsSeries: true } : {}
1699
+ });
1700
+ else if (startsSeries) session.append("request/header", {
1701
+ header,
1702
+ reason: "series"
1703
+ });
1704
+ this.requestSurfaceGeneration.set(session, session.surface.replaceGeneration);
1705
+ const requestContext = {
1706
+ provider,
1707
+ model,
1708
+ contextWindow: MODEL_CONTEXT_WINDOW
1709
+ };
1710
+ const previousContext = session.requestContext();
1711
+ if (previousContext?.provider !== requestContext.provider || previousContext.model !== requestContext.model || previousContext.contextWindow !== requestContext.contextWindow) session.append("request/context", requestContext);
1712
+ }
1713
+ appendToolResult(agent, turn, step, id, callSeq, result) {
1714
+ const message = createToolResultMessage({
1715
+ callId: ToolCallId(id),
1716
+ content: result.content,
1717
+ isError: result.isError
1718
+ });
1719
+ agent.session.append("tool/result", {
1720
+ turn,
1721
+ step,
1722
+ message,
1723
+ ...result.error?.info ? { error: result.error.info } : {},
1724
+ ...result.meta !== void 0 ? { meta: result.meta } : {}
1725
+ }, {
1726
+ surfaceOp: "append",
1727
+ sourceEventSeqs: [callSeq]
1728
+ });
1729
+ }
1730
+ };
1731
+ //#endregion
1732
+ //#region src/compaction-engine.ts
1733
+ function errorText(error) {
1734
+ return error instanceof Error ? error.message : String(error);
1735
+ }
1736
+ function supportedPrefix(session) {
1737
+ const nodes = [...session.surface.nodes];
1738
+ const messages = session.deriveMessages();
1739
+ if (nodes.length !== messages.length) throw new Error("Nanocodex compaction cannot select a mismatched DSH surface");
1740
+ let retainedIndex = -1;
1741
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
1742
+ const message = messages[index];
1743
+ if (message?.role === "user" && message.source.kind === "user") {
1744
+ retainedIndex = index;
1745
+ break;
1746
+ }
1747
+ }
1748
+ if (retainedIndex <= 0) return null;
1749
+ const start = nodes[0];
1750
+ const end = nodes[retainedIndex - 1];
1751
+ if (start === void 0 || end === void 0 || !toolPairingBalancedBefore(session, start) || !toolPairingBalancedAfter(session, end)) throw new Error("Nanocodex compaction prefix would split a DSH tool exchange");
1752
+ return {
1753
+ start,
1754
+ end,
1755
+ shadowedSeqs: nodes.slice(0, retainedIndex)
1756
+ };
1757
+ }
1758
+ function sameSelection(left, right) {
1759
+ return left.start === right.shadowedRange.start && left.end === right.shadowedRange.end && left.shadowedSeqs.length === right.shadowedSeqs.length && left.shadowedSeqs.every((seq, index) => right.shadowedSeqs[index] === seq);
1760
+ }
1761
+ function openTurn(session) {
1762
+ let turn = null;
1763
+ for (const event of session.snapshotEvents()) {
1764
+ if (event.type === "turn/start") turn = event.data.turn;
1765
+ if (event.type === "turn/end" && turn === event.data.turn) turn = null;
1766
+ }
1767
+ return turn;
1768
+ }
1769
+ /**
1770
+ * Minimal DSH compaction owner. The checkpoint is model-private and lands only
1771
+ * as the normal non-expandable compact checkpoint message. The selected range
1772
+ * and all transaction markers remain fully durable in DSH's event log.
1773
+ */
1774
+ var NanocodexCompactionEngine = class extends CompactionEngine {
1775
+ engine;
1776
+ constructor(ctx, engine) {
1777
+ super(ctx);
1778
+ this.engine = engine;
1779
+ }
1780
+ async compactIfNeeded(agent, _trigger, signal) {
1781
+ signal.throwIfAborted();
1782
+ return null;
1783
+ }
1784
+ async compactNow(agent, signal, sourceCommandId) {
1785
+ signal.throwIfAborted();
1786
+ let cancelled = false;
1787
+ let cancellationReason;
1788
+ try {
1789
+ return await agent.runMaintenance(async (agentSignal) => {
1790
+ const operationSignal = AbortSignal.any([agentSignal, signal]);
1791
+ operationSignal.throwIfAborted();
1792
+ const selection = supportedPrefix(agent.session);
1793
+ if (selection === null) return null;
1794
+ let committed = false;
1795
+ try {
1796
+ const result = await this.compactSelection(agent, selection, operationSignal, sourceCommandId);
1797
+ committed = true;
1798
+ await this.ctx.sessions.flush(agent.session);
1799
+ return result;
1800
+ } catch (error) {
1801
+ if (committed) await this.engine.invalidate(agent).catch(() => void 0);
1802
+ if (operationSignal.aborted) {
1803
+ cancelled = true;
1804
+ cancellationReason = operationSignal.reason;
1805
+ throw operationSignal.reason;
1806
+ }
1807
+ throw new ManualCompactionError("summary", "Nanocodex could not create a continuity checkpoint", { cause: error });
1808
+ }
1809
+ });
1810
+ } catch (error) {
1811
+ if (cancelled || signal.aborted) throw cancellationReason ?? signal.reason;
1812
+ if (error instanceof ManualCompactionError) throw error;
1813
+ throw new ManualCompactionError("busy", "manual compaction requires an idle agent", { cause: error });
1814
+ }
1815
+ }
1816
+ async compactRegion(start, end, agent, signal = new AbortController().signal) {
1817
+ const maintenanceAgent = agent;
1818
+ if (typeof maintenanceAgent.runMaintenance !== "function") throw new ManualCompactionError("busy", "region compaction requires an idle agent maintenance boundary");
1819
+ const initialNodes = [...agent.session.surface.nodes];
1820
+ const initialStartIndex = initialNodes.indexOf(start);
1821
+ const initialEndIndex = initialNodes.indexOf(end);
1822
+ const initialSupported = supportedPrefix(agent.session);
1823
+ if (initialStartIndex < 0 || initialEndIndex < initialStartIndex || initialSupported === null || initialSupported.start !== start || initialSupported.end !== end || initialSupported.shadowedSeqs.length !== initialEndIndex - initialStartIndex + 1 || initialSupported.shadowedSeqs.some((seq, index) => initialNodes[initialStartIndex + index] !== seq)) throw new ManualCompactionError("changed", "Nanocodex compactRegion only supports the current prefix before the latest real user tail");
1824
+ let cancelled = false;
1825
+ let cancellationReason;
1826
+ try {
1827
+ return await maintenanceAgent.runMaintenance(async (agentSignal) => {
1828
+ const operationSignal = AbortSignal.any([agentSignal, signal]);
1829
+ operationSignal.throwIfAborted();
1830
+ const nodes = [...agent.session.surface.nodes];
1831
+ const startIndex = nodes.indexOf(start);
1832
+ const endIndex = nodes.indexOf(end);
1833
+ if (startIndex < 0 || endIndex < 0 || startIndex > endIndex) throw new Error("Nanocodex compaction range is not a current surface span");
1834
+ const supported = supportedPrefix(agent.session);
1835
+ if (supported === null || supported.start !== start || supported.end !== end || supported.shadowedSeqs.length !== endIndex - startIndex + 1 || supported.shadowedSeqs.some((seq, index) => nodes[startIndex + index] !== seq)) throw new ManualCompactionError("changed", "Nanocodex compactRegion only supports the current prefix before the latest real user tail");
1836
+ let committed = false;
1837
+ try {
1838
+ const result = await this.compactSelection(agent, {
1839
+ start,
1840
+ end,
1841
+ shadowedSeqs: nodes.slice(startIndex, endIndex + 1)
1842
+ }, operationSignal);
1843
+ committed = true;
1844
+ await this.ctx.sessions.flush(agent.session);
1845
+ return result;
1846
+ } catch (error) {
1847
+ if (committed) await this.engine.invalidate(agent).catch(() => void 0);
1848
+ if (operationSignal.aborted) {
1849
+ cancelled = true;
1850
+ cancellationReason = operationSignal.reason;
1851
+ throw operationSignal.reason;
1852
+ }
1853
+ throw error;
1854
+ }
1855
+ });
1856
+ } catch (error) {
1857
+ if (cancelled || signal.aborted) throw cancellationReason ?? signal.reason;
1858
+ if (error instanceof ManualCompactionError) throw error;
1859
+ throw new ManualCompactionError("summary", "Nanocodex could not create a continuity checkpoint", { cause: error });
1860
+ }
1861
+ }
1862
+ /**
1863
+ * Commit a summary produced by Nanocodex's automatic private compaction.
1864
+ * Nanocodex owns the model call; DSH still owns the durable surface and the
1865
+ * non-expandable checkpoint visible to Companion.
1866
+ */
1867
+ async commitAutomaticSummary(agent, automatic, signal) {
1868
+ signal.throwIfAborted();
1869
+ const summary = automatic.outcome.summary?.trim() ?? "";
1870
+ if (!summary) {
1871
+ await this.engine.invalidate(agent).catch(() => void 0);
1872
+ throw new Error("Nanocodex automatic compaction returned an empty summary");
1873
+ }
1874
+ let selection;
1875
+ try {
1876
+ selection = await this.engine.mapCompactionOutcome(agent, automatic.outcome, signal, automatic);
1877
+ } catch (error) {
1878
+ await this.engine.invalidate(agent).catch(() => void 0);
1879
+ throw error;
1880
+ }
1881
+ const attempt = this.beginCompaction(agent);
1882
+ let committed = false;
1883
+ try {
1884
+ const result = this.commitSummary(agent, {
1885
+ start: selection.shadowedRange.start,
1886
+ end: selection.shadowedRange.end,
1887
+ shadowedSeqs: [...selection.shadowedSeqs]
1888
+ }, [{
1889
+ type: "text",
1890
+ text: summary
1891
+ }], signal, attempt);
1892
+ committed = true;
1893
+ return result;
1894
+ } catch (error) {
1895
+ this.endFailedCompaction(agent, attempt, error);
1896
+ await this.engine.invalidate(agent).catch(() => void 0);
1897
+ throw error;
1898
+ } finally {
1899
+ if (committed) this.engine.markSurfaceBoundary(agent);
1900
+ }
1901
+ }
1902
+ async compactSelection(agent, selection, signal, sourceCommandId) {
1903
+ signal.throwIfAborted();
1904
+ const attempt = this.beginCompaction(agent, sourceCommandId);
1905
+ let generated = false;
1906
+ try {
1907
+ const result = await this.engine.compact(agent, signal);
1908
+ generated = true;
1909
+ if (!sameSelection(selection, result.selection)) throw new ManualCompactionError("changed", "Nanocodex compacted a different DSH prefix than the requested range");
1910
+ const committed = this.commitSummary(agent, selection, [{
1911
+ type: "text",
1912
+ text: result.summary
1913
+ }], signal, attempt);
1914
+ this.engine.markSurfaceBoundary(agent);
1915
+ await this.engine.persistCheckpoint(agent, result.provider, result.model, result.snapshot, signal);
1916
+ return committed;
1917
+ } catch (error) {
1918
+ this.endFailedCompaction(agent, attempt, error);
1919
+ if (generated) await this.engine.invalidate(agent).catch(() => void 0);
1920
+ throw error;
1921
+ }
1922
+ }
1923
+ beginCompaction(agent, sourceCommandId) {
1924
+ const lifecycle = {
1925
+ compactionId: CompactionId(randomUUID()),
1926
+ ...sourceCommandId === void 0 ? {} : { sourceCommandId },
1927
+ turn: openTurn(agent.session)
1928
+ };
1929
+ const startEvent = agent.session.append("compaction/start", lifecycle);
1930
+ return {
1931
+ ...lifecycle,
1932
+ startSeq: startEvent.seq,
1933
+ ended: false
1934
+ };
1935
+ }
1936
+ endFailedCompaction(agent, attempt, error) {
1937
+ if (attempt.ended) return;
1938
+ try {
1939
+ agent.session.append("compaction/end", {
1940
+ compactionId: attempt.compactionId,
1941
+ ...attempt.sourceCommandId === void 0 ? {} : { sourceCommandId: attempt.sourceCommandId },
1942
+ turn: attempt.turn,
1943
+ error: errorText(error)
1944
+ });
1945
+ } catch {} finally {
1946
+ attempt.ended = true;
1947
+ }
1948
+ }
1949
+ commitSummary(agent, selection, summary, signal, attempt) {
1950
+ signal.throwIfAborted();
1951
+ const provider = agent.options.provider;
1952
+ const model = agent.options.model;
1953
+ if (!provider || !model) throw new Error("Nanocodex compaction requires an explicit provider/model");
1954
+ signal.throwIfAborted();
1955
+ const summaryEvent = agent.session.append("compaction/summary", {
1956
+ compactionId: attempt.compactionId,
1957
+ ...attempt.sourceCommandId === void 0 ? {} : { sourceCommandId: attempt.sourceCommandId },
1958
+ summary,
1959
+ rawOutput: summary,
1960
+ shadowedRange: {
1961
+ start: selection.start,
1962
+ end: selection.end
1963
+ },
1964
+ shadowedSeqs: selection.shadowedSeqs,
1965
+ shadowedTokenCount: Math.max(1, Math.ceil(textOfBlocks(summary).length / 4)),
1966
+ provider,
1967
+ model
1968
+ });
1969
+ const checkpoint = createUserMessage({
1970
+ content: summary,
1971
+ source: compactCheckpointSource(attempt.compactionId, attempt.sourceCommandId)
1972
+ });
1973
+ agent.session.append("user/message", checkpoint, {
1974
+ surfaceOp: {
1975
+ op: "replace",
1976
+ start: selection.start,
1977
+ end: selection.end
1978
+ },
1979
+ sourceEventSeqs: [
1980
+ attempt.startSeq,
1981
+ summaryEvent.seq,
1982
+ ...selection.shadowedSeqs
1983
+ ]
1984
+ });
1985
+ const endEvent = agent.session.append("compaction/end", {
1986
+ compactionId: attempt.compactionId,
1987
+ ...attempt.sourceCommandId === void 0 ? {} : { sourceCommandId: attempt.sourceCommandId },
1988
+ turn: attempt.turn
1989
+ });
1990
+ attempt.ended = true;
1991
+ const shadowedTokenCount = Math.max(1, Math.ceil(textOfBlocks(summary).length / 4));
1992
+ return {
1993
+ compactionId: attempt.compactionId,
1994
+ ...attempt.sourceCommandId === void 0 ? {} : { sourceCommandId: attempt.sourceCommandId },
1995
+ startSeq: attempt.startSeq,
1996
+ summarySeq: summaryEvent.seq,
1997
+ endSeq: endEvent.seq,
1998
+ summary,
1999
+ shadowedRange: {
2000
+ start: selection.start,
2001
+ end: selection.end
2002
+ },
2003
+ shadowedSeqs: [...selection.shadowedSeqs],
2004
+ shadowedTokenCount
2005
+ };
2006
+ }
2007
+ };
2008
+ function textOfBlocks(blocks) {
2009
+ return blocks.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
2010
+ }
2011
+ //#endregion
2012
+ //#region src/agent.ts
2013
+ const RUNTIME_CONTEXT_SOURCE = "@deepseek-ai/dsh-system-prompt";
2014
+ const CLEARED_RUNTIME_CONTEXT = "Current runtime context: none. Earlier runtime-context snapshots no longer apply.";
2015
+ function deferred() {
2016
+ let resolve;
2017
+ let reject;
2018
+ return {
2019
+ promise: new Promise((resolvePromise, rejectPromise) => {
2020
+ resolve = resolvePromise;
2021
+ reject = rejectPromise;
2022
+ }),
2023
+ resolve,
2024
+ reject
2025
+ };
2026
+ }
2027
+ function lastTurn(session) {
2028
+ let result = 0;
2029
+ for (const event of session.snapshotEvents()) if (event.type === "turn/start") result = Math.max(result, event.data.turn);
2030
+ return result;
2031
+ }
2032
+ function cancelCause(signal) {
2033
+ const reason = signal.reason;
2034
+ if (reason && typeof reason === "object" && "kind" in reason && (reason.kind === "user" || reason.kind === "parent" || reason.kind === "disposed" || reason.kind === "hook")) return reason;
2035
+ return {
2036
+ kind: "hook",
2037
+ reason: reason instanceof Error ? reason.message : String(reason ?? "aborted")
2038
+ };
2039
+ }
2040
+ function errorInfo(error) {
2041
+ return {
2042
+ message: error instanceof Error ? error.message : String(error),
2043
+ code: "UNKNOWN"
2044
+ };
2045
+ }
2046
+ /**
2047
+ * DSH's public Agent handle backed by Nanocodex turns. DSH owns the queue and
2048
+ * durable log; each model step is hydrated from the active surface.
2049
+ */
2050
+ var NanocodexAgent = class {
2051
+ id;
2052
+ options;
2053
+ session;
2054
+ inbox;
2055
+ ctx;
2056
+ loopCtx;
2057
+ engine;
2058
+ scope;
2059
+ dispatch;
2060
+ phase;
2061
+ activityDone = Promise.resolve();
2062
+ runtimeContextText;
2063
+ constructor(loopCtx, id, options, session, engine) {
2064
+ this.loopCtx = loopCtx;
2065
+ this.id = id;
2066
+ this.options = options;
2067
+ this.session = session;
2068
+ closeInterruptedToolCalls(session);
2069
+ this.engine = engine;
2070
+ this.dispatch = agentEvents(loopCtx, this);
2071
+ this.inbox = new Inbox(session, {
2072
+ inserted: (message) => this.dispatch.emit("agent/inbox/inserted", { message }),
2073
+ discarded: (message) => this.dispatch.emit("agent/inbox/discarded", { message }),
2074
+ claimed: (message, turn) => this.dispatch.emit("agent/inbox/claimed", {
2075
+ message,
2076
+ turn
2077
+ })
2078
+ });
2079
+ this.scope = createScope(loopCtx, this);
2080
+ this.ctx = this.scope.ctx.extend({ agent: this });
2081
+ this.phase = {
2082
+ kind: "idle",
2083
+ lastTurn: lastTurn(session)
2084
+ };
2085
+ this.runtimeContextText = this.findRuntimeContext();
2086
+ }
2087
+ get status() {
2088
+ return this.phase.kind === "running" ? "running" : "idle";
2089
+ }
2090
+ send(message, target, wakeup) {
2091
+ const wakingAfterAbort = wakeup && this.phase.kind !== "idle" && this.phase.abort.signal.aborted;
2092
+ this.inbox.splice(wakingAfterAbort ? "next-turn" : target, Infinity, 0, [message]);
2093
+ if (wakeup) this.wakeDriver(wakingAfterAbort);
2094
+ }
2095
+ followup(message) {
2096
+ this.send(message, "next-turn", true);
2097
+ }
2098
+ steer(message) {
2099
+ this.send(message, "next-step", true);
2100
+ }
2101
+ inject(message) {
2102
+ this.send(message, "next-step", false);
2103
+ }
2104
+ cancel(cause, options = {}) {
2105
+ if (!options.keepInbox) {
2106
+ this.inbox.clear();
2107
+ if (this.phase.kind !== "idle") this.phase.wakeRequested = false;
2108
+ } else if (this.phase.kind !== "idle" && this.inbox.hasPending) this.phase.wakeRequested = true;
2109
+ if (this.phase.kind !== "idle") this.phase.abort.abort(cause);
2110
+ }
2111
+ whenIdle() {
2112
+ return this.waitForIdle();
2113
+ }
2114
+ async waitForIdle() {
2115
+ let observed;
2116
+ do {
2117
+ observed = this.activityDone;
2118
+ await observed;
2119
+ } while (observed !== this.activityDone);
2120
+ }
2121
+ runMaintenance(task) {
2122
+ if (this.phase.kind !== "idle") throw new Error(`agent "${String(this.id)}" already has active work`);
2123
+ const done = deferred();
2124
+ const maintenance = {
2125
+ kind: "maintenance",
2126
+ abort: new AbortController(),
2127
+ lastTurn: this.phase.lastTurn,
2128
+ wakeRequested: false
2129
+ };
2130
+ this.phase = maintenance;
2131
+ this.activityDone = done.promise;
2132
+ return (async () => {
2133
+ try {
2134
+ return await task(maintenance.abort.signal);
2135
+ } finally {
2136
+ this.phase = {
2137
+ kind: "idle",
2138
+ lastTurn: maintenance.lastTurn
2139
+ };
2140
+ if (maintenance.wakeRequested && this.inbox.hasPending) this.wakeDriver();
2141
+ done.resolve();
2142
+ }
2143
+ })();
2144
+ }
2145
+ wakeDriver(wakeAfterAbort = false) {
2146
+ if (this.phase.kind !== "idle") {
2147
+ if (this.phase.abort.signal.reason?.kind !== "disposed" && (this.phase.kind === "maintenance" || wakeAfterAbort)) this.phase.wakeRequested = true;
2148
+ return;
2149
+ }
2150
+ const done = deferred();
2151
+ const running = {
2152
+ kind: "running",
2153
+ abort: new AbortController(),
2154
+ turn: this.phase.lastTurn,
2155
+ step: 0,
2156
+ wakeRequested: false
2157
+ };
2158
+ this.phase = running;
2159
+ this.activityDone = done.promise;
2160
+ this.dispatch.emit("agent/status", { status: "running" });
2161
+ this.loopCtx.agents.withInitiator(this, () => this.kick()).then(() => done.resolve(), (error) => {
2162
+ this.dispatch.emit("agent/error", {
2163
+ turn: running.turn,
2164
+ step: running.step,
2165
+ error
2166
+ });
2167
+ done.resolve();
2168
+ });
2169
+ }
2170
+ async dispose() {
2171
+ this.cancel({ kind: "disposed" });
2172
+ await this.waitForIdle();
2173
+ await this.engine.dispose(this);
2174
+ await this.scope.dispose();
2175
+ }
2176
+ async kick() {
2177
+ try {
2178
+ while (await this.turn());
2179
+ } catch (error) {
2180
+ const phase = this.phase;
2181
+ this.dispatch.emit("agent/error", {
2182
+ turn: phase.kind === "running" ? phase.turn : phase.lastTurn,
2183
+ step: phase.kind === "running" ? phase.step : 0,
2184
+ error
2185
+ });
2186
+ } finally {
2187
+ if (this.phase.kind === "running") {
2188
+ const { turn, wakeRequested } = this.phase;
2189
+ this.phase = {
2190
+ kind: "idle",
2191
+ lastTurn: turn
2192
+ };
2193
+ this.dispatch.emit("agent/status", { status: "idle" });
2194
+ if (wakeRequested && this.inbox.hasPending) this.wakeDriver();
2195
+ }
2196
+ }
2197
+ }
2198
+ async preStep(target, turn, step) {
2199
+ if (this.phase.kind !== "running") throw new Error("pre-step outside running phase");
2200
+ const signal = this.phase.abort.signal;
2201
+ const claimed = this.inbox.claim(target, turn);
2202
+ const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal));
2203
+ signal.throwIfAborted();
2204
+ const sections = renderContextSections(assembly);
2205
+ this.runtimeContextText = this.findRuntimeContext();
2206
+ const runtime = this.projectRuntimeContext(joinContextSections(sections), sections);
2207
+ const decision = await this.dispatch.waterfall("agent/pre-step", {
2208
+ messages: claimed,
2209
+ turn,
2210
+ step,
2211
+ signal
2212
+ }, () => Promise.resolve({
2213
+ kind: "enter",
2214
+ messages: runtime === void 0 ? claimed : [...claimed, runtime]
2215
+ }));
2216
+ signal.throwIfAborted();
2217
+ return decision.kind === "reject" ? { decision } : {
2218
+ decision,
2219
+ assembly
2220
+ };
2221
+ }
2222
+ async turn() {
2223
+ if (this.phase.kind !== "running") throw new Error("turn without driver reservation");
2224
+ const phase = this.phase;
2225
+ const signal = phase.abort.signal;
2226
+ signal.throwIfAborted();
2227
+ const turn = phase.turn + 1;
2228
+ phase.turn = turn;
2229
+ this.session.append("turn/start", { turn });
2230
+ let reason = { kind: "completed" };
2231
+ let target = "next-turn";
2232
+ try {
2233
+ while (true) {
2234
+ signal.throwIfAborted();
2235
+ const step = phase.step + 1;
2236
+ const prepared = await this.preStep(target, turn, step);
2237
+ if (prepared.decision.kind === "reject") {
2238
+ reason = { kind: "blocked" };
2239
+ break;
2240
+ }
2241
+ const messages = prepared.decision.messages;
2242
+ if (phase.step === 0 && messages.length === 0) break;
2243
+ signal.throwIfAborted();
2244
+ const previousMessages = this.session.deriveMessages();
2245
+ this.session.append("step/start", {
2246
+ turn,
2247
+ step
2248
+ });
2249
+ phase.step = step;
2250
+ try {
2251
+ for (const message of messages) this.session.append("user/message", message, { surfaceOp: "append" });
2252
+ const engineResult = await this.engine.run(this, previousMessages, messages, prepared.assembly, turn, step, signal, () => {
2253
+ this.session.append("step/end", {
2254
+ turn,
2255
+ step: phase.step
2256
+ });
2257
+ phase.step += 1;
2258
+ this.session.append("step/start", {
2259
+ turn,
2260
+ step: phase.step
2261
+ });
2262
+ return phase.step;
2263
+ });
2264
+ if (engineResult.automaticCompactions.length > 0) {
2265
+ const compaction = this.loopCtx.compaction;
2266
+ if (compaction === void 0 || typeof compaction.commitAutomaticSummary !== "function") throw new Error("Nanocodex automatic compaction has no DSH compaction owner");
2267
+ for (const automatic of engineResult.automaticCompactions) await compaction.commitAutomaticSummary(this, automatic, signal);
2268
+ }
2269
+ try {
2270
+ await this.engine.persistCheckpoint(this, engineResult.provider, engineResult.model, engineResult.snapshot, signal);
2271
+ } catch (error) {
2272
+ await this.engine.invalidate(this).catch(() => void 0);
2273
+ throw error;
2274
+ }
2275
+ } finally {
2276
+ this.session.append("step/end", {
2277
+ turn,
2278
+ step: phase.step
2279
+ });
2280
+ }
2281
+ reason = { kind: "completed" };
2282
+ signal.throwIfAborted();
2283
+ if (this.inbox.nextStep.length === 0) {
2284
+ await this.dispatch.serial("agent/turn-stopping", {
2285
+ turn,
2286
+ signal
2287
+ });
2288
+ signal.throwIfAborted();
2289
+ }
2290
+ if (this.inbox.nextStep.length === 0) break;
2291
+ target = "next-step";
2292
+ }
2293
+ } catch (error) {
2294
+ if (signal.aborted) reason = {
2295
+ kind: "aborted",
2296
+ reason: cancelCause(signal)
2297
+ };
2298
+ else {
2299
+ reason = {
2300
+ kind: "error",
2301
+ error: errorInfo(error)
2302
+ };
2303
+ this.dispatch.emit("agent/error", {
2304
+ turn,
2305
+ step: phase.step,
2306
+ error
2307
+ });
2308
+ }
2309
+ } finally {
2310
+ this.session.append("turn/end", {
2311
+ turn,
2312
+ reason
2313
+ });
2314
+ }
2315
+ if (!signal.aborted && reason.kind === "completed" && this.inbox.hasPending) {
2316
+ phase.abort = new AbortController();
2317
+ phase.step = 0;
2318
+ phase.wakeRequested = false;
2319
+ return true;
2320
+ }
2321
+ return false;
2322
+ }
2323
+ findRuntimeContext() {
2324
+ for (const message of this.session.deriveMessages().reverse()) if (message.source.kind === "plugin" && message.source.plugin === RUNTIME_CONTEXT_SOURCE && message.content.length === 1 && message.content[0]?.type === "text") return message.content[0].text;
2325
+ }
2326
+ projectRuntimeContext(current, sections) {
2327
+ if (this.runtimeContextText === void 0 && current.length === 0) return void 0;
2328
+ const next = current.length === 0 ? CLEARED_RUNTIME_CONTEXT : current;
2329
+ if (this.runtimeContextText === next) return void 0;
2330
+ this.runtimeContextText = next;
2331
+ return createUserMessage({
2332
+ content: [{
2333
+ type: "text",
2334
+ text: next
2335
+ }],
2336
+ source: sections.length === 0 ? {
2337
+ kind: "plugin",
2338
+ plugin: RUNTIME_CONTEXT_SOURCE
2339
+ } : {
2340
+ kind: "plugin",
2341
+ plugin: RUNTIME_CONTEXT_SOURCE,
2342
+ form: "snapshot",
2343
+ sections
2344
+ }
2345
+ });
2346
+ }
2347
+ };
2348
+ //#endregion
2349
+ //#region src/factory.ts
2350
+ /** AgentFactory implementation installed by the selected Cordis row. */
2351
+ var NanocodexFactory = class {
2352
+ ctx;
2353
+ engine;
2354
+ live = /* @__PURE__ */ new Set();
2355
+ closed = false;
2356
+ constructor(ctx, engine) {
2357
+ this.ctx = ctx;
2358
+ this.engine = engine;
2359
+ }
2360
+ async createAgent(ownerCtx, options) {
2361
+ const preparation = SessionPreparation.create(this.ctx.sessions.prepare(options.sessionId, {
2362
+ ...options.seed === void 0 ? {} : { seed: options.seed },
2363
+ ...options.meta === void 0 ? {} : { meta: options.meta },
2364
+ ...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
2365
+ }));
2366
+ try {
2367
+ return await this.publish(ownerCtx, preparation, options.agentOptions ?? {}, options.setup, options.signal, "startup");
2368
+ } finally {
2369
+ preparation[Symbol.dispose]();
2370
+ }
2371
+ }
2372
+ async resume(ownerCtx, options) {
2373
+ const persistence = this.ctx.get("sessionPersistence");
2374
+ if (persistence === void 0) throw new Error("cannot resume Nanocodex: dsh-session-persistence is not configured");
2375
+ const preparation = await persistence.prepare(options.resumeSessionId, options.signal);
2376
+ try {
2377
+ return await this.publish(ownerCtx, preparation, options.agentOptions ?? {}, options.setup, options.signal, "resume");
2378
+ } finally {
2379
+ preparation[Symbol.dispose]();
2380
+ }
2381
+ }
2382
+ async disposeAll() {
2383
+ this.closed = true;
2384
+ await Promise.all([...this.live].map((entry) => entry.dispose()));
2385
+ }
2386
+ async publish(ownerCtx, preparation, options, setup, callerSignal, source) {
2387
+ if (this.closed) throw new Error("Nanocodex factory is no longer active");
2388
+ const agent = new NanocodexAgent(this.ctx, preparation.session.id, options ?? {}, preparation.session, this.engine);
2389
+ let detachSession;
2390
+ let detachAgent;
2391
+ let disposed;
2392
+ const dispose = () => {
2393
+ disposed ??= (async () => {
2394
+ agent.cancel({ kind: "disposed" });
2395
+ await agent.whenIdle();
2396
+ detachAgent?.();
2397
+ detachSession?.();
2398
+ await agent.dispose();
2399
+ this.live.delete(entry);
2400
+ })();
2401
+ return disposed;
2402
+ };
2403
+ const entry = {
2404
+ agent,
2405
+ dispose
2406
+ };
2407
+ this.live.add(entry);
2408
+ ownerCtx.effect(() => () => dispose(), `dsh-nanocodex.lifecycle(${String(agent.id)})`);
2409
+ try {
2410
+ if (callerSignal?.aborted) throw callerSignal.reason;
2411
+ const commit = await setup?.(agent.ctx);
2412
+ apply$1(agent.ctx);
2413
+ if (commit) commit.commit();
2414
+ if (callerSignal?.aborted) throw callerSignal.reason;
2415
+ detachSession = agent.ctx.sessions.enter(preparation.session);
2416
+ detachAgent = this.ctx.agents.enter(agent, ownerCtx.agent);
2417
+ agent.ctx.sessions.announce(preparation.session);
2418
+ this.ctx.agents.announce(agent);
2419
+ emitAgentEvent(this.ctx, agent, "agent/session-start", { source });
2420
+ return {
2421
+ agent,
2422
+ dispose
2423
+ };
2424
+ } catch (error) {
2425
+ await dispose();
2426
+ throw error;
2427
+ }
2428
+ }
2429
+ };
2430
+ //#endregion
2431
+ //#region src/llm-adapter.ts
2432
+ const REASONING_EFFORTS = [
2433
+ ["none", "None"],
2434
+ ["low", "Low"],
2435
+ ["medium", "Medium"],
2436
+ ["high", "High"],
2437
+ ["xhigh", "Extra high"],
2438
+ ["max", "Maximum"],
2439
+ ["pro", "Pro"]
2440
+ ];
2441
+ function usage(value) {
2442
+ return {
2443
+ inputTokens: value.input_tokens,
2444
+ outputTokens: value.output_tokens,
2445
+ ...Number.isFinite(value.total_tokens) ? { totalTokens: value.total_tokens } : {},
2446
+ ...Number.isFinite(value.cached_input_tokens) && value.cached_input_tokens > 0 ? { cacheReadTokens: value.cached_input_tokens } : {},
2447
+ ...Number.isFinite(value.cache_write_input_tokens) && value.cache_write_input_tokens > 0 ? { cacheWriteTokens: value.cache_write_input_tokens } : {},
2448
+ ...Number.isFinite(value.reasoning_output_tokens) && value.reasoning_output_tokens > 0 ? { reasoningTokens: value.reasoning_output_tokens } : {}
2449
+ };
2450
+ }
2451
+ function providerName(provider) {
2452
+ return provider === "openai-codex-responses" ? "OpenAI Codex Responses" : "OpenAI Responses";
2453
+ }
2454
+ function modelName(model) {
2455
+ return model.replaceAll("-", " ").replace(/(^| )([a-z])/gu, (_, p, c) => `${p}${c.toUpperCase()}`);
2456
+ }
2457
+ function isProvider(value) {
2458
+ return value === "openai" || value === "openai-codex-responses";
2459
+ }
2460
+ function defaultReasoning(ctx, provider) {
2461
+ const settings = ctx.settings.get(SETTINGS_NAMESPACE);
2462
+ if (!settings || typeof settings !== "object") return void 0;
2463
+ const providers = settings.providers;
2464
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) return void 0;
2465
+ const profile = providers[provider];
2466
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) return void 0;
2467
+ return supportedThinking(profile.reasoning);
2468
+ }
2469
+ /** Native DSH model-catalog adapter for the Nanocodex-owned routes. */
2470
+ var NanocodexLlmAdapter = class extends LlmAdapter {
2471
+ ctx;
2472
+ constructor(ctx) {
2473
+ super();
2474
+ this.ctx = ctx;
2475
+ }
2476
+ providerInfo(provider) {
2477
+ if (!isProvider(provider)) throw new Error(`Nanocodex does not own provider ${JSON.stringify(provider)}`);
2478
+ return {
2479
+ id: provider,
2480
+ name: providerName(provider)
2481
+ };
2482
+ }
2483
+ async listModels(provider) {
2484
+ this.providerInfo(provider);
2485
+ return SUPPORTED_MODELS.map((id) => ({
2486
+ provider,
2487
+ id,
2488
+ name: modelName(id),
2489
+ inputModalities: ["text", "image"]
2490
+ }));
2491
+ }
2492
+ async resolveModel(provider, model, signal) {
2493
+ signal?.throwIfAborted();
2494
+ this.providerInfo(provider);
2495
+ if (!isSupportedModel(model)) throw new Error(`Nanocodex does not support model ${JSON.stringify(model)}`);
2496
+ const configured = defaultReasoning(this.ctx, provider);
2497
+ const efforts = REASONING_EFFORTS.map(([id, name]) => ({
2498
+ id: ReasoningEffortId(id),
2499
+ name
2500
+ }));
2501
+ return {
2502
+ provider,
2503
+ id: model,
2504
+ name: modelName(model),
2505
+ inputModalities: ["text", "image"],
2506
+ context: { contextWindow: MODEL_CONTEXT_WINDOW },
2507
+ reasoning: {
2508
+ efforts,
2509
+ ...configured === void 0 ? {} : { defaultEffort: ReasoningEffortId(configured) }
2510
+ }
2511
+ };
2512
+ }
2513
+ /**
2514
+ * The retained session-title consumer uses this one-shot path. Ordinary
2515
+ * conversation turns remain owned by NanocodexAgent/NanocodexEngine and do
2516
+ * not recurse through DSH's LLM runtime.
2517
+ */
2518
+ stream(options) {
2519
+ return this.streamOneShot(options);
2520
+ }
2521
+ async *streamOneShot(options) {
2522
+ const signal = options.signal;
2523
+ signal?.throwIfAborted();
2524
+ if (!isSupportedModel(options.model)) throw new Error(`Nanocodex does not support model ${JSON.stringify(options.model)}`);
2525
+ const route = await resolveNanocodexRoute(this.ctx, {
2526
+ provider: options.provider,
2527
+ model: options.model,
2528
+ ...options.reasoningEffort === void 0 ? {} : { reasoningEffort: options.reasoningEffort }
2529
+ });
2530
+ const last = options.messages.at(-1);
2531
+ const historySeed = await buildHistorySeed(last?.role === "user" ? options.messages.slice(0, -1) : options.messages, this.ctx, signal);
2532
+ const input = last?.role === "user" ? await buildPromptInput([last], this.ctx, signal) : "Continue from the current conversation.";
2533
+ const runtimeSessionId = randomUUID();
2534
+ const nodeAgent = await Agent.create({
2535
+ transport: Transport.openAi({
2536
+ apiKey: route.apiKey,
2537
+ ...route.apiBaseUrl ? { apiBaseUrl: route.apiBaseUrl } : {},
2538
+ ...route.websocketUrl ? { websocketUrl: route.websocketUrl } : {},
2539
+ websocketWarmup: false
2540
+ }),
2541
+ model: options.model,
2542
+ ...route.thinking ? { thinking: route.thinking } : {},
2543
+ ...options.reasoningEffort === "pro" ? { reasoningMode: "pro" } : {},
2544
+ sessionId: runtimeSessionId,
2545
+ subagents: false,
2546
+ historySeed,
2547
+ ...options.system ? { additionalInstructions: options.system } : {}
2548
+ });
2549
+ const removeTransportFallback = observeTransportFallback(this.ctx, options.sessionId === void 0 ? nodeAgent.sessionId : String(options.sessionId), nodeAgent);
2550
+ const turn = nodeAgent.turn.prompt({ input });
2551
+ const abort = () => void turn.cancel().catch(() => void 0);
2552
+ signal?.addEventListener("abort", abort, { once: true });
2553
+ try {
2554
+ await turn.accepted();
2555
+ const result = await turn.result();
2556
+ try {
2557
+ signal?.throwIfAborted();
2558
+ if (result.finalMessage) {
2559
+ yield {
2560
+ type: "block-start",
2561
+ index: 0,
2562
+ blockType: "text"
2563
+ };
2564
+ yield {
2565
+ type: "text-delta",
2566
+ index: 0,
2567
+ text: result.finalMessage
2568
+ };
2569
+ yield {
2570
+ type: "block-end",
2571
+ index: 0,
2572
+ block: {
2573
+ type: "text",
2574
+ text: result.finalMessage
2575
+ }
2576
+ };
2577
+ }
2578
+ yield {
2579
+ type: "usage",
2580
+ usage: usage(await result.usage())
2581
+ };
2582
+ yield {
2583
+ type: "finish",
2584
+ reason: { kind: "stop" }
2585
+ };
2586
+ } finally {
2587
+ result.dispose();
2588
+ }
2589
+ } finally {
2590
+ signal?.removeEventListener("abort", abort);
2591
+ removeTransportFallback();
2592
+ turn.dispose();
2593
+ nodeAgent.dispose();
2594
+ }
2595
+ }
2596
+ };
2597
+ //#endregion
2598
+ //#region src/apply-patch/codex-whitespace.ts
2599
+ function isRustWhitespace(character) {
2600
+ const codePoint = character?.codePointAt(0);
2601
+ return codePoint !== void 0 && (codePoint >= 9 && codePoint <= 13 || codePoint === 32 || codePoint === 133 || codePoint === 160 || codePoint === 5760 || codePoint >= 8192 && codePoint <= 8202 || codePoint === 8232 || codePoint === 8233 || codePoint === 8239 || codePoint === 8287 || codePoint === 12288);
2602
+ }
2603
+ function rustTrimStart(value) {
2604
+ let start = 0;
2605
+ while (start < value.length && isRustWhitespace(value[start])) start += 1;
2606
+ return value.slice(start);
2607
+ }
2608
+ function rustTrimEnd(value) {
2609
+ let end = value.length;
2610
+ while (end > 0 && isRustWhitespace(value[end - 1])) end -= 1;
2611
+ return value.slice(0, end);
2612
+ }
2613
+ function rustTrim(value) {
2614
+ return rustTrimEnd(rustTrimStart(value));
2615
+ }
2616
+ //#endregion
2617
+ //#region src/apply-patch/errors.ts
2618
+ var PatchError = class extends Error {
2619
+ code;
2620
+ line;
2621
+ constructor(message, code, options = {}) {
2622
+ super(message, options);
2623
+ this.name = "PatchError";
2624
+ this.code = code;
2625
+ if (options.line !== void 0) this.line = options.line;
2626
+ }
2627
+ };
2628
+ function patchErrorMessage(error) {
2629
+ return error instanceof Error ? error.message : String(error);
2630
+ }
2631
+ //#endregion
2632
+ //#region src/apply-patch/source-file.ts
2633
+ /** Preserve the first observed line ending, as the Codex patcher does. */
2634
+ var SourceFile = class SourceFile {
2635
+ lines;
2636
+ preferredEnding;
2637
+ constructor(lines, preferredEnding) {
2638
+ this.lines = lines;
2639
+ this.preferredEnding = preferredEnding;
2640
+ }
2641
+ static parse(content) {
2642
+ const lines = [];
2643
+ let preferredEnding;
2644
+ let lineStart = 0;
2645
+ let cursor = 0;
2646
+ while (cursor < content.length) {
2647
+ const current = content[cursor];
2648
+ let ending;
2649
+ let endingLength = 0;
2650
+ if (current === "\r" && content[cursor + 1] === "\n") {
2651
+ ending = "\r\n";
2652
+ endingLength = 2;
2653
+ } else if (current === "\r" || current === "\n") {
2654
+ ending = current;
2655
+ endingLength = 1;
2656
+ }
2657
+ if (ending === void 0) {
2658
+ cursor += 1;
2659
+ continue;
2660
+ }
2661
+ preferredEnding ??= ending;
2662
+ lines.push({
2663
+ text: content.slice(lineStart, cursor),
2664
+ ending
2665
+ });
2666
+ cursor += endingLength;
2667
+ lineStart = cursor;
2668
+ }
2669
+ if (lineStart < content.length) lines.push({ text: content.slice(lineStart) });
2670
+ return new SourceFile(lines, preferredEnding ?? "\n");
2671
+ }
2672
+ lineTexts() {
2673
+ return this.lines.map((line) => line.text);
2674
+ }
2675
+ applyReplacements(replacements) {
2676
+ const result = [];
2677
+ let sourceIndex = 0;
2678
+ for (const [start, oldLength, newLines] of replacements) {
2679
+ if (start < sourceIndex) throw new Error("overlapping replacements");
2680
+ result.push(...this.lines.slice(sourceIndex, start));
2681
+ result.push(...newLines.map((text) => ({
2682
+ text,
2683
+ ending: this.preferredEnding
2684
+ })));
2685
+ sourceIndex = start + oldLength;
2686
+ }
2687
+ result.push(...this.lines.slice(sourceIndex));
2688
+ for (const line of result) line.ending ??= this.preferredEnding;
2689
+ this.lines = result;
2690
+ }
2691
+ contents() {
2692
+ return this.lines.map((line) => `${line.text}${line.ending ?? ""}`).join("");
2693
+ }
2694
+ };
2695
+ //#endregion
2696
+ //#region src/apply-patch/matcher.ts
2697
+ const UNICODE_ASCII = /* @__PURE__ */ new Map([
2698
+ ["‐", "-"],
2699
+ ["‑", "-"],
2700
+ ["‒", "-"],
2701
+ ["–", "-"],
2702
+ ["—", "-"],
2703
+ ["―", "-"],
2704
+ ["−", "-"],
2705
+ ["‘", "'"],
2706
+ ["’", "'"],
2707
+ ["‚", "'"],
2708
+ ["‛", "'"],
2709
+ ["“", "\""],
2710
+ ["”", "\""],
2711
+ ["„", "\""],
2712
+ ["‟", "\""],
2713
+ ["\xA0", " "],
2714
+ [" ", " "],
2715
+ [" ", " "],
2716
+ [" ", " "],
2717
+ [" ", " "],
2718
+ [" ", " "],
2719
+ [" ", " "],
2720
+ [" ", " "],
2721
+ [" ", " "],
2722
+ [" ", " "],
2723
+ [" ", " "],
2724
+ [" ", " "],
2725
+ [" ", " "]
2726
+ ]);
2727
+ function normalizeUnicode(line) {
2728
+ return Array.from(rustTrim(line)).map((character) => UNICODE_ASCII.get(character) ?? character).join("");
2729
+ }
2730
+ function findWith(lines, pattern, start, compare) {
2731
+ const last = lines.length - pattern.length;
2732
+ for (let index = start; index <= last; index += 1) if (pattern.every((line, offset) => compare(lines[index + offset], line))) return index;
2733
+ }
2734
+ /** Codex's exact, trimmed, and Unicode-normalized context matching order. */
2735
+ function seekSequence(lines, pattern, start, atEnd) {
2736
+ if (pattern.length === 0) return start;
2737
+ if (pattern.length > lines.length) return void 0;
2738
+ const searchStart = atEnd ? Math.max(start, lines.length - pattern.length) : start;
2739
+ return findWith(lines, pattern, searchStart, (left, right) => left === right) ?? findWith(lines, pattern, searchStart, (left, right) => rustTrimEnd(left) === rustTrimEnd(right)) ?? findWith(lines, pattern, searchStart, (left, right) => rustTrim(left) === rustTrim(right)) ?? findWith(lines, pattern, searchStart, (left, right) => normalizeUnicode(left) === normalizeUnicode(right));
2740
+ }
2741
+ function computeReplacements(lines, path, chunks) {
2742
+ const replacements = [];
2743
+ let lineIndex = 0;
2744
+ for (const chunk of chunks) {
2745
+ if (chunk.changeContext !== void 0) {
2746
+ const contextIndex = seekSequence(lines, [chunk.changeContext], lineIndex, false);
2747
+ if (contextIndex === void 0) throw new PatchError(`apply_patch: Failed to find context '${chunk.changeContext}' in ${path}`, "PATCH_CONTEXT_NOT_FOUND", { line: chunk.line });
2748
+ lineIndex = contextIndex + 1;
2749
+ }
2750
+ if (chunk.oldLines.length === 0) {
2751
+ replacements.push([
2752
+ lines.length,
2753
+ 0,
2754
+ chunk.newLines
2755
+ ]);
2756
+ continue;
2757
+ }
2758
+ let pattern = chunk.oldLines;
2759
+ let newLines = chunk.newLines;
2760
+ let matchIndex = seekSequence(lines, pattern, lineIndex, chunk.isEndOfFile);
2761
+ if (matchIndex === void 0 && pattern.at(-1) === "") {
2762
+ pattern = pattern.slice(0, -1);
2763
+ if (newLines.at(-1) === "") newLines = newLines.slice(0, -1);
2764
+ matchIndex = seekSequence(lines, pattern, lineIndex, chunk.isEndOfFile);
2765
+ }
2766
+ if (matchIndex === void 0) throw new PatchError(`apply_patch: Failed to find expected lines in ${path}:\n${chunk.oldLines.join("\n")}`, "PATCH_CONTEXT_NOT_FOUND", { line: chunk.line });
2767
+ let oldStart = 0;
2768
+ let newStart = 0;
2769
+ for (const [oldContext, newContext] of chunk.contextLineIndices) {
2770
+ if (oldContext >= pattern.length || newContext >= newLines.length) break;
2771
+ if (oldStart !== oldContext || newStart !== newContext) replacements.push([
2772
+ matchIndex + oldStart,
2773
+ oldContext - oldStart,
2774
+ newLines.slice(newStart, newContext)
2775
+ ]);
2776
+ oldStart = oldContext + 1;
2777
+ newStart = newContext + 1;
2778
+ }
2779
+ if (oldStart !== pattern.length || newStart !== newLines.length) replacements.push([
2780
+ matchIndex + oldStart,
2781
+ pattern.length - oldStart,
2782
+ newLines.slice(newStart)
2783
+ ]);
2784
+ lineIndex = matchIndex + pattern.length;
2785
+ }
2786
+ replacements.sort((left, right) => left[0] - right[0]);
2787
+ return replacements;
2788
+ }
2789
+ /** Apply update chunks entirely in memory before a filesystem write. */
2790
+ function applyChunks(content, path, chunks) {
2791
+ const source = SourceFile.parse(content);
2792
+ source.applyReplacements(computeReplacements(source.lineTexts(), path, chunks));
2793
+ return source.contents();
2794
+ }
2795
+ //#endregion
2796
+ //#region src/apply-patch/parser.ts
2797
+ const BEGIN_PATCH = "*** Begin Patch";
2798
+ const END_PATCH = "*** End Patch";
2799
+ const ADD_FILE = "*** Add File: ";
2800
+ const UPDATE_FILE = "*** Update File: ";
2801
+ const DELETE_FILE = "*** Delete File: ";
2802
+ const MOVE_TO = "*** Move to: ";
2803
+ const END_OF_FILE = "*** End of File";
2804
+ const CHANGE_CONTEXT = "@@ ";
2805
+ const EMPTY_CHANGE_CONTEXT = "@@";
2806
+ function invalidPatch(message) {
2807
+ throw new PatchError(`apply_patch: invalid patch: ${message}`, "PATCH_INVALID");
2808
+ }
2809
+ function invalidHunk(message, line) {
2810
+ throw new PatchError(`apply_patch: invalid hunk at line ${line}: ${message}`, "PATCH_INVALID", { line });
2811
+ }
2812
+ function unsupported(message, line) {
2813
+ throw new PatchError(`apply_patch: unsupported operation: ${message}`, "PATCH_UNSUPPORTED", line === void 0 ? {} : { line });
2814
+ }
2815
+ function splitPatchLines(input) {
2816
+ const trimmed = rustTrim(input);
2817
+ const original = trimmed.length === 0 ? [] : trimmed.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
2818
+ const first = original[0] === void 0 ? void 0 : rustTrim(original[0]);
2819
+ const finalLine = original.at(-1);
2820
+ const last = finalLine === void 0 ? void 0 : rustTrim(finalLine);
2821
+ if (first === "*** Begin Patch" && last === "*** End Patch") return original;
2822
+ if (original.length >= 4 && (original[0] === "<<EOF" || original[0] === "<<'EOF'" || original[0] === "<<\"EOF\"") && original.at(-1)?.endsWith("EOF")) {
2823
+ const inner = original.slice(1, -1);
2824
+ if (rustTrim(inner[0]) === "*** Begin Patch" && rustTrim(inner.at(-1)) === "*** End Patch") return inner;
2825
+ if (rustTrim(inner[0]) !== "*** Begin Patch") invalidPatch(`The first line of the patch must be '${BEGIN_PATCH}'`);
2826
+ invalidPatch(`The last line of the patch must be '${END_PATCH}'`);
2827
+ }
2828
+ if (first !== void 0 && first !== "*** Begin Patch") invalidPatch(`The first line of the patch must be '${BEGIN_PATCH}'`);
2829
+ invalidPatch(`The last line of the patch must be '${END_PATCH}'`);
2830
+ }
2831
+ function newChunk(line, changeContext) {
2832
+ return {
2833
+ ...changeContext === void 0 ? {} : { changeContext },
2834
+ oldLines: [],
2835
+ newLines: [],
2836
+ contextLineIndices: [],
2837
+ isEndOfFile: false,
2838
+ line
2839
+ };
2840
+ }
2841
+ function ensureUpdateHunkIsNotEmpty(mode, nextLine, lineNumber) {
2842
+ if (mode.kind !== "update") return;
2843
+ const chunks = mode.operation.chunks;
2844
+ if (chunks.length === 0) invalidHunk(`Update file hunk for path '${mode.operation.path}' is empty`, mode.hunkLine);
2845
+ const last = chunks.at(-1);
2846
+ if (last.oldLines.length === 0 && last.newLines.length === 0) {
2847
+ if (nextLine === "*** End Patch") invalidHunk("Update hunk does not contain any lines", lineNumber);
2848
+ invalidHunk(`Unexpected line found in update hunk: '${nextLine}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, lineNumber);
2849
+ }
2850
+ }
2851
+ /** Parse the supported Add File and Update File Codex patch subset. */
2852
+ function parsePatch(input) {
2853
+ if (typeof input !== "string") invalidPatch("input must be a string");
2854
+ const lines = splitPatchLines(input);
2855
+ const operations = [];
2856
+ let mode = { kind: "started" };
2857
+ const startOperation = (trimmed, lineNumber) => {
2858
+ if (trimmed.startsWith(DELETE_FILE)) unsupported("Delete File is not supported; remove file content explicitly", lineNumber);
2859
+ if (trimmed.startsWith(MOVE_TO)) unsupported("Move to is not supported; use an explicit Add File/Update File patch", lineNumber);
2860
+ if (trimmed.startsWith(ADD_FILE)) {
2861
+ ensureUpdateHunkIsNotEmpty(mode, trimmed, lineNumber);
2862
+ const path = trimmed.slice(14);
2863
+ if (path.length === 0) invalidPatch("Add File path cannot be empty");
2864
+ const operation = {
2865
+ kind: "add",
2866
+ path,
2867
+ content: "",
2868
+ line: lineNumber
2869
+ };
2870
+ operations.push(operation);
2871
+ mode = {
2872
+ kind: "add",
2873
+ operation
2874
+ };
2875
+ return true;
2876
+ }
2877
+ if (trimmed.startsWith(UPDATE_FILE)) {
2878
+ ensureUpdateHunkIsNotEmpty(mode, trimmed, lineNumber);
2879
+ const path = trimmed.slice(17);
2880
+ if (path.length === 0) invalidPatch("Update File path cannot be empty");
2881
+ const operation = {
2882
+ kind: "update",
2883
+ path,
2884
+ chunks: [],
2885
+ line: lineNumber
2886
+ };
2887
+ operations.push(operation);
2888
+ mode = {
2889
+ kind: "update",
2890
+ operation,
2891
+ hunkLine: lineNumber
2892
+ };
2893
+ return true;
2894
+ }
2895
+ return false;
2896
+ };
2897
+ for (let index = 1; index < lines.length - 1; index += 1) {
2898
+ const line = lines[index];
2899
+ const lineNumber = index + 1;
2900
+ const activeMode = mode;
2901
+ if (activeMode.kind !== "update") {
2902
+ const trimmed = rustTrim(line);
2903
+ if (startOperation(trimmed, lineNumber)) continue;
2904
+ if (activeMode.kind === "add" && line.startsWith("+")) {
2905
+ activeMode.operation.content += `${line.slice(1)}\n`;
2906
+ continue;
2907
+ }
2908
+ invalidHunk(`'${trimmed}' is not a valid hunk header. Valid hunk headers: '${ADD_FILE}{path}', '${UPDATE_FILE}{path}'`, lineNumber);
2909
+ continue;
2910
+ }
2911
+ const updateMode = activeMode;
2912
+ const updateLine = rustTrimEnd(line);
2913
+ if (startOperation(updateLine, lineNumber)) continue;
2914
+ const chunks = updateMode.operation.chunks;
2915
+ const last = chunks.at(-1);
2916
+ if (last?.isEndOfFile) {
2917
+ if (updateLine.length === 0) continue;
2918
+ if (updateLine !== EMPTY_CHANGE_CONTEXT && !updateLine.startsWith(CHANGE_CONTEXT)) invalidHunk(`Expected update hunk to start with a @@ context marker, got: '${line}'`, lineNumber);
2919
+ }
2920
+ if (updateLine === EMPTY_CHANGE_CONTEXT) {
2921
+ if (last !== void 0 && last.oldLines.length === 0 && last.newLines.length === 0) invalidHunk(`Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, lineNumber);
2922
+ chunks.push(newChunk(lineNumber));
2923
+ continue;
2924
+ }
2925
+ if (updateLine.startsWith(CHANGE_CONTEXT)) {
2926
+ if (last !== void 0 && last.oldLines.length === 0 && last.newLines.length === 0) invalidHunk(`Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, lineNumber);
2927
+ chunks.push(newChunk(lineNumber, updateLine.slice(3)));
2928
+ continue;
2929
+ }
2930
+ if (updateLine === END_OF_FILE) {
2931
+ if (last !== void 0 && last.oldLines.length === 0 && last.newLines.length === 0) invalidHunk("Update hunk does not contain any lines", lineNumber);
2932
+ if (last === void 0) invalidHunk("End of File requires an update hunk", lineNumber);
2933
+ last.isEndOfFile = true;
2934
+ continue;
2935
+ }
2936
+ if (line.length === 0 || line.startsWith(" ")) {
2937
+ const chunk = chunks.at(-1) ?? newChunk(lineNumber);
2938
+ if (chunks.length === 0) chunks.push(chunk);
2939
+ chunk.contextLineIndices.push([chunk.oldLines.length, chunk.newLines.length]);
2940
+ const context = line.length === 0 ? "" : line.slice(1);
2941
+ chunk.oldLines.push(context);
2942
+ chunk.newLines.push(context);
2943
+ continue;
2944
+ }
2945
+ if (line.startsWith("+")) {
2946
+ const chunk = chunks.at(-1) ?? newChunk(lineNumber);
2947
+ if (chunks.length === 0) chunks.push(chunk);
2948
+ chunk.newLines.push(line.slice(1));
2949
+ continue;
2950
+ }
2951
+ if (line.startsWith("-")) {
2952
+ const chunk = chunks.at(-1) ?? newChunk(lineNumber);
2953
+ if (chunks.length === 0) chunks.push(chunk);
2954
+ chunk.oldLines.push(line.slice(1));
2955
+ continue;
2956
+ }
2957
+ if (last !== void 0 && (last.oldLines.length > 0 || last.newLines.length > 0)) invalidHunk(`Expected update hunk to start with a @@ context marker, got: '${line}'`, lineNumber);
2958
+ invalidHunk(`Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, lineNumber);
2959
+ }
2960
+ ensureUpdateHunkIsNotEmpty(mode, END_PATCH, lines.length);
2961
+ if (operations.length === 0) invalidPatch("patch contains no file operations");
2962
+ return {
2963
+ operations,
2964
+ normalizedPatch: lines.join("\n")
2965
+ };
2966
+ }
2967
+ //#endregion
2968
+ //#region src/apply-patch/tool.ts
2969
+ const DEFAULT_APPLY_PATCH_LIMITS = {
2970
+ maxPatchChars: DEFAULT_MAX_PATCH_CHARS,
2971
+ maxPatchFiles: 64,
2972
+ maxPatchFileBytes: DEFAULT_MAX_PATCH_FILE_BYTES
2973
+ };
2974
+ function workspaceCwd(exec) {
2975
+ const cwd = exec.agent?.session?.header?.cwd;
2976
+ if (typeof cwd !== "string" || cwd.length === 0) throw new PatchError("apply_patch requires an active Agent workspace", "PATCH_PATH_INVALID");
2977
+ return cwd;
2978
+ }
2979
+ function byteLength(value) {
2980
+ return new TextEncoder().encode(value).byteLength;
2981
+ }
2982
+ function parseArguments(value, limits) {
2983
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new PatchError("apply_patch expects an object containing one non-empty patch string", "PATCH_INVALID");
2984
+ const record = value;
2985
+ if (Object.keys(record).some((key) => key !== "patch") || typeof record.patch !== "string" || record.patch.length === 0) throw new PatchError("apply_patch expects an object containing one non-empty patch string", "PATCH_INVALID");
2986
+ if (record.patch.length > limits.maxPatchChars) throw new PatchError(`apply_patch input exceeds the ${limits.maxPatchChars}-character limit`, "PATCH_TOO_LARGE");
2987
+ return record.patch;
2988
+ }
2989
+ function operationAction(operation) {
2990
+ return operation.kind;
2991
+ }
2992
+ function errorCode(error) {
2993
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") return error.code;
2994
+ return "PATCH_IO";
2995
+ }
2996
+ function isCancellation(error, signal) {
2997
+ return signal.aborted || errorCode(error) === "FS_ABORTED" || error instanceof Error && error.name === "AbortError";
2998
+ }
2999
+ function observeVersion(services, target, version, exec) {
3000
+ services.ctx.emit(scopeTarget(services.fs, exec.agent), "fs/observed", target, {
3001
+ kind: "present",
3002
+ version
3003
+ }, exec);
3004
+ }
3005
+ async function writeIntent(services, target, fallback, exec) {
3006
+ return await services.ctx.waterfall(scopeTarget(services.fs, exec.agent), "fs/write-intent", target, exec, () => Promise.resolve(fallback)) ?? fallback;
3007
+ }
3008
+ async function editIntent(services, target, fallback, exec) {
3009
+ return await services.ctx.waterfall(scopeTarget(services.fs, exec.agent), "fs/edit-intent", target, exec, () => Promise.resolve(fallback)) ?? fallback;
3010
+ }
3011
+ function checkFileSize(info, path, limits) {
3012
+ if (info.size !== void 0 && info.size > limits.maxPatchFileBytes) throw new PatchError(`apply_patch target '${path}' exceeds the ${limits.maxPatchFileBytes}-byte limit`, "PATCH_TOO_LARGE");
3013
+ }
3014
+ async function preflight(patch, services, exec, limits) {
3015
+ if (patch.operations.length > limits.maxPatchFiles) throw new PatchError(`apply_patch contains more than ${limits.maxPatchFiles} file operations`, "PATCH_TOO_LARGE");
3016
+ const cwd = workspaceCwd(exec);
3017
+ const workspace = await services.fs.resolve(cwd, {
3018
+ cwd,
3019
+ signal: exec.signal
3020
+ });
3021
+ const targets = /* @__PURE__ */ new Map();
3022
+ const staged = [];
3023
+ for (const operation of patch.operations) {
3024
+ exec.signal.throwIfAborted();
3025
+ if (operation.path.trim().length === 0) throw new PatchError(`apply_patch ${operation.kind === "add" ? "Add" : "Update"} File path cannot be blank`, "PATCH_PATH_INVALID", { line: operation.line });
3026
+ const target = await services.fs.resolve(operation.path, {
3027
+ cwd,
3028
+ signal: exec.signal
3029
+ });
3030
+ exec.signal.throwIfAborted();
3031
+ if (!services.fs.contains(workspace, target)) throw new PatchError(`apply_patch target '${operation.path}' must stay inside the active workspace`, "PATCH_PATH_INVALID", { line: operation.line });
3032
+ const previousPath = targets.get(target.targetKey);
3033
+ if (previousPath !== void 0) throw new PatchError(`apply_patch contains duplicate target '${operation.path}' (same target as '${previousPath}')`, "PATCH_INVALID", { line: operation.line });
3034
+ targets.set(target.targetKey, operation.path);
3035
+ const info = await services.fs.stat(target, exec.signal);
3036
+ exec.signal.throwIfAborted();
3037
+ if (operation.kind === "add") {
3038
+ if (info !== void 0) throw new PatchError(`apply_patch cannot add '${operation.path}': the target already exists`, "PATCH_INVALID", { line: operation.line });
3039
+ if (byteLength(operation.content) > limits.maxPatchFileBytes) throw new PatchError(`apply_patch added file '${operation.path}' exceeds the ${limits.maxPatchFileBytes}-byte limit`, "PATCH_TOO_LARGE", { line: operation.line });
3040
+ const expected = await writeIntent(services, target, { kind: "createIfAbsent" }, exec);
3041
+ if (expected.kind !== "createIfAbsent") throw new FsError(`cannot add '${operation.path}': the target changed after its absence was observed`, "FS_STALE_VERSION");
3042
+ staged.push({
3043
+ operation,
3044
+ target,
3045
+ before: null,
3046
+ after: operation.content,
3047
+ expected
3048
+ });
3049
+ continue;
3050
+ }
3051
+ if (info === void 0) throw new PatchError(`apply_patch cannot update '${operation.path}': the target does not exist`, "PATCH_INVALID", { line: operation.line });
3052
+ if (info.type !== "file") throw new PatchError(`apply_patch cannot update '${operation.path}': the target is not a regular text file`, "PATCH_INVALID", { line: operation.line });
3053
+ const edit = await editIntent(services, target, { version: info.version }, exec);
3054
+ if (edit.version !== info.version) throw new FsError(`cannot update '${operation.path}': the target changed after preflight stat`, "FS_STALE_VERSION");
3055
+ const expected = await writeIntent(services, target, {
3056
+ kind: "replaceIfVersion",
3057
+ version: edit.version
3058
+ }, exec);
3059
+ if (expected.kind !== "replaceIfVersion" || expected.version !== info.version) throw new FsError(`cannot update '${operation.path}': the target is not observed at its current version`, "FS_STALE_VERSION");
3060
+ checkFileSize(info, operation.path, limits);
3061
+ const before = await services.fs.readText(target, exec.signal);
3062
+ exec.signal.throwIfAborted();
3063
+ if (byteLength(before) > limits.maxPatchFileBytes) throw new PatchError(`apply_patch target '${operation.path}' exceeds the ${limits.maxPatchFileBytes}-byte limit`, "PATCH_TOO_LARGE", { line: operation.line });
3064
+ const after = applyChunks(before, operation.path, operation.chunks);
3065
+ if (byteLength(after) > limits.maxPatchFileBytes) throw new PatchError(`apply_patch updated file '${operation.path}' exceeds the ${limits.maxPatchFileBytes}-byte limit`, "PATCH_TOO_LARGE", { line: operation.line });
3066
+ staged.push({
3067
+ operation,
3068
+ target,
3069
+ before,
3070
+ after,
3071
+ expected
3072
+ });
3073
+ exec.signal.throwIfAborted();
3074
+ }
3075
+ return staged;
3076
+ }
3077
+ function operationSummary(operations, status) {
3078
+ const committed = operations.filter((operation) => operation.status === "committed").map((operation) => operation.path);
3079
+ const failed = operations.find((operation) => operation.status === "failed");
3080
+ const unattempted = operations.filter((operation) => operation.status === "unattempted").map((operation) => operation.path);
3081
+ if (status === "applied") return `Applied patch to ${committed.length} file${committed.length === 1 ? "" : "s"}: ${committed.join(", ")}.`;
3082
+ const details = [`${status === "cancelled" ? "Patch publication cancelled" : "Patch publication failed"}.`];
3083
+ if (committed.length > 0) details.push(`Committed: ${committed.join(", ")}.`);
3084
+ if (failed !== void 0) details.push(`Failed: ${failed.path}.`);
3085
+ if (unattempted.length > 0) details.push(`Unattempted: ${unattempted.join(", ")}.`);
3086
+ return details.join(" ");
3087
+ }
3088
+ function unifiedDiff(diff) {
3089
+ const oldLines = diff.oldText === null ? [] : diff.oldText.split("\n");
3090
+ const newLines = diff.newText.split("\n");
3091
+ return [
3092
+ `--- ${diff.oldText === null ? "/dev/null" : `a/${diff.path}`}`,
3093
+ `+++ b/${diff.path}`,
3094
+ ...oldLines.map((line) => `-${line}`),
3095
+ ...newLines.map((line) => `+${line}`)
3096
+ ].join("\n");
3097
+ }
3098
+ function diffFromWrite(staged, outcome) {
3099
+ return {
3100
+ path: staged.operation.path,
3101
+ oldText: outcome.before ?? staged.before,
3102
+ newText: outcome.after
3103
+ };
3104
+ }
3105
+ function outputFor(status, operations, diffs, failure) {
3106
+ return {
3107
+ status,
3108
+ summary: operationSummary(operations, status),
3109
+ diff: diffs.map(unifiedDiff).join("\n"),
3110
+ operations,
3111
+ diffs,
3112
+ ...failure === void 0 ? {} : { error: failure }
3113
+ };
3114
+ }
3115
+ /**
3116
+ * Preflight and publish a supported Codex patch through the DSH filesystem.
3117
+ *
3118
+ * Every operation is resolved, observed, read, matched, and calculated before
3119
+ * the first guarded atomic write. Publication is ordered and intentionally
3120
+ * non-transactional: a later failure leaves committed earlier files in place
3121
+ * and returns their diffs with explicit per-operation progress.
3122
+ */
3123
+ async function applyPatchToFileSystem(input, services, exec, limits = DEFAULT_APPLY_PATCH_LIMITS) {
3124
+ if (input.length > limits.maxPatchChars) throw new PatchError(`apply_patch input exceeds the ${limits.maxPatchChars}-character limit`, "PATCH_TOO_LARGE");
3125
+ const staged = await preflight(parsePatch(input), services, exec, limits);
3126
+ const operations = staged.map(({ operation }) => ({
3127
+ path: operation.path,
3128
+ action: operationAction(operation),
3129
+ status: "unattempted"
3130
+ }));
3131
+ const diffs = [];
3132
+ for (const [index, candidate] of staged.entries()) {
3133
+ let writeStarted = false;
3134
+ try {
3135
+ exec.signal.throwIfAborted();
3136
+ writeStarted = true;
3137
+ const write = await services.fs.writeText(candidate.target, candidate.after, candidate.expected, exec.signal);
3138
+ const operation = operations[index];
3139
+ if (operation === void 0) throw new Error("patch operation disappeared");
3140
+ operations[index] = {
3141
+ ...operation,
3142
+ status: "committed"
3143
+ };
3144
+ diffs.push(diffFromWrite(candidate, write));
3145
+ observeVersion(services, candidate.target, write.version, exec);
3146
+ } catch (error) {
3147
+ const code = isCancellation(error, exec.signal) ? "FS_ABORTED" : errorCode(error);
3148
+ const message = isCancellation(error, exec.signal) ? "patch publication was cancelled" : patchErrorMessage(error);
3149
+ const operation = operations[index];
3150
+ if (operation !== void 0 && writeStarted) operations[index] = {
3151
+ ...operation,
3152
+ status: "failed",
3153
+ error: {
3154
+ code,
3155
+ message
3156
+ }
3157
+ };
3158
+ return outputFor(isCancellation(error, exec.signal) ? "cancelled" : diffs.length > 0 ? "partial" : "failed", operations, diffs, {
3159
+ code,
3160
+ message
3161
+ });
3162
+ }
3163
+ }
3164
+ return outputFor("applied", operations, diffs);
3165
+ }
3166
+ function outcomeText(outcome) {
3167
+ const error = outcome.error;
3168
+ return [
3169
+ outcome.summary,
3170
+ error === void 0 ? "" : `Error (${error.code}): ${error.message}`,
3171
+ outcome.diff.length === 0 ? "" : `\n${outcome.diff}`
3172
+ ].filter(Boolean).join("\n");
3173
+ }
3174
+ function renderOutcome(_args, value) {
3175
+ return [{
3176
+ type: "text",
3177
+ text: outcomeText(value)
3178
+ }];
3179
+ }
3180
+ function presentationDiffs(value) {
3181
+ return value.diffs.map((diff) => ({
3182
+ path: diff.path,
3183
+ oldText: diff.oldText,
3184
+ newText: diff.newText
3185
+ }));
3186
+ }
3187
+ function isDiffResultMeta(value) {
3188
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
3189
+ const diffs = value.diffs;
3190
+ return Array.isArray(diffs) && diffs.every((diff) => typeof diff === "object" && diff !== null && typeof diff.path === "string" && typeof diff.newText === "string");
3191
+ }
3192
+ function callView(args, limits) {
3193
+ try {
3194
+ const patch = parseArguments(args, limits);
3195
+ const locations = parsePatch(patch).operations.map((operation) => ({ path: operation.path }));
3196
+ return {
3197
+ card: "generic",
3198
+ title: `Apply patch (${locations.length} file${locations.length === 1 ? "" : "s"})`,
3199
+ kind: "edit",
3200
+ rawInput: patch,
3201
+ locations
3202
+ };
3203
+ } catch {
3204
+ return {
3205
+ card: "generic",
3206
+ title: "Apply patch",
3207
+ kind: "edit",
3208
+ rawInput: args
3209
+ };
3210
+ }
3211
+ }
3212
+ function resultView(_args, result) {
3213
+ if (!result.isError && isDiffResultMeta(result.meta)) return {
3214
+ card: "diff",
3215
+ diffs: result.meta.diffs
3216
+ };
3217
+ }
3218
+ const parameters = {
3219
+ type: "object",
3220
+ additionalProperties: false,
3221
+ properties: { patch: {
3222
+ type: "string",
3223
+ description: "Complete raw Codex Add File/Update File patch text."
3224
+ } },
3225
+ required: ["patch"]
3226
+ };
3227
+ const outputSchema = {
3228
+ type: "object",
3229
+ additionalProperties: false,
3230
+ properties: {
3231
+ status: {
3232
+ type: "string",
3233
+ enum: [
3234
+ "applied",
3235
+ "partial",
3236
+ "failed",
3237
+ "cancelled"
3238
+ ]
3239
+ },
3240
+ summary: { type: "string" },
3241
+ diff: { type: "string" },
3242
+ operations: {
3243
+ type: "array",
3244
+ items: {
3245
+ type: "object",
3246
+ additionalProperties: false,
3247
+ properties: {
3248
+ path: { type: "string" },
3249
+ action: {
3250
+ type: "string",
3251
+ enum: ["add", "update"]
3252
+ },
3253
+ status: {
3254
+ type: "string",
3255
+ enum: [
3256
+ "committed",
3257
+ "failed",
3258
+ "unattempted"
3259
+ ]
3260
+ },
3261
+ error: {
3262
+ type: "object",
3263
+ additionalProperties: false,
3264
+ properties: {
3265
+ code: { type: "string" },
3266
+ message: { type: "string" }
3267
+ },
3268
+ required: ["code", "message"]
3269
+ }
3270
+ },
3271
+ required: [
3272
+ "path",
3273
+ "action",
3274
+ "status"
3275
+ ]
3276
+ }
3277
+ },
3278
+ diffs: {
3279
+ type: "array",
3280
+ items: {
3281
+ type: "object",
3282
+ additionalProperties: false,
3283
+ properties: {
3284
+ path: { type: "string" },
3285
+ oldText: { oneOf: [{ type: "string" }, { type: "null" }] },
3286
+ newText: { type: "string" }
3287
+ },
3288
+ required: [
3289
+ "path",
3290
+ "oldText",
3291
+ "newText"
3292
+ ]
3293
+ }
3294
+ },
3295
+ error: {
3296
+ type: "object",
3297
+ additionalProperties: false,
3298
+ properties: {
3299
+ code: { type: "string" },
3300
+ message: { type: "string" }
3301
+ },
3302
+ required: ["code", "message"]
3303
+ }
3304
+ },
3305
+ required: [
3306
+ "status",
3307
+ "summary",
3308
+ "diff",
3309
+ "operations",
3310
+ "diffs"
3311
+ ]
3312
+ };
3313
+ /** Build the registry definition for the direct raw patch tool. */
3314
+ function createApplyPatchTool(services, progressByExecution, limits = DEFAULT_APPLY_PATCH_LIMITS) {
3315
+ return {
3316
+ name: APPLY_PATCH_NAME,
3317
+ description: APPLY_PATCH_DESCRIPTION,
3318
+ parameters,
3319
+ output: {
3320
+ schema: outputSchema,
3321
+ render: renderOutcome,
3322
+ presentationMeta: (_args, value) => ({ diffs: presentationDiffs(value).map((diff) => ({
3323
+ path: diff.path,
3324
+ oldText: diff.oldText,
3325
+ newText: diff.newText
3326
+ })) })
3327
+ },
3328
+ async execute(args, exec) {
3329
+ const outcome = await applyPatchToFileSystem(parseArguments(args, limits), services, exec, limits);
3330
+ progressByExecution.set(exec, { outcome });
3331
+ return outcome;
3332
+ },
3333
+ finalizeContent(exec, result) {
3334
+ const progress = progressByExecution.get(exec);
3335
+ progressByExecution.delete(exec);
3336
+ if (progress === void 0 || !result.isError) return void 0;
3337
+ return renderOutcome(exec.arguments, progress.outcome);
3338
+ },
3339
+ presentCall: (args) => callView(args, limits),
3340
+ presentResult: resultView
3341
+ };
3342
+ }
3343
+ //#endregion
3344
+ //#region src/index.ts
3345
+ const name = PLUGIN_NAME;
3346
+ const inject = [
3347
+ "agents",
3348
+ "sessions",
3349
+ "settings",
3350
+ "credentials",
3351
+ "llm",
3352
+ "attachments",
3353
+ "commands",
3354
+ "systemPrompt",
3355
+ "tools",
3356
+ "fs",
3357
+ "storageDomain"
3358
+ ];
3359
+ /** Install the selected Nanocodex AgentFactory and private compaction owner. */
3360
+ async function apply(ctx) {
3361
+ const checkpoints = await ctx.storageDomain.open(nanocodexCheckpointDomain);
3362
+ ctx.effect(() => () => checkpoints.close(), "dsh-nanocodex.checkpoints");
3363
+ const settings = ctx.settings.register(SETTINGS_NAMESPACE, NanocodexSettingsSchema, { applies: "live" });
3364
+ const adapter = new NanocodexLlmAdapter(ctx);
3365
+ const routes = configuredNanocodexProviders(settings.get());
3366
+ const adapterRegistration = ctx.llm.registerAdapter(routes.length > 0 ? routes : ["openai"], adapter);
3367
+ if (routes.length === 0) adapterRegistration.replace([]);
3368
+ ctx.effect(() => settings.watch((next) => {
3369
+ const nextRoutes = configuredNanocodexProviders(next);
3370
+ adapterRegistration.replace(nextRoutes);
3371
+ }), "dsh-nanocodex.settings");
3372
+ ctx.llm.registerConfigurableProviders([{
3373
+ provider: "openai",
3374
+ displayName: "OpenAI Responses",
3375
+ settingsNs: SETTINGS_NAMESPACE,
3376
+ settingsPath: ["providers", "openai"],
3377
+ declared: false
3378
+ }, {
3379
+ provider: "openai-codex-responses",
3380
+ displayName: "OpenAI Codex Responses",
3381
+ settingsNs: SETTINGS_NAMESPACE,
3382
+ settingsPath: ["providers", "openai-codex-responses"],
3383
+ declared: false
3384
+ }]);
3385
+ ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
3386
+ const engine = new NanocodexEngine(ctx, checkpoints.table("sessions"));
3387
+ const patchProgress = /* @__PURE__ */ new WeakMap();
3388
+ ctx.effect(() => {
3389
+ return ctx.tools.register(createApplyPatchTool({
3390
+ fs: ctx.fs,
3391
+ ctx
3392
+ }, patchProgress));
3393
+ }, "dsh-nanocodex.apply-patch");
3394
+ new NanocodexCompactionEngine(ctx, engine);
3395
+ const factory = new NanocodexFactory(ctx, engine);
3396
+ ctx.effect(() => {
3397
+ const unregister = ctx.agents.setFactory(factory);
3398
+ return async () => {
3399
+ unregister();
3400
+ await factory.disposeAll();
3401
+ };
3402
+ }, "dsh-nanocodex.factory");
3403
+ }
3404
+ //#endregion
3405
+ export { APPLY_PATCH_DESCRIPTION, APPLY_PATCH_GRAMMAR, APPLY_PATCH_NAME, DEFAULT_MAX_PATCH_CHARS, DEFAULT_MAX_PATCH_FILES, DEFAULT_MAX_PATCH_FILE_BYTES, INTERRUPTED_TOOL_OUTPUT, MODEL_CONTEXT_WINDOW, NanocodexAgent, NanocodexCompactionEngine, NanocodexEngine, NanocodexFactory, NanocodexLlmAdapter, NanocodexSettingsSchema, PLUGIN_NAME, PatchError, SETTINGS_NAMESPACE, SUPPORTED_MODELS, SUPPORTED_PROVIDERS, apply, applyChunks, applyPatchToFileSystem, buildHistoryProjection, buildHistorySeed, buildPromptInput, configuredNanocodexProviders, createApplyPatchTool, createToolBridge, historyToolCallId, inject, isSupportedModel, isSupportedRoute, name, nanocodexCheckpointDomain, normalizeNanocodexSessionId, parsePatch, plainText, resolveNanocodexRoute, seekSequence, supportedThinking };