112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
import { APIError } from "@letta-ai/letta-client/core/error";
|
|
|
|
const LETTA_USAGE_URL = "https://app.letta.com/settings/organization/usage";
|
|
|
|
/**
|
|
* Check if the error is a credit exhaustion error (402 with not-enough-credits)
|
|
*/
|
|
function isCreditExhaustedError(e: APIError): boolean {
|
|
// Check status code
|
|
if (e.status !== 402) return false;
|
|
|
|
// Check for "not-enough-credits" in various places it could appear
|
|
const errorBody = e.error;
|
|
if (errorBody && typeof errorBody === "object") {
|
|
// Check reasons array: {"error":"Rate limited","reasons":["not-enough-credits"]}
|
|
if ("reasons" in errorBody && Array.isArray(errorBody.reasons)) {
|
|
if (errorBody.reasons.includes("not-enough-credits")) {
|
|
return true;
|
|
}
|
|
}
|
|
// Check nested error.reasons
|
|
if ("error" in errorBody && typeof errorBody.error === "object") {
|
|
const nested = errorBody.error as Record<string, unknown>;
|
|
if ("reasons" in nested && Array.isArray(nested.reasons)) {
|
|
if (nested.reasons.includes("not-enough-credits")) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also check the message for "not-enough-credits" as a fallback
|
|
if (e.message?.includes("not-enough-credits")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extract comprehensive error details from any error object
|
|
* Handles APIError, Error, and other error types consistently
|
|
* @param e The error object to format
|
|
* @param agentId Optional agent ID to create hyperlinks to the Letta dashboard
|
|
*/
|
|
export function formatErrorDetails(e: unknown, agentId?: string): string {
|
|
let runId: string | undefined;
|
|
|
|
// Handle APIError from streaming (event: error)
|
|
if (e instanceof APIError) {
|
|
// Check for credit exhaustion error first - provide a friendly message
|
|
if (isCreditExhaustedError(e)) {
|
|
return `Your account is out of credits. Redeem additional credits or configure auto-recharge on your account page: ${LETTA_USAGE_URL}`;
|
|
}
|
|
// Check for nested error structure: e.error.error
|
|
if (e.error && typeof e.error === "object" && "error" in e.error) {
|
|
const errorData = e.error.error;
|
|
if (errorData && typeof errorData === "object") {
|
|
const type = "type" in errorData ? errorData.type : undefined;
|
|
const message =
|
|
"message" in errorData ? errorData.message : "An error occurred";
|
|
const detail = "detail" in errorData ? errorData.detail : undefined;
|
|
|
|
const errorType = type ? `[${type}] ` : "";
|
|
const errorDetail = detail ? `\nDetail: ${detail}` : "";
|
|
|
|
// Extract run_id from e.error
|
|
if ("run_id" in e.error && typeof e.error.run_id === "string") {
|
|
runId = e.error.run_id;
|
|
}
|
|
|
|
const baseError = `${errorType}${message}${errorDetail}`;
|
|
return runId && agentId
|
|
? `${baseError}\n${createAgentLink(runId, agentId)}`
|
|
: baseError;
|
|
}
|
|
}
|
|
|
|
// Handle APIError with direct error structure: e.error.detail
|
|
if (e.error && typeof e.error === "object") {
|
|
const detail = "detail" in e.error ? e.error.detail : undefined;
|
|
if ("run_id" in e.error && typeof e.error.run_id === "string") {
|
|
runId = e.error.run_id;
|
|
}
|
|
|
|
const baseError = detail ? `${e.message}\nDetail: ${detail}` : e.message;
|
|
return runId && agentId
|
|
? `${baseError}\n${createAgentLink(runId, agentId)}`
|
|
: baseError;
|
|
}
|
|
|
|
// Fallback for APIError with just message
|
|
return e.message;
|
|
}
|
|
|
|
// Handle regular Error objects
|
|
if (e instanceof Error) {
|
|
return e.message;
|
|
}
|
|
|
|
// Fallback for any other type
|
|
return String(e);
|
|
}
|
|
|
|
/**
|
|
* Create a terminal hyperlink to the agent with run ID displayed
|
|
*/
|
|
function createAgentLink(runId: string, agentId: string): string {
|
|
const url = `https://app.letta.com/agents/${agentId}`;
|
|
return `View agent: \x1b]8;;${url}\x1b\\${agentId}\x1b]8;;\x1b\\ (run: ${runId})`;
|
|
}
|