fix: add missing_key error kind for missing chat api_key_id (#25783)

Refs CODAGT-486

- `codersdk/chats.go`: New `ChatErrorKindMissingKey` constant and
`AllChatErrorKinds` entry
- `coderd/x/chatd/chaterror/message.go`: `terminalMessage` and
`retryMessage` cases
- `coderd/x/chatd/model_routing_aibridge.go`: Pre-classify error with
`WithClassification`
- `coderd/x/chatd/model_routing_internal_test.go`: Classification
assertion on production path (CRF-2)
- `chatStatusHelpers.ts`: Frontend title "Chat interrupted"
- `LiveStreamTail.stories.tsx`: Storybook story with `detail` assertion
- `docs/ai-coder/ai-gateway/clients/coder-agents.md`: Troubleshooting
entry
- Tests: classification round-trip, terminal message, metrics kind
enumeration

> Generated with [Coder Agents](https://coder.com/agents) on behalf of
@johnstcn
This commit is contained in:
Cian Johnston
2026-05-28 15:50:52 +01:00
committed by GitHub
parent ea280c5a90
commit 6df1536256
15 changed files with 119 additions and 15 deletions
+2
View File
@@ -1959,6 +1959,7 @@ export type ChatErrorKind =
| "auth"
| "config"
| "generic"
| "missing_key"
| "overloaded"
| "rate_limit"
| "startup_timeout"
@@ -1969,6 +1970,7 @@ export const ChatErrorKinds: ChatErrorKind[] = [
"auth",
"config",
"generic",
"missing_key",
"overloaded",
"rate_limit",
"startup_timeout",
@@ -193,6 +193,41 @@ export const TerminalTimeoutErrorUnknownProvider: Story = {
},
};
/** Missing API key shows the "Chat interrupted" terminal error. */
export const TerminalMissingKeyError: Story = {
args: {
...defaultArgs,
liveStatus: buildLiveStatus({
streamError: {
kind: "missing_key",
message:
"This conversation was started with an API key that is no longer available. Send your message again to continue.",
retryable: false,
detail:
"If this error persists after resending, please report it as a bug.",
},
}),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(
canvas.getByRole("heading", { name: /chat interrupted/i }),
).toBeVisible();
expect(
canvas.getByText(
/this conversation was started with an api key that is no longer available/i,
),
).toBeVisible();
expect(
canvas.getByText(/if this error persists after resending/i),
).toBeVisible();
// Guard against the generic fallback.
expect(
canvas.queryByText(/the chat request failed unexpectedly/i),
).not.toBeInTheDocument();
},
};
/** Retrying a transport timeout shows attempt + countdown. */
export const RetryingTimeoutAnthropic: Story = {
args: {
@@ -42,6 +42,8 @@ export const getErrorTitle = (
return "Configuration error";
case "usage_limit":
return "Usage limit reached";
case "missing_key":
return "Chat interrupted";
default:
return mode === "retry" ? "Retrying request" : "Request failed";
}