@dianshuv/copilot-api 0.7.10 → 0.8.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/main.mjs +257 -36
- 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.
|
|
1351
|
+
var version = "0.8.1";
|
|
1352
1352
|
|
|
1353
1353
|
//#endregion
|
|
1354
1354
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -3395,6 +3395,89 @@ 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
|
+
|
|
3450
|
+
//#endregion
|
|
3451
|
+
//#region src/lib/headers.ts
|
|
3452
|
+
/**
|
|
3453
|
+
* Vendor-neutral header-bag helpers.
|
|
3454
|
+
*
|
|
3455
|
+
* HTTP header names are case-insensitive, but a plain-object header bag is
|
|
3456
|
+
* case-sensitive on its keys. Code that wants to look up "anthropic-beta"
|
|
3457
|
+
* without knowing whether some other producer wrote "Anthropic-Beta" needs
|
|
3458
|
+
* `findHeaderKey`. Code that wants to set a header without creating a
|
|
3459
|
+
* second case variant of the same name needs `setHeader`.
|
|
3460
|
+
*/
|
|
3461
|
+
/** Case-insensitive lookup of a header key in a plain-object header bag. */
|
|
3462
|
+
function findHeaderKey(headers, name) {
|
|
3463
|
+
const lower = name.toLowerCase();
|
|
3464
|
+
return Object.keys(headers).find((k) => k.toLowerCase() === lower);
|
|
3465
|
+
}
|
|
3466
|
+
/** Case-insensitive read of a header value. */
|
|
3467
|
+
function getHeader(headers, name) {
|
|
3468
|
+
const key = findHeaderKey(headers, name);
|
|
3469
|
+
return key === void 0 ? void 0 : headers[key];
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Set a header value at the existing case variant if one is present, else at
|
|
3473
|
+
* the supplied canonical name. Prevents a second key (different case) from
|
|
3474
|
+
* being added for the same logical header.
|
|
3475
|
+
*/
|
|
3476
|
+
function setHeader(headers, name, value) {
|
|
3477
|
+
const key = findHeaderKey(headers, name) ?? name;
|
|
3478
|
+
headers[key] = value;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3398
3481
|
//#endregion
|
|
3399
3482
|
//#region src/services/copilot/create-chat-completions.ts
|
|
3400
3483
|
const GPT_MODEL_PATTERN = /^gpt-/i;
|
|
@@ -3415,21 +3498,27 @@ const createChatCompletions = async (payload, options) => {
|
|
|
3415
3498
|
const enableVision = wire.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
|
|
3416
3499
|
const isAgentCall = wire.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
3417
3500
|
const modelSupportsVision = options?.resolvedModel?.capabilities?.supports?.vision !== false;
|
|
3501
|
+
const headers = {
|
|
3502
|
+
...copilotHeaders(state, {
|
|
3503
|
+
vision: enableVision && modelSupportsVision,
|
|
3504
|
+
modelRequestHeaders: options?.resolvedModel?.request_headers,
|
|
3505
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
3506
|
+
}),
|
|
3507
|
+
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
|
|
3508
|
+
};
|
|
3509
|
+
if (options?.anthropicBeta) {
|
|
3510
|
+
const existingKey = findHeaderKey(headers, "anthropic-beta") ?? "anthropic-beta";
|
|
3511
|
+
headers[existingKey] = mergeBetaFeatures(headers[existingKey], options.anthropicBeta);
|
|
3512
|
+
consola.debug(`[ChatCompletions] anthropic-beta after merge: ${headers[existingKey]}`);
|
|
3513
|
+
}
|
|
3418
3514
|
const response = await copilotFetch("/chat/completions", {
|
|
3419
3515
|
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
|
-
},
|
|
3516
|
+
headers,
|
|
3428
3517
|
body: JSON.stringify(wire)
|
|
3429
3518
|
});
|
|
3430
3519
|
if (!response.ok) {
|
|
3431
3520
|
consola.error("Failed to create chat completions", response);
|
|
3432
|
-
throw await HTTPError.fromResponse("Failed to create chat completions", response, payload.model);
|
|
3521
|
+
throw await HTTPError.fromResponse("Failed to create chat completions", response, options?.errorModelIdOverride ?? payload.model);
|
|
3433
3522
|
}
|
|
3434
3523
|
if (payload.stream) return events(response);
|
|
3435
3524
|
return await response.json();
|
|
@@ -3964,7 +4053,7 @@ function isNonStreaming(response) {
|
|
|
3964
4053
|
return Object.hasOwn(response, "choices");
|
|
3965
4054
|
}
|
|
3966
4055
|
/** Build final payload with auto-truncate if needed */
|
|
3967
|
-
async function buildFinalPayload(payload, model) {
|
|
4056
|
+
async function buildFinalPayload(payload, model, autoTruncateConfig = {}) {
|
|
3968
4057
|
if (!state.autoTruncate || !model) {
|
|
3969
4058
|
if (state.autoTruncate && !model) consola.warn(`Auto-truncate: Model '${payload.model}' not found in cached models, skipping`);
|
|
3970
4059
|
return {
|
|
@@ -3973,7 +4062,7 @@ async function buildFinalPayload(payload, model) {
|
|
|
3973
4062
|
};
|
|
3974
4063
|
}
|
|
3975
4064
|
try {
|
|
3976
|
-
const check = await checkNeedsCompactionOpenAI(payload, model);
|
|
4065
|
+
const check = await checkNeedsCompactionOpenAI(payload, model, autoTruncateConfig);
|
|
3977
4066
|
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
4067
|
if (!check.needed) return {
|
|
3979
4068
|
finalPayload: payload,
|
|
@@ -3984,7 +4073,7 @@ async function buildFinalPayload(payload, model) {
|
|
|
3984
4073
|
else if (check.reason === "bytes") reasonText = "size";
|
|
3985
4074
|
else reasonText = "tokens";
|
|
3986
4075
|
consola.info(`Auto-truncate triggered: exceeds ${reasonText} limit`);
|
|
3987
|
-
const truncateResult = await autoTruncateOpenAI(payload, model);
|
|
4076
|
+
const truncateResult = await autoTruncateOpenAI(payload, model, autoTruncateConfig);
|
|
3988
4077
|
return {
|
|
3989
4078
|
finalPayload: truncateResult.payload,
|
|
3990
4079
|
truncateResult
|
|
@@ -6839,7 +6928,7 @@ function modelSupportsContextEditing(modelId) {
|
|
|
6839
6928
|
}
|
|
6840
6929
|
function modelSupportsToolSearch(modelId) {
|
|
6841
6930
|
const n = normalizeForMatching(modelId);
|
|
6842
|
-
return n.includes("claude") && (n.includes("opus45") || n.includes("opus46") || n.includes("sonnet45") || n.includes("sonnet46"));
|
|
6931
|
+
return n.includes("claude") && (n.includes("opus45") || n.includes("opus46") || n.includes("opus47") || n.includes("opus48") || n.includes("sonnet45") || n.includes("sonnet46"));
|
|
6843
6932
|
}
|
|
6844
6933
|
function isContextEditingEnabled(modelId) {
|
|
6845
6934
|
return modelSupportsContextEditing(modelId) && state.contextEditingMode !== "off";
|
|
@@ -7058,14 +7147,18 @@ async function createAnthropicMessages(payload, options) {
|
|
|
7058
7147
|
const headers = {
|
|
7059
7148
|
...copilotHeaders(state, {
|
|
7060
7149
|
vision: enableVision,
|
|
7061
|
-
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
7150
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel",
|
|
7151
|
+
modelRequestHeaders: resolvedModel?.request_headers
|
|
7062
7152
|
}),
|
|
7063
7153
|
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
7064
7154
|
"anthropic-version": "2023-06-01"
|
|
7065
7155
|
};
|
|
7066
|
-
const
|
|
7067
|
-
|
|
7068
|
-
if (
|
|
7156
|
+
const proxyBeta = buildAnthropicBetaHeaders(filteredPayload.model, resolvedModel)["anthropic-beta"];
|
|
7157
|
+
let mergedBeta = mergeBetaFeatures(getHeader(headers, "anthropic-beta"), proxyBeta);
|
|
7158
|
+
if (options?.injectContext1mBeta) mergedBeta = appendContext1mBeta(mergedBeta);
|
|
7159
|
+
if (options?.clientAnthropicBetaHeader) mergedBeta = mergeBetaFeatures(mergedBeta, options.clientAnthropicBetaHeader);
|
|
7160
|
+
if (mergedBeta.length > 0) setHeader(headers, "anthropic-beta", mergedBeta);
|
|
7161
|
+
if (!("context_management" in filteredPayload) && isContextEditingEnabled(filteredPayload.model)) {
|
|
7069
7162
|
const hasThinking = filteredPayload.thinking?.type === "enabled";
|
|
7070
7163
|
const cm = buildContextManagement(state.contextEditingMode, hasThinking);
|
|
7071
7164
|
if (cm) {
|
|
@@ -7091,7 +7184,7 @@ async function createAnthropicMessages(payload, options) {
|
|
|
7091
7184
|
thinking: filteredPayload.thinking,
|
|
7092
7185
|
messageCount: filteredPayload.messages.length
|
|
7093
7186
|
});
|
|
7094
|
-
throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, filteredPayload.model);
|
|
7187
|
+
throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, options?.errorModelIdOverride ?? filteredPayload.model);
|
|
7095
7188
|
}
|
|
7096
7189
|
if (payload.stream) return events(response);
|
|
7097
7190
|
return await response.json();
|
|
@@ -7115,12 +7208,71 @@ function stripServerToolsFromPayload(tools) {
|
|
|
7115
7208
|
return result.length > 0 ? result : void 0;
|
|
7116
7209
|
}
|
|
7117
7210
|
/**
|
|
7211
|
+
* Effective 1M context window. Two ways a request lands on this size:
|
|
7212
|
+
* (a) base model id + context-1m-2025-08-07 beta header (e.g. claude-opus-4.8),
|
|
7213
|
+
* (b) a distinct upstream "-1m-internal" model id (e.g. claude-opus-4.7-1m-internal).
|
|
7214
|
+
*/
|
|
7215
|
+
const ONE_MILLION_CONTEXT_WINDOW_TOKENS = 1e6;
|
|
7216
|
+
/**
|
|
7217
|
+
* Convert a Claude model id from the client-facing dash convention to the
|
|
7218
|
+
* upstream Copilot dot convention. The two conventions co-exist because
|
|
7219
|
+
* Anthropic-style clients use dashes ("claude-opus-4-8") and Copilot lists
|
|
7220
|
+
* the same model with dots ("claude-opus-4.8"). Only the first dash inside
|
|
7221
|
+
* the version segment is converted (a model id like "claude-opus-4.8-1m"
|
|
7222
|
+
* already in dot form is returned unchanged).
|
|
7223
|
+
*/
|
|
7224
|
+
function dashToDotClaudeId(modelId) {
|
|
7225
|
+
if (!modelId.startsWith("claude-")) return modelId;
|
|
7226
|
+
return modelId.replace(/^(claude-[a-z]+-)(\d+)-(\d+)/, "$1$2.$3");
|
|
7227
|
+
}
|
|
7228
|
+
function resolveAnthropicModelForDirectPath(modelId) {
|
|
7229
|
+
const exact = findModelById(modelId);
|
|
7230
|
+
if (exact?.vendor === "Anthropic") return {
|
|
7231
|
+
model: exact,
|
|
7232
|
+
baseModelId: modelId,
|
|
7233
|
+
oneMillionFallback: false,
|
|
7234
|
+
effectiveContextWindowTokens: exact.capabilities?.limits?.max_context_window_tokens ?? 2e5
|
|
7235
|
+
};
|
|
7236
|
+
const dotted = dashToDotClaudeId(modelId);
|
|
7237
|
+
if (dotted !== modelId) {
|
|
7238
|
+
const dottedExact = findModelById(dotted);
|
|
7239
|
+
if (dottedExact?.vendor === "Anthropic") return {
|
|
7240
|
+
model: dottedExact,
|
|
7241
|
+
baseModelId: dotted,
|
|
7242
|
+
oneMillionFallback: false,
|
|
7243
|
+
effectiveContextWindowTokens: dottedExact.capabilities?.limits?.max_context_window_tokens ?? 2e5
|
|
7244
|
+
};
|
|
7245
|
+
}
|
|
7246
|
+
if (modelId.endsWith("-1m")) {
|
|
7247
|
+
const baseId = modelId.slice(0, -3);
|
|
7248
|
+
const dottedBaseId = dashToDotClaudeId(baseId);
|
|
7249
|
+
for (const candidateId of [`${baseId}-1m-internal`, `${dottedBaseId}-1m-internal`]) {
|
|
7250
|
+
const internal = findModelById(candidateId);
|
|
7251
|
+
if (internal?.vendor === "Anthropic") return {
|
|
7252
|
+
model: internal,
|
|
7253
|
+
baseModelId: candidateId,
|
|
7254
|
+
oneMillionFallback: false,
|
|
7255
|
+
effectiveContextWindowTokens: ONE_MILLION_CONTEXT_WINDOW_TOKENS
|
|
7256
|
+
};
|
|
7257
|
+
}
|
|
7258
|
+
for (const candidateId of [baseId, dottedBaseId]) {
|
|
7259
|
+
const base = findModelById(candidateId);
|
|
7260
|
+
if (base?.vendor === "Anthropic") return {
|
|
7261
|
+
model: base,
|
|
7262
|
+
baseModelId: candidateId,
|
|
7263
|
+
oneMillionFallback: true,
|
|
7264
|
+
effectiveContextWindowTokens: ONE_MILLION_CONTEXT_WINDOW_TOKENS
|
|
7265
|
+
};
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
/**
|
|
7118
7270
|
* Check if a model supports direct Anthropic API.
|
|
7119
7271
|
* Returns true if redirect is disabled (direct API is on) and the model is from Anthropic vendor.
|
|
7120
7272
|
*/
|
|
7121
7273
|
function supportsDirectAnthropicApi(modelId) {
|
|
7122
7274
|
if (state.redirectAnthropic) return false;
|
|
7123
|
-
return
|
|
7275
|
+
return resolveAnthropicModelForDirectPath(modelId) !== void 0;
|
|
7124
7276
|
}
|
|
7125
7277
|
|
|
7126
7278
|
//#endregion
|
|
@@ -7359,18 +7511,42 @@ function findLatestModel(familyPrefix, fallback) {
|
|
|
7359
7511
|
const candidates = models.filter((m) => m.id.startsWith(familyPrefix));
|
|
7360
7512
|
if (candidates.length === 0) return fallback;
|
|
7361
7513
|
candidates.sort((a, b) => {
|
|
7362
|
-
const
|
|
7363
|
-
|
|
7514
|
+
const [aMajor, aMinor] = extractVersion(a.id, familyPrefix);
|
|
7515
|
+
const [bMajor, bMinor] = extractVersion(b.id, familyPrefix);
|
|
7516
|
+
if (aMajor !== bMajor) return bMajor - aMajor;
|
|
7517
|
+
return bMinor - aMinor;
|
|
7364
7518
|
});
|
|
7365
7519
|
return candidates[0].id;
|
|
7366
7520
|
}
|
|
7367
7521
|
/**
|
|
7368
|
-
* Extract numeric version from model
|
|
7369
|
-
*
|
|
7522
|
+
* Extract numeric [major, minor] version from a model id.
|
|
7523
|
+
*
|
|
7524
|
+
* Supports both naming conventions Anthropic/Copilot have used:
|
|
7525
|
+
* - dot: "claude-opus-4.5" → [4, 5]
|
|
7526
|
+
* - dash: "claude-opus-4-8" → [4, 8]
|
|
7527
|
+
* - dash double-digit: "claude-opus-4-10" → [4, 10]
|
|
7528
|
+
*
|
|
7529
|
+
* The dash form previously parsed as just the major via the regex
|
|
7530
|
+
* /^(\d+(?:\.\d+)?)/ (the dash stopped the match), which silently
|
|
7531
|
+
* downgraded dash-named candidates against any dot-named candidate in
|
|
7532
|
+
* findLatestModel. Parsing into a tuple also avoids the parseFloat
|
|
7533
|
+
* lossiness on double-digit minors ("4.10" → 4.1).
|
|
7534
|
+
*
|
|
7535
|
+
* Anything after the major/minor segment (date stamps, "-1m") is ignored.
|
|
7536
|
+
* The minor capture is bounded to 1-3 digits so that an 8-digit date suffix
|
|
7537
|
+
* directly after the major (e.g. "claude-opus-4-20250514") is NOT mistaken
|
|
7538
|
+
* for a minor version of 20_250_514 — without that bound, dated ids would
|
|
7539
|
+
* outrank legitimate dotted candidates like "claude-opus-4.8" in
|
|
7540
|
+
* findLatestModel's sort.
|
|
7541
|
+
*
|
|
7542
|
+
* Returns [0, 0] when no version can be extracted.
|
|
7370
7543
|
*/
|
|
7371
7544
|
function extractVersion(modelId, prefix) {
|
|
7372
|
-
const match = modelId.slice(prefix.length + 1).match(/^(\d+(
|
|
7373
|
-
|
|
7545
|
+
const match = modelId.slice(prefix.length + 1).match(/^(\d+)(?:[.-](\d{1,3}))?/);
|
|
7546
|
+
if (!match) return [0, 0];
|
|
7547
|
+
const major = Number.parseInt(match[1], 10);
|
|
7548
|
+
const rawMinor = match[2];
|
|
7549
|
+
return [major, rawMinor === void 0 ? 0 : Number.parseInt(rawMinor, 10) || 0];
|
|
7374
7550
|
}
|
|
7375
7551
|
function translateModelName(model) {
|
|
7376
7552
|
const aliasMap = {
|
|
@@ -7384,6 +7560,8 @@ function translateModelName(model) {
|
|
|
7384
7560
|
}
|
|
7385
7561
|
if (/^claude-sonnet-4-5-\d+$/.test(model)) return "claude-sonnet-4.5";
|
|
7386
7562
|
if (/^claude-sonnet-4-\d+$/.test(model)) return "claude-sonnet-4";
|
|
7563
|
+
if (model === "claude-opus-4-8-1m") return "claude-opus-4.8";
|
|
7564
|
+
if (model === "claude-opus-4-8") return "claude-opus-4.8";
|
|
7387
7565
|
if (model === "claude-opus-4-7-1m") return "claude-opus-4.7-1m-internal";
|
|
7388
7566
|
if (/^claude-opus-4-7$/.test(model)) return "claude-opus-4.7";
|
|
7389
7567
|
if (model === "claude-opus-4-6-1m") return "claude-opus-4.6-1m";
|
|
@@ -7763,22 +7941,44 @@ function translateErrorToAnthropicErrorEvent(error) {
|
|
|
7763
7941
|
*/
|
|
7764
7942
|
async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiatorOverride) {
|
|
7765
7943
|
consola.debug("Using direct Anthropic API path for model:", anthropicPayload.model);
|
|
7766
|
-
const
|
|
7767
|
-
|
|
7944
|
+
const resolution = resolveAnthropicModelForDirectPath(anthropicPayload.model);
|
|
7945
|
+
const selectedModel = resolution?.model;
|
|
7946
|
+
const baseModelId = resolution?.baseModelId ?? anthropicPayload.model;
|
|
7947
|
+
const needsContext1mBeta = resolution?.oneMillionFallback ?? false;
|
|
7948
|
+
const originalModelId = anthropicPayload.model;
|
|
7949
|
+
const resolvedPayload = resolution && baseModelId !== originalModelId ? {
|
|
7950
|
+
...anthropicPayload,
|
|
7951
|
+
model: baseModelId
|
|
7952
|
+
} : anthropicPayload;
|
|
7953
|
+
if (baseModelId !== originalModelId) {
|
|
7954
|
+
consola.debug(`[Anthropic] Mapping model for upstream: ${originalModelId} → ${baseModelId}${needsContext1mBeta ? " (+context-1m beta)" : ""}`);
|
|
7955
|
+
updateTrackerResolvedModel(ctx.trackingId, baseModelId);
|
|
7956
|
+
}
|
|
7957
|
+
const autoTruncateConfig = resolution && resolution.oneMillionFallback ? {
|
|
7958
|
+
contextWindowOverride: resolution.effectiveContextWindowTokens,
|
|
7959
|
+
tokenLimitCacheKeyOverride: originalModelId
|
|
7960
|
+
} : {};
|
|
7961
|
+
let effectivePayload = resolvedPayload;
|
|
7768
7962
|
let truncateResult;
|
|
7769
7963
|
if (state.autoTruncate && selectedModel) {
|
|
7770
|
-
const check = await checkNeedsCompactionAnthropic(
|
|
7964
|
+
const check = await checkNeedsCompactionAnthropic(resolvedPayload, selectedModel, autoTruncateConfig);
|
|
7771
7965
|
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
7966
|
if (check.needed) try {
|
|
7773
|
-
truncateResult = await autoTruncateAnthropic(
|
|
7967
|
+
truncateResult = await autoTruncateAnthropic(resolvedPayload, selectedModel, autoTruncateConfig);
|
|
7774
7968
|
if (truncateResult.wasCompacted) effectivePayload = truncateResult.payload;
|
|
7775
7969
|
} catch (error) {
|
|
7776
7970
|
consola.warn("[Anthropic] Auto-truncate failed, proceeding with original payload:", error instanceof Error ? error.message : error);
|
|
7777
7971
|
}
|
|
7778
7972
|
} else if (state.autoTruncate && !selectedModel) consola.debug(`[Anthropic] Model '${anthropicPayload.model}' not found, skipping auto-truncate`);
|
|
7779
7973
|
if (state.manualApprove) await awaitApproval();
|
|
7974
|
+
const clientAnthropicBetaHeader = c.req.header("anthropic-beta");
|
|
7780
7975
|
try {
|
|
7781
|
-
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
|
|
7976
|
+
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
|
|
7977
|
+
initiator: initiatorOverride,
|
|
7978
|
+
injectContext1mBeta: needsContext1mBeta,
|
|
7979
|
+
errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0,
|
|
7980
|
+
clientAnthropicBetaHeader
|
|
7981
|
+
}));
|
|
7782
7982
|
ctx.queueWaitMs = queueWaitMs;
|
|
7783
7983
|
if (Symbol.asyncIterator in response) {
|
|
7784
7984
|
consola.debug("Streaming response from Copilot (direct Anthropic)");
|
|
@@ -7976,17 +8176,33 @@ const parseSubagentMarkerFromSystemReminder = (text) => {
|
|
|
7976
8176
|
* Handle completion using OpenAI translation path (legacy)
|
|
7977
8177
|
*/
|
|
7978
8178
|
async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOverride) {
|
|
8179
|
+
const originalModelId = anthropicPayload.model;
|
|
8180
|
+
const hasOneMillionSuffix = isOneMillionSuffixedClaudeId(originalModelId);
|
|
8181
|
+
const oneMResolution = resolveAnthropicModelForDirectPath(originalModelId);
|
|
8182
|
+
const needsContext1mBeta = hasOneMillionSuffix || (oneMResolution?.oneMillionFallback ?? false);
|
|
7979
8183
|
const { payload: translatedPayload, toolNameMapping } = translateToOpenAI(anthropicPayload);
|
|
7980
8184
|
consola.debug("Translated OpenAI request payload:", JSON.stringify(translatedPayload));
|
|
7981
8185
|
updateTrackerResolvedModel(ctx.trackingId, translatedPayload.model);
|
|
7982
8186
|
const selectedModel = findModelById(translatedPayload.model);
|
|
7983
|
-
const
|
|
8187
|
+
const autoTruncateConfig = oneMResolution?.oneMillionFallback ? {
|
|
8188
|
+
contextWindowOverride: oneMResolution.effectiveContextWindowTokens,
|
|
8189
|
+
tokenLimitCacheKeyOverride: originalModelId
|
|
8190
|
+
} : {};
|
|
8191
|
+
const { finalPayload: openAIPayload, truncateResult } = await buildFinalPayload(translatedPayload, selectedModel, autoTruncateConfig);
|
|
7984
8192
|
if (truncateResult) ctx.truncateResult = truncateResult;
|
|
8193
|
+
let anthropicBeta = c.req.header("anthropic-beta");
|
|
8194
|
+
if (needsContext1mBeta) anthropicBeta = appendContext1mBeta(anthropicBeta);
|
|
7985
8195
|
if (state.manualApprove) await awaitApproval();
|
|
8196
|
+
let errorModelIdOverride;
|
|
8197
|
+
if (autoTruncateConfig.tokenLimitCacheKeyOverride !== void 0) errorModelIdOverride = autoTruncateConfig.tokenLimitCacheKeyOverride;
|
|
8198
|
+
else if (needsContext1mBeta && selectedModel) errorModelIdOverride = selectedModel.id;
|
|
8199
|
+
else if (hasOneMillionSuffix) errorModelIdOverride = originalModelId;
|
|
7986
8200
|
try {
|
|
7987
8201
|
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, {
|
|
7988
8202
|
initiator: initiatorOverride,
|
|
7989
|
-
resolvedModel: selectedModel
|
|
8203
|
+
resolvedModel: selectedModel,
|
|
8204
|
+
anthropicBeta,
|
|
8205
|
+
errorModelIdOverride
|
|
7990
8206
|
}));
|
|
7991
8207
|
ctx.queueWaitMs = queueWaitMs;
|
|
7992
8208
|
if (isNonStreaming(response)) return handleNonStreamingResponse({
|
|
@@ -8254,10 +8470,15 @@ async function handleCountTokens(c) {
|
|
|
8254
8470
|
consola.warn("Model not found, returning default token count");
|
|
8255
8471
|
return c.json({ input_tokens: 1 });
|
|
8256
8472
|
}
|
|
8473
|
+
const directResolution = resolveAnthropicModelForDirectPath(anthropicPayload.model);
|
|
8474
|
+
const autoTruncateConfig = directResolution?.oneMillionFallback ? {
|
|
8475
|
+
contextWindowOverride: directResolution.effectiveContextWindowTokens,
|
|
8476
|
+
tokenLimitCacheKeyOverride: anthropicPayload.model
|
|
8477
|
+
} : {};
|
|
8257
8478
|
if (state.autoTruncate) {
|
|
8258
|
-
const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
|
|
8479
|
+
const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel, autoTruncateConfig);
|
|
8259
8480
|
if (truncateCheck.needed) {
|
|
8260
|
-
const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
|
|
8481
|
+
const contextWindow = autoTruncateConfig.contextWindowOverride ?? selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
|
|
8261
8482
|
const inflatedTokens = Math.floor(contextWindow * .95);
|
|
8262
8483
|
consola.debug(`[count_tokens] Would trigger auto-truncate: ${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit}, returning inflated count: ${inflatedTokens}`);
|
|
8263
8484
|
return c.json({ input_tokens: inflatedTokens });
|