UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

224 lines (223 loc) 9.46 kB
import { r as asNullableRecord } from "./record-coerce-DItp3I4t.js"; import fs from "node:fs/promises"; //#region src/node-host/invoke-agent-cli-claude-params.ts const MAX_ARG_COUNT = 128; const MAX_ARG_BYTES = 1048576; const MAX_REQUEST_BYTES = 2097152; const MAX_TIMEOUT_MS = 864e5; const MIN_IDLE_TIMEOUT_MS = 1e3; const MAX_IDLE_TIMEOUT_MS = 18e5; const BARE_ARGS = /* @__PURE__ */ new Set([ "-p", "--print", "--include-partial-messages", "--verbose", "--fork-session", "--safe-mode", "--bare", "--no-chrome", "--disable-slash-commands", "--no-session-persistence", "--exclude-dynamic-system-prompt-sections", "--include-hook-events", "--replay-user-messages" ]); const VALUE_ARGS = /* @__PURE__ */ new Set([ "--output-format", "--input-format", "--setting-sources", "--permission-mode", "--resume", "-r", "--session-id", "--model", "--effort", "--max-turns", "--fallback-model", "--prompt-suggestions", "--max-budget-usd", "--tools", "--disallowedTools" ]); const ENV_ALLOWLIST = /* @__PURE__ */ new Set([ "ANTHROPIC_API_KEY", "CLAUDE_CODE_AUTO_COMPACT_WINDOW", "CLAUDE_CODE_DISABLE_1M_CONTEXT", "CLAUDE_CODE_OAUTH_TOKEN", "FORCE_COLOR", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "TERM" ]); const CLEAR_ENV_ALLOWLIST = /* @__PURE__ */ new Set([ "ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD", "ANTHROPIC_API_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "ANTHROPIC_CUSTOM_HEADERS", "ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_UNIX_SOCKET", "CLAUDE_CONFIG_DIR", "CLAUDE_CODE_AUTO_COMPACT_WINDOW", "CLAUDE_CODE_DISABLE_1M_CONTEXT", "CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_OAUTH_REFRESH_TOKEN", "CLAUDE_CODE_OAUTH_SCOPES", "CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR", "CLAUDE_CODE_PLUGIN_CACHE_DIR", "CLAUDE_CODE_PLUGIN_SEED_DIR", "CLAUDE_CODE_REMOTE", "CLAUDE_CODE_USE_COWORK_PLUGINS", "CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_FOUNDRY", "CLAUDE_CODE_USE_VERTEX", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "OTEL_EXPORTER_OTLP_LOGS_HEADERS", "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "OTEL_EXPORTER_OTLP_METRICS_HEADERS", "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_HEADERS", "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "OTEL_LOGS_EXPORTER", "OTEL_METRICS_EXPORTER", "OTEL_SDK_DISABLED", "OTEL_TRACES_EXPORTER" ]); function decodeJson(raw) { if (!raw) throw new Error("INVALID_REQUEST: paramsJSON required"); try { return JSON.parse(raw); } catch { throw new Error("INVALID_REQUEST: paramsJSON malformed JSON"); } } function requireBoundedString(value, label, maxBytes) { if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > maxBytes) throw new Error(`INVALID_REQUEST: ${label} must be a bounded string`); return value; } /** Claude CLI session ids are bounded, non-option argv values. */ function validateClaudeSessionId(value) { const sessionId = requireBoundedString(value, "threadId", MAX_ARG_BYTES).trim(); if (!sessionId || sessionId.startsWith("-")) throw new Error("INVALID_REQUEST: threadId must be a Claude session id"); return sessionId; } function validateArgs(value) { if (!Array.isArray(value) || value.length === 0 || value.length > MAX_ARG_COUNT) throw new Error("INVALID_REQUEST: argv must be a bounded non-empty array"); const args = value.map((entry, index) => requireBoundedString(entry, `argv[${index}]`, MAX_ARG_BYTES)); for (let index = 0; index < args.length; index += 1) { const arg = args[index] ?? ""; const equalsIndex = arg.indexOf("="); const name = equalsIndex > 0 ? arg.slice(0, equalsIndex) : arg; if (BARE_ARGS.has(arg)) continue; if (!VALUE_ARGS.has(name)) throw new Error(`INVALID_REQUEST: unsupported Claude CLI argument: ${arg || "<empty>"}`); if (equalsIndex > 0) { const inlineValue = arg.slice(equalsIndex + 1); if (!inlineValue || inlineValue.startsWith("-")) throw new Error(`INVALID_REQUEST: Claude CLI argument requires a non-option value: ${name}`); if (name === "--permission-mode" && inlineValue === "bypassPermissions") throw new Error("INVALID_REQUEST: bypassPermissions is not allowed for node agent runs"); continue; } if (index + 1 >= args.length) throw new Error(`INVALID_REQUEST: Claude CLI argument requires a value: ${name}`); if (args[index + 1]?.startsWith("-")) throw new Error(`INVALID_REQUEST: Claude CLI argument requires a non-option value: ${name}`); if (name === "--permission-mode" && args[index + 1] === "bypassPermissions") throw new Error("INVALID_REQUEST: bypassPermissions is not allowed for node agent runs"); index += 1; } return args; } function validateTimeout(value, label, min, max) { if (!Number.isInteger(value) || value < min || value > max) throw new Error(`INVALID_REQUEST: ${label} must be an integer from ${min} to ${max}`); return value; } /** Select framing before the command handler validates the complete narrow request. */ function requestsClaudeNodeSkillRuntime(raw) { if (!raw || Buffer.byteLength(raw, "utf8") > MAX_REQUEST_BYTES) return false; try { return asNullableRecord(JSON.parse(raw))?.skillRuntime === true; } catch { return false; } } /** Resource bytes use the negotiated duplex, never argv or node filesystem paths. */ async function decodeClaudeCliNodeRunParams(raw) { if (Buffer.byteLength(raw ?? "", "utf8") > MAX_REQUEST_BYTES) throw new Error("INVALID_REQUEST: Claude CLI request is too large"); const value = asNullableRecord(decodeJson(raw)); if (!value) throw new Error("INVALID_REQUEST: Claude CLI params must be an object"); const allowed = /* @__PURE__ */ new Set([ "argv", "stdin", "cwd", "env", "clearEnv", "systemPrompt", "agentId", "sessionKey", "approvalDecision", "systemRunPlan", "idleTimeoutMs", "timeoutMs", "skillRuntime" ]); const unknown = Object.keys(value).find((key) => !allowed.has(key)); if (unknown) throw new Error(`INVALID_REQUEST: unknown Claude CLI parameter: ${unknown}`); const argv = validateArgs(value.argv); if (value.skillRuntime !== void 0 && value.skillRuntime !== true) throw new Error("INVALID_REQUEST: skillRuntime must be true when supplied"); const stdin = value.stdin === void 0 ? void 0 : requireBoundedString(value.stdin, "stdin", MAX_REQUEST_BYTES); const systemPrompt = value.systemPrompt === void 0 ? void 0 : requireBoundedString(value.systemPrompt, "systemPrompt", MAX_REQUEST_BYTES); const agentId = value.agentId === void 0 ? void 0 : requireBoundedString(value.agentId, "agentId", MAX_ARG_BYTES); const sessionKey = value.sessionKey === void 0 ? void 0 : requireBoundedString(value.sessionKey, "sessionKey", MAX_ARG_BYTES); const approvalDecision = value.approvalDecision === "allow-once" || value.approvalDecision === "allow-always" ? value.approvalDecision : void 0; if (value.approvalDecision !== void 0 && !approvalDecision) throw new Error("INVALID_REQUEST: approvalDecision is invalid"); const systemRunPlan = value.systemRunPlan === void 0 ? void 0 : asNullableRecord(value.systemRunPlan); if (value.systemRunPlan !== void 0 && !systemRunPlan) throw new Error("INVALID_REQUEST: systemRunPlan must be an object"); const cwd = value.cwd === void 0 ? void 0 : requireBoundedString(value.cwd, "cwd", MAX_ARG_BYTES); if (cwd) { if (!(await fs.stat(cwd).catch(() => void 0))?.isDirectory()) throw new Error("INVALID_REQUEST: cwd must be an existing directory on the node"); } let env; if (value.env !== void 0) { const envValue = asNullableRecord(value.env); if (!envValue) throw new Error("INVALID_REQUEST: env must be an object"); env = {}; for (const [key, candidate] of Object.entries(envValue)) { if (!ENV_ALLOWLIST.has(key)) throw new Error(`INVALID_REQUEST: environment key is not allowed: ${key}`); env[key] = requireBoundedString(candidate, `env.${key}`, MAX_ARG_BYTES); } if (Object.hasOwn(env, "ANTHROPIC_API_KEY") && Object.hasOwn(env, "CLAUDE_CODE_OAUTH_TOKEN")) throw new Error("INVALID_REQUEST: exactly one Claude credential may be provided"); } let clearEnv; if (value.clearEnv !== void 0) { if (!Array.isArray(value.clearEnv) || value.clearEnv.length > CLEAR_ENV_ALLOWLIST.size) throw new Error("INVALID_REQUEST: clearEnv must be a bounded array"); clearEnv = []; for (const candidate of value.clearEnv) { const key = requireBoundedString(candidate, "clearEnv entry", MAX_ARG_BYTES); if (!CLEAR_ENV_ALLOWLIST.has(key)) throw new Error(`INVALID_REQUEST: clearEnv key is not allowed: ${key}`); if (!clearEnv.includes(key)) clearEnv.push(key); } } return { argv, ...value.skillRuntime === true ? { skillRuntime: true } : {}, ...stdin !== void 0 ? { stdin } : {}, ...systemPrompt !== void 0 ? { systemPrompt } : {}, ...agentId ? { agentId } : {}, ...sessionKey ? { sessionKey } : {}, ...approvalDecision ? { approvalDecision } : {}, ...systemRunPlan ? { systemRunPlan } : {}, ...cwd ? { cwd } : {}, ...env ? { env } : {}, ...clearEnv ? { clearEnv } : {}, idleTimeoutMs: validateTimeout(value.idleTimeoutMs, "idleTimeoutMs", MIN_IDLE_TIMEOUT_MS, MAX_IDLE_TIMEOUT_MS), timeoutMs: validateTimeout(value.timeoutMs, "timeoutMs", 1, MAX_TIMEOUT_MS) }; } //#endregion export { requestsClaudeNodeSkillRuntime as n, validateClaudeSessionId as r, decodeClaudeCliNodeRunParams as t };