diff --git a/packages/chat-ui/src/inference-failure.test.ts b/packages/chat-ui/src/inference-failure.test.ts index 35c4dc26..80c9326d 100644 --- a/packages/chat-ui/src/inference-failure.test.ts +++ b/packages/chat-ui/src/inference-failure.test.ts @@ -1,6 +1,42 @@ import { describe, expect, test } from "bun:test"; -import { isClassifiedInferenceFailureText } from "./inference-failure"; +import { + consumerFacingInferenceText, + isClassifiedInferenceFailureText, +} from "./inference-failure"; + +describe("consumerFacingInferenceText", () => { + test("keeps the vendor preamble and drops [HTTP …] plus the raw provider message", () => { + expect( + consumerFacingInferenceText( + "This agent could not complete your request due to a credential error [HTTP 401]: API key is invalid", + ), + ).toBe( + "This agent could not complete your request due to a credential error", + ); + expect( + consumerFacingInferenceText( + "This agent could not complete your request because the API quota has been exhausted [HTTP 429]: rate limited", + ), + ).toBe( + "This agent could not complete your request because the API quota has been exhausted", + ); + }); + + test("a forced [HTTP 401] dump is not consumer copy", () => { + const leaked = "[HTTP 401]: API key is invalid"; + const facing = consumerFacingInferenceText(leaked); + expect(facing).not.toContain("[HTTP"); + expect(facing).not.toContain("401"); + expect(facing).not.toContain("API key is invalid"); + }); + + test("leaves cause-aware undelivered-notice copy untouched", () => { + const notice = + "I can't reach a model right now — add or check your model key in Settings, then I'll pick this up."; + expect(consumerFacingInferenceText(notice)).toBe(notice); + }); +}); describe("isClassifiedInferenceFailureText", () => { test("matches a credential_failure reply, status code included", () => { @@ -11,6 +47,16 @@ describe("isClassifiedInferenceFailureText", () => { ).toBe(true); }); + test("matches a credential_failure reply after HTTP/raw is stripped", () => { + expect( + isClassifiedInferenceFailureText( + consumerFacingInferenceText( + "This agent could not complete your request due to a credential error [HTTP 401]: invalid api key", + ), + ), + ).toBe(true); + }); + test("matches a quota_exhausted reply", () => { expect( isClassifiedInferenceFailureText( diff --git a/packages/chat-ui/src/inference-failure.ts b/packages/chat-ui/src/inference-failure.ts index 9037d65d..0b3bb35a 100644 --- a/packages/chat-ui/src/inference-failure.ts +++ b/packages/chat-ui/src/inference-failure.ts @@ -29,6 +29,8 @@ export const CLASSIFIED_INFERENCE_FAILURE_PREAMBLES: readonly string[] = [ "This agent could not complete your request because the API quota has been exhausted", ]; +export { consumerFacingInferenceText } from "@corbits/chat/consumer-inference-text"; + export function isClassifiedInferenceFailureText(text: string): boolean { return CLASSIFIED_INFERENCE_FAILURE_PREAMBLES.some((preamble) => text.startsWith(preamble), diff --git a/packages/chat-ui/src/timeline.tsx b/packages/chat-ui/src/timeline.tsx index 26160143..cd99cfd0 100644 --- a/packages/chat-ui/src/timeline.tsx +++ b/packages/chat-ui/src/timeline.tsx @@ -57,7 +57,10 @@ import type { BlockResponseActions } from "./blocks/block-responses"; import type { ConnectGithubActions } from "./blocks/connect-github-actions"; import type { ConnectServiceActions } from "./blocks/connect-service-actions"; import { BlockPartView } from "./blocks/registry"; -import { isClassifiedInferenceFailureText } from "./inference-failure"; +import { + consumerFacingInferenceText, + isClassifiedInferenceFailureText, +} from "./inference-failure"; import { WorkbenchLoadingState } from "./loading-state"; import { Markdown } from "./markdown"; import type { ProfileSubject } from "./profile-subject"; @@ -455,6 +458,7 @@ function TextBubble({ pendingNonce?: string; pendingActions?: PendingActions; }) { + const consumerText = consumerFacingInferenceText(text); const display = senderDisplay(sender, participants, currentUser); const isOwn = currentUser !== undefined && @@ -545,10 +549,10 @@ function TextBubble({ )}
- +
{onFixConnection !== undefined && - isClassifiedInferenceFailureText(text) && ( + isClassifiedInferenceFailureText(consumerText) && (