plugin-custom-llm 1.3.0 → 1.3.1
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.
|
@@ -66,6 +66,11 @@ function getChatOpenAICompletions() {
|
|
|
66
66
|
}
|
|
67
67
|
return _ChatOpenAICompletions;
|
|
68
68
|
}
|
|
69
|
+
function sanitizeToolCallId(id) {
|
|
70
|
+
if (!id || typeof id !== "string") return id;
|
|
71
|
+
const idx = id.indexOf("__thought__");
|
|
72
|
+
return idx !== -1 ? id.substring(0, idx) : id;
|
|
73
|
+
}
|
|
69
74
|
function getToolCallsKey(toolCalls = []) {
|
|
70
75
|
return toolCalls.map((tc) => {
|
|
71
76
|
var _a;
|
|
@@ -537,6 +542,45 @@ function fixEmptyToolProperties(model) {
|
|
|
537
542
|
};
|
|
538
543
|
return model;
|
|
539
544
|
}
|
|
545
|
+
function wrapWithToolCallIdSanitizer(model) {
|
|
546
|
+
var _a, _b;
|
|
547
|
+
const originalGenerate = (_a = model._generate) == null ? void 0 : _a.bind(model);
|
|
548
|
+
if (originalGenerate) {
|
|
549
|
+
model._generate = async function(...args) {
|
|
550
|
+
const result = await originalGenerate(...args);
|
|
551
|
+
for (const gen of (result == null ? void 0 : result.generations) ?? []) {
|
|
552
|
+
const msg = gen == null ? void 0 : gen.message;
|
|
553
|
+
if (msg == null ? void 0 : msg.tool_calls) {
|
|
554
|
+
for (const tc of msg.tool_calls) {
|
|
555
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return result;
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
const streamMethod = typeof model._streamResponseChunks === "function" ? "_streamResponseChunks" : "_stream";
|
|
563
|
+
const originalStream = (_b = model[streamMethod]) == null ? void 0 : _b.bind(model);
|
|
564
|
+
if (originalStream) {
|
|
565
|
+
model[streamMethod] = async function* (...args) {
|
|
566
|
+
for await (const chunk of originalStream(...args)) {
|
|
567
|
+
const msg = chunk == null ? void 0 : chunk.message;
|
|
568
|
+
if (msg == null ? void 0 : msg.tool_call_chunks) {
|
|
569
|
+
for (const tc of msg.tool_call_chunks) {
|
|
570
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (msg == null ? void 0 : msg.tool_calls) {
|
|
574
|
+
for (const tc of msg.tool_calls) {
|
|
575
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
yield chunk;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return model;
|
|
583
|
+
}
|
|
540
584
|
class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
541
585
|
get baseURL() {
|
|
542
586
|
return null;
|
|
@@ -600,6 +644,7 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
600
644
|
}
|
|
601
645
|
let model = new ChatClass(config);
|
|
602
646
|
model = fixEmptyToolProperties(model);
|
|
647
|
+
model = wrapWithToolCallIdSanitizer(model);
|
|
603
648
|
if (streamKeepAlive && !disableStream) {
|
|
604
649
|
return wrapWithStreamKeepAlive(model, {
|
|
605
650
|
intervalMs: Number(keepAliveIntervalMs) || 5e3,
|
|
@@ -627,7 +672,7 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
627
672
|
workContext
|
|
628
673
|
};
|
|
629
674
|
if (toolCalls) {
|
|
630
|
-
content.tool_calls = toolCalls;
|
|
675
|
+
content.tool_calls = Array.isArray(toolCalls) ? toolCalls.map((tc) => ({ ...tc, id: sanitizeToolCallId(tc.id) })) : toolCalls;
|
|
631
676
|
}
|
|
632
677
|
if (Array.isArray(content.content)) {
|
|
633
678
|
const textBlocks = content.content.filter((block) => block.type === "text");
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "AI LLM: Custom (OpenAI Compatible)",
|
|
4
4
|
"displayName.zh-CN": "AI LLM:自定义(OpenAI 兼容)",
|
|
5
5
|
"description": "OpenAI-compatible LLM provider with auto response format detection for external LLM services.",
|
|
6
|
-
"version": "1.3.
|
|
6
|
+
"version": "1.3.1",
|
|
7
7
|
"main": "dist/server/index.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
@@ -50,6 +50,17 @@ function getChatOpenAICompletions() {
|
|
|
50
50
|
return _ChatOpenAICompletions;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Sanitize a tool call ID by stripping the `__thought__<base64>` suffix
|
|
55
|
+
* that Gemini models append during streaming. The suffix is excessively
|
|
56
|
+
* long and causes errors when langgraph reads messages back from history.
|
|
57
|
+
*/
|
|
58
|
+
function sanitizeToolCallId(id: string | undefined): string | undefined {
|
|
59
|
+
if (!id || typeof id !== 'string') return id;
|
|
60
|
+
const idx = id.indexOf('__thought__');
|
|
61
|
+
return idx !== -1 ? id.substring(0, idx) : id;
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
/**
|
|
54
65
|
* Build tool_calls key for reasoning content map lookup.
|
|
55
66
|
*/
|
|
@@ -715,6 +726,56 @@ function fixEmptyToolProperties(model: any) {
|
|
|
715
726
|
return model;
|
|
716
727
|
}
|
|
717
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Wrap a chat model to sanitize tool call IDs in outputs.
|
|
731
|
+
* Gemini models can return IDs like `call_xxx__thought__<long_base64>`
|
|
732
|
+
* which are too long for langgraph to handle on message replay.
|
|
733
|
+
* This strips the `__thought__...` suffix at the model output level
|
|
734
|
+
* so downstream code (convertAIMessage, etc.) only sees clean IDs.
|
|
735
|
+
*/
|
|
736
|
+
function wrapWithToolCallIdSanitizer(model: any) {
|
|
737
|
+
// Patch _generate (used by invoke / non-streaming)
|
|
738
|
+
const originalGenerate = model._generate?.bind(model);
|
|
739
|
+
if (originalGenerate) {
|
|
740
|
+
model._generate = async function (...args: any[]) {
|
|
741
|
+
const result = await originalGenerate(...args);
|
|
742
|
+
for (const gen of result?.generations ?? []) {
|
|
743
|
+
const msg = gen?.message;
|
|
744
|
+
if (msg?.tool_calls) {
|
|
745
|
+
for (const tc of msg.tool_calls) {
|
|
746
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return result;
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Patch _streamResponseChunks or _stream (used by streamEvents / streaming)
|
|
755
|
+
const streamMethod = typeof model._streamResponseChunks === 'function' ? '_streamResponseChunks' : '_stream';
|
|
756
|
+
const originalStream = model[streamMethod]?.bind(model);
|
|
757
|
+
if (originalStream) {
|
|
758
|
+
model[streamMethod] = async function* (...args: any[]) {
|
|
759
|
+
for await (const chunk of originalStream(...args)) {
|
|
760
|
+
const msg = chunk?.message;
|
|
761
|
+
if (msg?.tool_call_chunks) {
|
|
762
|
+
for (const tc of msg.tool_call_chunks) {
|
|
763
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (msg?.tool_calls) {
|
|
767
|
+
for (const tc of msg.tool_calls) {
|
|
768
|
+
tc.id = sanitizeToolCallId(tc.id);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
yield chunk;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return model;
|
|
777
|
+
}
|
|
778
|
+
|
|
718
779
|
export class CustomLLMProvider extends LLMProvider {
|
|
719
780
|
get baseURL() {
|
|
720
781
|
return null;
|
|
@@ -794,6 +855,9 @@ export class CustomLLMProvider extends LLMProvider {
|
|
|
794
855
|
// Fix empty tool properties for strict providers (Gemini, etc.)
|
|
795
856
|
model = fixEmptyToolProperties(model);
|
|
796
857
|
|
|
858
|
+
// Sanitize Gemini's __thought__<base64> suffixes in tool call IDs
|
|
859
|
+
model = wrapWithToolCallIdSanitizer(model);
|
|
860
|
+
|
|
797
861
|
// Wrap with keepalive proxy if enabled (and streaming is not disabled)
|
|
798
862
|
if (streamKeepAlive && !disableStream) {
|
|
799
863
|
return wrapWithStreamKeepAlive(model, {
|
|
@@ -830,7 +894,9 @@ export class CustomLLMProvider extends LLMProvider {
|
|
|
830
894
|
};
|
|
831
895
|
|
|
832
896
|
if (toolCalls) {
|
|
833
|
-
content.tool_calls = toolCalls
|
|
897
|
+
content.tool_calls = Array.isArray(toolCalls)
|
|
898
|
+
? toolCalls.map((tc: any) => ({ ...tc, id: sanitizeToolCallId(tc.id) }))
|
|
899
|
+
: toolCalls;
|
|
834
900
|
}
|
|
835
901
|
|
|
836
902
|
if (Array.isArray(content.content)) {
|