zidane 7.2.5 → 8.0.4
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.
- package/LICENSE +21 -0
- package/README.md +34 -6
- package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
- package/dist/acp-DQDhi2dj.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -1
- package/dist/acp-cli.js +9 -66
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +10 -590
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +3 -3
- package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
- package/dist/agent-DSLNmBp0.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
- package/dist/anthropic-CD00LhUP.js.map +1 -0
- package/dist/artifacts-CV2hUKnL.js.map +1 -1
- package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
- package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
- package/dist/call-tool-dHlbGOIY.js +70 -0
- package/dist/call-tool-dHlbGOIY.js.map +1 -0
- package/dist/chat/pure.d.ts +372 -6
- package/dist/chat/pure.d.ts.map +1 -1
- package/dist/chat/pure.js +35 -244
- package/dist/chat/pure.js.map +1 -1
- package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
- package/dist/chat-JPJJ8uu5.js.map +1 -0
- package/dist/chat.d.ts +4 -6
- package/dist/chat.js +23 -12
- package/dist/cli-shared-C-YCaglL.js +159 -0
- package/dist/cli-shared-C-YCaglL.js.map +1 -0
- package/dist/client-core-BQSyDALT.d.ts +2115 -0
- package/dist/client-core-BQSyDALT.d.ts.map +1 -0
- package/dist/codemode-worker.d.ts +1 -0
- package/dist/codemode-worker.js +155 -0
- package/dist/codemode-worker.js.map +1 -0
- package/dist/codemode.d.ts +352 -0
- package/dist/codemode.d.ts.map +1 -0
- package/dist/codemode.js +5 -0
- package/dist/connection-Dr-Js4yZ.d.ts +28 -0
- package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
- package/dist/connection-loop-gdh22LTI.js +112 -0
- package/dist/connection-loop-gdh22LTI.js.map +1 -0
- package/dist/context-segments-BHKlagL6.js +1016 -0
- package/dist/context-segments-BHKlagL6.js.map +1 -0
- package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
- package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +19 -8
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +1 -440
- package/dist/contexts/docker.d.ts +36 -7
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +1 -456
- package/dist/contexts/e2b.d.ts +68 -13
- package/dist/contexts/e2b.d.ts.map +1 -1
- package/dist/contexts/e2b.js +1 -382
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +5 -0
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
- package/dist/contexts-d7UolYot.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/cred-keys-GpBg71cP.js +105 -0
- package/dist/cred-keys-GpBg71cP.js.map +1 -0
- package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
- package/dist/credentials-JttEuRON.js.map +1 -0
- package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
- package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
- package/dist/daemon-cli.d.ts +1 -1
- package/dist/daemon-cli.js +3 -2
- package/dist/daemon-cli.js.map +1 -1
- package/dist/daytona-DG3omipZ.js +513 -0
- package/dist/daytona-DG3omipZ.js.map +1 -0
- package/dist/defaults-Cx3zvDOV.js +23 -0
- package/dist/defaults-Cx3zvDOV.js.map +1 -0
- package/dist/dispatcher-BMlu2BmZ.js +4941 -0
- package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
- package/dist/docker-BZhMMJUp.js +727 -0
- package/dist/docker-BZhMMJUp.js.map +1 -0
- package/dist/e2b-CEVOdRib.js +480 -0
- package/dist/e2b-CEVOdRib.js.map +1 -0
- package/dist/edit-utils-EGosADZq.js +234 -0
- package/dist/edit-utils-EGosADZq.js.map +1 -0
- package/dist/environment-8wsKE2Ln.js +214 -0
- package/dist/environment-8wsKE2Ln.js.map +1 -0
- package/dist/eval-BNcvle_J.js +1595 -0
- package/dist/eval-BNcvle_J.js.map +1 -0
- package/dist/eval.d.ts +2 -2
- package/dist/eval.js +1 -1489
- package/dist/executor-Cx5-8yqi.js +621 -0
- package/dist/executor-Cx5-8yqi.js.map +1 -0
- package/dist/extension-authoring-DyGGmq9V.js +385 -0
- package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +3 -3
- package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
- package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
- package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
- package/dist/format-C2NQEVUH.js.map +1 -0
- package/dist/format-bytes-C7tfc3Zm.js +55 -0
- package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
- package/dist/headless-C_rNUuYC.js +960 -0
- package/dist/headless-C_rNUuYC.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -413
- package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
- package/dist/history-search-CcYzF8-0.js.map +1 -0
- package/dist/history-search-worker.d.ts +1 -1
- package/dist/history-search-worker.js +1 -1
- package/dist/host-D7NrI03V.js +378 -0
- package/dist/host-D7NrI03V.js.map +1 -0
- package/dist/host-signals-BcP5QKUD.js +118 -0
- package/dist/host-signals-BcP5QKUD.js.map +1 -0
- package/dist/identity-CwfO8K-t.js +26 -0
- package/dist/identity-CwfO8K-t.js.map +1 -0
- package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
- package/dist/index-BGe1fQT7.d.ts.map +1 -0
- package/dist/index-BVQsIKGn.d.ts +448 -0
- package/dist/index-BVQsIKGn.d.ts.map +1 -0
- package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
- package/dist/index-CHABJd-L.d.ts.map +1 -0
- package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
- package/dist/index-D6a7qXpg.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +27 -23
- package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
- package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
- package/dist/ipc/remote.d.ts +313 -0
- package/dist/ipc/remote.d.ts.map +1 -0
- package/dist/ipc/remote.js +4 -0
- package/dist/ipc.d.ts +6 -964
- package/dist/ipc.js +403 -4
- package/dist/ipc.js.map +1 -0
- package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
- package/dist/isolation-D9L650W5.d.ts.map +1 -0
- package/dist/json-rpc--HXQti1E.js +350 -0
- package/dist/json-rpc--HXQti1E.js.map +1 -0
- package/dist/links-j8rED1sR.js +603 -0
- package/dist/links-j8rED1sR.js.map +1 -0
- package/dist/local-terminal-D8QThwo_.js +252 -0
- package/dist/local-terminal-D8QThwo_.js.map +1 -0
- package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
- package/dist/logger-Gtqlayx8.js.map +1 -0
- package/dist/logs-view-CWG8aUAG.js.map +1 -1
- package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
- package/dist/mcp-_wecH9Qe.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/media-sniff-UKijEaiQ.js.map +1 -1
- package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
- package/dist/messages-BqiwcD2Q.js.map +1 -0
- package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
- package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
- package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
- package/dist/openai-compat-CFrpH7uq.js.map +1 -0
- package/dist/orchestration-BCGmB8Ps.js +511 -0
- package/dist/orchestration-BCGmB8Ps.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/output/terminal.js +13 -7
- package/dist/output/terminal.js.map +1 -1
- package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
- package/dist/plugins-DS7mahzZ.js.map +1 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +1 -2
- package/dist/prelude-Cxo4t74B.js +517 -0
- package/dist/prelude-Cxo4t74B.js.map +1 -0
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -2
- package/dist/project-root-B8zADr4o.js +167 -0
- package/dist/project-root-B8zADr4o.js.map +1 -0
- package/dist/project-user-paths-BSgtrRxl.js +58 -0
- package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
- package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
- package/dist/prompts-DYcvqShf.d.ts.map +1 -0
- package/dist/protocol-DnhqqSmE.js +13 -0
- package/dist/protocol-DnhqqSmE.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +1 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +3 -2
- package/dist/providers/cerebras.js.map +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -2
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +15 -12
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +3 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +3 -3
- package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
- package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/registry-D6ShBWJU.js +5234 -0
- package/dist/registry-D6ShBWJU.js.map +1 -0
- package/dist/remote-hosts-BtA_9ZGp.js +97 -0
- package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
- package/dist/restate.d.ts +2 -3
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +631 -572
- package/dist/restate.js.map +1 -1
- package/dist/run-summary-CklLiZ4y.js +288 -0
- package/dist/run-summary-CklLiZ4y.js.map +1 -0
- package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
- package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
- package/dist/session/sqlite.d.ts +1 -2
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +1 -616
- package/dist/session-factory-BUGY5P40.js +1576 -0
- package/dist/session-factory-BUGY5P40.js.map +1 -0
- package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
- package/dist/session-xh9JXu6i.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/settings-keys-Ckf29NeM.js +677 -0
- package/dist/settings-keys-Ckf29NeM.js.map +1 -0
- package/dist/shell-path-Dqaoow2J.js +412 -0
- package/dist/shell-path-Dqaoow2J.js.map +1 -0
- package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
- package/dist/skills-DKqZJm4X.js.map +1 -0
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -2
- package/dist/source-mPExaUm8.js +381 -0
- package/dist/source-mPExaUm8.js.map +1 -0
- package/dist/sqlite-DV1DQzN2.js +698 -0
- package/dist/sqlite-DV1DQzN2.js.map +1 -0
- package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
- package/dist/src-BRMYRJib.js.map +1 -0
- package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
- package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
- package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
- package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
- package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
- package/dist/timeout-DEaqslLO.d.ts.map +1 -1
- package/dist/todo-edits-Dn251YUP.js +1790 -0
- package/dist/todo-edits-Dn251YUP.js.map +1 -0
- package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
- package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
- package/dist/tool-state-Dku6YqAB.js +989 -0
- package/dist/tool-state-Dku6YqAB.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -2
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +1 -2
- package/dist/tools/prompts.d.ts +1 -1
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/web-search.d.ts +1 -2
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +2 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools-_DnT3AGM.js +578 -0
- package/dist/tools-_DnT3AGM.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +4 -2
- package/dist/tui.d.ts +534 -517
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +34933 -27243
- package/dist/tui.js.map +1 -1
- package/dist/turn-operations-B8sokwi5.js +333 -0
- package/dist/turn-operations-B8sokwi5.js.map +1 -0
- package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
- package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
- package/dist/types-C7ZFOztE.d.ts +132 -0
- package/dist/types-C7ZFOztE.d.ts.map +1 -0
- package/dist/types-CH5XpIHw.js +114 -0
- package/dist/types-CH5XpIHw.js.map +1 -0
- package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
- package/dist/types-Cu0ZGDu9.js.map +1 -0
- package/dist/types-IqegbJEE.d.ts +624 -0
- package/dist/types-IqegbJEE.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -1
- package/dist/url-D8HCl65b.js +1259 -0
- package/dist/url-D8HCl65b.js.map +1 -0
- package/dist/wasm-BVT-j5cl.js +33 -0
- package/dist/wasm-BVT-j5cl.js.map +1 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
- package/dist/ws-CIHMvlSh.js +952 -0
- package/dist/ws-CIHMvlSh.js.map +1 -0
- package/dist/ws-client-N7NxzvQp.js +230 -0
- package/dist/ws-client-N7NxzvQp.js.map +1 -0
- package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
- package/dist/xai-CvJTeLrI.js.map +1 -0
- package/dist/xdg-zweed0-t.js +117 -0
- package/dist/xdg-zweed0-t.js.map +1 -0
- package/docs/ACP.md +52 -0
- package/docs/ARCHITECTURE.md +103 -6
- package/docs/ARTIFACTS.md +8 -4
- package/docs/CHAT.md +165 -14
- package/docs/EXECUTION_CONTEXT.md +8 -0
- package/docs/EXTENSIONS.md +123 -2
- package/docs/HEADLESS.md +2 -0
- package/docs/IPC.md +775 -64
- package/docs/MACHINES.md +456 -0
- package/docs/PANELS.md +3 -0
- package/docs/README.md +4 -0
- package/docs/RESTATE.md +3 -2
- package/docs/RUN_IN_BACKGROUND.md +10 -2
- package/docs/SERVER.md +1085 -0
- package/docs/SKILL.md +6 -1
- package/docs/TERMINAL.md +75 -0
- package/docs/TUI.md +65 -14
- package/package.json +58 -26
- package/scripts/eval.ts +4 -0
- package/dist/acp-D-tVyUjw.js.map +0 -1
- package/dist/agent-D0zZnGbt.js.map +0 -1
- package/dist/anthropic-C2aole0G.js.map +0 -1
- package/dist/approvals-DKaJZYlm.js.map +0 -1
- package/dist/atomic-write-B7zr1ddF.js.map +0 -1
- package/dist/auth-BG2rVKSt.js.map +0 -1
- package/dist/completion-core-BRo86R_L.js.map +0 -1
- package/dist/contexts/daytona.js.map +0 -1
- package/dist/contexts/docker.js.map +0 -1
- package/dist/contexts/e2b.js.map +0 -1
- package/dist/contexts-CRE9pw07.js.map +0 -1
- package/dist/eval.js.map +0 -1
- package/dist/format-BF745pLL.js.map +0 -1
- package/dist/glob-DluQFSw1.js +0 -270
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/headless.js.map +0 -1
- package/dist/history-search-C4bk1Wz_.js.map +0 -1
- package/dist/index-BgN1T3lk.d.ts.map +0 -1
- package/dist/index-C37_ppWv.d.ts.map +0 -1
- package/dist/index-C6NEd5y_.d.ts.map +0 -1
- package/dist/ipc-BKVIIOqH.js +0 -331
- package/dist/ipc-BKVIIOqH.js.map +0 -1
- package/dist/ipc.d.ts.map +0 -1
- package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
- package/dist/json-rpc-rSWMUh9b.js +0 -192
- package/dist/json-rpc-rSWMUh9b.js.map +0 -1
- package/dist/logger--C1VQjwJ.js.map +0 -1
- package/dist/mcp-CgNd-f3F.js.map +0 -1
- package/dist/messages-2jcOA_5R.js.map +0 -1
- package/dist/openai-compat-Dy56weK8.js.map +0 -1
- package/dist/output-cap-C9zukcCj.js +0 -100
- package/dist/output-cap-C9zukcCj.js.map +0 -1
- package/dist/panels-B6gRk4bf.js +0 -789
- package/dist/panels-B6gRk4bf.js.map +0 -1
- package/dist/plugins-gbI7SBA6.js.map +0 -1
- package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
- package/dist/run-summary-BU_NgwUT.js +0 -269
- package/dist/run-summary-BU_NgwUT.js.map +0 -1
- package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
- package/dist/session/sqlite.js.map +0 -1
- package/dist/session-BqRP1hS7.js.map +0 -1
- package/dist/shell-path-DfdXrGg9.js +0 -1549
- package/dist/shell-path-DfdXrGg9.js.map +0 -1
- package/dist/skills-BC3dodWp.js.map +0 -1
- package/dist/src-CkNQKFKr.js.map +0 -1
- package/dist/todo-edits-UOwgxGKu.js +0 -528
- package/dist/todo-edits-UOwgxGKu.js.map +0 -1
- package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
- package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
- package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
- package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
- package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
- package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
- package/dist/turn-operations-DrbJlpq9.js.map +0 -1
- package/dist/types-CWgENVLY.js.map +0 -1
- package/dist/xai-CazjStOK.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-state-Dku6YqAB.js","names":[],"sources":["../src/tools/read-state.ts","../src/tools/model-search.ts","../src/tools/spawn-helpers.ts","../src/tool-state.ts"],"sourcesContent":["/**\n * Per-session file read tracking + tool-dedup state.\n *\n * Two concerns, one module so they can share the `WeakMap<Session, …>`\n * pattern (state lives as long as the session does, GC'd alongside it —\n * no public schema changes, no persistence). Each section below stands\n * on its own; the two maps don't share keys or behavior.\n *\n * ## Read-state\n *\n * Shared between `read_file` (dedup re-reads) and `edit` / `multi_edit`\n * (`requireReadBeforeEdit` guard). The state map records, per canonical\n * absolute file path:\n *\n * - `contentHash` — 64-bit FNV-style hash of the bytes the model last saw. Identical\n * re-reads return a stub; edits compare against this hash to detect\n * stale content.\n * - `(offset, limit, maxBytes, lineNumbers)` — slice / shape of the last\n * read, so a wider re-read invalidates the prior stub and a different\n * `lineNumbers` mode doesn't dedup against the previous shape.\n * - `mtimeMs` — wall-clock at last read, diagnostic only.\n *\n * Resolution is split between two helpers so consumers don't have to\n * juggle the `Session` vs. explicit-map cases themselves:\n *\n * - {@link getReadState} returns the map keyed by `Session` instance.\n * - {@link resolveReadStateMap} prefers an explicit `ctx.readState`\n * (the path `spawn`'s `shareReadState` uses to forward the parent's\n * map into a sessionless child) and falls back to the session-keyed\n * lookup. Tools should call this helper, not `getReadState` directly.\n *\n * ## Tool-dedup\n *\n * Backs `behavior.dedupTools`. Records the most recent `(hash, result)`\n * per canonical tool name within a session. Same WeakMap pattern; no\n * cross-talk with the read-state map.\n */\n\nimport type { AliasMaps } from '../aliasing'\nimport type { Session } from '../session'\nimport type { SessionTurn, ToolResultContent } from '../types'\nimport { resolve } from 'node:path'\nimport { toCanonicalName } from '../aliasing'\nimport { toolResultToText } from '../types'\nimport { resolveOldString, styleReplacementForVia } from './edit-utils'\nimport { readFileBatchHeader, readFileTargets } from './read-file-targets'\n\n// ---------------------------------------------------------------------------\n// Read-state\n// ---------------------------------------------------------------------------\n\nexport interface ReadStateEntry {\n contentHash: string\n /** Slice parameters for the last read. */\n offset: number\n limit: number\n maxBytes: number\n /**\n * Whether the prior read emitted line-number prefixes. Tracked so a\n * read with `lineNumbers: true` doesn't dedup against one with\n * `lineNumbers: false` (or vice versa) — the dedup stub would mislead\n * the model about the prior output's shape.\n */\n lineNumbers?: boolean\n /** Wall-clock at last read — diagnostic only, not used for invalidation. */\n mtimeMs: number\n /**\n * Consecutive `requireReadBeforeEdit` drift refusals on this file since\n * the last fresh read. Fuel for {@link staleRefusalHint}: one refusal is\n * routine (the model edited after an out-of-band mutation and just needs\n * to re-read), but repeated refusals mean re-reading isn't fixing it —\n * something OUTSIDE the session (editor auto-save, formatter, codegen\n * watcher) keeps rewriting the file, and the error message must say so\n * instead of repeating advice the model already followed. That repetition\n * is exactly what teaches models to abandon `edit` for raw shell writes\n * that bypass every guard.\n *\n * Cleared by any fresh read (`rememberRead` writes a flag-less entry) and\n * by any successful edit (the mutation-site refresh drops it).\n */\n staleRejections?: number\n /**\n * Set when the on-wire read output this entry tracks can no longer be\n * trusted as-is, so `read_file`'s dedup must MISS on the next identical\n * re-read — the \"unchanged since the previous read\" stub would otherwise\n * point at content the model can no longer see, or that is no longer true.\n *\n * Two writers:\n * - the wire transforms (stale-read elision, tail compaction), which REMOVE\n * the output from the on-wire history;\n * - the mutating tools (`edit` / `multi_edit` / `write_file`), which refresh\n * `contentHash` to the post-mutation bytes — without the flag that refresh\n * would make the very next re-read hash-match and dedup would claim the\n * PRE-mutation body is \"still current\". Marking at the mutation keeps\n * dedup honest in every `elideStaleReads` mode, including off.\n *\n * The entry stays registered either way — `requireReadBeforeEdit` must keep\n * passing (the contentHash still guards against disk drift; un-registering\n * here is what degraded models into shell-edit one-liner workarounds).\n * Cleared by the next fresh read (`rememberRead` writes a flag-less entry).\n */\n elided?: true\n}\n\nexport type ReadStateMap = Map<string, ReadStateEntry>\n\nconst STATE = new WeakMap<Session, ReadStateMap>()\n\n/**\n * Get or lazily create the per-session read-state map. Returns `undefined`\n * when no session is provided.\n *\n * Most tool callers should prefer {@link resolveReadStateMap}, which\n * additionally honors an explicit `ctx.readState` (the path\n * `spawn`'s `shareReadState: true` uses to forward the parent's map\n * into a sessionless child). Use this helper directly only when the\n * Session-keyed map is exactly what you want and you don't need to\n * accept an override.\n */\nexport function getReadState(session: Session | undefined): ReadStateMap | undefined {\n if (!session)\n return undefined\n let map = STATE.get(session)\n if (!map) {\n map = new Map()\n STATE.set(session, map)\n }\n return map\n}\n\n/**\n * Resolve the active read-state map from a tool context. An explicit\n * `ctx.readState` wins (used by `spawn`'s `shareReadState` opt-in to\n * forward the parent's map into a sessionless child); otherwise the\n * usual `Session`-keyed lookup applies.\n *\n * Tools should call this helper instead of `getReadState(ctx.session)`\n * directly so the `shareReadState` plumbing is honored uniformly.\n */\nexport function resolveReadStateMap(ctx: {\n session?: Session\n readState?: ReadStateMap\n}): ReadStateMap | undefined {\n return ctx.readState ?? getReadState(ctx.session)\n}\n\n/**\n * Mark a read-state entry as elided from the on-wire history WITHOUT\n * un-registering it.\n *\n * Used by the loop's `elideStaleReads` pass. The entry must survive —\n * deleting it made the `requireReadBeforeEdit` gate reject the very next\n * edit on a file the model had demonstrably read and just edited (the\n * gate's failure mode then defeats the gate: models degrade to raw shell\n * edits that bypass the guard entirely). The `elided` flag instead\n * defeats only the dedup stub: the next identical re-read returns the\n * full body again, and `rememberRead` clears the flag.\n *\n * No-op when the path has no entry in the resolved map(s).\n */\nexport function markReadStateElided(ctx: {\n session?: Session\n readState?: ReadStateMap\n}, cwd: string, path: string): void {\n const key = readStateKey(cwd, path)\n for (const map of [ctx.readState, ctx.session ? STATE.get(ctx.session) : undefined]) {\n const entry = map?.get(key)\n if (entry && entry.elided !== true)\n map!.set(key, { ...entry, elided: true })\n }\n}\n\n/**\n * Record one `requireReadBeforeEdit` drift refusal against `entry` and\n * return the consecutive count (1 for the first). The caller composes the\n * refusal message with {@link staleRefusalHint}.\n */\nexport function noteStaleRejection(map: ReadStateMap, key: string, entry: ReadStateEntry): number {\n const count = (entry.staleRejections ?? 0) + 1\n map.set(key, { ...entry, staleRejections: count })\n return count\n}\n\n/**\n * Escalating suffix for a drift refusal. Empty on the first refusal — the\n * base \"re-read the file\" advice is correct and sufficient. From the second\n * consecutive refusal on, the model has demonstrably re-read and been\n * refused again, so repeating the same advice reads as a broken tool;\n * name the real hypothesis (an external writer) instead.\n */\nexport function staleRefusalHint(count: number): string {\n if (count < 2)\n return ''\n return ` This is refusal #${count} since your last read — a process outside this session (editor auto-save, formatter, codegen watcher) may be rewriting the file between your reads.`\n}\n\n/**\n * Canonical read-state key for a `(cwd, path)` pair.\n *\n * `ctx.execution.readFile(handle, path)` resolves the model-provided\n * path against `handle.cwd` before touching disk — so `src/App.tsx`,\n * `./src/App.tsx`, and `/abs/cwd/src/App.tsx` all read the **same**\n * bytes. The read-state map MUST mirror that resolution: without it,\n * a model that reads `src/App.tsx` and then edits `./src/App.tsx`\n * trips the `requireReadBeforeEdit` gate for a file it demonstrably\n * already saw (the gate's \"has not been read in this session\" message\n * fires on a stale key).\n *\n * `node:path`'s `resolve(cwd, path)` short-circuits when `path` is\n * already absolute, so absolute and relative shapes converge on the\n * same canonical form. We don't `realpath()` symlinks — the file IS\n * the path the model addressed, not the realpath behind it; the host's\n * execution context decides how to dereference.\n */\nexport function readStateKey(cwd: string, path: string): string {\n return resolve(cwd, path)\n}\n\n/**\n * 64-bit content hash: two independent 32-bit FNV-1a-style lanes (different\n * offset bases + multipliers) concatenated as 16 hex chars. Fast,\n * non-cryptographic — but wide enough that the `requireReadBeforeEdit`\n * drift gate, which silently corrupts a file on a false \"unchanged\"\n * verdict, isn't betting on a 32-bit birthday bound (~1 in 4 billion per\n * pair; uncomfortably reachable over many files × many sessions). Entries\n * live only in per-session in-memory maps, so widening needs no\n * stored-format migration. Still cheaper than allocating a Buffer and\n * pulling in `crypto`.\n */\nexport function hashContent(text: string): string {\n let h1 = 0x811C9DC5 // FNV-1a offset basis\n let h2 = 0x7EE36237 // arbitrary distinct basis for the second lane\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i)\n h1 = Math.imul(h1 ^ c, 0x01000193) >>> 0 // FNV prime 16777619\n h2 = Math.imul(h2 ^ c, 0x85EBCA6B) >>> 0 // murmur3 fmix multiplier\n }\n return h1.toString(16).padStart(8, '0') + h2.toString(16).padStart(8, '0')\n}\n\nexport interface HydrateReadStateOptions {\n /** Canonical read-file tool name. Defaults to `read_file`. */\n readFileToolName?: string\n /** Canonical write-file tool name. Defaults to `write_file`. */\n writeFileToolName?: string\n /** Canonical edit tool name. Defaults to `edit`. */\n editToolName?: string\n /** Canonical multi-edit tool name. Defaults to `multi_edit`. */\n multiEditToolName?: string\n /** Agent-level default for omitted `read_file.lineNumbers`. */\n defaultLineNumbers?: boolean\n /**\n * Optional replay-only fallback for read_file results whose transcript output\n * is partial/truncated. Receives the model-provided path and should return\n * the current full text bytes to hash exactly like live read_file.\n */\n readFileForHash?: (path: string) => Promise<string | null | undefined>\n /**\n * Tool-name aliases in effect for the transcript being replayed. zidane's\n * loop canonicalizes tool-call names before persisting, so durable turns\n * normally already carry canonical names — but a transcript captured from a\n * host that persists WIRE names (the aliased form the model saw, e.g.\n * `Read` / `Edit`), or one imported from another stack, would otherwise be\n * unrecognized and hydrate nothing. When provided, each tool-call name is\n * resolved to canonical (`toCanonicalName`) before matching, so both the\n * canonical and the aliased shape are honored. No-op for already-canonical\n * names.\n */\n aliasMaps?: AliasMaps\n}\n\ntype NormalizedHydrateReadStateOptions = Required<Omit<HydrateReadStateOptions, 'readFileForHash' | 'aliasMaps'>> & {\n readFileForHash?: HydrateReadStateOptions['readFileForHash']\n aliasMaps?: AliasMaps\n}\n\ninterface PendingToolCall {\n name: string\n input: Record<string, unknown>\n}\n\ninterface KnownFile {\n content: string\n entry: ReadStateEntry\n}\n\nconst READ_FILE_DEFAULT_OFFSET = 1\nconst READ_FILE_DEFAULT_LIMIT = 2000\nconst READ_FILE_DEFAULT_MAX_BYTES = 262_144\nconst READ_FILE_FOOTER_RE = /\\n\\n…(?:read lines|truncated)/\nconst READ_FILE_LINE_RE = /^\\d{1,9}\\t(.*)$/\n\n/**\n * Rebuild the per-session read-state map from durable session turns.\n *\n * Durable replay runtimes may return recorded tool results without re-running\n * the tool body, so in-memory side effects like `readState.set(...)` can be\n * missing even though the transcript contains the successful `read_file`.\n * This helper is intentionally opt-in: live hosts keep the normal side-effect\n * behavior unless they call it themselves.\n *\n * Hydration only fills missing keys. Live in-memory state may already reflect a\n * newer read or edit from the current run, and durable history cannot prove it\n * is fresher. Relative paths are resolved against the current execution cwd,\n * matching replay hosts that run sessions from a stable workspace.\n */\nexport function hydrateReadStateFromSession(\n session: Session | undefined,\n cwd: string,\n options: HydrateReadStateOptions = {},\n): number {\n if (!session)\n return 0\n\n const readState = getReadState(session)\n if (!readState)\n return 0\n\n const normalized = normalizeHydrateReadStateOptions(options)\n const pending = new Map<string, PendingToolCall>()\n const known = new Map<string, KnownFile>()\n const preexistingKeys = new Set(readState.keys())\n let hydrated = 0\n\n for (const turn of session.turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_call') {\n // Resolve wire aliases (e.g. `Read`/`Edit`) to canonical before\n // matching, and STORE the canonical name in `pending` so the\n // downstream `stateFromToolResult` dispatch (which keys off the\n // canonical tool-name options) lines up regardless of how the\n // transcript spelled the call.\n const canonicalName = canonicalToolCallName(block.name, normalized)\n if (\n canonicalName === normalized.readFileToolName\n || canonicalName === normalized.writeFileToolName\n || canonicalName === normalized.editToolName\n || canonicalName === normalized.multiEditToolName\n ) {\n pending.set(block.id, { name: canonicalName, input: block.input })\n }\n continue\n }\n\n if (block.type !== 'tool_result')\n continue\n\n const call = pending.get(block.callId)\n if (!call)\n continue\n pending.delete(block.callId)\n if (block.isError === true)\n continue\n\n const output = toolResultToText(block.output)\n for (const next of stateFromToolResult(call, output, turn, cwd, normalized, known)) {\n if (!preexistingKeys.has(next.key)) {\n readState.set(next.key, next.file.entry)\n hydrated += 1\n }\n known.set(next.key, next.file)\n }\n }\n }\n\n return hydrated\n}\n\n/**\n * Async replay hydrator for durable hosts that can cheaply read the current\n * full file through their execution context. Transcript-only reconstruction is\n * still preferred for full reads; partial/truncated read_file entries fall back\n * to `readFileForHash` and hash those full current bytes.\n */\nexport async function hydrateReadStateFromSessionAsync(\n session: Session | undefined,\n cwd: string,\n options: HydrateReadStateOptions = {},\n): Promise<number> {\n if (!session)\n return 0\n\n const readState = getReadState(session)\n if (!readState)\n return 0\n\n const normalized = normalizeHydrateReadStateOptions(options)\n const pending = new Map<string, PendingToolCall>()\n const known = new Map<string, KnownFile>()\n const preexistingKeys = new Set(readState.keys())\n let hydrated = 0\n\n for (const turn of session.turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_call') {\n // Resolve wire aliases (e.g. `Read`/`Edit`) to canonical before\n // matching, and STORE the canonical name in `pending` so the\n // downstream `stateFromToolResult` dispatch (which keys off the\n // canonical tool-name options) lines up regardless of how the\n // transcript spelled the call.\n const canonicalName = canonicalToolCallName(block.name, normalized)\n if (\n canonicalName === normalized.readFileToolName\n || canonicalName === normalized.writeFileToolName\n || canonicalName === normalized.editToolName\n || canonicalName === normalized.multiEditToolName\n ) {\n pending.set(block.id, { name: canonicalName, input: block.input })\n }\n continue\n }\n\n if (block.type !== 'tool_result')\n continue\n\n const call = pending.get(block.callId)\n if (!call)\n continue\n pending.delete(block.callId)\n if (block.isError === true)\n continue\n\n const output = toolResultToText(block.output)\n for (const next of await stateFromToolResultAsync(call, output, turn, cwd, normalized, known)) {\n if (!preexistingKeys.has(next.key)) {\n readState.set(next.key, next.file.entry)\n hydrated += 1\n }\n known.set(next.key, next.file)\n }\n }\n }\n\n return hydrated\n}\n\nfunction normalizeHydrateReadStateOptions(options: HydrateReadStateOptions): NormalizedHydrateReadStateOptions {\n return {\n readFileToolName: options.readFileToolName ?? 'read_file',\n writeFileToolName: options.writeFileToolName ?? 'write_file',\n editToolName: options.editToolName ?? 'edit',\n multiEditToolName: options.multiEditToolName ?? 'multi_edit',\n defaultLineNumbers: options.defaultLineNumbers ?? true,\n ...(options.readFileForHash ? { readFileForHash: options.readFileForHash } : {}),\n ...(options.aliasMaps ? { aliasMaps: options.aliasMaps } : {}),\n }\n}\n\n/**\n * Resolve a transcript tool-call name to its canonical form for matching.\n * Honors `aliasMaps` (wire → canonical) when present; otherwise the name is\n * already canonical (zidane persists canonical names) and passes through.\n */\nfunction canonicalToolCallName(name: string, options: NormalizedHydrateReadStateOptions): string {\n return options.aliasMaps ? toCanonicalName(name, options.aliasMaps) : name\n}\n\nfunction stateFromToolResult(\n call: PendingToolCall,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n options: NormalizedHydrateReadStateOptions,\n known: Map<string, KnownFile>,\n): Array<{ key: string, file: KnownFile }> {\n let next: { key: string, file: KnownFile } | undefined\n if (call.name === options.readFileToolName) {\n return readFileSections(call.input, output).flatMap(({ input, output }) => {\n const state = readFileStateFromResult(input, output, resultTurn, cwd, options.defaultLineNumbers)\n return state ? [state] : []\n })\n }\n if (call.name === options.writeFileToolName)\n next = writeFileStateFromResult(call.input, output, resultTurn, cwd)\n else if (call.name === options.editToolName)\n next = editStateFromResult(call.input, output, resultTurn, cwd, known)\n else if (call.name === options.multiEditToolName)\n next = multiEditStateFromResult(call.input, output, resultTurn, cwd, known)\n return next ? [next] : []\n}\n\n/**\n * A `read_file` call as single-path reads: the call itself, or, for a\n * `paths` batch, one per `==> path <==` section (each with that entry's\n * offset / limit). Sections are found in call order; hydration stops at the\n * first header it can't locate.\n */\nfunction readFileSections(input: Record<string, unknown>, output: string): Array<{ input: Record<string, unknown>, output: string }> {\n if (!Array.isArray(input.paths))\n return [{ input, output }]\n const targets = readFileTargets(input)\n const out: Array<{ input: Record<string, unknown>, output: string }> = []\n let cursor = 0\n for (let i = 0; i < targets.length; i++) {\n const header = `${readFileBatchHeader(targets[i]!.path)}\\n`\n if (!output.startsWith(header, cursor))\n break\n const start = cursor + header.length\n const nextHeader = i + 1 < targets.length ? `\\n\\n${readFileBatchHeader(targets[i + 1]!.path)}\\n` : undefined\n const end = nextHeader ? output.indexOf(nextHeader, start) : output.length\n if (end === -1)\n break\n const { path, offset, limit } = targets[i]!\n out.push({\n input: { path, offset, limit, ...(typeof input.lineNumbers === 'boolean' ? { lineNumbers: input.lineNumbers } : {}) },\n output: output.slice(start, end),\n })\n cursor = end + 2\n }\n return out\n}\n\nasync function stateFromToolResultAsync(\n call: PendingToolCall,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n options: NormalizedHydrateReadStateOptions,\n known: Map<string, KnownFile>,\n): Promise<Array<{ key: string, file: KnownFile }>> {\n if (call.name === options.readFileToolName) {\n const out: Array<{ key: string, file: KnownFile }> = []\n for (const section of readFileSections(call.input, output)) {\n const state = await readFileStateFromResultAsync(section.input, section.output, resultTurn, cwd, {\n defaultLineNumbers: options.defaultLineNumbers,\n readFileForHash: options.readFileForHash,\n })\n if (state)\n out.push(state)\n }\n return out\n }\n return stateFromToolResult(call, output, resultTurn, cwd, options, known)\n}\n\nfunction readFileStateFromResult(\n input: Record<string, unknown>,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n defaultLineNumbers: boolean,\n): { key: string, file: KnownFile } | undefined {\n const path = typeof input.path === 'string' ? input.path : undefined\n if (!path || !isSuccessfulFullReadOutput(output))\n return undefined\n\n const lineNumbers = typeof input.lineNumbers === 'boolean' ? input.lineNumbers : defaultLineNumbers\n const content = lineNumbers ? stripReadFileLineNumbers(output) : output\n if (content === undefined)\n return undefined\n\n const entry: ReadStateEntry = {\n contentHash: hashContent(content),\n offset: normalizeReadInteger(input.offset, READ_FILE_DEFAULT_OFFSET),\n limit: normalizeReadInteger(input.limit, READ_FILE_DEFAULT_LIMIT),\n maxBytes: normalizeReadInteger(input.maxBytes, READ_FILE_DEFAULT_MAX_BYTES),\n lineNumbers,\n mtimeMs: resultTurn.createdAt,\n }\n return { key: readStateKey(cwd, path), file: { content, entry } }\n}\n\nasync function readFileStateFromResultAsync(\n input: Record<string, unknown>,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n options: {\n defaultLineNumbers: boolean\n readFileForHash?: HydrateReadStateOptions['readFileForHash']\n },\n): Promise<{ key: string, file: KnownFile } | undefined> {\n const reconstructed = readFileStateFromResult(input, output, resultTurn, cwd, options.defaultLineNumbers)\n if (reconstructed)\n return reconstructed\n\n const path = typeof input.path === 'string' ? input.path : undefined\n if (!path || !options.readFileForHash || !isSuccessfulReadOutput(output))\n return undefined\n\n let content: string | null | undefined\n try {\n content = await options.readFileForHash(path)\n }\n catch {\n return undefined\n }\n if (typeof content !== 'string')\n return undefined\n\n const lineNumbers = typeof input.lineNumbers === 'boolean' ? input.lineNumbers : options.defaultLineNumbers\n const entry: ReadStateEntry = {\n contentHash: hashContent(content),\n offset: normalizeReadInteger(input.offset, READ_FILE_DEFAULT_OFFSET),\n limit: normalizeReadInteger(input.limit, READ_FILE_DEFAULT_LIMIT),\n maxBytes: normalizeReadInteger(input.maxBytes, READ_FILE_DEFAULT_MAX_BYTES),\n lineNumbers,\n mtimeMs: resultTurn.createdAt,\n }\n return { key: readStateKey(cwd, path), file: { content, entry } }\n}\n\nfunction writeFileStateFromResult(\n input: Record<string, unknown>,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n): { key: string, file: KnownFile } | undefined {\n const path = typeof input.path === 'string' ? input.path : undefined\n const content = typeof input.content === 'string' ? input.content : undefined\n if (!path || content === undefined)\n return undefined\n if (!output.startsWith(`Created ${path} (`) && !output.startsWith(`Updated ${path} (`))\n return undefined\n\n return {\n key: readStateKey(cwd, path),\n file: {\n content,\n entry: {\n contentHash: hashContent(content),\n offset: 0,\n limit: Number.POSITIVE_INFINITY,\n maxBytes: Number.POSITIVE_INFINITY,\n mtimeMs: resultTurn.createdAt,\n // Mirrors the live seed in `write-file.ts`: the write superseded any\n // prior on-wire read output, so the first re-read on the resumed\n // session must miss dedup and serve the fresh body — otherwise the\n // mutation-site honesty fix evaporates across a process restart.\n elided: true,\n },\n },\n }\n}\n\nfunction editStateFromResult(\n input: Record<string, unknown>,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n known: Map<string, KnownFile>,\n): { key: string, file: KnownFile } | undefined {\n const path = typeof input.path === 'string' ? input.path : undefined\n const oldString = typeof input.old_string === 'string' ? input.old_string : undefined\n const newString = typeof input.new_string === 'string' ? input.new_string : undefined\n if (!path || oldString === undefined || newString === undefined)\n return undefined\n if (!output.startsWith(`Edited ${path}: replaced `))\n return undefined\n\n const key = readStateKey(cwd, path)\n const prior = known.get(key)\n if (!prior)\n return undefined\n\n const nextContent = applyEdit(prior.content, oldString, newString, input.replace_all === true)\n if (nextContent === undefined)\n return undefined\n\n return {\n key,\n file: {\n content: nextContent,\n // `elided: true` mirrors the live refresh in `edit.ts`: the hash now\n // matches post-edit bytes while the replayed wire still shows the\n // pre-edit read, so dedup must miss on the next re-read.\n entry: { ...prior.entry, contentHash: hashContent(nextContent), mtimeMs: resultTurn.createdAt, elided: true },\n },\n }\n}\n\ninterface MultiEditStep {\n old_string?: unknown\n new_string?: unknown\n replace_all?: unknown\n}\n\nfunction multiEditStateFromResult(\n input: Record<string, unknown>,\n output: string,\n resultTurn: SessionTurn,\n cwd: string,\n known: Map<string, KnownFile>,\n): { key: string, file: KnownFile } | undefined {\n const path = typeof input.path === 'string' ? input.path : undefined\n const edits = Array.isArray(input.edits) ? (input.edits as MultiEditStep[]) : undefined\n if (!path || !edits)\n return undefined\n if (!output.startsWith(`Edited ${path}: applied `))\n return undefined\n\n const key = readStateKey(cwd, path)\n const prior = known.get(key)\n if (!prior)\n return undefined\n\n const applied = parseMultiEditAppliedSteps(output, edits.length)\n let current = prior.content\n for (let i = 0; i < edits.length; i++) {\n if (!applied.has(i))\n continue\n const step = edits[i]\n if (typeof step.old_string !== 'string' || typeof step.new_string !== 'string')\n return undefined\n const next = applyEdit(current, step.old_string, step.new_string, step.replace_all === true)\n if (next === undefined)\n return undefined\n current = next\n }\n\n return {\n key,\n file: {\n content: current,\n // same rationale as `editStateFromResult` above\n entry: { ...prior.entry, contentHash: hashContent(current), mtimeMs: resultTurn.createdAt, elided: true },\n },\n }\n}\n\nfunction applyEdit(content: string, oldString: string, newString: string, replaceAll: boolean): string | undefined {\n const match = resolveOldString(content, oldString)\n if (!match)\n return undefined\n if (match.occurrences > 1 && !replaceAll)\n return undefined\n const styledReplacement = styleReplacementForVia(newString, match.via, match.actual)\n return replaceAll\n ? content.split(match.actual).join(styledReplacement)\n : content.replace(match.actual, styledReplacement)\n}\n\nfunction parseMultiEditAppliedSteps(output: string, editCount: number): Set<number> {\n const annotationMatch = /<edit-outcomes>\\n([\\s\\S]*?)\\n<\\/edit-outcomes>/.exec(output)\n if (!annotationMatch) {\n return new Set(Array.from({ length: editCount }, (_, i) => i))\n }\n\n const applied = new Set<number>()\n for (const line of annotationMatch[1].split('\\n')) {\n const match = /^#(\\d+) applied$/.exec(line.trim())\n if (!match)\n continue\n const index = Number(match[1]) - 1\n if (index >= 0 && index < editCount)\n applied.add(index)\n }\n return applied\n}\n\nfunction isSuccessfulFullReadOutput(output: string): boolean {\n // Live read_file stores a hash of the raw full file before pagination or\n // truncation. Durable turns only contain rendered output, so partial reads\n // cannot be reconstructed safely; skip them and require a real re-read.\n if (!isSuccessfulReadOutput(output) || READ_FILE_FOOTER_RE.test(output))\n return false\n return true\n}\n\nfunction isSuccessfulReadOutput(output: string): boolean {\n if (output.length === 0)\n return true\n if (\n output.startsWith('File not found:')\n || output.startsWith('[binary file:')\n || output.startsWith('Image:')\n || output.startsWith('Document:')\n || output.startsWith('[image too large to inline:')\n || output.startsWith('[document:')\n || output.startsWith('[document too large to attach:')\n || output.startsWith('Image read failed:')\n || output.startsWith('Document read failed:')\n || output.startsWith('[skipped:')\n || output.includes(' unchanged since the previous read in this session ')\n ) {\n return false\n }\n return true\n}\n\nfunction stripReadFileLineNumbers(output: string): string | undefined {\n const lines = output.split('\\n')\n const stripped: string[] = []\n for (const line of lines) {\n const match = READ_FILE_LINE_RE.exec(line)\n if (!match)\n return undefined\n stripped.push(match[1])\n }\n return stripped.join('\\n')\n}\n\nfunction normalizeReadInteger(value: unknown, fallback: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0)\n return fallback\n return Math.floor(value)\n}\n\n// ---------------------------------------------------------------------------\n// Tool-dedup\n// ---------------------------------------------------------------------------\n\nexport interface ToolDedupEntry {\n hash: string\n result: string | ToolResultContent[]\n /**\n * Number of replay-hits accumulated against this `(name, hash)` since\n * the most recent fresh dispatch. `0` immediately after a fresh\n * dispatch; incremented every time a `tool:gate` handler returns the\n * cached result for an identical input. Used by `dedup-tools` to\n * implement `mode: 'block-after'` — when the count crosses the\n * configured threshold, the gate stops replaying and refuses the call\n * with a `Blocked:` tool_result so the model breaks out of the loop.\n *\n * Resets to `0` on a hash change (a different input under the same\n * tool name is treated as a fresh sequence). Optional for back-compat\n * with consumers that built the entry directly; absent is treated as\n * `0`.\n */\n repeats?: number\n /**\n * The recorded result was a failure or noop verdict (wire `isError`, or\n * `outcome: 'failed' | 'noop'`). Such verdicts are state-dependent —\n * replaying one would freeze a transient failure into a permanent one —\n * so `dedup-tools` never substitutes them on a hash hit; the call\n * re-executes instead. The entry still exists so identical failing\n * calls keep counting toward `mode: 'block-after'` thresholds.\n */\n failed?: boolean\n}\n\nexport type ToolDedupMap = Map<string, ToolDedupEntry>\n\nconst TOOL_DEDUP_STATE = new WeakMap<Session, ToolDedupMap>()\n\n/**\n * Get or lazily create the per-session tool-dedup map. Returns `undefined`\n * when no session is provided — middleware should treat that as \"no dedup\".\n */\nexport function getToolDedupState(session: Session | undefined): ToolDedupMap | undefined {\n if (!session)\n return undefined\n let map = TOOL_DEDUP_STATE.get(session)\n if (!map) {\n map = new Map()\n TOOL_DEDUP_STATE.set(session, map)\n }\n return map\n}\n","/**\n * `model_search` — browse the models children may be spawned on.\n *\n * Companion to `createSpawnTool({ models })` for hosts whose catalog is\n * too large to embed in the spawn schema. Reuses the chat layer's\n * `ModelInfo` objects verbatim (id, display name, context window, real\n * per-Mtok pricing) paired with the runtime `Provider.name` that serves\n * each model — the same pairing the spawn tool validates against, so\n * what this tool lists is exactly what `spawn({ model })` accepts.\n *\n * Kept out of the system prompt by design: the model reaches for this\n * on demand when it wants to cost-tier a delegation, instead of every\n * run paying the token cost of a full catalog dump.\n */\n\nimport type { ModelInfo } from '../chat/providers'\nimport type { ToolDef } from './types'\nimport { toolPrompts } from './prompts'\n\n/**\n * A {@link ModelInfo} paired with the runtime `Provider.name` that serves\n * it. `provider` must match a `Provider.name` available to the run (the\n * primary or a fallback-chain provider — see `ToolContext.providers`);\n * the spawn tool routes the child agent to that instance.\n */\nexport interface SpawnModelEntry {\n provider: string\n model: ModelInfo\n}\n\n/**\n * One display line per model: id, provider, real pricing, context\n * window, reasoning flag. The single source of truth for how models\n * render in BOTH the spawn schema's embedded catalog and `model_search`\n * output, so the model never sees two spellings of the same entry.\n */\nexport function formatModelLine(entry: SpawnModelEntry): string {\n const { model } = entry\n const parts = [`${model.id} (${entry.provider})`]\n if (model.cost)\n parts.push(`$${model.cost.input}/$${model.cost.output} per Mtok`)\n if (typeof model.contextWindow === 'number' && model.contextWindow > 0)\n parts.push(`${Math.round(model.contextWindow / 1000)}k ctx`)\n if (model.reasoning)\n parts.push('reasoning')\n if (model.name && model.name !== model.id)\n parts.push(model.name)\n return parts.join(' · ')\n}\n\n/**\n * Case-insensitive multi-term filter across id, display name, and\n * provider. Every whitespace-separated term must match somewhere\n * (mirrors the TUI picker's `filterModelCatalog` semantics). Empty /\n * whitespace queries pass everything through.\n */\nexport function matchModelEntries(\n entries: readonly SpawnModelEntry[],\n query: string,\n): SpawnModelEntry[] {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return [...entries]\n const terms = trimmed.split(/\\s+/)\n return entries.filter((entry) => {\n const corpus = `${entry.model.id} ${entry.model.name ?? ''} ${entry.provider}`.toLowerCase()\n return terms.every(t => corpus.includes(t))\n })\n}\n\n/**\n * Build the `model_search` tool over a spawn model catalog. Register it\n * alongside `createSpawnTool({ models })` — typically only when the\n * catalog is too large to embed in the spawn schema, though it is\n * harmless (and occasionally useful) alongside an embedded catalog too.\n */\nexport function createModelSearchTool(models: readonly SpawnModelEntry[]): ToolDef {\n return {\n spec: {\n name: 'model_search',\n description: toolPrompts.model_search,\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Optional filter: space-separated terms, ALL must match (e.g. \"haiku\" or \"openrouter qwen\").',\n },\n },\n additionalProperties: false,\n },\n },\n async execute(input): Promise<string> {\n const query = typeof input.query === 'string' ? input.query : ''\n const matches = matchModelEntries(models, query)\n if (matches.length === 0) {\n return query.trim() === ''\n ? 'No models are configured for subagent use.'\n : `No models match \"${query.trim()}\". Call model_search without a query to list all ${models.length}.`\n }\n const header = query.trim() === ''\n ? `${matches.length} model${matches.length === 1 ? '' : 's'} available for spawned subagents:`\n : `${matches.length} of ${models.length} model${models.length === 1 ? '' : 's'} match:`\n return [header, ...matches.map(m => `- ${formatModelLine(m)}`)].join('\\n')\n },\n }\n}\n","import type { McpConnector } from '../agent'\nimport type { McpConnection } from '../mcp'\nimport type { Provider } from '../providers'\nimport type { Session, SessionRun } from '../session'\nimport type { McpServerConfig } from '../types'\nimport type { SpawnModelEntry } from './model-search'\nimport type { SubagentDef, SubagentRegistry } from './spawn'\nimport type { ToolContext, ToolDef } from './types'\nimport { deriveMcpToolMeta, nonOwningMcpView } from '../mcp'\nimport { formatModelLine } from './model-search'\n\n// ---------------------------------------------------------------------------\n// Session-scoped child label counter\n//\n// The `child-N` label that surfaces in transcripts (and in the spawn tool's\n// return string) is allocated per-session — *seeded* from the persisted\n// run tree — rather than from a closure-local counter on the spawn-tool\n// instance. The reason: closure counters don't survive process restarts,\n// so a fresh CLI launch that reopens a session with two prior children\n// (`child-1`, `child-2`) would start its next spawn at `child-1` and\n// collide with the persisted labels. Anchoring on `session.runs.filter(\n// depth > 0).length` makes the live counter pick up exactly where the\n// reloaded transcript's labels left off — matching `eventsFromTurns`'s\n// chronological numbering on the read side.\n//\n// The WeakMap caches the counter for the lifetime of the `Session` object\n// after the first call so the read of `session.runs.length` only happens\n// once per session, even though spawns mutate `session.runs` mid-run.\n//\n// Sessionless agents (no `ctx.session`) fall back to the instance's\n// `state.counter` — the best we can do without a shared anchor.\n// ---------------------------------------------------------------------------\n\nconst sessionChildCounters = new WeakMap<Session, number>()\n\n/** Chronological child runs (depth > 0) — index + 1 is the `child-N` label. */\nexport function childRunsOf(session: Session): SessionRun[] {\n return session.runs.filter(r => (r.depth ?? 0) > 0)\n}\n\n/**\n * Human/model-facing list of this session's child runs. Prefers the label\n * persisted at `startRun` (`SessionRun.label`) and falls back to positional\n * numbering for legacy sessions recorded before labels were stored.\n * Rendered into resume-rejection messages so the model can self-correct\n * with a valid label.\n */\nexport function describeChildRuns(session: Session): string {\n const childRuns = childRunsOf(session)\n if (childRuns.length === 0)\n return 'No child runs exist in this session.'\n return childRuns\n .map((r, i) => `- ${r.label ?? `child-${i + 1}`} · ${r.status} · ${r.prompt.slice(0, 80)}`)\n .join('\\n')\n}\n\n/**\n * Reserve the next `child-N` label for this session. Exported so the durable\n * (child-as-invocation) spawn tool in `src/restate/spawn.ts` allocates labels\n * from the same session-anchored counter — otherwise the two tools would number\n * children independently and a session that used both would collide.\n */\nexport function reserveChildLabel(session: Session): string {\n let counter = sessionChildCounters.get(session)\n if (counter === undefined) {\n // Seed from the highest persisted label, not just the run count: a\n // reservation that never reached `startRun` (pre-run throw) leaves a\n // labeled gap, and count-based seeding would re-mint that label.\n const childRuns = childRunsOf(session)\n counter = childRuns.length\n for (const r of childRuns) {\n const m = r.label ? /^child-(\\d+)$/.exec(r.label) : null\n if (m)\n counter = Math.max(counter, Number(m[1]))\n }\n }\n counter += 1\n sessionChildCounters.set(session, counter)\n return `child-${counter}`\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function extractText(message: unknown): string {\n if (!message || typeof message !== 'object')\n return ''\n\n const msg = message as Record<string, unknown>\n\n if (typeof msg.content === 'string')\n return msg.content\n\n if (Array.isArray(msg.content)) {\n return msg.content\n .filter((block): block is Record<string, unknown> => !!block && typeof block === 'object' && (block as Record<string, unknown>).type === 'text')\n .map(block => block.text)\n .join('\\n')\n }\n\n return ''\n}\n\n/**\n * Read-only tool whitelist applied when a subagent preset has\n * `readonly: true` and no explicit `tools` list. Intentionally narrow —\n * only obviously non-mutating built-ins. Hosts wanting a different\n * read-only profile (e.g. include `glob` for a code-search agent) should\n * pass an explicit `tools` array on the subagent def.\n */\nconst READONLY_TOOL_DEFAULTS = ['read_file', 'grep', 'glob', 'list_files'] as const\n\n/**\n * Apply a subagent preset's tool filter to the parent's tool registry.\n * Always a strict subset of the input — the child agent never gains\n * tools the parent doesn't have. Names that don't match a parent tool\n * are silently dropped (matches MCP's lenient `enabledTools` behavior).\n *\n * Precedence: `def.tools` (explicit list) > `def.readonly: true`\n * (built-in read-only set) > unfiltered.\n */\nexport function filterToolsForSubagent(\n parentTools: Record<string, ToolDef> | undefined,\n def: SubagentDef,\n): Record<string, ToolDef> | undefined {\n if (!parentTools)\n return parentTools\n const explicit = def.tools\n // Match on `tool.spec.name` rather than the parent registry's object\n // key — registry keys are an implementation detail of the host\n // (`basicTools = { readFile, listFiles, ... }` uses camelCase JS\n // keys), but `spec.name` (`'read_file'`, `'list_files'`) is the\n // stable, documented, model-visible identifier hosts reason about.\n if (explicit && explicit.length > 0) {\n const wanted = new Set(explicit)\n const filtered: Record<string, ToolDef> = {}\n for (const [registryKey, t] of Object.entries(parentTools)) {\n if (wanted.has(t.spec.name))\n filtered[registryKey] = t\n }\n return filtered\n }\n if (def.readonly) {\n const wanted = new Set<string>(READONLY_TOOL_DEFAULTS)\n const filtered: Record<string, ToolDef> = {}\n for (const [registryKey, t] of Object.entries(parentTools)) {\n if (wanted.has(t.spec.name))\n filtered[registryKey] = t\n }\n return filtered\n }\n return parentTools\n}\n\n/**\n * Wrap the parent's live MCP connection as a child `mcpConnector`. The\n * child agent gets the SAME already-connected, already-wrapped tool defs\n * (and instructions) — no re-bootstrap, no re-auth, so servers connected\n * through a custom parent connector (e.g. the TUI's OAuth-aware one) stay\n * reachable from subagents. `close` is a no-op: the parent owns the\n * transports and closes them on its own destroy; a child's destroy must\n * never tear down connections the parent is still using.\n *\n * Shared tool defs keep the hook bus they were built with, so\n * `mcp:tool:gate` / `mcp:tool:transform` fire on the parent's hooks —\n * child MCP calls are governed by the same gate policy as the session\n * that spawned them.\n */\nfunction nonOwningMcpConnector(connection: Pick<McpConnection, 'tools' | 'instructions'>): McpConnector {\n return async () => nonOwningMcpView(connection)\n}\n\n/** MCP state a child agent inherits — `mcpServers` rides the preset, `mcpConnector` goes straight to `createAgent` (Preset omits it by design). */\ninterface InheritedMcp {\n mcpServers?: McpServerConfig[]\n mcpConnector?: McpConnector\n}\n\n/**\n * Decide what MCP state a child inherits, honoring the subagent tool\n * policy. `filterToolsForSubagent` only sees the parent's native registry\n * — MCP tools arrive later via the child's connector — so without this\n * gate a `readonly` or allowlisted subagent would inherit the parent's\n * FULL MCP toolset on top of its filtered natives.\n *\n * Policy mirror of {@link filterToolsForSubagent}:\n * - no def / unrestricted def → full inheritance (config + shared live\n * connection when present).\n * - `readonly: true` → nothing. MCP tools carry unknown mutation risk;\n * the read-only whitelist is intentionally native-only. Subagents that\n * need specific MCP reads should use an explicit `tools` list.\n * - explicit `tools` list → only MCP tools named in the list, and only\n * through the shared live connection — forwarding configs without one\n * would re-bootstrap every server and reopen the bypass this exists\n * to close. Instructions are trimmed to servers that still contribute\n * a tool.\n *\n * CONTRACT CAVEAT — configless parents: the child only invokes its\n * connector when its own `mcpServers` list is non-empty (`agent.ts` gates\n * every connect path on config count; pinned by `test/mcp.test.ts`\n * \"connector + zero configs → no connect\"). So a parent with a live\n * `ctx.mcpConnection` but an absent/empty `ctx.mcpServers` hands down a\n * connector that never fires — the child gets NO MCP tools, allowlisted\n * or not. Hosts sharing a connection must pass the matching transformed\n * config list alongside it (`agent.mcpServers` — see the command-spawn\n * seam in the TUI).\n */\nexport function inheritedMcpForSubagent(ctx: ToolContext, def: SubagentDef | undefined): InheritedMcp {\n const explicit = def?.tools\n const restricted = def !== undefined && (def.readonly === true || (explicit !== undefined && explicit.length > 0))\n if (!restricted) {\n return {\n ...(ctx.mcpServers !== undefined ? { mcpServers: ctx.mcpServers } : {}),\n ...(ctx.mcpConnection !== undefined ? { mcpConnector: nonOwningMcpConnector(ctx.mcpConnection) } : {}),\n }\n }\n if (!explicit || explicit.length === 0)\n return {}\n if (ctx.mcpConnection === undefined)\n return {}\n const wanted = new Set(explicit)\n const tools: Record<string, ToolDef> = {}\n for (const [key, t] of Object.entries(ctx.mcpConnection.tools)) {\n if (wanted.has(t.spec.name))\n tools[key] = t\n }\n const surviving = Object.keys(tools)\n if (surviving.length === 0)\n return {}\n const servers = ctx.mcpServers ?? []\n const keepServers = new Set(surviving.map(name => deriveMcpToolMeta(name, servers).server))\n const instructions = ctx.mcpConnection.instructions\n const filteredInstructions = instructions\n ? new Map([...instructions].filter(([name]) => keepServers.has(name)))\n : undefined\n return {\n ...(ctx.mcpServers !== undefined ? { mcpServers: ctx.mcpServers } : {}),\n mcpConnector: nonOwningMcpConnector({\n tools,\n ...(filteredInstructions !== undefined && filteredInstructions.size > 0 ? { instructions: filteredInstructions } : {}),\n }),\n }\n}\n\n/**\n * Render the per-type descriptions into a single schema-field\n * description string the model reads when choosing a `subagent_type`.\n * Falls back to a generic line when the host didn't supply any per-type\n * descriptions.\n */\nexport function buildSubagentTypeDescription(registry: SubagentRegistry): string {\n const lines: string[] = []\n let hasAny = false\n for (const [key, def] of Object.entries(registry)) {\n if (def.description) {\n lines.push(`- \"${key}\": ${def.description}`)\n hasAny = true\n }\n else {\n lines.push(`- \"${key}\"`)\n }\n }\n lines.push('- \"general-purpose\": no specialization; uses the spawn tool\\'s default config.')\n if (!hasAny) {\n return `Optional subagent preset. One of: ${lines.map(l => l.replace(/^- /, '').replace(/:.*$/, '')).join(', ')}.`\n }\n return `Optional subagent preset that overlays the spawn tool's defaults.\\n${lines.join('\\n')}`\n}\n\n/**\n * Catalogs up to this size embed their full model list in the spawn\n * schema's `model` field description; larger ones defer to the\n * `model_search` tool so the schema stays lean.\n */\nexport const EMBEDDED_CATALOG_MAX = 8\n\n/**\n * Build the `model` field's description: delegation doctrine plus either\n * the embedded catalog (small) or a pointer at `model_search` (large).\n */\nexport function buildModelFieldDescription(catalog: readonly SpawnModelEntry[]): string {\n const doctrine = 'Optional model id to run the child on. Cost-tier your delegation: mechanical, parallelizable, or high-volume search work suits cheaper/faster models; keep synthesis and final judgment on your own model. Omit to inherit your model.'\n if (catalog.length <= EMBEDDED_CATALOG_MAX)\n return `${doctrine}\\nAvailable:\\n${catalog.map(e => `- ${formatModelLine(e)}`).join('\\n')}`\n return `${doctrine}\\n${catalog.length} models are available: call \\`model_search\\` to browse ids, pricing, and context windows.`\n}\n\n/**\n * Resolve a catalog entry's `Provider.name` against the run's provider\n * list: the active provider first, then the fallback-chain providers\n * (`ToolContext.providers`, populated by the loop for multi-provider\n * runs). `undefined` when no instance with that name serves this run.\n */\nexport function resolveProviderByName(name: string, ctx: ToolContext): Provider | undefined {\n if (ctx.provider.name === name)\n return ctx.provider\n return ctx.providers?.find(p => p.name === name)\n}\n\n/**\n * Race `task` (an already-running child `agent.run()` promise) against a\n * timer. Does NOT race against the parent abort signal — the child agent\n * already observes the same signal internally and handles its own aborted\n * bookkeeping, so racing here would detach the spawn from the child's\n * session-persisting finally block.\n *\n * On timeout: rejects with `SpawnTimeoutError`; caller is expected to call\n * `agent.abort()` and subsequently `await` the original `task` so the\n * child's session state (runs, turns, status) gets flushed before the\n * parent moves on.\n */\nexport async function raceWithTimeout<T>(\n task: Promise<T>,\n timeoutMs: number | undefined,\n): Promise<T> {\n if (!timeoutMs || timeoutMs <= 0)\n return task\n\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n return await new Promise<T>((resolve, reject) => {\n timer = setTimeout(() => reject(new SpawnTimeoutError(timeoutMs)), timeoutMs)\n task.then(resolve, reject)\n })\n }\n finally {\n if (timer)\n clearTimeout(timer)\n }\n}\n\nexport class SpawnTimeoutError extends Error {\n readonly timeoutMs: number\n constructor(timeoutMs: number) {\n super(`Child agent timed out after ${timeoutMs}ms`)\n this.name = 'SpawnTimeoutError'\n this.timeoutMs = timeoutMs\n }\n}\n","/**\n * Durable key/value state for tools.\n *\n * A tool reaches this through {@link ToolContext.state} — a plain,\n * SYNCHRONOUS map view. Synchronous is not a convenience choice: under a\n * durable-execution runtime the tool body runs inside a journaled action\n * (Restate's `ctx.run`), and a durable read (`ctx.get`) is illegal in\n * there. So the flow is split into three phases the runtime can journal\n * independently:\n *\n * 1. **hydrate** — load the snapshot ONCE per agent, outside any\n * journaled action, before the first tool dispatch.\n * 2. **stage** — the tool mutates its per-call view. Mutations are\n * recorded as an ordered {@link ToolStateOp} list, NOT written\n * through: they're plain data, so a durable adapter can journal them\n * alongside the tool result.\n * 3. **commit** — the loop applies the staged ops through\n * {@link ToolStateStore.commit} after the call settles, outside the\n * journaled action.\n *\n * The invariant that makes this replay-safe:\n *\n * > A staged mutation is committed only if it came back OUT of a journal\n * > entry.\n *\n * A durable adapter (see `restateTool`) reads {@link StagedToolState.pending}\n * from inside its journaled closure, ships the ops out as part of the\n * journaled value, and calls {@link StagedToolState.restore} outside it. On\n * replay the tool body never runs, so nothing is staged live — `restore`\n * re-seeds the exact same ops from the journal and the commit re-issues an\n * identical write. Paths that produced no journal entry (a fatal error, a\n * control abort, a dead attempt) call {@link StagedToolState.clear} so live\n * mutations from an attempt nobody recorded can never reach the store.\n *\n * The store seam is deliberately runtime-agnostic: `zidane/restate` ships\n * `restateToolStateStore`, but a Redis / Postgres / file-backed\n * implementation is the same two methods. With no store configured the\n * controller is in-memory only — `ctx.state` still works everywhere\n * (local CLI, tests, subagents), it just doesn't outlive the process.\n */\n\nimport { Buffer } from 'node:buffer'\nimport { formatBytes } from './format-bytes'\n\n/**\n * Tool-facing view. Reads hit the hydrated snapshot merged with this\n * call's own staged writes; writes stage an op (see the module docstring\n * for why they aren't written through).\n */\nexport interface ToolStateView {\n /**\n * Value for `key`, or `undefined` when absent (or staged for deletion).\n * Pass the expected type (`get<boolean>('delivered')`) — what crossed the\n * store is JSON, so nothing here can validate it for you.\n */\n get: <T = unknown>(key: string) => T | undefined\n /**\n * Stage a write. Values must be JSON-serializable — they cross a journal\n * and a store. `set(key, undefined)` is normalized to a deletion, since\n * that is what a read would report and an undefined value would not\n * survive serialization intact.\n *\n * Throws if the value is not serializable, or if it exceeds the store's\n * size budget ({@link ToolStateStore.maxValueBytes}, default\n * {@link DEFAULT_MAX_VALUE_BYTES}). Failing HERE is the point: the\n * alternative is a rejection at commit time, long after the tool that\n * caused it returned — and under Restate an oversized state value fails\n * the whole invocation with `RT0003` and retries it, so an unguarded fat\n * write is a poison pill, not a dropped write.\n */\n set: (key: string, value: unknown) => void\n /** Stage a deletion. */\n delete: (key: string) => void\n /** Whether `key` currently resolves to a value. */\n has: (key: string) => boolean\n /** Keys visible to this call, snapshot + staged writes, ascending. */\n keys: () => string[]\n}\n\n/**\n * One staged mutation. JSON-shaped on purpose: durable adapters journal\n * these verbatim, so a tagged union with an explicit delete flag beats\n * `value: undefined` (which JSON drops).\n */\nexport type ToolStateOp\n = | { key: string, value: unknown, delete?: never }\n | { key: string, delete: true, value?: never }\n\n/**\n * Durable backing for tool state. NOT runtime-specific — implement it\n * against whatever the host already trusts with durable writes.\n *\n * `load` runs once per agent instance, before any tool dispatch. `commit`\n * receives the ops staged by one tool call, in the order the tool made them.\n *\n * Two requirements on `commit`:\n *\n * - **Idempotent.** A durable adapter may hand the same ops twice if the\n * invocation is retried before the commit is recorded.\n * - **Non-throwing, ideally.** A rejection does not fail the tool call — the\n * body already ran and its side effects already happened, so the dispatch\n * path logs the failure under `ZIDANE_DEBUG` and drops the write rather\n * than killing the run. Retry inside your implementation if the write\n * matters more than the latency.\n */\nexport interface ToolStateStore {\n load: () => Promise<Record<string, unknown>> | Record<string, unknown>\n commit: (ops: readonly ToolStateOp[]) => Promise<void> | void\n /**\n * Largest single value this store accepts, in bytes of JSON. Declared by\n * the store because the limit belongs to the backend, not the caller:\n * Restate's is a journal-entry cap, Redis's is a value cap, a file store's\n * is whatever you say it is. `ToolStateView.set` enforces it at stage time.\n * Defaults to {@link DEFAULT_MAX_VALUE_BYTES}.\n */\n maxValueBytes?: number\n}\n\n/**\n * Default per-value size budget: 64 KiB of JSON.\n *\n * Deliberately far below any backend's hard ceiling (Restate's journal-entry\n * limit is 32 MiB) because this is a design guard, not a protocol limit.\n * Durable tool state is for flags, ids, counters, and cursors; a value that\n * doesn't fit in 64 KiB is a payload, and payloads belong in the tool result\n * or in storage the tool owns. Raise it on the store if you disagree — but\n * know that everything here is rewritten on every commit.\n */\nexport const DEFAULT_MAX_VALUE_BYTES = 64 * 1024\n\n/**\n * Staging handle behind a {@link ToolStateView} — the seam durable\n * adapters use to move a call's mutations through a journal. Not part of\n * the tool-facing surface; resolve it with {@link stagedToolState}.\n */\nexport interface StagedToolState {\n /** Ops staged so far, in mutation order. Non-destructive. */\n pending: () => ToolStateOp[]\n /** Replace the staged ops (replay path: re-seed from the journaled value). */\n restore: (ops: readonly ToolStateOp[]) => void\n /** Drop staged ops — for paths whose mutations were never journaled. */\n clear: () => void\n}\n\n/**\n * Per-agent owner of the snapshot + per-call staging.\n *\n * One controller per agent instance: the snapshot is shared across runs\n * (so state written in run 1 is visible in run 2 without a reload), while\n * each tool call gets its own view so its mutations can be journaled and\n * committed independently.\n */\nexport interface ToolStateController {\n /**\n * Load the snapshot from the store. Idempotent and memoized — the first\n * caller performs the load, later callers await the same promise. Hosts\n * don't call this directly; the agent invokes it at run start, which is\n * the deterministic outside-the-journal position a durable store needs.\n */\n hydrate: () => Promise<void>\n /** Fresh staging view for `callId`. Repeated calls return the same view. */\n begin: (callId: string) => ToolStateView\n /**\n * Apply `callId`'s staged ops to the store and merge them into the\n * snapshot, then drop the view. No-op when nothing was staged — which\n * is the common case and, crucially, means no durable write happens for\n * calls that never touched state.\n */\n commit: (callId: string) => Promise<void>\n /** Discard `callId`'s view without committing. */\n discard: (callId: string) => void\n /**\n * Ops `callId` has staged but not yet committed, in staging order. For\n * reporting a failed commit (the controller drops the view on failure, so\n * a caller that wants the lost delta must read it beforehand).\n */\n pending: (callId: string) => readonly ToolStateOp[]\n /** Current snapshot, for hosts that want to inspect or persist it. */\n snapshot: () => Readonly<Record<string, unknown>>\n /**\n * Committed-only reads, straight off the backing map — no per-call copy\n * (unlike {@link snapshot}) and no prototype-key ambiguity. Staged writes of\n * in-flight calls are invisible here until their `commit`. This is what\n * `agent.toolState` is built on.\n */\n committed: Pick<ToolStateView, 'get' | 'has' | 'keys'>\n}\n\n/** Brand so {@link stagedToolState} can recognise views this module made. */\nconst STAGED = Symbol('zidane.toolState.staged')\n\ninterface BrandedView extends ToolStateView {\n [STAGED]: StagedToolState\n}\n\n/**\n * Resolve the staging handle behind a view. Returns `undefined` for a\n * missing view or a foreign implementation (a host that supplied its own\n * `ToolStateView`), which lets adapters degrade to \"no durable staging\"\n * instead of throwing.\n */\nexport function stagedToolState(view: ToolStateView | undefined): StagedToolState | undefined {\n if (!view)\n return undefined\n const branded = (view as Partial<BrandedView>)[STAGED]\n return branded ?? undefined\n}\n\nexport function createToolStateController(store?: ToolStateStore): ToolStateController {\n const snapshot = new Map<string, unknown>()\n const views = new Map<string, BrandedView>()\n const maxValueBytes = store?.maxValueBytes ?? DEFAULT_MAX_VALUE_BYTES\n let hydrated: Promise<void> | undefined\n\n const hydrate = (): Promise<void> => {\n if (hydrated)\n return hydrated\n const attempt = (async () => {\n if (!store)\n return\n const loaded = await store.load()\n // Keys already written this process win: a `commit` can only have\n // landed after a load in the same controller, but a host-supplied\n // store may resolve `load` lazily/slowly, and losing a committed\n // write to a stale snapshot would be silent corruption.\n for (const [key, value] of Object.entries(loaded ?? {})) {\n if (!snapshot.has(key))\n snapshot.set(key, value)\n }\n })()\n // Memoize the SUCCESS only. Caching a rejection would brick every later\n // run of a long-lived agent on one transient store blip — the agent\n // hydrates before each run, so a cleared memo simply retries the load.\n hydrated = attempt.catch((err: unknown) => {\n hydrated = undefined\n throw err\n })\n return hydrated\n }\n\n const begin = (callId: string): ToolStateView => {\n const existing = views.get(callId)\n if (existing)\n return existing\n const view = createStagingView(snapshot, maxValueBytes)\n views.set(callId, view)\n return view\n }\n\n const commit = async (callId: string): Promise<void> => {\n const view = views.get(callId)\n if (!view)\n return\n views.delete(callId)\n const ops = view[STAGED].pending()\n if (ops.length === 0)\n return\n // Store first, snapshot second: if the store rejects, memory must not be\n // left claiming a write that never persisted — later calls in the run\n // would read it back as if it had landed.\n await store?.commit(ops)\n applyOps(snapshot, ops)\n }\n\n return {\n hydrate,\n begin,\n commit,\n discard: (callId: string) => {\n views.delete(callId)\n },\n pending: (callId: string) => views.get(callId)?.[STAGED].pending() ?? [],\n snapshot: () => Object.fromEntries(snapshot),\n committed: {\n get: <T = unknown>(key: string): T | undefined => snapshot.get(key) as T | undefined,\n has: (key: string) => snapshot.has(key),\n keys: () => [...snapshot.keys()].sort(),\n },\n }\n}\n\n/**\n * Build a view over `snapshot` that records writes instead of applying\n * them. Reads layer staged ops on top of the snapshot so a tool sees its\n * own writes back — including deletions, which read as absent.\n */\nfunction createStagingView(snapshot: Map<string, unknown>, maxValueBytes: number): BrandedView {\n let ops: ToolStateOp[] = []\n // Last op per key — reads resolve against this, so repeated writes to\n // one key stay O(1) and last-write-wins matches what `commit` applies.\n const latest = new Map<string, ToolStateOp>()\n\n const stage = (op: ToolStateOp): void => {\n ops.push(op)\n latest.set(op.key, op)\n }\n\n const resolve = (key: string): { present: boolean, value?: unknown } => {\n const staged = latest.get(key)\n if (staged)\n return staged.delete === true ? { present: false } : { present: true, value: staged.value }\n return snapshot.has(key) ? { present: true, value: snapshot.get(key) } : { present: false }\n }\n\n const view: BrandedView = {\n get: <T = unknown>(key: string): T | undefined => {\n const resolved = resolve(key)\n return resolved.present ? (resolved.value as T) : undefined\n },\n set: (key: string, value: unknown): void => {\n assertStorable(key, value, maxValueBytes)\n // `set(k, undefined)` stages a DELETE, not a `{ key, value: undefined }`\n // op. Two reasons: `get(k)` reports undefined for both cases anyway, and\n // an op with an undefined value is not JSON-stable — a journal or store\n // round-trip drops the property, so the op would come back as a bare\n // `{ key }` that no longer matches its own schema.\n if (value === undefined)\n stage({ key, delete: true })\n else\n stage({ key, value })\n },\n delete: (key: string): void => {\n stage({ key, delete: true })\n },\n has: (key: string): boolean => resolve(key).present,\n keys: (): string[] => {\n const keys = new Set(snapshot.keys())\n for (const [key, op] of latest) {\n if (op.delete === true)\n keys.delete(key)\n else\n keys.add(key)\n }\n return [...keys].sort()\n },\n [STAGED]: {\n pending: () => [...ops],\n restore: (restored: readonly ToolStateOp[]) => {\n ops = [...restored]\n latest.clear()\n for (const op of ops)\n latest.set(op.key, op)\n },\n clear: () => {\n ops = []\n latest.clear()\n },\n },\n }\n // Keep the brand off `Object.keys` / spreads so a host that clones the\n // view for its own tool context doesn't silently carry the staging\n // handle onto an unrelated object.\n Object.defineProperty(view, STAGED, { value: view[STAGED], enumerable: false })\n return view\n}\n\n/**\n * Reject a value that can't survive the trip to the store, at the moment the\n * tool stages it.\n *\n * Scalars are skipped: they're bounded, and paying a `JSON.stringify` per\n * `set` on a hot tool would be a real cost for no signal. Strings and objects\n * are measured in UTF-8 bytes, which is what both the journal and the store\n * actually spend.\n */\nfunction assertStorable(key: string, value: unknown, maxValueBytes: number): void {\n if (value === undefined || value === null || typeof value === 'boolean' || typeof value === 'number')\n return\n if (typeof value === 'function' || typeof value === 'symbol' || typeof value === 'bigint') {\n throw new TypeError(\n `tool state value for \"${key}\" is a ${typeof value}, which a durable store can't hold. Use a JSON-serializable value.`,\n )\n }\n\n let json: string\n try {\n json = typeof value === 'string' ? value : JSON.stringify(value) ?? ''\n }\n catch (err) {\n throw new TypeError(\n `tool state value for \"${key}\" is not JSON-serializable (${err instanceof Error ? err.message : String(err)}). Durable tool state crosses a journal, so cycles and exotic values can't be stored.`,\n )\n }\n\n const bytes = Buffer.byteLength(json, 'utf8')\n if (bytes > maxValueBytes) {\n throw new RangeError(\n `tool state value for \"${key}\" is ${formatBytes(bytes)}, over the ${formatBytes(maxValueBytes)} limit. `\n + 'Durable tool state is for flags, ids, counters, and cursors. It is rewritten on every commit and crosses a journal entry. '\n + 'Keep the payload in the tool result or in storage the tool owns, and store a reference here.',\n )\n }\n}\n\n/** Apply ops to a snapshot map in order — last write per key wins. */\nfunction applyOps(snapshot: Map<string, unknown>, ops: readonly ToolStateOp[]): void {\n for (const op of ops) {\n if (op.delete === true)\n snapshot.delete(op.key)\n else\n snapshot.set(op.key, op.value)\n }\n}\n\n/** Runtime guard for a journaled op list — adapters validate before restoring. */\nexport function isToolStateOps(value: unknown): value is ToolStateOp[] {\n return Array.isArray(value) && value.every(op =>\n typeof op === 'object' && op !== null\n && typeof (op as ToolStateOp).key === 'string'\n && ((op as ToolStateOp).delete === true || 'value' in (op as object)),\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-url.d.ts","names":[],"sources":["../../src/tools/fetch-url.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"fetch-url.d.ts","names":[],"sources":["../../src/tools/fetch-url.ts"],"mappings":";;iBAiHgB,iBAAiB;;;;;;;;iBA8CjB,cAAc,kBAAkB;;;;;;;;;;;UA6DtC;EACR;EACA;EACA;EACA;EACA;;;;;;EAMA;;iBAUc,cACd,KAAK,KACL,kBACA,eACA,SAAS,wBACT,QAAQ,aACR,yBACC,QAAQ;;;;;;;;;;UA6IM;;EAEf,YAAY,qBAAqB;IAAU;IAAiB;;;EAE5D,qBAAqB;;iBAGD,mBACpB,UAAU,KACV,SAAS,wBACT,QAAQ,aACR,wBACA,OAAM,eACN,iCACC,QAAQ;iBAqDF,SAAS,aAAa,aAAa;iBAQnC,SAAS;iBAeT,SAAS,aAAa,gBAAgB;;iBA0B/B;;;;;;cAUH;;;;;;;cAQA,UAAU"}
|
package/dist/tools/fetch-url.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { __fetchCacheTestApi, clearFetchUrlCache, fetchUrl, fetchWithSsrfGuard, isBlockedAddress, isHostAllowed, pinnedRequest };
|
|
1
|
+
export { t as __fetchCacheTestApi, n as clearFetchUrlCache, r as fetchUrl, i as fetchWithSsrfGuard, a as isBlockedAddress, o as isHostAllowed, s as pinnedRequest } from "../fetch-url-Ct2TjDWt.js";
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as toolPrompts, t as ToolPrompts } from "../prompts-
|
|
1
|
+
import { n as toolPrompts, t as ToolPrompts } from "../prompts-DYcvqShf.js";
|
|
2
2
|
export { ToolPrompts, toolPrompts };
|
package/dist/tools/prompts.js
CHANGED
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
* against them is a precise edit.
|
|
29
29
|
*/
|
|
30
30
|
const toolPrompts = {
|
|
31
|
-
read_file: "Read a file by path. Text files return lines [offset..offset+limit). Default offset=1, limit=2000. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code's `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Images (png/jpg/gif/webp) and PDFs return structured attachments for capable models; other binary files return a short marker rather than mojibake.",
|
|
32
|
-
write_file: "Write `content` to `path`, creating any missing parent directories. Overwrites existing files in full; prefer `edit` / `multi_edit` for surgical changes when you only want to alter part of a file. Returns one of: `Created <path> (N bytes)` (file did not exist), `Updated <path> (N bytes)` (content differed), or `No change needed: <path> already at target state (N bytes)`, so the model can detect no-ops without a separate `read_file`.",
|
|
33
|
-
edit: "Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes: preserves the rest of the file. Tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`; they are stripped before matching/writing, so you can paste a numbered chunk verbatim.",
|
|
34
|
-
multi_edit: "Apply a sequential list of edits to a file. Each edit operates on the result of the previous APPLIED edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Edits run best-effort: a per-step failure (`old_string` not found, ambiguous match without `replace_all`, identical strings) is reported in the result but does NOT block the remaining steps. The file is written iff at least one step applied. The result lists per-hunk outcomes (`applied` / `failed`) so the model can re-issue just the failures without resending the whole batch. Each step tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`.",
|
|
31
|
+
read_file: "Read a file by path. Text files return lines [offset..offset+limit). Default offset=1, limit=2000; a negative offset reads from the end (`-50` = last 50 lines). Pass `paths` to read several files, or several ranges of one file, in one call. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code's `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Images (png/jpg/gif/webp) and PDFs return structured attachments for capable models; other binary files return a short marker rather than mojibake.",
|
|
32
|
+
write_file: "Write `content` to `path`, creating any missing parent directories. Overwrites existing files in full; prefer `edit` / `multi_edit` for surgical changes when you only want to alter part of a file. Returns one of: `Created <path> (N bytes)` (file did not exist), `Updated <path> (N bytes)` (content differed), or `No change needed: <path> already at target state (N bytes)`, so the model can detect no-ops without a separate `read_file`. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.",
|
|
33
|
+
edit: "Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes: preserves the rest of the file. Tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`; they are stripped before matching/writing, so you can paste a numbered chunk verbatim. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.",
|
|
34
|
+
multi_edit: "Apply a sequential list of edits to a file. Each edit operates on the result of the previous APPLIED edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Edits run best-effort: a per-step failure (`old_string` not found, ambiguous match without `replace_all`, identical strings) is reported in the result but does NOT block the remaining steps. The file is written iff at least one step applied. The result lists per-hunk outcomes (`applied` / `failed`) so the model can re-issue just the failures without resending the whole batch. Each step tolerates `read_file` line-number prefixes (`<N>\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.",
|
|
35
35
|
glob: "Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd; patterns may reach outside the workspace (e.g. \"../shared/**/*.ts\"). By default each row is `<path>\\t<size-bytes>\\t<mtime-iso>`; set `metadata: false` for a plain newline-separated list of paths. Always sorted.",
|
|
36
|
-
grep: "Search file contents by regex. Returns matching paths (default), match content, or per-file counts. Backed by ripgrep when available, with a portable shell-listing + regex fallback when `rg` is absent.",
|
|
36
|
+
grep: "Search file contents by regex. Returns matching paths (default), match content, or per-file counts. One call covers several patterns (`patterns`), several roots (`paths`), context lines, literal (`fixed_strings`) or whole-word (`-w`) matching, and dropping noise lines (`exclude`); `head_limit` caps output. Backed by ripgrep when available, with a portable shell-listing + regex fallback when `rg` is absent.",
|
|
37
37
|
list_files: "List the immediate entries (files and subdirectories) at literal directory `path`. Returns a newline-separated list; subdirectories carry a trailing `/`; or `(empty directory)` when the directory exists but is empty. Non-recursive: use `glob` for pattern matching across nested directories.",
|
|
38
38
|
fetch_url: "Fetch a public URL and return its body as plain text. HTML is reduced to text (scripts, styles, tags stripped; entities decoded). If a `web_search` tool is also available, use it first when you do not already know the exact URL. Only http(s) URLs are accepted; loopback, link-local, private, and cloud-metadata addresses are refused. Output is capped (default 200 KiB).",
|
|
39
39
|
shell_kill: [
|
|
@@ -88,7 +88,7 @@ const toolPrompts = {
|
|
|
88
88
|
{
|
|
89
89
|
canonical: "read_file",
|
|
90
90
|
label: "Read files",
|
|
91
|
-
instead: "cat/head/tail"
|
|
91
|
+
instead: "cat/head/tail/sed -n"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
canonical: "glob",
|
|
@@ -108,7 +108,7 @@ const toolPrompts = {
|
|
|
108
108
|
{
|
|
109
109
|
canonical: "edit",
|
|
110
110
|
label: "Edit files",
|
|
111
|
-
instead: "sed/awk"
|
|
111
|
+
instead: "sed/awk/python scripts"
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
canonical: "write_file",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","names":[],"sources":["../../src/tools/prompts.ts"],"sourcesContent":["/**\n * Every native tool's LLM-facing description, in one place.\n *\n * This module is the source of truth: each tool file imports its\n * `spec.description` from here rather than inlining it. It has no runtime\n * dependencies (pure strings and tiny template functions) so hosts can import\n * it from a config file — `zidane/tools/prompts` — read the defaults, and feed\n * adjusted text into `AgentOptions.toolDescriptions`:\n *\n * ```ts\n * import { toolPrompts } from 'zidane/tools/prompts'\n *\n * createAgent({\n * toolDescriptions: {\n * read_file: toolPrompts.read_file.replace('Claude Code', 'the editor'),\n * shell: cur => cur.replace(toolPrompts.shell.core, myShellIntro),\n * },\n * })\n * ```\n *\n * Scope is the top-level `spec.description` — the surface `toolDescriptions`\n * overrides. Parameter descriptions live with their schemas in the tool files.\n *\n * Static tools are plain strings. Tools whose description depends on runtime\n * configuration expose named fragments the factory joins conditionally; the\n * fragments are the exact bytes the model receives, so `String#replace`\n * against them is a precise edit.\n */\n\nexport const toolPrompts = {\n read_file: 'Read a file by path. Text files return lines [offset..offset+limit). Default offset=1, limit=2000. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code\\'s `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Images (png/jpg/gif/webp) and PDFs return structured attachments for capable models; other binary files return a short marker rather than mojibake.',\n\n write_file: 'Write `content` to `path`, creating any missing parent directories. Overwrites existing files in full; prefer `edit` / `multi_edit` for surgical changes when you only want to alter part of a file. Returns one of: `Created <path> (N bytes)` (file did not exist), `Updated <path> (N bytes)` (content differed), or `No change needed: <path> already at target state (N bytes)`, so the model can detect no-ops without a separate `read_file`.',\n\n edit: 'Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes: preserves the rest of the file. Tolerates `read_file` line-number prefixes (`<N>\\\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`; they are stripped before matching/writing, so you can paste a numbered chunk verbatim.',\n\n multi_edit: 'Apply a sequential list of edits to a file. Each edit operates on the result of the previous APPLIED edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Edits run best-effort: a per-step failure (`old_string` not found, ambiguous match without `replace_all`, identical strings) is reported in the result but does NOT block the remaining steps. The file is written iff at least one step applied. The result lists per-hunk outcomes (`applied` / `failed`) so the model can re-issue just the failures without resending the whole batch. Each step tolerates `read_file` line-number prefixes (`<N>\\\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`.',\n\n glob: 'Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd; patterns may reach outside the workspace (e.g. \"../shared/**/*.ts\"). By default each row is `<path>\\\\t<size-bytes>\\\\t<mtime-iso>`; set `metadata: false` for a plain newline-separated list of paths. Always sorted.',\n\n grep: 'Search file contents by regex. Returns matching paths (default), match content, or per-file counts. Backed by ripgrep when available, with a portable shell-listing + regex fallback when `rg` is absent.',\n\n list_files: 'List the immediate entries (files and subdirectories) at literal directory `path`. Returns a newline-separated list; subdirectories carry a trailing `/`; or `(empty directory)` when the directory exists but is empty. Non-recursive: use `glob` for pattern matching across nested directories.',\n\n fetch_url: 'Fetch a public URL and return its body as plain text. HTML is reduced to text (scripts, styles, tags stripped; entities decoded). If a `web_search` tool is also available, use it first when you do not already know the exact URL. Only http(s) URLs are accepted; loopback, link-local, private, and cloud-metadata addresses are refused. Output is capped (default 200 KiB).',\n\n shell_kill: [\n 'Terminate a running background task started by `shell({ run_in_background: true })`.',\n 'Sends SIGTERM to the whole process group so the shell wrapper AND its child commands die together.',\n 'Returns the final exit info (status, exit code, output path) or a \"no such task\" message when the id is unknown / already cleaned up.',\n 'Idempotent: calling on a task that has already terminated returns the cached exit info without re-killing.',\n ].join('\\n'),\n\n wait_task: [\n 'Wait for a background task started by `shell({ run_in_background: true })` to exit, then return its final status.',\n 'Returns immediately when the task has already terminated. On timeout the task keeps running and you can wait again, keep working, or `shell_kill` it.',\n 'Prefer ending your turn and receiving the `<task-notification>` when you have other useful work or nothing depends on the result; use wait_task only when the next step genuinely needs the task\\'s outcome.',\n ].join('\\n'),\n\n record_artifact: [\n 'Record a deliverable this session produced so the user can find it later in the artifacts panel. An artifact is an OUTPUT the user would want a link to — not a step of the work.',\n 'Worth recording: a pull request or merge request URL, a deployment / preview URL, an issue or ticket you filed, a release or tag, a generated report / export / dataset the user asked for, a build output they will pick up.',\n 'Not worth recording: source files you edited or created as part of implementing a change, scratch files, logs, intermediate results, anything already implied by the diff.',\n 'Pull requests from `gh pr create`, `git commit` commits and newly created branches are detected automatically — record those only when the automatic path could not see them (e.g. a PR opened from a background shell task).',\n 'Idempotent per (kind, uri): recording the same artifact again refreshes its label/meta.',\n ].join('\\n'),\n\n read_chat_turns: [\n 'Read selected turns from a previous chat explicitly referenced by the user with `#<chat>` in the current prompt.',\n 'Use this when the user asks to continue from, compare with, or inspect a referenced chat.',\n 'The tool refuses chats that were not referenced in the current user prompt.',\n ].join(' '),\n\n model_search: [\n 'List the models available for spawned subagents, with per-Mtok pricing and context windows.',\n 'Use it to cost-tier delegation: find a cheap, fast model for mechanical or high-volume work before calling `spawn` with its id in the `model` field.',\n 'Pass `query` to filter by substring across model id, name, and provider; omit it to list everything.',\n ].join('\\n'),\n\n skills_use: 'Activate or deactivate a specialized skill. '\n + 'Call with `mode: \"activate\"` (default) to load a skill\\'s full instructions when a task matches its catalog description. '\n + 'Call with `mode: \"deactivate\"` to release a skill whose allowed-tools restrictions are now in the way, e.g. when the skill\\'s work is done or the active skill is blocking unrelated tool calls. '\n + 'After activating, follow the returned instructions; use skills_read to load referenced files and skills_run_script to execute bundled scripts.',\n\n skills_read: 'Read a bundled resource file from an active skill. '\n + 'The skill must have been activated via skills_use first. '\n + 'Path is relative to the skill\\'s directory (e.g. \"references/REFERENCE.md\", \"assets/template.txt\").',\n\n skills_run_script: 'Execute a script bundled with an active skill (from its scripts/ directory). '\n + 'The skill must have been activated via skills_use first. '\n + 'Returns stdout, stderr, and the exit code. Honors the script\\'s shebang.',\n\n /** Default for `createInteractionTool` when the host passes no `description`. */\n interaction: 'Request structured input from the user or external system.',\n\n /**\n * `shell` is assembled per run by `createShellTool`: `core` always; the\n * `preferDedicated` block only for sibling tools actually registered\n * (`swapHints` filtered by canonical name, tool names alias-resolved);\n * `background` only when `run_in_background` is available.\n */\n shell: {\n core: [\n 'Execute a shell command from the workspace root (the `Cwd` in your environment) and return its combined stdout/stderr. Every call starts there; `cd` never persists between calls. Use relative paths (`cd src/app && bun test`, `ls docs`), not absolute ones.',\n 'Output is tail-priority truncated at 32 KiB by default; errors and exit-code summaries live in the tail.',\n 'By default each call appends a `(exit N, Nms)` footer and surfaces non-empty stderr in a separate section even on success; set `metadata: false` to return only stdout. Set maxOutputBytes=0 to disable truncation.',\n 'Pass a short `description` (5-10 words) explaining the intent of the command; it surfaces in the user-facing transcript so the human can follow your reasoning at a glance. Skip it only for trivial reads where the command speaks for itself.',\n ].join('\\n'),\n preferDedicated: 'When a dedicated tool fits, prefer it over shell:',\n /** One `- <label>: use \\`<tool>\\` (not <instead>)` line per registered sibling. */\n swapHints: [\n { canonical: 'read_file', label: 'Read files', instead: 'cat/head/tail' },\n { canonical: 'glob', label: 'File search', instead: 'find/ls' },\n { canonical: 'grep', label: 'Content search', instead: 'grep/rg' },\n { canonical: 'list_files', label: 'Directory listings', instead: 'ls' },\n { canonical: 'edit', label: 'Edit files', instead: 'sed/awk' },\n { canonical: 'write_file', label: 'Write files', instead: 'echo>/heredoc' },\n ],\n reminder: 'Re-running `ls`/`cat` on the same path is not useful: the prior result is still current unless you wrote to that path since.',\n background: [\n 'Long-running commands (`npm run dev`, `python train.py`, anything that would otherwise block your turn for minutes) → `run_in_background: true`. The call returns immediately with `{ task_id, output_path, pid }`; stdout + stderr stream to the log file at `output_path`.',\n 'After spawning a background task: end your current turn (do NOT keep iterating). A `<task-notification>` arrives on the agent\\'s NEXT user-turn with the final status. Polling the log file in a loop wastes tokens and blocks your turn: the notification IS the wake-up. If you NEED to check progress immediately (rare), call `read_file({ path: output_path, ... })` exactly once and decide. To terminate, use `shell_kill({ task_id })`. If, and only if, your very next step depends on the task\\'s outcome, block on it with `wait_task({ task_id })` instead of polling.',\n 'When called from inside a `spawn`\\'d subagent: you have NO next user-turn; your `agent.run` ends as soon as you finish responding. Start the background task, return a brief summary including the `task_id`, and end your turn. Ownership of the task is transferred to the parent agent when your run finishes; the parent will see the notification on ITS next user-turn.',\n ].join('\\n\\n'),\n },\n\n /**\n * `spawn`: `core` always; `modelTiering` when a model catalog is wired;\n * `resume` when the tool persists child turns (`persist: true`).\n */\n spawn: {\n core: 'Spawn a sub-agent for a self-contained task that benefits from isolation (separate context window, separate retries): for example, a deep research dive or a long codegen pass on a specific file. The sub-agent inherits your tool set (including `spawn` itself, depth-capped) and your system prompt unless overridden, runs independently, and returns its final response. Do NOT spawn for sequential steps you could do yourself.',\n modelTiering: ' Children can run on a DIFFERENT model via the `model` field: cost-tier your delegation: mechanical, parallelizable, or high-volume search work belongs on cheaper/faster models, while synthesis and judgment stay on your own model.',\n resume: ' A stopped or failed sub-agent can be CONTINUED instead of restarted: pass `resume` with its child label (e.g. \"child-2\") and put the continuation instruction in `task`; the child picks up with its prior conversation intact.',\n },\n\n /**\n * `tool_search`: `core` + one closing sentence — `callableVia(dispatcher)`\n * in dispatch mode (`call_tool`), `callable` otherwise.\n */\n tool_search: {\n core: [\n 'Discover and load schemas for additional tools listed in <searchable_tools>.',\n 'Tools listed there are advertised by name + description only; their input',\n 'schemas are not loaded into context until you surface them through this tool.',\n 'Pass `query` for a substring search, `names` to load specific tools, or `server`',\n 'to load every tool from one MCP server.',\n ].join(' '),\n callable: 'Returned tools become callable for the rest of this run.',\n callableVia: (dispatcher: string): string =>\n `Returned tools become callable through \\`${dispatcher}\\` (name + arguments) for the rest of this run.`,\n },\n\n /** `call_tool` names the discovery tool (`tool_search` or the host's wire name). */\n call_tool: (discovery: string): string =>\n `Invoke a tool listed in <searchable_tools>. Load its schema with \\`${discovery}\\` first `\n + '(the result shows the exact `name` and input schema), then call it here with `name` '\n + 'set to that tool name and `arguments` set to an object matching its schema. '\n + 'Arguments are validated against the tool\\'s own schema; a validation error names '\n + 'the offending field.',\n\n /**\n * `web_search`: `core` + `recency(monthYear)`, resolved on each spec read so\n * the hint tracks the current month.\n */\n web_search: {\n core: 'Search the public web and return ranked title / URL / snippet results. '\n + 'Use this to look up documentation, error messages, library versions, or recent events. '\n + 'If a `fetch_url` tool is also available, follow up with it to read a specific page in full.',\n recency: (monthYear: string): string =>\n `IMPORTANT: the current month is ${monthYear}. When searching for recent `\n + 'information, docs, or current events, use the current year in the query, not a year '\n + 'from your training cutoff (e.g. search \"React docs\" with this year, not an older one).',\n },\n} as const\n\nexport type ToolPrompts = typeof toolPrompts\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,cAAc;CACzB,WAAW;CAEX,YAAY;CAEZ,MAAM;CAEN,YAAY;CAEZ,MAAM;CAEN,MAAM;CAEN,YAAY;CAEZ,WAAW;CAEX,YAAY;EACV;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,WAAW;EACT;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,iBAAiB;EACf;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,iBAAiB;EACf;EACA;EACA;CACF,CAAC,CAAC,KAAK,GAAG;CAEV,cAAc;EACZ;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,YAAY;CAKZ,aAAa;CAIb,mBAAmB;;CAKnB,aAAa;;;;;;;CAQb,OAAO;EACL,MAAM;GACJ;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI;EACX,iBAAiB;;EAEjB,WAAW;GACT;IAAE,WAAW;IAAa,OAAO;IAAc,SAAS;GAAgB;GACxE;IAAE,WAAW;IAAQ,OAAO;IAAe,SAAS;GAAU;GAC9D;IAAE,WAAW;IAAQ,OAAO;IAAkB,SAAS;GAAU;GACjE;IAAE,WAAW;IAAc,OAAO;IAAsB,SAAS;GAAK;GACtE;IAAE,WAAW;IAAQ,OAAO;IAAc,SAAS;GAAU;GAC7D;IAAE,WAAW;IAAc,OAAO;IAAe,SAAS;GAAgB;EAC5E;EACA,UAAU;EACV,YAAY;GACV;GACA;GACA;EACF,CAAC,CAAC,KAAK,MAAM;CACf;;;;;CAMA,OAAO;EACL,MAAM;EACN,cAAc;EACd,QAAQ;CACV;;;;;CAMA,aAAa;EACX,MAAM;GACJ;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,GAAG;EACV,UAAU;EACV,cAAc,eACZ,4CAA4C,WAAW;CAC3D;;CAGA,YAAY,cACV,sEAAsE,UAAU;;;;;CAUlF,YAAY;EACV,MAAM;EAGN,UAAU,cACR,mCAAmC,UAAU;CAGjD;AACF"}
|
|
1
|
+
{"version":3,"file":"prompts.js","names":[],"sources":["../../src/tools/prompts.ts"],"sourcesContent":["/**\n * Every native tool's LLM-facing description, in one place.\n *\n * This module is the source of truth: each tool file imports its\n * `spec.description` from here rather than inlining it. It has no runtime\n * dependencies (pure strings and tiny template functions) so hosts can import\n * it from a config file — `zidane/tools/prompts` — read the defaults, and feed\n * adjusted text into `AgentOptions.toolDescriptions`:\n *\n * ```ts\n * import { toolPrompts } from 'zidane/tools/prompts'\n *\n * createAgent({\n * toolDescriptions: {\n * read_file: toolPrompts.read_file.replace('Claude Code', 'the editor'),\n * shell: cur => cur.replace(toolPrompts.shell.core, myShellIntro),\n * },\n * })\n * ```\n *\n * Scope is the top-level `spec.description` — the surface `toolDescriptions`\n * overrides. Parameter descriptions live with their schemas in the tool files.\n *\n * Static tools are plain strings. Tools whose description depends on runtime\n * configuration expose named fragments the factory joins conditionally; the\n * fragments are the exact bytes the model receives, so `String#replace`\n * against them is a precise edit.\n */\n\nexport const toolPrompts = {\n read_file: 'Read a file by path. Text files return lines [offset..offset+limit). Default offset=1, limit=2000; a negative offset reads from the end (`-50` = last 50 lines). Pass `paths` to read several files, or several ranges of one file, in one call. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code\\'s `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Images (png/jpg/gif/webp) and PDFs return structured attachments for capable models; other binary files return a short marker rather than mojibake.',\n\n write_file: 'Write `content` to `path`, creating any missing parent directories. Overwrites existing files in full; prefer `edit` / `multi_edit` for surgical changes when you only want to alter part of a file. Returns one of: `Created <path> (N bytes)` (file did not exist), `Updated <path> (N bytes)` (content differed), or `No change needed: <path> already at target state (N bytes)`, so the model can detect no-ops without a separate `read_file`. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.',\n\n edit: 'Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes: preserves the rest of the file. Tolerates `read_file` line-number prefixes (`<N>\\\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`; they are stripped before matching/writing, so you can paste a numbered chunk verbatim. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.',\n\n multi_edit: 'Apply a sequential list of edits to a file. Each edit operates on the result of the previous APPLIED edit. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Edits run best-effort: a per-step failure (`old_string` not found, ambiguous match without `replace_all`, identical strings) is reported in the result but does NOT block the remaining steps. The file is written iff at least one step applied. The result lists per-hunk outcomes (`applied` / `failed`) so the model can re-issue just the failures without resending the whole batch. Each step tolerates `read_file` line-number prefixes (`<N>\\\\t…`, `<N>|…`, or `<N>→…`) in `old_string` / `new_string`. Several `edit` / `multi_edit` / `write_file` calls and a verifying `shell` call (tests, typecheck) in one message run in order, in a single round-trip.',\n\n glob: 'Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd; patterns may reach outside the workspace (e.g. \"../shared/**/*.ts\"). By default each row is `<path>\\\\t<size-bytes>\\\\t<mtime-iso>`; set `metadata: false` for a plain newline-separated list of paths. Always sorted.',\n\n grep: 'Search file contents by regex. Returns matching paths (default), match content, or per-file counts. One call covers several patterns (`patterns`), several roots (`paths`), context lines, literal (`fixed_strings`) or whole-word (`-w`) matching, and dropping noise lines (`exclude`); `head_limit` caps output. Backed by ripgrep when available, with a portable shell-listing + regex fallback when `rg` is absent.',\n\n list_files: 'List the immediate entries (files and subdirectories) at literal directory `path`. Returns a newline-separated list; subdirectories carry a trailing `/`; or `(empty directory)` when the directory exists but is empty. Non-recursive: use `glob` for pattern matching across nested directories.',\n\n fetch_url: 'Fetch a public URL and return its body as plain text. HTML is reduced to text (scripts, styles, tags stripped; entities decoded). If a `web_search` tool is also available, use it first when you do not already know the exact URL. Only http(s) URLs are accepted; loopback, link-local, private, and cloud-metadata addresses are refused. Output is capped (default 200 KiB).',\n\n shell_kill: [\n 'Terminate a running background task started by `shell({ run_in_background: true })`.',\n 'Sends SIGTERM to the whole process group so the shell wrapper AND its child commands die together.',\n 'Returns the final exit info (status, exit code, output path) or a \"no such task\" message when the id is unknown / already cleaned up.',\n 'Idempotent: calling on a task that has already terminated returns the cached exit info without re-killing.',\n ].join('\\n'),\n\n wait_task: [\n 'Wait for a background task started by `shell({ run_in_background: true })` to exit, then return its final status.',\n 'Returns immediately when the task has already terminated. On timeout the task keeps running and you can wait again, keep working, or `shell_kill` it.',\n 'Prefer ending your turn and receiving the `<task-notification>` when you have other useful work or nothing depends on the result; use wait_task only when the next step genuinely needs the task\\'s outcome.',\n ].join('\\n'),\n\n record_artifact: [\n 'Record a deliverable this session produced so the user can find it later in the artifacts panel. An artifact is an OUTPUT the user would want a link to — not a step of the work.',\n 'Worth recording: a pull request or merge request URL, a deployment / preview URL, an issue or ticket you filed, a release or tag, a generated report / export / dataset the user asked for, a build output they will pick up.',\n 'Not worth recording: source files you edited or created as part of implementing a change, scratch files, logs, intermediate results, anything already implied by the diff.',\n 'Pull requests from `gh pr create`, `git commit` commits and newly created branches are detected automatically — record those only when the automatic path could not see them (e.g. a PR opened from a background shell task).',\n 'Idempotent per (kind, uri): recording the same artifact again refreshes its label/meta.',\n ].join('\\n'),\n\n read_chat_turns: [\n 'Read selected turns from a previous chat explicitly referenced by the user with `#<chat>` in the current prompt.',\n 'Use this when the user asks to continue from, compare with, or inspect a referenced chat.',\n 'The tool refuses chats that were not referenced in the current user prompt.',\n ].join(' '),\n\n model_search: [\n 'List the models available for spawned subagents, with per-Mtok pricing and context windows.',\n 'Use it to cost-tier delegation: find a cheap, fast model for mechanical or high-volume work before calling `spawn` with its id in the `model` field.',\n 'Pass `query` to filter by substring across model id, name, and provider; omit it to list everything.',\n ].join('\\n'),\n\n skills_use: 'Activate or deactivate a specialized skill. '\n + 'Call with `mode: \"activate\"` (default) to load a skill\\'s full instructions when a task matches its catalog description. '\n + 'Call with `mode: \"deactivate\"` to release a skill whose allowed-tools restrictions are now in the way, e.g. when the skill\\'s work is done or the active skill is blocking unrelated tool calls. '\n + 'After activating, follow the returned instructions; use skills_read to load referenced files and skills_run_script to execute bundled scripts.',\n\n skills_read: 'Read a bundled resource file from an active skill. '\n + 'The skill must have been activated via skills_use first. '\n + 'Path is relative to the skill\\'s directory (e.g. \"references/REFERENCE.md\", \"assets/template.txt\").',\n\n skills_run_script: 'Execute a script bundled with an active skill (from its scripts/ directory). '\n + 'The skill must have been activated via skills_use first. '\n + 'Returns stdout, stderr, and the exit code. Honors the script\\'s shebang.',\n\n /** Default for `createInteractionTool` when the host passes no `description`. */\n interaction: 'Request structured input from the user or external system.',\n\n /**\n * `shell` is assembled per run by `createShellTool`: `core` always; the\n * `preferDedicated` block only for sibling tools actually registered\n * (`swapHints` filtered by canonical name, tool names alias-resolved);\n * `background` only when `run_in_background` is available.\n */\n shell: {\n core: [\n 'Execute a shell command from the workspace root (the `Cwd` in your environment) and return its combined stdout/stderr. Every call starts there; `cd` never persists between calls. Use relative paths (`cd src/app && bun test`, `ls docs`), not absolute ones.',\n 'Output is tail-priority truncated at 32 KiB by default; errors and exit-code summaries live in the tail.',\n 'By default each call appends a `(exit N, Nms)` footer and surfaces non-empty stderr in a separate section even on success; set `metadata: false` to return only stdout. Set maxOutputBytes=0 to disable truncation.',\n 'Pass a short `description` (5-10 words) explaining the intent of the command; it surfaces in the user-facing transcript so the human can follow your reasoning at a glance. Skip it only for trivial reads where the command speaks for itself.',\n ].join('\\n'),\n preferDedicated: 'When a dedicated tool fits, prefer it over shell:',\n /** One `- <label>: use \\`<tool>\\` (not <instead>)` line per registered sibling. */\n swapHints: [\n { canonical: 'read_file', label: 'Read files', instead: 'cat/head/tail/sed -n' },\n { canonical: 'glob', label: 'File search', instead: 'find/ls' },\n { canonical: 'grep', label: 'Content search', instead: 'grep/rg' },\n { canonical: 'list_files', label: 'Directory listings', instead: 'ls' },\n { canonical: 'edit', label: 'Edit files', instead: 'sed/awk/python scripts' },\n { canonical: 'write_file', label: 'Write files', instead: 'echo>/heredoc' },\n ],\n reminder: 'Re-running `ls`/`cat` on the same path is not useful: the prior result is still current unless you wrote to that path since.',\n background: [\n 'Long-running commands (`npm run dev`, `python train.py`, anything that would otherwise block your turn for minutes) → `run_in_background: true`. The call returns immediately with `{ task_id, output_path, pid }`; stdout + stderr stream to the log file at `output_path`.',\n 'After spawning a background task: end your current turn (do NOT keep iterating). A `<task-notification>` arrives on the agent\\'s NEXT user-turn with the final status. Polling the log file in a loop wastes tokens and blocks your turn: the notification IS the wake-up. If you NEED to check progress immediately (rare), call `read_file({ path: output_path, ... })` exactly once and decide. To terminate, use `shell_kill({ task_id })`. If, and only if, your very next step depends on the task\\'s outcome, block on it with `wait_task({ task_id })` instead of polling.',\n 'When called from inside a `spawn`\\'d subagent: you have NO next user-turn; your `agent.run` ends as soon as you finish responding. Start the background task, return a brief summary including the `task_id`, and end your turn. Ownership of the task is transferred to the parent agent when your run finishes; the parent will see the notification on ITS next user-turn.',\n ].join('\\n\\n'),\n },\n\n /**\n * `spawn`: `core` always; `modelTiering` when a model catalog is wired;\n * `resume` when the tool persists child turns (`persist: true`).\n */\n spawn: {\n core: 'Spawn a sub-agent for a self-contained task that benefits from isolation (separate context window, separate retries): for example, a deep research dive or a long codegen pass on a specific file. The sub-agent inherits your tool set (including `spawn` itself, depth-capped) and your system prompt unless overridden, runs independently, and returns its final response. Do NOT spawn for sequential steps you could do yourself.',\n modelTiering: ' Children can run on a DIFFERENT model via the `model` field: cost-tier your delegation: mechanical, parallelizable, or high-volume search work belongs on cheaper/faster models, while synthesis and judgment stay on your own model.',\n resume: ' A stopped or failed sub-agent can be CONTINUED instead of restarted: pass `resume` with its child label (e.g. \"child-2\") and put the continuation instruction in `task`; the child picks up with its prior conversation intact.',\n },\n\n /**\n * `tool_search`: `core` + one closing sentence — `callableVia(dispatcher)`\n * in dispatch mode (`call_tool`), `callable` otherwise.\n */\n tool_search: {\n core: [\n 'Discover and load schemas for additional tools listed in <searchable_tools>.',\n 'Tools listed there are advertised by name + description only; their input',\n 'schemas are not loaded into context until you surface them through this tool.',\n 'Pass `query` for a substring search, `names` to load specific tools, or `server`',\n 'to load every tool from one MCP server.',\n ].join(' '),\n callable: 'Returned tools become callable for the rest of this run.',\n callableVia: (dispatcher: string): string =>\n `Returned tools become callable through \\`${dispatcher}\\` (name + arguments) for the rest of this run.`,\n },\n\n /** `call_tool` names the discovery tool (`tool_search` or the host's wire name). */\n call_tool: (discovery: string): string =>\n `Invoke a tool listed in <searchable_tools>. Load its schema with \\`${discovery}\\` first `\n + '(the result shows the exact `name` and input schema), then call it here with `name` '\n + 'set to that tool name and `arguments` set to an object matching its schema. '\n + 'Arguments are validated against the tool\\'s own schema; a validation error names '\n + 'the offending field.',\n\n /**\n * `web_search`: `core` + `recency(monthYear)`, resolved on each spec read so\n * the hint tracks the current month.\n */\n web_search: {\n core: 'Search the public web and return ranked title / URL / snippet results. '\n + 'Use this to look up documentation, error messages, library versions, or recent events. '\n + 'If a `fetch_url` tool is also available, follow up with it to read a specific page in full.',\n recency: (monthYear: string): string =>\n `IMPORTANT: the current month is ${monthYear}. When searching for recent `\n + 'information, docs, or current events, use the current year in the query, not a year '\n + 'from your training cutoff (e.g. search \"React docs\" with this year, not an older one).',\n },\n} as const\n\nexport type ToolPrompts = typeof toolPrompts\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,cAAc;CACzB,WAAW;CAEX,YAAY;CAEZ,MAAM;CAEN,YAAY;CAEZ,MAAM;CAEN,MAAM;CAEN,YAAY;CAEZ,WAAW;CAEX,YAAY;EACV;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,WAAW;EACT;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,iBAAiB;EACf;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,iBAAiB;EACf;EACA;EACA;CACF,CAAC,CAAC,KAAK,GAAG;CAEV,cAAc;EACZ;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,YAAY;CAKZ,aAAa;CAIb,mBAAmB;;CAKnB,aAAa;;;;;;;CAQb,OAAO;EACL,MAAM;GACJ;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI;EACX,iBAAiB;;EAEjB,WAAW;GACT;IAAE,WAAW;IAAa,OAAO;IAAc,SAAS;GAAuB;GAC/E;IAAE,WAAW;IAAQ,OAAO;IAAe,SAAS;GAAU;GAC9D;IAAE,WAAW;IAAQ,OAAO;IAAkB,SAAS;GAAU;GACjE;IAAE,WAAW;IAAc,OAAO;IAAsB,SAAS;GAAK;GACtE;IAAE,WAAW;IAAQ,OAAO;IAAc,SAAS;GAAyB;GAC5E;IAAE,WAAW;IAAc,OAAO;IAAe,SAAS;GAAgB;EAC5E;EACA,UAAU;EACV,YAAY;GACV;GACA;GACA;EACF,CAAC,CAAC,KAAK,MAAM;CACf;;;;;CAMA,OAAO;EACL,MAAM;EACN,cAAc;EACd,QAAQ;CACV;;;;;CAMA,aAAa;EACX,MAAM;GACJ;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,GAAG;EACV,UAAU;EACV,cAAc,eACZ,4CAA4C,WAAW;CAC3D;;CAGA,YAAY,cACV,sEAAsE,UAAU;;;;;CAUlF,YAAY;EACV,MAAM;EAGN,UAAU,cACR,mCAAmC,UAAU;CAGjD;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-search.d.ts","names":[],"sources":["../../src/tools/web-search.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-search.d.ts","names":[],"sources":["../../src/tools/web-search.ts"],"mappings":";;cAqNa,WAAW"}
|
package/dist/tools/web-search.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { u as errorMessage } from "../errors-DKiMrOKq.js";
|
|
2
2
|
import { toolPrompts } from "./prompts.js";
|
|
3
|
-
import { c as stripHtmlInline, o as isHostAllowed } from "../fetch-url-
|
|
3
|
+
import { c as stripHtmlInline, o as isHostAllowed } from "../fetch-url-Ct2TjDWt.js";
|
|
4
4
|
//#region src/tools/web-search.ts
|
|
5
5
|
/**
|
|
6
6
|
* Web search tool with a runtime-resolved backend.
|
|
@@ -33,7 +33,7 @@ const ERROR_BODY_MAX = 256;
|
|
|
33
33
|
* multi-MB documents; a real DDG results page is well under 512K, so
|
|
34
34
|
* anything past the cap is not results markup we'd want anyway.
|
|
35
35
|
*/
|
|
36
|
-
const DDG_HTML_MAX_CHARS =
|
|
36
|
+
const DDG_HTML_MAX_CHARS = 524288;
|
|
37
37
|
/**
|
|
38
38
|
* Current "Month YYYY" for the search description's recency hint. A model with
|
|
39
39
|
* an older training cutoff otherwise queries with a stale year (`React docs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-search.js","names":[],"sources":["../../src/tools/web-search.ts"],"sourcesContent":["import type { ToolContext, ToolDef } from './types'\nimport { errorMessage } from '../errors'\nimport { stripHtmlInline } from './_html'\nimport { isHostAllowed } from './fetch-url'\nimport { toolPrompts } from './prompts'\n\n/**\n * Web search tool with a runtime-resolved backend.\n *\n * Backend selection is keyless-fallback: the first backend whose env var\n * is set wins, otherwise we fall through to DuckDuckGo's HTML endpoint\n * (no key required, no auth, rate-limited and brittle to layout changes).\n *\n * 1. TAVILY_API_KEY → https://api.tavily.com/search\n * 2. BRAVE_SEARCH_API_KEY → https://api.search.brave.com/res/v1/web/search\n * 3. (fallback) → https://html.duckduckgo.com/html/\n *\n * Selection happens per call (not at module init) so a key added\n * mid-session via the chat layer's auth flow is picked up on the very next\n * search without restarting the agent. The 5 ms env-var read is negligible\n * compared to the network round-trip.\n *\n * Anthropic providers can take over this tool server-side via the\n * `nativeWebSearch` capability — when set, the provider's `formatTools`\n * drops this `ToolDef` from the function-tools list and emits the\n * server-tool block instead, so this `execute` body is never invoked.\n */\n\nconst DEFAULT_MAX_RESULTS = 5\nconst HARD_MAX_RESULTS = 20\nconst HTTP_TIMEOUT_MS = 15_000\nconst ERROR_BODY_MAX = 256\n/**\n * Cap on the DuckDuckGo HTML we run the scrape regex over. `blockRe` chains\n * lazy `[\\s\\S]*?` spans, which degrade badly (ReDoS-ish slow path) on\n * multi-MB documents; a real DDG results page is well under 512K, so\n * anything past the cap is not results markup we'd want anyway.\n */\nconst DDG_HTML_MAX_CHARS = 512 * 1024\n\n/**\n * Current \"Month YYYY\" for the search description's recency hint. A model with\n * an older training cutoff otherwise queries with a stale year (`React docs\n * 2023`) and misses everything newer. Resolved per read (the `description`\n * getter below) so a long-lived process picks up a month rollover; the value\n * is stable within a session, so it doesn't thrash the provider prompt cache.\n */\nfunction currentMonthYear(): string {\n return new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })\n}\n\ninterface SearchResult {\n title: string\n url: string\n snippet: string\n}\n\ntype Backend = 'tavily' | 'brave' | 'duckduckgo'\n\n/** Egress host each backend talks to — gated against `fetchUrlAllowHosts`. */\nconst BACKEND_HOST: Record<Backend, string> = {\n tavily: 'api.tavily.com',\n brave: 'api.search.brave.com',\n duckduckgo: 'html.duckduckgo.com',\n}\n\nfunction pickBackend(): Backend {\n if (process.env.TAVILY_API_KEY)\n return 'tavily'\n if (process.env.BRAVE_SEARCH_API_KEY)\n return 'brave'\n return 'duckduckgo'\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit, signal: AbortSignal): Promise<Response> {\n const ctrl = new AbortController()\n const onAbort = (): void => ctrl.abort()\n signal.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => ctrl.abort(), HTTP_TIMEOUT_MS)\n try {\n return await fetch(url, { ...init, signal: ctrl.signal })\n }\n finally {\n clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n }\n}\n\n/**\n * Truncate untrusted HTTP error bodies before embedding them in our own\n * error string. Without this, a backend that returns an HTML 500 page\n * leaks a kilobyte of unstructured chrome straight into the model's\n * context window — which is both noisy and a small prompt-injection\n * surface.\n */\nasync function readErrorBody(res: Response): Promise<string> {\n const body = await res.text().catch(() => '')\n if (body.length <= ERROR_BODY_MAX)\n return body\n return `${body.slice(0, ERROR_BODY_MAX)}… (truncated, ${body.length} bytes total)`\n}\n\nasync function searchTavily(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const res = await fetchWithTimeout('https://api.tavily.com/search', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n api_key: process.env.TAVILY_API_KEY,\n query,\n max_results: max,\n search_depth: 'basic',\n }),\n }, signal)\n if (!res.ok)\n throw new Error(`Tavily HTTP ${res.status}: ${await readErrorBody(res)}`)\n const json = await res.json() as { results?: Array<{ title?: string, url?: string, content?: string }> }\n return (json.results ?? []).map(r => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.content ?? '',\n }))\n}\n\nasync function searchBrave(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${max}`\n const res = await fetchWithTimeout(url, {\n headers: {\n 'accept': 'application/json',\n 'x-subscription-token': process.env.BRAVE_SEARCH_API_KEY!,\n },\n }, signal)\n if (!res.ok)\n throw new Error(`Brave HTTP ${res.status}: ${await readErrorBody(res)}`)\n const json = await res.json() as { web?: { results?: Array<{ title?: string, url?: string, description?: string }> } }\n return (json.web?.results ?? []).map(r => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.description ?? '',\n }))\n}\n\n// DDG's HTML endpoint redirects through /l/?uddg=<encoded> — unwrap it so the\n// model sees the destination URL directly.\nfunction unwrapDdgRedirect(href: string): string {\n try {\n const u = new URL(href, 'https://duckduckgo.com')\n if (u.pathname === '/l/' || u.pathname === '/l') {\n const target = u.searchParams.get('uddg')\n if (target)\n return decodeURIComponent(target)\n }\n return u.toString()\n }\n catch {\n return href\n }\n}\n\nasync function searchDuckDuckGo(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`\n const res = await fetchWithTimeout(url, {\n headers: {\n // DDG returns an empty results page to unbranded UAs.\n 'user-agent': 'Mozilla/5.0 (compatible; zidane/1.0)',\n 'accept': 'text/html',\n },\n }, signal)\n if (!res.ok)\n throw new Error(`DuckDuckGo HTTP ${res.status}: ${await readErrorBody(res)}`)\n const html = (await res.text()).slice(0, DDG_HTML_MAX_CHARS)\n // Each result block has anchor `result__a` (title+url) and `result__snippet`.\n // Brittle by design — DDG ships layout changes occasionally and the scrape\n // silently returns zero hits when its anchors move. The keyed-key\n // backends (Tavily / Brave) are the supported happy path; this fallback\n // exists only so the tool is usable with no setup. If `results` is empty,\n // the formatter surfaces a \"(no results from duckduckgo)\" line.\n const results: SearchResult[] = []\n const blockRe = /<a[^>]+class=\"[^\"]*result__a[^\"]*\"[^>]+href=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/a>[\\s\\S]*?<a[^>]+class=\"[^\"]*result__snippet[^\"]*\"[^>]*>([\\s\\S]*?)<\\/a>/g\n\n let m = blockRe.exec(html)\n\n while (m !== null) {\n results.push({\n url: unwrapDdgRedirect(m[1]!),\n title: stripHtmlInline(m[2]!),\n snippet: stripHtmlInline(m[3]!),\n })\n if (results.length >= max)\n break\n\n m = blockRe.exec(html)\n }\n return results\n}\n\nfunction formatResults(results: SearchResult[], backend: Backend): string {\n if (results.length === 0) {\n // The keyless DuckDuckGo scrape returns zero hits both for genuinely\n // empty queries AND when DDG ships a layout change that moves the anchors\n // this regex keys off. Distinguish it from the keyed backends with an\n // actionable hint so a confused model (or user reading the transcript)\n // knows the fix is a real search key, not a better query.\n if (backend === 'duckduckgo') {\n return '(no results from the keyless DuckDuckGo fallback: it is rate-limited and '\n + 'brittle to layout changes. Set TAVILY_API_KEY or BRAVE_SEARCH_API_KEY for reliable search.)'\n }\n return `(no results from ${backend})`\n }\n const header = `# Web search results (${backend}, ${results.length} ${results.length === 1 ? 'hit' : 'hits'})\\n`\n const body = results.map((r, i) => `${i + 1}. ${r.title}\\n ${r.url}\\n ${r.snippet}`).join('\\n\\n')\n return `${header}\\n${body}`\n}\n\nexport const webSearch: ToolDef = {\n isConcurrencySafe: true,\n spec: {\n name: 'web_search',\n // Getter, not a constant, so the recency hint reflects the current month\n // each time the spec is formatted for the provider (see `currentMonthYear`).\n get description(): string {\n return `${toolPrompts.web_search.core}\\n${toolPrompts.web_search.recency(currentMonthYear())}`\n },\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Search query. Plain natural-language or keyword form; quoting and site: operators are passed through verbatim.',\n },\n max_results: {\n type: 'number',\n description: `Maximum number of results to return. Default: ${DEFAULT_MAX_RESULTS}. Capped at ${HARD_MAX_RESULTS}.`,\n },\n },\n required: ['query'],\n },\n },\n async execute({ query, max_results }, ctx: ToolContext) {\n const q = typeof query === 'string' ? query.trim() : ''\n if (!q)\n return 'web_search error: `query` is required'\n const max = Math.min(\n typeof max_results === 'number' && max_results > 0 ? Math.floor(max_results) : DEFAULT_MAX_RESULTS,\n HARD_MAX_RESULTS,\n )\n const backend = pickBackend()\n // Honor the egress allowlist (when configured): refuse a backend whose\n // host isn't approved rather than silently making the request.\n const allowHosts = ctx.behavior?.fetchUrlAllowHosts\n if (!isHostAllowed(BACKEND_HOST[backend], allowHosts)) {\n return `web_search error: the ${backend} backend (${BACKEND_HOST[backend]}) is not in the configured egress allowlist`\n }\n try {\n const results = backend === 'tavily'\n ? await searchTavily(q, max, ctx.signal)\n : backend === 'brave'\n ? await searchBrave(q, max, ctx.signal)\n : await searchDuckDuckGo(q, max, ctx.signal)\n return formatResults(results, backend)\n }\n catch (err) {\n return `web_search error (${backend}): ${errorMessage(err)}`\n }\n },\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web-search.js","names":[],"sources":["../../src/tools/web-search.ts"],"sourcesContent":["import type { ToolContext, ToolDef } from './types'\nimport { errorMessage } from '../errors'\nimport { stripHtmlInline } from './_html'\nimport { isHostAllowed } from './fetch-url'\nimport { toolPrompts } from './prompts'\n\n/**\n * Web search tool with a runtime-resolved backend.\n *\n * Backend selection is keyless-fallback: the first backend whose env var\n * is set wins, otherwise we fall through to DuckDuckGo's HTML endpoint\n * (no key required, no auth, rate-limited and brittle to layout changes).\n *\n * 1. TAVILY_API_KEY → https://api.tavily.com/search\n * 2. BRAVE_SEARCH_API_KEY → https://api.search.brave.com/res/v1/web/search\n * 3. (fallback) → https://html.duckduckgo.com/html/\n *\n * Selection happens per call (not at module init) so a key added\n * mid-session via the chat layer's auth flow is picked up on the very next\n * search without restarting the agent. The 5 ms env-var read is negligible\n * compared to the network round-trip.\n *\n * Anthropic providers can take over this tool server-side via the\n * `nativeWebSearch` capability — when set, the provider's `formatTools`\n * drops this `ToolDef` from the function-tools list and emits the\n * server-tool block instead, so this `execute` body is never invoked.\n */\n\nconst DEFAULT_MAX_RESULTS = 5\nconst HARD_MAX_RESULTS = 20\nconst HTTP_TIMEOUT_MS = 15_000\nconst ERROR_BODY_MAX = 256\n/**\n * Cap on the DuckDuckGo HTML we run the scrape regex over. `blockRe` chains\n * lazy `[\\s\\S]*?` spans, which degrade badly (ReDoS-ish slow path) on\n * multi-MB documents; a real DDG results page is well under 512K, so\n * anything past the cap is not results markup we'd want anyway.\n */\nconst DDG_HTML_MAX_CHARS = 512 * 1024\n\n/**\n * Current \"Month YYYY\" for the search description's recency hint. A model with\n * an older training cutoff otherwise queries with a stale year (`React docs\n * 2023`) and misses everything newer. Resolved per read (the `description`\n * getter below) so a long-lived process picks up a month rollover; the value\n * is stable within a session, so it doesn't thrash the provider prompt cache.\n */\nfunction currentMonthYear(): string {\n return new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })\n}\n\ninterface SearchResult {\n title: string\n url: string\n snippet: string\n}\n\ntype Backend = 'tavily' | 'brave' | 'duckduckgo'\n\n/** Egress host each backend talks to — gated against `fetchUrlAllowHosts`. */\nconst BACKEND_HOST: Record<Backend, string> = {\n tavily: 'api.tavily.com',\n brave: 'api.search.brave.com',\n duckduckgo: 'html.duckduckgo.com',\n}\n\nfunction pickBackend(): Backend {\n if (process.env.TAVILY_API_KEY)\n return 'tavily'\n if (process.env.BRAVE_SEARCH_API_KEY)\n return 'brave'\n return 'duckduckgo'\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit, signal: AbortSignal): Promise<Response> {\n const ctrl = new AbortController()\n const onAbort = (): void => ctrl.abort()\n signal.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => ctrl.abort(), HTTP_TIMEOUT_MS)\n try {\n return await fetch(url, { ...init, signal: ctrl.signal })\n }\n finally {\n clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n }\n}\n\n/**\n * Truncate untrusted HTTP error bodies before embedding them in our own\n * error string. Without this, a backend that returns an HTML 500 page\n * leaks a kilobyte of unstructured chrome straight into the model's\n * context window — which is both noisy and a small prompt-injection\n * surface.\n */\nasync function readErrorBody(res: Response): Promise<string> {\n const body = await res.text().catch(() => '')\n if (body.length <= ERROR_BODY_MAX)\n return body\n return `${body.slice(0, ERROR_BODY_MAX)}… (truncated, ${body.length} bytes total)`\n}\n\nasync function searchTavily(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const res = await fetchWithTimeout('https://api.tavily.com/search', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n api_key: process.env.TAVILY_API_KEY,\n query,\n max_results: max,\n search_depth: 'basic',\n }),\n }, signal)\n if (!res.ok)\n throw new Error(`Tavily HTTP ${res.status}: ${await readErrorBody(res)}`)\n const json = await res.json() as { results?: Array<{ title?: string, url?: string, content?: string }> }\n return (json.results ?? []).map(r => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.content ?? '',\n }))\n}\n\nasync function searchBrave(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${max}`\n const res = await fetchWithTimeout(url, {\n headers: {\n 'accept': 'application/json',\n 'x-subscription-token': process.env.BRAVE_SEARCH_API_KEY!,\n },\n }, signal)\n if (!res.ok)\n throw new Error(`Brave HTTP ${res.status}: ${await readErrorBody(res)}`)\n const json = await res.json() as { web?: { results?: Array<{ title?: string, url?: string, description?: string }> } }\n return (json.web?.results ?? []).map(r => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.description ?? '',\n }))\n}\n\n// DDG's HTML endpoint redirects through /l/?uddg=<encoded> — unwrap it so the\n// model sees the destination URL directly.\nfunction unwrapDdgRedirect(href: string): string {\n try {\n const u = new URL(href, 'https://duckduckgo.com')\n if (u.pathname === '/l/' || u.pathname === '/l') {\n const target = u.searchParams.get('uddg')\n if (target)\n return decodeURIComponent(target)\n }\n return u.toString()\n }\n catch {\n return href\n }\n}\n\nasync function searchDuckDuckGo(query: string, max: number, signal: AbortSignal): Promise<SearchResult[]> {\n const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`\n const res = await fetchWithTimeout(url, {\n headers: {\n // DDG returns an empty results page to unbranded UAs.\n 'user-agent': 'Mozilla/5.0 (compatible; zidane/1.0)',\n 'accept': 'text/html',\n },\n }, signal)\n if (!res.ok)\n throw new Error(`DuckDuckGo HTTP ${res.status}: ${await readErrorBody(res)}`)\n const html = (await res.text()).slice(0, DDG_HTML_MAX_CHARS)\n // Each result block has anchor `result__a` (title+url) and `result__snippet`.\n // Brittle by design — DDG ships layout changes occasionally and the scrape\n // silently returns zero hits when its anchors move. The keyed-key\n // backends (Tavily / Brave) are the supported happy path; this fallback\n // exists only so the tool is usable with no setup. If `results` is empty,\n // the formatter surfaces a \"(no results from duckduckgo)\" line.\n const results: SearchResult[] = []\n const blockRe = /<a[^>]+class=\"[^\"]*result__a[^\"]*\"[^>]+href=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/a>[\\s\\S]*?<a[^>]+class=\"[^\"]*result__snippet[^\"]*\"[^>]*>([\\s\\S]*?)<\\/a>/g\n\n let m = blockRe.exec(html)\n\n while (m !== null) {\n results.push({\n url: unwrapDdgRedirect(m[1]!),\n title: stripHtmlInline(m[2]!),\n snippet: stripHtmlInline(m[3]!),\n })\n if (results.length >= max)\n break\n\n m = blockRe.exec(html)\n }\n return results\n}\n\nfunction formatResults(results: SearchResult[], backend: Backend): string {\n if (results.length === 0) {\n // The keyless DuckDuckGo scrape returns zero hits both for genuinely\n // empty queries AND when DDG ships a layout change that moves the anchors\n // this regex keys off. Distinguish it from the keyed backends with an\n // actionable hint so a confused model (or user reading the transcript)\n // knows the fix is a real search key, not a better query.\n if (backend === 'duckduckgo') {\n return '(no results from the keyless DuckDuckGo fallback: it is rate-limited and '\n + 'brittle to layout changes. Set TAVILY_API_KEY or BRAVE_SEARCH_API_KEY for reliable search.)'\n }\n return `(no results from ${backend})`\n }\n const header = `# Web search results (${backend}, ${results.length} ${results.length === 1 ? 'hit' : 'hits'})\\n`\n const body = results.map((r, i) => `${i + 1}. ${r.title}\\n ${r.url}\\n ${r.snippet}`).join('\\n\\n')\n return `${header}\\n${body}`\n}\n\nexport const webSearch: ToolDef = {\n isConcurrencySafe: true,\n spec: {\n name: 'web_search',\n // Getter, not a constant, so the recency hint reflects the current month\n // each time the spec is formatted for the provider (see `currentMonthYear`).\n get description(): string {\n return `${toolPrompts.web_search.core}\\n${toolPrompts.web_search.recency(currentMonthYear())}`\n },\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Search query. Plain natural-language or keyword form; quoting and site: operators are passed through verbatim.',\n },\n max_results: {\n type: 'number',\n description: `Maximum number of results to return. Default: ${DEFAULT_MAX_RESULTS}. Capped at ${HARD_MAX_RESULTS}.`,\n },\n },\n required: ['query'],\n },\n },\n async execute({ query, max_results }, ctx: ToolContext) {\n const q = typeof query === 'string' ? query.trim() : ''\n if (!q)\n return 'web_search error: `query` is required'\n const max = Math.min(\n typeof max_results === 'number' && max_results > 0 ? Math.floor(max_results) : DEFAULT_MAX_RESULTS,\n HARD_MAX_RESULTS,\n )\n const backend = pickBackend()\n // Honor the egress allowlist (when configured): refuse a backend whose\n // host isn't approved rather than silently making the request.\n const allowHosts = ctx.behavior?.fetchUrlAllowHosts\n if (!isHostAllowed(BACKEND_HOST[backend], allowHosts)) {\n return `web_search error: the ${backend} backend (${BACKEND_HOST[backend]}) is not in the configured egress allowlist`\n }\n try {\n const results = backend === 'tavily'\n ? await searchTavily(q, max, ctx.signal)\n : backend === 'brave'\n ? await searchBrave(q, max, ctx.signal)\n : await searchDuckDuckGo(q, max, ctx.signal)\n return formatResults(results, backend)\n }\n catch (err) {\n return `web_search error (${backend}): ${errorMessage(err)}`\n }\n },\n}\n"],"mappings":"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"}
|