copilot-relay 0.4.6 → 0.4.7

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
@@ -55,7 +55,7 @@ running daemon. Add `--details` for safe failure evidence and private replay hin
55
55
  Use `copilot-relay status --deep` for daemon health, and `copilot-relay stop` when finished.
56
56
 
57
57
  Check how well prompt caching works per model and upstream route. The report reads
58
- only local logs and flags rows below a hit-rate goal, 95% by default:
58
+ only local logs and shows a hit rate below the goal, 95% by default, in red:
59
59
 
60
60
  ```sh
61
61
  copilot-relay cache # last 24 hours
package/dist/main.js CHANGED
@@ -220,7 +220,7 @@ const formatUpstreamProxyForDisplay = (upstreamProxy) => {
220
220
  * `status` prints config values directly to stdout. A config string carrying
221
221
  * raw control bytes could clear the line and paint status rows the relay never
222
222
  * produced. The config file is user-owned, so this is hardening rather than a
223
- * vulnerability — but it costs nothing to make the output non-forgeable. #47.
223
+ * vulnerability — but it costs nothing to make the output non-forgeable.
224
224
  */
225
225
  const sanitizeTerminalString = (text$1) => text$1.replace(ansiEscapePattern, "").replace(controlBytePattern, "");
226
226
  /**
@@ -513,7 +513,7 @@ const cleanupLogsIfDue = async () => {
513
513
  * Each argument is rendered exactly once and scrubbed once, and the identical
514
514
  * array of strings goes to both the file and the console. Scrubbing only on the
515
515
  * way to disk would leave the original object on the console - the screen a
516
- * user screenshots into an issue - so the two sinks must not diverge. See #47.
516
+ * user screenshots into an issue - so the two sinks must not diverge.
517
517
  *
518
518
  * Rendering is skipped only when neither sink would emit, so the cost matches
519
519
  * the old behavior at info level. The console gate is read from consola rather
@@ -595,7 +595,7 @@ var InvalidProxyEnvironmentError = class extends Error {
595
595
  /**
596
596
  * Builds the dispatcher for one resolved upstreamProxy value.
597
597
  *
598
- * Empty connects directly, as every release before #153 did. "env" hands HTTPS_PROXY, HTTP_PROXY
598
+ * Empty connects directly. "env" hands HTTPS_PROXY, HTTP_PROXY
599
599
  * and NO_PROXY, in upper or lower case, to EnvHttpProxyAgent. Anything else is a proxy URL that
600
600
  * normalizeUpstreamProxy has already validated.
601
601
  */
@@ -1373,7 +1373,7 @@ const normalizeApiKey = (value) => {
1373
1373
  * through. None of them can be found again in arbitrary log text: practical URL
1374
1374
  * detection anchors on a literal "://", and loosening that to chase a bare
1375
1375
  * "https:" would start matching ordinary prose. Requiring the authority prefix
1376
- * is the one boundary where this has a definite answer. See #47.
1376
+ * is the one boundary where this has a definite answer.
1377
1377
  */
1378
1378
  const conventionalHttpUrlPattern = /^https?:\/\//i;
1379
1379
  /**
@@ -1390,7 +1390,7 @@ const conventionalHttpUrlPattern = /^https?:\/\//i;
1390
1390
  *
1391
1391
  * Rejecting the raw byte costs nothing: every one of these has a
1392
1392
  * percent-encoded spelling that is unambiguous in log text and is accepted
1393
- * here unchanged. See #47.
1393
+ * here unchanged.
1394
1394
  */
1395
1395
  const unsafeRawDelimiterPattern = /['"`<>\s\u0000-\u001F\u007F]/;
1396
1396
  /**
@@ -1404,7 +1404,7 @@ const copilotBaseUrlDelimiterMessage = "Invalid copilotBaseUrl: quotes, backtick
1404
1404
  /**
1405
1405
  * Validates copilotBaseUrl as a conventional HTTP(S) URL without credentials.
1406
1406
  *
1407
- * Four rules, all learned from #47:
1407
+ * Four rules:
1408
1408
  *
1409
1409
  * The value is concatenated as `${base}${path}` for every upstream call, so it
1410
1410
  * must be something Undici will accept. A relative or non-HTTP value fails at
@@ -2146,36 +2146,6 @@ const dataColumns = [
2146
2146
  alignRight: true,
2147
2147
  cell: (row) => formatCount(row.requests)
2148
2148
  },
2149
- {
2150
- header: "UNKNOWN",
2151
- alignRight: true,
2152
- cell: (row) => formatCount(row.unknownCacheRequests)
2153
- },
2154
- {
2155
- header: "0-READ",
2156
- alignRight: true,
2157
- cell: (row) => formatCount(row.zeroCacheReadRequests)
2158
- },
2159
- {
2160
- header: "INPUT",
2161
- alignRight: true,
2162
- cell: (row) => formatCount(row.totalInputTokens)
2163
- },
2164
- {
2165
- header: "CACHE READ",
2166
- alignRight: true,
2167
- cell: (row) => formatCount(row.cacheReadTokens)
2168
- },
2169
- {
2170
- header: "UNCACHED",
2171
- alignRight: true,
2172
- cell: (row) => formatCount(row.uncachedInputTokens)
2173
- },
2174
- {
2175
- header: "CACHE WRITE",
2176
- alignRight: true,
2177
- cell: (row) => row.cacheWriteTokens === null ? "-" : formatCount(row.cacheWriteTokens)
2178
- },
2179
2149
  {
2180
2150
  header: "HIT RATE",
2181
2151
  alignRight: true,
@@ -2190,12 +2160,10 @@ const renderTable = (rows, view, color) => {
2190
2160
  const pad = (text$1, index) => columns[index].alignRight ? text$1.padStart(widths[index]) : text$1.padEnd(widths[index]);
2191
2161
  const header = columns.map((column, index) => pad(column.header, index)).join(" ");
2192
2162
  const body = rows.map((row, rowIndex) => {
2193
- const rendered = columns.map((column, index) => {
2163
+ return ` ${columns.map((column, index) => {
2194
2164
  const text$1 = pad(cells[rowIndex][index], index);
2195
2165
  return column.tone === void 0 ? text$1 : colorText(text$1, column.tone(row), color);
2196
- });
2197
- const marker = row.belowGoal ? ` ${colorText("below goal", "bad", color)}` : "";
2198
- return ` ${rendered.join(" ")}${marker}`;
2166
+ }).join(" ")}`;
2199
2167
  });
2200
2168
  return [` ${header}`, ...body];
2201
2169
  };
@@ -2208,7 +2176,7 @@ const formatLocalTime = (date) => `${formatLogDate(date)} ${twoDigits(date.getHo
2208
2176
  const describeScope = (options) => {
2209
2177
  return `${options.since === void 0 ? "across all retained logs" : `since ${formatLocalTime(options.since)} local time`}${options.model === void 0 ? "" : `, for models matching "${terminalText(options.model)}"`}`;
2210
2178
  };
2211
- const unknownNote = "UNKNOWN calls logged no cache_read_input_tokens; they are left out of the token columns and HIT RATE.";
2179
+ const unknownNote = (calls) => `HIT RATE leaves out ${formatCount(calls)} ${calls === 1 ? "call" : "calls"} that logged no cache_read_input_tokens.`;
2212
2180
  /** The report as text lines. Kept free of IO, so it is tested without a terminal. */
2213
2181
  const renderCacheReport = (rows, options, color = false) => {
2214
2182
  if (rows.length === 0) return [
@@ -2221,7 +2189,8 @@ const renderCacheReport = (rows, options, color = false) => {
2221
2189
  "",
2222
2190
  ...renderTable(rows, options.view, color)
2223
2191
  ];
2224
- if (rows.some((row) => row.unknownCacheRequests > 0)) lines.push("", unknownNote);
2192
+ const unknownCalls = rows.reduce((total, row) => total + row.unknownCacheRequests, 0);
2193
+ if (unknownCalls > 0) lines.push("", unknownNote(unknownCalls));
2225
2194
  return lines;
2226
2195
  };
2227
2196
 
@@ -2255,7 +2224,7 @@ const cache = defineCommand({
2255
2224
  },
2256
2225
  goal: {
2257
2226
  default: String(defaultGoal),
2258
- description: "Hit-rate goal in percent, with at most two decimals. Rows below it are flagged.",
2227
+ description: "Hit-rate goal in percent, with at most two decimals. A hit rate below it is shown in red.",
2259
2228
  type: "string"
2260
2229
  }
2261
2230
  },
@@ -3358,10 +3327,10 @@ const pinCopilotModelCatalog = (config) => {
3358
3327
  pinnedCatalogs.set(config, config.modelCatalog);
3359
3328
  };
3360
3329
  const pendingCatalogs = /* @__PURE__ */ new WeakMap();
3361
- const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3330
+ const isRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3362
3331
  const isPositiveInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
3363
3332
  function parseModelTokenLimits(value) {
3364
- if (!isRecord$4(value)) return;
3333
+ if (!isRecord$5(value)) return;
3365
3334
  const context = value.max_context_window_tokens;
3366
3335
  const prompt = value.max_prompt_tokens;
3367
3336
  const output = value.max_output_tokens;
@@ -3417,13 +3386,13 @@ async function loadCopilotModelCatalog(config) {
3417
3386
  });
3418
3387
  if (!response.ok) throw new HTTPError("Failed to validate upstream models", response);
3419
3388
  const payload = await readCopilotJson(response, signal, config.upstreamTimeoutMs);
3420
- if (!isRecord$4(payload) || !Array.isArray(payload.data)) throw new Error("Copilot /models returned an invalid model catalog.");
3389
+ if (!isRecord$5(payload) || !Array.isArray(payload.data)) throw new Error("Copilot /models returned an invalid model catalog.");
3421
3390
  const models$1 = /* @__PURE__ */ new Map();
3422
3391
  for (const model of payload.data) {
3423
- if (!isRecord$4(model) || typeof model.id !== "string" || !model.id) continue;
3424
- const capabilities = isRecord$4(model.capabilities) ? model.capabilities : void 0;
3392
+ if (!isRecord$5(model) || typeof model.id !== "string" || !model.id) continue;
3393
+ const capabilities = isRecord$5(model.capabilities) ? model.capabilities : void 0;
3425
3394
  const limits = parseModelTokenLimits(capabilities?.limits);
3426
- const supports = isRecord$4(capabilities?.supports) ? capabilities.supports : void 0;
3395
+ const supports = isRecord$5(capabilities?.supports) ? capabilities.supports : void 0;
3427
3396
  const supportedEndpoints = parseStringArray(model.supported_endpoints);
3428
3397
  const reasoningEfforts = parseReasoningEfforts(supports);
3429
3398
  models$1.set(model.id, {
@@ -3671,11 +3640,15 @@ const probeHint = (code) => {
3671
3640
  //#endregion
3672
3641
  //#region src/claude/billing-line.ts
3673
3642
  const billingLinePrefix = "x-anthropic-billing-header:";
3674
- const afterBillingLine = (text$1) => {
3643
+ const leadingBillingLine = (text$1) => {
3675
3644
  if (!text$1.startsWith(billingLinePrefix)) return;
3676
3645
  const lineEnd = text$1.indexOf("\n");
3677
- if (lineEnd === -1) return "";
3678
- return text$1.slice(lineEnd + 1).replace(/^(?:\r?\n)+/, "");
3646
+ return lineEnd === -1 ? text$1 : text$1.slice(0, lineEnd);
3647
+ };
3648
+ const afterBillingLine = (text$1) => {
3649
+ const line = leadingBillingLine(text$1);
3650
+ if (line === void 0) return;
3651
+ return text$1.slice(line.length).replace(/^(?:\r?\n)+/, "");
3679
3652
  };
3680
3653
  const isTextBlock = (block) => typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string";
3681
3654
  const withSystem = (payload, system) => {
@@ -3711,6 +3684,120 @@ const removeBillingLine = (payload) => {
3711
3684
  if (!changed) return payload;
3712
3685
  return withSystem(payload, kept.length > 0 ? kept : void 0);
3713
3686
  };
3687
+ const systemTexts$1 = (system) => {
3688
+ if (typeof system === "string") return [system];
3689
+ return Array.isArray(system) ? system.filter(isTextBlock).map((block) => block.text) : [];
3690
+ };
3691
+ const readBillingLineField = (payload, name) => {
3692
+ if (typeof payload !== "object" || payload === null) return;
3693
+ const line = systemTexts$1(payload.system).map((text$1) => leadingBillingLine(text$1)).find((text$1) => text$1 !== void 0);
3694
+ if (line === void 0) return;
3695
+ for (const field of line.slice(27).split(";")) {
3696
+ const separator = field.indexOf("=");
3697
+ if (separator !== -1 && field.slice(0, separator).trim() === name) return field.slice(separator + 1).trim();
3698
+ }
3699
+ };
3700
+
3701
+ //#endregion
3702
+ //#region src/claude/initiator.ts
3703
+ const ownRequestSystemPrompts = ["You are a helpful AI assistant tasked with summarizing conversations.", "You are naming a coding session"];
3704
+ const compactionGuard = "CRITICAL: Respond with TEXT ONLY.";
3705
+ const compactionInstruction = "Your task is to create a detailed summary of the conversation so far";
3706
+ const personDeliveries = [
3707
+ "The user sent a new message while you were working:",
3708
+ "A message arrived in the bound thread while you were working:",
3709
+ "Messages arrived in the bound thread while you were working:"
3710
+ ];
3711
+ const claudeCodeTriggers = [
3712
+ "This session is being continued from a previous conversation",
3713
+ "The summarized conversation included Artifact content",
3714
+ "The previous response failed to produce a valid tool call.",
3715
+ "Your tool call was malformed and could not be parsed.",
3716
+ "[Your previous response had no visible output.",
3717
+ "[structured-output-enforce]",
3718
+ "[projects-reply-gate]",
3719
+ "Output token limit hit",
3720
+ "Your response above was cut off mid-stream",
3721
+ "Your response above was stopped by a safety classifier",
3722
+ "Activity in the bound conversation",
3723
+ "Another Claude session sent a message",
3724
+ "Your background observer",
3725
+ "[MESSAGE FROM NON-USER SOURCE",
3726
+ "[SCHEDULED TASK",
3727
+ "<channel source=",
3728
+ "<teammate-message"
3729
+ ];
3730
+ const claudeCodeTriggerPatterns = [/^\S+ hook (?:feedback|blocking error from command):/, /^The \S+ plugin sent a message/];
3731
+ const claudeCodeContext = [
3732
+ "[Request interrupted by user",
3733
+ "[Tool call ",
3734
+ "The user doesn't want to take this action right now.",
3735
+ "Base directory for this skill:"
3736
+ ];
3737
+ const claudeCodeContextPatterns = [/^\S+ hook additional context:/];
3738
+ const statusProbePrompt = "Reply with the single word: ok";
3739
+ const statusProbeMaxTokens = 16;
3740
+ const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3741
+ const innermostReminder = /<system-reminder>(?:(?!<system-reminder>)[\s\S])*?<\/system-reminder>/g;
3742
+ const withoutReminders = (text$1) => {
3743
+ let previous = text$1;
3744
+ let remaining = text$1.replace(innermostReminder, "");
3745
+ while (remaining !== previous) {
3746
+ previous = remaining;
3747
+ remaining = remaining.replace(innermostReminder, "");
3748
+ }
3749
+ return remaining.trim();
3750
+ };
3751
+ const startsWithAny = (text$1, prefixes) => prefixes.some((prefix) => text$1.startsWith(prefix));
3752
+ const matchesAny = (text$1, prefixes, patterns) => startsWithAny(text$1, prefixes) || patterns.some((pattern) => pattern.test(text$1));
3753
+ const contentBlocks = (content) => {
3754
+ if (typeof content === "string") return [{
3755
+ type: "text",
3756
+ text: content
3757
+ }];
3758
+ return Array.isArray(content) ? content.filter(isRecord$4) : [];
3759
+ };
3760
+ const originatingTurn = (messages) => {
3761
+ const blocks = [];
3762
+ let sawUser = false;
3763
+ for (let index = messages.length - 1; index >= 0; index--) {
3764
+ const message = messages[index];
3765
+ if (!isRecord$4(message) || message.role === "system") continue;
3766
+ if (message.role !== "user") break;
3767
+ sawUser = true;
3768
+ blocks.unshift(...contentBlocks(message.content));
3769
+ }
3770
+ return sawUser ? blocks : void 0;
3771
+ };
3772
+ const blockOrigin = (block) => {
3773
+ if (block.type === "tool_result") return "agent";
3774
+ if (block.type === "image" || block.type === "document") return "user";
3775
+ if (block.type !== "text" || typeof block.text !== "string") return;
3776
+ const text$1 = withoutReminders(block.text);
3777
+ if (text$1 === "" || matchesAny(text$1, claudeCodeContext, claudeCodeContextPatterns)) return;
3778
+ if (startsWithAny(text$1, personDeliveries)) return "user";
3779
+ if (text$1.includes(compactionGuard) && text$1.includes(compactionInstruction) || matchesAny(text$1, claudeCodeTriggers, claudeCodeTriggerPatterns)) return "agent";
3780
+ return "user";
3781
+ };
3782
+ const isStatusProbe = (body) => {
3783
+ if (Object.keys(body).sort().join(",") !== "max_tokens,messages,model" || body.max_tokens !== statusProbeMaxTokens) return false;
3784
+ const messages = body.messages;
3785
+ return Array.isArray(messages) && messages.length === 1 && isRecord$4(messages[0]) && messages[0].role === "user" && messages[0].content === statusProbePrompt;
3786
+ };
3787
+ const systemTexts = (system) => contentBlocks(system).flatMap((block) => block.type === "text" && typeof block.text === "string" ? [block.text] : []);
3788
+ const getClaudeRequestInitiator = (body) => {
3789
+ if (!isRecord$4(body) || !Array.isArray(body.messages) || isStatusProbe(body)) return "agent";
3790
+ const payload = body;
3791
+ if (readBillingLineField(payload, "cc_is_subagent") === "true" || readBillingLineField(payload, "cc_workload") === "cron") return "agent";
3792
+ if (systemTexts(removeBillingLine(payload).system).some((text$1) => startsWithAny(text$1.trimStart(), ownRequestSystemPrompts))) return "agent";
3793
+ const turn = originatingTurn(body.messages);
3794
+ if (turn === void 0) return "agent";
3795
+ for (let index = turn.length - 1; index >= 0; index--) {
3796
+ const origin = blockOrigin(turn[index]);
3797
+ if (origin !== void 0) return origin;
3798
+ }
3799
+ return "agent";
3800
+ };
3714
3801
 
3715
3802
  //#endregion
3716
3803
  //#region src/claude/utils.ts
@@ -3741,7 +3828,7 @@ function mapOpenAIStopReasonToClaude(finishReason) {
3741
3828
  }
3742
3829
  /**
3743
3830
  * Upstream sends an empty `arguments` string for tools that take no
3744
- * parameters (#114). Blank text is the empty object; anything else must be a
3831
+ * parameters. Blank text is the empty object; anything else must be a
3745
3832
  * JSON object, and a failure names the tool rather than surfacing a bare
3746
3833
  * SyntaxError. Argument text is never echoed: it can carry user data.
3747
3834
  */
@@ -5690,7 +5777,6 @@ const normalizeFinalAssistantPrefill = (payload) => {
5690
5777
  messages
5691
5778
  };
5692
5779
  };
5693
- const isAgentInitiator = (messages) => messages.some((message) => message.role === "assistant" || message.role === "tool") ? "agent" : "user";
5694
5780
  const holdsImage = (message) => Array.isArray(message.content) && message.content.some((part) => part.type === "image_url");
5695
5781
  const messagesIncludeImage = (messages) => messages.some(holdsImage);
5696
5782
  const isToolImageMessage = (message) => message.role === "tool" && holdsImage(message);
@@ -5767,7 +5853,7 @@ const createChatCompletions = async (config, payload, options = {}) => {
5767
5853
  const compatiblePayload = useResponsesApi ? upstreamPayload : normalizeFinalAssistantPrefill(moveToolImagesToUserMessages(upstreamPayload));
5768
5854
  const provider = getCopilotProviderContext(config);
5769
5855
  const enableVision = messagesIncludeImage(compatiblePayload.messages);
5770
- const initiator = isAgentInitiator(compatiblePayload.messages);
5856
+ const initiator = options.initiator ?? "agent";
5771
5857
  const requestPayload = buildRequestPayload(compatiblePayload);
5772
5858
  const effectiveEffortLabel = requestPayload.reasoning_effort ?? "omitted";
5773
5859
  log.info(sanitizeTerminalString([
@@ -5778,7 +5864,8 @@ const createChatCompletions = async (config, payload, options = {}) => {
5778
5864
  `upstream_model=${compatiblePayload.model}`,
5779
5865
  `requested_think_effort=${requestedThinkEffort}`,
5780
5866
  `requested_thinking=${requestedThinking}`,
5781
- `effective_think_effort=${effectiveEffortLabel}`
5867
+ `effective_think_effort=${effectiveEffortLabel}`,
5868
+ `initiator=${initiator}`
5782
5869
  ].join(" ")));
5783
5870
  if (useResponsesApi) return completeResponse(await createResponses(provider, compatiblePayload, {
5784
5871
  vision: enableVision,
@@ -6012,7 +6099,8 @@ async function createNativeMessages(config, payload, options) {
6012
6099
  const beta = options.headers?.get("anthropic-beta");
6013
6100
  if (beta) headers["anthropic-beta"] = beta;
6014
6101
  const effortLabel = effort === void 0 ? "omitted" : getClaudeTurnEffort(body).effective;
6015
- 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=${effortLabel}`));
6102
+ const initiator = options.initiator ?? "agent";
6103
+ 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=${effortLabel} initiator=${initiator}`));
6016
6104
  const response = await fetchCopilot(getCopilotProviderContext(config), "/v1/messages", {
6017
6105
  method: "POST",
6018
6106
  headers,
@@ -6021,7 +6109,7 @@ async function createNativeMessages(config, payload, options) {
6021
6109
  requestId: options.requestId,
6022
6110
  signal: options.signal,
6023
6111
  timeoutMs: config.upstreamTimeoutMs,
6024
- initiator: payload.messages.some((message) => message.role === "assistant") ? "agent" : "user"
6112
+ initiator
6025
6113
  });
6026
6114
  if (!response.ok) {
6027
6115
  const detail = await readCopilotText(response, options.signal, config.upstreamTimeoutMs);
@@ -6294,7 +6382,8 @@ async function handleNativeMessages(config, payload, options, write) {
6294
6382
  ]
6295
6383
  }, {
6296
6384
  ...options,
6297
- signal
6385
+ signal,
6386
+ initiator: "agent"
6298
6387
  });
6299
6388
  const offset = combined.length;
6300
6389
  let finalHeldFrom;
@@ -6497,13 +6586,14 @@ const translationEndpoint = (config, model) => {
6497
6586
  const selection = selectCopilotEndpoint(config, normalizeCopilotModelId(model));
6498
6587
  return selection.endpoint && !selection.responsesFallback ? selection.endpoint : void 0;
6499
6588
  };
6500
- const handleClaudeMessageRequest = async (config, claudePayload, requestSignal, requestId, writeEvent, requestHeaders) => {
6589
+ const handleClaudeMessageRequest = async (config, claudePayload, initiator, requestSignal, requestId, writeEvent, requestHeaders) => {
6501
6590
  const upstreamModel = translateModelName(claudePayload.model);
6502
6591
  if (shouldUseNativeMessages(config, upstreamModel)) return handleNativeMessages(config, {
6503
6592
  ...claudePayload,
6504
6593
  model: upstreamModel
6505
6594
  }, {
6506
6595
  requestId,
6596
+ initiator,
6507
6597
  signal: requestSignal,
6508
6598
  headers: requestHeaders
6509
6599
  }, writeEvent);
@@ -6515,6 +6605,7 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
6515
6605
  if (shouldLetModelDecideWebSearch && !canStreamWebSearchDecision) openAIPayload.stream = false;
6516
6606
  const response = await createChatCompletions(config, openAIPayload, {
6517
6607
  client: "claude",
6608
+ initiator,
6518
6609
  requestedModel: claudePayload.model,
6519
6610
  requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),
6520
6611
  requestedThinking: getClaudeRequestedThinking(claudePayload),
@@ -6588,6 +6679,7 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
6588
6679
  else {
6589
6680
  const finalResponse = await createChatCompletions(config, createFinalWebSearchPayload(openAIPayload, search, toolNameMapper), {
6590
6681
  client: "claude",
6682
+ initiator: "agent",
6591
6683
  requestedModel: claudePayload.model,
6592
6684
  requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),
6593
6685
  requestedThinking: getClaudeRequestedThinking(claudePayload),
@@ -6665,7 +6757,9 @@ claudeRoutes.get("/models", (c) => c.json({
6665
6757
  claudeRoutes.post("/messages", async (c) => {
6666
6758
  const config = c.get("config");
6667
6759
  const requestId = c.get("requestId");
6668
- const claudePayload = removeBillingLine(await c.req.json());
6760
+ const receivedPayload = await c.req.json();
6761
+ const initiator = c.env?.relayProbe === true ? "agent" : getClaudeRequestInitiator(receivedPayload);
6762
+ const claudePayload = removeBillingLine(receivedPayload);
6669
6763
  const requestSignal = createCopilotRequestSignal(c.req.raw.signal, config.upstreamTimeoutMs);
6670
6764
  try {
6671
6765
  getRequestReasoningEffort(claudePayload);
@@ -6716,7 +6810,7 @@ claudeRoutes.post("/messages", async (c) => {
6716
6810
  data: JSON.stringify(event)
6717
6811
  }));
6718
6812
  try {
6719
- await handleClaudeMessageRequest(config, claudePayload, requestSignal, requestId, writeEvent, c.req.raw.headers);
6813
+ await handleClaudeMessageRequest(config, claudePayload, initiator, requestSignal, requestId, writeEvent, c.req.raw.headers);
6720
6814
  } catch (error) {
6721
6815
  log.error(`request_id=${requestId} Error during Claude stream request:`, error);
6722
6816
  if (error instanceof UpstreamToolInputError) trace?.recordFailure("invalid-tool-input");
@@ -6730,7 +6824,7 @@ claudeRoutes.post("/messages", async (c) => {
6730
6824
  });
6731
6825
  }
6732
6826
  try {
6733
- return c.json(await handleClaudeMessageRequest(config, claudePayload, requestSignal, requestId, void 0, c.req.raw.headers));
6827
+ return c.json(await handleClaudeMessageRequest(config, claudePayload, initiator, requestSignal, requestId, void 0, c.req.raw.headers));
6734
6828
  } catch (error) {
6735
6829
  if (error instanceof ProxyNotImplementedError) {
6736
6830
  c.set("requestErrorMessage", error.message);
@@ -7140,7 +7234,7 @@ async function probeModels(config, entries, options) {
7140
7234
  content: "Reply with OK only."
7141
7235
  }]
7142
7236
  })
7143
- }));
7237
+ }), { relayProbe: true });
7144
7238
  return {
7145
7239
  response,
7146
7240
  body: await response.json().catch(() => void 0)
@@ -8820,7 +8914,7 @@ const restart = defineCommand({
8820
8914
  * for different responses - restart the service versus re-authenticate.
8821
8915
  *
8822
8916
  * A failed health probe is a 2, not a 0. Printing FAILED while exiting 0 would
8823
- * make every scripted caller treat an unreachable relay as fine. See #34.
8917
+ * make every scripted caller treat an unreachable relay as fine.
8824
8918
  */
8825
8919
  const exitCodes = {
8826
8920
  notRunning: 1,
@@ -8852,9 +8946,9 @@ const hasVersionMismatch = (status$1) => status$1.running && status$1.daemonVers
8852
8946
  *
8853
8947
  * 0 requires a live process *and* a passing health probe. Printing FAILED while
8854
8948
  * exiting 0 would make every scripted caller treat an unreachable relay as
8855
- * fine. See #34.
8949
+ * fine.
8856
8950
  *
8857
- * A version mismatch stays 0 on purpose (#43). The relay works; it is just not
8951
+ * A version mismatch stays 0 on purpose. The relay works; it is just not
8858
8952
  * the build that was installed. Mapping it to 2 would overstate it and break
8859
8953
  * every scripted caller that treats non-zero as broken. It is surfaced in the
8860
8954
  * text instead.
@@ -8994,7 +9088,7 @@ const describeError = (error) => {
8994
9088
  * The version rides along on a probe `status` already makes, so asking what is
8995
9089
  * running costs nothing extra. Absent for a daemon older than v0.3.1, which is
8996
9090
  * itself informative, so it stays optional rather than defaulting to the
8997
- * caller's version - that substitution is the bug. See #43.
9091
+ * caller's version - that substitution is the bug.
8998
9092
  */
8999
9093
  const checkHealth = async (baseUrl) => {
9000
9094
  try {
@@ -9037,9 +9131,9 @@ const checkDeep = async (baseUrl, model, apiKey) => {
9037
9131
  try {
9038
9132
  const result = await probe(`${baseUrl}/v1/messages`, {
9039
9133
  body: JSON.stringify({
9040
- max_tokens: 16,
9134
+ max_tokens: statusProbeMaxTokens,
9041
9135
  messages: [{
9042
- content: "Reply with the single word: ok",
9136
+ content: statusProbePrompt,
9043
9137
  role: "user"
9044
9138
  }],
9045
9139
  model