Skip to content

Combo models expose owned_by combo which breaks clients routing by owned_by #2401

Closed

Description

@ncepuee

Client or integration

Direct HTTP/API client (OpenAI-compatible GET /v1/models) consumed by an OpenAI-compatible client — DeepSeek Harness (@deepseek-ai/dsh@0.1.1-rc.2). The combo routing itself works in Codex App and via direct chat calls.

Area

Catalog / models

Summary

OpenCodex's GET /v1/models returns combo (failover-group) models with "owned_by": "combo". The literal string "combo" is not a real provider, so OpenAI-compatible clients that select their dispatch adapter by the owned_by field fail to find an adapter. Observed in DeepSeek Harness: registration.adapter.prepareCall is not a function.

Important: the chat routing/failover itself is fine — POST /v1/chat/completions with model: combo/fallback-free reaches the gateway, which resolves the failover group server-side, and it works in Codex App and direct calls. Only the model metadata (owned_by) is malformed for owned_by-routing clients.

Reproduction

  1. Configure a combo in the dashboard (#models/combos), e.g. combo/fallback-free.
  2. Start the gateway: ocx start --port 10100.
  3. Call GET http://127.0.0.1:10100/v1/models and observe combo entries with "owned_by": "combo".
  4. Point an owned_by-routing OpenAI-compatible client (e.g. DeepSeek Harness dsh) at the gateway and select the combo model → adapter lookup fails and the call crashes.
  5. Control: POST /v1/chat/completions with { "model": "combo/fallback-free", ... } works fine, and Codex App works fine.

Version

2.31.0

Operating system

Windows 11 24H2

Provider and model

opencodex / combo/fallback-free (also combo/fallback-think, combo/image)

Logs or error output

本轮运行失败 registration.adapter.prepareCall is not a function

Client-side (DeepSeek Harness dsh dsh-llm/lib/index.js around line 1498):

const registration = this.registration(config.provider);
const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);

The harness looks up the adapter registered for the owned_by value "combo", finds none (because no real provider is named combo), and the registration's adapter is undefined / lacks prepareCall.

Redacted configuration

{
  "combos": [
    { "id": "combo/fallback-free", "owned_by": "combo" },
    { "id": "combo/fallback-think", "owned_by": "combo" },
    { "id": "combo/image", "owned_by": "combo" }
  ]
}

Suggested fix

  • Set owned_by for combo models to a neutral/standard value (e.g. "opencodex") or to the primary target's underlying provider, instead of the literal "combo".
  • Or add an explicit "is_combo": true flag and document that combo owned_by is non-standard so clients route by the configured connection, not by owned_by.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Aug 22, 2026
  2. github-actions commented on Aug 22, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. added
    bugSomething isn't working
    catalogModel catalog, slugs, visibility, routed entries
    on Aug 22, 2026
  4. lidge-jun commented on Aug 22, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 46 / 80

    설명: 이 이슈는 콤보 모델이 GET /v1/models 에서 owned_by 를 combo 로 내보내서, 그 글자로 어댑터를 고르는 클라이언트가 죽는다고 한다. 지금 CURRENT dev HEAD 는 c9d10ed37 이다. 이번 시간에 origin/dev 는 5657fac 에서 여덟 커밋이 와서 여기까지 왔다. 착지는 2390, 2352, 2395, 2398, 2370, 2383, 2368, 2393 이다. package.json 은 2.27.0 이다. src/config.ts 는 3975줄이다. src/runtime 폴더는 지금 HEAD 에 없다. 지금 HEAD 의 src/server/index.ts 1196줄은 공개 모델 목록에서 owned_by 를 m.owned_by 가 없으면 m.provider 로 둔다. 콤보의 provider 는 src/combos/types.ts 12줄 COMBO_NAMESPACE 곧 combo 이다. 그래서 목록 줄이 owned_by combo 가 된다. 네이티브 줄은 1163줄에서 owned_by openai 로 고정이다. 채팅 길 POST /v1/chat/completions 는 서버가 combo/ 이름을 풀어서 보내므로, 작성자가 말한 대로 호출 자체는 된다. DeepSeek Harness 는 owned_by 글자로 등록된 어댑터를 찾는다. combo 라는 제공자는 없어서 prepareCall 이 없다. DSH 내보내기 src/clients/config-export.ts 1254줄은 이미 위험 콤보를 빼고, 1228줄 isKnownSafeDshCombo 는 목표가 openai 가 아닌 것만 남긴다. 그 내보내기는 클라이언트 설정 파일이고, 이 이슈의 직접 HTTP GET /v1/models 길은 그대로 combo 를 보여 준다. 작성자 버전은 2.31.0 이다. 지금 origin/dev 패키지는 2.27.0 이다. PR 이 아직 없다. bug, provider, catalog 라벨이 있다. 고치면 콤보 채팅은 그대로 두고, 목록의 owned_by 만 클라이언트가 아는 제공자 글로 바꾸거나, 콤보를 목록에서 빼는 길이 있다. 빼면 콤보를 고르지 못하고, 바꾸면 어댑터는 살아도 소유자가 거짓이 된다. 사용자 길이로는 콤보를 고르면 클라이언트가 죽는 목록 구멍이라서 46. 카탈로그 팁은 Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. Cursor 정적 카탈로그는 opus-4-8-fast / opus-5-fast. 2334 CursorCredentialRouter 는 여전히 src/providers/cursor-pool.ts 모듈+테스트만 있고 어댑터에 연결되지 않았다. 2332 H2 는 discovery 전용. 2320 overflow + 2342 는 이미 dev. 2188 사이드카는 이미 dev. 2382 데스크톱 앱 재시작은 이미 dev. 2292 는 아직 연다.

    src/server/index.ts 라인 1196 - 지금 HEAD 는 owned_by 를 m.owned_by 아니면 m.provider 로 둔다. 콤보는 provider 가 combo
    src/server/index.ts 라인 1163 - 네이티브 줄은 owned_by openai 로 고정이다
    src/combos/types.ts 라인 12 - COMBO_NAMESPACE 는 combo. 공개 별칭이 없어도 목록 소유자가 combo 가 된다
    src/clients/config-export.ts 라인 1254 - DSH 내보내기는 위험 콤보를 이미 뺀다. GET /v1/models 는 그 길을 안 탄다
    이슈 본문 POST /v1/chat/completions - 작성자가 채팅 길은 된다고 적었다. 목록 메타만 깨진다

    메인테이너의 판단이 필요한 지점

    • owned_by 를 첫 목표 제공자로 바꿀지, openai-compatible 같은 중립 글로 바꿀지, 콤보를 공개 목록에서 뺄지
    • DSH 내보내기의 안전 콤보 규칙과 GET /v1/models 를 같은 규칙으로 맞출지
    • 2.31.0 에서 본 이 구멍이 지금 origin/dev 2.27.0 목록과 같은지. 1196줄은 지금 HEAD 에도 있다

    너의 추천
    채팅 길을 깨지 말고 목록 owned_by 만 고치는 작은 PR 을 받는다. 콤보를 목록에서 지우지 말 것. 중립 글자 하나가 클라이언트가 아는 제공자면 그 글을 쓴다. 이슈는 열어 둔다. 라벨은 그대로 둔다. 프리뷰 배포가 아니다.

    이 댓글은 grok-bot이 작성했습니다

  5. lidge-jun commented on Aug 25, 2026

    @lidge-jun
    Owner

    Fixed on dev by #2630 (squash 324c1aa87).

    You were right that only the metadata was wrong — the routing was never the problem, and your note that failover works in Codex App and direct calls is what localized this to serialization.

    The canonical catalog row keeps provider: "combo". Internal sync and cleanup depend on that marker for catalog authority, so changing it would have traded your bug for a worse one. Only the plain /v1/models serialization changes:

    • owned_by: "openai" — the endpoint is OpenAI-compatible regardless of which target a failover selects, so this names the wire adapter a client should dispatch with
    • is_combo: true — so OpenCodex's own dashboard still classifies aliases as combos rather than as native OpenAI rows

    The tradeoff, stated plainly: owned_by now describes the compatible inbound adapter rather than literal model ownership. I considered using the combo's primary target's provider and rejected it — that value becomes false the moment failover picks a different target, and a client would then select the wrong wire adapter silently, which is worse than the error you got. "opencodex" would have been equally unknown to DeepSeek Harness.

    Clients that ignore owned_by are unaffected either way.

    Thanks for the precise failure signature — registration.adapter.prepareCall is not a function made the adapter-selection mechanism obvious.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entriesproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions