mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 15:40:57 -07:00
207 lines
6.9 KiB
TypeScript
207 lines
6.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Custom error types for A2A remote agent operations.
|
|
* Provides structured, user-friendly error messages for common failure modes
|
|
* during agent card fetching, authentication, and communication.
|
|
*/
|
|
|
|
/**
|
|
* Base class for all A2A agent errors.
|
|
* Provides a `userMessage` field with a human-readable description.
|
|
*/
|
|
export class A2AAgentError extends Error {
|
|
/** A user-friendly message suitable for display in the CLI. */
|
|
readonly userMessage: string;
|
|
/** The agent name associated with this error. */
|
|
readonly agentName: string;
|
|
|
|
constructor(
|
|
agentName: string,
|
|
message: string,
|
|
userMessage: string,
|
|
options?: ErrorOptions,
|
|
) {
|
|
super(message, options);
|
|
this.name = 'A2AAgentError';
|
|
this.agentName = agentName;
|
|
this.userMessage = userMessage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when the agent card URL returns a 404 Not Found response.
|
|
*/
|
|
export class AgentCardNotFoundError extends A2AAgentError {
|
|
constructor(agentName: string, agentCardUrl: string) {
|
|
const message = `Agent card not found at ${agentCardUrl} (HTTP 404)`;
|
|
const userMessage = `Agent card not found (404) at ${agentCardUrl}. Verify the agent_card_url in your agent definition.`;
|
|
super(agentName, message, userMessage);
|
|
this.name = 'AgentCardNotFoundError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when the agent card URL returns a 401/403 response,
|
|
* indicating an authentication or authorization failure.
|
|
*/
|
|
export class AgentCardAuthError extends A2AAgentError {
|
|
readonly statusCode: number;
|
|
|
|
constructor(agentName: string, agentCardUrl: string, statusCode: 401 | 403) {
|
|
const statusText = statusCode === 401 ? 'Unauthorized' : 'Forbidden';
|
|
const message = `Agent card request returned ${statusCode} ${statusText} for ${agentCardUrl}`;
|
|
const userMessage = `Authentication failed (${statusCode} ${statusText}) at ${agentCardUrl}. Check the "auth" configuration in your agent definition.`;
|
|
super(agentName, message, userMessage);
|
|
this.name = 'AgentCardAuthError';
|
|
this.statusCode = statusCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when the agent card's security schemes require authentication
|
|
* but the agent definition does not include the necessary auth configuration.
|
|
*/
|
|
export class AgentAuthConfigMissingError extends A2AAgentError {
|
|
/** Human-readable description of required authentication schemes. */
|
|
readonly requiredAuth: string;
|
|
/** Specific fields or config entries that are missing. */
|
|
readonly missingFields: string[];
|
|
|
|
constructor(
|
|
agentName: string,
|
|
requiredAuth: string,
|
|
missingFields: string[],
|
|
) {
|
|
const message = `Agent "${agentName}" requires authentication but none is configured`;
|
|
const userMessage = `Agent requires ${requiredAuth} but no auth is configured. Missing: ${missingFields.join(', ')}`;
|
|
super(agentName, message, userMessage);
|
|
this.name = 'AgentAuthConfigMissingError';
|
|
this.requiredAuth = requiredAuth;
|
|
this.missingFields = missingFields;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown when a generic/unexpected network or server error occurs
|
|
* while fetching the agent card or communicating with the remote agent.
|
|
*/
|
|
export class AgentConnectionError extends A2AAgentError {
|
|
constructor(agentName: string, agentCardUrl: string, cause: unknown) {
|
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
const message = `Failed to connect to agent "${agentName}" at ${agentCardUrl}: ${causeMessage}`;
|
|
const userMessage = `Connection failed for ${agentCardUrl}: ${causeMessage}`;
|
|
super(agentName, message, userMessage, { cause });
|
|
this.name = 'AgentConnectionError';
|
|
}
|
|
}
|
|
|
|
/** Shape of an error-like object in a cause chain (Error, HTTP response, or plain object). */
|
|
interface ErrorLikeObject {
|
|
message?: string;
|
|
code?: string;
|
|
status?: number;
|
|
statusCode?: number;
|
|
cause?: unknown;
|
|
}
|
|
|
|
/** Type guard for objects that may carry error metadata (message, code, status, cause). */
|
|
function isErrorLikeObject(val: unknown): val is ErrorLikeObject {
|
|
return typeof val === 'object' && val !== null;
|
|
}
|
|
|
|
/**
|
|
* Collects all error messages from an error's cause chain into a single string
|
|
* for pattern matching. This is necessary because the A2A SDK and Node's fetch
|
|
* often wrap the real error (e.g. HTTP status) deep inside nested causes.
|
|
*/
|
|
function collectErrorMessages(error: unknown): string {
|
|
const parts: string[] = [];
|
|
let current: unknown = error;
|
|
let depth = 0;
|
|
const maxDepth = 10;
|
|
|
|
while (current && depth < maxDepth) {
|
|
if (isErrorLikeObject(current)) {
|
|
// Save reference before instanceof narrows the type from ErrorLikeObject to Error.
|
|
const obj = current;
|
|
|
|
if (current instanceof Error) {
|
|
parts.push(current.message);
|
|
} else if (typeof obj.message === 'string') {
|
|
parts.push(obj.message);
|
|
}
|
|
|
|
if (typeof obj.code === 'string') {
|
|
parts.push(obj.code);
|
|
}
|
|
|
|
if (typeof obj.status === 'number') {
|
|
parts.push(String(obj.status));
|
|
} else if (typeof obj.statusCode === 'number') {
|
|
parts.push(String(obj.statusCode));
|
|
}
|
|
|
|
current = obj.cause;
|
|
} else if (typeof current === 'string') {
|
|
parts.push(current);
|
|
break;
|
|
} else {
|
|
parts.push(String(current));
|
|
break;
|
|
}
|
|
depth++;
|
|
}
|
|
|
|
return parts.join(' ');
|
|
}
|
|
|
|
/**
|
|
* Attempts to classify a raw error from the A2A SDK into a typed A2AAgentError.
|
|
*
|
|
* Inspects the error message and full cause chain for HTTP status codes and
|
|
* well-known patterns to produce a structured, user-friendly error.
|
|
*
|
|
* @param agentName The name of the agent being loaded.
|
|
* @param agentCardUrl The URL of the agent card.
|
|
* @param error The raw error caught during agent loading.
|
|
* @returns A classified A2AAgentError subclass.
|
|
*/
|
|
export function classifyAgentError(
|
|
agentName: string,
|
|
agentCardUrl: string,
|
|
error: unknown,
|
|
): A2AAgentError {
|
|
// Collect messages from the entire cause chain for thorough matching.
|
|
const fullErrorText = collectErrorMessages(error);
|
|
|
|
// Check for well-known connection error codes in the cause chain.
|
|
// NOTE: This is checked before the 404 pattern as a defensive measure
|
|
// to prevent DNS errors (ENOTFOUND) from being misclassified as 404s.
|
|
if (
|
|
/\b(ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT)\b/i.test(fullErrorText)
|
|
) {
|
|
return new AgentConnectionError(agentName, agentCardUrl, error);
|
|
}
|
|
|
|
// Check for HTTP status code patterns across the full cause chain.
|
|
if (/\b404\b|\bnot[\s_-]?found\b/i.test(fullErrorText)) {
|
|
return new AgentCardNotFoundError(agentName, agentCardUrl);
|
|
}
|
|
|
|
if (/\b401\b|unauthorized/i.test(fullErrorText)) {
|
|
return new AgentCardAuthError(agentName, agentCardUrl, 401);
|
|
}
|
|
|
|
if (/\b403\b|forbidden/i.test(fullErrorText)) {
|
|
return new AgentCardAuthError(agentName, agentCardUrl, 403);
|
|
}
|
|
|
|
// Fallback to a generic connection error.
|
|
return new AgentConnectionError(agentName, agentCardUrl, error);
|
|
}
|