2026-03-12 01:43:17 +09:00
|
|
|
declare const require: (name: string) => any
|
|
|
|
|
const { describe, expect, test } = require("bun:test")
|
2026-03-04 18:35:09 +01:00
|
|
|
|
2026-03-12 01:43:17 +09:00
|
|
|
import { classifyErrorType, extractAutoRetrySignal, isRetryableError } from "./error-classifier"
|
2026-03-04 18:35:09 +01:00
|
|
|
|
|
|
|
|
describe("runtime-fallback error classifier", () => {
|
|
|
|
|
test("detects cooling-down auto-retry status signals", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const info = {
|
|
|
|
|
status:
|
|
|
|
|
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const signal = extractAutoRetrySignal(info)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(signal).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-04 20:52:15 +01:00
|
|
|
test("detects single-word cooldown auto-retry status signals", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const info = {
|
|
|
|
|
status:
|
|
|
|
|
"All credentials for model claude-opus-4-6 are cooldown [retrying in 7m 56s attempt #1]",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const signal = extractAutoRetrySignal(info)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(signal).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-04 18:35:09 +01:00
|
|
|
test("treats cooling-down retry messages as retryable", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const error = {
|
|
|
|
|
message:
|
|
|
|
|
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const retryable = isRetryableError(error, [400, 403, 408, 429, 500, 502, 503, 504, 529])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(retryable).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-12 01:43:17 +09:00
|
|
|
test("classifies ProviderModelNotFoundError as model_not_found", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const error = {
|
|
|
|
|
name: "ProviderModelNotFoundError",
|
|
|
|
|
data: {
|
|
|
|
|
providerID: "anthropic",
|
|
|
|
|
modelID: "claude-opus-4-6",
|
|
|
|
|
message: "Model not found: anthropic/claude-opus-4-6.",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const errorType = classifyErrorType(error)
|
|
|
|
|
const retryable = isRetryableError(error, [429, 503, 529])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(errorType).toBe("model_not_found")
|
|
|
|
|
expect(retryable).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("classifies nested AI_LoadAPIKeyError as missing_api_key", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const error = {
|
|
|
|
|
data: {
|
|
|
|
|
name: "AI_LoadAPIKeyError",
|
|
|
|
|
message:
|
|
|
|
|
"Google Generative AI API key is missing. Pass it using the 'apiKey' parameter or the GOOGLE_GENERATIVE_AI_API_KEY environment variable.",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const errorType = classifyErrorType(error)
|
|
|
|
|
const retryable = isRetryableError(error, [429, 503, 529])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(errorType).toBe("missing_api_key")
|
|
|
|
|
expect(retryable).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-04 18:35:09 +01:00
|
|
|
test("ignores non-retry assistant status text", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const info = {
|
|
|
|
|
status: "Thinking...",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const signal = extractAutoRetrySignal(info)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(signal).toBeUndefined()
|
|
|
|
|
})
|
|
|
|
|
})
|