feat(ai): route copilot claude via anthropic messages api
This commit is contained in:
committed by
Mario Zechner
parent
cf1353b8e7
commit
0a132a30a1
@@ -28,6 +28,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
||||
import { parseStreamingJson } from "../utils/json-parse.js";
|
||||
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
||||
|
||||
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
||||
import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
@@ -217,11 +218,22 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
|
||||
try {
|
||||
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
||||
|
||||
let copilotDynamicHeaders: Record<string, string> | undefined;
|
||||
if (model.provider === "github-copilot") {
|
||||
const hasImages = hasCopilotVisionInput(context.messages);
|
||||
copilotDynamicHeaders = buildCopilotDynamicHeaders({
|
||||
messages: context.messages,
|
||||
hasImages,
|
||||
});
|
||||
}
|
||||
|
||||
const { client, isOAuthToken } = createClient(
|
||||
model,
|
||||
apiKey,
|
||||
options?.interleavedThinking ?? true,
|
||||
options?.headers,
|
||||
copilotDynamicHeaders,
|
||||
);
|
||||
const params = buildParams(model, context, isOAuthToken, options);
|
||||
options?.onPayload?.(params);
|
||||
@@ -471,12 +483,54 @@ function isOAuthToken(apiKey: string): boolean {
|
||||
return apiKey.includes("sk-ant-oat");
|
||||
}
|
||||
|
||||
function createClient(
|
||||
model: Model<"anthropic-messages">,
|
||||
apiKey: string,
|
||||
interleavedThinking: boolean,
|
||||
optionsHeaders?: Record<string, string>,
|
||||
): { client: Anthropic; isOAuthToken: boolean } {
|
||||
export interface BuildAnthropicClientOptionsParams {
|
||||
model: Model<"anthropic-messages">;
|
||||
apiKey: string;
|
||||
interleavedThinking: boolean;
|
||||
dynamicHeaders?: Record<string, string>;
|
||||
optionsHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface AnthropicClientConfig {
|
||||
apiKey: string | null;
|
||||
authToken?: string;
|
||||
baseURL: string;
|
||||
defaultHeaders: Record<string, string>;
|
||||
dangerouslyAllowBrowser: boolean;
|
||||
isOAuthToken: boolean;
|
||||
}
|
||||
|
||||
export function buildAnthropicClientOptions(params: BuildAnthropicClientOptionsParams): AnthropicClientConfig {
|
||||
const { model, apiKey, interleavedThinking, dynamicHeaders, optionsHeaders } = params;
|
||||
|
||||
// Copilot: Bearer auth, selective betas
|
||||
if (model.provider === "github-copilot") {
|
||||
const betaFeatures: string[] = [];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
}
|
||||
|
||||
const defaultHeaders = mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
dynamicHeaders,
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
return {
|
||||
apiKey: null,
|
||||
baseURL: model.baseUrl,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
isOAuthToken: false,
|
||||
};
|
||||
}
|
||||
|
||||
const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
@@ -484,7 +538,6 @@ function createClient(
|
||||
|
||||
const oauthToken = isOAuthToken(apiKey);
|
||||
if (oauthToken) {
|
||||
// Stealth mode: Mimic Claude Code's headers exactly
|
||||
const defaultHeaders = mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
@@ -497,15 +550,14 @@ function createClient(
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
const client = new Anthropic({
|
||||
return {
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: true };
|
||||
isOAuthToken: true,
|
||||
};
|
||||
}
|
||||
|
||||
const defaultHeaders = mergeHeaders(
|
||||
@@ -518,14 +570,39 @@ function createClient(
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
const client = new Anthropic({
|
||||
return {
|
||||
apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
isOAuthToken: false,
|
||||
};
|
||||
}
|
||||
|
||||
function createClient(
|
||||
model: Model<"anthropic-messages">,
|
||||
apiKey: string,
|
||||
interleavedThinking: boolean,
|
||||
optionsHeaders?: Record<string, string>,
|
||||
dynamicHeaders?: Record<string, string>,
|
||||
): { client: Anthropic; isOAuthToken: boolean } {
|
||||
const config = buildAnthropicClientOptions({
|
||||
model,
|
||||
apiKey,
|
||||
interleavedThinking,
|
||||
dynamicHeaders,
|
||||
optionsHeaders,
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: false };
|
||||
const client = new Anthropic({
|
||||
apiKey: config.apiKey,
|
||||
...(config.authToken ? { authToken: config.authToken } : {}),
|
||||
baseURL: config.baseURL,
|
||||
defaultHeaders: config.defaultHeaders,
|
||||
dangerouslyAllowBrowser: config.dangerouslyAllowBrowser,
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: config.isOAuthToken };
|
||||
}
|
||||
|
||||
function buildParams(
|
||||
|
||||
Reference in New Issue
Block a user