@burtson-labs/bandit-engine 2.0.59 → 2.0.60

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.
Files changed (44) hide show
  1. package/dist/chat-NLBCURUN.mjs +16 -0
  2. package/dist/chat-provider.js.map +1 -1
  3. package/dist/chat-provider.mjs +5 -5
  4. package/dist/{chunk-URKUD3OL.mjs → chunk-3AWAL2YH.mjs} +9 -9
  5. package/dist/{chunk-KNBWR4DS.mjs → chunk-5WQMMCZQ.mjs} +3 -3
  6. package/dist/{chunk-QPBG6JQE.mjs → chunk-6QTTNYF2.mjs} +2 -2
  7. package/dist/{chunk-POTQI33D.mjs → chunk-D55E6ZDV.mjs} +5 -5
  8. package/dist/{chunk-557E5VZ2.mjs → chunk-EUBVBTB3.mjs} +2 -2
  9. package/dist/{chunk-7ZDS33S2.mjs → chunk-IPMTNREZ.mjs} +2 -2
  10. package/dist/{chunk-7ZDS33S2.mjs.map → chunk-IPMTNREZ.mjs.map} +1 -1
  11. package/dist/{chunk-V5QRXIIO.mjs → chunk-MFDMM5MS.mjs} +357 -19
  12. package/dist/chunk-MFDMM5MS.mjs.map +1 -0
  13. package/dist/{chunk-WL7NV4WJ.mjs → chunk-PY7A3J5T.mjs} +4 -4
  14. package/dist/{chunk-KM7FUWCM.mjs → chunk-SRCCNBHF.mjs} +7 -3
  15. package/dist/chunk-SRCCNBHF.mjs.map +1 -0
  16. package/dist/{chunk-UFSEYVRS.mjs → chunk-VTC6AIWY.mjs} +3 -3
  17. package/dist/index.d.mts +2 -2
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +374 -14
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +10 -10
  22. package/dist/management/management.js +374 -14
  23. package/dist/management/management.js.map +1 -1
  24. package/dist/management/management.mjs +8 -8
  25. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  26. package/dist/modals/chat-modal/chat-modal.mjs +4 -4
  27. package/dist/{modelStore-XWFHNTBT.mjs → modelStore-FBPBG7TI.mjs} +2 -2
  28. package/dist/{public-BzsEWB08.d.mts → public-nrOOzXCZ.d.mts} +10 -0
  29. package/dist/{public-BzsEWB08.d.ts → public-nrOOzXCZ.d.ts} +10 -0
  30. package/dist/public-types.d.mts +1 -1
  31. package/dist/public-types.d.ts +1 -1
  32. package/package.json +1 -1
  33. package/dist/chat-3J4GDGWW.mjs +0 -16
  34. package/dist/chunk-KM7FUWCM.mjs.map +0 -1
  35. package/dist/chunk-V5QRXIIO.mjs.map +0 -1
  36. /package/dist/{chat-3J4GDGWW.mjs.map → chat-NLBCURUN.mjs.map} +0 -0
  37. /package/dist/{chunk-URKUD3OL.mjs.map → chunk-3AWAL2YH.mjs.map} +0 -0
  38. /package/dist/{chunk-KNBWR4DS.mjs.map → chunk-5WQMMCZQ.mjs.map} +0 -0
  39. /package/dist/{chunk-QPBG6JQE.mjs.map → chunk-6QTTNYF2.mjs.map} +0 -0
  40. /package/dist/{chunk-POTQI33D.mjs.map → chunk-D55E6ZDV.mjs.map} +0 -0
  41. /package/dist/{chunk-557E5VZ2.mjs.map → chunk-EUBVBTB3.mjs.map} +0 -0
  42. /package/dist/{chunk-WL7NV4WJ.mjs.map → chunk-PY7A3J5T.mjs.map} +0 -0
  43. /package/dist/{chunk-UFSEYVRS.mjs.map → chunk-VTC6AIWY.mjs.map} +0 -0
  44. /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-KM7FUWCM.mjs";
6
+ } from "./chunk-SRCCNBHF.mjs";
7
7
  import {
8
8
  useMCPToolsStore
9
- } from "./chunk-QPBG6JQE.mjs";
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-KNBWR4DS.mjs";
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-557E5VZ2.mjs";
70
+ } from "./chunk-EUBVBTB3.mjs";
71
71
  import {
72
72
  indexedDBService_default,
73
73
  useModelStore,
74
74
  usePackageSettingsStore,
75
75
  usePreferencesStore
76
- } from "./chunk-7ZDS33S2.mjs";
76
+ } from "./chunk-IPMTNREZ.mjs";
77
77
  import {
78
78
  useAIProviderStore
79
79
  } from "./chunk-H3BYFEIE.mjs";
@@ -1388,6 +1388,313 @@ 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
+
1391
1698
  // src/chat/hooks/useMemoryEnhancer.tsx
1392
1699
  import { lastValueFrom, map as map2 } from "rxjs";
1393
1700
  var MEMORY_LIMIT = 100;
@@ -2835,6 +3142,9 @@ ${protocol}`;
2835
3142
  setStreamBuffer(latestDisplayMessage);
2836
3143
  }, delay);
2837
3144
  };
3145
+ syncTelemetry();
3146
+ telemetryStartTurn(question, modelName);
3147
+ telemetryEvent("tool_loop:llm_start");
2838
3148
  const stream = provider.chat(request);
2839
3149
  const initialPlaceholderQuestion = lastEntry?.question;
2840
3150
  lastPartialRef.current = { text: "", images: [...imageList], usedDocs, question };
@@ -2848,7 +3158,10 @@ ${protocol}`;
2848
3158
  const sub = stream.subscribe({
2849
3159
  next: (data) => {
2850
3160
  if (!data?.message?.content && !data?.message?.tool_calls) return;
2851
- if (data.message.content) fullMessage += data.message.content;
3161
+ if (data.message.content) {
3162
+ fullMessage += data.message.content;
3163
+ telemetryEvent("tool_loop:llm_chunk", { chunk: data.message.content });
3164
+ }
2852
3165
  const inThinkBlock = /<think>/.test(fullMessage) && !/<think>[\s\S]*<\/think>/.test(fullMessage);
2853
3166
  setIsThinking?.(inThinkBlock);
2854
3167
  const visibleMessage = stripThinking(fullMessage);
@@ -2880,6 +3193,7 @@ ${protocol}`;
2880
3193
  setIsThinking?.(false);
2881
3194
  setPendingMessage(null);
2882
3195
  setLogoVisible(false);
3196
+ telemetryEndTurn({ error: err?.message || "stream error" });
2883
3197
  if (onError) {
2884
3198
  onError(err);
2885
3199
  }
@@ -2887,6 +3201,7 @@ ${protocol}`;
2887
3201
  complete: async () => {
2888
3202
  try {
2889
3203
  setIsThinking?.(false);
3204
+ telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
2890
3205
  latestDisplayMessage = stripThinking(fullMessage);
2891
3206
  if (!sawToolBlock) {
2892
3207
  flushNow();
@@ -2895,6 +3210,7 @@ ${protocol}`;
2895
3210
  let enhancedMessage = fullMessage;
2896
3211
  const summarizableResults = [];
2897
3212
  const inlineImageBlocks = [];
3213
+ const collectedSources = [];
2898
3214
  if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
2899
3215
  debugLogger.info("Detected tool calls in AI response", {
2900
3216
  toolCallCount: toolCallMatches.length,
@@ -2929,10 +3245,21 @@ ${protocol}`;
2929
3245
  });
2930
3246
  const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
2931
3247
  enhancedMessage = enhancedMessage.replace(match, placeholderToken);
3248
+ clearFlushTimer();
3249
+ 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";
3250
+ const toolPreamble = stripToolBlocks(fullMessage).trim();
3251
+ setStreamBuffer(toolPreamble ? `${toolPreamble}
3252
+
3253
+ _${toolStatus}_` : `_${toolStatus}_`);
3254
+ telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
2932
3255
  const result = await executeMCPTool({
2933
3256
  toolName: functionName,
2934
3257
  parameters: parsedParams
2935
3258
  });
3259
+ telemetryEvent(result.success ? "tool_loop:tool_result" : "tool_loop:tool_error", {
3260
+ name: functionName,
3261
+ isError: !result.success
3262
+ });
2936
3263
  let resultText = "";
2937
3264
  if (result.success) {
2938
3265
  if (functionName === "web_search" || functionName === "web-search") {
@@ -2946,18 +3273,16 @@ ${protocol}`;
2946
3273
  blocks.push(
2947
3274
  items.slice(0, 6).map((item, index) => {
2948
3275
  const title = item.title?.trim() || "Untitled";
2949
- const url = item.url?.trim();
3276
+ const url = item.url?.trim() || "";
2950
3277
  const snippet = item.content?.trim();
2951
- let line = `${index + 1}. **${title}**`;
2952
- if (url) line += `
2953
- ${url}`;
3278
+ if (url) collectedSources.push({ title, url });
3279
+ let line = url ? `${index + 1}. [${title}](${url})` : `${index + 1}. ${title}`;
2954
3280
  if (snippet) {
2955
3281
  const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
2956
- line += `
2957
- ${truncated}`;
3282
+ line += ` \u2014 ${truncated}`;
2958
3283
  }
2959
3284
  return line;
2960
- }).join("\n\n")
3285
+ }).join("\n")
2961
3286
  );
2962
3287
  }
2963
3288
  resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
@@ -3039,7 +3364,7 @@ ${r.output}`).join("\n\n");
3039
3364
 
3040
3365
  ${toolResultsText}
3041
3366
 
3042
- Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Cite source URLs inline when you rely on them. Do NOT output tool_code or call any tools again.`
3367
+ 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
3368
  }
3044
3369
  ];
3045
3370
  const summaryRequest = {
@@ -3053,7 +3378,7 @@ Using these results together with your own knowledge, answer my original questio
3053
3378
  setStreamBuffer(
3054
3379
  summaryPreamble ? `${summaryPreamble}
3055
3380
 
3056
- _Working on it\u2026_` : "_Working on it\u2026_"
3381
+ _Writing the answer\u2026_` : "_Writing the answer\u2026_"
3057
3382
  );
3058
3383
  const summaryText = await new Promise((resolve) => {
3059
3384
  let acc = "";
@@ -3094,7 +3419,18 @@ _Working on it\u2026_` : "_Working on it\u2026_"
3094
3419
  }, 3e4);
3095
3420
  });
3096
3421
  if (summaryText.trim()) {
3097
- enhancedMessage = summaryText + (inlineImageBlocks.length ? `
3422
+ const sourcesMd = collectedSources.length ? `
3423
+
3424
+ **Sources**
3425
+ ${collectedSources.slice(0, 6).map((s) => {
3426
+ let domain = s.url;
3427
+ try {
3428
+ domain = new URL(s.url).hostname.replace(/^www\./, "");
3429
+ } catch {
3430
+ }
3431
+ return `- [${s.title || domain}](${s.url}) \u2014 ${domain}`;
3432
+ }).join("\n")}` : "";
3433
+ enhancedMessage = summaryText + sourcesMd + (inlineImageBlocks.length ? `
3098
3434
 
3099
3435
  ${inlineImageBlocks.join("\n\n")}` : "");
3100
3436
  }
@@ -3143,6 +3479,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
3143
3479
  }
3144
3480
  setInputValue("");
3145
3481
  setPastedImages([]);
3482
+ telemetryEndTurn();
3146
3483
  setTimeout(() => {
3147
3484
  clearFlushTimer();
3148
3485
  setPendingMessage(null);
@@ -3158,6 +3495,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
3158
3495
  overrideComponentStatus("Idle");
3159
3496
  setIsSubmitting(false);
3160
3497
  setIsStreaming(false);
3498
+ telemetryEndTurn({ error: e instanceof Error ? e.message : String(e) });
3161
3499
  }
3162
3500
  }
3163
3501
  });
@@ -6059,10 +6397,10 @@ var EnhancedMobileConversationsModal = ({
6059
6397
  useEffect10(() => {
6060
6398
  setDeletedConversationIds((prev) => {
6061
6399
  let changed = false;
6062
- const active = new Set(conversations.map((conv) => conv.id));
6400
+ const active2 = new Set(conversations.map((conv) => conv.id));
6063
6401
  const next = /* @__PURE__ */ new Set();
6064
6402
  prev.forEach((id) => {
6065
- if (active.has(id)) {
6403
+ if (active2.has(id)) {
6066
6404
  next.add(id);
6067
6405
  } else {
6068
6406
  changed = true;
@@ -9248,4 +9586,4 @@ var chat_default = Chat;
9248
9586
  export {
9249
9587
  chat_default
9250
9588
  };
9251
- //# sourceMappingURL=chunk-V5QRXIIO.mjs.map
9589
+ //# sourceMappingURL=chunk-MFDMM5MS.mjs.map