콘텐츠로 이동

아키텍처

opencodex는 단일 Bun 프로세스입니다. 요청은 OpenAI Responses로 들어와 내부 모델로 정규화되고, 라우팅된 뒤, 어댑터를 통해 프로바이더로 전송되고, 다시 Responses SSE로 브리징됩니다. 엔드투엔드 플로우는 동작 원리를 참조하세요.

src/
├── cli.ts # ocx command dispatch
├── index.ts # public entry
├── server.ts # Bun.serve: /v1/* proxy + /api/* management API
├── router.ts # model id → provider + adapter
├── config.ts # ~/.opencodex/config.json, defaults, PID, env resolution
├── service.ts # launchd / systemd / Task Scheduler background service
├── init.ts # interactive setup wizard
├── bridge.ts # AdapterEvent stream → Responses SSE
├── codex-inject.ts # $CODEX_HOME/config.toml injection + restore
├── codex-catalog.ts # routed-model catalog merge + subagent ranking
├── reasoning-effort.ts # reasoning-effort translation, clamping, and catalog levels
├── model-cache.ts # per-provider /models TTL cache
├── types.ts # core interfaces + helpers (modelInList, namespacedToolName)
├── responses/
│ ├── parser.ts # Responses request → OcxParsedRequest
│ └── schema.ts # Zod validation
├── adapters/ # base + openai-chat, openai-responses, anthropic, google, azure, image
├── oauth/ # OAuth providers, API-key catalog, token store/refresh
├── web-search/ # web-search sidecar (synthetic tool, loop, executor, parser)
└── vision/ # vision sidecar (describe + plan)

responses/parser.ts는 들어오는 요청을 responses/schema.ts(Zod)로 검증한 다음 OcxParsedRequest를 구성합니다:

  • Messagesinput 항목은 정규화된 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).

bridge.ts는 어댑터의 내부 AdapterEvent 스트림을 Codex가 이해하는 Responses SSE로 다시 변환합니다:

AdapterEventResponses SSE emitted
text_deltaresponse.output_text.delta…done, response.content_part.done, response.output_item.done
thinking_deltaresponse.reasoning_summary_text.delta…done, item close
tool_call_startresponse.output_item.added (type: function_call / custom_tool_call / tool_search_call)
tool_call_deltaresponse.function_call_arguments.delta (skipped for freeform / tool_search)
tool_call_endresponse.function_call_arguments.doneresponse.output_item.done
doneresponse.completed (with usage)
errorresponse.failed (with last_error)

브리지는 또한 하트비트 킵얼라이브(RC3)를 실행합니다: 업스트림 침묵 시, 2초마다 파서에서 무시되는 response.heartbeat SSE 이벤트를 내보내 Codex의 유휴 타이머를 재설정합니다. 스톨 데드라인 150틱(기본 2초 간격에서 5분)이 경과하면 프로바이더가 재개하지 않을 경우 업스트림을 중단하고 스트림을 닫습니다 — Codex를 무기한 차단하는 행 커넥션을 방지합니다.

툴 호출은 파서가 캡처한 네임스페이스 맵, freeform 집합, tool-search 집합을 사용하여 세 가지 Responses 항목 타입으로 구분됩니다 — 따라서 MCP 네임스페이스, apply_patch 스타일의 freeform 툴, 클라이언트가 실행하는 tool_search가 모두 왕복합니다. buildResponseJSON() 변형은 동일한 이벤트로부터 단일 비스트리밍 응답 객체를 생성합니다.

  • model-cache.ts는 실시간 /models 결과를 프로바이더별로 메모리에 TTL 캐싱하며(기본 5분, Codex 자체 캐시와 일치), fetch가 실패하면 stale-fallback을 제공합니다.
  • codex-catalog.ts는 라우팅된 모델을 네임스페이스 항목으로 Codex의 카탈로그에 병합하고, 추천 서브에이전트 모델을 먼저 랭크하며, disabledModels를 필터링하고, 일회성 백업으로부터 원본 카탈로그를 완전히 복원할 수 있습니다.

reasoning-effort.ts는 Codex의 reasoning 레이블을 각 프로바이더의 와이어 값으로 변환합니다. Codex 카탈로그는 Codex가 수용하는 레이블(low / medium / high / xhigh)만 광고하지만, 업스트림 프로바이더는 다른 이름(예: max)을 사용하거나 더 작은 하위 집합을 지원할 수 있습니다. 이 모듈은:

  • 표준 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 태그 매칭).