@burtson-labs/bandit-engine 2.0.70 → 2.0.72
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/dist/{chat-XW4JIAOE.mjs → chat-GLBQOPNT.mjs} +3 -3
- package/dist/chat-provider.js +28 -1
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +2 -2
- package/dist/{chunk-5SA7PQK4.mjs → chunk-7ZHLQXHL.mjs} +3 -3
- package/dist/{chunk-IDZEEONG.mjs → chunk-DHYP4K5O.mjs} +29 -2
- package/dist/chunk-DHYP4K5O.mjs.map +1 -0
- package/dist/{chunk-PUUL2R3T.mjs → chunk-LDL4X6CB.mjs} +140 -33
- package/dist/chunk-LDL4X6CB.mjs.map +1 -0
- package/dist/{chunk-HHMGNCBS.mjs → chunk-SXLI47FV.mjs} +2 -2
- package/dist/index.js +167 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/management/management.js +167 -33
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-IDZEEONG.mjs.map +0 -1
- package/dist/chunk-PUUL2R3T.mjs.map +0 -1
- /package/dist/{chat-XW4JIAOE.mjs.map → chat-GLBQOPNT.mjs.map} +0 -0
- /package/dist/{chunk-5SA7PQK4.mjs.map → chunk-7ZHLQXHL.mjs.map} +0 -0
- /package/dist/{chunk-HHMGNCBS.mjs.map → chunk-SXLI47FV.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
chat_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LDL4X6CB.mjs";
|
|
4
4
|
import {
|
|
5
5
|
chat_provider_default
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SXLI47FV.mjs";
|
|
7
7
|
import "./chunk-ONQMRE2G.mjs";
|
|
8
8
|
import {
|
|
9
9
|
management_default,
|
|
10
10
|
useGatewayHealth,
|
|
11
11
|
useGatewayMemory,
|
|
12
12
|
useGatewayModels
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-7ZHLQXHL.mjs";
|
|
14
14
|
import "./chunk-U633CJBV.mjs";
|
|
15
15
|
import "./chunk-6ITUH375.mjs";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-DHYP4K5O.mjs";
|
|
17
17
|
import {
|
|
18
18
|
defineCustomElement
|
|
19
19
|
} from "./chunk-IXIM7BNO.mjs";
|
|
@@ -19599,7 +19599,7 @@ ${listMarkdown}`;
|
|
|
19599
19599
|
});
|
|
19600
19600
|
|
|
19601
19601
|
// src/store/mcpToolsStore.ts
|
|
19602
|
-
var import_zustand13, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, defaultTools, useMCPToolsStore;
|
|
19602
|
+
var import_zustand13, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, createFileTool, defaultTools, useMCPToolsStore;
|
|
19603
19603
|
var init_mcpToolsStore = __esm({
|
|
19604
19604
|
"src/store/mcpToolsStore.ts"() {
|
|
19605
19605
|
"use strict";
|
|
@@ -19693,7 +19693,34 @@ var init_mcpToolsStore = __esm({
|
|
|
19693
19693
|
method: "POST",
|
|
19694
19694
|
isBuiltIn: true
|
|
19695
19695
|
};
|
|
19696
|
-
|
|
19696
|
+
createFileTool = {
|
|
19697
|
+
id: "create-file",
|
|
19698
|
+
name: "create_file",
|
|
19699
|
+
description: "Create a downloadable file for the user (md, txt, csv, json, html, docx, pptx). Returns a temporary (~1 hour) download link.",
|
|
19700
|
+
enabled: true,
|
|
19701
|
+
type: "function",
|
|
19702
|
+
function: {
|
|
19703
|
+
name: "create_file",
|
|
19704
|
+
description: "Generate a file the user can download. For docx/pptx write well-structured Markdown (headings, lists, tables; use '## ' headings to start each slide). Returns a short-lived (~1 hour) download URL \u2014 tell the user it expires.",
|
|
19705
|
+
parameters: {
|
|
19706
|
+
type: "object",
|
|
19707
|
+
properties: {
|
|
19708
|
+
content: { type: "string", description: "The file content (Markdown for docx/pptx; raw text for others)." },
|
|
19709
|
+
filename: { type: "string", description: "Desired filename, e.g. 'report.docx' or 'notes.md'." },
|
|
19710
|
+
format: {
|
|
19711
|
+
type: "string",
|
|
19712
|
+
enum: ["md", "txt", "csv", "json", "html", "xml", "yaml", "docx", "pptx"],
|
|
19713
|
+
description: "File format. Defaults to md."
|
|
19714
|
+
}
|
|
19715
|
+
},
|
|
19716
|
+
required: ["content", "format"]
|
|
19717
|
+
}
|
|
19718
|
+
},
|
|
19719
|
+
endpoint: "/mcp/create-file",
|
|
19720
|
+
method: "POST",
|
|
19721
|
+
isBuiltIn: true
|
|
19722
|
+
};
|
|
19723
|
+
defaultTools = [healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, createFileTool];
|
|
19697
19724
|
useMCPToolsStore = (0, import_zustand13.create)((set, get) => ({
|
|
19698
19725
|
tools: defaultTools,
|
|
19699
19726
|
isLoaded: false,
|
|
@@ -23178,6 +23205,7 @@ TOOL USAGE PROTOCOL (conservative approach)
|
|
|
23178
23205
|
* web_search() - when asked about recent/current events, breaking news, live information (weather, prices, sports scores), or when you need to look up documentation, libraries, APIs, error messages, or verify a specific fact
|
|
23179
23206
|
* web_fetch() - to read the FULL contents of a specific URL you already have. Reach for this when the user wants to "tell me more", "go deeper", "read/open that article", or asks for details about a specific source, link, or article from an EARLIER answer: take that item's URL from the previous Sources list in this conversation and fetch it, then answer from the page's actual content (not just the prior summary)
|
|
23180
23207
|
* image_generation() - ONLY when explicitly asked to create or generate an image
|
|
23208
|
+
* create_file({"content": "...", "filename": "report.docx", "format": "docx"}) - when the user asks for a downloadable FILE (a document, spreadsheet, slides, markdown, code, etc.) or to "export"/"download"/"save" something. Formats: md, txt, csv, json, html, xml, yaml, docx, pptx. For docx/pptx write well-structured Markdown (use "## " headings to start each slide for pptx). It returns a temporary download link \u2014 ALWAYS tell the user the file expires (~1 hour). If it is unclear whether they want it shown inline vs. as a downloadable file, use ask_user first.
|
|
23181
23209
|
* ask_user({"questions": [{"question": "...", "header": "Format", "options": [{"label": "Inline (Recommended)"}, {"label": "Download a file"}]}]}) - when you are genuinely BLOCKED on a decision that is the USER's to make and cannot resolve from the request, context, or sensible defaults (e.g. show content inline vs. let them download it, which format/option they want). Renders clickable options the user answers in one step \u2014 better than asking in prose and ending your turn. Give 1-4 questions, each with 2-4 options; if one is clearly best, list it first and append " (Recommended)". The user may also type their own answer; act on it directly.
|
|
23182
23210
|
- For general questions about concepts, definitions, explanations, or how-to topics, use your built-in knowledge WITHOUT calling tools.
|
|
23183
23211
|
- Examples of what NOT to use tools for: "who are you?", "what is React?", "explain machine learning", "how does X work?", general programming questions.
|
|
@@ -23460,6 +23488,17 @@ _${toolStatus}_` : `_${toolStatus}_`);
|
|
|
23460
23488
|
|
|
23461
23489
|
${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
23462
23490
|
` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
|
|
23491
|
+
} else if (functionName === "create_file" || functionName === "create-file") {
|
|
23492
|
+
const fileData = result.data ?? {};
|
|
23493
|
+
if (fileData.url) {
|
|
23494
|
+
const mins = fileData.expiresInMinutes ?? 60;
|
|
23495
|
+
const name = fileData.filename || "your file";
|
|
23496
|
+
resultText = `\u{1F4C4} **[${name}](${fileData.url})** \u2014 ready to download.
|
|
23497
|
+
|
|
23498
|
+
_This link is temporary and expires in about ${mins} minutes._`;
|
|
23499
|
+
} else {
|
|
23500
|
+
resultText = "The file was created.";
|
|
23501
|
+
}
|
|
23463
23502
|
} else if (typeof result.data === "string") {
|
|
23464
23503
|
resultText = result.data;
|
|
23465
23504
|
} else if (result.data) {
|
|
@@ -23474,7 +23513,7 @@ ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
|
23474
23513
|
}
|
|
23475
23514
|
enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
|
|
23476
23515
|
if (result.success) {
|
|
23477
|
-
if (functionName === "image_generation" || functionName === "image-generation") {
|
|
23516
|
+
if (functionName === "image_generation" || functionName === "image-generation" || functionName === "create_file" || functionName === "create-file") {
|
|
23478
23517
|
inlineImageBlocks.push(resultText);
|
|
23479
23518
|
} else if (functionName !== "check_gateway_health") {
|
|
23480
23519
|
summarizableResults.push({ name: functionName, output: resultText });
|
|
@@ -23506,41 +23545,38 @@ ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
|
23506
23545
|
try {
|
|
23507
23546
|
const toolResultsText = summarizableResults.map((r) => `## ${r.name}
|
|
23508
23547
|
${r.output}`).join("\n\n");
|
|
23509
|
-
const
|
|
23510
|
-
|
|
23548
|
+
const MAX_CHAIN_ROUNDS = 4;
|
|
23549
|
+
const enabledToolsForChain = getEnabledMCPToolsForAI();
|
|
23550
|
+
const convo = [
|
|
23551
|
+
{ role: "system", content: enhancedSystemPrompt },
|
|
23511
23552
|
...contextMessages,
|
|
23512
23553
|
{ role: "user", content: question },
|
|
23513
|
-
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me
|
|
23554
|
+
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me work on that." },
|
|
23514
23555
|
{
|
|
23515
23556
|
role: "user",
|
|
23516
|
-
content: `
|
|
23557
|
+
content: `Here are the results of the tool(s) so far:
|
|
23517
23558
|
|
|
23518
23559
|
${toolResultsText}
|
|
23519
23560
|
|
|
23520
|
-
|
|
23561
|
+
Use them to fully complete my original request. If you still need to take an action I asked for (for example, actually create a file I want to download), call the appropriate tool now with a \`\`\`tool_code\`\`\` block. Otherwise give your final answer. Do NOT add a "Sources"/"References"/"Citations" list \u2014 one is appended automatically.`
|
|
23521
23562
|
}
|
|
23522
23563
|
];
|
|
23523
|
-
const
|
|
23524
|
-
model: modelName,
|
|
23525
|
-
messages: summaryMessages,
|
|
23526
|
-
stream: true,
|
|
23527
|
-
options: { num_predict: tokenLimit + 250 }
|
|
23528
|
-
};
|
|
23529
|
-
clearFlushTimer();
|
|
23530
|
-
setStreamBuffer("");
|
|
23531
|
-
setIsThinking?.(true);
|
|
23532
|
-
const summaryText = await new Promise((resolve) => {
|
|
23564
|
+
const streamTurn = (req) => new Promise((resolve) => {
|
|
23533
23565
|
let acc = "";
|
|
23566
|
+
const native = [];
|
|
23534
23567
|
let settled = false;
|
|
23535
23568
|
let timer;
|
|
23536
|
-
const
|
|
23569
|
+
const finish = (value) => {
|
|
23537
23570
|
if (settled) return;
|
|
23538
23571
|
settled = true;
|
|
23539
23572
|
if (timer) clearTimeout(timer);
|
|
23540
23573
|
resolve(value);
|
|
23541
23574
|
};
|
|
23542
|
-
const
|
|
23575
|
+
const sub2 = provider.chat(req).subscribe({
|
|
23543
23576
|
next: (data) => {
|
|
23577
|
+
if (Array.isArray(data?.message?.tool_calls) && data.message.tool_calls.length) {
|
|
23578
|
+
native.push(...data.message.tool_calls);
|
|
23579
|
+
}
|
|
23544
23580
|
if (data?.message?.content) {
|
|
23545
23581
|
acc += data.message.content;
|
|
23546
23582
|
const visible = stripThinking(acc);
|
|
@@ -23550,27 +23586,125 @@ Using these results together with your own knowledge, answer my original questio
|
|
|
23550
23586
|
setStreamBuffer(visible);
|
|
23551
23587
|
}
|
|
23552
23588
|
},
|
|
23553
|
-
error: (
|
|
23554
|
-
|
|
23555
|
-
error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
|
|
23556
|
-
});
|
|
23557
|
-
done("");
|
|
23558
|
-
},
|
|
23559
|
-
complete: () => done(stripThinking(acc).trim())
|
|
23589
|
+
error: () => finish({ text: stripThinking(acc).trim(), native }),
|
|
23590
|
+
complete: () => finish({ text: stripThinking(acc).trim(), native })
|
|
23560
23591
|
});
|
|
23561
|
-
currentSubRef.current =
|
|
23592
|
+
currentSubRef.current = sub2;
|
|
23562
23593
|
timer = setTimeout(() => {
|
|
23563
|
-
debugLogger.warn("Summarization pass timed out; using inline tool output");
|
|
23564
23594
|
try {
|
|
23565
|
-
|
|
23595
|
+
sub2.unsubscribe();
|
|
23566
23596
|
} catch {
|
|
23567
23597
|
}
|
|
23568
|
-
|
|
23598
|
+
finish({ text: stripThinking(acc).trim(), native });
|
|
23569
23599
|
}, 3e4);
|
|
23570
23600
|
});
|
|
23601
|
+
const runChainedTool = async (fn, params) => {
|
|
23602
|
+
if (fn === "ask_user" || fn === "ask-user") {
|
|
23603
|
+
const qs = parseAskUserQuestions(params.questions ?? params);
|
|
23604
|
+
if (!qs.length) return "ask_user failed: it needs a questions array.";
|
|
23605
|
+
const ans = await useAskUserStore.getState().ask(qs);
|
|
23606
|
+
return ans ? "The user answered:\n\n" + qs.map((q) => `Q: ${q.question}
|
|
23607
|
+
A: ${(ans[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user dismissed the question(s). Proceed with your best judgment.";
|
|
23608
|
+
}
|
|
23609
|
+
const status = fn === "create_file" || fn === "create-file" ? "Creating the file\u2026" : fn === "web_search" || fn === "web-search" ? "Searching the web\u2026" : fn === "web_fetch" || fn === "web-fetch" ? "Reading the page\u2026" : fn === "image_generation" || fn === "image-generation" ? "Generating the image\u2026" : "Working on it\u2026";
|
|
23610
|
+
setStreamBuffer(`_${status}_`);
|
|
23611
|
+
const result = await executeMCPTool({ toolName: fn, parameters: params });
|
|
23612
|
+
if (!result.success) return `That step failed: ${result.error || "unknown error"}.`;
|
|
23613
|
+
if (fn === "create_file" || fn === "create-file") {
|
|
23614
|
+
const f = result.data ?? {};
|
|
23615
|
+
if (f.url) {
|
|
23616
|
+
const mins = f.expiresInMinutes ?? 60;
|
|
23617
|
+
const name = f.filename || "your file";
|
|
23618
|
+
inlineImageBlocks.push(`\u{1F4C4} **[${name}](${f.url})** \u2014 ready to download.
|
|
23619
|
+
|
|
23620
|
+
_This link is temporary and expires in about ${mins} minutes._`);
|
|
23621
|
+
return `File created and its download link is now shown to the user. Briefly confirm it's ready and that it expires in ~${mins} minutes.`;
|
|
23622
|
+
}
|
|
23623
|
+
return "The file was created.";
|
|
23624
|
+
}
|
|
23625
|
+
if (fn === "image_generation" || fn === "image-generation") {
|
|
23626
|
+
const img = result.data ?? {};
|
|
23627
|
+
if (img.imageUrl) {
|
|
23628
|
+
inlineImageBlocks.push(``);
|
|
23629
|
+
return "Image generated and shown to the user.";
|
|
23630
|
+
}
|
|
23631
|
+
}
|
|
23632
|
+
if (typeof result.data === "string") return result.data.slice(0, 2e3);
|
|
23633
|
+
if (result.data) return JSON.stringify(result.data).slice(0, 1500);
|
|
23634
|
+
return "Done.";
|
|
23635
|
+
};
|
|
23636
|
+
clearFlushTimer();
|
|
23637
|
+
let finalText = "";
|
|
23638
|
+
let lastTurnText = "";
|
|
23639
|
+
for (let round = 0; round < MAX_CHAIN_ROUNDS; round++) {
|
|
23640
|
+
setStreamBuffer("");
|
|
23641
|
+
setIsThinking?.(true);
|
|
23642
|
+
const turnRequest = {
|
|
23643
|
+
model: modelName,
|
|
23644
|
+
messages: convo,
|
|
23645
|
+
stream: true,
|
|
23646
|
+
tools: enabledToolsForChain.length ? enabledToolsForChain : void 0,
|
|
23647
|
+
options: { num_predict: tokenLimit + 250 }
|
|
23648
|
+
};
|
|
23649
|
+
const { text: turnText, native: turnNative } = await streamTurn(turnRequest);
|
|
23650
|
+
setIsThinking?.(false);
|
|
23651
|
+
if (turnText.trim()) lastTurnText = turnText;
|
|
23652
|
+
let toolText = turnText;
|
|
23653
|
+
if (turnNative.length && !/```(?:tool_code|TOOL_CODE)/.test(toolText)) {
|
|
23654
|
+
for (const raw of turnNative) {
|
|
23655
|
+
const tc = raw;
|
|
23656
|
+
const fnName = tc.function?.name ?? tc.name;
|
|
23657
|
+
if (!fnName) continue;
|
|
23658
|
+
const a = tc.function?.arguments ?? tc.arguments ?? {};
|
|
23659
|
+
toolText += `
|
|
23660
|
+
|
|
23661
|
+
\`\`\`tool_code
|
|
23662
|
+
${fnName}(${typeof a === "string" ? a : JSON.stringify(a ?? {})})
|
|
23663
|
+
\`\`\``;
|
|
23664
|
+
}
|
|
23665
|
+
}
|
|
23666
|
+
const chainMatches = toolText.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
|
|
23667
|
+
if (!chainMatches || !chainMatches.length) {
|
|
23668
|
+
finalText = turnText;
|
|
23669
|
+
break;
|
|
23670
|
+
}
|
|
23671
|
+
const roundOut = [];
|
|
23672
|
+
for (const m of chainMatches) {
|
|
23673
|
+
const code = m.replace(/```(?:tool_code|TOOL_CODE)\s*\n|\n```/gi, "").trim();
|
|
23674
|
+
const fm = code.match(/^(\w+)\(\s*(.*?)\s*\)$/);
|
|
23675
|
+
if (!fm) continue;
|
|
23676
|
+
const [, fnName, rawParams] = fm;
|
|
23677
|
+
let parsed = {};
|
|
23678
|
+
const rp = rawParams.trim();
|
|
23679
|
+
if (rp) {
|
|
23680
|
+
try {
|
|
23681
|
+
parsed = JSON.parse(rp.startsWith("{") ? rp : `{${rp}}`);
|
|
23682
|
+
} catch {
|
|
23683
|
+
parsed = {};
|
|
23684
|
+
}
|
|
23685
|
+
}
|
|
23686
|
+
try {
|
|
23687
|
+
roundOut.push(`## ${fnName}
|
|
23688
|
+
${await runChainedTool(fnName, parsed)}`);
|
|
23689
|
+
} catch (e) {
|
|
23690
|
+
roundOut.push(`## ${fnName}
|
|
23691
|
+
That step failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
23692
|
+
}
|
|
23693
|
+
}
|
|
23694
|
+
convo.push({ role: "assistant", content: stripToolBlocks(turnText) || "(using a tool)" });
|
|
23695
|
+
convo.push({
|
|
23696
|
+
role: "user",
|
|
23697
|
+
content: `Tool results:
|
|
23698
|
+
|
|
23699
|
+
${roundOut.join("\n\n")}
|
|
23700
|
+
|
|
23701
|
+
Now give your final answer to my original request, or call another tool if you still genuinely need to. Do NOT add a "Sources" list.`
|
|
23702
|
+
});
|
|
23703
|
+
}
|
|
23571
23704
|
setIsThinking?.(false);
|
|
23572
|
-
|
|
23573
|
-
|
|
23705
|
+
const answerText = finalText.trim() ? finalText : lastTurnText;
|
|
23706
|
+
if (answerText.trim() || inlineImageBlocks.length) {
|
|
23707
|
+
const cleanedSummary = answerText.replace(
|
|
23574
23708
|
/\n{1,}\s*(?:[*_#>\s]*)(?:sources?|references?|citations?|further reading)(?:\s*:)?\s*(?:[*_]*)\s*\n[\s\S]*$/i,
|
|
23575
23709
|
""
|
|
23576
23710
|
).trimEnd();
|