@openhoo/hoopilot 0.7.3 → 0.7.4

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
@@ -571,6 +571,12 @@ function isLogLevel(value) {
571
571
 
572
572
  // src/openai.ts
573
573
  var DEFAULT_MODEL = "gpt-4.1";
574
+ var OpenAICompatibilityError = class extends Error {
575
+ constructor(message) {
576
+ super(message);
577
+ this.name = "OpenAICompatibilityError";
578
+ }
579
+ };
574
580
  function normalizeChatCompletionRequest(request) {
575
581
  return removeUndefined({
576
582
  ...request,
@@ -578,13 +584,22 @@ function normalizeChatCompletionRequest(request) {
578
584
  });
579
585
  }
580
586
  function completionsRequestToChatCompletion(request) {
587
+ assertSupportedLegacyCompletionRequest(request);
581
588
  return removeUndefined({
589
+ frequency_penalty: request.frequency_penalty,
590
+ logit_bias: request.logit_bias,
582
591
  max_tokens: request.max_tokens,
583
- messages: [{ content: promptToText(request.prompt), role: "user" }],
592
+ messages: [{ content: legacyPromptToText(request.prompt), role: "user" }],
584
593
  model: normalizeRequestedModel(request.model),
594
+ n: request.n,
595
+ presence_penalty: request.presence_penalty,
596
+ seed: request.seed,
597
+ stop: request.stop,
585
598
  stream: request.stream === true,
599
+ stream_options: request.stream_options,
586
600
  temperature: request.temperature,
587
- top_p: request.top_p
601
+ top_p: request.top_p,
602
+ user: request.user
588
603
  });
589
604
  }
590
605
  function normalizeRequestedModel(model) {
@@ -592,21 +607,21 @@ function normalizeRequestedModel(model) {
592
607
  return requested || DEFAULT_MODEL;
593
608
  }
594
609
  function chatCompletionToCompletion(completion) {
595
- const choice = firstChoice(completion);
596
- const message = asRecord(choice.message);
597
610
  return removeUndefined({
598
- choices: [
599
- {
611
+ choices: completionChoices(completion).map((choice, index) => {
612
+ const message = asRecord(choice.message);
613
+ return {
600
614
  finish_reason: choice.finish_reason ?? "stop",
601
- index: 0,
602
- logprobs: null,
603
- text: contentToText(message.content)
604
- }
605
- ],
615
+ index: typeof choice.index === "number" ? choice.index : index,
616
+ logprobs: choice.logprobs ?? null,
617
+ text: contentToText(choice.text) || contentToText(message.content)
618
+ };
619
+ }),
606
620
  created: completion.created ?? epochSeconds(),
607
621
  id: completion.id ?? `cmpl_${randomId()}`,
608
622
  model: completion.model ?? DEFAULT_MODEL,
609
623
  object: "text_completion",
624
+ system_fingerprint: completion.system_fingerprint,
610
625
  usage: completion.usage
611
626
  });
612
627
  }
@@ -679,11 +694,38 @@ function fallbackModels() {
679
694
  }
680
695
  ];
681
696
  }
682
- function promptToText(prompt) {
683
- if (Array.isArray(prompt)) {
684
- return prompt.map((item) => contentToText(item)).join("\n");
697
+ function legacyPromptToText(prompt) {
698
+ if (typeof prompt === "string") {
699
+ return prompt;
700
+ }
701
+ if (Array.isArray(prompt) && prompt.length === 1 && typeof prompt[0] === "string") {
702
+ return prompt[0];
703
+ }
704
+ throw new OpenAICompatibilityError(
705
+ "Hoopilot legacy completions compatibility supports exactly one string prompt per request."
706
+ );
707
+ }
708
+ function assertSupportedLegacyCompletionRequest(request) {
709
+ if (request.echo === true) {
710
+ throw new OpenAICompatibilityError(
711
+ "Hoopilot legacy completions compatibility does not support echo=true."
712
+ );
713
+ }
714
+ if (typeof request.best_of === "number" && request.best_of > 1) {
715
+ throw new OpenAICompatibilityError(
716
+ "Hoopilot legacy completions compatibility does not support best_of greater than 1."
717
+ );
718
+ }
719
+ if (typeof request.logprobs === "number" && request.logprobs > 0) {
720
+ throw new OpenAICompatibilityError(
721
+ "Hoopilot legacy completions compatibility does not support legacy logprobs."
722
+ );
723
+ }
724
+ if (contentToText(request.suffix)) {
725
+ throw new OpenAICompatibilityError(
726
+ "Hoopilot legacy completions compatibility does not support suffix."
727
+ );
685
728
  }
686
- return contentToText(prompt);
687
729
  }
688
730
  function contentToText(content) {
689
731
  if (typeof content === "string") {
@@ -741,9 +783,9 @@ function firstNumber(...values) {
741
783
  }
742
784
  return void 0;
743
785
  }
744
- function firstChoice(completion) {
786
+ function completionChoices(completion) {
745
787
  const choices = Array.isArray(completion.choices) ? completion.choices : [];
746
- return asRecord(choices[0]);
788
+ return choices.map((choice) => asRecord(choice));
747
789
  }
748
790
  function processCompletionSseBlock(block, enqueue, markTerminal) {
749
791
  let event = "message";
@@ -775,25 +817,28 @@ function processCompletionSseBlock(block, enqueue, markTerminal) {
775
817
  enqueue({ error });
776
818
  return;
777
819
  }
778
- const choice = firstChoice(parsed);
779
- const delta = asRecord(choice.delta);
780
- const text = contentToText(delta.content);
781
- const finishReason = choice.finish_reason ?? null;
820
+ const choices = completionChoices(parsed).map((choice, index) => {
821
+ const delta = asRecord(choice.delta);
822
+ const text = contentToText(delta.content);
823
+ const finishReason = choice.finish_reason ?? null;
824
+ if (!text && finishReason === null) {
825
+ return void 0;
826
+ }
827
+ return {
828
+ finish_reason: finishReason,
829
+ index: typeof choice.index === "number" ? choice.index : index,
830
+ logprobs: choice.logprobs ?? null,
831
+ text
832
+ };
833
+ }).filter((choice) => choice !== void 0);
782
834
  const usage = asRecord(parsed.usage);
783
835
  const hasUsage = Object.keys(usage).length > 0;
784
- if (!text && finishReason === null && !hasUsage) {
836
+ if (choices.length === 0 && !hasUsage) {
785
837
  return;
786
838
  }
787
839
  enqueue(
788
840
  removeUndefined({
789
- choices: text || finishReason !== null ? [
790
- {
791
- finish_reason: finishReason,
792
- index: typeof choice.index === "number" ? choice.index : 0,
793
- logprobs: null,
794
- text
795
- }
796
- ] : [],
841
+ choices,
797
842
  created: typeof parsed.created === "number" ? parsed.created : epochSeconds(),
798
843
  id: contentToText(parsed.id) || `cmpl_${randomId()}`,
799
844
  model: contentToText(parsed.model) || DEFAULT_MODEL,
@@ -1312,6 +1357,12 @@ function createHoopilotHandler(options = {}) {
1312
1357
  "request body was invalid json"
1313
1358
  );
1314
1359
  return finish(jsonError(400, "invalid_request_error", message));
1360
+ } else if (error instanceof OpenAICompatibilityError) {
1361
+ requestLogger.warn(
1362
+ { err: errorDetails(error), event: "http.request.failed" },
1363
+ "request body used unsupported OpenAI compatibility fields"
1364
+ );
1365
+ return finish(jsonError(400, "invalid_request_error", message));
1315
1366
  } else if (error instanceof RequestBodyTooLargeError) {
1316
1367
  requestLogger.warn(
1317
1368
  { err: errorDetails(error), event: "http.request.failed" },