Skip to content

Commit 1f146ad

Browse files
committed
feat: web-search stall timeout + progress stream, images proxy hardening, adapter error redaction
- web-search: routedModelStallTimeoutMs config, progress-stream module, loop timeout contract rework (src/web-search/*, src/lib/abort.ts, src/server/responses.ts) - images proxy: bounded request body (src/lib/bounded-body.ts), local /v1/images relay iteration with safety tests (src/server/images.ts) - adapters: formatErrorBody hook (base.ts) with redacted google/kiro error bodies; kiro retry hardening - catalog: opencode-go live-model handling updates - docs: sidecars/configuration guides (en/ko/zh) + README x3 + structure SoT
1 parent b918873 commit 1f146ad

40 files changed

Lines changed: 3492 additions & 212 deletions

README.ko.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ opencodex는 두 가지 동작을 분리해서 유지합니다:
179179
- **알맞은 모델에 위임.** 대시보드나 config에서 최대 5개의 라우팅/네이티브 모델을 Codex 서브에이전트 선택기에 노출해, 복잡한 작업은 reasoning 모델로, 빠른 작업은 저렴한 모델로 보낼 수 있습니다. v2 멀티에이전트 표면(GPT-5.6 Sol/Terra)에서는 프록시가 간결한 위임 가이드를 주입합니다. 선호 서브에이전트 모델·effort(`injectionModel` / `injectionEffort`), 노출된 모델 로스터와 각 모델이 지원하는 effort 사다리, 그리고 크로스모델 `spawn_agent` 호출이 실제로 먹히게 하는 `fork_turns` 규칙까지. 문구를 직접 쓰고 싶다면 `injectionPrompt``{{model}}` / `{{effort}}` / `{{roster}}` 플레이스홀더를 넣으면 됩니다.
180180
- **프리뷰 게이트된 OpenAI rollout에 대비.** GPT-5.6 Sol/Terra/Luna 항목은 upstream 스펙 그대로(Sol/Terra는 `ultra`까지, Luna는 `max`까지; 372k usable context) ChatGPT passthrough, OpenAI API key, OpenRouter route에 준비되어 있습니다.
181181
- **어떤 모델에도 초능력을.** OpenAI가 아닌 모델도 ChatGPT 로그인 위에서 도는 `gpt-5.4-mini` sidecar로 실제 웹 검색과 이미지 이해를 사용합니다.
182+
- **이미지를 네이티브로 생성.** Codex의 독립형 `image_gen` 도구는 생성할 때 `POST /v1/images/generations`, 편집할 때 `POST /v1/images/edits`를 사용합니다. Responses의 hosted `image_generation` 도구와는 별개입니다.
182183
- **무슨 일이 일어나는지 보이게.** 웹 대시보드가 프로바이더, OAuth 상태, 모델 선택, upstream이 보고한 cached/cache-write 토큰 수를 포함한 실시간 요청 로그를 보여줍니다 — 왜 요청이 실패했는지 더는 추측하지 않아도 됩니다.
183184
- **백그라운드 실행.** 시스템 서비스(launchd / systemd / Task Scheduler)로 설치하면 부팅 시 자동 시작되어 신경 쓸 필요가 없습니다.
184185
- **깔끔한 종료, 잔여물 제로.** `ocx stop`(또는 대시보드의 Stop 버튼)은 프록시를 종료하고, 설치된 백그라운드 서비스를 멈추며, Codex를 원래 설정으로 복원합니다. 이후 `codex`는 잔여 설정이나 좀비 프로세스 없이 이전과 똑같이 동작합니다.
@@ -321,7 +322,8 @@ WebSocket 전송은 기본적으로 꺼져 있습니다. Codex가 HTTP/SSE 대
321322
### 원격 접근
322323

323324
기본적으로 opencodex는 `127.0.0.1`(루프백)에 바인딩되며 별도 인증이 필요 없습니다.
324-
`"hostname": "0.0.0.0"`으로 LAN에 노출할 경우, opencodex는 관리 API(`/api/*`)와 데이터 플레인(`/v1/responses`) 모두에 bearer 토큰을 요구합니다:
325+
`"hostname": "0.0.0.0"`으로 LAN에 노출할 경우, opencodex는 관리 API(`/api/*`)와 데이터 플레인
326+
(`/v1/responses`, `/v1/images/generations`, `/v1/images/edits`) 모두에 bearer 토큰을 요구합니다:
325327

326328
```bash
327329
export OPENCODEX_API_AUTH_TOKEN="your-secret-token"
@@ -372,8 +374,9 @@ bun x tsc --noEmit # 타입 체크
372374
```
373375

374376
`bun run dev`는 호환성을 위해 `bun run dev:proxy`의 별칭으로 남아 있습니다. 소스 체크아웃에서 프록시
375-
API는 `/healthz`, `/v1/responses`, `/api/*`를 노출하며, `GET /``bun run build:gui``gui/dist`
376-
생성한 뒤에만 패키징된 대시보드를 서빙합니다. 대시보드를 수정할 때는 프론트엔드를 별도로 실행하세요:
377+
API는 `/healthz`, `/v1/responses`, `POST /v1/images/generations`, `POST /v1/images/edits`, `/api/*`
378+
노출하며, `GET /``bun run build:gui``gui/dist`를 생성한 뒤에만 패키징된 대시보드를 서빙합니다.
379+
대시보드를 수정할 때는 프론트엔드를 별도로 실행하세요:
377380

378381
```bash
379382
bun run dev:gui

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ next Codex session. opencodex keeps two separate behaviors:
184184
- **Delegate to the right model.** Feature up to five routed or native models in Codex's subagent picker from the dashboard or config — route complex tasks to a reasoning model, fast tasks to a cheap one. On the v2 multi-agent surface (GPT-5.6 Sol/Terra) the proxy injects compact delegation guidance: a preferred sub-agent model and effort (`injectionModel` / `injectionEffort`), the featured-model roster with the effort ladder each supports, and the `fork_turns` rules that make cross-model `spawn_agent` calls actually stick. Want your own wording? Set `injectionPrompt` with `{{model}}` / `{{effort}}` / `{{roster}}` placeholders.
185185
- **Prepare for preview-gated OpenAI rollouts.** GPT-5.6 Sol/Terra/Luna entries ship with the exact upstream spec (Sol/Terra reach `ultra`, Luna caps at `max`; 372k usable context) for ChatGPT passthrough, OpenAI API key, and OpenRouter routes when upstream access is available.
186186
- **Give any model superpowers.** Non-OpenAI models get real web search and image understanding via a `gpt-5.4-mini` sidecar over your ChatGPT login.
187+
- **Generate images natively.** Codex's standalone `image_gen` tool uses `POST /v1/images/generations` for generation and `POST /v1/images/edits` for edits; it is separate from the hosted Responses `image_generation` tool.
187188
- **See what's happening.** The web dashboard shows providers, OAuth status, model selection, and a live request log, including cached/cache-write token counts when upstream reports them — no more guessing why a request failed.
188189
- **Runs in the background.** Install as a system service (launchd / systemd / Task Scheduler) and forget about it. The proxy starts on boot and stays out of your way.
189190
- **Clean exit, zero residue.** `ocx stop` (or the dashboard's Stop button) shuts down the proxy, stops the background service if one is installed, and restores Codex to its original configuration. Plain `codex` works exactly as it did before — no leftover config, no orphaned processes.
@@ -341,7 +342,8 @@ WebSocket transport is off by default. Set `"websockets": true` only if you want
341342

342343
By default opencodex binds to `127.0.0.1` (loopback) and requires no extra authentication.
343344
If you set `"hostname": "0.0.0.0"` to expose the proxy on the LAN, opencodex requires a bearer token
344-
to protect both the management API (`/api/*`) and the data-plane (`/v1/responses`):
345+
to protect both the management API (`/api/*`) and the data-plane (`/v1/responses`,
346+
`/v1/images/generations`, and `/v1/images/edits`):
345347

346348
```bash
347349
export OPENCODEX_API_AUTH_TOKEN="your-secret-token"
@@ -392,9 +394,9 @@ bun x tsc --noEmit # typecheck
392394
```
393395

394396
`bun run dev` remains an alias for `bun run dev:proxy` for compatibility. In a source checkout,
395-
the proxy API exposes `/healthz`, `/v1/responses`, and `/api/*`; `GET /` serves the packaged
396-
dashboard only after `bun run build:gui` has produced `gui/dist`. While hacking on the dashboard,
397-
run the frontend separately:
397+
the proxy API exposes `/healthz`, `/v1/responses`, `POST /v1/images/generations`,
398+
`POST /v1/images/edits`, and `/api/*`; `GET /` serves the packaged dashboard only after
399+
`bun run build:gui` has produced `gui/dist`. While hacking on the dashboard, run the frontend separately:
398400

399401
```bash
400402
bun run dev:gui

README.zh-CN.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ npm install -g @bitkyc08/opencodex # 不要加 --ignore-scripts、--omit=optio
102102
- **委派给合适的模型。** 在仪表盘或 config 中把最多 5 个路由/原生模型放进 Codex 的 subagent 选择器 —— 复杂任务交给 reasoning 模型,快速任务交给便宜模型。在 v2 多智能体表面(GPT-5.6 Sol/Terra)上,代理会注入精简的委派指引:首选子智能体模型与 effort(`injectionModel` / `injectionEffort`)、featured 模型清单及各自支持的 effort 阶梯,以及让跨模型 `spawn_agent` 调用真正生效的 `fork_turns` 规则。想自定义文案,可在 `injectionPrompt` 中使用 `{{model}}` / `{{effort}}` / `{{roster}}` 占位符。
103103
- **为 preview-gated OpenAI rollout 做好准备。** GPT-5.6 Sol/Terra/Luna 条目采用与 upstream 完全一致的规格(Sol/Terra 到 `ultra`,Luna 到 `max`;372k 可用上下文),覆盖 ChatGPT passthrough、OpenAI API key 和 OpenRouter 路由。
104104
- **给任意模型超能力。** 非 OpenAI 模型也能通过你的 ChatGPT 登录上运行的 `gpt-5.4-mini` sidecar 获得真正的网页搜索和图片理解。
105+
- **原生生成图片。** Codex 的独立 `image_gen` 工具通过 `POST /v1/images/generations` 生成图片、通过 `POST /v1/images/edits` 编辑图片;它独立于 hosted Responses 的 `image_generation` 工具。
105106
- **看清正在发生什么。** Web 仪表盘展示 provider、OAuth 状态、模型选择和实时请求日志;当上游返回时,也会包含 cached/cache-write token 计数 —— 不必再猜测请求为何失败。
106107
- **后台运行。** 安装为系统服务(launchd / systemd / Task Scheduler)后开机自启,无需操心。
107108
- **干净退出,零残留。** `ocx stop`(或仪表盘的 Stop 按钮)会关闭代理、停止已安装的后台服务,并将 Codex 恢复为原始配置。之后 `codex` 就像从未安装过 opencodex 一样工作 —— 无残留配置,无僵尸进程。
@@ -303,7 +304,7 @@ WebSocket 传输默认关闭。只有当你希望 Codex 使用 Responses WebSock
303304

304305
默认情况下 opencodex 绑定到 `127.0.0.1`(回环)且无需额外认证。
305306
如果你设置 `"hostname": "0.0.0.0"` 把代理暴露到局域网,opencodex 会要求一个 bearer token 来同时保护管理
306-
API(`/api/*`)和数据平面(`/v1/responses`):
307+
API(`/api/*`)和数据平面(`/v1/responses``/v1/images/generations``/v1/images/edits`):
307308

308309
```bash
309310
export OPENCODEX_API_AUTH_TOKEN="your-secret-token"
@@ -350,8 +351,8 @@ bun x tsc --noEmit # 类型检查
350351
```
351352

352353
`bun run dev` 作为 `bun run dev:proxy` 的别名保留以兼容旧用法。在源码检出中,代理 API 暴露 `/healthz`
353-
`/v1/responses``/api/*`;只有在 `bun run build:gui` 生成 `gui/dist` 之后,`GET /` 才会提供打包后的仪表盘。
354-
开发前端时请单独运行:
354+
`/v1/responses``POST /v1/images/generations``POST /v1/images/edits``/api/*`;只有在
355+
`bun run build:gui` 生成 `gui/dist` 之后,`GET /` 才会提供打包后的仪表盘。开发前端时请单独运行:
355356

356357
```bash
357358
bun run dev:gui

docs-site/src/content/docs/guides/sidecars.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ When Codex requests hosted `web_search` for a non-passthrough routed model, open
2828
(default 3), then removes the search tool and forces a final answer. Real client tools such as
2929
`apply_patch` or shell finalize the turn so those calls reach Codex.
3030

31+
Every routed-model iteration requests upstream `stream: true`, but opencodex fully buffers semantic
32+
events internally before deciding whether to search or return the final answer. Only the first
33+
iteration's final headers/status and 429 key rotations are acquired eagerly. Thus synthetic search
34+
calls and preliminary output are never exposed as client-visible model output.
35+
3136
The injected result is wrapped in an untrusted-data boundary, length-capped, and de-duplicated by
3237
source URL. In structured-output turns (`json_schema` / `json_object`) it is handed over as compact
3338
JSON instead of prose. For text-only routed models, the search model is also told to describe
@@ -40,6 +45,7 @@ relevant images in words and include their source URLs.
4045
"model": "gpt-5.6-luna",
4146
"reasoning": "low",
4247
"maxSearchesPerTurn": 3,
48+
"routedModelStallTimeoutMs": 200000,
4349
"timeoutMs": 200000
4450
}
4551
}
@@ -49,6 +55,16 @@ relevant images in words and include their source URLs.
4955
search is returned to the routed model as a bounded error result, allowing it to answer from the
5056
context it already has.
5157

58+
Four separate clocks apply. `stallTimeoutSec` is the base bridge event-stall budget.
59+
`connectTimeoutMs` (default `200000`) covers only DNS/TCP/TLS and final response headers.
60+
Config-file-only `webSearchSidecar.routedModelStallTimeoutMs` (default `200000`, integer
61+
`1..2147483647`) bounds continuous raw response-byte inactivity for each routed-model iteration and
62+
resets on every non-empty byte. `webSearchSidecar.timeoutMs` separately bounds one hosted search
63+
request. The effective bridge watchdog is
64+
`max(base stall, connect timeout, routed-model stall, sidecar timeout) + 30 seconds`. The routed
65+
stall is not a total generation timeout. Failures before SSE starts return non-2xx JSON; generation
66+
failures after response headers have started are delivered as `response.failed` SSE.
67+
5268
## Vision sidecar
5369

5470
When the routed model is listed in its provider's `noVisionModels` and a request carries an image,

docs-site/src/content/docs/ko/guides/sidecars.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ Codex가 패스스루가 아닌 라우팅 모델에 호스팅 `web_search`를
2929
**반복**합니다. 한도에 닿으면 검색 도구를 제거하고 최종 답변을 강제합니다. `apply_patch`나 shell
3030
같은 실제 클라이언트 도구가 나오면 턴을 끝내 해당 호출이 Codex에 전달되게 합니다.
3131

32+
라우팅 모델의 모든 반복은 업스트림에 `stream: true`를 요청하지만, opencodex는 검색 여부나 최종
33+
답변을 결정하기 전에 의미 있는 event를 내부에서 전부 버퍼링합니다. 첫 번째 반복의 최종
34+
header/status와 429 key rotation만 미리 가져옵니다. 따라서 합성 검색 호출과 중간 출력은 클라이언트에
35+
모델 출력으로 노출되지 않습니다.
36+
3237
주입 결과는 신뢰할 수 없는 데이터 경계로 감싸고 길이를 제한하며, 소스 URL 기준으로 중복을
3338
제거합니다. 구조화된 출력 턴(`json_schema` / `json_object`)에서는 산문이 아니라 간결한 JSON으로
3439
전달합니다. 라우팅 모델이 텍스트 전용이면 검색 모델에 관련 이미지를 글로 설명하고 소스 URL도
@@ -41,6 +46,7 @@ Codex가 패스스루가 아닌 라우팅 모델에 호스팅 `web_search`를
4146
"model": "gpt-5.6-luna",
4247
"reasoning": "low",
4348
"maxSearchesPerTurn": 3,
49+
"routedModelStallTimeoutMs": 200000,
4450
"timeoutMs": 200000
4551
}
4652
}
@@ -49,6 +55,16 @@ Codex가 패스스루가 아닌 라우팅 모델에 호스팅 `web_search`를
4955
호스팅 백엔드가 `minimal` 강도에서 도구 사용을 거부하므로 기본값은 `low`입니다. 검색이 실패하면
5056
길이가 제한된 오류 결과를 라우팅 모델에 돌려주며, 모델은 이미 가진 문맥을 바탕으로 답할 수 있습니다.
5157

58+
서로 독립적인 네 가지 clock이 적용됩니다. `stallTimeoutSec`은 기본 bridge event stall 예산입니다.
59+
`connectTimeoutMs`(기본값 `200000`)는 DNS/TCP/TLS와 최종 응답 header까지만 제한합니다. 설정
60+
파일에서만 지정할 수 있는 `webSearchSidecar.routedModelStallTimeoutMs`(기본값 `200000`, 정수
61+
`1..2147483647`)는 라우팅 모델 반복에서 원시 응답 byte가 연속으로 오지 않는 시간을 제한하며,
62+
비어 있지 않은 byte가 올 때마다 다시 시작됩니다. `webSearchSidecar.timeoutMs`는 호스팅 검색 요청
63+
하나를 별도로 제한합니다. 실제 bridge watchdog은
64+
`max(기본 stall, connect timeout, 라우팅 모델 stall, 사이드카 timeout) + 30초`입니다. 라우팅 모델
65+
stall은 전체 생성 timeout이 아닙니다. SSE가 시작되기 전 실패는 2xx가 아닌 JSON으로 반환하고,
66+
응답 header가 시작된 뒤의 생성 실패는 `response.failed` SSE로 전달합니다.
67+
5268
## 비전 사이드카
5369

5470
라우팅 모델이 해당 프로바이더의 `noVisionModels`에 있고 요청에 이미지가 들어오면, opencodex는

docs-site/src/content/docs/ko/reference/configuration.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ opencodex는 `~/.opencodex/config.json`에서 설정을 읽습니다. `ocx init`
2626
| `providerContextCaps?` | `Record<string,number>` | `{}` | 프로바이더별 Codex 표시 context cap. 알려진 context window를 낮추기만 합니다. |
2727
| `contextCapValue?` | `number` | `350000` | 대시보드 context-cap control에서 쓸 값. 바꾸면 `providerContextCaps`에서 활성화된 모든 항목을 갱신합니다. |
2828
| `stallTimeoutSec?` | `number` | `90` | 업스트림 데이터가 오지 않을 때 bridge가 중단하고 `response.incomplete`를 내보내기까지의 초. 최소 1. |
29-
| `connectTimeoutMs?` | `number` | `200000` | DNS/TCP/TLS 연결과 응답 헤더를 기다리는 시도별 deadline. |
29+
| `connectTimeoutMs?` | `number` | `200000` | DNS/TCP/TLS와 최종 응답 헤더만 기다리는 시도별 deadline. 응답 body 생성 전 종료됩니다. |
3030
| `shutdownTimeoutMs?` | `number` | `5000` | 진행 중인 turn을 중단하기 전 graceful drain deadline. |
3131
| `websockets?` | `boolean` | `false` | `supports_websockets`를 알려 Codex가 Responses WebSocket 경로를 쓰게 합니다. 생략하거나 `false`이면 HTTP/SSE를 유지합니다. |
3232
| `apiKeys?` | `OcxApiKey[]` | `[]` | 비-loopback 바인드에서 관리 API와 data plane 인증에 추가로 허용할 생성형 `ocx_…` 자격 증명. 대시보드가 관리하며 항목 필드는 아래에 설명합니다. |
@@ -245,7 +245,14 @@ reasoning을 알리되 `xhigh`와 구분합니다. 실시간 프로바이더 결
245245
| `model?` | `string` | `gpt-5.6-luna` | 실제 `web_search`를 실행할 사이드카 모델(네이티브 ChatGPT 모델이어야 함). 명시적으로 남은 기존 `gpt-5.4-mini` 값은 시작할 때 마이그레이션합니다. |
246246
| `reasoning?` | `string` | `low` | 사이드카 reasoning effort(`minimal`은 웹 검색과 함께 쓸 수 없음). |
247247
| `maxSearchesPerTurn?` | `number` | `3` | 메인 모델 한 turn에서 실행할 실제 검색 총횟수(loop guard). |
248-
| `timeoutMs?` | `number` | `200000` | 사이드카 fetch timeout. |
248+
| `routedModelStallTimeoutMs?` | `number` | `200000` | 설정 파일에서만 지정할 수 있는 라우팅 모델 반복별 원시 응답 byte 연속 무활동 deadline. `1`부터 `2147483647`까지의 정수여야 하며, 비어 있지 않은 응답 body chunk가 올 때마다 다시 시작됩니다. |
249+
| `timeoutMs?` | `number` | `200000` | 호스팅 웹 검색 요청 하나를 제한하는 별도 deadline. |
250+
251+
웹 검색 경로에는 네 가지 clock이 있습니다. 기본 bridge event stall 예산(`stallTimeoutSec`),
252+
DNS/TCP/TLS/최종 header 예산(`connectTimeoutMs`), 라우팅 모델의 원시 byte 무활동
253+
(`routedModelStallTimeoutMs`), 호스팅 검색 하나의 제한(`timeoutMs`)입니다. 실제 bridge watchdog은
254+
`max(기본 stall, connect timeout, 라우팅 모델 stall, 사이드카 timeout) + 30초`입니다. 라우팅 모델
255+
stall은 무활동 감시 장치이며 전체 생성 timeout이 아닙니다.
249256

250257
### `visionSidecar` (`OcxVisionSidecarConfig`)
251258

@@ -284,7 +291,11 @@ reasoning을 알리되 `xhigh`와 구분합니다. 실시간 프로바이더 결
284291
"subagentModels": ["anthropic/claude-opus-4-8", "ollama-cloud/glm-5.2"],
285292
"disabledModels": [],
286293
"websockets": false,
287-
"webSearchSidecar": { "maxSearchesPerTurn": 3 },
294+
"webSearchSidecar": {
295+
"maxSearchesPerTurn": 3,
296+
"routedModelStallTimeoutMs": 200000,
297+
"timeoutMs": 200000
298+
},
288299
"visionSidecar": { "enabled": true }
289300
}
290301
```

0 commit comments

Comments
 (0)