@quilltap/plugin-utils 2.4.0 → 2.6.0

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
@@ -639,16 +639,21 @@ var OpenAICompatibleProvider = class {
639
639
  return out;
640
640
  }
641
641
  /**
642
- * Sends a message and returns the complete response.
642
+ * Build the Chat Completions request body — the ONE build both
643
+ * {@link sendMessage} and {@link streamMessage} call, so the streaming and
644
+ * non-streaming shapes cannot drift apart. Maps Quilltap messages to the
645
+ * OpenAI wire format (tool results and assistant tool calls included),
646
+ * applies {@link applySystemMessagePolicy}, assembles the body literal, and
647
+ * finishes with {@link applyProfileParams}.
643
648
  *
644
- * @param params - LLM parameters including messages, model, and settings
645
- * @param apiKey - API key for authentication
646
- * @returns Complete LLM response with content and usage statistics
649
+ * Subclasses that keep the base class's send/stream loops can override this
650
+ * to reshape the body in one place.
651
+ *
652
+ * @param params - LLM parameters for the request
653
+ * @param stream - Whether the body is for a streaming request; adds
654
+ * `stream: true` and `stream_options: { include_usage: true }`
647
655
  */
648
- async sendMessage(params, apiKey) {
649
- this.validateApiKeyRequirement(apiKey);
650
- const attachmentResults = this.collectAttachmentFailures(params);
651
- const client = this.createClient(apiKey);
656
+ buildRequestBody(params, stream) {
652
657
  const mappedMessages = params.messages.filter((m) => {
653
658
  if (m.role === "tool" && !m.toolCallId) return false;
654
659
  return true;
@@ -684,12 +689,30 @@ var OpenAICompatibleProvider = class {
684
689
  max_tokens: params.maxTokens ?? 4096,
685
690
  top_p: params.topP ?? 1,
686
691
  stop: params.stop,
692
+ // Spread here — not appended after the literal — so the streaming keys
693
+ // keep their historical position between `stop` and `user` and the
694
+ // serialized body stays byte-identical.
695
+ ...stream ? { stream: true, stream_options: { include_usage: true } } : {},
687
696
  ...params.cacheKey ? { user: params.cacheKey } : {},
688
697
  // Tools arrive only once the runtime gate (the profile's "Allow tool
689
698
  // use") has passed, so no separate guard is needed here.
690
699
  ...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
691
700
  };
692
701
  this.applyProfileParams(body, params);
702
+ return body;
703
+ }
704
+ /**
705
+ * Sends a message and returns the complete response.
706
+ *
707
+ * @param params - LLM parameters including messages, model, and settings
708
+ * @param apiKey - API key for authentication
709
+ * @returns Complete LLM response with content and usage statistics
710
+ */
711
+ async sendMessage(params, apiKey) {
712
+ this.validateApiKeyRequirement(apiKey);
713
+ const attachmentResults = this.collectAttachmentFailures(params);
714
+ const client = this.createClient(apiKey);
715
+ const body = this.buildRequestBody(params, false);
693
716
  try {
694
717
  const response = await client.chat.completions.create(
695
718
  body,
@@ -729,47 +752,7 @@ var OpenAICompatibleProvider = class {
729
752
  this.validateApiKeyRequirement(apiKey);
730
753
  const attachmentResults = this.collectAttachmentFailures(params);
731
754
  const client = this.createClient(apiKey);
732
- const mappedMessages = params.messages.filter((m) => {
733
- if (m.role === "tool" && !m.toolCallId) return false;
734
- return true;
735
- }).map((m) => {
736
- if (m.role === "tool" && m.toolCallId) {
737
- return {
738
- role: "tool",
739
- tool_call_id: m.toolCallId,
740
- content: m.content
741
- };
742
- }
743
- if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
744
- return {
745
- role: "assistant",
746
- content: m.content || null,
747
- tool_calls: m.toolCalls.map((tc) => ({
748
- id: tc.id,
749
- type: tc.type,
750
- function: tc.function
751
- }))
752
- };
753
- }
754
- return {
755
- role: m.role,
756
- content: m.content
757
- };
758
- });
759
- const messages = this.applySystemMessagePolicy(mappedMessages);
760
- const body = {
761
- model: params.model,
762
- messages,
763
- temperature: params.temperature ?? 0.7,
764
- max_tokens: params.maxTokens ?? 4096,
765
- top_p: params.topP ?? 1,
766
- stop: params.stop,
767
- stream: true,
768
- stream_options: { include_usage: true },
769
- ...params.cacheKey ? { user: params.cacheKey } : {},
770
- ...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
771
- };
772
- this.applyProfileParams(body, params);
755
+ const body = this.buildRequestBody(params, true);
773
756
  try {
774
757
  const stream = await client.chat.completions.create(
775
758
  body,
@@ -1015,9 +998,6 @@ var LOCALHOST_HOSTS = /* @__PURE__ */ new Set([
1015
998
  ]);
1016
999
  var cachedGatewayHost;
1017
1000
  var rewriteLogger = createPluginLogger("host-rewrite");
1018
- function isLimaEnvironment() {
1019
- return process.env.LIMA_CONTAINER === "true";
1020
- }
1021
1001
  function isDockerEnvironment() {
1022
1002
  if (process.env.DOCKER_CONTAINER === "true") {
1023
1003
  return true;
@@ -1035,7 +1015,7 @@ function isDockerEnvironment() {
1035
1015
  return false;
1036
1016
  }
1037
1017
  function isVMEnvironment() {
1038
- return isDockerEnvironment() || isLimaEnvironment();
1018
+ return isDockerEnvironment() || !!process.env.QUILLTAP_HOST_IP;
1039
1019
  }
1040
1020
  function resolveHostGateway() {
1041
1021
  if (cachedGatewayHost !== void 0) {
@@ -1047,65 +1027,11 @@ function resolveHostGateway() {
1047
1027
  cachedGatewayHost = envIP;
1048
1028
  return cachedGatewayHost;
1049
1029
  }
1050
- if (isDockerEnvironment() && !isLimaEnvironment()) {
1030
+ if (isDockerEnvironment()) {
1051
1031
  rewriteLogger.info("Docker environment detected \u2014 using host.docker.internal as gateway hostname");
1052
1032
  cachedGatewayHost = "host.docker.internal";
1053
1033
  return cachedGatewayHost;
1054
1034
  }
1055
- try {
1056
- if ((0, import_node_fs.existsSync)("/proc/sys/fs/binfmt_misc/WSLInterop")) {
1057
- const resolv = (0, import_node_fs.readFileSync)("/etc/resolv.conf", "utf-8");
1058
- for (const line of resolv.split("\n")) {
1059
- const trimmed = line.trim();
1060
- if (trimmed.startsWith("#") || trimmed === "") continue;
1061
- const match = trimmed.match(/^nameserver\s+(\S+)/);
1062
- if (match) {
1063
- const ip = match[1];
1064
- rewriteLogger.info("WSL2 host IP from /etc/resolv.conf nameserver", { ip });
1065
- cachedGatewayHost = ip;
1066
- return cachedGatewayHost;
1067
- }
1068
- }
1069
- }
1070
- } catch {
1071
- rewriteLogger.debug("Could not detect WSL2 or read /etc/resolv.conf");
1072
- }
1073
- try {
1074
- const routeTable = (0, import_node_fs.readFileSync)("/proc/net/route", "utf-8");
1075
- for (const line of routeTable.split("\n").slice(1)) {
1076
- const fields = line.trim().split(" ");
1077
- if (fields.length >= 3 && fields[1] === "00000000") {
1078
- const hexGateway = fields[2];
1079
- const ip = [
1080
- parseInt(hexGateway.substring(6, 8), 16),
1081
- parseInt(hexGateway.substring(4, 6), 16),
1082
- parseInt(hexGateway.substring(2, 4), 16),
1083
- parseInt(hexGateway.substring(0, 2), 16)
1084
- ].join(".");
1085
- rewriteLogger.info("Host gateway IP from /proc/net/route", { ip });
1086
- cachedGatewayHost = ip;
1087
- return cachedGatewayHost;
1088
- }
1089
- }
1090
- } catch {
1091
- rewriteLogger.debug("Could not read /proc/net/route for default gateway lookup");
1092
- }
1093
- try {
1094
- const hosts = (0, import_node_fs.readFileSync)("/etc/hosts", "utf-8");
1095
- for (const line of hosts.split("\n")) {
1096
- const trimmed = line.trim();
1097
- if (trimmed.startsWith("#") || trimmed === "") continue;
1098
- const parts = trimmed.split(/\s+/);
1099
- if (parts.length >= 2 && parts.slice(1).includes("host.docker.internal")) {
1100
- const ip = parts[0];
1101
- rewriteLogger.info("Host gateway IP from /etc/hosts (host.docker.internal)", { ip });
1102
- cachedGatewayHost = ip;
1103
- return cachedGatewayHost;
1104
- }
1105
- }
1106
- } catch {
1107
- rewriteLogger.debug("Could not read /etc/hosts for host.docker.internal lookup");
1108
- }
1109
1035
  rewriteLogger.warn("Could not resolve host gateway \u2014 localhost URLs will not be rewritten");
1110
1036
  cachedGatewayHost = null;
1111
1037
  return cachedGatewayHost;
@@ -1138,7 +1064,7 @@ function rewriteLocalhostUrl(url) {
1138
1064
  }
1139
1065
 
1140
1066
  // src/version.generated.ts
1141
- var PLUGIN_UTILS_VERSION = "2.4.0";
1067
+ var PLUGIN_UTILS_VERSION = "2.6.0";
1142
1068
  // Annotate the CommonJS export names for ESM import in node:
1143
1069
  0 && (module.exports = {
1144
1070
  BUILTIN_TOOL_NAMES,