copilot-api-plus 1.2.58 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-manager-BhLImNhx.js → account-manager-C6u4XrO1.js} +3 -2
- package/dist/account-manager-C6u4XrO1.js.map +1 -0
- package/dist/error-DO0tIuq0.js +3 -0
- package/dist/get-user-Brre_x1N.js +3 -0
- package/dist/main.js +603 -15
- package/dist/main.js.map +1 -1
- package/dist/{token-Bk6wTVD5.js → token-DOQwaFAc.js} +2 -2
- package/dist/{token-Bk6wTVD5.js.map → token-DOQwaFAc.js.map} +1 -1
- package/dist/token-Dv2Iyk1S.js +4 -0
- package/package.json +1 -1
- package/dist/account-manager-BhLImNhx.js.map +0 -1
- package/dist/error-BUk1uczJ.js +0 -3
- package/dist/get-user-gLE45Z2B.js +0 -3
- package/dist/token-SOdr_xJ0.js +0 -4
package/dist/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountManager, cacheModels, cacheVSCodeVersion, copilotBaseUrl, copilotHeaders, ensurePaths, findModel, forwardError, getAccountDispatcher, getCopilotUsage, initProxyFromEnv, isAccountProxied, isNullish, isProxyActive, notifyStreamEnd, notifyStreamStart, resetAccountConnections, resetConnections, rootCause, sleep, standardHeaders, state } from "./account-manager-
|
|
3
|
-
import { clearGithubToken, getDeviceCode, pollAccessToken, refreshCopilotToken, setupCopilotToken, setupGitHubToken, stopCopilotTokenRefresh } from "./token-
|
|
2
|
+
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountManager, cacheModels, cacheVSCodeVersion, copilotBaseUrl, copilotHeaders, ensurePaths, findModel, forwardError, getAccountDispatcher, getCopilotUsage, initProxyFromEnv, isAccountProxied, isNullish, isProxyActive, notifyStreamEnd, notifyStreamStart, resetAccountConnections, resetConnections, rootCause, sleep, standardHeaders, state } from "./account-manager-C6u4XrO1.js";
|
|
3
|
+
import { clearGithubToken, getDeviceCode, pollAccessToken, refreshCopilotToken, setupCopilotToken, setupGitHubToken, stopCopilotTokenRefresh } from "./token-DOQwaFAc.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
import consola from "consola";
|
|
@@ -1932,7 +1932,7 @@ function stripOpenAIReminders(payload) {
|
|
|
1932
1932
|
* Copilot's slow models (e.g. claude-opus with thinking) can take up to
|
|
1933
1933
|
* ~120s to start streaming, so we give a generous timeout for headers.
|
|
1934
1934
|
*/
|
|
1935
|
-
const FETCH_TIMEOUT_MS = 12e4;
|
|
1935
|
+
const FETCH_TIMEOUT_MS$1 = 12e4;
|
|
1936
1936
|
/** Minimum interval (ms) between requests on the same account. */
|
|
1937
1937
|
const MIN_SAME_ACCOUNT_INTERVAL_MS = 1e3;
|
|
1938
1938
|
/** Random jitter range (ms) added when switching between accounts. */
|
|
@@ -1946,7 +1946,7 @@ let lastUsedAccountId;
|
|
|
1946
1946
|
* arrive – once the body starts streaming, the timeout is cleared so that
|
|
1947
1947
|
* long SSE responses are not interrupted.
|
|
1948
1948
|
*/
|
|
1949
|
-
async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accountId, accountProxy } = {}) {
|
|
1949
|
+
async function fetchWithTimeout$1(url, init, { timeoutMs = FETCH_TIMEOUT_MS$1, accountId, accountProxy } = {}) {
|
|
1950
1950
|
const controller = new AbortController();
|
|
1951
1951
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1952
1952
|
try {
|
|
@@ -1976,8 +1976,8 @@ async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accou
|
|
|
1976
1976
|
*/
|
|
1977
1977
|
async function fetchWithRetry(url, buildInit, { accountId, accountProxy } = {}) {
|
|
1978
1978
|
try {
|
|
1979
|
-
return await fetchWithTimeout(url, buildInit(), {
|
|
1980
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
1979
|
+
return await fetchWithTimeout$1(url, buildInit(), {
|
|
1980
|
+
timeoutMs: FETCH_TIMEOUT_MS$1,
|
|
1981
1981
|
accountId,
|
|
1982
1982
|
accountProxy
|
|
1983
1983
|
});
|
|
@@ -2241,9 +2241,9 @@ async function retryWithModifiedPayload(payload, releaseSlot) {
|
|
|
2241
2241
|
* Dispatch request to either single-account or multi-account path.
|
|
2242
2242
|
*/
|
|
2243
2243
|
function dispatchRequest(payload) {
|
|
2244
|
-
return state.multiAccountEnabled && accountManager.hasAccounts() ? createWithMultiAccount(payload) : createWithSingleAccount(payload);
|
|
2244
|
+
return state.multiAccountEnabled && accountManager.hasAccounts() ? createWithMultiAccount$1(payload) : createWithSingleAccount$1(payload);
|
|
2245
2245
|
}
|
|
2246
|
-
async function createWithSingleAccount(payload) {
|
|
2246
|
+
async function createWithSingleAccount$1(payload) {
|
|
2247
2247
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
2248
2248
|
const enableVision = payload.messages.some((msg) => typeof msg.content !== "string" && msg.content?.some((part) => part.type === "image_url"));
|
|
2249
2249
|
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
@@ -2270,7 +2270,7 @@ async function createWithSingleAccount(payload) {
|
|
|
2270
2270
|
consola.warn("Copilot token expired, refreshing and retrying...");
|
|
2271
2271
|
try {
|
|
2272
2272
|
await refreshCopilotToken();
|
|
2273
|
-
response = await fetchWithTimeout(url, {
|
|
2273
|
+
response = await fetchWithTimeout$1(url, {
|
|
2274
2274
|
method: "POST",
|
|
2275
2275
|
headers: buildHeaders(),
|
|
2276
2276
|
body: bodyString
|
|
@@ -2422,7 +2422,7 @@ function recordBreakerFailure(reason) {
|
|
|
2422
2422
|
consola.warn(`Circuit breaker OPEN for ${CB_OPEN_MS / 1e3}s after ${breaker.failures} consecutive failures (last: ${reason})`);
|
|
2423
2423
|
}
|
|
2424
2424
|
}
|
|
2425
|
-
async function createWithMultiAccount(payload) {
|
|
2425
|
+
async function createWithMultiAccount$1(payload) {
|
|
2426
2426
|
const remaining = breakerOpenRemainingMs();
|
|
2427
2427
|
if (remaining > 0) throw new HTTPError("Upstream temporarily unavailable", new Response(JSON.stringify({ error: {
|
|
2428
2428
|
type: "service_unavailable",
|
|
@@ -3157,6 +3157,448 @@ async function handleCountTokens(c) {
|
|
|
3157
3157
|
}
|
|
3158
3158
|
}
|
|
3159
3159
|
|
|
3160
|
+
//#endregion
|
|
3161
|
+
//#region src/lib/anthropic-sanitizer.ts
|
|
3162
|
+
/** Upstream message that triggers the assistant-thinking-strip retry. */
|
|
3163
|
+
const INVALID_THINKING_SIGNATURE_PATTERN = /invalid [`'"]?signature[`'"]? in [`'"]?thinking[`'"]? block/i;
|
|
3164
|
+
function isRecord(value) {
|
|
3165
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3166
|
+
}
|
|
3167
|
+
/**
|
|
3168
|
+
* Strip fields the Copilot backend rejects.
|
|
3169
|
+
*
|
|
3170
|
+
* Mutates the payload in place.
|
|
3171
|
+
*/
|
|
3172
|
+
function sanitizeForCopilotBackend(payload) {
|
|
3173
|
+
const extended = payload;
|
|
3174
|
+
if ("context_management" in extended) {
|
|
3175
|
+
consola.debug("Stripping context_management (unsupported by Copilot backend)");
|
|
3176
|
+
delete extended.context_management;
|
|
3177
|
+
}
|
|
3178
|
+
sanitizeOutputConfigFormat(extended.output_config?.format);
|
|
3179
|
+
}
|
|
3180
|
+
function sanitizeOutputConfigFormat(format) {
|
|
3181
|
+
if (!isRecord(format) || format.type !== "json_schema") return;
|
|
3182
|
+
const nested = isRecord(format.json_schema) ? format.json_schema : void 0;
|
|
3183
|
+
const hasFlat = isRecord(format.schema);
|
|
3184
|
+
const hasNested = isRecord(nested?.schema);
|
|
3185
|
+
if (!hasFlat && hasNested) format.schema = nested.schema;
|
|
3186
|
+
if ("json_schema" in format) {
|
|
3187
|
+
consola.debug("Flattening output_config.format.json_schema → format.schema");
|
|
3188
|
+
delete format.json_schema;
|
|
3189
|
+
}
|
|
3190
|
+
if ("name" in format) {
|
|
3191
|
+
consola.debug("Stripping output_config.format.name (Copilot reject)");
|
|
3192
|
+
delete format.name;
|
|
3193
|
+
}
|
|
3194
|
+
if ("strict" in format) {
|
|
3195
|
+
consola.debug("Stripping output_config.format.strict (Copilot reject)");
|
|
3196
|
+
delete format.strict;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Adaptive thinking has a slightly different shape than enabled thinking;
|
|
3201
|
+
* Copilot rejects `budget_tokens_max`. Mutates in place.
|
|
3202
|
+
*/
|
|
3203
|
+
function normalizeAdaptiveThinkingForCopilot(payload) {
|
|
3204
|
+
const thinking = payload.thinking;
|
|
3205
|
+
if (!isRecord(thinking) || thinking.type !== "adaptive") return;
|
|
3206
|
+
if ("budget_tokens_max" in thinking) {
|
|
3207
|
+
consola.debug("Stripping budget_tokens_max from adaptive thinking (Copilot reject)");
|
|
3208
|
+
delete thinking.budget_tokens_max;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
/**
|
|
3212
|
+
* If the client did not specify a `thinking` field, inject the maximum
|
|
3213
|
+
* thinking budget the model supports — pulled from Copilot's `/models`
|
|
3214
|
+
* capabilities. Mutates in place.
|
|
3215
|
+
*
|
|
3216
|
+
* - Models with `adaptive_thinking: true` (claude-opus-4.7,
|
|
3217
|
+
* claude-sonnet-4.6) get `{ type: "adaptive" }` so the model
|
|
3218
|
+
* decides depth dynamically — recommended by Anthropic for
|
|
3219
|
+
* these models.
|
|
3220
|
+
* - Other thinking-capable models get
|
|
3221
|
+
* `{ type: "enabled", budget_tokens: max_thinking_budget }`.
|
|
3222
|
+
* - Models without thinking capability are left untouched.
|
|
3223
|
+
*
|
|
3224
|
+
* Skipped if the client already specified `thinking` (any value) — we
|
|
3225
|
+
* always defer to explicit client intent.
|
|
3226
|
+
*/
|
|
3227
|
+
function injectMaxThinkingBudget(payload) {
|
|
3228
|
+
if (payload.thinking !== void 0) return;
|
|
3229
|
+
const supports = findModel(payload.model)?.capabilities.supports;
|
|
3230
|
+
if (!supports) return;
|
|
3231
|
+
const maxBudget = supports.max_thinking_budget;
|
|
3232
|
+
if (!maxBudget || maxBudget <= 0) return;
|
|
3233
|
+
if (supports.adaptive_thinking === true) {
|
|
3234
|
+
payload.thinking = { type: "adaptive" };
|
|
3235
|
+
consola.debug(`Injected adaptive thinking for ${payload.model} (no client preference)`);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
payload.thinking = {
|
|
3239
|
+
type: "enabled",
|
|
3240
|
+
budget_tokens: maxBudget
|
|
3241
|
+
};
|
|
3242
|
+
consola.debug(`Injected enabled thinking budget=${maxBudget} for ${payload.model} (no client preference)`);
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Remove all `thinking` and `redacted_thinking` blocks from assistant
|
|
3246
|
+
* messages, and drop any assistant turns left empty as a result.
|
|
3247
|
+
*
|
|
3248
|
+
* Pure — returns a new payload, never mutates the input.
|
|
3249
|
+
*/
|
|
3250
|
+
function stripAssistantThinkingBlocks(payload) {
|
|
3251
|
+
let strippedBlocks = 0;
|
|
3252
|
+
let droppedAssistantMessages = 0;
|
|
3253
|
+
const messages = payload.messages.flatMap((message) => {
|
|
3254
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) return [message];
|
|
3255
|
+
const content = message.content.filter((block) => {
|
|
3256
|
+
const shouldStrip = block.type === "thinking" || block.type === "redacted_thinking";
|
|
3257
|
+
if (shouldStrip) strippedBlocks += 1;
|
|
3258
|
+
return !shouldStrip;
|
|
3259
|
+
});
|
|
3260
|
+
if (content.length === message.content.length) return [message];
|
|
3261
|
+
if (content.length === 0) {
|
|
3262
|
+
droppedAssistantMessages += 1;
|
|
3263
|
+
return [];
|
|
3264
|
+
}
|
|
3265
|
+
return [{
|
|
3266
|
+
...message,
|
|
3267
|
+
content
|
|
3268
|
+
}];
|
|
3269
|
+
});
|
|
3270
|
+
if (strippedBlocks === 0) return {
|
|
3271
|
+
payload,
|
|
3272
|
+
stripped: false,
|
|
3273
|
+
strippedBlocks: 0,
|
|
3274
|
+
droppedAssistantMessages: 0
|
|
3275
|
+
};
|
|
3276
|
+
return {
|
|
3277
|
+
payload: {
|
|
3278
|
+
...payload,
|
|
3279
|
+
messages
|
|
3280
|
+
},
|
|
3281
|
+
stripped: true,
|
|
3282
|
+
strippedBlocks,
|
|
3283
|
+
droppedAssistantMessages
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
/** Detect the upstream "invalid thinking signature" 400 to trigger retry. */
|
|
3287
|
+
async function isInvalidThinkingSignatureError(error) {
|
|
3288
|
+
if (!(error instanceof HTTPError) || error.response.status !== 400) return false;
|
|
3289
|
+
const message = await readUpstreamErrorMessage(error.response);
|
|
3290
|
+
return typeof message === "string" && INVALID_THINKING_SIGNATURE_PATTERN.test(message);
|
|
3291
|
+
}
|
|
3292
|
+
async function readUpstreamErrorMessage(response) {
|
|
3293
|
+
let text;
|
|
3294
|
+
try {
|
|
3295
|
+
text = await response.clone().text();
|
|
3296
|
+
} catch {
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
if (!text) return void 0;
|
|
3300
|
+
try {
|
|
3301
|
+
return extractErrorMessage(JSON.parse(text)) ?? text;
|
|
3302
|
+
} catch {
|
|
3303
|
+
return text;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function extractErrorMessage(payload) {
|
|
3307
|
+
if (!isRecord(payload)) return void 0;
|
|
3308
|
+
if (typeof payload.message === "string") return payload.message;
|
|
3309
|
+
const errorField = payload.error;
|
|
3310
|
+
if (isRecord(errorField) && typeof errorField.message === "string") return errorField.message;
|
|
3311
|
+
}
|
|
3312
|
+
function overrideAnthropicResponseModel(response, requestedModel) {
|
|
3313
|
+
return {
|
|
3314
|
+
...response,
|
|
3315
|
+
model: requestedModel
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Override the `model` field in a `message_start` SSE event payload.
|
|
3320
|
+
* Returns the original JSON string if the event is not a message_start
|
|
3321
|
+
* or cannot be parsed.
|
|
3322
|
+
*/
|
|
3323
|
+
function overrideMessageStartEventModel(rawData, requestedModel) {
|
|
3324
|
+
try {
|
|
3325
|
+
const parsed = JSON.parse(rawData);
|
|
3326
|
+
if (parsed.type !== "message_start" || !parsed.message) return rawData;
|
|
3327
|
+
parsed.message.model = requestedModel;
|
|
3328
|
+
return JSON.stringify(parsed);
|
|
3329
|
+
} catch {
|
|
3330
|
+
return rawData;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
//#endregion
|
|
3335
|
+
//#region src/lib/route-resolver.ts
|
|
3336
|
+
/**
|
|
3337
|
+
* Heuristic fallback when the model has no `supported_endpoints` field
|
|
3338
|
+
* (e.g. older Copilot deployments or a stripped models list). We only
|
|
3339
|
+
* use this when the wire-level capability is missing — never to override
|
|
3340
|
+
* an explicitly-advertised capability.
|
|
3341
|
+
*
|
|
3342
|
+
* Anthropic-published Claude models all natively support /v1/messages on
|
|
3343
|
+
* the Copilot backend at the time of writing. Non-Claude models do not.
|
|
3344
|
+
*/
|
|
3345
|
+
function looksLikeClaudeModel(model) {
|
|
3346
|
+
return /^claude-/i.test(model);
|
|
3347
|
+
}
|
|
3348
|
+
/** Per-process cache of the routing decision keyed by model id. */
|
|
3349
|
+
const routeCache = /* @__PURE__ */ new Map();
|
|
3350
|
+
/**
|
|
3351
|
+
* Endpoint identifiers that mean "native Anthropic /v1/messages".
|
|
3352
|
+
*
|
|
3353
|
+
* Copilot's `/models` response uses the literal `/v1/messages` path,
|
|
3354
|
+
* but jer-y/copilot-proxy and earlier Copilot betas used the symbolic
|
|
3355
|
+
* name `anthropic-messages`. Accept both so we are forward- and
|
|
3356
|
+
* backward-compatible with whichever wire format Copilot returns.
|
|
3357
|
+
*/
|
|
3358
|
+
const NATIVE_ANTHROPIC_ENDPOINT_IDS = new Set(["/v1/messages", "anthropic-messages"]);
|
|
3359
|
+
/**
|
|
3360
|
+
* Resolve the upstream route for an Anthropic /v1/messages payload.
|
|
3361
|
+
*
|
|
3362
|
+
* Order of precedence:
|
|
3363
|
+
* 1. User force-disabled native passthrough — always translate.
|
|
3364
|
+
* 2. Model advertises a native Anthropic endpoint → native.
|
|
3365
|
+
* 3. Model advertises supported_endpoints WITHOUT a native one → translate.
|
|
3366
|
+
* 4. Capability missing → fall back to name heuristic (claude-* → native).
|
|
3367
|
+
*/
|
|
3368
|
+
function resolveAnthropicRoute(model) {
|
|
3369
|
+
if (state.disableAnthropicPassthrough) return "translate-openai";
|
|
3370
|
+
const cached = routeCache.get(model);
|
|
3371
|
+
if (cached) return cached;
|
|
3372
|
+
const decision = decideRoute(model);
|
|
3373
|
+
routeCache.set(model, decision);
|
|
3374
|
+
return decision;
|
|
3375
|
+
}
|
|
3376
|
+
function decideRoute(model) {
|
|
3377
|
+
const endpoints = findModel(model)?.supported_endpoints;
|
|
3378
|
+
if (Array.isArray(endpoints) && endpoints.length > 0) return endpoints.some((ep) => NATIVE_ANTHROPIC_ENDPOINT_IDS.has(ep)) ? "native-anthropic" : "translate-openai";
|
|
3379
|
+
return looksLikeClaudeModel(model) ? "native-anthropic" : "translate-openai";
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
//#endregion
|
|
3383
|
+
//#region src/services/copilot/create-anthropic-messages.ts
|
|
3384
|
+
const FETCH_TIMEOUT_MS = 12e4;
|
|
3385
|
+
async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accountId, accountProxy, externalSignal } = {}) {
|
|
3386
|
+
const controller = new AbortController();
|
|
3387
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3388
|
+
const onExternalAbort = () => controller.abort();
|
|
3389
|
+
if (externalSignal) if (externalSignal.aborted) controller.abort();
|
|
3390
|
+
else externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
3391
|
+
try {
|
|
3392
|
+
const fetchOptions = {
|
|
3393
|
+
...init,
|
|
3394
|
+
signal: controller.signal
|
|
3395
|
+
};
|
|
3396
|
+
if (accountId) fetchOptions.dispatcher = getAccountDispatcher(accountId, accountProxy);
|
|
3397
|
+
return await fetch(url, fetchOptions);
|
|
3398
|
+
} catch (error) {
|
|
3399
|
+
if (error instanceof DOMException && error.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
3400
|
+
throw error;
|
|
3401
|
+
} finally {
|
|
3402
|
+
clearTimeout(timer);
|
|
3403
|
+
if (externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
function messageContainsVisionInput(message) {
|
|
3407
|
+
if (message.role !== "user" || !Array.isArray(message.content)) return false;
|
|
3408
|
+
return message.content.some((block) => block.type === "image" || block.type === "tool_result" && toolResultContainsImage(block));
|
|
3409
|
+
}
|
|
3410
|
+
function toolResultContainsImage(block) {
|
|
3411
|
+
if (!Array.isArray(block.content)) return false;
|
|
3412
|
+
return block.content.some((contentBlock) => contentBlock.type === "image");
|
|
3413
|
+
}
|
|
3414
|
+
function messageContinuesAgentLoop(message) {
|
|
3415
|
+
if (message.role === "assistant") return true;
|
|
3416
|
+
if (!Array.isArray(message.content)) return false;
|
|
3417
|
+
return message.content.some((block) => block.type === "tool_result");
|
|
3418
|
+
}
|
|
3419
|
+
function buildAnthropicHeaders(payload, source, options$1) {
|
|
3420
|
+
const enableVision = payload.messages.some((m) => messageContainsVisionInput(m));
|
|
3421
|
+
const isAgentCall = payload.messages.some((m) => messageContinuesAgentLoop(m));
|
|
3422
|
+
return {
|
|
3423
|
+
...copilotHeaders(source, enableVision),
|
|
3424
|
+
"X-Initiator": isAgentCall ? "agent" : "user",
|
|
3425
|
+
...options$1?.anthropicBeta ? { "anthropic-beta": options$1.anthropicBeta } : {}
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
async function createAnthropicMessages(payload, options$1) {
|
|
3429
|
+
injectMaxThinkingBudget(payload);
|
|
3430
|
+
sanitizeForCopilotBackend(payload);
|
|
3431
|
+
normalizeAdaptiveThinkingForCopilot(payload);
|
|
3432
|
+
try {
|
|
3433
|
+
return await dispatchAnthropicRequest(payload, options$1);
|
|
3434
|
+
} catch (error) {
|
|
3435
|
+
if (!await isInvalidThinkingSignatureError(error)) throw error;
|
|
3436
|
+
const stripped = stripAssistantThinkingBlocks(payload);
|
|
3437
|
+
if (!stripped.stripped) throw error;
|
|
3438
|
+
const droppedSuffix = stripped.droppedAssistantMessages > 0 ? ` and dropping ${stripped.droppedAssistantMessages} thinking-only assistant turn(s)` : "";
|
|
3439
|
+
consola.warn(`Native /v1/messages signature retry: stripped ${stripped.strippedBlocks} thinking block(s)${droppedSuffix}`);
|
|
3440
|
+
return await dispatchAnthropicRequest(stripped.payload, options$1);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
async function dispatchAnthropicRequest(payload, options$1) {
|
|
3444
|
+
if (state.multiAccountEnabled && accountManager.hasAccounts()) return createWithMultiAccount(payload, options$1);
|
|
3445
|
+
return createWithSingleAccount(payload, options$1);
|
|
3446
|
+
}
|
|
3447
|
+
async function createWithSingleAccount(payload, options$1) {
|
|
3448
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
3449
|
+
const url = `${copilotBaseUrl(state)}/v1/messages`;
|
|
3450
|
+
const buildHeaders = () => buildAnthropicHeaders(payload, state, options$1);
|
|
3451
|
+
const bodyString = JSON.stringify(payload);
|
|
3452
|
+
consola.debug("Sending request to Copilot (native Anthropic):", {
|
|
3453
|
+
model: payload.model,
|
|
3454
|
+
endpoint: url,
|
|
3455
|
+
stream: payload.stream
|
|
3456
|
+
});
|
|
3457
|
+
let response = await fetchWithTimeout(url, {
|
|
3458
|
+
method: "POST",
|
|
3459
|
+
headers: buildHeaders(),
|
|
3460
|
+
body: bodyString,
|
|
3461
|
+
signal: options$1?.signal
|
|
3462
|
+
});
|
|
3463
|
+
if (response.status === 401) {
|
|
3464
|
+
consola.warn("Copilot token expired, refreshing and retrying...");
|
|
3465
|
+
try {
|
|
3466
|
+
await refreshCopilotToken();
|
|
3467
|
+
response = await fetchWithTimeout(url, {
|
|
3468
|
+
method: "POST",
|
|
3469
|
+
headers: buildHeaders(),
|
|
3470
|
+
body: bodyString,
|
|
3471
|
+
signal: options$1?.signal
|
|
3472
|
+
});
|
|
3473
|
+
} catch (refreshError) {
|
|
3474
|
+
consola.warn(`Failed to refresh token: ${rootCause(refreshError)}`);
|
|
3475
|
+
consola.debug("Failed to refresh token:", refreshError);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
if (!response.ok) await throwUpstreamError(response);
|
|
3479
|
+
if (payload.stream) {
|
|
3480
|
+
const gen = events(response);
|
|
3481
|
+
gen.__accountInfo = { apiBaseUrl: copilotBaseUrl(state) };
|
|
3482
|
+
return gen;
|
|
3483
|
+
}
|
|
3484
|
+
return await response.json();
|
|
3485
|
+
}
|
|
3486
|
+
function buildTokenSource(account) {
|
|
3487
|
+
return {
|
|
3488
|
+
copilotToken: account.copilotToken,
|
|
3489
|
+
copilotApiEndpoint: account.copilotApiEndpoint,
|
|
3490
|
+
accountType: account.accountType,
|
|
3491
|
+
githubToken: account.githubToken,
|
|
3492
|
+
vsCodeVersion: state.vsCodeVersion,
|
|
3493
|
+
machineId: account.machineId,
|
|
3494
|
+
sessionId: account.sessionId,
|
|
3495
|
+
proxy: account.proxy
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
function tagStreamWithAccount(result, account, source) {
|
|
3499
|
+
if (typeof result === "object" && Symbol.asyncIterator in result) result.__accountInfo = {
|
|
3500
|
+
accountId: account.id,
|
|
3501
|
+
accountProxy: account.proxy,
|
|
3502
|
+
apiBaseUrl: copilotBaseUrl(source)
|
|
3503
|
+
};
|
|
3504
|
+
return result;
|
|
3505
|
+
}
|
|
3506
|
+
async function handleMultiAccount401(ctx, account) {
|
|
3507
|
+
try {
|
|
3508
|
+
await accountManager.refreshAccountToken(account);
|
|
3509
|
+
ctx.source.copilotToken = account.copilotToken;
|
|
3510
|
+
const retried = await doFetchAnthropic(ctx);
|
|
3511
|
+
accountManager.markAccountSuccess(account.id);
|
|
3512
|
+
return tagStreamWithAccount(retried, account, ctx.source);
|
|
3513
|
+
} catch (refreshError) {
|
|
3514
|
+
accountManager.markAccountStatus(account.id, "banned", "GitHub token invalid");
|
|
3515
|
+
throw refreshError;
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
async function createWithMultiAccount(payload, options$1) {
|
|
3519
|
+
const triedAccountIds = /* @__PURE__ */ new Set();
|
|
3520
|
+
let lastError;
|
|
3521
|
+
let networkRetried = false;
|
|
3522
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3523
|
+
const account = accountManager.getActiveAccount();
|
|
3524
|
+
if (!account || triedAccountIds.has(account.id)) break;
|
|
3525
|
+
triedAccountIds.add(account.id);
|
|
3526
|
+
if (!account.copilotToken) {
|
|
3527
|
+
consola.debug(`Account ${account.label} has no copilot token, refreshing...`);
|
|
3528
|
+
await accountManager.refreshAccountToken(account);
|
|
3529
|
+
if (!account.copilotToken) {
|
|
3530
|
+
accountManager.markAccountStatus(account.id, "error", "No copilot token");
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
const ctx = {
|
|
3535
|
+
payload,
|
|
3536
|
+
source: buildTokenSource(account),
|
|
3537
|
+
accountId: account.id,
|
|
3538
|
+
options: options$1
|
|
3539
|
+
};
|
|
3540
|
+
try {
|
|
3541
|
+
const result = await doFetchAnthropic(ctx);
|
|
3542
|
+
account.lastRequestAt = Date.now();
|
|
3543
|
+
accountManager.markAccountSuccess(account.id);
|
|
3544
|
+
return tagStreamWithAccount(result, account, ctx.source);
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
lastError = error;
|
|
3547
|
+
if (error instanceof HTTPError) {
|
|
3548
|
+
if (error.response.status === 401) return handleMultiAccount401(ctx, account);
|
|
3549
|
+
if (error.response.status >= 400 && error.response.status < 500) throw error;
|
|
3550
|
+
consola.warn(`Account ${account.label}: 5xx from /v1/messages, trying next account`);
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
const errMsg = error.message || String(error);
|
|
3554
|
+
if (!networkRetried) {
|
|
3555
|
+
networkRetried = true;
|
|
3556
|
+
consola.warn(`Account ${account.label}: network error on /v1/messages, resetting pool and retrying once: ${errMsg}`);
|
|
3557
|
+
resetAccountConnections(account.id);
|
|
3558
|
+
triedAccountIds.delete(account.id);
|
|
3559
|
+
continue;
|
|
3560
|
+
}
|
|
3561
|
+
consola.warn(`Account ${account.label}: network error after retry on /v1/messages (giving up): ${errMsg}`);
|
|
3562
|
+
throw error;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (lastError) throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Network request failed");
|
|
3566
|
+
throw new Error("No available accounts");
|
|
3567
|
+
}
|
|
3568
|
+
async function doFetchAnthropic(ctx) {
|
|
3569
|
+
const { payload, source, accountId, options: options$1 } = ctx;
|
|
3570
|
+
const url = `${copilotBaseUrl(source)}/v1/messages`;
|
|
3571
|
+
const bodyString = JSON.stringify(payload);
|
|
3572
|
+
consola.debug("Sending request to Copilot (multi-account, native Anthropic):", {
|
|
3573
|
+
model: payload.model,
|
|
3574
|
+
endpoint: url,
|
|
3575
|
+
stream: payload.stream
|
|
3576
|
+
});
|
|
3577
|
+
const response = await fetchWithTimeout(url, {
|
|
3578
|
+
method: "POST",
|
|
3579
|
+
headers: buildAnthropicHeaders(payload, source, options$1),
|
|
3580
|
+
body: bodyString,
|
|
3581
|
+
signal: options$1?.signal
|
|
3582
|
+
}, {
|
|
3583
|
+
accountId,
|
|
3584
|
+
accountProxy: source.proxy,
|
|
3585
|
+
externalSignal: options$1?.signal
|
|
3586
|
+
});
|
|
3587
|
+
if (!response.ok) await throwUpstreamError(response);
|
|
3588
|
+
if (payload.stream) return events(response);
|
|
3589
|
+
return await response.json();
|
|
3590
|
+
}
|
|
3591
|
+
async function throwUpstreamError(response) {
|
|
3592
|
+
const errorBody = await response.text();
|
|
3593
|
+
if (response.status === 400) consola.debug(`/v1/messages 400: ${errorBody}`);
|
|
3594
|
+
else consola.error("Failed native Anthropic request", {
|
|
3595
|
+
status: response.status,
|
|
3596
|
+
statusText: response.statusText,
|
|
3597
|
+
body: errorBody
|
|
3598
|
+
});
|
|
3599
|
+
throw new HTTPError(`Failed to call /v1/messages: ${response.status} ${errorBody}`, response);
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3160
3602
|
//#endregion
|
|
3161
3603
|
//#region src/routes/messages/stream-translation.ts
|
|
3162
3604
|
function isToolBlockOpen(state$1) {
|
|
@@ -3445,8 +3887,146 @@ async function handleCompletion(c) {
|
|
|
3445
3887
|
messages_count: anthropicPayload.messages.length,
|
|
3446
3888
|
max_tokens: anthropicPayload.max_tokens
|
|
3447
3889
|
});
|
|
3448
|
-
const openAIPayload = translateToOpenAI(stripSystemReminders(anthropicPayload));
|
|
3449
3890
|
if (state.manualApprove) await awaitApproval();
|
|
3891
|
+
const route = resolveAnthropicRoute(anthropicPayload.model);
|
|
3892
|
+
consola.debug(`Anthropic route resolved: ${route}`);
|
|
3893
|
+
if (route === "native-anthropic") return handleNativePassthrough(c, anthropicPayload);
|
|
3894
|
+
return handleTranslatedCompletion(c, anthropicPayload);
|
|
3895
|
+
}
|
|
3896
|
+
async function handleNativePassthrough(c, anthropicPayload) {
|
|
3897
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
3898
|
+
let result;
|
|
3899
|
+
try {
|
|
3900
|
+
result = await createAnthropicMessages(stripSystemReminders(anthropicPayload), { anthropicBeta });
|
|
3901
|
+
} catch (error) {
|
|
3902
|
+
consola.warn(`Native /v1/messages failed: ${error.message || String(error)}`);
|
|
3903
|
+
throw error;
|
|
3904
|
+
}
|
|
3905
|
+
if (!anthropicPayload.stream) return c.json(overrideAnthropicResponseModel(result, anthropicPayload.model));
|
|
3906
|
+
const stream = result;
|
|
3907
|
+
const accountInfo = stream.__accountInfo;
|
|
3908
|
+
const proxied = accountInfo ? isAccountProxied(accountInfo.accountProxy) : isProxyActive();
|
|
3909
|
+
const heartbeatMs = proxied ? HEARTBEAT_PROXIED_MS : HEARTBEAT_DIRECT_MS;
|
|
3910
|
+
const upstreamTimeoutMs = proxied ? UPSTREAM_TIMEOUT_PROXIED_MS : UPSTREAM_TIMEOUT_DIRECT_MS;
|
|
3911
|
+
consola.debug(`Native SSE config: proxied=${proxied}, heartbeat=${heartbeatMs / 1e3}s, timeout=${upstreamTimeoutMs / 1e3}s`);
|
|
3912
|
+
return streamSSE(c, async (sse) => {
|
|
3913
|
+
const abortController = new AbortController();
|
|
3914
|
+
sse.onAbort(() => abortController.abort());
|
|
3915
|
+
try {
|
|
3916
|
+
await consumeNativeStreamWithHeartbeat(stream, sse, {
|
|
3917
|
+
heartbeatMs,
|
|
3918
|
+
upstreamTimeoutMs,
|
|
3919
|
+
abortSignal: abortController.signal,
|
|
3920
|
+
requestedModel: anthropicPayload.model
|
|
3921
|
+
});
|
|
3922
|
+
} catch (error) {
|
|
3923
|
+
if (!abortController.signal.aborted) {
|
|
3924
|
+
const message = error.message || String(error);
|
|
3925
|
+
consola.warn(`Native SSE stream interrupted: ${message}`);
|
|
3926
|
+
resetConnections();
|
|
3927
|
+
await sendErrorEvent(sse);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
/** Resolve the Anthropic event type for a raw SSE frame. */
|
|
3933
|
+
function resolveAnthropicEventType(rawEvent) {
|
|
3934
|
+
if (rawEvent.event) return rawEvent.event;
|
|
3935
|
+
if (!rawEvent.data) return void 0;
|
|
3936
|
+
try {
|
|
3937
|
+
return JSON.parse(rawEvent.data).type;
|
|
3938
|
+
} catch {
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
async function handleNativeHeartbeatTick(stream, silenceMs, upstreamTimeoutMs) {
|
|
3943
|
+
if (silenceMs >= upstreamTimeoutMs) {
|
|
3944
|
+
consola.warn(`Upstream silent for ${Math.round(silenceMs / 1e3)}s (limit ${upstreamTimeoutMs / 1e3}s), closing native stream`);
|
|
3945
|
+
resetConnections();
|
|
3946
|
+
await sendErrorEvent(stream);
|
|
3947
|
+
return { action: "break" };
|
|
3948
|
+
}
|
|
3949
|
+
await stream.writeSSE({
|
|
3950
|
+
event: "ping",
|
|
3951
|
+
data: "{\"type\":\"ping\"}"
|
|
3952
|
+
});
|
|
3953
|
+
return { action: "continue" };
|
|
3954
|
+
}
|
|
3955
|
+
/** Forward one native event; returns true if it was `message_stop`. */
|
|
3956
|
+
async function forwardNativeEvent(stream, rawEvent, requestedModel) {
|
|
3957
|
+
if (rawEvent.data === "[DONE]") return {
|
|
3958
|
+
forwarded: false,
|
|
3959
|
+
isMessageStop: false
|
|
3960
|
+
};
|
|
3961
|
+
if (!rawEvent.data) return {
|
|
3962
|
+
forwarded: true,
|
|
3963
|
+
isMessageStop: false
|
|
3964
|
+
};
|
|
3965
|
+
const eventType = resolveAnthropicEventType(rawEvent);
|
|
3966
|
+
if (!eventType) {
|
|
3967
|
+
consola.debug("Skipping native SSE chunk with no resolvable type");
|
|
3968
|
+
return {
|
|
3969
|
+
forwarded: true,
|
|
3970
|
+
isMessageStop: false
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
const dataToSend = eventType === "message_start" && requestedModel ? overrideMessageStartEventModel(rawEvent.data, requestedModel) : rawEvent.data;
|
|
3974
|
+
await stream.writeSSE({
|
|
3975
|
+
event: eventType,
|
|
3976
|
+
data: dataToSend
|
|
3977
|
+
});
|
|
3978
|
+
return {
|
|
3979
|
+
forwarded: true,
|
|
3980
|
+
isMessageStop: eventType === "message_stop"
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
/**
|
|
3984
|
+
* Consume an upstream Anthropic SSE generator and forward events untouched.
|
|
3985
|
+
* Heartbeat and upstream-timeout logic mirrors the translated path.
|
|
3986
|
+
*/
|
|
3987
|
+
async function consumeNativeStreamWithHeartbeat(response, stream, opts) {
|
|
3988
|
+
const { heartbeatMs, upstreamTimeoutMs, abortSignal, requestedModel } = opts;
|
|
3989
|
+
const iter = response[Symbol.asyncIterator]();
|
|
3990
|
+
let pendingNext = iter.next();
|
|
3991
|
+
let lastDataAt = Date.now();
|
|
3992
|
+
let sawMessageStop = false;
|
|
3993
|
+
try {
|
|
3994
|
+
while (true) {
|
|
3995
|
+
if (abortSignal?.aborted) {
|
|
3996
|
+
consola.debug("Client disconnected, stopping native SSE consumption");
|
|
3997
|
+
break;
|
|
3998
|
+
}
|
|
3999
|
+
const raceResult = await Promise.race([pendingNext.then((r) => ({
|
|
4000
|
+
kind: "data",
|
|
4001
|
+
result: r
|
|
4002
|
+
})), heartbeatDelay(heartbeatMs)]);
|
|
4003
|
+
if (raceResult === HEARTBEAT) {
|
|
4004
|
+
if ((await handleNativeHeartbeatTick(stream, Date.now() - lastDataAt, upstreamTimeoutMs)).action === "break") break;
|
|
4005
|
+
continue;
|
|
4006
|
+
}
|
|
4007
|
+
const { result: iterResult } = raceResult;
|
|
4008
|
+
if (iterResult.done) break;
|
|
4009
|
+
lastDataAt = Date.now();
|
|
4010
|
+
pendingNext = iter.next();
|
|
4011
|
+
const rawEvent = iterResult.value;
|
|
4012
|
+
const result = await forwardNativeEvent(stream, rawEvent, requestedModel);
|
|
4013
|
+
if (!result.forwarded) break;
|
|
4014
|
+
if (result.isMessageStop) sawMessageStop = true;
|
|
4015
|
+
}
|
|
4016
|
+
if (!sawMessageStop && !abortSignal?.aborted) try {
|
|
4017
|
+
await stream.writeSSE({
|
|
4018
|
+
event: "message_stop",
|
|
4019
|
+
data: "{\"type\":\"message_stop\"}"
|
|
4020
|
+
});
|
|
4021
|
+
} catch {}
|
|
4022
|
+
} finally {
|
|
4023
|
+
try {
|
|
4024
|
+
await iter.return(void 0);
|
|
4025
|
+
} catch {}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
async function handleTranslatedCompletion(c, anthropicPayload) {
|
|
4029
|
+
const openAIPayload = translateToOpenAI(stripSystemReminders(anthropicPayload));
|
|
3450
4030
|
const response = await createChatCompletions(openAIPayload);
|
|
3451
4031
|
if (isNonStreaming(response)) return c.json(translateToAnthropic(response));
|
|
3452
4032
|
const accountInfo = response.__accountInfo;
|
|
@@ -3680,7 +4260,7 @@ async function validateGitHubToken(token) {
|
|
|
3680
4260
|
state.githubToken = token;
|
|
3681
4261
|
consola.info("Using provided GitHub token");
|
|
3682
4262
|
try {
|
|
3683
|
-
const { getGitHubUser } = await import("./get-user-
|
|
4263
|
+
const { getGitHubUser } = await import("./get-user-Brre_x1N.js");
|
|
3684
4264
|
const user = await getGitHubUser();
|
|
3685
4265
|
consola.info(`Logged in as ${user.login}`);
|
|
3686
4266
|
} catch (error) {
|
|
@@ -3719,6 +4299,8 @@ async function runServer(options$1) {
|
|
|
3719
4299
|
state.rateLimitWait = options$1.rateLimitWait;
|
|
3720
4300
|
state.showToken = options$1.showToken;
|
|
3721
4301
|
state.apiKeys = options$1.apiKeys;
|
|
4302
|
+
state.disableAnthropicPassthrough = options$1.disableAnthropicPassthrough;
|
|
4303
|
+
if (options$1.disableAnthropicPassthrough) consola.info("Native Anthropic passthrough DISABLED — all /v1/messages requests will translate via /chat/completions");
|
|
3722
4304
|
if (state.apiKeys && state.apiKeys.length > 0) consola.info(`API key authentication enabled with ${state.apiKeys.length} key(s)`);
|
|
3723
4305
|
await ensurePaths();
|
|
3724
4306
|
await cacheVSCodeVersion();
|
|
@@ -3731,10 +4313,10 @@ async function runServer(options$1) {
|
|
|
3731
4313
|
try {
|
|
3732
4314
|
await setupCopilotToken();
|
|
3733
4315
|
} catch (error) {
|
|
3734
|
-
const { HTTPError: HTTPError$1 } = await import("./error-
|
|
4316
|
+
const { HTTPError: HTTPError$1 } = await import("./error-DO0tIuq0.js");
|
|
3735
4317
|
if (error instanceof HTTPError$1 && error.response.status === 401) {
|
|
3736
4318
|
consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
|
|
3737
|
-
const { clearGithubToken: clearGithubToken$1 } = await import("./token-
|
|
4319
|
+
const { clearGithubToken: clearGithubToken$1 } = await import("./token-Dv2Iyk1S.js");
|
|
3738
4320
|
await clearGithubToken$1();
|
|
3739
4321
|
consola.info("Please restart to re-authenticate");
|
|
3740
4322
|
}
|
|
@@ -3817,6 +4399,11 @@ const start = defineCommand({
|
|
|
3817
4399
|
"api-key": {
|
|
3818
4400
|
type: "string",
|
|
3819
4401
|
description: "API keys for authentication"
|
|
4402
|
+
},
|
|
4403
|
+
"disable-anthropic-passthrough": {
|
|
4404
|
+
type: "boolean",
|
|
4405
|
+
default: false,
|
|
4406
|
+
description: "Force translate all /v1/messages requests via /chat/completions (disable native Copilot Anthropic endpoint)"
|
|
3820
4407
|
}
|
|
3821
4408
|
},
|
|
3822
4409
|
run({ args }) {
|
|
@@ -3836,7 +4423,8 @@ const start = defineCommand({
|
|
|
3836
4423
|
claudeCode: args["claude-code"],
|
|
3837
4424
|
showToken: args["show-token"],
|
|
3838
4425
|
proxyEnv: args["proxy-env"],
|
|
3839
|
-
apiKeys
|
|
4426
|
+
apiKeys,
|
|
4427
|
+
disableAnthropicPassthrough: args["disable-anthropic-passthrough"]
|
|
3840
4428
|
});
|
|
3841
4429
|
}
|
|
3842
4430
|
});
|