Address PR review follow-ups for retry status handling
This commit is contained in:
committed by
YeonGyu-Kim
parent
86c6bc7716
commit
c598afa521
@@ -17,6 +17,20 @@ describe("runtime-fallback error classifier", () => {
|
|||||||
expect(signal).toBeDefined()
|
expect(signal).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
test("treats cooling-down retry messages as retryable", () => {
|
test("treats cooling-down retry messages as retryable", () => {
|
||||||
//#given
|
//#given
|
||||||
const error = {
|
const error = {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export interface AutoRetrySignal {
|
|||||||
export const AUTO_RETRY_PATTERNS: Array<(combined: string) => boolean> = [
|
export const AUTO_RETRY_PATTERNS: Array<(combined: string) => boolean> = [
|
||||||
(combined) => /retrying\s+in/i.test(combined),
|
(combined) => /retrying\s+in/i.test(combined),
|
||||||
(combined) =>
|
(combined) =>
|
||||||
/(?:too\s+many\s+requests|quota\s*exceeded|quota\s+will\s+reset\s+after|usage\s+limit|rate\s+limit|limit\s+reached|all\s+credentials\s+for\s+model|cool(?:ing)?\s+down|exhausted\s+your\s+capacity)/i.test(combined),
|
/(?:too\s+many\s+requests|quota\s*exceeded|quota\s+will\s+reset\s+after|usage\s+limit|rate\s+limit|limit\s+reached|all\s+credentials\s+for\s+model|cool(?:ing)?\s*down|exhausted\s+your\s+capacity)/i.test(combined),
|
||||||
]
|
]
|
||||||
|
|
||||||
export function extractAutoRetrySignal(info: Record<string, unknown> | undefined): AutoRetrySignal | undefined {
|
export function extractAutoRetrySignal(info: Record<string, unknown> | undefined): AutoRetrySignal | undefined {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { extractStatusCode, extractErrorName, classifyErrorType, isRetryableErro
|
|||||||
import { createFallbackState, prepareFallback } from "./fallback-state"
|
import { createFallbackState, prepareFallback } from "./fallback-state"
|
||||||
import { getFallbackModelsForSession } from "./fallback-models"
|
import { getFallbackModelsForSession } from "./fallback-models"
|
||||||
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
||||||
|
import { normalizeRetryStatusMessage, extractRetryAttempt } from "../../shared/retry-status-utils"
|
||||||
|
|
||||||
export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
|
export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
|
||||||
const { config, pluginConfig, sessionStates, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionFallbackTimeouts } = deps
|
const { config, pluginConfig, sessionStates, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionFallbackTimeouts } = deps
|
||||||
@@ -184,22 +185,6 @@ export function createEventHandler(deps: HookDeps, helpers: AutoRetryHelpers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeRetryStatusMessage = (message: string): string =>
|
|
||||||
message
|
|
||||||
.replace(/\[retrying in [^\]]*attempt\s*#\d+\]/gi, "[retrying]")
|
|
||||||
.replace(/retrying in\s+[^(]*attempt\s*#\d+/gi, "retrying")
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
||||||
const extractRetryAttempt = (statusAttempt: unknown, message: string): string => {
|
|
||||||
if (typeof statusAttempt === "number" && Number.isFinite(statusAttempt)) {
|
|
||||||
return String(statusAttempt)
|
|
||||||
}
|
|
||||||
const match = message.match(/attempt\s*#\s*(\d+)/i)
|
|
||||||
return match?.[1] ?? "?"
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSessionStatus = async (props: Record<string, unknown> | undefined) => {
|
const handleSessionStatus = async (props: Record<string, unknown> | undefined) => {
|
||||||
const sessionID = props?.sessionID as string | undefined
|
const sessionID = props?.sessionID as string | undefined
|
||||||
const status = props?.status as { type?: string; message?: string; attempt?: number } | undefined
|
const status = props?.status as { type?: string; message?: string; attempt?: number } | undefined
|
||||||
|
|||||||
@@ -515,6 +515,7 @@ describe("runtime-fallback", () => {
|
|||||||
sessionID,
|
sessionID,
|
||||||
status: {
|
status: {
|
||||||
type: "retry",
|
type: "retry",
|
||||||
|
next: 476,
|
||||||
attempt: 1,
|
attempt: 1,
|
||||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||||
},
|
},
|
||||||
@@ -573,6 +574,7 @@ describe("runtime-fallback", () => {
|
|||||||
sessionID,
|
sessionID,
|
||||||
status: {
|
status: {
|
||||||
type: "retry",
|
type: "retry",
|
||||||
|
next: 476,
|
||||||
attempt: 1,
|
attempt: 1,
|
||||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]",
|
||||||
},
|
},
|
||||||
@@ -587,6 +589,7 @@ describe("runtime-fallback", () => {
|
|||||||
sessionID,
|
sessionID,
|
||||||
status: {
|
status: {
|
||||||
type: "retry",
|
type: "retry",
|
||||||
|
next: 475,
|
||||||
attempt: 1,
|
attempt: 1,
|
||||||
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 55s attempt #1]",
|
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 55s attempt #1]",
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-22
@@ -22,6 +22,7 @@ import { getAgentConfigKey } from "../shared/agent-display-names";
|
|||||||
import { log } from "../shared/logger";
|
import { log } from "../shared/logger";
|
||||||
import { shouldRetryError } from "../shared/model-error-classifier";
|
import { shouldRetryError } from "../shared/model-error-classifier";
|
||||||
import { buildFallbackChainFromModels } from "../shared/fallback-chain-from-models";
|
import { buildFallbackChainFromModels } from "../shared/fallback-chain-from-models";
|
||||||
|
import { extractRetryAttempt, normalizeRetryStatusMessage } from "../shared/retry-status-utils";
|
||||||
import { clearSessionModel, setSessionModel } from "../shared/session-model-state";
|
import { clearSessionModel, setSessionModel } from "../shared/session-model-state";
|
||||||
import { deleteSessionTools } from "../shared/session-tools-store";
|
import { deleteSessionTools } from "../shared/session-tools-store";
|
||||||
import { lspManager } from "../tools";
|
import { lspManager } from "../tools";
|
||||||
@@ -47,28 +48,6 @@ function normalizeFallbackModelID(modelID: string): string {
|
|||||||
.replace(/-high$/i, "");
|
.replace(/-high$/i, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRetryStatusMessage(message: string): string {
|
|
||||||
return message
|
|
||||||
.replace(/\[retrying in [^\]]*attempt\s*#\d+\]/gi, "[retrying]")
|
|
||||||
.replace(/retrying in\s+[^(]*attempt\s*#\d+/gi, "retrying")
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractRetryAttempt(statusAttempt: unknown, message: string): string {
|
|
||||||
if (typeof statusAttempt === "number" && Number.isFinite(statusAttempt)) {
|
|
||||||
return String(statusAttempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attemptMatch = message.match(/attempt\s*#\s*(\d+)/i);
|
|
||||||
if (attemptMatch?.[1]) {
|
|
||||||
return attemptMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractErrorName(error: unknown): string | undefined {
|
function extractErrorName(error: unknown): string | undefined {
|
||||||
if (isRecord(error) && typeof error.name === "string") return error.name;
|
if (isRecord(error) && typeof error.name === "string") return error.name;
|
||||||
if (error instanceof Error) return error.name;
|
if (error instanceof Error) return error.name;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export function normalizeRetryStatusMessage(message: string): string {
|
||||||
|
return message
|
||||||
|
.replace(/\[retrying in [^\]]*attempt\s*#\d+\]/gi, "[retrying]")
|
||||||
|
.replace(/retrying in\s+[^(]*attempt\s*#\d+/gi, "retrying")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractRetryAttempt(statusAttempt: unknown, message: string): string {
|
||||||
|
if (typeof statusAttempt === "number" && Number.isFinite(statusAttempt)) {
|
||||||
|
return String(statusAttempt)
|
||||||
|
}
|
||||||
|
const attemptMatch = message.match(/attempt\s*#\s*(\d+)/i)
|
||||||
|
if (attemptMatch?.[1]) {
|
||||||
|
return attemptMatch[1]
|
||||||
|
}
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user