Skip to content

Commit 64f851c

Browse files
committed
Merge branch 'main' into static-context-ide-readfile
2 parents 040e758 + ab214e3 commit 64f851c

22 files changed

+147
-468
lines changed

core/config/migrateSharedConfig.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,6 @@ export function migrateJsonSharedConfig(filepath: string, ide: IDE): void {
173173
effected = true;
174174
}
175175

176-
const { autoAcceptEditToolDiffs, ...withoutAutoApply } = migratedUI;
177-
if (autoAcceptEditToolDiffs !== undefined) {
178-
shareConfigUpdates.autoAcceptEditToolDiffs = autoAcceptEditToolDiffs;
179-
migratedUI = withoutAutoApply;
180-
effected = true;
181-
}
182-
183176
const { showChatScrollbar, ...withoutShowChatScrollbar } = migratedUI;
184177
if (showChatScrollbar !== undefined) {
185178
shareConfigUpdates.showChatScrollbar = showChatScrollbar;

core/config/sharedConfig.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export const sharedConfigSchema = z
3131
codeWrap: z.boolean(),
3232
displayRawMarkdown: z.boolean(),
3333
showChatScrollbar: z.boolean(),
34-
autoAcceptEditToolDiffs: z.boolean(),
3534
continueAfterToolRejection: z.boolean(),
3635

3736
// `tabAutocompleteOptions` in `ContinueConfig`
@@ -140,10 +139,6 @@ export function modifyAnyConfigWithSharedConfig<
140139
if (sharedConfig.showChatScrollbar !== undefined) {
141140
configCopy.ui.showChatScrollbar = sharedConfig.showChatScrollbar;
142141
}
143-
if (sharedConfig.autoAcceptEditToolDiffs !== undefined) {
144-
configCopy.ui.autoAcceptEditToolDiffs =
145-
sharedConfig.autoAcceptEditToolDiffs;
146-
}
147142

148143
if (sharedConfig.allowAnonymousTelemetry !== undefined) {
149144
configCopy.allowAnonymousTelemetry = sharedConfig.allowAnonymousTelemetry;

core/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,7 +1404,6 @@ export interface ContinueUIConfig {
14041404
showChatScrollbar?: boolean;
14051405
codeWrap?: boolean;
14061406
showSessionTabs?: boolean;
1407-
autoAcceptEditToolDiffs?: boolean;
14081407
continueAfterToolRejection?: boolean;
14091408
}
14101409

extensions/cli/src/stream/handleToolCalls.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,26 +123,18 @@ export async function handleToolCalls(
123123
return true; // Signal early return needed
124124
}
125125

126-
// Convert tool results and add them to the chat history with per-result status
126+
// Convert tool results and add them to the chat history with status from execution
127127
toolResults.forEach((toolResult) => {
128128
const resultContent =
129129
typeof toolResult.content === "string" ? toolResult.content : "";
130130

131-
// Derive per-result status instead of applying batch-wide hasRejection
132-
let status: ToolStatus = "done";
133-
const lower = resultContent.toLowerCase();
134-
if (
135-
lower.includes("permission denied by user") ||
136-
lower.includes("cancelled due to previous tool rejection") ||
137-
lower.includes("canceled due to previous tool rejection")
138-
) {
139-
status = "canceled";
140-
} else if (
141-
lower.startsWith("error executing tool") ||
142-
lower.startsWith("error:")
143-
) {
144-
status = "errored" as ToolStatus;
145-
}
131+
// Use the status from the tool execution result instead of text matching
132+
const status = toolResult.status;
133+
134+
logger.debug("Tool result status", {
135+
status,
136+
toolCallId: toolResult.tool_call_id,
137+
});
146138

147139
if (useService) {
148140
chatHistorySvc.addToolResult(

extensions/cli/src/stream/streamChatResponse.helpers.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Helper functions extracted from streamChatResponse.ts to reduce file size
22

3+
import type { ToolStatus } from "core/index.js";
34
import { ContinueError, ContinueErrorReason } from "core/util/errors.js";
45
import { ChatCompletionToolMessageParam } from "openai/resources/chat/completions.mjs";
56

@@ -27,6 +28,10 @@ import { logger } from "../util/logger.js";
2728

2829
import { StreamCallbacks } from "./streamChatResponse.types.js";
2930

31+
export interface ToolResultWithStatus extends ChatCompletionToolMessageParam {
32+
status: ToolStatus;
33+
}
34+
3035
// Helper function to handle permission denied
3136
export function handlePermissionDenied(
3237
toolCall: PreprocessedToolCall,
@@ -389,15 +394,15 @@ export async function preprocessStreamedToolCalls(
389394
* Executes preprocessed tool calls, handling permissions and results
390395
* @param preprocessedCalls - The preprocessed tool calls ready for execution
391396
* @param callbacks - Optional callbacks for notifying of events
392-
* @returns - Chat history entries with tool results
397+
* @returns - Chat history entries with tool results and status information
393398
*/
394399
export async function executeStreamedToolCalls(
395400
preprocessedCalls: PreprocessedToolCall[],
396401
callbacks?: StreamCallbacks,
397402
isHeadless?: boolean,
398403
): Promise<{
399404
hasRejection: boolean;
400-
chatHistoryEntries: ChatCompletionToolMessageParam[];
405+
chatHistoryEntries: ToolResultWithStatus[];
401406
}> {
402407
// Strategy: queue permissions (preserve order), then run approved tools in parallel.
403408
// If any permission is rejected, cancel the remaining tools in this batch.
@@ -408,7 +413,7 @@ export async function executeStreamedToolCalls(
408413
call,
409414
}));
410415

411-
const entriesByIndex = new Map<number, ChatCompletionToolMessageParam>();
416+
const entriesByIndex = new Map<number, ToolResultWithStatus>();
412417
const execPromises: Promise<void>[] = [];
413418

414419
let hasRejection = false;
@@ -439,17 +444,18 @@ export async function executeStreamedToolCalls(
439444
);
440445

441446
if (!permissionResult.approved) {
442-
// Permission denied: record and mark rejection
447+
// Permission denied: create entry with canceled status
443448
const denialReason = permissionResult.denialReason || "user";
444449
const deniedMessage =
445450
denialReason === "policy"
446451
? `Command blocked by security policy`
447452
: `Permission denied by user`;
448453

449-
const deniedEntry: ChatCompletionToolMessageParam = {
454+
const deniedEntry: ToolResultWithStatus = {
450455
role: "tool",
451456
tool_call_id: call.id,
452457
content: deniedMessage,
458+
status: "canceled",
453459
};
454460
entriesByIndex.set(index, deniedEntry);
455461
callbacks?.onToolResult?.(
@@ -484,10 +490,11 @@ export async function executeStreamedToolCalls(
484490
arguments: call.arguments,
485491
});
486492
const toolResult = await executeToolCall(call);
487-
const entry: ChatCompletionToolMessageParam = {
493+
const entry: ToolResultWithStatus = {
488494
role: "tool",
489495
tool_call_id: call.id,
490496
content: toolResult,
497+
status: "done",
491498
};
492499
entriesByIndex.set(index, entry);
493500
callbacks?.onToolResult?.(toolResult, call.name, "done");
@@ -511,6 +518,7 @@ export async function executeStreamedToolCalls(
511518
role: "tool",
512519
tool_call_id: call.id,
513520
content: errorMessage,
521+
status: "errored",
514522
});
515523
callbacks?.onToolError?.(errorMessage, call.name);
516524
// Immediate service update for UI feedback
@@ -536,6 +544,7 @@ export async function executeStreamedToolCalls(
536544
role: "tool",
537545
tool_call_id: call.id,
538546
content: errorMessage,
547+
status: "errored",
539548
});
540549
callbacks?.onToolError?.(errorMessage, call.name);
541550
// Treat permission errors like execution errors but do not stop the batch
@@ -552,9 +561,9 @@ export async function executeStreamedToolCalls(
552561
await Promise.all(execPromises);
553562

554563
// Assemble final entries in original order
555-
const chatHistoryEntries: ChatCompletionToolMessageParam[] = preprocessedCalls
564+
const chatHistoryEntries: ToolResultWithStatus[] = preprocessedCalls
556565
.map((_, index) => entriesByIndex.get(index))
557-
.filter((e): e is ChatCompletionToolMessageParam => !!e);
566+
.filter((e): e is ToolResultWithStatus => !!e);
558567

559568
return {
560569
hasRejection,

extensions/cli/src/tools/fetch.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,21 @@ describe("fetchTool", () => {
9696
expect(result).toBe("Content from page 1\n\nContent from page 2");
9797
});
9898

99-
it("should return error message when no content items returned", async () => {
99+
it("should throw error when no content items returned", async () => {
100100
mockFetchUrlContentImpl.mockResolvedValue([]);
101101

102-
const result = await fetchTool.run({ url: "https://example.com" });
103-
104-
expect(result).toBe(
105-
"Error: Could not fetch content from https://example.com",
102+
await expect(fetchTool.run({ url: "https://example.com" })).rejects.toThrow(
103+
"Could not fetch content from https://example.com",
106104
);
107105
});
108106

109-
it("should handle errors from core implementation", async () => {
107+
it("should throw errors from core implementation", async () => {
110108
const error = new Error("Network error");
111109
mockFetchUrlContentImpl.mockRejectedValue(error);
112110

113-
const result = await fetchTool.run({ url: "https://example.com" });
114-
115-
expect(result).toBe("Error: Network error");
111+
await expect(fetchTool.run({ url: "https://example.com" })).rejects.toThrow(
112+
"Error: Network error",
113+
);
116114
});
117115

118116
it("should call fetchUrlContentImpl with correct arguments", async () => {

extensions/cli/src/tools/fetch.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ContextItem } from "core/index.js";
22
import { fetchUrlContentImpl } from "core/tools/implementations/fetchUrlContent.js";
3+
import { ContinueError, ContinueErrorReason } from "core/util/errors.js";
34

45
import { Tool } from "./types.js";
56

@@ -46,7 +47,10 @@ export const fetchTool: Tool = {
4647
console.error = originalConsoleError;
4748

4849
if (contextItems.length === 0) {
49-
return `Error: Could not fetch content from ${url}`;
50+
throw new ContinueError(
51+
ContinueErrorReason.Unspecified,
52+
`Could not fetch content from ${url}`,
53+
);
5054
}
5155

5256
// Format the results for CLI display
@@ -59,7 +63,12 @@ export const fetchTool: Tool = {
5963
})
6064
.join("\n\n");
6165
} catch (error) {
62-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
66+
if (error instanceof ContinueError) {
67+
throw error;
68+
}
69+
throw new Error(
70+
`Error: ${error instanceof Error ? error.message : String(error)}`,
71+
);
6372
}
6473
},
6574
};

extensions/cli/src/tools/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export async function executeToolCall(
228228
errorReason,
229229
});
230230

231-
return `Error executing tool "${toolCall.name}": ${errorMessage}`;
231+
throw error;
232232
}
233233
}
234234

extensions/cli/src/tools/listFiles.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ export const listFilesTool: Tool = {
6161

6262
return `Files in ${args.dirpath}:\n${fileDetails.join("\n")}`;
6363
} catch (error) {
64-
return `Error listing files: ${
65-
error instanceof Error ? error.message : String(error)
66-
}`;
64+
throw new Error(
65+
`Error listing files: ${
66+
error instanceof Error ? error.message : String(error)
67+
}`,
68+
);
6769
}
6870
},
6971
};

extensions/cli/src/tools/readFile.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from "fs";
22

33
import { throwIfFileIsSecurityConcern } from "core/indexing/ignore.js";
4+
import { ContinueError, ContinueErrorReason } from "core/util/errors.js";
45

56
import { formatToolArgument } from "./formatters.js";
67
import { Tool } from "./types.js";
@@ -51,7 +52,10 @@ export const readFileTool: Tool = {
5152
}
5253

5354
if (!fs.existsSync(filepath)) {
54-
return `Error: File does not exist: ${filepath}`;
55+
throw new ContinueError(
56+
ContinueErrorReason.Unspecified,
57+
`File does not exist: ${filepath}`,
58+
);
5559
}
5660
const realPath = fs.realpathSync(filepath);
5761
const content = fs.readFileSync(realPath, "utf-8");
@@ -66,9 +70,14 @@ export const readFileTool: Tool = {
6670

6771
return `Content of ${filepath}:\n${content}`;
6872
} catch (error) {
69-
return `Error reading file: ${
70-
error instanceof Error ? error.message : String(error)
71-
}`;
73+
if (error instanceof ContinueError) {
74+
throw error;
75+
}
76+
throw new Error(
77+
`Error reading file: ${
78+
error instanceof Error ? error.message : String(error)
79+
}`,
80+
);
7281
}
7382
},
7483
};

0 commit comments

Comments
 (0)