| 511 | } |
| 512 | |
| 513 | export async function getToolsForModel( |
| 514 | modelString: string, |
| 515 | config: ToolConfiguration, |
| 516 | workspaceId: string, |
| 517 | initStateManager: InitStateManager, |
| 518 | toolInstructions?: Record<string, string>, |
| 519 | mcpTools?: Record<string, Tool> |
| 520 | ): Promise<Record<string, Tool>> { |
| 521 | const [provider, modelId] = modelString.split(":"); |
| 522 | |
| 523 | // Helper to reduce repetition when wrapping runtime tools |
| 524 | const wrap = <TParameters, TResult>(tool: Tool<TParameters, TResult>) => |
| 525 | wrapWithInitWait(tool, workspaceId, initStateManager); |
| 526 | |
| 527 | // Lazy-load web_fetch to avoid loading jsdom (ESM-only) at Jest setup time |
| 528 | // This allows integration tests to run without transforming jsdom's dependencies |
| 529 | const { createWebFetchTool } = await import("@/node/services/tools/web_fetch"); |
| 530 | |
| 531 | // Runtime-dependent tools need to wait for workspace initialization |
| 532 | // Wrap them to handle init waiting centrally instead of in each tool |
| 533 | const runtimeTools: Record<string, Tool> = { |
| 534 | file_read: wrap(createFileReadTool(config)), |
| 535 | attach_file: wrap(createAttachFileTool(config)), |
| 536 | agent_skill_read: wrap(createAgentSkillReadTool(config)), |
| 537 | agent_skill_read_file: wrap(createAgentSkillReadFileTool(config)), |
| 538 | file_edit_replace_string: wrap(createFileEditReplaceStringTool(config)), |
| 539 | file_edit_insert: wrap(createFileEditInsertTool(config)), |
| 540 | // DISABLED: file_edit_replace_lines - causes models (particularly GPT-5-Codex) |
| 541 | // to leave repository in broken state due to issues with concurrent file modifications |
| 542 | // and line number miscalculations. Use file_edit_replace_string instead. |
| 543 | // file_edit_replace_lines: wrap(createFileEditReplaceLinesTool(config)), |
| 544 | |
| 545 | // Sub-agent task orchestration (child workspaces) |
| 546 | task: wrap(createTaskTool(config)), |
| 547 | task_await: wrap(createTaskAwaitTool(config)), |
| 548 | task_apply_git_patch: wrap(createTaskApplyGitPatchTool(config)), |
| 549 | task_terminate: wrap(createTaskTerminateTool(config)), |
| 550 | task_workspace_lifecycle: wrap(createTaskWorkspaceLifecycleTool(config)), |
| 551 | task_list: wrap(createTaskListTool(config)), |
| 552 | |
| 553 | // Bash execution (foreground/background). Manage background output via task_await/task_list/task_terminate. |
| 554 | bash: wrap(createBashTool(config)), |
| 555 | |
| 556 | // Legacy bash process tools (deprecated) |
| 557 | bash_output: wrap(createBashOutputTool(config)), |
| 558 | bash_background_list: wrap(createBashBackgroundListTool(config)), |
| 559 | bash_background_terminate: wrap(createBashBackgroundTerminateTool(config)), |
| 560 | |
| 561 | web_fetch: wrap(createWebFetchTool(config)), |
| 562 | |
| 563 | // Agent memory (experiment-gated; off => no tool, no context cost) |
| 564 | ...(config.memoryService && config.experiments?.memory |
| 565 | ? { memory: wrap(createMemoryTool(config)) } |
| 566 | : {}), |
| 567 | }; |
| 568 | |
| 569 | // HeartbeatService intentionally skips child task workspaces, and the |
| 570 | // workspace-heartbeats experiment gates every user-facing way to create schedules. |