Repository navigation
Combo models expose owned_by combo which breaks clients routing by owned_by #2401
Description
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
- Configure a combo in the dashboard (
#models/combos), e.g.combo/fallback-free. - Start the gateway:
ocx start --port 10100. - Call
GET http://127.0.0.1:10100/v1/modelsand observe combo entries with"owned_by": "combo". - Point an
owned_by-routing OpenAI-compatible client (e.g. DeepSeek Harnessdsh) at the gateway and select the combo model → adapter lookup fails and the call crashes. - Control:
POST /v1/chat/completionswith{ "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_byfor 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": trueflag and document that comboowned_byis non-standard so clients route by the configured connection, not byowned_by.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Aug 22, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Aug 22, 2026
리뷰 · 우선순위 46 / 80
설명: 이 이슈는 콤보 모델이 GET /v1/models 에서 owned_by 를 combo 로 내보내서, 그 글자로 어댑터를 고르는 클라이언트가 죽는다고 한다. 지금 CURRENT
devHEAD 는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 만 고치는 작은 PR 을 받는다. 콤보를 목록에서 지우지 말 것. 중립 글자 하나가 클라이언트가 아는 제공자면 그 글을 쓴다. 이슈는 열어 둔다. 라벨은 그대로 둔다. 프리뷰 배포가 아니다.
이 댓글은 grok-bot이 작성했습니다
fix: catalog effort ladders, v2 pin precedence, and combo owned_by (#…
lidge-jun commentedon Aug 25, 2026
Fixed on
devby #2630 (squash324c1aa87).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/modelsserialization 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 withis_combo: true— so OpenCodex's own dashboard still classifies aliases as combos rather than as native OpenAI rowsThe tradeoff, stated plainly:
owned_bynow 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_byare unaffected either way.Thanks for the precise failure signature —
registration.adapter.prepareCall is not a functionmade the adapter-selection mechanism obvious.fix: catalog effort ladders, v2 pin precedence, and combo owned_by (l…
fix: catalog effort ladders, v2 pin precedence, and combo owned_by (l…