@openhoo/hoopilot 0.7.3 → 0.7.5
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 +1 -1
- package/dist/cli.js +152 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +215 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +215 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -126,10 +126,16 @@ interface RequestObservation {
|
|
|
126
126
|
/** One quota category (chat, completions, or premium_interactions/credits). */
|
|
127
127
|
interface CopilotQuota {
|
|
128
128
|
entitlement?: number;
|
|
129
|
+
hasQuota?: boolean;
|
|
129
130
|
overageCount?: number;
|
|
131
|
+
overageEntitlement?: number;
|
|
130
132
|
overagePermitted?: boolean;
|
|
131
133
|
percentRemaining?: number;
|
|
134
|
+
quotaId?: string;
|
|
135
|
+
quotaResetAt?: string;
|
|
132
136
|
remaining?: number;
|
|
137
|
+
timestampUtc?: string;
|
|
138
|
+
tokenBasedBilling?: boolean;
|
|
133
139
|
unlimited?: boolean;
|
|
134
140
|
used?: number;
|
|
135
141
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -126,10 +126,16 @@ interface RequestObservation {
|
|
|
126
126
|
/** One quota category (chat, completions, or premium_interactions/credits). */
|
|
127
127
|
interface CopilotQuota {
|
|
128
128
|
entitlement?: number;
|
|
129
|
+
hasQuota?: boolean;
|
|
129
130
|
overageCount?: number;
|
|
131
|
+
overageEntitlement?: number;
|
|
130
132
|
overagePermitted?: boolean;
|
|
131
133
|
percentRemaining?: number;
|
|
134
|
+
quotaId?: string;
|
|
135
|
+
quotaResetAt?: string;
|
|
132
136
|
remaining?: number;
|
|
137
|
+
timestampUtc?: string;
|
|
138
|
+
tokenBasedBilling?: boolean;
|
|
133
139
|
unlimited?: boolean;
|
|
134
140
|
used?: number;
|
|
135
141
|
}
|
package/dist/index.js
CHANGED
|
@@ -329,22 +329,31 @@ function normalizeCopilotUsage(body) {
|
|
|
329
329
|
}
|
|
330
330
|
function normalizeQuotaDetail(detail) {
|
|
331
331
|
const entitlement = numberOrUndefined(detail.entitlement);
|
|
332
|
+
const overageCount = numberOrUndefined(detail.overage_count);
|
|
332
333
|
const remaining = numberOrUndefined(detail.remaining) ?? numberOrUndefined(detail.quota_remaining);
|
|
333
334
|
return removeUndefinedQuota({
|
|
334
335
|
entitlement,
|
|
335
|
-
|
|
336
|
+
hasQuota: typeof detail.has_quota === "boolean" ? detail.has_quota : void 0,
|
|
337
|
+
overageCount,
|
|
338
|
+
overageEntitlement: numberOrUndefined(detail.overage_entitlement),
|
|
336
339
|
overagePermitted: typeof detail.overage_permitted === "boolean" ? detail.overage_permitted : void 0,
|
|
337
340
|
percentRemaining: numberOrUndefined(detail.percent_remaining),
|
|
341
|
+
quotaId: stringOrUndefined(detail.quota_id),
|
|
342
|
+
quotaResetAt: stringOrUndefined(detail.quota_reset_at),
|
|
338
343
|
remaining,
|
|
344
|
+
timestampUtc: stringOrUndefined(detail.timestamp_utc),
|
|
345
|
+
tokenBasedBilling: typeof detail.token_based_billing === "boolean" ? detail.token_based_billing : void 0,
|
|
339
346
|
unlimited: typeof detail.unlimited === "boolean" ? detail.unlimited : void 0,
|
|
340
|
-
used: usedFrom(entitlement, remaining)
|
|
347
|
+
used: usedFrom(entitlement, remaining, overageCount)
|
|
341
348
|
});
|
|
342
349
|
}
|
|
343
|
-
function usedFrom(entitlement, remaining) {
|
|
350
|
+
function usedFrom(entitlement, remaining, overageCount) {
|
|
344
351
|
if (entitlement === void 0 || remaining === void 0) {
|
|
345
352
|
return void 0;
|
|
346
353
|
}
|
|
347
|
-
|
|
354
|
+
const base = entitlement - remaining;
|
|
355
|
+
const overage = remaining === 0 ? overageCount ?? 0 : 0;
|
|
356
|
+
return Math.max(0, base + overage);
|
|
348
357
|
}
|
|
349
358
|
function numberOrUndefined(value) {
|
|
350
359
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
@@ -607,6 +616,12 @@ function isLogLevel(value) {
|
|
|
607
616
|
|
|
608
617
|
// src/openai.ts
|
|
609
618
|
var DEFAULT_MODEL = "gpt-4.1";
|
|
619
|
+
var OpenAICompatibilityError = class extends Error {
|
|
620
|
+
constructor(message) {
|
|
621
|
+
super(message);
|
|
622
|
+
this.name = "OpenAICompatibilityError";
|
|
623
|
+
}
|
|
624
|
+
};
|
|
610
625
|
function responsesRequestToChatCompletion(request) {
|
|
611
626
|
const messages = [];
|
|
612
627
|
const instructions = contentToText(request.instructions);
|
|
@@ -640,13 +655,22 @@ function normalizeChatCompletionRequest(request) {
|
|
|
640
655
|
});
|
|
641
656
|
}
|
|
642
657
|
function completionsRequestToChatCompletion(request) {
|
|
658
|
+
assertSupportedLegacyCompletionRequest(request);
|
|
643
659
|
return removeUndefined({
|
|
660
|
+
frequency_penalty: request.frequency_penalty,
|
|
661
|
+
logit_bias: request.logit_bias,
|
|
644
662
|
max_tokens: request.max_tokens,
|
|
645
|
-
messages: [{ content:
|
|
663
|
+
messages: [{ content: legacyPromptToText(request.prompt), role: "user" }],
|
|
646
664
|
model: normalizeRequestedModel(request.model),
|
|
665
|
+
n: request.n,
|
|
666
|
+
presence_penalty: request.presence_penalty,
|
|
667
|
+
seed: request.seed,
|
|
668
|
+
stop: request.stop,
|
|
647
669
|
stream: request.stream === true,
|
|
670
|
+
stream_options: request.stream_options,
|
|
648
671
|
temperature: request.temperature,
|
|
649
|
-
top_p: request.top_p
|
|
672
|
+
top_p: request.top_p,
|
|
673
|
+
user: request.user
|
|
650
674
|
});
|
|
651
675
|
}
|
|
652
676
|
function normalizeRequestedModel(model) {
|
|
@@ -682,21 +706,21 @@ function chatCompletionToResponse(completion, responseId) {
|
|
|
682
706
|
});
|
|
683
707
|
}
|
|
684
708
|
function chatCompletionToCompletion(completion) {
|
|
685
|
-
const choice = firstChoice(completion);
|
|
686
|
-
const message = asRecord(choice.message);
|
|
687
709
|
return removeUndefined({
|
|
688
|
-
choices:
|
|
689
|
-
|
|
710
|
+
choices: completionChoices(completion).map((choice, index) => {
|
|
711
|
+
const message = asRecord(choice.message);
|
|
712
|
+
return {
|
|
690
713
|
finish_reason: choice.finish_reason ?? "stop",
|
|
691
|
-
index:
|
|
692
|
-
logprobs: null,
|
|
693
|
-
text: contentToText(message.content)
|
|
694
|
-
}
|
|
695
|
-
|
|
714
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
715
|
+
logprobs: choice.logprobs ?? null,
|
|
716
|
+
text: contentToText(choice.text) || contentToText(message.content)
|
|
717
|
+
};
|
|
718
|
+
}),
|
|
696
719
|
created: completion.created ?? epochSeconds(),
|
|
697
720
|
id: completion.id ?? `cmpl_${randomId()}`,
|
|
698
721
|
model: completion.model ?? DEFAULT_MODEL,
|
|
699
722
|
object: "text_completion",
|
|
723
|
+
system_fingerprint: completion.system_fingerprint,
|
|
700
724
|
usage: completion.usage
|
|
701
725
|
});
|
|
702
726
|
}
|
|
@@ -960,7 +984,8 @@ function inputToMessages(input) {
|
|
|
960
984
|
const messages = [];
|
|
961
985
|
for (const item of input) {
|
|
962
986
|
const record = asRecord(item);
|
|
963
|
-
|
|
987
|
+
const type = contentToText(record.type);
|
|
988
|
+
if (type === "function_call_output") {
|
|
964
989
|
messages.push({
|
|
965
990
|
content: contentToText(record.output),
|
|
966
991
|
role: "tool",
|
|
@@ -968,7 +993,7 @@ function inputToMessages(input) {
|
|
|
968
993
|
});
|
|
969
994
|
continue;
|
|
970
995
|
}
|
|
971
|
-
if (
|
|
996
|
+
if (type === "function_call") {
|
|
972
997
|
messages.push({
|
|
973
998
|
role: "assistant",
|
|
974
999
|
tool_calls: [
|
|
@@ -984,7 +1009,10 @@ function inputToMessages(input) {
|
|
|
984
1009
|
});
|
|
985
1010
|
continue;
|
|
986
1011
|
}
|
|
987
|
-
|
|
1012
|
+
if (type && type !== "message") {
|
|
1013
|
+
unsupportedResponsesFeature(`input item type "${type}"`);
|
|
1014
|
+
}
|
|
1015
|
+
const role = responsesRoleToChatRole(contentToText(record.role));
|
|
988
1016
|
const content = chatMessageContent(record.content);
|
|
989
1017
|
if (role && content !== void 0) {
|
|
990
1018
|
messages.push({ content, role });
|
|
@@ -997,7 +1025,10 @@ function chatMessageContent(content) {
|
|
|
997
1025
|
return content;
|
|
998
1026
|
}
|
|
999
1027
|
if (!Array.isArray(content)) {
|
|
1000
|
-
|
|
1028
|
+
if (content === void 0 || content === null) {
|
|
1029
|
+
return void 0;
|
|
1030
|
+
}
|
|
1031
|
+
unsupportedResponsesFeature("non-array message content objects");
|
|
1001
1032
|
}
|
|
1002
1033
|
const parts = [];
|
|
1003
1034
|
for (const part of content) {
|
|
@@ -1005,13 +1036,31 @@ function chatMessageContent(content) {
|
|
|
1005
1036
|
const type = contentToText(record.type);
|
|
1006
1037
|
if (type === "input_text" || type === "output_text" || type === "text") {
|
|
1007
1038
|
parts.push({ text: contentToText(record.text), type: "text" });
|
|
1039
|
+
continue;
|
|
1008
1040
|
}
|
|
1009
1041
|
if (type === "input_image") {
|
|
1042
|
+
if (contentToText(record.file_id)) {
|
|
1043
|
+
unsupportedResponsesFeature("input_image file_id parts");
|
|
1044
|
+
}
|
|
1010
1045
|
const imageUrl = contentToText(record.image_url);
|
|
1011
|
-
if (imageUrl) {
|
|
1012
|
-
|
|
1046
|
+
if (!imageUrl) {
|
|
1047
|
+
unsupportedResponsesFeature("input_image parts without image_url");
|
|
1048
|
+
}
|
|
1049
|
+
const image = { url: imageUrl };
|
|
1050
|
+
const detail = contentToText(record.detail);
|
|
1051
|
+
if (detail) {
|
|
1052
|
+
image.detail = detail;
|
|
1013
1053
|
}
|
|
1054
|
+
parts.push({ image_url: image, type: "image_url" });
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (type === "input_file") {
|
|
1058
|
+
unsupportedResponsesFeature("input_file parts");
|
|
1059
|
+
}
|
|
1060
|
+
if (type === "input_audio") {
|
|
1061
|
+
unsupportedResponsesFeature("input_audio parts");
|
|
1014
1062
|
}
|
|
1063
|
+
unsupportedResponsesFeature(`content part type "${type || "unknown"}"`);
|
|
1015
1064
|
}
|
|
1016
1065
|
if (parts.length === 0) {
|
|
1017
1066
|
return void 0;
|
|
@@ -1021,11 +1070,38 @@ function chatMessageContent(content) {
|
|
|
1021
1070
|
}
|
|
1022
1071
|
return parts;
|
|
1023
1072
|
}
|
|
1024
|
-
function
|
|
1025
|
-
if (
|
|
1026
|
-
return prompt
|
|
1073
|
+
function legacyPromptToText(prompt) {
|
|
1074
|
+
if (typeof prompt === "string") {
|
|
1075
|
+
return prompt;
|
|
1076
|
+
}
|
|
1077
|
+
if (Array.isArray(prompt) && prompt.length === 1 && typeof prompt[0] === "string") {
|
|
1078
|
+
return prompt[0];
|
|
1079
|
+
}
|
|
1080
|
+
throw new OpenAICompatibilityError(
|
|
1081
|
+
"Hoopilot legacy completions compatibility supports exactly one string prompt per request."
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
function assertSupportedLegacyCompletionRequest(request) {
|
|
1085
|
+
if (request.echo === true) {
|
|
1086
|
+
throw new OpenAICompatibilityError(
|
|
1087
|
+
"Hoopilot legacy completions compatibility does not support echo=true."
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof request.best_of === "number" && request.best_of > 1) {
|
|
1091
|
+
throw new OpenAICompatibilityError(
|
|
1092
|
+
"Hoopilot legacy completions compatibility does not support best_of greater than 1."
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (typeof request.logprobs === "number" && request.logprobs > 0) {
|
|
1096
|
+
throw new OpenAICompatibilityError(
|
|
1097
|
+
"Hoopilot legacy completions compatibility does not support legacy logprobs."
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
if (contentToText(request.suffix)) {
|
|
1101
|
+
throw new OpenAICompatibilityError(
|
|
1102
|
+
"Hoopilot legacy completions compatibility does not support suffix."
|
|
1103
|
+
);
|
|
1027
1104
|
}
|
|
1028
|
-
return contentToText(prompt);
|
|
1029
1105
|
}
|
|
1030
1106
|
function contentToText(content) {
|
|
1031
1107
|
if (typeof content === "string") {
|
|
@@ -1049,25 +1125,35 @@ function contentToText(content) {
|
|
|
1049
1125
|
}
|
|
1050
1126
|
return "";
|
|
1051
1127
|
}
|
|
1052
|
-
function
|
|
1053
|
-
if (role
|
|
1128
|
+
function responsesRoleToChatRole(role) {
|
|
1129
|
+
if (!role) {
|
|
1130
|
+
return "user";
|
|
1131
|
+
}
|
|
1132
|
+
if (role === "assistant" || role === "developer" || role === "system" || role === "tool" || role === "user") {
|
|
1054
1133
|
return role === "developer" ? "system" : role;
|
|
1055
1134
|
}
|
|
1056
|
-
|
|
1135
|
+
unsupportedResponsesFeature(`message role "${role}"`);
|
|
1057
1136
|
}
|
|
1058
1137
|
function chatTools(tools) {
|
|
1059
1138
|
if (!Array.isArray(tools)) {
|
|
1060
1139
|
return void 0;
|
|
1061
1140
|
}
|
|
1062
|
-
const converted = tools.map((tool) =>
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1141
|
+
const converted = tools.map((tool) => {
|
|
1142
|
+
const record = asRecord(tool);
|
|
1143
|
+
const type = contentToText(record.type);
|
|
1144
|
+
if (type !== "function") {
|
|
1145
|
+
unsupportedResponsesFeature(`tool type "${type || "unknown"}"`);
|
|
1146
|
+
}
|
|
1147
|
+
return {
|
|
1148
|
+
function: removeUndefined({
|
|
1149
|
+
description: record.description,
|
|
1150
|
+
name: record.name,
|
|
1151
|
+
parameters: record.parameters,
|
|
1152
|
+
strict: record.strict
|
|
1153
|
+
}),
|
|
1154
|
+
type: "function"
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1071
1157
|
return converted.length > 0 ? converted : void 0;
|
|
1072
1158
|
}
|
|
1073
1159
|
function chatToolChoice(toolChoice) {
|
|
@@ -1075,10 +1161,16 @@ function chatToolChoice(toolChoice) {
|
|
|
1075
1161
|
return toolChoice;
|
|
1076
1162
|
}
|
|
1077
1163
|
const record = asRecord(toolChoice);
|
|
1078
|
-
|
|
1164
|
+
const type = contentToText(record.type);
|
|
1165
|
+
if (type === "function" && typeof record.name === "string") {
|
|
1079
1166
|
return { function: { name: record.name }, type: "function" };
|
|
1080
1167
|
}
|
|
1081
|
-
|
|
1168
|
+
unsupportedResponsesFeature(`tool_choice type "${type || "unknown"}"`);
|
|
1169
|
+
}
|
|
1170
|
+
function unsupportedResponsesFeature(feature) {
|
|
1171
|
+
throw new OpenAICompatibilityError(
|
|
1172
|
+
`Hoopilot Responses-to-chat compatibility does not support ${feature}.`
|
|
1173
|
+
);
|
|
1082
1174
|
}
|
|
1083
1175
|
function outputItemsFromMessage(message) {
|
|
1084
1176
|
const output = [];
|
|
@@ -1193,8 +1285,11 @@ function firstNumber(...values) {
|
|
|
1193
1285
|
return void 0;
|
|
1194
1286
|
}
|
|
1195
1287
|
function firstChoice(completion) {
|
|
1288
|
+
return completionChoices(completion)[0] ?? {};
|
|
1289
|
+
}
|
|
1290
|
+
function completionChoices(completion) {
|
|
1196
1291
|
const choices = Array.isArray(completion.choices) ? completion.choices : [];
|
|
1197
|
-
return asRecord(
|
|
1292
|
+
return choices.map((choice) => asRecord(choice));
|
|
1198
1293
|
}
|
|
1199
1294
|
function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
1200
1295
|
let event = "message";
|
|
@@ -1226,25 +1321,28 @@ function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
|
1226
1321
|
enqueue({ error });
|
|
1227
1322
|
return;
|
|
1228
1323
|
}
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1324
|
+
const choices = completionChoices(parsed).map((choice, index) => {
|
|
1325
|
+
const delta = asRecord(choice.delta);
|
|
1326
|
+
const text = contentToText(delta.content);
|
|
1327
|
+
const finishReason = choice.finish_reason ?? null;
|
|
1328
|
+
if (!text && finishReason === null) {
|
|
1329
|
+
return void 0;
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
finish_reason: finishReason,
|
|
1333
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
1334
|
+
logprobs: choice.logprobs ?? null,
|
|
1335
|
+
text
|
|
1336
|
+
};
|
|
1337
|
+
}).filter((choice) => choice !== void 0);
|
|
1233
1338
|
const usage = asRecord(parsed.usage);
|
|
1234
1339
|
const hasUsage = Object.keys(usage).length > 0;
|
|
1235
|
-
if (
|
|
1340
|
+
if (choices.length === 0 && !hasUsage) {
|
|
1236
1341
|
return;
|
|
1237
1342
|
}
|
|
1238
1343
|
enqueue(
|
|
1239
1344
|
removeUndefined({
|
|
1240
|
-
choices
|
|
1241
|
-
{
|
|
1242
|
-
finish_reason: finishReason,
|
|
1243
|
-
index: typeof choice.index === "number" ? choice.index : 0,
|
|
1244
|
-
logprobs: null,
|
|
1245
|
-
text
|
|
1246
|
-
}
|
|
1247
|
-
] : [],
|
|
1345
|
+
choices,
|
|
1248
1346
|
created: typeof parsed.created === "number" ? parsed.created : epochSeconds(),
|
|
1249
1347
|
id: contentToText(parsed.id) || `cmpl_${randomId()}`,
|
|
1250
1348
|
model: contentToText(parsed.model) || DEFAULT_MODEL,
|
|
@@ -1553,11 +1651,43 @@ var MetricsRegistry = class {
|
|
|
1553
1651
|
gauge("remaining", "Remaining quota for the Copilot category.", (q) => q.remaining);
|
|
1554
1652
|
gauge("entitlement", "Quota entitlement for the Copilot category.", (q) => q.entitlement);
|
|
1555
1653
|
gauge("used", "Used quota (entitlement minus remaining) for the category.", (q) => q.used);
|
|
1654
|
+
gauge("overage_count", "Overage count for the Copilot category.", (q) => q.overageCount);
|
|
1655
|
+
gauge(
|
|
1656
|
+
"overage_entitlement",
|
|
1657
|
+
"Overage entitlement for the Copilot category.",
|
|
1658
|
+
(q) => q.overageEntitlement
|
|
1659
|
+
);
|
|
1556
1660
|
gauge(
|
|
1557
1661
|
"percent_remaining",
|
|
1558
1662
|
"Percent of quota remaining for the Copilot category.",
|
|
1559
1663
|
(q) => q.percentRemaining
|
|
1560
1664
|
);
|
|
1665
|
+
booleanGauge(
|
|
1666
|
+
"unlimited",
|
|
1667
|
+
"Whether the Copilot quota category is unlimited.",
|
|
1668
|
+
(q) => q.unlimited
|
|
1669
|
+
);
|
|
1670
|
+
booleanGauge(
|
|
1671
|
+
"overage_permitted",
|
|
1672
|
+
"Whether overage is permitted for the Copilot category.",
|
|
1673
|
+
(q) => q.overagePermitted
|
|
1674
|
+
);
|
|
1675
|
+
booleanGauge("has_quota", "Whether the Copilot quota category has a quota.", (q) => q.hasQuota);
|
|
1676
|
+
booleanGauge(
|
|
1677
|
+
"token_based_billing",
|
|
1678
|
+
"Whether the Copilot quota category uses token-based billing.",
|
|
1679
|
+
(q) => q.tokenBasedBilling
|
|
1680
|
+
);
|
|
1681
|
+
dateGauge(
|
|
1682
|
+
"category_reset_timestamp_seconds",
|
|
1683
|
+
"Unix epoch of the Copilot category-specific quota reset.",
|
|
1684
|
+
(q) => q.quotaResetAt
|
|
1685
|
+
);
|
|
1686
|
+
dateGauge(
|
|
1687
|
+
"category_snapshot_timestamp_seconds",
|
|
1688
|
+
"Unix epoch of the Copilot category quota snapshot.",
|
|
1689
|
+
(q) => q.timestampUtc
|
|
1690
|
+
);
|
|
1561
1691
|
const resetMs = usage.quotaResetDate ? Date.parse(usage.quotaResetDate) : Number.NaN;
|
|
1562
1692
|
if (Number.isFinite(resetMs)) {
|
|
1563
1693
|
lines.push(
|
|
@@ -1576,6 +1706,30 @@ var MetricsRegistry = class {
|
|
|
1576
1706
|
})} 1`
|
|
1577
1707
|
);
|
|
1578
1708
|
}
|
|
1709
|
+
function booleanGauge(suffix, help, pick) {
|
|
1710
|
+
const present = categories.filter(([, quota]) => pick(quota) !== void 0);
|
|
1711
|
+
if (present.length === 0) {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
lines.push(`# HELP hoopilot_copilot_quota_${suffix} ${help}`);
|
|
1715
|
+
lines.push(`# TYPE hoopilot_copilot_quota_${suffix} gauge`);
|
|
1716
|
+
for (const [category, quota] of present) {
|
|
1717
|
+
lines.push(
|
|
1718
|
+
`hoopilot_copilot_quota_${suffix}${labels({ category })} ${pick(quota) ? 1 : 0}`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
function dateGauge(suffix, help, pick) {
|
|
1723
|
+
const present = categories.map(([category, quota]) => [category, Date.parse(pick(quota) ?? "")]).filter(([, timestamp]) => Number.isFinite(timestamp));
|
|
1724
|
+
if (present.length === 0) {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
lines.push(`# HELP hoopilot_copilot_quota_${suffix} ${help}`);
|
|
1728
|
+
lines.push(`# TYPE hoopilot_copilot_quota_${suffix} gauge`);
|
|
1729
|
+
for (const [category, timestamp] of present) {
|
|
1730
|
+
lines.push(`hoopilot_copilot_quota_${suffix}${labels({ category })} ${timestamp / 1e3}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1579
1733
|
}
|
|
1580
1734
|
};
|
|
1581
1735
|
function observeResponseUsage(response, fallbackModel, onUsage, signal) {
|
|
@@ -1816,6 +1970,12 @@ function createHoopilotHandler(options = {}) {
|
|
|
1816
1970
|
"request body was invalid json"
|
|
1817
1971
|
);
|
|
1818
1972
|
return finish(jsonError(400, "invalid_request_error", message));
|
|
1973
|
+
} else if (error instanceof OpenAICompatibilityError) {
|
|
1974
|
+
requestLogger.warn(
|
|
1975
|
+
{ err: errorDetails(error), event: "http.request.failed" },
|
|
1976
|
+
"request body used unsupported OpenAI compatibility fields"
|
|
1977
|
+
);
|
|
1978
|
+
return finish(jsonError(400, "invalid_request_error", message));
|
|
1819
1979
|
} else if (error instanceof RequestBodyTooLargeError) {
|
|
1820
1980
|
requestLogger.warn(
|
|
1821
1981
|
{ err: errorDetails(error), event: "http.request.failed" },
|
|
@@ -2267,8 +2427,8 @@ function metricsResponse(metrics) {
|
|
|
2267
2427
|
});
|
|
2268
2428
|
}
|
|
2269
2429
|
async function handleUsage(metrics, readUsage, signal) {
|
|
2270
|
-
const proxy = metrics.snapshot();
|
|
2271
2430
|
const { copilot, error } = await readUsage(signal);
|
|
2431
|
+
const proxy = metrics.snapshot();
|
|
2272
2432
|
const body = { copilot: copilot ?? null, object: "usage", proxy };
|
|
2273
2433
|
if (error) {
|
|
2274
2434
|
body.copilot_error = error;
|
|
@@ -2293,10 +2453,10 @@ function createUsageReader(client, metrics, now = Date.now, ttlMs = USAGE_CACHE_
|
|
|
2293
2453
|
metrics.recordCopilotQuota(value);
|
|
2294
2454
|
return { copilot: value };
|
|
2295
2455
|
} catch (error) {
|
|
2296
|
-
metrics.recordUpstream(usagePath, false);
|
|
2297
2456
|
if (error instanceof CopilotAuthError) {
|
|
2298
2457
|
return { error: error.message };
|
|
2299
2458
|
}
|
|
2459
|
+
metrics.recordUpstream(usagePath, false);
|
|
2300
2460
|
return { error: errorMessage(error) };
|
|
2301
2461
|
}
|
|
2302
2462
|
};
|