Integrate Composio for external tool access

Agents can now call external tools (GitHub, Slack, Gmail, etc.) via
Composio's managed API. A thin HTTP client calls the Composio REST API
directly — no heavy SDK dependency. External tools are loaded per-org
from composio_settings and merged with internal tools at run time. The
agent run loop routes composio_* tool calls to the Composio execution
endpoint while internal tools dispatch as before.

Includes: migration 043 for composio_settings table, IPC handler for
saving Composio API key and toolkit configuration, and the composio
client/bridge modules.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-30 21:31:04 -04:00
1 parent 65c5bec9a3
commit 77f191fa25
12 files changed
+263 -10

No files matched your search

@@ -1,7 +1,8 @@
import type { ProviderService } from "./provider-service";
import { AGENT_TOOLS, dispatchAgentTool, type AgentToolContext, type AgentToolDeps } from "./agent-tools";
import { canonicalMentionHandle } from "../../shared/mention-handles";
import type { AgentTurnMessage } from "./provider-tool-turn";
import type { AgentTurnMessage, ToolDefinition } from "./provider-tool-turn";
import { isComposioTool, executeComposioTool } from "./composio/composio-tool-bridge";
const MAX_ITERATIONS = 12;
@@ -12,6 +13,7 @@ export interface AgentRunLoopInput {
instructions: string;
initialUserTurn: string;
toolContext: AgentToolContext;
externalTools?: ToolDefinition[];
}
export interface AgentRunLoopResult {
@@ -35,6 +37,7 @@ export interface AgentRunLoopDeps {
* iteration cap is hit (which throws rather than silently truncating).
*/
export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopInput): Promise<AgentRunLoopResult> {
const allTools = input.externalTools ? [...AGENT_TOOLS, ...input.externalTools] : AGENT_TOOLS;
const messages: AgentTurnMessage[] = [{ role: "user", text: input.initialUserTurn }];
let toolCallCount = 0;
let lastRequestId = "";
@@ -46,7 +49,7 @@ export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopIn
workspaceId: input.workspaceId,
instructions: input.instructions,
messages,
tools: AGENT_TOOLS,
tools: allTools,
});
lastRequestId = turn.requestId;
@@ -61,16 +64,19 @@ export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopIn
}
messages.push({ role: "assistant", text: turn.text, toolCalls: turn.toolCalls });
const results = turn.toolCalls.map((call) => {
const results: Array<{ toolCallId: string; toolName: string; output: string }> = [];
for (const call of turn.toolCalls) {
toolCallCount += 1;
return {
toolCallId: call.id,
toolName: call.name,
output: dispatchAgentTool(deps.tools, input.toolContext, call, {
let output: string;
if (isComposioTool(call.name)) {
output = await executeComposioTool(deps.tools.db, input.orgId, call);
} else {
output = dispatchAgentTool(deps.tools, input.toolContext, call, {
announceTaskAssignments: shouldAnnounceTaskAssignment(call, turn.toolCalls),
}),
};
});
});
}
results.push({ toolCallId: call.id, toolName: call.name, output });
}
messages.push({ role: "tool", results });
deps.onToolResults?.();
}