// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/platform-infra-sub2api-codex.ts.

// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:1-319 for #903.

import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
  codexPoolSentinelSummary,
  codexPoolSentinelRuntimeImage,
  readCodexPoolSentinelConfig,
  renderCodexPoolSentinelManifest,
  type CodexPoolSentinelConfig,
  type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";

import { readCodexPoolConfig } from "./config";
import { codexPoolRuntimeTarget } from "./runtime-target";

export const serviceName = "sub2api";

export const fieldManager = "unidesk-platform-infra";

export const sub2apiConfigPath = rootPath("config", "platform-infra", "sub2api.yaml");

export const codexPoolConfigPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml");

export const sentinelImageDockerfilePath = rootPath("src", "components", "platform-infra", "sub2api", "sentinel.Dockerfile");

export const remoteJobDir = "/tmp/unidesk-platform-infra-sub2api-codex-pool";

export const remoteJobTimeoutMs = 15 * 60_000;

export const remoteJobPollMs = 5_000;

export interface DisclosureOptions {
  full: boolean;
  raw: boolean;
  targetId: string;
}

export interface SyncOptions extends DisclosureOptions {
  confirm: boolean;
  pruneRemoved: boolean;
}

export interface ConfirmOptions extends DisclosureOptions {
  confirm: boolean;
}

export interface SentinelProbeOptions extends ConfirmOptions {
  accounts: string[];
}

export interface SentinelReportOptions extends DisclosureOptions {
  events: number;
}

export interface TraceOptions extends DisclosureOptions {
  requestId: string | null;
  since: string;
  tail: number;
  contextSeconds: number;
  showLines: boolean;
}

export interface SentinelImageOptions extends DisclosureOptions {
  action: "status" | "build";
  confirm: boolean;
  dryRun: boolean;
}

export interface CodexPoolRuntimeTarget {
  id: string;
  route: string;
  namespace: string;
  serviceName: string;
  serviceDns: string;
  publicBaseUrl: string | null;
  publicExposure: PublicServiceExposure | null;
  appSecretName: string;
  secretsRoot: string;
  sentinelEnabled: boolean;
  sentinelImageBuild: {
    baseImageCachePolicy: "pull" | "local-if-present";
    noProxy: string;
  };
  egressProxy: {
    enabled: boolean;
    applyToSentinel: boolean;
    serviceName: string;
    listenPort: number;
    httpProxy: string;
    noProxy: string;
  } | null;
}

export interface CodexProfile {
  profile: string;
  accountName: string;
  configFile: string;
  authFile: string;
  provider: string;
  baseUrl: string;
  wireApi: string | null;
  model: string | null;
  envKey: string | null;
  apiKey: string | null;
  apiKeySource: "auth-json" | "env" | null;
  openaiResponsesWebSocketsV2Mode: OpenAIResponsesWebSocketsV2Mode | null;
  upstreamUserAgent: string | null;
  trustUpstream: boolean;
  sentinelProtect: CodexSentinelProtectPolicy;
  priority: number;
  capacity: number;
  loadFactor: number;
  tempUnschedulable: CodexTempUnschedulablePolicy;
  authOpenAIKeyShape: string;
  ok: boolean;
  error: string | null;
}

export type OpenAIResponsesWebSocketsV2Mode = "off" | "ctx_pool" | "passthrough";

export interface CodexTempUnschedulableRule {
  statusCode: number;
  keywords: string[];
  durationMinutes: number;
  description: string | null;
}

export interface CodexTempUnschedulablePolicy {
  enabled: boolean;
  rules: CodexTempUnschedulableRule[];
}

export interface CodexPoolConfig {
  version: number;
  kind: string;
  metadata: {
    id: string;
    owner: string;
    relatedIssues: number[];
  };
  groupName: string;
  groupDescription: string;
  apiKeyName: string;
  apiKeySecretName: string;
  apiKeySecretKey: string;
  adminEmailDefault: string;
  minOwnerBalanceUsd: number;
  minOwnerConcurrency: number;
  minOwnerConcurrencySource: "auto" | "yaml";
  defaultAccountPriority: number;
  defaultAccountCapacity: number;
  defaultAccountLoadFactor: number;
  defaultTempUnschedulable: CodexTempUnschedulablePolicy;
  defaultSentinelProtect: CodexSentinelProtectPolicy;
  profiles: CodexPoolProfileConfig[];
  manualAccounts: CodexPoolManualAccountsConfig;
  publicExposure: CodexPoolPublicExposureConfig;
  localCodex: CodexPoolLocalCodexConfig;
  sentinel: CodexPoolSentinelConfig;
}

export interface CodexPoolManualAccountsConfig {
  bindingSources: CodexPoolManualBindingSourcesConfig;
  protected: CodexPoolManualAccountProtection[];
}

export type CodexPoolManualBindingKind = "proxy" | "group";

export type CodexPoolManualBindingProvider = "target-egress-proxy" | "pool-group";

export interface CodexPoolManualBindingSource {
  id: string;
  enabled: boolean;
  kind: CodexPoolManualBindingKind;
  provider: CodexPoolManualBindingProvider;
  description: string | null;
}

export interface CodexPoolManualBindingSourcesConfig {
  items: CodexPoolManualBindingSource[];
  byId: Record<string, CodexPoolManualBindingSource>;
}

export interface CodexPoolManualAccountProxyBinding {
  enabled: boolean;
  source: string;
  proxyName: string;
}

export interface CodexPoolManualAccountGroupBinding {
  enabled: boolean;
  source: string;
}

export interface CodexPoolManualAccountProtection {
  accountName: string;
  reason: string | null;
  proxyBinding: CodexPoolManualAccountProxyBinding | null;
  groupBinding: CodexPoolManualAccountGroupBinding | null;
}

export interface CodexPoolProfileConfig {
  profile: string;
  accountName: string | null;
  configFile: string;
  authFile: string;
  fallbackConfigFile: string | null;
  fallbackAuthFile: string | null;
  openaiResponsesWebSocketsV2Mode: OpenAIResponsesWebSocketsV2Mode | null;
  upstreamUserAgent: string | null;
  trustUpstream: boolean;
  sentinelProtect: CodexSentinelProtectPolicy;
  priority: number;
  capacity: number | null;
  loadFactor: number | null;
  tempUnschedulable: CodexTempUnschedulablePolicy;
}

export interface CodexSentinelProtectPolicy {
  enabled: boolean;
  consecutiveFailures: number;
  initialRetryDelaySeconds: number;
  maxRetryDelaySeconds: number;
  backoffMultiplier: number;
}

export interface CodexPoolPublicExposureConfig {
  enabled: boolean;
  proxyName: string;
  configMapName: string;
  deploymentName: string;
  frpcImage: string;
  serverAddr: string;
  serverPort: number;
  remotePort: number;
  localIP: string;
  localPort: number;
  publicBaseUrl: string;
  masterBaseUrl: string;
  masterFrps: {
    configPath: string;
    containerName: string;
  };
  masterCaddy: {
    enabled: boolean;
    domain: string;
    configPath: string;
    serviceName: string;
    upstreamBaseUrl: string;
    responseHeaderTimeoutSeconds: number;
    edgeRetry: CodexPoolCaddyEdgeRetryConfig;
  };
}

export interface CodexPoolCaddyEdgeRetryConfig {
  enabled: boolean;
  tryDurationSeconds: number;
  tryIntervalMilliseconds: number;
  retryMatch: {
    methods: string[];
    paths: string[];
  };
}

export interface CodexPoolLocalCodexConfig {
  backupSuffix: string;
  providerName: string;
  wireApi: string;
  modelContextWindow: number;
  modelAutoCompactTokenLimit: number;
  supportsWebSockets: boolean;
  responsesWebSocketsV2: boolean;
  responsesSmokeModel: string;
}

export interface CodexLocalConsumerTomlOptions {
  providerName: string;
  baseUrl: string;
  wireApi: string;
  modelContextWindow: number;
  modelAutoCompactTokenLimit: number;
  supportsWebSockets: boolean;
  responsesWebSocketsV2: boolean;
}

export function codexPoolHelp(): unknown {
  const pool = readCodexPoolConfig();
  const runtimeTarget = codexPoolRuntimeTarget();
  return {
    command: "platform-infra sub2api codex-pool plan|sync|validate|trace|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local",
    output: "json, except trace and sentinel-report default to low-noise text tables",
    usage: [
      "bun scripts/cli.ts platform-infra sub2api codex-pool plan",
      "bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601",
      "bun scripts/cli.ts platform-infra sub2api codex-pool sync [--target D601] --confirm [--prune-removed]",
      "bun scripts/cli.ts platform-infra sub2api codex-pool validate [--target D601] [--full|--raw]",
      "bun scripts/cli.ts platform-infra sub2api codex-pool trace [--target D601] --request-id <requestId> [--since 24h|--tail 20000|--context-seconds 300|--show-lines|--raw]",
      "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status [--target D601]",
      "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build [--target D601] --confirm",
      "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe [--target D601] --account unidesk-codex-hy --confirm",
      "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report [--target D601] [--events 20|--full|--raw]",
      "bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes [--target D601] --confirm",
      "bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm",
      "bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm",
    ],
    description: "Import YAML-selected ~/.codex API-key profiles into one Sub2API OpenAI pool, expose one unified API_KEY, and optionally configure this master server's ~/.codex consumer endpoint.",
    target: {
      default: runtimeTarget.id,
      route: runtimeTarget.route,
      namespace: runtimeTarget.namespace,
      serviceDns: runtimeTarget.serviceDns,
      configPath: codexPoolConfigPath,
      poolGroupName: pool.groupName,
      poolApiKeySecretName: pool.apiKeySecretName,
      poolApiKeySecretKey: pool.apiKeySecretKey,
      publicBaseUrl: runtimeTarget.publicBaseUrl,
      publicExposureSource: `${sub2apiConfigPath}.targets[${runtimeTarget.id}].publicExposure`,
      sentinelMonitorEnabled: pool.sentinel.monitor.enabled,
      sentinelActionsEnabled: pool.sentinel.actions.enabled,
      secretValuesPrinted: false,
    },
  };
}
