From 29b5875822ba61c96f1a63366eab4b879e140b8c Mon Sep 17 00:00:00 2001 From: heomin86 <126128010+heomin86@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:57:26 +0900 Subject: [PATCH] docs: add Factory Droid bridge guide --- docs-site/astro.config.mjs | 1 + .../src/content/docs/guides/factory-droid.md | 138 +++++++++++++++++ .../content/docs/ko/guides/factory-droid.md | 139 ++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 docs-site/src/content/docs/guides/factory-droid.md create mode 100644 docs-site/src/content/docs/ko/guides/factory-droid.md diff --git a/docs-site/astro.config.mjs b/docs-site/astro.config.mjs index c1dfc83613..a7cc2576c5 100644 --- a/docs-site/astro.config.mjs +++ b/docs-site/astro.config.mjs @@ -85,6 +85,7 @@ export default defineConfig({ translations: { ko: "가이드", "zh-CN": "指南", "zh-TW": "指南", ru: "Руководства", ja: "ガイド", tr: "Kılavuzlar" }, items: [ { label: "Providers", translations: { ko: "프로바이더", "zh-CN": "提供商", "zh-TW": "供應商", ru: "Провайдеры", ja: "プロバイダー", tr: "Sağlayıcılar" }, slug: "guides/providers" }, + { label: "Factory Droid Bridge", translations: { ko: "Factory Droid 브리지" }, slug: "guides/factory-droid" }, { label: "Model Routing", translations: { ko: "모델 라우팅", "zh-CN": "模型路由", "zh-TW": "模型路由", ru: "Маршрутизация моделей", ja: "モデルルーティング", tr: "Model Yönlendirme" }, slug: "guides/model-routing" }, { label: "Codex Integration", translations: { ko: "Codex 통합", "zh-CN": "Codex 集成", "zh-TW": "Codex 整合", ru: "Интеграция с Codex", ja: "Codex 連携", tr: "Codex Entegrasyonu" }, slug: "guides/codex-integration" }, { label: "Codex App Model Picker", translations: { ko: "Codex App 모델 선택기", "zh-CN": "Codex App 模型选择器", "zh-TW": "Codex App 模型選擇器", ru: "Выбор модели в Codex App", ja: "Codex App モデルピッカー", tr: "Codex App Model Seçici" }, slug: "guides/codex-app-models" }, diff --git a/docs-site/src/content/docs/guides/factory-droid.md b/docs-site/src/content/docs/guides/factory-droid.md new file mode 100644 index 0000000000..810b3e5c58 --- /dev/null +++ b/docs-site/src/content/docs/guides/factory-droid.md @@ -0,0 +1,138 @@ +--- +title: Factory Droid bridge +description: Connect Factory Droid models to opencodex through a local Responses-compatible bridge. +--- + +Factory Droid is an agent runtime, not a documented OpenAI-compatible inference endpoint. If a +custom provider pointed at an internal Factory LLM URL returns `403 Forbidden`, changing only the +opencodex adapter or adding provider headers does not make that private route a supported public API. + +The working integration is: + +```text +Codex App or CLI + -> opencodex (http://127.0.0.1:10100/v1/responses) + -> local Responses bridge (http://127.0.0.1:11435/v1/responses) + -> official droid exec command + -> Factory account and selected model +``` + +This keeps the Factory credential inside the official Droid client. OpenCodex receives a separate, +local-only bridge token. + +## What failed and why + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `403 Forbidden` from a Factory LLM URL | The URL is not a documented general-purpose OpenAI endpoint for third-party clients | Invoke Factory through the official Droid CLI or SDK | +| `404` at `/models/models` | The provider base URL already ended in `/models` | Use an API root as `baseUrl`; never include the discovery path | +| Model search fails | The bridge does not expose a complete live catalog | Set `liveModels: false` and provide a static `models` list | +| Loopback provider is rejected | Private-network access is denied by default | Set `allowPrivateNetwork: true` only for the loopback bridge | +| `${DROID_BRIDGE_TOKEN}` is unresolved | The variable is missing from the opencodex service environment | Inject it into the service process, not only an interactive shell | +| `OutputTextDelta without active item` | The bridge emitted a text delta before opening an output item and content part | Emit the complete Responses SSE lifecycle in order | + +The same Factory credential can therefore work in `droid exec` while a direct request to an +undocumented LLM URL still returns `403`. Those results test different products and should not be +treated as contradictory. + +## Prerequisites + +1. Install and sign in to the [Droid CLI](https://docs.factory.ai/droid-cli/quickstart). +2. Confirm a bounded headless request works: + + ```bash + droid exec --model glm-5.2 --output-format json "Reply with DROID_OK only." + ``` + +3. Run a local bridge that invokes `droid exec` (or the official Droid SDK) and exposes: + + - `GET /healthz` + - `GET /v1/models` + - `POST /v1/responses` + +Factory documents `droid exec` as its non-interactive automation surface and recommends JSON output +for scripts. For a longer-lived integration, Factory also documents stream JSON-RPC and official +TypeScript and Python SDKs in the +[Droid Exec guide](https://docs.factory.ai/droid-exec/overview). + +## Bridge contract + +Bind the bridge to `127.0.0.1`, require a randomly generated bearer token, cap request sizes, and +allowlist model IDs. A minimal text bridge should: + +1. Convert the Responses `input` array to a prompt. +2. invoke `droid exec --model --output-format json `; +3. parse the final `result` and `session_id`; +4. return an OpenAI Responses envelope; and +5. map `previous_response_id` to the Droid session ID when continuation is required. + +For streaming responses, emit this lifecycle in order: + +```text +response.created +response.output_item.added +response.content_part.added +response.output_text.delta +response.output_text.done +response.content_part.done +response.output_item.done +response.completed +``` + +Do not expose the bridge on `0.0.0.0` and do not reuse the Factory credential as the bridge bearer +token. + +## OpenCodex provider configuration + +Add a custom provider, then use **Edit JSON** to configure it: + +```json +{ + "adapter": "openai-responses", + "baseUrl": "http://127.0.0.1:11435/v1", + "responsesPath": "/responses", + "allowPrivateNetwork": true, + "authMode": "key", + "apiKey": "${DROID_BRIDGE_TOKEN}", + "liveModels": false, + "models": ["glm-5.2", "glm-5.2-fast", "kimi-k3"], + "defaultModel": "glm-5.2" +} +``` + +The model IDs are examples. Keep only models that `droid exec` can use for the signed-in Factory +account. Do not add Factory-specific inference headers to this provider: its upstream is the local +bridge, not a Factory HTTP endpoint. + +After saving a provider or changing its static catalog, synchronize and restart the Codex +app-server so new sessions read the updated catalog: + +```bash +ocx sync --restart-codex +ocx doctor +``` + +Restarting Codex app-server processes interrupts active Codex work. Run the restart only after +finishing or saving those sessions. + +## Verify the complete route + +Check each boundary separately: + +```bash +curl -fsS http://127.0.0.1:11435/healthz +ocx doctor +codex exec --ephemeral --model droid/glm-5.2 \ + "Reply with CODEX_DROID_OK only. Do not call tools." +``` + +A provider row or model-picker entry proves only catalog visibility. The integration is working only +after a new Codex process returns a response through the `droid/` route. + +## Current limitation + +The minimal bridge above translates text and the Responses SSE lifecycle. It does **not** implement +the full bidirectional Codex function/tool-call protocol. Tool definitions, tool calls, tool results, +permissions, cancellation, and rich Droid events require a stateful bridge built on Factory's stream +JSON-RPC mode or an official Droid SDK. Treat text success as text-path verification, not tool-path +verification. diff --git a/docs-site/src/content/docs/ko/guides/factory-droid.md b/docs-site/src/content/docs/ko/guides/factory-droid.md new file mode 100644 index 0000000000..3f98d40f7a --- /dev/null +++ b/docs-site/src/content/docs/ko/guides/factory-droid.md @@ -0,0 +1,139 @@ +--- +title: Factory Droid 브리지 +description: 로컬 Responses 호환 브리지를 통해 Factory Droid 모델을 opencodex에 연결합니다. +--- + +Factory Droid는 에이전트 런타임이며, 문서화된 OpenAI 호환 추론 엔드포인트가 아닙니다. 내부 +Factory LLM URL을 사용자 지정 프로바이더로 등록했을 때 `403 Forbidden`이 발생한다면, +opencodex 어댑터나 프로바이더 헤더만 바꿔도 그 비공개 경로가 지원되는 공개 API로 바뀌지는 +않습니다. + +검증된 연결 구조는 다음과 같습니다. + +```text +Codex App 또는 CLI + -> opencodex (http://127.0.0.1:10100/v1/responses) + -> 로컬 Responses 브리지 (http://127.0.0.1:11435/v1/responses) + -> 공식 droid exec 명령 + -> Factory 계정과 선택 모델 +``` + +이 구조에서는 Factory 자격 증명을 공식 Droid 클라이언트 안에 유지합니다. OpenCodex에는 별도의 +로컬 전용 브리지 토큰만 전달합니다. + +## 실패 원인과 수정 방법 + +| 증상 | 원인 | 해결 | +| --- | --- | --- | +| Factory LLM URL에서 `403 Forbidden` | 해당 URL은 서드파티 클라이언트용 범용 OpenAI 엔드포인트로 문서화되지 않음 | 공식 Droid CLI 또는 SDK를 통해 호출 | +| `/models/models`에서 `404` | 프로바이더 Base URL에 `/models`가 이미 포함됨 | `baseUrl`에는 API 루트만 사용하고 검색 경로는 넣지 않음 | +| 모델 검색 실패 | 브리지가 완전한 실시간 카탈로그를 제공하지 않음 | `liveModels: false`와 정적 `models` 목록 사용 | +| 루프백 프로바이더 거부 | 사설 네트워크 접근은 기본적으로 차단됨 | 루프백 브리지에만 `allowPrivateNetwork: true` 설정 | +| `${DROID_BRIDGE_TOKEN}`을 찾지 못함 | opencodex 서비스 환경에 변수가 없음 | 대화형 셸이 아니라 서비스 프로세스에 변수 주입 | +| `OutputTextDelta without active item` | 출력 item과 content part를 열기 전에 text delta를 보냄 | Responses SSE 수명주기 전체를 순서대로 전송 | + +따라서 같은 Factory 자격 증명으로 `droid exec`는 성공하지만, 문서화되지 않은 LLM URL 직접 +요청은 `403`을 반환할 수 있습니다. 두 결과는 서로 다른 제품 표면을 시험한 것이므로 모순이 +아닙니다. + +## 준비 사항 + +1. [Droid CLI](https://docs.factory.ai/droid-cli/quickstart)를 설치하고 로그인합니다. +2. 제한된 headless 요청이 성공하는지 확인합니다. + + ```bash + droid exec --model glm-5.2 --output-format json "DROID_OK만 답하세요." + ``` + +3. `droid exec` 또는 공식 Droid SDK를 호출하면서 아래 엔드포인트를 제공하는 로컬 브리지를 + 실행합니다. + + - `GET /healthz` + - `GET /v1/models` + - `POST /v1/responses` + +Factory는 `droid exec`를 비대화형 자동화 표면으로 문서화하며, 스크립트에서는 JSON 출력을 +권장합니다. 장시간 유지되는 통합에는 stream JSON-RPC와 공식 TypeScript/Python SDK도 사용할 수 +있습니다. 자세한 내용은 [Droid Exec 가이드](https://docs.factory.ai/droid-exec/overview)를 +참고하세요. + +## 브리지 계약 + +브리지는 `127.0.0.1`에만 바인딩하고, 무작위 bearer 토큰을 요구하며, 요청 크기와 모델 ID를 +제한해야 합니다. 최소 텍스트 브리지는 다음 작업을 수행합니다. + +1. Responses `input` 배열을 프롬프트로 변환합니다. +2. `droid exec --model --output-format json `를 실행합니다. +3. 최종 `result`와 `session_id`를 파싱합니다. +4. OpenAI Responses envelope을 반환합니다. +5. 대화 연속성이 필요하면 `previous_response_id`를 Droid session ID에 매핑합니다. + +스트리밍 응답은 다음 수명주기를 순서대로 보내야 합니다. + +```text +response.created +response.output_item.added +response.content_part.added +response.output_text.delta +response.output_text.done +response.content_part.done +response.output_item.done +response.completed +``` + +브리지를 `0.0.0.0`에 노출하지 말고, Factory 자격 증명을 브리지 bearer 토큰으로 재사용하지 +마세요. + +## OpenCodex 프로바이더 설정 + +사용자 지정 프로바이더를 추가한 뒤 **JSON 편집**에서 다음과 같이 설정합니다. + +```json +{ + "adapter": "openai-responses", + "baseUrl": "http://127.0.0.1:11435/v1", + "responsesPath": "/responses", + "allowPrivateNetwork": true, + "authMode": "key", + "apiKey": "${DROID_BRIDGE_TOKEN}", + "liveModels": false, + "models": ["glm-5.2", "glm-5.2-fast", "kimi-k3"], + "defaultModel": "glm-5.2" +} +``` + +모델 ID는 예시입니다. 로그인한 Factory 계정의 `droid exec`에서 실제로 사용할 수 있는 모델만 +남기세요. 이 프로바이더의 업스트림은 Factory HTTP 엔드포인트가 아니라 로컬 브리지이므로 +Factory 추론 전용 헤더를 추가하지 않습니다. + +프로바이더를 저장하거나 정적 카탈로그를 바꾼 뒤에는 새 세션이 갱신된 카탈로그를 읽도록 Codex +app-server를 동기화하고 재시작합니다. + +```bash +ocx sync --restart-codex +ocx doctor +``` + +Codex app-server 재시작은 진행 중인 Codex 작업을 중단합니다. 해당 세션을 끝내거나 저장한 뒤에만 +재시작하세요. + +## 전체 경로 검증 + +각 경계를 따로 확인합니다. + +```bash +curl -fsS http://127.0.0.1:11435/healthz +ocx doctor +codex exec --ephemeral --model droid/glm-5.2 \ + "도구를 호출하지 말고 CODEX_DROID_OK만 답하세요." +``` + +프로바이더 행이나 모델 선택기 표시는 카탈로그 노출만 증명합니다. 새 Codex 프로세스가 +`droid/` 경로를 통해 실제 응답을 반환해야 연동 성공입니다. + +## 현재 한계 + +위 최소 브리지는 텍스트와 Responses SSE 수명주기만 변환합니다. Codex의 완전한 양방향 +function/tool-call 프로토콜은 구현하지 않습니다. 도구 정의, 도구 호출과 결과, 권한, 취소, 풍부한 +Droid 이벤트를 처리하려면 Factory stream JSON-RPC 모드 또는 공식 Droid SDK를 사용하는 상태 유지 +브리지가 필요합니다. 텍스트 성공을 도구 경로 성공으로 간주하지 마세요.