@openhoo/hoopilot 0.7.3 → 0.7.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 +81 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +144 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -677,6 +677,12 @@ function isLogLevel(value) {
|
|
|
677
677
|
|
|
678
678
|
// src/openai.ts
|
|
679
679
|
var DEFAULT_MODEL = "gpt-4.1";
|
|
680
|
+
var OpenAICompatibilityError = class extends Error {
|
|
681
|
+
constructor(message) {
|
|
682
|
+
super(message);
|
|
683
|
+
this.name = "OpenAICompatibilityError";
|
|
684
|
+
}
|
|
685
|
+
};
|
|
680
686
|
function responsesRequestToChatCompletion(request) {
|
|
681
687
|
const messages = [];
|
|
682
688
|
const instructions = contentToText(request.instructions);
|
|
@@ -710,13 +716,22 @@ function normalizeChatCompletionRequest(request) {
|
|
|
710
716
|
});
|
|
711
717
|
}
|
|
712
718
|
function completionsRequestToChatCompletion(request) {
|
|
719
|
+
assertSupportedLegacyCompletionRequest(request);
|
|
713
720
|
return removeUndefined({
|
|
721
|
+
frequency_penalty: request.frequency_penalty,
|
|
722
|
+
logit_bias: request.logit_bias,
|
|
714
723
|
max_tokens: request.max_tokens,
|
|
715
|
-
messages: [{ content:
|
|
724
|
+
messages: [{ content: legacyPromptToText(request.prompt), role: "user" }],
|
|
716
725
|
model: normalizeRequestedModel(request.model),
|
|
726
|
+
n: request.n,
|
|
727
|
+
presence_penalty: request.presence_penalty,
|
|
728
|
+
seed: request.seed,
|
|
729
|
+
stop: request.stop,
|
|
717
730
|
stream: request.stream === true,
|
|
731
|
+
stream_options: request.stream_options,
|
|
718
732
|
temperature: request.temperature,
|
|
719
|
-
top_p: request.top_p
|
|
733
|
+
top_p: request.top_p,
|
|
734
|
+
user: request.user
|
|
720
735
|
});
|
|
721
736
|
}
|
|
722
737
|
function normalizeRequestedModel(model) {
|
|
@@ -752,21 +767,21 @@ function chatCompletionToResponse(completion, responseId) {
|
|
|
752
767
|
});
|
|
753
768
|
}
|
|
754
769
|
function chatCompletionToCompletion(completion) {
|
|
755
|
-
const choice = firstChoice(completion);
|
|
756
|
-
const message = asRecord(choice.message);
|
|
757
770
|
return removeUndefined({
|
|
758
|
-
choices:
|
|
759
|
-
|
|
771
|
+
choices: completionChoices(completion).map((choice, index) => {
|
|
772
|
+
const message = asRecord(choice.message);
|
|
773
|
+
return {
|
|
760
774
|
finish_reason: choice.finish_reason ?? "stop",
|
|
761
|
-
index:
|
|
762
|
-
logprobs: null,
|
|
763
|
-
text: contentToText(message.content)
|
|
764
|
-
}
|
|
765
|
-
|
|
775
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
776
|
+
logprobs: choice.logprobs ?? null,
|
|
777
|
+
text: contentToText(choice.text) || contentToText(message.content)
|
|
778
|
+
};
|
|
779
|
+
}),
|
|
766
780
|
created: completion.created ?? epochSeconds(),
|
|
767
781
|
id: completion.id ?? `cmpl_${randomId()}`,
|
|
768
782
|
model: completion.model ?? DEFAULT_MODEL,
|
|
769
783
|
object: "text_completion",
|
|
784
|
+
system_fingerprint: completion.system_fingerprint,
|
|
770
785
|
usage: completion.usage
|
|
771
786
|
});
|
|
772
787
|
}
|
|
@@ -1030,7 +1045,8 @@ function inputToMessages(input) {
|
|
|
1030
1045
|
const messages = [];
|
|
1031
1046
|
for (const item of input) {
|
|
1032
1047
|
const record = asRecord(item);
|
|
1033
|
-
|
|
1048
|
+
const type = contentToText(record.type);
|
|
1049
|
+
if (type === "function_call_output") {
|
|
1034
1050
|
messages.push({
|
|
1035
1051
|
content: contentToText(record.output),
|
|
1036
1052
|
role: "tool",
|
|
@@ -1038,7 +1054,7 @@ function inputToMessages(input) {
|
|
|
1038
1054
|
});
|
|
1039
1055
|
continue;
|
|
1040
1056
|
}
|
|
1041
|
-
if (
|
|
1057
|
+
if (type === "function_call") {
|
|
1042
1058
|
messages.push({
|
|
1043
1059
|
role: "assistant",
|
|
1044
1060
|
tool_calls: [
|
|
@@ -1054,7 +1070,10 @@ function inputToMessages(input) {
|
|
|
1054
1070
|
});
|
|
1055
1071
|
continue;
|
|
1056
1072
|
}
|
|
1057
|
-
|
|
1073
|
+
if (type && type !== "message") {
|
|
1074
|
+
unsupportedResponsesFeature(`input item type "${type}"`);
|
|
1075
|
+
}
|
|
1076
|
+
const role = responsesRoleToChatRole(contentToText(record.role));
|
|
1058
1077
|
const content = chatMessageContent(record.content);
|
|
1059
1078
|
if (role && content !== void 0) {
|
|
1060
1079
|
messages.push({ content, role });
|
|
@@ -1067,7 +1086,10 @@ function chatMessageContent(content) {
|
|
|
1067
1086
|
return content;
|
|
1068
1087
|
}
|
|
1069
1088
|
if (!Array.isArray(content)) {
|
|
1070
|
-
|
|
1089
|
+
if (content === void 0 || content === null) {
|
|
1090
|
+
return void 0;
|
|
1091
|
+
}
|
|
1092
|
+
unsupportedResponsesFeature("non-array message content objects");
|
|
1071
1093
|
}
|
|
1072
1094
|
const parts = [];
|
|
1073
1095
|
for (const part of content) {
|
|
@@ -1075,13 +1097,31 @@ function chatMessageContent(content) {
|
|
|
1075
1097
|
const type = contentToText(record.type);
|
|
1076
1098
|
if (type === "input_text" || type === "output_text" || type === "text") {
|
|
1077
1099
|
parts.push({ text: contentToText(record.text), type: "text" });
|
|
1100
|
+
continue;
|
|
1078
1101
|
}
|
|
1079
1102
|
if (type === "input_image") {
|
|
1103
|
+
if (contentToText(record.file_id)) {
|
|
1104
|
+
unsupportedResponsesFeature("input_image file_id parts");
|
|
1105
|
+
}
|
|
1080
1106
|
const imageUrl = contentToText(record.image_url);
|
|
1081
|
-
if (imageUrl) {
|
|
1082
|
-
|
|
1107
|
+
if (!imageUrl) {
|
|
1108
|
+
unsupportedResponsesFeature("input_image parts without image_url");
|
|
1083
1109
|
}
|
|
1110
|
+
const image = { url: imageUrl };
|
|
1111
|
+
const detail = contentToText(record.detail);
|
|
1112
|
+
if (detail) {
|
|
1113
|
+
image.detail = detail;
|
|
1114
|
+
}
|
|
1115
|
+
parts.push({ image_url: image, type: "image_url" });
|
|
1116
|
+
continue;
|
|
1084
1117
|
}
|
|
1118
|
+
if (type === "input_file") {
|
|
1119
|
+
unsupportedResponsesFeature("input_file parts");
|
|
1120
|
+
}
|
|
1121
|
+
if (type === "input_audio") {
|
|
1122
|
+
unsupportedResponsesFeature("input_audio parts");
|
|
1123
|
+
}
|
|
1124
|
+
unsupportedResponsesFeature(`content part type "${type || "unknown"}"`);
|
|
1085
1125
|
}
|
|
1086
1126
|
if (parts.length === 0) {
|
|
1087
1127
|
return void 0;
|
|
@@ -1091,11 +1131,38 @@ function chatMessageContent(content) {
|
|
|
1091
1131
|
}
|
|
1092
1132
|
return parts;
|
|
1093
1133
|
}
|
|
1094
|
-
function
|
|
1095
|
-
if (
|
|
1096
|
-
return prompt
|
|
1134
|
+
function legacyPromptToText(prompt) {
|
|
1135
|
+
if (typeof prompt === "string") {
|
|
1136
|
+
return prompt;
|
|
1137
|
+
}
|
|
1138
|
+
if (Array.isArray(prompt) && prompt.length === 1 && typeof prompt[0] === "string") {
|
|
1139
|
+
return prompt[0];
|
|
1140
|
+
}
|
|
1141
|
+
throw new OpenAICompatibilityError(
|
|
1142
|
+
"Hoopilot legacy completions compatibility supports exactly one string prompt per request."
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
function assertSupportedLegacyCompletionRequest(request) {
|
|
1146
|
+
if (request.echo === true) {
|
|
1147
|
+
throw new OpenAICompatibilityError(
|
|
1148
|
+
"Hoopilot legacy completions compatibility does not support echo=true."
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof request.best_of === "number" && request.best_of > 1) {
|
|
1152
|
+
throw new OpenAICompatibilityError(
|
|
1153
|
+
"Hoopilot legacy completions compatibility does not support best_of greater than 1."
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
if (typeof request.logprobs === "number" && request.logprobs > 0) {
|
|
1157
|
+
throw new OpenAICompatibilityError(
|
|
1158
|
+
"Hoopilot legacy completions compatibility does not support legacy logprobs."
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
if (contentToText(request.suffix)) {
|
|
1162
|
+
throw new OpenAICompatibilityError(
|
|
1163
|
+
"Hoopilot legacy completions compatibility does not support suffix."
|
|
1164
|
+
);
|
|
1097
1165
|
}
|
|
1098
|
-
return contentToText(prompt);
|
|
1099
1166
|
}
|
|
1100
1167
|
function contentToText(content) {
|
|
1101
1168
|
if (typeof content === "string") {
|
|
@@ -1119,25 +1186,35 @@ function contentToText(content) {
|
|
|
1119
1186
|
}
|
|
1120
1187
|
return "";
|
|
1121
1188
|
}
|
|
1122
|
-
function
|
|
1123
|
-
if (role
|
|
1189
|
+
function responsesRoleToChatRole(role) {
|
|
1190
|
+
if (!role) {
|
|
1191
|
+
return "user";
|
|
1192
|
+
}
|
|
1193
|
+
if (role === "assistant" || role === "developer" || role === "system" || role === "tool" || role === "user") {
|
|
1124
1194
|
return role === "developer" ? "system" : role;
|
|
1125
1195
|
}
|
|
1126
|
-
|
|
1196
|
+
unsupportedResponsesFeature(`message role "${role}"`);
|
|
1127
1197
|
}
|
|
1128
1198
|
function chatTools(tools) {
|
|
1129
1199
|
if (!Array.isArray(tools)) {
|
|
1130
1200
|
return void 0;
|
|
1131
1201
|
}
|
|
1132
|
-
const converted = tools.map((tool) =>
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1202
|
+
const converted = tools.map((tool) => {
|
|
1203
|
+
const record = asRecord(tool);
|
|
1204
|
+
const type = contentToText(record.type);
|
|
1205
|
+
if (type !== "function") {
|
|
1206
|
+
unsupportedResponsesFeature(`tool type "${type || "unknown"}"`);
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
function: removeUndefined({
|
|
1210
|
+
description: record.description,
|
|
1211
|
+
name: record.name,
|
|
1212
|
+
parameters: record.parameters,
|
|
1213
|
+
strict: record.strict
|
|
1214
|
+
}),
|
|
1215
|
+
type: "function"
|
|
1216
|
+
};
|
|
1217
|
+
});
|
|
1141
1218
|
return converted.length > 0 ? converted : void 0;
|
|
1142
1219
|
}
|
|
1143
1220
|
function chatToolChoice(toolChoice) {
|
|
@@ -1145,10 +1222,16 @@ function chatToolChoice(toolChoice) {
|
|
|
1145
1222
|
return toolChoice;
|
|
1146
1223
|
}
|
|
1147
1224
|
const record = asRecord(toolChoice);
|
|
1148
|
-
|
|
1225
|
+
const type = contentToText(record.type);
|
|
1226
|
+
if (type === "function" && typeof record.name === "string") {
|
|
1149
1227
|
return { function: { name: record.name }, type: "function" };
|
|
1150
1228
|
}
|
|
1151
|
-
|
|
1229
|
+
unsupportedResponsesFeature(`tool_choice type "${type || "unknown"}"`);
|
|
1230
|
+
}
|
|
1231
|
+
function unsupportedResponsesFeature(feature) {
|
|
1232
|
+
throw new OpenAICompatibilityError(
|
|
1233
|
+
`Hoopilot Responses-to-chat compatibility does not support ${feature}.`
|
|
1234
|
+
);
|
|
1152
1235
|
}
|
|
1153
1236
|
function outputItemsFromMessage(message) {
|
|
1154
1237
|
const output = [];
|
|
@@ -1263,8 +1346,11 @@ function firstNumber(...values) {
|
|
|
1263
1346
|
return void 0;
|
|
1264
1347
|
}
|
|
1265
1348
|
function firstChoice(completion) {
|
|
1349
|
+
return completionChoices(completion)[0] ?? {};
|
|
1350
|
+
}
|
|
1351
|
+
function completionChoices(completion) {
|
|
1266
1352
|
const choices = Array.isArray(completion.choices) ? completion.choices : [];
|
|
1267
|
-
return asRecord(
|
|
1353
|
+
return choices.map((choice) => asRecord(choice));
|
|
1268
1354
|
}
|
|
1269
1355
|
function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
1270
1356
|
let event = "message";
|
|
@@ -1296,25 +1382,28 @@ function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
|
1296
1382
|
enqueue({ error });
|
|
1297
1383
|
return;
|
|
1298
1384
|
}
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1385
|
+
const choices = completionChoices(parsed).map((choice, index) => {
|
|
1386
|
+
const delta = asRecord(choice.delta);
|
|
1387
|
+
const text = contentToText(delta.content);
|
|
1388
|
+
const finishReason = choice.finish_reason ?? null;
|
|
1389
|
+
if (!text && finishReason === null) {
|
|
1390
|
+
return void 0;
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
finish_reason: finishReason,
|
|
1394
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
1395
|
+
logprobs: choice.logprobs ?? null,
|
|
1396
|
+
text
|
|
1397
|
+
};
|
|
1398
|
+
}).filter((choice) => choice !== void 0);
|
|
1303
1399
|
const usage = asRecord(parsed.usage);
|
|
1304
1400
|
const hasUsage = Object.keys(usage).length > 0;
|
|
1305
|
-
if (
|
|
1401
|
+
if (choices.length === 0 && !hasUsage) {
|
|
1306
1402
|
return;
|
|
1307
1403
|
}
|
|
1308
1404
|
enqueue(
|
|
1309
1405
|
removeUndefined({
|
|
1310
|
-
choices
|
|
1311
|
-
{
|
|
1312
|
-
finish_reason: finishReason,
|
|
1313
|
-
index: typeof choice.index === "number" ? choice.index : 0,
|
|
1314
|
-
logprobs: null,
|
|
1315
|
-
text
|
|
1316
|
-
}
|
|
1317
|
-
] : [],
|
|
1406
|
+
choices,
|
|
1318
1407
|
created: typeof parsed.created === "number" ? parsed.created : epochSeconds(),
|
|
1319
1408
|
id: contentToText(parsed.id) || `cmpl_${randomId()}`,
|
|
1320
1409
|
model: contentToText(parsed.model) || DEFAULT_MODEL,
|
|
@@ -1886,6 +1975,12 @@ function createHoopilotHandler(options = {}) {
|
|
|
1886
1975
|
"request body was invalid json"
|
|
1887
1976
|
);
|
|
1888
1977
|
return finish(jsonError(400, "invalid_request_error", message));
|
|
1978
|
+
} else if (error instanceof OpenAICompatibilityError) {
|
|
1979
|
+
requestLogger.warn(
|
|
1980
|
+
{ err: errorDetails(error), event: "http.request.failed" },
|
|
1981
|
+
"request body used unsupported OpenAI compatibility fields"
|
|
1982
|
+
);
|
|
1983
|
+
return finish(jsonError(400, "invalid_request_error", message));
|
|
1889
1984
|
} else if (error instanceof RequestBodyTooLargeError) {
|
|
1890
1985
|
requestLogger.warn(
|
|
1891
1986
|
{ err: errorDetails(error), event: "http.request.failed" },
|