copilot-relay 0.4.0 → 0.4.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.js CHANGED
@@ -2299,6 +2299,22 @@ async function boundModelOutputTokens(config, model, requested) {
2299
2299
 
2300
2300
  //#endregion
2301
2301
  //#region src/claude/utils.ts
2302
+ const isEffortOnlyControl = (control) => control !== null && typeof control === "object" && !Array.isArray(control) && Object.keys(control).length === 1 && "effort" in control && isConfiguredReasoningEffort(control.effort);
2303
+ function getClaudeTurnEffort(payload) {
2304
+ let requested = getRequestReasoningEffort(payload);
2305
+ let pending;
2306
+ if (Array.isArray(payload.messages)) {
2307
+ for (const message of payload.messages) if (message?.role === "system" && message.clear_at === void 0 && isEffortOnlyControl(message.output_config)) pending = message.output_config.effort;
2308
+ else if (message?.role === "user" && pending !== void 0) {
2309
+ requested = pending;
2310
+ pending = void 0;
2311
+ }
2312
+ }
2313
+ return {
2314
+ requested,
2315
+ effective: resolveReasoningEffort(requested)
2316
+ };
2317
+ }
2302
2318
  function mapOpenAIStopReasonToClaude(finishReason) {
2303
2319
  if (finishReason === null) return null;
2304
2320
  return {
@@ -2379,8 +2395,8 @@ function translateToOpenAI(payload, _settings, toolNameMapper) {
2379
2395
  const model = translateModelName(payload.model);
2380
2396
  const mapper = toolNameMapper ?? createClaudeToolNameMapper(payload.tools, { ...getToolNameMapperOptionsForModel(model) });
2381
2397
  const tools = translateClaudeToolsToOpenAI(payload.tools, mapper);
2382
- const effort = resolveReasoningEffort(getRequestReasoningEffort(payload));
2383
- validateClaudeMessages(payload.messages, false, effort);
2398
+ const { effective: effort } = getClaudeTurnEffort(payload);
2399
+ validateClaudeMessages(payload.messages);
2384
2400
  return {
2385
2401
  model,
2386
2402
  messages: normalizeFinalAssistantPrefill$1(translateClaudeMessagesToOpenAI(payload.messages, payload.system, mapper)),
@@ -2395,7 +2411,7 @@ function translateToOpenAI(payload, _settings, toolNameMapper) {
2395
2411
  tool_choice: tools && tools.length > 0 ? translateClaudeToolChoiceToOpenAI(payload.tool_choice, mapper) : void 0
2396
2412
  };
2397
2413
  }
2398
- function validateClaudeMessages(messages, native = false, effort) {
2414
+ function validateClaudeMessages(messages, native = false) {
2399
2415
  if (!Array.isArray(messages)) throw invalidMessage("Messages must be an array.");
2400
2416
  for (const message of messages) {
2401
2417
  if (!message || ![
@@ -2405,8 +2421,7 @@ function validateClaudeMessages(messages, native = false, effort) {
2405
2421
  ].includes(message.role)) throw invalidMessage("Unsupported message role.");
2406
2422
  if (message.role !== "system") continue;
2407
2423
  const control = message.output_config;
2408
- const redundantEffort = control !== null && typeof control === "object" && !Array.isArray(control) && Object.keys(control).length === 1 && isConfiguredReasoningEffort(control.effort) && control.effort === effort;
2409
- if (!native && (message.clear_at !== void 0 || control !== void 0 && !redundantEffort)) throw invalidMessage("Per-message system controls require the native Messages route unless effort exactly matches the request.");
2424
+ if (!native && (message.clear_at !== void 0 || control !== void 0 && !isEffortOnlyControl(control))) throw invalidMessage("Translated system controls support only output_config.effort with low, medium, high, xhigh, or max; other controls require the native Messages route.");
2410
2425
  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.");
2411
2426
  }
2412
2427
  }
@@ -2416,7 +2431,9 @@ function translateClaudeMessagesToOpenAI(claudeMessages, system, toolNameMapper)
2416
2431
  switch (message.role) {
2417
2432
  case "user": return handleUserMessage(message);
2418
2433
  case "assistant": return handleAssistantMessage(message, toolNameMapper);
2419
- case "system": return handleSystemPrompt(message.content);
2434
+ case "system":
2435
+ if (message.output_config !== void 0 && (typeof message.content === "string" ? message.content.length === 0 : message.content.every((block) => block.text.length === 0))) return [];
2436
+ return handleSystemPrompt(message.content);
2420
2437
  default: throw invalidMessage("Unsupported message role.");
2421
2438
  }
2422
2439
  });
@@ -2854,7 +2871,11 @@ const getClaudeWebSearchToolCallFromChatResponse = (response, toolNameMapper) =>
2854
2871
  };
2855
2872
  const buildSearchInput = (payload, requestedQuery) => {
2856
2873
  const systemText = typeof payload.system === "string" ? payload.system : Array.isArray(payload.system) ? payload.system.map((block) => block.text).join("\n\n") : "";
2857
- const messages = payload.messages.map((message) => `${message.role}: ${textFromMessageContent(message.content)}`).join("\n\n");
2874
+ const messages = payload.messages.flatMap((message) => {
2875
+ const text$1 = textFromMessageContent(message.content);
2876
+ if (message.role === "system" && message.output_config !== void 0 && (typeof message.content === "string" ? message.content.length === 0 : message.content.every((block) => block.text.length === 0))) return [];
2877
+ return [`${message.role}: ${text$1}`];
2878
+ }).join("\n\n");
2858
2879
  return [
2859
2880
  "You are fulfilling an Anthropic web_search server tool request for Claude Code.",
2860
2881
  "Search the web using the provided web_search_preview tool.",
@@ -2870,7 +2891,7 @@ const buildWebSearchRequestPayload = (payload, requestedQuery, model) => ({
2870
2891
  model,
2871
2892
  input: buildSearchInput(payload, requestedQuery),
2872
2893
  tools: [{ type: "web_search_preview" }],
2873
- reasoning: { effort: resolveReasoningEffort(getRequestReasoningEffort(payload)) },
2894
+ reasoning: { effort: getClaudeTurnEffort(payload).effective },
2874
2895
  max_output_tokens: Math.max(256, Math.min(payload.max_tokens ?? 1024, 1200)),
2875
2896
  temperature: payload.temperature,
2876
2897
  top_p: payload.top_p
@@ -3096,7 +3117,7 @@ const createClaudeWebSearchExecution = async (config, payload, requestedQuery, o
3096
3117
  } catch (error) {
3097
3118
  if (!(error instanceof SyntaxError)) throw error;
3098
3119
  }
3099
- return interpretSearchResponse(upstream, config, requestedQuery, request, getRequestReasoningEffort(payload) ?? "unset", options.requestId);
3120
+ return interpretSearchResponse(upstream, config, requestedQuery, request, getClaudeTurnEffort(payload).requested ?? "unset", options.requestId);
3100
3121
  };
3101
3122
  const buildSearchResultBlock = (toolUseId, results) => ({
3102
3123
  type: "web_search_tool_result",
@@ -4346,7 +4367,7 @@ async function createNativeMessages(config, payload, options) {
4346
4367
  };
4347
4368
  const beta = options.headers?.get("anthropic-beta");
4348
4369
  if (beta) headers["anthropic-beta"] = beta;
4349
- log.info(sanitizeTerminalString(`request_id=${options.requestId} Model request client=claude requested_model=${payload.model} upstream_model=${payload.model} upstream_api=messages effective_think_effort=${body.output_config.effort}`));
4370
+ log.info(sanitizeTerminalString(`request_id=${options.requestId} Model request client=claude requested_model=${payload.model} upstream_model=${payload.model} upstream_api=messages effective_think_effort=${getClaudeTurnEffort(body).effective}`));
4350
4371
  const response = await fetchCopilot(getCopilotProviderContext(config), "/v1/messages", {
4351
4372
  method: "POST",
4352
4373
  headers,
@@ -4560,7 +4581,10 @@ async function handleNativeMessages(config, payload, options, write) {
4560
4581
  }
4561
4582
  const searchCall = searches[0];
4562
4583
  if (!searchCall.id || !searchCall.name || typeof searchCall.input?.query !== "string" || !searchCall.input.query.trim()) throw new Error("Invalid native search call.");
4563
- const search = await createClaudeWebSearchExecution(config, payload, searchCall.input.query, {
4584
+ const search = await createClaudeWebSearchExecution(config, {
4585
+ ...payload,
4586
+ messages: normalizeNativeHistory(payload.messages)
4587
+ }, searchCall.input.query, {
4564
4588
  ...options,
4565
4589
  signal
4566
4590
  });
@@ -4672,7 +4696,7 @@ const createTokenCountModel = (modelId, tokenizer = "o200k_base") => ({
4672
4696
  id: modelId
4673
4697
  });
4674
4698
  const isNonStreamingResponse = (response) => typeof response === "object" && response !== null && Object.hasOwn(response, "choices");
4675
- const getClaudeRequestedThinkEffort = (payload) => getRequestReasoningEffort(payload) ?? "unset";
4699
+ const getClaudeRequestedThinkEffort = (payload) => getClaudeTurnEffort(payload).requested ?? "unset";
4676
4700
  const getClaudeRequestedThinking = (payload) => {
4677
4701
  if (!payload.thinking) return "none";
4678
4702
  return [`type:${payload.thinking.type}`, `budget:${payload.thinking.budget_tokens ?? "none"}`].join(",");
@@ -4877,10 +4901,7 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
4877
4901
  const webSearchToolCall = shouldLetModelDecideWebSearch ? getClaudeWebSearchToolCallFromChatResponse(effectiveResponse, toolNameMapper) : void 0;
4878
4902
  let claudeResponse;
4879
4903
  if (webSearchToolCall) {
4880
- const search = await createClaudeWebSearchExecution(config, {
4881
- ...claudePayload,
4882
- reasoning_effort: openAIPayload.reasoning_effort
4883
- }, webSearchToolCall.query, {
4904
+ const search = await createClaudeWebSearchExecution(config, claudePayload, webSearchToolCall.query, {
4884
4905
  requestId,
4885
4906
  signal: requestSignal,
4886
4907
  timeoutMs: config.upstreamTimeoutMs
@@ -4995,11 +5016,11 @@ claudeRoutes.post("/messages", async (c) => {
4995
5016
  const claudePayload = await c.req.json();
4996
5017
  const requestSignal = createCopilotRequestSignal(c.req.raw.signal, config.upstreamTimeoutMs);
4997
5018
  try {
4998
- const effort = resolveReasoningEffort(getRequestReasoningEffort(claudePayload));
5019
+ getRequestReasoningEffort(claudePayload);
4999
5020
  const upstreamModel = translateModelName(claudePayload.model);
5000
5021
  const validate = () => {
5001
5022
  if (shouldUseNativeMessages(config, upstreamModel)) validateNativeMessages(claudePayload);
5002
- else validateClaudeMessages(claudePayload.messages, false, effort);
5023
+ else validateClaudeMessages(claudePayload.messages);
5003
5024
  };
5004
5025
  validateClaudeMessages(claudePayload.messages, true);
5005
5026
  if (config.claudeUpstreamApi !== "auto") validate();