copilot-api-plus 1.2.57 → 1.3.0

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/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-DUSOybLm.js";
3
- import { clearGithubToken, getDeviceCode, pollAccessToken, refreshCopilotToken, setupCopilotToken, setupGitHubToken, stopCopilotTokenRefresh } from "./token-Dhk6Zi-n.js";
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,405 @@ 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
+ * Remove all `thinking` and `redacted_thinking` blocks from assistant
3213
+ * messages, and drop any assistant turns left empty as a result.
3214
+ *
3215
+ * Pure — returns a new payload, never mutates the input.
3216
+ */
3217
+ function stripAssistantThinkingBlocks(payload) {
3218
+ let strippedBlocks = 0;
3219
+ let droppedAssistantMessages = 0;
3220
+ const messages = payload.messages.flatMap((message) => {
3221
+ if (message.role !== "assistant" || !Array.isArray(message.content)) return [message];
3222
+ const content = message.content.filter((block) => {
3223
+ const shouldStrip = block.type === "thinking" || block.type === "redacted_thinking";
3224
+ if (shouldStrip) strippedBlocks += 1;
3225
+ return !shouldStrip;
3226
+ });
3227
+ if (content.length === message.content.length) return [message];
3228
+ if (content.length === 0) {
3229
+ droppedAssistantMessages += 1;
3230
+ return [];
3231
+ }
3232
+ return [{
3233
+ ...message,
3234
+ content
3235
+ }];
3236
+ });
3237
+ if (strippedBlocks === 0) return {
3238
+ payload,
3239
+ stripped: false,
3240
+ strippedBlocks: 0,
3241
+ droppedAssistantMessages: 0
3242
+ };
3243
+ return {
3244
+ payload: {
3245
+ ...payload,
3246
+ messages
3247
+ },
3248
+ stripped: true,
3249
+ strippedBlocks,
3250
+ droppedAssistantMessages
3251
+ };
3252
+ }
3253
+ /** Detect the upstream "invalid thinking signature" 400 to trigger retry. */
3254
+ async function isInvalidThinkingSignatureError(error) {
3255
+ if (!(error instanceof HTTPError) || error.response.status !== 400) return false;
3256
+ const message = await readUpstreamErrorMessage(error.response);
3257
+ return typeof message === "string" && INVALID_THINKING_SIGNATURE_PATTERN.test(message);
3258
+ }
3259
+ async function readUpstreamErrorMessage(response) {
3260
+ let text;
3261
+ try {
3262
+ text = await response.clone().text();
3263
+ } catch {
3264
+ return;
3265
+ }
3266
+ if (!text) return void 0;
3267
+ try {
3268
+ return extractErrorMessage(JSON.parse(text)) ?? text;
3269
+ } catch {
3270
+ return text;
3271
+ }
3272
+ }
3273
+ function extractErrorMessage(payload) {
3274
+ if (!isRecord(payload)) return void 0;
3275
+ if (typeof payload.message === "string") return payload.message;
3276
+ const errorField = payload.error;
3277
+ if (isRecord(errorField) && typeof errorField.message === "string") return errorField.message;
3278
+ }
3279
+ function overrideAnthropicResponseModel(response, requestedModel) {
3280
+ return {
3281
+ ...response,
3282
+ model: requestedModel
3283
+ };
3284
+ }
3285
+ /**
3286
+ * Override the `model` field in a `message_start` SSE event payload.
3287
+ * Returns the original JSON string if the event is not a message_start
3288
+ * or cannot be parsed.
3289
+ */
3290
+ function overrideMessageStartEventModel(rawData, requestedModel) {
3291
+ try {
3292
+ const parsed = JSON.parse(rawData);
3293
+ if (parsed.type !== "message_start" || !parsed.message) return rawData;
3294
+ parsed.message.model = requestedModel;
3295
+ return JSON.stringify(parsed);
3296
+ } catch {
3297
+ return rawData;
3298
+ }
3299
+ }
3300
+
3301
+ //#endregion
3302
+ //#region src/lib/route-resolver.ts
3303
+ /**
3304
+ * Heuristic fallback when the model has no `supported_endpoints` field
3305
+ * (e.g. older Copilot deployments or a stripped models list). We only
3306
+ * use this when the wire-level capability is missing — never to override
3307
+ * an explicitly-advertised capability.
3308
+ *
3309
+ * Anthropic-published Claude models all natively support /v1/messages on
3310
+ * the Copilot backend at the time of writing. Non-Claude models do not.
3311
+ */
3312
+ function looksLikeClaudeModel(model) {
3313
+ return /^claude-/i.test(model);
3314
+ }
3315
+ /** Per-process cache of the routing decision keyed by model id. */
3316
+ const routeCache = /* @__PURE__ */ new Map();
3317
+ /**
3318
+ * Resolve the upstream route for an Anthropic /v1/messages payload.
3319
+ *
3320
+ * Order of precedence:
3321
+ * 1. User force-disabled native passthrough — always translate.
3322
+ * 2. Model advertises `anthropic-messages` in supported_endpoints → native.
3323
+ * 3. Model advertises supported_endpoints WITHOUT anthropic-messages → translate.
3324
+ * 4. Capability missing → fall back to name heuristic (claude-* → native).
3325
+ */
3326
+ function resolveAnthropicRoute(model) {
3327
+ if (state.disableAnthropicPassthrough) return "translate-openai";
3328
+ const cached = routeCache.get(model);
3329
+ if (cached) return cached;
3330
+ const decision = decideRoute(model);
3331
+ routeCache.set(model, decision);
3332
+ return decision;
3333
+ }
3334
+ function decideRoute(model) {
3335
+ const endpoints = findModel(model)?.supported_endpoints;
3336
+ if (Array.isArray(endpoints) && endpoints.length > 0) return endpoints.includes("anthropic-messages") ? "native-anthropic" : "translate-openai";
3337
+ return looksLikeClaudeModel(model) ? "native-anthropic" : "translate-openai";
3338
+ }
3339
+
3340
+ //#endregion
3341
+ //#region src/services/copilot/create-anthropic-messages.ts
3342
+ const FETCH_TIMEOUT_MS = 12e4;
3343
+ async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accountId, accountProxy, externalSignal } = {}) {
3344
+ const controller = new AbortController();
3345
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3346
+ const onExternalAbort = () => controller.abort();
3347
+ if (externalSignal) if (externalSignal.aborted) controller.abort();
3348
+ else externalSignal.addEventListener("abort", onExternalAbort, { once: true });
3349
+ try {
3350
+ const fetchOptions = {
3351
+ ...init,
3352
+ signal: controller.signal
3353
+ };
3354
+ if (accountId) fetchOptions.dispatcher = getAccountDispatcher(accountId, accountProxy);
3355
+ return await fetch(url, fetchOptions);
3356
+ } catch (error) {
3357
+ if (error instanceof DOMException && error.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms`);
3358
+ throw error;
3359
+ } finally {
3360
+ clearTimeout(timer);
3361
+ if (externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
3362
+ }
3363
+ }
3364
+ function messageContainsVisionInput(message) {
3365
+ if (message.role !== "user" || !Array.isArray(message.content)) return false;
3366
+ return message.content.some((block) => block.type === "image" || block.type === "tool_result" && toolResultContainsImage(block));
3367
+ }
3368
+ function toolResultContainsImage(block) {
3369
+ if (!Array.isArray(block.content)) return false;
3370
+ return block.content.some((contentBlock) => contentBlock.type === "image");
3371
+ }
3372
+ function messageContinuesAgentLoop(message) {
3373
+ if (message.role === "assistant") return true;
3374
+ if (!Array.isArray(message.content)) return false;
3375
+ return message.content.some((block) => block.type === "tool_result");
3376
+ }
3377
+ function buildAnthropicHeaders(payload, source, options$1) {
3378
+ const enableVision = payload.messages.some((m) => messageContainsVisionInput(m));
3379
+ const isAgentCall = payload.messages.some((m) => messageContinuesAgentLoop(m));
3380
+ return {
3381
+ ...copilotHeaders(source, enableVision),
3382
+ "X-Initiator": isAgentCall ? "agent" : "user",
3383
+ ...options$1?.anthropicBeta ? { "anthropic-beta": options$1.anthropicBeta } : {}
3384
+ };
3385
+ }
3386
+ async function createAnthropicMessages(payload, options$1) {
3387
+ sanitizeForCopilotBackend(payload);
3388
+ normalizeAdaptiveThinkingForCopilot(payload);
3389
+ try {
3390
+ return await dispatchAnthropicRequest(payload, options$1);
3391
+ } catch (error) {
3392
+ if (!await isInvalidThinkingSignatureError(error)) throw error;
3393
+ const stripped = stripAssistantThinkingBlocks(payload);
3394
+ if (!stripped.stripped) throw error;
3395
+ const droppedSuffix = stripped.droppedAssistantMessages > 0 ? ` and dropping ${stripped.droppedAssistantMessages} thinking-only assistant turn(s)` : "";
3396
+ consola.warn(`Native /v1/messages signature retry: stripped ${stripped.strippedBlocks} thinking block(s)${droppedSuffix}`);
3397
+ return await dispatchAnthropicRequest(stripped.payload, options$1);
3398
+ }
3399
+ }
3400
+ async function dispatchAnthropicRequest(payload, options$1) {
3401
+ if (state.multiAccountEnabled && accountManager.hasAccounts()) return createWithMultiAccount(payload, options$1);
3402
+ return createWithSingleAccount(payload, options$1);
3403
+ }
3404
+ async function createWithSingleAccount(payload, options$1) {
3405
+ if (!state.copilotToken) throw new Error("Copilot token not found");
3406
+ const url = `${copilotBaseUrl(state)}/v1/messages`;
3407
+ const buildHeaders = () => buildAnthropicHeaders(payload, state, options$1);
3408
+ const bodyString = JSON.stringify(payload);
3409
+ consola.debug("Sending request to Copilot (native Anthropic):", {
3410
+ model: payload.model,
3411
+ endpoint: url,
3412
+ stream: payload.stream
3413
+ });
3414
+ let response = await fetchWithTimeout(url, {
3415
+ method: "POST",
3416
+ headers: buildHeaders(),
3417
+ body: bodyString,
3418
+ signal: options$1?.signal
3419
+ });
3420
+ if (response.status === 401) {
3421
+ consola.warn("Copilot token expired, refreshing and retrying...");
3422
+ try {
3423
+ await refreshCopilotToken();
3424
+ response = await fetchWithTimeout(url, {
3425
+ method: "POST",
3426
+ headers: buildHeaders(),
3427
+ body: bodyString,
3428
+ signal: options$1?.signal
3429
+ });
3430
+ } catch (refreshError) {
3431
+ consola.warn(`Failed to refresh token: ${rootCause(refreshError)}`);
3432
+ consola.debug("Failed to refresh token:", refreshError);
3433
+ }
3434
+ }
3435
+ if (!response.ok) await throwUpstreamError(response);
3436
+ if (payload.stream) {
3437
+ const gen = events(response);
3438
+ gen.__accountInfo = { apiBaseUrl: copilotBaseUrl(state) };
3439
+ return gen;
3440
+ }
3441
+ return await response.json();
3442
+ }
3443
+ function buildTokenSource(account) {
3444
+ return {
3445
+ copilotToken: account.copilotToken,
3446
+ copilotApiEndpoint: account.copilotApiEndpoint,
3447
+ accountType: account.accountType,
3448
+ githubToken: account.githubToken,
3449
+ vsCodeVersion: state.vsCodeVersion,
3450
+ machineId: account.machineId,
3451
+ sessionId: account.sessionId,
3452
+ proxy: account.proxy
3453
+ };
3454
+ }
3455
+ function tagStreamWithAccount(result, account, source) {
3456
+ if (typeof result === "object" && Symbol.asyncIterator in result) result.__accountInfo = {
3457
+ accountId: account.id,
3458
+ accountProxy: account.proxy,
3459
+ apiBaseUrl: copilotBaseUrl(source)
3460
+ };
3461
+ return result;
3462
+ }
3463
+ async function handleMultiAccount401(ctx, account) {
3464
+ try {
3465
+ await accountManager.refreshAccountToken(account);
3466
+ ctx.source.copilotToken = account.copilotToken;
3467
+ const retried = await doFetchAnthropic(ctx);
3468
+ accountManager.markAccountSuccess(account.id);
3469
+ return tagStreamWithAccount(retried, account, ctx.source);
3470
+ } catch (refreshError) {
3471
+ accountManager.markAccountStatus(account.id, "banned", "GitHub token invalid");
3472
+ throw refreshError;
3473
+ }
3474
+ }
3475
+ async function createWithMultiAccount(payload, options$1) {
3476
+ const triedAccountIds = /* @__PURE__ */ new Set();
3477
+ let lastError;
3478
+ let networkRetried = false;
3479
+ for (let attempt = 0; attempt < 3; attempt++) {
3480
+ const account = accountManager.getActiveAccount();
3481
+ if (!account || triedAccountIds.has(account.id)) break;
3482
+ triedAccountIds.add(account.id);
3483
+ if (!account.copilotToken) {
3484
+ consola.debug(`Account ${account.label} has no copilot token, refreshing...`);
3485
+ await accountManager.refreshAccountToken(account);
3486
+ if (!account.copilotToken) {
3487
+ accountManager.markAccountStatus(account.id, "error", "No copilot token");
3488
+ continue;
3489
+ }
3490
+ }
3491
+ const ctx = {
3492
+ payload,
3493
+ source: buildTokenSource(account),
3494
+ accountId: account.id,
3495
+ options: options$1
3496
+ };
3497
+ try {
3498
+ const result = await doFetchAnthropic(ctx);
3499
+ account.lastRequestAt = Date.now();
3500
+ accountManager.markAccountSuccess(account.id);
3501
+ return tagStreamWithAccount(result, account, ctx.source);
3502
+ } catch (error) {
3503
+ lastError = error;
3504
+ if (error instanceof HTTPError) {
3505
+ if (error.response.status === 401) return handleMultiAccount401(ctx, account);
3506
+ if (error.response.status >= 400 && error.response.status < 500) throw error;
3507
+ consola.warn(`Account ${account.label}: 5xx from /v1/messages, trying next account`);
3508
+ continue;
3509
+ }
3510
+ const errMsg = error.message || String(error);
3511
+ if (!networkRetried) {
3512
+ networkRetried = true;
3513
+ consola.warn(`Account ${account.label}: network error on /v1/messages, resetting pool and retrying once: ${errMsg}`);
3514
+ resetAccountConnections(account.id);
3515
+ triedAccountIds.delete(account.id);
3516
+ continue;
3517
+ }
3518
+ consola.warn(`Account ${account.label}: network error after retry on /v1/messages (giving up): ${errMsg}`);
3519
+ throw error;
3520
+ }
3521
+ }
3522
+ if (lastError) throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Network request failed");
3523
+ throw new Error("No available accounts");
3524
+ }
3525
+ async function doFetchAnthropic(ctx) {
3526
+ const { payload, source, accountId, options: options$1 } = ctx;
3527
+ const url = `${copilotBaseUrl(source)}/v1/messages`;
3528
+ const bodyString = JSON.stringify(payload);
3529
+ consola.debug("Sending request to Copilot (multi-account, native Anthropic):", {
3530
+ model: payload.model,
3531
+ endpoint: url,
3532
+ stream: payload.stream
3533
+ });
3534
+ const response = await fetchWithTimeout(url, {
3535
+ method: "POST",
3536
+ headers: buildAnthropicHeaders(payload, source, options$1),
3537
+ body: bodyString,
3538
+ signal: options$1?.signal
3539
+ }, {
3540
+ accountId,
3541
+ accountProxy: source.proxy,
3542
+ externalSignal: options$1?.signal
3543
+ });
3544
+ if (!response.ok) await throwUpstreamError(response);
3545
+ if (payload.stream) return events(response);
3546
+ return await response.json();
3547
+ }
3548
+ async function throwUpstreamError(response) {
3549
+ const errorBody = await response.text();
3550
+ if (response.status === 400) consola.debug(`/v1/messages 400: ${errorBody}`);
3551
+ else consola.error("Failed native Anthropic request", {
3552
+ status: response.status,
3553
+ statusText: response.statusText,
3554
+ body: errorBody
3555
+ });
3556
+ throw new HTTPError(`Failed to call /v1/messages: ${response.status} ${errorBody}`, response);
3557
+ }
3558
+
3160
3559
  //#endregion
3161
3560
  //#region src/routes/messages/stream-translation.ts
3162
3561
  function isToolBlockOpen(state$1) {
@@ -3445,8 +3844,146 @@ async function handleCompletion(c) {
3445
3844
  messages_count: anthropicPayload.messages.length,
3446
3845
  max_tokens: anthropicPayload.max_tokens
3447
3846
  });
3448
- const openAIPayload = translateToOpenAI(stripSystemReminders(anthropicPayload));
3449
3847
  if (state.manualApprove) await awaitApproval();
3848
+ const route = resolveAnthropicRoute(anthropicPayload.model);
3849
+ consola.debug(`Anthropic route resolved: ${route}`);
3850
+ if (route === "native-anthropic") return handleNativePassthrough(c, anthropicPayload);
3851
+ return handleTranslatedCompletion(c, anthropicPayload);
3852
+ }
3853
+ async function handleNativePassthrough(c, anthropicPayload) {
3854
+ const anthropicBeta = c.req.header("anthropic-beta");
3855
+ let result;
3856
+ try {
3857
+ result = await createAnthropicMessages(stripSystemReminders(anthropicPayload), { anthropicBeta });
3858
+ } catch (error) {
3859
+ consola.warn(`Native /v1/messages failed: ${error.message || String(error)}`);
3860
+ throw error;
3861
+ }
3862
+ if (!anthropicPayload.stream) return c.json(overrideAnthropicResponseModel(result, anthropicPayload.model));
3863
+ const stream = result;
3864
+ const accountInfo = stream.__accountInfo;
3865
+ const proxied = accountInfo ? isAccountProxied(accountInfo.accountProxy) : isProxyActive();
3866
+ const heartbeatMs = proxied ? HEARTBEAT_PROXIED_MS : HEARTBEAT_DIRECT_MS;
3867
+ const upstreamTimeoutMs = proxied ? UPSTREAM_TIMEOUT_PROXIED_MS : UPSTREAM_TIMEOUT_DIRECT_MS;
3868
+ consola.debug(`Native SSE config: proxied=${proxied}, heartbeat=${heartbeatMs / 1e3}s, timeout=${upstreamTimeoutMs / 1e3}s`);
3869
+ return streamSSE(c, async (sse) => {
3870
+ const abortController = new AbortController();
3871
+ sse.onAbort(() => abortController.abort());
3872
+ try {
3873
+ await consumeNativeStreamWithHeartbeat(stream, sse, {
3874
+ heartbeatMs,
3875
+ upstreamTimeoutMs,
3876
+ abortSignal: abortController.signal,
3877
+ requestedModel: anthropicPayload.model
3878
+ });
3879
+ } catch (error) {
3880
+ if (!abortController.signal.aborted) {
3881
+ const message = error.message || String(error);
3882
+ consola.warn(`Native SSE stream interrupted: ${message}`);
3883
+ resetConnections();
3884
+ await sendErrorEvent(sse);
3885
+ }
3886
+ }
3887
+ });
3888
+ }
3889
+ /** Resolve the Anthropic event type for a raw SSE frame. */
3890
+ function resolveAnthropicEventType(rawEvent) {
3891
+ if (rawEvent.event) return rawEvent.event;
3892
+ if (!rawEvent.data) return void 0;
3893
+ try {
3894
+ return JSON.parse(rawEvent.data).type;
3895
+ } catch {
3896
+ return;
3897
+ }
3898
+ }
3899
+ async function handleNativeHeartbeatTick(stream, silenceMs, upstreamTimeoutMs) {
3900
+ if (silenceMs >= upstreamTimeoutMs) {
3901
+ consola.warn(`Upstream silent for ${Math.round(silenceMs / 1e3)}s (limit ${upstreamTimeoutMs / 1e3}s), closing native stream`);
3902
+ resetConnections();
3903
+ await sendErrorEvent(stream);
3904
+ return { action: "break" };
3905
+ }
3906
+ await stream.writeSSE({
3907
+ event: "ping",
3908
+ data: "{\"type\":\"ping\"}"
3909
+ });
3910
+ return { action: "continue" };
3911
+ }
3912
+ /** Forward one native event; returns true if it was `message_stop`. */
3913
+ async function forwardNativeEvent(stream, rawEvent, requestedModel) {
3914
+ if (rawEvent.data === "[DONE]") return {
3915
+ forwarded: false,
3916
+ isMessageStop: false
3917
+ };
3918
+ if (!rawEvent.data) return {
3919
+ forwarded: true,
3920
+ isMessageStop: false
3921
+ };
3922
+ const eventType = resolveAnthropicEventType(rawEvent);
3923
+ if (!eventType) {
3924
+ consola.debug("Skipping native SSE chunk with no resolvable type");
3925
+ return {
3926
+ forwarded: true,
3927
+ isMessageStop: false
3928
+ };
3929
+ }
3930
+ const dataToSend = eventType === "message_start" && requestedModel ? overrideMessageStartEventModel(rawEvent.data, requestedModel) : rawEvent.data;
3931
+ await stream.writeSSE({
3932
+ event: eventType,
3933
+ data: dataToSend
3934
+ });
3935
+ return {
3936
+ forwarded: true,
3937
+ isMessageStop: eventType === "message_stop"
3938
+ };
3939
+ }
3940
+ /**
3941
+ * Consume an upstream Anthropic SSE generator and forward events untouched.
3942
+ * Heartbeat and upstream-timeout logic mirrors the translated path.
3943
+ */
3944
+ async function consumeNativeStreamWithHeartbeat(response, stream, opts) {
3945
+ const { heartbeatMs, upstreamTimeoutMs, abortSignal, requestedModel } = opts;
3946
+ const iter = response[Symbol.asyncIterator]();
3947
+ let pendingNext = iter.next();
3948
+ let lastDataAt = Date.now();
3949
+ let sawMessageStop = false;
3950
+ try {
3951
+ while (true) {
3952
+ if (abortSignal?.aborted) {
3953
+ consola.debug("Client disconnected, stopping native SSE consumption");
3954
+ break;
3955
+ }
3956
+ const raceResult = await Promise.race([pendingNext.then((r) => ({
3957
+ kind: "data",
3958
+ result: r
3959
+ })), heartbeatDelay(heartbeatMs)]);
3960
+ if (raceResult === HEARTBEAT) {
3961
+ if ((await handleNativeHeartbeatTick(stream, Date.now() - lastDataAt, upstreamTimeoutMs)).action === "break") break;
3962
+ continue;
3963
+ }
3964
+ const { result: iterResult } = raceResult;
3965
+ if (iterResult.done) break;
3966
+ lastDataAt = Date.now();
3967
+ pendingNext = iter.next();
3968
+ const rawEvent = iterResult.value;
3969
+ const result = await forwardNativeEvent(stream, rawEvent, requestedModel);
3970
+ if (!result.forwarded) break;
3971
+ if (result.isMessageStop) sawMessageStop = true;
3972
+ }
3973
+ if (!sawMessageStop && !abortSignal?.aborted) try {
3974
+ await stream.writeSSE({
3975
+ event: "message_stop",
3976
+ data: "{\"type\":\"message_stop\"}"
3977
+ });
3978
+ } catch {}
3979
+ } finally {
3980
+ try {
3981
+ await iter.return(void 0);
3982
+ } catch {}
3983
+ }
3984
+ }
3985
+ async function handleTranslatedCompletion(c, anthropicPayload) {
3986
+ const openAIPayload = translateToOpenAI(stripSystemReminders(anthropicPayload));
3450
3987
  const response = await createChatCompletions(openAIPayload);
3451
3988
  if (isNonStreaming(response)) return c.json(translateToAnthropic(response));
3452
3989
  const accountInfo = response.__accountInfo;
@@ -3680,7 +4217,7 @@ async function validateGitHubToken(token) {
3680
4217
  state.githubToken = token;
3681
4218
  consola.info("Using provided GitHub token");
3682
4219
  try {
3683
- const { getGitHubUser } = await import("./get-user-DiK-7IaC.js");
4220
+ const { getGitHubUser } = await import("./get-user-Brre_x1N.js");
3684
4221
  const user = await getGitHubUser();
3685
4222
  consola.info(`Logged in as ${user.login}`);
3686
4223
  } catch (error) {
@@ -3719,6 +4256,8 @@ async function runServer(options$1) {
3719
4256
  state.rateLimitWait = options$1.rateLimitWait;
3720
4257
  state.showToken = options$1.showToken;
3721
4258
  state.apiKeys = options$1.apiKeys;
4259
+ state.disableAnthropicPassthrough = options$1.disableAnthropicPassthrough;
4260
+ if (options$1.disableAnthropicPassthrough) consola.info("Native Anthropic passthrough DISABLED — all /v1/messages requests will translate via /chat/completions");
3722
4261
  if (state.apiKeys && state.apiKeys.length > 0) consola.info(`API key authentication enabled with ${state.apiKeys.length} key(s)`);
3723
4262
  await ensurePaths();
3724
4263
  await cacheVSCodeVersion();
@@ -3731,10 +4270,10 @@ async function runServer(options$1) {
3731
4270
  try {
3732
4271
  await setupCopilotToken();
3733
4272
  } catch (error) {
3734
- const { HTTPError: HTTPError$1 } = await import("./error-HsNBNEyW.js");
4273
+ const { HTTPError: HTTPError$1 } = await import("./error-DO0tIuq0.js");
3735
4274
  if (error instanceof HTTPError$1 && error.response.status === 401) {
3736
4275
  consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
3737
- const { clearGithubToken: clearGithubToken$1 } = await import("./token-C8PZ2fm8.js");
4276
+ const { clearGithubToken: clearGithubToken$1 } = await import("./token-Dv2Iyk1S.js");
3738
4277
  await clearGithubToken$1();
3739
4278
  consola.info("Please restart to re-authenticate");
3740
4279
  }
@@ -3817,6 +4356,11 @@ const start = defineCommand({
3817
4356
  "api-key": {
3818
4357
  type: "string",
3819
4358
  description: "API keys for authentication"
4359
+ },
4360
+ "disable-anthropic-passthrough": {
4361
+ type: "boolean",
4362
+ default: false,
4363
+ description: "Force translate all /v1/messages requests via /chat/completions (disable native Copilot Anthropic endpoint)"
3820
4364
  }
3821
4365
  },
3822
4366
  run({ args }) {
@@ -3836,7 +4380,8 @@ const start = defineCommand({
3836
4380
  claudeCode: args["claude-code"],
3837
4381
  showToken: args["show-token"],
3838
4382
  proxyEnv: args["proxy-env"],
3839
- apiKeys
4383
+ apiKeys,
4384
+ disableAnthropicPassthrough: args["disable-anthropic-passthrough"]
3840
4385
  });
3841
4386
  }
3842
4387
  });