@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.js
CHANGED
|
@@ -607,6 +607,12 @@ function isLogLevel(value) {
|
|
|
607
607
|
|
|
608
608
|
// src/openai.ts
|
|
609
609
|
var DEFAULT_MODEL = "gpt-4.1";
|
|
610
|
+
var OpenAICompatibilityError = class extends Error {
|
|
611
|
+
constructor(message) {
|
|
612
|
+
super(message);
|
|
613
|
+
this.name = "OpenAICompatibilityError";
|
|
614
|
+
}
|
|
615
|
+
};
|
|
610
616
|
function responsesRequestToChatCompletion(request) {
|
|
611
617
|
const messages = [];
|
|
612
618
|
const instructions = contentToText(request.instructions);
|
|
@@ -640,13 +646,22 @@ function normalizeChatCompletionRequest(request) {
|
|
|
640
646
|
});
|
|
641
647
|
}
|
|
642
648
|
function completionsRequestToChatCompletion(request) {
|
|
649
|
+
assertSupportedLegacyCompletionRequest(request);
|
|
643
650
|
return removeUndefined({
|
|
651
|
+
frequency_penalty: request.frequency_penalty,
|
|
652
|
+
logit_bias: request.logit_bias,
|
|
644
653
|
max_tokens: request.max_tokens,
|
|
645
|
-
messages: [{ content:
|
|
654
|
+
messages: [{ content: legacyPromptToText(request.prompt), role: "user" }],
|
|
646
655
|
model: normalizeRequestedModel(request.model),
|
|
656
|
+
n: request.n,
|
|
657
|
+
presence_penalty: request.presence_penalty,
|
|
658
|
+
seed: request.seed,
|
|
659
|
+
stop: request.stop,
|
|
647
660
|
stream: request.stream === true,
|
|
661
|
+
stream_options: request.stream_options,
|
|
648
662
|
temperature: request.temperature,
|
|
649
|
-
top_p: request.top_p
|
|
663
|
+
top_p: request.top_p,
|
|
664
|
+
user: request.user
|
|
650
665
|
});
|
|
651
666
|
}
|
|
652
667
|
function normalizeRequestedModel(model) {
|
|
@@ -682,21 +697,21 @@ function chatCompletionToResponse(completion, responseId) {
|
|
|
682
697
|
});
|
|
683
698
|
}
|
|
684
699
|
function chatCompletionToCompletion(completion) {
|
|
685
|
-
const choice = firstChoice(completion);
|
|
686
|
-
const message = asRecord(choice.message);
|
|
687
700
|
return removeUndefined({
|
|
688
|
-
choices:
|
|
689
|
-
|
|
701
|
+
choices: completionChoices(completion).map((choice, index) => {
|
|
702
|
+
const message = asRecord(choice.message);
|
|
703
|
+
return {
|
|
690
704
|
finish_reason: choice.finish_reason ?? "stop",
|
|
691
|
-
index:
|
|
692
|
-
logprobs: null,
|
|
693
|
-
text: contentToText(message.content)
|
|
694
|
-
}
|
|
695
|
-
|
|
705
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
706
|
+
logprobs: choice.logprobs ?? null,
|
|
707
|
+
text: contentToText(choice.text) || contentToText(message.content)
|
|
708
|
+
};
|
|
709
|
+
}),
|
|
696
710
|
created: completion.created ?? epochSeconds(),
|
|
697
711
|
id: completion.id ?? `cmpl_${randomId()}`,
|
|
698
712
|
model: completion.model ?? DEFAULT_MODEL,
|
|
699
713
|
object: "text_completion",
|
|
714
|
+
system_fingerprint: completion.system_fingerprint,
|
|
700
715
|
usage: completion.usage
|
|
701
716
|
});
|
|
702
717
|
}
|
|
@@ -960,7 +975,8 @@ function inputToMessages(input) {
|
|
|
960
975
|
const messages = [];
|
|
961
976
|
for (const item of input) {
|
|
962
977
|
const record = asRecord(item);
|
|
963
|
-
|
|
978
|
+
const type = contentToText(record.type);
|
|
979
|
+
if (type === "function_call_output") {
|
|
964
980
|
messages.push({
|
|
965
981
|
content: contentToText(record.output),
|
|
966
982
|
role: "tool",
|
|
@@ -968,7 +984,7 @@ function inputToMessages(input) {
|
|
|
968
984
|
});
|
|
969
985
|
continue;
|
|
970
986
|
}
|
|
971
|
-
if (
|
|
987
|
+
if (type === "function_call") {
|
|
972
988
|
messages.push({
|
|
973
989
|
role: "assistant",
|
|
974
990
|
tool_calls: [
|
|
@@ -984,7 +1000,10 @@ function inputToMessages(input) {
|
|
|
984
1000
|
});
|
|
985
1001
|
continue;
|
|
986
1002
|
}
|
|
987
|
-
|
|
1003
|
+
if (type && type !== "message") {
|
|
1004
|
+
unsupportedResponsesFeature(`input item type "${type}"`);
|
|
1005
|
+
}
|
|
1006
|
+
const role = responsesRoleToChatRole(contentToText(record.role));
|
|
988
1007
|
const content = chatMessageContent(record.content);
|
|
989
1008
|
if (role && content !== void 0) {
|
|
990
1009
|
messages.push({ content, role });
|
|
@@ -997,7 +1016,10 @@ function chatMessageContent(content) {
|
|
|
997
1016
|
return content;
|
|
998
1017
|
}
|
|
999
1018
|
if (!Array.isArray(content)) {
|
|
1000
|
-
|
|
1019
|
+
if (content === void 0 || content === null) {
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
unsupportedResponsesFeature("non-array message content objects");
|
|
1001
1023
|
}
|
|
1002
1024
|
const parts = [];
|
|
1003
1025
|
for (const part of content) {
|
|
@@ -1005,13 +1027,31 @@ function chatMessageContent(content) {
|
|
|
1005
1027
|
const type = contentToText(record.type);
|
|
1006
1028
|
if (type === "input_text" || type === "output_text" || type === "text") {
|
|
1007
1029
|
parts.push({ text: contentToText(record.text), type: "text" });
|
|
1030
|
+
continue;
|
|
1008
1031
|
}
|
|
1009
1032
|
if (type === "input_image") {
|
|
1033
|
+
if (contentToText(record.file_id)) {
|
|
1034
|
+
unsupportedResponsesFeature("input_image file_id parts");
|
|
1035
|
+
}
|
|
1010
1036
|
const imageUrl = contentToText(record.image_url);
|
|
1011
|
-
if (imageUrl) {
|
|
1012
|
-
|
|
1037
|
+
if (!imageUrl) {
|
|
1038
|
+
unsupportedResponsesFeature("input_image parts without image_url");
|
|
1013
1039
|
}
|
|
1040
|
+
const image = { url: imageUrl };
|
|
1041
|
+
const detail = contentToText(record.detail);
|
|
1042
|
+
if (detail) {
|
|
1043
|
+
image.detail = detail;
|
|
1044
|
+
}
|
|
1045
|
+
parts.push({ image_url: image, type: "image_url" });
|
|
1046
|
+
continue;
|
|
1014
1047
|
}
|
|
1048
|
+
if (type === "input_file") {
|
|
1049
|
+
unsupportedResponsesFeature("input_file parts");
|
|
1050
|
+
}
|
|
1051
|
+
if (type === "input_audio") {
|
|
1052
|
+
unsupportedResponsesFeature("input_audio parts");
|
|
1053
|
+
}
|
|
1054
|
+
unsupportedResponsesFeature(`content part type "${type || "unknown"}"`);
|
|
1015
1055
|
}
|
|
1016
1056
|
if (parts.length === 0) {
|
|
1017
1057
|
return void 0;
|
|
@@ -1021,11 +1061,38 @@ function chatMessageContent(content) {
|
|
|
1021
1061
|
}
|
|
1022
1062
|
return parts;
|
|
1023
1063
|
}
|
|
1024
|
-
function
|
|
1025
|
-
if (
|
|
1026
|
-
return prompt
|
|
1064
|
+
function legacyPromptToText(prompt) {
|
|
1065
|
+
if (typeof prompt === "string") {
|
|
1066
|
+
return prompt;
|
|
1067
|
+
}
|
|
1068
|
+
if (Array.isArray(prompt) && prompt.length === 1 && typeof prompt[0] === "string") {
|
|
1069
|
+
return prompt[0];
|
|
1070
|
+
}
|
|
1071
|
+
throw new OpenAICompatibilityError(
|
|
1072
|
+
"Hoopilot legacy completions compatibility supports exactly one string prompt per request."
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
function assertSupportedLegacyCompletionRequest(request) {
|
|
1076
|
+
if (request.echo === true) {
|
|
1077
|
+
throw new OpenAICompatibilityError(
|
|
1078
|
+
"Hoopilot legacy completions compatibility does not support echo=true."
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
if (typeof request.best_of === "number" && request.best_of > 1) {
|
|
1082
|
+
throw new OpenAICompatibilityError(
|
|
1083
|
+
"Hoopilot legacy completions compatibility does not support best_of greater than 1."
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (typeof request.logprobs === "number" && request.logprobs > 0) {
|
|
1087
|
+
throw new OpenAICompatibilityError(
|
|
1088
|
+
"Hoopilot legacy completions compatibility does not support legacy logprobs."
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
if (contentToText(request.suffix)) {
|
|
1092
|
+
throw new OpenAICompatibilityError(
|
|
1093
|
+
"Hoopilot legacy completions compatibility does not support suffix."
|
|
1094
|
+
);
|
|
1027
1095
|
}
|
|
1028
|
-
return contentToText(prompt);
|
|
1029
1096
|
}
|
|
1030
1097
|
function contentToText(content) {
|
|
1031
1098
|
if (typeof content === "string") {
|
|
@@ -1049,25 +1116,35 @@ function contentToText(content) {
|
|
|
1049
1116
|
}
|
|
1050
1117
|
return "";
|
|
1051
1118
|
}
|
|
1052
|
-
function
|
|
1053
|
-
if (role
|
|
1119
|
+
function responsesRoleToChatRole(role) {
|
|
1120
|
+
if (!role) {
|
|
1121
|
+
return "user";
|
|
1122
|
+
}
|
|
1123
|
+
if (role === "assistant" || role === "developer" || role === "system" || role === "tool" || role === "user") {
|
|
1054
1124
|
return role === "developer" ? "system" : role;
|
|
1055
1125
|
}
|
|
1056
|
-
|
|
1126
|
+
unsupportedResponsesFeature(`message role "${role}"`);
|
|
1057
1127
|
}
|
|
1058
1128
|
function chatTools(tools) {
|
|
1059
1129
|
if (!Array.isArray(tools)) {
|
|
1060
1130
|
return void 0;
|
|
1061
1131
|
}
|
|
1062
|
-
const converted = tools.map((tool) =>
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1132
|
+
const converted = tools.map((tool) => {
|
|
1133
|
+
const record = asRecord(tool);
|
|
1134
|
+
const type = contentToText(record.type);
|
|
1135
|
+
if (type !== "function") {
|
|
1136
|
+
unsupportedResponsesFeature(`tool type "${type || "unknown"}"`);
|
|
1137
|
+
}
|
|
1138
|
+
return {
|
|
1139
|
+
function: removeUndefined({
|
|
1140
|
+
description: record.description,
|
|
1141
|
+
name: record.name,
|
|
1142
|
+
parameters: record.parameters,
|
|
1143
|
+
strict: record.strict
|
|
1144
|
+
}),
|
|
1145
|
+
type: "function"
|
|
1146
|
+
};
|
|
1147
|
+
});
|
|
1071
1148
|
return converted.length > 0 ? converted : void 0;
|
|
1072
1149
|
}
|
|
1073
1150
|
function chatToolChoice(toolChoice) {
|
|
@@ -1075,10 +1152,16 @@ function chatToolChoice(toolChoice) {
|
|
|
1075
1152
|
return toolChoice;
|
|
1076
1153
|
}
|
|
1077
1154
|
const record = asRecord(toolChoice);
|
|
1078
|
-
|
|
1155
|
+
const type = contentToText(record.type);
|
|
1156
|
+
if (type === "function" && typeof record.name === "string") {
|
|
1079
1157
|
return { function: { name: record.name }, type: "function" };
|
|
1080
1158
|
}
|
|
1081
|
-
|
|
1159
|
+
unsupportedResponsesFeature(`tool_choice type "${type || "unknown"}"`);
|
|
1160
|
+
}
|
|
1161
|
+
function unsupportedResponsesFeature(feature) {
|
|
1162
|
+
throw new OpenAICompatibilityError(
|
|
1163
|
+
`Hoopilot Responses-to-chat compatibility does not support ${feature}.`
|
|
1164
|
+
);
|
|
1082
1165
|
}
|
|
1083
1166
|
function outputItemsFromMessage(message) {
|
|
1084
1167
|
const output = [];
|
|
@@ -1193,8 +1276,11 @@ function firstNumber(...values) {
|
|
|
1193
1276
|
return void 0;
|
|
1194
1277
|
}
|
|
1195
1278
|
function firstChoice(completion) {
|
|
1279
|
+
return completionChoices(completion)[0] ?? {};
|
|
1280
|
+
}
|
|
1281
|
+
function completionChoices(completion) {
|
|
1196
1282
|
const choices = Array.isArray(completion.choices) ? completion.choices : [];
|
|
1197
|
-
return asRecord(
|
|
1283
|
+
return choices.map((choice) => asRecord(choice));
|
|
1198
1284
|
}
|
|
1199
1285
|
function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
1200
1286
|
let event = "message";
|
|
@@ -1226,25 +1312,28 @@ function processCompletionSseBlock(block, enqueue, markTerminal) {
|
|
|
1226
1312
|
enqueue({ error });
|
|
1227
1313
|
return;
|
|
1228
1314
|
}
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1315
|
+
const choices = completionChoices(parsed).map((choice, index) => {
|
|
1316
|
+
const delta = asRecord(choice.delta);
|
|
1317
|
+
const text = contentToText(delta.content);
|
|
1318
|
+
const finishReason = choice.finish_reason ?? null;
|
|
1319
|
+
if (!text && finishReason === null) {
|
|
1320
|
+
return void 0;
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
finish_reason: finishReason,
|
|
1324
|
+
index: typeof choice.index === "number" ? choice.index : index,
|
|
1325
|
+
logprobs: choice.logprobs ?? null,
|
|
1326
|
+
text
|
|
1327
|
+
};
|
|
1328
|
+
}).filter((choice) => choice !== void 0);
|
|
1233
1329
|
const usage = asRecord(parsed.usage);
|
|
1234
1330
|
const hasUsage = Object.keys(usage).length > 0;
|
|
1235
|
-
if (
|
|
1331
|
+
if (choices.length === 0 && !hasUsage) {
|
|
1236
1332
|
return;
|
|
1237
1333
|
}
|
|
1238
1334
|
enqueue(
|
|
1239
1335
|
removeUndefined({
|
|
1240
|
-
choices
|
|
1241
|
-
{
|
|
1242
|
-
finish_reason: finishReason,
|
|
1243
|
-
index: typeof choice.index === "number" ? choice.index : 0,
|
|
1244
|
-
logprobs: null,
|
|
1245
|
-
text
|
|
1246
|
-
}
|
|
1247
|
-
] : [],
|
|
1336
|
+
choices,
|
|
1248
1337
|
created: typeof parsed.created === "number" ? parsed.created : epochSeconds(),
|
|
1249
1338
|
id: contentToText(parsed.id) || `cmpl_${randomId()}`,
|
|
1250
1339
|
model: contentToText(parsed.model) || DEFAULT_MODEL,
|
|
@@ -1816,6 +1905,12 @@ function createHoopilotHandler(options = {}) {
|
|
|
1816
1905
|
"request body was invalid json"
|
|
1817
1906
|
);
|
|
1818
1907
|
return finish(jsonError(400, "invalid_request_error", message));
|
|
1908
|
+
} else if (error instanceof OpenAICompatibilityError) {
|
|
1909
|
+
requestLogger.warn(
|
|
1910
|
+
{ err: errorDetails(error), event: "http.request.failed" },
|
|
1911
|
+
"request body used unsupported OpenAI compatibility fields"
|
|
1912
|
+
);
|
|
1913
|
+
return finish(jsonError(400, "invalid_request_error", message));
|
|
1819
1914
|
} else if (error instanceof RequestBodyTooLargeError) {
|
|
1820
1915
|
requestLogger.warn(
|
|
1821
1916
|
{ err: errorDetails(error), event: "http.request.failed" },
|