copilot-relay 0.3.7 → 0.3.8

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/README.md CHANGED
@@ -117,16 +117,19 @@ for live model discovery, compatible thinking efforts, and copy-paste updates.
117
117
  | Level | Logs |
118
118
  | --- | --- |
119
119
  | `error` | Startup, preflight, and request failures |
120
- | `info` | Errors plus startup status, preflight status, request IDs, upstream lifecycle, and local HTTP status codes |
121
- | `debug` | Info plus model routing summaries, Copilot upstream timings, and request payloads |
120
+ | `info` | Errors plus startup/preflight status, request IDs, model/effort summaries, upstream lifecycle, and local HTTP status codes |
121
+ | `debug` | Info plus detailed Copilot timings and request payloads |
122
122
 
123
123
  Any other `logLevel` value is invalid and stops startup.
124
124
 
125
- Valid `thinkEffort`: `none`, `low`, `medium`, `high`, `xhigh`, `max`.
125
+ Valid `thinkEffort` defaults: `low`, `medium`, `high`, `xhigh`, `max`.
126
126
  It is a fallback, not a forced override: Claude Code's `output_config.effort`
127
127
  wins, followed by the legacy `reasoning_effort` field, then `thinkEffort` when
128
128
  neither request field supplies a value. The selected effort is preserved through
129
129
  all model and WebSearch passes; existing config values are not rewritten.
130
+ `none` and other invalid explicit defaults fail startup before authentication or
131
+ config write-back, with a clear list of valid choices. They are not silently
132
+ replaced with `max`. Explicit request-level `none` remains model-dependent.
130
133
 
131
134
  `upstreamTimeoutSeconds` controls the maximum time a single Claude request can
132
135
  spend waiting on upstream Copilot calls, including chat, Responses, preflight,
@@ -212,7 +215,10 @@ health probe, so a relay that cannot answer `/healthz` never reports success.
212
215
 
213
216
  ## Logging
214
217
 
215
- At `debug`, every model request logs the requested model, upstream model, requested think effort, requested thinking, and effective think effort.
218
+ At `info`, every model request logs the requested model, upstream model, requested
219
+ think effort, requested thinking, and effective think effort. Missing request
220
+ effort is shown as `unset`, distinct from explicit `none`; full normal request
221
+ payloads remain at `debug`.
216
222
 
217
223
  Upstream failures are logged at `error` with full request and response context in the same log file.
218
224
 
@@ -26,10 +26,9 @@ claudeSetup: true
26
26
 
27
27
  # Log verbosity:
28
28
  # error - startup/preflight/request failures only
29
- # info - error logs plus startup status, preflight status, and local HTTP
30
- # status codes
31
- # debug - info logs plus model routing summaries, Copilot upstream timings,
32
- # and request payloads
29
+ # info - error logs plus startup/preflight status, model/effort summaries,
30
+ # and local HTTP status codes
31
+ # debug - info logs plus detailed Copilot timings and request payloads
33
32
  # Any other value is invalid and stops startup.
34
33
  logLevel: info
35
34
 
@@ -41,7 +40,7 @@ logRetentionDays: 3
41
40
 
42
41
  # Fallback upstream effort when Claude Code does not specify one.
43
42
  # Request output_config.effort wins, followed by legacy reasoning_effort.
44
- # Valid values: none, low, medium, high, xhigh, max.
43
+ # Valid values: low, medium, high, xhigh, max.
45
44
  thinkEffort: max
46
45
 
47
46
  # Max seconds to wait for one Claude request's upstream Copilot calls.
package/dist/main.js CHANGED
@@ -541,12 +541,20 @@ const runtimeState = {};
541
541
 
542
542
  //#endregion
543
543
  //#region src/lib/models.ts
544
+ const configurableReasoningEfforts = [
545
+ "low",
546
+ "medium",
547
+ "high",
548
+ "xhigh",
549
+ "max"
550
+ ];
544
551
  const defaultReasoningEffort = "max";
545
552
  const defaultModelRouting = {
546
553
  gptModel: "gpt-6-astra",
547
554
  opusModel: "claude-opus-5"
548
555
  };
549
- const isReasoningEffort = (value) => value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
556
+ const isConfiguredReasoningEffort = (value) => configurableReasoningEfforts.some((effort) => effort === value);
557
+ const isReasoningEffort = (value) => value === "none" || isConfiguredReasoningEffort(value);
550
558
  const invalidRequestEffort = (message) => new HTTPError(message, new Response(JSON.stringify({
551
559
  type: "error",
552
560
  error: {
@@ -736,10 +744,19 @@ const normalizeCopilotBaseUrl = (value) => {
736
744
  if (parsed.username || parsed.password) throw new Error(copilotBaseUrlCredentialsMessage);
737
745
  return trimmed;
738
746
  };
747
+ var InvalidThinkEffortError = class extends Error {
748
+ constructor() {
749
+ super(`Invalid thinkEffort. Valid values: ${configurableReasoningEfforts.join(", ")}. "none" is not allowed as a configured default.`);
750
+ this.name = "InvalidThinkEffortError";
751
+ }
752
+ };
739
753
  const normalizeThinkEffort = (value) => {
740
- if (typeof value !== "string") return;
741
- const normalized = value.toLowerCase() === "minimal" ? "low" : value.toLowerCase();
742
- return isReasoningEffort(normalized) ? normalized : void 0;
754
+ if (value === void 0) return void 0;
755
+ if (typeof value === "string") {
756
+ const normalized = value.toLowerCase() === "minimal" ? "low" : value.toLowerCase();
757
+ if (isConfiguredReasoningEffort(normalized)) return normalized;
758
+ }
759
+ throw new InvalidThinkEffortError();
743
760
  };
744
761
  const normalizeUpstreamTimeoutSeconds = (value) => {
745
762
  if (value === void 0) return;
@@ -858,16 +875,15 @@ const serializeConfig = (config) => [
858
875
  "",
859
876
  "# Log verbosity:",
860
877
  "# error - startup/preflight/request failures only",
861
- "# info - error logs plus startup status, preflight status, and local HTTP",
862
- "# status codes",
863
- "# debug - info logs plus model routing summaries, Copilot upstream timings,",
864
- "# and request payloads",
878
+ "# info - error logs plus startup/preflight status, model/effort summaries,",
879
+ "# and local HTTP status codes",
880
+ "# debug - info logs plus detailed Copilot timings and request payloads",
865
881
  `logLevel: ${config.logLevel}`,
866
882
  "",
867
883
  "# Number of days to keep files in ~/.copilot-relay/logs.",
868
884
  `logRetentionDays: ${config.logRetentionDays}`,
869
885
  "",
870
- "# Fallback effort when the request omits it: none, low, medium, high, xhigh, max.",
886
+ `# Fallback effort when the request omits it: ${configurableReasoningEfforts.join(", ")}.`,
871
887
  `thinkEffort: ${config.thinkEffort}`,
872
888
  "",
873
889
  "# Max seconds for one request's upstream calls; 0 disables the relay deadline.",
@@ -926,7 +942,10 @@ const watchAppConfig = (onReload) => {
926
942
  if (nextMtime === lastMtime) return;
927
943
  lastMtime = nextMtime;
928
944
  onReload(await readAppConfig());
929
- } catch {}
945
+ } catch (error) {
946
+ if (error instanceof InvalidThinkEffortError) log.error(`${error.message} Keeping the previous runtime settings.`);
947
+ else log.error(`Could not reload config. Check ${paths.configPath} for invalid values or file errors.`);
948
+ }
930
949
  }, 1e3);
931
950
  if (typeof timer.unref === "function") timer.unref();
932
951
  getConfigMtime().then((mtime) => {
@@ -2081,7 +2100,7 @@ const messagesIncludeImage = (messages) => messages.some((msg) => typeof msg.con
2081
2100
  const createChatCompletions = async (config, payload, options = {}) => {
2082
2101
  const client = options.client ?? "generic";
2083
2102
  const requestedModel = options.requestedModel ?? payload.model;
2084
- const requestedThinkEffort = options.requestedThinkEffort ?? getRequestReasoningEffort(payload) ?? "none";
2103
+ const requestedThinkEffort = options.requestedThinkEffort ?? getRequestReasoningEffort(payload) ?? "unset";
2085
2104
  const reasoningEffort = getRequestedReasoningEffort(payload);
2086
2105
  const requestedThinking = options.requestedThinking ?? "none";
2087
2106
  const signal = createCopilotRequestSignal(options.signal, options.timeoutMs);
@@ -2103,7 +2122,7 @@ const createChatCompletions = async (config, payload, options = {}) => {
2103
2122
  const enableVision = messagesIncludeImage(compatiblePayload.messages);
2104
2123
  const initiator = isAgentInitiator(compatiblePayload.messages);
2105
2124
  const requestPayload = buildRequestPayload(compatiblePayload);
2106
- log.debug([
2125
+ log.info(sanitizeTerminalString([
2107
2126
  "Model request",
2108
2127
  `client=${client}`,
2109
2128
  `requested_model=${requestedModel}`,
@@ -2111,8 +2130,8 @@ const createChatCompletions = async (config, payload, options = {}) => {
2111
2130
  `upstream_model=${compatiblePayload.model}`,
2112
2131
  `requested_think_effort=${requestedThinkEffort}`,
2113
2132
  `requested_thinking=${requestedThinking}`,
2114
- `effective_think_effort=${requestPayload.reasoning_effort ?? "none"}`
2115
- ].join(" "));
2133
+ `effective_think_effort=${requestPayload.reasoning_effort ?? "unset"}`
2134
+ ].join(" ")));
2116
2135
  log.debug("Full request payload", { payload: requestPayload });
2117
2136
  if (useResponsesApi) return completeResponse(await createResponses(provider, compatiblePayload, {
2118
2137
  vision: enableVision,
@@ -3216,7 +3235,7 @@ const createTokenCountModel = (modelId, tokenizer = "o200k_base") => ({
3216
3235
  id: modelId
3217
3236
  });
3218
3237
  const isNonStreamingResponse = (response) => typeof response === "object" && response !== null && Object.hasOwn(response, "choices");
3219
- const getClaudeRequestedThinkEffort = (payload) => getRequestReasoningEffort(payload) ?? "none";
3238
+ const getClaudeRequestedThinkEffort = (payload) => getRequestReasoningEffort(payload) ?? "unset";
3220
3239
  const getClaudeRequestedThinking = (payload) => {
3221
3240
  if (!payload.thinking) return "none";
3222
3241
  return [`type:${payload.thinking.type}`, `budget:${payload.thinking.budget_tokens ?? "none"}`].join(",");
@@ -3645,6 +3664,10 @@ const startServer = (config) => new Promise((resolve$1, reject) => {
3645
3664
  const canCloseConnections = (server) => typeof server.closeIdleConnections === "function" && typeof server.closeAllConnections === "function";
3646
3665
  async function startRelay(appConfig) {
3647
3666
  appConfig ??= await readAppConfig();
3667
+ appConfig = {
3668
+ ...appConfig,
3669
+ thinkEffort: normalizeThinkEffort(appConfig.thinkEffort) ?? defaultReasoningEffort
3670
+ };
3648
3671
  setLogLevel(appConfig.logLevel);
3649
3672
  await cleanupLogs(appConfig.logRetentionDays);
3650
3673
  const claudeConfigPath$1 = claudeConfigPath;