const DEFAULT_COMPOSIO_API_BASE_URL = "https://backend.composio.dev/api/v3.1"; export class ComposioApiError extends Error { constructor(message: string, readonly status: number) { super(message); this.name = "ComposioApiError"; } } export type ComposioPage = { items: T[]; next_cursor?: string | null; total_pages?: number; current_page?: number; total_items?: number; }; export type ComposioToolkit = { slug: string; name: string; type?: string; auth_schemes?: string[]; composio_managed_auth_schemes?: string[]; no_auth?: boolean; auth_guide_url?: string | null; meta?: { description?: string | null; logo?: string | null; app_url?: string | null; tools_count?: number; triggers_count?: number; version?: string; [key: string]: unknown; }; [key: string]: unknown; }; export type ComposioAuthConfig = { id: string; uuid?: string; name?: string; auth_scheme: string; is_composio_managed: boolean; status?: string; toolkit: { slug: string; logo?: string | null; auth_guide_url?: string | null; auth_hint_url?: string | null }; [key: string]: unknown; }; export type ComposioConnectLink = { link_token: string; redirect_url: string; expires_at: string; connected_account_id?: string | null; [key: string]: unknown; }; export type ComposioConnectedAccount = { id: string; user_id: string; status: string; status_reason?: string | null; is_disabled?: boolean; toolkit: { slug: string }; auth_config: { id: string; auth_scheme: string; is_composio_managed: boolean; is_disabled?: boolean }; [key: string]: unknown; }; export type ComposioSession = { session_id: string; mcp: { type?: string; url: string; headers?: Record }; config?: Record; config_version?: number; warnings?: Array<{ code: string; message: string }>; [key: string]: unknown; }; export type ComposioListOptions = { cursor?: string; limit?: number }; export type ComposioSessionOptions = { mcp: true; toolkits?: string[]; tools?: Record; authConfigs?: Record; connectedAccounts?: Record; }; export interface ComposioClient { validateApiKey(): Promise; listToolkits(options?: ComposioListOptions): Promise>; listAuthConfigs(options?: ComposioListOptions & { toolkitSlugs?: string[]; showDisabled?: boolean }): Promise>; createConnectLink(input: { authConfigId: string; userId: string; alias?: string; callbackUrl?: string }): Promise; listConnectedAccounts(options?: ComposioListOptions & { toolkitSlugs?: string[]; statuses?: string[]; userIds?: string[]; authConfigIds?: string[] }): Promise>; deleteConnectedAccount(connectedAccountId: string): Promise; createSession(userId: string, options: ComposioSessionOptions): Promise; resumeSession(sessionId: string, options: { mcp: true }): Promise; } type ComposioClientOptions = { apiKey: string; baseUrl?: string; fetch?: typeof globalThis.fetch }; function appendQuery(url: URL, key: string, value: string | number | boolean | undefined) { if (value !== undefined) url.searchParams.append(key, String(value)); } function appendQueryList(url: URL, key: string, values: string[] | undefined) { for (const value of values ?? []) url.searchParams.append(key, value); } function errorMessage(status: number): string { if (status === 401 || status === 403) return "Composio rejected the API key."; if (status === 429) return "Composio rate-limited the request. Try again shortly."; return `Composio request failed with HTTP ${status}.`; } export function createComposioClient(options: ComposioClientOptions): ComposioClient { const apiKey = options.apiKey.trim(); if (!apiKey) throw new Error("A Composio API key is required."); const baseUrl = (options.baseUrl ?? DEFAULT_COMPOSIO_API_BASE_URL).replace(/\/+$/, ""); const fetchImpl = options.fetch ?? globalThis.fetch; async function request(path: string, init: RequestInit = {}, query?: (url: URL) => void): Promise { const url = new URL(`${baseUrl}/${path.replace(/^\/+/, "")}`); query?.(url); let response: Response; try { response = await fetchImpl(url, { ...init, headers: { accept: "application/json", "x-api-key": apiKey, ...(init.body ? { "content-type": "application/json" } : {}), ...init.headers, }, }); } catch { throw new ComposioApiError("Could not reach Composio.", 0); } if (!response.ok) throw new ComposioApiError(errorMessage(response.status), response.status); try { return await response.json() as T; } catch { throw new ComposioApiError("Composio returned an invalid response.", response.status); } } return { async validateApiKey() { await this.listToolkits({ limit: 1 }); }, listToolkits(options = {}) { return request>("toolkits", {}, (url) => { appendQuery(url, "cursor", options.cursor); appendQuery(url, "limit", options.limit); }); }, listAuthConfigs(options = {}) { return request>("auth_configs", {}, (url) => { appendQuery(url, "cursor", options.cursor); appendQuery(url, "limit", options.limit); appendQuery(url, "show_disabled", options.showDisabled); appendQuery(url, "toolkit_slug", options.toolkitSlugs?.join(",")); }); }, createConnectLink(input) { return request("connected_accounts/link", { method: "POST", body: JSON.stringify({ auth_config_id: input.authConfigId, user_id: input.userId, ...(input.alias ? { alias: input.alias } : {}), ...(input.callbackUrl ? { callback_url: input.callbackUrl } : {}), }), }); }, listConnectedAccounts(options = {}) { return request>("connected_accounts", {}, (url) => { appendQuery(url, "cursor", options.cursor); appendQuery(url, "limit", options.limit); appendQueryList(url, "toolkit_slugs", options.toolkitSlugs); appendQueryList(url, "statuses", options.statuses); appendQueryList(url, "user_ids", options.userIds); appendQueryList(url, "auth_config_ids", options.authConfigIds); }); }, async deleteConnectedAccount(connectedAccountId) { const url = new URL(`${baseUrl}/connected_accounts/${encodeURIComponent(connectedAccountId)}`); let response: Response; try { response = await fetchImpl(url, { method: "DELETE", headers: { accept: "application/json", "x-api-key": apiKey }, }); } catch { throw new ComposioApiError("Could not reach Composio.", 0); } if (!response.ok) throw new ComposioApiError(errorMessage(response.status), response.status); }, createSession(userId, options) { return request("tool_router/session", { method: "POST", body: JSON.stringify({ user_id: userId, mcp: options.mcp, ...(options.toolkits ? { toolkits: { enabled: options.toolkits } } : {}), ...(options.tools ? { tools: options.tools } : {}), ...(options.authConfigs ? { auth_configs: options.authConfigs } : {}), ...(options.connectedAccounts ? { connected_accounts: options.connectedAccounts } : {}), }), }); }, resumeSession(sessionId, _options) { return request(`tool_router/session/${encodeURIComponent(sessionId)}/attach`, { method: "POST", body: JSON.stringify({}), }); }, }; }