Cache Composio tools and load saved settings in the UI
Tools are now cached per-org with a 5-minute TTL to avoid hitting the Composio API on every agent run. The ComposioPanel loads the currently saved API key status and pre-checks enabled toolkits from the snapshot, so users see their existing configuration when opening Settings > Tools.
This commit is contained in:
1 parent
3d685e5a4b
commit
e83f379143
5 files changed
+59
-7
No files matched your search
@@ -3,6 +3,15 @@ import type { ToolDefinition, ToolCall } from "../provider-tool-turn";
|
||||
import { ComposioClient, type ComposioToolSchema } from "./composio-client";
|
||||
|
||||
const COMPOSIO_TOOL_PREFIX = "composio_";
|
||||
const CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
interface CacheEntry {
|
||||
tools: ToolDefinition[];
|
||||
fetchedAt: number;
|
||||
toolkitsKey: string;
|
||||
}
|
||||
|
||||
const toolCache = new Map<string, CacheEntry>();
|
||||
|
||||
export function isComposioTool(toolName: string): boolean {
|
||||
return toolName.startsWith(COMPOSIO_TOOL_PREFIX);
|
||||
@@ -34,9 +43,16 @@ export async function loadComposioTools(db: DatabaseSync, orgId: string): Promis
|
||||
if (!apiKey) return [];
|
||||
const toolkits = getEnabledToolkits(db, orgId);
|
||||
if (toolkits.length === 0) return [];
|
||||
const toolkitsKey = toolkits.sort().join(",");
|
||||
const cached = toolCache.get(orgId);
|
||||
if (cached && cached.toolkitsKey === toolkitsKey && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
||||
return cached.tools;
|
||||
}
|
||||
const client = new ComposioClient(apiKey);
|
||||
const schemas = await client.getTools(toolkits);
|
||||
return schemas.map(composioSchemaToToolDefinition);
|
||||
const tools = schemas.map(composioSchemaToToolDefinition);
|
||||
toolCache.set(orgId, { tools, fetchedAt: Date.now(), toolkitsKey });
|
||||
return tools;
|
||||
}
|
||||
|
||||
export async function executeComposioTool(
|
||||
|
||||
@@ -162,6 +162,7 @@ export class SnapshotService {
|
||||
.map((rule) => ({ ...rule, enabled: toBool(rule.enabled) })),
|
||||
meetings: list(this.db, "meetings", "scheduled_at desc"),
|
||||
sprints: list(this.db, "sprints", "start_date desc"),
|
||||
composioSettings: list(this.db, "composio_settings", "org_id asc, key asc"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{tab === "tools" && <ComposioPanel orgId={orgId} actions={actions} />}
|
||||
{tab === "tools" && <ComposioPanel orgId={orgId} settings={snapshot.composioSettings} actions={actions} />}
|
||||
{tab === "skills" && (
|
||||
<SkillsPanel
|
||||
orgId={orgId}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { useMemo, useState, type FormEvent } from "react";
|
||||
import { Button } from "../../shared/ui";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain";
|
||||
|
||||
const AVAILABLE_TOOLKITS = [
|
||||
{ id: "github", label: "GitHub" },
|
||||
@@ -25,15 +26,35 @@ const AVAILABLE_TOOLKITS = [
|
||||
{ id: "airtable", label: "Airtable" },
|
||||
];
|
||||
|
||||
function parseSavedToolkits(settings: ComposioSettingRecord[], orgId: string): Set<string> {
|
||||
const row = settings.find((s) => s.orgId === orgId && s.key === "toolkits");
|
||||
if (!row?.value) return new Set();
|
||||
try {
|
||||
const parsed = JSON.parse(row.value);
|
||||
return new Set(Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === "string") : []);
|
||||
} catch {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
function hasSavedKey(settings: ComposioSettingRecord[], orgId: string): boolean {
|
||||
return settings.some((s) => s.orgId === orgId && s.key === "api_key" && s.value.length > 0);
|
||||
}
|
||||
|
||||
export function ComposioPanel({
|
||||
orgId,
|
||||
settings,
|
||||
actions,
|
||||
}: {
|
||||
orgId: string;
|
||||
settings: ComposioSettingRecord[];
|
||||
actions: SnapshotActions;
|
||||
}) {
|
||||
const savedToolkits = useMemo(() => parseSavedToolkits(settings, orgId), [settings, orgId]);
|
||||
const keyConfigured = useMemo(() => hasSavedKey(settings, orgId), [settings, orgId]);
|
||||
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [selected, setSelected] = useState<Set<string>>(savedToolkits);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
const toggle = (id: string) => {
|
||||
@@ -47,12 +68,17 @@ export function ComposioPanel({
|
||||
|
||||
const handleSave = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!apiKey.trim()) return;
|
||||
const key = apiKey.trim();
|
||||
if (!key && !keyConfigured) return;
|
||||
const row = settings.find((s) => s.orgId === orgId && s.key === "api_key");
|
||||
const effectiveKey = key || row?.value || "";
|
||||
if (!effectiveKey) return;
|
||||
await actions.run(() => window.agentCompany.saveComposioSettings({
|
||||
orgId,
|
||||
apiKey: apiKey.trim(),
|
||||
apiKey: effectiveKey,
|
||||
toolkits: [...selected],
|
||||
}));
|
||||
setApiKey("");
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
};
|
||||
@@ -71,6 +97,7 @@ export function ComposioPanel({
|
||||
className="input"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder={keyConfigured ? "••••••••" : undefined}
|
||||
autoComplete="off"
|
||||
style={{ width: "100%", maxWidth: 400 }}
|
||||
/>
|
||||
@@ -109,7 +136,7 @@ export function ComposioPanel({
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<Button tone="primary" type="submit" disabled={!apiKey.trim()}>
|
||||
<Button tone="primary" type="submit" disabled={!apiKey.trim() && !keyConfigured}>
|
||||
Save
|
||||
</Button>
|
||||
{saved && <span style={{ fontSize: 13, color: "var(--green)" }}>Saved</span>}
|
||||
|
||||
@@ -138,4 +138,12 @@ export interface AppSnapshot {
|
||||
automationRules: AutomationRuleRecord[];
|
||||
meetings: MeetingRecord[];
|
||||
sprints: SprintRecord[];
|
||||
composioSettings: ComposioSettingRecord[];
|
||||
}
|
||||
|
||||
export interface ComposioSettingRecord {
|
||||
orgId: string;
|
||||
key: string;
|
||||
value: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
Reference in new issue
Block a user