export type RuntimeEndpointKind = "web" | "api"; type EnvLike = Record; type ParsedArgs = Record; type RuntimeHints = { lane: string | null; rawLane: string | null; namespace: string | null; rawNamespace: string | null; }; type EndpointCandidate = { value: string | null; source: string; sourceName: string; explicitOverride?: boolean; }; export type RuntimeEndpoint = { kind: RuntimeEndpointKind; baseUrl: string; source: string; sourceName: string; explicitOverride: boolean; lane: string | null; namespace: string | null; urlLane: string | null; urlNamespace: string | null; warnings: Array>; }; export class RuntimeEndpointError extends Error { code: string; details: Record; constructor(code: string, message: string, details: Record = {}) { super(message); this.name = "RuntimeEndpointError"; this.code = code; this.details = details; } } export function resolveRuntimeEndpoint({ kind, parsed = {}, env = {} }: { kind: RuntimeEndpointKind; parsed?: ParsedArgs; env?: EnvLike }): RuntimeEndpoint { const hints = runtimeHints(parsed, env); assertNoManualUrlWhenLocked(kind, parsed, env); for (const candidate of endpointCandidates(kind, parsed, env, hints)) { const baseUrl = normalizeUrl(candidate.value); if (!baseUrl) continue; return validateEndpoint({ kind, baseUrl, candidate, hints }); } throw new RuntimeEndpointError( "runtime_endpoint_required", `HWLAB ${kind} endpoint could not be resolved from the current runtime context`, { kind, lane: hints.lane, rawLane: hints.rawLane, namespace: hints.namespace, rawNamespace: hints.rawNamespace, required: kind === "web" ? ["HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_CLOUD_WEB_URL"] : ["HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_CLOUD_API_URL"], suggestion: "let WEB/AgentRun assemble HWLAB_RUNTIME_* for the current lane; do not pass endpoint URLs in locked HWLAB runners" } ); } export function runtimeEndpointVisibility(endpoint: RuntimeEndpoint | null | undefined) { if (!endpoint) return null; return pruneUndefined({ kind: endpoint.kind, baseUrl: endpoint.baseUrl, source: endpoint.source, sourceName: endpoint.sourceName, explicitOverride: endpoint.explicitOverride, lane: endpoint.lane, namespace: endpoint.namespace, urlLane: endpoint.urlLane, urlNamespace: endpoint.urlNamespace, warnings: endpoint.warnings.length > 0 ? endpoint.warnings : undefined }); } export function runtimeEndpointScopeForUrl(value: unknown) { const baseUrl = normalizeUrl(value); if (!baseUrl) return { baseUrl: null, lane: null, namespace: null }; const namespace = namespaceForUrl(baseUrl); const lane = laneForUrl(baseUrl) ?? laneForNamespace(namespace); return { baseUrl, lane, namespace }; } export function sameRuntimeEndpointScope(left: unknown, right: unknown) { const leftScope = runtimeEndpointScopeForUrl(left); const rightScope = runtimeEndpointScopeForUrl(right); if (!leftScope.baseUrl || !rightScope.baseUrl) return false; if (leftScope.baseUrl === rightScope.baseUrl) return true; if (leftScope.namespace && rightScope.namespace) return leftScope.namespace === rightScope.namespace; return Boolean(leftScope.lane && rightScope.lane && leftScope.lane === rightScope.lane); } export function runtimeHints(parsed: ParsedArgs = {}, env: EnvLike = {}): RuntimeHints { const rawNamespace = firstText( parsed.runtimeNamespace, parsed.hwlabNamespace, env.HWLAB_RUNTIME_NAMESPACE, env.HWLAB_NAMESPACE, validHwlabNamespace(env.POD_NAMESPACE), validHwlabNamespace(env.KUBERNETES_NAMESPACE) ); const rawLane = firstText( parsed.runtimeLane, parsed.runtimeProfile, parsed.hwlabLane, env.HWLAB_RUNTIME_LANE, env.HWLAB_RUNTIME_PROFILE, env.HWLAB_GITOPS_LANE, env.HWLAB_ARTIFACT_LANE, env.HWLAB_GITOPS_PROFILE, env.HWLAB_ENVIRONMENT, env.HWLAB_BOOT_REF ); const laneFromRaw = normalizeLane(rawLane); const namespace = normalizeNamespace(rawNamespace) ?? namespaceForLane(laneFromRaw); return { lane: laneFromRaw ?? laneForNamespace(namespace), rawLane: rawLane || null, namespace, rawNamespace: rawNamespace || null }; } function endpointCandidates(kind: RuntimeEndpointKind, parsed: ParsedArgs, env: EnvLike, hints: RuntimeHints): EndpointCandidate[] { const namespaceUrl = hints.namespace ? runtimeUrlForNamespace(kind, hints.namespace, parsed, env) : null; if (kind === "web") { return withLocalDebugOverride([ candidate(firstText(env.HWLAB_RUNTIME_WEB_URL, env.HWLAB_CURRENT_WEB_URL), "runtime-env", "HWLAB_RUNTIME_WEB_URL"), candidate(namespaceUrl, "runtime-namespace", "HWLAB_RUNTIME_NAMESPACE"), candidate(firstText(env.HWLAB_PUBLIC_WEB_URL, env.HWLAB_CLOUD_WEB_PUBLIC_URL), "public-env", "HWLAB_PUBLIC_WEB_URL"), candidate(firstText(env.HWLAB_CLIENT_BASE_URL, env.HWLAB_CLOUD_WEB_URL, env.HWLAB_CLI_BASE_URL), "env", "HWLAB_CLIENT_BASE_URL/HWLAB_CLOUD_WEB_URL/HWLAB_CLI_BASE_URL") ], candidate(parsed.baseUrl, "explicit-override", "--base-url", true), env); } return withLocalDebugOverride([ candidate(firstText(env.HWLAB_RUNTIME_API_URL, env.HWLAB_CURRENT_API_URL), "runtime-env", "HWLAB_RUNTIME_API_URL"), candidate(namespaceUrl, "runtime-namespace", "HWLAB_RUNTIME_NAMESPACE"), candidate(firstText(env.HWLAB_PUBLIC_API_URL, env.HWLAB_PUBLIC_ENDPOINT), "public-env", "HWLAB_PUBLIC_API_URL/HWLAB_PUBLIC_ENDPOINT"), candidate(firstText(env.HWLAB_CLOUD_API_URL, env.HWLAB_CLOUD_API_BASE_URL), "env", "HWLAB_CLOUD_API_URL") ], candidate(firstText(parsed.apiBaseUrl, parsed.apiUrl, parsed.cloudApiUrl, parsed.baseUrl), "explicit-override", "--api-base-url", true), env); } function withLocalDebugOverride(candidates: EndpointCandidate[], manual: EndpointCandidate, env: EnvLike) { if (endpointLocked(env)) return candidates; return manual.value ? [manual, ...candidates] : candidates; } function assertNoManualUrlWhenLocked(kind: RuntimeEndpointKind, parsed: ParsedArgs, env: EnvLike) { if (!endpointLocked(env)) return; const manualValue = kind === "web" ? firstText(parsed.baseUrl) : firstText(parsed.apiBaseUrl, parsed.apiUrl, parsed.cloudApiUrl, parsed.baseUrl); if (!manualValue) return; throw new RuntimeEndpointError( "runtime_endpoint_manual_url_forbidden", `HWLAB ${kind} endpoint is assembled by runtime context; manual URL parameters are forbidden in locked runner environments`, { kind, forbidden: kind === "web" ? ["--base-url"] : ["--api-base-url", "--api-url", "--cloud-api-url", "--base-url"], source: "HWLAB_RUNTIME_ENDPOINT_LOCKED", suggestion: "drop the URL argument and let hwpod-cli/hwlab-cli auto-locate from the assembled runtime env" } ); } function endpointLocked(env: EnvLike) { return [env.HWLAB_RUNTIME_ENDPOINT_LOCKED, env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME].some((value) => ["1", "true", "yes", "locked"].includes(firstText(value).toLowerCase())); } function runtimeUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) { if (runningInsideKubernetes(env)) return serviceUrlForNamespace(kind, namespace, parsed, env); return publicUrlForNamespace(kind, namespace); } function runningInsideKubernetes(env: EnvLike) { return Boolean(firstText(env.KUBERNETES_SERVICE_HOST, env.KUBERNETES_PORT, env.POD_NAMESPACE, env.KUBERNETES_NAMESPACE)); } function publicUrlForNamespace(kind: RuntimeEndpointKind, namespace: string) { const lane = laneForNamespace(namespace); if (lane === "v02") return kind === "web" ? "http://74.48.78.17:19666" : "http://74.48.78.17:19667"; if (lane === "prod") return kind === "web" ? "http://74.48.78.17:18666" : "http://74.48.78.17:18667"; if (lane === "dev") return kind === "web" ? "http://74.48.78.17:17666" : "http://74.48.78.17:17667"; return null; } function serviceUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) { if (kind === "web") { const serviceName = firstText(parsed.webServiceName, env.HWLAB_CLOUD_WEB_SERVICE_NAME) || "hwlab-cloud-web"; const port = positiveInteger(firstText(parsed.webServicePort, env.HWLAB_CLOUD_WEB_PORT), 8080); return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`; } const serviceName = firstText(parsed.apiServiceName, env.HWLAB_CODE_AGENT_INTERNAL_API_SERVICE_NAME, env.HWLAB_CLOUD_API_SERVICE_NAME) || "hwlab-cloud-api"; const port = positiveInteger(firstText(parsed.apiServicePort, env.HWLAB_CODE_AGENT_INTERNAL_API_PORT, env.HWLAB_CLOUD_API_PORT), 6667); return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`; } function validateEndpoint({ kind, baseUrl, candidate, hints }: { kind: RuntimeEndpointKind; baseUrl: string; candidate: EndpointCandidate; hints: RuntimeHints }): RuntimeEndpoint { const urlLane = laneForUrl(baseUrl); const urlNamespace = namespaceForUrl(baseUrl); const explicitOverride = candidate.explicitOverride === true; const warnings: Array> = []; if (!explicitOverride) { if (hints.namespace && urlNamespace && urlNamespace !== hints.namespace) { throw endpointMismatch(kind, baseUrl, candidate, hints, { urlLane, urlNamespace, field: "namespace" }); } if (hints.lane && urlLane && urlLane !== hints.lane) { throw endpointMismatch(kind, baseUrl, candidate, hints, { urlLane, urlNamespace, field: "lane" }); } if (!hints.lane && !hints.namespace && candidate.source === "env" && urlLane === "dev") { throw new RuntimeEndpointError( "runtime_context_required", `HWLAB ${kind} endpoint ${candidate.sourceName} points at the legacy DEV lane but no current runtime lane/namespace is set`, { kind, baseUrl, source: candidate.source, sourceName: candidate.sourceName, urlLane, suggestion: "use assembled HWLAB_RUNTIME_* env for the current lane; manual URL is only a local debug override outside locked runners" } ); } } else if ((hints.lane && urlLane && urlLane !== hints.lane) || (hints.namespace && urlNamespace && urlNamespace !== hints.namespace)) { warnings.push({ code: "runtime_endpoint_explicit_mismatch", message: "explicit endpoint override does not match current runtime hints", expectedLane: hints.lane, expectedNamespace: hints.namespace, urlLane, urlNamespace }); } return pruneUndefined({ kind, baseUrl, source: candidate.source, sourceName: candidate.sourceName, explicitOverride, lane: hints.lane, namespace: hints.namespace, urlLane, urlNamespace, warnings }) as RuntimeEndpoint; } function endpointMismatch(kind: RuntimeEndpointKind, baseUrl: string, candidate: EndpointCandidate, hints: RuntimeHints, observed: Record) { return new RuntimeEndpointError( "runtime_endpoint_mismatch", `HWLAB ${kind} endpoint source ${candidate.sourceName} does not match the current runtime lane/namespace`, { kind, baseUrl, source: candidate.source, sourceName: candidate.sourceName, expectedLane: hints.lane, expectedNamespace: hints.namespace, rawLane: hints.rawLane, rawNamespace: hints.rawNamespace, ...observed, suggestion: "fix the runtime env injected by WEB/AgentRun; manual URL is only a local debug override outside locked runners" } ); } function candidate(value: unknown, source: string, sourceName: string, explicitOverride = false): EndpointCandidate { return { value: firstText(value), source, sourceName, explicitOverride }; } function normalizeUrl(value: unknown) { const raw = firstText(value); if (!raw) return null; return raw.replace(/\/+$/u, ""); } function laneForUrl(value: string) { const text = value.toLowerCase(); if (/hwlab-v02|:19666(?:\/|$)|:19667(?:\/|$)/u.test(text)) return "v02"; if (/hwlab-prod|:18666(?:\/|$)|:18667(?:\/|$)/u.test(text)) return "prod"; if (/hwlab-dev|:17666(?:\/|$)|:17667(?:\/|$)|:16666(?:\/|$)|:16667(?:\/|$)/u.test(text)) return "dev"; return null; } function namespaceForUrl(value: string) { const match = value.match(/\.((?:hwlab-[a-z0-9-]+))\.svc\.cluster\.local(?::|\/|$)/iu); return normalizeNamespace(match?.[1]); } function normalizeNamespace(value: unknown) { const namespace = firstText(value); if (!namespace || !/^hwlab-[a-z0-9-]+$/u.test(namespace)) return null; return namespace; } function validHwlabNamespace(value: unknown) { return normalizeNamespace(value) ?? null; } function normalizeLane(value: unknown) { const raw = firstText(value).toLowerCase(); if (!raw) return null; if (["v02", "v0.2", "0.2", "v2", "v0_2", "v0-2"].includes(raw)) return "v02"; if (["g14", "dev", "development"].includes(raw)) return "dev"; if (["prod", "production"].includes(raw)) return "prod"; return null; } function namespaceForLane(lane: string | null) { if (lane === "v02") return "hwlab-v02"; if (lane === "prod") return "hwlab-prod"; if (lane === "dev") return "hwlab-dev"; return null; } function laneForNamespace(namespace: string | null) { if (namespace === "hwlab-v02") return "v02"; if (namespace === "hwlab-prod") return "prod"; if (namespace === "hwlab-dev") return "dev"; return null; } function positiveInteger(value: unknown, fallback: number) { const parsed = Number.parseInt(firstText(value), 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } function firstText(...values: unknown[]) { for (const value of values) { const text = String(value ?? "").trim(); if (text) return text; } return ""; } function pruneUndefined>(value: T): T { return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T; }