UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

312 lines (311 loc) 14 kB
import { s as resolveDefaultAgentDir } from "./agent-scope-config-CgCYpZfK.js"; import { r as resolveProviderIdForAuth } from "./provider-auth-aliases-BNZrcvHv.js"; import { o as withFileLock } from "./file-lock-BOaqUSu6.js"; import { n as ensureAuthProfileStore } from "./store-C8spD0DG.js"; import { t as log } from "./logger-B8odRltZ.js"; import "./agent-runtime-CvpQRNVf.js"; import "./agent-harness-runtime-zRYzuZJP.js"; import { l as normalizeCodexServiceTier } from "./config-i2AzQJy8.js"; import fs from "node:fs/promises"; import { AsyncLocalStorage } from "node:async_hooks"; //#region extensions/codex/src/app-server/session-binding.ts /** * Persists and normalizes the Codex app-server thread binding associated with * an OpenClaw session file. */ const CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER = "openai"; const PUBLIC_OPENAI_MODEL_PROVIDER = "openai"; const CODEX_APP_SERVER_BINDING_GUARDED_REQUEST_TIMEOUT_MS = 6e4; const CODEX_APP_SERVER_BINDING_LOCK_RETRY_INTERVAL_MS = 1e3; const CODEX_APP_SERVER_BINDING_LOCK_OPTIONS = { retries: { retries: Math.ceil(75e3 / CODEX_APP_SERVER_BINDING_LOCK_RETRY_INTERVAL_MS), factor: 1, minTimeout: CODEX_APP_SERVER_BINDING_LOCK_RETRY_INTERVAL_MS, maxTimeout: CODEX_APP_SERVER_BINDING_LOCK_RETRY_INTERVAL_MS }, stale: CODEX_APP_SERVER_BINDING_GUARDED_REQUEST_TIMEOUT_MS * 2 }; const bindingMutationQueues = /* @__PURE__ */ new Map(); const bindingMutationContext = new AsyncLocalStorage(); /** Returns the JSON sidecar path for the Codex app-server binding beside a session file. */ function resolveCodexAppServerBindingPath(sessionFile) { return `${sessionFile}.codex-app-server.json`; } /** Serializes mutation of the Codex app-server binding sidecar for a session file. */ async function withCodexAppServerBindingLock(sessionFile, run) { const bindingPath = resolveCodexAppServerBindingPath(sessionFile); const ownedBindings = bindingMutationContext.getStore(); if (ownedBindings?.has(bindingPath)) return await withFileLock(bindingPath, CODEX_APP_SERVER_BINDING_LOCK_OPTIONS, run); const previous = bindingMutationQueues.get(bindingPath) ?? Promise.resolve(); let releaseCurrent; const current = new Promise((resolve) => { releaseCurrent = resolve; }); const queued = previous.then(() => current, () => current); bindingMutationQueues.set(bindingPath, queued); await previous.catch(() => void 0); const nestedOwnedBindings = new Set(ownedBindings); nestedOwnedBindings.add(bindingPath); try { return await bindingMutationContext.run(nestedOwnedBindings, () => withFileLock(bindingPath, CODEX_APP_SERVER_BINDING_LOCK_OPTIONS, run)); } finally { releaseCurrent(); if (bindingMutationQueues.get(bindingPath) === queued) bindingMutationQueues.delete(bindingPath); } } /** Reads and normalizes a Codex app-server binding sidecar, returning undefined on stale data. */ async function readCodexAppServerBinding(sessionFile, lookup = {}) { const path = resolveCodexAppServerBindingPath(sessionFile); let raw; try { raw = await fs.readFile(path, "utf8"); } catch (error) { if (isNotFound(error)) return; log.warn("failed to read codex app-server binding", { path, error }); return; } try { const parsed = JSON.parse(raw); if (parsed.schemaVersion !== 1 || typeof parsed.threadId !== "string") return; const authProfileId = typeof parsed.authProfileId === "string" ? parsed.authProfileId : void 0; return { schemaVersion: 1, threadId: parsed.threadId, sessionFile, cwd: typeof parsed.cwd === "string" ? parsed.cwd : "", authProfileId, model: typeof parsed.model === "string" ? parsed.model : void 0, modelProvider: normalizeCodexAppServerBindingModelProvider({ ...lookup, authProfileId, modelProvider: typeof parsed.modelProvider === "string" ? parsed.modelProvider : void 0 }), approvalPolicy: readApprovalPolicy(parsed.approvalPolicy), sandbox: readSandboxMode(parsed.sandbox), serviceTier: readServiceTier(parsed.serviceTier), dynamicToolsFingerprint: typeof parsed.dynamicToolsFingerprint === "string" ? parsed.dynamicToolsFingerprint : void 0, dynamicToolsContainDeferred: typeof parsed.dynamicToolsContainDeferred === "boolean" ? parsed.dynamicToolsContainDeferred : void 0, userMcpServersFingerprint: typeof parsed.userMcpServersFingerprint === "string" ? parsed.userMcpServersFingerprint : void 0, mcpServersFingerprint: typeof parsed.mcpServersFingerprint === "string" ? parsed.mcpServersFingerprint : void 0, nativeHookRelayGeneration: typeof parsed.nativeHookRelayGeneration === "string" && parsed.nativeHookRelayGeneration.trim() ? parsed.nativeHookRelayGeneration : void 0, pluginAppsFingerprint: typeof parsed.pluginAppsFingerprint === "string" ? parsed.pluginAppsFingerprint : void 0, pluginAppsInputFingerprint: typeof parsed.pluginAppsInputFingerprint === "string" ? parsed.pluginAppsInputFingerprint : void 0, pluginAppPolicyContext: readPluginAppPolicyContext(parsed.pluginAppPolicyContext), contextEngine: readContextEngineBinding(parsed.contextEngine), environmentSelectionFingerprint: typeof parsed.environmentSelectionFingerprint === "string" ? parsed.environmentSelectionFingerprint : void 0, createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date()).toISOString(), updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString() }; } catch (error) { log.warn("failed to parse codex app-server binding", { path, error }); return; } } /** Writes the Codex app-server binding sidecar with normalized provider/auth metadata. */ async function writeCodexAppServerBinding(sessionFile, binding, lookup = {}) { await withCodexAppServerBindingLock(sessionFile, async () => { const now = (/* @__PURE__ */ new Date()).toISOString(); const payload = { schemaVersion: 1, sessionFile, threadId: binding.threadId, cwd: binding.cwd, authProfileId: binding.authProfileId, model: binding.model, modelProvider: normalizeCodexAppServerBindingModelProvider({ ...lookup, authProfileId: binding.authProfileId, modelProvider: binding.modelProvider }), approvalPolicy: binding.approvalPolicy, sandbox: binding.sandbox, serviceTier: binding.serviceTier, dynamicToolsFingerprint: binding.dynamicToolsFingerprint, dynamicToolsContainDeferred: binding.dynamicToolsContainDeferred, userMcpServersFingerprint: binding.userMcpServersFingerprint, mcpServersFingerprint: binding.mcpServersFingerprint, nativeHookRelayGeneration: binding.nativeHookRelayGeneration, pluginAppsFingerprint: binding.pluginAppsFingerprint, pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint, pluginAppPolicyContext: binding.pluginAppPolicyContext, contextEngine: binding.contextEngine, environmentSelectionFingerprint: binding.environmentSelectionFingerprint, createdAt: binding.createdAt ?? now, updatedAt: now }; await fs.writeFile(resolveCodexAppServerBindingPath(sessionFile), `${JSON.stringify(payload, null, 2)}\n`); }); } function readContextEngineBinding(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const record = value; if (record.schemaVersion !== 1 || typeof record.engineId !== "string" || typeof record.policyFingerprint !== "string") return; return { schemaVersion: 1, engineId: record.engineId, policyFingerprint: record.policyFingerprint, projection: readContextEngineProjectionBinding(record.projection) }; } function readContextEngineProjectionBinding(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const record = value; if (record.schemaVersion !== 1 || record.mode !== "thread_bootstrap" || typeof record.epoch !== "string" || !record.epoch.trim()) return; return { schemaVersion: 1, mode: "thread_bootstrap", epoch: record.epoch, fingerprint: typeof record.fingerprint === "string" ? record.fingerprint : void 0 }; } function readPluginAppPolicyContext(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const record = value; if (typeof record.fingerprint !== "string") return; const apps = record.apps; if (!apps || typeof apps !== "object" || Array.isArray(apps)) return; const parsedApps = {}; for (const [appId, rawEntry] of Object.entries(apps)) { if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) return; const entry = rawEntry; if ("appId" in entry || typeof entry.configKey !== "string" || entry.marketplaceName !== "openai-curated" || typeof entry.pluginName !== "string" || typeof entry.allowDestructiveActions !== "boolean" || !Array.isArray(entry.mcpServerNames) || entry.mcpServerNames.some((serverName) => typeof serverName !== "string")) return; parsedApps[appId] = { configKey: entry.configKey, marketplaceName: entry.marketplaceName, pluginName: entry.pluginName, allowDestructiveActions: entry.allowDestructiveActions, mcpServerNames: entry.mcpServerNames }; } const parsedPluginAppIds = {}; const rawPluginAppIds = record.pluginAppIds; if (rawPluginAppIds && (typeof rawPluginAppIds !== "object" || Array.isArray(rawPluginAppIds))) return; if (rawPluginAppIds && typeof rawPluginAppIds === "object") for (const [configKey, appIds] of Object.entries(rawPluginAppIds)) { if (!Array.isArray(appIds) || appIds.some((appId) => typeof appId !== "string")) return; parsedPluginAppIds[configKey] = appIds; } return { fingerprint: record.fingerprint, apps: parsedApps, pluginAppIds: parsedPluginAppIds }; } /** Removes the Codex app-server binding sidecar if present. */ async function clearCodexAppServerBinding(sessionFile, _lookup = {}) { if (!await codexAppServerBindingSidecarExists(sessionFile)) return; await withCodexAppServerBindingLock(sessionFile, async () => { await unlinkCodexAppServerBinding(sessionFile); }); } async function codexAppServerBindingSidecarExists(sessionFile) { try { await fs.access(resolveCodexAppServerBindingPath(sessionFile)); return true; } catch (error) { if (!isNotFound(error)) log.warn("failed to inspect codex app-server binding", { sessionFile, error }); return false; } } async function unlinkCodexAppServerBinding(sessionFile) { try { await fs.unlink(resolveCodexAppServerBindingPath(sessionFile)); return true; } catch (error) { if (!isNotFound(error)) log.warn("failed to clear codex app-server binding", { sessionFile, error }); return false; } } /** Clears a binding only when it still points at the expected Codex thread id. */ async function clearCodexAppServerBindingForThread(sessionFile, threadId, lookup = {}) { if (!await readCodexAppServerBinding(sessionFile, lookup)) return false; return await withCodexAppServerBindingLock(sessionFile, async () => { const binding = await readCodexAppServerBinding(sessionFile, lookup); if (!binding) return false; if (binding.threadId !== threadId) { log.debug("codex app-server binding points at a different thread; preserving", { sessionFile, threadId, boundThreadId: binding.threadId }); return false; } return await unlinkCodexAppServerBinding(sessionFile); }); } function isNotFound(error) { return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT"); } /** Returns true when an auth profile uses native Codex/OpenAI app-server auth. */ function isCodexAppServerNativeAuthProfile(lookup) { const authProfileId = lookup.authProfileId?.trim(); if (!authProfileId) return false; try { const credential = resolveCodexAppServerAuthProfileCredential({ ...lookup, authProfileId }); if (!credential || credential.type === "api_key") return false; return isOpenAiAuthProvider({ provider: credential.provider, config: lookup.config }); } catch (error) { log.debug("failed to resolve codex app-server auth profile provider", { authProfileId, error }); return false; } } /** Hides redundant OpenAI provider attribution for native Codex auth bindings. */ function normalizeCodexAppServerBindingModelProvider(params) { const modelProvider = params.modelProvider?.trim(); if (!modelProvider) return; if (isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === PUBLIC_OPENAI_MODEL_PROVIDER) return; return modelProvider; } function resolveCodexAppServerAuthProfileCredential(lookup) { const authProfileId = lookup.authProfileId?.trim(); if (!authProfileId) return; return (lookup.authProfileStore ?? loadCodexAppServerAuthProfileStore({ agentDir: lookup.agentDir, authProfileId, config: lookup.config })).profiles[authProfileId]; } function loadCodexAppServerAuthProfileStore(params) { return ensureAuthProfileStore(params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {}), { allowKeychainPrompt: false, config: params.config, externalCliProviderIds: [CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER], externalCliProfileIds: [params.authProfileId] }); } function isOpenAiAuthProvider(params) { const provider = params.provider?.trim(); return Boolean(provider && resolveProviderIdForAuth(provider, { config: params.config }) === CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER); } function readApprovalPolicy(value) { return value === "never" || value === "on-request" || value === "on-failure" || value === "untrusted" ? value : void 0; } function readSandboxMode(value) { return value === "read-only" || value === "workspace-write" || value === "danger-full-access" ? value : void 0; } function readServiceTier(value) { return normalizeCodexServiceTier(value); } //#endregion export { normalizeCodexAppServerBindingModelProvider as a, withCodexAppServerBindingLock as c, isCodexAppServerNativeAuthProfile as i, writeCodexAppServerBinding as l, clearCodexAppServerBinding as n, readCodexAppServerBinding as o, clearCodexAppServerBindingForThread as r, resolveCodexAppServerBindingPath as s, CODEX_APP_SERVER_BINDING_GUARDED_REQUEST_TIMEOUT_MS as t };