@openhoo/hoopilot 0.9.0 → 0.9.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/README.md CHANGED
@@ -162,6 +162,14 @@ chat-completions endpoint. Before starting Codex, `codexx` checks
162
162
  not advertise the requested model. Set `CODEXX_MODEL` to one of the listed models,
163
163
  or log in with a Copilot account that has `gpt-5.5`.
164
164
 
165
+ When Codex compacts a long session it POSTs to `/v1/responses/compact` — a server-side
166
+ endpoint it expects from `OpenAI`- and Azure-named providers and for which it has no
167
+ local fallback, so an unhandled route would abort compaction. Hoopilot handles it by
168
+ running the supplied conversation through Copilot's Responses endpoint as a unary
169
+ request and returning the resulting `{ "output": [...] }` summary, so compaction works
170
+ whether Codex points at Hoopilot through `codexx` or through a plain `OPENAI_BASE_URL`
171
+ override of the built-in `openai` provider.
172
+
165
173
  If no `HOOPILOT_API_KEY` is configured, Hoopilot accepts local requests without client authentication. Binding to a non-loopback host requires `HOOPILOT_API_KEY` unless `--allow-unauthenticated` is set.
166
174
 
167
175
  ## Logging
package/dist/cli.js CHANGED
@@ -626,6 +626,48 @@ function normalizeRequestedModel(model) {
626
626
  const requested = contentToText(model).trim();
627
627
  return requested || DEFAULT_MODEL;
628
628
  }
629
+ function responsesCompactionResult(upstreamText, isSse) {
630
+ const output = isSse ? compactionOutputFromResponsesSse(upstreamText) : compactionOutputFromResponse(asRecord(safeJsonParse(upstreamText)));
631
+ return { output };
632
+ }
633
+ function compactionOutputFromResponse(response) {
634
+ if (Array.isArray(response.output) && response.output.length > 0) {
635
+ return response.output;
636
+ }
637
+ const text = contentToText(response.output_text);
638
+ return text ? [messageOutputItem(text)] : [];
639
+ }
640
+ function compactionOutputFromResponsesSse(text) {
641
+ let deltas = "";
642
+ let completedOutput;
643
+ for (const block of text.split(/\r?\n\r?\n/)) {
644
+ const data = block.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("");
645
+ if (!data || data === "[DONE]") {
646
+ continue;
647
+ }
648
+ const record = asRecord(safeJsonParse(data));
649
+ const type = contentToText(record.type);
650
+ if (type === "response.output_text.delta") {
651
+ deltas += contentToText(record.delta);
652
+ } else if (type === "response.completed" || type === "response.incomplete") {
653
+ const response = asRecord(record.response);
654
+ if (Array.isArray(response.output)) {
655
+ completedOutput = response.output;
656
+ }
657
+ }
658
+ }
659
+ if (completedOutput && completedOutput.length > 0) {
660
+ return completedOutput;
661
+ }
662
+ return deltas ? [messageOutputItem(deltas)] : [];
663
+ }
664
+ function safeJsonParse(text) {
665
+ try {
666
+ return JSON.parse(text);
667
+ } catch {
668
+ return void 0;
669
+ }
670
+ }
629
671
  function chatCompletionToCompletion(completion) {
630
672
  return removeUndefined({
631
673
  choices: completionChoices(completion).map((choice, index) => {
@@ -788,6 +830,21 @@ function contentToText(content) {
788
830
  }
789
831
  return "";
790
832
  }
833
+ function messageOutputItem(text, id = `msg_${randomId()}`) {
834
+ return {
835
+ content: [
836
+ {
837
+ annotations: [],
838
+ text,
839
+ type: "output_text"
840
+ }
841
+ ],
842
+ id,
843
+ role: "assistant",
844
+ status: "completed",
845
+ type: "message"
846
+ };
847
+ }
791
848
  function extractTokenUsage(usage) {
792
849
  const record = asRecord(usage);
793
850
  const prompt = firstNumber(record.prompt_tokens, record.input_tokens);
@@ -2165,6 +2222,11 @@ function createHoopilotHandler(options = {}) {
2165
2222
  )
2166
2223
  );
2167
2224
  }
2225
+ if (request.method === "POST" && apiPath === "/v1/responses/compact") {
2226
+ return finish(
2227
+ await handleResponsesCompact(client, metrics, recordTokens, request, requestLogger)
2228
+ );
2229
+ }
2168
2230
  if (request.method === "POST" && apiPath === "/v1/responses") {
2169
2231
  return finish(
2170
2232
  await handleResponses(
@@ -2379,6 +2441,22 @@ async function handleResponses(client, metrics, recordTokens, request, logger, b
2379
2441
  )
2380
2442
  );
2381
2443
  }
2444
+ async function handleResponsesCompact(client, metrics, recordTokens, request, logger) {
2445
+ const body = await readJson(request);
2446
+ const upstream = await client.responses(
2447
+ JSON.stringify({ ...body, stream: false }),
2448
+ request.signal
2449
+ );
2450
+ metrics.recordUpstream("/responses", upstream.ok);
2451
+ if (!upstream.ok) {
2452
+ return proxyError(upstream, logger);
2453
+ }
2454
+ logUpstreamSuccess(logger, "/responses", upstream.status);
2455
+ const isSse = isStreamingResponse(upstream);
2456
+ const text = await upstream.text();
2457
+ recordResponseTextUsage(text, isSse, normalizeRequestedModel(body.model), recordTokens);
2458
+ return jsonResponse(responsesCompactionResult(text, isSse));
2459
+ }
2382
2460
  async function responseWithObservedUsage(response, fallbackModel, recordTokens, signal, bufferBody) {
2383
2461
  const isSse = isStreamingResponse(response);
2384
2462
  if (bufferBody && response.body) {
@@ -2698,6 +2776,8 @@ function canonicalApiPath(path) {
2698
2776
  return "/v1/messages/count_tokens";
2699
2777
  case "/responses":
2700
2778
  return "/v1/responses";
2779
+ case "/responses/compact":
2780
+ return "/v1/responses/compact";
2701
2781
  case "/usage":
2702
2782
  return "/v1/usage";
2703
2783
  default:
@@ -2732,6 +2812,9 @@ function routeFor(method, path) {
2732
2812
  if (method === "POST" && path === "/v1/completions") {
2733
2813
  return "completions";
2734
2814
  }
2815
+ if (method === "POST" && path === "/v1/responses/compact") {
2816
+ return "responses_compact";
2817
+ }
2735
2818
  if (method === "POST" && path === "/v1/responses") {
2736
2819
  return "responses";
2737
2820
  }