@burtson-labs/bandit-engine 2.0.59 → 2.0.61
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-E23BSMK5.mjs +16 -0
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +5 -5
- package/dist/{chunk-KNBWR4DS.mjs → chunk-5WQMMCZQ.mjs} +3 -3
- package/dist/{chunk-QPBG6JQE.mjs → chunk-6QTTNYF2.mjs} +2 -2
- package/dist/{chunk-POTQI33D.mjs → chunk-D55E6ZDV.mjs} +5 -5
- package/dist/{chunk-557E5VZ2.mjs → chunk-EUBVBTB3.mjs} +2 -2
- package/dist/{chunk-V5QRXIIO.mjs → chunk-G7U2FNUK.mjs} +494 -20
- package/dist/chunk-G7U2FNUK.mjs.map +1 -0
- package/dist/{chunk-7ZDS33S2.mjs → chunk-IPMTNREZ.mjs} +2 -2
- package/dist/{chunk-7ZDS33S2.mjs.map → chunk-IPMTNREZ.mjs.map} +1 -1
- package/dist/{chunk-WL7NV4WJ.mjs → chunk-PY7A3J5T.mjs} +4 -4
- package/dist/{chunk-URKUD3OL.mjs → chunk-SKMJ43NN.mjs} +9 -9
- package/dist/{chunk-KM7FUWCM.mjs → chunk-SRCCNBHF.mjs} +7 -3
- package/dist/chunk-SRCCNBHF.mjs.map +1 -0
- package/dist/{chunk-UFSEYVRS.mjs → chunk-VTC6AIWY.mjs} +3 -3
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +522 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -13
- package/dist/management/management.js +522 -16
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +8 -8
- package/dist/modals/chat-modal/chat-modal.js.map +1 -1
- package/dist/modals/chat-modal/chat-modal.mjs +4 -4
- package/dist/{modelStore-XWFHNTBT.mjs → modelStore-FBPBG7TI.mjs} +2 -2
- package/dist/{public-BzsEWB08.d.mts → public-nrOOzXCZ.d.mts} +10 -0
- package/dist/{public-BzsEWB08.d.ts → public-nrOOzXCZ.d.ts} +10 -0
- package/dist/public-types.d.mts +1 -1
- package/dist/public-types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chat-3J4GDGWW.mjs +0 -16
- package/dist/chunk-KM7FUWCM.mjs.map +0 -1
- package/dist/chunk-V5QRXIIO.mjs.map +0 -1
- /package/dist/{chat-3J4GDGWW.mjs.map → chat-E23BSMK5.mjs.map} +0 -0
- /package/dist/{chunk-KNBWR4DS.mjs.map → chunk-5WQMMCZQ.mjs.map} +0 -0
- /package/dist/{chunk-QPBG6JQE.mjs.map → chunk-6QTTNYF2.mjs.map} +0 -0
- /package/dist/{chunk-POTQI33D.mjs.map → chunk-D55E6ZDV.mjs.map} +0 -0
- /package/dist/{chunk-557E5VZ2.mjs.map → chunk-EUBVBTB3.mjs.map} +0 -0
- /package/dist/{chunk-WL7NV4WJ.mjs.map → chunk-PY7A3J5T.mjs.map} +0 -0
- /package/dist/{chunk-URKUD3OL.mjs.map → chunk-SKMJ43NN.mjs.map} +0 -0
- /package/dist/{chunk-UFSEYVRS.mjs.map → chunk-VTC6AIWY.mjs.map} +0 -0
- /package/dist/{modelStore-XWFHNTBT.mjs.map → modelStore-FBPBG7TI.mjs.map} +0 -0
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-ONQMRE2G.mjs";
|
|
4
4
|
import {
|
|
5
5
|
StreamingMarkdown_default
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SRCCNBHF.mjs";
|
|
7
7
|
import {
|
|
8
8
|
useMCPToolsStore
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6QTTNYF2.mjs";
|
|
10
10
|
import {
|
|
11
11
|
AddIcon,
|
|
12
12
|
ArrowDownwardIcon,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
useNotificationService,
|
|
43
43
|
useTTS,
|
|
44
44
|
useVoiceStore
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-5WQMMCZQ.mjs";
|
|
46
46
|
import {
|
|
47
47
|
authenticationService,
|
|
48
48
|
brandingService_default,
|
|
@@ -67,13 +67,13 @@ import {
|
|
|
67
67
|
useMemoryStore,
|
|
68
68
|
useProjectStore,
|
|
69
69
|
useVectorStore
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-EUBVBTB3.mjs";
|
|
71
71
|
import {
|
|
72
72
|
indexedDBService_default,
|
|
73
73
|
useModelStore,
|
|
74
74
|
usePackageSettingsStore,
|
|
75
75
|
usePreferencesStore
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-IPMTNREZ.mjs";
|
|
77
77
|
import {
|
|
78
78
|
useAIProviderStore
|
|
79
79
|
} from "./chunk-H3BYFEIE.mjs";
|
|
@@ -1388,6 +1388,362 @@ var chat_input_default = ChatInput;
|
|
|
1388
1388
|
// src/chat/hooks/useAIProvider.tsx
|
|
1389
1389
|
import { useCallback, useRef as useRef3 } from "react";
|
|
1390
1390
|
|
|
1391
|
+
// src/services/telemetry/otlpExporter.ts
|
|
1392
|
+
function redactSecretsString(s) {
|
|
1393
|
+
return s.replace(/\b(?:sk|tvly|ghp|gho|pk|rk)[-_][A-Za-z0-9]{8,}\b/gi, "[redacted]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[redacted-jwt]").replace(/\bBearer\s+[A-Za-z0-9._-]{8,}/gi, "Bearer [redacted]");
|
|
1394
|
+
}
|
|
1395
|
+
function resolveTelemetryConfig(opts) {
|
|
1396
|
+
if (!opts.telemetry?.enabled) return null;
|
|
1397
|
+
const endpoint = (opts.telemetry.endpoint ?? "https://otlp.burtson.ai").replace(/\/+$/, "");
|
|
1398
|
+
const headers = { ...opts.telemetry.headers ?? {} };
|
|
1399
|
+
const hasAuth = Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
|
|
1400
|
+
if (!hasAuth && opts.banditApiKey) {
|
|
1401
|
+
headers["Authorization"] = `Bearer ${opts.banditApiKey}`;
|
|
1402
|
+
}
|
|
1403
|
+
const mode = opts.telemetry.mode ?? "metrics+traces";
|
|
1404
|
+
return { endpoint, headers, mode, serviceName: opts.telemetry.serviceName ?? "bandit-web" };
|
|
1405
|
+
}
|
|
1406
|
+
function toAttrs(rec) {
|
|
1407
|
+
const out = [];
|
|
1408
|
+
for (const [key, v] of Object.entries(rec)) {
|
|
1409
|
+
if (v === void 0 || v === null || v === "") continue;
|
|
1410
|
+
if (typeof v === "boolean") out.push({ key, value: { boolValue: v } });
|
|
1411
|
+
else if (typeof v === "number")
|
|
1412
|
+
out.push({ key, value: Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v } });
|
|
1413
|
+
else out.push({ key, value: { stringValue: v } });
|
|
1414
|
+
}
|
|
1415
|
+
return out;
|
|
1416
|
+
}
|
|
1417
|
+
var webCrypto = globalThis.crypto;
|
|
1418
|
+
function hex(bytes) {
|
|
1419
|
+
const arr = new Uint8Array(bytes);
|
|
1420
|
+
if (webCrypto?.getRandomValues) webCrypto.getRandomValues(arr);
|
|
1421
|
+
return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1422
|
+
}
|
|
1423
|
+
var nano = (ms) => String(Math.round(ms * 1e6));
|
|
1424
|
+
var TTFT_BUCKETS = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30];
|
|
1425
|
+
var DURATION_BUCKETS = [0.5, 1, 2, 5, 10, 30, 60, 120, 300];
|
|
1426
|
+
function histogramPoint(value, bounds, attrs, startMs, endMs) {
|
|
1427
|
+
const counts = new Array(bounds.length + 1).fill(0);
|
|
1428
|
+
let idx = bounds.findIndex((b) => value <= b);
|
|
1429
|
+
if (idx === -1) idx = bounds.length;
|
|
1430
|
+
counts[idx] = 1;
|
|
1431
|
+
return {
|
|
1432
|
+
attributes: toAttrs(attrs),
|
|
1433
|
+
startTimeUnixNano: nano(startMs),
|
|
1434
|
+
timeUnixNano: nano(endMs),
|
|
1435
|
+
count: "1",
|
|
1436
|
+
sum: value,
|
|
1437
|
+
bucketCounts: counts.map(String),
|
|
1438
|
+
explicitBounds: bounds
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function sumPoint(value, attrs, startMs, endMs) {
|
|
1442
|
+
return {
|
|
1443
|
+
attributes: toAttrs(attrs),
|
|
1444
|
+
startTimeUnixNano: nano(startMs),
|
|
1445
|
+
timeUnixNano: nano(endMs),
|
|
1446
|
+
asInt: String(Math.round(value))
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
var clip = (s, n = 120) => redactSecretsString(s.slice(0, n)).slice(0, n);
|
|
1450
|
+
var TelemetryExporter = class {
|
|
1451
|
+
cfg;
|
|
1452
|
+
now;
|
|
1453
|
+
traceId = "";
|
|
1454
|
+
turn = null;
|
|
1455
|
+
llm = null;
|
|
1456
|
+
llmFirstChunkMs = 0;
|
|
1457
|
+
openTools = [];
|
|
1458
|
+
completedSpans = [];
|
|
1459
|
+
model = "";
|
|
1460
|
+
turnChunkChars = 0;
|
|
1461
|
+
turnTokens = 0;
|
|
1462
|
+
ttftSeconds = null;
|
|
1463
|
+
constructor(cfg, opts) {
|
|
1464
|
+
this.cfg = cfg;
|
|
1465
|
+
this.now = opts?.now ?? (() => Date.now());
|
|
1466
|
+
}
|
|
1467
|
+
startTurn(goal, model) {
|
|
1468
|
+
this.traceId = hex(16);
|
|
1469
|
+
this.model = model;
|
|
1470
|
+
this.turnChunkChars = 0;
|
|
1471
|
+
this.turnTokens = 0;
|
|
1472
|
+
this.ttftSeconds = null;
|
|
1473
|
+
this.llm = null;
|
|
1474
|
+
this.llmFirstChunkMs = 0;
|
|
1475
|
+
this.openTools = [];
|
|
1476
|
+
this.completedSpans = [];
|
|
1477
|
+
this.turn = {
|
|
1478
|
+
spanId: hex(8),
|
|
1479
|
+
name: "agent.turn",
|
|
1480
|
+
startMs: this.now(),
|
|
1481
|
+
attrs: { "gen_ai.request.model": model, "bandit.turn.goal": clip(goal, 160) }
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
/** Fed from the chat turn lifecycle. Best-effort; swallows bad payloads. */
|
|
1485
|
+
onEvent(type, payload) {
|
|
1486
|
+
if (!this.turn) return;
|
|
1487
|
+
try {
|
|
1488
|
+
const p = payload ?? {};
|
|
1489
|
+
switch (type) {
|
|
1490
|
+
case "tool_loop:llm_start":
|
|
1491
|
+
this.llm = {
|
|
1492
|
+
spanId: hex(8),
|
|
1493
|
+
parentSpanId: this.turn.spanId,
|
|
1494
|
+
name: "llm.generate",
|
|
1495
|
+
startMs: this.now(),
|
|
1496
|
+
attrs: { "gen_ai.request.model": this.model }
|
|
1497
|
+
};
|
|
1498
|
+
this.llmFirstChunkMs = 0;
|
|
1499
|
+
break;
|
|
1500
|
+
case "tool_loop:llm_chunk": {
|
|
1501
|
+
const chunk = typeof p.chunk === "string" ? p.chunk : "";
|
|
1502
|
+
if (this.llm && this.llmFirstChunkMs === 0 && chunk.length > 0) {
|
|
1503
|
+
this.llmFirstChunkMs = this.now();
|
|
1504
|
+
const ttft = (this.llmFirstChunkMs - this.llm.startMs) / 1e3;
|
|
1505
|
+
if (this.ttftSeconds === null) this.ttftSeconds = ttft;
|
|
1506
|
+
this.llm.attrs["bandit.llm.ttft_seconds"] = ttft;
|
|
1507
|
+
}
|
|
1508
|
+
this.turnChunkChars += chunk.length;
|
|
1509
|
+
this.turnTokens = Math.floor(this.turnChunkChars / 4);
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
case "tool_loop:llm_response":
|
|
1513
|
+
if (this.llm) {
|
|
1514
|
+
this.llm.endMs = this.now();
|
|
1515
|
+
if (typeof p.responseLength === "number") this.llm.attrs["bandit.llm.response_chars"] = p.responseLength;
|
|
1516
|
+
this.completedSpans.push(this.llm);
|
|
1517
|
+
this.llm = null;
|
|
1518
|
+
}
|
|
1519
|
+
break;
|
|
1520
|
+
case "tool_loop:tool_execute": {
|
|
1521
|
+
const name = typeof p.name === "string" ? p.name : "tool";
|
|
1522
|
+
const params = p.params ?? {};
|
|
1523
|
+
const primary = params.query ?? params.url ?? params.prompt ?? params.topic ?? "";
|
|
1524
|
+
this.openTools.push({
|
|
1525
|
+
spanId: hex(8),
|
|
1526
|
+
parentSpanId: this.turn.spanId,
|
|
1527
|
+
name: `tool.${name}`,
|
|
1528
|
+
startMs: this.now(),
|
|
1529
|
+
attrs: { "bandit.tool.name": name, "bandit.tool.primary": primary ? clip(primary) : void 0 }
|
|
1530
|
+
});
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
case "tool_loop:tool_result":
|
|
1534
|
+
case "tool_loop:tool_error": {
|
|
1535
|
+
const name = typeof p.name === "string" ? p.name : void 0;
|
|
1536
|
+
const span = this.takeOpenTool(name);
|
|
1537
|
+
if (span) {
|
|
1538
|
+
span.endMs = this.now();
|
|
1539
|
+
if (type === "tool_loop:tool_error" || p.isError === true) span.error = "tool error";
|
|
1540
|
+
this.completedSpans.push(span);
|
|
1541
|
+
}
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
takeOpenTool(name) {
|
|
1549
|
+
if (name) {
|
|
1550
|
+
for (let i = this.openTools.length - 1; i >= 0; i -= 1) {
|
|
1551
|
+
if (this.openTools[i].name === `tool.${name}`) return this.openTools.splice(i, 1)[0];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return this.openTools.shift();
|
|
1555
|
+
}
|
|
1556
|
+
/** Close the turn, build OTLP traces + metrics, and flush. Never rejects. */
|
|
1557
|
+
async endTurn(outcome) {
|
|
1558
|
+
const turn = this.turn;
|
|
1559
|
+
if (!turn) return;
|
|
1560
|
+
this.turn = null;
|
|
1561
|
+
const end = this.now();
|
|
1562
|
+
if (this.llm && !this.llm.endMs) {
|
|
1563
|
+
this.llm.endMs = end;
|
|
1564
|
+
this.completedSpans.push(this.llm);
|
|
1565
|
+
this.llm = null;
|
|
1566
|
+
}
|
|
1567
|
+
for (const t of this.openTools.splice(0)) {
|
|
1568
|
+
t.endMs = end;
|
|
1569
|
+
t.error = t.error ?? "incomplete";
|
|
1570
|
+
this.completedSpans.push(t);
|
|
1571
|
+
}
|
|
1572
|
+
turn.endMs = end;
|
|
1573
|
+
if (outcome?.error) turn.error = outcome.error;
|
|
1574
|
+
const traceId = this.traceId;
|
|
1575
|
+
const spans = [turn, ...this.completedSpans];
|
|
1576
|
+
const jobs = [];
|
|
1577
|
+
if (this.cfg.mode === "metrics+traces") jobs.push(this.post("/v1/traces", this.buildTraces(traceId, spans)));
|
|
1578
|
+
jobs.push(this.post("/v1/metrics", this.buildMetrics(turn)));
|
|
1579
|
+
try {
|
|
1580
|
+
await Promise.all(jobs);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
buildTraces(traceId, spans) {
|
|
1585
|
+
return {
|
|
1586
|
+
resourceSpans: [
|
|
1587
|
+
{
|
|
1588
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
1589
|
+
scopeSpans: [
|
|
1590
|
+
{
|
|
1591
|
+
scope: { name: this.cfg.serviceName },
|
|
1592
|
+
spans: spans.map((s) => ({
|
|
1593
|
+
traceId,
|
|
1594
|
+
spanId: s.spanId,
|
|
1595
|
+
parentSpanId: s.parentSpanId,
|
|
1596
|
+
name: s.name,
|
|
1597
|
+
kind: 1,
|
|
1598
|
+
startTimeUnixNano: nano(s.startMs),
|
|
1599
|
+
endTimeUnixNano: nano(s.endMs ?? s.startMs),
|
|
1600
|
+
attributes: toAttrs(s.attrs),
|
|
1601
|
+
status: s.error ? { code: 2, message: s.error.slice(0, 200) } : { code: 1 }
|
|
1602
|
+
}))
|
|
1603
|
+
}
|
|
1604
|
+
]
|
|
1605
|
+
}
|
|
1606
|
+
]
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
buildMetrics(turn) {
|
|
1610
|
+
const start = turn.startMs;
|
|
1611
|
+
const end = turn.endMs ?? this.now();
|
|
1612
|
+
const metrics = [];
|
|
1613
|
+
if (this.turnTokens > 0) {
|
|
1614
|
+
metrics.push({
|
|
1615
|
+
name: "bandit.llm.tokens",
|
|
1616
|
+
sum: {
|
|
1617
|
+
aggregationTemporality: 1,
|
|
1618
|
+
isMonotonic: true,
|
|
1619
|
+
dataPoints: [sumPoint(this.turnTokens, { type: "output", "gen_ai.request.model": this.model }, start, end)]
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
if (this.ttftSeconds !== null) {
|
|
1624
|
+
metrics.push({
|
|
1625
|
+
name: "bandit.llm.ttft",
|
|
1626
|
+
unit: "s",
|
|
1627
|
+
histogram: {
|
|
1628
|
+
aggregationTemporality: 1,
|
|
1629
|
+
dataPoints: [histogramPoint(this.ttftSeconds, TTFT_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
metrics.push({
|
|
1634
|
+
name: "bandit.turn.duration",
|
|
1635
|
+
unit: "s",
|
|
1636
|
+
histogram: {
|
|
1637
|
+
aggregationTemporality: 1,
|
|
1638
|
+
dataPoints: [histogramPoint((end - start) / 1e3, DURATION_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
return {
|
|
1642
|
+
resourceMetrics: [
|
|
1643
|
+
{
|
|
1644
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
1645
|
+
scopeMetrics: [{ scope: { name: this.cfg.serviceName }, metrics }]
|
|
1646
|
+
}
|
|
1647
|
+
]
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
async post(path, body) {
|
|
1651
|
+
const doFetch = globalThis.fetch;
|
|
1652
|
+
if (!doFetch) return;
|
|
1653
|
+
const ctrl = new AbortController();
|
|
1654
|
+
const timer = setTimeout(() => ctrl.abort(), 4e3);
|
|
1655
|
+
try {
|
|
1656
|
+
await doFetch(`${this.cfg.endpoint}${path}`, {
|
|
1657
|
+
method: "POST",
|
|
1658
|
+
headers: { "Content-Type": "application/json", ...this.cfg.headers },
|
|
1659
|
+
body: JSON.stringify(body),
|
|
1660
|
+
signal: ctrl.signal
|
|
1661
|
+
});
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
debugLogger.debug("[telemetry] OTLP post failed", {
|
|
1664
|
+
path,
|
|
1665
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1666
|
+
});
|
|
1667
|
+
} finally {
|
|
1668
|
+
clearTimeout(timer);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
// src/services/telemetry/index.ts
|
|
1674
|
+
var active = null;
|
|
1675
|
+
function syncTelemetry() {
|
|
1676
|
+
try {
|
|
1677
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
1678
|
+
const cfg = resolveTelemetryConfig({
|
|
1679
|
+
telemetry: settings?.telemetry,
|
|
1680
|
+
banditApiKey: authenticationService.getToken() ?? void 0
|
|
1681
|
+
});
|
|
1682
|
+
active = cfg ? new TelemetryExporter(cfg) : null;
|
|
1683
|
+
} catch {
|
|
1684
|
+
active = null;
|
|
1685
|
+
}
|
|
1686
|
+
return active !== null;
|
|
1687
|
+
}
|
|
1688
|
+
function telemetryStartTurn(goal, model) {
|
|
1689
|
+
active?.startTurn(goal, model);
|
|
1690
|
+
}
|
|
1691
|
+
function telemetryEvent(type, payload) {
|
|
1692
|
+
active?.onEvent(type, payload);
|
|
1693
|
+
}
|
|
1694
|
+
function telemetryEndTurn(outcome) {
|
|
1695
|
+
void active?.endTurn(outcome);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/store/engineStore.ts
|
|
1699
|
+
import { create as create2 } from "zustand";
|
|
1700
|
+
var STORAGE_KEY = "bandit.selectedEngine";
|
|
1701
|
+
var readStored = () => {
|
|
1702
|
+
try {
|
|
1703
|
+
return typeof window !== "undefined" ? window.localStorage.getItem(STORAGE_KEY) : null;
|
|
1704
|
+
} catch {
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
var useEngineStore = create2((set, get) => ({
|
|
1709
|
+
selectedEngine: readStored(),
|
|
1710
|
+
engines: [],
|
|
1711
|
+
loaded: false,
|
|
1712
|
+
setSelectedEngine: (id) => {
|
|
1713
|
+
set({ selectedEngine: id });
|
|
1714
|
+
try {
|
|
1715
|
+
window.localStorage.setItem(STORAGE_KEY, id);
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
},
|
|
1719
|
+
getSelectedEngine: () => get().selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core",
|
|
1720
|
+
fetchEngines: async () => {
|
|
1721
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
1722
|
+
const base = settings?.gatewayApiUrl?.replace(/\/$/, "") ?? "";
|
|
1723
|
+
if (!base || settings?.playgroundMode || base.toLowerCase().startsWith("playground://")) {
|
|
1724
|
+
set({ loaded: true });
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
const headers = { "Content-Type": "application/json" };
|
|
1729
|
+
const token = authenticationService.getToken();
|
|
1730
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1731
|
+
const res = await fetch(`${base}/models`, { headers });
|
|
1732
|
+
const data = await res.json();
|
|
1733
|
+
if (res.ok && Array.isArray(data?.models)) {
|
|
1734
|
+
set({ engines: data.models, loaded: true });
|
|
1735
|
+
} else {
|
|
1736
|
+
set({ loaded: true });
|
|
1737
|
+
}
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
debugLogger.error("Failed to fetch engines", {
|
|
1740
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1741
|
+
});
|
|
1742
|
+
set({ loaded: true });
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}));
|
|
1746
|
+
|
|
1391
1747
|
// src/chat/hooks/useMemoryEnhancer.tsx
|
|
1392
1748
|
import { lastValueFrom, map as map2 } from "rxjs";
|
|
1393
1749
|
var MEMORY_LIMIT = 100;
|
|
@@ -2440,7 +2796,7 @@ var useAIProvider = ({
|
|
|
2440
2796
|
question: pendingQuestion,
|
|
2441
2797
|
images: pendingImages
|
|
2442
2798
|
});
|
|
2443
|
-
const modelName =
|
|
2799
|
+
const modelName = useEngineStore.getState().getSelectedEngine();
|
|
2444
2800
|
const CONFIG = modelConfigs[modelName] ?? modelConfigs["bandit-core:4b-it-qat"];
|
|
2445
2801
|
const base64Images = imageList.map((img) => img.split(",")[1]);
|
|
2446
2802
|
const latestEntries = history.slice(-CONFIG.historyMessages);
|
|
@@ -2835,6 +3191,9 @@ ${protocol}`;
|
|
|
2835
3191
|
setStreamBuffer(latestDisplayMessage);
|
|
2836
3192
|
}, delay);
|
|
2837
3193
|
};
|
|
3194
|
+
syncTelemetry();
|
|
3195
|
+
telemetryStartTurn(question, modelName);
|
|
3196
|
+
telemetryEvent("tool_loop:llm_start");
|
|
2838
3197
|
const stream = provider.chat(request);
|
|
2839
3198
|
const initialPlaceholderQuestion = lastEntry?.question;
|
|
2840
3199
|
lastPartialRef.current = { text: "", images: [...imageList], usedDocs, question };
|
|
@@ -2848,7 +3207,10 @@ ${protocol}`;
|
|
|
2848
3207
|
const sub = stream.subscribe({
|
|
2849
3208
|
next: (data) => {
|
|
2850
3209
|
if (!data?.message?.content && !data?.message?.tool_calls) return;
|
|
2851
|
-
if (data.message.content)
|
|
3210
|
+
if (data.message.content) {
|
|
3211
|
+
fullMessage += data.message.content;
|
|
3212
|
+
telemetryEvent("tool_loop:llm_chunk", { chunk: data.message.content });
|
|
3213
|
+
}
|
|
2852
3214
|
const inThinkBlock = /<think>/.test(fullMessage) && !/<think>[\s\S]*<\/think>/.test(fullMessage);
|
|
2853
3215
|
setIsThinking?.(inThinkBlock);
|
|
2854
3216
|
const visibleMessage = stripThinking(fullMessage);
|
|
@@ -2880,6 +3242,7 @@ ${protocol}`;
|
|
|
2880
3242
|
setIsThinking?.(false);
|
|
2881
3243
|
setPendingMessage(null);
|
|
2882
3244
|
setLogoVisible(false);
|
|
3245
|
+
telemetryEndTurn({ error: err?.message || "stream error" });
|
|
2883
3246
|
if (onError) {
|
|
2884
3247
|
onError(err);
|
|
2885
3248
|
}
|
|
@@ -2887,6 +3250,7 @@ ${protocol}`;
|
|
|
2887
3250
|
complete: async () => {
|
|
2888
3251
|
try {
|
|
2889
3252
|
setIsThinking?.(false);
|
|
3253
|
+
telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
|
|
2890
3254
|
latestDisplayMessage = stripThinking(fullMessage);
|
|
2891
3255
|
if (!sawToolBlock) {
|
|
2892
3256
|
flushNow();
|
|
@@ -2895,6 +3259,7 @@ ${protocol}`;
|
|
|
2895
3259
|
let enhancedMessage = fullMessage;
|
|
2896
3260
|
const summarizableResults = [];
|
|
2897
3261
|
const inlineImageBlocks = [];
|
|
3262
|
+
const collectedSources = [];
|
|
2898
3263
|
if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
|
|
2899
3264
|
debugLogger.info("Detected tool calls in AI response", {
|
|
2900
3265
|
toolCallCount: toolCallMatches.length,
|
|
@@ -2929,10 +3294,21 @@ ${protocol}`;
|
|
|
2929
3294
|
});
|
|
2930
3295
|
const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
|
|
2931
3296
|
enhancedMessage = enhancedMessage.replace(match, placeholderToken);
|
|
3297
|
+
clearFlushTimer();
|
|
3298
|
+
const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web\u2026" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page\u2026" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image\u2026" : "Working on it\u2026";
|
|
3299
|
+
const toolPreamble = stripToolBlocks(fullMessage).trim();
|
|
3300
|
+
setStreamBuffer(toolPreamble ? `${toolPreamble}
|
|
3301
|
+
|
|
3302
|
+
_${toolStatus}_` : `_${toolStatus}_`);
|
|
3303
|
+
telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
|
|
2932
3304
|
const result = await executeMCPTool({
|
|
2933
3305
|
toolName: functionName,
|
|
2934
3306
|
parameters: parsedParams
|
|
2935
3307
|
});
|
|
3308
|
+
telemetryEvent(result.success ? "tool_loop:tool_result" : "tool_loop:tool_error", {
|
|
3309
|
+
name: functionName,
|
|
3310
|
+
isError: !result.success
|
|
3311
|
+
});
|
|
2936
3312
|
let resultText = "";
|
|
2937
3313
|
if (result.success) {
|
|
2938
3314
|
if (functionName === "web_search" || functionName === "web-search") {
|
|
@@ -2946,18 +3322,16 @@ ${protocol}`;
|
|
|
2946
3322
|
blocks.push(
|
|
2947
3323
|
items.slice(0, 6).map((item, index) => {
|
|
2948
3324
|
const title = item.title?.trim() || "Untitled";
|
|
2949
|
-
const url = item.url?.trim();
|
|
3325
|
+
const url = item.url?.trim() || "";
|
|
2950
3326
|
const snippet = item.content?.trim();
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
${url}`;
|
|
3327
|
+
if (url) collectedSources.push({ title, url });
|
|
3328
|
+
let line = url ? `${index + 1}. [${title}](${url})` : `${index + 1}. ${title}`;
|
|
2954
3329
|
if (snippet) {
|
|
2955
3330
|
const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
|
|
2956
|
-
line += `
|
|
2957
|
-
${truncated}`;
|
|
3331
|
+
line += ` \u2014 ${truncated}`;
|
|
2958
3332
|
}
|
|
2959
3333
|
return line;
|
|
2960
|
-
}).join("\n
|
|
3334
|
+
}).join("\n")
|
|
2961
3335
|
);
|
|
2962
3336
|
}
|
|
2963
3337
|
resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
|
|
@@ -3039,7 +3413,7 @@ ${r.output}`).join("\n\n");
|
|
|
3039
3413
|
|
|
3040
3414
|
${toolResultsText}
|
|
3041
3415
|
|
|
3042
|
-
Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way.
|
|
3416
|
+
Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Reference sources by name where relevant, but do NOT paste raw URLs or a list of links \u2014 a clean Sources section is added automatically below your answer. Do NOT output tool_code or call any tools again.`
|
|
3043
3417
|
}
|
|
3044
3418
|
];
|
|
3045
3419
|
const summaryRequest = {
|
|
@@ -3053,7 +3427,7 @@ Using these results together with your own knowledge, answer my original questio
|
|
|
3053
3427
|
setStreamBuffer(
|
|
3054
3428
|
summaryPreamble ? `${summaryPreamble}
|
|
3055
3429
|
|
|
3056
|
-
|
|
3430
|
+
_Writing the answer\u2026_` : "_Writing the answer\u2026_"
|
|
3057
3431
|
);
|
|
3058
3432
|
const summaryText = await new Promise((resolve) => {
|
|
3059
3433
|
let acc = "";
|
|
@@ -3094,7 +3468,18 @@ _Working on it\u2026_` : "_Working on it\u2026_"
|
|
|
3094
3468
|
}, 3e4);
|
|
3095
3469
|
});
|
|
3096
3470
|
if (summaryText.trim()) {
|
|
3097
|
-
|
|
3471
|
+
const sourcesMd = collectedSources.length ? `
|
|
3472
|
+
|
|
3473
|
+
**Sources**
|
|
3474
|
+
${collectedSources.slice(0, 6).map((s) => {
|
|
3475
|
+
let domain = s.url;
|
|
3476
|
+
try {
|
|
3477
|
+
domain = new URL(s.url).hostname.replace(/^www\./, "");
|
|
3478
|
+
} catch {
|
|
3479
|
+
}
|
|
3480
|
+
return `- [${s.title || domain}](${s.url}) \u2014 ${domain}`;
|
|
3481
|
+
}).join("\n")}` : "";
|
|
3482
|
+
enhancedMessage = summaryText + sourcesMd + (inlineImageBlocks.length ? `
|
|
3098
3483
|
|
|
3099
3484
|
${inlineImageBlocks.join("\n\n")}` : "");
|
|
3100
3485
|
}
|
|
@@ -3143,6 +3528,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
3143
3528
|
}
|
|
3144
3529
|
setInputValue("");
|
|
3145
3530
|
setPastedImages([]);
|
|
3531
|
+
telemetryEndTurn();
|
|
3146
3532
|
setTimeout(() => {
|
|
3147
3533
|
clearFlushTimer();
|
|
3148
3534
|
setPendingMessage(null);
|
|
@@ -3158,6 +3544,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
3158
3544
|
overrideComponentStatus("Idle");
|
|
3159
3545
|
setIsSubmitting(false);
|
|
3160
3546
|
setIsStreaming(false);
|
|
3547
|
+
telemetryEndTurn({ error: e instanceof Error ? e.message : String(e) });
|
|
3161
3548
|
}
|
|
3162
3549
|
}
|
|
3163
3550
|
});
|
|
@@ -6059,10 +6446,10 @@ var EnhancedMobileConversationsModal = ({
|
|
|
6059
6446
|
useEffect10(() => {
|
|
6060
6447
|
setDeletedConversationIds((prev) => {
|
|
6061
6448
|
let changed = false;
|
|
6062
|
-
const
|
|
6449
|
+
const active2 = new Set(conversations.map((conv) => conv.id));
|
|
6063
6450
|
const next = /* @__PURE__ */ new Set();
|
|
6064
6451
|
prev.forEach((id) => {
|
|
6065
|
-
if (
|
|
6452
|
+
if (active2.has(id)) {
|
|
6066
6453
|
next.add(id);
|
|
6067
6454
|
} else {
|
|
6068
6455
|
changed = true;
|
|
@@ -6711,6 +7098,7 @@ var ChatAppBar = ({
|
|
|
6711
7098
|
menuText
|
|
6712
7099
|
} = theme.palette.chat.appBar;
|
|
6713
7100
|
const [modelAnchorEl, setModelAnchorEl] = useState12(null);
|
|
7101
|
+
const [engineAnchorEl, setEngineAnchorEl] = useState12(null);
|
|
6714
7102
|
const [voiceAnchorEl, setVoiceAnchorEl] = useState12(null);
|
|
6715
7103
|
const [modalOpen, setModalOpen] = useState12(false);
|
|
6716
7104
|
const [confirmModelChangeOpen, setConfirmModelChangeOpen] = useState12(false);
|
|
@@ -6831,6 +7219,14 @@ var ChatAppBar = ({
|
|
|
6831
7219
|
const selectedModel = useModelStore((s) => s.selectedModel);
|
|
6832
7220
|
const currentModel = useModelStore((s) => s.availableModels.find((m) => m.name === selectedModel));
|
|
6833
7221
|
const currentAvatar = currentModel?.avatarBase64 || modelAvatars[selectedModel] || banditHead;
|
|
7222
|
+
const engines = useEngineStore((s) => s.engines);
|
|
7223
|
+
const selectedEngine = useEngineStore((s) => s.selectedEngine);
|
|
7224
|
+
const effectiveEngineId = selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core";
|
|
7225
|
+
const currentEngine = engines.find((e) => e.id === effectiveEngineId);
|
|
7226
|
+
const engineLabel = currentEngine?.displayName?.replace(/^Bandit /, "") || "Engine";
|
|
7227
|
+
useEffect11(() => {
|
|
7228
|
+
useEngineStore.getState().fetchEngines();
|
|
7229
|
+
}, []);
|
|
6834
7230
|
const pendingModelAvatar = useModelStore.getState().availableModels.find((m) => m.name === pendingModel)?.avatarBase64 || modelAvatars[pendingModel || ""] || banditHead;
|
|
6835
7231
|
const resolvedHomeUrl = preferences.homeUrl?.trim() || packageSettings?.homeUrl?.trim() || "";
|
|
6836
7232
|
const homeTooltip = (() => {
|
|
@@ -7143,6 +7539,84 @@ var ChatAppBar = ({
|
|
|
7143
7539
|
)
|
|
7144
7540
|
}
|
|
7145
7541
|
) }),
|
|
7542
|
+
/* @__PURE__ */ jsx13(Tooltip4, { title: `Engine: ${currentEngine?.displayName ?? effectiveEngineId}`, arrow: true, children: /* @__PURE__ */ jsxs10(
|
|
7543
|
+
IconButton9,
|
|
7544
|
+
{
|
|
7545
|
+
onClick: (e) => setEngineAnchorEl(e.currentTarget),
|
|
7546
|
+
sx: pillButtonStyles,
|
|
7547
|
+
"aria-label": `Change base model (engine). Currently ${effectiveEngineId}`,
|
|
7548
|
+
children: [
|
|
7549
|
+
currentEngine?.cloud ? /* @__PURE__ */ jsx13(CloudDoneIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx13(CloudOffIcon, { fontSize: "small" }),
|
|
7550
|
+
/* @__PURE__ */ jsx13(Typography8, { variant: "caption", sx: { ml: 0.75, fontWeight: 600, whiteSpace: "nowrap" }, children: engineLabel })
|
|
7551
|
+
]
|
|
7552
|
+
}
|
|
7553
|
+
) }),
|
|
7554
|
+
/* @__PURE__ */ jsxs10(
|
|
7555
|
+
Menu5,
|
|
7556
|
+
{
|
|
7557
|
+
anchorEl: engineAnchorEl,
|
|
7558
|
+
open: Boolean(engineAnchorEl),
|
|
7559
|
+
onClose: () => setEngineAnchorEl(null),
|
|
7560
|
+
transformOrigin: { horizontal: "right", vertical: "top" },
|
|
7561
|
+
anchorOrigin: { horizontal: "right", vertical: "bottom" },
|
|
7562
|
+
children: [
|
|
7563
|
+
/* @__PURE__ */ jsx13(Typography8, { variant: "overline", sx: { px: 2, color: theme.palette.text.secondary }, children: "Engine \xB7 base model" }),
|
|
7564
|
+
engines.length === 0 && /* @__PURE__ */ jsx13(MenuItem5, { disabled: true, children: /* @__PURE__ */ jsx13(Typography8, { variant: "body2", children: "No engines available" }) }),
|
|
7565
|
+
engines.map((engine) => {
|
|
7566
|
+
const badges = [
|
|
7567
|
+
engine.vision && "vision",
|
|
7568
|
+
engine.tools && "tools",
|
|
7569
|
+
engine.thinking && "thinking",
|
|
7570
|
+
engine.cloud && "cloud"
|
|
7571
|
+
].filter(Boolean);
|
|
7572
|
+
return /* @__PURE__ */ jsxs10(
|
|
7573
|
+
MenuItem5,
|
|
7574
|
+
{
|
|
7575
|
+
selected: engine.id === effectiveEngineId,
|
|
7576
|
+
disabled: !engine.available,
|
|
7577
|
+
onClick: () => {
|
|
7578
|
+
useEngineStore.getState().setSelectedEngine(engine.id);
|
|
7579
|
+
setEngineAnchorEl(null);
|
|
7580
|
+
},
|
|
7581
|
+
sx: {
|
|
7582
|
+
display: "flex",
|
|
7583
|
+
flexDirection: "column",
|
|
7584
|
+
alignItems: "flex-start",
|
|
7585
|
+
gap: 0.5,
|
|
7586
|
+
py: 1,
|
|
7587
|
+
px: 2,
|
|
7588
|
+
maxWidth: 360,
|
|
7589
|
+
whiteSpace: "normal"
|
|
7590
|
+
},
|
|
7591
|
+
children: [
|
|
7592
|
+
/* @__PURE__ */ jsxs10(Box10, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%" }, children: [
|
|
7593
|
+
/* @__PURE__ */ jsx13(Typography8, { variant: "body2", sx: { fontWeight: 600, flex: 1 }, children: engine.displayName }),
|
|
7594
|
+
engine.id === effectiveEngineId && /* @__PURE__ */ jsx13(Box10, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: theme.palette.primary.main } })
|
|
7595
|
+
] }),
|
|
7596
|
+
/* @__PURE__ */ jsx13(Typography8, { variant: "caption", sx: { color: theme.palette.text.secondary }, children: engine.available ? engine.description : engine.unavailableReason || "Unavailable" }),
|
|
7597
|
+
badges.length > 0 && /* @__PURE__ */ jsx13(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.25 }, children: badges.map((b) => /* @__PURE__ */ jsx13(
|
|
7598
|
+
Box10,
|
|
7599
|
+
{
|
|
7600
|
+
sx: {
|
|
7601
|
+
fontSize: "0.65rem",
|
|
7602
|
+
px: 0.75,
|
|
7603
|
+
py: 0.1,
|
|
7604
|
+
borderRadius: 1,
|
|
7605
|
+
bgcolor: theme.palette.primary.main + "22",
|
|
7606
|
+
color: theme.palette.primary.main
|
|
7607
|
+
},
|
|
7608
|
+
children: b
|
|
7609
|
+
},
|
|
7610
|
+
b
|
|
7611
|
+
)) })
|
|
7612
|
+
]
|
|
7613
|
+
},
|
|
7614
|
+
engine.id
|
|
7615
|
+
);
|
|
7616
|
+
})
|
|
7617
|
+
]
|
|
7618
|
+
}
|
|
7619
|
+
),
|
|
7146
7620
|
/* @__PURE__ */ jsx13(
|
|
7147
7621
|
Menu5,
|
|
7148
7622
|
{
|
|
@@ -9248,4 +9722,4 @@ var chat_default = Chat;
|
|
|
9248
9722
|
export {
|
|
9249
9723
|
chat_default
|
|
9250
9724
|
};
|
|
9251
|
-
//# sourceMappingURL=chunk-
|
|
9725
|
+
//# sourceMappingURL=chunk-G7U2FNUK.mjs.map
|