@dianshuv/copilot-api 0.2.0 → 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.
Files changed (2) hide show
  1. package/dist/main.mjs +518 -506
  2. 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.2.0";
1020
+ var version = "0.2.1";
1021
1021
 
1022
1022
  //#endregion
1023
1023
  //#region src/lib/adaptive-rate-limiter.ts
@@ -4907,6 +4907,211 @@ async function checkNeedsCompactionAnthropic(payload, model, config = {}) {
4907
4907
  };
4908
4908
  }
4909
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
+
4910
5115
  //#endregion
4911
5116
  //#region src/routes/messages/message-utils.ts
4912
5117
  function convertAnthropicMessages(messages) {
@@ -4976,56 +5181,111 @@ function mapOpenAIStopReasonToAnthropic(finishReason) {
4976
5181
  }
4977
5182
 
4978
5183
  //#endregion
4979
- //#region src/routes/messages/non-stream-translation.ts
4980
- const OPENAI_TOOL_NAME_LIMIT = 64;
4981
- /**
4982
- * Ensure all tool_use blocks have corresponding tool_result responses.
4983
- * This handles edge cases where conversation history may be incomplete:
4984
- * - Session interruptions where tool execution was cut off
4985
- * - Previous request failures
4986
- * - Client sending truncated history
4987
- *
4988
- * Adding placeholder responses prevents API errors and maintains protocol compliance.
4989
- */
4990
- function fixMessageSequence(messages) {
4991
- const fixedMessages = [];
4992
- for (let i = 0; i < messages.length; i++) {
4993
- const message = messages[i];
4994
- fixedMessages.push(message);
4995
- if (message.role === "assistant" && message.tool_calls && message.tool_calls.length > 0) {
4996
- const foundToolResponses = /* @__PURE__ */ new Set();
4997
- let j = i + 1;
4998
- while (j < messages.length && messages[j].role === "tool") {
4999
- const toolMessage = messages[j];
5000
- if (toolMessage.tool_call_id) foundToolResponses.add(toolMessage.tool_call_id);
5001
- j++;
5002
- }
5003
- for (const toolCall of message.tool_calls) if (!foundToolResponses.has(toolCall.id)) {
5004
- consola.debug(`Adding placeholder tool_result for ${toolCall.id}`);
5005
- fixedMessages.push({
5006
- role: "tool",
5007
- tool_call_id: toolCall.id,
5008
- content: "Tool execution was interrupted or failed."
5009
- });
5010
- }
5011
- }
5012
- }
5013
- return fixedMessages;
5014
- }
5015
- function translateToOpenAI(payload) {
5016
- const toolNameMapping = {
5017
- truncatedToOriginal: /* @__PURE__ */ new Map(),
5018
- originalToTruncated: /* @__PURE__ */ new Map()
5019
- };
5020
- const messages = translateAnthropicMessagesToOpenAI(payload.messages, payload.system, toolNameMapping);
5184
+ //#region src/routes/messages/stream-accumulator.ts
5185
+ function createAnthropicStreamAccumulator() {
5021
5186
  return {
5022
- payload: {
5023
- model: translateModelName(payload.model),
5024
- messages: fixMessageSequence(messages),
5025
- max_tokens: payload.max_tokens,
5026
- stop: payload.stop_sequences,
5027
- stream: payload.stream,
5028
- temperature: payload.temperature,
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);
5281
+ return {
5282
+ payload: {
5283
+ model: translateModelName(payload.model),
5284
+ messages: fixMessageSequence(messages),
5285
+ max_tokens: payload.max_tokens,
5286
+ stop: payload.stop_sequences,
5287
+ stream: payload.stream,
5288
+ temperature: payload.temperature,
5029
5289
  top_p: payload.top_p,
5030
5290
  user: payload.metadata?.user_id,
5031
5291
  tools: translateAnthropicToolsToOpenAI(payload.tools, toolNameMapping),
@@ -5073,7 +5333,8 @@ function translateModelName(model) {
5073
5333
  }
5074
5334
  if (/^claude-sonnet-4-5-\d+$/.test(model)) return "claude-sonnet-4.5";
5075
5335
  if (/^claude-sonnet-4-\d+$/.test(model)) return "claude-sonnet-4";
5076
- if (/^claude-opus-4-6$/.test(model)) return findLatestModel("claude-opus-4.6", "claude-opus-4.6");
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";
5077
5338
  if (/^claude-opus-4-5-\d+$/.test(model)) return "claude-opus-4.5";
5078
5339
  if (/^claude-opus-4-\d+$/.test(model)) return findLatestModel("claude-opus", "claude-opus-4.5");
5079
5340
  if (/^claude-haiku-4-5-\d+$/.test(model)) return "claude-haiku-4.5";
@@ -5144,474 +5405,164 @@ function handleAssistantMessage(message, toolNameMapping) {
5144
5405
  content: allTextContent || null,
5145
5406
  tool_calls: toolUseBlocks.map((toolUse) => ({
5146
5407
  id: toolUse.id,
5147
- type: "function",
5148
- function: {
5149
- name: getTruncatedToolName(toolUse.name, toolNameMapping),
5150
- arguments: JSON.stringify(toolUse.input)
5151
- }
5152
- }))
5153
- }] : [{
5154
- role: "assistant",
5155
- content: mapContent(message.content)
5156
- }];
5157
- }
5158
- function mapContent(content) {
5159
- if (typeof content === "string") return content;
5160
- if (!Array.isArray(content)) return null;
5161
- if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
5162
- const contentParts = [];
5163
- for (const block of content) switch (block.type) {
5164
- case "text":
5165
- contentParts.push({
5166
- type: "text",
5167
- text: block.text
5168
- });
5169
- break;
5170
- case "thinking":
5171
- contentParts.push({
5172
- type: "text",
5173
- text: block.thinking
5174
- });
5175
- break;
5176
- case "image":
5177
- contentParts.push({
5178
- type: "image_url",
5179
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
5180
- });
5181
- break;
5182
- }
5183
- return contentParts;
5184
- }
5185
- function getTruncatedToolName(originalName, toolNameMapping) {
5186
- if (originalName.length <= OPENAI_TOOL_NAME_LIMIT) return originalName;
5187
- const existingTruncated = toolNameMapping.originalToTruncated.get(originalName);
5188
- if (existingTruncated) return existingTruncated;
5189
- let hash = 0;
5190
- for (let i = 0; i < originalName.length; i++) {
5191
- const char = originalName.codePointAt(i) ?? 0;
5192
- hash = (hash << 5) - hash + char;
5193
- hash = Math.trunc(hash);
5194
- }
5195
- const hashSuffix = Math.abs(hash).toString(36).slice(0, 8);
5196
- const truncatedName = originalName.slice(0, OPENAI_TOOL_NAME_LIMIT - 9) + "_" + hashSuffix;
5197
- toolNameMapping.truncatedToOriginal.set(truncatedName, originalName);
5198
- toolNameMapping.originalToTruncated.set(originalName, truncatedName);
5199
- consola.debug(`Truncated tool name: "${originalName}" -> "${truncatedName}"`);
5200
- return truncatedName;
5201
- }
5202
- function translateAnthropicToolsToOpenAI(anthropicTools, toolNameMapping) {
5203
- if (!anthropicTools) return;
5204
- return anthropicTools.map((tool) => ({
5205
- type: "function",
5206
- function: {
5207
- name: getTruncatedToolName(tool.name, toolNameMapping),
5208
- description: tool.description,
5209
- parameters: tool.input_schema ?? {}
5210
- }
5211
- }));
5212
- }
5213
- function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice, toolNameMapping) {
5214
- if (!anthropicToolChoice) return;
5215
- switch (anthropicToolChoice.type) {
5216
- case "auto": return "auto";
5217
- case "any": return "required";
5218
- case "tool":
5219
- if (anthropicToolChoice.name) return {
5220
- type: "function",
5221
- function: { name: getTruncatedToolName(anthropicToolChoice.name, toolNameMapping) }
5222
- };
5223
- return;
5224
- case "none": return "none";
5225
- default: return;
5226
- }
5227
- }
5228
- /** Create empty response for edge case of no choices */
5229
- function createEmptyResponse(response) {
5230
- return {
5231
- id: response.id,
5232
- type: "message",
5233
- role: "assistant",
5234
- model: response.model,
5235
- content: [],
5236
- stop_reason: "end_turn",
5237
- stop_sequence: null,
5238
- usage: {
5239
- input_tokens: response.usage?.prompt_tokens ?? 0,
5240
- output_tokens: response.usage?.completion_tokens ?? 0
5241
- }
5242
- };
5243
- }
5244
- /** Build usage object from response */
5245
- function buildUsageObject(response) {
5246
- const cachedTokens = response.usage?.prompt_tokens_details?.cached_tokens;
5247
- return {
5248
- input_tokens: (response.usage?.prompt_tokens ?? 0) - (cachedTokens ?? 0),
5249
- output_tokens: response.usage?.completion_tokens ?? 0,
5250
- ...cachedTokens !== void 0 && { cache_read_input_tokens: cachedTokens }
5251
- };
5252
- }
5253
- function translateToAnthropic(response, toolNameMapping) {
5254
- if (response.choices.length === 0) return createEmptyResponse(response);
5255
- const allTextBlocks = [];
5256
- const allToolUseBlocks = [];
5257
- let stopReason = null;
5258
- stopReason = response.choices[0]?.finish_reason ?? stopReason;
5259
- for (const choice of response.choices) {
5260
- const textBlocks = getAnthropicTextBlocks(choice.message.content);
5261
- const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls, toolNameMapping);
5262
- allTextBlocks.push(...textBlocks);
5263
- allToolUseBlocks.push(...toolUseBlocks);
5264
- if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
5265
- }
5266
- return {
5267
- id: response.id,
5268
- type: "message",
5269
- role: "assistant",
5270
- model: response.model,
5271
- content: [...allTextBlocks, ...allToolUseBlocks],
5272
- stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
5273
- stop_sequence: null,
5274
- usage: buildUsageObject(response)
5275
- };
5276
- }
5277
- function getAnthropicTextBlocks(messageContent) {
5278
- if (typeof messageContent === "string") return [{
5279
- type: "text",
5280
- text: messageContent
5281
- }];
5282
- if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
5283
- type: "text",
5284
- text: part.text
5285
- }));
5286
- return [];
5287
- }
5288
- function getAnthropicToolUseBlocks(toolCalls, toolNameMapping) {
5289
- if (!toolCalls) return [];
5290
- return toolCalls.map((toolCall) => {
5291
- let input = {};
5292
- try {
5293
- input = JSON.parse(toolCall.function.arguments);
5294
- } catch (error) {
5295
- consola.warn(`Failed to parse tool call arguments for ${toolCall.function.name}:`, error);
5296
- }
5297
- const originalName = toolNameMapping?.truncatedToOriginal.get(toolCall.function.name) ?? toolCall.function.name;
5298
- return {
5299
- type: "tool_use",
5300
- id: toolCall.id,
5301
- name: originalName,
5302
- input
5303
- };
5304
- });
5305
- }
5306
-
5307
- //#endregion
5308
- //#region src/routes/messages/count-tokens-handler.ts
5309
- /**
5310
- * Handles token counting for Anthropic messages.
5311
- *
5312
- * For Anthropic models (vendor === "Anthropic"), uses the official Anthropic tokenizer.
5313
- * For other models, uses GPT tokenizers with appropriate buffers.
5314
- *
5315
- * When auto-truncate is enabled and the request would exceed limits,
5316
- * returns an inflated token count to trigger Claude Code's auto-compact mechanism.
5317
- */
5318
- async function handleCountTokens(c) {
5319
- try {
5320
- const anthropicBeta = c.req.header("anthropic-beta");
5321
- const anthropicPayload = await c.req.json();
5322
- const { payload: openAIPayload } = translateToOpenAI(anthropicPayload);
5323
- const selectedModel = state.models?.data.find((model) => model.id === openAIPayload.model);
5324
- if (!selectedModel) {
5325
- consola.warn("Model not found, returning default token count");
5326
- return c.json({ input_tokens: 1 });
5327
- }
5328
- if (state.autoTruncate) {
5329
- const truncateCheck = await checkNeedsCompactionAnthropic(anthropicPayload, selectedModel);
5330
- if (truncateCheck.needed) {
5331
- const contextWindow = selectedModel.capabilities?.limits?.max_context_window_tokens ?? 2e5;
5332
- const inflatedTokens = Math.floor(contextWindow * .95);
5333
- consola.debug(`[count_tokens] Would trigger auto-truncate: ${truncateCheck.currentTokens} tokens > ${truncateCheck.tokenLimit}, returning inflated count: ${inflatedTokens}`);
5334
- return c.json({ input_tokens: inflatedTokens });
5335
- }
5336
- }
5337
- const tokenizerName = selectedModel.capabilities?.tokenizer ?? "o200k_base";
5338
- const tokenCount = await getTokenCount(openAIPayload, selectedModel);
5339
- if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {
5340
- let mcpToolExist = false;
5341
- if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__"));
5342
- if (!mcpToolExist) {
5343
- if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346;
5344
- else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480;
5345
- }
5346
- }
5347
- let finalTokenCount = tokenCount.input + tokenCount.output;
5348
- if (!(selectedModel.vendor === "Anthropic")) finalTokenCount = anthropicPayload.model.startsWith("grok") ? Math.round(finalTokenCount * 1.03) : Math.round(finalTokenCount * 1.05);
5349
- consola.debug(`Token count: ${finalTokenCount} (tokenizer: ${tokenizerName})`);
5350
- return c.json({ input_tokens: finalTokenCount });
5351
- } catch (error) {
5352
- consola.error("Error counting tokens:", error);
5353
- return c.json({ input_tokens: 1 });
5354
- }
5355
- }
5356
-
5357
- //#endregion
5358
- //#region src/services/copilot/create-anthropic-messages.ts
5359
- /**
5360
- * Direct Anthropic-style message API for Copilot.
5361
- * Used when the model vendor is Anthropic and supports /v1/messages endpoint.
5362
- */
5363
- /**
5364
- * Fields that are supported by Copilot's Anthropic API endpoint.
5365
- * Any other fields in the incoming request will be stripped.
5366
- */
5367
- const COPILOT_SUPPORTED_FIELDS = new Set([
5368
- "model",
5369
- "messages",
5370
- "max_tokens",
5371
- "system",
5372
- "metadata",
5373
- "stop_sequences",
5374
- "stream",
5375
- "temperature",
5376
- "top_p",
5377
- "top_k",
5378
- "tools",
5379
- "tool_choice",
5380
- "thinking",
5381
- "service_tier"
5382
- ]);
5383
- /**
5384
- * Filter payload to only include fields supported by Copilot's Anthropic API.
5385
- * This prevents errors like "Extra inputs are not permitted" for unsupported
5386
- * fields like `output_config`.
5387
- *
5388
- * Also converts server-side tools (web_search, etc.) to custom tools.
5389
- */
5390
- function filterPayloadForCopilot(payload) {
5391
- const filtered = {};
5392
- const unsupportedFields = [];
5393
- for (const [key, value] of Object.entries(payload)) if (COPILOT_SUPPORTED_FIELDS.has(key)) filtered[key] = value;
5394
- else unsupportedFields.push(key);
5395
- if (unsupportedFields.length > 0) consola.debug(`[DirectAnthropic] Filtered unsupported fields: ${unsupportedFields.join(", ")}`);
5396
- if (filtered.tools) filtered.tools = convertServerToolsToCustom(filtered.tools);
5397
- return filtered;
5398
- }
5399
- /**
5400
- * Adjust max_tokens if thinking is enabled.
5401
- * According to Anthropic docs, max_tokens must be greater than thinking.budget_tokens.
5402
- * max_tokens = thinking_budget + response_tokens
5403
- */
5404
- function adjustMaxTokensForThinking(payload) {
5405
- const thinking = payload.thinking;
5406
- if (!thinking) return payload;
5407
- const budgetTokens = thinking.budget_tokens;
5408
- if (!budgetTokens) return payload;
5409
- if (payload.max_tokens <= budgetTokens) {
5410
- const newMaxTokens = budgetTokens + Math.min(16384, budgetTokens);
5411
- consola.debug(`[DirectAnthropic] Adjusted max_tokens: ${payload.max_tokens} → ${newMaxTokens} (thinking.budget_tokens=${budgetTokens})`);
5412
- return {
5413
- ...payload,
5414
- max_tokens: newMaxTokens
5415
- };
5416
- }
5417
- return payload;
5418
- }
5419
- /**
5420
- * Create messages using Anthropic-style API directly.
5421
- * This bypasses the OpenAI translation layer for Anthropic models.
5422
- */
5423
- async function createAnthropicMessages(payload, options) {
5424
- if (!state.copilotToken) throw new Error("Copilot token not found");
5425
- let filteredPayload = filterPayloadForCopilot(payload);
5426
- filteredPayload = adjustMaxTokensForThinking(filteredPayload);
5427
- const enableVision = filteredPayload.messages.some((msg) => {
5428
- if (typeof msg.content === "string") return false;
5429
- return msg.content.some((block) => block.type === "image");
5430
- });
5431
- const isAgentCall = filteredPayload.messages.some((msg) => msg.role === "assistant");
5432
- const headers = {
5433
- ...copilotHeaders(state, enableVision),
5434
- "X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
5435
- "anthropic-version": "2023-06-01"
5436
- };
5437
- consola.debug("Sending direct Anthropic request to Copilot /v1/messages");
5438
- const response = await fetch(`${copilotBaseUrl(state)}/v1/messages`, {
5439
- method: "POST",
5440
- headers,
5441
- body: JSON.stringify(filteredPayload)
5442
- });
5443
- if (!response.ok) {
5444
- consola.debug("Request failed:", {
5445
- model: filteredPayload.model,
5446
- max_tokens: filteredPayload.max_tokens,
5447
- stream: filteredPayload.stream,
5448
- tools: filteredPayload.tools?.map((t) => ({
5449
- name: t.name,
5450
- type: t.type
5451
- })),
5452
- thinking: filteredPayload.thinking,
5453
- messageCount: filteredPayload.messages.length
5454
- });
5455
- throw await HTTPError.fromResponse("Failed to create Anthropic messages", response, filteredPayload.model);
5456
- }
5457
- if (payload.stream) return events(response);
5458
- return await response.json();
5459
- }
5460
- const SERVER_TOOL_CONFIGS = {
5461
- web_search: {
5462
- 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.",
5463
- input_schema: {
5464
- type: "object",
5465
- properties: { query: {
5466
- type: "string",
5467
- description: "The search query"
5468
- } },
5469
- required: ["query"]
5470
- }
5471
- },
5472
- web_fetch: {
5473
- description: "Fetch content from a URL. NOTE: This is a client-side tool - the client must fetch the URL and return the content.",
5474
- input_schema: {
5475
- type: "object",
5476
- properties: { url: {
5477
- type: "string",
5478
- description: "The URL to fetch"
5479
- } },
5480
- required: ["url"]
5481
- }
5482
- },
5483
- code_execution: {
5484
- description: "Execute code in a sandbox. NOTE: This is a client-side tool - the client must execute the code.",
5485
- input_schema: {
5486
- type: "object",
5487
- properties: {
5488
- code: {
5489
- type: "string",
5490
- description: "The code to execute"
5491
- },
5492
- language: {
5493
- type: "string",
5494
- description: "The programming language"
5495
- }
5496
- },
5497
- required: ["code"]
5498
- }
5499
- },
5500
- computer: {
5501
- description: "Control computer desktop. NOTE: This is a client-side tool - the client must handle computer control.",
5502
- input_schema: {
5503
- type: "object",
5504
- properties: { action: {
5505
- type: "string",
5506
- description: "The action to perform"
5507
- } },
5508
- required: ["action"]
5509
- }
5510
- }
5511
- };
5512
- /**
5513
- * Check if a tool is a server-side tool that needs conversion.
5514
- */
5515
- function getServerToolPrefix(tool) {
5516
- if (tool.type) {
5517
- for (const prefix of Object.keys(SERVER_TOOL_CONFIGS)) if (tool.type.startsWith(prefix)) return prefix;
5518
- }
5519
- return null;
5520
- }
5521
- /**
5522
- * Convert server-side tools to custom tools, or pass them through unchanged.
5523
- * This allows them to be passed to the API and handled by the client.
5524
- *
5525
- * Note: Server-side tools are only converted if state.rewriteAnthropicTools is enabled.
5526
- */
5527
- function convertServerToolsToCustom(tools) {
5528
- if (!tools) return;
5529
- const result = [];
5530
- for (const tool of tools) {
5531
- const serverToolPrefix = getServerToolPrefix(tool);
5532
- if (serverToolPrefix) {
5533
- const config = SERVER_TOOL_CONFIGS[serverToolPrefix];
5534
- if (!state.rewriteAnthropicTools) {
5535
- consola.debug(`[DirectAnthropic] Passing ${serverToolPrefix} through unchanged (use --rewrite-anthropic-tools to convert)`);
5536
- result.push(tool);
5537
- continue;
5538
- }
5539
- if (config.remove) {
5540
- consola.warn(`[DirectAnthropic] Removing unsupported server tool: ${tool.name}. Reason: ${config.removalReason}`);
5541
- continue;
5408
+ type: "function",
5409
+ function: {
5410
+ name: getTruncatedToolName(toolUse.name, toolNameMapping),
5411
+ arguments: JSON.stringify(toolUse.input)
5542
5412
  }
5543
- consola.debug(`[DirectAnthropic] Converting server tool to custom: ${tool.name} (type: ${tool.type})`);
5544
- result.push({
5545
- name: tool.name,
5546
- description: config.description,
5547
- input_schema: config.input_schema
5413
+ }))
5414
+ }] : [{
5415
+ role: "assistant",
5416
+ content: mapContent(message.content)
5417
+ }];
5418
+ }
5419
+ function mapContent(content) {
5420
+ if (typeof content === "string") return content;
5421
+ if (!Array.isArray(content)) return null;
5422
+ if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n");
5423
+ const contentParts = [];
5424
+ for (const block of content) switch (block.type) {
5425
+ case "text":
5426
+ contentParts.push({
5427
+ type: "text",
5428
+ text: block.text
5548
5429
  });
5549
- } else result.push(tool);
5430
+ break;
5431
+ case "thinking":
5432
+ contentParts.push({
5433
+ type: "text",
5434
+ text: block.thinking
5435
+ });
5436
+ break;
5437
+ case "image":
5438
+ contentParts.push({
5439
+ type: "image_url",
5440
+ image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
5441
+ });
5442
+ break;
5550
5443
  }
5551
- return result.length > 0 ? result : void 0;
5444
+ return contentParts;
5552
5445
  }
5553
- /**
5554
- * Check if a model supports direct Anthropic API.
5555
- * Returns true if redirect is disabled (direct API is on) and the model is from Anthropic vendor.
5556
- */
5557
- function supportsDirectAnthropicApi(modelId) {
5558
- if (state.redirectAnthropic) return false;
5559
- return (state.models?.data.find((m) => m.id === modelId))?.vendor === "Anthropic";
5446
+ function getTruncatedToolName(originalName, toolNameMapping) {
5447
+ if (originalName.length <= OPENAI_TOOL_NAME_LIMIT) return originalName;
5448
+ const existingTruncated = toolNameMapping.originalToTruncated.get(originalName);
5449
+ if (existingTruncated) return existingTruncated;
5450
+ let hash = 0;
5451
+ for (let i = 0; i < originalName.length; i++) {
5452
+ const char = originalName.codePointAt(i) ?? 0;
5453
+ hash = (hash << 5) - hash + char;
5454
+ hash = Math.trunc(hash);
5455
+ }
5456
+ const hashSuffix = Math.abs(hash).toString(36).slice(0, 8);
5457
+ const truncatedName = originalName.slice(0, OPENAI_TOOL_NAME_LIMIT - 9) + "_" + hashSuffix;
5458
+ toolNameMapping.truncatedToOriginal.set(truncatedName, originalName);
5459
+ toolNameMapping.originalToTruncated.set(originalName, truncatedName);
5460
+ consola.debug(`Truncated tool name: "${originalName}" -> "${truncatedName}"`);
5461
+ return truncatedName;
5560
5462
  }
5561
-
5562
- //#endregion
5563
- //#region src/routes/messages/stream-accumulator.ts
5564
- function createAnthropicStreamAccumulator() {
5565
- return {
5566
- model: "",
5567
- inputTokens: 0,
5568
- outputTokens: 0,
5569
- stopReason: "",
5570
- content: "",
5571
- toolCalls: [],
5572
- currentToolCall: null
5573
- };
5463
+ function translateAnthropicToolsToOpenAI(anthropicTools, toolNameMapping) {
5464
+ if (!anthropicTools) return;
5465
+ return anthropicTools.map((tool) => ({
5466
+ type: "function",
5467
+ function: {
5468
+ name: getTruncatedToolName(tool.name, toolNameMapping),
5469
+ description: tool.description,
5470
+ parameters: tool.input_schema ?? {}
5471
+ }
5472
+ }));
5574
5473
  }
5575
- function processAnthropicEvent(event, acc) {
5576
- switch (event.type) {
5577
- case "content_block_delta":
5578
- handleContentBlockDelta(event.delta, acc);
5579
- break;
5580
- case "content_block_start":
5581
- handleContentBlockStart(event.content_block, acc);
5582
- break;
5583
- case "content_block_stop":
5584
- handleContentBlockStop(acc);
5585
- break;
5586
- case "message_delta":
5587
- handleMessageDelta(event.delta, event.usage, acc);
5588
- break;
5589
- default: break;
5474
+ function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice, toolNameMapping) {
5475
+ if (!anthropicToolChoice) return;
5476
+ switch (anthropicToolChoice.type) {
5477
+ case "auto": return "auto";
5478
+ case "any": return "required";
5479
+ case "tool":
5480
+ if (anthropicToolChoice.name) return {
5481
+ type: "function",
5482
+ function: { name: getTruncatedToolName(anthropicToolChoice.name, toolNameMapping) }
5483
+ };
5484
+ return;
5485
+ case "none": return "none";
5486
+ default: return;
5590
5487
  }
5591
5488
  }
5592
- function handleContentBlockDelta(delta, acc) {
5593
- if (delta.type === "text_delta") acc.content += delta.text;
5594
- else if (delta.type === "input_json_delta" && acc.currentToolCall) acc.currentToolCall.input += delta.partial_json;
5489
+ /** Create empty response for edge case of no choices */
5490
+ function createEmptyResponse(response) {
5491
+ return {
5492
+ id: response.id,
5493
+ type: "message",
5494
+ role: "assistant",
5495
+ model: response.model,
5496
+ content: [],
5497
+ stop_reason: "end_turn",
5498
+ stop_sequence: null,
5499
+ usage: {
5500
+ input_tokens: response.usage?.prompt_tokens ?? 0,
5501
+ output_tokens: response.usage?.completion_tokens ?? 0
5502
+ }
5503
+ };
5595
5504
  }
5596
- function handleContentBlockStart(block, acc) {
5597
- if (block.type === "tool_use") acc.currentToolCall = {
5598
- id: block.id,
5599
- name: block.name,
5600
- input: ""
5505
+ /** Build usage object from response */
5506
+ function buildUsageObject(response) {
5507
+ const cachedTokens = response.usage?.prompt_tokens_details?.cached_tokens;
5508
+ return {
5509
+ input_tokens: (response.usage?.prompt_tokens ?? 0) - (cachedTokens ?? 0),
5510
+ output_tokens: response.usage?.completion_tokens ?? 0,
5511
+ ...cachedTokens !== void 0 && { cache_read_input_tokens: cachedTokens }
5601
5512
  };
5602
5513
  }
5603
- function handleContentBlockStop(acc) {
5604
- if (acc.currentToolCall) {
5605
- acc.toolCalls.push(acc.currentToolCall);
5606
- acc.currentToolCall = null;
5514
+ function translateToAnthropic(response, toolNameMapping) {
5515
+ if (response.choices.length === 0) return createEmptyResponse(response);
5516
+ const allTextBlocks = [];
5517
+ const allToolUseBlocks = [];
5518
+ let stopReason = null;
5519
+ stopReason = response.choices[0]?.finish_reason ?? stopReason;
5520
+ for (const choice of response.choices) {
5521
+ const textBlocks = getAnthropicTextBlocks(choice.message.content);
5522
+ const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls, toolNameMapping);
5523
+ allTextBlocks.push(...textBlocks);
5524
+ allToolUseBlocks.push(...toolUseBlocks);
5525
+ if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
5607
5526
  }
5527
+ return {
5528
+ id: response.id,
5529
+ type: "message",
5530
+ role: "assistant",
5531
+ model: response.model,
5532
+ content: [...allTextBlocks, ...allToolUseBlocks],
5533
+ stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
5534
+ stop_sequence: null,
5535
+ usage: buildUsageObject(response)
5536
+ };
5608
5537
  }
5609
- function handleMessageDelta(delta, usage, acc) {
5610
- if (delta.stop_reason) acc.stopReason = delta.stop_reason;
5611
- if (usage) {
5612
- acc.inputTokens = usage.input_tokens ?? 0;
5613
- acc.outputTokens = usage.output_tokens;
5614
- }
5538
+ function getAnthropicTextBlocks(messageContent) {
5539
+ if (typeof messageContent === "string") return [{
5540
+ type: "text",
5541
+ text: messageContent
5542
+ }];
5543
+ if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
5544
+ type: "text",
5545
+ text: part.text
5546
+ }));
5547
+ return [];
5548
+ }
5549
+ function getAnthropicToolUseBlocks(toolCalls, toolNameMapping) {
5550
+ if (!toolCalls) return [];
5551
+ return toolCalls.map((toolCall) => {
5552
+ let input = {};
5553
+ try {
5554
+ input = JSON.parse(toolCall.function.arguments);
5555
+ } catch (error) {
5556
+ consola.warn(`Failed to parse tool call arguments for ${toolCall.function.name}:`, error);
5557
+ }
5558
+ const originalName = toolNameMapping?.truncatedToOriginal.get(toolCall.function.name) ?? toolCall.function.name;
5559
+ return {
5560
+ type: "tool_use",
5561
+ id: toolCall.id,
5562
+ name: originalName,
5563
+ input
5564
+ };
5565
+ });
5615
5566
  }
5616
5567
 
5617
5568
  //#endregion
@@ -6212,9 +6163,19 @@ function recordStreamingResponse(acc, fallbackModel, ctx) {
6212
6163
 
6213
6164
  //#endregion
6214
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
+ }
6215
6174
  async function handleCompletion(c) {
6216
6175
  const anthropicPayload = await c.req.json();
6217
6176
  consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
6177
+ const betaHeader = c.req.header("anthropic-beta");
6178
+ anthropicPayload.model = resolveModelFromBetaHeader(anthropicPayload.model, betaHeader);
6218
6179
  logToolInfo(anthropicPayload);
6219
6180
  const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
6220
6181
  const initiatorOverride = subagentMarker ? "agent" : void 0;
@@ -6259,6 +6220,57 @@ function logToolInfo(anthropicPayload) {
6259
6220
  }
6260
6221
  }
6261
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
+
6262
6274
  //#endregion
6263
6275
  //#region src/routes/messages/route.ts
6264
6276
  const messageRoutes = new Hono();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",