Composio TypeScript SDK 错误处理完全指南:错误层次结构、捕获策略与用户友好的格式化输出
【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio
导读
在基于 Composio SDK 构建 AI Agent 应用时,工具执行、连接授权、输入校验等环节都会产生可预期的失败路径。Composio TypeScript SDK 提供了一套完整的错误体系:以ComposioError为基类,衍生出覆盖工具执行、认证配置、连接请求、校验失败等场景的子类,并内置了带颜色格式化的prettyPrint()、统一的handle()等错误展示工具。本文将围绕 ts/docs/advanced/error-handling.md 的完整内容,结合@composio/core包的源码实现,系统讲解如何识别、捕获、分类与展示这些错误,最终帮助你写出健壮、可观测、对用户友好的 Agent 应用。
错误层次结构(Error Hierarchy)
Composio SDK 采用"单一基类 + 领域子类"的结构化错误体系。所有错误最终都继承自ComposioError,而ComposioError本身继承自 JavaScript 内置的Error。从源码 ComposioError.ts 可以看到完整的继承树:
ComposioError:所有 Composio 错误的基类AuthConfigErrors:与认证配置(Auth Config)相关的错误,例如ComposioAuthConfigNotFoundErrorConnectedAccountsError:与已连接账号相关的错误,例如ComposioConnectedAccountNotFoundErrorConnectionRequestError:与连接请求(OAuth 授权流程)相关的错误,例如ConnectionRequestTimeoutError、ConnectionRequestFailedErrorToolErrors:与工具及其执行相关的错误,例如ComposioToolNotFoundError、ComposioToolExecutionError、ComposioToolVersionRequiredError、ComposioInvalidToolArgumentsErrorToolkitErrors:与工具包(Toolkit)相关的错误,例如ComposioToolkitNotFoundError、ComposioToolkitFetchErrorValidationError:与输入校验相关的错误
所有错误类都通过 errors/index.ts 统一从@composio/core导出,你只需要一条 import 语句即可按需引用。
ComposioError 基类的核心字段
在 ComposioError.ts 中,ComposioError在原生Error之上增加了以下结构化字段:
| 字段 | 类型 | 说明 |
|---|---|---|
code | string | 错误分类代码,构造时自动加上TS-SDK::前缀,例如TS-SDK::TOOL_NOT_FOUND |
statusCode | number | 关联的 HTTP 状态码;若cause是BadRequestError,会自动继承其status |
cause | unknown | 底层原因,可以是原生Error、ZodError 或任意值 |
meta | Record<string, unknown> | 附加元数据,便于携带上下文信息 |
possibleFixes | string[] | 建议的修复措施列表,会直接展示给用户 |
errorId | string | 错误标识(可选) |
stack | string | 合并后的堆栈:当包装底层错误时,combineStackTraces会把原始堆栈与包装堆栈拼接,并以Caused by:分隔(见 ComposioError.ts) |
实现细节:
ComposioError使用definePropertyIfExists只为有值的属性定义可枚举属性(ComposioError.ts),因此未设置的字段不会以undefined的形式出现在错误输出中,保证展示信息干净。
常见错误类型与捕获示例
输入校验错误(ValidationError)
当传给 SDK 方法的输入不符合预期的 Zod Schema 时,会抛出ValidationError。它内部包装了一个ZodError,并把校验问题逐条映射到possibleFixes(见 ValidationErrors.ts):
try { await composio.tools.get('default', { invalidParam: 'value', // 触发校验错误 }); } catch (error) { if (error instanceof ValidationError) { console.error('Validation error:', error.message); console.error('Validation details:', error.validationError); } }值得注意的源码细节:ValidationError的构造函数会调用generateUserFriendlyMessage()(ValidationErrors.ts),当底层 Zod issue 是invalid_type时,会自动生成类似The owner should be a string, but you provided a number这样易于理解的提示,并追加到message中。因此即使你不做任何额外加工,error.message本身已经足够友好。
同文件还定义了与 Schema 转换相关的JsonSchemaToZodError与JsonSchemaRefResolutionError(分别对应JSON_SCHEMA_TO_ZOD_ERROR与JSON_SCHEMA_REF_RESOLUTION_ERROR错误码),在自定义工具 Schema 转换失败时抛出。
工具执行错误(ComposioToolExecutionError)
工具执行期间发生的错误会被包装为ComposioToolExecutionError(ToolErrors.ts),并携带toolSlug、请求体等上下文:
try { const result = await composio.tools.execute('GITHUB_GET_REPO', { userId: 'default', arguments: { owner: 'composio', // 缺少 'repo' 参数会触发错误 }, }); } catch (error) { if (error instanceof ComposioToolExecutionError) { console.error('Tool execution error:', error.message); console.error('Tool:', error.context.toolSlug); console.error('Execution params:', error.context.body); } }从源码可以进一步了解其底层映射逻辑:SDK 在捕获 API 层错误时,会调用handleToolExecutionError(tool, actualError)(ToolErrors.ts)。该函数会读取服务端返回的错误体(ComposioAPIServerErrorBody),若错误码命中ERROR_CODE_HANDLERS映射表(目前 1803 对应ComposioConnectedAccountNotFoundError),则返回对应的具体错误类型;否则回退为通用的ComposioToolExecutionError,并把原始错误作为cause保留。
资源未找到错误(ComposioToolNotFoundError)
当请求不存在的工具时,抛出ComposioToolNotFoundError(ToolErrors.ts),错误码为TS-SDK::TOOL_NOT_FOUND:
try { await composio.tools.get('default', 'NON_EXISTENT_TOOL'); } catch (error) { if (error instanceof ComposioToolNotFoundError) { console.error('Tool not found:', error.message); } }同类别的工具错误还包括:ComposioProviderNotDefinedError、ComposioInvalidModifierError、ComposioInvalidToolArgumentsError、ComposioInvalidExecuteFunctionError、ComposioGlobalExecuteToolFnNotSetError,以及一个值得在生产环境重点关注的ComposioToolVersionRequiredError。
工具版本缺失错误(ComposioToolVersionRequiredError)
该错误是"手动执行工具时未指定 toolkit 版本"才会出现的场景:当解析到的版本为latest且未显式设置dangerouslySkipVersionCheck时,tools.execute()会抛出此错误(见 ToolErrors.ts 的 JSDoc 示例)。修复方式有四种,按推荐程度排列:
// 方案一:在 execute 调用中显式传入版本 await composio.tools.execute('GITHUB_GET_REPOS', { userId: 'default', version: '20250909_00', arguments: { owner: 'composio' }, }); // 方案二:在 SDK 初始化时配置 toolkitVersions const composio = new Composio({ toolkitVersions: { github: '20250909_00' }, }); // 方案三:使用环境变量 COMPOSIO_TOOLKIT_VERSION_<TOOLKIT_SLUG> // COMPOSIO_TOOLKIT_VERSION_GITHUB=20250909_00 // 方案四:跳过版本检查(仅建议在非生产环境使用) await composio.tools.execute('GITHUB_GET_REPOS', { userId: 'default', dangerouslySkipVersionCheck: true, arguments: { owner: 'composio' }, });SDK 级错误
ComposioNoAPIKeyError(SDKErrors.ts)在 SDK 无法从参数、环境变量(COMPOSIO_API_KEY)或用户配置文件中找到 API Key 时抛出,默认错误码为TS-SDK::NO_API_KEY_PROVIDED、状态码 401,并附上三条修复建议。ComposioRequestCancelledError(SDKErrors.ts)则对应调用方通过AbortSignal主动取消请求的场景:
try { await composio.tools.execute(slug, body, { signal: AbortSignal.timeout(5_000) }); } catch (err) { if (err instanceof ComposioRequestCancelledError) return; // 调用方主动取消,属预期行为 throw err; }SDK 还导出了isRequestAbortError()辅助函数(SDKErrors.ts),它能够穿透多层cause链识别各类 Abort 错误,对 dual-package 场景做了兜底判断,适合在中间件层统一识别"取消"语义。
处理工具执行中的双层错误
执行工具时,需要同时处理两种错误形态:SDK 抛出的异常(连接失败、鉴权失败等),以及执行结果中的业务失败。Composio 的execute返回对象带有successful标志,即便工具在远端执行失败,SDK 调用本身也可能正常返回:
try { const result = await composio.tools.execute('GITHUB_GET_REPO', { userId: 'default', arguments: { owner: 'composio', repo: 'sdk', }, }); // 先检查执行是否成功 if (result.successful) { console.log('Repository details:', result.data); } else { // 处理"执行失败但 SDK 调用成功返回"的情况 console.error('Execution failed:', result.error); } } catch (error) { // 处理 SDK 层异常 console.error('SDK error:', error.message); }这一模式也是官方文档强调的 Best Practice 之一:永远不要假定execute()只通过异常表达失败。result.successful === false时,result.error中已经包含了结构化的失败信息,直接透传即可。
连接流程中的错误处理
连接第三方账号(OAuth 授权)涉及"发起授权 → 等待连接建立"两个阶段,两个阶段都可能失败:
try { // 第一步:发起授权请求 const connectionRequest = await composio.toolkits.authorize('user123', 'github'); // 第二步:等待连接建立(60 秒超时) try { const connectedAccount = await composio.connectedAccounts.waitForConnection( connectionRequest.id, 60000 // 60 second timeout ); console.log('Connected account:', connectedAccount); } catch (timeoutError) { if (timeoutError instanceof ConnectionRequestTimeoutError) { console.error('Connection timed out. Please try again.'); } else if (timeoutError instanceof ConnectionRequestFailedError) { console.error('Connection failed:', timeoutError.message); } } } catch (error) { if (error instanceof ComposioAuthConfigNotFoundError) { console.error('Auth config not found:', error.message); } else { console.error('Error initiating connection:', error.message); } }源码层面,ConnectionRequestTimeoutError与ConnectionRequestFailedError定义在 ConnectionRequestErrors.ts,错误码分别为TS-SDK::CONNECTION_REQUEST_TIMEOUT与TS-SDK::CONNECTION_REQUEST_FAILED;ComposioAuthConfigNotFoundError定义在 AuthConfigErrors.ts,错误码为TS-SDK::AUTH_CONFIG_NOT_FOUND,其possibleFixes内置了"检查 auth config 是否存在 / id 是否正确 / 是否启用"三条排查建议。建议为waitForConnection设置合理的超时时间(文档示例为 60 秒),避免调用无限挂起。
全局错误处理器:集中式的错误分类与展示
对于规模较大的应用,可以定义一个集中式的错误处理函数,统一对所有错误分类处理。官方文档给出了一个完整的handleComposioError示例,它按"具体子类 → 基类 → 兜底"的顺序做instanceof判断:
function handleComposioError(error: unknown): void { if (error instanceof ValidationError) { console.error('Validation error:', error.message); } else if (error instanceof ComposioToolNotFoundError) { console.error('Tool not found:', error.message); } else if (error instanceof ComposioToolExecutionError) { console.error('Tool execution error:', error.message); } else if (error instanceof ComposioAuthConfigNotFoundError) { console.error('Auth config not found:', error.message); } else if (error instanceof ConnectionRequestFailedError) { console.error('Connection failed:', error.message); } else if (error instanceof ConnectionRequestTimeoutError) { console.error('Connection timed out:', error.message); } else if (error instanceof ComposioError) { console.error('Composio error:', error.message); } else { console.error('Unexpected error:', error); } } try { const result = await composio.tools.execute('GITHUB_GET_REPO', { userId: 'default', arguments: { owner: 'composio', repo: 'sdk' }, }); if (!result.successful) { console.error('Execution failed:', result.error); } } catch (error) { handleComposioError(error); }注意判断顺序:必须把更具体的子类放在前面,把ComposioError基类放在后面,否则基类分支会吞掉所有具体错误信息。
Session 自定义工具中的错误处理(Tool Router Custom Tools)
在使用 Tool Router 创建会话自定义工具时,handler 内部如果无法继续执行,直接抛出普通Error即可。SDK 会把抛出的错误包装进标准的会话执行响应中,无需自行处理响应格式。experimental_createTool从@composio/core导出(见 experimental/index.ts):
import { experimental_createTool } from '@composio/core'; import { z } from 'zod'; const customTool = experimental_createTool('MY_CUSTOM_TOOL', { name: 'My Custom Tool', description: 'A custom tool with error handling', inputParams: z.object({ param1: z.string().describe('Required parameter'), }), execute: async (input) => { const { param1 } = input; if (param1.trim() === '') { throw new Error('param1 cannot be empty'); } const result = await someExternalService(param1); return { result }; }, });这种设计让自定义工具与内置工具的错误语义保持一致:Agent 侧看到的是统一格式的失败响应,便于 LLM 理解并修正参数后重试。
用户友好的错误展示
Composio SDK 内置了带颜色与格式的错误输出能力,底层依赖picocolors进行终端着色,所有输出统一走logger.error(实现见 ComposioError.ts)。
使用 toString()
ComposioError及其子类的toString()返回格式化的错误字符串表示:
try { // 可能失败的操作 } catch (error) { if (error instanceof ComposioError) { // 输出带颜色的格式化错误信息 console.error(error.toString()); } }使用 prettyPrint()
prettyPrint()提供更美观的错误展示,直接输出到console.error。从源码看,它会按区块渲染:红色背景的ERROR标识与加粗消息、黄色错误码与状态码、灰色Reason、Additional Information(JSON 格式化缩进)、青色Try the following:修复建议列表,以及可选的堆栈追踪:
try { // 可能失败的操作 } catch (error) { if (error instanceof ComposioError) { error.prettyPrint(); // 基础展示 error.prettyPrint(true); // 包含堆栈追踪 // 重要:prettyPrint 之后不要再重复 log 或直接 re-throw, // 否则控制台会出现重复的错误信息 } }使用静态 handle() 工具
ComposioError.handle(error, options)(ComposioError.ts)是官方推荐的统一入口。从源码可以看到它的完整分派逻辑:
- 错误是
ComposioError→ 调用prettyPrint(includeStack); - 错误是
ZodError→ 调用handleZodError,逐条列出Invalid parameters与Expected parameters,对 LLM 或调试者非常友好; - 错误是普通
Error→ 调用handleStandardError做同风格的基础格式化; - 其余未知值 → 调用
handleUnknownError优雅兜底。
try { // 可能失败的操作 } catch (error) { // 统一处理所有类型错误,自动格式化 ComposioError.handle(error); // 包含堆栈追踪 ComposioError.handle(error, { includeStack: true }); }使用 handleAndThrow() 处理致命错误
对于必须终止执行的致命错误,使用handleAndThrow():先按handle()的格式展示错误,再原样抛出。源码中该方法签名返回never类型(ComposioError.ts),TypeScript 编译器会因此识别调用点之后的代码不可达:
try { // 可能失败的操作 } catch (error) { // 展示错误后抛出(适用于致命错误) ComposioError.handleAndThrow(error); // 抛出的同时包含堆栈追踪 ComposioError.handleAndThrow(error, true); }与process.exit()不同,handleAndThrow只是"展示 + 抛出",因此它兼容 Serverless 环境——错误可以继续向上冒泡交给运行时或上层框架处理,而不会在函数内部直接终止进程。
一步完成创建与打印:createAndPrint()
静态工厂方法createAndPrint(message, options, includeStack?)会创建错误、调用prettyPrint并返回错误实例(ComposioError.ts),适合在自定义错误处理器或格式化器中直接使用:
// 创建、打印并抛出错误 throw ComposioError.createAndPrint('Something went wrong', { code: 'CUSTOM_ERROR', cause: 'The operation failed because of XYZ', possibleFixes: ['Try solution A', 'Try solution B'], });最佳实践清单
结合官方文档与源码实现,构建健壮的错误处理流程时建议遵循以下原则:
- 调用 SDK 方法时始终使用 try/catch,不要依赖未捕获异常;
- 工具执行后检查
result.successful,区分"SDK 层异常"与"业务执行失败"两种形态; - 对不同错误类型提供针对性处理,
instanceof判断时把具体子类放在基类之前; - 记录详细错误信息,充分利用
error.code、error.cause、error.meta、合并堆栈等结构化字段辅助排查; - 面向用户展示友好消息,优先使用
prettyPrint()/handle()的格式化输出,或基于possibleFixes生成修复提示; - 为
waitForConnection等阻塞操作设置合理超时,避免挂起; - 调用 SDK 前自行校验输入,减少不必要的远端往返;
- 对瞬时错误实现重试逻辑,例如网络抖动、超时类错误。
导入错误类与自定义错误
所有错误类均从@composio/core主包导出(见 errors/index.ts),导入方式如下:
import { ComposioError, ComposioNoAPIKeyError, ComposioToolNotFoundError, ValidationError, } from '@composio/core';你还可以把 SDK 的错误处理工具集成到应用自身的集中错误处理流程中,例如按环境决定是否包含堆栈:
import { ComposioError } from '@composio/core'; // 集中式错误处理器 function handleApplicationError(error: unknown) { // 使用内置错误处理工具 ComposioError.handle(error, { includeStack: process.env.NODE_ENV === 'development', }); // 追加应用自定义处理逻辑,例如上报监控服务 } try { // 应用代码 } catch (error) { handleApplicationError(error); }自定义错误类型
如果需要融入 Composio 错误体系,可以继承ComposioError并传入code、possibleFixes等选项。构造时基类会自动为code添加TS-SDK::前缀,handle()也能自动识别并格式化你的自定义子类:
import { ComposioError } from '@composio/core'; class MyCustomError extends ComposioError { constructor(message: string) { super(message, { code: 'MY_CUSTOM_ERROR', possibleFixes: [ 'Check your application configuration', 'Ensure all required dependencies are installed', ], }); this.name = 'MyCustomError'; } } try { // 某些条件 if (!config.isValid) { throw new MyCustomError('Invalid configuration'); } } catch (error) { ComposioError.handle(error); // 自动识别并格式化 MyCustomError }结语
Composio TS SDK 的错误体系设计呈现出两个鲜明特点:一是结构化,code/statusCode/cause/meta/possibleFixes字段让错误不再是一段难以解析的字符串,而是可直接用于诊断、重试与上报的数据;二是面向用户,prettyPrint()、handle()、handleAndThrow()、createAndPrint()等工具让终端输出具备可读性与可操作性。理解并善用这套机制(错误基类、工具错误、校验错误、连接请求错误),再配合"检查result.successful+ 全局分类处理 + 合理超时重试"的组合拳,就能显著提升 Agent 应用在生产环境中的稳定性与可维护性。
【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考