Skip to content

Error Handling

Patterns for handling errors on both the agent side (MCP Client) and the frontend (React SDK).

import {
GguiClient,
GguiTimeoutError,
GguiAuthError,
GguiSessionNotFoundError,
McpProtocolError,
} from "@ggui-ai/mcp-client";
const ggui = new GguiClient({
apiKey: process.env.GGUI_API_KEY!,
appId: process.env.GGUI_APP_ID!,
timeout: 60000,
});
async function pushWithRetry(
intent: string,
prompt: string,
maxRetries = 3
): Promise<{ sessionId: string; url: string }> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await ggui.push({ story: { intent, prompt } });
} catch (error) {
if (error instanceof GguiAuthError) {
throw new Error("Invalid API key. Check your GGUI_API_KEY.");
}
if (attempt === maxRetries) throw error;
if (error instanceof GguiTimeoutError || error instanceof McpProtocolError) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await new Promise((r) => setTimeout(r, delay));
continue;
}
throw error;
}
}
throw new Error("Unreachable");
}
async function resilientWait(sessionId: string) {
try {
return await ggui.waitForCompletion(sessionId, { maxWait: 300_000 });
} catch (error) {
if (error instanceof GguiSessionNotFoundError) {
console.log("Session expired, creating new one...");
const { sessionId: newId } = await ggui.push({
story: {
intent: "Recover from expired session",
prompt: "The previous session expired. Please try again.",
},
});
return ggui.waitForCompletion(newId);
}
throw error;
}
}
function getUserMessage(error: unknown): string {
if (error instanceof GguiAuthError)
return "Authentication failed. Please check your API configuration.";
if (error instanceof GguiTimeoutError) return "The request timed out. Please try again.";
if (error instanceof GguiSessionNotFoundError) return "Your session has expired.";
if (error instanceof McpProtocolError && error.code === -32004)
return "Failed to generate the UI. Try a simpler request.";
return "An unexpected error occurred.";
}

import { StackItemRenderer } from "@ggui-ai/react";
function SafeRenderer({ stackItem }) {
const [renderError, setRenderError] = useState<Error | null>(null);
if (renderError) {
return (
<div>
<p>This component failed to render.</p>
<button onClick={() => setRenderError(null)}>Retry</button>
</div>
);
}
return (
<StackItemRenderer
stackItem={stackItem}
onError={(error) => setRenderError(error)}
fallback={<div>Loading...</div>}
/>
);
}
import { SelfRepairBoundary, DynamicComponent } from "@ggui-ai/react";
<SelfRepairBoundary
sessionId={sessionId}
appId={appId}
stackItemId={stackItem.id}
config={{ enabled: true, maxAttempts: 3, retryDelayMs: 2000, showRepairUI: true }}
onReportError={async (report) => {
await fetch("/api/report-error", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(report),
});
}}
repairingFallback={<div>Auto-fixing component...</div>}
errorFallback={<div>This component could not be repaired.</div>}
>
<DynamicComponent code={stackItem.componentCode} />
</SelfRepairBoundary>;
{
({ submit, connectionStatus }) => (
<form
onSubmit={(e) => {
e.preventDefault();
submit(formData);
// Events are buffered by WebSocketManager when disconnected
}}
>
<button type="submit">Submit</button>
{connectionStatus !== "connected" && <small>Connection: {connectionStatus}</small>}
</form>
);
}