Skip to content

fix(core): follow models.dev packages for Copilot fallback routing - #54210

Merged
rekram1-node merged 1 commit into
devfrom
copilot-catalog-routes
Oct 10, 2026
Merged

rekram1-node merged 1 commit into
devfrom
copilot-catalog-routes

Conversation

@rekram1-node

Copy link
Copy Markdown
Collaborator

When Copilot's GET /models fails (e.g. a proxy blocks it, as reported in #51466), the plugin rewrote every models.dev model to @ai-sdk/github-copilot, so Claude went to /chat/completions instead of Anthropic Messages.

The fallback now follows the models.dev package (anomalyco/models.dev#9277 adds the per-model packages):

  • @ai-sdk/anthropic → stays on native Anthropic Messages; the base URL becomes <account Copilot endpoint>/v1 when known. Catalog variants were already resolved for Messages.
  • @ai-sdk/openai → bundled Copilot SDK with endpoint: "responses".
  • anything else → bundled Copilot SDK; the existing model ID fallback picks chat or Responses.

Until the models.dev change ships, untagged models behave exactly as before. The loaded /models path is unchanged.

Verification

  • New plugin tests for the Anthropic, account-endpoint, and Responses cases fail without the change; bun test test/plugin/provider-github-copilot.test.ts and bun typecheck pass in packages/core.
  • Live: opencode run --standalone against a local proxy that returns 503 for GET /models and forwards everything else to Copilot, with models.dev patched as in [FEATURE]: support for .ts agent files #9277:
model before after
claude-haiku-5.5#high POST /chat/completions POST /v1/messages (200, tool calls)
gpt-5.4-mini — POST /responses (200)
gemini-3.8-flash — POST /chat/completions (200)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks sound. In the fallback, Anthropic-tagged models now stay on Messages with <account endpoint>/v1, and OpenAI-tagged ones go to Copilot Responses. That matches what CopilotModels.build does when /models loads, so the two paths agree, and untagged models behave as before.

I couldn't run it against Copilot (no credentials). The Copilot plugin tests pass (32/32), the 3 new ones fail with the base plugin code, and typecheck in packages/core passes.

Public API is unchanged; this only affects Copilot's internal model routing when GET /models fails.

One small test concern, noted inline: the account-endpoint test polls with no limit.

}),
})
yield* addPlugin()
// The plugin loads the model list in the background; poll on real time because the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop has no limit and runs against a real server, so if the routing breaks again the test hangs until the runner's timeout instead of failing with a clear message. Could you cap it (e.g. times/Schedule.recurs or an Effect.timeout), or wait on the plugin's reload directly if there's a hook for it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capped in a83094c with times: 200 (~2s of 10ms real-time polls), so a regression falls through to the assertions instead of hanging. Effect.timeout/Schedule would use the suite's TestClock, which does not advance here, and the plugin has no reload hook to await. Checked by removing the /v1 base URL assignment: the test fails after ~2.3s with Expected "http://localhost:…/v1", Received "https://api.githubcopilot.com/v1".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That fixes it. In a83094c, Effect.repeat({ until, times: 200 }) at provider-github-copilot.test.ts:446-450 caps the loop, and the wait comes from the real Bun.sleep(10) instead of a schedule. So TestClock isn't involved, and the test can't hang. When the cap is hit, the last model value goes on to the assertions on lines 451-452, so a regression still fails with the baseURL diff, as you saw. I agree with your reasons for not using Effect.timeout or Schedule here.

I ran bun test test/plugin/provider-github-copilot.test.ts at pr/head in a temporary worktree: 32 passed, 0 failed. I didn't repeat your check with the /v1 assignment removed. I removed the worktree afterwards and didn't change the shared checkout.

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