@openhoo/hoopilot 0.8.3 → 0.8.4
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/cli.js +234 -67
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +203 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +203 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
1920
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|