@quilltap/plugin-utils 2.4.0 → 2.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to @quilltap/plugin-utils will be documented in this file.
4
4
 
5
+ ## [2.5.0] - 2026-08-26
6
+
7
+ ### Added
8
+
9
+ - `OpenAICompatibleProvider` gained a `protected buildRequestBody(params, stream)` — the one Chat Completions body build both `sendMessage` and `streamMessage` now call, replacing two byte-identical copies of the message mapping and body literal. Behavior-preserving: the streaming keys (`stream`, `stream_options`) keep their historical position between `stop` and `user`, so the serialized body is unchanged. Subclasses that keep the base send/stream loops get a single override point for reshaping the body.
10
+
5
11
  ## [2.4.0] - 2026-08-19
6
12
 
7
13
  ### Added
package/dist/index.d.mts CHANGED
@@ -124,6 +124,6 @@ declare function getQuilltapUserAgent(): string;
124
124
  * Version of the plugin-utils package.
125
125
  * Can be used at runtime to check compatibility.
126
126
  */
127
- declare const PLUGIN_UTILS_VERSION = "2.4.0";
127
+ declare const PLUGIN_UTILS_VERSION = "2.5.0";
128
128
 
129
129
  export { BUILTIN_TOOL_NAMES, PLUGIN_UTILS_VERSION, __clearQuilltapVersion, __injectQuilltapVersion, getBuiltinToolNames, getQuilltapUserAgent, getQuilltapVersion };
package/dist/index.d.ts CHANGED
@@ -124,6 +124,6 @@ declare function getQuilltapUserAgent(): string;
124
124
  * Version of the plugin-utils package.
125
125
  * Can be used at runtime to check compatibility.
126
126
  */
127
- declare const PLUGIN_UTILS_VERSION = "2.4.0";
127
+ declare const PLUGIN_UTILS_VERSION = "2.5.0";
128
128
 
129
129
  export { BUILTIN_TOOL_NAMES, PLUGIN_UTILS_VERSION, __clearQuilltapVersion, __injectQuilltapVersion, getBuiltinToolNames, getQuilltapUserAgent, getQuilltapVersion };
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,
@@ -1138,7 +1121,7 @@ function rewriteLocalhostUrl(url) {
1138
1121
  }
1139
1122
 
1140
1123
  // src/version.generated.ts
1141
- var PLUGIN_UTILS_VERSION = "2.4.0";
1124
+ var PLUGIN_UTILS_VERSION = "2.5.0";
1142
1125
  // Annotate the CommonJS export names for ESM import in node:
1143
1126
  0 && (module.exports = {
1144
1127
  BUILTIN_TOOL_NAMES,