fix(runtime-fallback): 9 critical bug fixes for auto-retry, agent preservation, and model override
Bug fixes: 1. extractStatusCode: handle nested data.statusCode (Anthropic error structure) 2. Error regex: relax credit.*balance.*too.*low pattern for multi-char gaps 3. Zod schema: bump max_fallback_attempts from 10 to 20 (config rejected silently) 4. getFallbackModelsForSession: fallback to sisyphus/any agent when session.error lacks agent 5. Model detection: derive model from agent config when session.error lacks model info 6. Auto-retry: resend last user message with fallback model via promptAsync 7. Persistent fallback: override model on every chat.message (not just pendingFallbackModel) 8. Manual model change: detect UI model changes and reset fallback state 9. Agent preservation: include agent in promptAsync body to prevent defaulting to sisyphus Additional: - Add sessionRetryInFlight guard to prevent double-retries - Add resolveAgentForSession with 3-tier resolution (event → session memory → session ID) - Add normalizeAgentName for display names like "Prometheus (Planner)" → "prometheus" - Add resolveAgentForSessionFromContext to fetch agent from session messages - Move AGENT_NAMES and agentPattern to module scope for reuse - Register runtime-fallback hooks in event.ts and chat-message.ts - Remove diagnostic debug logging from isRetryableError - Add 400 to default retry_on_errors and credit/balance patterns to RETRYABLE_ERROR_PATTERNS
This commit is contained in:
committed by
YeonGyu-Kim
parent
708b9ce9ff
commit
fbafb8cf67
@@ -23,7 +23,12 @@ describe("runtime-fallback", () => {
|
||||
logSpy?.mockRestore()
|
||||
})
|
||||
|
||||
function createMockPluginInput() {
|
||||
function createMockPluginInput(overrides?: {
|
||||
session?: {
|
||||
messages?: (args: unknown) => Promise<unknown>
|
||||
promptAsync?: (args: unknown) => Promise<unknown>
|
||||
}
|
||||
}) {
|
||||
return {
|
||||
client: {
|
||||
tui: {
|
||||
@@ -35,6 +40,10 @@ describe("runtime-fallback", () => {
|
||||
})
|
||||
},
|
||||
},
|
||||
session: {
|
||||
messages: overrides?.session?.messages ?? (async () => ({ data: [] })),
|
||||
promptAsync: overrides?.session?.promptAsync ?? (async () => ({})),
|
||||
},
|
||||
},
|
||||
directory: "/test/dir",
|
||||
} as any
|
||||
@@ -174,7 +183,10 @@ describe("runtime-fallback", () => {
|
||||
})
|
||||
|
||||
test("should log when no fallback models configured", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), { config: createMockConfig() })
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig(),
|
||||
pluginConfig: {},
|
||||
})
|
||||
const sessionID = "test-session-no-fallbacks"
|
||||
|
||||
await hook.event({
|
||||
@@ -487,7 +499,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const output = { message: {}, parts: [] }
|
||||
await hook["chat.message"]?.(
|
||||
{ sessionID, model: { providerID: "anthropic", modelID: "claude-opus-4-5" } },
|
||||
{ sessionID },
|
||||
output
|
||||
)
|
||||
|
||||
@@ -588,6 +600,50 @@ describe("runtime-fallback", () => {
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.2" })
|
||||
})
|
||||
|
||||
test("should preserve resolved agent during auto-retry", async () => {
|
||||
const promptCalls: Array<Record<string, unknown>> = []
|
||||
const hook = createRuntimeFallbackHook(
|
||||
createMockPluginInput({
|
||||
session: {
|
||||
messages: async () => ({
|
||||
data: [
|
||||
{
|
||||
info: { role: "user" },
|
||||
parts: [{ type: "text", text: "test" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
promptAsync: async (args: unknown) => {
|
||||
promptCalls.push(args as Record<string, unknown>)
|
||||
return {}
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("prometheus", ["github-copilot/claude-opus-4.6"]),
|
||||
},
|
||||
)
|
||||
const sessionID = "test-preserve-agent-on-retry"
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.error",
|
||||
properties: {
|
||||
sessionID,
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
error: { statusCode: 503, message: "Service unavailable" },
|
||||
agent: "prometheus",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptCalls.length).toBe(1)
|
||||
const callBody = promptCalls[0]?.body as Record<string, unknown>
|
||||
expect(callBody?.agent).toBe("prometheus")
|
||||
expect(callBody?.model).toEqual({ providerID: "github-copilot", modelID: "claude-opus-4.6" })
|
||||
})
|
||||
})
|
||||
|
||||
describe("cooldown mechanism", () => {
|
||||
|
||||
Reference in New Issue
Block a user