@burtson-labs/bandit-engine 2.0.71 → 2.0.73
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-2QWK6OUO.mjs → chat-W4HX45DE.mjs} +2 -2
- package/dist/{chunk-VKDU2OMI.mjs → chunk-E7GT3NR4.mjs} +2 -2
- package/dist/{chunk-CMBYMC3G.mjs → chunk-YBQRVTZF.mjs} +161 -37
- package/dist/chunk-YBQRVTZF.mjs.map +1 -0
- package/dist/index.js +160 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/management/management.js +160 -36
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-CMBYMC3G.mjs.map +0 -1
- /package/dist/{chat-2QWK6OUO.mjs.map → chat-W4HX45DE.mjs.map} +0 -0
- /package/dist/{chunk-VKDU2OMI.mjs.map → chunk-E7GT3NR4.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -21021,6 +21021,7 @@ ${protocol}`;
|
|
|
21021
21021
|
let latestDisplayMessage = "";
|
|
21022
21022
|
let sawToolBlock = false;
|
|
21023
21023
|
const nativeToolCalls = [];
|
|
21024
|
+
let workingTimer = null;
|
|
21024
21025
|
const stripThinking = (text) => {
|
|
21025
21026
|
let result = text.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
21026
21027
|
const openIdx = result.indexOf("<think>");
|
|
@@ -21028,6 +21029,28 @@ ${protocol}`;
|
|
|
21028
21029
|
return result.trimStart();
|
|
21029
21030
|
};
|
|
21030
21031
|
const stripToolBlocks = (text) => text.replace(/```(?:tool_code|TOOL_CODE)\s*\n[\s\S]*?\n```/gi, "").trim();
|
|
21032
|
+
let workingLabel = "Working on it";
|
|
21033
|
+
let workingPreamble = "";
|
|
21034
|
+
const stopWorking = () => {
|
|
21035
|
+
if (workingTimer) {
|
|
21036
|
+
clearInterval(workingTimer);
|
|
21037
|
+
workingTimer = null;
|
|
21038
|
+
}
|
|
21039
|
+
};
|
|
21040
|
+
const startWorking = (label = "Working on it") => {
|
|
21041
|
+
workingLabel = label.replace(/[.…]+\s*$/, "").trim() || "Working on it";
|
|
21042
|
+
workingPreamble = stripToolBlocks(stripThinking(fullMessage)).trim();
|
|
21043
|
+
if (workingTimer) return;
|
|
21044
|
+
let dots = 0;
|
|
21045
|
+
const render = () => {
|
|
21046
|
+
dots = (dots + 1) % 4;
|
|
21047
|
+
setStreamBuffer(
|
|
21048
|
+
`${workingPreamble}${workingPreamble ? "\n\n" : ""}_${workingLabel}${".".repeat(dots)}_`
|
|
21049
|
+
);
|
|
21050
|
+
};
|
|
21051
|
+
render();
|
|
21052
|
+
workingTimer = setInterval(render, 450);
|
|
21053
|
+
};
|
|
21031
21054
|
const flushNow = () => {
|
|
21032
21055
|
clearFlushTimer();
|
|
21033
21056
|
if (!sawToolBlock) {
|
|
@@ -21063,6 +21086,7 @@ ${protocol}`;
|
|
|
21063
21086
|
nativeToolCalls.push(...data.message.tool_calls);
|
|
21064
21087
|
sawToolBlock = true;
|
|
21065
21088
|
clearFlushTimer();
|
|
21089
|
+
startWorking();
|
|
21066
21090
|
}
|
|
21067
21091
|
if (data.message.content) {
|
|
21068
21092
|
fullMessage += data.message.content;
|
|
@@ -21074,6 +21098,7 @@ ${protocol}`;
|
|
|
21074
21098
|
if (/```(?:tool_code|TOOL_CODE)/.test(visibleMessage)) {
|
|
21075
21099
|
sawToolBlock = true;
|
|
21076
21100
|
clearFlushTimer();
|
|
21101
|
+
startWorking();
|
|
21077
21102
|
}
|
|
21078
21103
|
latestDisplayMessage = visibleMessage;
|
|
21079
21104
|
if (!sawToolBlock) {
|
|
@@ -21082,6 +21107,7 @@ ${protocol}`;
|
|
|
21082
21107
|
},
|
|
21083
21108
|
error: (err) => {
|
|
21084
21109
|
debugLogger.error("Stream error:", err);
|
|
21110
|
+
stopWorking();
|
|
21085
21111
|
overrideComponentStatus("Idle");
|
|
21086
21112
|
setIsSubmitting(false);
|
|
21087
21113
|
setIsStreaming(false);
|
|
@@ -21166,6 +21192,7 @@ ${fn}(${argStr})
|
|
|
21166
21192
|
if (functionName === "ask_user" || functionName === "ask-user") {
|
|
21167
21193
|
enhancedMessage = enhancedMessage.replace(match, "");
|
|
21168
21194
|
clearFlushTimer();
|
|
21195
|
+
stopWorking();
|
|
21169
21196
|
const askPreamble = stripToolBlocks(fullMessage).trim();
|
|
21170
21197
|
setStreamBuffer(askPreamble || "_Waiting for your answer\u2026_");
|
|
21171
21198
|
const questions = parseAskUserQuestions(
|
|
@@ -21188,11 +21215,8 @@ A: ${(answers[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user d
|
|
|
21188
21215
|
const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
|
|
21189
21216
|
enhancedMessage = enhancedMessage.replace(match, placeholderToken);
|
|
21190
21217
|
clearFlushTimer();
|
|
21191
|
-
const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web
|
|
21192
|
-
|
|
21193
|
-
setStreamBuffer(toolPreamble ? `${toolPreamble}
|
|
21194
|
-
|
|
21195
|
-
_${toolStatus}_` : `_${toolStatus}_`);
|
|
21218
|
+
const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image" : functionName === "create_file" || functionName === "create-file" ? "Creating your file" : "Working on it";
|
|
21219
|
+
startWorking(toolStatus);
|
|
21196
21220
|
telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
|
|
21197
21221
|
const result = await executeMCPTool({
|
|
21198
21222
|
toolName: functionName,
|
|
@@ -21306,71 +21330,170 @@ _This link is temporary and expires in about ${mins} minutes._`;
|
|
|
21306
21330
|
try {
|
|
21307
21331
|
const toolResultsText = summarizableResults.map((r) => `## ${r.name}
|
|
21308
21332
|
${r.output}`).join("\n\n");
|
|
21309
|
-
const
|
|
21310
|
-
|
|
21333
|
+
const MAX_CHAIN_ROUNDS = 4;
|
|
21334
|
+
const enabledToolsForChain = getEnabledMCPToolsForAI();
|
|
21335
|
+
const convo = [
|
|
21336
|
+
{ role: "system", content: enhancedSystemPrompt },
|
|
21311
21337
|
...contextMessages,
|
|
21312
21338
|
{ role: "user", content: question },
|
|
21313
|
-
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me
|
|
21339
|
+
{ role: "assistant", content: stripToolBlocks(fullMessage) || "Let me work on that." },
|
|
21314
21340
|
{
|
|
21315
21341
|
role: "user",
|
|
21316
|
-
content: `
|
|
21342
|
+
content: `Here are the results of the tool(s) so far:
|
|
21317
21343
|
|
|
21318
21344
|
${toolResultsText}
|
|
21319
21345
|
|
|
21320
|
-
|
|
21346
|
+
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.`
|
|
21321
21347
|
}
|
|
21322
21348
|
];
|
|
21323
|
-
const
|
|
21324
|
-
model: modelName,
|
|
21325
|
-
messages: summaryMessages,
|
|
21326
|
-
stream: true,
|
|
21327
|
-
options: { num_predict: tokenLimit + 250 }
|
|
21328
|
-
};
|
|
21329
|
-
clearFlushTimer();
|
|
21330
|
-
setStreamBuffer("");
|
|
21331
|
-
setIsThinking?.(true);
|
|
21332
|
-
const summaryText = await new Promise((resolve) => {
|
|
21349
|
+
const streamTurn = (req) => new Promise((resolve) => {
|
|
21333
21350
|
let acc = "";
|
|
21351
|
+
const native = [];
|
|
21334
21352
|
let settled = false;
|
|
21335
21353
|
let timer;
|
|
21336
|
-
const
|
|
21354
|
+
const finish = (value) => {
|
|
21337
21355
|
if (settled) return;
|
|
21338
21356
|
settled = true;
|
|
21339
21357
|
if (timer) clearTimeout(timer);
|
|
21340
21358
|
resolve(value);
|
|
21341
21359
|
};
|
|
21342
|
-
const
|
|
21360
|
+
const sub2 = provider.chat(req).subscribe({
|
|
21343
21361
|
next: (data) => {
|
|
21362
|
+
if (Array.isArray(data?.message?.tool_calls) && data.message.tool_calls.length) {
|
|
21363
|
+
native.push(...data.message.tool_calls);
|
|
21364
|
+
}
|
|
21344
21365
|
if (data?.message?.content) {
|
|
21345
21366
|
acc += data.message.content;
|
|
21346
21367
|
const visible = stripThinking(acc);
|
|
21347
21368
|
latestDisplayMessage = visible;
|
|
21348
21369
|
lastPartialRef.current.text = visible;
|
|
21349
|
-
if (visible)
|
|
21370
|
+
if (visible) {
|
|
21371
|
+
stopWorking();
|
|
21372
|
+
setIsThinking?.(false);
|
|
21373
|
+
}
|
|
21350
21374
|
setStreamBuffer(visible);
|
|
21351
21375
|
}
|
|
21352
21376
|
},
|
|
21353
|
-
error: (
|
|
21354
|
-
|
|
21355
|
-
error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
|
|
21356
|
-
});
|
|
21357
|
-
done("");
|
|
21358
|
-
},
|
|
21359
|
-
complete: () => done(stripThinking(acc).trim())
|
|
21377
|
+
error: () => finish({ text: stripThinking(acc).trim(), native }),
|
|
21378
|
+
complete: () => finish({ text: stripThinking(acc).trim(), native })
|
|
21360
21379
|
});
|
|
21361
|
-
currentSubRef.current =
|
|
21380
|
+
currentSubRef.current = sub2;
|
|
21362
21381
|
timer = setTimeout(() => {
|
|
21363
|
-
debugLogger.warn("Summarization pass timed out; using inline tool output");
|
|
21364
21382
|
try {
|
|
21365
|
-
|
|
21383
|
+
sub2.unsubscribe();
|
|
21366
21384
|
} catch {
|
|
21367
21385
|
}
|
|
21368
|
-
|
|
21386
|
+
finish({ text: stripThinking(acc).trim(), native });
|
|
21369
21387
|
}, 3e4);
|
|
21370
21388
|
});
|
|
21389
|
+
const runChainedTool = async (fn, params) => {
|
|
21390
|
+
if (fn === "ask_user" || fn === "ask-user") {
|
|
21391
|
+
const qs = parseAskUserQuestions(params.questions ?? params);
|
|
21392
|
+
if (!qs.length) return "ask_user failed: it needs a questions array.";
|
|
21393
|
+
const ans = await useAskUserStore.getState().ask(qs);
|
|
21394
|
+
return ans ? "The user answered:\n\n" + qs.map((q) => `Q: ${q.question}
|
|
21395
|
+
A: ${(ans[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user dismissed the question(s). Proceed with your best judgment.";
|
|
21396
|
+
}
|
|
21397
|
+
const status = fn === "create_file" || fn === "create-file" ? "Creating your file" : fn === "web_search" || fn === "web-search" ? "Searching the web" : fn === "web_fetch" || fn === "web-fetch" ? "Reading the page" : fn === "image_generation" || fn === "image-generation" ? "Generating the image" : "Working on it";
|
|
21398
|
+
startWorking(status);
|
|
21399
|
+
const result = await executeMCPTool({ toolName: fn, parameters: params });
|
|
21400
|
+
if (!result.success) return `That step failed: ${result.error || "unknown error"}.`;
|
|
21401
|
+
if (fn === "create_file" || fn === "create-file") {
|
|
21402
|
+
const f = result.data ?? {};
|
|
21403
|
+
if (f.url) {
|
|
21404
|
+
const mins = f.expiresInMinutes ?? 60;
|
|
21405
|
+
const name = f.filename || "your file";
|
|
21406
|
+
inlineImageBlocks.push(`\u{1F4C4} **[${name}](${f.url})** \u2014 ready to download.
|
|
21407
|
+
|
|
21408
|
+
_This link is temporary and expires in about ${mins} minutes._`);
|
|
21409
|
+
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.`;
|
|
21410
|
+
}
|
|
21411
|
+
return "The file was created.";
|
|
21412
|
+
}
|
|
21413
|
+
if (fn === "image_generation" || fn === "image-generation") {
|
|
21414
|
+
const img = result.data ?? {};
|
|
21415
|
+
if (img.imageUrl) {
|
|
21416
|
+
inlineImageBlocks.push(``);
|
|
21417
|
+
return "Image generated and shown to the user.";
|
|
21418
|
+
}
|
|
21419
|
+
}
|
|
21420
|
+
if (typeof result.data === "string") return result.data.slice(0, 2e3);
|
|
21421
|
+
if (result.data) return JSON.stringify(result.data).slice(0, 1500);
|
|
21422
|
+
return "Done.";
|
|
21423
|
+
};
|
|
21424
|
+
clearFlushTimer();
|
|
21425
|
+
let finalText = "";
|
|
21426
|
+
let lastTurnText = "";
|
|
21427
|
+
for (let round = 0; round < MAX_CHAIN_ROUNDS; round++) {
|
|
21428
|
+
stopWorking();
|
|
21429
|
+
setStreamBuffer("");
|
|
21430
|
+
setIsThinking?.(true);
|
|
21431
|
+
const turnRequest = {
|
|
21432
|
+
model: modelName,
|
|
21433
|
+
messages: convo,
|
|
21434
|
+
stream: true,
|
|
21435
|
+
tools: enabledToolsForChain.length ? enabledToolsForChain : void 0,
|
|
21436
|
+
options: { num_predict: tokenLimit + 250 }
|
|
21437
|
+
};
|
|
21438
|
+
const { text: turnText, native: turnNative } = await streamTurn(turnRequest);
|
|
21439
|
+
setIsThinking?.(false);
|
|
21440
|
+
if (turnText.trim()) lastTurnText = turnText;
|
|
21441
|
+
let toolText = turnText;
|
|
21442
|
+
if (turnNative.length && !/```(?:tool_code|TOOL_CODE)/.test(toolText)) {
|
|
21443
|
+
for (const raw of turnNative) {
|
|
21444
|
+
const tc = raw;
|
|
21445
|
+
const fnName = tc.function?.name ?? tc.name;
|
|
21446
|
+
if (!fnName) continue;
|
|
21447
|
+
const a = tc.function?.arguments ?? tc.arguments ?? {};
|
|
21448
|
+
toolText += `
|
|
21449
|
+
|
|
21450
|
+
\`\`\`tool_code
|
|
21451
|
+
${fnName}(${typeof a === "string" ? a : JSON.stringify(a ?? {})})
|
|
21452
|
+
\`\`\``;
|
|
21453
|
+
}
|
|
21454
|
+
}
|
|
21455
|
+
const chainMatches = toolText.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
|
|
21456
|
+
if (!chainMatches || !chainMatches.length) {
|
|
21457
|
+
finalText = turnText;
|
|
21458
|
+
break;
|
|
21459
|
+
}
|
|
21460
|
+
const roundOut = [];
|
|
21461
|
+
for (const m of chainMatches) {
|
|
21462
|
+
const code = m.replace(/```(?:tool_code|TOOL_CODE)\s*\n|\n```/gi, "").trim();
|
|
21463
|
+
const fm = code.match(/^(\w+)\(\s*(.*?)\s*\)$/);
|
|
21464
|
+
if (!fm) continue;
|
|
21465
|
+
const [, fnName, rawParams] = fm;
|
|
21466
|
+
let parsed = {};
|
|
21467
|
+
const rp = rawParams.trim();
|
|
21468
|
+
if (rp) {
|
|
21469
|
+
try {
|
|
21470
|
+
parsed = JSON.parse(rp.startsWith("{") ? rp : `{${rp}}`);
|
|
21471
|
+
} catch {
|
|
21472
|
+
parsed = {};
|
|
21473
|
+
}
|
|
21474
|
+
}
|
|
21475
|
+
try {
|
|
21476
|
+
roundOut.push(`## ${fnName}
|
|
21477
|
+
${await runChainedTool(fnName, parsed)}`);
|
|
21478
|
+
} catch (e) {
|
|
21479
|
+
roundOut.push(`## ${fnName}
|
|
21480
|
+
That step failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
21481
|
+
}
|
|
21482
|
+
}
|
|
21483
|
+
convo.push({ role: "assistant", content: stripToolBlocks(turnText) || "(using a tool)" });
|
|
21484
|
+
convo.push({
|
|
21485
|
+
role: "user",
|
|
21486
|
+
content: `Tool results:
|
|
21487
|
+
|
|
21488
|
+
${roundOut.join("\n\n")}
|
|
21489
|
+
|
|
21490
|
+
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.`
|
|
21491
|
+
});
|
|
21492
|
+
}
|
|
21371
21493
|
setIsThinking?.(false);
|
|
21372
|
-
|
|
21373
|
-
|
|
21494
|
+
const answerText = finalText.trim() ? finalText : lastTurnText;
|
|
21495
|
+
if (answerText.trim() || inlineImageBlocks.length) {
|
|
21496
|
+
const cleanedSummary = answerText.replace(
|
|
21374
21497
|
/\n{1,}\s*(?:[*_#>\s]*)(?:sources?|references?|citations?|further reading)(?:\s*:)?\s*(?:[*_]*)\s*\n[\s\S]*$/i,
|
|
21375
21498
|
""
|
|
21376
21499
|
).trimEnd();
|
|
@@ -21396,6 +21519,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
21396
21519
|
}
|
|
21397
21520
|
}
|
|
21398
21521
|
}
|
|
21522
|
+
stopWorking();
|
|
21399
21523
|
overrideComponentStatus("Idle");
|
|
21400
21524
|
setIsSubmitting(false);
|
|
21401
21525
|
setPreviousQuestion(question);
|