@openhoo/hoopilot 0.7.5 → 0.8.1
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 +16 -2
- package/dist/cli.js +734 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1967 -1255
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +1962 -1255
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -447,7 +447,18 @@ function oauthHeaders() {
|
|
|
447
447
|
return headers;
|
|
448
448
|
}
|
|
449
449
|
function normalizeDomain(value) {
|
|
450
|
-
|
|
450
|
+
const raw = value.trim();
|
|
451
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
452
|
+
let url;
|
|
453
|
+
try {
|
|
454
|
+
url = new URL(withScheme);
|
|
455
|
+
} catch {
|
|
456
|
+
throw new Error(`Invalid GitHub domain: ${value}.`);
|
|
457
|
+
}
|
|
458
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !url.hostname || url.pathname !== "" && url.pathname !== "/" || url.search || url.hash) {
|
|
459
|
+
throw new Error(`Invalid GitHub domain: ${value}. Provide only a hostname.`);
|
|
460
|
+
}
|
|
461
|
+
return url.host;
|
|
451
462
|
}
|
|
452
463
|
function positiveSeconds(value, fallback) {
|
|
453
464
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
@@ -897,6 +908,641 @@ function epochSeconds() {
|
|
|
897
908
|
return Math.floor(Date.now() / 1e3);
|
|
898
909
|
}
|
|
899
910
|
|
|
911
|
+
// src/anthropic.ts
|
|
912
|
+
var AnthropicCompatibilityError = class extends Error {
|
|
913
|
+
constructor(message) {
|
|
914
|
+
super(message);
|
|
915
|
+
this.name = "AnthropicCompatibilityError";
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
function anthropicMessagesToResponsesRequest(request) {
|
|
919
|
+
return removeUndefined2({
|
|
920
|
+
input: anthropicMessagesToResponsesInput(request.messages),
|
|
921
|
+
instructions: anthropicSystemToInstructions(request.system),
|
|
922
|
+
max_output_tokens: typeof request.max_tokens === "number" && Number.isFinite(request.max_tokens) ? request.max_tokens : void 0,
|
|
923
|
+
metadata: request.metadata,
|
|
924
|
+
model: normalizeRequestedModel(request.model),
|
|
925
|
+
parallel_tool_calls: true,
|
|
926
|
+
reasoning: anthropicThinkingToReasoning(request.thinking),
|
|
927
|
+
stop: anthropicStopSequences(request.stop_sequences),
|
|
928
|
+
stream: request.stream === true,
|
|
929
|
+
temperature: request.temperature,
|
|
930
|
+
tool_choice: anthropicToolChoice(request.tool_choice),
|
|
931
|
+
tools: anthropicTools(request.tools),
|
|
932
|
+
top_p: request.top_p
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function responsesResponseToAnthropicMessage(response, fallbackModel) {
|
|
936
|
+
const content = anthropicContentFromResponsesOutput(response);
|
|
937
|
+
const usage = anthropicUsage(response.usage);
|
|
938
|
+
return {
|
|
939
|
+
content,
|
|
940
|
+
id: textValue(response.id) || `msg_${randomId2()}`,
|
|
941
|
+
model: textValue(response.model) || fallbackModel,
|
|
942
|
+
role: "assistant",
|
|
943
|
+
stop_reason: anthropicStopReason(response, content),
|
|
944
|
+
stop_sequence: null,
|
|
945
|
+
type: "message",
|
|
946
|
+
usage
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function responsesStreamToAnthropicStream(stream, options) {
|
|
950
|
+
const decoder = new TextDecoder();
|
|
951
|
+
const encoder = new TextEncoder();
|
|
952
|
+
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
|
+
};
|
|
963
|
+
return new ReadableStream({
|
|
964
|
+
async start(controller) {
|
|
965
|
+
const enqueue = (event, data) => {
|
|
966
|
+
controller.enqueue(encoder.encode(encodeSse(event, data)));
|
|
967
|
+
};
|
|
968
|
+
const reader = stream.getReader();
|
|
969
|
+
try {
|
|
970
|
+
while (true) {
|
|
971
|
+
const result = await reader.read();
|
|
972
|
+
if (result.done) {
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
976
|
+
const blocks = buffer.split(/\r?\n\r?\n/);
|
|
977
|
+
buffer = blocks.pop() ?? "";
|
|
978
|
+
for (const block of blocks) {
|
|
979
|
+
processResponsesSseBlock(block, state, enqueue);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const tail = `${buffer}${decoder.decode()}`;
|
|
983
|
+
if (tail.trim()) {
|
|
984
|
+
processResponsesSseBlock(tail, state, enqueue);
|
|
985
|
+
}
|
|
986
|
+
finishAnthropicStream(state, enqueue);
|
|
987
|
+
controller.close();
|
|
988
|
+
} catch (error) {
|
|
989
|
+
await reader.cancel(error).catch(() => {
|
|
990
|
+
});
|
|
991
|
+
controller.error(error);
|
|
992
|
+
} finally {
|
|
993
|
+
reader.releaseLock();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
function estimateAnthropicMessageTokens(request) {
|
|
999
|
+
const chars = estimatedTextSize(request.system) + estimatedTextSize(request.messages) + estimatedTextSize(request.tools) + estimatedTextSize(request.tool_choice) + estimatedTextSize(request.thinking);
|
|
1000
|
+
const messageCount = Array.isArray(request.messages) ? request.messages.length : 1;
|
|
1001
|
+
const toolCount = Array.isArray(request.tools) ? request.tools.length : 0;
|
|
1002
|
+
const inputTokens = Math.max(1, Math.ceil(chars / 4) + messageCount * 4 + toolCount * 16);
|
|
1003
|
+
return {
|
|
1004
|
+
input_tokens: inputTokens,
|
|
1005
|
+
total_tokens: inputTokens
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function anthropicMessagesToResponsesInput(messages) {
|
|
1009
|
+
if (!Array.isArray(messages)) {
|
|
1010
|
+
throw new AnthropicCompatibilityError("Anthropic Messages requests require messages[].");
|
|
1011
|
+
}
|
|
1012
|
+
const input = [];
|
|
1013
|
+
for (const message of messages) {
|
|
1014
|
+
const record = asRecord(message);
|
|
1015
|
+
const role = anthropicRole(record.role);
|
|
1016
|
+
const parts = anthropicContentParts(record.content);
|
|
1017
|
+
const messageParts = [];
|
|
1018
|
+
const flushMessage = () => {
|
|
1019
|
+
if (messageParts.length === 0) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
input.push({
|
|
1023
|
+
content: [...messageParts],
|
|
1024
|
+
role,
|
|
1025
|
+
type: "message"
|
|
1026
|
+
});
|
|
1027
|
+
messageParts.length = 0;
|
|
1028
|
+
};
|
|
1029
|
+
for (const part of parts) {
|
|
1030
|
+
const type = textValue(part.type) || "text";
|
|
1031
|
+
if (type === "text") {
|
|
1032
|
+
const text = textValue(part.text);
|
|
1033
|
+
if (text) {
|
|
1034
|
+
messageParts.push({
|
|
1035
|
+
text,
|
|
1036
|
+
type: role === "assistant" ? "output_text" : "input_text"
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (type === "image") {
|
|
1042
|
+
if (role !== "user") {
|
|
1043
|
+
throw new AnthropicCompatibilityError(
|
|
1044
|
+
"Anthropic image content is only supported for user messages."
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
messageParts.push(anthropicImageToResponsesPart(part));
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (type === "tool_use") {
|
|
1051
|
+
flushMessage();
|
|
1052
|
+
input.push({
|
|
1053
|
+
arguments: JSON.stringify(asRecord(part.input)),
|
|
1054
|
+
call_id: textValue(part.id) || `call_${randomId2()}`,
|
|
1055
|
+
name: textValue(part.name),
|
|
1056
|
+
type: "function_call"
|
|
1057
|
+
});
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (type === "tool_result") {
|
|
1061
|
+
flushMessage();
|
|
1062
|
+
input.push({
|
|
1063
|
+
call_id: textValue(part.tool_use_id),
|
|
1064
|
+
output: anthropicToolResultOutput(part.content),
|
|
1065
|
+
type: "function_call_output"
|
|
1066
|
+
});
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
if (type === "thinking" || type === "redacted_thinking") {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
throw new AnthropicCompatibilityError(
|
|
1073
|
+
`Anthropic content block type "${type}" is not supported.`
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
flushMessage();
|
|
1077
|
+
}
|
|
1078
|
+
return input;
|
|
1079
|
+
}
|
|
1080
|
+
function anthropicRole(value) {
|
|
1081
|
+
const role = textValue(value);
|
|
1082
|
+
if (role === "assistant" || role === "user") {
|
|
1083
|
+
return role;
|
|
1084
|
+
}
|
|
1085
|
+
if (!role) {
|
|
1086
|
+
return "user";
|
|
1087
|
+
}
|
|
1088
|
+
throw new AnthropicCompatibilityError(`Anthropic message role "${role}" is not supported.`);
|
|
1089
|
+
}
|
|
1090
|
+
function anthropicContentParts(content) {
|
|
1091
|
+
if (typeof content === "string") {
|
|
1092
|
+
return [{ text: content, type: "text" }];
|
|
1093
|
+
}
|
|
1094
|
+
if (Array.isArray(content)) {
|
|
1095
|
+
return content.map(
|
|
1096
|
+
(part) => typeof part === "string" ? { text: part, type: "text" } : asRecord(part)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
if (content === void 0 || content === null) {
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
1102
|
+
return [asRecord(content)];
|
|
1103
|
+
}
|
|
1104
|
+
function anthropicImageToResponsesPart(part) {
|
|
1105
|
+
const source = asRecord(part.source);
|
|
1106
|
+
const sourceType = textValue(source.type);
|
|
1107
|
+
if (sourceType === "base64") {
|
|
1108
|
+
const mediaType = textValue(source.media_type) || "image/png";
|
|
1109
|
+
const data = textValue(source.data);
|
|
1110
|
+
if (!data) {
|
|
1111
|
+
throw new AnthropicCompatibilityError("Anthropic base64 image content requires source.data.");
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
detail: "auto",
|
|
1115
|
+
image_url: `data:${mediaType};base64,${data}`,
|
|
1116
|
+
type: "input_image"
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (sourceType === "url") {
|
|
1120
|
+
const url = textValue(source.url);
|
|
1121
|
+
if (!url) {
|
|
1122
|
+
throw new AnthropicCompatibilityError("Anthropic URL image content requires source.url.");
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
detail: "auto",
|
|
1126
|
+
image_url: url,
|
|
1127
|
+
type: "input_image"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
throw new AnthropicCompatibilityError(
|
|
1131
|
+
`Anthropic image source type "${sourceType || "unknown"}" is not supported.`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
function anthropicToolResultOutput(content) {
|
|
1135
|
+
if (typeof content === "string") {
|
|
1136
|
+
return content;
|
|
1137
|
+
}
|
|
1138
|
+
if (Array.isArray(content)) {
|
|
1139
|
+
return content.map((part) => {
|
|
1140
|
+
const record = asRecord(part);
|
|
1141
|
+
return textValue(record.text) || textValue(record.content) || JSON.stringify(part);
|
|
1142
|
+
}).filter(Boolean).join("\n");
|
|
1143
|
+
}
|
|
1144
|
+
if (content === void 0 || content === null) {
|
|
1145
|
+
return "";
|
|
1146
|
+
}
|
|
1147
|
+
return typeof content === "object" ? JSON.stringify(content) : String(content);
|
|
1148
|
+
}
|
|
1149
|
+
function anthropicSystemToInstructions(system) {
|
|
1150
|
+
if (typeof system === "string") {
|
|
1151
|
+
return system || void 0;
|
|
1152
|
+
}
|
|
1153
|
+
if (!Array.isArray(system)) {
|
|
1154
|
+
return void 0;
|
|
1155
|
+
}
|
|
1156
|
+
const text = system.map((part) => textValue(asRecord(part).text) || textValue(part)).filter(Boolean).join("\n");
|
|
1157
|
+
return text || void 0;
|
|
1158
|
+
}
|
|
1159
|
+
function anthropicTools(tools) {
|
|
1160
|
+
if (!Array.isArray(tools)) {
|
|
1161
|
+
return void 0;
|
|
1162
|
+
}
|
|
1163
|
+
const converted = tools.map((tool) => {
|
|
1164
|
+
const record = asRecord(tool);
|
|
1165
|
+
return removeUndefined2({
|
|
1166
|
+
description: record.description,
|
|
1167
|
+
name: record.name,
|
|
1168
|
+
parameters: record.input_schema,
|
|
1169
|
+
strict: record.strict,
|
|
1170
|
+
type: "function"
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
return converted.length > 0 ? converted : void 0;
|
|
1174
|
+
}
|
|
1175
|
+
function anthropicToolChoice(toolChoice) {
|
|
1176
|
+
if (toolChoice === void 0 || toolChoice === null) {
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
1179
|
+
const record = asRecord(toolChoice);
|
|
1180
|
+
const type = textValue(record.type);
|
|
1181
|
+
if (type === "auto") {
|
|
1182
|
+
return "auto";
|
|
1183
|
+
}
|
|
1184
|
+
if (type === "any") {
|
|
1185
|
+
return "required";
|
|
1186
|
+
}
|
|
1187
|
+
if (type === "none") {
|
|
1188
|
+
return "none";
|
|
1189
|
+
}
|
|
1190
|
+
if (type === "tool") {
|
|
1191
|
+
return { name: textValue(record.name), type: "function" };
|
|
1192
|
+
}
|
|
1193
|
+
throw new AnthropicCompatibilityError(
|
|
1194
|
+
`Anthropic tool_choice type "${type || "unknown"}" is not supported.`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
function anthropicThinkingToReasoning(thinking) {
|
|
1198
|
+
const record = asRecord(thinking);
|
|
1199
|
+
if (Object.keys(record).length === 0) {
|
|
1200
|
+
return void 0;
|
|
1201
|
+
}
|
|
1202
|
+
const type = textValue(record.type);
|
|
1203
|
+
if (type && type !== "enabled") {
|
|
1204
|
+
return void 0;
|
|
1205
|
+
}
|
|
1206
|
+
const budget = typeof record.budget_tokens === "number" ? record.budget_tokens : 0;
|
|
1207
|
+
return {
|
|
1208
|
+
effort: budget >= 16e3 ? "high" : budget >= 4e3 ? "medium" : "low"
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function anthropicStopSequences(stopSequences) {
|
|
1212
|
+
if (!Array.isArray(stopSequences) || stopSequences.length === 0) {
|
|
1213
|
+
return void 0;
|
|
1214
|
+
}
|
|
1215
|
+
return stopSequences.map((sequence) => textValue(sequence)).filter(Boolean);
|
|
1216
|
+
}
|
|
1217
|
+
function anthropicContentFromResponsesOutput(response) {
|
|
1218
|
+
const content = [];
|
|
1219
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
1220
|
+
for (const item of output) {
|
|
1221
|
+
const record = asRecord(item);
|
|
1222
|
+
const type = textValue(record.type);
|
|
1223
|
+
if (type === "message") {
|
|
1224
|
+
const parts = Array.isArray(record.content) ? record.content : [];
|
|
1225
|
+
for (const part of parts) {
|
|
1226
|
+
const partRecord = asRecord(part);
|
|
1227
|
+
const text = textValue(partRecord.text) || textValue(partRecord.output_text);
|
|
1228
|
+
if (text) {
|
|
1229
|
+
content.push({ text, type: "text" });
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (type === "function_call") {
|
|
1235
|
+
content.push({
|
|
1236
|
+
id: textValue(record.call_id) || textValue(record.id) || `call_${randomId2()}`,
|
|
1237
|
+
input: parseToolInput(textValue(record.arguments)),
|
|
1238
|
+
name: textValue(record.name),
|
|
1239
|
+
type: "tool_use"
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (content.length === 0) {
|
|
1244
|
+
const outputText = textValue(response.output_text);
|
|
1245
|
+
if (outputText) {
|
|
1246
|
+
content.push({ text: outputText, type: "text" });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return content;
|
|
1250
|
+
}
|
|
1251
|
+
function anthropicStopReason(response, content) {
|
|
1252
|
+
if (content.some((part) => part.type === "tool_use")) {
|
|
1253
|
+
return "tool_use";
|
|
1254
|
+
}
|
|
1255
|
+
const incompleteReason = textValue(asRecord(response.incomplete_details).reason);
|
|
1256
|
+
if (textValue(response.status) === "incomplete" || incompleteReason === "max_output_tokens") {
|
|
1257
|
+
return "max_tokens";
|
|
1258
|
+
}
|
|
1259
|
+
return "end_turn";
|
|
1260
|
+
}
|
|
1261
|
+
function anthropicUsage(usage) {
|
|
1262
|
+
const record = asRecord(usage);
|
|
1263
|
+
const inputTokens = firstNumber2(record.input_tokens, record.prompt_tokens) ?? 0;
|
|
1264
|
+
const outputTokens = firstNumber2(record.output_tokens, record.completion_tokens) ?? 0;
|
|
1265
|
+
const details = asRecord(record.input_tokens_details);
|
|
1266
|
+
return removeUndefined2({
|
|
1267
|
+
cache_creation_input_tokens: firstNumber2(record.cache_creation_input_tokens),
|
|
1268
|
+
cache_read_input_tokens: firstNumber2(record.cache_read_input_tokens, details.cached_tokens) ?? void 0,
|
|
1269
|
+
input_tokens: inputTokens,
|
|
1270
|
+
output_tokens: outputTokens
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
function processResponsesSseBlock(block, state, enqueue) {
|
|
1274
|
+
const { data, event } = parseSseBlock(block);
|
|
1275
|
+
if (!data || data === "[DONE]") {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const parsed = parseJsonObject(data);
|
|
1279
|
+
if (!parsed) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const type = textValue(parsed.type) || event;
|
|
1283
|
+
if (type === "response.created") {
|
|
1284
|
+
const response = asRecord(parsed.response);
|
|
1285
|
+
state.messageId = textValue(response.id) || state.messageId;
|
|
1286
|
+
state.model = textValue(response.model) || state.model;
|
|
1287
|
+
startAnthropicMessage(state, enqueue);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if (type === "response.output_item.added") {
|
|
1291
|
+
const item = asRecord(parsed.item);
|
|
1292
|
+
if (textValue(item.type) === "function_call") {
|
|
1293
|
+
ensureToolBlock(state, parsed, item, enqueue);
|
|
1294
|
+
}
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (type === "response.output_text.delta") {
|
|
1298
|
+
const blockState = ensureTextBlock(state, parsed, enqueue);
|
|
1299
|
+
const delta = textValue(parsed.delta);
|
|
1300
|
+
if (delta) {
|
|
1301
|
+
blockState.sentText += delta;
|
|
1302
|
+
enqueue("content_block_delta", {
|
|
1303
|
+
delta: { text: delta, type: "text_delta" },
|
|
1304
|
+
index: blockState.index,
|
|
1305
|
+
type: "content_block_delta"
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (type === "response.output_text.done" || type === "response.content_part.done") {
|
|
1311
|
+
const blockState = ensureTextBlock(state, parsed, enqueue);
|
|
1312
|
+
const text = textValue(parsed.text) || textValue(asRecord(parsed.part).text);
|
|
1313
|
+
if (text && !blockState.sentText) {
|
|
1314
|
+
blockState.sentText = text;
|
|
1315
|
+
enqueue("content_block_delta", {
|
|
1316
|
+
delta: { text, type: "text_delta" },
|
|
1317
|
+
index: blockState.index,
|
|
1318
|
+
type: "content_block_delta"
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
stopBlock(blockState, enqueue);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (type === "response.function_call_arguments.delta") {
|
|
1325
|
+
const blockState = ensureToolBlock(state, parsed, {}, enqueue);
|
|
1326
|
+
const delta = textValue(parsed.delta);
|
|
1327
|
+
if (delta) {
|
|
1328
|
+
blockState.sentText += delta;
|
|
1329
|
+
enqueue("content_block_delta", {
|
|
1330
|
+
delta: { partial_json: delta, type: "input_json_delta" },
|
|
1331
|
+
index: blockState.index,
|
|
1332
|
+
type: "content_block_delta"
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (type === "response.function_call_arguments.done") {
|
|
1338
|
+
const blockState = ensureToolBlock(state, parsed, {}, enqueue);
|
|
1339
|
+
const args = textValue(parsed.arguments);
|
|
1340
|
+
if (args && !blockState.sentText) {
|
|
1341
|
+
blockState.sentText = args;
|
|
1342
|
+
enqueue("content_block_delta", {
|
|
1343
|
+
delta: { partial_json: args, type: "input_json_delta" },
|
|
1344
|
+
index: blockState.index,
|
|
1345
|
+
type: "content_block_delta"
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
stopBlock(blockState, enqueue);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (type === "response.output_item.done") {
|
|
1352
|
+
const item = asRecord(parsed.item);
|
|
1353
|
+
if (textValue(item.type) === "function_call") {
|
|
1354
|
+
const blockState = ensureToolBlock(state, parsed, item, enqueue);
|
|
1355
|
+
const args = textValue(item.arguments);
|
|
1356
|
+
if (args && !blockState.sentText) {
|
|
1357
|
+
blockState.sentText = args;
|
|
1358
|
+
enqueue("content_block_delta", {
|
|
1359
|
+
delta: { partial_json: args, type: "input_json_delta" },
|
|
1360
|
+
index: blockState.index,
|
|
1361
|
+
type: "content_block_delta"
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
stopBlock(blockState, enqueue);
|
|
1365
|
+
}
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (type === "response.completed") {
|
|
1369
|
+
const response = asRecord(parsed.response);
|
|
1370
|
+
state.model = textValue(response.model) || state.model;
|
|
1371
|
+
state.usage = anthropicUsage(response.usage);
|
|
1372
|
+
finishAnthropicStream(state, enqueue);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (type === "response.failed" || event === "error") {
|
|
1376
|
+
const error = asRecord(asRecord(parsed.response).error);
|
|
1377
|
+
enqueue("error", {
|
|
1378
|
+
error: {
|
|
1379
|
+
message: textValue(error.message) || textValue(parsed.message) || "Upstream stream failed.",
|
|
1380
|
+
type: textValue(error.type) || "api_error"
|
|
1381
|
+
},
|
|
1382
|
+
type: "error"
|
|
1383
|
+
});
|
|
1384
|
+
state.completed = true;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function startAnthropicMessage(state, enqueue) {
|
|
1388
|
+
if (state.started) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
state.started = true;
|
|
1392
|
+
enqueue("message_start", {
|
|
1393
|
+
message: {
|
|
1394
|
+
content: [],
|
|
1395
|
+
id: state.messageId,
|
|
1396
|
+
model: state.model,
|
|
1397
|
+
role: "assistant",
|
|
1398
|
+
stop_reason: null,
|
|
1399
|
+
stop_sequence: null,
|
|
1400
|
+
type: "message",
|
|
1401
|
+
usage: anthropicUsage(void 0)
|
|
1402
|
+
},
|
|
1403
|
+
type: "message_start"
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
function finishAnthropicStream(state, enqueue) {
|
|
1407
|
+
if (state.completed) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
startAnthropicMessage(state, enqueue);
|
|
1411
|
+
for (const block of [...state.blocks.values()].sort((left, right) => left.index - right.index)) {
|
|
1412
|
+
stopBlock(block, enqueue);
|
|
1413
|
+
}
|
|
1414
|
+
enqueue("message_delta", {
|
|
1415
|
+
delta: {
|
|
1416
|
+
stop_reason: state.sawToolUse ? "tool_use" : "end_turn",
|
|
1417
|
+
stop_sequence: null
|
|
1418
|
+
},
|
|
1419
|
+
type: "message_delta",
|
|
1420
|
+
usage: state.usage
|
|
1421
|
+
});
|
|
1422
|
+
enqueue("message_stop", { type: "message_stop" });
|
|
1423
|
+
state.completed = true;
|
|
1424
|
+
}
|
|
1425
|
+
function ensureTextBlock(state, payload, enqueue) {
|
|
1426
|
+
startAnthropicMessage(state, enqueue);
|
|
1427
|
+
const key = `text:${indexValue(payload.output_index)}:${indexValue(payload.content_index)}`;
|
|
1428
|
+
let block = state.blocks.get(key);
|
|
1429
|
+
if (!block) {
|
|
1430
|
+
block = { index: state.nextBlockIndex++, sentText: "", stopped: false, type: "text" };
|
|
1431
|
+
state.blocks.set(key, block);
|
|
1432
|
+
enqueue("content_block_start", {
|
|
1433
|
+
content_block: { text: "", type: "text" },
|
|
1434
|
+
index: block.index,
|
|
1435
|
+
type: "content_block_start"
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
return block;
|
|
1439
|
+
}
|
|
1440
|
+
function ensureToolBlock(state, payload, item, enqueue) {
|
|
1441
|
+
startAnthropicMessage(state, enqueue);
|
|
1442
|
+
state.sawToolUse = true;
|
|
1443
|
+
const key = `tool:${indexValue(payload.output_index)}`;
|
|
1444
|
+
let block = state.blocks.get(key);
|
|
1445
|
+
if (!block) {
|
|
1446
|
+
block = { index: state.nextBlockIndex++, sentText: "", stopped: false, type: "tool_use" };
|
|
1447
|
+
state.blocks.set(key, block);
|
|
1448
|
+
enqueue("content_block_start", {
|
|
1449
|
+
content_block: {
|
|
1450
|
+
id: textValue(item.call_id) || textValue(item.id) || `call_${randomId2()}`,
|
|
1451
|
+
input: {},
|
|
1452
|
+
name: textValue(item.name),
|
|
1453
|
+
type: "tool_use"
|
|
1454
|
+
},
|
|
1455
|
+
index: block.index,
|
|
1456
|
+
type: "content_block_start"
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
return block;
|
|
1460
|
+
}
|
|
1461
|
+
function stopBlock(block, enqueue) {
|
|
1462
|
+
if (block.stopped) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
block.stopped = true;
|
|
1466
|
+
enqueue("content_block_stop", {
|
|
1467
|
+
index: block.index,
|
|
1468
|
+
type: "content_block_stop"
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
function parseSseBlock(block) {
|
|
1472
|
+
let event = "message";
|
|
1473
|
+
const data = [];
|
|
1474
|
+
for (const line of block.split(/\r?\n/)) {
|
|
1475
|
+
const trimmed = line.trim();
|
|
1476
|
+
if (trimmed.startsWith("event:")) {
|
|
1477
|
+
event = trimmed.slice("event:".length).trim() || event;
|
|
1478
|
+
} else if (trimmed.startsWith("data:")) {
|
|
1479
|
+
data.push(trimmed.slice("data:".length).trim());
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return { data: data.join("\n"), event };
|
|
1483
|
+
}
|
|
1484
|
+
function parseJsonObject(text) {
|
|
1485
|
+
try {
|
|
1486
|
+
return asRecord(JSON.parse(text));
|
|
1487
|
+
} catch {
|
|
1488
|
+
return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function parseToolInput(argumentsText) {
|
|
1492
|
+
const parsed = parseJsonObject(argumentsText);
|
|
1493
|
+
return parsed ?? {};
|
|
1494
|
+
}
|
|
1495
|
+
function estimatedTextSize(value) {
|
|
1496
|
+
if (value === void 0 || value === null) {
|
|
1497
|
+
return 0;
|
|
1498
|
+
}
|
|
1499
|
+
if (typeof value === "string") {
|
|
1500
|
+
return value.length;
|
|
1501
|
+
}
|
|
1502
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1503
|
+
return String(value).length;
|
|
1504
|
+
}
|
|
1505
|
+
if (Array.isArray(value)) {
|
|
1506
|
+
return value.reduce((sum, item) => sum + estimatedTextSize(item), 0);
|
|
1507
|
+
}
|
|
1508
|
+
if (typeof value === "object") {
|
|
1509
|
+
return Object.values(value).reduce((sum, item) => sum + estimatedTextSize(item), 0);
|
|
1510
|
+
}
|
|
1511
|
+
return 0;
|
|
1512
|
+
}
|
|
1513
|
+
function textValue(value) {
|
|
1514
|
+
if (typeof value === "string") {
|
|
1515
|
+
return value;
|
|
1516
|
+
}
|
|
1517
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1518
|
+
return String(value);
|
|
1519
|
+
}
|
|
1520
|
+
return "";
|
|
1521
|
+
}
|
|
1522
|
+
function firstNumber2(...values) {
|
|
1523
|
+
for (const value of values) {
|
|
1524
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1525
|
+
return value;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return void 0;
|
|
1529
|
+
}
|
|
1530
|
+
function indexValue(value) {
|
|
1531
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1532
|
+
}
|
|
1533
|
+
function removeUndefined2(record) {
|
|
1534
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== void 0));
|
|
1535
|
+
}
|
|
1536
|
+
function encodeSse(event, data) {
|
|
1537
|
+
return `event: ${event}
|
|
1538
|
+
data: ${JSON.stringify(data)}
|
|
1539
|
+
|
|
1540
|
+
`;
|
|
1541
|
+
}
|
|
1542
|
+
function randomId2() {
|
|
1543
|
+
return crypto.randomUUID().replaceAll("-", "");
|
|
1544
|
+
}
|
|
1545
|
+
|
|
900
1546
|
// src/metrics.ts
|
|
901
1547
|
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
902
1548
|
var DURATION_BUCKETS_SECONDS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
|
|
@@ -1324,6 +1970,7 @@ var DEFAULT_HOST = "127.0.0.1";
|
|
|
1324
1970
|
var DEFAULT_PORT = 4141;
|
|
1325
1971
|
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "Browser-origin requests require HOOPILOT_API_KEY unless the Origin is loopback.";
|
|
1326
1972
|
var INVALID_JSON_MESSAGE = "Request body must be valid JSON.";
|
|
1973
|
+
var JSON_OBJECT_MESSAGE = "Request body must be a JSON object.";
|
|
1327
1974
|
var MAX_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
|
|
1328
1975
|
var REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1329
1976
|
var REQUEST_TOO_LARGE_MESSAGE = `Request body must be ${MAX_REQUEST_BODY_BYTES} bytes or smaller.`;
|
|
@@ -1393,6 +2040,14 @@ function createHoopilotHandler(options = {}) {
|
|
|
1393
2040
|
if (request.method === "GET" && apiPath === "/v1/models") {
|
|
1394
2041
|
return finish(await handleModels(client, metrics, request.signal, requestLogger));
|
|
1395
2042
|
}
|
|
2043
|
+
if (request.method === "POST" && apiPath === "/v1/messages") {
|
|
2044
|
+
return finish(
|
|
2045
|
+
await handleAnthropicMessages(client, metrics, recordTokens, request, requestLogger)
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
if (request.method === "POST" && apiPath === "/v1/messages/count_tokens") {
|
|
2049
|
+
return finish(handleAnthropicCountTokens(await readJson(request)));
|
|
2050
|
+
}
|
|
1396
2051
|
if (request.method === "POST" && apiPath === "/v1/chat/completions") {
|
|
1397
2052
|
return finish(
|
|
1398
2053
|
await handleChatCompletions(client, metrics, recordTokens, request, requestLogger)
|
|
@@ -1416,16 +2071,16 @@ function createHoopilotHandler(options = {}) {
|
|
|
1416
2071
|
return finish(jsonError(401, "copilot_auth_error", error.message));
|
|
1417
2072
|
}
|
|
1418
2073
|
const message = errorMessage(error);
|
|
1419
|
-
if (message === INVALID_JSON_MESSAGE) {
|
|
2074
|
+
if (message === INVALID_JSON_MESSAGE || message === JSON_OBJECT_MESSAGE) {
|
|
1420
2075
|
requestLogger.warn(
|
|
1421
2076
|
{ err: errorDetails(error), event: "http.request.failed" },
|
|
1422
|
-
"request body was
|
|
2077
|
+
"request body was not usable json"
|
|
1423
2078
|
);
|
|
1424
2079
|
return finish(jsonError(400, "invalid_request_error", message));
|
|
1425
|
-
} else if (error instanceof OpenAICompatibilityError) {
|
|
2080
|
+
} else if (error instanceof OpenAICompatibilityError || error instanceof AnthropicCompatibilityError) {
|
|
1426
2081
|
requestLogger.warn(
|
|
1427
2082
|
{ err: errorDetails(error), event: "http.request.failed" },
|
|
1428
|
-
"request body used unsupported
|
|
2083
|
+
"request body used unsupported compatibility fields"
|
|
1429
2084
|
);
|
|
1430
2085
|
return finish(jsonError(400, "invalid_request_error", message));
|
|
1431
2086
|
} else if (error instanceof RequestBodyTooLargeError) {
|
|
@@ -1469,6 +2124,40 @@ function startHoopilotServer(options = {}) {
|
|
|
1469
2124
|
url: `http://${urlHost(host)}:${server.port}`
|
|
1470
2125
|
};
|
|
1471
2126
|
}
|
|
2127
|
+
async function handleAnthropicMessages(client, metrics, recordTokens, request, logger) {
|
|
2128
|
+
const anthropicRequest = await readJson(request);
|
|
2129
|
+
const responsesRequest = anthropicMessagesToResponsesRequest(anthropicRequest);
|
|
2130
|
+
const upstream = await client.responses(JSON.stringify(responsesRequest), request.signal);
|
|
2131
|
+
metrics.recordUpstream("/responses", upstream.ok);
|
|
2132
|
+
if (!upstream.ok) {
|
|
2133
|
+
return proxyError(upstream, logger);
|
|
2134
|
+
}
|
|
2135
|
+
logUpstreamSuccess(logger, "/responses", upstream.status);
|
|
2136
|
+
const model = normalizeRequestedModel(responsesRequest.model);
|
|
2137
|
+
if (isStreamingResponse(upstream) && upstream.body) {
|
|
2138
|
+
const observed = observeResponseUsage(upstream, model, recordTokens, request.signal);
|
|
2139
|
+
if (!observed.body) {
|
|
2140
|
+
return proxyResponse(observed);
|
|
2141
|
+
}
|
|
2142
|
+
return proxyResponse(
|
|
2143
|
+
new Response(responsesStreamToAnthropicStream(observed.body, { model }), {
|
|
2144
|
+
headers: observed.headers,
|
|
2145
|
+
status: observed.status,
|
|
2146
|
+
statusText: observed.statusText
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
const body = asRecord(await upstream.json());
|
|
2151
|
+
const usage = extractTokenUsage(body.usage);
|
|
2152
|
+
if (usage) {
|
|
2153
|
+
const responseModel = typeof body.model === "string" ? body.model.trim() : "";
|
|
2154
|
+
recordTokens(responseModel || model, usage);
|
|
2155
|
+
}
|
|
2156
|
+
return jsonResponse(responsesResponseToAnthropicMessage(body, model));
|
|
2157
|
+
}
|
|
2158
|
+
function handleAnthropicCountTokens(body) {
|
|
2159
|
+
return jsonResponse(estimateAnthropicMessageTokens(body));
|
|
2160
|
+
}
|
|
1472
2161
|
async function handleModels(client, metrics, signal, logger) {
|
|
1473
2162
|
const upstream = await client.models(signal);
|
|
1474
2163
|
metrics.recordUpstream("/models", upstream.ok);
|
|
@@ -1576,20 +2265,24 @@ function proxyResponse(upstream) {
|
|
|
1576
2265
|
}
|
|
1577
2266
|
async function readJson(request) {
|
|
1578
2267
|
const text = await readRequestText(request);
|
|
2268
|
+
return parseJsonObject2(text);
|
|
2269
|
+
}
|
|
2270
|
+
function parseJsonObject2(text) {
|
|
2271
|
+
let parsed;
|
|
1579
2272
|
try {
|
|
1580
|
-
|
|
2273
|
+
parsed = JSON.parse(text);
|
|
1581
2274
|
} catch {
|
|
1582
2275
|
throw new Error(INVALID_JSON_MESSAGE);
|
|
1583
2276
|
}
|
|
2277
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2278
|
+
throw new Error(JSON_OBJECT_MESSAGE);
|
|
2279
|
+
}
|
|
2280
|
+
return parsed;
|
|
1584
2281
|
}
|
|
1585
2282
|
async function readJsonText(request) {
|
|
1586
2283
|
const text = await readRequestText(request);
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
return text;
|
|
1590
|
-
} catch {
|
|
1591
|
-
throw new Error(INVALID_JSON_MESSAGE);
|
|
1592
|
-
}
|
|
2284
|
+
parseJsonObject2(text);
|
|
2285
|
+
return text;
|
|
1593
2286
|
}
|
|
1594
2287
|
async function readRequestText(request) {
|
|
1595
2288
|
const contentLength = request.headers.get("content-length");
|
|
@@ -1664,9 +2357,10 @@ function websocketUnsupportedResponse() {
|
|
|
1664
2357
|
}
|
|
1665
2358
|
function corsHeaders() {
|
|
1666
2359
|
return {
|
|
1667
|
-
"access-control-allow-headers": "authorization, content-type, x-api-key",
|
|
2360
|
+
"access-control-allow-headers": "anthropic-beta, anthropic-dangerous-direct-browser-access, anthropic-version, authorization, content-type, x-api-key, x-request-id",
|
|
1668
2361
|
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
1669
|
-
"access-control-allow-origin": "*"
|
|
2362
|
+
"access-control-allow-origin": "*",
|
|
2363
|
+
"access-control-expose-headers": "x-request-id"
|
|
1670
2364
|
};
|
|
1671
2365
|
}
|
|
1672
2366
|
function isAuthorized(request, apiKey) {
|
|
@@ -1818,6 +2512,10 @@ function canonicalApiPath(path) {
|
|
|
1818
2512
|
return "/v1/chat/completions";
|
|
1819
2513
|
case "/completions":
|
|
1820
2514
|
return "/v1/completions";
|
|
2515
|
+
case "/messages":
|
|
2516
|
+
return "/v1/messages";
|
|
2517
|
+
case "/messages/count_tokens":
|
|
2518
|
+
return "/v1/messages/count_tokens";
|
|
1821
2519
|
case "/responses":
|
|
1822
2520
|
return "/v1/responses";
|
|
1823
2521
|
case "/usage":
|
|
@@ -1842,6 +2540,12 @@ function routeFor(method, path) {
|
|
|
1842
2540
|
if (method === "GET" && path === "/v1/models") {
|
|
1843
2541
|
return "models";
|
|
1844
2542
|
}
|
|
2543
|
+
if (method === "POST" && path === "/v1/messages") {
|
|
2544
|
+
return "anthropic_messages";
|
|
2545
|
+
}
|
|
2546
|
+
if (method === "POST" && path === "/v1/messages/count_tokens") {
|
|
2547
|
+
return "anthropic_count_tokens";
|
|
2548
|
+
}
|
|
1845
2549
|
if (method === "POST" && path === "/v1/chat/completions") {
|
|
1846
2550
|
return "chat_completions";
|
|
1847
2551
|
}
|
|
@@ -2514,8 +3218,7 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2514
3218
|
const command = argv[0];
|
|
2515
3219
|
if (command === "update" || command === "upgrade") {
|
|
2516
3220
|
const args2 = withRuntimeEnv(parseArgs(argv.slice(1)));
|
|
2517
|
-
if (args2
|
|
2518
|
-
console.log(helpText(await getVersion()));
|
|
3221
|
+
if (await printMetaOption(args2)) {
|
|
2519
3222
|
return;
|
|
2520
3223
|
}
|
|
2521
3224
|
const logger2 = commandLogger(args2, command);
|
|
@@ -2528,8 +3231,7 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2528
3231
|
}
|
|
2529
3232
|
if (command === "login") {
|
|
2530
3233
|
const args2 = withRuntimeEnv(parseArgs(argv.slice(1)));
|
|
2531
|
-
if (args2
|
|
2532
|
-
console.log(helpText(await getVersion()));
|
|
3234
|
+
if (await printMetaOption(args2)) {
|
|
2533
3235
|
return;
|
|
2534
3236
|
}
|
|
2535
3237
|
args2.logger = commandLogger(args2, "login");
|
|
@@ -2538,8 +3240,7 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2538
3240
|
}
|
|
2539
3241
|
if (command === "models") {
|
|
2540
3242
|
const args2 = withRuntimeEnv(parseArgs(argv.slice(1)));
|
|
2541
|
-
if (args2
|
|
2542
|
-
console.log(helpText(await getVersion()));
|
|
3243
|
+
if (await printMetaOption(args2)) {
|
|
2543
3244
|
return;
|
|
2544
3245
|
}
|
|
2545
3246
|
args2.logger = commandLogger(args2, "models");
|
|
@@ -2548,8 +3249,7 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2548
3249
|
}
|
|
2549
3250
|
if (command === "usage") {
|
|
2550
3251
|
const args2 = withRuntimeEnv(parseArgs(argv.slice(1)));
|
|
2551
|
-
if (args2
|
|
2552
|
-
console.log(helpText(await getVersion()));
|
|
3252
|
+
if (await printMetaOption(args2)) {
|
|
2553
3253
|
return;
|
|
2554
3254
|
}
|
|
2555
3255
|
args2.logger = commandLogger(args2, "usage");
|
|
@@ -2557,12 +3257,7 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2557
3257
|
return;
|
|
2558
3258
|
}
|
|
2559
3259
|
const args = withRuntimeEnv(parseArgs(argv));
|
|
2560
|
-
if (args
|
|
2561
|
-
console.log(helpText(await getVersion()));
|
|
2562
|
-
return;
|
|
2563
|
-
}
|
|
2564
|
-
if (args.version) {
|
|
2565
|
-
console.log(await getVersion());
|
|
3260
|
+
if (await printMetaOption(args)) {
|
|
2566
3261
|
return;
|
|
2567
3262
|
}
|
|
2568
3263
|
const logger = commandLogger(args, "serve");
|
|
@@ -2584,6 +3279,17 @@ async function main2(argv = Bun.argv.slice(2)) {
|
|
|
2584
3279
|
);
|
|
2585
3280
|
}
|
|
2586
3281
|
}
|
|
3282
|
+
async function printMetaOption(args) {
|
|
3283
|
+
if (args.help) {
|
|
3284
|
+
console.log(helpText(await getVersion()));
|
|
3285
|
+
return true;
|
|
3286
|
+
}
|
|
3287
|
+
if (args.version) {
|
|
3288
|
+
console.log(await getVersion());
|
|
3289
|
+
return true;
|
|
3290
|
+
}
|
|
3291
|
+
return false;
|
|
3292
|
+
}
|
|
2587
3293
|
function parseArgs(argv) {
|
|
2588
3294
|
const args = {};
|
|
2589
3295
|
const rest = [...argv];
|