아키텍처
opencodex는 단일 Bun 프로세스입니다. 요청은 OpenAI Responses로 들어와 내부 모델로 정규화되고, 라우팅된 뒤, 어댑터를 통해 프로바이더로 전송되고, 다시 Responses SSE로 브리징됩니다. 엔드투엔드 플로우는 동작 원리를 참조하세요.
모듈 맵
섹션 제목: “모듈 맵”src/├── cli/ # ocx command dispatch, init, status, provider commands├── server/ # Bun.serve, /v1/* proxy, /api/* management API, WS bridge├── codex/ # Codex config injection, catalog sync, auth/account integration├── providers/ # provider metadata, API-key pool, quota and labels├── adapters/ # wire adapter, 공통 guard/util, Cursor protobuf transport├── oauth/ # OAuth providers, API-key catalog, token store/refresh├── usage/ # usage extraction, JSONL logs, summaries, totals├── lib/ # runtime, process, retry, privacy, token estimate helpers├── web-search/ # web-search sidecar (synthetic tool, loop, executor, parser)├── vision/ # vision sidecar (describe + plan)├── config.ts # ~/.opencodex/config.json, defaults, PID, env resolution├── router.ts # model id → provider + adapter├── bridge.ts # facade over bridge/├── bridge/ # AdapterEvent stream → Responses SSE (sse.ts) / JSON (response-json.ts)├── reasoning-effort.ts # reasoning-effort translation, clamping, and catalog levels├── responses/│ ├── parser.ts # Responses request → OcxParsedRequest│ ├── schema.ts # Zod validation│ └── compaction.ts # remote compaction prompts, envelopes, compact history├── service.ts # launchd / systemd / Task Scheduler background service├── types.ts # core interfaces + helpers (modelInList, namespacedToolName)└── index.ts # public entry기존의 대형 진입 파일들은 이제 호환성 facade입니다. codex/catalog.ts는
codex/catalog/*.ts 모듈을, server/management-api.ts는 server/management/*.ts
모듈을, server/responses.ts는 server/responses/*.ts 모듈을, bridge.ts는 bridge/*.ts
모듈을 연결합니다. facade는 안정적인 import 경로일 뿐 구현이 아닙니다. 아래 각 단계는
실제 코드를 소유한 모듈을 가리키며, Responses 표면의 전체 소유권 목록은
structure/transports/responses.md에 있습니다.
요청 처리 흐름
섹션 제목: “요청 처리 흐름”HTTP 경계는 server/index/serve-options.ts가 맡고, Responses 데이터 플레인은 server/responses.ts facade와
server/responses/*.ts 모듈로 넘깁니다.
server/index/serve-options.ts에서 CORS와 API 인증을 확인하고, 종료 대기 중이면 새 요청을 거부한 뒤 요청 수명 주기를 기록합니다. 여기서GET /v1/models,POST /v1/responses,POST /v1/responses/compact,POST /v1/images/generations/POST /v1/images/edits(Codex 내장image_gen도구용 —server/images.ts가 OpenAI 계열 업스트림으로 중계),POST /v1/live/POST /v1/realtime/calls(ChatGPT / Codex App 음성 및 OpenAI Realtime 호출 생성,server/live.ts가 중계)와/v1/live/{callId}사이드밴드 WebSocket, 그리고/v1/responses의 선택적 WebSocket 업그레이드를 제공합니다.server/responses/request-prepare.ts가 압축을 풀고 JSON을 읽습니다. 기억해 둔previous_response_id입력이 있으면 펼친 다음responses/parser.ts로 넘깁니다.router.ts가 일반 모델 id 또는provider/modelid를 해석합니다. 이어서 Codex 계정 affinity를 결정하고, 필요하면 프로바이더 OAuth를 갱신해 선택된 자격 증명을 route에 적용합니다.- 본 요청 전에
vision/이noVisionModels모델용 이미지 설명을 만듭니다. 안전한 사이드카 경로가 없으면 텍스트 전용 업스트림에 이미지를 보내지 않고 제거합니다. server/adapter-resolve.ts가 모델별 wire override를 적용하고 등록된 어댑터 중 하나를 만듭니다. Responses passthrough는 원본 body를 중계하고, Cursor는 양방향runTurntransport를 사용하며, 나머지 변환형 어댑터는 업스트림 요청을 build/fetch/parse합니다.- 라우팅 모델이 호스티드
web_search를 요청하면web-search/가 합성 함수를 노출합니다. 실제 검색은 ChatGPT 사이드카로 실행하고 결과를 라우팅 모델에 다시 넣으며, 설정된 횟수 안에서 반복합니다. bridge/sse.ts/bridge/response-json.ts가 Responses SSE 또는 JSON을 만듭니다.server/request-log.ts와usage/는 응답을 건드리지 않은 채 종료 상태, 지연 시간, 프로바이더/모델, 최선 추정 토큰 사용량을 기록합니다.
요청 전 입력량 추정은 라우팅된 어댑터가 실제로 보내는 내용을 따릅니다. openai-chat 모델이
명시적 preserveReasoningContentModels 목록 또는 미설정 시 DeepSeek Flash/Pro 감지에 따른
유효 thinking 보존 정책에서 제외되면 어댑터가 이전 assistant thinking을 보내지 않으므로
추정에서도 뺍니다. 그래서 보내지도 않는 기록 때문에 로컬 컨텍스트 한도에서 잘못 거부되는 일이
없습니다. reasoning을 보존하는 모델과 다른 어댑터는 이전 thinking을
실제로 보내므로 계속 계산에 넣습니다.
responses/parser.ts는 들어오는 요청을 responses/schema.ts(Zod)로 검증한 다음
OcxParsedRequest를 구성합니다:
- Messages —
input항목은 정규화된OcxMessage[]가 됩니다: user / developer / assistant / toolResult.reasoning항목은 thinking 블록이 되고,function_call,custom_tool_call,tool_search_call항목은 툴 호출이 되며, 그에 대응하는*_output은 툴 결과가 됩니다. - Tools — function 툴은 그대로 통과합니다. 네임스페이스가 있는 (MCP) 툴은 평탄화되어
namespace__name이 됩니다(반환 시 복원됨). freeform 툴(예:apply_patch)과 tool_search 디스커버리 툴은 플래그가 지정됩니다. 호스티드 툴(web_search, image gen, …)은 제거되며, 이를 처리할 사이드카가 있을 경우에만 다시 주입됩니다. - Images — 실제 content 파트(data URL 또는 원격 https)로 보존되며, 절대 텍스트로 인라인되지 않습니다.
- Feature flags —
_webSearch(호스티드 웹 검색 요청),_structuredOutput(text.format이 json_schema / json_object),_compactionRequest(remote compaction v2).
브리지
섹션 제목: “브리지”bridge/sse.ts는 어댑터의 내부 AdapterEvent 스트림을 Codex가 이해하는 Responses SSE로 다시
변환합니다:
| AdapterEvent | Responses SSE emitted |
|---|---|
text_delta |
response.output_text.delta → …done, response.content_part.done, response.output_item.done |
thinking_delta |
response.reasoning_summary_text.delta → …done, item close |
reasoning_raw_delta |
raw reasoning_text 항목 또는 숨김 왕복 envelope |
thinking_signature / redacted_thinking |
encrypted_content reasoning envelope에 보존 |
tool_call_start |
response.output_item.added (type: function_call / custom_tool_call / tool_search_call) |
tool_call_delta |
response.function_call_arguments.delta (skipped for freeform / tool_search) |
tool_call_end |
response.function_call_arguments.done → response.output_item.done |
web_search_call_begin / web_search_call_end |
실시간 web_search_call 항목 하나와 URL citation |
heartbeat |
업스트림 활동 표시. 사용자에게 보이는 출력 항목은 없음 |
done |
response.completed (with usage) |
error |
response.failed (with last_error) |
브리지는 하트비트 킵얼라이브(RC3)도 실행합니다. 업스트림에서 데이터가 오지 않을 때 2초마다
파서가 무시하는 : opencodex heartbeat SSE 주석 줄을 보내 Codex의 유휴 타이머를 다시 시작합니다.
주석 줄은 이벤트를 생성하지 않고 모든 eventsource 파서에 의해 버려지므로, 엄격한 Responses 디코더는
알 수 없는 variant를 절대 보지 못합니다. 기본 stall deadline은 300초(stallTimeoutSec)입니다.
이 시간을 넘기면 업스트림을 중단하고 이유가 upstream_stall_timeout인 response.incomplete를
내보내 연결이 끝없이 매달리지 않게 합니다.
툴 호출은 파서가 캡처한 네임스페이스 맵, freeform 집합, tool-search 집합을 사용하여 세 가지
Responses 항목 타입으로 구분됩니다 — 따라서 MCP 네임스페이스, apply_patch 스타일의 freeform
툴, 클라이언트가 실행하는 tool_search가 모두 왕복합니다. buildResponseJSON() 변형은 동일한
이벤트로부터 단일 비스트리밍 응답 객체를 생성합니다.
전송과 compaction
섹션 제목: “전송과 compaction”server/index/serve-options.ts는 기본적으로 /v1/responses를 HTTP/SSE로 제공합니다. websockets가 false인
상태에서 Codex가 Responses WebSocket 업그레이드를 시도하면 opencodex는 426 upgrade_required를
반환하고, Codex는 해당 세션에서 HTTP로 폴백합니다. "websockets": true가 설정되면 같은
엔드포인트가 업그레이드를 받아들이고 WebSocket 브리지를 사용합니다.
이 클라이언트 설정과 별개로, 루트 stream: true인 canonical ChatGPT forward 요청은
stable Bun 1.4.0 이상에서 Codex 업스트림 WebSocket을 사용할 수 있습니다. 번들 Bun 1.3.14,
prerelease, 또는 검증할 수 없는 런타임 identity는 HTTP/SSE를 사용합니다. 성공한 업스트림 WS
응답은 같은 downstream SSE 계약을 유지하며, 원시 JSON WebSocket 프레임과 downstream SSE
envelope를 각각 4 MiB로 제한하고 8 MiB producer queue 상한이 있는 bounded eager single-reader
relay를 거칩니다. queue overflow 시 업스트림을 닫고 downstream에는
terminal response.failed 이벤트와 [DONE]을 내보냅니다.
최종 전송 모델이 gpt-5.3-codex-spark이면 canonical ChatGPT forward 경로는 HTTP 헤더와
네이티브 WS 프레임 메타데이터 모두에서 Responses Lite를 명시적으로 끕니다. 별칭으로 Spark를
선택해도 동일합니다. 다만 이 비활성화는 전송 본문에 비어 있지 않은 tools 배열을 가진 additional_tools 항목이 없을 때만
적용됩니다. 이 그룹 자체가 Lite의 도구 전달 형식이므로, 그것을 사용하는 Spark 본문은 호출자나
설정 헤더가 무엇이든 Lite를 켠 상태로 유지합니다. Lite 식별값이 바뀌면 기존 소켓은 사용을 종료하며, 이후 같은 식별값으로
재사용 조건을 충족하는 요청은 새 소켓을 재사용할 수 있습니다. 다른 모델과 게이트웨이는 기존
Lite 정책을 유지합니다. 네이티브 메타데이터 형식이 잘못된 경우에는 본문을 바꾸지 않고
기존처럼 HTTP로 폴백합니다.
Codex 컨텍스트 compaction은 라우팅된 모델에서도 동작합니다. server/responses/compact.ts는
POST /v1/responses/compact를 내부 라우팅 요약 턴으로 처리해 압축된 히스토리를 반환합니다.
responses/parser.ts와 bridge/sse.ts는 remote compaction v2의 compaction_trigger 턴을 처리해
합성 compaction 출력 항목을 정확히 하나 내보냅니다.
캐싱과 카탈로그
섹션 제목: “캐싱과 카탈로그”codex/model-cache.ts는 실시간/models결과를 프로바이더별로 메모리에 TTL 캐싱하며(기본 5분, Codex 자체 캐시와 일치), fetch가 실패하면 stale-fallback을 제공합니다.codex/catalog.tsfacade가 내보내는codex/catalog/sync.ts는 라우팅된 모델을 네임스페이스 항목으로 Codex의 카탈로그에 병합하고, 추천 서브에이전트 모델을 먼저 랭크하며,disabledModels를 필터링하고, 일회성 백업으로부터 원본 카탈로그를 완전히 복원할 수 있습니다.
Reasoning effort
섹션 제목: “Reasoning effort”reasoning-effort.ts는 Codex의 reasoning 레이블을 각 프로바이더의 와이어 값으로 변환합니다.
Codex 카탈로그는 Codex가 수용하는 레이블(low / medium / high / xhigh / max)을
광고하지만, 업스트림 프로바이더는 더 작은 하위 집합만 지원하거나 실제 alias가 필요할 수 있습니다.
이 모듈은:
- 표준
CODEX_REASONING_LEVELS와 그 정렬 순서를 정의합니다. - 요청된 effort를 정확한 레벨이 없을 때 가장 가까운 지원 단계로 클램핑합니다.
- 커스텀 와이어 매핑을 위한 모델별 및 프로바이더별
reasoningEffortMap오버라이드를 해석합니다. noReasoningModels에 나열된 모델에 대해서는 effort를 완전히 제거합니다.
코어 타입
섹션 제목: “코어 타입”내부 모델은 types.ts에 있습니다: OcxParsedRequest, OcxContext, OcxMessage 유니온,
OcxContentPart(text / image), OcxToolCall, OcxTool, AdapterEvent, 그리고 설정 타입
(OcxConfig, OcxProviderConfig). 두 가지 헬퍼가 널리 사용됩니다: namespacedToolName()과
modelInList()(noVisionModels / noReasoningModels에 대한 관대한 :size 태그 매칭).

