Move Composio, logo.dev and Antigravity keys into Settings
The three third-party keys were hardcoded or read from the environment. Operators now enter them in Settings > Integrations; they are stored encrypted in the app-wide integration_keys table (migration 058). An agent holding connector grants fails loudly when no Composio key is saved. The goal-review test no longer depends on absolute dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1ea040c0f0
commit
56b0594f91
36 files changed
+430
-100
No files matched your search
@@ -1,14 +1,14 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { ConnectorCatalogEntry, ConnectComposioToolkitResult } from "../../../shared/commands";
|
||||
import { ComposioClient } from "./composio-client";
|
||||
import { composioApiKey } from "./composio-credentials";
|
||||
import { IntegrationKeyStore } from "../integration-key-store";
|
||||
import { getComposioUserId } from "./composio-tool-bridge";
|
||||
import { connectorDisplayName, connectorLogoUrl } from "./connector-brands";
|
||||
import { normalizeSlug } from "./connector-grants";
|
||||
|
||||
const CATALOG_TTL_MS = 10 * 60 * 1000;
|
||||
|
||||
let catalogCache: { fetchedAt: number; entries: ConnectorCatalogEntry[] } | null = null;
|
||||
let catalogCache: { fetchedAt: number; keys: string; entries: ConnectorCatalogEntry[] } | null = null;
|
||||
|
||||
/**
|
||||
* Company-level Composio connections. The catalog mirrors the auth configs
|
||||
@@ -25,7 +25,10 @@ let catalogCache: { fetchedAt: number; entries: ConnectorCatalogEntry[] } | null
|
||||
export class ComposioConnectionService {
|
||||
private static queues = new Map<string, Promise<unknown>>();
|
||||
|
||||
constructor(private readonly db: DatabaseSync) {}
|
||||
constructor(
|
||||
private readonly db: DatabaseSync,
|
||||
private readonly keys: IntegrationKeyStore = new IntegrationKeyStore(db),
|
||||
) {}
|
||||
|
||||
listCatalog(): Promise<ConnectorCatalogEntry[]> {
|
||||
return this.catalog(this.requireClient());
|
||||
@@ -149,13 +152,16 @@ export class ComposioConnectionService {
|
||||
}
|
||||
|
||||
private requireClient(): ComposioClient {
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) throw new Error("The Composio integration is not configured in this build.");
|
||||
const apiKey = this.keys.read("composio");
|
||||
if (!apiKey) throw new Error("No Composio API key is saved. Add one in Settings > Integrations.");
|
||||
return new ComposioClient(apiKey);
|
||||
}
|
||||
|
||||
private async catalog(client: ComposioClient): Promise<ConnectorCatalogEntry[]> {
|
||||
if (catalogCache && Date.now() - catalogCache.fetchedAt < CATALOG_TTL_MS) {
|
||||
const logoDevToken = this.keys.read("logo_dev");
|
||||
const keys = `${this.keys.read("composio")}
|
||||
${logoDevToken}`;
|
||||
if (catalogCache && catalogCache.keys === keys && Date.now() - catalogCache.fetchedAt < CATALOG_TTL_MS) {
|
||||
return catalogCache.entries;
|
||||
}
|
||||
const configs = await client.listAuthConfigs();
|
||||
@@ -167,11 +173,11 @@ export class ComposioConnectionService {
|
||||
slug: config.toolkitSlug,
|
||||
name: connectorDisplayName(config.toolkitSlug),
|
||||
authScheme: config.authScheme,
|
||||
logoUrl: connectorLogoUrl(config.toolkitSlug),
|
||||
logoUrl: connectorLogoUrl(config.toolkitSlug, logoDevToken),
|
||||
});
|
||||
}
|
||||
const entries = [...bySlug.values()].sort((a, b) => a.name.localeCompare(b.name));
|
||||
catalogCache = { fetchedAt: Date.now(), entries };
|
||||
catalogCache = { fetchedAt: Date.now(), keys, entries };
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* The Composio integration key is hardcoded on purpose: the repo is private
|
||||
* and every checkout must build a configured app, with no credential file or
|
||||
* build-time injection involved. Rotating the key = editing this constant.
|
||||
*/
|
||||
const COMPOSIO_API_KEY = "***REMOVED***";
|
||||
|
||||
export function composioApiKey(): string | null {
|
||||
return normalizeComposioApiKey(COMPOSIO_API_KEY);
|
||||
}
|
||||
|
||||
/** Null for blank input so callers surface "not configured" explicitly. */
|
||||
export function normalizeComposioApiKey(raw: string): string | null {
|
||||
const key = raw.trim();
|
||||
return key ? key : null;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { ToolDefinition, ToolCall } from "../provider-tool-turn";
|
||||
import { ComposioClient, type ComposioToolSchema } from "./composio-client";
|
||||
import { composioApiKey } from "./composio-credentials";
|
||||
import type { IntegrationKeyStore } from "../integration-key-store";
|
||||
import { ConnectorGrantsService } from "./connector-grants";
|
||||
|
||||
const COMPOSIO_TOOL_PREFIX = "composio_";
|
||||
@@ -52,14 +52,16 @@ export function composioSchemaToToolDefinition(schema: ComposioToolSchema): Tool
|
||||
* intersection of the company's active connections and the toolkits the CEO
|
||||
* granted this agent.
|
||||
*/
|
||||
export async function loadComposioTools(db: DatabaseSync, orgId: string, agentId: string): Promise<ToolDefinition[]> {
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) return [];
|
||||
export async function loadComposioTools(db: DatabaseSync, keys: IntegrationKeyStore, orgId: string, agentId: string): Promise<ToolDefinition[]> {
|
||||
const grants = new ConnectorGrantsService(db);
|
||||
const toolkits = grants.toolkitsForAgent(orgId, agentId);
|
||||
if (toolkits.length === 0) return [];
|
||||
const apiKey = keys.read("composio");
|
||||
if (!apiKey) {
|
||||
throw new Error(`Agent ${agentId} holds connector grants (${toolkits.join(", ")}) but no Composio API key is saved in Settings > Integrations.`);
|
||||
}
|
||||
const toolkitsKey = toolkits.join(",");
|
||||
const cacheKey = `${orgId}:${toolkitsKey}`;
|
||||
const cacheKey = `${apiKey}:${orgId}:${toolkitsKey}`;
|
||||
const cached = toolCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
||||
return cached.tools;
|
||||
@@ -80,12 +82,13 @@ export async function loadComposioTools(db: DatabaseSync, orgId: string, agentId
|
||||
|
||||
export async function executeComposioTool(
|
||||
db: DatabaseSync,
|
||||
keys: IntegrationKeyStore,
|
||||
orgId: string,
|
||||
agentId: string,
|
||||
call: ToolCall,
|
||||
): Promise<string> {
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) return "ERROR: The Composio integration is not configured in this build.";
|
||||
const apiKey = keys.read("composio");
|
||||
if (!apiKey) return "ERROR: No Composio API key is saved. Ask the board to add one in Settings > Integrations.";
|
||||
const toolSlug = originalToolName(call.name);
|
||||
const toolkit = toolkitForTool(toolSlug);
|
||||
const grants = new ConnectorGrantsService(db);
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
/**
|
||||
* Display names and logo domains for the toolkits configured in the Composio
|
||||
* project. Logos come from img.logo.dev keyed by company domain (publishable
|
||||
* token — safe to embed). A slug outside this table renders with a prettified
|
||||
* name and the letter tile until its brand row is added here.
|
||||
*/
|
||||
const LOGO_DEV_TOKEN = "***REMOVED***";
|
||||
|
||||
/** Toolkit display names and logo.dev domains. */
|
||||
const BRANDS: Record<string, { name: string; domain: string }> = {
|
||||
airtable: { name: "Airtable", domain: "airtable.com" },
|
||||
attio: { name: "Attio", domain: "attio.com" },
|
||||
@@ -60,8 +53,8 @@ export function connectorDisplayName(slug: string): string {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function connectorLogoUrl(slug: string): string | null {
|
||||
export function connectorLogoUrl(slug: string, logoDevToken: string | null): string | null {
|
||||
const brand = BRANDS[slug];
|
||||
if (!brand) return null;
|
||||
return `https://img.logo.dev/${brand.domain}?token=${LOGO_DEV_TOKEN}`;
|
||||
if (!brand || !logoDevToken) return null;
|
||||
return `https://img.logo.dev/${brand.domain}?token=${encodeURIComponent(logoDevToken)}`;
|
||||
}
|
||||
Reference in new issue
Block a user