@openhoo/hoopilot 0.8.3 → 0.9.0

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
@@ -50,6 +50,27 @@ curl -fsSL https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/insta
50
50
 
51
51
  The standalone installer also installs a `codexx` wrapper next to `hoopilot`. Re-run the installer if `hoopilot` works but your shell does not recognize `codexx`; the installer stops the installed `hoopilot.exe` if needed and replaces the existing files in place.
52
52
 
53
+ ### Docker
54
+
55
+ Run Hoopilot as a long-lived service from the published multi-arch image on the GitHub Container Registry (`linux/amd64` and `linux/arm64`):
56
+
57
+ ```sh
58
+ # 1. Sign in once; the OAuth credential is written to the persisted /data volume.
59
+ docker run --rm -it -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login
60
+
61
+ # 2. Run the proxy. An API key is required because the container binds 0.0.0.0.
62
+ docker run -d --name hoopilot --restart unless-stopped \
63
+ -p 4141:4141 -e HOOPILOT_API_KEY=local-key \
64
+ -v hoopilot-data:/data ghcr.io/openhoo/hoopilot
65
+ ```
66
+
67
+ Tags follow the release version (e.g. `ghcr.io/openhoo/hoopilot:0.8`, `:0.8.3`) plus `:latest`. The image listens on `0.0.0.0:4141`, runs as a non-root user, and stores its OAuth credential at `/data/auth.json` (override with `HOOPILOT_AUTH_FILE`). A `docker-compose.yml` is provided in the repository:
68
+
69
+ ```sh
70
+ docker compose run --rm hoopilot login # one-time GitHub OAuth
71
+ HOOPILOT_API_KEY=local-key docker compose up -d
72
+ ```
73
+
53
74
  ## Update
54
75
 
55
76
  Standalone binaries update themselves in place from the latest GitHub release:
package/dist/cli.js CHANGED
@@ -690,6 +690,25 @@ function completionStreamFromChatStream(chatStream) {
690
690
  }
691
691
  });
692
692
  }
693
+ function completionSseTextFromChatSseText(text) {
694
+ const chunks = [];
695
+ let sawTerminalEvent = false;
696
+ const enqueue = (data) => {
697
+ chunks.push(encodeDataSse(data));
698
+ };
699
+ const markTerminal = () => {
700
+ sawTerminalEvent = true;
701
+ };
702
+ for (const block of text.split(/\r?\n\r?\n/)) {
703
+ if (block.trim()) {
704
+ processCompletionSseBlock(block, enqueue, markTerminal);
705
+ }
706
+ }
707
+ if (!sawTerminalEvent) {
708
+ enqueue("[DONE]");
709
+ }
710
+ return chunks.join("");
711
+ }
693
712
  function normalizeModelsResponse(upstream) {
694
713
  const record = asRecord(upstream);
695
714
  const data = Array.isArray(record.data) ? record.data : Array.isArray(upstream) ? upstream : [];
@@ -950,16 +969,7 @@ function responsesStreamToAnthropicStream(stream, options) {
950
969
  const decoder = new TextDecoder();
951
970
  const encoder = new TextEncoder();
952
971
  let buffer = "";
953
- const state = {
954
- blocks: /* @__PURE__ */ new Map(),
955
- completed: false,
956
- messageId: options.messageId ?? `msg_${randomId2()}`,
957
- model: options.model,
958
- nextBlockIndex: 0,
959
- sawToolUse: false,
960
- started: false,
961
- usage: anthropicUsage(void 0)
962
- };
972
+ const state = createAnthropicStreamState(options);
963
973
  return new ReadableStream({
964
974
  async start(controller) {
965
975
  const enqueue = (event, data) => {
@@ -995,6 +1005,20 @@ function responsesStreamToAnthropicStream(stream, options) {
995
1005
  }
996
1006
  });
997
1007
  }
1008
+ function responsesSseTextToAnthropicSseText(text, options) {
1009
+ const chunks = [];
1010
+ const state = createAnthropicStreamState(options);
1011
+ const enqueue = (event, data) => {
1012
+ chunks.push(encodeSse(event, data));
1013
+ };
1014
+ for (const block of text.split(/\r?\n\r?\n/)) {
1015
+ if (block.trim()) {
1016
+ processResponsesSseBlock(block, state, enqueue);
1017
+ }
1018
+ }
1019
+ finishAnthropicStream(state, enqueue);
1020
+ return chunks.join("");
1021
+ }
998
1022
  function estimateAnthropicMessageTokens(request) {
999
1023
  const chars = estimatedTextSize(request.system) + estimatedTextSize(request.messages) + estimatedTextSize(request.tools) + estimatedTextSize(request.tool_choice) + estimatedTextSize(request.thinking);
1000
1024
  const messageCount = Array.isArray(request.messages) ? request.messages.length : 1;
@@ -1005,6 +1029,18 @@ function estimateAnthropicMessageTokens(request) {
1005
1029
  total_tokens: inputTokens
1006
1030
  };
1007
1031
  }
1032
+ function createAnthropicStreamState(options) {
1033
+ return {
1034
+ blocks: /* @__PURE__ */ new Map(),
1035
+ completed: false,
1036
+ messageId: options.messageId ?? `msg_${randomId2()}`,
1037
+ model: options.model,
1038
+ nextBlockIndex: 0,
1039
+ sawToolUse: false,
1040
+ started: false,
1041
+ usage: anthropicUsage(void 0)
1042
+ };
1043
+ }
1008
1044
  function anthropicMessagesToResponsesInput(messages) {
1009
1045
  if (!Array.isArray(messages)) {
1010
1046
  throw new AnthropicCompatibilityError("Anthropic Messages requests require messages[].");
@@ -1845,6 +1881,20 @@ function observeResponseUsage(response, fallbackModel, onUsage, signal) {
1845
1881
  statusText: response.statusText
1846
1882
  });
1847
1883
  }
1884
+ function recordResponseTextUsage(text, isSse, fallbackModel, onUsage) {
1885
+ const accumulator = createUsageAccumulator(fallbackModel, onUsage);
1886
+ if (isSse) {
1887
+ for (const line of text.split(/\r?\n/)) {
1888
+ considerSseLine(line, accumulator.consider);
1889
+ }
1890
+ } else {
1891
+ const parsed = safeParse(text);
1892
+ if (parsed !== void 0) {
1893
+ accumulator.consider(parsed);
1894
+ }
1895
+ }
1896
+ accumulator.finish();
1897
+ }
1848
1898
  async function consumeUsage(stream, isSse, fallbackModel, onUsage, signal) {
1849
1899
  const reader = stream.getReader();
1850
1900
  const onAbort = () => {
@@ -1858,22 +1908,10 @@ async function consumeUsage(stream, isSse, fallbackModel, onUsage, signal) {
1858
1908
  signal?.addEventListener("abort", onAbort, { once: true });
1859
1909
  }
1860
1910
  const decoder = new TextDecoder();
1861
- let model = fallbackModel;
1862
- let usage;
1911
+ const accumulator = createUsageAccumulator(fallbackModel, onUsage);
1863
1912
  let buffer = "";
1864
1913
  let bufferedBytes = 0;
1865
1914
  let overflowed = false;
1866
- const consider = (payload) => {
1867
- const record = asRecord(payload);
1868
- const found = extractTokenUsage(record.usage) ?? extractTokenUsage(asRecord(record.response).usage);
1869
- if (found) {
1870
- usage = found;
1871
- }
1872
- const candidateModel = modelText(record.model) || modelText(asRecord(record.response).model);
1873
- if (candidateModel) {
1874
- model = candidateModel;
1875
- }
1876
- };
1877
1915
  try {
1878
1916
  while (true) {
1879
1917
  const result = await reader.read();
@@ -1886,7 +1924,7 @@ async function consumeUsage(stream, isSse, fallbackModel, onUsage, signal) {
1886
1924
  const lines = buffer.split(/\r?\n/);
1887
1925
  buffer = lines.pop() ?? "";
1888
1926
  for (const line of lines) {
1889
- considerSseLine(line, consider);
1927
+ considerSseLine(line, accumulator.consider);
1890
1928
  }
1891
1929
  if (buffer.length > USAGE_BUFFER_LIMIT_BYTES) {
1892
1930
  buffer = "";
@@ -1904,21 +1942,41 @@ async function consumeUsage(stream, isSse, fallbackModel, onUsage, signal) {
1904
1942
  const finalBuffer = buffer + decoder.decode();
1905
1943
  if (isSse) {
1906
1944
  if (finalBuffer) {
1907
- considerSseLine(finalBuffer, consider);
1945
+ considerSseLine(finalBuffer, accumulator.consider);
1908
1946
  }
1909
1947
  } else if (!overflowed && finalBuffer) {
1910
1948
  const parsed = safeParse(finalBuffer);
1911
1949
  if (parsed !== void 0) {
1912
- consider(parsed);
1950
+ accumulator.consider(parsed);
1913
1951
  }
1914
1952
  }
1915
1953
  } finally {
1916
1954
  signal?.removeEventListener("abort", onAbort);
1917
1955
  reader.releaseLock();
1918
1956
  }
1919
- if (usage) {
1920
- onUsage(model, usage);
1921
- }
1957
+ accumulator.finish();
1958
+ }
1959
+ function createUsageAccumulator(fallbackModel, onUsage) {
1960
+ let model = fallbackModel;
1961
+ let usage;
1962
+ return {
1963
+ consider(payload) {
1964
+ const record = asRecord(payload);
1965
+ const found = extractTokenUsage(record.usage) ?? extractTokenUsage(asRecord(record.response).usage);
1966
+ if (found) {
1967
+ usage = found;
1968
+ }
1969
+ const candidateModel = modelText(record.model) || modelText(asRecord(record.response).model);
1970
+ if (candidateModel) {
1971
+ model = candidateModel;
1972
+ }
1973
+ },
1974
+ finish() {
1975
+ if (usage) {
1976
+ onUsage(model, usage);
1977
+ }
1978
+ }
1979
+ };
1922
1980
  }
1923
1981
  function considerSseLine(line, consider) {
1924
1982
  const trimmed = line.trim();
@@ -1965,6 +2023,30 @@ function formatNumber(value) {
1965
2023
  return Number.isInteger(value) ? value.toString() : String(value);
1966
2024
  }
1967
2025
 
2026
+ // src/version.ts
2027
+ var BAKED_VERSION = typeof HOOPILOT_VERSION !== "undefined" ? HOOPILOT_VERSION : void 0;
2028
+ var BAKED_TARGET = typeof HOOPILOT_TARGET !== "undefined" ? HOOPILOT_TARGET : void 0;
2029
+ var IS_STANDALONE_BINARY = BAKED_VERSION !== void 0;
2030
+ var cachedVersion;
2031
+ async function getVersion() {
2032
+ if (cachedVersion !== void 0) {
2033
+ return cachedVersion;
2034
+ }
2035
+ let resolved;
2036
+ if (BAKED_VERSION) {
2037
+ resolved = BAKED_VERSION;
2038
+ } else {
2039
+ try {
2040
+ const manifest = await Bun.file(new URL("../package.json", import.meta.url)).json();
2041
+ resolved = typeof manifest.version === "string" ? manifest.version : "0.0.0";
2042
+ } catch {
2043
+ resolved = "0.0.0";
2044
+ }
2045
+ }
2046
+ cachedVersion = resolved;
2047
+ return resolved;
2048
+ }
2049
+
1968
2050
  // src/server.ts
1969
2051
  var DEFAULT_HOST = "127.0.0.1";
1970
2052
  var DEFAULT_PORT = 4141;
@@ -1988,6 +2070,8 @@ function createHoopilotHandler(options = {}) {
1988
2070
  const metrics = options.metrics ?? new MetricsRegistry();
1989
2071
  const readUsage = createUsageReader(client, metrics);
1990
2072
  const recordTokens = (model, usage) => metrics.recordTokens(model, usage);
2073
+ const streamingProxyMode = resolveStreamingProxyMode(options);
2074
+ const bufferProxyBodies = shouldBufferProxyBodies(streamingProxyMode);
1991
2075
  return async (request) => {
1992
2076
  const startedAt = performance.now();
1993
2077
  const url = new URL(request.url);
@@ -2007,7 +2091,9 @@ function createHoopilotHandler(options = {}) {
2007
2091
  metrics,
2008
2092
  requestId,
2009
2093
  route,
2010
- startedAt
2094
+ startedAt,
2095
+ closeConnection: bufferProxyBodies,
2096
+ trackStreamingBody: !bufferProxyBodies
2011
2097
  });
2012
2098
  const browserOrigin = forbiddenBrowserOrigin(request, apiKey);
2013
2099
  if (browserOrigin) {
@@ -2042,7 +2128,14 @@ function createHoopilotHandler(options = {}) {
2042
2128
  }
2043
2129
  if (request.method === "POST" && apiPath === "/v1/messages") {
2044
2130
  return finish(
2045
- await handleAnthropicMessages(client, metrics, recordTokens, request, requestLogger)
2131
+ await handleAnthropicMessages(
2132
+ client,
2133
+ metrics,
2134
+ recordTokens,
2135
+ request,
2136
+ requestLogger,
2137
+ bufferProxyBodies
2138
+ )
2046
2139
  );
2047
2140
  }
2048
2141
  if (request.method === "POST" && apiPath === "/v1/messages/count_tokens") {
@@ -2050,16 +2143,39 @@ function createHoopilotHandler(options = {}) {
2050
2143
  }
2051
2144
  if (request.method === "POST" && apiPath === "/v1/chat/completions") {
2052
2145
  return finish(
2053
- await handleChatCompletions(client, metrics, recordTokens, request, requestLogger)
2146
+ await handleChatCompletions(
2147
+ client,
2148
+ metrics,
2149
+ recordTokens,
2150
+ request,
2151
+ requestLogger,
2152
+ bufferProxyBodies
2153
+ )
2054
2154
  );
2055
2155
  }
2056
2156
  if (request.method === "POST" && apiPath === "/v1/completions") {
2057
2157
  return finish(
2058
- await handleCompletions(client, metrics, recordTokens, request, requestLogger)
2158
+ await handleCompletions(
2159
+ client,
2160
+ metrics,
2161
+ recordTokens,
2162
+ request,
2163
+ requestLogger,
2164
+ bufferProxyBodies
2165
+ )
2059
2166
  );
2060
2167
  }
2061
2168
  if (request.method === "POST" && apiPath === "/v1/responses") {
2062
- return finish(await handleResponses(client, metrics, recordTokens, request, requestLogger));
2169
+ return finish(
2170
+ await handleResponses(
2171
+ client,
2172
+ metrics,
2173
+ recordTokens,
2174
+ request,
2175
+ requestLogger,
2176
+ bufferProxyBodies
2177
+ )
2178
+ );
2063
2179
  }
2064
2180
  return finish(jsonError(404, "not_found", `No route for ${request.method} ${url.pathname}.`));
2065
2181
  } catch (error) {
@@ -2124,7 +2240,7 @@ function startHoopilotServer(options = {}) {
2124
2240
  url: `http://${urlHost(host)}:${server.port}`
2125
2241
  };
2126
2242
  }
2127
- async function handleAnthropicMessages(client, metrics, recordTokens, request, logger) {
2243
+ async function handleAnthropicMessages(client, metrics, recordTokens, request, logger, bufferProxyBodies) {
2128
2244
  const anthropicRequest = await readJson(request);
2129
2245
  const responsesRequest = anthropicMessagesToResponsesRequest(anthropicRequest);
2130
2246
  const upstream = await client.responses(JSON.stringify(responsesRequest), request.signal);
@@ -2135,6 +2251,13 @@ async function handleAnthropicMessages(client, metrics, recordTokens, request, l
2135
2251
  logUpstreamSuccess(logger, "/responses", upstream.status);
2136
2252
  const model = normalizeRequestedModel(responsesRequest.model);
2137
2253
  if (isStreamingResponse(upstream) && upstream.body) {
2254
+ if (bufferProxyBodies) {
2255
+ const text = await upstream.text();
2256
+ recordResponseTextUsage(text, true, model, recordTokens);
2257
+ return proxyResponse(
2258
+ responseFromText(upstream, responsesSseTextToAnthropicSseText(text, { model }))
2259
+ );
2260
+ }
2138
2261
  const observed = observeResponseUsage(upstream, model, recordTokens, request.signal);
2139
2262
  if (!observed.body) {
2140
2263
  return proxyResponse(observed);
@@ -2178,7 +2301,7 @@ async function handleModels(client, metrics, signal, logger) {
2178
2301
  logUpstreamSuccess(logger, "/models", upstream.status);
2179
2302
  return jsonResponse(normalizeModelsResponse(await upstream.json()));
2180
2303
  }
2181
- async function handleChatCompletions(client, metrics, recordTokens, request, logger) {
2304
+ async function handleChatCompletions(client, metrics, recordTokens, request, logger, bufferProxyBodies) {
2182
2305
  const chatRequest = normalizeChatCompletionRequest(await readJson(request));
2183
2306
  const upstream = await client.chatCompletions(chatRequest, request.signal);
2184
2307
  metrics.recordUpstream("/chat/completions", upstream.ok);
@@ -2187,9 +2310,17 @@ async function handleChatCompletions(client, metrics, recordTokens, request, log
2187
2310
  }
2188
2311
  logUpstreamSuccess(logger, "/chat/completions", upstream.status);
2189
2312
  const model = normalizeRequestedModel(chatRequest.model);
2190
- return proxyResponse(observeResponseUsage(upstream, model, recordTokens, request.signal));
2313
+ return proxyResponse(
2314
+ await responseWithObservedUsage(
2315
+ upstream,
2316
+ model,
2317
+ recordTokens,
2318
+ request.signal,
2319
+ bufferProxyBodies
2320
+ )
2321
+ );
2191
2322
  }
2192
- async function handleCompletions(client, metrics, recordTokens, request, logger) {
2323
+ async function handleCompletions(client, metrics, recordTokens, request, logger, bufferProxyBodies) {
2193
2324
  const body = await readJson(request);
2194
2325
  const upstream = await client.chatCompletions(
2195
2326
  completionsRequestToChatCompletion(body),
@@ -2202,6 +2333,12 @@ async function handleCompletions(client, metrics, recordTokens, request, logger)
2202
2333
  logUpstreamSuccess(logger, "/chat/completions", upstream.status);
2203
2334
  const model = normalizeRequestedModel(body.model);
2204
2335
  if (isStreamingResponse(upstream) && upstream.body) {
2336
+ if (bufferProxyBodies) {
2337
+ const upstreamText = await upstream.text();
2338
+ recordResponseTextUsage(upstreamText, true, model, recordTokens);
2339
+ const text = completionSseTextFromChatSseText(upstreamText);
2340
+ return proxyResponse(responseFromText(upstream, text));
2341
+ }
2205
2342
  return proxyResponse(
2206
2343
  observeResponseUsage(
2207
2344
  new Response(completionStreamFromChatStream(upstream.body), {
@@ -2223,7 +2360,7 @@ async function handleCompletions(client, metrics, recordTokens, request, logger)
2223
2360
  }
2224
2361
  return jsonResponse(chatCompletionToCompletion(completion));
2225
2362
  }
2226
- async function handleResponses(client, metrics, recordTokens, request, logger) {
2363
+ async function handleResponses(client, metrics, recordTokens, request, logger, bufferProxyBodies) {
2227
2364
  const body = await readJsonText(request);
2228
2365
  const upstream = await client.responses(body, request.signal);
2229
2366
  metrics.recordUpstream("/responses", upstream.ok);
@@ -2232,7 +2369,31 @@ async function handleResponses(client, metrics, recordTokens, request, logger) {
2232
2369
  }
2233
2370
  logUpstreamSuccess(logger, "/responses", upstream.status);
2234
2371
  const model = normalizeRequestedModel(asRecord(safeParseJson(body)).model);
2235
- return proxyResponse(observeResponseUsage(upstream, model, recordTokens, request.signal));
2372
+ return proxyResponse(
2373
+ await responseWithObservedUsage(
2374
+ upstream,
2375
+ model,
2376
+ recordTokens,
2377
+ request.signal,
2378
+ bufferProxyBodies
2379
+ )
2380
+ );
2381
+ }
2382
+ async function responseWithObservedUsage(response, fallbackModel, recordTokens, signal, bufferBody) {
2383
+ const isSse = isStreamingResponse(response);
2384
+ if (bufferBody && response.body) {
2385
+ const text = await response.text();
2386
+ recordResponseTextUsage(text, isSse, fallbackModel, recordTokens);
2387
+ return responseFromText(response, text);
2388
+ }
2389
+ return observeResponseUsage(response, fallbackModel, recordTokens, signal);
2390
+ }
2391
+ function responseFromText(source, text) {
2392
+ return new Response(text, {
2393
+ headers: source.headers,
2394
+ status: source.status,
2395
+ statusText: source.statusText
2396
+ });
2236
2397
  }
2237
2398
  async function proxyError(upstream, logger) {
2238
2399
  const text = await upstream.text();
@@ -2424,8 +2585,24 @@ function serverLogger(options) {
2424
2585
  }
2425
2586
  return noopLogger;
2426
2587
  }
2588
+ function resolveStreamingProxyMode(options) {
2589
+ const value = options.streamingProxyMode ?? envValue(options.env?.HOOPILOT_STREAM_MODE) ?? envValue(options.env?.HOOPILOT_STREAMING_PROXY_MODE) ?? "auto";
2590
+ if (value === "auto" || value === "buffer" || value === "live") {
2591
+ return value;
2592
+ }
2593
+ throw new Error(`Invalid stream mode: ${value}. Expected auto, live, or buffer.`);
2594
+ }
2595
+ function shouldBufferProxyBodies(mode) {
2596
+ if (mode === "buffer") {
2597
+ return true;
2598
+ }
2599
+ if (mode === "live") {
2600
+ return false;
2601
+ }
2602
+ return process.platform === "win32" && IS_STANDALONE_BINARY;
2603
+ }
2427
2604
  function finishResponse(response, options) {
2428
- const withRequestId = responseWithRequestId(response, options.requestId);
2605
+ const withRequestId = responseWithRequestId(response, options.requestId, options.closeConnection);
2429
2606
  const stream = isStreamingResponse(withRequestId);
2430
2607
  const status = withRequestId.status;
2431
2608
  const complete = () => {
@@ -2433,7 +2610,7 @@ function finishResponse(response, options) {
2433
2610
  options.metrics.observe({ durationMs, method: options.method, route: options.route, status });
2434
2611
  logRequestCompleted(options.logger, status, stream, durationMs);
2435
2612
  };
2436
- if (stream && withRequestId.body) {
2613
+ if (stream && withRequestId.body && options.trackStreamingBody) {
2437
2614
  return new Response(trackStreamCompletion(withRequestId.body, complete), {
2438
2615
  headers: withRequestId.headers,
2439
2616
  status,
@@ -2443,9 +2620,12 @@ function finishResponse(response, options) {
2443
2620
  complete();
2444
2621
  return withRequestId;
2445
2622
  }
2446
- function responseWithRequestId(response, requestId) {
2623
+ function responseWithRequestId(response, requestId, closeConnection) {
2447
2624
  const headers = new Headers(response.headers);
2448
2625
  headers.set("x-request-id", requestId);
2626
+ if (closeConnection) {
2627
+ headers.set("connection", "close");
2628
+ }
2449
2629
  return new Response(response.body, {
2450
2630
  headers,
2451
2631
  status: response.status,
@@ -2864,30 +3044,6 @@ function latestReleaseApiUrl() {
2864
3044
  return `https://api.github.com/repos/${REPO}/releases/latest`;
2865
3045
  }
2866
3046
 
2867
- // src/version.ts
2868
- var BAKED_VERSION = typeof HOOPILOT_VERSION !== "undefined" ? HOOPILOT_VERSION : void 0;
2869
- var BAKED_TARGET = typeof HOOPILOT_TARGET !== "undefined" ? HOOPILOT_TARGET : void 0;
2870
- var IS_STANDALONE_BINARY = BAKED_VERSION !== void 0;
2871
- var cachedVersion;
2872
- async function getVersion() {
2873
- if (cachedVersion !== void 0) {
2874
- return cachedVersion;
2875
- }
2876
- let resolved;
2877
- if (BAKED_VERSION) {
2878
- resolved = BAKED_VERSION;
2879
- } else {
2880
- try {
2881
- const manifest = await Bun.file(new URL("../package.json", import.meta.url)).json();
2882
- resolved = typeof manifest.version === "string" ? manifest.version : "0.0.0";
2883
- } catch {
2884
- resolved = "0.0.0";
2885
- }
2886
- }
2887
- cachedVersion = resolved;
2888
- return resolved;
2889
- }
2890
-
2891
3047
  // src/update.ts
2892
3048
  var REQUEST_TIMEOUT_MS2 = 8e3;
2893
3049
  var SHA256SUMS = "SHA256SUMS";
@@ -3340,6 +3496,9 @@ function parseArgs(argv) {
3340
3496
  case "--log-level":
3341
3497
  args.logLevel = parseLogLevel(optionValue(name, inlineValue, rest));
3342
3498
  break;
3499
+ case "--stream-mode":
3500
+ args.streamingProxyMode = parseStreamMode(optionValue(name, inlineValue, rest));
3501
+ break;
3343
3502
  case "--host":
3344
3503
  args.host = optionValue(name, inlineValue, rest);
3345
3504
  break;
@@ -3358,6 +3517,12 @@ function parseArgs(argv) {
3358
3517
  }
3359
3518
  return args;
3360
3519
  }
3520
+ function parseStreamMode(value) {
3521
+ if (value === "auto" || value === "buffer" || value === "live") {
3522
+ return value;
3523
+ }
3524
+ throw new Error(`Invalid stream mode: ${value}. Expected auto, live, or buffer.`);
3525
+ }
3361
3526
  function optionValue(name, inlineValue, rest) {
3362
3527
  const value = inlineValue ?? rest.shift();
3363
3528
  if (!value) {
@@ -3615,6 +3780,7 @@ Options:
3615
3780
  --copilot-api-base-url <url> Copilot API base URL override
3616
3781
  --log-level <level> trace, debug, info, warn, error, fatal, or silent
3617
3782
  --log-format <format> json or pretty. Default: pretty
3783
+ --stream-mode <mode> auto, live, or buffer. Auto buffers Windows standalone streams.
3618
3784
  --no-update-check Do not check GitHub for a newer release
3619
3785
  --allow-unauthenticated Allow non-loopback bind without --api-key
3620
3786
  -h, --help Show help
@@ -3627,6 +3793,7 @@ Environment:
3627
3793
  HOOPILOT_GITHUB_DOMAIN
3628
3794
  HOOPILOT_LOG_FORMAT json or pretty. Default: pretty
3629
3795
  HOOPILOT_LOG_LEVEL trace, debug, info, warn, error, fatal, or silent
3796
+ HOOPILOT_STREAM_MODE auto, live, or buffer
3630
3797
  COPILOT_API_BASE_URL
3631
3798
  HOOPILOT_GITHUB_API_BASE_URL GitHub REST base for the usage/quota lookup. Default: https://api.github.com
3632
3799
  HOOPILOT_ALLOW_UNSAFE_UPSTREAM Set to 1 to allow nonstandard HTTPS token hosts