@quilltap/plugin-utils 2.2.20 → 2.4.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 +15 -0
- package/README.md +31 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +189 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +187 -26
- package/dist/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +160 -1
- package/dist/providers/index.d.ts +160 -1
- package/dist/providers/index.js +188 -25
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +186 -25
- package/dist/providers/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to @quilltap/plugin-utils will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.4.0] - 2026-08-19
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `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).
|
|
10
|
+
- `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.)
|
|
11
|
+
|
|
12
|
+
## [2.3.0] - 2026-08-15
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `applyProfileParameters(body, params, allowlist, normalize?)` — the one mechanism providers use to forward a connection profile's free-form `parameters` blob onto a request body. Allow-listed (never spread, so `model` / `messages` / `stream` / `tools` stay unreachable from a profile); `undefined`, `null` and the empty string omit the key. Exported as a free function rather than a base-class method because only one plugin extends `OpenAICompatibleProvider` — Z.AI, OpenRouter and Ollama implement their providers directly and reach it by composition.
|
|
17
|
+
- `OpenAICompatibleProvider` gained `profileParamAllowlist` (empty by default, so every existing subclass is byte-identical on the wire until it opts in) and an overridable `normalizeProfileParam`, applied in both the streaming and non-streaming body builds.
|
|
18
|
+
- `OpenAICompatibleProvider` now sends `tools` / `tool_choice` when the caller supplies tools, and parses `tool_calls` back — from `choice.message.tool_calls` on the non-streaming path, and by accumulating index-keyed `delta.tool_calls` fragments while streaming. Previously it could never call a tool.
|
|
19
|
+
|
|
5
20
|
## [2.2.19] - 2026-08-03
|
|
6
21
|
|
|
7
22
|
### Fixed
|
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, 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.4.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, 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.4.0";
|
|
128
128
|
|
|
129
129
|
export { BUILTIN_TOOL_NAMES, PLUGIN_UTILS_VERSION, __clearQuilltapVersion, __injectQuilltapVersion, getBuiltinToolNames, getQuilltapUserAgent, getQuilltapVersion };
|
package/dist/index.js
CHANGED
|
@@ -39,9 +39,11 @@ __export(src_exports, {
|
|
|
39
39
|
__injectCoreLoggerFactory: () => __injectCoreLoggerFactory,
|
|
40
40
|
__injectQuilltapVersion: () => __injectQuilltapVersion,
|
|
41
41
|
applyDescriptionLimit: () => applyDescriptionLimit,
|
|
42
|
+
applyProfileParameters: () => applyProfileParameters,
|
|
42
43
|
buildRequestAbortSignal: () => buildRequestAbortSignal,
|
|
43
44
|
buildSdkClientOptions: () => buildSdkClientOptions,
|
|
44
45
|
buildSdkRequestOptions: () => buildSdkRequestOptions,
|
|
46
|
+
collapseLeadingSystemMessages: () => collapseLeadingSystemMessages,
|
|
45
47
|
convertFromAnthropicFormat: () => convertFromAnthropicFormat,
|
|
46
48
|
convertFromGoogleFormat: () => convertFromGoogleFormat,
|
|
47
49
|
convertOpenAIToAnthropicFormat: () => convertOpenAIToAnthropicFormat,
|
|
@@ -427,6 +429,36 @@ function buildRequestAbortSignal(params, defaultMs = DEFAULT_REQUEST_TIMEOUT_MS)
|
|
|
427
429
|
return AbortSignal.timeout(resolveRequestTimeoutMs(params, defaultMs));
|
|
428
430
|
}
|
|
429
431
|
|
|
432
|
+
// src/providers/profile-parameters.ts
|
|
433
|
+
function applyProfileParameters(body, params, allowlist, normalize) {
|
|
434
|
+
const profile = params.profileParameters;
|
|
435
|
+
if (!profile || typeof profile !== "object") return;
|
|
436
|
+
const bag = profile;
|
|
437
|
+
for (const key of allowlist) {
|
|
438
|
+
const raw = bag[key];
|
|
439
|
+
if (raw === void 0 || raw === null) continue;
|
|
440
|
+
if (typeof raw === "string" && raw === "") continue;
|
|
441
|
+
const value = normalize ? normalize(key, raw, params, body) : raw;
|
|
442
|
+
if (value === void 0) continue;
|
|
443
|
+
body[key] = value;
|
|
444
|
+
}
|
|
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
|
+
|
|
430
462
|
// src/providers/openai-compatible.ts
|
|
431
463
|
var DEFAULT_MAX_RETRIES = 2;
|
|
432
464
|
var OpenAICompatibleProvider = class {
|
|
@@ -442,6 +474,28 @@ var OpenAICompatibleProvider = class {
|
|
|
442
474
|
this.supportedMimeTypes = [];
|
|
443
475
|
/** Web search is not supported */
|
|
444
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;
|
|
489
|
+
/**
|
|
490
|
+
* Profile-parameter keys this provider forwards from
|
|
491
|
+
* `LLMParams.profileParameters` onto the request body.
|
|
492
|
+
*
|
|
493
|
+
* **Empty by default**, so a subclass that declares nothing sends exactly the
|
|
494
|
+
* bytes it sent before this mechanism existed. Subclasses opt in by
|
|
495
|
+
* overriding with their own allow-list; `model`, `messages`, `stream`,
|
|
496
|
+
* `stream_options` and `tools` must never appear in one.
|
|
497
|
+
*/
|
|
498
|
+
this.profileParamAllowlist = [];
|
|
445
499
|
if (typeof config === "string") {
|
|
446
500
|
this.baseUrl = config;
|
|
447
501
|
this.providerName = "OpenAICompatible";
|
|
@@ -459,6 +513,14 @@ var OpenAICompatibleProvider = class {
|
|
|
459
513
|
}
|
|
460
514
|
this.logger = createPluginLogger(`${this.providerName}Provider`);
|
|
461
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
|
+
}
|
|
462
524
|
/**
|
|
463
525
|
* Collects attachment failures for messages with attachments.
|
|
464
526
|
* Since attachments are not supported, all attachments are marked as failed.
|
|
@@ -529,6 +591,53 @@ var OpenAICompatibleProvider = class {
|
|
|
529
591
|
buildRequestOptions(params) {
|
|
530
592
|
return buildSdkRequestOptions(params);
|
|
531
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Per-key hook for allow-listed profile parameters. The default forwards the
|
|
596
|
+
* stored value verbatim.
|
|
597
|
+
*
|
|
598
|
+
* Override to reshape a value (the editor stores flat strings where some APIs
|
|
599
|
+
* want objects), to gate a key on the model, or — by writing into `body` and
|
|
600
|
+
* returning `undefined` — to send it under a different key entirely.
|
|
601
|
+
*/
|
|
602
|
+
normalizeProfileParam(_key, value, _params, _body) {
|
|
603
|
+
return value;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Apply {@link profileParamAllowlist} to a request body under construction.
|
|
607
|
+
* Called by both body builds so the streaming and non-streaming shapes cannot
|
|
608
|
+
* drift apart.
|
|
609
|
+
*/
|
|
610
|
+
applyProfileParams(body, params) {
|
|
611
|
+
applyProfileParameters(
|
|
612
|
+
body,
|
|
613
|
+
params,
|
|
614
|
+
this.profileParamAllowlist,
|
|
615
|
+
(key, value, callParams, target) => this.normalizeProfileParam(key, value, callParams, target)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Normalize an OpenAI-shaped `tool_calls` array into Quilltap's `ToolCall`
|
|
620
|
+
* shape, tolerating servers that hand back already-parsed argument objects
|
|
621
|
+
* where the OpenAI wire format specifies a JSON string.
|
|
622
|
+
*/
|
|
623
|
+
normalizeToolCalls(toolCalls) {
|
|
624
|
+
if (!toolCalls || toolCalls.length === 0) return [];
|
|
625
|
+
const out = [];
|
|
626
|
+
for (const raw of toolCalls) {
|
|
627
|
+
const tc = raw;
|
|
628
|
+
if (!tc?.function?.name) continue;
|
|
629
|
+
const args = tc.function.arguments;
|
|
630
|
+
out.push({
|
|
631
|
+
id: tc.id ?? "",
|
|
632
|
+
type: "function",
|
|
633
|
+
function: {
|
|
634
|
+
name: tc.function.name,
|
|
635
|
+
arguments: typeof args === "string" ? args : JSON.stringify(args ?? {})
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return out;
|
|
640
|
+
}
|
|
532
641
|
/**
|
|
533
642
|
* Sends a message and returns the complete response.
|
|
534
643
|
*
|
|
@@ -540,7 +649,7 @@ var OpenAICompatibleProvider = class {
|
|
|
540
649
|
this.validateApiKeyRequirement(apiKey);
|
|
541
650
|
const attachmentResults = this.collectAttachmentFailures(params);
|
|
542
651
|
const client = this.createClient(apiKey);
|
|
543
|
-
const
|
|
652
|
+
const mappedMessages = params.messages.filter((m) => {
|
|
544
653
|
if (m.role === "tool" && !m.toolCallId) return false;
|
|
545
654
|
return true;
|
|
546
655
|
}).map((m) => {
|
|
@@ -567,17 +676,27 @@ var OpenAICompatibleProvider = class {
|
|
|
567
676
|
content: m.content
|
|
568
677
|
};
|
|
569
678
|
});
|
|
679
|
+
const messages = this.applySystemMessagePolicy(mappedMessages);
|
|
680
|
+
const body = {
|
|
681
|
+
model: params.model,
|
|
682
|
+
messages,
|
|
683
|
+
temperature: params.temperature ?? 0.7,
|
|
684
|
+
max_tokens: params.maxTokens ?? 4096,
|
|
685
|
+
top_p: params.topP ?? 1,
|
|
686
|
+
stop: params.stop,
|
|
687
|
+
...params.cacheKey ? { user: params.cacheKey } : {},
|
|
688
|
+
// Tools arrive only once the runtime gate (the profile's "Allow tool
|
|
689
|
+
// use") has passed, so no separate guard is needed here.
|
|
690
|
+
...params.tools && params.tools.length > 0 ? { tools: params.tools, tool_choice: params.toolChoice ?? "auto" } : {}
|
|
691
|
+
};
|
|
692
|
+
this.applyProfileParams(body, params);
|
|
570
693
|
try {
|
|
571
|
-
const response = await client.chat.completions.create(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
max_tokens: params.maxTokens ?? 4096,
|
|
576
|
-
top_p: params.topP ?? 1,
|
|
577
|
-
stop: params.stop,
|
|
578
|
-
...params.cacheKey ? { user: params.cacheKey } : {}
|
|
579
|
-
}, this.buildRequestOptions(params));
|
|
694
|
+
const response = await client.chat.completions.create(
|
|
695
|
+
body,
|
|
696
|
+
this.buildRequestOptions(params)
|
|
697
|
+
);
|
|
580
698
|
const choice = response.choices[0];
|
|
699
|
+
const toolCalls = this.normalizeToolCalls(choice.message.tool_calls);
|
|
581
700
|
return {
|
|
582
701
|
content: choice.message.content ?? "",
|
|
583
702
|
finishReason: choice.finish_reason,
|
|
@@ -587,7 +706,8 @@ var OpenAICompatibleProvider = class {
|
|
|
587
706
|
totalTokens: response.usage?.total_tokens ?? 0
|
|
588
707
|
},
|
|
589
708
|
raw: response,
|
|
590
|
-
attachmentResults
|
|
709
|
+
attachmentResults,
|
|
710
|
+
...toolCalls.length > 0 ? { toolCalls } : {}
|
|
591
711
|
};
|
|
592
712
|
} catch (error) {
|
|
593
713
|
this.logger.error(
|
|
@@ -609,7 +729,7 @@ var OpenAICompatibleProvider = class {
|
|
|
609
729
|
this.validateApiKeyRequirement(apiKey);
|
|
610
730
|
const attachmentResults = this.collectAttachmentFailures(params);
|
|
611
731
|
const client = this.createClient(apiKey);
|
|
612
|
-
const
|
|
732
|
+
const mappedMessages = params.messages.filter((m) => {
|
|
613
733
|
if (m.role === "tool" && !m.toolCallId) return false;
|
|
614
734
|
return true;
|
|
615
735
|
}).map((m) => {
|
|
@@ -636,23 +756,33 @@ var OpenAICompatibleProvider = class {
|
|
|
636
756
|
content: m.content
|
|
637
757
|
};
|
|
638
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);
|
|
639
773
|
try {
|
|
640
|
-
const stream = await client.chat.completions.create(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
max_tokens: params.maxTokens ?? 4096,
|
|
645
|
-
top_p: params.topP ?? 1,
|
|
646
|
-
stop: params.stop,
|
|
647
|
-
stream: true,
|
|
648
|
-
stream_options: { include_usage: true },
|
|
649
|
-
...params.cacheKey ? { user: params.cacheKey } : {}
|
|
650
|
-
}, this.buildRequestOptions(params));
|
|
774
|
+
const stream = await client.chat.completions.create(
|
|
775
|
+
body,
|
|
776
|
+
this.buildRequestOptions(params)
|
|
777
|
+
);
|
|
651
778
|
let chunkCount = 0;
|
|
652
779
|
let accumulatedUsage = null;
|
|
780
|
+
const toolCallAccumulator = /* @__PURE__ */ new Map();
|
|
781
|
+
let finishReason = null;
|
|
653
782
|
for await (const chunk of stream) {
|
|
654
783
|
chunkCount++;
|
|
655
|
-
const
|
|
784
|
+
const choice = chunk.choices[0];
|
|
785
|
+
const content = choice?.delta?.content;
|
|
656
786
|
const hasUsage = chunk.usage;
|
|
657
787
|
if (hasUsage) {
|
|
658
788
|
accumulatedUsage = {
|
|
@@ -661,6 +791,17 @@ var OpenAICompatibleProvider = class {
|
|
|
661
791
|
total_tokens: chunk.usage?.total_tokens
|
|
662
792
|
};
|
|
663
793
|
}
|
|
794
|
+
if (choice?.delta?.tool_calls) {
|
|
795
|
+
for (const tcDelta of choice.delta.tool_calls) {
|
|
796
|
+
const idx = tcDelta.index ?? 0;
|
|
797
|
+
const existing = toolCallAccumulator.get(idx) ?? { id: "", name: "", arguments: "" };
|
|
798
|
+
if (tcDelta.id) existing.id = tcDelta.id;
|
|
799
|
+
if (tcDelta.function?.name) existing.name = tcDelta.function.name;
|
|
800
|
+
if (tcDelta.function?.arguments) existing.arguments += tcDelta.function.arguments;
|
|
801
|
+
toolCallAccumulator.set(idx, existing);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
664
805
|
if (content) {
|
|
665
806
|
yield {
|
|
666
807
|
content,
|
|
@@ -668,6 +809,11 @@ var OpenAICompatibleProvider = class {
|
|
|
668
809
|
};
|
|
669
810
|
}
|
|
670
811
|
}
|
|
812
|
+
const toolCalls = Array.from(toolCallAccumulator.values()).filter((tc) => tc.name).map((tc) => ({
|
|
813
|
+
id: tc.id,
|
|
814
|
+
type: "function",
|
|
815
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
816
|
+
}));
|
|
671
817
|
yield {
|
|
672
818
|
content: "",
|
|
673
819
|
done: true,
|
|
@@ -676,7 +822,22 @@ var OpenAICompatibleProvider = class {
|
|
|
676
822
|
completionTokens: accumulatedUsage.completion_tokens ?? 0,
|
|
677
823
|
totalTokens: accumulatedUsage.total_tokens ?? 0
|
|
678
824
|
} : void 0,
|
|
679
|
-
attachmentResults
|
|
825
|
+
attachmentResults,
|
|
826
|
+
// Only synthesized when the stream actually carried tool calls: the
|
|
827
|
+
// host's tool detection reads `rawResponse`, and an empty one on every
|
|
828
|
+
// ordinary turn would be noise in the logs for no gain.
|
|
829
|
+
...toolCalls.length > 0 ? {
|
|
830
|
+
toolCalls,
|
|
831
|
+
rawResponse: {
|
|
832
|
+
choices: [
|
|
833
|
+
{
|
|
834
|
+
index: 0,
|
|
835
|
+
message: { role: "assistant", content: "", tool_calls: toolCalls },
|
|
836
|
+
finish_reason: finishReason
|
|
837
|
+
}
|
|
838
|
+
]
|
|
839
|
+
}
|
|
840
|
+
} : {}
|
|
680
841
|
};
|
|
681
842
|
} catch (error) {
|
|
682
843
|
this.logger.error(
|
|
@@ -977,7 +1138,7 @@ function rewriteLocalhostUrl(url) {
|
|
|
977
1138
|
}
|
|
978
1139
|
|
|
979
1140
|
// src/version.generated.ts
|
|
980
|
-
var PLUGIN_UTILS_VERSION = "2.
|
|
1141
|
+
var PLUGIN_UTILS_VERSION = "2.4.0";
|
|
981
1142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
982
1143
|
0 && (module.exports = {
|
|
983
1144
|
BUILTIN_TOOL_NAMES,
|
|
@@ -989,9 +1150,11 @@ var PLUGIN_UTILS_VERSION = "2.2.20";
|
|
|
989
1150
|
__injectCoreLoggerFactory,
|
|
990
1151
|
__injectQuilltapVersion,
|
|
991
1152
|
applyDescriptionLimit,
|
|
1153
|
+
applyProfileParameters,
|
|
992
1154
|
buildRequestAbortSignal,
|
|
993
1155
|
buildSdkClientOptions,
|
|
994
1156
|
buildSdkRequestOptions,
|
|
1157
|
+
collapseLeadingSystemMessages,
|
|
995
1158
|
convertFromAnthropicFormat,
|
|
996
1159
|
convertFromGoogleFormat,
|
|
997
1160
|
convertOpenAIToAnthropicFormat,
|