@dianshuv/copilot-api 0.1.2 → 0.2.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 +390 -359
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -1017,7 +1017,7 @@ const patchClaude = defineCommand({
|
|
|
1017
1017
|
|
|
1018
1018
|
//#endregion
|
|
1019
1019
|
//#region package.json
|
|
1020
|
-
var version = "0.1
|
|
1020
|
+
var version = "0.2.1";
|
|
1021
1021
|
|
|
1022
1022
|
//#endregion
|
|
1023
1023
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1832,6 +1832,15 @@ var ConsoleRenderer = class {
|
|
|
1832
1832
|
process.stdout.write(message + "\n");
|
|
1833
1833
|
this.renderFooter();
|
|
1834
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Format model display string, showing resolved model when different.
|
|
1837
|
+
* e.g., "claude-opus-4-6 -> claude-opus-4.6-1m"
|
|
1838
|
+
*/
|
|
1839
|
+
formatModelDisplay(request) {
|
|
1840
|
+
if (!request.model) return void 0;
|
|
1841
|
+
if (request.resolvedModel) return `${request.model} -> ${request.resolvedModel}`;
|
|
1842
|
+
return request.model;
|
|
1843
|
+
}
|
|
1835
1844
|
onRequestStart(request) {
|
|
1836
1845
|
this.activeRequests.set(request.id, request);
|
|
1837
1846
|
if (this.showActive && consola.level >= 5) {
|
|
@@ -1840,7 +1849,7 @@ var ConsoleRenderer = class {
|
|
|
1840
1849
|
time: formatTime(),
|
|
1841
1850
|
method: request.method,
|
|
1842
1851
|
path: request.path,
|
|
1843
|
-
model: request
|
|
1852
|
+
model: this.formatModelDisplay(request),
|
|
1844
1853
|
extra: request.queuePosition !== void 0 && request.queuePosition > 0 ? `[q#${request.queuePosition}]` : void 0,
|
|
1845
1854
|
isDim: true
|
|
1846
1855
|
});
|
|
@@ -1857,7 +1866,7 @@ var ConsoleRenderer = class {
|
|
|
1857
1866
|
time: formatTime(),
|
|
1858
1867
|
method: request.method,
|
|
1859
1868
|
path: request.path,
|
|
1860
|
-
model: request
|
|
1869
|
+
model: this.formatModelDisplay(request),
|
|
1861
1870
|
extra: "streaming...",
|
|
1862
1871
|
isDim: true
|
|
1863
1872
|
});
|
|
@@ -1875,7 +1884,7 @@ var ConsoleRenderer = class {
|
|
|
1875
1884
|
time: formatTime(),
|
|
1876
1885
|
method: request.method,
|
|
1877
1886
|
path: request.path,
|
|
1878
|
-
model: request
|
|
1887
|
+
model: this.formatModelDisplay(request),
|
|
1879
1888
|
status,
|
|
1880
1889
|
duration: formatDuration(request.durationMs ?? 0),
|
|
1881
1890
|
queueWait,
|
|
@@ -2790,10 +2799,18 @@ const createChatCompletions = async (payload, options) => {
|
|
|
2790
2799
|
* Contains common functions used by both OpenAI and Anthropic message handlers.
|
|
2791
2800
|
*/
|
|
2792
2801
|
/** Helper to update tracker model */
|
|
2793
|
-
function updateTrackerModel(trackingId, model) {
|
|
2802
|
+
function updateTrackerModel(trackingId, model, resolvedModel) {
|
|
2794
2803
|
if (!trackingId) return;
|
|
2795
2804
|
const request = requestTracker.getRequest(trackingId);
|
|
2796
|
-
if (request)
|
|
2805
|
+
if (!request) return;
|
|
2806
|
+
request.model = model;
|
|
2807
|
+
request.resolvedModel = resolvedModel ?? model;
|
|
2808
|
+
}
|
|
2809
|
+
/** Helper to update only the resolved (post-translation) model */
|
|
2810
|
+
function updateTrackerResolvedModel(trackingId, resolvedModel) {
|
|
2811
|
+
if (!trackingId) return;
|
|
2812
|
+
const request = requestTracker.getRequest(trackingId);
|
|
2813
|
+
if (request) request.resolvedModel = resolvedModel;
|
|
2797
2814
|
}
|
|
2798
2815
|
/** Helper to update tracker status */
|
|
2799
2816
|
function updateTrackerStatus(trackingId, status) {
|
|
@@ -4890,6 +4907,211 @@ async function checkNeedsCompactionAnthropic(payload, model, config = {}) {
|
|
|
4890
4907
|
};
|
|
4891
4908
|
}
|
|
4892
4909
|
|
|
4910
|
+
//#endregion
|
|
4911
|
+
//#region src/services/copilot/create-anthropic-messages.ts
|
|
4912
|
+
/**
|
|
4913
|
+
* Direct Anthropic-style message API for Copilot.
|
|
4914
|
+
* Used when the model vendor is Anthropic and supports /v1/messages endpoint.
|
|
4915
|
+
*/
|
|
4916
|
+
/**
|
|
4917
|
+
* Fields that are supported by Copilot's Anthropic API endpoint.
|
|
4918
|
+
* Any other fields in the incoming request will be stripped.
|
|
4919
|
+
*/
|
|
4920
|
+
const COPILOT_SUPPORTED_FIELDS = new Set([
|
|
4921
|
+
"model",
|
|
4922
|
+
"messages",
|
|
4923
|
+
"max_tokens",
|
|
4924
|
+
"system",
|
|
4925
|
+
"metadata",
|
|
4926
|
+
"stop_sequences",
|
|
4927
|
+
"stream",
|
|
4928
|
+
"temperature",
|
|
4929
|
+
"top_p",
|
|
4930
|
+
"top_k",
|
|
4931
|
+
"tools",
|
|
4932
|
+
"tool_choice",
|
|
4933
|
+
"thinking",
|
|
4934
|
+
"service_tier"
|
|
4935
|
+
]);
|
|
4936
|
+
/**
|
|
4937
|
+
* Filter payload to only include fields supported by Copilot's Anthropic API.
|
|
4938
|
+
* This prevents errors like "Extra inputs are not permitted" for unsupported
|
|
4939
|
+
* fields like `output_config`.
|
|
4940
|
+
*
|
|
4941
|
+
* Also converts server-side tools (web_search, etc.) to custom tools.
|
|
4942
|
+
*/
|
|
4943
|
+
function filterPayloadForCopilot(payload) {
|
|
4944
|
+
const filtered = {};
|
|
4945
|
+
const unsupportedFields = [];
|
|
4946
|
+
for (const [key, value] of Object.entries(payload)) if (COPILOT_SUPPORTED_FIELDS.has(key)) filtered[key] = value;
|
|
4947
|
+
else unsupportedFields.push(key);
|
|
4948
|
+
if (unsupportedFields.length > 0) consola.debug(`[DirectAnthropic] Filtered unsupported fields: ${unsupportedFields.join(", ")}`);
|
|
4949
|
+
if (filtered.tools) filtered.tools = convertServerToolsToCustom(filtered.tools);
|
|
4950
|
+
return filtered;
|
|
4951
|
+
}
|
|
4952
|
+
/**
|
|
4953
|
+
* Adjust max_tokens if thinking is enabled.
|
|
4954
|
+
* According to Anthropic docs, max_tokens must be greater than thinking.budget_tokens.
|
|
4955
|
+
* max_tokens = thinking_budget + response_tokens
|
|
4956
|
+
*/
|
|
4957
|
+
function adjustMaxTokensForThinking(payload) {
|
|
4958
|
+
const thinking = payload.thinking;
|
|
4959
|
+
if (!thinking) return payload;
|
|
4960
|
+
const budgetTokens = thinking.budget_tokens;
|
|
4961
|
+
if (!budgetTokens) return payload;
|
|
4962
|
+
if (payload.max_tokens <= budgetTokens) {
|
|
4963
|
+
const newMaxTokens = budgetTokens + Math.min(16384, budgetTokens);
|
|
4964
|
+
consola.debug(`[DirectAnthropic] Adjusted max_tokens: ${payload.max_tokens} → ${newMaxTokens} (thinking.budget_tokens=${budgetTokens})`);
|
|
4965
|
+
return {
|
|
4966
|
+
...payload,
|
|
4967
|
+
max_tokens: newMaxTokens
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4970
|
+
return payload;
|
|
4971
|
+
}
|
|
4972
|
+
/**
|
|
4973
|
+
* Create messages using Anthropic-style API directly.
|
|
4974
|
+
* This bypasses the OpenAI translation layer for Anthropic models.
|
|
4975
|
+
*/
|
|
4976
|
+
async function createAnthropicMessages(payload, options) {
|
|
4977
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
4978
|
+
let filteredPayload = filterPayloadForCopilot(payload);
|
|
4979
|
+
filteredPayload = adjustMaxTokensForThinking(filteredPayload);
|
|
4980
|
+
const enableVision = filteredPayload.messages.some((msg) => {
|
|
4981
|
+
if (typeof msg.content === "string") return false;
|
|
4982
|
+
return msg.content.some((block) => block.type === "image");
|
|
4983
|
+
});
|
|
4984
|
+
const isAgentCall = filteredPayload.messages.some((msg) => msg.role === "assistant");
|
|
4985
|
+
const headers = {
|
|
4986
|
+
...copilotHeaders(state, enableVision),
|
|
4987
|
+
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
4988
|
+
"anthropic-version": "2023-06-01"
|
|
4989
|
+
};
|
|
4990
|
+
consola.debug("Sending direct Anthropic request to Copilot /v1/messages");
|
|
4991
|
+
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
4992
|
+
method: "POST",
|
|
4993
|
+
headers,
|
|
4994
|
+
body: JSON.stringify(filteredPayload)
|
|
4995
|
+
});
|
|
4996
|
+
if (!response.ok) {
|
|
4997
|
+
consola.debug("Request failed:", {
|
|
4998
|
+
model: filteredPayload.model,
|
|
4999
|
+
max_tokens: filteredPayload.max_tokens,
|
|
5000
|
+
stream: filteredPayload.stream,
|
|
5001
|
+
tools: filteredPayload.tools?.map((t) => ({
|
|
5002
|
+
name: t.name,
|
|
5003
|
+
type: t.type
|
|
5004
|
+
})),
|
|
5005
|
+
thinking: filteredPayload.thinking,
|
|
5006
|
+
messageCount: filteredPayload.messages.length
|
|
5007
|
+
});
|
|
5008
|
+
throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, filteredPayload.model);
|
|
5009
|
+
}
|
|
5010
|
+
if (payload.stream) return events(response);
|
|
5011
|
+
return await response.json();
|
|
5012
|
+
}
|
|
5013
|
+
const SERVER_TOOL_CONFIGS = {
|
|
5014
|
+
web_search: {
|
|
5015
|
+
description: "Search the web for current information. Returns web search results that can help answer questions about recent events, current data, or information that may have changed since your knowledge cutoff.",
|
|
5016
|
+
input_schema: {
|
|
5017
|
+
type: "object",
|
|
5018
|
+
properties: { query: {
|
|
5019
|
+
type: "string",
|
|
5020
|
+
description: "The search query"
|
|
5021
|
+
} },
|
|
5022
|
+
required: ["query"]
|
|
5023
|
+
}
|
|
5024
|
+
},
|
|
5025
|
+
web_fetch: {
|
|
5026
|
+
description: "Fetch content from a URL. NOTE: This is a client-side tool - the client must fetch the URL and return the content.",
|
|
5027
|
+
input_schema: {
|
|
5028
|
+
type: "object",
|
|
5029
|
+
properties: { url: {
|
|
5030
|
+
type: "string",
|
|
5031
|
+
description: "The URL to fetch"
|
|
5032
|
+
} },
|
|
5033
|
+
required: ["url"]
|
|
5034
|
+
}
|
|
5035
|
+
},
|
|
5036
|
+
code_execution: {
|
|
5037
|
+
description: "Execute code in a sandbox. NOTE: This is a client-side tool - the client must execute the code.",
|
|
5038
|
+
input_schema: {
|
|
5039
|
+
type: "object",
|
|
5040
|
+
properties: {
|
|
5041
|
+
code: {
|
|
5042
|
+
type: "string",
|
|
5043
|
+
description: "The code to execute"
|
|
5044
|
+
},
|
|
5045
|
+
language: {
|
|
5046
|
+
type: "string",
|
|
5047
|
+
description: "The programming language"
|
|
5048
|
+
}
|
|
5049
|
+
},
|
|
5050
|
+
required: ["code"]
|
|
5051
|
+
}
|
|
5052
|
+
},
|
|
5053
|
+
computer: {
|
|
5054
|
+
description: "Control computer desktop. NOTE: This is a client-side tool - the client must handle computer control.",
|
|
5055
|
+
input_schema: {
|
|
5056
|
+
type: "object",
|
|
5057
|
+
properties: { action: {
|
|
5058
|
+
type: "string",
|
|
5059
|
+
description: "The action to perform"
|
|
5060
|
+
} },
|
|
5061
|
+
required: ["action"]
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
};
|
|
5065
|
+
/**
|
|
5066
|
+
* Check if a tool is a server-side tool that needs conversion.
|
|
5067
|
+
*/
|
|
5068
|
+
function getServerToolPrefix(tool) {
|
|
5069
|
+
if (tool.type) {
|
|
5070
|
+
for (const prefix of Object.keys(SERVER_TOOL_CONFIGS)) if (tool.type.startsWith(prefix)) return prefix;
|
|
5071
|
+
}
|
|
5072
|
+
return null;
|
|
5073
|
+
}
|
|
5074
|
+
/**
|
|
5075
|
+
* Convert server-side tools to custom tools, or pass them through unchanged.
|
|
5076
|
+
* This allows them to be passed to the API and handled by the client.
|
|
5077
|
+
*
|
|
5078
|
+
* Note: Server-side tools are only converted if state.rewriteAnthropicTools is enabled.
|
|
5079
|
+
*/
|
|
5080
|
+
function convertServerToolsToCustom(tools) {
|
|
5081
|
+
if (!tools) return;
|
|
5082
|
+
const result = [];
|
|
5083
|
+
for (const tool of tools) {
|
|
5084
|
+
const serverToolPrefix = getServerToolPrefix(tool);
|
|
5085
|
+
if (serverToolPrefix) {
|
|
5086
|
+
const config = SERVER_TOOL_CONFIGS[serverToolPrefix];
|
|
5087
|
+
if (!state.rewriteAnthropicTools) {
|
|
5088
|
+
consola.debug(`[DirectAnthropic] Passing ${serverToolPrefix} through unchanged (use --rewrite-anthropic-tools to convert)`);
|
|
5089
|
+
result.push(tool);
|
|
5090
|
+
continue;
|
|
5091
|
+
}
|
|
5092
|
+
if (config.remove) {
|
|
5093
|
+
consola.warn(`[DirectAnthropic] Removing unsupported server tool: ${tool.name}. Reason: ${config.removalReason}`);
|
|
5094
|
+
continue;
|
|
5095
|
+
}
|
|
5096
|
+
consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`);
|
|
5097
|
+
result.push({
|
|
5098
|
+
name: tool.name,
|
|
5099
|
+
description: config.description,
|
|
5100
|
+
input_schema: config.input_schema
|
|
5101
|
+
});
|
|
5102
|
+
} else result.push(tool);
|
|
5103
|
+
}
|
|
5104
|
+
return result.length > 0 ? result : void 0;
|
|
5105
|
+
}
|
|
5106
|
+
/**
|
|
5107
|
+
* Check if a model supports direct Anthropic API.
|
|
5108
|
+
* Returns true if redirect is disabled (direct API is on) and the model is from Anthropic vendor.
|
|
5109
|
+
*/
|
|
5110
|
+
function supportsDirectAnthropicApi(modelId) {
|
|
5111
|
+
if (state.redirectAnthropic) return false;
|
|
5112
|
+
return (state.models?.data.find((m) => m.id === modelId))?.vendor === "Anthropic";
|
|
5113
|
+
}
|
|
5114
|
+
|
|
4893
5115
|
//#endregion
|
|
4894
5116
|
//#region src/routes/messages/message-utils.ts
|
|
4895
5117
|
function convertAnthropicMessages(messages) {
|
|
@@ -4959,48 +5181,103 @@ function mapOpenAIStopReasonToAnthropic(finishReason) {
|
|
|
4959
5181
|
}
|
|
4960
5182
|
|
|
4961
5183
|
//#endregion
|
|
4962
|
-
//#region src/routes/messages/
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5184
|
+
//#region src/routes/messages/stream-accumulator.ts
|
|
5185
|
+
function createAnthropicStreamAccumulator() {
|
|
5186
|
+
return {
|
|
5187
|
+
model: "",
|
|
5188
|
+
inputTokens: 0,
|
|
5189
|
+
outputTokens: 0,
|
|
5190
|
+
stopReason: "",
|
|
5191
|
+
content: "",
|
|
5192
|
+
toolCalls: [],
|
|
5193
|
+
currentToolCall: null
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
function processAnthropicEvent(event, acc) {
|
|
5197
|
+
switch (event.type) {
|
|
5198
|
+
case "content_block_delta":
|
|
5199
|
+
handleContentBlockDelta(event.delta, acc);
|
|
5200
|
+
break;
|
|
5201
|
+
case "content_block_start":
|
|
5202
|
+
handleContentBlockStart(event.content_block, acc);
|
|
5203
|
+
break;
|
|
5204
|
+
case "content_block_stop":
|
|
5205
|
+
handleContentBlockStop(acc);
|
|
5206
|
+
break;
|
|
5207
|
+
case "message_delta":
|
|
5208
|
+
handleMessageDelta(event.delta, event.usage, acc);
|
|
5209
|
+
break;
|
|
5210
|
+
default: break;
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
function handleContentBlockDelta(delta, acc) {
|
|
5214
|
+
if (delta.type === "text_delta") acc.content += delta.text;
|
|
5215
|
+
else if (delta.type === "input_json_delta" && acc.currentToolCall) acc.currentToolCall.input += delta.partial_json;
|
|
5216
|
+
}
|
|
5217
|
+
function handleContentBlockStart(block, acc) {
|
|
5218
|
+
if (block.type === "tool_use") acc.currentToolCall = {
|
|
5219
|
+
id: block.id,
|
|
5220
|
+
name: block.name,
|
|
5221
|
+
input: ""
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5224
|
+
function handleContentBlockStop(acc) {
|
|
5225
|
+
if (acc.currentToolCall) {
|
|
5226
|
+
acc.toolCalls.push(acc.currentToolCall);
|
|
5227
|
+
acc.currentToolCall = null;
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
function handleMessageDelta(delta, usage, acc) {
|
|
5231
|
+
if (delta.stop_reason) acc.stopReason = delta.stop_reason;
|
|
5232
|
+
if (usage) {
|
|
5233
|
+
acc.inputTokens = usage.input_tokens ?? 0;
|
|
5234
|
+
acc.outputTokens = usage.output_tokens;
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
//#endregion
|
|
5239
|
+
//#region src/routes/messages/non-stream-translation.ts
|
|
5240
|
+
const OPENAI_TOOL_NAME_LIMIT = 64;
|
|
5241
|
+
/**
|
|
5242
|
+
* Ensure all tool_use blocks have corresponding tool_result responses.
|
|
5243
|
+
* This handles edge cases where conversation history may be incomplete:
|
|
5244
|
+
* - Session interruptions where tool execution was cut off
|
|
5245
|
+
* - Previous request failures
|
|
5246
|
+
* - Client sending truncated history
|
|
5247
|
+
*
|
|
5248
|
+
* Adding placeholder responses prevents API errors and maintains protocol compliance.
|
|
5249
|
+
*/
|
|
5250
|
+
function fixMessageSequence(messages) {
|
|
5251
|
+
const fixedMessages = [];
|
|
5252
|
+
for (let i = 0; i < messages.length; i++) {
|
|
5253
|
+
const message = messages[i];
|
|
5254
|
+
fixedMessages.push(message);
|
|
5255
|
+
if (message.role === "assistant" && message.tool_calls && message.tool_calls.length > 0) {
|
|
5256
|
+
const foundToolResponses = /* @__PURE__ */ new Set();
|
|
5257
|
+
let j = i + 1;
|
|
5258
|
+
while (j < messages.length && messages[j].role === "tool") {
|
|
5259
|
+
const toolMessage = messages[j];
|
|
5260
|
+
if (toolMessage.tool_call_id) foundToolResponses.add(toolMessage.tool_call_id);
|
|
5261
|
+
j++;
|
|
5262
|
+
}
|
|
5263
|
+
for (const toolCall of message.tool_calls) if (!foundToolResponses.has(toolCall.id)) {
|
|
5264
|
+
consola.debug(`Adding placeholder tool_result for ${toolCall.id}`);
|
|
5265
|
+
fixedMessages.push({
|
|
5266
|
+
role: "tool",
|
|
5267
|
+
tool_call_id: toolCall.id,
|
|
5268
|
+
content: "Tool execution was interrupted or failed."
|
|
5269
|
+
});
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
return fixedMessages;
|
|
5274
|
+
}
|
|
5275
|
+
function translateToOpenAI(payload) {
|
|
5276
|
+
const toolNameMapping = {
|
|
5277
|
+
truncatedToOriginal: /* @__PURE__ */ new Map(),
|
|
5278
|
+
originalToTruncated: /* @__PURE__ */ new Map()
|
|
5279
|
+
};
|
|
5280
|
+
const messages = translateAnthropicMessagesToOpenAI(payload.messages, payload.system, toolNameMapping);
|
|
5004
5281
|
return {
|
|
5005
5282
|
payload: {
|
|
5006
5283
|
model: translateModelName(payload.model),
|
|
@@ -5056,7 +5333,8 @@ function translateModelName(model) {
|
|
|
5056
5333
|
}
|
|
5057
5334
|
if (/^claude-sonnet-4-5-\d+$/.test(model)) return "claude-sonnet-4.5";
|
|
5058
5335
|
if (/^claude-sonnet-4-\d+$/.test(model)) return "claude-sonnet-4";
|
|
5059
|
-
if (
|
|
5336
|
+
if (model === "claude-opus-4-6-1m") return "claude-opus-4.6-1m";
|
|
5337
|
+
if (/^claude-opus-4-6$/.test(model)) return "claude-opus-4.6";
|
|
5060
5338
|
if (/^claude-opus-4-5-\d+$/.test(model)) return "claude-opus-4.5";
|
|
5061
5339
|
if (/^claude-opus-4-\d+$/.test(model)) return findLatestModel("claude-opus", "claude-opus-4.5");
|
|
5062
5340
|
if (/^claude-haiku-4-5-\d+$/.test(model)) return "claude-haiku-4.5";
|
|
@@ -5287,316 +5565,6 @@ function getAnthropicToolUseBlocks(toolCalls, toolNameMapping) {
|
|
|
5287
5565
|
});
|
|
5288
5566
|
}
|
|
5289
5567
|
|
|
5290
|
-
//#endregion
|
|
5291
|
-
//#region src/routes/messages/count-tokens-handler.ts
|
|
5292
|
-
/**
|
|
5293
|
-
* Handles token counting for Anthropic messages.
|
|
5294
|
-
*
|
|
5295
|
-
* For Anthropic models (vendor === "Anthropic"), uses the official Anthropic tokenizer.
|
|
5296
|
-
* For other models, uses GPT tokenizers with appropriate buffers.
|
|
5297
|
-
*
|
|
5298
|
-
* When auto-truncate is enabled and the request would exceed limits,
|
|
5299
|
-
* returns an inflated token count to trigger Claude Code's auto-compact mechanism.
|
|
5300
|
-
*/
|
|
5301
|
-
async function handleCountTokens(c) {
|
|
5302
|
-
try {
|
|
5303
|
-
const anthropicBeta = c.req.header("anthropic-beta");
|
|
5304
|
-
const anthropicPayload = await c.req.json();
|
|
5305
|
-
const { payload: openAIPayload } = translateToOpenAI(anthropicPayload);
|
|
5306
|
-
const selectedModel = state.models?.data.find((model) => model.id === openAIPayload.model);
|
|
5307
|
-
if (!selectedModel) {
|
|
5308
|
-
consola.warn("Model not found, returning default token count");
|
|
5309
|
-
return c.json({ input_tokens: 1 });
|
|
5310
|
-
}
|
|
5311
|
-
if (state.autoTruncate) {
|
|
5312
|
-
const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
|
|
5313
|
-
if (truncateCheck.needed) {
|
|
5314
|
-
const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
|
|
5315
|
-
const inflatedTokens = Math.floor(contextWindow * .95);
|
|
5316
|
-
consola.debug(`[count_tokens] Would trigger auto-truncate: ${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit}, returning inflated count: ${inflatedTokens}`);
|
|
5317
|
-
return c.json({ input_tokens: inflatedTokens });
|
|
5318
|
-
}
|
|
5319
|
-
}
|
|
5320
|
-
const tokenizerName = selectedModel.capabilities?.tokenizer ?? "o200k_base";
|
|
5321
|
-
const tokenCount = await getTokenCount(openAIPayload, selectedModel);
|
|
5322
|
-
if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
|
|
5323
|
-
let mcpToolExist = false;
|
|
5324
|
-
if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__"));
|
|
5325
|
-
if (!mcpToolExist) {
|
|
5326
|
-
if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
|
|
5327
|
-
else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480;
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
let finalTokenCount = tokenCount.input + tokenCount.output;
|
|
5331
|
-
if (!(selectedModel.vendor === "Anthropic")) finalTokenCount = anthropicPayload.model.startsWith("grok") ? Math.round(finalTokenCount * 1.03) : Math.round(finalTokenCount * 1.05);
|
|
5332
|
-
consola.debug(`Token count: ${finalTokenCount} (tokenizer: ${tokenizerName})`);
|
|
5333
|
-
return c.json({ input_tokens: finalTokenCount });
|
|
5334
|
-
} catch (error) {
|
|
5335
|
-
consola.error("Error counting tokens:", error);
|
|
5336
|
-
return c.json({ input_tokens: 1 });
|
|
5337
|
-
}
|
|
5338
|
-
}
|
|
5339
|
-
|
|
5340
|
-
//#endregion
|
|
5341
|
-
//#region src/services/copilot/create-anthropic-messages.ts
|
|
5342
|
-
/**
|
|
5343
|
-
* Direct Anthropic-style message API for Copilot.
|
|
5344
|
-
* Used when the model vendor is Anthropic and supports /v1/messages endpoint.
|
|
5345
|
-
*/
|
|
5346
|
-
/**
|
|
5347
|
-
* Fields that are supported by Copilot's Anthropic API endpoint.
|
|
5348
|
-
* Any other fields in the incoming request will be stripped.
|
|
5349
|
-
*/
|
|
5350
|
-
const COPILOT_SUPPORTED_FIELDS = new Set([
|
|
5351
|
-
"model",
|
|
5352
|
-
"messages",
|
|
5353
|
-
"max_tokens",
|
|
5354
|
-
"system",
|
|
5355
|
-
"metadata",
|
|
5356
|
-
"stop_sequences",
|
|
5357
|
-
"stream",
|
|
5358
|
-
"temperature",
|
|
5359
|
-
"top_p",
|
|
5360
|
-
"top_k",
|
|
5361
|
-
"tools",
|
|
5362
|
-
"tool_choice",
|
|
5363
|
-
"thinking",
|
|
5364
|
-
"service_tier"
|
|
5365
|
-
]);
|
|
5366
|
-
/**
|
|
5367
|
-
* Filter payload to only include fields supported by Copilot's Anthropic API.
|
|
5368
|
-
* This prevents errors like "Extra inputs are not permitted" for unsupported
|
|
5369
|
-
* fields like `output_config`.
|
|
5370
|
-
*
|
|
5371
|
-
* Also converts server-side tools (web_search, etc.) to custom tools.
|
|
5372
|
-
*/
|
|
5373
|
-
function filterPayloadForCopilot(payload) {
|
|
5374
|
-
const filtered = {};
|
|
5375
|
-
const unsupportedFields = [];
|
|
5376
|
-
for (const [key, value] of Object.entries(payload)) if (COPILOT_SUPPORTED_FIELDS.has(key)) filtered[key] = value;
|
|
5377
|
-
else unsupportedFields.push(key);
|
|
5378
|
-
if (unsupportedFields.length > 0) consola.debug(`[DirectAnthropic] Filtered unsupported fields: ${unsupportedFields.join(", ")}`);
|
|
5379
|
-
if (filtered.tools) filtered.tools = convertServerToolsToCustom(filtered.tools);
|
|
5380
|
-
return filtered;
|
|
5381
|
-
}
|
|
5382
|
-
/**
|
|
5383
|
-
* Adjust max_tokens if thinking is enabled.
|
|
5384
|
-
* According to Anthropic docs, max_tokens must be greater than thinking.budget_tokens.
|
|
5385
|
-
* max_tokens = thinking_budget + response_tokens
|
|
5386
|
-
*/
|
|
5387
|
-
function adjustMaxTokensForThinking(payload) {
|
|
5388
|
-
const thinking = payload.thinking;
|
|
5389
|
-
if (!thinking) return payload;
|
|
5390
|
-
const budgetTokens = thinking.budget_tokens;
|
|
5391
|
-
if (!budgetTokens) return payload;
|
|
5392
|
-
if (payload.max_tokens <= budgetTokens) {
|
|
5393
|
-
const newMaxTokens = budgetTokens + Math.min(16384, budgetTokens);
|
|
5394
|
-
consola.debug(`[DirectAnthropic] Adjusted max_tokens: ${payload.max_tokens} → ${newMaxTokens} (thinking.budget_tokens=${budgetTokens})`);
|
|
5395
|
-
return {
|
|
5396
|
-
...payload,
|
|
5397
|
-
max_tokens: newMaxTokens
|
|
5398
|
-
};
|
|
5399
|
-
}
|
|
5400
|
-
return payload;
|
|
5401
|
-
}
|
|
5402
|
-
/**
|
|
5403
|
-
* Create messages using Anthropic-style API directly.
|
|
5404
|
-
* This bypasses the OpenAI translation layer for Anthropic models.
|
|
5405
|
-
*/
|
|
5406
|
-
async function createAnthropicMessages(payload, options) {
|
|
5407
|
-
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
5408
|
-
let filteredPayload = filterPayloadForCopilot(payload);
|
|
5409
|
-
filteredPayload = adjustMaxTokensForThinking(filteredPayload);
|
|
5410
|
-
const enableVision = filteredPayload.messages.some((msg) => {
|
|
5411
|
-
if (typeof msg.content === "string") return false;
|
|
5412
|
-
return msg.content.some((block) => block.type === "image");
|
|
5413
|
-
});
|
|
5414
|
-
const isAgentCall = filteredPayload.messages.some((msg) => msg.role === "assistant");
|
|
5415
|
-
const headers = {
|
|
5416
|
-
...copilotHeaders(state, enableVision),
|
|
5417
|
-
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
5418
|
-
"anthropic-version": "2023-06-01"
|
|
5419
|
-
};
|
|
5420
|
-
consola.debug("Sending direct Anthropic request to Copilot /v1/messages");
|
|
5421
|
-
const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
|
|
5422
|
-
method: "POST",
|
|
5423
|
-
headers,
|
|
5424
|
-
body: JSON.stringify(filteredPayload)
|
|
5425
|
-
});
|
|
5426
|
-
if (!response.ok) {
|
|
5427
|
-
consola.debug("Request failed:", {
|
|
5428
|
-
model: filteredPayload.model,
|
|
5429
|
-
max_tokens: filteredPayload.max_tokens,
|
|
5430
|
-
stream: filteredPayload.stream,
|
|
5431
|
-
tools: filteredPayload.tools?.map((t) => ({
|
|
5432
|
-
name: t.name,
|
|
5433
|
-
type: t.type
|
|
5434
|
-
})),
|
|
5435
|
-
thinking: filteredPayload.thinking,
|
|
5436
|
-
messageCount: filteredPayload.messages.length
|
|
5437
|
-
});
|
|
5438
|
-
throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, filteredPayload.model);
|
|
5439
|
-
}
|
|
5440
|
-
if (payload.stream) return events(response);
|
|
5441
|
-
return await response.json();
|
|
5442
|
-
}
|
|
5443
|
-
const SERVER_TOOL_CONFIGS = {
|
|
5444
|
-
web_search: {
|
|
5445
|
-
description: "Search the web for current information. Returns web search results that can help answer questions about recent events, current data, or information that may have changed since your knowledge cutoff.",
|
|
5446
|
-
input_schema: {
|
|
5447
|
-
type: "object",
|
|
5448
|
-
properties: { query: {
|
|
5449
|
-
type: "string",
|
|
5450
|
-
description: "The search query"
|
|
5451
|
-
} },
|
|
5452
|
-
required: ["query"]
|
|
5453
|
-
}
|
|
5454
|
-
},
|
|
5455
|
-
web_fetch: {
|
|
5456
|
-
description: "Fetch content from a URL. NOTE: This is a client-side tool - the client must fetch the URL and return the content.",
|
|
5457
|
-
input_schema: {
|
|
5458
|
-
type: "object",
|
|
5459
|
-
properties: { url: {
|
|
5460
|
-
type: "string",
|
|
5461
|
-
description: "The URL to fetch"
|
|
5462
|
-
} },
|
|
5463
|
-
required: ["url"]
|
|
5464
|
-
}
|
|
5465
|
-
},
|
|
5466
|
-
code_execution: {
|
|
5467
|
-
description: "Execute code in a sandbox. NOTE: This is a client-side tool - the client must execute the code.",
|
|
5468
|
-
input_schema: {
|
|
5469
|
-
type: "object",
|
|
5470
|
-
properties: {
|
|
5471
|
-
code: {
|
|
5472
|
-
type: "string",
|
|
5473
|
-
description: "The code to execute"
|
|
5474
|
-
},
|
|
5475
|
-
language: {
|
|
5476
|
-
type: "string",
|
|
5477
|
-
description: "The programming language"
|
|
5478
|
-
}
|
|
5479
|
-
},
|
|
5480
|
-
required: ["code"]
|
|
5481
|
-
}
|
|
5482
|
-
},
|
|
5483
|
-
computer: {
|
|
5484
|
-
description: "Control computer desktop. NOTE: This is a client-side tool - the client must handle computer control.",
|
|
5485
|
-
input_schema: {
|
|
5486
|
-
type: "object",
|
|
5487
|
-
properties: { action: {
|
|
5488
|
-
type: "string",
|
|
5489
|
-
description: "The action to perform"
|
|
5490
|
-
} },
|
|
5491
|
-
required: ["action"]
|
|
5492
|
-
}
|
|
5493
|
-
}
|
|
5494
|
-
};
|
|
5495
|
-
/**
|
|
5496
|
-
* Check if a tool is a server-side tool that needs conversion.
|
|
5497
|
-
*/
|
|
5498
|
-
function getServerToolPrefix(tool) {
|
|
5499
|
-
if (tool.type) {
|
|
5500
|
-
for (const prefix of Object.keys(SERVER_TOOL_CONFIGS)) if (tool.type.startsWith(prefix)) return prefix;
|
|
5501
|
-
}
|
|
5502
|
-
return null;
|
|
5503
|
-
}
|
|
5504
|
-
/**
|
|
5505
|
-
* Convert server-side tools to custom tools, or pass them through unchanged.
|
|
5506
|
-
* This allows them to be passed to the API and handled by the client.
|
|
5507
|
-
*
|
|
5508
|
-
* Note: Server-side tools are only converted if state.rewriteAnthropicTools is enabled.
|
|
5509
|
-
*/
|
|
5510
|
-
function convertServerToolsToCustom(tools) {
|
|
5511
|
-
if (!tools) return;
|
|
5512
|
-
const result = [];
|
|
5513
|
-
for (const tool of tools) {
|
|
5514
|
-
const serverToolPrefix = getServerToolPrefix(tool);
|
|
5515
|
-
if (serverToolPrefix) {
|
|
5516
|
-
const config = SERVER_TOOL_CONFIGS[serverToolPrefix];
|
|
5517
|
-
if (!state.rewriteAnthropicTools) {
|
|
5518
|
-
consola.debug(`[DirectAnthropic] Passing ${serverToolPrefix} through unchanged (use --rewrite-anthropic-tools to convert)`);
|
|
5519
|
-
result.push(tool);
|
|
5520
|
-
continue;
|
|
5521
|
-
}
|
|
5522
|
-
if (config.remove) {
|
|
5523
|
-
consola.warn(`[DirectAnthropic] Removing unsupported server tool: ${tool.name}. Reason: ${config.removalReason}`);
|
|
5524
|
-
continue;
|
|
5525
|
-
}
|
|
5526
|
-
consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`);
|
|
5527
|
-
result.push({
|
|
5528
|
-
name: tool.name,
|
|
5529
|
-
description: config.description,
|
|
5530
|
-
input_schema: config.input_schema
|
|
5531
|
-
});
|
|
5532
|
-
} else result.push(tool);
|
|
5533
|
-
}
|
|
5534
|
-
return result.length > 0 ? result : void 0;
|
|
5535
|
-
}
|
|
5536
|
-
/**
|
|
5537
|
-
* Check if a model supports direct Anthropic API.
|
|
5538
|
-
* Returns true if redirect is disabled (direct API is on) and the model is from Anthropic vendor.
|
|
5539
|
-
*/
|
|
5540
|
-
function supportsDirectAnthropicApi(modelId) {
|
|
5541
|
-
if (state.redirectAnthropic) return false;
|
|
5542
|
-
return (state.models?.data.find((m) => m.id === modelId))?.vendor === "Anthropic";
|
|
5543
|
-
}
|
|
5544
|
-
|
|
5545
|
-
//#endregion
|
|
5546
|
-
//#region src/routes/messages/stream-accumulator.ts
|
|
5547
|
-
function createAnthropicStreamAccumulator() {
|
|
5548
|
-
return {
|
|
5549
|
-
model: "",
|
|
5550
|
-
inputTokens: 0,
|
|
5551
|
-
outputTokens: 0,
|
|
5552
|
-
stopReason: "",
|
|
5553
|
-
content: "",
|
|
5554
|
-
toolCalls: [],
|
|
5555
|
-
currentToolCall: null
|
|
5556
|
-
};
|
|
5557
|
-
}
|
|
5558
|
-
function processAnthropicEvent(event, acc) {
|
|
5559
|
-
switch (event.type) {
|
|
5560
|
-
case "content_block_delta":
|
|
5561
|
-
handleContentBlockDelta(event.delta, acc);
|
|
5562
|
-
break;
|
|
5563
|
-
case "content_block_start":
|
|
5564
|
-
handleContentBlockStart(event.content_block, acc);
|
|
5565
|
-
break;
|
|
5566
|
-
case "content_block_stop":
|
|
5567
|
-
handleContentBlockStop(acc);
|
|
5568
|
-
break;
|
|
5569
|
-
case "message_delta":
|
|
5570
|
-
handleMessageDelta(event.delta, event.usage, acc);
|
|
5571
|
-
break;
|
|
5572
|
-
default: break;
|
|
5573
|
-
}
|
|
5574
|
-
}
|
|
5575
|
-
function handleContentBlockDelta(delta, acc) {
|
|
5576
|
-
if (delta.type === "text_delta") acc.content += delta.text;
|
|
5577
|
-
else if (delta.type === "input_json_delta" && acc.currentToolCall) acc.currentToolCall.input += delta.partial_json;
|
|
5578
|
-
}
|
|
5579
|
-
function handleContentBlockStart(block, acc) {
|
|
5580
|
-
if (block.type === "tool_use") acc.currentToolCall = {
|
|
5581
|
-
id: block.id,
|
|
5582
|
-
name: block.name,
|
|
5583
|
-
input: ""
|
|
5584
|
-
};
|
|
5585
|
-
}
|
|
5586
|
-
function handleContentBlockStop(acc) {
|
|
5587
|
-
if (acc.currentToolCall) {
|
|
5588
|
-
acc.toolCalls.push(acc.currentToolCall);
|
|
5589
|
-
acc.currentToolCall = null;
|
|
5590
|
-
}
|
|
5591
|
-
}
|
|
5592
|
-
function handleMessageDelta(delta, usage, acc) {
|
|
5593
|
-
if (delta.stop_reason) acc.stopReason = delta.stop_reason;
|
|
5594
|
-
if (usage) {
|
|
5595
|
-
acc.inputTokens = usage.input_tokens ?? 0;
|
|
5596
|
-
acc.outputTokens = usage.output_tokens;
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
|
|
5600
5568
|
//#endregion
|
|
5601
5569
|
//#region src/routes/messages/stream-translation.ts
|
|
5602
5570
|
function isToolBlockOpen(state) {
|
|
@@ -5979,6 +5947,7 @@ const parseSubagentMarkerFromSystemReminder = (text) => {
|
|
|
5979
5947
|
async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOverride) {
|
|
5980
5948
|
const { payload: translatedPayload, toolNameMapping } = translateToOpenAI(anthropicPayload);
|
|
5981
5949
|
consola.debug("Translated OpenAI request payload:", JSON.stringify(translatedPayload));
|
|
5950
|
+
updateTrackerResolvedModel(ctx.trackingId, translatedPayload.model);
|
|
5982
5951
|
const selectedModel = state.models?.data.find((model) => model.id === translatedPayload.model);
|
|
5983
5952
|
const { finalPayload: openAIPayload, truncateResult } = await buildFinalPayload(translatedPayload, selectedModel);
|
|
5984
5953
|
if (truncateResult) ctx.truncateResult = truncateResult;
|
|
@@ -6194,9 +6163,19 @@ function recordStreamingResponse(acc, fallbackModel, ctx) {
|
|
|
6194
6163
|
|
|
6195
6164
|
//#endregion
|
|
6196
6165
|
//#region src/routes/messages/handler.ts
|
|
6166
|
+
function resolveModelFromBetaHeader(model, betaHeader) {
|
|
6167
|
+
if (!betaHeader || !/\bcontext-1m\b/.test(betaHeader)) return model;
|
|
6168
|
+
if (!model.startsWith("claude-")) return model;
|
|
6169
|
+
if (model.endsWith("-1m")) return model;
|
|
6170
|
+
const resolved = `${model}-1m`;
|
|
6171
|
+
consola.debug(`Detected context-1m in anthropic-beta header, resolving model: ${model} → ${resolved}`);
|
|
6172
|
+
return resolved;
|
|
6173
|
+
}
|
|
6197
6174
|
async function handleCompletion(c) {
|
|
6198
6175
|
const anthropicPayload = await c.req.json();
|
|
6199
6176
|
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
6177
|
+
const betaHeader = c.req.header("anthropic-beta");
|
|
6178
|
+
anthropicPayload.model = resolveModelFromBetaHeader(anthropicPayload.model, betaHeader);
|
|
6200
6179
|
logToolInfo(anthropicPayload);
|
|
6201
6180
|
const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
|
|
6202
6181
|
const initiatorOverride = subagentMarker ? "agent" : void 0;
|
|
@@ -6241,6 +6220,57 @@ function logToolInfo(anthropicPayload) {
|
|
|
6241
6220
|
}
|
|
6242
6221
|
}
|
|
6243
6222
|
|
|
6223
|
+
//#endregion
|
|
6224
|
+
//#region src/routes/messages/count-tokens-handler.ts
|
|
6225
|
+
/**
|
|
6226
|
+
* Handles token counting for Anthropic messages.
|
|
6227
|
+
*
|
|
6228
|
+
* For Anthropic models (vendor === "Anthropic"), uses the official Anthropic tokenizer.
|
|
6229
|
+
* For other models, uses GPT tokenizers with appropriate buffers.
|
|
6230
|
+
*
|
|
6231
|
+
* When auto-truncate is enabled and the request would exceed limits,
|
|
6232
|
+
* returns an inflated token count to trigger Claude Code's auto-compact mechanism.
|
|
6233
|
+
*/
|
|
6234
|
+
async function handleCountTokens(c) {
|
|
6235
|
+
try {
|
|
6236
|
+
const anthropicBeta = c.req.header("anthropic-beta");
|
|
6237
|
+
const anthropicPayload = await c.req.json();
|
|
6238
|
+
anthropicPayload.model = resolveModelFromBetaHeader(anthropicPayload.model, anthropicBeta);
|
|
6239
|
+
const { payload: openAIPayload } = translateToOpenAI(anthropicPayload);
|
|
6240
|
+
const selectedModel = state.models?.data.find((model) => model.id === openAIPayload.model);
|
|
6241
|
+
if (!selectedModel) {
|
|
6242
|
+
consola.warn("Model not found, returning default token count");
|
|
6243
|
+
return c.json({ input_tokens: 1 });
|
|
6244
|
+
}
|
|
6245
|
+
if (state.autoTruncate) {
|
|
6246
|
+
const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
|
|
6247
|
+
if (truncateCheck.needed) {
|
|
6248
|
+
const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
|
|
6249
|
+
const inflatedTokens = Math.floor(contextWindow * .95);
|
|
6250
|
+
consola.debug(`[count_tokens] Would trigger auto-truncate: ${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit}, returning inflated count: ${inflatedTokens}`);
|
|
6251
|
+
return c.json({ input_tokens: inflatedTokens });
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
const tokenizerName = selectedModel.capabilities?.tokenizer ?? "o200k_base";
|
|
6255
|
+
const tokenCount = await getTokenCount(openAIPayload, selectedModel);
|
|
6256
|
+
if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
|
|
6257
|
+
let mcpToolExist = false;
|
|
6258
|
+
if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__"));
|
|
6259
|
+
if (!mcpToolExist) {
|
|
6260
|
+
if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
|
|
6261
|
+
else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480;
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
let finalTokenCount = tokenCount.input + tokenCount.output;
|
|
6265
|
+
if (!(selectedModel.vendor === "Anthropic")) finalTokenCount = anthropicPayload.model.startsWith("grok") ? Math.round(finalTokenCount * 1.03) : Math.round(finalTokenCount * 1.05);
|
|
6266
|
+
consola.debug(`Token count: ${finalTokenCount} (tokenizer: ${tokenizerName})`);
|
|
6267
|
+
return c.json({ input_tokens: finalTokenCount });
|
|
6268
|
+
} catch (error) {
|
|
6269
|
+
consola.error("Error counting tokens:", error);
|
|
6270
|
+
return c.json({ input_tokens: 1 });
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6244
6274
|
//#endregion
|
|
6245
6275
|
//#region src/routes/messages/route.ts
|
|
6246
6276
|
const messageRoutes = new Hono();
|
|
@@ -6396,6 +6426,7 @@ const RESPONSES_ENDPOINT = "/responses";
|
|
|
6396
6426
|
const handleResponses = async (c) => {
|
|
6397
6427
|
const payload = await c.req.json();
|
|
6398
6428
|
consola.debug("Responses request payload:", JSON.stringify(payload));
|
|
6429
|
+
updateTrackerModel(c.get("trackingId"), payload.model);
|
|
6399
6430
|
useFunctionApplyPatch(payload);
|
|
6400
6431
|
removeWebSearchTool(payload);
|
|
6401
6432
|
if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
|