Skip to content

fix: resolve translated model name against live Copilot catalog#41

Open
clairewangjia wants to merge 1 commit into
nocoo:mainfrom
clairewangjia:fix/catalog-aware-model-resolution
Open

fix: resolve translated model name against live Copilot catalog#41
clairewangjia wants to merge 1 commit into
nocoo:mainfrom
clairewangjia:fix/catalog-aware-model-resolution

Conversation

@clairewangjia
Copy link
Copy Markdown

Summary

The Copilot /v1/models endpoint sometimes exposes 1M / preview variants under an -internal suffix.

Concrete case I hit today on a Microsoft Copilot account:

  • Catalog contains claude-opus-4.7-1m-internal
  • Catalog does not contain claude-opus-4.7-1m

translateModelName(\"claude-opus-4-7\", \"context-1m-2025-08-07\") always emits claude-opus-4.7-1m, so the request is rejected upstream:

```
400 {"error":{"message":"The requested model is not supported.","code":"model_not_supported",...}}
```

Fix

New pure helper resolveAgainstCatalog(name, catalogIds):

  • if catalogIds is empty → return name (back-compat for callers that don't pass catalog)
  • if name is in catalog → return name (the common case — no behaviour change for accounts where Copilot exposes the plain id)
  • if ${name}-internal is in catalog → return that
  • else → return name (let upstream reject as before, so we don't mask other errors)

Threaded through:

  • preprocessPayload(..., catalogIds = []) — new optional 3rd arg, defaults to []
  • routes/messages/handler.ts — passes state.models?.data?.map(m => m.id) ?? [] and overrides openAIPayload.model = copilotModel on the translated-Copilot path so the resolved id is actually used on the wire

Why this is safe for users who don't see -internal variants

The exact-match branch fires first. If your catalog has claude-opus-4.7-1m directly, this PR is a no-op for your traffic.

Test plan

  • Existing 51 preprocess.test.ts tests still pass
  • 5 new cases on resolveAgainstCatalog (empty catalog, exact match, internal-only, both-present prefers-exact, neither-present)
  • 2 new cases on preprocessPayload catalog threading (with + without catalog)
  • bun test packages/proxy/test/routes/preprocess.test.ts → 56 pass
  • Typecheck clean
  • Pre-commit + pre-push gates pass (lint, typecheck, gitleaks, osv-scanner, coverage, arch)
  • Manually verified: claude-cli → opus-4.7 + 1M context beta now works end-to-end through raven against my Copilot account (previously 400)

Open question for maintainer

Happy to narrow the heuristic if you'd prefer (e.g. only attempt -internal for known -1m suffixes) — current version is intentionally a generic fallback since the upstream naming may extend to other variants.

The Copilot models endpoint sometimes exposes 1m/preview variants
under an `-internal` suffix (observed today: `claude-opus-4.7-1m-internal`,
no plain `claude-opus-4.7-1m`). `translateModelName` always emits the
plain form, so requests with `anthropic-beta: context-1m-*` against
opus-4.7 are rejected upstream with:

    400 {"code":"model_not_supported", ...}

This adds `resolveAgainstCatalog(name, catalogIds)`: if the translated
name is missing from the live `/v1/models` catalog but the `-internal`
variant exists, use the variant. Catalog passed through `preprocessPayload`
(defaults to `[]`, preserving prior behaviour for any caller that doesn't
opt in). `routes/messages/handler.ts` threads `state.models.data` in and
also overrides `openAIPayload.model` on the translated-Copilot path so
the resolved id is what actually goes on the wire.

When the catalog already contains the plain id (the common case for
accounts where Copilot exposes the model directly), behaviour is
unchanged — the resolver short-circuits on exact match.

Tests: 5 new cases on `resolveAgainstCatalog` + 2 on `preprocessPayload`
threading. All existing tests still pass.
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