Skip to content

Commit 9077711

Browse files
committed
Refactor: Use tool wrapper for init wait instead of per-tool calls
- Created wrapWithInitWait() wrapper to centralize init waiting - Removed waitForWorkspaceInit() calls from bash, file_read, file_edit_insert, file_edit_operation - Deleted toolHelpers.ts (no longer needed) - Updated tools.ts to wrap runtime tools (bash, file_read, file_edit_*) - Non-runtime tools (propose_plan, todo, web_search) unchanged - Clearer separation: explicit list of which tools wait for init - Net: -36 lines of code, better architecture
1 parent 7d4be68 commit 9077711

File tree

8 files changed

+58
-37
lines changed

8 files changed

+58
-37
lines changed

src/services/tools/bash.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "@/constants/exitCodes";
1616
import type { BashToolResult } from "@/types/tools";
1717
import type { ToolConfiguration, ToolFactory } from "@/utils/tools/tools";
1818
import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions";
19-
import { waitForWorkspaceInit } from "./toolHelpers";
2019

2120
/**
2221
* Bash execution tool factory for AI assistant
@@ -39,9 +38,6 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
3938
description: TOOL_DEFINITIONS.bash.description + "\nRuns in " + config.cwd + " - no cd needed",
4039
inputSchema: TOOL_DEFINITIONS.bash.schema,
4140
execute: async ({ script, timeout_secs }, { abortSignal }): Promise<BashToolResult> => {
42-
// Wait for workspace initialization to complete (no-op if already complete or not needed)
43-
await waitForWorkspaceInit(config);
44-
4541
// Validate script is not empty - likely indicates a malformed tool call
4642

4743
if (!script || script.trim().length === 0) {

src/services/tools/file_edit_insert.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { executeFileEditOperation } from "./file_edit_operation";
88
import { RuntimeError } from "@/runtime/Runtime";
99
import { fileExists } from "@/utils/runtime/fileExists";
1010
import { writeFileString } from "@/utils/runtime/helpers";
11-
import { waitForWorkspaceInit } from "./toolHelpers";
1211

1312
/**
1413
* File edit insert tool factory for AI assistant
@@ -25,9 +24,6 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
2524
content,
2625
create,
2726
}): Promise<FileEditInsertToolResult> => {
28-
// Wait for workspace initialization to complete (no-op if already complete or not needed)
29-
await waitForWorkspaceInit(config);
30-
3127
try {
3228
// Validate no redundant path prefix (must come first to catch absolute paths)
3329
const redundantPrefixValidation = validateNoRedundantPrefix(

src/services/tools/file_edit_operation.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
} from "./fileCommon";
1010
import { RuntimeError } from "@/runtime/Runtime";
1111
import { readFileString, writeFileString } from "@/utils/runtime/helpers";
12-
import { waitForWorkspaceInit } from "./toolHelpers";
1312

1413
type FileEditOperationResult<TMetadata> =
1514
| {
@@ -41,9 +40,6 @@ export async function executeFileEditOperation<TMetadata>({
4140
}: ExecuteFileEditOperationOptions<TMetadata>): Promise<
4241
FileEditErrorResult | (FileEditDiffSuccessBase & TMetadata)
4342
> {
44-
// Wait for workspace initialization to complete (no-op if already complete or not needed)
45-
await waitForWorkspaceInit(config);
46-
4743
try {
4844
// Validate no redundant path prefix (must come first to catch absolute paths)
4945
const redundantPrefixValidation = validateNoRedundantPrefix(

src/services/tools/file_read.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions";
55
import { validatePathInCwd, validateFileSize, validateNoRedundantPrefix } from "./fileCommon";
66
import { RuntimeError } from "@/runtime/Runtime";
77
import { readFileString } from "@/utils/runtime/helpers";
8-
import { waitForWorkspaceInit } from "./toolHelpers";
98

109
/**
1110
* File read tool factory for AI assistant
@@ -22,9 +21,6 @@ export const createFileReadTool: ToolFactory = (config: ToolConfiguration) => {
2221
): Promise<FileReadToolResult> => {
2322
// Note: abortSignal available but not used - file reads are fast and complete quickly
2423

25-
// Wait for workspace initialization to complete (no-op if already complete or not needed)
26-
await waitForWorkspaceInit(config);
27-
2824
try {
2925
// Validate no redundant path prefix (must come first to catch absolute paths)
3026
const redundantPrefixValidation = validateNoRedundantPrefix(

src/services/tools/toolHelpers.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Tool } from "ai";
2+
import type { ToolConfiguration } from "@/utils/tools/tools";
3+
4+
/**
5+
* Wraps a tool to wait for workspace initialization before execution.
6+
*
7+
* This wrapper handles the cross-cutting concern of init state waiting,
8+
* keeping individual tools simple and focused on their core functionality.
9+
*
10+
* Only runtime-dependent tools (bash, file_read, file_edit_*) need this wrapper.
11+
* Non-runtime tools (propose_plan, todo, web_search) execute immediately.
12+
*
13+
* @param tool The tool to wrap (returned from a tool factory)
14+
* @param config Tool configuration containing initStateManager
15+
* @returns Wrapped tool that waits for init before executing
16+
*/
17+
export function wrapWithInitWait<TParameters, TResult>(
18+
tool: Tool<TParameters, TResult>,
19+
config: ToolConfiguration
20+
): Tool<TParameters, TResult> {
21+
return {
22+
...tool,
23+
execute: async (args: TParameters, options) => {
24+
// Wait for workspace initialization to complete (no-op if not needed)
25+
// This never throws - tools proceed regardless of init outcome
26+
await config.initStateManager.waitForInit(config.workspaceId);
27+
28+
// Execute the actual tool with all arguments
29+
if (!tool.execute) {
30+
throw new Error("Tool does not have an execute function");
31+
}
32+
return tool.execute(args, options);
33+
},
34+
} as Tool<TParameters, TResult>;
35+
}
36+

src/utils/tools/tools.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createFileEditReplaceStringTool } from "@/services/tools/file_edit_repl
66
import { createFileEditInsertTool } from "@/services/tools/file_edit_insert";
77
import { createProposePlanTool } from "@/services/tools/propose_plan";
88
import { createTodoWriteTool, createTodoReadTool } from "@/services/tools/todo";
9+
import { wrapWithInitWait } from "@/services/tools/wrapWithInitWait";
910
import { log } from "@/services/log";
1011

1112
import type { Runtime } from "@/runtime/Runtime";
@@ -54,22 +55,32 @@ export async function getToolsForModel(
5455
): Promise<Record<string, Tool>> {
5556
const [provider, modelId] = modelString.split(":");
5657

57-
// Base tools available for all models
58-
const baseTools: Record<string, Tool> = {
59-
// Use snake_case for tool names to match what seems to be the convention.
60-
file_read: createFileReadTool(config),
61-
file_edit_replace_string: createFileEditReplaceStringTool(config),
58+
// Runtime-dependent tools need to wait for workspace initialization
59+
// Wrap them to handle init waiting centrally instead of in each tool
60+
const runtimeTools: Record<string, Tool> = {
61+
file_read: wrapWithInitWait(createFileReadTool(config), config),
62+
file_edit_replace_string: wrapWithInitWait(createFileEditReplaceStringTool(config), config),
6263
// DISABLED: file_edit_replace_lines - causes models (particularly GPT-5-Codex)
6364
// to leave repository in broken state due to issues with concurrent file modifications
6465
// and line number miscalculations. Use file_edit_replace_string or file_edit_insert instead.
65-
// file_edit_replace_lines: createFileEditReplaceLinesTool(config),
66-
file_edit_insert: createFileEditInsertTool(config),
67-
bash: createBashTool(config),
66+
// file_edit_replace_lines: wrapWithInitWait(createFileEditReplaceLinesTool(config), config),
67+
file_edit_insert: wrapWithInitWait(createFileEditInsertTool(config), config),
68+
bash: wrapWithInitWait(createBashTool(config), config),
69+
};
70+
71+
// Non-runtime tools execute immediately (no init wait needed)
72+
const nonRuntimeTools: Record<string, Tool> = {
6873
propose_plan: createProposePlanTool(config),
6974
todo_write: createTodoWriteTool(config),
7075
todo_read: createTodoReadTool(config),
7176
};
7277

78+
// Base tools available for all models
79+
const baseTools: Record<string, Tool> = {
80+
...runtimeTools,
81+
...nonRuntimeTools,
82+
};
83+
7384
// Try to add provider-specific web search tools if available
7485
// Lazy-load providers to avoid loading all AI SDKs at startup
7586
try {

tests/ipcMain/helpers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,9 @@ export function collectInitEvents(
565565
return env.sentEvents
566566
.filter((e) => e.channel === getChatChannel(workspaceId))
567567
.map((e) => e.data as WorkspaceChatMessage)
568-
.filter((msg) => isInitStart(msg) || isInitOutput(msg) || isInitEnd(msg)) as WorkspaceInitEvent[];
568+
.filter(
569+
(msg) => isInitStart(msg) || isInitOutput(msg) || isInitEnd(msg)
570+
) as WorkspaceInitEvent[];
569571
}
570572

571573
/**

0 commit comments

Comments
 (0)