@openhoo/hoopilot 2.1.2 → 2.1.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/README.md +5 -0
- package/dist/{chunk-CYR6I4C3.js → chunk-4ZG5QEYJ.js} +24 -4
- package/dist/chunk-4ZG5QEYJ.js.map +1 -0
- package/dist/cli.js +289 -34
- package/dist/cli.js.map +1 -1
- package/dist/codexx.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +288 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-CYR6I4C3.js.map +0 -1
package/dist/codexx.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -195,7 +195,7 @@ function chatCompletionToResponse(completion, responseId) {
|
|
|
195
195
|
}
|
|
196
196
|
function responsesCompactionResult(upstreamText, isSse) {
|
|
197
197
|
const summary = compactionSummaryText(upstreamText, isSse);
|
|
198
|
-
return { output: [
|
|
198
|
+
return { output: [compactionSummaryOutputMessageItem(summary)] };
|
|
199
199
|
}
|
|
200
200
|
function isResponsesCompactionRequest(request) {
|
|
201
201
|
return responseInputItems(request.input).some(
|
|
@@ -839,8 +839,14 @@ function messageOutputItem(text, id = `msg_${randomId()}`) {
|
|
|
839
839
|
type: "message"
|
|
840
840
|
};
|
|
841
841
|
}
|
|
842
|
-
function
|
|
843
|
-
return {
|
|
842
|
+
function compactionSummaryOutputMessageItem(text) {
|
|
843
|
+
return compactionSummaryMessageItem(text, `msg_${randomId()}`);
|
|
844
|
+
}
|
|
845
|
+
function compactionSummaryInputMessageItem(text) {
|
|
846
|
+
return compactionSummaryMessageItem(text);
|
|
847
|
+
}
|
|
848
|
+
function compactionSummaryMessageItem(text, id) {
|
|
849
|
+
return removeUndefined({
|
|
844
850
|
content: [
|
|
845
851
|
{
|
|
846
852
|
text: `${COMPACTION_SUMMARY_PREFIX}
|
|
@@ -851,7 +857,7 @@ ${text}`,
|
|
|
851
857
|
id,
|
|
852
858
|
role: "user",
|
|
853
859
|
type: "message"
|
|
854
|
-
};
|
|
860
|
+
});
|
|
855
861
|
}
|
|
856
862
|
function compactionOutputItem(text, id = `cmpct_${randomId()}`) {
|
|
857
863
|
return {
|
|
@@ -875,7 +881,7 @@ function normalizeCompactionInputForCopilot(input, options) {
|
|
|
875
881
|
if (type === "compaction" || type === "compaction_summary" || type === "context_compaction") {
|
|
876
882
|
const text = contentToText(record.encrypted_content);
|
|
877
883
|
if (text) {
|
|
878
|
-
normalized.push(
|
|
884
|
+
normalized.push(compactionSummaryInputMessageItem(text));
|
|
879
885
|
}
|
|
880
886
|
continue;
|
|
881
887
|
}
|
|
@@ -956,6 +962,7 @@ function extractTokenUsage(usage) {
|
|
|
956
962
|
asRecord(record.output_tokens_details).reasoning_tokens
|
|
957
963
|
);
|
|
958
964
|
const cached = firstNumber(
|
|
965
|
+
record.cache_read_input_tokens,
|
|
959
966
|
asRecord(record.prompt_tokens_details).cached_tokens,
|
|
960
967
|
asRecord(record.input_tokens_details).cached_tokens
|
|
961
968
|
);
|
|
@@ -1128,9 +1135,10 @@ var AnthropicCompatibilityError = class extends Error {
|
|
|
1128
1135
|
}
|
|
1129
1136
|
};
|
|
1130
1137
|
function anthropicMessagesToResponsesRequest(request) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1138
|
+
const system = anthropicSystemToResponses(request.system);
|
|
1139
|
+
const response = removeUndefined({
|
|
1140
|
+
input: [...system.input, ...anthropicMessagesToResponsesInput(request.messages)],
|
|
1141
|
+
instructions: system.instructions,
|
|
1134
1142
|
max_output_tokens: typeof request.max_tokens === "number" && Number.isFinite(request.max_tokens) ? request.max_tokens : void 0,
|
|
1135
1143
|
metadata: request.metadata,
|
|
1136
1144
|
model: normalizeRequestedModel(request.model),
|
|
@@ -1143,6 +1151,8 @@ function anthropicMessagesToResponsesRequest(request) {
|
|
|
1143
1151
|
tools: anthropicTools(request.tools),
|
|
1144
1152
|
top_p: request.top_p
|
|
1145
1153
|
});
|
|
1154
|
+
applyCacheControlToLastBlock(response, anthropicCacheControl(request.cache_control));
|
|
1155
|
+
return response;
|
|
1146
1156
|
}
|
|
1147
1157
|
function responsesResponseToAnthropicMessage(response, fallbackModel) {
|
|
1148
1158
|
const content = anthropicContentFromResponsesOutput(response);
|
|
@@ -1239,6 +1249,7 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1239
1249
|
throw new AnthropicCompatibilityError("Anthropic Messages requests require messages[].");
|
|
1240
1250
|
}
|
|
1241
1251
|
const input = [];
|
|
1252
|
+
let fallbackToolCallIndex = 0;
|
|
1242
1253
|
for (const message of messages) {
|
|
1243
1254
|
const record = asRecord(message);
|
|
1244
1255
|
const role = anthropicRole(record.role);
|
|
@@ -1260,10 +1271,13 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1260
1271
|
if (type === "text") {
|
|
1261
1272
|
const text = textValue(part.text);
|
|
1262
1273
|
if (text) {
|
|
1263
|
-
messageParts.push(
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1274
|
+
messageParts.push(
|
|
1275
|
+
removeUndefined({
|
|
1276
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1277
|
+
text,
|
|
1278
|
+
type: role === "assistant" ? "output_text" : "input_text"
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1267
1281
|
}
|
|
1268
1282
|
continue;
|
|
1269
1283
|
}
|
|
@@ -1278,21 +1292,27 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1278
1292
|
}
|
|
1279
1293
|
if (type === "tool_use") {
|
|
1280
1294
|
flushMessage();
|
|
1281
|
-
input.push(
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1295
|
+
input.push(
|
|
1296
|
+
removeUndefined({
|
|
1297
|
+
arguments: JSON.stringify(asRecord(part.input)),
|
|
1298
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1299
|
+
call_id: textValue(part.id) || `call_hoopilot_${fallbackToolCallIndex++}`,
|
|
1300
|
+
name: textValue(part.name),
|
|
1301
|
+
type: "function_call"
|
|
1302
|
+
})
|
|
1303
|
+
);
|
|
1287
1304
|
continue;
|
|
1288
1305
|
}
|
|
1289
1306
|
if (type === "tool_result") {
|
|
1290
1307
|
flushMessage();
|
|
1291
|
-
input.push(
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1308
|
+
input.push(
|
|
1309
|
+
removeUndefined({
|
|
1310
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1311
|
+
call_id: textValue(part.tool_use_id),
|
|
1312
|
+
output: anthropicToolResultOutput(part.content),
|
|
1313
|
+
type: "function_call_output"
|
|
1314
|
+
})
|
|
1315
|
+
);
|
|
1296
1316
|
continue;
|
|
1297
1317
|
}
|
|
1298
1318
|
if (type === "thinking" || type === "redacted_thinking") {
|
|
@@ -1339,22 +1359,24 @@ function anthropicImageToResponsesPart(part) {
|
|
|
1339
1359
|
if (!data) {
|
|
1340
1360
|
throw new AnthropicCompatibilityError("Anthropic base64 image content requires source.data.");
|
|
1341
1361
|
}
|
|
1342
|
-
return {
|
|
1362
|
+
return removeUndefined({
|
|
1363
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1343
1364
|
detail: "auto",
|
|
1344
1365
|
image_url: `data:${mediaType};base64,${data}`,
|
|
1345
1366
|
type: "input_image"
|
|
1346
|
-
};
|
|
1367
|
+
});
|
|
1347
1368
|
}
|
|
1348
1369
|
if (sourceType === "url") {
|
|
1349
1370
|
const url = textValue(source.url);
|
|
1350
1371
|
if (!url) {
|
|
1351
1372
|
throw new AnthropicCompatibilityError("Anthropic URL image content requires source.url.");
|
|
1352
1373
|
}
|
|
1353
|
-
return {
|
|
1374
|
+
return removeUndefined({
|
|
1375
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1354
1376
|
detail: "auto",
|
|
1355
1377
|
image_url: url,
|
|
1356
1378
|
type: "input_image"
|
|
1357
|
-
};
|
|
1379
|
+
});
|
|
1358
1380
|
}
|
|
1359
1381
|
throw new AnthropicCompatibilityError(
|
|
1360
1382
|
`Anthropic image source type "${sourceType || "unknown"}" is not supported.`
|
|
@@ -1375,15 +1397,42 @@ function anthropicToolResultOutput(content) {
|
|
|
1375
1397
|
}
|
|
1376
1398
|
return typeof content === "object" ? JSON.stringify(content) : String(content);
|
|
1377
1399
|
}
|
|
1378
|
-
function
|
|
1400
|
+
function anthropicSystemToResponses(system) {
|
|
1379
1401
|
if (typeof system === "string") {
|
|
1380
|
-
return system || void 0;
|
|
1402
|
+
return { input: [], instructions: system || void 0 };
|
|
1381
1403
|
}
|
|
1382
1404
|
if (!Array.isArray(system)) {
|
|
1405
|
+
return { input: [] };
|
|
1406
|
+
}
|
|
1407
|
+
const parts = system.map((part) => anthropicSystemPartToResponsesPart(part)).filter((part) => part !== void 0);
|
|
1408
|
+
if (parts.length === 0) {
|
|
1409
|
+
return { input: [] };
|
|
1410
|
+
}
|
|
1411
|
+
if (parts.some((part) => part.cache_control !== void 0)) {
|
|
1412
|
+
return {
|
|
1413
|
+
input: [
|
|
1414
|
+
{
|
|
1415
|
+
content: parts,
|
|
1416
|
+
role: "system",
|
|
1417
|
+
type: "message"
|
|
1418
|
+
}
|
|
1419
|
+
]
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
const text = parts.map((part) => textValue(part.text)).filter(Boolean).join("\n");
|
|
1423
|
+
return { input: [], instructions: text || void 0 };
|
|
1424
|
+
}
|
|
1425
|
+
function anthropicSystemPartToResponsesPart(part) {
|
|
1426
|
+
const record = asRecord(part);
|
|
1427
|
+
const text = textValue(record.text) || textValue(part);
|
|
1428
|
+
if (!text) {
|
|
1383
1429
|
return void 0;
|
|
1384
1430
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1431
|
+
return removeUndefined({
|
|
1432
|
+
cache_control: anthropicCacheControl(record.cache_control),
|
|
1433
|
+
text,
|
|
1434
|
+
type: "input_text"
|
|
1435
|
+
});
|
|
1387
1436
|
}
|
|
1388
1437
|
function anthropicTools(tools) {
|
|
1389
1438
|
if (!Array.isArray(tools)) {
|
|
@@ -1392,6 +1441,7 @@ function anthropicTools(tools) {
|
|
|
1392
1441
|
const converted = tools.map((tool) => {
|
|
1393
1442
|
const record = asRecord(tool);
|
|
1394
1443
|
return removeUndefined({
|
|
1444
|
+
cache_control: anthropicCacheControl(record.cache_control),
|
|
1395
1445
|
description: record.description,
|
|
1396
1446
|
name: record.name,
|
|
1397
1447
|
parameters: record.input_schema,
|
|
@@ -1401,6 +1451,55 @@ function anthropicTools(tools) {
|
|
|
1401
1451
|
});
|
|
1402
1452
|
return converted.length > 0 ? converted : void 0;
|
|
1403
1453
|
}
|
|
1454
|
+
function anthropicCacheControl(value) {
|
|
1455
|
+
if (value === void 0 || value === null) {
|
|
1456
|
+
return void 0;
|
|
1457
|
+
}
|
|
1458
|
+
const record = asRecord(value);
|
|
1459
|
+
const type = textValue(record.type);
|
|
1460
|
+
if (type !== "ephemeral") {
|
|
1461
|
+
throw new AnthropicCompatibilityError(
|
|
1462
|
+
`Anthropic cache_control type "${type || "unknown"}" is not supported.`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
const ttl = textValue(record.ttl);
|
|
1466
|
+
if (ttl && ttl !== "5m" && ttl !== "1h") {
|
|
1467
|
+
throw new AnthropicCompatibilityError(`Anthropic cache_control ttl "${ttl}" is not supported.`);
|
|
1468
|
+
}
|
|
1469
|
+
return removeUndefined({
|
|
1470
|
+
ttl: ttl || void 0,
|
|
1471
|
+
type
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
function applyCacheControlToLastBlock(request, cacheControl) {
|
|
1475
|
+
if (!cacheControl) {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const input = Array.isArray(request.input) ? request.input : [];
|
|
1479
|
+
for (let itemIndex = input.length - 1; itemIndex >= 0; itemIndex -= 1) {
|
|
1480
|
+
const item = asRecord(input[itemIndex]);
|
|
1481
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
1482
|
+
for (let partIndex = content.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
1483
|
+
const part = asRecord(content[partIndex]);
|
|
1484
|
+
if (part.cache_control === void 0 && isCacheableResponsesPart(part)) {
|
|
1485
|
+
part.cache_control = cacheControl;
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const tools = Array.isArray(request.tools) ? request.tools : [];
|
|
1491
|
+
for (let index = tools.length - 1; index >= 0; index -= 1) {
|
|
1492
|
+
const tool = asRecord(tools[index]);
|
|
1493
|
+
if (tool.cache_control === void 0) {
|
|
1494
|
+
tool.cache_control = cacheControl;
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function isCacheableResponsesPart(part) {
|
|
1500
|
+
const type = textValue(part.type);
|
|
1501
|
+
return type === "input_text" || type === "output_text" || type === "text" || type === "input_image";
|
|
1502
|
+
}
|
|
1404
1503
|
function anthropicToolChoice(toolChoice) {
|
|
1405
1504
|
if (toolChoice === void 0 || toolChoice === null) {
|
|
1406
1505
|
return void 0;
|
|
@@ -1906,6 +2005,14 @@ var COPILOT_USAGE_API_VERSION = "2025-04-01";
|
|
|
1906
2005
|
var EDITOR_PLUGIN_VERSION = "hoopilot/0.1.0";
|
|
1907
2006
|
var EDITOR_VERSION = "Hoopilot/0.1.0";
|
|
1908
2007
|
var HOOPILOT_USER_AGENT = "hoopilot/0.1.0";
|
|
2008
|
+
var DEFAULT_UPSTREAM_TIMEOUT_MS = 12e4;
|
|
2009
|
+
var DEFAULT_UPSTREAM_STREAM_IDLE_TIMEOUT_MS = 12e4;
|
|
2010
|
+
var CopilotUpstreamTimeoutError = class extends Error {
|
|
2011
|
+
constructor(message) {
|
|
2012
|
+
super(message);
|
|
2013
|
+
this.name = "CopilotUpstreamTimeoutError";
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
1909
2016
|
function applyCopilotHeaders(headers, token) {
|
|
1910
2017
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
1911
2018
|
headers.set("authorization", `Bearer ${token}`);
|
|
@@ -1958,6 +2065,8 @@ var CopilotClient = class {
|
|
|
1958
2065
|
#allowUnsafeUpstream;
|
|
1959
2066
|
#fetch;
|
|
1960
2067
|
#githubApiBaseUrl;
|
|
2068
|
+
#upstreamStreamIdleTimeoutMs;
|
|
2069
|
+
#upstreamTimeoutMs;
|
|
1961
2070
|
constructor(options = {}) {
|
|
1962
2071
|
this.#auth = new CopilotAuth(options);
|
|
1963
2072
|
this.#allowUnsafeUpstream = envValue(options.env?.HOOPILOT_ALLOW_UNSAFE_UPSTREAM) === "1";
|
|
@@ -1965,6 +2074,18 @@ var CopilotClient = class {
|
|
|
1965
2074
|
this.#githubApiBaseUrl = trimTrailingSlash(
|
|
1966
2075
|
options.githubApiBaseUrl ?? envValue(options.env?.HOOPILOT_GITHUB_API_BASE_URL) ?? DEFAULT_GITHUB_API_BASE_URL
|
|
1967
2076
|
);
|
|
2077
|
+
this.#upstreamTimeoutMs = parseTimeoutMs(
|
|
2078
|
+
options.upstreamTimeoutMs,
|
|
2079
|
+
options.env?.HOOPILOT_UPSTREAM_TIMEOUT_MS,
|
|
2080
|
+
DEFAULT_UPSTREAM_TIMEOUT_MS,
|
|
2081
|
+
"HOOPILOT_UPSTREAM_TIMEOUT_MS"
|
|
2082
|
+
);
|
|
2083
|
+
this.#upstreamStreamIdleTimeoutMs = parseTimeoutMs(
|
|
2084
|
+
options.upstreamStreamIdleTimeoutMs,
|
|
2085
|
+
options.env?.HOOPILOT_UPSTREAM_STREAM_IDLE_TIMEOUT_MS,
|
|
2086
|
+
DEFAULT_UPSTREAM_STREAM_IDLE_TIMEOUT_MS,
|
|
2087
|
+
"HOOPILOT_UPSTREAM_STREAM_IDLE_TIMEOUT_MS"
|
|
2088
|
+
);
|
|
1968
2089
|
}
|
|
1969
2090
|
/**
|
|
1970
2091
|
* Fetch the Copilot account's quota / premium-request usage from the GitHub
|
|
@@ -1983,7 +2104,7 @@ var CopilotClient = class {
|
|
|
1983
2104
|
}
|
|
1984
2105
|
const access = await this.#auth.getAccess();
|
|
1985
2106
|
const headers = applyGithubApiHeaders(new Headers(), access.token);
|
|
1986
|
-
return this.#
|
|
2107
|
+
return this.#fetchWithTimeout(`${this.#githubApiBaseUrl}/copilot_internal/user`, {
|
|
1987
2108
|
headers,
|
|
1988
2109
|
method: "GET",
|
|
1989
2110
|
signal
|
|
@@ -2030,12 +2151,139 @@ var CopilotClient = class {
|
|
|
2030
2151
|
);
|
|
2031
2152
|
}
|
|
2032
2153
|
const headers = applyCopilotHeaders(new Headers(init.headers), access.token);
|
|
2033
|
-
return this.#
|
|
2154
|
+
return this.#fetchWithTimeout(`${access.apiBaseUrl}${path}`, {
|
|
2034
2155
|
...init,
|
|
2035
2156
|
headers
|
|
2036
2157
|
});
|
|
2037
2158
|
}
|
|
2159
|
+
async #fetchWithTimeout(input, init) {
|
|
2160
|
+
const timeout = abortSignalWithTimeout(init.signal ?? void 0, this.#upstreamTimeoutMs);
|
|
2161
|
+
try {
|
|
2162
|
+
const response = await this.#fetch(input, {
|
|
2163
|
+
...init,
|
|
2164
|
+
signal: timeout.signal
|
|
2165
|
+
});
|
|
2166
|
+
return responseWithStreamIdleTimeout(response, this.#upstreamStreamIdleTimeoutMs, input);
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
if (timeout.timedOut()) {
|
|
2169
|
+
throw new CopilotUpstreamTimeoutError(
|
|
2170
|
+
`Copilot upstream request timed out after ${this.#upstreamTimeoutMs} ms before response headers arrived.`
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
throw error;
|
|
2174
|
+
} finally {
|
|
2175
|
+
timeout.cleanup();
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2038
2178
|
};
|
|
2179
|
+
function parseTimeoutMs(optionValue, envRaw, fallback, name) {
|
|
2180
|
+
const raw = optionValue ?? envValue(envRaw);
|
|
2181
|
+
if (raw === void 0) {
|
|
2182
|
+
return fallback;
|
|
2183
|
+
}
|
|
2184
|
+
const value = typeof raw === "number" ? raw : Number(raw);
|
|
2185
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2186
|
+
throw new Error(`${name} must be a non-negative integer number of milliseconds.`);
|
|
2187
|
+
}
|
|
2188
|
+
return value;
|
|
2189
|
+
}
|
|
2190
|
+
function abortSignalWithTimeout(parent, timeoutMs) {
|
|
2191
|
+
if (timeoutMs === 0) {
|
|
2192
|
+
return { cleanup: () => {
|
|
2193
|
+
}, signal: parent, timedOut: () => false };
|
|
2194
|
+
}
|
|
2195
|
+
const controller = new AbortController();
|
|
2196
|
+
let timedOut = false;
|
|
2197
|
+
const timer = setTimeout(() => {
|
|
2198
|
+
if (controller.signal.aborted) {
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
timedOut = true;
|
|
2202
|
+
controller.abort(
|
|
2203
|
+
new CopilotUpstreamTimeoutError(`Copilot upstream request timed out after ${timeoutMs} ms.`)
|
|
2204
|
+
);
|
|
2205
|
+
}, timeoutMs);
|
|
2206
|
+
const onAbort = () => controller.abort(parent?.reason);
|
|
2207
|
+
if (parent?.aborted) {
|
|
2208
|
+
controller.abort(parent.reason);
|
|
2209
|
+
} else {
|
|
2210
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
cleanup: () => {
|
|
2214
|
+
clearTimeout(timer);
|
|
2215
|
+
parent?.removeEventListener("abort", onAbort);
|
|
2216
|
+
},
|
|
2217
|
+
signal: controller.signal,
|
|
2218
|
+
timedOut: () => timedOut
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function responseWithStreamIdleTimeout(response, idleTimeoutMs, input) {
|
|
2222
|
+
if (!response.body || idleTimeoutMs === 0) {
|
|
2223
|
+
return response;
|
|
2224
|
+
}
|
|
2225
|
+
return new Response(streamWithIdleTimeout(response.body, idleTimeoutMs, input), {
|
|
2226
|
+
headers: response.headers,
|
|
2227
|
+
status: response.status,
|
|
2228
|
+
statusText: response.statusText
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
function streamWithIdleTimeout(body, idleTimeoutMs, input) {
|
|
2232
|
+
const reader = body.getReader();
|
|
2233
|
+
let released = false;
|
|
2234
|
+
const release = () => {
|
|
2235
|
+
if (!released) {
|
|
2236
|
+
released = true;
|
|
2237
|
+
reader.releaseLock();
|
|
2238
|
+
}
|
|
2239
|
+
};
|
|
2240
|
+
return new ReadableStream({
|
|
2241
|
+
async pull(controller) {
|
|
2242
|
+
let timer;
|
|
2243
|
+
const read = reader.read();
|
|
2244
|
+
read.catch(() => {
|
|
2245
|
+
});
|
|
2246
|
+
try {
|
|
2247
|
+
const result = await Promise.race([
|
|
2248
|
+
read,
|
|
2249
|
+
new Promise((_, reject) => {
|
|
2250
|
+
timer = setTimeout(() => {
|
|
2251
|
+
reject(
|
|
2252
|
+
new CopilotUpstreamTimeoutError(
|
|
2253
|
+
`Copilot upstream stream was idle for ${idleTimeoutMs} ms while reading ${input}.`
|
|
2254
|
+
)
|
|
2255
|
+
);
|
|
2256
|
+
}, idleTimeoutMs);
|
|
2257
|
+
})
|
|
2258
|
+
]);
|
|
2259
|
+
if (timer) {
|
|
2260
|
+
clearTimeout(timer);
|
|
2261
|
+
}
|
|
2262
|
+
if (result.done) {
|
|
2263
|
+
controller.close();
|
|
2264
|
+
release();
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
controller.enqueue(result.value);
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
if (timer) {
|
|
2270
|
+
clearTimeout(timer);
|
|
2271
|
+
}
|
|
2272
|
+
await reader.cancel(error).catch(() => {
|
|
2273
|
+
});
|
|
2274
|
+
controller.error(error);
|
|
2275
|
+
release();
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
async cancel(reason) {
|
|
2279
|
+
try {
|
|
2280
|
+
await reader.cancel(reason);
|
|
2281
|
+
} finally {
|
|
2282
|
+
release();
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2039
2287
|
function normalizeCopilotUsage(body) {
|
|
2040
2288
|
const record = asRecord(body);
|
|
2041
2289
|
const quotas = {};
|
|
@@ -3990,6 +4238,13 @@ function buildApp(deps) {
|
|
|
3990
4238
|
);
|
|
3991
4239
|
return jsonError(413, "request_too_large", message);
|
|
3992
4240
|
}
|
|
4241
|
+
if (error instanceof CopilotUpstreamTimeoutError) {
|
|
4242
|
+
logger.warn(
|
|
4243
|
+
{ err: errorDetails(error), event: "copilot.request.timeout" },
|
|
4244
|
+
"copilot upstream request timed out"
|
|
4245
|
+
);
|
|
4246
|
+
return jsonError(504, "copilot_timeout", message);
|
|
4247
|
+
}
|
|
3993
4248
|
logger.error({ err: errorDetails(error), event: "http.request.failed" }, "request failed");
|
|
3994
4249
|
return jsonError(500, "internal_error", message);
|
|
3995
4250
|
}).get("/", () => jsonResponse({ name: "hoopilot", object: "health", status: "ok" })).get("/healthz", () => jsonResponse({ name: "hoopilot", object: "health", status: "ok" })).get("/metrics", () => metricsResponse(metrics)).get("/v1/usage", ({ request }) => handleUsage(metrics, readUsage, request.signal)).get(
|