diff --git a/server/src/services/native-runtime/native-provider-failure.test.ts b/server/src/services/native-runtime/native-provider-failure.test.ts index f8ec77405b4..dbab03ed300 100644 --- a/server/src/services/native-runtime/native-provider-failure.test.ts +++ b/server/src/services/native-runtime/native-provider-failure.test.ts @@ -40,6 +40,10 @@ describe("native provider failure observation", () => { const result = observed.forTerminal("turn", terminal); expect(result).toMatchObject({ errorCode: "native_provider_model_rejected", errorMessage: NATIVE_MODEL_REJECTION_MESSAGE }); expect(JSON.stringify(result)).not.toContain("private-model"); + // A closed diagnostic only, and the classified turn is not also reported as unclassified. + expect(result?.diagnostic).toEqual(expect.objectContaining({ provider: kind })); + expect(JSON.stringify(result?.diagnostic)).not.toContain("not a valid model"); + expect(observed.unclassifiedTurnFailure("turn", terminal)).toBeNull(); expect(observed.forTerminal("other-turn", terminal)).toBeNull(); expect(observed.forTerminal("turn", { ...terminal, runTerminalState: "succeeded" })).toBeNull(); }); diff --git a/server/src/services/native-runtime/native-provider-failure.ts b/server/src/services/native-runtime/native-provider-failure.ts index 477f61ffba1..ecbd4ed837a 100644 --- a/server/src/services/native-runtime/native-provider-failure.ts +++ b/server/src/services/native-runtime/native-provider-failure.ts @@ -216,6 +216,11 @@ export function createNativeProviderFailureObservation(provider: { kind: string; let overloadedTurnId: string | null = null; const failedTurns = new Map(); const providerFailures = new Map(); + /** A driver-classified, action-required model rejection; `forTerminal` classifies it, so it is not "unclassified". */ + const modelUnavailableTurn = (turnId: string) => { + const failure = providerFailures.get(turnId); + return failure?.category === "model_unavailable" && failure.retryability === "requires_action"; + }; return { observe(event: PrpEvent) { if (event.sourceKind === "runner" && event.eventType === "turn.failed" && event.turnId @@ -270,8 +275,8 @@ export function createNativeProviderFailureObservation(provider: { kind: string; if (turnId !== rejectedTurnId) { // A driver's closed category marks a rejected model for any provider. Only an // action-required rejection qualifies; a transient one may succeed on retry. - const failure = providerFailures.get(turnId); - if (failure?.category !== "model_unavailable" || failure.retryability !== "requires_action") return null; + if (!modelUnavailableTurn(turnId)) return null; + const failure = providerFailures.get(turnId)!; return { errorCode: "native_provider_model_rejected" as const, errorMessage: NATIVE_MODEL_REJECTION_MESSAGE, @@ -287,7 +292,7 @@ export function createNativeProviderFailureObservation(provider: { kind: string; /** Diagnostics only: the failed turn that `forTerminal` did not classify. */ unclassifiedTurnFailure(turnId: string | null | undefined, terminal: PrpTerminalState) { if (!turnId || terminal.turnTerminalState !== "failed" || terminal.runTerminalState !== "failed" || - turnId === overloadedTurnId || turnId === rejectedTurnId) return null; + turnId === overloadedTurnId || turnId === rejectedTurnId || modelUnavailableTurn(turnId)) return null; const diagnostic = failedTurns.get(turnId); return diagnostic ? { ...diagnostic } : null; },