Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion core/protocol/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,11 @@ export type ToCoreFromIdeOrWebviewProtocol = {
"auth/getAuthUrl": [{ useOnboarding: boolean }, { url: string }];
"tools/call": [
{ toolCall: ToolCall },
{ contextItems: ContextItem[]; errorMessage?: string },
{
contextItems: ContextItem[];
errorMessage?: string;
errorReason?: ContinueErrorReason;
},
];
"tools/evaluatePolicy": [
{ toolName: string; basePolicy: ToolPolicy; args: Record<string, unknown> },
Expand Down
11 changes: 10 additions & 1 deletion core/tools/callTool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
import { ContextItem, Tool, ToolCall, ToolExtras } from "..";
import { MCPManagerSingleton } from "../context/mcp/MCPManagerSingleton";
import { ContinueError, ContinueErrorReason } from "../util/errors";
import { canParseUrl } from "../util/url";
import { BuiltInToolNames } from "./builtIn";

Expand Down Expand Up @@ -197,6 +198,7 @@ export async function callTool(
): Promise<{
contextItems: ContextItem[];
errorMessage: string | undefined;
errorReason?: ContinueErrorReason;
}> {
try {
const args = safeParseToolCallArgs(toolCall);
Expand All @@ -214,12 +216,19 @@ export async function callTool(
};
} catch (e) {
let errorMessage = `${e}`;
if (e instanceof Error) {
let errorReason: ContinueErrorReason | undefined;

if (e instanceof ContinueError) {
errorMessage = e.message;
errorReason = e.reason;
} else if (e instanceof Error) {
errorMessage = e.message;
}

return {
contextItems: [],
errorMessage,
errorReason,
};
}
}
9 changes: 7 additions & 2 deletions core/tools/implementations/createNewFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inferResolvedUriFromRelativePath } from "../../util/ideUtils";
import { ToolImpl } from ".";
import { getCleanUriPath, getUriPathBasename } from "../../util/uri";
import { getStringArg } from "../parseArgs";
import { ContinueError, ContinueErrorReason } from "../../util/errors";

export const createNewFileImpl: ToolImpl = async (args, extras) => {
const filepath = getStringArg(args, "filepath");
Expand All @@ -15,7 +16,8 @@ export const createNewFileImpl: ToolImpl = async (args, extras) => {
if (resolvedFileUri) {
const exists = await extras.ide.fileExists(resolvedFileUri);
if (exists) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.FileAlreadyExists,
`File ${filepath} already exists. Use the edit tool to edit this file`,
);
}
Expand All @@ -37,6 +39,9 @@ export const createNewFileImpl: ToolImpl = async (args, extras) => {
},
];
} else {
throw new Error("Failed to resolve path");
throw new ContinueError(
ContinueErrorReason.PathResolutionFailed,
"Failed to resolve path",
);
}
};
6 changes: 5 additions & 1 deletion core/tools/implementations/grepSearch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ToolImpl } from ".";
import { ContextItem } from "../..";
import { ContinueError, ContinueErrorReason } from "../../util/errors";
import { formatGrepSearchResults } from "../../util/grepSearch";
import { prepareQueryForRipgrep } from "../../util/regexValidator";
import { getStringArg } from "../parseArgs";
Expand Down Expand Up @@ -63,7 +64,10 @@ export const grepSearchImpl: ToolImpl = async (args, extras) => {
];
}

throw error;
throw new ContinueError(
ContinueErrorReason.SearchExecutionFailed,
errorMessage,
);
}

const { formatted, numResults, truncated } = formatGrepSearchResults(
Expand Down
4 changes: 3 additions & 1 deletion core/tools/implementations/lsTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ignore from "ignore";
import { ToolImpl } from ".";
import { walkDir } from "../../indexing/walkDir";
import { resolveRelativePathInDir } from "../../util/ideUtils";
import { ContinueError, ContinueErrorReason } from "../../util/errors";

export function resolveLsToolDirPath(dirPath: string | undefined) {
if (!dirPath || dirPath === ".") {
Expand All @@ -20,7 +21,8 @@ export const lsToolImpl: ToolImpl = async (args, extras) => {
const dirPath = resolveLsToolDirPath(args?.dirPath);
const uri = await resolveRelativePathInDir(dirPath, extras.ide);
if (!uri) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.DirectoryNotFound,
`Directory ${args.dirPath} not found. Make sure to use forward-slash paths`,
);
}
Expand Down
4 changes: 3 additions & 1 deletion core/tools/implementations/readFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { ToolImpl } from ".";
import { throwIfFileIsSecurityConcern } from "../../indexing/ignore";
import { getStringArg } from "../parseArgs";
import { throwIfFileExceedsHalfOfContext } from "./readFileLimit";
import { ContinueError, ContinueErrorReason } from "../../util/errors";

export const readFileImpl: ToolImpl = async (args, extras) => {
const filepath = getStringArg(args, "filepath");
throwIfFileIsSecurityConcern(filepath);

const firstUriMatch = await resolveRelativePathInDir(filepath, extras.ide);
if (!firstUriMatch) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.FileNotFound,
`File "${filepath}" does not exist. You might want to check the path and try again.`,
);
}
Expand Down
4 changes: 3 additions & 1 deletion core/tools/implementations/readFileLimit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ILLM } from "../..";
import { countTokensAsync } from "../../llm/countTokens";
import { ContinueError, ContinueErrorReason } from "../../util/errors";

export async function throwIfFileExceedsHalfOfContext(
filepath: string,
Expand All @@ -10,7 +11,8 @@ export async function throwIfFileExceedsHalfOfContext(
const tokens = await countTokensAsync(content, model.title);
const tokenLimit = model.contextLength / 2;
if (tokens > tokenLimit) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.FileTooLarge,
`File ${filepath} is too large (${tokens} tokens vs ${tokenLimit} token limit). Try another approach`,
);
}
Expand Down
13 changes: 9 additions & 4 deletions core/tools/implementations/readFileRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getUriPathBasename } from "../../util/uri";
import { ToolImpl } from ".";
import { getNumberArg, getStringArg } from "../parseArgs";
import { throwIfFileExceedsHalfOfContext } from "./readFileLimit";
import { ContinueError, ContinueErrorReason } from "../../util/errors";

export const readFileRangeImpl: ToolImpl = async (args, extras) => {
const filepath = getStringArg(args, "filepath");
Expand All @@ -12,24 +13,28 @@ export const readFileRangeImpl: ToolImpl = async (args, extras) => {

// Validate that line numbers are positive integers
if (startLine < 1) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.InvalidLineNumber,
"startLine must be 1 or greater. Negative line numbers are not supported - use the terminal tool with 'tail' command for reading from file end.",
);
}
if (endLine < 1) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.InvalidLineNumber,
"endLine must be 1 or greater. Negative line numbers are not supported - use the terminal tool with 'tail' command for reading from file end.",
);
}
if (endLine < startLine) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.InvalidLineNumber,
`endLine (${endLine}) must be greater than or equal to startLine (${startLine})`,
);
}

const firstUriMatch = await resolveRelativePathInDir(filepath, extras.ide);
if (!firstUriMatch) {
throw new Error(
throw new ContinueError(
ContinueErrorReason.FileNotFound,
`File "${filepath}" does not exist. You might want to check the path and try again.`,
);
}
Expand Down
6 changes: 5 additions & 1 deletion core/tools/implementations/requestRule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ToolImpl } from ".";
import { ContinueError, ContinueErrorReason } from "../../util/errors";
import { getStringArg } from "../parseArgs";

export const requestRuleImpl: ToolImpl = async (args, extras) => {
Expand All @@ -8,7 +9,10 @@ export const requestRuleImpl: ToolImpl = async (args, extras) => {
const rule = extras.config.rules.find((r) => r.name === name);

if (!rule || !rule.sourceFile) {
throw new Error(`Rule with name "${name}" not found or has no file path`);
throw new ContinueError(
ContinueErrorReason.RuleNotFound,
`Rule with name "${name}" not found or has no file path`,
);
}

return [
Expand Down
4 changes: 3 additions & 1 deletion core/tools/implementations/runTerminalCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import iconv from "iconv-lite";
import childProcess from "node:child_process";
import os from "node:os";
import util from "node:util";
import { ContinueError, ContinueErrorReason } from "../../util/errors";
// Automatically decode the buffer according to the platform to avoid garbled Chinese
function getDecodedOutput(data: Buffer): string {
if (process.platform === "win32") {
Expand Down Expand Up @@ -337,7 +338,8 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
if (code === 0) {
resolve({ stdout, stderr });
} else {
const error = new Error(
const error = new ContinueError(
ContinueErrorReason.CommandExecutionFailed,
`Command failed with exit code ${code}`,
);
(error as any).stderr = stderr;
Expand Down
6 changes: 5 additions & 1 deletion core/tools/implementations/viewSubdirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import generateRepoMap from "../../util/generateRepoMap";
import { resolveRelativePathInDir } from "../../util/ideUtils";

import { ToolImpl } from ".";
import { ContinueError, ContinueErrorReason } from "../../util/errors";
import { getStringArg } from "../parseArgs";

export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => {
Expand All @@ -10,7 +11,10 @@ export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => {
const uri = await resolveRelativePathInDir(directory_path, extras.ide);

if (!uri) {
throw new Error(`Directory path "${directory_path}" does not exist.`);
throw new ContinueError(
ContinueErrorReason.DirectoryNotFound,
`Directory path "${directory_path}" does not exist.`,
);
}

const repoMap = await generateRepoMap(extras.llm, extras.ide, {
Expand Down
14 changes: 14 additions & 0 deletions core/util/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export enum ContinueErrorReason {
FileWriteError = "file_write_error",
FileIsSecurityConcern = "file_is_security_concern",
ParentDirectoryNotFound = "parent_directory_not_found",
FileTooLarge = "file_too_large",
PathResolutionFailed = "path_resolution_failed",
InvalidLineNumber = "invalid_line_number",
DirectoryNotFound = "directory_not_found",

// Terminal/Command execution
CommandExecutionFailed = "command_execution_failed",
CommandNotAvailableInRemote = "command_not_available_in_remote",

// Search
SearchExecutionFailed = "search_execution_failed",

// Rules
RuleNotFound = "rule_not_found",

// Other
Unspecified = "unspecified", // I.e. a known error but no specific code for it
Expand Down
3 changes: 2 additions & 1 deletion extensions/cli/src/telemetry/telemetryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "@opentelemetry/semantic-conventions";
import { v4 as uuidv4 } from "uuid";

import { ContinueErrorReason } from "../../../core/util/errors.js";
import { isHeadlessMode } from "../util/cli.js";
import { isContinueRemoteAgent, isGitHubActions } from "../util/git.js";
import { logger } from "../util/logger.js";
Expand Down Expand Up @@ -498,7 +499,7 @@ class TelemetryService {
success: boolean;
durationMs: number;
error?: string;
errorReason?: string;
errorReason?: ContinueErrorReason;
decision?: "accept" | "reject";
source?: string;
toolParameters?: string;
Expand Down
2 changes: 1 addition & 1 deletion gui/src/redux/thunks/callToolById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const callToolById = createAsyncThunk<
output = result.content.contextItems;
error = result.content.errorMessage
? new ContinueError(
ContinueErrorReason.Unspecified,
result.content.errorReason || ContinueErrorReason.Unspecified,
result.content.errorMessage,
)
: undefined;
Expand Down
Loading