@dianshuv/copilot-api 0.7.10 → 0.8.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.
Files changed (2) hide show
  1. package/dist/main.mjs +208 -32
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -243,7 +243,7 @@ function compressToolResultContent(content) {
243
243
  return `${start}\n\n[... ${(content.length - COMPRESSED_SUMMARY_LENGTH).toLocaleString()} characters omitted for brevity ...]\n\n${end}`;
244
244
  }
245
245
  function calculateLimits(model, config, defaultContextWindow) {
246
- const rawTokenLimit = getEffectiveTokenLimit(model.id) ?? model.capabilities?.limits?.max_context_window_tokens ?? model.capabilities?.limits?.max_prompt_tokens ?? defaultContextWindow;
246
+ const rawTokenLimit = getEffectiveTokenLimit(config.tokenLimitCacheKeyOverride ?? model.id) ?? config.contextWindowOverride ?? model.capabilities?.limits?.max_context_window_tokens ?? model.capabilities?.limits?.max_prompt_tokens ?? defaultContextWindow;
247
247
  return {
248
248
  tokenLimit: Math.floor(rawTokenLimit * (1 - config.safetyMarginPercent / 100)),
249
249
  byteLimit: getEffectiveByteLimitBytes()
@@ -1348,7 +1348,7 @@ const patchClaude = defineCommand({
1348
1348
 
1349
1349
  //#endregion
1350
1350
  //#region package.json
1351
- var version = "0.7.10";
1351
+ var version = "0.8.0";
1352
1352
 
1353
1353
  //#endregion
1354
1354
  //#region src/lib/adaptive-rate-limiter.ts
@@ -3395,9 +3395,66 @@ const getTokenCount = async (payload, model) => {
3395
3395
  };
3396
3396
  };
3397
3397
 
3398
+ //#endregion
3399
+ //#region src/lib/anthropic/beta.ts
3400
+ /**
3401
+ * Vendor-neutral utilities for manipulating the `anthropic-beta` request header.
3402
+ *
3403
+ * Lives in `lib/anthropic/` (not in either transport module) so both the
3404
+ * Anthropic-native and OpenAI-translated transport layers can share these
3405
+ * helpers without introducing cross-transport imports.
3406
+ */
3407
+ /** Anthropic beta feature that unlocks the 1M context window. */
3408
+ const CONTEXT_1M_BETA_FEATURE = "context-1m-2025-08-07";
3409
+ /**
3410
+ * Merge two comma-separated anthropic-beta header values. Trims whitespace,
3411
+ * drops empty tokens, and dedupes by exact string match. Returns a canonical
3412
+ * comma-joined string with no spaces.
3413
+ *
3414
+ * Either input may be undefined / empty.
3415
+ */
3416
+ function mergeBetaFeatures(existing, incoming) {
3417
+ const seen = /* @__PURE__ */ new Set();
3418
+ const out = [];
3419
+ for (const raw of [existing, incoming]) {
3420
+ if (!raw) continue;
3421
+ for (const part of raw.split(",")) {
3422
+ const f = part.trim();
3423
+ if (f.length === 0 || seen.has(f)) continue;
3424
+ seen.add(f);
3425
+ out.push(f);
3426
+ }
3427
+ }
3428
+ return out.join(",");
3429
+ }
3430
+ /**
3431
+ * Append the context-1m feature to an anthropic-beta header value, deduping
3432
+ * any prior occurrence. Returns the merged comma-separated string.
3433
+ */
3434
+ function appendContext1mBeta(existing) {
3435
+ return mergeBetaFeatures(existing, CONTEXT_1M_BETA_FEATURE);
3436
+ }
3437
+ /**
3438
+ * True iff a model id appears to be the suffixed 1M-context variant of an
3439
+ * Anthropic Claude model (e.g. claude-opus-4-8-1m, claude-opus-4.6-1m).
3440
+ *
3441
+ * Used as a state.models-independent signal for whether to inject the
3442
+ * context-1m-2025-08-07 beta header, so the 1M intent survives a stale or
3443
+ * empty model cache (where `resolveAnthropicModelForDirectPath` would return
3444
+ * undefined). Forwarding the beta is harmless to upstreams that ignore it.
3445
+ */
3446
+ function isOneMillionSuffixedClaudeId(modelId) {
3447
+ return modelId.startsWith("claude-") && modelId.endsWith("-1m");
3448
+ }
3449
+
3398
3450
  //#endregion
3399
3451
  //#region src/services/copilot/create-chat-completions.ts
3400
3452
  const GPT_MODEL_PATTERN = /^gpt-/i;
3453
+ /** Case-insensitive lookup of a header key in a plain-object header bag. */
3454
+ function findHeaderKey(headers, name) {
3455
+ const lower = name.toLowerCase();
3456
+ return Object.keys(headers).find((k) => k.toLowerCase() === lower);
3457
+ }
3401
3458
  const createChatCompletions = async (payload, options) => {
3402
3459
  if (!state.copilotToken) throw new Error("Copilot token not found");
3403
3460
  const vendor = options?.resolvedModel?.vendor;
@@ -3415,21 +3472,27 @@ const createChatCompletions = async (payload, options) => {
3415
3472
  const enableVision = wire.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
3416
3473
  const isAgentCall = wire.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
3417
3474
  const modelSupportsVision = options?.resolvedModel?.capabilities?.supports?.vision !== false;
3475
+ const headers = {
3476
+ ...copilotHeaders(state, {
3477
+ vision: enableVision && modelSupportsVision,
3478
+ modelRequestHeaders: options?.resolvedModel?.request_headers,
3479
+ intent: isAgentCall ? "conversation-agent" : "conversation-panel"
3480
+ }),
3481
+ "X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
3482
+ };
3483
+ if (options?.anthropicBeta) {
3484
+ const existingKey = findHeaderKey(headers, "anthropic-beta") ?? "anthropic-beta";
3485
+ headers[existingKey] = mergeBetaFeatures(headers[existingKey], options.anthropicBeta);
3486
+ consola.debug(`[ChatCompletions] anthropic-beta after merge: ${headers[existingKey]}`);
3487
+ }
3418
3488
  const response = await copilotFetch("/chat/completions", {
3419
3489
  method: "POST",
3420
- headers: {
3421
- ...copilotHeaders(state, {
3422
- vision: enableVision && modelSupportsVision,
3423
- modelRequestHeaders: options?.resolvedModel?.request_headers,
3424
- intent: isAgentCall ? "conversation-agent" : "conversation-panel"
3425
- }),
3426
- "X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
3427
- },
3490
+ headers,
3428
3491
  body: JSON.stringify(wire)
3429
3492
  });
3430
3493
  if (!response.ok) {
3431
3494
  consola.error("Failed to create chat completions", response);
3432
- throw await HTTPError.fromResponse("Failed to create chat completions", response, payload.model);
3495
+ throw await HTTPError.fromResponse("Failed to create chat completions", response, options?.errorModelIdOverride ?? payload.model);
3433
3496
  }
3434
3497
  if (payload.stream) return events(response);
3435
3498
  return await response.json();
@@ -3964,7 +4027,7 @@ function isNonStreaming(response) {
3964
4027
  return Object.hasOwn(response, "choices");
3965
4028
  }
3966
4029
  /** Build final payload with auto-truncate if needed */
3967
- async function buildFinalPayload(payload, model) {
4030
+ async function buildFinalPayload(payload, model, autoTruncateConfig = {}) {
3968
4031
  if (!state.autoTruncate || !model) {
3969
4032
  if (state.autoTruncate && !model) consola.warn(`Auto-truncate: Model '${payload.model}' not found in cached models, skipping`);
3970
4033
  return {
@@ -3973,7 +4036,7 @@ async function buildFinalPayload(payload, model) {
3973
4036
  };
3974
4037
  }
3975
4038
  try {
3976
- const check = await checkNeedsCompactionOpenAI(payload, model);
4039
+ const check = await checkNeedsCompactionOpenAI(payload, model, autoTruncateConfig);
3977
4040
  consola.debug(`Auto-truncate check: ${check.currentTokens} tokens (limit ${check.tokenLimit}), ${Math.round(check.currentBytes / 1024)}KB (limit ${check.byteLimit === Infinity ? "unlimited" : `${Math.round(check.byteLimit / 1024)}KB`}), needed: ${check.needed}${check.reason ? ` (${check.reason})` : ""}`);
3978
4041
  if (!check.needed) return {
3979
4042
  finalPayload: payload,
@@ -3984,7 +4047,7 @@ async function buildFinalPayload(payload, model) {
3984
4047
  else if (check.reason === "bytes") reasonText = "size";
3985
4048
  else reasonText = "tokens";
3986
4049
  consola.info(`Auto-truncate triggered: exceeds ${reasonText} limit`);
3987
- const truncateResult = await autoTruncateOpenAI(payload, model);
4050
+ const truncateResult = await autoTruncateOpenAI(payload, model, autoTruncateConfig);
3988
4051
  return {
3989
4052
  finalPayload: truncateResult.payload,
3990
4053
  truncateResult
@@ -6839,7 +6902,7 @@ function modelSupportsContextEditing(modelId) {
6839
6902
  }
6840
6903
  function modelSupportsToolSearch(modelId) {
6841
6904
  const n = normalizeForMatching(modelId);
6842
- return n.includes("claude") && (n.includes("opus45") || n.includes("opus46") || n.includes("sonnet45") || n.includes("sonnet46"));
6905
+ return n.includes("claude") && (n.includes("opus45") || n.includes("opus46") || n.includes("opus47") || n.includes("opus48") || n.includes("sonnet45") || n.includes("sonnet46"));
6843
6906
  }
6844
6907
  function isContextEditingEnabled(modelId) {
6845
6908
  return modelSupportsContextEditing(modelId) && state.contextEditingMode !== "off";
@@ -7065,6 +7128,7 @@ async function createAnthropicMessages(payload, options) {
7065
7128
  };
7066
7129
  const betaHeaders = buildAnthropicBetaHeaders(filteredPayload.model, resolvedModel);
7067
7130
  Object.assign(headers, betaHeaders);
7131
+ if (options?.injectContext1mBeta) headers["anthropic-beta"] = appendContext1mBeta(headers["anthropic-beta"]);
7068
7132
  if (isContextEditingEnabled(filteredPayload.model)) {
7069
7133
  const hasThinking = filteredPayload.thinking?.type === "enabled";
7070
7134
  const cm = buildContextManagement(state.contextEditingMode, hasThinking);
@@ -7091,7 +7155,7 @@ async function createAnthropicMessages(payload, options) {
7091
7155
  thinking: filteredPayload.thinking,
7092
7156
  messageCount: filteredPayload.messages.length
7093
7157
  });
7094
- throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, filteredPayload.model);
7158
+ throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, options?.errorModelIdOverride ?? filteredPayload.model);
7095
7159
  }
7096
7160
  if (payload.stream) return events(response);
7097
7161
  return await response.json();
@@ -7114,13 +7178,58 @@ function stripServerToolsFromPayload(tools) {
7114
7178
  }
7115
7179
  return result.length > 0 ? result : void 0;
7116
7180
  }
7181
+ /** Context window unlocked by the context-1m-2025-08-07 beta header. */
7182
+ const ONE_MILLION_CONTEXT_WINDOW_TOKENS = 1e6;
7183
+ /**
7184
+ * Convert a Claude model id from the client-facing dash convention to the
7185
+ * upstream Copilot dot convention. The two conventions co-exist because
7186
+ * Anthropic-style clients use dashes ("claude-opus-4-8") and Copilot lists
7187
+ * the same model with dots ("claude-opus-4.8"). Only the first dash inside
7188
+ * the version segment is converted (a model id like "claude-opus-4.8-1m"
7189
+ * already in dot form is returned unchanged).
7190
+ */
7191
+ function dashToDotClaudeId(modelId) {
7192
+ if (!modelId.startsWith("claude-")) return modelId;
7193
+ return modelId.replace(/^(claude-[a-z]+-)(\d+)-(\d+)/, "$1$2.$3");
7194
+ }
7195
+ function resolveAnthropicModelForDirectPath(modelId) {
7196
+ const exact = findModelById(modelId);
7197
+ if (exact?.vendor === "Anthropic") return {
7198
+ model: exact,
7199
+ baseModelId: modelId,
7200
+ oneMillionFallback: false,
7201
+ effectiveContextWindowTokens: exact.capabilities?.limits?.max_context_window_tokens ?? 2e5
7202
+ };
7203
+ const dotted = dashToDotClaudeId(modelId);
7204
+ if (dotted !== modelId) {
7205
+ const dottedExact = findModelById(dotted);
7206
+ if (dottedExact?.vendor === "Anthropic") return {
7207
+ model: dottedExact,
7208
+ baseModelId: dotted,
7209
+ oneMillionFallback: false,
7210
+ effectiveContextWindowTokens: dottedExact.capabilities?.limits?.max_context_window_tokens ?? 2e5
7211
+ };
7212
+ }
7213
+ if (modelId.endsWith("-1m")) {
7214
+ const baseId = modelId.slice(0, -3);
7215
+ for (const candidateId of [baseId, dashToDotClaudeId(baseId)]) {
7216
+ const base = findModelById(candidateId);
7217
+ if (base?.vendor === "Anthropic") return {
7218
+ model: base,
7219
+ baseModelId: candidateId,
7220
+ oneMillionFallback: true,
7221
+ effectiveContextWindowTokens: ONE_MILLION_CONTEXT_WINDOW_TOKENS
7222
+ };
7223
+ }
7224
+ }
7225
+ }
7117
7226
  /**
7118
7227
  * Check if a model supports direct Anthropic API.
7119
7228
  * Returns true if redirect is disabled (direct API is on) and the model is from Anthropic vendor.
7120
7229
  */
7121
7230
  function supportsDirectAnthropicApi(modelId) {
7122
7231
  if (state.redirectAnthropic) return false;
7123
- return findModelById(modelId)?.vendor === "Anthropic";
7232
+ return resolveAnthropicModelForDirectPath(modelId) !== void 0;
7124
7233
  }
7125
7234
 
7126
7235
  //#endregion
@@ -7359,18 +7468,42 @@ function findLatestModel(familyPrefix, fallback) {
7359
7468
  const candidates = models.filter((m) => m.id.startsWith(familyPrefix));
7360
7469
  if (candidates.length === 0) return fallback;
7361
7470
  candidates.sort((a, b) => {
7362
- const versionA = extractVersion(a.id, familyPrefix);
7363
- return extractVersion(b.id, familyPrefix) - versionA;
7471
+ const [aMajor, aMinor] = extractVersion(a.id, familyPrefix);
7472
+ const [bMajor, bMinor] = extractVersion(b.id, familyPrefix);
7473
+ if (aMajor !== bMajor) return bMajor - aMajor;
7474
+ return bMinor - aMinor;
7364
7475
  });
7365
7476
  return candidates[0].id;
7366
7477
  }
7367
7478
  /**
7368
- * Extract numeric version from model ID.
7369
- * e.g., "claude-opus-4.5" with prefix "claude-opus" -> 4.5
7479
+ * Extract numeric [major, minor] version from a model id.
7480
+ *
7481
+ * Supports both naming conventions Anthropic/Copilot have used:
7482
+ * - dot: "claude-opus-4.5" → [4, 5]
7483
+ * - dash: "claude-opus-4-8" → [4, 8]
7484
+ * - dash double-digit: "claude-opus-4-10" → [4, 10]
7485
+ *
7486
+ * The dash form previously parsed as just the major via the regex
7487
+ * /^(\d+(?:\.\d+)?)/ (the dash stopped the match), which silently
7488
+ * downgraded dash-named candidates against any dot-named candidate in
7489
+ * findLatestModel. Parsing into a tuple also avoids the parseFloat
7490
+ * lossiness on double-digit minors ("4.10" → 4.1).
7491
+ *
7492
+ * Anything after the major/minor segment (date stamps, "-1m") is ignored.
7493
+ * The minor capture is bounded to 1-3 digits so that an 8-digit date suffix
7494
+ * directly after the major (e.g. "claude-opus-4-20250514") is NOT mistaken
7495
+ * for a minor version of 20_250_514 — without that bound, dated ids would
7496
+ * outrank legitimate dotted candidates like "claude-opus-4.8" in
7497
+ * findLatestModel's sort.
7498
+ *
7499
+ * Returns [0, 0] when no version can be extracted.
7370
7500
  */
7371
7501
  function extractVersion(modelId, prefix) {
7372
- const match = modelId.slice(prefix.length + 1).match(/^(\d+(?:\.\d+)?)/);
7373
- return match ? Number.parseFloat(match[1]) : 0;
7502
+ const match = modelId.slice(prefix.length + 1).match(/^(\d+)(?:[.-](\d{1,3}))?/);
7503
+ if (!match) return [0, 0];
7504
+ const major = Number.parseInt(match[1], 10);
7505
+ const rawMinor = match[2];
7506
+ return [major, rawMinor === void 0 ? 0 : Number.parseInt(rawMinor, 10) || 0];
7374
7507
  }
7375
7508
  function translateModelName(model) {
7376
7509
  const aliasMap = {
@@ -7384,6 +7517,8 @@ function translateModelName(model) {
7384
7517
  }
7385
7518
  if (/^claude-sonnet-4-5-\d+$/.test(model)) return "claude-sonnet-4.5";
7386
7519
  if (/^claude-sonnet-4-\d+$/.test(model)) return "claude-sonnet-4";
7520
+ if (model === "claude-opus-4-8-1m") return "claude-opus-4.8";
7521
+ if (model === "claude-opus-4-8") return "claude-opus-4.8";
7387
7522
  if (model === "claude-opus-4-7-1m") return "claude-opus-4.7-1m-internal";
7388
7523
  if (/^claude-opus-4-7$/.test(model)) return "claude-opus-4.7";
7389
7524
  if (model === "claude-opus-4-6-1m") return "claude-opus-4.6-1m";
@@ -7763,14 +7898,30 @@ function translateErrorToAnthropicErrorEvent(error) {
7763
7898
  */
7764
7899
  async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiatorOverride) {
7765
7900
  consola.debug("Using direct Anthropic API path for model:", anthropicPayload.model);
7766
- const selectedModel = findModelById(anthropicPayload.model);
7767
- let effectivePayload = anthropicPayload;
7901
+ const resolution = resolveAnthropicModelForDirectPath(anthropicPayload.model);
7902
+ const selectedModel = resolution?.model;
7903
+ const baseModelId = resolution?.baseModelId ?? anthropicPayload.model;
7904
+ const needsContext1mBeta = resolution?.oneMillionFallback ?? false;
7905
+ const originalModelId = anthropicPayload.model;
7906
+ const resolvedPayload = resolution && baseModelId !== originalModelId ? {
7907
+ ...anthropicPayload,
7908
+ model: baseModelId
7909
+ } : anthropicPayload;
7910
+ if (baseModelId !== originalModelId) {
7911
+ consola.debug(`[Anthropic] Mapping model for upstream: ${originalModelId} → ${baseModelId}${needsContext1mBeta ? " (+context-1m beta)" : ""}`);
7912
+ updateTrackerResolvedModel(ctx.trackingId, baseModelId);
7913
+ }
7914
+ const autoTruncateConfig = resolution && resolution.oneMillionFallback ? {
7915
+ contextWindowOverride: resolution.effectiveContextWindowTokens,
7916
+ tokenLimitCacheKeyOverride: originalModelId
7917
+ } : {};
7918
+ let effectivePayload = resolvedPayload;
7768
7919
  let truncateResult;
7769
7920
  if (state.autoTruncate && selectedModel) {
7770
- const check = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
7921
+ const check = await checkNeedsCompactionAnthropic(resolvedPayload, selectedModel, autoTruncateConfig);
7771
7922
  consola.debug(`[Anthropic] Auto-truncate check: ${check.currentTokens} tokens (limit ${check.tokenLimit}), ${Math.round(check.currentBytes / 1024)}KB (limit ${check.byteLimit === Infinity ? "unlimited" : `${Math.round(check.byteLimit / 1024)}KB`}), needed: ${check.needed}${check.reason ? ` (${check.reason})` : ""}`);
7772
7923
  if (check.needed) try {
7773
- truncateResult = await autoTruncateAnthropic(anthropicPayload, selectedModel);
7924
+ truncateResult = await autoTruncateAnthropic(resolvedPayload, selectedModel, autoTruncateConfig);
7774
7925
  if (truncateResult.wasCompacted) effectivePayload = truncateResult.payload;
7775
7926
  } catch (error) {
7776
7927
  consola.warn("[Anthropic] Auto-truncate failed, proceeding with original payload:", error instanceof Error ? error.message : error);
@@ -7778,7 +7929,11 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
7778
7929
  } else if (state.autoTruncate && !selectedModel) consola.debug(`[Anthropic] Model '${anthropicPayload.model}' not found, skipping auto-truncate`);
7779
7930
  if (state.manualApprove) await awaitApproval();
7780
7931
  try {
7781
- const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, { initiator: initiatorOverride }));
7932
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
7933
+ initiator: initiatorOverride,
7934
+ injectContext1mBeta: needsContext1mBeta,
7935
+ errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0
7936
+ }));
7782
7937
  ctx.queueWaitMs = queueWaitMs;
7783
7938
  if (Symbol.asyncIterator in response) {
7784
7939
  consola.debug("Streaming response from Copilot (direct Anthropic)");
@@ -7976,17 +8131,33 @@ const parseSubagentMarkerFromSystemReminder = (text) => {
7976
8131
  * Handle completion using OpenAI translation path (legacy)
7977
8132
  */
7978
8133
  async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOverride) {
8134
+ const originalModelId = anthropicPayload.model;
8135
+ const hasOneMillionSuffix = isOneMillionSuffixedClaudeId(originalModelId);
8136
+ const oneMResolution = resolveAnthropicModelForDirectPath(originalModelId);
8137
+ const needsContext1mBeta = hasOneMillionSuffix || (oneMResolution?.oneMillionFallback ?? false);
7979
8138
  const { payload: translatedPayload, toolNameMapping } = translateToOpenAI(anthropicPayload);
7980
8139
  consola.debug("Translated OpenAI request payload:", JSON.stringify(translatedPayload));
7981
8140
  updateTrackerResolvedModel(ctx.trackingId, translatedPayload.model);
7982
8141
  const selectedModel = findModelById(translatedPayload.model);
7983
- const { finalPayload: openAIPayload, truncateResult } = await buildFinalPayload(translatedPayload, selectedModel);
8142
+ const autoTruncateConfig = oneMResolution?.oneMillionFallback ? {
8143
+ contextWindowOverride: oneMResolution.effectiveContextWindowTokens,
8144
+ tokenLimitCacheKeyOverride: originalModelId
8145
+ } : {};
8146
+ const { finalPayload: openAIPayload, truncateResult } = await buildFinalPayload(translatedPayload, selectedModel, autoTruncateConfig);
7984
8147
  if (truncateResult) ctx.truncateResult = truncateResult;
8148
+ let anthropicBeta = c.req.header("anthropic-beta");
8149
+ if (needsContext1mBeta) anthropicBeta = appendContext1mBeta(anthropicBeta);
7985
8150
  if (state.manualApprove) await awaitApproval();
8151
+ let errorModelIdOverride;
8152
+ if (autoTruncateConfig.tokenLimitCacheKeyOverride !== void 0) errorModelIdOverride = autoTruncateConfig.tokenLimitCacheKeyOverride;
8153
+ else if (needsContext1mBeta && selectedModel) errorModelIdOverride = selectedModel.id;
8154
+ else if (hasOneMillionSuffix) errorModelIdOverride = originalModelId;
7986
8155
  try {
7987
8156
  const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, {
7988
8157
  initiator: initiatorOverride,
7989
- resolvedModel: selectedModel
8158
+ resolvedModel: selectedModel,
8159
+ anthropicBeta,
8160
+ errorModelIdOverride
7990
8161
  }));
7991
8162
  ctx.queueWaitMs = queueWaitMs;
7992
8163
  if (isNonStreaming(response)) return handleNonStreamingResponse({
@@ -8254,10 +8425,15 @@ async function handleCountTokens(c) {
8254
8425
  consola.warn("Model not found, returning default token count");
8255
8426
  return c.json({ input_tokens: 1 });
8256
8427
  }
8428
+ const directResolution = resolveAnthropicModelForDirectPath(anthropicPayload.model);
8429
+ const autoTruncateConfig = directResolution?.oneMillionFallback ? {
8430
+ contextWindowOverride: directResolution.effectiveContextWindowTokens,
8431
+ tokenLimitCacheKeyOverride: anthropicPayload.model
8432
+ } : {};
8257
8433
  if (state.autoTruncate) {
8258
- const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
8434
+ const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel, autoTruncateConfig);
8259
8435
  if (truncateCheck.needed) {
8260
- const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
8436
+ const contextWindow = autoTruncateConfig.contextWindowOverride ?? selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
8261
8437
  const inflatedTokens = Math.floor(contextWindow * .95);
8262
8438
  consola.debug(`[count_tokens] Would trigger auto-truncate: ${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit}, returning inflated count: ${inflatedTokens}`);
8263
8439
  return c.json({ input_tokens: inflatedTokens });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.7.10",
3
+ "version": "0.8.0",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",