@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
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-6ITUH375.mjs";
|
|
7
7
|
import {
|
|
8
8
|
useMCPToolsStore
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DHYP4K5O.mjs";
|
|
10
10
|
import {
|
|
11
11
|
AUTH_TOKEN_CHANGED_EVENT,
|
|
12
12
|
FeatureFlagProvider,
|
|
@@ -188,4 +188,4 @@ export {
|
|
|
188
188
|
ChatProvider,
|
|
189
189
|
chat_provider_default
|
|
190
190
|
};
|
|
191
|
-
//# sourceMappingURL=chunk-
|
|
191
|
+
//# sourceMappingURL=chunk-SXLI47FV.mjs.map
|
package/dist/index.js
CHANGED
|
@@ -3264,7 +3264,7 @@ var init_knowledgeStore = __esm({
|
|
|
3264
3264
|
});
|
|
3265
3265
|
|
|
3266
3266
|
// src/store/mcpToolsStore.ts
|
|
3267
|
-
var import_zustand11, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, defaultTools, useMCPToolsStore;
|
|
3267
|
+
var import_zustand11, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, createFileTool, defaultTools, useMCPToolsStore;
|
|
3268
3268
|
var init_mcpToolsStore = __esm({
|
|
3269
3269
|
"src/store/mcpToolsStore.ts"() {
|
|
3270
3270
|
"use strict";
|
|
@@ -3358,7 +3358,34 @@ var init_mcpToolsStore = __esm({
|
|
|
3358
3358
|
method: "POST",
|
|
3359
3359
|
isBuiltIn: true
|
|
3360
3360
|
};
|
|
3361
|
-
|
|
3361
|
+
createFileTool = {
|
|
3362
|
+
id: "create-file",
|
|
3363
|
+
name: "create_file",
|
|
3364
|
+
description: "Create a downloadable file for the user (md, txt, csv, json, html, docx, pptx). Returns a temporary (~1 hour) download link.",
|
|
3365
|
+
enabled: true,
|
|
3366
|
+
type: "function",
|
|
3367
|
+
function: {
|
|
3368
|
+
name: "create_file",
|
|
3369
|
+
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.",
|
|
3370
|
+
parameters: {
|
|
3371
|
+
type: "object",
|
|
3372
|
+
properties: {
|
|
3373
|
+
content: { type: "string", description: "The file content (Markdown for docx/pptx; raw text for others)." },
|
|
3374
|
+
filename: { type: "string", description: "Desired filename, e.g. 'report.docx' or 'notes.md'." },
|
|
3375
|
+
format: {
|
|
3376
|
+
type: "string",
|
|
3377
|
+
enum: ["md", "txt", "csv", "json", "html", "xml", "yaml", "docx", "pptx"],
|
|
3378
|
+
description: "File format. Defaults to md."
|
|
3379
|
+
}
|
|
3380
|
+
},
|
|
3381
|
+
required: ["content", "format"]
|
|
3382
|
+
}
|
|
3383
|
+
},
|
|
3384
|
+
endpoint: "/mcp/create-file",
|
|
3385
|
+
method: "POST",
|
|
3386
|
+
isBuiltIn: true
|
|
3387
|
+
};
|
|
3388
|
+
defaultTools = [healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, createFileTool];
|
|
3362
3389
|
useMCPToolsStore = (0, import_zustand11.create)((set, get) => ({
|
|
3363
3390
|
tools: defaultTools,
|
|
3364
3391
|
isLoaded: false,
|
|
@@ -20939,6 +20966,7 @@ TOOL USAGE PROTOCOL (conservative approach)
|
|
|
20939
20966
|
* 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
|
|
20940
20967
|
* 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)
|
|
20941
20968
|
* image_generation() - ONLY when explicitly asked to create or generate an image
|
|
20969
|
+
* 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.
|
|
20942
20970
|
* 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.
|
|
20943
20971
|
- For general questions about concepts, definitions, explanations, or how-to topics, use your built-in knowledge WITHOUT calling tools.
|
|
20944
20972
|
- Examples of what NOT to use tools for: "who are you?", "what is React?", "explain machine learning", "how does X work?", general programming questions.
|
|
@@ -21221,6 +21249,17 @@ _${toolStatus}_` : `_${toolStatus}_`);
|
|
|
21221
21249
|
|
|
21222
21250
|
${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
21223
21251
|
` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
|
|
21252
|
+
} else if (functionName === "create_file" || functionName === "create-file") {
|
|
21253
|
+
const fileData = result.data ?? {};
|
|
21254
|
+
if (fileData.url) {
|
|
21255
|
+
const mins = fileData.expiresInMinutes ?? 60;
|
|
21256
|
+
const name = fileData.filename || "your file";
|
|
21257
|
+
resultText = `\u{1F4C4} **[${name}](${fileData.url})** \u2014 ready to download.
|
|
21258
|
+
|
|
21259
|
+
_This link is temporary and expires in about ${mins} minutes._`;
|
|
21260
|
+
} else {
|
|
21261
|
+
resultText = "The file was created.";
|
|
21262
|
+
}
|
|
21224
21263
|
} else if (typeof result.data === "string") {
|
|
21225
21264
|
resultText = result.data;
|
|
21226
21265
|
} else if (result.data) {
|
|
@@ -21235,7 +21274,7 @@ ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
|
21235
21274
|
}
|
|
21236
21275
|
enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
|
|
21237
21276
|
if (result.success) {
|
|
21238
|
-
if (functionName === "image_generation" || functionName === "image-generation") {
|
|
21277
|
+
if (functionName === "image_generation" || functionName === "image-generation" || functionName === "create_file" || functionName === "create-file") {
|
|
21239
21278
|
inlineImageBlocks.push(resultText);
|
|
21240
21279
|
} else if (functionName !== "check_gateway_health") {
|
|
21241
21280
|
summarizableResults.push({ name: functionName, output: resultText });
|
|
@@ -21267,41 +21306,38 @@ ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
|
|
|
21267
21306
|
try {
|
|
21268
21307
|
const toolResultsText = summarizableResults.map((r) => `## ${r.name}
|
|
21269
21308
|
${r.output}`).join("\n\n");
|
|
21270
|
-
const
|
|
21271
|
-
|
|
21309
|
+
const MAX_CHAIN_ROUNDS = 4;
|
|
21310
|
+
const enabledToolsForChain = getEnabledMCPToolsForAI();
|
|
21311
|
+
const convo = [
|
|
21312
|
+
{ role: "system", content: enhancedSystemPrompt },
|
|
21272
21313
|
...contextMessages,
|
|
21273
21314
|
{ role: "user", content: question },
|
|
21274
|
-
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me
|
|
21315
|
+
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me work on that." },
|
|
21275
21316
|
{
|
|
21276
21317
|
role: "user",
|
|
21277
|
-
content: `
|
|
21318
|
+
content: `Here are the results of the tool(s) so far:
|
|
21278
21319
|
|
|
21279
21320
|
${toolResultsText}
|
|
21280
21321
|
|
|
21281
|
-
|
|
21322
|
+
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.`
|
|
21282
21323
|
}
|
|
21283
21324
|
];
|
|
21284
|
-
const
|
|
21285
|
-
model: modelName,
|
|
21286
|
-
messages: summaryMessages,
|
|
21287
|
-
stream: true,
|
|
21288
|
-
options: { num_predict: tokenLimit + 250 }
|
|
21289
|
-
};
|
|
21290
|
-
clearFlushTimer();
|
|
21291
|
-
setStreamBuffer("");
|
|
21292
|
-
setIsThinking?.(true);
|
|
21293
|
-
const summaryText = await new Promise((resolve) => {
|
|
21325
|
+
const streamTurn = (req) => new Promise((resolve) => {
|
|
21294
21326
|
let acc = "";
|
|
21327
|
+
const native = [];
|
|
21295
21328
|
let settled = false;
|
|
21296
21329
|
let timer;
|
|
21297
|
-
const
|
|
21330
|
+
const finish = (value) => {
|
|
21298
21331
|
if (settled) return;
|
|
21299
21332
|
settled = true;
|
|
21300
21333
|
if (timer) clearTimeout(timer);
|
|
21301
21334
|
resolve(value);
|
|
21302
21335
|
};
|
|
21303
|
-
const
|
|
21336
|
+
const sub2 = provider.chat(req).subscribe({
|
|
21304
21337
|
next: (data) => {
|
|
21338
|
+
if (Array.isArray(data?.message?.tool_calls) && data.message.tool_calls.length) {
|
|
21339
|
+
native.push(...data.message.tool_calls);
|
|
21340
|
+
}
|
|
21305
21341
|
if (data?.message?.content) {
|
|
21306
21342
|
acc += data.message.content;
|
|
21307
21343
|
const visible = stripThinking(acc);
|
|
@@ -21311,27 +21347,125 @@ Using these results together with your own knowledge, answer my original questio
|
|
|
21311
21347
|
setStreamBuffer(visible);
|
|
21312
21348
|
}
|
|
21313
21349
|
},
|
|
21314
|
-
error: (
|
|
21315
|
-
|
|
21316
|
-
error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
|
|
21317
|
-
});
|
|
21318
|
-
done("");
|
|
21319
|
-
},
|
|
21320
|
-
complete: () => done(stripThinking(acc).trim())
|
|
21350
|
+
error: () => finish({ text: stripThinking(acc).trim(), native }),
|
|
21351
|
+
complete: () => finish({ text: stripThinking(acc).trim(), native })
|
|
21321
21352
|
});
|
|
21322
|
-
currentSubRef.current =
|
|
21353
|
+
currentSubRef.current = sub2;
|
|
21323
21354
|
timer = setTimeout(() => {
|
|
21324
|
-
debugLogger.warn("Summarization pass timed out; using inline tool output");
|
|
21325
21355
|
try {
|
|
21326
|
-
|
|
21356
|
+
sub2.unsubscribe();
|
|
21327
21357
|
} catch {
|
|
21328
21358
|
}
|
|
21329
|
-
|
|
21359
|
+
finish({ text: stripThinking(acc).trim(), native });
|
|
21330
21360
|
}, 3e4);
|
|
21331
21361
|
});
|
|
21362
|
+
const runChainedTool = async (fn, params) => {
|
|
21363
|
+
if (fn === "ask_user" || fn === "ask-user") {
|
|
21364
|
+
const qs = parseAskUserQuestions(params.questions ?? params);
|
|
21365
|
+
if (!qs.length) return "ask_user failed: it needs a questions array.";
|
|
21366
|
+
const ans = await useAskUserStore.getState().ask(qs);
|
|
21367
|
+
return ans ? "The user answered:\n\n" + qs.map((q) => `Q: ${q.question}
|
|
21368
|
+
A: ${(ans[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user dismissed the question(s). Proceed with your best judgment.";
|
|
21369
|
+
}
|
|
21370
|
+
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";
|
|
21371
|
+
setStreamBuffer(`_${status}_`);
|
|
21372
|
+
const result = await executeMCPTool({ toolName: fn, parameters: params });
|
|
21373
|
+
if (!result.success) return `That step failed: ${result.error || "unknown error"}.`;
|
|
21374
|
+
if (fn === "create_file" || fn === "create-file") {
|
|
21375
|
+
const f = result.data ?? {};
|
|
21376
|
+
if (f.url) {
|
|
21377
|
+
const mins = f.expiresInMinutes ?? 60;
|
|
21378
|
+
const name = f.filename || "your file";
|
|
21379
|
+
inlineImageBlocks.push(`\u{1F4C4} **[${name}](${f.url})** \u2014 ready to download.
|
|
21380
|
+
|
|
21381
|
+
_This link is temporary and expires in about ${mins} minutes._`);
|
|
21382
|
+
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.`;
|
|
21383
|
+
}
|
|
21384
|
+
return "The file was created.";
|
|
21385
|
+
}
|
|
21386
|
+
if (fn === "image_generation" || fn === "image-generation") {
|
|
21387
|
+
const img = result.data ?? {};
|
|
21388
|
+
if (img.imageUrl) {
|
|
21389
|
+
inlineImageBlocks.push(``);
|
|
21390
|
+
return "Image generated and shown to the user.";
|
|
21391
|
+
}
|
|
21392
|
+
}
|
|
21393
|
+
if (typeof result.data === "string") return result.data.slice(0, 2e3);
|
|
21394
|
+
if (result.data) return JSON.stringify(result.data).slice(0, 1500);
|
|
21395
|
+
return "Done.";
|
|
21396
|
+
};
|
|
21397
|
+
clearFlushTimer();
|
|
21398
|
+
let finalText = "";
|
|
21399
|
+
let lastTurnText = "";
|
|
21400
|
+
for (let round = 0; round < MAX_CHAIN_ROUNDS; round++) {
|
|
21401
|
+
setStreamBuffer("");
|
|
21402
|
+
setIsThinking?.(true);
|
|
21403
|
+
const turnRequest = {
|
|
21404
|
+
model: modelName,
|
|
21405
|
+
messages: convo,
|
|
21406
|
+
stream: true,
|
|
21407
|
+
tools: enabledToolsForChain.length ? enabledToolsForChain : void 0,
|
|
21408
|
+
options: { num_predict: tokenLimit + 250 }
|
|
21409
|
+
};
|
|
21410
|
+
const { text: turnText, native: turnNative } = await streamTurn(turnRequest);
|
|
21411
|
+
setIsThinking?.(false);
|
|
21412
|
+
if (turnText.trim()) lastTurnText = turnText;
|
|
21413
|
+
let toolText = turnText;
|
|
21414
|
+
if (turnNative.length && !/```(?:tool_code|TOOL_CODE)/.test(toolText)) {
|
|
21415
|
+
for (const raw of turnNative) {
|
|
21416
|
+
const tc = raw;
|
|
21417
|
+
const fnName = tc.function?.name ?? tc.name;
|
|
21418
|
+
if (!fnName) continue;
|
|
21419
|
+
const a = tc.function?.arguments ?? tc.arguments ?? {};
|
|
21420
|
+
toolText += `
|
|
21421
|
+
|
|
21422
|
+
\`\`\`tool_code
|
|
21423
|
+
${fnName}(${typeof a === "string" ? a : JSON.stringify(a ?? {})})
|
|
21424
|
+
\`\`\``;
|
|
21425
|
+
}
|
|
21426
|
+
}
|
|
21427
|
+
const chainMatches = toolText.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
|
|
21428
|
+
if (!chainMatches || !chainMatches.length) {
|
|
21429
|
+
finalText = turnText;
|
|
21430
|
+
break;
|
|
21431
|
+
}
|
|
21432
|
+
const roundOut = [];
|
|
21433
|
+
for (const m of chainMatches) {
|
|
21434
|
+
const code = m.replace(/```(?:tool_code|TOOL_CODE)\s*\n|\n```/gi, "").trim();
|
|
21435
|
+
const fm = code.match(/^(\w+)\(\s*(.*?)\s*\)$/);
|
|
21436
|
+
if (!fm) continue;
|
|
21437
|
+
const [, fnName, rawParams] = fm;
|
|
21438
|
+
let parsed = {};
|
|
21439
|
+
const rp = rawParams.trim();
|
|
21440
|
+
if (rp) {
|
|
21441
|
+
try {
|
|
21442
|
+
parsed = JSON.parse(rp.startsWith("{") ? rp : `{${rp}}`);
|
|
21443
|
+
} catch {
|
|
21444
|
+
parsed = {};
|
|
21445
|
+
}
|
|
21446
|
+
}
|
|
21447
|
+
try {
|
|
21448
|
+
roundOut.push(`## ${fnName}
|
|
21449
|
+
${await runChainedTool(fnName, parsed)}`);
|
|
21450
|
+
} catch (e) {
|
|
21451
|
+
roundOut.push(`## ${fnName}
|
|
21452
|
+
That step failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
21453
|
+
}
|
|
21454
|
+
}
|
|
21455
|
+
convo.push({ role: "assistant", content: stripToolBlocks(turnText) || "(using a tool)" });
|
|
21456
|
+
convo.push({
|
|
21457
|
+
role: "user",
|
|
21458
|
+
content: `Tool results:
|
|
21459
|
+
|
|
21460
|
+
${roundOut.join("\n\n")}
|
|
21461
|
+
|
|
21462
|
+
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.`
|
|
21463
|
+
});
|
|
21464
|
+
}
|
|
21332
21465
|
setIsThinking?.(false);
|
|
21333
|
-
|
|
21334
|
-
|
|
21466
|
+
const answerText = finalText.trim() ? finalText : lastTurnText;
|
|
21467
|
+
if (answerText.trim() || inlineImageBlocks.length) {
|
|
21468
|
+
const cleanedSummary = answerText.replace(
|
|
21335
21469
|
/\n{1,}\s*(?:[*_#>\s]*)(?:sources?|references?|citations?|further reading)(?:\s*:)?\s*(?:[*_]*)\s*\n[\s\S]*$/i,
|
|
21336
21470
|
""
|
|
21337
21471
|
).trimEnd();
|