Refactor Composio connection handling and improve tool display logic

- Removed the connection bridge for Composio toolkit OAuth, simplifying the connection process.
- Updated ComposioConnectCard to display a more user-friendly connection message.
- Introduced a new utility function, getToolDisplayName, to provide human-friendly names for builtin tools.
- Refactored App and ChatSidebar components to utilize the new getToolDisplayName function for improved clarity in tool titles.
- Cleaned up imports and removed unused code for better maintainability.
This commit is contained in:
tusharmagar 2026-04-02 16:37:25 +05:30
parent 587778cb07
commit fd1632ea5c
7 changed files with 65 additions and 79 deletions

View file

@ -34,7 +34,6 @@ import { triggerSync as triggerGranolaSync } from '@x/core/dist/knowledge/granol
import { ISlackConfigRepo } from '@x/core/dist/slack/repo.js';
import { isOnboardingComplete, markOnboardingComplete } from '@x/core/dist/config/note_creation_config.js';
import * as composioHandler from './composio-handler.js';
import { setConnectionInitiator } from '@x/core/dist/composio/connection-bridge.js';
import { IAgentScheduleRepo } from '@x/core/dist/agent-schedule/repo.js';
import { IAgentScheduleStateRepo } from '@x/core/dist/agent-schedule/state-repo.js';
import { triggerRun as triggerAgentScheduleRun } from '@x/core/dist/agent-schedule/runner.js';
@ -377,9 +376,6 @@ export function setupIpcHandlers() {
// Forward knowledge commit events to renderer for panel refresh
versionHistory.onCommit(() => emitKnowledgeCommitEvent());
// Wire the connection bridge so builtin tools (in core) can trigger OAuth (in main)
setConnectionInitiator(composioHandler.initiateConnection);
registerIpcHandlers({
'app:getVersions': async () => {
// args is null for this channel (no request payload)

View file

@ -69,7 +69,7 @@ import {
getWebSearchCardData,
getAppActionCardData,
getComposioConnectCardData,
getComposioActionCardData,
getToolDisplayName,
inferRunTitleFromMessage,
isChatMessage,
isErrorMessage,
@ -3849,8 +3849,7 @@ function App() {
/>
)
}
const composioActionData = getComposioActionCardData(item)
const toolTitle = composioActionData ? composioActionData.label : item.name
const toolTitle = getToolDisplayName(item)
const errorText = item.status === 'error' ? 'Tool error' : ''
const output = normalizeToolOutput(item.result, item.status)
const input = normalizeToolInput(item.input)

View file

@ -5,7 +5,6 @@ import {
CheckCircleIcon,
Link2Icon,
LoaderIcon,
WrenchIcon,
XCircleIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
@ -67,18 +66,21 @@ export function ComposioConnectCard({
}, [toolkitSlug]);
const isToolRunning = status === "pending" || status === "running";
const displayName = toolkitDisplayName || toolkitSlug;
return (
<div className="not-prose mb-4 flex items-center gap-3 rounded-lg border px-3 py-2.5">
{/* Toolkit initial */}
<div className="size-7 rounded bg-muted flex items-center justify-center flex-shrink-0">
<WrenchIcon className="size-3.5 text-muted-foreground" />
<span className="text-xs font-bold text-muted-foreground">
{displayName.charAt(0).toUpperCase()}
</span>
</div>
{/* Name & status */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-sm font-medium truncate">
{toolkitDisplayName || toolkitSlug}
</span>
<span className="text-sm font-medium truncate">{displayName}</span>
{connectionState === "connected" && (
<span className="rounded-full bg-green-500/10 px-1.5 py-0.5 text-[10px] font-medium leading-none text-green-600">
Connected
@ -93,6 +95,7 @@ export function ComposioConnectCard({
)}
</div>
{/* Action area */}
{connectionState === "connected" ? (
<CheckCircleIcon className="size-4 text-green-600 flex-shrink-0" />
) : connectionState === "connecting" ? (

View file

@ -36,7 +36,7 @@ import {
createEmptyChatTabViewState,
getWebSearchCardData,
getComposioConnectCardData,
getComposioActionCardData,
getToolDisplayName,
isChatMessage,
isErrorMessage,
isToolCall,
@ -355,8 +355,7 @@ export function ChatSidebar({
/>
)
}
const composioActionData = getComposioActionCardData(item)
const toolTitle = composioActionData ? composioActionData.label : item.name
const toolTitle = getToolDisplayName(item)
const errorText = item.status === 'error' ? 'Tool error' : ''
const output = normalizeToolOutput(item.result, item.status)
const input = normalizeToolInput(item.input)

View file

@ -278,6 +278,51 @@ export const getComposioConnectCardData = (tool: ToolCall): ComposioConnectCardD
}
}
// Human-friendly display names for builtin tools
const TOOL_DISPLAY_NAMES: Record<string, string> = {
'workspace-readFile': 'Reading file',
'workspace-writeFile': 'Writing file',
'workspace-edit': 'Editing file',
'workspace-readdir': 'Reading directory',
'workspace-exists': 'Checking path',
'workspace-stat': 'Getting file info',
'workspace-glob': 'Finding files',
'workspace-grep': 'Searching files',
'workspace-mkdir': 'Creating directory',
'workspace-rename': 'Renaming',
'workspace-copy': 'Copying file',
'workspace-remove': 'Removing',
'workspace-getRoot': 'Getting workspace root',
'loadSkill': 'Loading skill',
'parseFile': 'Parsing file',
'LLMParse': 'Extracting content',
'analyzeAgent': 'Analyzing agent',
'executeCommand': 'Running command',
'addMcpServer': 'Adding MCP server',
'listMcpServers': 'Listing MCP servers',
'listMcpTools': 'Listing MCP tools',
'executeMcpTool': 'Running MCP tool',
'web-search': 'Searching the web',
'save-to-memory': 'Saving to memory',
'app-navigation': 'Navigating app',
'composio-list-toolkits': 'Listing integrations',
'composio-search-tools': 'Searching tools',
'composio-execute-tool': 'Running tool',
'composio-connect-toolkit': 'Connecting service',
}
/**
* Get a human-friendly display name for a tool call.
* For Composio tools, returns a contextual label (e.g., "Found 3 tools for 'send email' in Gmail").
* For builtin tools, returns a static friendly name (e.g., "Reading file").
* Falls back to the raw tool name if no mapping exists.
*/
export const getToolDisplayName = (tool: ToolCall): string => {
const composioData = getComposioActionCardData(tool)
if (composioData) return composioData.label
return TOOL_DISPLAY_NAMES[tool.name] || tool.name
}
// Composio action card data (for search, execute, list tools)
export type ComposioActionCardData = {
actionType: 'search' | 'execute' | 'list'

View file

@ -15,7 +15,6 @@ import { WorkDir } from "../../config/config.js";
import { composioAccountsRepo } from "../../composio/repo.js";
import { executeAction as executeComposioAction, isConfigured as isComposioConfigured, searchTools as searchComposioTools } from "../../composio/client.js";
import { CURATED_TOOLKITS, CURATED_TOOLKIT_SLUGS } from "@x/shared/dist/composio.js";
import { getConnectionInitiator } from "../../composio/connection-bridge.js";
import type { ToolContext } from "./exec-tool.js";
import { generateText } from "ai";
import { createProvider } from "../../models/models.js";
@ -1292,7 +1291,7 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
},
'composio-connect-toolkit': {
description: 'Connect a Composio service (Gmail, Slack, GitHub, etc.) via OAuth. Opens the user\'s browser for authentication. After authenticating, the user can use tools from that service.',
description: 'Connect a Composio service (Gmail, Slack, GitHub, etc.) via OAuth. Shows a connect card for the user to authenticate.',
inputSchema: z.object({
toolkitSlug: z.string().describe('The toolkit slug to connect (e.g., "gmail", "github", "slack", "notion")'),
}),
@ -1315,35 +1314,13 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
};
}
// Use the connection bridge to trigger OAuth
const initiator = getConnectionInitiator();
if (!initiator) {
return {
success: false,
error: 'Connection system not available. Please try connecting via Settings > Tools Library instead.',
};
}
try {
const result = await initiator(toolkitSlug);
if (result.success) {
const toolkit = CURATED_TOOLKITS.find(t => t.slug === toolkitSlug);
return {
success: true,
message: `Opening browser to authenticate with ${toolkit?.displayName ?? toolkitSlug}. Please complete the authentication in your browser, then let me know when you're done.`,
};
}
return {
success: false,
error: result.error || 'Failed to initiate connection',
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Connection failed: ${message}`,
};
}
// Return signal — the UI renders a ComposioConnectCard with a Connect button.
// OAuth only starts when the user clicks that button.
const toolkit = CURATED_TOOLKITS.find(t => t.slug === toolkitSlug);
return {
success: true,
message: `Please connect ${toolkit?.displayName ?? toolkitSlug} to continue.`,
};
},
isAvailable: async () => isComposioConfigured(),
},

View file

@ -1,33 +0,0 @@
/**
* Connection bridge for Composio toolkit OAuth.
*
* Builtin tools run in the core package which cannot import Electron-specific
* code from the main process. This module provides a callback registry so the
* main process can register its `initiateConnection` function at startup, and
* builtin tools can call it at runtime.
*/
type ConnectionInitiator = (toolkitSlug: string) => Promise<{
success: boolean;
redirectUrl?: string;
connectedAccountId?: string;
error?: string;
}>;
let connectionInitiator: ConnectionInitiator | null = null;
/**
* Register the connection initiator callback.
* Called once by the main process at startup.
*/
export function setConnectionInitiator(fn: ConnectionInitiator): void {
connectionInitiator = fn;
}
/**
* Get the registered connection initiator.
* Returns null if not yet registered (app not fully initialized).
*/
export function getConnectionInitiator(): ConnectionInitiator | null {
return connectionInitiator;
}