@mcp-use/inspector 7.0.0-canary.9 → 7.0.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/README.md +3 -1
- package/dist/cdn/inspector.css +1 -1
- package/dist/cdn/inspector.js +15042 -14411
- package/dist/cli.js +477 -48
- package/dist/client/components/MCPAppsRenderer.d.ts +3 -1
- package/dist/client/components/MCPAppsRenderer.d.ts.map +1 -1
- package/dist/client/components/OpenAIComponentRenderer.d.ts.map +1 -1
- package/dist/client/components/chat/AssistantMessage.d.ts.map +1 -1
- package/dist/client/components/chat/ChatHeader.d.ts +5 -4
- package/dist/client/components/chat/ChatHeader.d.ts.map +1 -1
- package/dist/client/components/chat/ChatLandingForm.d.ts.map +1 -1
- package/dist/client/components/chat/ConfigurationDialog.d.ts +5 -5
- package/dist/client/components/chat/ConfigurationDialog.d.ts.map +1 -1
- package/dist/client/components/chat/CopyButton.d.ts +10 -0
- package/dist/client/components/chat/CopyButton.d.ts.map +1 -0
- package/dist/client/components/chat/UserMessage.d.ts.map +1 -1
- package/dist/client/components/chat/providerMeta.d.ts +7 -0
- package/dist/client/components/chat/providerMeta.d.ts.map +1 -0
- package/dist/client/components/chat/types.d.ts +6 -8
- package/dist/client/components/chat/types.d.ts.map +1 -1
- package/dist/client/components/chat/useConfig.d.ts +3 -2
- package/dist/client/components/chat/useConfig.d.ts.map +1 -1
- package/dist/client/components/tools/ToolInputForm.d.ts.map +1 -1
- package/dist/client/components/ui/SandboxedIframe.d.ts +2 -0
- package/dist/client/components/ui/SandboxedIframe.d.ts.map +1 -1
- package/dist/client/index.js +1769 -1107
- package/dist/client/lib/use-sandbox-remount-generation.d.ts +9 -0
- package/dist/client/lib/use-sandbox-remount-generation.d.ts.map +1 -0
- package/dist/client/lib/widget-fullscreen.d.ts +17 -0
- package/dist/client/lib/widget-fullscreen.d.ts.map +1 -0
- package/dist/client/telemetry/events.d.ts +2 -1
- package/dist/client/telemetry/events.d.ts.map +1 -1
- package/dist/llm/__tests__/messageFormat.test.d.ts +2 -0
- package/dist/llm/__tests__/messageFormat.test.d.ts.map +1 -0
- package/dist/llm/__tests__/providerToolResults.test.d.ts +2 -0
- package/dist/llm/__tests__/providerToolResults.test.d.ts.map +1 -0
- package/dist/llm/__tests__/toolResultParts.test.d.ts +2 -0
- package/dist/llm/__tests__/toolResultParts.test.d.ts.map +1 -0
- package/dist/llm/messageFormat.d.ts.map +1 -1
- package/dist/llm/ndjson.d.ts +5 -0
- package/dist/llm/ndjson.d.ts.map +1 -0
- package/dist/llm/providers/anthropic.d.ts +1 -0
- package/dist/llm/providers/anthropic.d.ts.map +1 -1
- package/dist/llm/providers/google.d.ts +1 -0
- package/dist/llm/providers/google.d.ts.map +1 -1
- package/dist/llm/providers/index.d.ts.map +1 -1
- package/dist/llm/providers/ollama/index.d.ts +18 -0
- package/dist/llm/providers/ollama/index.d.ts.map +1 -0
- package/dist/llm/providers/ollama/utils.d.ts +7 -0
- package/dist/llm/providers/ollama/utils.d.ts.map +1 -0
- package/dist/llm/providers/openai.d.ts +1 -0
- package/dist/llm/providers/openai.d.ts.map +1 -1
- package/dist/llm/toolLoop.d.ts.map +1 -1
- package/dist/llm/toolResultParts.d.ts +28 -0
- package/dist/llm/toolResultParts.d.ts.map +1 -0
- package/dist/llm/types.d.ts +1 -1
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/server/{chunk-FWTBCKWN.js → chunk-A4VH7AIP.js} +1 -1
- package/dist/server/{chunk-POSNI7O3.js → chunk-PJNKFX2R.js} +477 -48
- package/dist/server/{chunk-2PI2LIIX.js → chunk-QNKR35CW.js} +1 -1
- package/dist/server/cli.js +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/middleware.js +3 -3
- package/dist/server/server.js +2 -2
- package/dist/server/shared-routes.js +2 -2
- package/dist/server/shared-utils.d.ts +2 -1
- package/dist/server/shared-utils.d.ts.map +1 -1
- package/dist/server/shared-utils.js +1 -1
- package/dist/web/assets/index-DB2aVJQh.css +2 -0
- package/dist/web/assets/{index-BGeNa-KU.js → index-MGfHvrb0.js} +118 -115
- package/dist/web/assets/module-BXKdgN2b.js +4 -0
- package/dist/web/index.html +3 -3
- package/package.json +5 -2
- package/dist/web/assets/index-BL3J9vaD.css +0 -2
- package/dist/web/assets/module-DjisYqbk.js +0 -4
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,142 @@ import open from "open";
|
|
|
14
14
|
import { telFetch } from "mcp-use/telemetry/tel-fetch";
|
|
15
15
|
import { mountMcpProxy, mountOAuthProxy } from "mcp-use/server";
|
|
16
16
|
|
|
17
|
+
// src/llm/toolResultParts.ts
|
|
18
|
+
function makeText(text) {
|
|
19
|
+
return { type: "text", text };
|
|
20
|
+
}
|
|
21
|
+
function makeImage(data, mimeType) {
|
|
22
|
+
return {
|
|
23
|
+
type: "image",
|
|
24
|
+
url: `data:${mimeType};base64,${data}`,
|
|
25
|
+
mimeType,
|
|
26
|
+
data
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function isMcpResultShape(value) {
|
|
30
|
+
if (!value || typeof value !== "object") return false;
|
|
31
|
+
const v = value;
|
|
32
|
+
if (Array.isArray(v.content)) return true;
|
|
33
|
+
return "structuredContent" in v;
|
|
34
|
+
}
|
|
35
|
+
function isToolResultError(result) {
|
|
36
|
+
return typeof result === "object" && result !== null && result.isError === true;
|
|
37
|
+
}
|
|
38
|
+
function blockToPart(block) {
|
|
39
|
+
if (!block || typeof block !== "object") return null;
|
|
40
|
+
switch (block.type) {
|
|
41
|
+
case "text": {
|
|
42
|
+
const text = block.text;
|
|
43
|
+
return typeof text === "string" && text.length > 0 ? makeText(text) : null;
|
|
44
|
+
}
|
|
45
|
+
case "image": {
|
|
46
|
+
const b = block;
|
|
47
|
+
if (typeof b.data !== "string" || !b.data) return null;
|
|
48
|
+
const mime = typeof b.mimeType === "string" && b.mimeType ? b.mimeType : "image/png";
|
|
49
|
+
return makeImage(b.data, mime);
|
|
50
|
+
}
|
|
51
|
+
case "audio": {
|
|
52
|
+
const b = block;
|
|
53
|
+
const mime = typeof b.mimeType === "string" && b.mimeType ? b.mimeType : "audio/*";
|
|
54
|
+
const bytes = typeof b.data === "string" ? b.data.length : 0;
|
|
55
|
+
return makeText(`[audio: ${mime}, base64 omitted (${bytes} chars)]`);
|
|
56
|
+
}
|
|
57
|
+
case "resource": {
|
|
58
|
+
const r = block.resource ?? {};
|
|
59
|
+
if (typeof r.text === "string" && r.text.length > 0) {
|
|
60
|
+
return makeText(r.text);
|
|
61
|
+
}
|
|
62
|
+
if (typeof r.blob === "string" && typeof r.mimeType === "string" && r.mimeType.startsWith("image/")) {
|
|
63
|
+
return makeImage(r.blob, r.mimeType);
|
|
64
|
+
}
|
|
65
|
+
const uri = typeof r.uri === "string" ? r.uri : "<unknown>";
|
|
66
|
+
const mime = typeof r.mimeType === "string" ? r.mimeType : "unknown";
|
|
67
|
+
return makeText(`[resource: ${uri} (${mime})]`);
|
|
68
|
+
}
|
|
69
|
+
case "resource_link": {
|
|
70
|
+
const b = block;
|
|
71
|
+
const uri = typeof b.uri === "string" ? b.uri : "<unknown>";
|
|
72
|
+
const name = typeof b.name === "string" ? ` "${b.name}"` : "";
|
|
73
|
+
return makeText(`[resource_link${name}: ${uri}]`);
|
|
74
|
+
}
|
|
75
|
+
default: {
|
|
76
|
+
return makeText(
|
|
77
|
+
`[unsupported tool content block: ${String(block.type)}]`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function stripMeta(value) {
|
|
83
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
84
|
+
const { _meta: _ignored, ...rest } = value;
|
|
85
|
+
return rest;
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
function extractToolResultParts(result) {
|
|
90
|
+
const stripped = stripMeta(result);
|
|
91
|
+
if (typeof stripped === "string") {
|
|
92
|
+
return stripped.length > 0 ? [makeText(stripped)] : [];
|
|
93
|
+
}
|
|
94
|
+
if (isMcpResultShape(stripped)) {
|
|
95
|
+
const r = stripped;
|
|
96
|
+
const parts = [];
|
|
97
|
+
if (Array.isArray(r.content)) {
|
|
98
|
+
for (const block of r.content) {
|
|
99
|
+
const p = blockToPart(block);
|
|
100
|
+
if (p) parts.push(p);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (r.structuredContent !== void 0) {
|
|
104
|
+
try {
|
|
105
|
+
parts.push(
|
|
106
|
+
makeText(`structuredContent: ${JSON.stringify(r.structuredContent)}`)
|
|
107
|
+
);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (r.isError) {
|
|
112
|
+
parts.unshift(makeText("[tool reported isError=true]"));
|
|
113
|
+
}
|
|
114
|
+
return parts;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return [makeText(JSON.stringify(stripped))];
|
|
118
|
+
} catch {
|
|
119
|
+
return [makeText(String(stripped))];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function collapseToString(parts) {
|
|
123
|
+
if (parts.length === 0) return "";
|
|
124
|
+
if (parts.every((p) => p.type === "text")) {
|
|
125
|
+
return parts.map((p) => p.text).join("\n");
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function toolResultToContent(result) {
|
|
130
|
+
const parts = extractToolResultParts(result);
|
|
131
|
+
const collapsed = collapseToString(parts);
|
|
132
|
+
return collapsed !== null ? collapsed : parts;
|
|
133
|
+
}
|
|
134
|
+
function partitionToolContent(content) {
|
|
135
|
+
if (typeof content === "string") {
|
|
136
|
+
return { text: content, imageParts: [], isString: true };
|
|
137
|
+
}
|
|
138
|
+
const texts = [];
|
|
139
|
+
const imageParts = [];
|
|
140
|
+
for (const p of content) {
|
|
141
|
+
if (p.type === "text") {
|
|
142
|
+
if (p.text) texts.push(p.text);
|
|
143
|
+
} else if (p.type === "image") {
|
|
144
|
+
imageParts.push(p);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { text: texts.join("\n"), imageParts, isString: false };
|
|
148
|
+
}
|
|
149
|
+
function toolImageFollowupHeader(toolName, count) {
|
|
150
|
+
return `(Tool "${toolName ?? "<tool>"}" returned the following image${count === 1 ? "" : "s"}:)`;
|
|
151
|
+
}
|
|
152
|
+
|
|
17
153
|
// src/llm/messageFormat.ts
|
|
18
154
|
function extractText(m) {
|
|
19
155
|
const raw = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((x) => x?.text ?? "").join("\n") : JSON.stringify(m.content ?? "");
|
|
@@ -23,13 +159,6 @@ function extractText(m) {
|
|
|
23
159
|
}
|
|
24
160
|
return "";
|
|
25
161
|
}
|
|
26
|
-
function serializeToolResult(result) {
|
|
27
|
-
if (result && typeof result === "object") {
|
|
28
|
-
const { _meta: _ignored, ...rest } = result;
|
|
29
|
-
return JSON.stringify(rest);
|
|
30
|
-
}
|
|
31
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
32
|
-
}
|
|
33
162
|
function convertMessagesToProvider(messages) {
|
|
34
163
|
const out = [];
|
|
35
164
|
messages.forEach((m, mi) => {
|
|
@@ -73,12 +202,14 @@ function convertMessagesToProvider(messages) {
|
|
|
73
202
|
toolCalls
|
|
74
203
|
});
|
|
75
204
|
toolParts.forEach((p, i) => {
|
|
205
|
+
const result = p.toolInvocation.result;
|
|
76
206
|
out.push({
|
|
77
207
|
role: "tool",
|
|
78
|
-
content:
|
|
208
|
+
content: toolResultToContent(result),
|
|
79
209
|
toolCallId: `call_${mi}_${i}_${p.toolInvocation.toolName}`,
|
|
80
210
|
toolName: p.toolInvocation.toolName,
|
|
81
|
-
toolResult:
|
|
211
|
+
toolResult: result,
|
|
212
|
+
toolIsError: isToolResultError(result)
|
|
82
213
|
});
|
|
83
214
|
});
|
|
84
215
|
});
|
|
@@ -202,6 +333,24 @@ function toAnthropicContent(content) {
|
|
|
202
333
|
if (blocks.length === 0) blocks.push({ type: "text", text: "[no content]" });
|
|
203
334
|
return blocks;
|
|
204
335
|
}
|
|
336
|
+
function toAnthropicToolResultContent(content) {
|
|
337
|
+
if (typeof content === "string") {
|
|
338
|
+
return content;
|
|
339
|
+
}
|
|
340
|
+
const blocks = [];
|
|
341
|
+
for (const p of content) {
|
|
342
|
+
if (p.type === "text") {
|
|
343
|
+
if (p.text) blocks.push({ type: "text", text: p.text });
|
|
344
|
+
} else if (p.type === "image") {
|
|
345
|
+
const img = buildImageBlock(p);
|
|
346
|
+
if (img) blocks.push(img);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (blocks.length === 0) {
|
|
350
|
+
return "[no content]";
|
|
351
|
+
}
|
|
352
|
+
return blocks;
|
|
353
|
+
}
|
|
205
354
|
function toAnthropicMessages(messages) {
|
|
206
355
|
const out = [];
|
|
207
356
|
for (const m of messages) {
|
|
@@ -210,7 +359,7 @@ function toAnthropicMessages(messages) {
|
|
|
210
359
|
const block = {
|
|
211
360
|
type: "tool_result",
|
|
212
361
|
tool_use_id: m.toolCallId,
|
|
213
|
-
content:
|
|
362
|
+
content: toAnthropicToolResultContent(m.content),
|
|
214
363
|
...m.toolIsError ? { is_error: true } : {}
|
|
215
364
|
};
|
|
216
365
|
if (last && last.role === "user" && Array.isArray(last.content)) {
|
|
@@ -499,21 +648,39 @@ function toGeminiParts(content) {
|
|
|
499
648
|
if (parts.length === 0) parts.push({ text: "[no content]" });
|
|
500
649
|
return parts;
|
|
501
650
|
}
|
|
651
|
+
function buildGeminiToolResponse(content) {
|
|
652
|
+
if (typeof content === "string") {
|
|
653
|
+
let parsed;
|
|
654
|
+
try {
|
|
655
|
+
parsed = JSON.parse(content);
|
|
656
|
+
} catch {
|
|
657
|
+
parsed = { result: content };
|
|
658
|
+
}
|
|
659
|
+
if (!parsed || typeof parsed !== "object") {
|
|
660
|
+
parsed = { result: parsed };
|
|
661
|
+
}
|
|
662
|
+
return { response: parsed, imageParts: [] };
|
|
663
|
+
}
|
|
664
|
+
const { text, imageParts } = partitionToolContent(content);
|
|
665
|
+
const response = {};
|
|
666
|
+
if (text) response.text = text;
|
|
667
|
+
if (imageParts.length > 0) {
|
|
668
|
+
response.images = imageParts.map((p) => ({
|
|
669
|
+
mimeType: p.mimeType ?? "image/*",
|
|
670
|
+
omitted: true
|
|
671
|
+
}));
|
|
672
|
+
response.note = "Image bytes were sent in the next user turn as inlineData.";
|
|
673
|
+
}
|
|
674
|
+
if (Object.keys(response).length === 0) {
|
|
675
|
+
response.result = null;
|
|
676
|
+
}
|
|
677
|
+
return { response, imageParts };
|
|
678
|
+
}
|
|
502
679
|
function toGeminiContents(messages) {
|
|
503
680
|
const out = [];
|
|
504
681
|
for (const m of messages) {
|
|
505
682
|
if (m.role === "tool") {
|
|
506
|
-
|
|
507
|
-
try {
|
|
508
|
-
response = typeof m.content === "string" ? JSON.parse(m.content) : m.toolResult;
|
|
509
|
-
} catch {
|
|
510
|
-
response = {
|
|
511
|
-
result: typeof m.content === "string" ? m.content : m.toolResult
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
if (!response || typeof response !== "object") {
|
|
515
|
-
response = { result: response };
|
|
516
|
-
}
|
|
683
|
+
const { response, imageParts } = buildGeminiToolResponse(m.content);
|
|
517
684
|
const last = out[out.length - 1];
|
|
518
685
|
const part = {
|
|
519
686
|
functionResponse: {
|
|
@@ -526,6 +693,16 @@ function toGeminiContents(messages) {
|
|
|
526
693
|
} else {
|
|
527
694
|
out.push({ role: "function", parts: [part] });
|
|
528
695
|
}
|
|
696
|
+
if (imageParts.length > 0) {
|
|
697
|
+
const userParts = [
|
|
698
|
+
{ text: toolImageFollowupHeader(m.toolName, imageParts.length) }
|
|
699
|
+
];
|
|
700
|
+
for (const p of imageParts) {
|
|
701
|
+
const block = buildInlineData(p);
|
|
702
|
+
if (block) userParts.push(block);
|
|
703
|
+
}
|
|
704
|
+
out.push({ role: "user", parts: userParts });
|
|
705
|
+
}
|
|
529
706
|
continue;
|
|
530
707
|
}
|
|
531
708
|
if (m.role === "assistant") {
|
|
@@ -671,12 +848,252 @@ async function chat2(params) {
|
|
|
671
848
|
return { text, toolCalls };
|
|
672
849
|
}
|
|
673
850
|
|
|
851
|
+
// src/llm/ndjson.ts
|
|
852
|
+
async function* parseNDJSON(body, signal) {
|
|
853
|
+
const reader = body.getReader();
|
|
854
|
+
const decoder = new TextDecoder();
|
|
855
|
+
let buffer = "";
|
|
856
|
+
try {
|
|
857
|
+
while (true) {
|
|
858
|
+
if (signal?.aborted) return;
|
|
859
|
+
const { done, value } = await reader.read();
|
|
860
|
+
if (done) break;
|
|
861
|
+
buffer += decoder.decode(value, { stream: true });
|
|
862
|
+
const lines = buffer.split("\n");
|
|
863
|
+
buffer = lines.pop() ?? "";
|
|
864
|
+
for (const line of lines) {
|
|
865
|
+
const trimmed = line.trim();
|
|
866
|
+
if (!trimmed) continue;
|
|
867
|
+
try {
|
|
868
|
+
yield JSON.parse(trimmed);
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const trailing = buffer.trim();
|
|
874
|
+
if (trailing) {
|
|
875
|
+
try {
|
|
876
|
+
yield JSON.parse(trailing);
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
} finally {
|
|
881
|
+
reader.releaseLock();
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/llm/providers/ollama/utils.ts
|
|
886
|
+
var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
|
|
887
|
+
function normalizeOllamaBaseUrl(baseUrl) {
|
|
888
|
+
const raw = (baseUrl || DEFAULT_OLLAMA_BASE_URL).trim();
|
|
889
|
+
let end = raw.length;
|
|
890
|
+
while (end > 0 && raw.charCodeAt(end - 1) === 47) {
|
|
891
|
+
end--;
|
|
892
|
+
}
|
|
893
|
+
const trimmed = raw.slice(0, end);
|
|
894
|
+
return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed;
|
|
895
|
+
}
|
|
896
|
+
function buildOllamaApiUrl(baseUrl, path) {
|
|
897
|
+
return `${normalizeOllamaBaseUrl(baseUrl)}${path}`;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/llm/providers/ollama/index.ts
|
|
901
|
+
function toOllamaImages(content) {
|
|
902
|
+
const images = [];
|
|
903
|
+
for (const part of content) {
|
|
904
|
+
if (part.type !== "image") continue;
|
|
905
|
+
if (part.data) {
|
|
906
|
+
images.push(part.data);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const parsed = parseDataUrl(part.url);
|
|
910
|
+
if (parsed?.data) {
|
|
911
|
+
images.push(parsed.data);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return images;
|
|
915
|
+
}
|
|
916
|
+
function toOllamaContent(content) {
|
|
917
|
+
if (typeof content === "string") {
|
|
918
|
+
return { content };
|
|
919
|
+
}
|
|
920
|
+
const text = content.filter(
|
|
921
|
+
(part) => part.type === "text"
|
|
922
|
+
).map((part) => part.text).join("");
|
|
923
|
+
const images = toOllamaImages(content);
|
|
924
|
+
return {
|
|
925
|
+
content: text,
|
|
926
|
+
...images.length > 0 ? { images } : {}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function toOllamaMessages(messages) {
|
|
930
|
+
return messages.map((message) => {
|
|
931
|
+
if (message.role === "tool") {
|
|
932
|
+
return {
|
|
933
|
+
role: "tool",
|
|
934
|
+
tool_name: message.toolName,
|
|
935
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.toolResult ?? message.content)
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
if (message.role === "assistant") {
|
|
939
|
+
const content = typeof message.content === "string" ? { content: message.content } : toOllamaContent(message.content);
|
|
940
|
+
return {
|
|
941
|
+
role: "assistant",
|
|
942
|
+
...content,
|
|
943
|
+
...message.toolCalls?.length ? {
|
|
944
|
+
tool_calls: message.toolCalls.map((toolCall, index) => ({
|
|
945
|
+
type: "function",
|
|
946
|
+
function: {
|
|
947
|
+
index,
|
|
948
|
+
name: toolCall.name,
|
|
949
|
+
arguments: toolCall.args
|
|
950
|
+
}
|
|
951
|
+
}))
|
|
952
|
+
} : {}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
role: message.role,
|
|
957
|
+
...toOllamaContent(message.content)
|
|
958
|
+
};
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
function buildBody3(params, stream) {
|
|
962
|
+
const { config, messages, tools } = params;
|
|
963
|
+
const body = {
|
|
964
|
+
model: config.model,
|
|
965
|
+
messages: toOllamaMessages(messages),
|
|
966
|
+
stream
|
|
967
|
+
};
|
|
968
|
+
const options = {};
|
|
969
|
+
if (config.temperature !== void 0)
|
|
970
|
+
options.temperature = config.temperature;
|
|
971
|
+
if (config.maxTokens !== void 0) options.num_predict = config.maxTokens;
|
|
972
|
+
if (Object.keys(options).length > 0) body.options = options;
|
|
973
|
+
if (tools && tools.length > 0) {
|
|
974
|
+
body.tools = tools.map((tool) => ({
|
|
975
|
+
type: "function",
|
|
976
|
+
function: {
|
|
977
|
+
name: tool.name,
|
|
978
|
+
description: tool.description,
|
|
979
|
+
parameters: tool.inputSchema
|
|
980
|
+
}
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
983
|
+
return body;
|
|
984
|
+
}
|
|
985
|
+
function buildHeaders(config) {
|
|
986
|
+
return {
|
|
987
|
+
"Content-Type": "application/json",
|
|
988
|
+
...config.apiKey.trim() ? { Authorization: `Bearer ${config.apiKey.trim()}` } : {}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function normalizeToolCalls(toolCalls) {
|
|
992
|
+
if (!Array.isArray(toolCalls)) return [];
|
|
993
|
+
return toolCalls.map((toolCall) => {
|
|
994
|
+
const functionCall = toolCall && typeof toolCall === "object" ? toolCall.function : void 0;
|
|
995
|
+
const name = typeof functionCall?.name === "string" ? functionCall.name : "";
|
|
996
|
+
const rawArgs = functionCall?.arguments;
|
|
997
|
+
const args2 = rawArgs && typeof rawArgs === "object" ? rawArgs : {};
|
|
998
|
+
return {
|
|
999
|
+
name,
|
|
1000
|
+
args: args2
|
|
1001
|
+
};
|
|
1002
|
+
}).filter((toolCall) => toolCall.name);
|
|
1003
|
+
}
|
|
1004
|
+
async function* streamChat3(params) {
|
|
1005
|
+
const { config, signal } = params;
|
|
1006
|
+
const res = await fetch(buildOllamaApiUrl(config.baseUrl, "/api/chat"), {
|
|
1007
|
+
method: "POST",
|
|
1008
|
+
headers: buildHeaders(config),
|
|
1009
|
+
body: JSON.stringify(buildBody3(params, true)),
|
|
1010
|
+
signal
|
|
1011
|
+
});
|
|
1012
|
+
if (!res.ok || !res.body) {
|
|
1013
|
+
const text = await res.text().catch(() => "");
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
`Ollama request failed (${res.status} ${res.statusText}): ${text}`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
let toolCallsEmitted = false;
|
|
1019
|
+
for await (const chunk of parseNDJSON(res.body, signal)) {
|
|
1020
|
+
const message = chunk && typeof chunk === "object" ? chunk.message : void 0;
|
|
1021
|
+
if (typeof message?.content === "string" && message.content.length > 0) {
|
|
1022
|
+
yield { type: "text-delta", delta: message.content };
|
|
1023
|
+
}
|
|
1024
|
+
if (!toolCallsEmitted) {
|
|
1025
|
+
const toolCalls = normalizeToolCalls(message?.tool_calls);
|
|
1026
|
+
if (toolCalls.length > 0) {
|
|
1027
|
+
for (const [index, toolCall] of toolCalls.entries()) {
|
|
1028
|
+
const toolCallId = `call_${index}_${toolCall.name || "tool"}`;
|
|
1029
|
+
yield {
|
|
1030
|
+
type: "tool-call-start",
|
|
1031
|
+
index,
|
|
1032
|
+
toolCallId,
|
|
1033
|
+
toolName: toolCall.name
|
|
1034
|
+
};
|
|
1035
|
+
const argsJson = JSON.stringify(toolCall.args);
|
|
1036
|
+
if (argsJson && argsJson !== "{}") {
|
|
1037
|
+
yield {
|
|
1038
|
+
type: "tool-call-args-delta",
|
|
1039
|
+
index,
|
|
1040
|
+
toolCallId,
|
|
1041
|
+
toolName: toolCall.name,
|
|
1042
|
+
argsDelta: argsJson
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
yield {
|
|
1046
|
+
type: "tool-call-ready",
|
|
1047
|
+
index,
|
|
1048
|
+
toolCallId,
|
|
1049
|
+
toolName: toolCall.name,
|
|
1050
|
+
args: toolCall.args
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
toolCallsEmitted = true;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (chunk && typeof chunk === "object" && chunk.error) {
|
|
1057
|
+
yield {
|
|
1058
|
+
type: "error",
|
|
1059
|
+
message: String(chunk.error)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
yield { type: "done" };
|
|
1064
|
+
}
|
|
1065
|
+
async function chat3(params) {
|
|
1066
|
+
const { config, signal } = params;
|
|
1067
|
+
const res = await fetch(buildOllamaApiUrl(config.baseUrl, "/api/chat"), {
|
|
1068
|
+
method: "POST",
|
|
1069
|
+
headers: buildHeaders(config),
|
|
1070
|
+
body: JSON.stringify(buildBody3(params, false)),
|
|
1071
|
+
signal
|
|
1072
|
+
});
|
|
1073
|
+
if (!res.ok) {
|
|
1074
|
+
const text = await res.text().catch(() => "");
|
|
1075
|
+
throw new Error(
|
|
1076
|
+
`Ollama request failed (${res.status} ${res.statusText}): ${text}`
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
const json = await res.json();
|
|
1080
|
+
const message = json?.message ?? {};
|
|
1081
|
+
return {
|
|
1082
|
+
text: typeof message.content === "string" ? message.content : "",
|
|
1083
|
+
toolCalls: normalizeToolCalls(message.tool_calls).map((tc, index) => ({
|
|
1084
|
+
id: `call_${index}_${tc.name || "tool"}`,
|
|
1085
|
+
name: tc.name,
|
|
1086
|
+
args: tc.args
|
|
1087
|
+
}))
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
674
1091
|
// src/llm/providers/openai.ts
|
|
675
1092
|
var OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
676
1093
|
function buildEndpoint(config, path) {
|
|
677
1094
|
return `${config.baseUrl ?? OPENAI_BASE_URL}${path}`;
|
|
678
1095
|
}
|
|
679
|
-
function
|
|
1096
|
+
function buildHeaders2(config) {
|
|
680
1097
|
const headers = {
|
|
681
1098
|
"Content-Type": "application/json",
|
|
682
1099
|
...config.extraHeaders
|
|
@@ -697,11 +1114,26 @@ function toOpenAIMessages(messages) {
|
|
|
697
1114
|
const out = [];
|
|
698
1115
|
for (const m of messages) {
|
|
699
1116
|
if (m.role === "tool") {
|
|
1117
|
+
const { text, imageParts } = partitionToolContent(m.content);
|
|
1118
|
+
const fallback = imageParts.length > 0 ? "[image content; see next message]" : "[no content]";
|
|
700
1119
|
out.push({
|
|
701
1120
|
role: "tool",
|
|
702
1121
|
tool_call_id: m.toolCallId,
|
|
703
|
-
content:
|
|
1122
|
+
content: text || fallback
|
|
704
1123
|
});
|
|
1124
|
+
if (imageParts.length > 0) {
|
|
1125
|
+
const userParts = [
|
|
1126
|
+
{
|
|
1127
|
+
type: "text",
|
|
1128
|
+
text: toolImageFollowupHeader(m.toolName, imageParts.length)
|
|
1129
|
+
},
|
|
1130
|
+
...imageParts.map((p) => ({
|
|
1131
|
+
type: "image_url",
|
|
1132
|
+
image_url: { url: p.url }
|
|
1133
|
+
}))
|
|
1134
|
+
];
|
|
1135
|
+
out.push({ role: "user", content: userParts });
|
|
1136
|
+
}
|
|
705
1137
|
continue;
|
|
706
1138
|
}
|
|
707
1139
|
if (m.role === "assistant") {
|
|
@@ -721,10 +1153,9 @@ function toOpenAIMessages(messages) {
|
|
|
721
1153
|
}
|
|
722
1154
|
out.push({ role: m.role, content: toOpenAIContent(m.content) });
|
|
723
1155
|
}
|
|
724
|
-
void parseDataUrl;
|
|
725
1156
|
return out;
|
|
726
1157
|
}
|
|
727
|
-
async function*
|
|
1158
|
+
async function* streamChat4(params) {
|
|
728
1159
|
const { config, messages, tools, signal } = params;
|
|
729
1160
|
const body = {
|
|
730
1161
|
model: config.model,
|
|
@@ -746,7 +1177,7 @@ async function* streamChat3(params) {
|
|
|
746
1177
|
const endpoint = buildEndpoint(config, "/chat/completions");
|
|
747
1178
|
const res = await fetch(endpoint, {
|
|
748
1179
|
method: "POST",
|
|
749
|
-
headers:
|
|
1180
|
+
headers: buildHeaders2(config),
|
|
750
1181
|
body: JSON.stringify(body),
|
|
751
1182
|
signal
|
|
752
1183
|
});
|
|
@@ -834,7 +1265,7 @@ async function* streamChat3(params) {
|
|
|
834
1265
|
}
|
|
835
1266
|
yield { type: "done" };
|
|
836
1267
|
}
|
|
837
|
-
async function
|
|
1268
|
+
async function chat4(params) {
|
|
838
1269
|
const { config, messages, tools, signal } = params;
|
|
839
1270
|
const body = {
|
|
840
1271
|
model: config.model,
|
|
@@ -855,7 +1286,7 @@ async function chat3(params) {
|
|
|
855
1286
|
const endpoint = buildEndpoint(config, "/chat/completions");
|
|
856
1287
|
const res = await fetch(endpoint, {
|
|
857
1288
|
method: "POST",
|
|
858
|
-
headers:
|
|
1289
|
+
headers: buildHeaders2(config),
|
|
859
1290
|
body: JSON.stringify(body),
|
|
860
1291
|
signal
|
|
861
1292
|
});
|
|
@@ -898,32 +1329,36 @@ function withOpenRouter(params) {
|
|
|
898
1329
|
}
|
|
899
1330
|
};
|
|
900
1331
|
}
|
|
901
|
-
function
|
|
1332
|
+
function streamChat5(params) {
|
|
902
1333
|
switch (params.config.provider) {
|
|
903
1334
|
case "openai":
|
|
904
1335
|
case "openai-compatible":
|
|
905
|
-
return
|
|
1336
|
+
return streamChat4(params);
|
|
906
1337
|
case "anthropic":
|
|
907
1338
|
return streamChat(params);
|
|
908
1339
|
case "google":
|
|
909
1340
|
return streamChat2(params);
|
|
910
1341
|
case "openrouter":
|
|
911
|
-
return
|
|
1342
|
+
return streamChat4(withOpenRouter(params));
|
|
1343
|
+
case "ollama":
|
|
1344
|
+
return streamChat3(params);
|
|
912
1345
|
default:
|
|
913
1346
|
throw new Error(`Unsupported LLM provider: ${params.config.provider}`);
|
|
914
1347
|
}
|
|
915
1348
|
}
|
|
916
|
-
function
|
|
1349
|
+
function chat5(params) {
|
|
917
1350
|
switch (params.config.provider) {
|
|
918
1351
|
case "openai":
|
|
919
1352
|
case "openai-compatible":
|
|
920
|
-
return
|
|
1353
|
+
return chat4(params);
|
|
921
1354
|
case "anthropic":
|
|
922
1355
|
return chat(params);
|
|
923
1356
|
case "google":
|
|
924
1357
|
return chat2(params);
|
|
925
1358
|
case "openrouter":
|
|
926
|
-
return
|
|
1359
|
+
return chat4(withOpenRouter(params));
|
|
1360
|
+
case "ollama":
|
|
1361
|
+
return chat3(params);
|
|
927
1362
|
default:
|
|
928
1363
|
throw new Error(`Unsupported LLM provider: ${params.config.provider}`);
|
|
929
1364
|
}
|
|
@@ -939,7 +1374,7 @@ async function* runToolLoop(params) {
|
|
|
939
1374
|
const pendingToolCalls = [];
|
|
940
1375
|
let assistantText = "";
|
|
941
1376
|
try {
|
|
942
|
-
for await (const ev of
|
|
1377
|
+
for await (const ev of streamChat5({
|
|
943
1378
|
config,
|
|
944
1379
|
messages,
|
|
945
1380
|
tools,
|
|
@@ -977,17 +1412,17 @@ async function* runToolLoop(params) {
|
|
|
977
1412
|
let isError = false;
|
|
978
1413
|
try {
|
|
979
1414
|
result = await callTool(tc.name, tc.args);
|
|
1415
|
+
isError = isToolResultError(result);
|
|
980
1416
|
} catch (err) {
|
|
981
1417
|
isError = true;
|
|
982
1418
|
result = {
|
|
1419
|
+
isError: true,
|
|
983
1420
|
error: err instanceof Error ? err.message : String(err)
|
|
984
1421
|
};
|
|
985
1422
|
}
|
|
986
|
-
const normalized = stripMeta(result);
|
|
987
|
-
const payloadText = typeof normalized === "string" ? normalized : JSON.stringify(normalized);
|
|
988
1423
|
messages.push({
|
|
989
1424
|
role: "tool",
|
|
990
|
-
content:
|
|
1425
|
+
content: toolResultToContent(result),
|
|
991
1426
|
toolCallId: tc.id,
|
|
992
1427
|
toolName: tc.name,
|
|
993
1428
|
toolResult: result,
|
|
@@ -1003,13 +1438,6 @@ async function* runToolLoop(params) {
|
|
|
1003
1438
|
}
|
|
1004
1439
|
}
|
|
1005
1440
|
}
|
|
1006
|
-
function stripMeta(value) {
|
|
1007
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1008
|
-
const { _meta: _ignored, ...rest } = value;
|
|
1009
|
-
return rest;
|
|
1010
|
-
}
|
|
1011
|
-
return value;
|
|
1012
|
-
}
|
|
1013
1441
|
async function runToolLoopNonStreaming(params) {
|
|
1014
1442
|
const { config, tools, callTool, signal } = params;
|
|
1015
1443
|
const maxSteps = params.maxSteps ?? 10;
|
|
@@ -1018,7 +1446,7 @@ async function runToolLoopNonStreaming(params) {
|
|
|
1018
1446
|
let finalText = "";
|
|
1019
1447
|
for (let step = 0; step < maxSteps; step++) {
|
|
1020
1448
|
if (signal?.aborted) break;
|
|
1021
|
-
const { text, toolCalls } = await
|
|
1449
|
+
const { text, toolCalls } = await chat5({ config, messages, tools, signal });
|
|
1022
1450
|
if (toolCalls.length === 0) {
|
|
1023
1451
|
finalText = text;
|
|
1024
1452
|
break;
|
|
@@ -1033,9 +1461,11 @@ async function runToolLoopNonStreaming(params) {
|
|
|
1033
1461
|
let isError = false;
|
|
1034
1462
|
try {
|
|
1035
1463
|
result = await callTool(tc.name, tc.args);
|
|
1464
|
+
isError = isToolResultError(result);
|
|
1036
1465
|
} catch (err) {
|
|
1037
1466
|
isError = true;
|
|
1038
1467
|
result = {
|
|
1468
|
+
isError: true,
|
|
1039
1469
|
error: err instanceof Error ? err.message : String(err)
|
|
1040
1470
|
};
|
|
1041
1471
|
}
|
|
@@ -1044,10 +1474,9 @@ async function runToolLoopNonStreaming(params) {
|
|
|
1044
1474
|
args: tc.args,
|
|
1045
1475
|
result
|
|
1046
1476
|
});
|
|
1047
|
-
const payloadText = typeof result === "string" ? result : JSON.stringify(stripMeta(result));
|
|
1048
1477
|
messages.push({
|
|
1049
1478
|
role: "tool",
|
|
1050
|
-
content:
|
|
1479
|
+
content: toolResultToContent(result),
|
|
1051
1480
|
toolCallId: tc.id,
|
|
1052
1481
|
toolName: tc.name,
|
|
1053
1482
|
toolResult: result,
|