evOo-local/packages/adapters/src/composio-catalog-cache.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

65 lines
2.0 KiB
TypeScript

export type ToolkitDirectoryEntry = {
slug: string;
name: string;
logo: string | null;
noAuth: boolean;
};
export type ToolkitCatalogEntry = ToolkitDirectoryEntry & { connected: boolean };
export const COMPOSIO_DIRECTORY_TTL_MS = 60 * 60 * 1000;
export function mergeCatalogWithConnected(
directory: ToolkitDirectoryEntry[],
connectedSlugs: Iterable<string>,
): ToolkitCatalogEntry[] {
const connected = new Set([...connectedSlugs].map((slug) => slug.trim().toLowerCase()));
return directory.map((item) => ({
...item,
connected: connected.has(item.slug.trim().toLowerCase()),
}));
}
/**
* Coalesces directory loads and serves stale entries during refreshes. Failed
* refreshes preserve stale entries for later retries; cold-load errors propagate.
*/
export function createToolkitDirectoryCache(opts?: { ttlMs?: number; now?: () => number }) {
const ttlMs = opts?.ttlMs ?? COMPOSIO_DIRECTORY_TTL_MS;
const now = opts?.now ?? Date.now;
let entry: { items: ToolkitDirectoryEntry[]; fetchedAt: number } | undefined;
let inflight: Promise<ToolkitDirectoryEntry[]> | undefined;
async function load(loader: () => Promise<ToolkitDirectoryEntry[]>) {
inflight ??= loader()
.then((items) => {
entry = { items, fetchedAt: now() };
return items;
})
.finally(() => {
inflight = undefined;
});
return inflight;
}
return {
peek(): ToolkitDirectoryEntry[] | undefined {
return entry?.items;
},
async get(loader: () => Promise<ToolkitDirectoryEntry[]>): Promise<ToolkitDirectoryEntry[]> {
if (!entry) return load(loader);
if (now() - entry.fetchedAt < ttlMs) return entry.items;
if (!inflight) {
// Keep stale items on failure; load clears inflight so a later read can retry.
void load(loader).catch(() => undefined);
}
return entry.items;
},
invalidate() {
entry = undefined;
},
};
}
export const composioToolkitDirectory = createToolkitDirectoryCache();