kimi-code / packages /node-sdk /src /errors.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw
History Blame Contribute Delete
17.9 kB
import {
APIConnectionError,
APIEmptyResponseError,
APIProviderQuotaExhaustedError,
APIStatusError,
APITimeoutError,
ChatProviderError,
} from '@moonshot-ai/kosong';
export const ErrorCodes = {
CONFIG_INVALID: 'config.invalid',
SESSION_NOT_FOUND: 'session.not_found',
SESSION_ALREADY_EXISTS: 'session.already_exists',
SESSION_ID_INVALID: 'session.id_invalid',
SESSION_ID_REQUIRED: 'session.id_required',
SESSION_ID_EMPTY: 'session.id_empty',
SESSION_TITLE_EMPTY: 'session.title_empty',
SESSION_STATE_NOT_FOUND: 'session.state_not_found',
SESSION_STATE_INVALID: 'session.state_invalid',
SESSION_FORK_ACTIVE_TURN: 'session.fork_active_turn',
SESSION_EXPORT_NOT_FOUND: 'session.export_not_found',
SESSION_EXPORT_MISSING_VERSION: 'session.export_missing_version',
SESSION_CLOSED: 'session.closed',
SESSION_PERMISSION_MODE_INVALID: 'session.permission_mode_invalid',
SESSION_THINKING_EMPTY: 'session.thinking_empty',
SESSION_MODEL_EMPTY: 'session.model_empty',
SESSION_PLAN_MODE_INVALID: 'session.plan_mode_invalid',
SESSION_APPROVAL_HANDLER_ERROR: 'session.approval_handler_error',
SESSION_QUESTION_HANDLER_ERROR: 'session.question_handler_error',
SESSION_INIT_FAILED: 'session.init_failed',
AGENT_NOT_FOUND: 'agent.not_found',
TURN_AGENT_BUSY: 'turn.agent_busy',
GOAL_ALREADY_EXISTS: 'goal.already_exists',
GOAL_NOT_FOUND: 'goal.not_found',
GOAL_OBJECTIVE_EMPTY: 'goal.objective_empty',
GOAL_OBJECTIVE_TOO_LONG: 'goal.objective_too_long',
GOAL_STATUS_INVALID: 'goal.status_invalid',
GOAL_METADATA_RESERVED: 'goal.metadata_reserved',
GOAL_NOT_RESUMABLE: 'goal.not_resumable',
MODEL_NOT_CONFIGURED: 'model.not_configured',
MODEL_CONFIG_INVALID: 'model.config_invalid',
AUTH_LOGIN_REQUIRED: 'auth.login_required',
CONTEXT_OVERFLOW: 'context.overflow',
LOOP_MAX_STEPS_EXCEEDED: 'loop.max_steps_exceeded',
PROVIDER_API_ERROR: 'provider.api_error',
PROVIDER_FILTERED: 'provider.filtered',
PROVIDER_RATE_LIMIT: 'provider.rate_limit',
PROVIDER_AUTH_ERROR: 'provider.auth_error',
PROVIDER_CONNECTION_ERROR: 'provider.connection_error',
SKILL_NOT_FOUND: 'skill.not_found',
SKILL_TYPE_UNSUPPORTED: 'skill.type_unsupported',
SKILL_NAME_EMPTY: 'skill.name_empty',
RECORDS_WRITE_FAILED: 'records.write_failed',
COMPACTION_FAILED: 'compaction.failed',
COMPACTION_UNABLE: 'compaction.unable',
BACKGROUND_TASK_ID_EMPTY: 'task.task_id_empty',
MCP_SERVER_NOT_FOUND: 'mcp.server_not_found',
MCP_SERVER_DISABLED: 'mcp.server_disabled',
MCP_STARTUP_FAILED: 'mcp.startup_failed',
MCP_TOOL_NAME_COLLISION: 'mcp.tool_name_collision',
MCP_OAUTH_FAILED: 'mcp.oauth_failed',
PLUGIN_NOT_FOUND: 'plugin.not_found',
PLUGIN_LOAD_FAILED: 'plugin.load_failed',
REQUEST_INVALID: 'request.invalid',
REQUEST_WORK_DIR_REQUIRED: 'request.work_dir_required',
REQUEST_PROMPT_INPUT_EMPTY: 'request.prompt_input_empty',
SHELL_GIT_BASH_NOT_FOUND: 'shell.git_bash_not_found',
NOT_IMPLEMENTED: 'not_implemented',
INTERNAL: 'internal',
} as const;
export type KimiErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
export interface KimiErrorInfo {
readonly title: string;
readonly retryable: boolean;
readonly public: boolean;
readonly action?: string;
}
export const KIMI_ERROR_INFO = {
'config.invalid': {
title: 'Invalid configuration',
retryable: false,
public: true,
action: 'Check config.toml and provider/model settings.',
},
'session.not_found': {
title: 'Session not found',
retryable: false,
public: true,
action: 'Check the session id or list available sessions.',
},
'session.already_exists': {
title: 'Session already exists',
retryable: false,
public: true,
action: 'Use a different session id or remove the existing session first.',
},
'session.id_invalid': {
title: 'Invalid session id',
retryable: false,
public: true,
action: 'Use a session id without path-traversal characters.',
},
'session.id_required': {
title: 'Session id required',
retryable: false,
public: true,
action: 'Provide a session id when calling this method.',
},
'session.id_empty': {
title: 'Session id is empty',
retryable: false,
public: true,
action: 'Provide a non-empty session id.',
},
'session.title_empty': {
title: 'Session title is empty',
retryable: false,
public: true,
action: 'Provide a non-empty session title.',
},
'session.state_not_found': {
title: 'Session state missing',
retryable: false,
public: true,
action: 'The session directory is corrupted or missing state.json.',
},
'session.state_invalid': {
title: 'Session state invalid',
retryable: false,
public: true,
action: 'The session state.json is corrupted; remove the session or repair the file.',
},
'session.fork_active_turn': {
title: 'Cannot fork session during active turn',
retryable: true,
public: true,
action: 'Wait for the active turn to complete before forking.',
},
'session.export_not_found': {
title: 'Session export directory missing',
retryable: false,
public: true,
action: 'The session has not been persisted to disk yet.',
},
'session.export_missing_version': {
title: 'Export version is missing',
retryable: false,
public: true,
action: 'Provide a version when exporting the session.',
},
'session.closed': {
title: 'Session is closed',
retryable: false,
public: true,
action: 'Create a new session.',
},
'session.permission_mode_invalid': {
title: 'Invalid permission mode',
retryable: false,
public: true,
action: 'Use one of: yolo / manual / auto.',
},
'session.thinking_empty': {
title: 'Thinking value is empty',
retryable: false,
public: true,
action: 'Provide a non-empty thinking option.',
},
'session.model_empty': {
title: 'Model is empty',
retryable: false,
public: true,
action: 'Provide a non-empty model identifier.',
},
'session.plan_mode_invalid': {
title: 'Invalid plan mode',
retryable: false,
public: true,
action: 'Provide a boolean plan mode.',
},
'session.approval_handler_error': {
title: 'Approval handler threw',
retryable: false,
public: true,
action: 'Inspect the SDK approval handler for an unhandled exception.',
},
'session.question_handler_error': {
title: 'Question handler threw',
retryable: false,
public: true,
action: 'Inspect the SDK question handler for an unhandled exception.',
},
'session.init_failed': {
title: 'Session init failed',
retryable: false,
public: false,
action: 'Review the init failure details and try again.',
},
'agent.not_found': {
title: 'Agent not found',
retryable: false,
public: true,
action: 'Check the agent id or list available agents.',
},
'turn.agent_busy': {
title: 'Agent is busy',
retryable: true,
public: true,
action: 'Wait for the current turn to finish or steer it.',
},
'goal.already_exists': {
title: 'A goal is already active',
retryable: false,
public: true,
action: 'Use `/goal replace <objective>` to replace the current goal.',
},
'goal.not_found': {
title: 'No goal found',
retryable: false,
public: true,
action: 'Start a goal with `/goal <objective>` first.',
},
'goal.objective_empty': {
title: 'Goal objective is empty',
retryable: false,
public: true,
action: 'Provide a non-empty objective.',
},
'goal.objective_too_long': {
title: 'Goal objective is too long',
retryable: false,
public: true,
action: 'Keep the objective under 4000 characters; reference long details by file path.',
},
'goal.status_invalid': {
title: 'Invalid goal status transition',
retryable: false,
public: true,
action: 'Use a status allowed for this actor (complete, blocked, or impossible).',
},
'goal.metadata_reserved': {
title: 'Goal metadata is reserved',
retryable: false,
public: true,
action: 'Do not write metadata.custom.goal directly; use the goal lifecycle methods.',
},
'goal.not_resumable': {
title: 'Goal is not resumable',
retryable: false,
public: true,
action: 'Only paused goals can be resumed.',
},
'model.not_configured': {
title: 'No model configured',
retryable: false,
public: true,
action: 'Set a default model in config.toml or via setModel.',
},
'model.config_invalid': {
title: 'Invalid model configuration',
retryable: false,
public: true,
action: 'Check the model and provider entries in config.toml.',
},
'auth.login_required': {
title: 'Login required',
retryable: false,
public: true,
action: 'Run the login flow for the provider before retrying.',
},
'context.overflow': {
title: 'Context window overflow',
retryable: true,
public: true,
action: 'Compact the conversation or start a new session.',
},
'loop.max_steps_exceeded': {
title: 'Turn exceeded max steps',
retryable: false,
public: true,
action: 'Increase loop_control.max_steps_per_turn in config.toml or split the task.',
},
'provider.api_error': {
title: 'Provider API error',
retryable: false,
public: true,
action: 'Inspect details.statusCode / details.requestId; check provider status.',
},
'provider.filtered': {
title: 'Provider filtered response',
retryable: false,
public: true,
action: 'Revise the prompt or model configuration to avoid provider safety filtering.',
},
'provider.rate_limit': {
title: 'Provider rate limit',
retryable: true,
public: true,
action: 'Retry after a delay or reduce request frequency.',
},
'provider.auth_error': {
title: 'Provider authentication error',
retryable: false,
public: true,
action: 'Re-authenticate with the provider.',
},
'provider.connection_error': {
title: 'Provider connection error',
retryable: true,
public: true,
action: 'Check network connectivity and retry.',
},
'skill.not_found': {
title: 'Skill not found',
retryable: false,
public: true,
action: 'List available skills via the skill registry.',
},
'skill.type_unsupported': {
title: 'Skill type not supported',
retryable: false,
public: true,
action: 'Only inline skills can be activated by the user.',
},
'skill.name_empty': {
title: 'Skill name is empty',
retryable: false,
public: true,
action: 'Provide a non-empty skill name.',
},
'records.write_failed': {
title: 'Failed to write records',
retryable: true,
public: true,
action: 'Check disk space and permissions on the session directory.',
},
'compaction.failed': {
title: 'Compaction failed',
retryable: false,
public: true,
action: 'Inspect logs and consider increasing compaction limits.',
},
'compaction.unable': {
title: 'Unable to compact',
retryable: false,
public: true,
action: 'The current history has no prefix that can be compacted (e.g. only a pending user message). Start a new turn or session instead.',
},
'task.task_id_empty': {
title: 'Background task id is empty',
retryable: false,
public: true,
action: 'Provide a non-empty task id.',
},
'mcp.server_not_found': {
title: 'MCP server not found',
retryable: false,
public: true,
action: 'List configured MCP servers and check the requested name.',
},
'mcp.server_disabled': {
title: 'MCP server is disabled',
retryable: false,
public: true,
action: 'Enable the MCP server entry in config before reconnecting.',
},
'mcp.startup_failed': {
title: 'MCP server startup failed',
retryable: true,
public: true,
action: 'Inspect the MCP server log or call reconnect once the server is healthy.',
},
'mcp.tool_name_collision': {
title: 'MCP tool name collision',
retryable: false,
public: true,
action: 'Rename one of the colliding MCP tools or servers so their qualified names are unique.',
},
'mcp.oauth_failed': {
title: 'MCP OAuth authorization failed',
retryable: true,
public: true,
action: 'Begin the authorization flow again; inspect the error details if it keeps failing.',
},
'plugin.not_found': {
title: 'Plugin not found',
retryable: false,
public: true,
action: 'List installed plugins via /plugins and check the requested id.',
},
'plugin.load_failed': {
title: 'Plugin state failed to load',
retryable: true,
public: true,
action: 'Fix the installed.json file under $KIMI_CODE_HOME/plugins/ and run /plugins reload.',
},
'request.invalid': {
title: 'Invalid request',
retryable: false,
public: true,
action: 'Check the input shape matches the API contract.',
},
'request.work_dir_required': {
title: 'workDir is required',
retryable: false,
public: true,
action: 'Provide workDir in the request payload.',
},
'request.prompt_input_empty': {
title: 'Prompt input is empty',
retryable: false,
public: true,
action: 'Provide non-empty prompt input.',
},
'shell.git_bash_not_found': {
title: 'Git Bash not found',
retryable: false,
public: true,
action: 'Install Git for Windows from https://gitforwindows.org/ or set KIMI_SHELL_PATH to a bash.exe.',
},
not_implemented: {
title: 'Not implemented',
retryable: false,
public: true,
action: 'This feature is not implemented yet.',
},
internal: {
title: 'Internal error',
retryable: false,
public: true,
action: 'Inspect logs or report the issue with diagnostics.',
},
} as const satisfies Record<KimiErrorCode, KimiErrorInfo>;
export function isKimiErrorCode(code: unknown): code is KimiErrorCode {
return typeof code === 'string' && Object.hasOwn(KIMI_ERROR_INFO, code);
}
export interface KimiErrorOptions {
readonly details?: Record<string, unknown>;
readonly cause?: unknown;
}
export class KimiError extends Error {
readonly code: KimiErrorCode;
readonly details?: Record<string, unknown>;
override readonly cause?: unknown;
constructor(code: KimiErrorCode, message: string, options: KimiErrorOptions = {}) {
super(message);
this.name = 'KimiError';
this.code = code;
this.details = options.details;
this.cause = options.cause;
}
}
export interface KimiErrorPayload {
readonly code: KimiErrorCode;
readonly message: string;
readonly name?: string;
readonly details?: Record<string, unknown>;
readonly retryable: boolean;
}
export function isKimiError(error: unknown): error is KimiError {
return error instanceof KimiError;
}
export function makeErrorPayload(
code: KimiErrorCode,
message: string,
options?: { readonly details?: Record<string, unknown>; readonly name?: string },
): KimiErrorPayload {
return {
code,
message,
name: options?.name,
details: options?.details,
retryable: KIMI_ERROR_INFO[code].retryable,
};
}
export function toKimiErrorPayload(error: unknown): KimiErrorPayload {
if (isKimiError(error)) {
return {
code: error.code,
message: error.message,
name: error.name,
details: error.details,
retryable: KIMI_ERROR_INFO[error.code].retryable,
};
}
if (error instanceof APIStatusError) {
const code: KimiErrorCode =
error instanceof APIProviderQuotaExhaustedError
? ErrorCodes.PROVIDER_API_ERROR
: error.statusCode === 429
? ErrorCodes.PROVIDER_RATE_LIMIT
: error.statusCode === 401
? ErrorCodes.PROVIDER_AUTH_ERROR
: ErrorCodes.PROVIDER_API_ERROR;
return {
code,
message: sanitizeStatusErrorMessage(error.message),
name: error.name,
details: {
statusCode: error.statusCode,
requestId: error.requestId,
},
retryable: KIMI_ERROR_INFO[code].retryable,
};
}
if (error instanceof APIConnectionError || error instanceof APITimeoutError) {
return {
code: ErrorCodes.PROVIDER_CONNECTION_ERROR,
message: error.message,
name: error.name,
retryable: KIMI_ERROR_INFO[ErrorCodes.PROVIDER_CONNECTION_ERROR].retryable,
};
}
if (error instanceof APIEmptyResponseError) {
const code =
error.finishReason === 'filtered'
? ErrorCodes.PROVIDER_FILTERED
: ErrorCodes.PROVIDER_API_ERROR;
return {
code,
message: error.message,
name: error.name,
details: {
finishReason: error.finishReason,
rawFinishReason: error.rawFinishReason,
},
retryable: KIMI_ERROR_INFO[code].retryable,
};
}
if (error instanceof ChatProviderError) {
return {
code: ErrorCodes.PROVIDER_API_ERROR,
message: error.message,
name: error.name,
retryable: KIMI_ERROR_INFO[ErrorCodes.PROVIDER_API_ERROR].retryable,
};
}
if (error instanceof Error) {
return {
code: ErrorCodes.INTERNAL,
message: error.message,
name: error.name,
retryable: KIMI_ERROR_INFO[ErrorCodes.INTERNAL].retryable,
};
}
return {
code: ErrorCodes.INTERNAL,
message: String(error),
retryable: KIMI_ERROR_INFO[ErrorCodes.INTERNAL].retryable,
};
}
function sanitizeStatusErrorMessage(message: string): string {
const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(message);
const extracted = titleMatch?.[1]?.trim();
const normalized = extracted !== undefined && extracted.length > 0 ? extracted : message;
return normalized.replaceAll('\r', '');
}
export function fromKimiErrorPayload(payload: KimiErrorPayload): KimiError {
return new KimiError(payload.code, payload.message, {
details: payload.details,
});
}