@hsupu/copilot-api 0.7.20 → 0.7.22

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.mjs CHANGED
@@ -21,6 +21,47 @@ import { cors } from "hono/cors";
21
21
  import { trimTrailingSlash } from "hono/trailing-slash";
22
22
  import { streamSSE } from "hono/streaming";
23
23
 
24
+ //#region src/lib/state.ts
25
+ /**
26
+ * Rebuild model lookup indexes from state.models.
27
+ * Called by cacheModels() in production; call directly in tests after setting state.models.
28
+ */
29
+ function rebuildModelIndex() {
30
+ const data = state.models?.data ?? [];
31
+ state.modelIndex = new Map(data.map((m) => [m.id, m]));
32
+ state.modelIds = new Set(data.map((m) => m.id));
33
+ }
34
+ const DEFAULT_MODEL_OVERRIDES = {
35
+ opus: "claude-opus-4.6",
36
+ sonnet: "claude-sonnet-4.6",
37
+ haiku: "claude-haiku-4.5"
38
+ };
39
+ const state = {
40
+ accountType: "individual",
41
+ autoTruncate: true,
42
+ compressToolResultsBeforeTruncate: true,
43
+ contextEditingMode: "off",
44
+ stripServerTools: false,
45
+ dedupToolCalls: false,
46
+ fetchTimeout: 300,
47
+ historyLimit: 200,
48
+ historyMinEntries: 50,
49
+ modelIds: /* @__PURE__ */ new Set(),
50
+ modelIndex: /* @__PURE__ */ new Map(),
51
+ modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },
52
+ rewriteSystemReminders: false,
53
+ showGitHubToken: false,
54
+ shutdownAbortWait: 120,
55
+ shutdownGracefulWait: 60,
56
+ staleRequestMaxAge: 600,
57
+ streamIdleTimeout: 300,
58
+ systemPromptOverrides: [],
59
+ stripReadToolResultTags: false,
60
+ normalizeResponsesCallIds: false,
61
+ verbose: false
62
+ };
63
+
64
+ //#endregion
24
65
  //#region src/lib/utils.ts
25
66
  const sleep = (ms) => new Promise((resolve) => {
26
67
  setTimeout(resolve, ms);
@@ -408,6 +449,7 @@ function updateEntry(id, update) {
408
449
  if (update.pipelineInfo) entry.pipelineInfo = update.pipelineInfo;
409
450
  if (update.durationMs !== void 0) entry.durationMs = update.durationMs;
410
451
  if (update.sseEvents) entry.sseEvents = update.sseEvents;
452
+ if (update.httpHeaders) entry.httpHeaders = update.httpHeaders;
411
453
  if (update.response) {
412
454
  const session = historyState.sessions.get(entry.sessionId);
413
455
  if (session) {
@@ -619,45 +661,6 @@ function exportHistory(format = "json") {
619
661
  return [headers.join(","), ...rows.map((r) => r.map((v) => escapeCsvValue(v)).join(","))].join("\n");
620
662
  }
621
663
 
622
- //#endregion
623
- //#region src/lib/state.ts
624
- /**
625
- * Rebuild model lookup indexes from state.models.
626
- * Called by cacheModels() in production; call directly in tests after setting state.models.
627
- */
628
- function rebuildModelIndex() {
629
- const data = state.models?.data ?? [];
630
- state.modelIndex = new Map(data.map((m) => [m.id, m]));
631
- state.modelIds = new Set(data.map((m) => m.id));
632
- }
633
- const DEFAULT_MODEL_OVERRIDES = {
634
- opus: "claude-opus-4.6",
635
- sonnet: "claude-sonnet-4.6",
636
- haiku: "claude-haiku-4.5"
637
- };
638
- const state = {
639
- accountType: "individual",
640
- autoTruncate: true,
641
- compressToolResultsBeforeTruncate: true,
642
- stripServerTools: false,
643
- dedupToolCalls: false,
644
- fetchTimeout: 300,
645
- historyLimit: 200,
646
- historyMinEntries: 50,
647
- modelIds: /* @__PURE__ */ new Set(),
648
- modelIndex: /* @__PURE__ */ new Map(),
649
- modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },
650
- rewriteSystemReminders: false,
651
- showGitHubToken: false,
652
- shutdownAbortWait: 120,
653
- shutdownGracefulWait: 60,
654
- staleRequestMaxAge: 600,
655
- streamIdleTimeout: 300,
656
- systemPromptOverrides: [],
657
- stripReadToolResultTags: false,
658
- verbose: false
659
- };
660
-
661
664
  //#endregion
662
665
  //#region src/lib/history/memory-pressure.ts
663
666
  /**
@@ -746,7 +749,7 @@ function startMemoryPressureMonitor() {
746
749
  consola.error("[memory] Error in memory pressure check:", error);
747
750
  });
748
751
  }, CHECK_INTERVAL_MS);
749
- if (timer && "unref" in timer) timer.unref();
752
+ if ("unref" in timer) timer.unref();
750
753
  }
751
754
  /** Stop the memory pressure monitor */
752
755
  function stopMemoryPressureMonitor() {
@@ -772,12 +775,15 @@ async function ensurePaths() {
772
775
  await ensureFile(PATHS.GITHUB_TOKEN_PATH);
773
776
  }
774
777
  async function ensureFile(filePath) {
778
+ const isWindows = process.platform === "win32";
775
779
  try {
776
780
  await fs.access(filePath, fs.constants.W_OK);
777
- if (((await fs.stat(filePath)).mode & 511) !== 384) await fs.chmod(filePath, 384);
781
+ if (!isWindows) {
782
+ if (((await fs.stat(filePath)).mode & 511) !== 384) await fs.chmod(filePath, 384);
783
+ }
778
784
  } catch {
779
785
  await fs.writeFile(filePath, "");
780
- await fs.chmod(filePath, 384);
786
+ if (!isWindows) await fs.chmod(filePath, 384);
781
787
  }
782
788
  }
783
789
 
@@ -878,6 +884,7 @@ async function applyConfigToState() {
878
884
  if (a.strip_server_tools !== void 0) state.stripServerTools = a.strip_server_tools;
879
885
  if (a.dedup_tool_calls !== void 0) state.dedupToolCalls = a.dedup_tool_calls === true ? "input" : a.dedup_tool_calls;
880
886
  if (a.strip_read_tool_result_tags !== void 0) state.stripReadToolResultTags = a.strip_read_tool_result_tags;
887
+ if (a.context_editing !== void 0) state.contextEditingMode = a.context_editing;
881
888
  if (a.rewrite_system_reminders !== void 0) {
882
889
  if (typeof a.rewrite_system_reminders === "boolean") state.rewriteSystemReminders = a.rewrite_system_reminders;
883
890
  else if (Array.isArray(a.rewrite_system_reminders)) state.rewriteSystemReminders = compileRewriteRules(a.rewrite_system_reminders);
@@ -905,6 +912,8 @@ async function applyConfigToState() {
905
912
  if (config.fetch_timeout !== void 0) state.fetchTimeout = config.fetch_timeout;
906
913
  if (config.stream_idle_timeout !== void 0) state.streamIdleTimeout = config.stream_idle_timeout;
907
914
  if (config.stale_request_max_age !== void 0) state.staleRequestMaxAge = config.stale_request_max_age;
915
+ const responsesConfig = config["openai-responses"];
916
+ if (responsesConfig && responsesConfig.normalize_call_ids !== void 0) state.normalizeResponsesCallIds = responsesConfig.normalize_call_ids;
908
917
  const currentMtime = getConfigMtimeMs();
909
918
  if (hasApplied && currentMtime !== lastAppliedMtimeMs) consola.info("[config] Reloaded config.yaml");
910
919
  hasApplied = true;
@@ -1076,97 +1085,6 @@ function initProxyBun(options) {
1076
1085
  consola.debug(`Proxy configured (Bun env): ${formatProxyDisplay(options.url)}`);
1077
1086
  }
1078
1087
 
1079
- //#endregion
1080
- //#region src/lib/copilot-api.ts
1081
- const standardHeaders = () => ({
1082
- "content-type": "application/json",
1083
- accept: "application/json"
1084
- });
1085
- const COPILOT_VERSION = "0.38.0";
1086
- const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
1087
- const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
1088
- /** Copilot Chat API version (for chat/completions requests) */
1089
- const COPILOT_API_VERSION = "2025-05-01";
1090
- /** Copilot internal API version (for token & usage endpoints) */
1091
- const COPILOT_INTERNAL_API_VERSION = "2025-04-01";
1092
- /** GitHub public API version (for /user, repos, etc.) */
1093
- const GITHUB_API_VERSION = "2022-11-28";
1094
- /**
1095
- * Session-level interaction ID.
1096
- * Used to correlate all requests within a single server session.
1097
- * Unlike x-request-id (per-request UUID), this stays constant for the server lifetime.
1098
- */
1099
- const INTERACTION_ID = randomUUID();
1100
- const copilotBaseUrl = (state) => state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
1101
- const copilotHeaders = (state, opts) => {
1102
- const headers = {
1103
- Authorization: `Bearer ${state.copilotToken}`,
1104
- "content-type": standardHeaders()["content-type"],
1105
- "copilot-integration-id": "vscode-chat",
1106
- "editor-version": `vscode/${state.vsCodeVersion}`,
1107
- "editor-plugin-version": EDITOR_PLUGIN_VERSION,
1108
- "user-agent": USER_AGENT,
1109
- "openai-intent": opts?.intent ?? "conversation-panel",
1110
- "x-github-api-version": COPILOT_API_VERSION,
1111
- "x-request-id": randomUUID(),
1112
- "X-Interaction-Id": INTERACTION_ID,
1113
- "x-vscode-user-agent-library-version": "electron-fetch"
1114
- };
1115
- if (opts?.vision) headers["copilot-vision-request"] = "true";
1116
- if (opts?.modelRequestHeaders) {
1117
- const coreKeysLower = new Set(Object.keys(headers).map((k) => k.toLowerCase()));
1118
- for (const [key, value] of Object.entries(opts.modelRequestHeaders)) if (!coreKeysLower.has(key.toLowerCase())) headers[key] = value;
1119
- }
1120
- return headers;
1121
- };
1122
- const GITHUB_API_BASE_URL = "https://api.github.com";
1123
- const githubHeaders = (state) => ({
1124
- ...standardHeaders(),
1125
- authorization: `token ${state.githubToken}`,
1126
- "editor-version": `vscode/${state.vsCodeVersion}`,
1127
- "editor-plugin-version": EDITOR_PLUGIN_VERSION,
1128
- "user-agent": USER_AGENT,
1129
- "x-github-api-version": GITHUB_API_VERSION,
1130
- "x-vscode-user-agent-library-version": "electron-fetch"
1131
- });
1132
- const GITHUB_BASE_URL = "https://github.com";
1133
- const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1134
- const GITHUB_APP_SCOPES = ["read:user"].join(" ");
1135
- /** Fallback VSCode version when GitHub API is unavailable */
1136
- const VSCODE_VERSION_FALLBACK = "1.104.3";
1137
- /** GitHub API endpoint for latest VSCode release */
1138
- const VSCODE_RELEASE_URL = "https://api.github.com/repos/microsoft/vscode/releases/latest";
1139
- /** Fetch the latest VSCode version and cache in global state */
1140
- async function cacheVSCodeVersion() {
1141
- const response = await getVSCodeVersion();
1142
- state.vsCodeVersion = response;
1143
- consola.info(`Using VSCode version: ${response}`);
1144
- }
1145
- /** Fetch the latest VSCode version from GitHub releases, falling back to a hardcoded version */
1146
- async function getVSCodeVersion() {
1147
- const controller = new AbortController();
1148
- const timeout = setTimeout(() => {
1149
- controller.abort();
1150
- }, 5e3);
1151
- try {
1152
- const response = await fetch(VSCODE_RELEASE_URL, {
1153
- signal: controller.signal,
1154
- headers: {
1155
- Accept: "application/vnd.github.v3+json",
1156
- "User-Agent": "copilot-api"
1157
- }
1158
- });
1159
- if (!response.ok) return VSCODE_VERSION_FALLBACK;
1160
- const version = (await response.json()).tag_name;
1161
- if (version && /^\d+\.\d+\.\d+$/.test(version)) return version;
1162
- return VSCODE_VERSION_FALLBACK;
1163
- } catch {
1164
- return VSCODE_VERSION_FALLBACK;
1165
- } finally {
1166
- clearTimeout(timeout);
1167
- }
1168
- }
1169
-
1170
1088
  //#endregion
1171
1089
  //#region src/lib/sanitize-system-reminder.ts
1172
1090
  /**
@@ -2009,63 +1927,162 @@ function getErrorMessage(error, fallback = "Unknown error") {
2009
1927
  }
2010
1928
 
2011
1929
  //#endregion
2012
- //#region src/lib/token/copilot-client.ts
2013
- /** Copilot API client token and usage */
2014
- const getCopilotToken = async () => {
2015
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: {
2016
- ...githubHeaders(state),
2017
- "x-github-api-version": COPILOT_INTERNAL_API_VERSION
2018
- } });
2019
- if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot token", response);
2020
- return await response.json();
2021
- };
2022
- const getCopilotUsage = async () => {
2023
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: {
2024
- ...githubHeaders(state),
2025
- "x-github-api-version": COPILOT_INTERNAL_API_VERSION
2026
- } });
2027
- if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot usage", response);
2028
- return await response.json();
2029
- };
2030
-
2031
- //#endregion
2032
- //#region src/lib/token/copilot-token-manager.ts
1930
+ //#region src/lib/copilot-api.ts
1931
+ const standardHeaders = () => ({
1932
+ "content-type": "application/json",
1933
+ accept: "application/json"
1934
+ });
1935
+ const COPILOT_VERSION = "0.38.0";
1936
+ const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
1937
+ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
1938
+ /** Copilot Chat API version (for chat/completions requests) */
1939
+ const COPILOT_API_VERSION = "2025-05-01";
1940
+ /** Copilot internal API version (for token & usage endpoints) */
1941
+ const COPILOT_INTERNAL_API_VERSION = "2025-04-01";
1942
+ /** GitHub public API version (for /user, repos, etc.) */
1943
+ const GITHUB_API_VERSION = "2022-11-28";
2033
1944
  /**
2034
- * Manages Copilot token lifecycle including automatic refresh.
2035
- * Depends on GitHubTokenManager for authentication.
2036
- *
2037
- * All refresh paths (scheduled + on-demand via 401) go through `refresh()`,
2038
- * which deduplicates concurrent callers and reschedules the next refresh based
2039
- * on the server's `refresh_in` value.
1945
+ * Session-level interaction ID.
1946
+ * Used to correlate all requests within a single server session.
1947
+ * Unlike x-request-id (per-request UUID), this stays constant for the server lifetime.
2040
1948
  */
2041
- var CopilotTokenManager = class {
2042
- githubTokenManager;
2043
- currentToken = null;
2044
- refreshTimeout = null;
2045
- minRefreshIntervalMs;
2046
- maxRetries;
2047
- /** Shared promise to prevent concurrent refresh attempts */
2048
- refreshInFlight = null;
2049
- constructor(options) {
2050
- this.githubTokenManager = options.githubTokenManager;
2051
- this.minRefreshIntervalMs = (options.minRefreshIntervalSeconds ?? 60) * 1e3;
2052
- this.maxRetries = options.maxRetries ?? 3;
2053
- }
2054
- /**
2055
- * Get the current Copilot token info.
2056
- */
2057
- getCurrentToken() {
2058
- return this.currentToken;
2059
- }
2060
- /**
2061
- * Initialize the Copilot token and start automatic refresh.
2062
- */
2063
- async initialize() {
2064
- const tokenInfo = await this.fetchCopilotToken();
2065
- state.copilotToken = tokenInfo.token;
2066
- consola.debug("GitHub Copilot Token fetched successfully!");
2067
- this.scheduleRefresh(tokenInfo.refreshIn);
2068
- return tokenInfo;
1949
+ const INTERACTION_ID = randomUUID();
1950
+ const copilotBaseUrl = (state) => state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
1951
+ const copilotHeaders = (state, opts) => {
1952
+ const headers = {
1953
+ Authorization: `Bearer ${state.copilotToken}`,
1954
+ "content-type": standardHeaders()["content-type"],
1955
+ "copilot-integration-id": "vscode-chat",
1956
+ "editor-version": `vscode/${state.vsCodeVersion}`,
1957
+ "editor-plugin-version": EDITOR_PLUGIN_VERSION,
1958
+ "user-agent": USER_AGENT,
1959
+ "openai-intent": opts?.intent ?? "conversation-panel",
1960
+ "x-github-api-version": COPILOT_API_VERSION,
1961
+ "x-request-id": randomUUID(),
1962
+ "X-Interaction-Id": INTERACTION_ID,
1963
+ "x-vscode-user-agent-library-version": "electron-fetch"
1964
+ };
1965
+ if (opts?.vision) headers["copilot-vision-request"] = "true";
1966
+ if (opts?.modelRequestHeaders) {
1967
+ const coreKeysLower = new Set(Object.keys(headers).map((k) => k.toLowerCase()));
1968
+ for (const [key, value] of Object.entries(opts.modelRequestHeaders)) if (!coreKeysLower.has(key.toLowerCase())) headers[key] = value;
1969
+ }
1970
+ return headers;
1971
+ };
1972
+ const GITHUB_API_BASE_URL = "https://api.github.com";
1973
+ const githubHeaders = (state) => ({
1974
+ ...standardHeaders(),
1975
+ authorization: `token ${state.githubToken}`,
1976
+ "editor-version": `vscode/${state.vsCodeVersion}`,
1977
+ "editor-plugin-version": EDITOR_PLUGIN_VERSION,
1978
+ "user-agent": USER_AGENT,
1979
+ "x-github-api-version": GITHUB_API_VERSION,
1980
+ "x-vscode-user-agent-library-version": "electron-fetch"
1981
+ });
1982
+ const GITHUB_BASE_URL = "https://github.com";
1983
+ const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
1984
+ const GITHUB_APP_SCOPES = ["read:user"].join(" ");
1985
+ /** Fallback VSCode version when GitHub API is unavailable */
1986
+ const VSCODE_VERSION_FALLBACK = "1.104.3";
1987
+ /** GitHub API endpoint for latest VSCode release */
1988
+ const VSCODE_RELEASE_URL = "https://api.github.com/repos/microsoft/vscode/releases/latest";
1989
+ /** Fetch the latest VSCode version and cache in global state */
1990
+ async function cacheVSCodeVersion() {
1991
+ const response = await getVSCodeVersion();
1992
+ state.vsCodeVersion = response;
1993
+ consola.info(`Using VSCode version: ${response}`);
1994
+ }
1995
+ /** Fetch the latest VSCode version from GitHub releases, falling back to a hardcoded version */
1996
+ async function getVSCodeVersion() {
1997
+ const controller = new AbortController();
1998
+ const timeout = setTimeout(() => {
1999
+ controller.abort();
2000
+ }, 5e3);
2001
+ try {
2002
+ const response = await fetch(VSCODE_RELEASE_URL, {
2003
+ signal: controller.signal,
2004
+ headers: {
2005
+ Accept: "application/vnd.github.v3+json",
2006
+ "User-Agent": "copilot-api"
2007
+ }
2008
+ });
2009
+ if (!response.ok) return VSCODE_VERSION_FALLBACK;
2010
+ const version = (await response.json()).tag_name;
2011
+ if (version && /^\d+\.\d+\.\d+$/.test(version)) return version;
2012
+ return VSCODE_VERSION_FALLBACK;
2013
+ } catch {
2014
+ return VSCODE_VERSION_FALLBACK;
2015
+ } finally {
2016
+ clearTimeout(timeout);
2017
+ }
2018
+ }
2019
+
2020
+ //#endregion
2021
+ //#region src/lib/token/copilot-client.ts
2022
+ /** Copilot API client — token and usage */
2023
+ const getCopilotToken = async () => {
2024
+ const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, {
2025
+ headers: {
2026
+ ...githubHeaders(state),
2027
+ "x-github-api-version": COPILOT_INTERNAL_API_VERSION
2028
+ },
2029
+ signal: AbortSignal.timeout(15e3)
2030
+ });
2031
+ if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot token", response);
2032
+ return await response.json();
2033
+ };
2034
+ const getCopilotUsage = async () => {
2035
+ const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {
2036
+ headers: {
2037
+ ...githubHeaders(state),
2038
+ "x-github-api-version": COPILOT_INTERNAL_API_VERSION
2039
+ },
2040
+ signal: AbortSignal.timeout(15e3)
2041
+ });
2042
+ if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot usage", response);
2043
+ return await response.json();
2044
+ };
2045
+
2046
+ //#endregion
2047
+ //#region src/lib/token/copilot-token-manager.ts
2048
+ /**
2049
+ * Manages Copilot token lifecycle including automatic refresh.
2050
+ * Depends on GitHubTokenManager for authentication.
2051
+ *
2052
+ * All refresh paths (scheduled + on-demand via 401) go through `refresh()`,
2053
+ * which deduplicates concurrent callers and reschedules the next refresh based
2054
+ * on the server's `refresh_in` value.
2055
+ */
2056
+ var CopilotTokenManager = class {
2057
+ githubTokenManager;
2058
+ currentToken = null;
2059
+ refreshTimeout = null;
2060
+ minRefreshIntervalMs;
2061
+ maxRetries;
2062
+ /** Shared promise to prevent concurrent refresh attempts */
2063
+ refreshInFlight = null;
2064
+ /** Set when a refresh attempt fails; cleared on next success */
2065
+ _refreshNeeded = false;
2066
+ constructor(options) {
2067
+ this.githubTokenManager = options.githubTokenManager;
2068
+ this.minRefreshIntervalMs = (options.minRefreshIntervalSeconds ?? 60) * 1e3;
2069
+ this.maxRetries = options.maxRetries ?? 3;
2070
+ }
2071
+ /**
2072
+ * Get the current Copilot token info.
2073
+ */
2074
+ getCurrentToken() {
2075
+ return this.currentToken;
2076
+ }
2077
+ /**
2078
+ * Initialize the Copilot token and start automatic refresh.
2079
+ */
2080
+ async initialize() {
2081
+ const tokenInfo = await this.fetchCopilotToken();
2082
+ state.copilotToken = tokenInfo.token;
2083
+ consola.debug("GitHub Copilot Token fetched successfully!");
2084
+ this.scheduleRefresh(tokenInfo.refreshIn);
2085
+ return tokenInfo;
2069
2086
  }
2070
2087
  /**
2071
2088
  * Fetch a new Copilot token from the API.
@@ -2100,10 +2117,12 @@ var CopilotTokenManager = class {
2100
2117
  }
2101
2118
  }
2102
2119
  const delay = Math.min(1e3 * 2 ** attempt, 3e4);
2103
- consola.warn(`Token refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay}ms`);
2120
+ const reason = error instanceof Error ? formatErrorWithCause(error) : String(error);
2121
+ consola.warn(`Token refresh attempt ${attempt + 1}/${this.maxRetries} failed: ${reason}, retrying in ${delay}ms`);
2104
2122
  await new Promise((resolve) => setTimeout(resolve, delay));
2105
2123
  }
2106
- consola.error("All token refresh attempts failed:", lastError);
2124
+ const reason = lastError instanceof Error ? formatErrorWithCause(lastError) : String(lastError);
2125
+ consola.error(`All token refresh attempts failed: ${reason}`);
2107
2126
  return null;
2108
2127
  }
2109
2128
  /**
@@ -2166,10 +2185,12 @@ var CopilotTokenManager = class {
2166
2185
  }
2167
2186
  this.refreshInFlight = this.fetchTokenWithRetry().then((tokenInfo) => {
2168
2187
  if (tokenInfo) {
2188
+ this._refreshNeeded = false;
2169
2189
  state.copilotToken = tokenInfo.token;
2170
2190
  this.scheduleRefresh(tokenInfo.refreshIn);
2171
2191
  consola.verbose(`[CopilotToken] Token refreshed (next refresh_in=${tokenInfo.refreshIn}s)`);
2172
2192
  } else {
2193
+ this._refreshNeeded = true;
2173
2194
  consola.error("[CopilotToken] Token refresh failed, keeping existing token");
2174
2195
  this.scheduleRefresh(300);
2175
2196
  }
@@ -2180,6 +2201,16 @@ var CopilotTokenManager = class {
2180
2201
  return this.refreshInFlight;
2181
2202
  }
2182
2203
  /**
2204
+ * Proactively ensure the token is valid before sending a request.
2205
+ * Triggers a refresh if the token is expired/expiring or the last
2206
+ * refresh attempt failed. Concurrent callers share the same in-flight
2207
+ * refresh via `refresh()`.
2208
+ */
2209
+ async ensureValidToken() {
2210
+ if (!this.isExpiredOrExpiring() && !this._refreshNeeded) return;
2211
+ await this.refresh();
2212
+ }
2213
+ /**
2183
2214
  * Check if the current token is expired or about to expire.
2184
2215
  */
2185
2216
  isExpiredOrExpiring(marginSeconds = 60) {
@@ -2651,6 +2682,14 @@ function getCopilotTokenManager() {
2651
2682
  function stopTokenRefresh() {
2652
2683
  copilotTokenManager?.stopAutoRefresh();
2653
2684
  }
2685
+ /**
2686
+ * Proactively ensure the Copilot token is valid.
2687
+ * Triggers a refresh if the token is expired/expiring or the last
2688
+ * background refresh failed. No-op if the manager is not initialized.
2689
+ */
2690
+ async function ensureValidCopilotToken() {
2691
+ await copilotTokenManager?.ensureValidToken();
2692
+ }
2654
2693
 
2655
2694
  //#endregion
2656
2695
  //#region src/auth.ts
@@ -2760,6 +2799,15 @@ const checkUsage = defineCommand({
2760
2799
  function createFetchSignal() {
2761
2800
  return state.fetchTimeout > 0 ? AbortSignal.timeout(state.fetchTimeout * 1e3) : void 0;
2762
2801
  }
2802
+ /**
2803
+ * Populate a HeadersCapture object with request and response headers.
2804
+ * Should be called immediately after fetch(), before !response.ok check,
2805
+ * so headers are captured even for error responses.
2806
+ */
2807
+ function captureHttpHeaders(capture, requestHeaders, response) {
2808
+ capture.request = { ...requestHeaders };
2809
+ capture.response = Object.fromEntries(response.headers.entries());
2810
+ }
2763
2811
 
2764
2812
  //#endregion
2765
2813
  //#region src/lib/models/client.ts
@@ -3444,6 +3492,7 @@ function createRequestContext(opts) {
3444
3492
  let _pipelineInfo = null;
3445
3493
  let _preprocessInfo = null;
3446
3494
  let _sseEvents = null;
3495
+ let _httpHeaders = null;
3447
3496
  const _sanitizationHistory = [];
3448
3497
  let _queueWaitMs = 0;
3449
3498
  const _attempts = [];
@@ -3480,6 +3529,9 @@ function createRequestContext(opts) {
3480
3529
  get preprocessInfo() {
3481
3530
  return _preprocessInfo;
3482
3531
  },
3532
+ get httpHeaders() {
3533
+ return _httpHeaders;
3534
+ },
3483
3535
  get attempts() {
3484
3536
  return _attempts;
3485
3537
  },
@@ -3518,6 +3570,12 @@ function createRequestContext(opts) {
3518
3570
  setSseEvents(events) {
3519
3571
  _sseEvents = events.length > 0 ? events : null;
3520
3572
  },
3573
+ setHttpHeaders(capture) {
3574
+ if (capture.request && capture.response) _httpHeaders = {
3575
+ request: capture.request,
3576
+ response: capture.response
3577
+ };
3578
+ },
3521
3579
  beginAttempt(attemptOpts) {
3522
3580
  const attempt = {
3523
3581
  index: _attempts.length,
@@ -3648,6 +3706,7 @@ function createRequestContext(opts) {
3648
3706
  if (lastTruncation) entry.truncation = lastTruncation;
3649
3707
  if (_pipelineInfo) entry.pipelineInfo = _pipelineInfo;
3650
3708
  if (_sseEvents) entry.sseEvents = _sseEvents;
3709
+ if (_httpHeaders) entry.httpHeaders = _httpHeaders;
3651
3710
  if (_attempts.length > 1) entry.attempts = _attempts.map((a) => ({
3652
3711
  index: a.index,
3653
3712
  strategy: a.strategy,
@@ -4723,7 +4782,7 @@ const setupClaudeCode = defineCommand({
4723
4782
 
4724
4783
  //#endregion
4725
4784
  //#region package.json
4726
- var version = "0.7.20";
4785
+ var version = "0.7.22";
4727
4786
 
4728
4787
  //#endregion
4729
4788
  //#region src/lib/context/error-persistence.ts
@@ -4840,7 +4899,8 @@ function handleHistoryEvent(event) {
4840
4899
  updateEntry(entryData.id, {
4841
4900
  response,
4842
4901
  durationMs: entryData.durationMs,
4843
- sseEvents: entryData.sseEvents
4902
+ sseEvents: entryData.sseEvents,
4903
+ httpHeaders: entryData.httpHeaders
4844
4904
  });
4845
4905
  break;
4846
4906
  }
@@ -4957,6 +5017,22 @@ function isEndpointSupported(model, endpoint) {
4957
5017
  return model.supported_endpoints.includes(endpoint);
4958
5018
  }
4959
5019
 
5020
+ //#endregion
5021
+ //#region src/lib/ws.ts
5022
+ /** Create a shared WebSocket adapter for the given Hono app */
5023
+ async function createWebSocketAdapter(app) {
5024
+ if (typeof globalThis.Bun !== "undefined") {
5025
+ const { upgradeWebSocket } = await import("hono/bun");
5026
+ return { upgradeWebSocket };
5027
+ }
5028
+ const { createNodeWebSocket } = await import("@hono/node-ws");
5029
+ const nodeWs = createNodeWebSocket({ app });
5030
+ return {
5031
+ upgradeWebSocket: nodeWs.upgradeWebSocket,
5032
+ injectWebSocket: (server) => nodeWs.injectWebSocket(server)
5033
+ };
5034
+ }
5035
+
4960
5036
  //#endregion
4961
5037
  //#region src/routes/history/api.ts
4962
5038
  function handleGetEntries(c) {
@@ -5066,25 +5142,12 @@ historyRoutes.get("/api/sessions/:id", handleGetSession);
5066
5142
  historyRoutes.delete("/api/sessions/:id", handleDeleteSession);
5067
5143
  /**
5068
5144
  * Initialize WebSocket support for history real-time updates.
5069
- * Registers the /ws route on historyRoutes using the appropriate WebSocket
5070
- * adapter for the current runtime (hono/bun for Bun, @hono/node-ws for Node.js).
5145
+ * Registers the /history/ws route on the root app using the shared WebSocket adapter.
5071
5146
  *
5072
- * @param rootApp - The root Hono app instance (needed by @hono/node-ws to match upgrade requests)
5073
- * @returns An `injectWebSocket` function that must be called with the Node.js HTTP server
5074
- * after the server is created. Returns `undefined` under Bun (no injection needed).
5147
+ * @param rootApp - The root Hono app instance
5148
+ * @param upgradeWs - Shared WebSocket upgrade function from createWebSocketAdapter
5075
5149
  */
5076
- async function initHistoryWebSocket(rootApp) {
5077
- let upgradeWs;
5078
- let injectFn;
5079
- if (typeof globalThis.Bun !== "undefined") {
5080
- const { upgradeWebSocket } = await import("hono/bun");
5081
- upgradeWs = upgradeWebSocket;
5082
- } else {
5083
- const { createNodeWebSocket } = await import("@hono/node-ws");
5084
- const nodeWs = createNodeWebSocket({ app: rootApp });
5085
- upgradeWs = nodeWs.upgradeWebSocket;
5086
- injectFn = (server) => nodeWs.injectWebSocket(server);
5087
- }
5150
+ function initHistoryWebSocket(rootApp, upgradeWs) {
5088
5151
  rootApp.get("/history/ws", upgradeWs(() => ({
5089
5152
  onOpen(_event, ws) {
5090
5153
  addClient(ws.raw);
@@ -5098,7 +5161,6 @@ async function initHistoryWebSocket(rootApp) {
5098
5161
  removeClient(ws.raw);
5099
5162
  }
5100
5163
  })));
5101
- return injectFn;
5102
5164
  }
5103
5165
  /**
5104
5166
  * Resolve a UI directory that exists at runtime.
@@ -5831,6 +5893,7 @@ const createResponses = async (payload, opts) => {
5831
5893
  body: JSON.stringify(payload),
5832
5894
  signal: fetchSignal
5833
5895
  });
5896
+ if (opts?.headersCapture) captureHttpHeaders(opts.headersCapture, headers, response);
5834
5897
  if (!response.ok) {
5835
5898
  consola.error("Failed to create responses", response);
5836
5899
  throw await HTTPError.fromResponse("Failed to create responses", response, payload.model);
@@ -5947,15 +6010,20 @@ function createTokenRefreshStrategy() {
5947
6010
  * centralizes that configuration to avoid duplication.
5948
6011
  */
5949
6012
  /** Create the FormatAdapter for Responses API pipeline execution */
5950
- function createResponsesAdapter(selectedModel) {
6013
+ function createResponsesAdapter(selectedModel, headersCapture) {
5951
6014
  return {
5952
6015
  format: "openai-responses",
5953
- sanitize: (p) => ({
5954
- payload: p,
5955
- blocksRemoved: 0,
5956
- systemReminderRemovals: 0
5957
- }),
5958
- execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p, { resolvedModel: selectedModel })),
6016
+ sanitize: (p) => {
6017
+ return {
6018
+ payload: state.normalizeResponsesCallIds ? normalizeCallIds(p) : p,
6019
+ blocksRemoved: 0,
6020
+ systemReminderRemovals: 0
6021
+ };
6022
+ },
6023
+ execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p, {
6024
+ resolvedModel: selectedModel,
6025
+ headersCapture
6026
+ })),
5959
6027
  logPayloadSize: (p) => {
5960
6028
  const count = typeof p.input === "string" ? 1 : p.input.length;
5961
6029
  consola.debug(`Responses payload: ${count} input item(s), model: ${p.model}`);
@@ -5966,6 +6034,36 @@ function createResponsesAdapter(selectedModel) {
5966
6034
  function createResponsesStrategies() {
5967
6035
  return [createNetworkRetryStrategy(), createTokenRefreshStrategy()];
5968
6036
  }
6037
+ const CALL_PREFIX = "call_";
6038
+ const FC_PREFIX = "fc_";
6039
+ /**
6040
+ * Normalize function call IDs in Responses API input.
6041
+ * Converts Chat Completions format `call_xxx` IDs to Responses format `fc_xxx` IDs
6042
+ * on `function_call` and `function_call_output` items.
6043
+ */
6044
+ function normalizeCallIds(payload) {
6045
+ if (typeof payload.input === "string") return payload;
6046
+ let count = 0;
6047
+ const normalizedInput = payload.input.map((item) => {
6048
+ if (item.type !== "function_call" && item.type !== "function_call_output") return item;
6049
+ const newItem = { ...item };
6050
+ if (newItem.id?.startsWith(CALL_PREFIX)) {
6051
+ newItem.id = FC_PREFIX + newItem.id.slice(5);
6052
+ count++;
6053
+ }
6054
+ if (newItem.call_id?.startsWith(CALL_PREFIX)) {
6055
+ newItem.call_id = FC_PREFIX + newItem.call_id.slice(5);
6056
+ count++;
6057
+ }
6058
+ return newItem;
6059
+ });
6060
+ if (count === 0) return payload;
6061
+ consola.debug(`[responses] Normalized ${count} call ID(s) (call_ → fc_)`);
6062
+ return {
6063
+ ...payload,
6064
+ input: normalizedInput
6065
+ };
6066
+ }
5969
6067
 
5970
6068
  //#endregion
5971
6069
  //#region src/routes/responses/ws.ts
@@ -6047,17 +6145,20 @@ async function handleResponseCreate(ws, payload) {
6047
6145
  model: resolvedModel,
6048
6146
  clientModel: requestedModel
6049
6147
  });
6050
- const adapter = createResponsesAdapter(selectedModel);
6148
+ const headersCapture = {};
6149
+ const adapter = createResponsesAdapter(selectedModel, headersCapture);
6051
6150
  const strategies = createResponsesStrategies();
6052
6151
  try {
6053
- const iterator = (await executeRequestPipeline({
6152
+ const pipelineResult = await executeRequestPipeline({
6054
6153
  adapter,
6055
6154
  strategies,
6056
6155
  payload,
6057
6156
  originalPayload: payload,
6058
6157
  model: selectedModel,
6059
6158
  maxRetries: 1
6060
- })).response[Symbol.asyncIterator]();
6159
+ });
6160
+ reqCtx.setHttpHeaders(headersCapture);
6161
+ const iterator = pipelineResult.response[Symbol.asyncIterator]();
6061
6162
  const acc = createResponsesStreamAccumulator();
6062
6163
  const idleTimeoutMs = state.streamIdleTimeout > 0 ? state.streamIdleTimeout * 1e3 : 0;
6063
6164
  const shutdownSignal = getShutdownSignal();
@@ -6085,6 +6186,7 @@ async function handleResponseCreate(ws, payload) {
6085
6186
  reqCtx.complete(responseData);
6086
6187
  ws.close(1e3, "done");
6087
6188
  } catch (error) {
6189
+ reqCtx.setHttpHeaders(headersCapture);
6088
6190
  reqCtx.fail(resolvedModel, error);
6089
6191
  const message = error instanceof Error ? error.message : String(error);
6090
6192
  consola.error(`[WS] Responses API error: ${message}`);
@@ -6095,25 +6197,13 @@ async function handleResponseCreate(ws, payload) {
6095
6197
  * Initialize WebSocket routes for the Responses API.
6096
6198
  *
6097
6199
  * Registers GET /v1/responses and GET /responses on the root Hono app
6098
- * with WebSocket upgrade handling. Follows the same pattern as
6099
- * initHistoryWebSocket in src/routes/history/route.ts.
6200
+ * with WebSocket upgrade handling. Uses the shared WebSocket adapter
6201
+ * to avoid multiple upgrade listeners on the same HTTP server.
6100
6202
  *
6101
- * @returns An inject function for Node.js HTTP server (undefined for Bun)
6203
+ * @param rootApp - The root Hono app instance
6204
+ * @param upgradeWs - Shared WebSocket upgrade function from createWebSocketAdapter
6102
6205
  */
6103
- async function initResponsesWebSocket(rootApp) {
6104
- let upgradeWs;
6105
- let injectFn;
6106
- if (typeof globalThis.Bun !== "undefined") {
6107
- const { upgradeWebSocket } = await import("hono/bun");
6108
- upgradeWs = upgradeWebSocket;
6109
- } else {
6110
- const { createNodeWebSocket } = await import("@hono/node-ws");
6111
- const nodeWs = createNodeWebSocket({ app: rootApp });
6112
- upgradeWs = nodeWs.upgradeWebSocket;
6113
- injectFn = (server) => {
6114
- nodeWs.injectWebSocket(server);
6115
- };
6116
- }
6206
+ function initResponsesWebSocket(rootApp, upgradeWs) {
6117
6207
  const wsHandler = upgradeWs(() => ({
6118
6208
  onOpen(_event, _ws) {
6119
6209
  consola.debug("[WS] Responses API WebSocket connected");
@@ -6147,7 +6237,6 @@ async function initResponsesWebSocket(rootApp) {
6147
6237
  rootApp.get("/v1/responses", wsHandler);
6148
6238
  rootApp.get("/responses", wsHandler);
6149
6239
  consola.debug("[WS] Responses API WebSocket routes registered");
6150
- return injectFn;
6151
6240
  }
6152
6241
 
6153
6242
  //#endregion
@@ -6885,6 +6974,7 @@ const createChatCompletions = async (payload, opts) => {
6885
6974
  body: JSON.stringify(payload),
6886
6975
  signal: fetchSignal
6887
6976
  });
6977
+ if (opts?.headersCapture) captureHttpHeaders(opts.headersCapture, headers, response);
6888
6978
  if (!response.ok) {
6889
6979
  consola.error("Failed to create chat completions", response);
6890
6980
  throw await HTTPError.fromResponse("Failed to create chat completions", response, payload.model);
@@ -6984,14 +7074,14 @@ function sanitizeOpenAIMessages(payload) {
6984
7074
  content: filtered
6985
7075
  };
6986
7076
  });
6987
- const removedCount = originalCount - messages.length;
6988
- if (removedCount > 0) consola.info(`[Sanitizer:OpenAI] Filtered ${removedCount} orphaned tool messages`);
7077
+ const blocksRemoved = originalCount - messages.length;
7078
+ if (blocksRemoved > 0) consola.info(`[Sanitizer:OpenAI] Filtered ${blocksRemoved} orphaned tool messages`);
6989
7079
  return {
6990
7080
  payload: {
6991
7081
  ...payload,
6992
7082
  messages: allMessages
6993
7083
  },
6994
- blocksRemoved: removedCount,
7084
+ blocksRemoved,
6995
7085
  systemReminderRemovals
6996
7086
  };
6997
7087
  }
@@ -7268,10 +7358,14 @@ async function handleChatCompletion(c) {
7268
7358
  */
7269
7359
  async function executeRequest(opts) {
7270
7360
  const { c, payload, originalPayload, selectedModel, reqCtx } = opts;
7361
+ const headersCapture = {};
7271
7362
  const adapter = {
7272
7363
  format: "openai-chat-completions",
7273
7364
  sanitize: (p) => sanitizeOpenAIMessages(p),
7274
- execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p, { resolvedModel: selectedModel })),
7365
+ execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p, {
7366
+ resolvedModel: selectedModel,
7367
+ headersCapture
7368
+ })),
7275
7369
  logPayloadSize: (p) => logPayloadSizeInfo(p, selectedModel)
7276
7370
  };
7277
7371
  const strategies = [
@@ -7286,7 +7380,7 @@ async function executeRequest(opts) {
7286
7380
  ];
7287
7381
  let truncateResult;
7288
7382
  try {
7289
- const response = (await executeRequestPipeline({
7383
+ const result = await executeRequestPipeline({
7290
7384
  adapter,
7291
7385
  strategies,
7292
7386
  payload,
@@ -7299,7 +7393,9 @@ async function executeRequest(opts) {
7299
7393
  if (retryTruncateResult) truncateResult = retryTruncateResult;
7300
7394
  if (reqCtx.tuiLogId) tuiLogger.updateRequest(reqCtx.tuiLogId, { tags: ["truncated", `retry-${attempt + 1}`] });
7301
7395
  }
7302
- })).response;
7396
+ });
7397
+ reqCtx.setHttpHeaders(headersCapture);
7398
+ const response = result.response;
7303
7399
  if (isNonStreaming(response)) return handleNonStreamingResponse(c, response, reqCtx, truncateResult);
7304
7400
  consola.debug("Streaming response");
7305
7401
  reqCtx.transition("streaming");
@@ -7316,6 +7412,7 @@ async function executeRequest(opts) {
7316
7412
  });
7317
7413
  });
7318
7414
  } catch (error) {
7415
+ reqCtx.setHttpHeaders(headersCapture);
7319
7416
  reqCtx.fail(payload.model, error);
7320
7417
  throw error;
7321
7418
  }
@@ -8718,6 +8815,14 @@ function modelSupportsContextEditing(modelId) {
8718
8815
  return normalized.startsWith("claude-haiku-4-5") || normalized.startsWith("claude-sonnet-4-5") || normalized.startsWith("claude-sonnet-4") || normalized.startsWith("claude-opus-4-5") || normalized.startsWith("claude-opus-4-6") || normalized.startsWith("claude-opus-4-1") || normalized.startsWith("claude-opus-4");
8719
8816
  }
8720
8817
  /**
8818
+ * Check if context editing is enabled for a model.
8819
+ * Requires both model support AND config mode != 'off'.
8820
+ * Mirrors VSCode Copilot Chat's isAnthropicContextEditingEnabled().
8821
+ */
8822
+ function isContextEditingEnabled(modelId) {
8823
+ return modelSupportsContextEditing(modelId) && state.contextEditingMode !== "off";
8824
+ }
8825
+ /**
8721
8826
  * Tool search is supported by:
8722
8827
  * - Claude Opus 4.5/4.6
8723
8828
  */
@@ -8756,7 +8861,7 @@ function buildAnthropicBetaHeaders(modelId, resolvedModel) {
8756
8861
  const headers = {};
8757
8862
  const betaFeatures = [];
8758
8863
  if (!modelHasAdaptiveThinking(resolvedModel)) betaFeatures.push("interleaved-thinking-2025-05-14");
8759
- if (modelSupportsContextEditing(modelId)) betaFeatures.push("context-management-2025-06-27");
8864
+ if (isContextEditingEnabled(modelId)) betaFeatures.push("context-management-2025-06-27");
8760
8865
  if (modelSupportsToolSearch(modelId)) betaFeatures.push("advanced-tool-use-2025-11-20");
8761
8866
  if (betaFeatures.length > 0) headers["anthropic-beta"] = betaFeatures.join(",");
8762
8867
  return headers;
@@ -8767,22 +8872,28 @@ function buildAnthropicBetaHeaders(modelId, resolvedModel) {
8767
8872
  * From anthropic.ts:270-329 (buildContextManagement + getContextManagementFromConfig):
8768
8873
  * - clear_thinking: keep last N thinking turns
8769
8874
  * - clear_tool_uses: triggered by input_tokens threshold, keep last N tool uses
8875
+ *
8876
+ * Only builds edits matching the requested mode:
8877
+ * - "off" → undefined (no context management)
8878
+ * - "clear-thinking" → clear_thinking only (if thinking is enabled)
8879
+ * - "clear-tooluse" → clear_tool_uses only
8880
+ * - "clear-both" → both edits
8770
8881
  */
8771
- function buildContextManagement(modelId, hasThinking) {
8772
- if (!modelSupportsContextEditing(modelId)) return;
8882
+ function buildContextManagement(mode, hasThinking) {
8883
+ if (mode === "off") return;
8773
8884
  const triggerType = "input_tokens";
8774
8885
  const triggerValue = 1e5;
8775
8886
  const keepCount = 3;
8776
8887
  const thinkingKeepTurns = 1;
8777
8888
  const edits = [];
8778
- if (hasThinking) edits.push({
8889
+ if ((mode === "clear-thinking" || mode === "clear-both") && hasThinking) edits.push({
8779
8890
  type: "clear_thinking_20251015",
8780
8891
  keep: {
8781
8892
  type: "thinking_turns",
8782
8893
  value: Math.max(1, thinkingKeepTurns)
8783
8894
  }
8784
8895
  });
8785
- edits.push({
8896
+ if (mode === "clear-tooluse" || mode === "clear-both") edits.push({
8786
8897
  type: "clear_tool_uses_20250919",
8787
8898
  trigger: {
8788
8899
  type: triggerType,
@@ -8793,7 +8904,7 @@ function buildContextManagement(modelId, hasThinking) {
8793
8904
  value: keepCount
8794
8905
  }
8795
8906
  });
8796
- return { edits };
8907
+ return edits.length > 0 ? { edits } : void 0;
8797
8908
  }
8798
8909
 
8799
8910
  //#endregion
@@ -9108,8 +9219,9 @@ async function createAnthropicMessages(payload, opts) {
9108
9219
  "anthropic-version": "2023-06-01",
9109
9220
  ...buildAnthropicBetaHeaders(model, opts?.resolvedModel)
9110
9221
  };
9111
- if (!wire.context_management) {
9112
- const contextManagement = buildContextManagement(model, Boolean(thinking && thinking.type !== "disabled"));
9222
+ if (!wire.context_management && isContextEditingEnabled(model)) {
9223
+ const hasThinking = Boolean(thinking && thinking.type !== "disabled");
9224
+ const contextManagement = buildContextManagement(state.contextEditingMode, hasThinking);
9113
9225
  if (contextManagement) {
9114
9226
  wire.context_management = contextManagement;
9115
9227
  consola.debug("[DirectAnthropic] Added context_management:", JSON.stringify(contextManagement));
@@ -9123,6 +9235,7 @@ async function createAnthropicMessages(payload, opts) {
9123
9235
  body: JSON.stringify(wire),
9124
9236
  signal: fetchSignal
9125
9237
  });
9238
+ if (opts?.headersCapture) captureHttpHeaders(opts.headersCapture, headers, response);
9126
9239
  if (!response.ok) {
9127
9240
  consola.debug("Request failed:", {
9128
9241
  model,
@@ -9138,6 +9251,161 @@ async function createAnthropicMessages(payload, opts) {
9138
9251
  return await response.json();
9139
9252
  }
9140
9253
 
9254
+ //#endregion
9255
+ //#region src/lib/anthropic/message-mapping.ts
9256
+ /**
9257
+ * Check if two messages likely correspond to the same original message.
9258
+ * Used by buildMessageMapping to handle cases where sanitization removes
9259
+ * content blocks within a message (changing its shape) or removes entire messages.
9260
+ */
9261
+ function messagesMatch(orig, rewritten) {
9262
+ if (orig.role !== rewritten.role) return false;
9263
+ if (typeof orig.content === "string" && typeof rewritten.content === "string") return rewritten.content.startsWith(orig.content.slice(0, 100)) || orig.content.startsWith(rewritten.content.slice(0, 100));
9264
+ const origBlocks = Array.isArray(orig.content) ? orig.content : [];
9265
+ const rwBlocks = Array.isArray(rewritten.content) ? rewritten.content : [];
9266
+ if (origBlocks.length === 0 || rwBlocks.length === 0) return true;
9267
+ const ob = origBlocks[0];
9268
+ const rb = rwBlocks[0];
9269
+ if (ob.type !== rb.type) return false;
9270
+ if (ob.type === "tool_use" && rb.type === "tool_use") return ob.id === rb.id;
9271
+ if (ob.type === "tool_result" && rb.type === "tool_result") return ob.tool_use_id === rb.tool_use_id;
9272
+ return true;
9273
+ }
9274
+ /**
9275
+ * Build messageMapping (rwIdx → origIdx) for the direct Anthropic path.
9276
+ * Uses a two-pointer approach since rewritten messages maintain the same relative
9277
+ * order as originals (all transformations are deletions, never reorderings).
9278
+ */
9279
+ function buildMessageMapping(original, rewritten) {
9280
+ const mapping = [];
9281
+ let origIdx = 0;
9282
+ for (const element of rewritten) while (origIdx < original.length) {
9283
+ if (messagesMatch(original[origIdx], element)) {
9284
+ mapping.push(origIdx);
9285
+ origIdx++;
9286
+ break;
9287
+ }
9288
+ origIdx++;
9289
+ }
9290
+ while (mapping.length < rewritten.length) mapping.push(-1);
9291
+ return mapping;
9292
+ }
9293
+
9294
+ //#endregion
9295
+ //#region src/lib/anthropic/server-tool-filter.ts
9296
+ /**
9297
+ * Server tool block filter for Anthropic SSE streams and non-streaming responses.
9298
+ *
9299
+ * Always active — matching vscode-copilot-chat behavior, which intercepts
9300
+ * server_tool_use and *_tool_result blocks unconditionally. These are server-side
9301
+ * artifacts (e.g. tool_search injected by copilot-api, web_search) that clients
9302
+ * don't expect and most SDKs can't validate.
9303
+ *
9304
+ * Also provides logging for server tool blocks (called before filtering,
9305
+ * so information is never lost even when blocks are stripped).
9306
+ */
9307
+ /** Check if a block type is a server-side tool result (ends with _tool_result, but not plain tool_result) */
9308
+ function isServerToolResultType(type) {
9309
+ return type !== "tool_result" && type.endsWith("_tool_result");
9310
+ }
9311
+ /**
9312
+ * Check if a content block is a server-side tool block.
9313
+ * Matches `server_tool_use` (any name) and all server tool result types
9314
+ * (web_search_tool_result, tool_search_tool_result, code_execution_tool_result, etc.).
9315
+ */
9316
+ function isServerToolBlock(block) {
9317
+ if (block.type === "server_tool_use") return true;
9318
+ return isServerToolResultType(block.type);
9319
+ }
9320
+ /**
9321
+ * Log a single server tool block (server_tool_use or *_tool_result).
9322
+ * No-op for non-server-tool blocks — safe to call unconditionally.
9323
+ *
9324
+ * Called before filtering, so information is never lost even when blocks are stripped.
9325
+ */
9326
+ function logServerToolBlock(block) {
9327
+ if (block.type === "server_tool_use") {
9328
+ consola.debug(`[ServerTool] server_tool_use: ${block.name}`);
9329
+ return;
9330
+ }
9331
+ if (!isServerToolResultType(block.type)) return;
9332
+ const content = block.content;
9333
+ if (!content) return;
9334
+ const contentType = content.type;
9335
+ if (contentType === "tool_search_tool_search_result") {
9336
+ const toolNames = content.tool_references?.map((r) => r.tool_name).filter(Boolean) ?? [];
9337
+ consola.debug(`[ServerTool] tool_search result: discovered ${toolNames.length} tools${toolNames.length > 0 ? ` [${toolNames.join(", ")}]` : ""}`);
9338
+ } else if (contentType === "tool_search_tool_result_error") consola.warn(`[ServerTool] tool_search error: ${content.error_code}`);
9339
+ else consola.debug(`[ServerTool] ${block.type}: ${contentType ?? "unknown"}`);
9340
+ }
9341
+ /**
9342
+ * Log all server tool blocks from a non-streaming response content array.
9343
+ * Must be called before filterServerToolBlocksFromResponse() to preserve info.
9344
+ */
9345
+ function logServerToolBlocks(content) {
9346
+ for (const block of content) logServerToolBlock(block);
9347
+ }
9348
+ /**
9349
+ * Filters server tool blocks from the SSE stream before forwarding to the client.
9350
+ * Handles index remapping so block indices remain dense/sequential after filtering.
9351
+ *
9352
+ * Always active — matching vscode-copilot-chat behavior, which intercepts
9353
+ * server_tool_use and *_tool_result blocks unconditionally. These are server-side
9354
+ * artifacts (e.g. tool_search injected by copilot-api, web_search) that clients
9355
+ * don't expect and most SDKs can't validate.
9356
+ */
9357
+ function createServerToolBlockFilter() {
9358
+ const filteredIndices = /* @__PURE__ */ new Set();
9359
+ const clientIndexMap = /* @__PURE__ */ new Map();
9360
+ let nextClientIndex = 0;
9361
+ function getClientIndex(apiIndex) {
9362
+ let idx = clientIndexMap.get(apiIndex);
9363
+ if (idx === void 0) {
9364
+ idx = nextClientIndex++;
9365
+ clientIndexMap.set(apiIndex, idx);
9366
+ }
9367
+ return idx;
9368
+ }
9369
+ return { rewriteEvent(parsed, rawData) {
9370
+ if (!parsed) return rawData;
9371
+ if (parsed.type === "content_block_start") {
9372
+ const block = parsed.content_block;
9373
+ if (isServerToolBlock(block)) {
9374
+ filteredIndices.add(parsed.index);
9375
+ return null;
9376
+ }
9377
+ if (filteredIndices.size === 0) {
9378
+ getClientIndex(parsed.index);
9379
+ return rawData;
9380
+ }
9381
+ const clientIndex = getClientIndex(parsed.index);
9382
+ if (clientIndex === parsed.index) return rawData;
9383
+ const obj = JSON.parse(rawData);
9384
+ obj.index = clientIndex;
9385
+ return JSON.stringify(obj);
9386
+ }
9387
+ if (parsed.type === "content_block_delta" || parsed.type === "content_block_stop") {
9388
+ if (filteredIndices.has(parsed.index)) return null;
9389
+ if (filteredIndices.size === 0) return rawData;
9390
+ const clientIndex = getClientIndex(parsed.index);
9391
+ if (clientIndex === parsed.index) return rawData;
9392
+ const obj = JSON.parse(rawData);
9393
+ obj.index = clientIndex;
9394
+ return JSON.stringify(obj);
9395
+ }
9396
+ return rawData;
9397
+ } };
9398
+ }
9399
+ /** Filter server tool blocks from a non-streaming response */
9400
+ function filterServerToolBlocksFromResponse(response) {
9401
+ const filtered = response.content.filter((block) => !isServerToolBlock(block));
9402
+ if (filtered.length === response.content.length) return response;
9403
+ return {
9404
+ ...response,
9405
+ content: filtered
9406
+ };
9407
+ }
9408
+
9141
9409
  //#endregion
9142
9410
  //#region src/lib/anthropic/stream-accumulator.ts
9143
9411
  /**
@@ -9260,10 +9528,6 @@ function handleContentBlockStart(index, block, acc) {
9260
9528
  }
9261
9529
  acc.contentBlocks[index] = newBlock;
9262
9530
  }
9263
- /** Check if a block type is a server-side tool result (ends with _tool_result, but not plain tool_result) */
9264
- function isServerToolResultType(type) {
9265
- return type !== "tool_result" && type.endsWith("_tool_result");
9266
- }
9267
9531
  function handleContentBlockDelta(index, delta, acc, copilotAnnotations) {
9268
9532
  const block = acc.contentBlocks[index];
9269
9533
  if (!block) return;
@@ -9313,7 +9577,7 @@ function handleMessageDelta(delta, usage, acc) {
9313
9577
  }
9314
9578
 
9315
9579
  //#endregion
9316
- //#region src/lib/anthropic/handlers.ts
9580
+ //#region src/lib/anthropic/sse.ts
9317
9581
  /**
9318
9582
  * Check if a model supports direct Anthropic API.
9319
9583
  * Returns a decision with reason so callers can log/display the routing rationale.
@@ -9374,46 +9638,6 @@ async function* processAnthropicStream(response, acc, clientAbortSignal) {
9374
9638
  }
9375
9639
  }
9376
9640
 
9377
- //#endregion
9378
- //#region src/lib/anthropic/message-mapping.ts
9379
- /**
9380
- * Check if two messages likely correspond to the same original message.
9381
- * Used by buildMessageMapping to handle cases where sanitization removes
9382
- * content blocks within a message (changing its shape) or removes entire messages.
9383
- */
9384
- function messagesMatch(orig, rewritten) {
9385
- if (orig.role !== rewritten.role) return false;
9386
- if (typeof orig.content === "string" && typeof rewritten.content === "string") return rewritten.content.startsWith(orig.content.slice(0, 100)) || orig.content.startsWith(rewritten.content.slice(0, 100));
9387
- const origBlocks = Array.isArray(orig.content) ? orig.content : [];
9388
- const rwBlocks = Array.isArray(rewritten.content) ? rewritten.content : [];
9389
- if (origBlocks.length === 0 || rwBlocks.length === 0) return true;
9390
- const ob = origBlocks[0];
9391
- const rb = rwBlocks[0];
9392
- if (ob.type !== rb.type) return false;
9393
- if (ob.type === "tool_use" && rb.type === "tool_use") return ob.id === rb.id;
9394
- if (ob.type === "tool_result" && rb.type === "tool_result") return ob.tool_use_id === rb.tool_use_id;
9395
- return true;
9396
- }
9397
- /**
9398
- * Build messageMapping (rwIdx → origIdx) for the direct Anthropic path.
9399
- * Uses a two-pointer approach since rewritten messages maintain the same relative
9400
- * order as originals (all transformations are deletions, never reorderings).
9401
- */
9402
- function buildMessageMapping(original, rewritten) {
9403
- const mapping = [];
9404
- let origIdx = 0;
9405
- for (const element of rewritten) while (origIdx < original.length) {
9406
- if (messagesMatch(original[origIdx], element)) {
9407
- mapping.push(origIdx);
9408
- origIdx++;
9409
- break;
9410
- }
9411
- origIdx++;
9412
- }
9413
- while (mapping.length < rewritten.length) mapping.push(-1);
9414
- return mapping;
9415
- }
9416
-
9417
9641
  //#endregion
9418
9642
  //#region src/lib/repetition-detector.ts
9419
9643
  /**
@@ -9704,10 +9928,14 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx) {
9704
9928
  if (initialSanitized.thinking && initialSanitized.thinking.type !== "disabled") tags.push(`thinking:${initialSanitized.thinking.type}`);
9705
9929
  if (tags.length > 0) tuiLogger.updateRequest(reqCtx.tuiLogId, { tags });
9706
9930
  }
9931
+ const headersCapture = {};
9707
9932
  const adapter = {
9708
9933
  format: "anthropic-messages",
9709
9934
  sanitize: (p) => sanitizeAnthropicMessages(preprocessTools(p)),
9710
- execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p, { resolvedModel: selectedModel })),
9935
+ execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p, {
9936
+ resolvedModel: selectedModel,
9937
+ headersCapture
9938
+ })),
9711
9939
  logPayloadSize: (p) => logPayloadSizeInfoAnthropic(p, selectedModel)
9712
9940
  };
9713
9941
  const strategies = [
@@ -9755,6 +9983,7 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx) {
9755
9983
  }
9756
9984
  }
9757
9985
  });
9986
+ reqCtx.setHttpHeaders(headersCapture);
9758
9987
  const response = result.response;
9759
9988
  const effectivePayload = result.effectivePayload;
9760
9989
  if (Symbol.asyncIterator in response) {
@@ -9774,6 +10003,7 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx) {
9774
10003
  }
9775
10004
  return handleDirectAnthropicNonStreamingResponse(c, response, reqCtx, truncateResult);
9776
10005
  } catch (error) {
10006
+ reqCtx.setHttpHeaders(headersCapture);
9777
10007
  reqCtx.fail(anthropicPayload.model, error);
9778
10008
  throw error;
9779
10009
  }
@@ -9789,7 +10019,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
9789
10019
  let eventsIn = 0;
9790
10020
  let currentBlockType = "";
9791
10021
  let firstEventLogged = false;
9792
- const serverToolFilter = state.stripServerTools ? createServerToolBlockFilter() : null;
10022
+ const serverToolFilter = createServerToolBlockFilter();
9793
10023
  try {
9794
10024
  for await (const { raw: rawEvent, parsed } of processAnthropicStream(response, acc, clientAbortSignal)) {
9795
10025
  const dataLen = rawEvent.data?.length ?? 0;
@@ -9809,8 +10039,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
9809
10039
  currentBlockType = parsed.content_block.type;
9810
10040
  consola.debug(`[Stream] Block #${parsed.index} start: ${currentBlockType} at +${Date.now() - streamStartMs}ms`);
9811
10041
  const block = parsed.content_block;
9812
- if (block.type === "server_tool_use") consola.debug(`[ServerTool] server_tool_use: ${block.name}`);
9813
- else if (block.type !== "tool_result" && block.type.endsWith("_tool_result")) logServerToolResult(block);
10042
+ logServerToolBlock(block);
9814
10043
  } else if (parsed?.type === "content_block_stop") {
9815
10044
  const offset = Date.now() - streamStartMs;
9816
10045
  consola.debug(`[Stream] Block #${parsed.index} stop (${currentBlockType}) at +${offset}ms, cumulative ↓${bytesIn}B ${eventsIn}ev`);
@@ -9825,7 +10054,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
9825
10054
  const delta = parsed.delta;
9826
10055
  if (delta.type === "text_delta" && delta.text) checkRepetition(delta.text);
9827
10056
  }
9828
- const forwardData = serverToolFilter ? serverToolFilter.rewriteEvent(parsed, rawEvent.data ?? "") : rawEvent.data ?? "";
10057
+ const forwardData = serverToolFilter.rewriteEvent(parsed, rawEvent.data ?? "");
9829
10058
  if (forwardData === null) continue;
9830
10059
  await stream.writeSSE({
9831
10060
  data: forwardData,
@@ -9880,31 +10109,9 @@ function handleDirectAnthropicNonStreamingResponse(c, response, reqCtx, truncate
9880
10109
  let finalResponse = response;
9881
10110
  if (state.verbose && truncateResult?.wasTruncated) finalResponse = prependMarkerToResponse(response, createTruncationMarker$1(truncateResult));
9882
10111
  logServerToolBlocks(finalResponse.content);
9883
- if (state.stripServerTools) finalResponse = filterServerToolBlocksFromResponse(finalResponse);
10112
+ finalResponse = filterServerToolBlocksFromResponse(finalResponse);
9884
10113
  return c.json(finalResponse);
9885
10114
  }
9886
- /**
9887
- * Log information extracted from a server tool result block.
9888
- * Called before filtering, so information is never lost even when blocks are stripped.
9889
- */
9890
- function logServerToolResult(block) {
9891
- const content = block.content;
9892
- if (!content) return;
9893
- const contentType = content.type;
9894
- if (contentType === "tool_search_tool_search_result") {
9895
- const toolNames = content.tool_references?.map((r) => r.tool_name).filter(Boolean) ?? [];
9896
- consola.debug(`[ServerTool] tool_search result: discovered ${toolNames.length} tools${toolNames.length > 0 ? ` [${toolNames.join(", ")}]` : ""}`);
9897
- } else if (contentType === "tool_search_tool_result_error") consola.warn(`[ServerTool] tool_search error: ${content.error_code}`);
9898
- else consola.debug(`[ServerTool] ${block.type}: ${contentType ?? "unknown"}`);
9899
- }
9900
- /**
9901
- * Log server tool blocks from a non-streaming response.
9902
- * Must be called before filterServerToolBlocksFromResponse() to preserve info.
9903
- */
9904
- function logServerToolBlocks(content) {
9905
- for (const block of content) if (block.type === "server_tool_use") consola.debug(`[ServerTool] server_tool_use: ${block.name}`);
9906
- else if (block.type !== "tool_result" && block.type.endsWith("_tool_result")) logServerToolResult(block);
9907
- }
9908
10115
  /** Convert SanitizationStats to the format expected by rewrites */
9909
10116
  function toSanitizationInfo(stats) {
9910
10117
  return {
@@ -9916,75 +10123,6 @@ function toSanitizationInfo(stats) {
9916
10123
  systemReminderRemovals: stats.systemReminderRemovals
9917
10124
  };
9918
10125
  }
9919
- /**
9920
- * Check if a content block is a server-side tool block.
9921
- * Matches `server_tool_use` (any name) and all server tool result types
9922
- * (web_search_tool_result, tool_search_tool_result, code_execution_tool_result, etc.).
9923
- */
9924
- function isServerToolBlock(block) {
9925
- if (block.type === "server_tool_use") return true;
9926
- return block.type !== "tool_result" && block.type.endsWith("_tool_result");
9927
- }
9928
- /**
9929
- * Filters server tool blocks from the SSE stream before forwarding to the client.
9930
- * Handles index remapping so block indices remain dense/sequential after filtering.
9931
- *
9932
- * Only active when stripServerTools is enabled — in that mode, server tools
9933
- * were stripped from the request, so any server_tool_use blocks in the response
9934
- * are unexpected artifacts. When disabled (default), server tool blocks are
9935
- * transparently forwarded per Anthropic protocol.
9936
- */
9937
- function createServerToolBlockFilter() {
9938
- const filteredIndices = /* @__PURE__ */ new Set();
9939
- const clientIndexMap = /* @__PURE__ */ new Map();
9940
- let nextClientIndex = 0;
9941
- function getClientIndex(apiIndex) {
9942
- let idx = clientIndexMap.get(apiIndex);
9943
- if (idx === void 0) {
9944
- idx = nextClientIndex++;
9945
- clientIndexMap.set(apiIndex, idx);
9946
- }
9947
- return idx;
9948
- }
9949
- return { rewriteEvent(parsed, rawData) {
9950
- if (!parsed) return rawData;
9951
- if (parsed.type === "content_block_start") {
9952
- const block = parsed.content_block;
9953
- if (isServerToolBlock(block)) {
9954
- filteredIndices.add(parsed.index);
9955
- return null;
9956
- }
9957
- if (filteredIndices.size === 0) {
9958
- getClientIndex(parsed.index);
9959
- return rawData;
9960
- }
9961
- const clientIndex = getClientIndex(parsed.index);
9962
- if (clientIndex === parsed.index) return rawData;
9963
- const obj = JSON.parse(rawData);
9964
- obj.index = clientIndex;
9965
- return JSON.stringify(obj);
9966
- }
9967
- if (parsed.type === "content_block_delta" || parsed.type === "content_block_stop") {
9968
- if (filteredIndices.has(parsed.index)) return null;
9969
- if (filteredIndices.size === 0) return rawData;
9970
- const clientIndex = getClientIndex(parsed.index);
9971
- if (clientIndex === parsed.index) return rawData;
9972
- const obj = JSON.parse(rawData);
9973
- obj.index = clientIndex;
9974
- return JSON.stringify(obj);
9975
- }
9976
- return rawData;
9977
- } };
9978
- }
9979
- /** Filter server tool blocks from a non-streaming response */
9980
- function filterServerToolBlocksFromResponse(response) {
9981
- const filtered = response.content.filter((block) => !isServerToolBlock(block));
9982
- if (filtered.length === response.content.length) return response;
9983
- return {
9984
- ...response,
9985
- content: filtered
9986
- };
9987
- }
9988
10126
 
9989
10127
  //#endregion
9990
10128
  //#region src/routes/messages/route.ts
@@ -10093,7 +10231,8 @@ async function handleResponses(c) {
10093
10231
  async function handleDirectResponses(opts) {
10094
10232
  const { c, payload, reqCtx } = opts;
10095
10233
  const selectedModel = state.modelIndex.get(payload.model);
10096
- const adapter = createResponsesAdapter(selectedModel);
10234
+ const headersCapture = {};
10235
+ const adapter = createResponsesAdapter(selectedModel, headersCapture);
10097
10236
  const strategies = createResponsesStrategies();
10098
10237
  try {
10099
10238
  const pipelineResult = await executeRequestPipeline({
@@ -10105,6 +10244,7 @@ async function handleDirectResponses(opts) {
10105
10244
  maxRetries: 1,
10106
10245
  requestContext: reqCtx
10107
10246
  });
10247
+ reqCtx.setHttpHeaders(headersCapture);
10108
10248
  const response = pipelineResult.response;
10109
10249
  reqCtx.addQueueWaitMs(pipelineResult.queueWaitMs);
10110
10250
  if (!payload.stream) {
@@ -10177,6 +10317,7 @@ async function handleDirectResponses(opts) {
10177
10317
  }
10178
10318
  });
10179
10319
  } catch (error) {
10320
+ reqCtx.setHttpHeaders(headersCapture);
10180
10321
  reqCtx.fail(payload.model, error);
10181
10322
  throw error;
10182
10323
  }
@@ -10259,6 +10400,7 @@ server.notFound((c) => {
10259
10400
  });
10260
10401
  server.use(async (_c, next) => {
10261
10402
  await applyConfigToState();
10403
+ await ensureValidCopilotToken();
10262
10404
  await next();
10263
10405
  });
10264
10406
  server.use(tuiMiddleware());
@@ -10343,6 +10485,7 @@ async function runServer(options) {
10343
10485
  state.showGitHubToken = options.showGitHubToken;
10344
10486
  state.autoTruncate = options.autoTruncate;
10345
10487
  await ensurePaths();
10488
+ consola.info(`Data directory: ${PATHS.APP_DIR}`);
10346
10489
  const config = await applyConfigToState();
10347
10490
  const proxyUrl = options.proxy ?? config.proxy;
10348
10491
  initProxy({
@@ -10428,8 +10571,9 @@ async function runServer(options) {
10428
10571
  if (runtime?.bun?.server) c.env = { server: runtime.bun.server };
10429
10572
  await next();
10430
10573
  });
10431
- const injectHistoryWs = await initHistoryWebSocket(server);
10432
- const injectResponsesWs = await initResponsesWebSocket(server);
10574
+ const wsAdapter = await createWebSocketAdapter(server);
10575
+ initHistoryWebSocket(server, wsAdapter.upgradeWebSocket);
10576
+ initResponsesWebSocket(server, wsAdapter.upgradeWebSocket);
10433
10577
  consola.box(`Web UI:\n🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage\n📜 History UI: ${serverUrl}/history`);
10434
10578
  const bunWebSocket = typeof globalThis.Bun !== "undefined" ? (await import("hono/bun")).websocket : void 0;
10435
10579
  let serverInstance;
@@ -10451,13 +10595,9 @@ async function runServer(options) {
10451
10595
  }
10452
10596
  setServerInstance(serverInstance);
10453
10597
  setupShutdownHandlers();
10454
- if (injectHistoryWs || injectResponsesWs) {
10598
+ if (wsAdapter.injectWebSocket) {
10455
10599
  const nodeServer = serverInstance.node?.server;
10456
- if (nodeServer && "on" in nodeServer) {
10457
- const ns = nodeServer;
10458
- injectHistoryWs?.(ns);
10459
- injectResponsesWs?.(ns);
10460
- }
10600
+ if (nodeServer && "on" in nodeServer) wsAdapter.injectWebSocket(nodeServer);
10461
10601
  }
10462
10602
  await waitForShutdown();
10463
10603
  }