{"id":50439931,"url":"https://github.com/harryplusplus/codex2compat","last_synced_at":"2026-05-31T18:31:11.463Z","repository":{"id":357167018,"uuid":"1234700091","full_name":"harryplusplus/codex2compat","owner":"harryplusplus","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-11T15:29:47.000Z","size":41,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-11T17:29:09.638Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harryplusplus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-10T14:28:30.000Z","updated_at":"2026-05-11T15:41:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/harryplusplus/codex2compat","commit_stats":null,"previous_names":["harryplusplus/codex2compat"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/harryplusplus/codex2compat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fcodex2compat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fcodex2compat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fcodex2compat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fcodex2compat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harryplusplus","download_url":"https://codeload.github.com/harryplusplus/codex2compat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fcodex2compat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33744444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T18:31:10.906Z","updated_at":"2026-05-31T18:31:11.453Z","avatar_url":"https://github.com/harryplusplus.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# codex2compat\n\nCodex CLI **Responses API** → OpenAI **Chat Completions API** 변환 프록시.\n\n```\nCodex CLI ── POST /responses ──→ codex2compat ── POST /v1/chat/completions ──→ upstream\n               GET /models      (localhost:4444)    (OpenAI 호환 API)\n```\n\n\u003e 지원 upstream: CrofAI, OpenRouter, LiteLLM, Ollama, OpenAI 등 Chat Completions API를 제공하는 모든 곳\n\n---\n\n## 왜 필요한가\n\nCodex CLI는 자체 **Responses API** (`POST /responses`, 전용 ModelsResponse 포맷)를 사용하지만,\n대부분의 LLM 제공자는 OpenAI **Chat Completions API** (`POST /v1/chat/completions`)만 지원합니다.\n이 어댑터가 그 차이를 중개합니다.\n\n---\n\n## 현재 상태 (로컬)\n\n| 엔드포인트 | 설명 | 상태 |\n|-----------|------|------|\n| `POST /responses` (streaming) | Responses API 요청 → Chat Completions 변환 → SSE 스트리밍 | ✅ 작동 |\n| `GET /models` | 로컬 models.json 파일 기반 모델 목록 반환 | ✅ 작동 |\n| `POST /responses` (non-streaming) | 미지원, Codex CLI는 항상 `stream:true` | ❌ 불필요 |\n\n---\n\n## 실행\n\n```bash\n# 의존성 설치\npnpm install\n\n# 실행 (환경변수)\nBASE_URL=https://crof.ai/v1  API_KEY=...  npx tsx src/cli.ts\n\n# 로컬 models 파일로 실행 (선택)\nnpx tsx src/cli.ts --models ./models.jsonc --log-level debug --port 4444\n```\n\n### CLI 플래그\n\n| 플래그 | 환경변수 | 기본값 | 설명 |\n|--------|---------|--------|------|\n| `--port` | `PORT` | `4444` | 서버 포트 |\n| `--host` | `HOST` | `0.0.0.0` | 서버 호스트 |\n| `--base-url` | `BASE_URL` | `https://crof.ai/v1` | upstream Chat Completions API URL |\n| `--models` | `MODELS_PATH` | — | 로컬 models.jsonc 파일 경로 |\n| `--log-level` | `LOG_LEVEL` | `info` | 로깅 레벨 (debug / info / warn / error) |\n\n### Codex CLI 설정\n\n```toml\n# ~/.codex/config.toml\n[model_providers.codex2compat]\nname = \"codex2compat\"\nbase_url = \"http://localhost:4444\"\n\n# 활성화\nmodel_provider = \"codex2compat\"\n```\n\n---\n\n## SSE 이벤트 포맷\n\nCodex CLI가 소비하는 Responses API SSE 이벤트 목록입니다.\n(OpenAI HTTP/SSE 응답을 Codex CLI 로그로 직접 검증 완료 — 2026-05-12)\n\n### 전송해야 하는 이벤트 (Codex CLI가 처리함)\n\n| 이벤트 | 설명 | 비고 |\n|--------|------|------|\n| `response.created` | 응답 시작 | `response.id` (resp_ + 50 hex) 포함 |\n| `response.output_item.added` | 새 output item 추가 | reasoning/message/function_call |\n| `response.output_text.delta` | 텍스트 스트리밍 | `item_id`, `delta` |\n| `response.output_item.done` | output item 완료 | 완전한 item payload |\n| `response.completed` | 응답 완료 | `usage` (tokens) 포함 |\n\n### Codex CLI가 무시하는 이벤트 (전송 불필요)\n\nOpenAI도 보내지만 Codex CLI의 `process_responses_event()`에서 `trace(\"unhandled\")`로 드랍됩니다.\n\n| 이벤트 | 드랍 이유 |\n|--------|-----------|\n| `response.in_progress` | 처리 로직 없음 |\n| `response.content_part.added` | 처리 로직 없음 |\n| `response.content_part.done` | 처리 로직 없음 |\n| `response.output_text.done` | text는 `output_item.done`에서 제공 |\n| `response.function_call_arguments.delta` | args는 `output_item.done`에서 제공 |\n| `response.function_call_arguments.done` | args는 `output_item.done`에서 제공 |\n\n**결론**: `function_call_arguments.delta`는 전송해도 Codex CLI가 무시하므로 생략 가능. `output_item.done`에 `name`, `arguments`, `call_id`를 모두 포함해야 함.\n\n### OpenAI 검증된 이벤트 시퀀스\n\n```\n1. response.created\n2. response.output_item.added  (reasoning, rs_ prefix)\n3. response.output_item.done   (reasoning)\n4. response.output_item.added  (message, msg_ prefix)\n5. response.output_text.delta × N\n6. response.output_item.done   (message)\n7. response.output_item.added  (function_call, fc_ prefix)\n8. response.output_item.done   (function_call)\n9. response.completed          (usage 포함)\n```\n\n---\n\n## ID 포맷 (OpenAI 검증 완료)\n\n| ID | 포맷 | 예시 |\n|----|------|------|\n| `response.id` | `resp_` + 50 hex chars (25 bytes) | `resp_0309c0d6cb4ff519016a032143c2288191b3759a2e031f11b2` |\n| `item_id` (message) | `msg_` + 50 hex chars (25 bytes) | `msg_0309c0d6cb4ff519016a03214e4e7c8191bf036ec8113050a7` |\n| `item_id` (function_call) | `fc_` + 50 hex chars (25 bytes) | `fc_0309c0d6cb4ff519016a032152eb1c819182b3994c61de195b` |\n| `item_id` (reasoning) | `rs_` + 50 hex chars (25 bytes) | `rs_0309c0d6cb4ff519016a03214c9eb08191b938b46b170f9d90` |\n| `call_id` | `call_` + base64url(18 bytes) = 24 chars | `call_ueWI5DaDk7YLNXdK8uBWyUTg` |\n\n---\n\n## 아키텍처\n\n### 디렉터리 구조\n\n```\nsrc/\n├── cli.ts                  # CLI 엔트리포인트 (yargs)\n├── config.ts               # 환경변수 기반 Config 파싱\n├── logging.ts              # JSONL 로깅 (log, createNamedLog)\n├── error.ts                # 에러 타입\n├── deep-merge.ts           # JSON-safe 재귀 deep merge\n├── json.ts                 # OpenAI Chat Completions JSON 파싱 유틸\n├── maybe.ts                # Maybe 모나드\n├── server.ts               # Hono 서버 생성 + graceful shutdown\n├── app.ts                  # 라우트 등록 + notFound 핸들러\n├── context.ts              # 서비스 컨텍스트 (Config, ModelsManager)\n├── models-manager.ts       # 로컬 models.jsonc 파일 로딩 + 검증\n├── utils.ts                # 공통 유틸\n├── routes/\n│   ├── responses.ts        # POST /responses — 변환 + SSE 스트리밍 (1034줄)\n│   └── models.ts           # GET /models — 모델 목록 반환\n└── schemas/\n    └── models.ts           # Zod 스키마: ModelsResponse\ntmp/                        # 이전 단일-파일 구현 백업 (참고용)\n```\n\n### 핵심 변환 로직 (`src/routes/responses.ts`)\n\n1. **요청 변환**: Responses API → Chat Completions\n   - Zod 스키마 검증 → `toChatMessages()`로 메시지 변환\n   - `convertTools()`로 function tools 변환\n   - Upstream `POST /v1/chat/completions` 전송\n\n2. **SSE 스트리밍**: Chat Completions 청크 → Responses API 이벤트\n   - `delta.content` → `response.output_text.delta`\n   - `delta.tool_calls` → `response.output_item.added`/`done` (function_call)\n   - `usage` + `response.id` → `response.completed`\n   - ID 생성 (resp_/msg_/fc_ + hex, call_ + base64url)\n\n3. **상태 관리**\n   - `accumulatedContent`: 현재 메시지 텍스트 누적\n   - `accumulatedToolCalls`: tool call 인자 누적 (index별 Map)\n   - `flushCurrentItem()`: 메시지/tool call 완료 시 output_item.done 전송\n\n---\n\n## 개발\n\n```bash\npnpm tsc --noEmit              # 타입 체크\npnpm oxfmt src/                # 포맷\npnpm oxlint --fix src/         # 린트\nnpx tsx src/cli.ts             # 실행 (CLI)\n```\n\n### 출력 (stdout)\n\n```\n{\"timestamp\":\"...\",\"level\":\"info\",\"name\":\"server\",\"message\":\"listening on http://0.0.0.0:4444\"}\n```\n\n`--log-level debug` 시 upstream 청크 디버그 로그도 출력:\n```\n{\"timestamp\":\"...\",\"level\":\"debug\",\"name\":\"routes.responses\",\"message\":\"upstream chunk\",\"id\":\"chatcmpl-...\",\"choices\":[...]}\n```\n\n---\n\n## 기술 스택\n\n- **런타임**: Node.js 22+\n- **언어**: TypeScript (ESM, `module: nodenext`)\n- **웹 프레임워크**: Hono (`@hono/zod-openapi`)\n- **검증**: Zod v4\n- **패키지 매니저**: pnpm\n- **포맷/린트**: oxfmt / oxlint\n- **포맷팅**: JSONL 로깅 (표준 출력)\n\n---\n\n## 참고 자료\n\n- `~/repo/upstreams/codex` — Codex CLI 소스 코드 (Rust)\n  - `codex-api/src/sse/responses.rs` — SSE 이벤트 처리 로직\n  - `codex-api/src/common.rs` — ResponseEvent enum\n  - `codex-api/src/endpoint/responses.rs` — HTTP 요청 구성\n- `~/.codex/log/codex-tui.log` — Codex CLI 로그 (`RUST_LOG=codex_api=trace`)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharryplusplus%2Fcodex2compat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharryplusplus%2Fcodex2compat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharryplusplus%2Fcodex2compat/lists"}