diff --git a/apps/x/apps/main/src/composio-handler.ts b/apps/x/apps/main/src/composio-handler.ts index e5b25d1a..731492a0 100644 --- a/apps/x/apps/main/src/composio-handler.ts +++ b/apps/x/apps/main/src/composio-handler.ts @@ -2,7 +2,8 @@ import { shell, BrowserWindow } from 'electron'; import { createAuthServer } from './auth-server.js'; import * as composioClient from '@x/core/dist/composio/client.js'; import { composioAccountsRepo } from '@x/core/dist/composio/repo.js'; -import type { LocalConnectedAccount } from '@x/core/dist/composio/types.js'; +import type { LocalConnectedAccount, ZExecuteActionResponse } from '@x/core/dist/composio/types.js'; +import { z } from 'zod'; const REDIRECT_URI = 'http://localhost:8081/oauth/callback'; @@ -28,8 +29,8 @@ export function emitComposioEvent(event: { toolkitSlug: string; success: boolean /** * Check if Composio is configured with an API key */ -export function isConfigured(): { configured: boolean } { - return { configured: composioClient.isConfigured() }; +export async function isConfigured(): Promise<{ configured: boolean }> { + return { configured: await composioClient.isConfigured() }; } /** @@ -272,23 +273,28 @@ export async function executeAction( actionSlug: string, toolkitSlug: string, input: Record -): Promise<{ success: boolean; data: unknown; error?: string }> { +): Promise> { try { const account = composioAccountsRepo.getAccount(toolkitSlug); if (!account || account.status !== 'ACTIVE') { return { - success: false, data: null, + successful: false, error: `Toolkit ${toolkitSlug} is not connected`, }; } - const result = await composioClient.executeAction(actionSlug, account.id, input); + const result = await composioClient.executeAction(actionSlug, { + connected_account_id: account.id, + user_id: 'rowboat-user', + version: 'latest', + arguments: input, + }); return result; } catch (error) { console.error('[Composio] Action execution failed:', error); return { - success: false, + successful: false, data: null, error: error instanceof Error ? error.message : 'Unknown error', }; diff --git a/apps/x/packages/core/src/composio/client.ts b/apps/x/packages/core/src/composio/client.ts index 070b4642..452041db 100644 --- a/apps/x/packages/core/src/composio/client.ts +++ b/apps/x/packages/core/src/composio/client.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import fs from "fs"; import path from "path"; -import { Composio } from "@composio/core"; import { WorkDir } from "../config/config.js"; import { ZAuthConfig, @@ -12,33 +11,36 @@ import { ZCreateConnectedAccountResponse, ZDeleteOperationResponse, ZErrorResponse, + ZExecuteActionRequest, ZExecuteActionResponse, ZListResponse, + ZTool, ZToolkit, } from "./types.js"; +import { isSignedIn } from "../account/account.js"; +import { getAccessToken } from "../auth/tokens.js"; +import { API_URL } from "../config/env.js"; -const BASE_URL = 'https://backend.composio.dev/api/v3'; +const COMPOSIO_BASE_URL = 'https://backend.composio.dev/api/v3'; const CONFIG_FILE = path.join(WorkDir, 'config', 'composio.json'); -// Composio SDK client (lazily initialized) -let composioClient: Composio | null = null; - -function getComposioClient(): Composio { - if (composioClient) { - return composioClient; +async function getBaseUrl(): Promise { + if (await isSignedIn()) { + return `${API_URL}/v1/composio`; } + return COMPOSIO_BASE_URL; +} +async function getAuthHeaders(): Promise> { + if (await isSignedIn()) { + const token = await getAccessToken(); + return { 'Authorization': `Bearer ${token}` }; + } const apiKey = getApiKey(); if (!apiKey) { throw new Error('Composio API key not configured'); } - - composioClient = new Composio({ apiKey }); - return composioClient; -} - -function resetComposioClient(): void { - composioClient = null; + return { 'x-api-key': apiKey }; } /** @@ -91,13 +93,13 @@ export function setApiKey(apiKey: string): void { const config = loadConfig(); config.apiKey = apiKey; saveConfig(config); - resetComposioClient(); } /** * Check if Composio is configured */ -export function isConfigured(): boolean { +export async function isConfigured(): Promise { + if (await isSignedIn()) return true; return !!getApiKey(); } @@ -106,23 +108,25 @@ export function isConfigured(): boolean { */ export async function composioApiCall( schema: T, - url: string, + path: string, + params: Record = {}, options: RequestInit = {}, ): Promise> { - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error('Composio API key not configured'); - } + const authHeaders = await getAuthHeaders(); + const baseURL = await getBaseUrl(); + const url = new URL(path, baseURL); console.log(`[Composio] ${options.method || 'GET'} ${url}`); const startTime = Date.now(); try { + Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value)); + const response = await fetch(url, { ...options, headers: { ...options.headers, - "x-api-key": apiKey, + ...authHeaders, ...(options.method === 'POST' ? { "Content-Type": "application/json" } : {}), }, }); @@ -174,47 +178,20 @@ export async function composioApiCall( * List available toolkits */ export async function listToolkits(cursor: string | null = null): Promise>>> { - const url = new URL(`${BASE_URL}/toolkits`); - url.searchParams.set("sort_by", "usage"); + const params: Record = { + sort_by: "usage", + }; if (cursor) { - url.searchParams.set("cursor", cursor); + params.cursor = cursor; } - return composioApiCall(ZListResponse(ZToolkit), url.toString()); + return composioApiCall(ZListResponse(ZToolkit), "/toolkits", params); } /** * Get a specific toolkit */ export async function getToolkit(toolkitSlug: string): Promise> { - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error('Composio API key not configured'); - } - - const url = `${BASE_URL}/toolkits/${toolkitSlug}`; - console.log(`[Composio] GET ${url}`); - - const response = await fetch(url, { - headers: { "x-api-key": apiKey }, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch toolkit: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - - const no_auth = data.composio_managed_auth_schemes?.includes('NO_AUTH') || - data.auth_config_details?.some((config: { mode: string }) => config.mode === 'NO_AUTH') || - false; - - return ZToolkit.parse({ - ...data, - no_auth, - meta: data.meta || { description: '', logo: '', tools_count: 0, triggers_count: 0 }, - auth_schemes: data.auth_schemes || [], - composio_managed_auth_schemes: data.composio_managed_auth_schemes || [], - }); + return composioApiCall(ZToolkit, `/toolkits/${toolkitSlug}`); } /** @@ -225,15 +202,16 @@ export async function listAuthConfigs( cursor: string | null = null, managedOnly: boolean = false ): Promise>>> { - const url = new URL(`${BASE_URL}/auth_configs`); - url.searchParams.set("toolkit_slug", toolkitSlug); + const params: Record = { + toolkit_slug: toolkitSlug, + }; if (cursor) { - url.searchParams.set("cursor", cursor); + params.cursor = cursor; } if (managedOnly) { - url.searchParams.set("is_composio_managed", "true"); + params.is_composio_managed = "true"; } - return composioApiCall(ZListResponse(ZAuthConfig), url.toString()); + return composioApiCall(ZListResponse(ZAuthConfig), "/auth_configs", params); } /** @@ -242,8 +220,7 @@ export async function listAuthConfigs( export async function createAuthConfig( request: z.infer ): Promise> { - const url = new URL(`${BASE_URL}/auth_configs`); - return composioApiCall(ZCreateAuthConfigResponse, url.toString(), { + return composioApiCall(ZCreateAuthConfigResponse, "/auth_configs", {}, { method: 'POST', body: JSON.stringify(request), }); @@ -253,8 +230,7 @@ export async function createAuthConfig( * Delete an auth config */ export async function deleteAuthConfig(authConfigId: string): Promise> { - const url = new URL(`${BASE_URL}/auth_configs/${authConfigId}`); - return composioApiCall(ZDeleteOperationResponse, url.toString(), { + return composioApiCall(ZDeleteOperationResponse, `/auth_configs/${authConfigId}`, {}, { method: 'DELETE', }); } @@ -265,8 +241,7 @@ export async function deleteAuthConfig(authConfigId: string): Promise ): Promise> { - const url = new URL(`${BASE_URL}/connected_accounts`); - return composioApiCall(ZCreateConnectedAccountResponse, url.toString(), { + return composioApiCall(ZCreateConnectedAccountResponse, "/connected_accounts", {}, { method: 'POST', body: JSON.stringify(request), }); @@ -276,16 +251,14 @@ export async function createConnectedAccount( * Get a connected account */ export async function getConnectedAccount(connectedAccountId: string): Promise> { - const url = new URL(`${BASE_URL}/connected_accounts/${connectedAccountId}`); - return composioApiCall(ZConnectedAccount, url.toString()); + return composioApiCall(ZConnectedAccount, `/connected_accounts/${connectedAccountId}`); } /** * Delete a connected account */ export async function deleteConnectedAccount(connectedAccountId: string): Promise> { - const url = new URL(`${BASE_URL}/connected_accounts/${connectedAccountId}`); - return composioApiCall(ZDeleteOperationResponse, url.toString(), { + return composioApiCall(ZDeleteOperationResponse, `/connected_accounts/${connectedAccountId}`, {}, { method: 'DELETE', }); } @@ -296,64 +269,26 @@ export async function deleteConnectedAccount(connectedAccountId: string): Promis export async function listToolkitTools( toolkitSlug: string, searchQuery: string | null = null, -): Promise<{ items: Array<{ slug: string; name: string; description: string }> }> { - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error('Composio API key not configured'); - } - - const url = new URL(`${BASE_URL}/tools`); - url.searchParams.set('toolkit_slug', toolkitSlug); - url.searchParams.set('limit', '200'); - if (searchQuery) { - url.searchParams.set('search', searchQuery); - } - - console.log(`[Composio] Listing tools for toolkit: ${toolkitSlug}`); - - const response = await fetch(url.toString(), { - headers: { "x-api-key": apiKey }, - }); - - if (!response.ok) { - throw new Error(`Failed to list tools: ${response.status} ${response.statusText}`); - } - - const data = await response.json() as { items?: Array> }; - - return { - items: (data.items || []).map((item) => ({ - slug: String(item.slug ?? ''), - name: String(item.name ?? ''), - description: String(item.description ?? ''), - })), +): Promise>>> { + const params: Record = { + toolkit_slug: toolkitSlug, + limit: '200', }; + if (searchQuery) { + params.search = searchQuery; + } + return composioApiCall(ZListResponse(ZTool), "/tools", params); } /** - * Execute a tool action using Composio SDK + * Execute a tool action */ export async function executeAction( actionSlug: string, - connectedAccountId: string, - input: Record + request: z.infer ): Promise> { - console.log(`[Composio] Executing action: ${actionSlug} (account: ${connectedAccountId})`); - - try { - const client = getComposioClient(); - const result = await client.tools.execute(actionSlug, { - userId: 'rowboat-user', - arguments: input, - connectedAccountId, - dangerouslySkipVersionCheck: true, - }); - - console.log(`[Composio] Action completed successfully`); - return { success: true, data: result.data }; - } catch (error) { - console.error(`[Composio] Action execution failed:`, JSON.stringify(error, Object.getOwnPropertyNames(error ?? {}), 2)); - const message = error instanceof Error ? error.message : (typeof error === 'object' ? JSON.stringify(error) : 'Unknown error'); - return { success: false, data: null, error: message }; - } + return composioApiCall(ZExecuteActionResponse, `/tools/execute/${actionSlug}`, {}, { + method: 'POST', + body: JSON.stringify(request), + }); } diff --git a/apps/x/packages/core/src/composio/types.ts b/apps/x/packages/core/src/composio/types.ts index e2cbaf56..f79961d2 100644 --- a/apps/x/packages/core/src/composio/types.ts +++ b/apps/x/packages/core/src/composio/types.ts @@ -200,18 +200,19 @@ export const ZListResponse = (schema: T) => z.object({ * Execute action request */ export const ZExecuteActionRequest = z.object({ - action: z.string(), connected_account_id: z.string(), - input: z.record(z.string(), z.unknown()), + user_id: z.string(), + version: z.string(), + arguments: z.any().optional(), }); /** * Execute action response */ export const ZExecuteActionResponse = z.object({ - success: z.boolean(), data: z.unknown(), - error: z.string().optional(), + successful: z.boolean(), + error: z.string().nullable(), }); /** diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 4d7e6492..f6f5c0ee 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -375,9 +375,9 @@ const ipcSchemas = { input: z.record(z.string(), z.unknown()), }), res: z.object({ - success: z.boolean(), data: z.unknown(), - error: z.string().optional(), + successful: z.boolean(), + error: z.string().nullable(), }), }, 'composio:didConnect': {