@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 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 output = isSse ? compactionOutputFromResponsesSse(upstreamText) : compactionOutputFromResponse(asRecord(safeJsonParse(upstreamText)));
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 compactionOutputFromResponse(response) {
684
- if (Array.isArray(response.output) && response.output.length > 0) {
685
- return response.output;
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 = contentToText(response.output_text);
688
- return text ? [messageOutputItem(text)] : [];
786
+ const text = outputText(output);
787
+ if (text) {
788
+ return text;
789
+ }
790
+ return contentToText(response.output_text);
689
791
  }
690
- function compactionOutputFromResponsesSse(text) {
792
+ function compactionSummaryTextFromResponsesSse(text) {
691
793
  let deltas = "";
692
- let completedOutput;
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
- const response = asRecord(record.response);
704
- if (Array.isArray(response.output)) {
705
- completedOutput = response.output;
706
- }
805
+ completedResponse = asRecord(record.response);
707
806
  }
708
807
  }
709
- if (completedOutput && completedOutput.length > 0) {
710
- return completedOutput;
808
+ if (completedResponse) {
809
+ const summary = compactionSummaryTextFromResponse(completedResponse);
810
+ if (summary) {
811
+ return summary;
812
+ }
711
813
  }
712
- return deltas ? [messageOutputItem(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 messageOutputItem(text, id = `msg_${randomId()}`) {
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
- annotations: [],
881
- text,
882
- type: "output_text"
988
+ text: `${COMPACTION_SUMMARY_PREFIX}
989
+ ${text}`,
990
+ type: "input_text"
883
991
  }
884
992
  ],
885
993
  id,
886
- role: "assistant",
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
- return removeUndefined({
1023
- input: anthropicMessagesToResponsesInput(request.messages),
1024
- instructions: anthropicSystemToInstructions(request.system),
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(encodeSse(event, data)));
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(encodeSse(event, data));
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
- text,
1156
- type: role === "assistant" ? "output_text" : "input_text"
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
- arguments: JSON.stringify(asRecord(part.input)),
1174
- call_id: textValue(part.id) || `call_${randomId()}`,
1175
- name: textValue(part.name),
1176
- type: "function_call"
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
- call_id: textValue(part.tool_use_id),
1184
- output: anthropicToolResultOutput(part.content),
1185
- type: "function_call_output"
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 anthropicSystemToInstructions(system) {
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
- const text = system.map((part) => textValue(asRecord(part).text) || textValue(part)).filter(Boolean).join("\n");
1277
- return text || void 0;
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 outputText = textValue(response.output_text);
1365
- if (outputText) {
1366
- content.push({ text: outputText, type: "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 encodeSse(event, data) {
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
- const upstream = await client.responses(body, request.signal);
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
  {