@jait/gateway 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/jait.mjs +144 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +37 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +85 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +4 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +24 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +312 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/schema.d.ts +2253 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +195 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/foundation.d.ts +26 -0
- package/dist/foundation.d.ts.map +1 -0
- package/dist/foundation.js +15 -0
- package/dist/foundation.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/uuidv7.d.ts +10 -0
- package/dist/lib/uuidv7.d.ts.map +1 -0
- package/dist/lib/uuidv7.js +33 -0
- package/dist/lib/uuidv7.js.map +1 -0
- package/dist/memory/contracts.d.ts +42 -0
- package/dist/memory/contracts.d.ts.map +1 -0
- package/dist/memory/contracts.js +2 -0
- package/dist/memory/contracts.js.map +1 -0
- package/dist/memory/embeddings.d.ts +4 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +26 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/service.d.ts +17 -0
- package/dist/memory/service.d.ts.map +1 -0
- package/dist/memory/service.js +82 -0
- package/dist/memory/service.js.map +1 -0
- package/dist/memory/sqlite-backend.d.ts +11 -0
- package/dist/memory/sqlite-backend.d.ts.map +1 -0
- package/dist/memory/sqlite-backend.js +68 -0
- package/dist/memory/sqlite-backend.js.map +1 -0
- package/dist/plugins/contracts.d.ts +11 -0
- package/dist/plugins/contracts.d.ts.map +1 -0
- package/dist/plugins/contracts.js +2 -0
- package/dist/plugins/contracts.js.map +1 -0
- package/dist/providers/claude-code-provider.d.ts +39 -0
- package/dist/providers/claude-code-provider.d.ts.map +1 -0
- package/dist/providers/claude-code-provider.js +322 -0
- package/dist/providers/claude-code-provider.js.map +1 -0
- package/dist/providers/codex-provider.d.ts +51 -0
- package/dist/providers/codex-provider.d.ts.map +1 -0
- package/dist/providers/codex-provider.js +826 -0
- package/dist/providers/codex-provider.js.map +1 -0
- package/dist/providers/contracts.d.ts +167 -0
- package/dist/providers/contracts.d.ts.map +1 -0
- package/dist/providers/contracts.js +13 -0
- package/dist/providers/contracts.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/jait-provider.d.ts +23 -0
- package/dist/providers/jait-provider.d.ts.map +1 -0
- package/dist/providers/jait-provider.js +67 -0
- package/dist/providers/jait-provider.js.map +1 -0
- package/dist/providers/registry.d.ts +39 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +64 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/pty-broker-client.d.ts +46 -0
- package/dist/pty-broker-client.d.ts.map +1 -0
- package/dist/pty-broker-client.js +142 -0
- package/dist/pty-broker-client.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +236 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/chat.d.ts +32 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +1503 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/consent.d.ts +10 -0
- package/dist/routes/consent.d.ts.map +1 -0
- package/dist/routes/consent.js +127 -0
- package/dist/routes/consent.js.map +1 -0
- package/dist/routes/filesystem.d.ts +14 -0
- package/dist/routes/filesystem.d.ts.map +1 -0
- package/dist/routes/filesystem.js +152 -0
- package/dist/routes/filesystem.js.map +1 -0
- package/dist/routes/git.d.ts +17 -0
- package/dist/routes/git.d.ts.map +1 -0
- package/dist/routes/git.js +213 -0
- package/dist/routes/git.js.map +1 -0
- package/dist/routes/health.d.ts +7 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +21 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/hooks.d.ts +9 -0
- package/dist/routes/hooks.d.ts.map +1 -0
- package/dist/routes/hooks.js +22 -0
- package/dist/routes/hooks.js.map +1 -0
- package/dist/routes/jobs.d.ts +5 -0
- package/dist/routes/jobs.d.ts.map +1 -0
- package/dist/routes/jobs.js +333 -0
- package/dist/routes/jobs.js.map +1 -0
- package/dist/routes/mcp-server.d.ts +23 -0
- package/dist/routes/mcp-server.d.ts.map +1 -0
- package/dist/routes/mcp-server.js +177 -0
- package/dist/routes/mcp-server.js.map +1 -0
- package/dist/routes/mobile.d.ts +12 -0
- package/dist/routes/mobile.d.ts.map +1 -0
- package/dist/routes/mobile.js +64 -0
- package/dist/routes/mobile.js.map +1 -0
- package/dist/routes/network.d.ts +3 -0
- package/dist/routes/network.d.ts.map +1 -0
- package/dist/routes/network.js +367 -0
- package/dist/routes/network.js.map +1 -0
- package/dist/routes/repositories.d.ts +18 -0
- package/dist/routes/repositories.d.ts.map +1 -0
- package/dist/routes/repositories.js +90 -0
- package/dist/routes/repositories.js.map +1 -0
- package/dist/routes/screen-share.d.ts +17 -0
- package/dist/routes/screen-share.d.ts.map +1 -0
- package/dist/routes/screen-share.js +92 -0
- package/dist/routes/screen-share.js.map +1 -0
- package/dist/routes/sessions.d.ts +18 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +169 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/terminals.d.ts +15 -0
- package/dist/routes/terminals.d.ts.map +1 -0
- package/dist/routes/terminals.js +326 -0
- package/dist/routes/terminals.js.map +1 -0
- package/dist/routes/threads.d.ts +38 -0
- package/dist/routes/threads.d.ts.map +1 -0
- package/dist/routes/threads.js +488 -0
- package/dist/routes/threads.js.map +1 -0
- package/dist/routes/trust.d.ts +9 -0
- package/dist/routes/trust.d.ts.map +1 -0
- package/dist/routes/trust.js +25 -0
- package/dist/routes/trust.js.map +1 -0
- package/dist/routes/voice.d.ts +5 -0
- package/dist/routes/voice.d.ts.map +1 -0
- package/dist/routes/voice.js +37 -0
- package/dist/routes/voice.js.map +1 -0
- package/dist/routes/workspace.d.ts +13 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +275 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/scheduler/contracts.d.ts +15 -0
- package/dist/scheduler/contracts.d.ts.map +1 -0
- package/dist/scheduler/contracts.js +2 -0
- package/dist/scheduler/contracts.js.map +1 -0
- package/dist/scheduler/hooks.d.ts +20 -0
- package/dist/scheduler/hooks.d.ts.map +1 -0
- package/dist/scheduler/hooks.js +78 -0
- package/dist/scheduler/hooks.js.map +1 -0
- package/dist/scheduler/service.d.ts +65 -0
- package/dist/scheduler/service.d.ts.map +1 -0
- package/dist/scheduler/service.js +188 -0
- package/dist/scheduler/service.js.map +1 -0
- package/dist/security/consent-executor.d.ts +48 -0
- package/dist/security/consent-executor.d.ts.map +1 -0
- package/dist/security/consent-executor.js +158 -0
- package/dist/security/consent-executor.js.map +1 -0
- package/dist/security/consent-manager.d.ts +105 -0
- package/dist/security/consent-manager.d.ts.map +1 -0
- package/dist/security/consent-manager.js +227 -0
- package/dist/security/consent-manager.js.map +1 -0
- package/dist/security/contracts.d.ts +31 -0
- package/dist/security/contracts.d.ts.map +1 -0
- package/dist/security/contracts.js +2 -0
- package/dist/security/contracts.js.map +1 -0
- package/dist/security/http-auth.d.ts +10 -0
- package/dist/security/http-auth.d.ts.map +1 -0
- package/dist/security/http-auth.js +48 -0
- package/dist/security/http-auth.js.map +1 -0
- package/dist/security/index.d.ts +10 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +9 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/path-guard.d.ts +40 -0
- package/dist/security/path-guard.d.ts.map +1 -0
- package/dist/security/path-guard.js +125 -0
- package/dist/security/path-guard.js.map +1 -0
- package/dist/security/sandbox-manager.d.ts +43 -0
- package/dist/security/sandbox-manager.d.ts.map +1 -0
- package/dist/security/sandbox-manager.js +110 -0
- package/dist/security/sandbox-manager.js.map +1 -0
- package/dist/security/ssrf-guard.d.ts +11 -0
- package/dist/security/ssrf-guard.d.ts.map +1 -0
- package/dist/security/ssrf-guard.js +59 -0
- package/dist/security/ssrf-guard.js.map +1 -0
- package/dist/security/tool-permissions.d.ts +61 -0
- package/dist/security/tool-permissions.d.ts.map +1 -0
- package/dist/security/tool-permissions.js +105 -0
- package/dist/security/tool-permissions.js.map +1 -0
- package/dist/security/tool-profiles.d.ts +23 -0
- package/dist/security/tool-profiles.d.ts.map +1 -0
- package/dist/security/tool-profiles.js +106 -0
- package/dist/security/tool-profiles.js.map +1 -0
- package/dist/security/trust-engine.d.ts +61 -0
- package/dist/security/trust-engine.d.ts.map +1 -0
- package/dist/security/trust-engine.js +192 -0
- package/dist/security/trust-engine.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +188 -0
- package/dist/server.js.map +1 -0
- package/dist/services/audit.d.ts +60 -0
- package/dist/services/audit.d.ts.map +1 -0
- package/dist/services/audit.js +58 -0
- package/dist/services/audit.js.map +1 -0
- package/dist/services/device-registry.d.ts +15 -0
- package/dist/services/device-registry.d.ts.map +1 -0
- package/dist/services/device-registry.js +32 -0
- package/dist/services/device-registry.js.map +1 -0
- package/dist/services/git.d.ts +168 -0
- package/dist/services/git.d.ts.map +1 -0
- package/dist/services/git.js +957 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/repositories.d.ts +32 -0
- package/dist/services/repositories.d.ts.map +1 -0
- package/dist/services/repositories.js +70 -0
- package/dist/services/repositories.js.map +1 -0
- package/dist/services/session-state.d.ts +20 -0
- package/dist/services/session-state.d.ts.map +1 -0
- package/dist/services/session-state.js +89 -0
- package/dist/services/session-state.js.map +1 -0
- package/dist/services/sessions.d.ts +68 -0
- package/dist/services/sessions.d.ts.map +1 -0
- package/dist/services/sessions.js +136 -0
- package/dist/services/sessions.js.map +1 -0
- package/dist/services/thread-title.d.ts +23 -0
- package/dist/services/thread-title.d.ts.map +1 -0
- package/dist/services/thread-title.js +141 -0
- package/dist/services/thread-title.js.map +1 -0
- package/dist/services/threads.d.ts +64 -0
- package/dist/services/threads.d.ts.map +1 -0
- package/dist/services/threads.js +202 -0
- package/dist/services/threads.js.map +1 -0
- package/dist/services/users.d.ts +39 -0
- package/dist/services/users.d.ts.map +1 -0
- package/dist/services/users.js +203 -0
- package/dist/services/users.js.map +1 -0
- package/dist/sessions/contracts.d.ts +14 -0
- package/dist/sessions/contracts.d.ts.map +1 -0
- package/dist/sessions/contracts.js +2 -0
- package/dist/sessions/contracts.js.map +1 -0
- package/dist/surfaces/browser.d.ts +65 -0
- package/dist/surfaces/browser.d.ts.map +1 -0
- package/dist/surfaces/browser.js +615 -0
- package/dist/surfaces/browser.js.map +1 -0
- package/dist/surfaces/contracts.d.ts +34 -0
- package/dist/surfaces/contracts.d.ts.map +1 -0
- package/dist/surfaces/contracts.js +2 -0
- package/dist/surfaces/contracts.js.map +1 -0
- package/dist/surfaces/filesystem.d.ts +76 -0
- package/dist/surfaces/filesystem.d.ts.map +1 -0
- package/dist/surfaces/filesystem.js +245 -0
- package/dist/surfaces/filesystem.js.map +1 -0
- package/dist/surfaces/index.d.ts +6 -0
- package/dist/surfaces/index.d.ts.map +1 -0
- package/dist/surfaces/index.js +5 -0
- package/dist/surfaces/index.js.map +1 -0
- package/dist/surfaces/registry.d.ts +24 -0
- package/dist/surfaces/registry.d.ts.map +1 -0
- package/dist/surfaces/registry.js +59 -0
- package/dist/surfaces/registry.js.map +1 -0
- package/dist/surfaces/terminal.d.ts +76 -0
- package/dist/surfaces/terminal.d.ts.map +1 -0
- package/dist/surfaces/terminal.js +271 -0
- package/dist/surfaces/terminal.js.map +1 -0
- package/dist/tools/agent-loop.d.ts +302 -0
- package/dist/tools/agent-loop.d.ts.map +1 -0
- package/dist/tools/agent-loop.js +918 -0
- package/dist/tools/agent-loop.js.map +1 -0
- package/dist/tools/agent-tools.d.ts +39 -0
- package/dist/tools/agent-tools.d.ts.map +1 -0
- package/dist/tools/agent-tools.js +263 -0
- package/dist/tools/agent-tools.js.map +1 -0
- package/dist/tools/browser-tools.d.ts +38 -0
- package/dist/tools/browser-tools.d.ts.map +1 -0
- package/dist/tools/browser-tools.js +725 -0
- package/dist/tools/browser-tools.js.map +1 -0
- package/dist/tools/chat-modes.d.ts +75 -0
- package/dist/tools/chat-modes.d.ts.map +1 -0
- package/dist/tools/chat-modes.js +228 -0
- package/dist/tools/chat-modes.js.map +1 -0
- package/dist/tools/contracts.d.ts +69 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +2 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/core/agent.d.ts +31 -0
- package/dist/tools/core/agent.d.ts.map +1 -0
- package/dist/tools/core/agent.js +65 -0
- package/dist/tools/core/agent.js.map +1 -0
- package/dist/tools/core/edit.d.ts +30 -0
- package/dist/tools/core/edit.d.ts.map +1 -0
- package/dist/tools/core/edit.js +109 -0
- package/dist/tools/core/edit.js.map +1 -0
- package/dist/tools/core/execute.d.ts +36 -0
- package/dist/tools/core/execute.d.ts.map +1 -0
- package/dist/tools/core/execute.js +81 -0
- package/dist/tools/core/execute.js.map +1 -0
- package/dist/tools/core/get-fs.d.ts +32 -0
- package/dist/tools/core/get-fs.d.ts.map +1 -0
- package/dist/tools/core/get-fs.js +143 -0
- package/dist/tools/core/get-fs.js.map +1 -0
- package/dist/tools/core/index.d.ts +26 -0
- package/dist/tools/core/index.d.ts.map +1 -0
- package/dist/tools/core/index.js +26 -0
- package/dist/tools/core/index.js.map +1 -0
- package/dist/tools/core/jait.d.ts +60 -0
- package/dist/tools/core/jait.d.ts.map +1 -0
- package/dist/tools/core/jait.js +256 -0
- package/dist/tools/core/jait.js.map +1 -0
- package/dist/tools/core/read.d.ts +26 -0
- package/dist/tools/core/read.d.ts.map +1 -0
- package/dist/tools/core/read.js +118 -0
- package/dist/tools/core/read.js.map +1 -0
- package/dist/tools/core/search.d.ts +34 -0
- package/dist/tools/core/search.d.ts.map +1 -0
- package/dist/tools/core/search.js +187 -0
- package/dist/tools/core/search.js.map +1 -0
- package/dist/tools/core/todo.d.ts +38 -0
- package/dist/tools/core/todo.d.ts.map +1 -0
- package/dist/tools/core/todo.js +116 -0
- package/dist/tools/core/todo.js.map +1 -0
- package/dist/tools/core/web.d.ts +34 -0
- package/dist/tools/core/web.d.ts.map +1 -0
- package/dist/tools/core/web.js +120 -0
- package/dist/tools/core/web.js.map +1 -0
- package/dist/tools/cron-tools.d.ts +7 -0
- package/dist/tools/cron-tools.d.ts.map +1 -0
- package/dist/tools/cron-tools.js +116 -0
- package/dist/tools/cron-tools.js.map +1 -0
- package/dist/tools/file-tools.d.ts +32 -0
- package/dist/tools/file-tools.d.ts.map +1 -0
- package/dist/tools/file-tools.js +178 -0
- package/dist/tools/file-tools.js.map +1 -0
- package/dist/tools/gateway-tools.d.ts +15 -0
- package/dist/tools/gateway-tools.d.ts.map +1 -0
- package/dist/tools/gateway-tools.js +39 -0
- package/dist/tools/gateway-tools.js.map +1 -0
- package/dist/tools/index.d.ts +57 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +170 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/mcp-bridge.d.ts +111 -0
- package/dist/tools/mcp-bridge.d.ts.map +1 -0
- package/dist/tools/mcp-bridge.js +166 -0
- package/dist/tools/mcp-bridge.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +19 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +78 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/meta-tools.d.ts +25 -0
- package/dist/tools/meta-tools.d.ts.map +1 -0
- package/dist/tools/meta-tools.js +125 -0
- package/dist/tools/meta-tools.js.map +1 -0
- package/dist/tools/network-tools.d.ts +21 -0
- package/dist/tools/network-tools.d.ts.map +1 -0
- package/dist/tools/network-tools.js +189 -0
- package/dist/tools/network-tools.js.map +1 -0
- package/dist/tools/os-tools.d.ts +18 -0
- package/dist/tools/os-tools.d.ts.map +1 -0
- package/dist/tools/os-tools.js +210 -0
- package/dist/tools/os-tools.js.map +1 -0
- package/dist/tools/prompts/claude-prompt.d.ts +8 -0
- package/dist/tools/prompts/claude-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/claude-prompt.js +228 -0
- package/dist/tools/prompts/claude-prompt.js.map +1 -0
- package/dist/tools/prompts/default-openai-prompt.d.ts +8 -0
- package/dist/tools/prompts/default-openai-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/default-openai-prompt.js +67 -0
- package/dist/tools/prompts/default-openai-prompt.js.map +1 -0
- package/dist/tools/prompts/default-prompt.d.ts +7 -0
- package/dist/tools/prompts/default-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/default-prompt.js +50 -0
- package/dist/tools/prompts/default-prompt.js.map +1 -0
- package/dist/tools/prompts/gemini-prompt.d.ts +8 -0
- package/dist/tools/prompts/gemini-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/gemini-prompt.js +118 -0
- package/dist/tools/prompts/gemini-prompt.js.map +1 -0
- package/dist/tools/prompts/gpt5-codex-prompt.d.ts +8 -0
- package/dist/tools/prompts/gpt5-codex-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/gpt5-codex-prompt.js +72 -0
- package/dist/tools/prompts/gpt5-codex-prompt.js.map +1 -0
- package/dist/tools/prompts/gpt5-prompt.d.ts +8 -0
- package/dist/tools/prompts/gpt5-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/gpt5-prompt.js +177 -0
- package/dist/tools/prompts/gpt5-prompt.js.map +1 -0
- package/dist/tools/prompts/gpt51-prompt.d.ts +8 -0
- package/dist/tools/prompts/gpt51-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/gpt51-prompt.js +178 -0
- package/dist/tools/prompts/gpt51-prompt.js.map +1 -0
- package/dist/tools/prompts/gpt52-prompt.d.ts +8 -0
- package/dist/tools/prompts/gpt52-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/gpt52-prompt.js +198 -0
- package/dist/tools/prompts/gpt52-prompt.js.map +1 -0
- package/dist/tools/prompts/index.d.ts +22 -0
- package/dist/tools/prompts/index.d.ts.map +1 -0
- package/dist/tools/prompts/index.js +23 -0
- package/dist/tools/prompts/index.js.map +1 -0
- package/dist/tools/prompts/prompt-registry.d.ts +44 -0
- package/dist/tools/prompts/prompt-registry.d.ts.map +1 -0
- package/dist/tools/prompts/prompt-registry.js +60 -0
- package/dist/tools/prompts/prompt-registry.js.map +1 -0
- package/dist/tools/prompts/shared-sections.d.ts +28 -0
- package/dist/tools/prompts/shared-sections.d.ts.map +1 -0
- package/dist/tools/prompts/shared-sections.js +111 -0
- package/dist/tools/prompts/shared-sections.js.map +1 -0
- package/dist/tools/prompts/xai-prompt.d.ts +8 -0
- package/dist/tools/prompts/xai-prompt.d.ts.map +1 -0
- package/dist/tools/prompts/xai-prompt.js +68 -0
- package/dist/tools/prompts/xai-prompt.js.map +1 -0
- package/dist/tools/redeploy-tools.d.ts +30 -0
- package/dist/tools/redeploy-tools.d.ts.map +1 -0
- package/dist/tools/redeploy-tools.js +191 -0
- package/dist/tools/redeploy-tools.js.map +1 -0
- package/dist/tools/registry.d.ts +51 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +148 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/screen-share-tools.d.ts +31 -0
- package/dist/tools/screen-share-tools.d.ts.map +1 -0
- package/dist/tools/screen-share-tools.js +183 -0
- package/dist/tools/screen-share-tools.js.map +1 -0
- package/dist/tools/surface-tools.d.ts +23 -0
- package/dist/tools/surface-tools.d.ts.map +1 -0
- package/dist/tools/surface-tools.js +99 -0
- package/dist/tools/surface-tools.js.map +1 -0
- package/dist/tools/terminal-tools.d.ts +37 -0
- package/dist/tools/terminal-tools.d.ts.map +1 -0
- package/dist/tools/terminal-tools.js +448 -0
- package/dist/tools/terminal-tools.js.map +1 -0
- package/dist/tools/thread-tools.d.ts +61 -0
- package/dist/tools/thread-tools.d.ts.map +1 -0
- package/dist/tools/thread-tools.js +484 -0
- package/dist/tools/thread-tools.js.map +1 -0
- package/dist/tools/token-estimator.d.ts +55 -0
- package/dist/tools/token-estimator.d.ts.map +1 -0
- package/dist/tools/token-estimator.js +82 -0
- package/dist/tools/token-estimator.js.map +1 -0
- package/dist/tools/tool-names.d.ts +64 -0
- package/dist/tools/tool-names.d.ts.map +1 -0
- package/dist/tools/tool-names.js +76 -0
- package/dist/tools/tool-names.js.map +1 -0
- package/dist/tools/validate.d.ts +27 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +99 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/voice-tools.d.ts +8 -0
- package/dist/tools/voice-tools.d.ts.map +1 -0
- package/dist/tools/voice-tools.js +32 -0
- package/dist/tools/voice-tools.js.map +1 -0
- package/dist/voice/service.d.ts +42 -0
- package/dist/voice/service.d.ts.map +1 -0
- package/dist/voice/service.js +75 -0
- package/dist/voice/service.js.map +1 -0
- package/dist/ws.d.ts +90 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +562 -0
- package/dist/ws.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Loop — reusable, streamable tool-calling loop.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from chat.ts and enhanced with:
|
|
5
|
+
* - Input validation (catches bad LLM args immediately)
|
|
6
|
+
* - Parallel tool execution (independent calls run concurrently)
|
|
7
|
+
* - Retry for individual failed tool calls
|
|
8
|
+
* - Steering (inject guidance mid-loop)
|
|
9
|
+
* - Tool call queueing with priority
|
|
10
|
+
*
|
|
11
|
+
* Both the main chat route and the agent.spawn sub-agent tool use this.
|
|
12
|
+
*/
|
|
13
|
+
import { validateToolInput } from "./validate.js";
|
|
14
|
+
import { ASK_MODE_TOOLS, MUTATING_TOOLS } from "./chat-modes.js";
|
|
15
|
+
import { getReminderInstructions } from "./prompts/index.js";
|
|
16
|
+
import { computeContextUsage, estimateTokens } from "./token-estimator.js";
|
|
17
|
+
/** Format an LLM HTTP error into a user-friendly message, similar to VS Code Copilot. */
|
|
18
|
+
function formatLLMError(status, responseText) {
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(responseText);
|
|
22
|
+
}
|
|
23
|
+
catch { /* not JSON */ }
|
|
24
|
+
const serverMsg = parsed?.error?.message;
|
|
25
|
+
const code = parsed?.error?.code;
|
|
26
|
+
switch (status) {
|
|
27
|
+
case 401:
|
|
28
|
+
return "Your API key is invalid or expired. Please check your settings.";
|
|
29
|
+
case 403:
|
|
30
|
+
return "Access denied by the model provider. Please check your API key permissions.";
|
|
31
|
+
case 429:
|
|
32
|
+
if (code === "insufficient_quota" || serverMsg?.includes("quota"))
|
|
33
|
+
return "You've exceeded your API quota. Please check your plan and billing details.";
|
|
34
|
+
return `Rate limited by the model provider. Please wait a moment and try again.${serverMsg ? `\n${serverMsg}` : ""}`;
|
|
35
|
+
case 500:
|
|
36
|
+
case 502:
|
|
37
|
+
case 503:
|
|
38
|
+
return `The model provider is experiencing issues (${status}). Please try again later.`;
|
|
39
|
+
default:
|
|
40
|
+
return serverMsg
|
|
41
|
+
? `Request failed (${status}): ${serverMsg}`
|
|
42
|
+
: `Request failed with status ${status}. Please try again.`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Priority levels for queued tool calls */
|
|
46
|
+
export var ToolCallPriority;
|
|
47
|
+
(function (ToolCallPriority) {
|
|
48
|
+
/** Run before anything else (e.g. abort-checks, validation) */
|
|
49
|
+
ToolCallPriority[ToolCallPriority["Critical"] = 0] = "Critical";
|
|
50
|
+
/** Normal tool calls from the LLM */
|
|
51
|
+
ToolCallPriority[ToolCallPriority["Normal"] = 1] = "Normal";
|
|
52
|
+
/** Deferred / low-priority background work */
|
|
53
|
+
ToolCallPriority[ToolCallPriority["Low"] = 2] = "Low";
|
|
54
|
+
})(ToolCallPriority || (ToolCallPriority = {}));
|
|
55
|
+
// ── Steering controller ──────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* Steering lets the user (or system) inject guidance into the agent
|
|
58
|
+
* loop while it's running. The steered message gets appended to the
|
|
59
|
+
* conversation as a system message before the next LLM call.
|
|
60
|
+
*/
|
|
61
|
+
export class SteeringController {
|
|
62
|
+
queue = [];
|
|
63
|
+
/** Inject a steering message into the loop */
|
|
64
|
+
steer(message) {
|
|
65
|
+
this.queue.push(message);
|
|
66
|
+
}
|
|
67
|
+
/** Drain all pending steering messages (called by the loop) */
|
|
68
|
+
drain() {
|
|
69
|
+
const msgs = this.queue.splice(0);
|
|
70
|
+
return msgs;
|
|
71
|
+
}
|
|
72
|
+
get hasPending() {
|
|
73
|
+
return this.queue.length > 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ── Tool call queue ──────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Priority queue for tool calls. Sorts by priority (lower = first),
|
|
79
|
+
* then partitions into parallel-safe batches.
|
|
80
|
+
*/
|
|
81
|
+
export class ToolCallQueue {
|
|
82
|
+
items = [];
|
|
83
|
+
/** Enqueue a tool call with optional priority and parallelism hint */
|
|
84
|
+
enqueue(toolCall, priority = ToolCallPriority.Normal, parallelSafe = false) {
|
|
85
|
+
this.items.push({ toolCall, priority, parallelSafe });
|
|
86
|
+
// Keep sorted by priority
|
|
87
|
+
this.items.sort((a, b) => a.priority - b.priority);
|
|
88
|
+
}
|
|
89
|
+
/** Enqueue multiple tool calls at the same priority */
|
|
90
|
+
enqueueAll(toolCalls, priority = ToolCallPriority.Normal, parallelSafe = false) {
|
|
91
|
+
for (const tc of toolCalls) {
|
|
92
|
+
this.enqueue(tc, priority, parallelSafe);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Dequeue the next batch. If parallel execution is enabled, returns
|
|
97
|
+
* all contiguous parallel-safe items at the same priority level.
|
|
98
|
+
* Otherwise returns one at a time.
|
|
99
|
+
*/
|
|
100
|
+
dequeueBatch(allowParallel) {
|
|
101
|
+
if (this.items.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
if (!allowParallel) {
|
|
104
|
+
return [this.items.shift()];
|
|
105
|
+
}
|
|
106
|
+
const first = this.items[0];
|
|
107
|
+
if (!first.parallelSafe) {
|
|
108
|
+
return [this.items.shift()];
|
|
109
|
+
}
|
|
110
|
+
// Grab all contiguous items at the same priority that are parallel-safe
|
|
111
|
+
const batch = [];
|
|
112
|
+
while (this.items.length > 0 &&
|
|
113
|
+
this.items[0].priority === first.priority &&
|
|
114
|
+
this.items[0].parallelSafe) {
|
|
115
|
+
batch.push(this.items.shift());
|
|
116
|
+
}
|
|
117
|
+
return batch;
|
|
118
|
+
}
|
|
119
|
+
get length() {
|
|
120
|
+
return this.items.length;
|
|
121
|
+
}
|
|
122
|
+
get isEmpty() {
|
|
123
|
+
return this.items.length === 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ── Tool name conversion ─────────────────────────────────────────────
|
|
127
|
+
/** OpenAI requires function names to match ^[a-zA-Z0-9_-]+$ — no dots */
|
|
128
|
+
export function toOpenAIName(name) {
|
|
129
|
+
return name.replace(/\./g, "_");
|
|
130
|
+
}
|
|
131
|
+
export function fromOpenAIName(name) {
|
|
132
|
+
const idx = name.indexOf("_");
|
|
133
|
+
if (idx === -1)
|
|
134
|
+
return name;
|
|
135
|
+
return name.slice(0, idx) + "." + name.slice(idx + 1);
|
|
136
|
+
}
|
|
137
|
+
// ── Tools that are safe to run in parallel ───────────────────────────
|
|
138
|
+
/**
|
|
139
|
+
* Read-only / side-effect-free tools that can safely execute concurrently.
|
|
140
|
+
* Tools NOT in this set run sequentially to preserve ordering guarantees.
|
|
141
|
+
*/
|
|
142
|
+
const PARALLEL_SAFE_TOOLS = new Set([
|
|
143
|
+
"file.read",
|
|
144
|
+
"file.list",
|
|
145
|
+
"file.stat",
|
|
146
|
+
"os.query",
|
|
147
|
+
"memory.search",
|
|
148
|
+
"web.fetch",
|
|
149
|
+
"web.search",
|
|
150
|
+
"gateway.status",
|
|
151
|
+
"browser.snapshot",
|
|
152
|
+
]);
|
|
153
|
+
function isParallelSafe(toolName) {
|
|
154
|
+
return PARALLEL_SAFE_TOOLS.has(toolName);
|
|
155
|
+
}
|
|
156
|
+
// ── Serialize messages for OpenAI API ────────────────────────────────
|
|
157
|
+
export function serializeMessages(messages) {
|
|
158
|
+
return messages.map((m) => {
|
|
159
|
+
const msg = { role: m.role, content: m.content };
|
|
160
|
+
if (m.tool_calls)
|
|
161
|
+
msg.tool_calls = m.tool_calls;
|
|
162
|
+
if (m.tool_call_id)
|
|
163
|
+
msg.tool_call_id = m.tool_call_id;
|
|
164
|
+
if (m.name)
|
|
165
|
+
msg.name = m.name;
|
|
166
|
+
return msg;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// ── Build OpenAI tool schemas ────────────────────────────────────────
|
|
170
|
+
export function buildToolSchemas(registry, allowedTools) {
|
|
171
|
+
let tools = registry.list();
|
|
172
|
+
if (allowedTools) {
|
|
173
|
+
tools = tools.filter((t) => allowedTools.has(t.name));
|
|
174
|
+
}
|
|
175
|
+
return tools.map((t) => ({
|
|
176
|
+
type: "function",
|
|
177
|
+
function: {
|
|
178
|
+
name: toOpenAIName(t.name),
|
|
179
|
+
description: t.description,
|
|
180
|
+
parameters: t.parameters,
|
|
181
|
+
},
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Build schemas respecting tiers and user disabled tools.
|
|
186
|
+
*
|
|
187
|
+
* Only "core" and "standard" (non-disabled) tools are included in the
|
|
188
|
+
* initial payload. External / MCP tools must be discovered via tools.search.
|
|
189
|
+
*/
|
|
190
|
+
export function buildTieredToolSchemas(registry, disabledTools) {
|
|
191
|
+
const tools = registry.listForLLM(disabledTools);
|
|
192
|
+
return tools.map((t) => ({
|
|
193
|
+
type: "function",
|
|
194
|
+
function: {
|
|
195
|
+
name: toOpenAIName(t.name),
|
|
196
|
+
description: t.description,
|
|
197
|
+
parameters: t.parameters,
|
|
198
|
+
},
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Convert individual tool definitions into OpenAI schemas.
|
|
203
|
+
* Used to dynamically inject schemas discovered via tools.search.
|
|
204
|
+
*/
|
|
205
|
+
export function toolDefsToSchemas(defs) {
|
|
206
|
+
return defs.map((t) => ({
|
|
207
|
+
type: "function",
|
|
208
|
+
function: {
|
|
209
|
+
name: toOpenAIName(t.name),
|
|
210
|
+
description: t.description,
|
|
211
|
+
parameters: t.parameters,
|
|
212
|
+
},
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
export async function parseOpenAIStream(reader, onEvent) {
|
|
216
|
+
const decoder = new TextDecoder();
|
|
217
|
+
let buffer = "";
|
|
218
|
+
let contentText = "";
|
|
219
|
+
let finishReason = null;
|
|
220
|
+
const toolCallMap = new Map();
|
|
221
|
+
while (true) {
|
|
222
|
+
const { done, value } = await reader.read();
|
|
223
|
+
if (done)
|
|
224
|
+
break;
|
|
225
|
+
buffer += decoder.decode(value, { stream: true });
|
|
226
|
+
const lines = buffer.split("\n");
|
|
227
|
+
buffer = lines.pop() || "";
|
|
228
|
+
for (const line of lines) {
|
|
229
|
+
const trimmed = line.trim();
|
|
230
|
+
if (!trimmed || !trimmed.startsWith("data: "))
|
|
231
|
+
continue;
|
|
232
|
+
const payload = trimmed.slice(6);
|
|
233
|
+
if (payload === "[DONE]")
|
|
234
|
+
continue;
|
|
235
|
+
try {
|
|
236
|
+
const chunk = JSON.parse(payload);
|
|
237
|
+
const choice = chunk.choices?.[0];
|
|
238
|
+
if (!choice)
|
|
239
|
+
continue;
|
|
240
|
+
const delta = choice.delta;
|
|
241
|
+
if (!delta)
|
|
242
|
+
continue;
|
|
243
|
+
// Text content
|
|
244
|
+
if (delta.content) {
|
|
245
|
+
contentText += delta.content;
|
|
246
|
+
onEvent?.({ type: "token", content: delta.content });
|
|
247
|
+
}
|
|
248
|
+
// Tool calls (streamed incrementally)
|
|
249
|
+
if (delta.tool_calls) {
|
|
250
|
+
for (const tc of delta.tool_calls) {
|
|
251
|
+
const idx = tc.index ?? 0;
|
|
252
|
+
const isNew = !toolCallMap.has(idx);
|
|
253
|
+
if (isNew) {
|
|
254
|
+
toolCallMap.set(idx, {
|
|
255
|
+
id: tc.id ?? "",
|
|
256
|
+
type: "function",
|
|
257
|
+
function: { name: tc.function?.name ?? "", arguments: "" },
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const existing = toolCallMap.get(idx);
|
|
261
|
+
if (tc.id)
|
|
262
|
+
existing.id = tc.id;
|
|
263
|
+
if (!isNew && tc.function?.name)
|
|
264
|
+
existing.function.name += tc.function.name;
|
|
265
|
+
if (tc.function?.arguments)
|
|
266
|
+
existing.function.arguments += tc.function.arguments;
|
|
267
|
+
const callId = existing.id || `pending-${idx}`;
|
|
268
|
+
onEvent?.({
|
|
269
|
+
type: "tool_call_delta",
|
|
270
|
+
call_id: callId,
|
|
271
|
+
index: idx,
|
|
272
|
+
name_delta: tc.function?.name || undefined,
|
|
273
|
+
args_delta: tc.function?.arguments || undefined,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (choice.finish_reason) {
|
|
278
|
+
finishReason = choice.finish_reason;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// partial JSON chunk — ignore
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const toolCalls = [...toolCallMap.entries()]
|
|
287
|
+
.sort(([a], [b]) => a - b)
|
|
288
|
+
.map(([, tc]) => tc);
|
|
289
|
+
return { contentText, toolCalls, finishReason };
|
|
290
|
+
}
|
|
291
|
+
async function executeOneToolCall(opts) {
|
|
292
|
+
const { tc, sessionId, auth, signal, toolRegistry, maxRetries, onEvent, executeTool } = opts;
|
|
293
|
+
const startedAt = Date.now();
|
|
294
|
+
let args;
|
|
295
|
+
try {
|
|
296
|
+
args = JSON.parse(tc.function.arguments);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
args = {};
|
|
300
|
+
}
|
|
301
|
+
const internalName = fromOpenAIName(tc.function.name);
|
|
302
|
+
// ── Input validation (fast reject bad LLM args) ──
|
|
303
|
+
if (toolRegistry) {
|
|
304
|
+
const toolDef = toolRegistry.get(internalName);
|
|
305
|
+
if (toolDef) {
|
|
306
|
+
const validation = validateToolInput(toolDef.parameters, args);
|
|
307
|
+
if (!validation.valid) {
|
|
308
|
+
onEvent?.({
|
|
309
|
+
type: "tool_validation_error",
|
|
310
|
+
call_id: tc.id,
|
|
311
|
+
tool: internalName,
|
|
312
|
+
errors: validation.errors,
|
|
313
|
+
});
|
|
314
|
+
// Return the validation error as a tool result so the LLM can self-correct
|
|
315
|
+
const errorMsg = `INPUT VALIDATION ERROR: ${validation.errors.join("; ")}`;
|
|
316
|
+
const result = { ok: false, message: errorMsg };
|
|
317
|
+
return {
|
|
318
|
+
result,
|
|
319
|
+
executed: {
|
|
320
|
+
callId: tc.id,
|
|
321
|
+
tool: internalName,
|
|
322
|
+
args,
|
|
323
|
+
ok: false,
|
|
324
|
+
message: errorMsg,
|
|
325
|
+
startedAt,
|
|
326
|
+
completedAt: Date.now(),
|
|
327
|
+
retryCount: 0,
|
|
328
|
+
},
|
|
329
|
+
historyEntry: {
|
|
330
|
+
role: "tool",
|
|
331
|
+
content: JSON.stringify({ ok: false, message: errorMsg }),
|
|
332
|
+
tool_call_id: tc.id,
|
|
333
|
+
name: tc.function.name,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// ── Execute with retries ──
|
|
340
|
+
onEvent?.({ type: "tool_start", tool: internalName, args, call_id: tc.id });
|
|
341
|
+
let result = { ok: false, message: "Not executed" };
|
|
342
|
+
let retryCount = 0;
|
|
343
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
344
|
+
if (signal?.aborted) {
|
|
345
|
+
result = { ok: false, message: "Cancelled" };
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
if (attempt > 0) {
|
|
349
|
+
onEvent?.({ type: "tool_retry", call_id: tc.id, attempt, maxAttempts: maxRetries });
|
|
350
|
+
// Exponential backoff: 500ms, 1s, 2s
|
|
351
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * 2 ** (attempt - 1), 4000)));
|
|
352
|
+
}
|
|
353
|
+
result = await executeTool(internalName, args, sessionId, auth, (chunk) => {
|
|
354
|
+
onEvent?.({ type: "tool_output", call_id: tc.id, content: chunk });
|
|
355
|
+
}, signal);
|
|
356
|
+
if (result.ok)
|
|
357
|
+
break;
|
|
358
|
+
// Only retry transient failures, not logical errors
|
|
359
|
+
if (!isTransientFailure(result.message))
|
|
360
|
+
break;
|
|
361
|
+
retryCount = attempt + 1;
|
|
362
|
+
}
|
|
363
|
+
const completedAt = Date.now();
|
|
364
|
+
onEvent?.({
|
|
365
|
+
type: "tool_result",
|
|
366
|
+
call_id: tc.id,
|
|
367
|
+
tool: internalName,
|
|
368
|
+
ok: result.ok,
|
|
369
|
+
message: result.message,
|
|
370
|
+
data: result.data,
|
|
371
|
+
});
|
|
372
|
+
// If this was a todo tool call, emit todo_list event for the UI
|
|
373
|
+
if (internalName === "todo" && result.ok && result.data) {
|
|
374
|
+
const items = result.data.items;
|
|
375
|
+
if (Array.isArray(items)) {
|
|
376
|
+
onEvent?.({ type: "todo_list", items });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// If this was a file-modifying tool, emit file_changed event for cross-client sync
|
|
380
|
+
if (result.ok && (internalName === "file.write" || internalName === "file.patch" || internalName === "edit")) {
|
|
381
|
+
const filePath = String(args?.path ?? "");
|
|
382
|
+
if (filePath) {
|
|
383
|
+
onEvent?.({
|
|
384
|
+
type: "file_changed",
|
|
385
|
+
path: filePath,
|
|
386
|
+
name: filePath.split("/").pop() ?? filePath,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
result,
|
|
392
|
+
executed: {
|
|
393
|
+
callId: tc.id,
|
|
394
|
+
tool: internalName,
|
|
395
|
+
args,
|
|
396
|
+
ok: result.ok,
|
|
397
|
+
message: result.message,
|
|
398
|
+
data: result.data,
|
|
399
|
+
startedAt,
|
|
400
|
+
completedAt,
|
|
401
|
+
retryCount,
|
|
402
|
+
},
|
|
403
|
+
historyEntry: {
|
|
404
|
+
role: "tool",
|
|
405
|
+
content: JSON.stringify({ ok: result.ok, message: result.message, data: result.data }),
|
|
406
|
+
tool_call_id: tc.id,
|
|
407
|
+
name: tc.function.name,
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Heuristic: is this error transient and worth retrying? */
|
|
412
|
+
function isTransientFailure(message) {
|
|
413
|
+
const lower = message.toLowerCase();
|
|
414
|
+
return (lower.includes("timeout") ||
|
|
415
|
+
lower.includes("econnrefused") ||
|
|
416
|
+
lower.includes("econnreset") ||
|
|
417
|
+
lower.includes("socket hang up") ||
|
|
418
|
+
lower.includes("rate limit") ||
|
|
419
|
+
lower.includes("429") ||
|
|
420
|
+
lower.includes("503") ||
|
|
421
|
+
lower.includes("502") ||
|
|
422
|
+
lower.includes("network") ||
|
|
423
|
+
lower.includes("unavailable"));
|
|
424
|
+
}
|
|
425
|
+
// ── Main agent loop ──────────────────────────────────────────────────
|
|
426
|
+
const DEFAULT_MAX_ROUNDS = 15;
|
|
427
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
428
|
+
/**
|
|
429
|
+
* Run the agentic tool-calling loop.
|
|
430
|
+
*
|
|
431
|
+
* This is the core reusable loop used by both the main chat route and
|
|
432
|
+
* the agent.spawn sub-agent tool. It streams LLM responses, executes
|
|
433
|
+
* tool calls (with validation, retry, parallel batching, and steering),
|
|
434
|
+
* and returns the accumulated result.
|
|
435
|
+
*/
|
|
436
|
+
// ── Context pruning ──────────────────────────────────────────────────
|
|
437
|
+
/** Target ratio after pruning — leave headroom for the next LLM response */
|
|
438
|
+
const PRUNE_TARGET_RATIO = 0.65;
|
|
439
|
+
/**
|
|
440
|
+
* Prune oldest conversation turns to bring context usage below the target.
|
|
441
|
+
*
|
|
442
|
+
* Strategy (similar to Copilot):
|
|
443
|
+
* 1. Never remove the system prompt (index 0) or the last user message.
|
|
444
|
+
* 2. Remove oldest user/assistant/tool turn groups first.
|
|
445
|
+
* 3. Insert a `[conversation-summary]` placeholder so the model knows
|
|
446
|
+
* context was trimmed.
|
|
447
|
+
*
|
|
448
|
+
* Mutates `history` in place. Returns true if anything was pruned.
|
|
449
|
+
*/
|
|
450
|
+
function pruneHistory(history, contextWindow, toolSchemas) {
|
|
451
|
+
const usage = computeContextUsage(history, toolSchemas, contextWindow);
|
|
452
|
+
const targetTokens = Math.floor(contextWindow * PRUNE_TARGET_RATIO);
|
|
453
|
+
if (usage.total <= targetTokens)
|
|
454
|
+
return false;
|
|
455
|
+
let tokensToFree = usage.total - targetTokens;
|
|
456
|
+
let pruned = false;
|
|
457
|
+
// Find removable range: skip system messages at the start and keep
|
|
458
|
+
// the last user message + everything after it.
|
|
459
|
+
let firstRemovable = 0;
|
|
460
|
+
while (firstRemovable < history.length && history[firstRemovable].role === "system") {
|
|
461
|
+
firstRemovable++;
|
|
462
|
+
}
|
|
463
|
+
// Find last user message index
|
|
464
|
+
let lastUserIdx = history.length - 1;
|
|
465
|
+
while (lastUserIdx >= 0 && history[lastUserIdx].role !== "user") {
|
|
466
|
+
lastUserIdx--;
|
|
467
|
+
}
|
|
468
|
+
// Keep the last user message and everything after it
|
|
469
|
+
const safeEnd = Math.max(lastUserIdx, firstRemovable);
|
|
470
|
+
// Remove messages from firstRemovable forward until we've freed enough
|
|
471
|
+
const removedIndices = [];
|
|
472
|
+
for (let i = firstRemovable; i < safeEnd && tokensToFree > 0; i++) {
|
|
473
|
+
const msg = history[i];
|
|
474
|
+
const cost = estimateTokens(msg.content) + 4; // rough per-message estimate
|
|
475
|
+
if (msg.role === "tool" && msg.tool_calls) {
|
|
476
|
+
// tool_calls are heavier
|
|
477
|
+
try {
|
|
478
|
+
tokensToFree -= estimateTokens(JSON.stringify(msg.tool_calls));
|
|
479
|
+
}
|
|
480
|
+
catch { /* */ }
|
|
481
|
+
}
|
|
482
|
+
tokensToFree -= cost;
|
|
483
|
+
removedIndices.push(i);
|
|
484
|
+
pruned = true;
|
|
485
|
+
}
|
|
486
|
+
if (removedIndices.length > 0) {
|
|
487
|
+
// Remove in reverse order to keep indices stable
|
|
488
|
+
for (let i = removedIndices.length - 1; i >= 0; i--) {
|
|
489
|
+
history.splice(removedIndices[i], 1);
|
|
490
|
+
}
|
|
491
|
+
// Insert a summary placeholder after the system messages
|
|
492
|
+
const insertAt = firstRemovable;
|
|
493
|
+
history.splice(insertAt, 0, {
|
|
494
|
+
role: "system",
|
|
495
|
+
content: `[conversation-summary] ${removedIndices.length} earlier messages were removed to fit the context window. The conversation continues from the remaining messages below.`,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return pruned;
|
|
499
|
+
}
|
|
500
|
+
export async function runAgentLoop(options, executeTool, steering) {
|
|
501
|
+
const { llm, history, toolSchemas: initialToolSchemas, hasTools, sessionId, auth, abort, maxRounds = DEFAULT_MAX_ROUNDS, maxRetries = DEFAULT_MAX_RETRIES, parallel = true, toolRegistry, disabledTools, mode = "agent", onEvent, onPersist, log = console, } = options;
|
|
502
|
+
let fullContent = "";
|
|
503
|
+
const executedToolCalls = [];
|
|
504
|
+
const segments = [];
|
|
505
|
+
const queue = new ToolCallQueue();
|
|
506
|
+
// ── Plan mode state ──
|
|
507
|
+
const plannedActions = [];
|
|
508
|
+
const planId = mode === "plan" ? `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` : "";
|
|
509
|
+
// ── Mode-aware schema filtering ──
|
|
510
|
+
// Ask mode: only read-only tools. Plan/Agent mode: full set.
|
|
511
|
+
let modeFilteredSchemas = initialToolSchemas;
|
|
512
|
+
if (mode === "ask") {
|
|
513
|
+
modeFilteredSchemas = initialToolSchemas.filter((s) => ASK_MODE_TOOLS.has(fromOpenAIName(s.function.name)));
|
|
514
|
+
onEvent?.({ type: "mode_notice", mode: "ask", message: "Running in Ask mode — read-only tools only." });
|
|
515
|
+
}
|
|
516
|
+
else if (mode === "plan") {
|
|
517
|
+
onEvent?.({ type: "mode_notice", mode: "plan", message: "Running in Plan mode — mutating actions will be proposed, not executed." });
|
|
518
|
+
}
|
|
519
|
+
// Dynamic schema set — starts with filtered schemas, grows when tools.search
|
|
520
|
+
// discovers additional tools (e.g. external/MCP tools).
|
|
521
|
+
const activeSchemas = [...modeFilteredSchemas];
|
|
522
|
+
const activeSchemaNames = new Set(activeSchemas.map((s) => s.function.name));
|
|
523
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
524
|
+
// ── Check abort ──
|
|
525
|
+
if (abort.signal.aborted) {
|
|
526
|
+
log.info(`Agent loop cancelled for session ${sessionId} — stopping before round ${round}`);
|
|
527
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round, aborted: true, hitMaxRounds: false };
|
|
528
|
+
}
|
|
529
|
+
// ── Apply steering messages ──
|
|
530
|
+
if (steering) {
|
|
531
|
+
const steered = steering.drain();
|
|
532
|
+
for (const msg of steered) {
|
|
533
|
+
history.push({ role: "system", content: `[STEERING] ${msg}` });
|
|
534
|
+
onEvent?.({ type: "steering", message: msg });
|
|
535
|
+
log.info(`Steering injected for session ${sessionId}: ${msg.slice(0, 100)}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// ── Periodic reminder (every 4 rounds) to reinforce good agent behaviour ──
|
|
539
|
+
if (round > 0 && round % 4 === 0 && mode !== "ask") {
|
|
540
|
+
const modelEndpoint = { model: llm.openaiModel, baseUrl: "" };
|
|
541
|
+
const reminder = getReminderInstructions(mode, modelEndpoint);
|
|
542
|
+
if (reminder) {
|
|
543
|
+
history.push({ role: "system", content: reminder });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// ── Context budget tracking & pruning ──────────────────────────────
|
|
547
|
+
const contextWindow = llm.contextWindow;
|
|
548
|
+
if (contextWindow > 0) {
|
|
549
|
+
let usage = computeContextUsage(history, activeSchemas, contextWindow);
|
|
550
|
+
onEvent?.({ type: "context_usage", ...usage });
|
|
551
|
+
// If context usage ≥ 85%, prune oldest conversation turns
|
|
552
|
+
if (usage.ratio >= 0.85) {
|
|
553
|
+
const pruned = pruneHistory(history, contextWindow, activeSchemas);
|
|
554
|
+
if (pruned) {
|
|
555
|
+
usage = computeContextUsage(history, activeSchemas, contextWindow);
|
|
556
|
+
onEvent?.({ type: "context_usage", ...usage, pruned: true });
|
|
557
|
+
log.info(`Context pruned: ${usage.total}/${contextWindow} tokens (${(usage.ratio * 100).toFixed(0)}%)`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// ── LLM request ──
|
|
562
|
+
const reqBody = {
|
|
563
|
+
model: llm.openaiModel,
|
|
564
|
+
messages: serializeMessages(history),
|
|
565
|
+
stream: true,
|
|
566
|
+
};
|
|
567
|
+
if (hasTools) {
|
|
568
|
+
reqBody.tools = activeSchemas;
|
|
569
|
+
reqBody.tool_choice = "auto";
|
|
570
|
+
}
|
|
571
|
+
let contentText = "";
|
|
572
|
+
let toolCalls = [];
|
|
573
|
+
let finishReason = null;
|
|
574
|
+
try {
|
|
575
|
+
const response = await fetch(`${llm.openaiBaseUrl}/chat/completions`, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
headers: {
|
|
578
|
+
"Content-Type": "application/json",
|
|
579
|
+
Authorization: `Bearer ${llm.openaiApiKey}`,
|
|
580
|
+
},
|
|
581
|
+
body: JSON.stringify(reqBody),
|
|
582
|
+
signal: abort.signal,
|
|
583
|
+
});
|
|
584
|
+
if (!response.ok) {
|
|
585
|
+
const errText = await response.text();
|
|
586
|
+
log.error(`LLM error ${response.status}: ${errText}`);
|
|
587
|
+
const friendlyMessage = formatLLMError(response.status, errText);
|
|
588
|
+
onEvent?.({ type: "error", message: friendlyMessage });
|
|
589
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round + 1, aborted: false, hitMaxRounds: false };
|
|
590
|
+
}
|
|
591
|
+
const reader = response.body?.getReader();
|
|
592
|
+
if (!reader) {
|
|
593
|
+
onEvent?.({ type: "error", message: "No response body from LLM" });
|
|
594
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round + 1, aborted: false, hitMaxRounds: false };
|
|
595
|
+
}
|
|
596
|
+
const parsed = await parseOpenAIStream(reader, onEvent);
|
|
597
|
+
contentText = parsed.contentText;
|
|
598
|
+
toolCalls = parsed.toolCalls;
|
|
599
|
+
finishReason = parsed.finishReason;
|
|
600
|
+
}
|
|
601
|
+
catch (fetchErr) {
|
|
602
|
+
if (abort.signal.aborted) {
|
|
603
|
+
log.info(`Agent loop cancelled during LLM streaming (round ${round})`);
|
|
604
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round + 1, aborted: true, hitMaxRounds: false };
|
|
605
|
+
}
|
|
606
|
+
throw fetchErr;
|
|
607
|
+
}
|
|
608
|
+
fullContent += contentText;
|
|
609
|
+
// ── Track segments for interleaved rendering ──
|
|
610
|
+
if (contentText) {
|
|
611
|
+
const last = segments[segments.length - 1];
|
|
612
|
+
if (last?.type === "text") {
|
|
613
|
+
segments[segments.length - 1] = { type: "text", content: last.content + contentText };
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
segments.push({ type: "text", content: contentText });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// ── Model returned tool calls → queue & execute ──
|
|
620
|
+
if (toolCalls.length > 0) {
|
|
621
|
+
if (finishReason && finishReason !== "tool_calls") {
|
|
622
|
+
log.warn(`LLM returned ${toolCalls.length} tool call(s) with finish_reason="${finishReason}" — executing anyway`);
|
|
623
|
+
}
|
|
624
|
+
// Push assistant message with tool_calls to history
|
|
625
|
+
history.push({
|
|
626
|
+
role: "assistant",
|
|
627
|
+
content: contentText || "",
|
|
628
|
+
tool_calls: toolCalls,
|
|
629
|
+
});
|
|
630
|
+
// Enqueue all tool calls with appropriate parallelism hints
|
|
631
|
+
for (const tc of toolCalls) {
|
|
632
|
+
const internalName = fromOpenAIName(tc.function.name);
|
|
633
|
+
queue.enqueue(tc, ToolCallPriority.Normal, isParallelSafe(internalName));
|
|
634
|
+
}
|
|
635
|
+
// Track tool calls as a segment group for interleaved rendering
|
|
636
|
+
const callIds = toolCalls.map(tc => tc.id);
|
|
637
|
+
const lastSeg = segments[segments.length - 1];
|
|
638
|
+
if (lastSeg?.type === "toolGroup") {
|
|
639
|
+
// Extend existing group (shouldn't normally happen, but defensive)
|
|
640
|
+
const merged = new Set([...lastSeg.callIds, ...callIds]);
|
|
641
|
+
segments[segments.length - 1] = { type: "toolGroup", callIds: [...merged] };
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
segments.push({ type: "toolGroup", callIds });
|
|
645
|
+
}
|
|
646
|
+
// ── Plan-mode & Ask-mode interception ──
|
|
647
|
+
// In plan mode, mutating tools are captured as plan actions.
|
|
648
|
+
// In ask mode, any tool that slipped through is blocked.
|
|
649
|
+
if (mode === "plan" || mode === "ask") {
|
|
650
|
+
const intercepted = [];
|
|
651
|
+
const passthrough = [];
|
|
652
|
+
while (!queue.isEmpty) {
|
|
653
|
+
const batch = queue.dequeueBatch(false);
|
|
654
|
+
for (const item of batch) {
|
|
655
|
+
const name = fromOpenAIName(item.toolCall.function.name);
|
|
656
|
+
const isMutating = MUTATING_TOOLS.has(name);
|
|
657
|
+
if (mode === "ask" && !ASK_MODE_TOOLS.has(name)) {
|
|
658
|
+
// Ask mode: block non-read tools, return error to LLM
|
|
659
|
+
intercepted.push(item);
|
|
660
|
+
}
|
|
661
|
+
else if (mode === "plan" && isMutating) {
|
|
662
|
+
// Plan mode: capture mutating tools as planned actions
|
|
663
|
+
intercepted.push(item);
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
passthrough.push(item);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Handle intercepted tool calls
|
|
671
|
+
for (const item of intercepted) {
|
|
672
|
+
const tc = item.toolCall;
|
|
673
|
+
const name = fromOpenAIName(tc.function.name);
|
|
674
|
+
let args;
|
|
675
|
+
try {
|
|
676
|
+
args = JSON.parse(tc.function.arguments);
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
args = {};
|
|
680
|
+
}
|
|
681
|
+
if (mode === "ask") {
|
|
682
|
+
// Return an error to the LLM
|
|
683
|
+
const msg = `Tool "${name}" is not available in Ask mode. Only read-only tools can be used. Suggest the user switch to Agent or Plan mode for this action.`;
|
|
684
|
+
history.push({
|
|
685
|
+
role: "tool",
|
|
686
|
+
content: JSON.stringify({ ok: false, message: msg }),
|
|
687
|
+
tool_call_id: tc.id,
|
|
688
|
+
name: tc.function.name,
|
|
689
|
+
});
|
|
690
|
+
executedToolCalls.push({
|
|
691
|
+
callId: tc.id,
|
|
692
|
+
tool: name,
|
|
693
|
+
args,
|
|
694
|
+
ok: false,
|
|
695
|
+
message: msg,
|
|
696
|
+
startedAt: Date.now(),
|
|
697
|
+
completedAt: Date.now(),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Plan mode: capture as planned action
|
|
702
|
+
const action = {
|
|
703
|
+
id: tc.id,
|
|
704
|
+
tool: name,
|
|
705
|
+
args,
|
|
706
|
+
description: `${name}(${JSON.stringify(args).slice(0, 200)})`,
|
|
707
|
+
order: plannedActions.length,
|
|
708
|
+
status: "pending",
|
|
709
|
+
};
|
|
710
|
+
plannedActions.push(action);
|
|
711
|
+
onEvent?.({ type: "plan_action", action });
|
|
712
|
+
// Tell the LLM the action was captured
|
|
713
|
+
const msg = `[PLANNED] Action "${name}" has been added to the plan (step ${action.order + 1}). It will execute after user approval. Continue analyzing and propose more actions if needed.`;
|
|
714
|
+
history.push({
|
|
715
|
+
role: "tool",
|
|
716
|
+
content: JSON.stringify({ ok: true, message: msg }),
|
|
717
|
+
tool_call_id: tc.id,
|
|
718
|
+
name: tc.function.name,
|
|
719
|
+
});
|
|
720
|
+
executedToolCalls.push({
|
|
721
|
+
callId: tc.id,
|
|
722
|
+
tool: name,
|
|
723
|
+
args,
|
|
724
|
+
ok: true,
|
|
725
|
+
message: msg,
|
|
726
|
+
startedAt: Date.now(),
|
|
727
|
+
completedAt: Date.now(),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Re-enqueue passthrough items
|
|
732
|
+
for (const item of passthrough) {
|
|
733
|
+
queue.enqueue(item.toolCall, item.priority, item.parallelSafe);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// ── Process the queue ──
|
|
737
|
+
while (!queue.isEmpty) {
|
|
738
|
+
if (abort.signal.aborted) {
|
|
739
|
+
// Mark remaining queued calls as cancelled
|
|
740
|
+
while (!queue.isEmpty) {
|
|
741
|
+
const batch = queue.dequeueBatch(false);
|
|
742
|
+
for (const item of batch) {
|
|
743
|
+
let rArgs;
|
|
744
|
+
try {
|
|
745
|
+
rArgs = JSON.parse(item.toolCall.function.arguments);
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
rArgs = {};
|
|
749
|
+
}
|
|
750
|
+
executedToolCalls.push({
|
|
751
|
+
callId: item.toolCall.id,
|
|
752
|
+
tool: fromOpenAIName(item.toolCall.function.name),
|
|
753
|
+
args: rArgs,
|
|
754
|
+
ok: false,
|
|
755
|
+
message: "Cancelled",
|
|
756
|
+
startedAt: Date.now(),
|
|
757
|
+
completedAt: Date.now(),
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round + 1, aborted: true, hitMaxRounds: false };
|
|
762
|
+
}
|
|
763
|
+
const batch = queue.dequeueBatch(parallel);
|
|
764
|
+
if (batch.length === 1) {
|
|
765
|
+
// Sequential execution (single item or non-parallel-safe)
|
|
766
|
+
const item = batch[0];
|
|
767
|
+
const { executed, historyEntry } = await executeOneToolCall({
|
|
768
|
+
tc: item.toolCall,
|
|
769
|
+
sessionId,
|
|
770
|
+
auth,
|
|
771
|
+
signal: abort.signal,
|
|
772
|
+
toolRegistry,
|
|
773
|
+
maxRetries,
|
|
774
|
+
onEvent,
|
|
775
|
+
executeTool,
|
|
776
|
+
});
|
|
777
|
+
executedToolCalls.push(executed);
|
|
778
|
+
history.push(historyEntry);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
// ── Parallel execution ──
|
|
782
|
+
log.info(`Executing ${batch.length} tool calls in parallel`);
|
|
783
|
+
const results = await Promise.all(batch.map((item) => executeOneToolCall({
|
|
784
|
+
tc: item.toolCall,
|
|
785
|
+
sessionId,
|
|
786
|
+
auth,
|
|
787
|
+
signal: abort.signal,
|
|
788
|
+
toolRegistry,
|
|
789
|
+
maxRetries,
|
|
790
|
+
onEvent,
|
|
791
|
+
executeTool,
|
|
792
|
+
})));
|
|
793
|
+
for (const { executed, historyEntry } of results) {
|
|
794
|
+
executedToolCalls.push(executed);
|
|
795
|
+
history.push(historyEntry);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// ── Dynamic schema expansion ──
|
|
800
|
+
// If any tool call was tools.search/tools.list, check if the result
|
|
801
|
+
// contains new tool schemas that should be injected for subsequent rounds.
|
|
802
|
+
for (const exec of executedToolCalls) {
|
|
803
|
+
if (exec.tool === "tools.search" && exec.ok && exec.data) {
|
|
804
|
+
const data = exec.data;
|
|
805
|
+
if (Array.isArray(data.matches)) {
|
|
806
|
+
for (const match of data.matches) {
|
|
807
|
+
if (match.name && match.description && match.parameters) {
|
|
808
|
+
const oaiName = toOpenAIName(match.name);
|
|
809
|
+
// In ask mode, only add read-only tools
|
|
810
|
+
if (mode === "ask" && !ASK_MODE_TOOLS.has(match.name))
|
|
811
|
+
continue;
|
|
812
|
+
if (!activeSchemaNames.has(oaiName) && !disabledTools?.has(match.name)) {
|
|
813
|
+
activeSchemas.push({
|
|
814
|
+
type: "function",
|
|
815
|
+
function: {
|
|
816
|
+
name: oaiName,
|
|
817
|
+
description: match.description,
|
|
818
|
+
parameters: match.parameters,
|
|
819
|
+
},
|
|
820
|
+
});
|
|
821
|
+
activeSchemaNames.add(oaiName);
|
|
822
|
+
log.info(`Dynamic schema expansion: added ${match.name}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Loop continues — LLM sees results and decides next
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
// ── Normal text response — done ──
|
|
833
|
+
if (contentText) {
|
|
834
|
+
history.push({ role: "assistant", content: contentText });
|
|
835
|
+
const tcJson = executedToolCalls.length > 0 ? JSON.stringify(executedToolCalls) : undefined;
|
|
836
|
+
const segJson = segments.length > 0 ? JSON.stringify(segments) : undefined;
|
|
837
|
+
onPersist?.(sessionId, "assistant", contentText, tcJson, segJson);
|
|
838
|
+
}
|
|
839
|
+
// ── Emit plan completion in plan mode ──
|
|
840
|
+
if (mode === "plan" && plannedActions.length > 0) {
|
|
841
|
+
onEvent?.({
|
|
842
|
+
type: "plan_complete",
|
|
843
|
+
planId,
|
|
844
|
+
summary: contentText || "Plan ready for review.",
|
|
845
|
+
actions: plannedActions,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
const planResult = mode === "plan" && plannedActions.length > 0
|
|
849
|
+
? { id: planId, summary: contentText || "Plan ready for review.", actions: plannedActions }
|
|
850
|
+
: undefined;
|
|
851
|
+
return { content: fullContent, executedToolCalls, segments, rounds: round + 1, aborted: false, hitMaxRounds: false, plan: planResult };
|
|
852
|
+
}
|
|
853
|
+
// Hit max rounds
|
|
854
|
+
log.warn(`Agent loop hit max rounds (${maxRounds}) for session ${sessionId}`);
|
|
855
|
+
const msg = "\n\n[Reached maximum tool execution rounds. Stopping.]";
|
|
856
|
+
onEvent?.({ type: "token", content: msg });
|
|
857
|
+
fullContent += msg;
|
|
858
|
+
const planResultMaxRounds = mode === "plan" && plannedActions.length > 0
|
|
859
|
+
? { id: planId, summary: fullContent, actions: plannedActions }
|
|
860
|
+
: undefined;
|
|
861
|
+
return { content: fullContent, executedToolCalls, segments, rounds: maxRounds, aborted: false, hitMaxRounds: true, plan: planResultMaxRounds };
|
|
862
|
+
}
|
|
863
|
+
// ── Retry API ────────────────────────────────────────────────────────
|
|
864
|
+
/**
|
|
865
|
+
* Retry a specific failed tool call by its callId.
|
|
866
|
+
*
|
|
867
|
+
* This re-executes the tool with its original arguments, updates the
|
|
868
|
+
* conversation history in-place (replaces the old tool result message),
|
|
869
|
+
* and returns the new result.
|
|
870
|
+
*
|
|
871
|
+
* Designed to be called from a REST endpoint like:
|
|
872
|
+
* POST /api/sessions/:sessionId/retry-tool
|
|
873
|
+
* { callId: "call_abc123" }
|
|
874
|
+
*/
|
|
875
|
+
export async function retryToolCall(callId, history, executedToolCalls, executeTool, sessionId, auth, onEvent, signal) {
|
|
876
|
+
// Find the original call
|
|
877
|
+
const original = executedToolCalls.find((tc) => tc.callId === callId);
|
|
878
|
+
if (!original) {
|
|
879
|
+
return { ok: false, message: `Tool call ${callId} not found` };
|
|
880
|
+
}
|
|
881
|
+
// Find and update history entry
|
|
882
|
+
const histIdx = history.findIndex((m) => m.role === "tool" && m.tool_call_id === callId);
|
|
883
|
+
const startedAt = Date.now();
|
|
884
|
+
onEvent?.({
|
|
885
|
+
type: "tool_start",
|
|
886
|
+
tool: original.tool,
|
|
887
|
+
args: original.args,
|
|
888
|
+
call_id: callId,
|
|
889
|
+
});
|
|
890
|
+
const result = await executeTool(original.tool, original.args, sessionId, auth, (chunk) => onEvent?.({ type: "tool_output", call_id: callId, content: chunk }), signal);
|
|
891
|
+
const completedAt = Date.now();
|
|
892
|
+
onEvent?.({
|
|
893
|
+
type: "tool_result",
|
|
894
|
+
call_id: callId,
|
|
895
|
+
tool: original.tool,
|
|
896
|
+
ok: result.ok,
|
|
897
|
+
message: result.message,
|
|
898
|
+
data: result.data,
|
|
899
|
+
});
|
|
900
|
+
// Update the executed tool call record
|
|
901
|
+
original.ok = result.ok;
|
|
902
|
+
original.message = result.message;
|
|
903
|
+
original.data = result.data;
|
|
904
|
+
original.startedAt = startedAt;
|
|
905
|
+
original.completedAt = completedAt;
|
|
906
|
+
original.retryCount = (original.retryCount ?? 0) + 1;
|
|
907
|
+
// Update conversation history so the LLM sees the new result
|
|
908
|
+
if (histIdx !== -1) {
|
|
909
|
+
history[histIdx] = {
|
|
910
|
+
role: "tool",
|
|
911
|
+
content: JSON.stringify({ ok: result.ok, message: result.message, data: result.data }),
|
|
912
|
+
tool_call_id: callId,
|
|
913
|
+
name: toOpenAIName(original.tool),
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
918
|
+
//# sourceMappingURL=agent-loop.js.map
|