@openhoo/hoopilot 2.1.1 → 2.1.2
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 +251 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.js +219 -21
- 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: [compactionSummaryMessageItem(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
|
+
});
|
|
752
|
+
}
|
|
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("");
|
|
682
775
|
}
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
|
|
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);
|
|
785
|
+
}
|
|
786
|
+
const text = outputText(output);
|
|
787
|
+
if (text) {
|
|
788
|
+
return text;
|
|
686
789
|
}
|
|
687
|
-
|
|
688
|
-
return text ? [messageOutputItem(text)] : [];
|
|
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,72 @@ function contentToText(content) {
|
|
|
873
975
|
}
|
|
874
976
|
return "";
|
|
875
977
|
}
|
|
876
|
-
function
|
|
978
|
+
function compactionSummaryMessageItem(text, id = `msg_${randomId()}`) {
|
|
877
979
|
return {
|
|
878
980
|
content: [
|
|
879
981
|
{
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
type: "
|
|
982
|
+
text: `${COMPACTION_SUMMARY_PREFIX}
|
|
983
|
+
${text}`,
|
|
984
|
+
type: "input_text"
|
|
883
985
|
}
|
|
884
986
|
],
|
|
885
987
|
id,
|
|
886
|
-
role: "
|
|
887
|
-
status: "completed",
|
|
988
|
+
role: "user",
|
|
888
989
|
type: "message"
|
|
889
990
|
};
|
|
890
991
|
}
|
|
992
|
+
function compactionOutputItem(text, id = `cmpct_${randomId()}`) {
|
|
993
|
+
return {
|
|
994
|
+
encrypted_content: text,
|
|
995
|
+
id,
|
|
996
|
+
type: "compaction"
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function normalizeCompactionInputForCopilot(input, options) {
|
|
1000
|
+
const items = responseInputItems(input);
|
|
1001
|
+
if (items.length === 0) {
|
|
1002
|
+
return input;
|
|
1003
|
+
}
|
|
1004
|
+
const normalized = [];
|
|
1005
|
+
for (const item of items) {
|
|
1006
|
+
const record = asRecord(item);
|
|
1007
|
+
const type = contentToText(record.type);
|
|
1008
|
+
if (type === "compaction_trigger" && options.dropTrigger) {
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (type === "compaction" || type === "compaction_summary" || type === "context_compaction") {
|
|
1012
|
+
const text = contentToText(record.encrypted_content);
|
|
1013
|
+
if (text) {
|
|
1014
|
+
normalized.push(compactionSummaryMessageItem(text));
|
|
1015
|
+
}
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
normalized.push(item);
|
|
1019
|
+
}
|
|
1020
|
+
return normalized;
|
|
1021
|
+
}
|
|
1022
|
+
function compactionInputItemsForCopilot(input) {
|
|
1023
|
+
if (Array.isArray(input)) {
|
|
1024
|
+
return normalizeCompactionInputForCopilot(input, { dropTrigger: true });
|
|
1025
|
+
}
|
|
1026
|
+
const text = contentToText(input);
|
|
1027
|
+
return text ? [
|
|
1028
|
+
{
|
|
1029
|
+
content: [{ text, type: "input_text" }],
|
|
1030
|
+
role: "user",
|
|
1031
|
+
type: "message"
|
|
1032
|
+
}
|
|
1033
|
+
] : [];
|
|
1034
|
+
}
|
|
1035
|
+
function responseInputItems(input) {
|
|
1036
|
+
return Array.isArray(input) ? input : [];
|
|
1037
|
+
}
|
|
1038
|
+
function outputText(output) {
|
|
1039
|
+
return output.flatMap((item) => {
|
|
1040
|
+
const content = item.content;
|
|
1041
|
+
return Array.isArray(content) ? content : [];
|
|
1042
|
+
}).map((part) => contentToText(asRecord(part).text)).filter(Boolean).join("");
|
|
1043
|
+
}
|
|
891
1044
|
function extractTokenUsage(usage) {
|
|
892
1045
|
const record = asRecord(usage);
|
|
893
1046
|
const prompt = firstNumber(record.prompt_tokens, record.input_tokens);
|
|
@@ -999,6 +1152,35 @@ function completionStreamError(event, parsed) {
|
|
|
999
1152
|
}
|
|
1000
1153
|
return void 0;
|
|
1001
1154
|
}
|
|
1155
|
+
function baseStreamResponse(id, model, createdAt, status, output) {
|
|
1156
|
+
return {
|
|
1157
|
+
created_at: createdAt,
|
|
1158
|
+
error: null,
|
|
1159
|
+
id,
|
|
1160
|
+
incomplete_details: null,
|
|
1161
|
+
instructions: null,
|
|
1162
|
+
max_output_tokens: null,
|
|
1163
|
+
metadata: {},
|
|
1164
|
+
model,
|
|
1165
|
+
object: "response",
|
|
1166
|
+
output,
|
|
1167
|
+
parallel_tool_calls: true,
|
|
1168
|
+
status,
|
|
1169
|
+
temperature: null,
|
|
1170
|
+
tool_choice: "auto",
|
|
1171
|
+
tools: [],
|
|
1172
|
+
top_p: null
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function encodeSse(event, data) {
|
|
1176
|
+
if (data === "[DONE]") {
|
|
1177
|
+
return "data: [DONE]\n\n";
|
|
1178
|
+
}
|
|
1179
|
+
return `event: ${event}
|
|
1180
|
+
data: ${JSON.stringify(data)}
|
|
1181
|
+
|
|
1182
|
+
`;
|
|
1183
|
+
}
|
|
1002
1184
|
function encodeDataSse(data) {
|
|
1003
1185
|
if (data === "[DONE]") {
|
|
1004
1186
|
return "data: [DONE]\n\n";
|
|
@@ -1057,7 +1239,7 @@ function responsesStreamToAnthropicStream(stream, options) {
|
|
|
1057
1239
|
return new ReadableStream({
|
|
1058
1240
|
async start(controller) {
|
|
1059
1241
|
const enqueue = (event, data) => {
|
|
1060
|
-
controller.enqueue(encoder.encode(
|
|
1242
|
+
controller.enqueue(encoder.encode(encodeSse2(event, data)));
|
|
1061
1243
|
};
|
|
1062
1244
|
const reader = stream.getReader();
|
|
1063
1245
|
try {
|
|
@@ -1093,7 +1275,7 @@ function responsesSseTextToAnthropicSseText(text, options) {
|
|
|
1093
1275
|
const chunks = [];
|
|
1094
1276
|
const state = createAnthropicStreamState(options);
|
|
1095
1277
|
const enqueue = (event, data) => {
|
|
1096
|
-
chunks.push(
|
|
1278
|
+
chunks.push(encodeSse2(event, data));
|
|
1097
1279
|
};
|
|
1098
1280
|
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
1099
1281
|
if (block.trim()) {
|
|
@@ -1361,9 +1543,9 @@ function anthropicContentFromResponsesOutput(response) {
|
|
|
1361
1543
|
}
|
|
1362
1544
|
}
|
|
1363
1545
|
if (content.length === 0) {
|
|
1364
|
-
const
|
|
1365
|
-
if (
|
|
1366
|
-
content.push({ text:
|
|
1546
|
+
const outputText2 = textValue(response.output_text);
|
|
1547
|
+
if (outputText2) {
|
|
1548
|
+
content.push({ text: outputText2, type: "text" });
|
|
1367
1549
|
}
|
|
1368
1550
|
}
|
|
1369
1551
|
return content;
|
|
@@ -1635,7 +1817,7 @@ function textValue(value) {
|
|
|
1635
1817
|
function indexValue(value) {
|
|
1636
1818
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1637
1819
|
}
|
|
1638
|
-
function
|
|
1820
|
+
function encodeSse2(event, data) {
|
|
1639
1821
|
return `event: ${event}
|
|
1640
1822
|
data: ${JSON.stringify(data)}
|
|
1641
1823
|
|
|
@@ -3516,7 +3698,21 @@ async function handleCompletions(client, metrics, recordTokens, recordExtraction
|
|
|
3516
3698
|
}
|
|
3517
3699
|
async function handleResponses(client, metrics, recordTokens, recordExtraction, request, logger, bufferProxyBodies) {
|
|
3518
3700
|
const { json, text: body } = await readJsonText(request);
|
|
3519
|
-
|
|
3701
|
+
if (isResponsesCompactionRequest(json)) {
|
|
3702
|
+
return handleResponsesCompactionV2(
|
|
3703
|
+
client,
|
|
3704
|
+
metrics,
|
|
3705
|
+
recordTokens,
|
|
3706
|
+
recordExtraction,
|
|
3707
|
+
json,
|
|
3708
|
+
request,
|
|
3709
|
+
logger
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
const upstream = await client.responses(
|
|
3713
|
+
responsesRequestNeedsCopilotNormalization(json) ? normalizeResponsesRequestForCopilotBody(json) : body,
|
|
3714
|
+
request.signal
|
|
3715
|
+
);
|
|
3520
3716
|
metrics.recordUpstream("/responses", upstream.ok);
|
|
3521
3717
|
if (!upstream.ok) {
|
|
3522
3718
|
return proxyError(upstream, logger);
|
|
@@ -3536,10 +3732,7 @@ async function handleResponses(client, metrics, recordTokens, recordExtraction,
|
|
|
3536
3732
|
}
|
|
3537
3733
|
async function handleResponsesCompact(client, metrics, recordTokens, recordExtraction, request, logger) {
|
|
3538
3734
|
const body = await readJson(request);
|
|
3539
|
-
const upstream = await client.responses(
|
|
3540
|
-
JSON.stringify({ ...body, stream: false }),
|
|
3541
|
-
request.signal
|
|
3542
|
-
);
|
|
3735
|
+
const upstream = await client.responses(responsesCompactionRequestBody(body), request.signal);
|
|
3543
3736
|
metrics.recordUpstream("/responses", upstream.ok);
|
|
3544
3737
|
if (!upstream.ok) {
|
|
3545
3738
|
return proxyError(upstream, logger);
|
|
@@ -3556,6 +3749,22 @@ async function handleResponsesCompact(client, metrics, recordTokens, recordExtra
|
|
|
3556
3749
|
);
|
|
3557
3750
|
return jsonResponse(responsesCompactionResult(text, isSse));
|
|
3558
3751
|
}
|
|
3752
|
+
async function handleResponsesCompactionV2(client, metrics, recordTokens, recordExtraction, json, request, logger) {
|
|
3753
|
+
const upstream = await client.responses(responsesCompactionRequestBody(json), request.signal);
|
|
3754
|
+
metrics.recordUpstream("/responses", upstream.ok);
|
|
3755
|
+
if (!upstream.ok) {
|
|
3756
|
+
return proxyError(upstream, logger);
|
|
3757
|
+
}
|
|
3758
|
+
logUpstreamSuccess(logger, "/responses", upstream.status);
|
|
3759
|
+
const isSse = isStreamingResponse(upstream);
|
|
3760
|
+
const text = await upstream.text();
|
|
3761
|
+
const model = normalizeRequestedModel(json.model);
|
|
3762
|
+
recordResponseTextUsage(text, isSse, model, recordTokens, recordExtraction);
|
|
3763
|
+
if (json.stream === true) {
|
|
3764
|
+
return textResponse(responsesCompactionSseText(text, isSse, model), "text/event-stream");
|
|
3765
|
+
}
|
|
3766
|
+
return jsonResponse(responsesCompactionResponse(text, isSse, model));
|
|
3767
|
+
}
|
|
3559
3768
|
async function responseWithObservedUsage(response, fallbackModel, recordTokens, signal, bufferBody, recordExtraction) {
|
|
3560
3769
|
const isSse = isStreamingResponse(response);
|
|
3561
3770
|
if (bufferBody && response.body) {
|
|
@@ -3664,6 +3873,15 @@ function jsonResponse(body, status = 200) {
|
|
|
3664
3873
|
status
|
|
3665
3874
|
});
|
|
3666
3875
|
}
|
|
3876
|
+
function textResponse(body, contentType, status = 200) {
|
|
3877
|
+
return new Response(body, {
|
|
3878
|
+
headers: {
|
|
3879
|
+
...corsHeaders(),
|
|
3880
|
+
"content-type": `${contentType}; charset=utf-8`
|
|
3881
|
+
},
|
|
3882
|
+
status
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3667
3885
|
function jsonError(status, code, message) {
|
|
3668
3886
|
return jsonResponse(
|
|
3669
3887
|
{
|