fix: detect team/org-level budget errors in error banner (#13003)

This commit is contained in:
sp.wack
2026-02-24 20:55:11 +04:00
committed by GitHub
parent 3afeccfe7f
commit 5367bef43a
3 changed files with 97 additions and 2 deletions

View File

@@ -358,6 +358,30 @@ describe("Conversation WebSocket Handler", () => {
});
});
it("should show friendly i18n message for budget ConversationErrorEvent", async () => {
const mockBudgetConversationError = createMockConversationErrorEvent({
detail:
"Budget has been exceeded! Current cost: 18.51, Max budget: 18.24",
});
mswServer.use(
wsLink.addEventListener("connection", ({ client, server }) => {
server.connect();
client.send(JSON.stringify(mockBudgetConversationError));
}),
);
renderWithWebSocketContext(<ErrorMessageStoreComponent />);
expect(screen.getByTestId("error-message")).toHaveTextContent("none");
await waitFor(() => {
expect(screen.getByTestId("error-message")).toHaveTextContent(
"STATUS$ERROR_LLM_OUT_OF_CREDITS",
);
});
});
it("should set error message store on WebSocket connection errors", async () => {
// Simulate a connect-then-fail sequence (the MSW server auto-connects by default).
// This should surface an error message because the app has previously connected.

View File

@@ -9,7 +9,10 @@ import {
} from "vitest";
import { screen, waitFor, render, cleanup } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createMockAgentErrorEvent } from "#/mocks/mock-ws-helpers";
import {
createMockAgentErrorEvent,
createMockConversationErrorEvent,
} from "#/mocks/mock-ws-helpers";
import { ConversationWebSocketProvider } from "#/contexts/conversation-websocket-context";
import { conversationWebSocketTestSetup } from "./helpers/msw-websocket-setup";
import { ConnectionStatusComponent } from "./helpers/websocket-test-components";
@@ -229,5 +232,35 @@ describe("PostHog Analytics Tracking", () => {
}),
);
});
it("should track credit_limit_reached when ConversationErrorEvent contains budget error", async () => {
const mockBudgetConversationError = createMockConversationErrorEvent({
detail:
"Budget has been exceeded! Current cost: 18.51, Max budget: 18.24",
});
mswServer.use(
wsLink.addEventListener("connection", ({ client, server }) => {
server.connect();
client.send(JSON.stringify(mockBudgetConversationError));
}),
);
renderWithProviders(<ConnectionStatusComponent />);
await waitFor(() => {
expect(screen.getByTestId("connection-state")).toHaveTextContent(
"OPEN",
);
});
await waitFor(() => {
expect(mockTrackCreditLimitReached).toHaveBeenCalledWith(
expect.objectContaining({
conversationId: "test-conversation-123",
}),
);
});
});
});
});

View File

@@ -358,7 +358,14 @@ export function ConversationWebSocketProvider({
},
posthog,
});
if (isBudgetOrCreditError(event.detail)) {
setErrorMessage(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS);
trackCreditLimitReached({
conversationId: conversationId || "unknown",
});
} else {
setErrorMessage(event.detail);
}
} else {
// Clear error message on any non-ConversationErrorEvent
removeErrorMessage();
@@ -498,6 +505,31 @@ export function ConversationWebSocketProvider({
};
addEvent(eventWithPlanningFlag);
// Handle ConversationErrorEvent specifically - show error banner
// AgentErrorEvent errors are displayed inline in the chat, not as banners
if (isConversationErrorEvent(event)) {
trackError({
message: event.detail,
source: "planning_conversation",
metadata: {
eventId: event.id,
errorCode: event.code,
},
posthog,
});
if (isBudgetOrCreditError(event.detail)) {
setErrorMessage(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS);
trackCreditLimitReached({
conversationId: conversationId || "unknown",
});
} else {
setErrorMessage(event.detail);
}
} else {
// Clear error message on any non-ConversationErrorEvent
removeErrorMessage();
}
// Handle AgentErrorEvent specifically
if (isAgentErrorEvent(event)) {
trackError({
@@ -513,6 +545,9 @@ export function ConversationWebSocketProvider({
// Use friendly i18n message for budget/credit errors instead of raw error
if (isBudgetOrCreditError(event.error)) {
setErrorMessage(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS);
trackCreditLimitReached({
conversationId: conversationId || "unknown",
});
} else {
setErrorMessage(event.error);
}
@@ -609,15 +644,18 @@ export function ConversationWebSocketProvider({
isLoadingHistoryPlanning,
expectedEventCountPlanning,
setErrorMessage,
removeErrorMessage,
removeOptimisticUserMessage,
queryClient,
subConversations,
conversationId,
setExecutionStatus,
appendInput,
appendOutput,
readConversationFile,
setPlanContent,
updateMetricsFromStats,
trackCreditLimitReached,
posthog,
],
);