Skip to content

feat(gateway): prefix non-Anthropic ids for the Claude Code picker - #483

Draft
yyyr-p wants to merge 3 commits into
Menci:mainfrom
yyyr-p:discovery-cc-picker-prefix-port
Draft

feat(gateway): prefix non-Anthropic ids for the Claude Code picker#483
yyyr-p wants to merge 3 commits into
Menci:mainfrom
yyyr-p:discovery-cc-picker-prefix-port

Conversation

@yyyr-p

@yyyr-p yyyr-p commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Claude Code CLI's gateway-discovery model picker filters /v1/models entries by id: Anthropic documents that it ignores any entry whose id doesn't begin with claude or anthropic, and the compiled Bootstrap Gateway /v1/models handler in @anthropic-ai/claude-code@2.1.211 matches that verbatim — models.filter(m => /^(claude|anthropic)/i.test(m.id)), then a second pass that drops ids resolving to a built-in Claude family other than Fable. Any non-Anthropic model Floway advertises through discovery is silently dropped from the /model menu, so an operator who routes GPT-5 or Gemini through a gateway upstream never sees it selectable.

The /v1/models handler now prepends a claude-code! marker to every non-Anthropic id when the caller is the CLI (claude-code/<version> User-Agent). The picker admits the prefixed id, and because it renders display_name ?? id, the operator-configured label is still what the user sees — the id rewrite is invisible in the UI. The marker is an encoding prefix rather than a reserved namespace: a raw id that already begins with claude-code! gains another layer, so M, P+M, and P+P+M map to P+M, P+P+M, P+P+P+M and every advertised id stays unique without reserving ! from upstream ids. The catalog is also narrowed to chat models before the rewrite, matching the Codex and Gemini discovery handlers — the picker is a chat surface and embedding/image entries only clutter it.

On the Messages path the same id comes back from the CLI's inference User-Agent (claude-cli/<version>, distinct from the claude-code/<version> discovery identity), and the serve boundary strips exactly one prefix layer before model resolution, so the generic candidate resolver stays unaware of the compatibility projection. Other clients' ids pass through untouched: the decode is gated on the claude-cli/ User-Agent so a literal upstream id that happens to begin with claude-code! is preserved rather than truncated.

Test Plan

  • pnpm run verify — the gateway suite is green, including 36 tests across http_test.ts and serve_test.ts covering the encode/decode round-trip, prefix doubling, the [1m] suffix composition, the chat-only narrow, and the non-Claude-Code passthrough. Five apps/web failures (Copilot device-flow polling, OAuth authorize-url) are pre-existing on upstream/main and unrelated to this PR — apps/web is unchanged in this branch.
  • A non-Anthropic id (gpt-4o) is served as claude-code!gpt-4o to the CLI and decoded back to gpt-4o before resolution; a literal claude-code!gpt-4o upstream id round-trips through a doubled prefix to its original form.

@yyyr-p

yyyr-p commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Wait for pipeline refactoring before merging this

The Claude Code picker prefix only triggered on the standalone CLI's
`claude-code/` discovery UA and `claude-cli/` inference UA. The Claude
Desktop app embeds the same picker but its HTTP layer is an Electron
fetch, so its `/v1/models` and `/v1/messages` requests carry a
`Mozilla/5.0 ... Claude/<version> ... Electron/...` UA instead. Those
requests fell through to the public OpenAI-Anthropic superset, so every
non-Anthropic model was silently dropped by the picker's
`claude`/`anthropic` id filter, and a prefixed id picked from the
desktop chat would reach the resolver undecoded.

Match the desktop app's `Claude/<version>` product token on both the
discovery and inference paths so it receives the Anthropic-native
catalog with prefixed ids and gets the prefix stripped before
resolution. The begins-with ACCEPT predicate is kept (not loosened to
the v2.1.223+ contains-anywhere rule) so a single encoding survives
both picker generations.
@yyyr-p

yyyr-p commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Added support for Claude Desktop. This also solves #496

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant