@quilltap/plugin-utils 2.3.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 +13 -0
- package/README.md +31 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +72 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -50
- package/dist/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +80 -1
- package/dist/providers/index.d.ts +80 -1
- package/dist/providers/index.js +71 -49
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +70 -49
- package/dist/providers/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
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
|
+
|
|
11
|
+
## [2.4.0] - 2026-08-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `collapseLeadingSystemMessages(messages)` — folds a run of consecutive leading `system` messages into one, joining the contents with a blank line. Arrays with fewer than two leading system messages come back untouched (same reference).
|
|
16
|
+
- `OpenAICompatibleProvider` gained `acceptsRepeatedSystemMessages` (default `true`, so every existing subclass is byte-identical on the wire) and applies it through `applySystemMessagePolicy` in both the streaming and non-streaming body builds. A subclass pointing at a local runtime sets it `false`: the model's own chat template is what runs there, and the Qwen family — plus several Llama- and Gemma-derived templates — `raise_exception` on any system message after index 0, rejecting the whole request. Quilltap emits up to three leading system blocks so its cache breakpoints survive, so every non-opening turn against such a model failed outright. (Quilltap bug 82.)
|
|
17
|
+
|
|
5
18
|
## [2.3.0] - 2026-08-15
|
|
6
19
|
|
|
7
20
|
### Added
|
package/README.md
CHANGED
|
@@ -150,6 +150,37 @@ This gives you a complete `LLMProvider` implementation with:
|
|
|
150
150
|
npm install openai
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
### Profile Parameters
|
|
154
|
+
|
|
155
|
+
A connection profile's `parameters` blob is free-form JSON, handed to the
|
|
156
|
+
provider as `LLMParams.profileParameters`. A provider that never reads it drops
|
|
157
|
+
every setting the user saved, in silence — so forwarding is opt-in but explicit,
|
|
158
|
+
and always allow-listed:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { applyProfileParameters } from '@quilltap/plugin-utils';
|
|
162
|
+
|
|
163
|
+
const MY_ALLOWLIST = ['top_k', 'min_p', 'reasoning_effort'] as const;
|
|
164
|
+
|
|
165
|
+
applyProfileParameters(body, params, MY_ALLOWLIST, (key, value, callParams, target) => {
|
|
166
|
+
// Return the value to send, or `undefined` to omit the key.
|
|
167
|
+
// Write into `target` to send it under a different key entirely.
|
|
168
|
+
return value;
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`undefined`, `null` and the empty string omit the key — the empty string is what
|
|
173
|
+
the schema-driven profile editor stores for "use the model default". Never
|
|
174
|
+
spread the blob: `model`, `messages`, `stream`, `stream_options` and `tools`
|
|
175
|
+
must stay unreachable from a profile.
|
|
176
|
+
|
|
177
|
+
Subclasses of `OpenAICompatibleProvider` get this by declaring
|
|
178
|
+
`profileParamAllowlist` (empty by default, so a subclass that declares nothing
|
|
179
|
+
is byte-identical on the wire) and optionally overriding `normalizeProfileParam`;
|
|
180
|
+
providers that implement `TextProvider` directly call the function themselves.
|
|
181
|
+
The keys must match whatever the plugin's `getProviderOptionsSchema()` renders,
|
|
182
|
+
or a field the editor draws goes nowhere.
|
|
183
|
+
|
|
153
184
|
### System Prompt Plugin Utilities
|
|
154
185
|
|
|
155
186
|
Create system prompt plugins that provide character prompt templates from `.md` files:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AnthropicToolDefinition, CharacterPluginDataEntry, CharacterPluginDataMap, GoogleToolDefinition, LogContext, LogLevel, OpenAIToolDefinition, PluginLogger, ToolCall, ToolCallRequest, ToolFormatOptions, ToolResult, UniversalTool, createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';
|
|
2
2
|
export { T as ToolCallFormat, a as ToolConvertTarget, b as applyDescriptionLimit, c as convertFromAnthropicFormat, d as convertFromGoogleFormat, e as convertOpenAIToAnthropicFormat, f as convertOpenAIToGoogleFormat, g as convertToAnthropicFormat, h as convertToGoogleFormat, i as convertToolTo, j as convertToolsTo, k as detectToolCallFormat, l as hasToolCalls, p as parseAnthropicToolCalls, m as parseGoogleToolCalls, n as parseOpenAIToolCalls, o as parseToolCalls } from './converters-BPYo8xdH.mjs';
|
|
3
3
|
export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFactory, createPluginLogger, getLogLevelFromEnv, hasCoreLogger } from './logging/index.mjs';
|
|
4
|
-
export { DEFAULT_REQUEST_TIMEOUT_MS, OpenAICompatibleProvider, OpenAICompatibleProviderConfig, ProfileParamNormalizer, applyProfileParameters, buildRequestAbortSignal, buildSdkClientOptions, buildSdkRequestOptions, resolveRequestTimeoutMs } from './providers/index.mjs';
|
|
4
|
+
export { DEFAULT_REQUEST_TIMEOUT_MS, OpenAICompatibleProvider, OpenAICompatibleProviderConfig, ProfileParamNormalizer, applyProfileParameters, buildRequestAbortSignal, buildSdkClientOptions, buildSdkRequestOptions, collapseLeadingSystemMessages, resolveRequestTimeoutMs } from './providers/index.mjs';
|
|
5
5
|
export { CreateSystemPromptPluginOptions, createSystemPromptPlugin, validateSystemPromptPlugin } from './system-prompts/index.mjs';
|
|
6
6
|
export { isVMEnvironment, resolveHostGateway, rewriteLocalhostUrl } from './host-rewrite.mjs';
|
|
7
7
|
import 'openai';
|
|
@@ -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.
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AnthropicToolDefinition, CharacterPluginDataEntry, CharacterPluginDataMap, GoogleToolDefinition, LogContext, LogLevel, OpenAIToolDefinition, PluginLogger, ToolCall, ToolCallRequest, ToolFormatOptions, ToolResult, UniversalTool, createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';
|
|
2
2
|
export { T as ToolCallFormat, a as ToolConvertTarget, b as applyDescriptionLimit, c as convertFromAnthropicFormat, d as convertFromGoogleFormat, e as convertOpenAIToAnthropicFormat, f as convertOpenAIToGoogleFormat, g as convertToAnthropicFormat, h as convertToGoogleFormat, i as convertToolTo, j as convertToolsTo, k as detectToolCallFormat, l as hasToolCalls, p as parseAnthropicToolCalls, m as parseGoogleToolCalls, n as parseOpenAIToolCalls, o as parseToolCalls } from './converters-BPYo8xdH.js';
|
|
3
3
|
export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFactory, createPluginLogger, getLogLevelFromEnv, hasCoreLogger } from './logging/index.js';
|
|
4
|
-
export { DEFAULT_REQUEST_TIMEOUT_MS, OpenAICompatibleProvider, OpenAICompatibleProviderConfig, ProfileParamNormalizer, applyProfileParameters, buildRequestAbortSignal, buildSdkClientOptions, buildSdkRequestOptions, resolveRequestTimeoutMs } from './providers/index.js';
|
|
4
|
+
export { DEFAULT_REQUEST_TIMEOUT_MS, OpenAICompatibleProvider, OpenAICompatibleProviderConfig, ProfileParamNormalizer, applyProfileParameters, buildRequestAbortSignal, buildSdkClientOptions, buildSdkRequestOptions, collapseLeadingSystemMessages, resolveRequestTimeoutMs } from './providers/index.js';
|
|
5
5
|
export { CreateSystemPromptPluginOptions, createSystemPromptPlugin, validateSystemPromptPlugin } from './system-prompts/index.js';
|
|
6
6
|
export { isVMEnvironment, resolveHostGateway, rewriteLocalhostUrl } from './host-rewrite.js';
|
|
7
7
|
import 'openai';
|
|
@@ -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.
|
|
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
|
@@ -43,6 +43,7 @@ __export(src_exports, {
|
|
|
43
43
|
buildRequestAbortSignal: () => buildRequestAbortSignal,
|
|
44
44
|
buildSdkClientOptions: () => buildSdkClientOptions,
|
|
45
45
|
buildSdkRequestOptions: () => buildSdkRequestOptions,
|
|
46
|
+
collapseLeadingSystemMessages: () => collapseLeadingSystemMessages,
|
|
46
47
|
convertFromAnthropicFormat: () => convertFromAnthropicFormat,
|
|
47
48
|
convertFromGoogleFormat: () => convertFromGoogleFormat,
|
|
48
49
|
convertOpenAIToAnthropicFormat: () => convertOpenAIToAnthropicFormat,
|
|
@@ -443,6 +444,21 @@ function applyProfileParameters(body, params, allowlist, normalize) {
|
|
|
443
444
|
}
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
// src/providers/system-messages.ts
|
|
448
|
+
function collapseLeadingSystemMessages(messages) {
|
|
449
|
+
let runLength = 0;
|
|
450
|
+
while (runLength < messages.length && messages[runLength].role === "system") {
|
|
451
|
+
runLength++;
|
|
452
|
+
}
|
|
453
|
+
if (runLength < 2) return messages;
|
|
454
|
+
const run = messages.slice(0, runLength);
|
|
455
|
+
const merged = {
|
|
456
|
+
...run[0],
|
|
457
|
+
content: run.map((m) => m.content ?? "").filter((c) => c.length > 0).join("\n\n")
|
|
458
|
+
};
|
|
459
|
+
return [merged, ...messages.slice(runLength)];
|
|
460
|
+
}
|
|
461
|
+
|
|
446
462
|
// src/providers/openai-compatible.ts
|
|
447
463
|
var DEFAULT_MAX_RETRIES = 2;
|
|
448
464
|
var OpenAICompatibleProvider = class {
|
|
@@ -458,6 +474,18 @@ var OpenAICompatibleProvider = class {
|
|
|
458
474
|
this.supportedMimeTypes = [];
|
|
459
475
|
/** Web search is not supported */
|
|
460
476
|
this.supportsWebSearch = false;
|
|
477
|
+
/**
|
|
478
|
+
* Whether the endpoint tolerates more than one leading `system` message.
|
|
479
|
+
*
|
|
480
|
+
* True for every hosted service — and so the default, which keeps the
|
|
481
|
+
* outbound bytes of OpenRouter, Z.AI and every other subclass exactly as they
|
|
482
|
+
* were. A subclass that points at a local runtime applying the *model's* chat
|
|
483
|
+
* template sets this false: several template families reject a system message
|
|
484
|
+
* anywhere but index 0, and Quilltap's context builder deliberately emits up
|
|
485
|
+
* to three of them so its cache breakpoints survive. See Bug 82 and
|
|
486
|
+
* {@link collapseLeadingSystemMessages}.
|
|
487
|
+
*/
|
|
488
|
+
this.acceptsRepeatedSystemMessages = true;
|
|
461
489
|
/**
|
|
462
490
|
* Profile-parameter keys this provider forwards from
|
|
463
491
|
* `LLMParams.profileParameters` onto the request body.
|
|
@@ -485,6 +513,14 @@ var OpenAICompatibleProvider = class {
|
|
|
485
513
|
}
|
|
486
514
|
this.logger = createPluginLogger(`${this.providerName}Provider`);
|
|
487
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Apply {@link acceptsRepeatedSystemMessages} to a mapped wire-message array.
|
|
518
|
+
* Called by both body builds so the streaming and non-streaming shapes cannot
|
|
519
|
+
* drift apart.
|
|
520
|
+
*/
|
|
521
|
+
applySystemMessagePolicy(messages) {
|
|
522
|
+
return this.acceptsRepeatedSystemMessages ? messages : collapseLeadingSystemMessages(messages);
|
|
523
|
+
}
|
|
488
524
|
/**
|
|
489
525
|
* Collects attachment failures for messages with attachments.
|
|
490
526
|
* Since attachments are not supported, all attachments are marked as failed.
|
|
@@ -603,17 +639,22 @@ var OpenAICompatibleProvider = class {
|
|
|
603
639
|
return out;
|
|
604
640
|
}
|
|
605
641
|
/**
|
|
606
|
-
*
|
|
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}.
|
|
607
648
|
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
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 }`
|
|
611
655
|
*/
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const attachmentResults = this.collectAttachmentFailures(params);
|
|
615
|
-
const client = this.createClient(apiKey);
|
|
616
|
-
const messages = params.messages.filter((m) => {
|
|
656
|
+
buildRequestBody(params, stream) {
|
|
657
|
+
const mappedMessages = params.messages.filter((m) => {
|
|
617
658
|
if (m.role === "tool" && !m.toolCallId) return false;
|
|
618
659
|
return true;
|
|
619
660
|
}).map((m) => {
|
|
@@ -640,6 +681,7 @@ var OpenAICompatibleProvider = class {
|
|
|
640
681
|
content: m.content
|
|
641
682
|
};
|
|
642
683
|
});
|
|
684
|
+
const messages = this.applySystemMessagePolicy(mappedMessages);
|
|
643
685
|
const body = {
|
|
644
686
|
model: params.model,
|
|
645
687
|
messages,
|
|
@@ -647,12 +689,30 @@ var OpenAICompatibleProvider = class {
|
|
|
647
689
|
max_tokens: params.maxTokens ?? 4096,
|
|
648
690
|
top_p: params.topP ?? 1,
|
|
649
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 } } : {},
|
|
650
696
|
...params.cacheKey ? { user: params.cacheKey } : {},
|
|
651
697
|
// Tools arrive only once the runtime gate (the profile's "Allow tool
|
|
652
698
|
// use") has passed, so no separate guard is needed here.
|
|
653
699
|
...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
|
|
654
700
|
};
|
|
655
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);
|
|
656
716
|
try {
|
|
657
717
|
const response = await client.chat.completions.create(
|
|
658
718
|
body,
|
|
@@ -692,46 +752,7 @@ var OpenAICompatibleProvider = class {
|
|
|
692
752
|
this.validateApiKeyRequirement(apiKey);
|
|
693
753
|
const attachmentResults = this.collectAttachmentFailures(params);
|
|
694
754
|
const client = this.createClient(apiKey);
|
|
695
|
-
const
|
|
696
|
-
if (m.role === "tool" && !m.toolCallId) return false;
|
|
697
|
-
return true;
|
|
698
|
-
}).map((m) => {
|
|
699
|
-
if (m.role === "tool" && m.toolCallId) {
|
|
700
|
-
return {
|
|
701
|
-
role: "tool",
|
|
702
|
-
tool_call_id: m.toolCallId,
|
|
703
|
-
content: m.content
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
|
|
707
|
-
return {
|
|
708
|
-
role: "assistant",
|
|
709
|
-
content: m.content || null,
|
|
710
|
-
tool_calls: m.toolCalls.map((tc) => ({
|
|
711
|
-
id: tc.id,
|
|
712
|
-
type: tc.type,
|
|
713
|
-
function: tc.function
|
|
714
|
-
}))
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
return {
|
|
718
|
-
role: m.role,
|
|
719
|
-
content: m.content
|
|
720
|
-
};
|
|
721
|
-
});
|
|
722
|
-
const body = {
|
|
723
|
-
model: params.model,
|
|
724
|
-
messages,
|
|
725
|
-
temperature: params.temperature ?? 0.7,
|
|
726
|
-
max_tokens: params.maxTokens ?? 4096,
|
|
727
|
-
top_p: params.topP ?? 1,
|
|
728
|
-
stop: params.stop,
|
|
729
|
-
stream: true,
|
|
730
|
-
stream_options: { include_usage: true },
|
|
731
|
-
...params.cacheKey ? { user: params.cacheKey } : {},
|
|
732
|
-
...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
|
|
733
|
-
};
|
|
734
|
-
this.applyProfileParams(body, params);
|
|
755
|
+
const body = this.buildRequestBody(params, true);
|
|
735
756
|
try {
|
|
736
757
|
const stream = await client.chat.completions.create(
|
|
737
758
|
body,
|
|
@@ -1100,7 +1121,7 @@ function rewriteLocalhostUrl(url) {
|
|
|
1100
1121
|
}
|
|
1101
1122
|
|
|
1102
1123
|
// src/version.generated.ts
|
|
1103
|
-
var PLUGIN_UTILS_VERSION = "2.
|
|
1124
|
+
var PLUGIN_UTILS_VERSION = "2.5.0";
|
|
1104
1125
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1105
1126
|
0 && (module.exports = {
|
|
1106
1127
|
BUILTIN_TOOL_NAMES,
|
|
@@ -1116,6 +1137,7 @@ var PLUGIN_UTILS_VERSION = "2.3.0";
|
|
|
1116
1137
|
buildRequestAbortSignal,
|
|
1117
1138
|
buildSdkClientOptions,
|
|
1118
1139
|
buildSdkRequestOptions,
|
|
1140
|
+
collapseLeadingSystemMessages,
|
|
1119
1141
|
convertFromAnthropicFormat,
|
|
1120
1142
|
convertFromGoogleFormat,
|
|
1121
1143
|
convertOpenAIToAnthropicFormat,
|