diff --git a/src/hooks/session-recovery/detect-error-type.test.ts b/src/hooks/session-recovery/detect-error-type.test.ts index d20e7cc9..350ad11b 100644 --- a/src/hooks/session-recovery/detect-error-type.test.ts +++ b/src/hooks/session-recovery/detect-error-type.test.ts @@ -1,6 +1,6 @@ /// import { describe, expect, it } from "bun:test" -import { detectErrorType, extractMessageIndex } from "./detect-error-type" +import { detectErrorType, extractMessageIndex, extractUnavailableToolName } from "./detect-error-type" describe("detectErrorType", () => { it("#given a tool_use/tool_result error #when detecting #then returns tool_result_missing", () => { @@ -101,6 +101,45 @@ describe("detectErrorType", () => { //#then expect(result).toBe("tool_result_missing") }) + + it("#given a dummy_tool unavailable tool error #when detecting #then returns unavailable_tool", () => { + //#given + const error = { message: "model tried to call unavailable tool 'invalid'" } + + //#when + const result = detectErrorType(error) + + //#then + expect(result).toBe("unavailable_tool") + }) + + it("#given a no such tool error #when detecting #then returns unavailable_tool", () => { + //#given + const error = { message: "No such tool: grepppp" } + + //#when + const result = detectErrorType(error) + + //#then + expect(result).toBe("unavailable_tool") + }) + + it("#given a dummy_tool token in nested error #when detecting #then returns unavailable_tool", () => { + //#given + const error = { + data: { + error: { + message: "dummy_tool Model tried to call unavailable tool 'invalid'", + }, + }, + } + + //#when + const result = detectErrorType(error) + + //#then + expect(result).toBe("unavailable_tool") + }) }) describe("extractMessageIndex", () => { @@ -127,3 +166,27 @@ describe("extractMessageIndex", () => { expect(result).toBeNull() }) }) + +describe("extractUnavailableToolName", () => { + it("#given unavailable tool error with quoted tool name #when extracting #then returns tool name", () => { + //#given + const error = { message: "model tried to call unavailable tool 'invalid'" } + + //#when + const result = extractUnavailableToolName(error) + + //#then + expect(result).toBe("invalid") + }) + + it("#given error without unavailable tool name #when extracting #then returns null", () => { + //#given + const error = { message: "dummy_tool appeared without tool name" } + + //#when + const result = extractUnavailableToolName(error) + + //#then + expect(result).toBeNull() + }) +}) diff --git a/src/hooks/session-recovery/detect-error-type.ts b/src/hooks/session-recovery/detect-error-type.ts index 3f2f9a1c..9282a068 100644 --- a/src/hooks/session-recovery/detect-error-type.ts +++ b/src/hooks/session-recovery/detect-error-type.ts @@ -3,6 +3,7 @@ export type RecoveryErrorType = | "thinking_block_order" | "thinking_disabled_violation" | "assistant_prefill_unsupported" + | "unavailable_tool" | null function getErrorMessage(error: unknown): string { @@ -43,6 +44,16 @@ export function extractMessageIndex(error: unknown): number | null { } } +export function extractUnavailableToolName(error: unknown): string | null { + try { + const message = getErrorMessage(error) + const match = message.match(/unavailable tool ['"]?([^'".\s]+)['"]?/) + return match ? match[1] : null + } catch { + return null + } +} + export function detectErrorType(error: unknown): RecoveryErrorType { try { const message = getErrorMessage(error) @@ -74,6 +85,16 @@ export function detectErrorType(error: unknown): RecoveryErrorType { return "tool_result_missing" } + if ( + message.includes("dummy_tool") || + message.includes("unavailable tool") || + message.includes("model tried to call unavailable") || + message.includes("nosuchtoolarror") || + message.includes("no such tool") + ) { + return "unavailable_tool" + } + return null } catch { return null