Ctrl K
ComunidadGuías…

src/domain/local-endpoints.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { validatePort } from "./validation.js";
import { getProviderTargetBinding } from "./provider-lifecycle.js";
import { validateSiwcAuthBinding, validateSiwcRegistrationId } from "./safety.js";
import packageManifest from "../../package.json" with { type: "json" };
// The constant lives in model-discovery.mjs. Importing it from the gateway would load the whole sidecar server and jose.
import { CODEX_CLI_VERSION } from "../services/model-discovery.mjs";

export const GROK_BUILD_CLI_VERSION = "1.0.13";
export const GROK_BUILD_REQUIREMENTS_TOML = [
  "[ui]",
  "disable_bypass_permissions_mode = true",
  "",
  "[permission]",
  "rules = [",
  '  { action = "deny", tool = "Bash" },',
  '  { action = "deny", tool = "Edit" },',
  '  { action = "deny", tool = "Read" },',
  '  { action = "deny", tool = "Grep" },',
  '  { action = "deny", tool = "MCPTool" },',
  '  { action = "deny", tool = "WebFetch" },',
  '  { action = "deny", tool = "WebSearch" },',
  "]",
  "",
].join("\n");
const PACKAGE_VERSION = packageManifest.version;

function createLocalTargetDefinition(targetId, runtimeMetadata) {
  const binding = getProviderTargetBinding(targetId);
  if (
    !runtimeMetadata ||
    typeof runtimeMetadata !== "object" ||
    Object.keys(runtimeMetadata).length !== 3 ||
    !Object.hasOwn(runtimeMetadata, "browserClients") ||
    !Object.hasOwn(runtimeMetadata, "experimental") ||
    !Object.hasOwn(runtimeMetadata, "containerPort")
  ) {
    throw new TypeError("Local endpoint runtime metadata is invalid.");
  }

  return Object.freeze({
    label: binding.label,
    protocol: binding.protocol,
    upstreamAuth: binding.upstreamAuth,
    browserClients: runtimeMetadata.browserClients,
    experimental: runtimeMetadata.experimental,
    containerPort: runtimeMetadata.containerPort,
  });
}

export const LOCAL_TARGETS = Object.freeze({
  "codex-chatgpt": createLocalTargetDefinition("codex-chatgpt", {
    browserClients: false,
    experimental: true,
    containerPort: 4_500,
  }),
  "codex-chat": createLocalTargetDefinition("codex-chat", {
    browserClients: false,
    experimental: true,
    containerPort: 14_501,
  }),
  "xai-grok-build": createLocalTargetDefinition("xai-grok-build", {
    browserClients: false,
    experimental: true,
    containerPort: 14_502,
  }),
});

export function validateLocalTarget(value) {
  if (
    typeof value !== "string" ||
    !Object.prototype.hasOwnProperty.call(LOCAL_TARGETS, value)
  ) {
    throw new TypeError("Local endpoint target is invalid.");
  }
  return value;
}

export function validateLocalPort(value) {
  const port = validatePort(value);
  if (port < 1_024) {
    throw new TypeError("Local endpoint port must be between 1024 and 65535.");
  }
  return port;
}

export function validateSha256Verifier(value) {
  if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) {
    throw new TypeError("Local capability verifier is invalid.");
  }
  return value;
}

export function validateInstallId(value) {
  if (typeof value !== "string" || !/^[a-f0-9]{32}$/u.test(value)) {
    throw new TypeError("Local endpoint installation ID is invalid.");
  }
  return value;
}

export function createLocalDeploymentPlan({
  target,
  port,
  authBinding,
}) {
  const safeTarget = validateLocalTarget(target);
  const safePort = validateLocalPort(
    safeTarget === "xai-grok-build" ? port ?? 14_502 : port,
  );
  const targetDefinition = LOCAL_TARGETS[safeTarget];
  const endpoint =
    safeTarget === "codex-chatgpt"
      ? `ws://127.0.0.1:${safePort}`
      : `http://127.0.0.1:${safePort}`;

  return {
    target: safeTarget,
    label: targetDefinition.label,
    bindHost: "127.0.0.1",
    port: safePort,
    endpoint,
    protocol: targetDefinition.protocol,
    upstreamAuth: targetDefinition.upstreamAuth,
    browserClients: targetDefinition.browserClients,
    experimental: targetDefinition.experimental,
    ...(safeTarget === "xai-grok-build" ? {} : { authBinding: validateSiwcAuthBinding(authBinding) }),
    managedPath: `~/.relmio/local/${safeTarget}`,
  };
}

export function createLocalDockerignore(target) {
  const safeTarget = validateLocalTarget(target);
  if (safeTarget === "xai-grok-build") {
    return "**\n!Dockerfile\n!gateway.js\n!chat.js\n!session.js\n";
  }
  return safeTarget === "codex-chat"
    ? "**\n!Dockerfile\n!package.json\n!package-lock.json\n!gateway/\n!gateway/codex-chat.js\n!gateway/openai-oauth-sidecar.mjs\n!services/\n!services/siwc-session.mjs\n!services/siwc-handoff.mjs\n!services/local-integration-lifecycle-lock.js\n!services/codex-images.mjs\n!services/model-discovery.mjs\n!infrastructure/\n!infrastructure/local-process.js\n!infrastructure/process-identity.js\n!config.toml\n!requirements.toml\n"
    : "**\n!Dockerfile\n!package.json\n!package-lock.json\n!gateway/\n!gateway/codex-app-server.mjs\n!gateway/openai-oauth-sidecar.mjs\n!services/\n!services/siwc-session.mjs\n!services/siwc-handoff.mjs\n!services/local-integration-lifecycle-lock.js\n!services/codex-images.mjs\n!services/model-discovery.mjs\n!infrastructure/\n!infrastructure/local-process.js\n!infrastructure/process-identity.js\n!config.toml\n!requirements.toml\n";
}

export function createGrokBuildDockerfile() {
  // The npm launcher prefers GROK_HOME/bin/grok, which is a writable credential
  // volume. Extract the pinned platform payload into the image instead.
  const bootstrap = [
    'const fs = require("node:fs");',
    'const path = require("node:path");',
    'const zlib = require("node:zlib");',
    'if (process.platform !== "linux" || !["x64", "arm64"].includes(process.arch)) throw new Error("Unsupported Grok image platform");',
    'const metadata = require.resolve("@xai-official/grok-linux-" + process.arch + "/package.json", { paths: ["/usr/local/lib/node_modules/@xai-official/grok"] });',
    `if (JSON.parse(fs.readFileSync(metadata, "utf8")).version !== "${GROK_BUILD_CLI_VERSION}") throw new Error("Grok platform version mismatch");`,
    'const binary = zlib.brotliDecompressSync(fs.readFileSync(path.join(path.dirname(metadata), "bin/grok.br")));',
    'fs.mkdirSync("/opt/relmio-grok", { mode: 0o755 });',
    'fs.writeFileSync("/opt/relmio-grok/grok", binary, { mode: 0o755, flag: "wx" });',
  ].join(" ");
  const requirements = GROK_BUILD_REQUIREMENTS_TOML.trimEnd()
    .split("\n")
    .map((line) => `'${line}'`)
    .join(" ");
  return `FROM node:22-bookworm-slim

WORKDIR /app

RUN npm install --global --include=optional --ignore-scripts @xai-official/grok@${GROK_BUILD_CLI_VERSION} \\
    && node -e '${bootstrap}' \\
    && ln -sf /opt/relmio-grok/grok /usr/local/bin/grok \\
    && GROK_HOME=/tmp/relmio-grok-bootstrap GROK_MANAGED_BY_NPM=1 grok --no-auto-update --version \\
    && npm cache clean --force \\
    && mkdir -p /etc/grok \\
    && printf '%s\\n' ${requirements} > /etc/grok/requirements.toml \\
    && chmod 0444 /etc/grok/requirements.toml \\
    && mkdir -p /home/node/.grok /workspace \\
    && chown -R node:node /home/node/.grok /workspace

COPY --chown=node:node gateway.js chat.js session.js /app/

ENV GROK_HOME=/home/node/.grok
ENV GROK_MANAGED_BY_NPM=1
WORKDIR /workspace
USER node

ENTRYPOINT ["node", "/app/gateway.js"]
`;
}

export function createGrokBuildComposeFile({ port, tokenSha256, installId }) {
  const safePort = validateLocalPort(port);
  const safeVerifier = validateSha256Verifier(tokenSha256);
  const safeInstallId = validateInstallId(installId);
  return `services:
  grok-build:
    image: relmio-grok-build-${safeInstallId}:local
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    init: true
    environment:
      RELMIO_GATEWAY_TOKEN_SHA256: ${safeVerifier}
      RELMIO_GATEWAY_VERSION: ${PACKAGE_VERSION}
      RELMIO_GATEWAY_HOST: 0.0.0.0
      RELMIO_GATEWAY_PORT: "14502"
      RELMIO_GATEWAY_PUBLIC_PORT: "${safePort}"
      RELMIO_GATEWAY_INSTALL_ID: "${safeInstallId}"
    ports:
      - "127.0.0.1:${safePort}:14502"
    volumes:
      - grok-home:/home/node/.grok
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp:size=64m,mode=1777,nodev,nosuid
      - /run:size=16m,mode=0755,nodev,nosuid
    pids_limit: 128
    mem_limit: 2g
    cpus: 2.0
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:14502/health').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 9
      start_period: 20s
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "xai-grok-build"
      io.relmio.install: "${safeInstallId}"

networks:
  default:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "xai-grok-build"
      io.relmio.install: "${safeInstallId}"

volumes:
  grok-home:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "xai-grok-build"
      io.relmio.install: "${safeInstallId}"
`;
}

function renderCodexConfig({
  approvalPolicy,
  permissionProfile,
  permissionProfileBase,
}) {
  const filesystemPermissions = permissionProfile === "relmio-chat-readonly"
    ? `
[permissions.${permissionProfile}.filesystem]
":root" = "deny"
":minimal" = "read"
":tmpdir" = "deny"
":slash_tmp" = "deny"
"/workspace" = "read"
"/home/node/.relmio-siwc" = "deny"
`
    : `
[permissions.${permissionProfile}.filesystem]
"/home/node/.relmio-siwc" = "deny"
`;
  return `approval_policy = "${approvalPolicy}"
approvals_reviewer = "user"
allow_login_shell = false
check_for_update_on_startup = false
default_permissions = "${permissionProfile}"
model_provider = "openai_chatgpt_plan"
web_search = "disabled"

[model_providers.openai_chatgpt_plan]
name = "ChatGPT plan"
base_url = "https://api.openai.com/v1"
env_key = "ACCESS_TOKEN"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false

[analytics]
enabled = false

[feedback]
enabled = false

[permissions.${permissionProfile}]
extends = "${permissionProfileBase}"
${filesystemPermissions}

[permissions.${permissionProfile}.network]
enabled = false

[shell_environment_policy]
inherit = "none"
ignore_default_excludes = false

[agents]
enabled = false

[features]
apps = false
auth_elicitation = false
browser_use = false
browser_use_external = false
browser_use_full_cdp_access = false
code_mode_host = false
computer_use = false
fast_mode = false
goals = false
hooks = false
image_generation = false
in_app_browser = false
multi_agent = false
plugins = false
plugin_sharing = false
remote_plugin = false
shell_snapshot = false
skill_mcp_dependency_install = false
tool_call_mcp_elicitation = false
tool_suggest = false
`;
}

export function createCodexConfig() {
  return renderCodexConfig({
    approvalPolicy: "on-request",
    permissionProfile: "relmio-workspace",
    permissionProfileBase: ":workspace",
  });
}

export function createCodexChatConfig() {
  return renderCodexConfig({
    approvalPolicy: "never",
    permissionProfile: "relmio-chat-readonly",
    permissionProfileBase: ":read-only",
  });
}

function renderCodexRequirements({ approvalPolicy, permissionProfile }) {
  return `allowed_approval_policies = ["${approvalPolicy}"]
allowed_approvals_reviewers = ["user"]
allowed_web_search_modes = ["disabled"]
allow_managed_hooks_only = true
allow_remote_control = false
check_for_update_on_startup = false
allow_login_shell = false
default_permissions = "${permissionProfile}"

[allowed_permission_profiles]
"${permissionProfile}" = true

[feedback]
enabled = false

[features]
apps = false
auth_elicitation = false
browser_use = false
browser_use_external = false
browser_use_full_cdp_access = false
code_mode_host = false
computer_use = false
fast_mode = false
goals = false
hooks = false
image_generation = false
in_app_browser = false
multi_agent = false
plugins = false
plugin_sharing = false
remote_plugin = false
shell_snapshot = false
skill_mcp_dependency_install = false
tool_call_mcp_elicitation = false
tool_suggest = false
`;
}

export function createCodexRequirements() {
  return renderCodexRequirements({
    approvalPolicy: "on-request",
    permissionProfile: "relmio-workspace",
  });
}

export function createCodexChatRequirements() {
  return renderCodexRequirements({
    approvalPolicy: "never",
    permissionProfile: "relmio-chat-readonly",
  });
}

export function createCodexDockerfile() {
  return `FROM node:24-bookworm-slim

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts \\
    && npm install --global --ignore-scripts @openai/codex@${CODEX_CLI_VERSION} \\
    && npm cache clean --force \\
    && mkdir -p /etc/codex /home/node/.codex /home/node/.relmio-siwc /workspace \\
    && chown -R node:node /home/node/.codex /home/node/.relmio-siwc /workspace

COPY --chmod=0444 requirements.toml /etc/codex/requirements.toml
COPY --chown=node:node config.toml /home/node/.codex/config.toml
COPY --chown=node:node services/ ./services/
COPY --chown=node:node gateway/ ./gateway/
COPY --chown=node:node infrastructure/ ./infrastructure/

ENV CODEX_HOME=/home/node/.codex
WORKDIR /workspace
USER node

ENTRYPOINT ["node", "/app/gateway/codex-app-server.mjs"]
`;
}

export function createCodexChatDockerfile() {
  return `FROM node:24-bookworm-slim

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts \\
    && npm install --global --ignore-scripts @openai/codex@${CODEX_CLI_VERSION} \\
    && npm cache clean --force \\
    && mkdir -p /etc/codex /home/node/.codex /home/node/.relmio-siwc /workspace \\
    && chown -R node:node /home/node/.codex /home/node/.relmio-siwc /workspace

COPY --chmod=0444 requirements.toml /etc/codex/requirements.toml
COPY --chown=node:node config.toml /home/node/.codex/config.toml
COPY --chown=node:node services/ ./services/
COPY --chown=node:node gateway/ ./gateway/
COPY --chown=node:node infrastructure/ ./infrastructure/

ENV CODEX_HOME=/home/node/.codex
WORKDIR /workspace
USER node

ENTRYPOINT ["node", "/app/gateway/codex-chat.js"]
`;
}

export function createCodexChatComposeFile({ port, tokenSha256, installId, registrationId }) {
  const safePort = validateLocalPort(port);
  const safeVerifier = validateSha256Verifier(tokenSha256);
  const safeInstallId = validateInstallId(installId);
  const safeRegistrationId = validateSiwcRegistrationId(registrationId);
  const gatewayImage = `relmio-codex-chat-${safeInstallId}:local`;

  return `services:
  codex-chat:
    image: ${gatewayImage}
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    init: true
    environment:
      RELMIO_GATEWAY_TOKEN_SHA256: ${safeVerifier}
      RELMIO_GATEWAY_HOST: 0.0.0.0
      RELMIO_GATEWAY_PORT: "14501"
      RELMIO_PACKAGE_VERSION: "${PACKAGE_VERSION}"
      N8N_OPENAI_OAUTH_HOME: /home/node/.relmio-siwc
      RELMIO_REGISTRATION_ID: "${safeRegistrationId}"
      RELMIO_RUNTIME_ID: "${safeInstallId}"
    ports:
      - "127.0.0.1:${safePort}:14501"
    volumes:
      - codex-state:/home/node/.codex
      - siwc-store:/home/node/.relmio-siwc
      - codex-workspace:/workspace
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp:size=64m,mode=1777,nodev,nosuid
      - /run:size=16m,mode=0755,nodev,nosuid
      - /home/node/.cache:uid=1000,gid=1000,mode=0700,nodev,nosuid
    pids_limit: 128
    mem_limit: 2g
    cpus: 2.0
    ulimits:
      nofile:
        soft: 1024
        hard: 1024
      core: 0
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: "3"
    healthcheck:
      test:
        - CMD
        - node
        - -e
        - 'fetch("http://127.0.0.1:14501/health").then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))'
      interval: 10s
      timeout: 5s
      retries: 9
      start_period: 20s
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chat"
      io.relmio.install: "${safeInstallId}"

networks:
  default:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chat"
      io.relmio.install: "${safeInstallId}"

volumes:
  codex-state:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chat"
      io.relmio.install: "${safeInstallId}"
  siwc-store:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chat"
      io.relmio.install: "${safeInstallId}"
  codex-workspace:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chat"
      io.relmio.install: "${safeInstallId}"
`;
}

export function createCodexComposeFile({ port, tokenSha256, installId, registrationId }) {
  const safePort = validateLocalPort(port);
  const safeVerifier = validateSha256Verifier(tokenSha256);
  const safeInstallId = validateInstallId(installId);
  const safeRegistrationId = validateSiwcRegistrationId(registrationId);

  return `services:
  codex:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    init: true
    environment:
      RELMIO_GATEWAY_TOKEN_SHA256: ${safeVerifier}
      RELMIO_GATEWAY_HOST: 0.0.0.0
      RELMIO_GATEWAY_PORT: "4500"
      RELMIO_PACKAGE_VERSION: "${PACKAGE_VERSION}"
      N8N_OPENAI_OAUTH_HOME: /home/node/.relmio-siwc
      RELMIO_REGISTRATION_ID: "${safeRegistrationId}"
      RELMIO_RUNTIME_ID: "${safeInstallId}"
    ports:
      - "127.0.0.1:${safePort}:4500"
    volumes:
      - codex-state:/home/node/.codex
      - siwc-store:/home/node/.relmio-siwc
      - codex-workspace:/workspace
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp:size=64m,mode=1777,nodev,nosuid
      - /run:size=16m,mode=0755,nodev,nosuid
      - /home/node/.cache:uid=1000,gid=1000,mode=0700,nodev,nosuid
    pids_limit: 128
    mem_limit: 2g
    cpus: 2.0
    ulimits:
      nofile:
        soft: 1024
        hard: 1024
      core: 0
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: "3"
    healthcheck:
      test:
        - CMD
        - node
        - -e
        - 'fetch("http://127.0.0.1:4500/readyz").then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))'
      interval: 10s
      timeout: 5s
      retries: 9
      start_period: 20s
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chatgpt"
      io.relmio.install: "${safeInstallId}"

networks:
  default:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chatgpt"
      io.relmio.install: "${safeInstallId}"

volumes:
  codex-state:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chatgpt"
      io.relmio.install: "${safeInstallId}"
  siwc-store:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chatgpt"
      io.relmio.install: "${safeInstallId}"
  codex-workspace:
    labels:
      io.relmio.managed: "true"
      io.relmio.target: "codex-chatgpt"
      io.relmio.install: "${safeInstallId}"
`;
}