@openhoo/hoopilot 2.1.1 → 2.1.3
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 +377 -60
- package/dist/cli.js.map +1 -1
- package/dist/index.js +344 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -641,6 +641,16 @@ import { Elysia } from "elysia";
|
|
|
641
641
|
|
|
642
642
|
// src/openai.ts
|
|
643
643
|
var DEFAULT_MODEL = "gpt-4.1";
|
|
644
|
+
var COMPACTION_SUMMARIZATION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
|
|
645
|
+
|
|
646
|
+
Include:
|
|
647
|
+
- Current progress and key decisions made
|
|
648
|
+
- Important context, constraints, or user preferences
|
|
649
|
+
- What remains to be done (clear next steps)
|
|
650
|
+
- Any critical data, examples, or references needed to continue
|
|
651
|
+
|
|
652
|
+
Be concise, structured, and focused on helping the next LLM seamlessly continue the work.`;
|
|
653
|
+
var COMPACTION_SUMMARY_PREFIX = "Another language model started to solve this problem and produced a summary of its thinking process. You also have access to the state of the tools that were used by that language model. Use this to build on the work that has already been done and avoid duplicating work. Here is the summary produced by the other language model, use the information in this summary to assist with your own analysis:";
|
|
644
654
|
var OpenAICompatibilityError = class extends Error {
|
|
645
655
|
constructor(message) {
|
|
646
656
|
super(message);
|
|
@@ -677,19 +687,111 @@ function normalizeRequestedModel(model) {
|
|
|
677
687
|
return requested || DEFAULT_MODEL;
|
|
678
688
|
}
|
|
679
689
|
function responsesCompactionResult(upstreamText, isSse) {
|
|
680
|
-
const
|
|
681
|
-
return { output };
|
|
690
|
+
const summary = compactionSummaryText(upstreamText, isSse);
|
|
691
|
+
return { output: [compactionSummaryOutputMessageItem(summary)] };
|
|
692
|
+
}
|
|
693
|
+
function isResponsesCompactionRequest(request) {
|
|
694
|
+
return responseInputItems(request.input).some(
|
|
695
|
+
(item) => contentToText(asRecord(item).type) === "compaction_trigger"
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
function responsesCompactionRequestBody(request) {
|
|
699
|
+
return JSON.stringify(
|
|
700
|
+
removeUndefined({
|
|
701
|
+
...request,
|
|
702
|
+
input: [
|
|
703
|
+
...compactionInputItemsForCopilot(request.input),
|
|
704
|
+
{
|
|
705
|
+
content: [{ text: COMPACTION_SUMMARIZATION_PROMPT, type: "input_text" }],
|
|
706
|
+
role: "user",
|
|
707
|
+
type: "message"
|
|
708
|
+
}
|
|
709
|
+
],
|
|
710
|
+
parallel_tool_calls: false,
|
|
711
|
+
stream: false,
|
|
712
|
+
tool_choice: "none",
|
|
713
|
+
tools: []
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function normalizeResponsesRequestForCopilotBody(request) {
|
|
718
|
+
return JSON.stringify(
|
|
719
|
+
removeUndefined({
|
|
720
|
+
...request,
|
|
721
|
+
input: normalizeCompactionInputForCopilot(request.input, { dropTrigger: false })
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
function responsesRequestNeedsCopilotNormalization(request) {
|
|
726
|
+
return responseInputItems(request.input).some((item) => {
|
|
727
|
+
const type = contentToText(asRecord(item).type);
|
|
728
|
+
return type === "compaction" || type === "compaction_summary" || type === "context_compaction";
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function responsesCompactionResponse(upstreamText, isSse, model) {
|
|
732
|
+
const output = [compactionOutputItem(compactionSummaryText(upstreamText, isSse))];
|
|
733
|
+
return removeUndefined({
|
|
734
|
+
created_at: epochSeconds(),
|
|
735
|
+
error: null,
|
|
736
|
+
id: `resp_${randomId()}`,
|
|
737
|
+
incomplete_details: null,
|
|
738
|
+
instructions: null,
|
|
739
|
+
max_output_tokens: null,
|
|
740
|
+
metadata: {},
|
|
741
|
+
model,
|
|
742
|
+
object: "response",
|
|
743
|
+
output,
|
|
744
|
+
output_text: "",
|
|
745
|
+
parallel_tool_calls: false,
|
|
746
|
+
status: "completed",
|
|
747
|
+
temperature: null,
|
|
748
|
+
tool_choice: "none",
|
|
749
|
+
tools: [],
|
|
750
|
+
top_p: null
|
|
751
|
+
});
|
|
682
752
|
}
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
|
|
753
|
+
function responsesCompactionSseText(upstreamText, isSse, model) {
|
|
754
|
+
const responseId = `resp_${randomId()}`;
|
|
755
|
+
const item = compactionOutputItem(compactionSummaryText(upstreamText, isSse));
|
|
756
|
+
const createdAt = epochSeconds();
|
|
757
|
+
let sequenceNumber = 0;
|
|
758
|
+
const event = (name, data) => encodeSse(name, data === "[DONE]" ? data : { ...data, sequence_number: sequenceNumber++ });
|
|
759
|
+
return [
|
|
760
|
+
event("response.created", {
|
|
761
|
+
response: baseStreamResponse(responseId, model, createdAt, "in_progress", []),
|
|
762
|
+
type: "response.created"
|
|
763
|
+
}),
|
|
764
|
+
event("response.output_item.done", {
|
|
765
|
+
item,
|
|
766
|
+
output_index: 0,
|
|
767
|
+
type: "response.output_item.done"
|
|
768
|
+
}),
|
|
769
|
+
event("response.completed", {
|
|
770
|
+
response: baseStreamResponse(responseId, model, createdAt, "completed", [item]),
|
|
771
|
+
type: "response.completed"
|
|
772
|
+
}),
|
|
773
|
+
event("done", "[DONE]")
|
|
774
|
+
].join("");
|
|
775
|
+
}
|
|
776
|
+
function compactionSummaryText(upstreamText, isSse) {
|
|
777
|
+
const summary = isSse ? compactionSummaryTextFromResponsesSse(upstreamText) : compactionSummaryTextFromResponse(asRecord(safeJsonParse(upstreamText)));
|
|
778
|
+
return summary.trim() || "(no summary available)";
|
|
779
|
+
}
|
|
780
|
+
function compactionSummaryTextFromResponse(response) {
|
|
781
|
+
const output = Array.isArray(response.output) ? response.output.map((item) => asRecord(item)) : [];
|
|
782
|
+
const compaction = output.find((item) => contentToText(item.type) === "compaction");
|
|
783
|
+
if (compaction) {
|
|
784
|
+
return contentToText(compaction.encrypted_content);
|
|
686
785
|
}
|
|
687
|
-
const text =
|
|
688
|
-
|
|
786
|
+
const text = outputText(output);
|
|
787
|
+
if (text) {
|
|
788
|
+
return text;
|
|
789
|
+
}
|
|
790
|
+
return contentToText(response.output_text);
|
|
689
791
|
}
|
|
690
|
-
function
|
|
792
|
+
function compactionSummaryTextFromResponsesSse(text) {
|
|
691
793
|
let deltas = "";
|
|
692
|
-
let
|
|
794
|
+
let completedResponse;
|
|
693
795
|
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
694
796
|
const data = block.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("");
|
|
695
797
|
if (!data || data === "[DONE]") {
|
|
@@ -700,16 +802,16 @@ function compactionOutputFromResponsesSse(text) {
|
|
|
700
802
|
if (type === "response.output_text.delta") {
|
|
701
803
|
deltas += contentToText(record.delta);
|
|
702
804
|
} else if (type === "response.completed" || type === "response.incomplete") {
|
|
703
|
-
|
|
704
|
-
if (Array.isArray(response.output)) {
|
|
705
|
-
completedOutput = response.output;
|
|
706
|
-
}
|
|
805
|
+
completedResponse = asRecord(record.response);
|
|
707
806
|
}
|
|
708
807
|
}
|
|
709
|
-
if (
|
|
710
|
-
|
|
808
|
+
if (completedResponse) {
|
|
809
|
+
const summary = compactionSummaryTextFromResponse(completedResponse);
|
|
810
|
+
if (summary) {
|
|
811
|
+
return summary;
|
|
812
|
+
}
|
|
711
813
|
}
|
|
712
|
-
return deltas
|
|
814
|
+
return deltas;
|
|
713
815
|
}
|
|
714
816
|
function chatCompletionToCompletion(completion) {
|
|
715
817
|
return removeUndefined({
|
|
@@ -873,21 +975,78 @@ function contentToText(content) {
|
|
|
873
975
|
}
|
|
874
976
|
return "";
|
|
875
977
|
}
|
|
876
|
-
function
|
|
877
|
-
return {
|
|
978
|
+
function compactionSummaryOutputMessageItem(text) {
|
|
979
|
+
return compactionSummaryMessageItem(text, `msg_${randomId()}`);
|
|
980
|
+
}
|
|
981
|
+
function compactionSummaryInputMessageItem(text) {
|
|
982
|
+
return compactionSummaryMessageItem(text);
|
|
983
|
+
}
|
|
984
|
+
function compactionSummaryMessageItem(text, id) {
|
|
985
|
+
return removeUndefined({
|
|
878
986
|
content: [
|
|
879
987
|
{
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
type: "
|
|
988
|
+
text: `${COMPACTION_SUMMARY_PREFIX}
|
|
989
|
+
${text}`,
|
|
990
|
+
type: "input_text"
|
|
883
991
|
}
|
|
884
992
|
],
|
|
885
993
|
id,
|
|
886
|
-
role: "
|
|
887
|
-
status: "completed",
|
|
994
|
+
role: "user",
|
|
888
995
|
type: "message"
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
function compactionOutputItem(text, id = `cmpct_${randomId()}`) {
|
|
999
|
+
return {
|
|
1000
|
+
encrypted_content: text,
|
|
1001
|
+
id,
|
|
1002
|
+
type: "compaction"
|
|
889
1003
|
};
|
|
890
1004
|
}
|
|
1005
|
+
function normalizeCompactionInputForCopilot(input, options) {
|
|
1006
|
+
const items = responseInputItems(input);
|
|
1007
|
+
if (items.length === 0) {
|
|
1008
|
+
return input;
|
|
1009
|
+
}
|
|
1010
|
+
const normalized = [];
|
|
1011
|
+
for (const item of items) {
|
|
1012
|
+
const record = asRecord(item);
|
|
1013
|
+
const type = contentToText(record.type);
|
|
1014
|
+
if (type === "compaction_trigger" && options.dropTrigger) {
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (type === "compaction" || type === "compaction_summary" || type === "context_compaction") {
|
|
1018
|
+
const text = contentToText(record.encrypted_content);
|
|
1019
|
+
if (text) {
|
|
1020
|
+
normalized.push(compactionSummaryInputMessageItem(text));
|
|
1021
|
+
}
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
normalized.push(item);
|
|
1025
|
+
}
|
|
1026
|
+
return normalized;
|
|
1027
|
+
}
|
|
1028
|
+
function compactionInputItemsForCopilot(input) {
|
|
1029
|
+
if (Array.isArray(input)) {
|
|
1030
|
+
return normalizeCompactionInputForCopilot(input, { dropTrigger: true });
|
|
1031
|
+
}
|
|
1032
|
+
const text = contentToText(input);
|
|
1033
|
+
return text ? [
|
|
1034
|
+
{
|
|
1035
|
+
content: [{ text, type: "input_text" }],
|
|
1036
|
+
role: "user",
|
|
1037
|
+
type: "message"
|
|
1038
|
+
}
|
|
1039
|
+
] : [];
|
|
1040
|
+
}
|
|
1041
|
+
function responseInputItems(input) {
|
|
1042
|
+
return Array.isArray(input) ? input : [];
|
|
1043
|
+
}
|
|
1044
|
+
function outputText(output) {
|
|
1045
|
+
return output.flatMap((item) => {
|
|
1046
|
+
const content = item.content;
|
|
1047
|
+
return Array.isArray(content) ? content : [];
|
|
1048
|
+
}).map((part) => contentToText(asRecord(part).text)).filter(Boolean).join("");
|
|
1049
|
+
}
|
|
891
1050
|
function extractTokenUsage(usage) {
|
|
892
1051
|
const record = asRecord(usage);
|
|
893
1052
|
const prompt = firstNumber(record.prompt_tokens, record.input_tokens);
|
|
@@ -903,6 +1062,7 @@ function extractTokenUsage(usage) {
|
|
|
903
1062
|
asRecord(record.output_tokens_details).reasoning_tokens
|
|
904
1063
|
);
|
|
905
1064
|
const cached = firstNumber(
|
|
1065
|
+
record.cache_read_input_tokens,
|
|
906
1066
|
asRecord(record.prompt_tokens_details).cached_tokens,
|
|
907
1067
|
asRecord(record.input_tokens_details).cached_tokens
|
|
908
1068
|
);
|
|
@@ -999,6 +1159,35 @@ function completionStreamError(event, parsed) {
|
|
|
999
1159
|
}
|
|
1000
1160
|
return void 0;
|
|
1001
1161
|
}
|
|
1162
|
+
function baseStreamResponse(id, model, createdAt, status, output) {
|
|
1163
|
+
return {
|
|
1164
|
+
created_at: createdAt,
|
|
1165
|
+
error: null,
|
|
1166
|
+
id,
|
|
1167
|
+
incomplete_details: null,
|
|
1168
|
+
instructions: null,
|
|
1169
|
+
max_output_tokens: null,
|
|
1170
|
+
metadata: {},
|
|
1171
|
+
model,
|
|
1172
|
+
object: "response",
|
|
1173
|
+
output,
|
|
1174
|
+
parallel_tool_calls: true,
|
|
1175
|
+
status,
|
|
1176
|
+
temperature: null,
|
|
1177
|
+
tool_choice: "auto",
|
|
1178
|
+
tools: [],
|
|
1179
|
+
top_p: null
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function encodeSse(event, data) {
|
|
1183
|
+
if (data === "[DONE]") {
|
|
1184
|
+
return "data: [DONE]\n\n";
|
|
1185
|
+
}
|
|
1186
|
+
return `event: ${event}
|
|
1187
|
+
data: ${JSON.stringify(data)}
|
|
1188
|
+
|
|
1189
|
+
`;
|
|
1190
|
+
}
|
|
1002
1191
|
function encodeDataSse(data) {
|
|
1003
1192
|
if (data === "[DONE]") {
|
|
1004
1193
|
return "data: [DONE]\n\n";
|
|
@@ -1019,9 +1208,10 @@ var AnthropicCompatibilityError = class extends Error {
|
|
|
1019
1208
|
}
|
|
1020
1209
|
};
|
|
1021
1210
|
function anthropicMessagesToResponsesRequest(request) {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1211
|
+
const system = anthropicSystemToResponses(request.system);
|
|
1212
|
+
const response = removeUndefined({
|
|
1213
|
+
input: [...system.input, ...anthropicMessagesToResponsesInput(request.messages)],
|
|
1214
|
+
instructions: system.instructions,
|
|
1025
1215
|
max_output_tokens: typeof request.max_tokens === "number" && Number.isFinite(request.max_tokens) ? request.max_tokens : void 0,
|
|
1026
1216
|
metadata: request.metadata,
|
|
1027
1217
|
model: normalizeRequestedModel(request.model),
|
|
@@ -1034,6 +1224,8 @@ function anthropicMessagesToResponsesRequest(request) {
|
|
|
1034
1224
|
tools: anthropicTools(request.tools),
|
|
1035
1225
|
top_p: request.top_p
|
|
1036
1226
|
});
|
|
1227
|
+
applyCacheControlToLastBlock(response, anthropicCacheControl(request.cache_control));
|
|
1228
|
+
return response;
|
|
1037
1229
|
}
|
|
1038
1230
|
function responsesResponseToAnthropicMessage(response, fallbackModel) {
|
|
1039
1231
|
const content = anthropicContentFromResponsesOutput(response);
|
|
@@ -1057,7 +1249,7 @@ function responsesStreamToAnthropicStream(stream, options) {
|
|
|
1057
1249
|
return new ReadableStream({
|
|
1058
1250
|
async start(controller) {
|
|
1059
1251
|
const enqueue = (event, data) => {
|
|
1060
|
-
controller.enqueue(encoder.encode(
|
|
1252
|
+
controller.enqueue(encoder.encode(encodeSse2(event, data)));
|
|
1061
1253
|
};
|
|
1062
1254
|
const reader = stream.getReader();
|
|
1063
1255
|
try {
|
|
@@ -1093,7 +1285,7 @@ function responsesSseTextToAnthropicSseText(text, options) {
|
|
|
1093
1285
|
const chunks = [];
|
|
1094
1286
|
const state = createAnthropicStreamState(options);
|
|
1095
1287
|
const enqueue = (event, data) => {
|
|
1096
|
-
chunks.push(
|
|
1288
|
+
chunks.push(encodeSse2(event, data));
|
|
1097
1289
|
};
|
|
1098
1290
|
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
1099
1291
|
if (block.trim()) {
|
|
@@ -1130,6 +1322,7 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1130
1322
|
throw new AnthropicCompatibilityError("Anthropic Messages requests require messages[].");
|
|
1131
1323
|
}
|
|
1132
1324
|
const input = [];
|
|
1325
|
+
let fallbackToolCallIndex = 0;
|
|
1133
1326
|
for (const message of messages) {
|
|
1134
1327
|
const record = asRecord(message);
|
|
1135
1328
|
const role = anthropicRole(record.role);
|
|
@@ -1151,10 +1344,13 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1151
1344
|
if (type === "text") {
|
|
1152
1345
|
const text = textValue(part.text);
|
|
1153
1346
|
if (text) {
|
|
1154
|
-
messageParts.push(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1347
|
+
messageParts.push(
|
|
1348
|
+
removeUndefined({
|
|
1349
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1350
|
+
text,
|
|
1351
|
+
type: role === "assistant" ? "output_text" : "input_text"
|
|
1352
|
+
})
|
|
1353
|
+
);
|
|
1158
1354
|
}
|
|
1159
1355
|
continue;
|
|
1160
1356
|
}
|
|
@@ -1169,21 +1365,27 @@ function anthropicMessagesToResponsesInput(messages) {
|
|
|
1169
1365
|
}
|
|
1170
1366
|
if (type === "tool_use") {
|
|
1171
1367
|
flushMessage();
|
|
1172
|
-
input.push(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1368
|
+
input.push(
|
|
1369
|
+
removeUndefined({
|
|
1370
|
+
arguments: JSON.stringify(asRecord(part.input)),
|
|
1371
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1372
|
+
call_id: textValue(part.id) || `call_hoopilot_${fallbackToolCallIndex++}`,
|
|
1373
|
+
name: textValue(part.name),
|
|
1374
|
+
type: "function_call"
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1178
1377
|
continue;
|
|
1179
1378
|
}
|
|
1180
1379
|
if (type === "tool_result") {
|
|
1181
1380
|
flushMessage();
|
|
1182
|
-
input.push(
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1381
|
+
input.push(
|
|
1382
|
+
removeUndefined({
|
|
1383
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1384
|
+
call_id: textValue(part.tool_use_id),
|
|
1385
|
+
output: anthropicToolResultOutput(part.content),
|
|
1386
|
+
type: "function_call_output"
|
|
1387
|
+
})
|
|
1388
|
+
);
|
|
1187
1389
|
continue;
|
|
1188
1390
|
}
|
|
1189
1391
|
if (type === "thinking" || type === "redacted_thinking") {
|
|
@@ -1230,22 +1432,24 @@ function anthropicImageToResponsesPart(part) {
|
|
|
1230
1432
|
if (!data) {
|
|
1231
1433
|
throw new AnthropicCompatibilityError("Anthropic base64 image content requires source.data.");
|
|
1232
1434
|
}
|
|
1233
|
-
return {
|
|
1435
|
+
return removeUndefined({
|
|
1436
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1234
1437
|
detail: "auto",
|
|
1235
1438
|
image_url: `data:${mediaType};base64,${data}`,
|
|
1236
1439
|
type: "input_image"
|
|
1237
|
-
};
|
|
1440
|
+
});
|
|
1238
1441
|
}
|
|
1239
1442
|
if (sourceType === "url") {
|
|
1240
1443
|
const url = textValue(source.url);
|
|
1241
1444
|
if (!url) {
|
|
1242
1445
|
throw new AnthropicCompatibilityError("Anthropic URL image content requires source.url.");
|
|
1243
1446
|
}
|
|
1244
|
-
return {
|
|
1447
|
+
return removeUndefined({
|
|
1448
|
+
cache_control: anthropicCacheControl(part.cache_control),
|
|
1245
1449
|
detail: "auto",
|
|
1246
1450
|
image_url: url,
|
|
1247
1451
|
type: "input_image"
|
|
1248
|
-
};
|
|
1452
|
+
});
|
|
1249
1453
|
}
|
|
1250
1454
|
throw new AnthropicCompatibilityError(
|
|
1251
1455
|
`Anthropic image source type "${sourceType || "unknown"}" is not supported.`
|
|
@@ -1266,15 +1470,42 @@ function anthropicToolResultOutput(content) {
|
|
|
1266
1470
|
}
|
|
1267
1471
|
return typeof content === "object" ? JSON.stringify(content) : String(content);
|
|
1268
1472
|
}
|
|
1269
|
-
function
|
|
1473
|
+
function anthropicSystemToResponses(system) {
|
|
1270
1474
|
if (typeof system === "string") {
|
|
1271
|
-
return system || void 0;
|
|
1475
|
+
return { input: [], instructions: system || void 0 };
|
|
1272
1476
|
}
|
|
1273
1477
|
if (!Array.isArray(system)) {
|
|
1478
|
+
return { input: [] };
|
|
1479
|
+
}
|
|
1480
|
+
const parts = system.map((part) => anthropicSystemPartToResponsesPart(part)).filter((part) => part !== void 0);
|
|
1481
|
+
if (parts.length === 0) {
|
|
1482
|
+
return { input: [] };
|
|
1483
|
+
}
|
|
1484
|
+
if (parts.some((part) => part.cache_control !== void 0)) {
|
|
1485
|
+
return {
|
|
1486
|
+
input: [
|
|
1487
|
+
{
|
|
1488
|
+
content: parts,
|
|
1489
|
+
role: "system",
|
|
1490
|
+
type: "message"
|
|
1491
|
+
}
|
|
1492
|
+
]
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
const text = parts.map((part) => textValue(part.text)).filter(Boolean).join("\n");
|
|
1496
|
+
return { input: [], instructions: text || void 0 };
|
|
1497
|
+
}
|
|
1498
|
+
function anthropicSystemPartToResponsesPart(part) {
|
|
1499
|
+
const record = asRecord(part);
|
|
1500
|
+
const text = textValue(record.text) || textValue(part);
|
|
1501
|
+
if (!text) {
|
|
1274
1502
|
return void 0;
|
|
1275
1503
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1504
|
+
return removeUndefined({
|
|
1505
|
+
cache_control: anthropicCacheControl(record.cache_control),
|
|
1506
|
+
text,
|
|
1507
|
+
type: "input_text"
|
|
1508
|
+
});
|
|
1278
1509
|
}
|
|
1279
1510
|
function anthropicTools(tools) {
|
|
1280
1511
|
if (!Array.isArray(tools)) {
|
|
@@ -1283,6 +1514,7 @@ function anthropicTools(tools) {
|
|
|
1283
1514
|
const converted = tools.map((tool) => {
|
|
1284
1515
|
const record = asRecord(tool);
|
|
1285
1516
|
return removeUndefined({
|
|
1517
|
+
cache_control: anthropicCacheControl(record.cache_control),
|
|
1286
1518
|
description: record.description,
|
|
1287
1519
|
name: record.name,
|
|
1288
1520
|
parameters: record.input_schema,
|
|
@@ -1292,6 +1524,55 @@ function anthropicTools(tools) {
|
|
|
1292
1524
|
});
|
|
1293
1525
|
return converted.length > 0 ? converted : void 0;
|
|
1294
1526
|
}
|
|
1527
|
+
function anthropicCacheControl(value) {
|
|
1528
|
+
if (value === void 0 || value === null) {
|
|
1529
|
+
return void 0;
|
|
1530
|
+
}
|
|
1531
|
+
const record = asRecord(value);
|
|
1532
|
+
const type = textValue(record.type);
|
|
1533
|
+
if (type !== "ephemeral") {
|
|
1534
|
+
throw new AnthropicCompatibilityError(
|
|
1535
|
+
`Anthropic cache_control type "${type || "unknown"}" is not supported.`
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
const ttl = textValue(record.ttl);
|
|
1539
|
+
if (ttl && ttl !== "5m" && ttl !== "1h") {
|
|
1540
|
+
throw new AnthropicCompatibilityError(`Anthropic cache_control ttl "${ttl}" is not supported.`);
|
|
1541
|
+
}
|
|
1542
|
+
return removeUndefined({
|
|
1543
|
+
ttl: ttl || void 0,
|
|
1544
|
+
type
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
function applyCacheControlToLastBlock(request, cacheControl) {
|
|
1548
|
+
if (!cacheControl) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const input = Array.isArray(request.input) ? request.input : [];
|
|
1552
|
+
for (let itemIndex = input.length - 1; itemIndex >= 0; itemIndex -= 1) {
|
|
1553
|
+
const item = asRecord(input[itemIndex]);
|
|
1554
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
1555
|
+
for (let partIndex = content.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
1556
|
+
const part = asRecord(content[partIndex]);
|
|
1557
|
+
if (part.cache_control === void 0 && isCacheableResponsesPart(part)) {
|
|
1558
|
+
part.cache_control = cacheControl;
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
const tools = Array.isArray(request.tools) ? request.tools : [];
|
|
1564
|
+
for (let index = tools.length - 1; index >= 0; index -= 1) {
|
|
1565
|
+
const tool = asRecord(tools[index]);
|
|
1566
|
+
if (tool.cache_control === void 0) {
|
|
1567
|
+
tool.cache_control = cacheControl;
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function isCacheableResponsesPart(part) {
|
|
1573
|
+
const type = textValue(part.type);
|
|
1574
|
+
return type === "input_text" || type === "output_text" || type === "text" || type === "input_image";
|
|
1575
|
+
}
|
|
1295
1576
|
function anthropicToolChoice(toolChoice) {
|
|
1296
1577
|
if (toolChoice === void 0 || toolChoice === null) {
|
|
1297
1578
|
return void 0;
|
|
@@ -1361,9 +1642,9 @@ function anthropicContentFromResponsesOutput(response) {
|
|
|
1361
1642
|
}
|
|
1362
1643
|
}
|
|
1363
1644
|
if (content.length === 0) {
|
|
1364
|
-
const
|
|
1365
|
-
if (
|
|
1366
|
-
content.push({ text:
|
|
1645
|
+
const outputText2 = textValue(response.output_text);
|
|
1646
|
+
if (outputText2) {
|
|
1647
|
+
content.push({ text: outputText2, type: "text" });
|
|
1367
1648
|
}
|
|
1368
1649
|
}
|
|
1369
1650
|
return content;
|
|
@@ -1635,7 +1916,7 @@ function textValue(value) {
|
|
|
1635
1916
|
function indexValue(value) {
|
|
1636
1917
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1637
1918
|
}
|
|
1638
|
-
function
|
|
1919
|
+
function encodeSse2(event, data) {
|
|
1639
1920
|
return `event: ${event}
|
|
1640
1921
|
data: ${JSON.stringify(data)}
|
|
1641
1922
|
|
|
@@ -3516,7 +3797,21 @@ async function handleCompletions(client, metrics, recordTokens, recordExtraction
|
|
|
3516
3797
|
}
|
|
3517
3798
|
async function handleResponses(client, metrics, recordTokens, recordExtraction, request, logger, bufferProxyBodies) {
|
|
3518
3799
|
const { json, text: body } = await readJsonText(request);
|
|
3519
|
-
|
|
3800
|
+
if (isResponsesCompactionRequest(json)) {
|
|
3801
|
+
return handleResponsesCompactionV2(
|
|
3802
|
+
client,
|
|
3803
|
+
metrics,
|
|
3804
|
+
recordTokens,
|
|
3805
|
+
recordExtraction,
|
|
3806
|
+
json,
|
|
3807
|
+
request,
|
|
3808
|
+
logger
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
const upstream = await client.responses(
|
|
3812
|
+
responsesRequestNeedsCopilotNormalization(json) ? normalizeResponsesRequestForCopilotBody(json) : body,
|
|
3813
|
+
request.signal
|
|
3814
|
+
);
|
|
3520
3815
|
metrics.recordUpstream("/responses", upstream.ok);
|
|
3521
3816
|
if (!upstream.ok) {
|
|
3522
3817
|
return proxyError(upstream, logger);
|
|
@@ -3536,10 +3831,7 @@ async function handleResponses(client, metrics, recordTokens, recordExtraction,
|
|
|
3536
3831
|
}
|
|
3537
3832
|
async function handleResponsesCompact(client, metrics, recordTokens, recordExtraction, request, logger) {
|
|
3538
3833
|
const body = await readJson(request);
|
|
3539
|
-
const upstream = await client.responses(
|
|
3540
|
-
JSON.stringify({ ...body, stream: false }),
|
|
3541
|
-
request.signal
|
|
3542
|
-
);
|
|
3834
|
+
const upstream = await client.responses(responsesCompactionRequestBody(body), request.signal);
|
|
3543
3835
|
metrics.recordUpstream("/responses", upstream.ok);
|
|
3544
3836
|
if (!upstream.ok) {
|
|
3545
3837
|
return proxyError(upstream, logger);
|
|
@@ -3556,6 +3848,22 @@ async function handleResponsesCompact(client, metrics, recordTokens, recordExtra
|
|
|
3556
3848
|
);
|
|
3557
3849
|
return jsonResponse(responsesCompactionResult(text, isSse));
|
|
3558
3850
|
}
|
|
3851
|
+
async function handleResponsesCompactionV2(client, metrics, recordTokens, recordExtraction, json, request, logger) {
|
|
3852
|
+
const upstream = await client.responses(responsesCompactionRequestBody(json), request.signal);
|
|
3853
|
+
metrics.recordUpstream("/responses", upstream.ok);
|
|
3854
|
+
if (!upstream.ok) {
|
|
3855
|
+
return proxyError(upstream, logger);
|
|
3856
|
+
}
|
|
3857
|
+
logUpstreamSuccess(logger, "/responses", upstream.status);
|
|
3858
|
+
const isSse = isStreamingResponse(upstream);
|
|
3859
|
+
const text = await upstream.text();
|
|
3860
|
+
const model = normalizeRequestedModel(json.model);
|
|
3861
|
+
recordResponseTextUsage(text, isSse, model, recordTokens, recordExtraction);
|
|
3862
|
+
if (json.stream === true) {
|
|
3863
|
+
return textResponse(responsesCompactionSseText(text, isSse, model), "text/event-stream");
|
|
3864
|
+
}
|
|
3865
|
+
return jsonResponse(responsesCompactionResponse(text, isSse, model));
|
|
3866
|
+
}
|
|
3559
3867
|
async function responseWithObservedUsage(response, fallbackModel, recordTokens, signal, bufferBody, recordExtraction) {
|
|
3560
3868
|
const isSse = isStreamingResponse(response);
|
|
3561
3869
|
if (bufferBody && response.body) {
|
|
@@ -3664,6 +3972,15 @@ function jsonResponse(body, status = 200) {
|
|
|
3664
3972
|
status
|
|
3665
3973
|
});
|
|
3666
3974
|
}
|
|
3975
|
+
function textResponse(body, contentType, status = 200) {
|
|
3976
|
+
return new Response(body, {
|
|
3977
|
+
headers: {
|
|
3978
|
+
...corsHeaders(),
|
|
3979
|
+
"content-type": `${contentType}; charset=utf-8`
|
|
3980
|
+
},
|
|
3981
|
+
status
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3667
3984
|
function jsonError(status, code, message) {
|
|
3668
3985
|
return jsonResponse(
|
|
3669
3986
|
{
|