@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/index.js CHANGED
@@ -94,6 +94,16 @@ function parseStreamingProxyMode(value) {
94
94
 
95
95
  // src/openai.ts
96
96
  var DEFAULT_MODEL = "gpt-4.1";
97
+ var COMPACTION_SUMMARIZATION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
98
+
99
+ Include:
100
+ - Current progress and key decisions made
101
+ - Important context, constraints, or user preferences
102
+ - What remains to be done (clear next steps)
103
+ - Any critical data, examples, or references needed to continue
104
+
105
+ Be concise, structured, and focused on helping the next LLM seamlessly continue the work.`;
106
+ 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:";
97
107
  var OpenAICompatibilityError = class extends Error {
98
108
  constructor(message) {
99
109
  super(message);
@@ -184,19 +194,111 @@ function chatCompletionToResponse(completion, responseId) {
184
194
  });
185
195
  }
186
196
  function responsesCompactionResult(upstreamText, isSse) {
187
- const output = isSse ? compactionOutputFromResponsesSse(upstreamText) : compactionOutputFromResponse(asRecord(safeJsonParse(upstreamText)));
188
- return { output };
197
+ const summary = compactionSummaryText(upstreamText, isSse);
198
+ return { output: [compactionSummaryMessageItem(summary)] };
199
+ }
200
+ function isResponsesCompactionRequest(request) {
201
+ return responseInputItems(request.input).some(
202
+ (item) => contentToText(asRecord(item).type) === "compaction_trigger"
203
+ );
204
+ }
205
+ function responsesCompactionRequestBody(request) {
206
+ return JSON.stringify(
207
+ removeUndefined({
208
+ ...request,
209
+ input: [
210
+ ...compactionInputItemsForCopilot(request.input),
211
+ {
212
+ content: [{ text: COMPACTION_SUMMARIZATION_PROMPT, type: "input_text" }],
213
+ role: "user",
214
+ type: "message"
215
+ }
216
+ ],
217
+ parallel_tool_calls: false,
218
+ stream: false,
219
+ tool_choice: "none",
220
+ tools: []
221
+ })
222
+ );
223
+ }
224
+ function normalizeResponsesRequestForCopilotBody(request) {
225
+ return JSON.stringify(
226
+ removeUndefined({
227
+ ...request,
228
+ input: normalizeCompactionInputForCopilot(request.input, { dropTrigger: false })
229
+ })
230
+ );
231
+ }
232
+ function responsesRequestNeedsCopilotNormalization(request) {
233
+ return responseInputItems(request.input).some((item) => {
234
+ const type = contentToText(asRecord(item).type);
235
+ return type === "compaction" || type === "compaction_summary" || type === "context_compaction";
236
+ });
237
+ }
238
+ function responsesCompactionResponse(upstreamText, isSse, model) {
239
+ const output = [compactionOutputItem(compactionSummaryText(upstreamText, isSse))];
240
+ return removeUndefined({
241
+ created_at: epochSeconds(),
242
+ error: null,
243
+ id: `resp_${randomId()}`,
244
+ incomplete_details: null,
245
+ instructions: null,
246
+ max_output_tokens: null,
247
+ metadata: {},
248
+ model,
249
+ object: "response",
250
+ output,
251
+ output_text: "",
252
+ parallel_tool_calls: false,
253
+ status: "completed",
254
+ temperature: null,
255
+ tool_choice: "none",
256
+ tools: [],
257
+ top_p: null
258
+ });
259
+ }
260
+ function responsesCompactionSseText(upstreamText, isSse, model) {
261
+ const responseId = `resp_${randomId()}`;
262
+ const item = compactionOutputItem(compactionSummaryText(upstreamText, isSse));
263
+ const createdAt = epochSeconds();
264
+ let sequenceNumber = 0;
265
+ const event = (name, data) => encodeSse(name, data === "[DONE]" ? data : { ...data, sequence_number: sequenceNumber++ });
266
+ return [
267
+ event("response.created", {
268
+ response: baseStreamResponse(responseId, model, createdAt, "in_progress", []),
269
+ type: "response.created"
270
+ }),
271
+ event("response.output_item.done", {
272
+ item,
273
+ output_index: 0,
274
+ type: "response.output_item.done"
275
+ }),
276
+ event("response.completed", {
277
+ response: baseStreamResponse(responseId, model, createdAt, "completed", [item]),
278
+ type: "response.completed"
279
+ }),
280
+ event("done", "[DONE]")
281
+ ].join("");
189
282
  }
190
- function compactionOutputFromResponse(response) {
191
- if (Array.isArray(response.output) && response.output.length > 0) {
192
- return response.output;
283
+ function compactionSummaryText(upstreamText, isSse) {
284
+ const summary = isSse ? compactionSummaryTextFromResponsesSse(upstreamText) : compactionSummaryTextFromResponse(asRecord(safeJsonParse(upstreamText)));
285
+ return summary.trim() || "(no summary available)";
286
+ }
287
+ function compactionSummaryTextFromResponse(response) {
288
+ const output = Array.isArray(response.output) ? response.output.map((item) => asRecord(item)) : [];
289
+ const compaction = output.find((item) => contentToText(item.type) === "compaction");
290
+ if (compaction) {
291
+ return contentToText(compaction.encrypted_content);
292
+ }
293
+ const text = outputText(output);
294
+ if (text) {
295
+ return text;
193
296
  }
194
- const text = contentToText(response.output_text);
195
- return text ? [messageOutputItem(text)] : [];
297
+ return contentToText(response.output_text);
196
298
  }
197
- function compactionOutputFromResponsesSse(text) {
299
+ function compactionSummaryTextFromResponsesSse(text) {
198
300
  let deltas = "";
199
- let completedOutput;
301
+ let completedResponse;
200
302
  for (const block of text.split(/\r?\n\r?\n/)) {
201
303
  const data = block.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("");
202
304
  if (!data || data === "[DONE]") {
@@ -207,16 +309,16 @@ function compactionOutputFromResponsesSse(text) {
207
309
  if (type === "response.output_text.delta") {
208
310
  deltas += contentToText(record.delta);
209
311
  } else if (type === "response.completed" || type === "response.incomplete") {
210
- const response = asRecord(record.response);
211
- if (Array.isArray(response.output)) {
212
- completedOutput = response.output;
213
- }
312
+ completedResponse = asRecord(record.response);
214
313
  }
215
314
  }
216
- if (completedOutput && completedOutput.length > 0) {
217
- return completedOutput;
315
+ if (completedResponse) {
316
+ const summary = compactionSummaryTextFromResponse(completedResponse);
317
+ if (summary) {
318
+ return summary;
319
+ }
218
320
  }
219
- return deltas ? [messageOutputItem(deltas)] : [];
321
+ return deltas;
220
322
  }
221
323
  function chatCompletionToCompletion(completion) {
222
324
  return removeUndefined({
@@ -737,6 +839,66 @@ function messageOutputItem(text, id = `msg_${randomId()}`) {
737
839
  type: "message"
738
840
  };
739
841
  }
842
+ function compactionSummaryMessageItem(text, id = `msg_${randomId()}`) {
843
+ return {
844
+ content: [
845
+ {
846
+ text: `${COMPACTION_SUMMARY_PREFIX}
847
+ ${text}`,
848
+ type: "input_text"
849
+ }
850
+ ],
851
+ id,
852
+ role: "user",
853
+ type: "message"
854
+ };
855
+ }
856
+ function compactionOutputItem(text, id = `cmpct_${randomId()}`) {
857
+ return {
858
+ encrypted_content: text,
859
+ id,
860
+ type: "compaction"
861
+ };
862
+ }
863
+ function normalizeCompactionInputForCopilot(input, options) {
864
+ const items = responseInputItems(input);
865
+ if (items.length === 0) {
866
+ return input;
867
+ }
868
+ const normalized = [];
869
+ for (const item of items) {
870
+ const record = asRecord(item);
871
+ const type = contentToText(record.type);
872
+ if (type === "compaction_trigger" && options.dropTrigger) {
873
+ continue;
874
+ }
875
+ if (type === "compaction" || type === "compaction_summary" || type === "context_compaction") {
876
+ const text = contentToText(record.encrypted_content);
877
+ if (text) {
878
+ normalized.push(compactionSummaryMessageItem(text));
879
+ }
880
+ continue;
881
+ }
882
+ normalized.push(item);
883
+ }
884
+ return normalized;
885
+ }
886
+ function compactionInputItemsForCopilot(input) {
887
+ if (Array.isArray(input)) {
888
+ return normalizeCompactionInputForCopilot(input, { dropTrigger: true });
889
+ }
890
+ const text = contentToText(input);
891
+ return text ? [
892
+ {
893
+ content: [{ text, type: "input_text" }],
894
+ role: "user",
895
+ type: "message"
896
+ }
897
+ ] : [];
898
+ }
899
+ function responseInputItems(input) {
900
+ return Array.isArray(input) ? input : [];
901
+ }
740
902
  function functionCallItem(tool, status = "completed") {
741
903
  return {
742
904
  arguments: tool.arguments,
@@ -4080,7 +4242,21 @@ async function handleCompletions(client, metrics, recordTokens, recordExtraction
4080
4242
  }
4081
4243
  async function handleResponses(client, metrics, recordTokens, recordExtraction, request, logger, bufferProxyBodies) {
4082
4244
  const { json, text: body } = await readJsonText(request);
4083
- const upstream = await client.responses(body, request.signal);
4245
+ if (isResponsesCompactionRequest(json)) {
4246
+ return handleResponsesCompactionV2(
4247
+ client,
4248
+ metrics,
4249
+ recordTokens,
4250
+ recordExtraction,
4251
+ json,
4252
+ request,
4253
+ logger
4254
+ );
4255
+ }
4256
+ const upstream = await client.responses(
4257
+ responsesRequestNeedsCopilotNormalization(json) ? normalizeResponsesRequestForCopilotBody(json) : body,
4258
+ request.signal
4259
+ );
4084
4260
  metrics.recordUpstream("/responses", upstream.ok);
4085
4261
  if (!upstream.ok) {
4086
4262
  return proxyError(upstream, logger);
@@ -4100,10 +4276,7 @@ async function handleResponses(client, metrics, recordTokens, recordExtraction,
4100
4276
  }
4101
4277
  async function handleResponsesCompact(client, metrics, recordTokens, recordExtraction, request, logger) {
4102
4278
  const body = await readJson(request);
4103
- const upstream = await client.responses(
4104
- JSON.stringify({ ...body, stream: false }),
4105
- request.signal
4106
- );
4279
+ const upstream = await client.responses(responsesCompactionRequestBody(body), request.signal);
4107
4280
  metrics.recordUpstream("/responses", upstream.ok);
4108
4281
  if (!upstream.ok) {
4109
4282
  return proxyError(upstream, logger);
@@ -4120,6 +4293,22 @@ async function handleResponsesCompact(client, metrics, recordTokens, recordExtra
4120
4293
  );
4121
4294
  return jsonResponse(responsesCompactionResult(text, isSse));
4122
4295
  }
4296
+ async function handleResponsesCompactionV2(client, metrics, recordTokens, recordExtraction, json, request, logger) {
4297
+ const upstream = await client.responses(responsesCompactionRequestBody(json), request.signal);
4298
+ metrics.recordUpstream("/responses", upstream.ok);
4299
+ if (!upstream.ok) {
4300
+ return proxyError(upstream, logger);
4301
+ }
4302
+ logUpstreamSuccess(logger, "/responses", upstream.status);
4303
+ const isSse = isStreamingResponse(upstream);
4304
+ const text = await upstream.text();
4305
+ const model = normalizeRequestedModel(json.model);
4306
+ recordResponseTextUsage(text, isSse, model, recordTokens, recordExtraction);
4307
+ if (json.stream === true) {
4308
+ return textResponse(responsesCompactionSseText(text, isSse, model), "text/event-stream");
4309
+ }
4310
+ return jsonResponse(responsesCompactionResponse(text, isSse, model));
4311
+ }
4123
4312
  async function responseWithObservedUsage(response, fallbackModel, recordTokens, signal, bufferBody, recordExtraction) {
4124
4313
  const isSse = isStreamingResponse(response);
4125
4314
  if (bufferBody && response.body) {
@@ -4228,6 +4417,15 @@ function jsonResponse(body, status = 200) {
4228
4417
  status
4229
4418
  });
4230
4419
  }
4420
+ function textResponse(body, contentType, status = 200) {
4421
+ return new Response(body, {
4422
+ headers: {
4423
+ ...corsHeaders(),
4424
+ "content-type": `${contentType}; charset=utf-8`
4425
+ },
4426
+ status
4427
+ });
4428
+ }
4231
4429
  function jsonError(status, code, message) {
4232
4430
  return jsonResponse(
4233
4431
  {