fix(session-recovery): detect unavailable_tool errors

This commit is contained in:
YeonGyu-Kim
2026-02-21 02:23:14 +09:00
parent 51654c1c5e
commit 43b8884db6
2 changed files with 85 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
/// <reference types="bun-types" />
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()
})
})

View File

@@ -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