import type { ServerAdapterModule } from "./types.js"; import { getAdapterSessionManagement } from "@paperclipai/adapter-utils"; import { execute as claudeExecute, listClaudeSkills, syncClaudeSkills, testEnvironment as claudeTestEnvironment, sessionCodec as claudeSessionCodec, getQuotaWindows as claudeGetQuotaWindows, } from "@paperclipai/adapter-claude-local/server"; import { agentConfigurationDoc as claudeAgentConfigurationDoc, models as claudeModels } from "@paperclipai/adapter-claude-local"; import { execute as codexExecute, listCodexSkills, syncCodexSkills, testEnvironment as codexTestEnvironment, sessionCodec as codexSessionCodec, getQuotaWindows as codexGetQuotaWindows, } from "@paperclipai/adapter-codex-local/server"; import { agentConfigurationDoc as codexAgentConfigurationDoc, models as codexModels } from "@paperclipai/adapter-codex-local"; import { execute as cursorExecute, listCursorSkills, syncCursorSkills, testEnvironment as cursorTestEnvironment, sessionCodec as cursorSessionCodec, } from "@paperclipai/adapter-cursor-local/server"; import { agentConfigurationDoc as cursorAgentConfigurationDoc, models as cursorModels } from "@paperclipai/adapter-cursor-local"; import { execute as geminiExecute, listGeminiSkills, syncGeminiSkills, testEnvironment as geminiTestEnvironment, sessionCodec as geminiSessionCodec, } from "@paperclipai/adapter-gemini-local/server"; import { agentConfigurationDoc as geminiAgentConfigurationDoc, models as geminiModels } from "@paperclipai/adapter-gemini-local"; import { execute as openCodeExecute, listOpenCodeSkills, syncOpenCodeSkills, testEnvironment as openCodeTestEnvironment, sessionCodec as openCodeSessionCodec, listOpenCodeModels, } from "@paperclipai/adapter-opencode-local/server"; import { agentConfigurationDoc as openCodeAgentConfigurationDoc, models as openCodeModels, } from "@paperclipai/adapter-opencode-local"; import { execute as openclawGatewayExecute, testEnvironment as openclawGatewayTestEnvironment, } from "@paperclipai/adapter-openclaw-gateway/server"; import { agentConfigurationDoc as openclawGatewayAgentConfigurationDoc, models as openclawGatewayModels, } from "@paperclipai/adapter-openclaw-gateway"; import { listCodexModels } from "./codex-models.js"; import { listCursorModels } from "./cursor-models.js"; import { execute as piExecute, listPiSkills, syncPiSkills, testEnvironment as piTestEnvironment, sessionCodec as piSessionCodec, listPiModels, } from "@paperclipai/adapter-pi-local/server"; import { agentConfigurationDoc as piAgentConfigurationDoc, } from "@paperclipai/adapter-pi-local"; import { execute as hermesExecute, testEnvironment as hermesTestEnvironment, sessionCodec as hermesSessionCodec, listSkills as hermesListSkills, syncSkills as hermesSyncSkills, detectModel as detectModelFromHermes, } from "hermes-paperclip-adapter/server"; import { agentConfigurationDoc as hermesAgentConfigurationDoc, models as hermesModels, } from "hermes-paperclip-adapter"; import { BUILTIN_ADAPTER_TYPES } from "./builtin-adapter-types.js"; import { buildExternalAdapters } from "./plugin-loader.js"; import { getDisabledAdapterTypes } from "../services/adapter-plugin-store.js"; import { processAdapter } from "./process/index.js"; import { httpAdapter } from "./http/index.js"; const claudeLocalAdapter: ServerAdapterModule = { type: "claude_local", execute: claudeExecute, testEnvironment: claudeTestEnvironment, listSkills: listClaudeSkills, syncSkills: syncClaudeSkills, sessionCodec: claudeSessionCodec, sessionManagement: getAdapterSessionManagement("claude_local") ?? undefined, models: claudeModels, supportsLocalAgentJwt: true, agentConfigurationDoc: claudeAgentConfigurationDoc, getQuotaWindows: claudeGetQuotaWindows, }; const codexLocalAdapter: ServerAdapterModule = { type: "codex_local", execute: codexExecute, testEnvironment: codexTestEnvironment, listSkills: listCodexSkills, syncSkills: syncCodexSkills, sessionCodec: codexSessionCodec, sessionManagement: getAdapterSessionManagement("codex_local") ?? undefined, models: codexModels, listModels: listCodexModels, supportsLocalAgentJwt: true, agentConfigurationDoc: codexAgentConfigurationDoc, getQuotaWindows: codexGetQuotaWindows, }; const cursorLocalAdapter: ServerAdapterModule = { type: "cursor", execute: cursorExecute, testEnvironment: cursorTestEnvironment, listSkills: listCursorSkills, syncSkills: syncCursorSkills, sessionCodec: cursorSessionCodec, sessionManagement: getAdapterSessionManagement("cursor") ?? undefined, models: cursorModels, listModels: listCursorModels, supportsLocalAgentJwt: true, agentConfigurationDoc: cursorAgentConfigurationDoc, }; const geminiLocalAdapter: ServerAdapterModule = { type: "gemini_local", execute: geminiExecute, testEnvironment: geminiTestEnvironment, listSkills: listGeminiSkills, syncSkills: syncGeminiSkills, sessionCodec: geminiSessionCodec, sessionManagement: getAdapterSessionManagement("gemini_local") ?? undefined, models: geminiModels, supportsLocalAgentJwt: true, agentConfigurationDoc: geminiAgentConfigurationDoc, }; const openclawGatewayAdapter: ServerAdapterModule = { type: "openclaw_gateway", execute: openclawGatewayExecute, testEnvironment: openclawGatewayTestEnvironment, models: openclawGatewayModels, supportsLocalAgentJwt: false, agentConfigurationDoc: openclawGatewayAgentConfigurationDoc, }; const openCodeLocalAdapter: ServerAdapterModule = { type: "opencode_local", execute: openCodeExecute, testEnvironment: openCodeTestEnvironment, listSkills: listOpenCodeSkills, syncSkills: syncOpenCodeSkills, sessionCodec: openCodeSessionCodec, models: openCodeModels, sessionManagement: getAdapterSessionManagement("opencode_local") ?? undefined, listModels: listOpenCodeModels, supportsLocalAgentJwt: true, agentConfigurationDoc: openCodeAgentConfigurationDoc, }; const piLocalAdapter: ServerAdapterModule = { type: "pi_local", execute: piExecute, testEnvironment: piTestEnvironment, listSkills: listPiSkills, syncSkills: syncPiSkills, sessionCodec: piSessionCodec, sessionManagement: getAdapterSessionManagement("pi_local") ?? undefined, models: [], listModels: listPiModels, supportsLocalAgentJwt: true, agentConfigurationDoc: piAgentConfigurationDoc, }; const hermesLocalAdapter: ServerAdapterModule = { type: "hermes_local", execute: hermesExecute, testEnvironment: hermesTestEnvironment, sessionCodec: hermesSessionCodec, listSkills: hermesListSkills, syncSkills: hermesSyncSkills, models: hermesModels, supportsLocalAgentJwt: true, agentConfigurationDoc: hermesAgentConfigurationDoc, detectModel: () => detectModelFromHermes(), }; const adaptersByType = new Map(); function registerBuiltInAdapters() { for (const adapter of [ claudeLocalAdapter, codexLocalAdapter, openCodeLocalAdapter, piLocalAdapter, cursorLocalAdapter, geminiLocalAdapter, openclawGatewayAdapter, hermesLocalAdapter, processAdapter, httpAdapter, ]) { adaptersByType.set(adapter.type, adapter); } } registerBuiltInAdapters(); // --------------------------------------------------------------------------- // Load external adapter plugins (e.g. droid_local) // // External adapter packages export createServerAdapter() which returns a // ServerAdapterModule. The host fills in sessionManagement. // --------------------------------------------------------------------------- /** Cached sync wrapper — the store is a simple JSON file read, safe to call frequently. */ function getDisabledAdapterTypesFromStore(): string[] { return getDisabledAdapterTypes(); } /** * Load external adapters from the plugin store and hardcoded sources. * Called once at module initialization. The promise is exported so that * callers (e.g. assertKnownAdapterType, app startup) can await completion * and avoid racing against the loading window. */ const externalAdaptersReady: Promise = (async () => { try { const externalAdapters = await buildExternalAdapters(); for (const externalAdapter of externalAdapters) { if (BUILTIN_ADAPTER_TYPES.has(externalAdapter.type)) { console.warn( `[paperclip] Skipping external adapter "${externalAdapter.type}" — conflicts with built-in adapter`, ); continue; } adaptersByType.set( externalAdapter.type, { ...externalAdapter, sessionManagement: getAdapterSessionManagement(externalAdapter.type) ?? undefined, }, ); } } catch (err) { console.error("[paperclip] Failed to load external adapters:", err); } })(); /** * Await this before validating adapter types to avoid race conditions * during server startup. External adapters are loaded asynchronously; * calling assertKnownAdapterType before this resolves will reject * valid external adapter types. */ export function waitForExternalAdapters(): Promise { return externalAdaptersReady; } export function registerServerAdapter(adapter: ServerAdapterModule): void { adaptersByType.set(adapter.type, adapter); } export function unregisterServerAdapter(type: string): void { if (type === processAdapter.type || type === httpAdapter.type) return; adaptersByType.delete(type); } export function requireServerAdapter(type: string): ServerAdapterModule { const adapter = adaptersByType.get(type); if (!adapter) { throw new Error(`Unknown adapter type: ${type}`); } return adapter; } export function getServerAdapter(type: string): ServerAdapterModule { return adaptersByType.get(type) ?? processAdapter; } export async function listAdapterModels(type: string): Promise<{ id: string; label: string }[]> { const adapter = adaptersByType.get(type); if (!adapter) return []; if (adapter.listModels) { const discovered = await adapter.listModels(); if (discovered.length > 0) return discovered; } return adapter.models ?? []; } export function listServerAdapters(): ServerAdapterModule[] { return Array.from(adaptersByType.values()); } /** * List adapters excluding those that are disabled in settings. * Used for menus and agent creation flows — disabled adapters remain * functional for existing agents but hidden from selection. */ export function listEnabledServerAdapters(): ServerAdapterModule[] { const disabled = getDisabledAdapterTypesFromStore(); const disabledSet = disabled.length > 0 ? new Set(disabled) : null; return disabledSet ? Array.from(adaptersByType.values()).filter((a) => !disabledSet.has(a.type)) : Array.from(adaptersByType.values()); } export async function detectAdapterModel( type: string, ): Promise<{ model: string; provider: string; source: string; candidates?: string[] } | null> { const adapter = adaptersByType.get(type); if (!adapter?.detectModel) return null; const detected = await adapter.detectModel(); if (!detected) return null; return { model: detected.model, provider: detected.provider, source: detected.source, ...(detected.candidates?.length ? { candidates: detected.candidates } : {}), }; } export function findServerAdapter(type: string): ServerAdapterModule | null { return adaptersByType.get(type) ?? null; }