UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

269 lines (268 loc) 9.21 kB
import { n as consumeRootOptionToken } from "./cli-root-options-CgExE0zj.js"; import { s as normalizeNullableString } from "./string-coerce-CIXf7egm.js"; import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js"; import { i as isSensitiveUrlQueryParamName } from "./redact-sensitive-url-BN1NZvXG.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import { S as normalizeWebSocketProtocol } from "./net-DbNPs6Xm.js"; import { t as buildGatewayConnectionDetailsWithResolvers } from "./connection-details-dzrfNKN8.js"; import { a as normalizeControlUiBasePath, i as isReservedSessionRest, o as parseShortSessionRef } from "./share-DPKiewIx.js"; //#region packages/session-url-contract/src/parse.ts function normalizePath(path) { const trimmed = path.trim(); if (!trimmed) return "/"; const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; return withSlash.length > 1 && withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash; } function decodePathSegment(segment) { if (segment === "~dot") return "."; if (segment === "~dotdot") return ".."; try { return decodeURIComponent(segment.startsWith("~~") ? segment.slice(1) : segment) || null; } catch { return null; } } function literalSessionKey(agentId, restSegments) { const normalizedAgentId = normalizeNullableString(agentId); if (!normalizedAgentId || restSegments.length === 0 || restSegments.some((segment) => !segment)) return null; return `agent:${normalizeAgentId(normalizedAgentId)}:${restSegments.join(":")}`; } function parseControlUiSessionPath(pathname, basePath = "", mainKey) { const normalizedPath = normalizePath(pathname); for (const namespace of ["chat", "dashboard"]) { const prefix = `${normalizeControlUiBasePath(basePath)}/${namespace}/`; if (!normalizedPath.startsWith(prefix)) continue; const rawSegments = normalizedPath.slice(prefix.length).split("/"); const rawAgentId = decodePathSegment(rawSegments[0] ?? ""); if (!rawAgentId) return null; const agentId = normalizeAgentId(rawAgentId); if (rawSegments.length === 1) return { namespace, kind: "main", agentId }; const forceLiteral = rawSegments[1] === "~key"; const restSegments = rawSegments.slice(forceLiteral ? 2 : 1).map(decodePathSegment); if (restSegments.some((segment) => segment === null)) return null; const literalRestSegments = restSegments; const sessionKey = literalSessionKey(agentId, literalRestSegments); if (!sessionKey) return null; if (forceLiteral) return { namespace, kind: "literal", agentId, sessionKey }; if (literalRestSegments.length !== 1) return { namespace, kind: "literal", agentId, sessionKey }; const segment = literalRestSegments[0] ?? ""; if (isReservedSessionRest(segment, mainKey)) return { namespace, kind: "literal", agentId, sessionKey }; const shortRef = parseShortSessionRef(segment); if (!shortRef) return { namespace, kind: "literal", agentId, sessionKey, slugCandidate: segment }; return { namespace, kind: "short", agentId, literalSessionKey: sessionKey, ...shortRef }; } return null; } //#endregion //#region src/cli/session-ref.ts const SESSION_TARGET_HELP = "Accepted session targets: https://host[/base]/{chat|dashboard}/<agent>[/<ref>], <host>/<agent>/<ref>, or a bare <slug>-<shortid>, <shortid>, or agent:... key."; var SessionTargetParseError = class extends Error { constructor() { super(SESSION_TARGET_HELP); this.name = "SessionTargetParseError"; } }; const BARE_SESSION_TUI_VALUE_OPTIONS = { "--token": "token", "--password": "password", "--tls-fingerprint": "tlsFingerprint", "--thinking": "thinking", "--message": "message", "--timeout-ms": "timeoutMs", "--history-limit": "historyLimit" }; function refFromPathTarget(target) { if (target.kind === "main") return { kind: "main" }; if (target.kind === "short") return { kind: "short", shortId: target.shortId, ...target.slugHint ? { slugHint: target.slugHint } : {} }; return { kind: "literal", sessionKey: target.sessionKey }; } function parseControlPath(pathname) { const direct = parseControlUiSessionPath(pathname); if (direct) return { basePath: "", target: direct }; const segments = pathname.split("/"); for (let index = segments.length - 1; index > 0; index -= 1) { if (segments[index] !== "chat" && segments[index] !== "dashboard") continue; const basePath = segments.slice(0, index).join("/"); const target = parseControlUiSessionPath(pathname, basePath); if (target) return { basePath, target }; } throw new SessionTargetParseError(); } function rejectUrlCredentials(url) { const fragmentParams = new URLSearchParams(url.hash.replace(/^#/u, "")); const sensitiveParam = [...url.searchParams.keys(), ...fragmentParams.keys()].some(isSensitiveUrlQueryParamName); if (url.username || url.password || sensitiveParam) throw new Error("Session URLs must not contain credentials. Pass --token or --password instead."); } function parseSessionUrl(raw) { let url; try { url = new URL(raw); } catch { throw new SessionTargetParseError(); } rejectUrlCredentials(url); if (![ "http:", "https:", "ws:", "wss:" ].includes(url.protocol)) throw new SessionTargetParseError(); url.protocol = normalizeWebSocketProtocol(url.protocol); const parsed = parseControlPath(url.pathname); const gatewayUrl = `${url.origin}${parsed.basePath}`; buildGatewayConnectionDetailsWithResolvers({ config: {}, url: gatewayUrl }); return { kind: "url", origin: url.origin, basePath: parsed.basePath, agentId: parsed.target.agentId, ref: refFromPathTarget(parsed.target) }; } function parseHostShorthand(raw) { const parts = (raw.endsWith("/") ? raw.slice(0, -1) : raw).split("/"); if (parts.length !== 3 || parts.some((part) => !part)) return null; let host; try { host = new URL(`wss://${parts[0]}`); } catch { throw new SessionTargetParseError(); } rejectUrlCredentials(host); if (host.pathname !== "/" || host.search || host.hash) throw new SessionTargetParseError(); const target = parseControlUiSessionPath(`/dashboard/${parts[1]}/${parts[2]}`); if (!target) throw new SessionTargetParseError(); return { kind: "url", origin: host.origin, basePath: "", agentId: target.agentId, ref: refFromPathTarget(target) }; } function parseSessionTargetInput(raw) { const value = raw.trim(); if (!value) throw new SessionTargetParseError(); if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(value)) return parseSessionUrl(value); const agentKey = parseAgentSessionKey(value); if (agentKey) return { kind: "ref", ref: { kind: "literal", sessionKey: `agent:${agentKey.agentId}:${agentKey.rest}` } }; const shorthand = parseHostShorthand(value); if (shorthand) return shorthand; const short = parseControlUiSessionPath(`/dashboard/main/${value}`); if (short?.kind === "short") return { kind: "ref", ref: { kind: "short", shortId: short.shortId, ...short.slugHint ? { slugHint: short.slugHint } : {} } }; throw new SessionTargetParseError(); } function isSessionUrlInputCandidate(raw) { return /^(?:https?|wss?):\/\//iu.test(raw.trim()); } function bareSessionOptionError(flag) { return /* @__PURE__ */ new Error(`Unsupported bare session URL option: ${sanitizeTerminalText(flag)}. Use \`openclaw tui <url> --help\` for the full option list.`); } /** Parse the complete bare-root URL invocation before generic command discovery can see secrets. */ function parseBareSessionInvocation(argv) { if (!argv.slice(2).some(isSessionUrlInputCandidate)) return null; const options = {}; let target; let consumedUrlFlag; for (let index = 2; index < argv.length; index += 1) { const arg = argv[index]; if (!arg) continue; if (arg === "--") throw bareSessionOptionError("--"); const rootConsumed = consumeRootOptionToken(argv, index); if (rootConsumed > 0) { index += rootConsumed - 1; continue; } if (arg === "--deliver") { options.deliver = true; continue; } if (!arg.startsWith("-")) { if (!target) { if (!isSessionUrlInputCandidate(arg)) break; target = arg; continue; } throw new Error("Unexpected extra argument for bare session URL. Use `openclaw tui <url> --help` for the full option list."); } const equalsIndex = arg.indexOf("="); const flag = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex); const optionKey = BARE_SESSION_TUI_VALUE_OPTIONS[flag]; if (!optionKey) throw bareSessionOptionError(flag); const value = equalsIndex === -1 ? argv[index + 1] : arg.slice(equalsIndex + 1); if (!value || value === "--" || equalsIndex === -1 && value.startsWith("-")) throw new Error(`${flag} requires a value.`); options[optionKey] = value; if (equalsIndex === -1) { if (!target && isSessionUrlInputCandidate(value)) consumedUrlFlag ??= flag; index += 1; } } if (!target && consumedUrlFlag) throw new Error(`${consumedUrlFlag} requires a value.`); return target ? { target, options } : null; } //#endregion export { parseBareSessionInvocation as n, parseSessionTargetInput as r, SessionTargetParseError as t };