copilot-relay 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -831,6 +831,7 @@ const normalizeClaudeModelId = (model, contextWindowTokens) => {
831
831
  return contextWindowTokens !== void 0 && contextWindowTokens !== 1e6 ? normalized : `${normalized}[1m]`;
832
832
  };
833
833
  const normalizeCopilotModelId = (model) => normalizeOneMillionContextModel(model) ?? model;
834
+ const isClaudeModelId = (model) => model.startsWith("claude-");
834
835
  const getConfiguredModelRouting = () => getRuntimeState().modelRouting ?? defaultModelRouting;
835
836
  const getModelRouting = () => {
836
837
  const routing = getConfiguredModelRouting();
@@ -2570,12 +2571,12 @@ const copilotEndpoints = [
2570
2571
  "/responses",
2571
2572
  "/v1/messages"
2572
2573
  ];
2573
- const legacyResponsesPattern = /^(?:gpt-5\.5|gpt-5\.6|gpt-6-astra)(?:-|$)/i;
2574
+ const legacyResponsesPattern = /^(?:gpt-5\.4(?!-mini)|gpt-5\.5|gpt-5\.6|gpt-6-astra)(?:-|$)/i;
2574
2575
  function selectCopilotEndpoint(config, model) {
2575
2576
  const capabilities = getCachedCopilotModel(config, model);
2576
2577
  const advertisedEndpoints = capabilities?.supportedEndpoints;
2577
2578
  if (capabilities?.type !== void 0 && capabilities.type !== "chat") return { reason: "unsupported-model-type" };
2578
- const isClaude = model.startsWith("claude-");
2579
+ const isClaude = isClaudeModelId(model);
2579
2580
  const protocolMode = config.claudeUpstreamApi ?? "chat-completions";
2580
2581
  if (isClaude && protocolMode === "messages") return {
2581
2582
  endpoint: "/v1/messages",
@@ -2911,7 +2912,7 @@ const createClaudeToolNameMapper = (tools, options = {}) => {
2911
2912
  function translateModelName(model) {
2912
2913
  return routeModelId(model);
2913
2914
  }
2914
- function translateToOpenAI(payload, _settings, toolNameMapper) {
2915
+ function translateToOpenAI(payload, _settings, toolNameMapper, options = {}) {
2915
2916
  const model = translateModelName(payload.model);
2916
2917
  const mapper = toolNameMapper ?? createClaudeToolNameMapper(payload.tools, { ...getToolNameMapperOptionsForModel(model) });
2917
2918
  const tools = translateClaudeToolsToOpenAI(payload.tools, mapper);
@@ -2919,7 +2920,7 @@ function translateToOpenAI(payload, _settings, toolNameMapper) {
2919
2920
  validateClaudeMessages(payload.messages);
2920
2921
  return {
2921
2922
  model,
2922
- messages: normalizeFinalAssistantPrefill$1(translateClaudeMessagesToOpenAI(payload.messages, payload.system, mapper)),
2923
+ messages: normalizeFinalAssistantPrefill$1(translateClaudeMessagesToOpenAI(payload.messages, payload.system, mapper, options.endpoint === "/chat/completions" && isClaudeModelId(model))),
2923
2924
  max_tokens: payload.max_tokens,
2924
2925
  stop: payload.stop_sequences,
2925
2926
  stream: payload.stream,
@@ -2945,20 +2946,50 @@ function validateClaudeMessages(messages, native = false) {
2945
2946
  if (typeof message.content !== "string" && (!Array.isArray(message.content) || message.content.some((block) => !block || block.type !== "text" || typeof block.text !== "string"))) throw invalidMessage("System message content must contain only text.");
2946
2947
  }
2947
2948
  }
2948
- function translateClaudeMessagesToOpenAI(claudeMessages, system, toolNameMapper) {
2949
- const systemMessages = handleSystemPrompt(system);
2950
- const otherMessages = claudeMessages.flatMap((message) => {
2951
- switch (message.role) {
2952
- case "user": return handleUserMessage(message);
2953
- case "assistant": return handleAssistantMessage(message, toolNameMapper);
2954
- case "system":
2955
- if (message.output_config !== void 0 && (typeof message.content === "string" ? message.content.length === 0 : message.content.every((block) => block.text.length === 0))) return [];
2956
- return handleSystemPrompt(message.content);
2957
- default: throw invalidMessage("Unsupported message role.");
2958
- }
2959
- });
2960
- return [...systemMessages, ...otherMessages];
2949
+ function translateClaudeMessagesToOpenAI(claudeMessages, system, toolNameMapper, claudeChatRoute) {
2950
+ const translated = handleSystemPrompt(system);
2951
+ if (claudeChatRoute && hasCacheBreakpoint(system)) markCacheBreakpoint(translated.at(-1));
2952
+ for (const message of claudeMessages) {
2953
+ const start$1 = translated.length;
2954
+ translated.push(...translateClaudeMessage(message, toolNameMapper, claudeChatRoute));
2955
+ if (claudeChatRoute) markMessageBreakpoints(message, translated, start$1);
2956
+ }
2957
+ return translated;
2958
+ }
2959
+ function markMessageBreakpoints(message, translated, start$1) {
2960
+ if (message.role !== "user" || !Array.isArray(message.content)) {
2961
+ if (hasCacheBreakpoint(message.content)) markCacheBreakpoint(translated.at(-1));
2962
+ return;
2963
+ }
2964
+ let toolIndex = start$1;
2965
+ for (const block of message.content) if (block.type === "tool_result") {
2966
+ if (isCacheBreakpoint(block)) markCacheBreakpoint(translated[toolIndex]);
2967
+ toolIndex += 1;
2968
+ } else if (isCacheBreakpoint(block)) markCacheBreakpoint(translated.at(-1));
2969
+ }
2970
+ function translateClaudeMessage(message, toolNameMapper, claudeChatRoute) {
2971
+ switch (message.role) {
2972
+ case "user": return handleUserMessage(message);
2973
+ case "assistant": return handleAssistantMessage(message, toolNameMapper);
2974
+ case "system":
2975
+ if (message.output_config !== void 0 && (typeof message.content === "string" ? message.content.length === 0 : message.content.every((block) => block.text.length === 0))) return [];
2976
+ return claudeChatRoute ? toSystemReminder(message.content) : handleSystemPrompt(message.content);
2977
+ default: throw invalidMessage("Unsupported message role.");
2978
+ }
2961
2979
  }
2980
+ function toSystemReminder(content) {
2981
+ const text$1 = typeof content === "string" ? content : content.map((block) => block.text).join("\n\n");
2982
+ if (text$1.trim().length === 0) return [];
2983
+ return [{
2984
+ role: "user",
2985
+ content: `<system-reminder>\n${text$1}\n</system-reminder>`
2986
+ }];
2987
+ }
2988
+ const isCacheBreakpoint = (block) => "cache_control" in block && Boolean(block.cache_control);
2989
+ const hasCacheBreakpoint = (content) => Array.isArray(content) && content.some(isCacheBreakpoint);
2990
+ const markCacheBreakpoint = (message) => {
2991
+ if (message) message.copilot_cache_control = { type: "ephemeral" };
2992
+ };
2962
2993
  const invalidMessage = (message) => new HTTPError(message, new Response(JSON.stringify({
2963
2994
  type: "error",
2964
2995
  error: {
@@ -4818,14 +4849,14 @@ async function logUpstreamError(message, response, context, signal, timeoutMs) {
4818
4849
  route: context.route,
4819
4850
  model: context.model,
4820
4851
  requestId: context.requestId,
4821
- request: context.request,
4822
4852
  response: {
4823
4853
  status: response.status,
4824
4854
  statusText: response.statusText || void 0,
4825
4855
  url: response.url || void 0,
4826
4856
  headers: Object.fromEntries(response.headers.entries()),
4827
4857
  body: errorBody || void 0
4828
- }
4858
+ },
4859
+ request: context.request
4829
4860
  });
4830
4861
  return detail;
4831
4862
  }
@@ -5452,6 +5483,10 @@ const createQueuedClaudeStreamWriter = (write) => {
5452
5483
  const writeClaudeStreamEvents = async (events$1, writeEvent) => {
5453
5484
  for (const event of events$1) await writeEvent(event);
5454
5485
  };
5486
+ const translationEndpoint = (config, model) => {
5487
+ const selection = selectCopilotEndpoint(config, normalizeCopilotModelId(model));
5488
+ return selection.endpoint && !selection.responsesFallback ? selection.endpoint : void 0;
5489
+ };
5455
5490
  const handleClaudeMessageRequest = async (config, claudePayload, requestSignal, requestId, writeEvent, requestHeaders) => {
5456
5491
  const upstreamModel = translateModelName(claudePayload.model);
5457
5492
  if (shouldUseNativeMessages(config, upstreamModel)) return handleNativeMessages(config, {
@@ -5465,7 +5500,7 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
5465
5500
  const shouldLetModelDecideWebSearch = hasClaudeWebSearch(claudePayload);
5466
5501
  const decisionPayload = shouldLetModelDecideWebSearch ? prepareClaudeWebSearchDecisionPayload(claudePayload) : claudePayload;
5467
5502
  const toolNameMapper = createClaudeToolNameMapper(decisionPayload.tools, { ...getToolNameMapperOptionsForModel(upstreamModel) });
5468
- const openAIPayload = translateToOpenAI(decisionPayload, void 0, toolNameMapper);
5503
+ const openAIPayload = translateToOpenAI(decisionPayload, void 0, toolNameMapper, { endpoint: translationEndpoint(config, upstreamModel) });
5469
5504
  const canStreamWebSearchDecision = shouldLetModelDecideWebSearch && !!writeEvent;
5470
5505
  if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) openAIPayload.stream = false;
5471
5506
  const response = await createChatCompletions(config, openAIPayload, {
@@ -5717,7 +5752,7 @@ claudeRoutes.post("/messages/count_tokens", async (c) => {
5717
5752
  const { output_config: _outputConfig, clear_at: _clearAt,...textMessage } = message;
5718
5753
  return textMessage;
5719
5754
  })
5720
- } : claudePayload);
5755
+ } : claudePayload, void 0, void 0, { endpoint: translationEndpoint(c.get("config"), translateModelName(claudePayload.model)) });
5721
5756
  const exposedModels = getExposedModelIds();
5722
5757
  const upstreamModel = getCachedCopilotModel(c.get("config"), openAIPayload.model);
5723
5758
  const hasDiscoveredTokenizer = upstreamModel?.tokenizer !== void 0 && isSupportedTokenizer(upstreamModel.tokenizer);