@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/dist/index.mjs
CHANGED
|
@@ -367,6 +367,21 @@ function applyProfileParameters(body, params, allowlist, normalize) {
|
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// src/providers/system-messages.ts
|
|
371
|
+
function collapseLeadingSystemMessages(messages) {
|
|
372
|
+
let runLength = 0;
|
|
373
|
+
while (runLength < messages.length && messages[runLength].role === "system") {
|
|
374
|
+
runLength++;
|
|
375
|
+
}
|
|
376
|
+
if (runLength < 2) return messages;
|
|
377
|
+
const run = messages.slice(0, runLength);
|
|
378
|
+
const merged = {
|
|
379
|
+
...run[0],
|
|
380
|
+
content: run.map((m) => m.content ?? "").filter((c) => c.length > 0).join("\n\n")
|
|
381
|
+
};
|
|
382
|
+
return [merged, ...messages.slice(runLength)];
|
|
383
|
+
}
|
|
384
|
+
|
|
370
385
|
// src/providers/openai-compatible.ts
|
|
371
386
|
var DEFAULT_MAX_RETRIES = 2;
|
|
372
387
|
var OpenAICompatibleProvider = class {
|
|
@@ -382,6 +397,18 @@ var OpenAICompatibleProvider = class {
|
|
|
382
397
|
this.supportedMimeTypes = [];
|
|
383
398
|
/** Web search is not supported */
|
|
384
399
|
this.supportsWebSearch = false;
|
|
400
|
+
/**
|
|
401
|
+
* Whether the endpoint tolerates more than one leading `system` message.
|
|
402
|
+
*
|
|
403
|
+
* True for every hosted service — and so the default, which keeps the
|
|
404
|
+
* outbound bytes of OpenRouter, Z.AI and every other subclass exactly as they
|
|
405
|
+
* were. A subclass that points at a local runtime applying the *model's* chat
|
|
406
|
+
* template sets this false: several template families reject a system message
|
|
407
|
+
* anywhere but index 0, and Quilltap's context builder deliberately emits up
|
|
408
|
+
* to three of them so its cache breakpoints survive. See Bug 82 and
|
|
409
|
+
* {@link collapseLeadingSystemMessages}.
|
|
410
|
+
*/
|
|
411
|
+
this.acceptsRepeatedSystemMessages = true;
|
|
385
412
|
/**
|
|
386
413
|
* Profile-parameter keys this provider forwards from
|
|
387
414
|
* `LLMParams.profileParameters` onto the request body.
|
|
@@ -409,6 +436,14 @@ var OpenAICompatibleProvider = class {
|
|
|
409
436
|
}
|
|
410
437
|
this.logger = createPluginLogger(`${this.providerName}Provider`);
|
|
411
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Apply {@link acceptsRepeatedSystemMessages} to a mapped wire-message array.
|
|
441
|
+
* Called by both body builds so the streaming and non-streaming shapes cannot
|
|
442
|
+
* drift apart.
|
|
443
|
+
*/
|
|
444
|
+
applySystemMessagePolicy(messages) {
|
|
445
|
+
return this.acceptsRepeatedSystemMessages ? messages : collapseLeadingSystemMessages(messages);
|
|
446
|
+
}
|
|
412
447
|
/**
|
|
413
448
|
* Collects attachment failures for messages with attachments.
|
|
414
449
|
* Since attachments are not supported, all attachments are marked as failed.
|
|
@@ -527,17 +562,22 @@ var OpenAICompatibleProvider = class {
|
|
|
527
562
|
return out;
|
|
528
563
|
}
|
|
529
564
|
/**
|
|
530
|
-
*
|
|
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}.
|
|
531
571
|
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
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 }`
|
|
535
578
|
*/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const attachmentResults = this.collectAttachmentFailures(params);
|
|
539
|
-
const client = this.createClient(apiKey);
|
|
540
|
-
const messages = params.messages.filter((m) => {
|
|
579
|
+
buildRequestBody(params, stream) {
|
|
580
|
+
const mappedMessages = params.messages.filter((m) => {
|
|
541
581
|
if (m.role === "tool" && !m.toolCallId) return false;
|
|
542
582
|
return true;
|
|
543
583
|
}).map((m) => {
|
|
@@ -564,6 +604,7 @@ var OpenAICompatibleProvider = class {
|
|
|
564
604
|
content: m.content
|
|
565
605
|
};
|
|
566
606
|
});
|
|
607
|
+
const messages = this.applySystemMessagePolicy(mappedMessages);
|
|
567
608
|
const body = {
|
|
568
609
|
model: params.model,
|
|
569
610
|
messages,
|
|
@@ -571,12 +612,30 @@ var OpenAICompatibleProvider = class {
|
|
|
571
612
|
max_tokens: params.maxTokens ?? 4096,
|
|
572
613
|
top_p: params.topP ?? 1,
|
|
573
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 } } : {},
|
|
574
619
|
...params.cacheKey ? { user: params.cacheKey } : {},
|
|
575
620
|
// Tools arrive only once the runtime gate (the profile's "Allow tool
|
|
576
621
|
// use") has passed, so no separate guard is needed here.
|
|
577
622
|
...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
|
|
578
623
|
};
|
|
579
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);
|
|
580
639
|
try {
|
|
581
640
|
const response = await client.chat.completions.create(
|
|
582
641
|
body,
|
|
@@ -616,46 +675,7 @@ var OpenAICompatibleProvider = class {
|
|
|
616
675
|
this.validateApiKeyRequirement(apiKey);
|
|
617
676
|
const attachmentResults = this.collectAttachmentFailures(params);
|
|
618
677
|
const client = this.createClient(apiKey);
|
|
619
|
-
const
|
|
620
|
-
if (m.role === "tool" && !m.toolCallId) return false;
|
|
621
|
-
return true;
|
|
622
|
-
}).map((m) => {
|
|
623
|
-
if (m.role === "tool" && m.toolCallId) {
|
|
624
|
-
return {
|
|
625
|
-
role: "tool",
|
|
626
|
-
tool_call_id: m.toolCallId,
|
|
627
|
-
content: m.content
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
|
|
631
|
-
return {
|
|
632
|
-
role: "assistant",
|
|
633
|
-
content: m.content || null,
|
|
634
|
-
tool_calls: m.toolCalls.map((tc) => ({
|
|
635
|
-
id: tc.id,
|
|
636
|
-
type: tc.type,
|
|
637
|
-
function: tc.function
|
|
638
|
-
}))
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
return {
|
|
642
|
-
role: m.role,
|
|
643
|
-
content: m.content
|
|
644
|
-
};
|
|
645
|
-
});
|
|
646
|
-
const body = {
|
|
647
|
-
model: params.model,
|
|
648
|
-
messages,
|
|
649
|
-
temperature: params.temperature ?? 0.7,
|
|
650
|
-
max_tokens: params.maxTokens ?? 4096,
|
|
651
|
-
top_p: params.topP ?? 1,
|
|
652
|
-
stop: params.stop,
|
|
653
|
-
stream: true,
|
|
654
|
-
stream_options: { include_usage: true },
|
|
655
|
-
...params.cacheKey ? { user: params.cacheKey } : {},
|
|
656
|
-
...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
|
|
657
|
-
};
|
|
658
|
-
this.applyProfileParams(body, params);
|
|
678
|
+
const body = this.buildRequestBody(params, true);
|
|
659
679
|
try {
|
|
660
680
|
const stream = await client.chat.completions.create(
|
|
661
681
|
body,
|
|
@@ -1024,7 +1044,7 @@ function rewriteLocalhostUrl(url) {
|
|
|
1024
1044
|
}
|
|
1025
1045
|
|
|
1026
1046
|
// src/version.generated.ts
|
|
1027
|
-
var PLUGIN_UTILS_VERSION = "2.
|
|
1047
|
+
var PLUGIN_UTILS_VERSION = "2.5.0";
|
|
1028
1048
|
export {
|
|
1029
1049
|
BUILTIN_TOOL_NAMES,
|
|
1030
1050
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
@@ -1039,6 +1059,7 @@ export {
|
|
|
1039
1059
|
buildRequestAbortSignal,
|
|
1040
1060
|
buildSdkClientOptions,
|
|
1041
1061
|
buildSdkRequestOptions,
|
|
1062
|
+
collapseLeadingSystemMessages,
|
|
1042
1063
|
convertFromAnthropicFormat,
|
|
1043
1064
|
convertFromGoogleFormat,
|
|
1044
1065
|
convertOpenAIToAnthropicFormat,
|