@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.mjs CHANGED
@@ -562,16 +562,21 @@ var OpenAICompatibleProvider = class {
562
562
  return out;
563
563
  }
564
564
  /**
565
- * Sends a message and returns the complete response.
565
+ * Build the Chat Completions request body — the ONE build both
566
+ * {@link sendMessage} and {@link streamMessage} call, so the streaming and
567
+ * non-streaming shapes cannot drift apart. Maps Quilltap messages to the
568
+ * OpenAI wire format (tool results and assistant tool calls included),
569
+ * applies {@link applySystemMessagePolicy}, assembles the body literal, and
570
+ * finishes with {@link applyProfileParams}.
566
571
  *
567
- * @param params - LLM parameters including messages, model, and settings
568
- * @param apiKey - API key for authentication
569
- * @returns Complete LLM response with content and usage statistics
572
+ * Subclasses that keep the base class's send/stream loops can override this
573
+ * to reshape the body in one place.
574
+ *
575
+ * @param params - LLM parameters for the request
576
+ * @param stream - Whether the body is for a streaming request; adds
577
+ * `stream: true` and `stream_options: { include_usage: true }`
570
578
  */
571
- async sendMessage(params, apiKey) {
572
- this.validateApiKeyRequirement(apiKey);
573
- const attachmentResults = this.collectAttachmentFailures(params);
574
- const client = this.createClient(apiKey);
579
+ buildRequestBody(params, stream) {
575
580
  const mappedMessages = params.messages.filter((m) => {
576
581
  if (m.role === "tool" && !m.toolCallId) return false;
577
582
  return true;
@@ -607,12 +612,30 @@ var OpenAICompatibleProvider = class {
607
612
  max_tokens: params.maxTokens ?? 4096,
608
613
  top_p: params.topP ?? 1,
609
614
  stop: params.stop,
615
+ // Spread here — not appended after the literal — so the streaming keys
616
+ // keep their historical position between `stop` and `user` and the
617
+ // serialized body stays byte-identical.
618
+ ...stream ? { stream: true, stream_options: { include_usage: true } } : {},
610
619
  ...params.cacheKey ? { user: params.cacheKey } : {},
611
620
  // Tools arrive only once the runtime gate (the profile's "Allow tool
612
621
  // use") has passed, so no separate guard is needed here.
613
622
  ...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
614
623
  };
615
624
  this.applyProfileParams(body, params);
625
+ return body;
626
+ }
627
+ /**
628
+ * Sends a message and returns the complete response.
629
+ *
630
+ * @param params - LLM parameters including messages, model, and settings
631
+ * @param apiKey - API key for authentication
632
+ * @returns Complete LLM response with content and usage statistics
633
+ */
634
+ async sendMessage(params, apiKey) {
635
+ this.validateApiKeyRequirement(apiKey);
636
+ const attachmentResults = this.collectAttachmentFailures(params);
637
+ const client = this.createClient(apiKey);
638
+ const body = this.buildRequestBody(params, false);
616
639
  try {
617
640
  const response = await client.chat.completions.create(
618
641
  body,
@@ -652,47 +675,7 @@ var OpenAICompatibleProvider = class {
652
675
  this.validateApiKeyRequirement(apiKey);
653
676
  const attachmentResults = this.collectAttachmentFailures(params);
654
677
  const client = this.createClient(apiKey);
655
- const mappedMessages = params.messages.filter((m) => {
656
- if (m.role === "tool" && !m.toolCallId) return false;
657
- return true;
658
- }).map((m) => {
659
- if (m.role === "tool" && m.toolCallId) {
660
- return {
661
- role: "tool",
662
- tool_call_id: m.toolCallId,
663
- content: m.content
664
- };
665
- }
666
- if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
667
- return {
668
- role: "assistant",
669
- content: m.content || null,
670
- tool_calls: m.toolCalls.map((tc) => ({
671
- id: tc.id,
672
- type: tc.type,
673
- function: tc.function
674
- }))
675
- };
676
- }
677
- return {
678
- role: m.role,
679
- content: m.content
680
- };
681
- });
682
- const messages = this.applySystemMessagePolicy(mappedMessages);
683
- const body = {
684
- model: params.model,
685
- messages,
686
- temperature: params.temperature ?? 0.7,
687
- max_tokens: params.maxTokens ?? 4096,
688
- top_p: params.topP ?? 1,
689
- stop: params.stop,
690
- stream: true,
691
- stream_options: { include_usage: true },
692
- ...params.cacheKey ? { user: params.cacheKey } : {},
693
- ...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
694
- };
695
- this.applyProfileParams(body, params);
678
+ const body = this.buildRequestBody(params, true);
696
679
  try {
697
680
  const stream = await client.chat.completions.create(
698
681
  body,
@@ -929,7 +912,7 @@ function getBuiltinToolNames() {
929
912
  }
930
913
 
931
914
  // src/host-rewrite.ts
932
- import { readFileSync, existsSync, statSync } from "fs";
915
+ import { existsSync, statSync } from "fs";
933
916
  var LOCALHOST_HOSTS = /* @__PURE__ */ new Set([
934
917
  "localhost",
935
918
  "127.0.0.1",
@@ -938,9 +921,6 @@ var LOCALHOST_HOSTS = /* @__PURE__ */ new Set([
938
921
  ]);
939
922
  var cachedGatewayHost;
940
923
  var rewriteLogger = createPluginLogger("host-rewrite");
941
- function isLimaEnvironment() {
942
- return process.env.LIMA_CONTAINER === "true";
943
- }
944
924
  function isDockerEnvironment() {
945
925
  if (process.env.DOCKER_CONTAINER === "true") {
946
926
  return true;
@@ -958,7 +938,7 @@ function isDockerEnvironment() {
958
938
  return false;
959
939
  }
960
940
  function isVMEnvironment() {
961
- return isDockerEnvironment() || isLimaEnvironment();
941
+ return isDockerEnvironment() || !!process.env.QUILLTAP_HOST_IP;
962
942
  }
963
943
  function resolveHostGateway() {
964
944
  if (cachedGatewayHost !== void 0) {
@@ -970,65 +950,11 @@ function resolveHostGateway() {
970
950
  cachedGatewayHost = envIP;
971
951
  return cachedGatewayHost;
972
952
  }
973
- if (isDockerEnvironment() && !isLimaEnvironment()) {
953
+ if (isDockerEnvironment()) {
974
954
  rewriteLogger.info("Docker environment detected \u2014 using host.docker.internal as gateway hostname");
975
955
  cachedGatewayHost = "host.docker.internal";
976
956
  return cachedGatewayHost;
977
957
  }
978
- try {
979
- if (existsSync("/proc/sys/fs/binfmt_misc/WSLInterop")) {
980
- const resolv = readFileSync("/etc/resolv.conf", "utf-8");
981
- for (const line of resolv.split("\n")) {
982
- const trimmed = line.trim();
983
- if (trimmed.startsWith("#") || trimmed === "") continue;
984
- const match = trimmed.match(/^nameserver\s+(\S+)/);
985
- if (match) {
986
- const ip = match[1];
987
- rewriteLogger.info("WSL2 host IP from /etc/resolv.conf nameserver", { ip });
988
- cachedGatewayHost = ip;
989
- return cachedGatewayHost;
990
- }
991
- }
992
- }
993
- } catch {
994
- rewriteLogger.debug("Could not detect WSL2 or read /etc/resolv.conf");
995
- }
996
- try {
997
- const routeTable = readFileSync("/proc/net/route", "utf-8");
998
- for (const line of routeTable.split("\n").slice(1)) {
999
- const fields = line.trim().split(" ");
1000
- if (fields.length >= 3 && fields[1] === "00000000") {
1001
- const hexGateway = fields[2];
1002
- const ip = [
1003
- parseInt(hexGateway.substring(6, 8), 16),
1004
- parseInt(hexGateway.substring(4, 6), 16),
1005
- parseInt(hexGateway.substring(2, 4), 16),
1006
- parseInt(hexGateway.substring(0, 2), 16)
1007
- ].join(".");
1008
- rewriteLogger.info("Host gateway IP from /proc/net/route", { ip });
1009
- cachedGatewayHost = ip;
1010
- return cachedGatewayHost;
1011
- }
1012
- }
1013
- } catch {
1014
- rewriteLogger.debug("Could not read /proc/net/route for default gateway lookup");
1015
- }
1016
- try {
1017
- const hosts = readFileSync("/etc/hosts", "utf-8");
1018
- for (const line of hosts.split("\n")) {
1019
- const trimmed = line.trim();
1020
- if (trimmed.startsWith("#") || trimmed === "") continue;
1021
- const parts = trimmed.split(/\s+/);
1022
- if (parts.length >= 2 && parts.slice(1).includes("host.docker.internal")) {
1023
- const ip = parts[0];
1024
- rewriteLogger.info("Host gateway IP from /etc/hosts (host.docker.internal)", { ip });
1025
- cachedGatewayHost = ip;
1026
- return cachedGatewayHost;
1027
- }
1028
- }
1029
- } catch {
1030
- rewriteLogger.debug("Could not read /etc/hosts for host.docker.internal lookup");
1031
- }
1032
958
  rewriteLogger.warn("Could not resolve host gateway \u2014 localhost URLs will not be rewritten");
1033
959
  cachedGatewayHost = null;
1034
960
  return cachedGatewayHost;
@@ -1061,7 +987,7 @@ function rewriteLocalhostUrl(url) {
1061
987
  }
1062
988
 
1063
989
  // src/version.generated.ts
1064
- var PLUGIN_UTILS_VERSION = "2.4.0";
990
+ var PLUGIN_UTILS_VERSION = "2.6.0";
1065
991
  export {
1066
992
  BUILTIN_TOOL_NAMES,
1067
993
  DEFAULT_REQUEST_TIMEOUT_MS,