mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-26 08:56:22 +02:00
support managed composio
This commit is contained in:
parent
d0a48d7f51
commit
d2bb11f104
4 changed files with 78 additions and 136 deletions
|
|
@ -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<string> {
|
||||
if (await isSignedIn()) {
|
||||
return `${API_URL}/v1/composio`;
|
||||
}
|
||||
return COMPOSIO_BASE_URL;
|
||||
}
|
||||
|
||||
async function getAuthHeaders(): Promise<Record<string, string>> {
|
||||
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<boolean> {
|
||||
if (await isSignedIn()) return true;
|
||||
return !!getApiKey();
|
||||
}
|
||||
|
||||
|
|
@ -106,23 +108,25 @@ export function isConfigured(): boolean {
|
|||
*/
|
||||
export async function composioApiCall<T extends z.ZodTypeAny>(
|
||||
schema: T,
|
||||
url: string,
|
||||
path: string,
|
||||
params: Record<string, string> = {},
|
||||
options: RequestInit = {},
|
||||
): Promise<z.infer<T>> {
|
||||
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<T extends z.ZodTypeAny>(
|
|||
* List available toolkits
|
||||
*/
|
||||
export async function listToolkits(cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZToolkit>>>> {
|
||||
const url = new URL(`${BASE_URL}/toolkits`);
|
||||
url.searchParams.set("sort_by", "usage");
|
||||
const params: Record<string, string> = {
|
||||
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<z.infer<typeof ZToolkit>> {
|
||||
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<z.infer<ReturnType<typeof ZListResponse<typeof ZAuthConfig>>>> {
|
||||
const url = new URL(`${BASE_URL}/auth_configs`);
|
||||
url.searchParams.set("toolkit_slug", toolkitSlug);
|
||||
const params: Record<string, string> = {
|
||||
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<typeof ZCreateAuthConfigRequest>
|
||||
): Promise<z.infer<typeof ZCreateAuthConfigResponse>> {
|
||||
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<z.infer<typeof ZDeleteOperationResponse>> {
|
||||
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<z.infer<ty
|
|||
export async function createConnectedAccount(
|
||||
request: z.infer<typeof ZCreateConnectedAccountRequest>
|
||||
): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
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<z.infer<typeof ZConnectedAccount>> {
|
||||
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<z.infer<typeof ZDeleteOperationResponse>> {
|
||||
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<Record<string, unknown>> };
|
||||
|
||||
return {
|
||||
items: (data.items || []).map((item) => ({
|
||||
slug: String(item.slug ?? ''),
|
||||
name: String(item.name ?? ''),
|
||||
description: String(item.description ?? ''),
|
||||
})),
|
||||
): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
const params: Record<string, string> = {
|
||||
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<string, unknown>
|
||||
request: z.infer<typeof ZExecuteActionRequest>
|
||||
): Promise<z.infer<typeof ZExecuteActionResponse>> {
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,18 +200,19 @@ export const ZListResponse = <T extends z.ZodTypeAny>(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(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue