Commit ·
12032b1
1
Parent(s): 199daad
switch Codex Harness to OpenCode API
Browse files- Dockerfile +3 -3
- README.md +2 -2
- harness_safe.cordis.yml +8 -28
- services/chat_runtime.py +2 -2
- start.sh +7 -50
- static/js/app.js +4 -4
- ui_server.py +1 -1
Dockerfile
CHANGED
|
@@ -6,8 +6,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 6 |
python3 python3-pip python3-venv ca-certificates curl git bash procps \
|
| 7 |
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
RUN npm install -g
|
| 11 |
|
| 12 |
RUN usermod -l user -d /home/user -m node && groupmod -n user node
|
| 13 |
|
|
@@ -15,7 +15,7 @@ ENV HOME=/home/user \
|
|
| 15 |
PATH=/home/user/.local/bin:/usr/local/bin:$PATH \
|
| 16 |
PYTHONUNBUFFERED=1 \
|
| 17 |
PORT=7860 \
|
| 18 |
-
|
| 19 |
|
| 20 |
WORKDIR /home/user/app
|
| 21 |
|
|
|
|
| 6 |
python3 python3-pip python3-venv ca-certificates curl git bash procps \
|
| 7 |
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
|
| 9 |
+
# Official OpenAI Codex CLI runtime.
|
| 10 |
+
RUN npm install -g @openai/codex
|
| 11 |
|
| 12 |
RUN usermod -l user -d /home/user -m node && groupmod -n user node
|
| 13 |
|
|
|
|
| 15 |
PATH=/home/user/.local/bin:/usr/local/bin:$PATH \
|
| 16 |
PYTHONUNBUFFERED=1 \
|
| 17 |
PORT=7860 \
|
| 18 |
+
|
| 19 |
|
| 20 |
WORKDIR /home/user/app
|
| 21 |
|
README.md
CHANGED
|
@@ -100,10 +100,10 @@ favorites and preferences. Backups exclude credentials, tokens and uploaded
|
|
| 100 |
file contents, providing a manual cross-device migration path without claiming
|
| 101 |
server-side synchronization.
|
| 102 |
|
| 103 |
-
Custom chat UI backed by
|
| 104 |
|
| 105 |
Public UI: port 7860.
|
| 106 |
-
Private
|
| 107 |
The Runtime token never enters the browser.
|
| 108 |
|
| 109 |
## Functional sidebar
|
|
|
|
| 100 |
file contents, providing a manual cross-device migration path without claiming
|
| 101 |
server-side synchronization.
|
| 102 |
|
| 103 |
+
Custom chat UI backed by OpenAI Codex/OpenCode and a restricted Marine MCP bridge.
|
| 104 |
|
| 105 |
Public UI: port 7860.
|
| 106 |
+
Private OpenAI Codex Runtime: 127.0.0.1:7878.
|
| 107 |
The Runtime token never enters the browser.
|
| 108 |
|
| 109 |
## Functional sidebar
|
harness_safe.cordis.yml
CHANGED
|
@@ -1,31 +1,11 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
maxTokensAsSuccess: true
|
| 5 |
-
|
| 6 |
-
- id: llm-deepseek
|
| 7 |
-
name: '@deepseek-ai/dsh-llm-deepseek'
|
| 8 |
-
config:
|
| 9 |
-
apiKeyEnv: DEEPSEEK_API_KEY
|
| 10 |
-
streamIdleTimeoutMs: 300000
|
| 11 |
-
models:
|
| 12 |
-
- id: !!js process.env.DSH_MODEL ?? 'glm-5.2'
|
| 13 |
-
contextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 128000)
|
| 14 |
|
| 15 |
-
- id:
|
| 16 |
-
name:
|
| 17 |
config:
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
workspaceContext: false
|
| 22 |
-
skills:
|
| 23 |
-
enabled: false
|
| 24 |
-
toolBash: false
|
| 25 |
-
toolJobs: false
|
| 26 |
|
| 27 |
-
- id: sessions
|
| 28 |
-
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
| 29 |
-
config:
|
| 30 |
-
root: !!js process.env.DSH_SESSION_ROOT ?? '/tmp/deepseek-harness-sessions'
|
| 31 |
-
compression: none
|
|
|
|
| 1 |
+
# Codex Harness configuration
|
| 2 |
+
# Model execution is handled by official OpenAI Codex CLI.
|
| 3 |
+
# Provider endpoint/key are injected through OPENAI-compatible environment variables.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
- id: codex-harness
|
| 6 |
+
name: "@openai/codex"
|
| 7 |
config:
|
| 8 |
+
modelEnv: CODEX_MODEL
|
| 9 |
+
apiKeyEnv: OPENAI_API_KEY
|
| 10 |
+
baseUrlEnv: OPENAI_BASE_URL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
services/chat_runtime.py
CHANGED
|
@@ -263,7 +263,7 @@ async def stream_chat(tid,prompt):
|
|
| 263 |
})
|
| 264 |
turn=((tr or {}).get("turn") or {}).get("id")
|
| 265 |
answer=""
|
| 266 |
-
yield out("status",{"text":"
|
| 267 |
|
| 268 |
async for rec in events(tid,since):
|
| 269 |
if turn and rec.get("turn_id") and rec["turn_id"]!=turn:
|
|
@@ -364,7 +364,7 @@ async def stream_chat(tid,prompt):
|
|
| 364 |
if detail:
|
| 365 |
raise RuntimeError(detail)
|
| 366 |
raise RuntimeError(
|
| 367 |
-
"
|
| 368 |
)
|
| 369 |
final_answer=_sanitize_final_answer(answer)
|
| 370 |
if not final_answer:
|
|
|
|
| 263 |
})
|
| 264 |
turn=((tr or {}).get("turn") or {}).get("id")
|
| 265 |
answer=""
|
| 266 |
+
yield out("status",{"text":"Codex 正在处理…","stage":"compose","step":4,"total_steps":5})
|
| 267 |
|
| 268 |
async for rec in events(tid,since):
|
| 269 |
if turn and rec.get("turn_id") and rec["turn_id"]!=turn:
|
|
|
|
| 364 |
if detail:
|
| 365 |
raise RuntimeError(detail)
|
| 366 |
raise RuntimeError(
|
| 367 |
+
"Codex 回合已结束,但 CodeWhale 没有产生 assistant 文本。"
|
| 368 |
)
|
| 369 |
final_answer=_sanitize_final_answer(answer)
|
| 370 |
if not final_answer:
|
start.sh
CHANGED
|
@@ -8,33 +8,34 @@ python3 /home/user/app/scripts/preflight.py
|
|
| 8 |
# Required non-model settings
|
| 9 |
# ============================================================
|
| 10 |
|
| 11 |
-
: "${CODEWHALE_RUNTIME_TOKEN:?Missing CODEWHALE_RUNTIME_TOKEN secret}"
|
| 12 |
: "${MARINE_API_URL:?Missing MARINE_API_URL variable}"
|
| 13 |
|
| 14 |
# ============================================================
|
| 15 |
# Model key
|
| 16 |
#
|
| 17 |
-
# We do NOT require or use
|
| 18 |
# Prefer the dedicated OpenCode Go secret.
|
| 19 |
# Fall back to OPENAI_API_KEY only if you chose to store the same
|
| 20 |
# OpenCode Go key under that name in Hugging Face.
|
| 21 |
# ============================================================
|
| 22 |
|
| 23 |
-
MODEL_API_KEY="${OPENAI_API_KEY:-}"
|
| 24 |
|
| 25 |
if [ -z "${MODEL_API_KEY}" ]; then
|
| 26 |
echo "[startup] ERROR: no OpenCode Go key was injected."
|
| 27 |
-
echo "[startup] Expected: OPENAI_API_KEY"
|
| 28 |
echo "[startup] Available environment variable names containing KEY/API:"
|
| 29 |
env | cut -d= -f1 | grep -E 'KEY|API' | sort || true
|
| 30 |
exit 1
|
| 31 |
fi
|
| 32 |
|
| 33 |
# ============================================================
|
| 34 |
-
#
|
| 35 |
# ============================================================
|
| 36 |
|
| 37 |
export OPENAI_API_KEY="${MODEL_API_KEY}"
|
|
|
|
|
|
|
| 38 |
export CODEX_MODEL="${CODEX_MODEL:-gpt-5-codex}"
|
| 39 |
|
| 40 |
# ============================================================
|
|
@@ -42,7 +43,6 @@ export CODEX_MODEL="${CODEX_MODEL:-gpt-5-codex}"
|
|
| 42 |
# ============================================================
|
| 43 |
|
| 44 |
export PORT="${PORT:-7860}"
|
| 45 |
-
export CODEWHALE_INTERNAL_URL="http://127.0.0.1:7878"
|
| 46 |
export FISHERIES_EXPORT_ROOT="${FISHERIES_EXPORT_ROOT:-/tmp/squid_fisheries_exports}"
|
| 47 |
export HF_FISHERIES_CACHE_ROOT="${HF_FISHERIES_CACHE_ROOT:-/tmp/squid_hf_fisheries_cache}"
|
| 48 |
if [ -z "${PUBLIC_BASE_URL:-}" ] && [ -n "${SPACE_HOST:-}" ]; then
|
|
@@ -53,7 +53,7 @@ fi
|
|
| 53 |
# Build private Marine MCP runtime configuration
|
| 54 |
# ============================================================
|
| 55 |
|
| 56 |
-
RUNTIME_MCP_CONFIG="/tmp/
|
| 57 |
export RUNTIME_MCP_CONFIG
|
| 58 |
|
| 59 |
python3 - <<'PY_MCP_CONFIG'
|
|
@@ -117,49 +117,6 @@ p.chmod(0o600)
|
|
| 117 |
print("[startup] private Marine MCP runtime config ready")
|
| 118 |
PY_MCP_CONFIG
|
| 119 |
|
| 120 |
-
# Legacy CodeWhale MCP-config variable name.
|
| 121 |
-
# This only points to the MCP config file; it does NOT select DeepSeek.
|
| 122 |
-
export DEEPSEEK_MCP_CONFIG="${RUNTIME_MCP_CONFIG}"
|
| 123 |
-
|
| 124 |
-
# ============================================================
|
| 125 |
-
# Startup information
|
| 126 |
-
# ============================================================
|
| 127 |
-
|
| 128 |
-
echo "[startup] CodeWhale:"
|
| 129 |
-
codewhale --version
|
| 130 |
-
echo "[startup] provider=${CODEWHALE_PROVIDER}"
|
| 131 |
-
echo "[startup] model=${CODEWHALE_MODEL}"
|
| 132 |
-
echo "[startup] endpoint=${OPENAI_BASE_URL:-}"
|
| 133 |
-
echo "[startup] OpenCode Go key present=yes"
|
| 134 |
-
|
| 135 |
-
# ============================================================
|
| 136 |
-
# Start CodeWhale Runtime
|
| 137 |
-
# ============================================================
|
| 138 |
-
|
| 139 |
-
codewhale app-server --http \
|
| 140 |
-
--host 127.0.0.1 \
|
| 141 |
-
--port 7878 \
|
| 142 |
-
--auth-token "${CODEWHALE_RUNTIME_TOKEN}" &
|
| 143 |
-
|
| 144 |
-
CW_PID=$!
|
| 145 |
-
trap 'kill "$CW_PID" 2>/dev/null || true' EXIT INT TERM
|
| 146 |
-
|
| 147 |
-
for i in $(seq 1 60); do
|
| 148 |
-
if curl -fsS --max-time 2 \
|
| 149 |
-
http://127.0.0.1:7878/health \
|
| 150 |
-
>/dev/null 2>&1; then
|
| 151 |
-
echo "[startup] private CodeWhale Runtime ready"
|
| 152 |
-
break
|
| 153 |
-
fi
|
| 154 |
-
|
| 155 |
-
kill -0 "$CW_PID" 2>/dev/null || {
|
| 156 |
-
echo "[startup] CodeWhale exited"
|
| 157 |
-
exit 1
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
sleep 1
|
| 161 |
-
done
|
| 162 |
-
|
| 163 |
# ============================================================
|
| 164 |
# Marine API health check
|
| 165 |
# ============================================================
|
|
|
|
| 8 |
# Required non-model settings
|
| 9 |
# ============================================================
|
| 10 |
|
|
|
|
| 11 |
: "${MARINE_API_URL:?Missing MARINE_API_URL variable}"
|
| 12 |
|
| 13 |
# ============================================================
|
| 14 |
# Model key
|
| 15 |
#
|
| 16 |
+
# We do NOT require or use OPENCODE_API_KEY.
|
| 17 |
# Prefer the dedicated OpenCode Go secret.
|
| 18 |
# Fall back to OPENAI_API_KEY only if you chose to store the same
|
| 19 |
# OpenCode Go key under that name in Hugging Face.
|
| 20 |
# ============================================================
|
| 21 |
|
| 22 |
+
MODEL_API_KEY="${OPENCODE_API_KEY:-${OPENAI_API_KEY:-}}"
|
| 23 |
|
| 24 |
if [ -z "${MODEL_API_KEY}" ]; then
|
| 25 |
echo "[startup] ERROR: no OpenCode Go key was injected."
|
| 26 |
+
echo "[startup] Expected: OPENCODE_API_KEY or OPENAI_API_KEY"
|
| 27 |
echo "[startup] Available environment variable names containing KEY/API:"
|
| 28 |
env | cut -d= -f1 | grep -E 'KEY|API' | sort || true
|
| 29 |
exit 1
|
| 30 |
fi
|
| 31 |
|
| 32 |
# ============================================================
|
| 33 |
+
# Official OpenAI Codex CLI runtime
|
| 34 |
# ============================================================
|
| 35 |
|
| 36 |
export OPENAI_API_KEY="${MODEL_API_KEY}"
|
| 37 |
+
# OpenCode is an OpenAI-compatible provider for Codex CLI
|
| 38 |
+
export OPENAI_BASE_URL="${OPENCODE_BASE_URL:-${OPENAI_BASE_URL:-}}"
|
| 39 |
export CODEX_MODEL="${CODEX_MODEL:-gpt-5-codex}"
|
| 40 |
|
| 41 |
# ============================================================
|
|
|
|
| 43 |
# ============================================================
|
| 44 |
|
| 45 |
export PORT="${PORT:-7860}"
|
|
|
|
| 46 |
export FISHERIES_EXPORT_ROOT="${FISHERIES_EXPORT_ROOT:-/tmp/squid_fisheries_exports}"
|
| 47 |
export HF_FISHERIES_CACHE_ROOT="${HF_FISHERIES_CACHE_ROOT:-/tmp/squid_hf_fisheries_cache}"
|
| 48 |
if [ -z "${PUBLIC_BASE_URL:-}" ] && [ -n "${SPACE_HOST:-}" ]; then
|
|
|
|
| 53 |
# Build private Marine MCP runtime configuration
|
| 54 |
# ============================================================
|
| 55 |
|
| 56 |
+
RUNTIME_MCP_CONFIG="/tmp/codex-marine-mcp.json"
|
| 57 |
export RUNTIME_MCP_CONFIG
|
| 58 |
|
| 59 |
python3 - <<'PY_MCP_CONFIG'
|
|
|
|
| 117 |
print("[startup] private Marine MCP runtime config ready")
|
| 118 |
PY_MCP_CONFIG
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
# ============================================================
|
| 121 |
# Marine API health check
|
| 122 |
# ============================================================
|
static/js/app.js
CHANGED
|
@@ -905,7 +905,7 @@ function openServiceDiagnostics(data,appState){
|
|
| 905 |
document.querySelector(".drawer-backdrop")?.remove();
|
| 906 |
const marine=data.marine||{},hf=data.huggingface||{},d=data.diagnostic||{};
|
| 907 |
const items=[
|
| 908 |
-
["
|
| 909 |
["Marine API",!!marine.health?.ok,marine.health?.response_ms,serviceText(marine.health)],
|
| 910 |
["Domains",!!marine.domains?.ok,marine.domains?.response_ms,serviceText(marine.domains)],
|
| 911 |
["Ocean",!!marine.ocean?.ok,marine.ocean?.response_ms,serviceText(marine.ocean)],
|
|
@@ -961,7 +961,7 @@ async function renderServices(){
|
|
| 961 |
const [appState,data]=await Promise.all([j("/api/status"),j("/api/sidebar/services")]);
|
| 962 |
const marine=data.marine||{},hf=data.huggingface||{},d=data.diagnostic||{};
|
| 963 |
const rows=[
|
| 964 |
-
{name:appState.harness_available?"
|
| 965 |
{name:"学校 Marine API",ok:!!marine.health?.ok,detail:serviceText(marine.health),ms:marine.health?.response_ms},
|
| 966 |
{name:"Ocean 数据域",ok:!!marine.ocean?.ok,detail:serviceText(marine.ocean),ms:marine.ocean?.response_ms},
|
| 967 |
{name:"Tuna 渔业数据",ok:!!hf.ok&&Number(hf.tuna_file_count||0)>0,detail:hf.ok?("Hugging Face Dataset · "+(hf.tuna_file_count||0)+" 个匹配文件 · "+formatBytes(hf.tuna_size_bytes)):(hf.error||"读取失败"),ms:hf.response_ms},
|
|
@@ -1516,8 +1516,8 @@ async function status(){
|
|
| 1516 |
const ok=llmOk&&s.marine_api;
|
| 1517 |
$("dot").className="dot "+(ok?"ok":"warn");
|
| 1518 |
const runtimeName=s.harness_available
|
| 1519 |
-
? "
|
| 1520 |
-
: "
|
| 1521 |
$("st").textContent=ok
|
| 1522 |
? runtimeName+" · "+(s.marine_mcp?"Ocean MCP 已连接":"数据服务在线")
|
| 1523 |
: "部分服务不可用";
|
|
|
|
| 905 |
document.querySelector(".drawer-backdrop")?.remove();
|
| 906 |
const marine=data.marine||{},hf=data.huggingface||{},d=data.diagnostic||{};
|
| 907 |
const items=[
|
| 908 |
+
["OpenAI Codex Harness",!!(appState.harness_available||appState.runtime),null,appState.harness_model||appState.model||"-"],
|
| 909 |
["Marine API",!!marine.health?.ok,marine.health?.response_ms,serviceText(marine.health)],
|
| 910 |
["Domains",!!marine.domains?.ok,marine.domains?.response_ms,serviceText(marine.domains)],
|
| 911 |
["Ocean",!!marine.ocean?.ok,marine.ocean?.response_ms,serviceText(marine.ocean)],
|
|
|
|
| 961 |
const [appState,data]=await Promise.all([j("/api/status"),j("/api/sidebar/services")]);
|
| 962 |
const marine=data.marine||{},hf=data.huggingface||{},d=data.diagnostic||{};
|
| 963 |
const rows=[
|
| 964 |
+
{name:appState.harness_available?"OpenAI Codex Harness":"OpenAI Codex Runtime",ok:!!(appState.harness_available||appState.runtime),detail:"模型:"+(appState.harness_model||appState.model||"-"),ms:null},
|
| 965 |
{name:"学校 Marine API",ok:!!marine.health?.ok,detail:serviceText(marine.health),ms:marine.health?.response_ms},
|
| 966 |
{name:"Ocean 数据域",ok:!!marine.ocean?.ok,detail:serviceText(marine.ocean),ms:marine.ocean?.response_ms},
|
| 967 |
{name:"Tuna 渔业数据",ok:!!hf.ok&&Number(hf.tuna_file_count||0)>0,detail:hf.ok?("Hugging Face Dataset · "+(hf.tuna_file_count||0)+" 个匹配文件 · "+formatBytes(hf.tuna_size_bytes)):(hf.error||"读取失败"),ms:hf.response_ms},
|
|
|
|
| 1516 |
const ok=llmOk&&s.marine_api;
|
| 1517 |
$("dot").className="dot "+(ok?"ok":"warn");
|
| 1518 |
const runtimeName=s.harness_available
|
| 1519 |
+
? "OpenAI Codex Harness"
|
| 1520 |
+
: "OpenAI Codex fallback";
|
| 1521 |
$("st").textContent=ok
|
| 1522 |
? runtimeName+" · "+(s.marine_mcp?"Ocean MCP 已连接":"数据服务在线")
|
| 1523 |
: "部分服务不可用";
|
ui_server.py
CHANGED
|
@@ -2746,7 +2746,7 @@ input::placeholder{color:#526f86}
|
|
| 2746 |
<div class="visual-copy">
|
| 2747 |
<span class="eyebrow"><i></i>SECURE RESEARCH WORKSPACE</span>
|
| 2748 |
<h1>连接全球海洋数据<br>与智能分析能力</h1>
|
| 2749 |
-
<p>统一访问 Ocean、Tuna 与 Squid 数据资产,连接学校数据服务器、
|
| 2750 |
</div>
|
| 2751 |
<div class="feature-row">
|
| 2752 |
<span class="feature">🌊 Ocean Data</span>
|
|
|
|
| 2746 |
<div class="visual-copy">
|
| 2747 |
<span class="eyebrow"><i></i>SECURE RESEARCH WORKSPACE</span>
|
| 2748 |
<h1>连接全球海洋数据<br>与智能分析能力</h1>
|
| 2749 |
+
<p>统一访问 Ocean、Tuna 与 Squid 数据资产,连接学校数据服务器、OpenAI Codex Harness 与 Marine MCP。</p>
|
| 2750 |
</div>
|
| 2751 |
<div class="feature-row">
|
| 2752 |
<span class="feature">🌊 Ocean Data</span>
|