fix(ai): excluded copilot urls from interleaved http beta

- Excluded effective github-copilot URLs from the interleaved beta path so custom-provider and rerouted Copilot hosts don't send the rejected header.
- Covered a custom provider on a Copilot host omitting the beta.

Fixes #6717
This commit is contained in:
roboomp
2026-07-26 16:03:56 +00:00
parent 6c254ca4f1
commit 8a0cb21092
2 changed files with 22 additions and 5 deletions
+10 -5
View File
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
import { scheduler } from "node:timers/promises";
import * as tls from "node:tls";
import { isAnthropicSigningProxyUrl, isOfficialAnthropicApiUrl } from "@oh-my-pi/pi-catalog/compat/anthropic";
import { isVertexRawPredictUrl } from "@oh-my-pi/pi-catalog/hosts";
import { hostMatchesUrl, isVertexRawPredictUrl } from "@oh-my-pi/pi-catalog/hosts";
import { mapEffortToAnthropicAdaptiveEffort } from "@oh-my-pi/pi-catalog/model-thinking";
import { calculateCost, getBundledModel } from "@oh-my-pi/pi-catalog/models";
import { isAnthropicOAuthToken } from "@oh-my-pi/pi-catalog/utils";
@@ -2856,15 +2856,20 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
// official API, so only known non-official signing routes need the beta
// (#6717). Classify the effective URL: Foundry and provider overrides can
// reroute a model without rebuilding its materialized compat.
// Vertex rawPredict is excluded like every other HTTP-beta path here: it can
// only accept betas in the JSON body (`anthropic_beta`), never as this
// `anthropic-beta` HTTP header, so advertising it there earns a 400 (#5614).
// Two signing routes still can't take the beta as this `anthropic-beta` HTTP
// header, so they're excluded: Vertex rawPredict accepts betas only in the
// JSON body (`anthropic_beta`) and 400s on the header (#5614), and GitHub
// Copilot rejects Anthropic betas outright — the `github-copilot` provider
// branch below strips them, but a custom provider id or a canonical model
// rerouted to `api.githubcopilot.com` / `copilot-api.*` reaches the generic
// header builder instead, so exclude those effective URLs here too.
const needsInterleavedBeta =
interleavedThinking &&
(!model.thinking?.supportsDisplay ||
(!isOfficialAnthropicApiUrl(baseUrl) &&
isAnthropicSigningProxyUrl(baseUrl) &&
!isVertexRawPredictUrl(baseUrl ?? "")));
!isVertexRawPredictUrl(baseUrl ?? "") &&
!hostMatchesUrl(baseUrl, "githubCopilot")));
const oauthToken = isOAuth ?? isAnthropicOAuthToken(apiKey);
const supportsEagerToolInputStreaming = resolveEagerToolInputStreamingSupport(model, baseUrl);
const needsFineGrainedToolStreamingBeta =
@@ -1852,11 +1852,23 @@ describe("Anthropic request fingerprint alignment", () => {
apiKey: "vertex-adc",
interleavedThinking: true,
});
// A custom provider on a Copilot host is signing, but the proxy rejects
// Anthropic betas outright and this path bypasses the provider branch.
const copilotUrlProxy = buildAnthropicClientOptions({
model: buildModel({
...adaptiveProxySpec,
provider: "custom-copilot",
baseUrl: "https://api.githubcopilot.com",
}),
apiKey: "ghu_test",
interleavedThinking: true,
});
expect(signingProxy.defaultHeaders["anthropic-beta"]).toContain("interleaved-thinking-2025-05-14");
expect(reroutedSigningProxy.defaultHeaders["anthropic-beta"]).toContain("interleaved-thinking-2025-05-14");
expect(nonSigningProxy.defaultHeaders["anthropic-beta"] ?? "").not.toContain("interleaved-thinking-2025-05-14");
expect(vertexRawPredict.defaultHeaders["anthropic-beta"] ?? "").not.toContain("interleaved-thinking-2025-05-14");
expect(copilotUrlProxy.defaultHeaders["anthropic-beta"] ?? "").not.toContain("interleaved-thinking-2025-05-14");
});
it("adds legacy fine-grained tool-streaming beta only for tool requests on incompatible models", () => {