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({
>
)}