#!/usr/bin/env bash set -euo pipefail # Fail fast on broken Space metadata, duplicate templates, or stale cache artifacts. APP_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" export APP_ROOT python3 "${APP_ROOT}/scripts/preflight.py" # ============================================================ # Required non-model settings # ============================================================ : "${MARINE_API_URL:?Missing MARINE_API_URL variable}" # ============================================================ # Model provider and key. # # This deployment is intentionally ZAI-only. The official Codex CLI remains # the runtime, while ZAI/GLM-5.3 is its OpenAI-compatible model endpoint. # ============================================================ CODEX_PROVIDER="${CODEX_PROVIDER:-zai}" case "${CODEX_PROVIDER,,}" in auto|glm|zhipu|zai) CODEX_PROVIDER="zai" ;; *) echo "[startup] ERROR: this deployment only supports CODEX_PROVIDER=zai" exit 1 ;; esac MODEL_API_KEY="${ZAI_API_KEY:-${GLM_API_KEY:-${ZHIPU_API_KEY:-}}}" export OPENAI_BASE_URL="${OPENAI_BASE_URL:-https://open.bigmodel.cn/api/v1}" export CODEX_MODEL="${CODEX_MODEL:-glm-5.3}" export CODEX_PROVIDER_ID="ZAI" export CODEX_PROVIDER_NAME="ZAI" echo "[startup] Codex provider=${CODEX_PROVIDER_NAME}; model=${CODEX_MODEL}; base_url=${OPENAI_BASE_URL}" if [ -z "${MODEL_API_KEY}" ]; then echo "[startup] ERROR: no ${CODEX_PROVIDER_NAME} API key was injected." echo "[startup] Expected secret: ZAI_API_KEY (or GLM_API_KEY/ZHIPU_API_KEY)" echo "[startup] Available environment variable names containing KEY/API:" env | cut -d= -f1 | grep -E 'KEY|API' | sort || true exit 1 fi # ============================================================ # Official OpenAI Codex CLI runtime # ============================================================ export OPENAI_API_KEY="${MODEL_API_KEY}" # Both deployments use the official native Codex CLI Harness. Do not # silently fall back to the legacy HTTP adapter. export CODEX_HARNESS_RUNTIME="${CODEX_HARNESS_RUNTIME:-native}" export REQUIRE_CODEX_HARNESS="${REQUIRE_CODEX_HARNESS:-1}" if [ "${CODEX_HARNESS_RUNTIME}" != "native" ]; then echo "[startup] ERROR: CODEX_HARNESS_RUNTIME must be native" exit 1 fi # Both providers use the official Codex Responses transport. Disable the # optional WebSocket transport because the compatible endpoints expose HTTPS # Responses reliably and do not require a WebSocket session. export CODEX_DISABLE_WEBSOCKETS="${CODEX_DISABLE_WEBSOCKETS:-1}" # This service is a public data interface, not an operations shell. Refuse a # stale or accidental bypass setting before the official CLI is started. case "${CODEX_ALLOW_MCP_BYPASS:-0}" in 0|false|FALSE|no|NO|off|OFF|"") ;; *) echo "[startup] ERROR: CODEX_ALLOW_MCP_BYPASS is forbidden for this deployment" exit 1 ;; esac export CODEX_ALLOW_MCP_BYPASS=0 export MARINE_EXPORT_RANGE_MAX_DAYS="${MARINE_EXPORT_RANGE_MAX_DAYS:-31}" # ============================================================ # Web / Runtime # ============================================================ export PORT="${PORT:-7860}" export FISHERIES_EXPORT_ROOT="${FISHERIES_EXPORT_ROOT:-/tmp/squid_fisheries_exports}" export HF_FISHERIES_CACHE_ROOT="${HF_FISHERIES_CACHE_ROOT:-/tmp/squid_hf_fisheries_cache}" if [ -z "${PUBLIC_BASE_URL:-}" ] && [ -n "${SPACE_HOST:-}" ]; then export PUBLIC_BASE_URL="https://${SPACE_HOST}" fi # ============================================================ # Build private Marine MCP runtime configuration # ============================================================ RUNTIME_MCP_CONFIG="/tmp/codex-marine-mcp.json" export RUNTIME_MCP_CONFIG python3 - <<'PY_MCP_CONFIG' import json import os from pathlib import Path env = { "MARINE_API_URL": os.environ["MARINE_API_URL"], "HF_SQUID_DATASET_REPO": ( os.environ.get("HF_SQUID_DATASET_REPO") or os.environ.get("HF_DATASET_REPO") or "globalsquiddatabase/squid_dataset" ), "HF_TUNA_DATASET_REPO": ( os.environ.get("HF_TUNA_DATASET_REPO") or "globalsquiddatabase/Tuna-Fisheries-Dataset" ), "FISHERIES_EXPORT_ROOT": os.environ.get( "FISHERIES_EXPORT_ROOT", "/tmp/squid_fisheries_exports" ), "HF_FISHERIES_CACHE_ROOT": os.environ.get( "HF_FISHERIES_CACHE_ROOT", "/tmp/squid_hf_fisheries_cache" ), "HF_SQUID_DATASET_REVISION": ( os.environ.get("HF_SQUID_DATASET_REVISION") or os.environ.get("HF_DATASET_REVISION", "") ), "HF_TUNA_DATASET_REVISION": os.environ.get("HF_TUNA_DATASET_REVISION", ""), "PUBLIC_BASE_URL": os.environ.get("PUBLIC_BASE_URL", ""), "MARINE_PUBLIC_BASE_URL": os.environ.get("MARINE_PUBLIC_BASE_URL", ""), "MARINE_EXPORT_RANGE_MAX_DAYS": os.environ.get("MARINE_EXPORT_RANGE_MAX_DAYS", "31"), } hf_token = os.environ.get("HF_TOKEN", "").strip() if hf_token: env["HF_TOKEN"] = hf_token for name in ("LOCAL_SQUID_DATA_ROOT", "LOCAL_TUNA_DATA_ROOT"): if os.environ.get(name, "").strip(): env[name] = os.environ[name] cfg = { "timeouts": { "connect_timeout": 15, "execute_timeout": 900, "read_timeout": 900, }, "servers": { "marine": { "command": "python3", "args": [os.path.join(os.environ["APP_ROOT"], "marine_mcp.py")], "disabled": False, "required": False, "env": env, } }, } p = Path(os.environ["RUNTIME_MCP_CONFIG"]) p.write_text( json.dumps(cfg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) p.chmod(0o600) print("[startup] private Marine MCP runtime config ready") PY_MCP_CONFIG # Official Codex CLI configuration. The JSON file above is retained for # backwards compatibility with the adapter; native mode reads this TOML file. export CODEX_HOME="${CODEX_HOME:-${HOME:-/tmp}/.codex-home}" mkdir -p "${CODEX_HOME}" python3 - <<'PY_CODEX_CONFIG' import json import os from pathlib import Path def q(value): return json.dumps(str(value), ensure_ascii=False) home = Path(os.environ["CODEX_HOME"]) home.mkdir(parents=True, exist_ok=True) marine_env = { "MARINE_API_URL": os.environ["MARINE_API_URL"], "HF_SQUID_DATASET_REPO": os.environ.get("HF_SQUID_DATASET_REPO", "globalsquiddatabase/squid_dataset"), "HF_TUNA_DATASET_REPO": os.environ.get("HF_TUNA_DATASET_REPO", "globalsquiddatabase/Tuna-Fisheries-Dataset"), "FISHERIES_EXPORT_ROOT": os.environ.get("FISHERIES_EXPORT_ROOT", "/tmp/squid_fisheries_exports"), "HF_FISHERIES_CACHE_ROOT": os.environ.get("HF_FISHERIES_CACHE_ROOT", "/tmp/squid_hf_fisheries_cache"), "PUBLIC_BASE_URL": os.environ.get("PUBLIC_BASE_URL", ""), "MARINE_PUBLIC_BASE_URL": os.environ.get("MARINE_PUBLIC_BASE_URL", ""), "MARINE_EXPORT_RANGE_MAX_DAYS": os.environ.get("MARINE_EXPORT_RANGE_MAX_DAYS", "31"), } for name in ("HF_TOKEN", "HF_SQUID_DATASET_REVISION", "HF_TUNA_DATASET_REVISION"): if os.environ.get(name, "").strip(): marine_env[name] = os.environ[name] for name in ("LOCAL_SQUID_DATA_ROOT", "LOCAL_TUNA_DATA_ROOT"): if os.environ.get(name, "").strip(): marine_env[name] = os.environ[name] auth_line = ( "experimental_bearer_token = " + q(os.environ["OPENAI_API_KEY"]) if os.environ.get("CODEX_PROVIDER") == "zai" else "env_key = \"OPENAI_API_KEY\"" ) lines = [ "model_provider = " + q(os.environ["CODEX_PROVIDER_ID"]), "model = " + q(os.environ.get("CODEX_MODEL", "glm-5.3")), "model_reasoning_effort = " + q(os.environ.get("CODEX_REASONING_EFFORT", "max")), "cli_auth_credentials_store = \"file\"", "", "[model_providers." + os.environ["CODEX_PROVIDER_ID"] + "]", "name = " + q(os.environ["CODEX_PROVIDER_NAME"]), "base_url = " + q(os.environ.get("OPENAI_BASE_URL", "https://open.bigmodel.cn/api/v1")), auth_line, "requires_openai_auth = false", "wire_api = \"responses\"", "supports_websockets = false", "", "[mcp_servers.marine]", "command = \"python3\"", "args = [" + q(os.path.join(os.environ["APP_ROOT"], "marine_mcp.py")) + "]", "required = false", # Native/headless Codex runs cannot display an interactive MCP approval # prompt. Marine is the deployment's explicitly configured, read-only # data server, so approve its tool calls in the CLI config instead of # leaving requests pending until the harness timeout. "default_tools_approval_mode = \"approve\"", "startup_timeout_sec = 30", "tool_timeout_sec = 900", "env = { " + ", ".join(f"{k} = {q(v)}" for k, v in marine_env.items()) + " }", ] # Publish metadata for whichever OpenAI-compatible provider/model is active. if os.environ.get("CODEX_MODEL", "").strip(): catalog = { "models": [{ "slug": os.environ.get("CODEX_MODEL", "glm-5.3"), "display_name": os.environ.get("CODEX_MODEL", "glm-5.3"), "description": ( os.environ.get("CODEX_PROVIDER_NAME", "OpenAI-compatible") + " model" ), "base_instructions": "You are a helpful coding assistant. Use the configured Marine MCP tools when the user asks for marine data.", "model_messages": { "instructions_template": "You are a helpful coding assistant. Follow the user's request and use available tools when needed." }, "default_reasoning_level": os.environ.get("CODEX_REASONING_EFFORT", "max"), "supported_reasoning_levels": [ {"effort": "low", "description": "Light reasoning"}, {"effort": "medium", "description": "Balanced reasoning"}, {"effort": "high", "description": "Deep reasoning"}, {"effort": "max", "description": "Maximum reasoning"}, ], # These fields are required by the official Codex model catalog # parser. Keep the legacy shell_command spelling accepted by # Codex 0.147.x while preserving the standard tool capability. "shell_type": "shell_command", "visibility": "list", "supported_in_api": True, "priority": 1, "truncation_policy": {"mode": "tokens", "limit": 204800}, "supports_reasoning_summaries": True, "default_reasoning_summary": "none", "support_verbosity": False, "default_verbosity": None, "context_window": 204800, "max_context_window": 204800, "effective_context_window_percent": 95, "supports_parallel_tool_calls": True, "experimental_supported_tools": [], "input_modalities": ["text"], }] } (home / "models.json").write_text( json.dumps(catalog, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) lines.insert(3, "model_catalog_json = " + q(str(home / "models.json"))) path = home / "config.toml" path.write_text("\n".join(lines) + "\n", encoding="utf-8") path.chmod(0o600) print(f"[startup] official Codex config ready: {path}") PY_CODEX_CONFIG # ============================================================ # Marine API health check # ============================================================ if ! curl -fsS --max-time 12 "${MARINE_API_URL%/}/health"; then echo "[startup] WARNING: Marine API health check failed. Chat remains available; Ocean tools will be unavailable until MARINE_API_URL is corrected." fi echo # ============================================================ # Start Squid Agent Web UI # ============================================================ exec uvicorn ui_server:app \ --host 0.0.0.0 \ --port "${PORT}" \ --proxy-headers