llm_meta_widget 0.4.1 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17cad71af08d5e4c49a3e7418055bd9c034880d9ab6cae2cbb3fb6cf3a0f0804
4
- data.tar.gz: 24e7aa0c38400bccee9ddc832a6da10cf2788f42e343b67901d0cfc9a7748e24
3
+ metadata.gz: 2503371d1d6229fbe6e57a807fb8d672190de589ccdb877d2a90a06feae20be0
4
+ data.tar.gz: bce91755bb903ab02f886be7e9ab90733566699359e468624f3244f1202ad17a
5
5
  SHA512:
6
- metadata.gz: 2e55a245ad87d21f2d235fe9c211751520172df72f7197ad70b558a546c0b80e62ce63e9f00718ac0f91145d2a14bf920db808f70a550c7822d603c20a565833
7
- data.tar.gz: bdad3eda95fd643db1dc11fc10c4e2c48d0d3442d3e2a1a79012f1e8bb2af769dc98217d897b6fd7b173a17f06e7ba55ab3209d50a3cd7186349dbce5920030b
6
+ metadata.gz: d833790ba0fcdb48cb291a9cde65ab45e2c07284a18a9699c755289a2ded830695e84b3125e552f770c66db8e9930e9c28410baaef2c5e0459d75ef9b93636a4
7
+ data.tar.gz: 819cd9aa17be97c44b3828980b9faa6a3f79476a2802337b3e59a4149c17a45058a0355a7e5a29f39594ed8c8dfae373cb6040dde762795b6c031b9467d70128
@@ -32,6 +32,7 @@
32
32
  // onTextDelta: (str) => {},
33
33
  // onThinkingDelta: (str) => {},
34
34
  // onToolCall: (tc) => {}, // { id, name, arguments }
35
+ // onToolDispatched: ({toolCall, value, error}) => {}, // after it ran
35
36
  // onPhase: (name) => {}, // 'thinking' | 'tool_execution' | ...
36
37
  // signal: abortController.signal
37
38
  // })
@@ -329,6 +330,11 @@ export async function runChatLoop(opts) {
329
330
  onTextDelta,
330
331
  onThinkingDelta,
331
332
  onToolCall,
333
+ // Fired as each tool finishes, so a caller can show what ran WHILE the
334
+ // turn is still going. The returned `dispatched` array says the same
335
+ // thing, but only once the whole loop ends — which on a slow model is
336
+ // minutes after the work happened, with the user watching unnamed tools.
337
+ onToolDispatched,
332
338
  onPhase,
333
339
  ...singleOpts
334
340
  } = opts
@@ -454,7 +460,8 @@ export async function runChatLoop(opts) {
454
460
  // the write. It also means a failed action is something the model can
455
461
  // see and correct, instead of a red mark only the user notices.
456
462
  const roundTripResults = []
457
- for (const { toolCall, error } of localOut.dispatched) {
463
+ for (const { toolCall, value, error } of localOut.dispatched) {
464
+ onToolDispatched?.({ toolCall, value, error })
458
465
  roundTripResults.push({
459
466
  tc: toolCall,
460
467
  result: error ? { error: String(error.message || error) } : { ok: true, applied: toolCall.name }
@@ -468,9 +475,11 @@ export async function runChatLoop(opts) {
468
475
  try {
469
476
  const value = await callMcpTool({ endpoint: tool.endpoint, name: tc.name, args, signal })
470
477
  allDispatched.push({ toolCall: tc, value })
478
+ onToolDispatched?.({ toolCall: tc, value })
471
479
  roundTripResults.push({ tc, result: value })
472
480
  } catch (error) {
473
481
  allDispatched.push({ toolCall: tc, error })
482
+ onToolDispatched?.({ toolCall: tc, error })
474
483
  roundTripResults.push({ tc, result: { error: String(error.message || error) } })
475
484
  }
476
485
  }
@@ -488,9 +497,11 @@ export async function runChatLoop(opts) {
488
497
  signal
489
498
  })
490
499
  allDispatched.push({ toolCall: tc, value })
500
+ onToolDispatched?.({ toolCall: tc, value })
491
501
  roundTripResults.push({ tc, result: value })
492
502
  } catch (error) {
493
503
  allDispatched.push({ toolCall: tc, error })
504
+ onToolDispatched?.({ toolCall: tc, error })
494
505
  // Feed the error text back to the LLM as the tool result — better
495
506
  // than dropping it (the LLM can react, apologize, retry differently).
496
507
  roundTripResults.push({ tc, result: { error: String(error.message || error) } })
@@ -1070,6 +1070,48 @@
1070
1070
  label.textContent = roleLabel("assistant");
1071
1071
  }
1072
1072
 
1073
+ // Tool chips, written as the tools run rather than assembled at the end.
1074
+ // The end-of-turn footer was invisible for as long as the turn lasted —
1075
+ // minutes on a thinking model — so a visitor watched tools execute with
1076
+ // no idea which ones.
1077
+ var liveChips = null; // the container inside the current bubble
1078
+ var pendingChips = []; // chips awaiting their outcome, oldest first
1079
+
1080
+ function chipRow() {
1081
+ if (liveChips && liveChips.parentNode) return liveChips;
1082
+ if (!activeAssistantBody || !activeAssistantBody.parentNode) return null;
1083
+ liveChips = document.createElement("div");
1084
+ liveChips.className = "lmw-tool-chips";
1085
+ // Inside the bubble but OUTSIDE .message-content: every text delta
1086
+ // re-renders that element's markdown from scratch, which silently
1087
+ // erased any chip already written into it.
1088
+ activeAssistantBody.parentNode.appendChild(liveChips);
1089
+ return liveChips;
1090
+ }
1091
+
1092
+ function announceToolCall(toolCall) {
1093
+ var row = chipRow();
1094
+ if (!row) return;
1095
+ var chip = document.createElement("span");
1096
+ chip.className = "lmw-tool-chip running";
1097
+ chip.textContent = "⏳ " + toolCall.name;
1098
+ chip.dataset.tool = toolCall.name;
1099
+ row.appendChild(chip);
1100
+ pendingChips.push(chip);
1101
+ historyEl.scrollTop = historyEl.scrollHeight;
1102
+ }
1103
+
1104
+ function resolveToolCall(outcome) {
1105
+ // Match by name, oldest first: ids are not always echoed back, and a
1106
+ // repeated name resolves in call order.
1107
+ var idx = pendingChips.findIndex(function(c) { return c.dataset.tool === outcome.toolCall.name; });
1108
+ var chip = idx === -1 ? null : pendingChips.splice(idx, 1)[0];
1109
+ if (!chip) return;
1110
+ chip.className = "lmw-tool-chip" + (outcome.error ? " error" : "");
1111
+ chip.textContent = (outcome.error ? "❌ " : "🔧 ") + outcome.toolCall.name;
1112
+ if (outcome.error) chip.title = outcome.error.message || String(outcome.error);
1113
+ }
1114
+
1073
1115
  var currentThinkingBlock = null;
1074
1116
  var currentThinkingBody = null;
1075
1117
  // The assistant bubble of the turn in flight. Reasoning belongs ABOVE that
@@ -1196,6 +1238,8 @@
1196
1238
 
1197
1239
  var assistantBody = appendTurn("assistant", "");
1198
1240
  activeAssistantBody = assistantBody;
1241
+ liveChips = null;
1242
+ pendingChips = [];
1199
1243
  markWorking(assistantBody.roleLabel);
1200
1244
  var assistantMarkdown = ""; // accumulate raw markdown, re-render on each delta
1201
1245
 
@@ -1222,6 +1266,8 @@
1222
1266
  aiActions: window[ACTIONS_GLOBAL] || {},
1223
1267
  maxRounds: MAX_ROUNDS,
1224
1268
  signal: currentAbort.signal,
1269
+ onToolCall: function(toolCall) { announceToolCall(toolCall); },
1270
+ onToolDispatched: function(outcome) { resolveToolCall(outcome); },
1225
1271
  onPhase: function(name) {
1226
1272
  // 'thinking' covers the long silence before the first
1227
1273
  // token; 'responding' means text is on its way.
@@ -1258,23 +1304,15 @@
1258
1304
  conversation.push({ role: "user", content: userText });
1259
1305
  conversation.push({ role: "assistant", content: result.content });
1260
1306
 
1261
- // Append a compact "tools used" footer INSIDE the assistant
1262
- // bubble so users have visual confirmation of what the LLM
1263
- // actually did — especially important when the LLM's own text
1264
- // is incomplete (e.g. "Let me first search…" with no follow-up
1265
- // synthesis, common with weaker tool-use models).
1266
- if (result.dispatched.length > 0) {
1267
- var chips = document.createElement("div");
1268
- chips.className = "lmw-tool-chips";
1269
- result.dispatched.forEach(function(d) {
1270
- var chip = document.createElement("span");
1271
- chip.className = "lmw-tool-chip" + (d.error ? " error" : "");
1272
- chip.textContent = (d.error ? "❌ " : "🔧 ") + d.toolCall.name;
1273
- if (d.error) chip.title = d.error.message;
1274
- chips.appendChild(chip);
1275
- });
1276
- assistantBody.appendChild(chips);
1277
- }
1307
+ // Anything still pending never reported an outcome (a class that
1308
+ // does not round-trip, or a dispatch that vanished). Reconcile
1309
+ // from the loop's own record so no chip is left spinning.
1310
+ result.dispatched.forEach(function(d) { resolveToolCall(d); });
1311
+ pendingChips.forEach(function(chip) {
1312
+ chip.className = "lmw-tool-chip";
1313
+ chip.textContent = "🔧 " + chip.dataset.tool;
1314
+ });
1315
+ pendingChips = [];
1278
1316
 
1279
1317
  // Skipped = LLM tried a tool that doesn't exist. Real signal.
1280
1318
  if (result.skipped.length > 0) {
@@ -1,3 +1,3 @@
1
1
  module LlmMetaWidget
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_meta_widget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - jdkim