@cap-js/agents 0.9.7 → 0.9.8
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/_i18n/messages.properties +2 -0
- package/cds-plugin.js +13 -10
- package/lib/agents/middleware/content-filter.js +4 -0
- package/lib/agents/middleware/hitl-decision-note-injector.js +18 -6
- package/lib/agents/middleware/hitl.js +17 -6
- package/lib/agents/middleware/index.js +1 -1
- package/lib/agents/middleware/masking.js +6 -3
- package/lib/agents/middleware/remote-mcp.js +5 -2
- package/lib/agents/middleware/status-update.js +209 -76
- package/lib/agents/middleware/tool-wrap.js +1 -2
- package/lib/agents/summarize-on-timeout.js +5 -2
- package/lib/config/local.js +121 -8
- package/lib/eval/eval-run.js +50 -4
- package/lib/masking/unstructured/hana.js +13 -5
- package/lib/models/aicore.js +16 -3
- package/lib/models/openai.js +9 -0
- package/lib/preview/chat.html +561 -93
- package/lib/telemetry/chat-tracing.js +2 -0
- package/lib/telemetry/mlflow/evaluation.js +83 -4
- package/lib/telemetry/mlflow/exporter/DatabricksExporter.js +39 -5
- package/lib/telemetry/mlflow/exporter/MlflowExporter.js +43 -4
- package/lib/telemetry/mlflow/index.js +1 -7
- package/lib/telemetry/mlflow/prompts.js +15 -11
- package/lib/utils/markdown.js +1 -8
- package/lib/utils/resilience.js +2 -1
- package/lib/utils/toml.js +67 -0
- package/lib/utils/usage.js +34 -0
- package/lib/utils/utils.js +43 -1
- package/package.json +31 -26
- package/srv/handlers/graph-executor/crash-handler.js +47 -0
- package/srv/handlers/graph-executor/hitl.js +27 -0
- package/srv/handlers/graph-executor.js +19 -5
- package/srv/handlers/index.js +6 -1
- package/srv/handlers/mcp-tools.js +11 -2
- package/srv/handlers/subagent-tools.js +13 -4
- package/srv/handlers/system-prompt.js +30 -27
- package/srv/handlers/tools.js +14 -13
package/lib/preview/chat.html
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
align-items: center;
|
|
27
27
|
gap: 12px;
|
|
28
28
|
flex-shrink: 0;
|
|
29
|
+
position: relative;
|
|
29
30
|
}
|
|
30
31
|
header h1 {
|
|
31
32
|
font-size: 1rem;
|
|
@@ -74,11 +75,15 @@
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
.msg,
|
|
77
|
-
.
|
|
78
|
+
.turn-panel {
|
|
78
79
|
max-width: 75%;
|
|
79
80
|
box-sizing: border-box;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
.msg-wrap .msg {
|
|
84
|
+
max-width: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
.msg {
|
|
83
88
|
padding: 10px 14px;
|
|
84
89
|
border-radius: 12px;
|
|
@@ -410,7 +415,7 @@
|
|
|
410
415
|
|
|
411
416
|
/* Thinking messages are streamed into an expanded <details> panel,
|
|
412
417
|
then collapsed once the final response arrives. */
|
|
413
|
-
.
|
|
418
|
+
.turn-panel {
|
|
414
419
|
align-self: flex-start;
|
|
415
420
|
margin: 4px 0 8px 0;
|
|
416
421
|
font-size: 0.88em;
|
|
@@ -419,17 +424,17 @@
|
|
|
419
424
|
padding-left: 10px;
|
|
420
425
|
overflow-wrap: anywhere;
|
|
421
426
|
}
|
|
422
|
-
.
|
|
427
|
+
.turn-panel summary {
|
|
423
428
|
cursor: pointer;
|
|
424
429
|
user-select: none;
|
|
425
430
|
color: #888;
|
|
426
431
|
list-style: none;
|
|
427
432
|
}
|
|
428
|
-
.
|
|
433
|
+
.turn-panel > summary::before {
|
|
429
434
|
content: "▶ ";
|
|
430
435
|
font-size: 0.8em;
|
|
431
436
|
}
|
|
432
|
-
.
|
|
437
|
+
.turn-panel[open] > summary::before {
|
|
433
438
|
content: "▼ ";
|
|
434
439
|
}
|
|
435
440
|
.thinking-step {
|
|
@@ -438,27 +443,231 @@
|
|
|
438
443
|
font-style: italic;
|
|
439
444
|
overflow-x: auto;
|
|
440
445
|
}
|
|
441
|
-
.thinking-step
|
|
446
|
+
.thinking-step p {
|
|
447
|
+
margin: 0;
|
|
448
|
+
}
|
|
449
|
+
.thinking-step + .thinking-step,
|
|
450
|
+
.thinking-step + .tool-call-item,
|
|
451
|
+
.tool-call-item + .thinking-step,
|
|
452
|
+
.tool-call-item + .tool-call-item {
|
|
442
453
|
border-top: 1px dashed #e0e0e0;
|
|
443
454
|
margin-top: 4px;
|
|
444
455
|
}
|
|
445
456
|
@media (prefers-reduced-motion: no-preference) {
|
|
446
|
-
.
|
|
457
|
+
.turn-panel {
|
|
447
458
|
transition: height 0.16s ease;
|
|
448
459
|
}
|
|
449
460
|
}
|
|
450
461
|
@media (prefers-color-scheme: dark) {
|
|
451
|
-
.
|
|
462
|
+
.turn-panel {
|
|
452
463
|
color: #999;
|
|
453
464
|
border-left-color: #444;
|
|
454
465
|
}
|
|
455
|
-
.
|
|
466
|
+
.turn-panel summary {
|
|
456
467
|
color: #aaa;
|
|
457
468
|
}
|
|
458
|
-
.thinking-step + .thinking-step
|
|
469
|
+
.thinking-step + .thinking-step,
|
|
470
|
+
.thinking-step + .tool-call-item,
|
|
471
|
+
.tool-call-item + .thinking-step,
|
|
472
|
+
.tool-call-item + .tool-call-item {
|
|
459
473
|
border-top-color: #333;
|
|
460
474
|
}
|
|
461
475
|
}
|
|
476
|
+
.tool-call-item {
|
|
477
|
+
padding: 2px 0;
|
|
478
|
+
list-style: none;
|
|
479
|
+
}
|
|
480
|
+
.tool-call-item summary {
|
|
481
|
+
cursor: pointer;
|
|
482
|
+
user-select: none;
|
|
483
|
+
list-style: none;
|
|
484
|
+
display: flex;
|
|
485
|
+
align-items: baseline;
|
|
486
|
+
gap: 4px;
|
|
487
|
+
padding: 2px 0;
|
|
488
|
+
}
|
|
489
|
+
.tool-call-item > summary::before {
|
|
490
|
+
content: "▶";
|
|
491
|
+
font-size: 0.7em;
|
|
492
|
+
color: #aaa;
|
|
493
|
+
flex-shrink: 0;
|
|
494
|
+
}
|
|
495
|
+
.tool-call-item[open] > summary::before {
|
|
496
|
+
content: "▼";
|
|
497
|
+
}
|
|
498
|
+
.tool-call-item:not([hasdetails]) > summary::before {
|
|
499
|
+
visibility: hidden;
|
|
500
|
+
}
|
|
501
|
+
.tool-call-status {
|
|
502
|
+
font-size: 0.85em;
|
|
503
|
+
}
|
|
504
|
+
.tool-call-label {
|
|
505
|
+
font-weight: 500;
|
|
506
|
+
}
|
|
507
|
+
.tool-call-kind {
|
|
508
|
+
font-size: 0.78em;
|
|
509
|
+
padding: 1px 5px;
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
background: #eee;
|
|
512
|
+
color: #555;
|
|
513
|
+
}
|
|
514
|
+
.tool-call-kind.kind-query {
|
|
515
|
+
background: #dbeafe;
|
|
516
|
+
color: #1d4ed8;
|
|
517
|
+
}
|
|
518
|
+
.tool-call-kind.kind-call {
|
|
519
|
+
background: #fef3c7;
|
|
520
|
+
color: #92400e;
|
|
521
|
+
}
|
|
522
|
+
.tool-call-kind.kind-agent {
|
|
523
|
+
background: #ede9fe;
|
|
524
|
+
color: #5b21b6;
|
|
525
|
+
}
|
|
526
|
+
.tool-call-detail {
|
|
527
|
+
margin: 4px 0 2px 12px;
|
|
528
|
+
padding: 4px 8px;
|
|
529
|
+
background: #f9f9f9;
|
|
530
|
+
border-radius: 4px;
|
|
531
|
+
font-family: monospace;
|
|
532
|
+
font-size: 0.85em;
|
|
533
|
+
white-space: pre-wrap;
|
|
534
|
+
overflow-x: auto;
|
|
535
|
+
max-height: 200px;
|
|
536
|
+
overflow-y: auto;
|
|
537
|
+
}
|
|
538
|
+
.tool-call-detail-label {
|
|
539
|
+
font-size: 0.75em;
|
|
540
|
+
color: #999;
|
|
541
|
+
margin: 6px 0 2px 12px;
|
|
542
|
+
text-transform: uppercase;
|
|
543
|
+
letter-spacing: 0.04em;
|
|
544
|
+
}
|
|
545
|
+
@media (prefers-color-scheme: dark) {
|
|
546
|
+
.tool-call-item > summary::before {
|
|
547
|
+
color: #555;
|
|
548
|
+
}
|
|
549
|
+
.tool-call-kind {
|
|
550
|
+
background: #333;
|
|
551
|
+
color: #aaa;
|
|
552
|
+
}
|
|
553
|
+
.tool-call-kind.kind-query {
|
|
554
|
+
background: #1e3a5f;
|
|
555
|
+
color: #93c5fd;
|
|
556
|
+
}
|
|
557
|
+
.tool-call-kind.kind-call {
|
|
558
|
+
background: #451a03;
|
|
559
|
+
color: #fcd34d;
|
|
560
|
+
}
|
|
561
|
+
.tool-call-kind.kind-agent {
|
|
562
|
+
background: #2e1065;
|
|
563
|
+
color: #c4b5fd;
|
|
564
|
+
}
|
|
565
|
+
.tool-call-detail {
|
|
566
|
+
background: #1a1a1a;
|
|
567
|
+
color: #ccc;
|
|
568
|
+
}
|
|
569
|
+
.tool-call-detail-label {
|
|
570
|
+
color: #555;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/* Subtle per-message metadata: context size + response time */
|
|
574
|
+
.msg-wrap {
|
|
575
|
+
display: flex;
|
|
576
|
+
flex-direction: column;
|
|
577
|
+
align-self: flex-start;
|
|
578
|
+
max-width: 75%;
|
|
579
|
+
}
|
|
580
|
+
.msg-meta {
|
|
581
|
+
display: inline-block;
|
|
582
|
+
align-self: flex-end;
|
|
583
|
+
margin-top: 3px;
|
|
584
|
+
font-size: 10px;
|
|
585
|
+
cursor: default;
|
|
586
|
+
position: relative;
|
|
587
|
+
white-space: nowrap;
|
|
588
|
+
color: rgba(83, 116, 146, 0.5);
|
|
589
|
+
}
|
|
590
|
+
.msg-meta:hover {
|
|
591
|
+
color: rgba(83, 116, 146, 0.85);
|
|
592
|
+
}
|
|
593
|
+
/* Hover tooltip with full token breakdown */
|
|
594
|
+
.msg-meta-tooltip {
|
|
595
|
+
display: none;
|
|
596
|
+
position: absolute;
|
|
597
|
+
bottom: calc(100% + 6px);
|
|
598
|
+
right: 0;
|
|
599
|
+
background: rgba(42, 61, 82, 0.72);
|
|
600
|
+
color: #e8f2fa;
|
|
601
|
+
font-size: 11px;
|
|
602
|
+
border-radius: 6px;
|
|
603
|
+
padding: 7px 9px;
|
|
604
|
+
z-index: 100;
|
|
605
|
+
pointer-events: none;
|
|
606
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
607
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
608
|
+
backdrop-filter: blur(8px);
|
|
609
|
+
-webkit-backdrop-filter: blur(8px);
|
|
610
|
+
text-align: left;
|
|
611
|
+
}
|
|
612
|
+
.msg-meta-tooltip-grid {
|
|
613
|
+
display: grid;
|
|
614
|
+
grid-template-columns: auto auto;
|
|
615
|
+
column-gap: 16px;
|
|
616
|
+
row-gap: 2px;
|
|
617
|
+
white-space: nowrap;
|
|
618
|
+
}
|
|
619
|
+
.msg-meta-tooltip-grid .tt-indent {
|
|
620
|
+
padding-left: 12px;
|
|
621
|
+
}
|
|
622
|
+
.msg-meta-tooltip-grid .tt-val {
|
|
623
|
+
text-align: right;
|
|
624
|
+
}
|
|
625
|
+
.msg-meta:hover .msg-meta-tooltip {
|
|
626
|
+
display: block;
|
|
627
|
+
}
|
|
628
|
+
/* Total context indicator: right-aligned row just above the input bar */
|
|
629
|
+
#context-indicator-row {
|
|
630
|
+
display: none;
|
|
631
|
+
padding: 2px 20px 2px;
|
|
632
|
+
text-align: right;
|
|
633
|
+
flex-shrink: 0;
|
|
634
|
+
}
|
|
635
|
+
#context-indicator-row.visible {
|
|
636
|
+
display: block;
|
|
637
|
+
}
|
|
638
|
+
#context-indicator {
|
|
639
|
+
font-size: 10px;
|
|
640
|
+
opacity: 0.45;
|
|
641
|
+
white-space: nowrap;
|
|
642
|
+
color: #537492;
|
|
643
|
+
position: relative;
|
|
644
|
+
cursor: default;
|
|
645
|
+
transition: opacity 0.15s;
|
|
646
|
+
}
|
|
647
|
+
#context-indicator:hover {
|
|
648
|
+
opacity: 0.85;
|
|
649
|
+
}
|
|
650
|
+
/* Hover tooltip mirroring per-message breakdown, anchored to the indicator */
|
|
651
|
+
#context-indicator:hover .msg-meta-tooltip {
|
|
652
|
+
display: block;
|
|
653
|
+
}
|
|
654
|
+
@media (prefers-color-scheme: dark) {
|
|
655
|
+
.msg-meta {
|
|
656
|
+
color: rgba(145, 173, 197, 0.55);
|
|
657
|
+
}
|
|
658
|
+
.msg-meta:hover {
|
|
659
|
+
color: rgba(145, 173, 197, 0.9);
|
|
660
|
+
}
|
|
661
|
+
.msg-meta-tooltip {
|
|
662
|
+
background: rgba(10, 30, 48, 0.72);
|
|
663
|
+
color: #c8dff0;
|
|
664
|
+
border-color: rgba(255, 255, 255, 0.12);
|
|
665
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
|
|
666
|
+
}
|
|
667
|
+
#context-indicator {
|
|
668
|
+
color: #91adc5;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
462
671
|
</style>
|
|
463
672
|
<script src="marked.min.js"></script>
|
|
464
673
|
</head>
|
|
@@ -474,6 +683,12 @@
|
|
|
474
683
|
<div class="status-text" id="status-text"></div>
|
|
475
684
|
</div>
|
|
476
685
|
|
|
686
|
+
<div id="context-indicator-row">
|
|
687
|
+
<span id="context-indicator"
|
|
688
|
+
><span class="msg-meta-tooltip" id="context-indicator-tooltip"></span
|
|
689
|
+
></span>
|
|
690
|
+
</div>
|
|
691
|
+
|
|
477
692
|
<div id="input-row">
|
|
478
693
|
<textarea id="prompt" rows="1" placeholder="Message {{agentName}}…" autofocus></textarea>
|
|
479
694
|
<button id="send">Send</button>
|
|
@@ -525,16 +740,149 @@
|
|
|
525
740
|
let activeTaskId = null
|
|
526
741
|
let activeAbort = null
|
|
527
742
|
let streamingBubble = null
|
|
743
|
+
let lastStreamingBubble = null
|
|
528
744
|
let streamingText = ""
|
|
529
745
|
let thinkingFinalized = false
|
|
530
|
-
let thinkingStepEls = []
|
|
531
|
-
let thinkingTexts = []
|
|
532
|
-
let
|
|
533
|
-
let thinkingSummaryEl = null // <details> containing thinking steps
|
|
534
|
-
let currentThinkingArtifactId = null // artifactId of open thinking step
|
|
535
|
-
let currentThinkingClosed = false // true once lastChunk seen for current id
|
|
746
|
+
let thinkingStepEls = []
|
|
747
|
+
let thinkingTexts = []
|
|
748
|
+
let turnPanelEl = null // single <details> for thinking steps + tool calls
|
|
536
749
|
let pendingThinkingUpdate = null
|
|
537
750
|
let pendingThinkingTimer = null
|
|
751
|
+
let toolCallsMap = new Map() // tool call id → { el, data }
|
|
752
|
+
|
|
753
|
+
// Token accounting. The server sends cumulative token counts (a running sum
|
|
754
|
+
// over every model call in the conversation) plus context_tokens — the prompt
|
|
755
|
+
// size of the most recent model call, i.e. the current live context occupancy.
|
|
756
|
+
// Every field except context_tokens is cumulative, so per-turn values are
|
|
757
|
+
// derived by diffing against the previous turn.
|
|
758
|
+
let contextTokens = 0 // current live context size (last-call input incl. cache)
|
|
759
|
+
let prevContextTokens = 0 // context size after the previous turn
|
|
760
|
+
// Cumulative counts as of the previous turn, for per-turn deltas.
|
|
761
|
+
let prev = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0, reasoning: 0 }
|
|
762
|
+
let responseStartTime = null
|
|
763
|
+
const contextIndicator = document.getElementById("context-indicator")
|
|
764
|
+
const contextIndicatorTooltip = document.getElementById("context-indicator-tooltip")
|
|
765
|
+
|
|
766
|
+
function fmt(n) {
|
|
767
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M"
|
|
768
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + "k"
|
|
769
|
+
return String(n)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function fmtDuration(ms) {
|
|
773
|
+
if (ms < 1000) return ms + "ms"
|
|
774
|
+
return (ms / 1000).toFixed(1) + "s"
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function buildTooltipTable(rows) {
|
|
778
|
+
const grid = document.createElement("div")
|
|
779
|
+
grid.className = "msg-meta-tooltip-grid"
|
|
780
|
+
for (const row of rows) {
|
|
781
|
+
const label = document.createElement("div")
|
|
782
|
+
if (row.indent) label.className = "tt-indent"
|
|
783
|
+
label.textContent = row.label
|
|
784
|
+
const val = document.createElement("div")
|
|
785
|
+
val.className = "tt-val"
|
|
786
|
+
val.textContent = row.value
|
|
787
|
+
grid.appendChild(label)
|
|
788
|
+
grid.appendChild(val)
|
|
789
|
+
}
|
|
790
|
+
return grid
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function buildTokenRows(contextVal, input, cacheRead, cacheCreate, output, reasoning) {
|
|
794
|
+
const rows = [{ label: "Context", value: contextVal }]
|
|
795
|
+
if (input > 0) {
|
|
796
|
+
rows.push({ label: "Input", value: fmt(input) })
|
|
797
|
+
if (cacheRead > 0) rows.push({ label: "Cache read", value: fmt(cacheRead), indent: true })
|
|
798
|
+
if (cacheCreate > 0)
|
|
799
|
+
rows.push({ label: "Cache write", value: fmt(cacheCreate), indent: true })
|
|
800
|
+
}
|
|
801
|
+
if (output > 0)
|
|
802
|
+
rows.push({
|
|
803
|
+
label: "Output" + (reasoning > 0 ? " (reasoning " + fmt(reasoning) + ")" : ""),
|
|
804
|
+
value: fmt(output),
|
|
805
|
+
})
|
|
806
|
+
return rows
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function updateContextIndicator() {
|
|
810
|
+
if (contextTokens === 0) return
|
|
811
|
+
contextIndicator.childNodes[0].nodeType === Node.TEXT_NODE
|
|
812
|
+
? (contextIndicator.childNodes[0].textContent = "⛁ " + fmt(contextTokens))
|
|
813
|
+
: contextIndicator.insertBefore(
|
|
814
|
+
document.createTextNode("⛁ " + fmt(contextTokens)),
|
|
815
|
+
contextIndicatorTooltip,
|
|
816
|
+
)
|
|
817
|
+
contextIndicatorTooltip.innerHTML = ""
|
|
818
|
+
contextIndicatorTooltip.appendChild(
|
|
819
|
+
buildTooltipTable(
|
|
820
|
+
buildTokenRows(
|
|
821
|
+
fmt(contextTokens),
|
|
822
|
+
prev.input,
|
|
823
|
+
prev.cacheRead,
|
|
824
|
+
prev.cacheCreate,
|
|
825
|
+
prev.output,
|
|
826
|
+
prev.reasoning,
|
|
827
|
+
),
|
|
828
|
+
),
|
|
829
|
+
)
|
|
830
|
+
document.getElementById("context-indicator-row").classList.add("visible")
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function attachMsgMeta(el, usage, durationMs) {
|
|
834
|
+
// All these fields are cumulative across the conversation; diff against the
|
|
835
|
+
// previous turn to get this turn's billing-relevant amounts. input_tokens
|
|
836
|
+
// already includes cache-read/cache-write tokens, so the uncached (full-rate)
|
|
837
|
+
// input is input minus those.
|
|
838
|
+
const stepInTotal = (usage.input_tokens || 0) - prev.input
|
|
839
|
+
const stepOut = (usage.output_tokens || 0) - prev.output
|
|
840
|
+
const stepCacheRead = (usage.cache_read_input_tokens || 0) - prev.cacheRead
|
|
841
|
+
const stepCacheCreate = (usage.cache_creation_input_tokens || 0) - prev.cacheCreate
|
|
842
|
+
const stepReasoning = (usage.reasoning_tokens || 0) - prev.reasoning
|
|
843
|
+
const newContext = usage.context_tokens || usage.input_tokens || 0
|
|
844
|
+
|
|
845
|
+
// Context growth: how much this exchange added to the live context window.
|
|
846
|
+
const contextGrowth = newContext - prevContextTokens
|
|
847
|
+
|
|
848
|
+
prevContextTokens = newContext
|
|
849
|
+
contextTokens = newContext
|
|
850
|
+
prev = {
|
|
851
|
+
input: usage.input_tokens || 0,
|
|
852
|
+
output: usage.output_tokens || 0,
|
|
853
|
+
cacheRead: usage.cache_read_input_tokens || 0,
|
|
854
|
+
cacheCreate: usage.cache_creation_input_tokens || 0,
|
|
855
|
+
reasoning: usage.reasoning_tokens || 0,
|
|
856
|
+
}
|
|
857
|
+
updateContextIndicator()
|
|
858
|
+
|
|
859
|
+
const meta = document.createElement("span")
|
|
860
|
+
meta.className = "msg-meta"
|
|
861
|
+
|
|
862
|
+
const summary =
|
|
863
|
+
"⛁ " + fmt(contextGrowth) + (durationMs != null ? " · " + fmtDuration(durationMs) : "")
|
|
864
|
+
meta.appendChild(document.createTextNode(summary))
|
|
865
|
+
|
|
866
|
+
// Hover tooltip: per-turn, billing-relevant token breakdown.
|
|
867
|
+
const tooltip = document.createElement("span")
|
|
868
|
+
tooltip.className = "msg-meta-tooltip"
|
|
869
|
+
const growthLabel = (contextGrowth >= 0 ? "+" : "") + fmt(contextGrowth)
|
|
870
|
+
tooltip.appendChild(
|
|
871
|
+
buildTooltipTable(
|
|
872
|
+
buildTokenRows(
|
|
873
|
+
fmt(contextTokens) + " (" + growthLabel + ")",
|
|
874
|
+
stepInTotal,
|
|
875
|
+
stepCacheRead,
|
|
876
|
+
stepCacheCreate,
|
|
877
|
+
stepOut,
|
|
878
|
+
stepReasoning,
|
|
879
|
+
),
|
|
880
|
+
),
|
|
881
|
+
)
|
|
882
|
+
meta.appendChild(tooltip)
|
|
883
|
+
|
|
884
|
+
;(el._wrap ?? el).appendChild(meta)
|
|
885
|
+
}
|
|
538
886
|
|
|
539
887
|
// Auto-enable streaming when agent card advertises streaming capability
|
|
540
888
|
fetch(AGENT_URL + ".well-known/agent-card.json", { headers: authHeaders() })
|
|
@@ -604,39 +952,43 @@
|
|
|
604
952
|
el.innerHTML = md(text, { sanitize: true })
|
|
605
953
|
else el.textContent = text
|
|
606
954
|
if (role === "agent_thinking") el.className = "msg agent_thinking"
|
|
607
|
-
messages
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
955
|
+
const anchor = beforeEl ?? (typing.parentElement === messages ? typing : statusText)
|
|
956
|
+
if (role === "agent") {
|
|
957
|
+
const wrap = document.createElement("div")
|
|
958
|
+
wrap.className = "msg-wrap"
|
|
959
|
+
wrap.appendChild(el)
|
|
960
|
+
el._wrap = wrap
|
|
961
|
+
messages.insertBefore(wrap, anchor)
|
|
962
|
+
} else {
|
|
963
|
+
messages.insertBefore(el, anchor)
|
|
964
|
+
}
|
|
611
965
|
messages.scrollTop = messages.scrollHeight
|
|
612
966
|
return el
|
|
613
967
|
}
|
|
614
968
|
|
|
615
|
-
function
|
|
616
|
-
if (!
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
const count = thinkingStepCount
|
|
620
|
-
summary.textContent = `Thinking (${count} step${count === 1 ? "" : "s"})`
|
|
969
|
+
function updateTurnPanelLabel() {
|
|
970
|
+
if (!turnPanelEl) return
|
|
971
|
+
const n = thinkingStepEls.length + toolCallsMap.size
|
|
972
|
+
turnPanelEl.querySelector("summary").textContent = `Steps (${n})`
|
|
621
973
|
}
|
|
622
974
|
|
|
623
|
-
function
|
|
624
|
-
if (!
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
975
|
+
function ensureTurnPanel() {
|
|
976
|
+
if (!turnPanelEl) {
|
|
977
|
+
turnPanelEl = document.createElement("details")
|
|
978
|
+
turnPanelEl.className = "turn-panel"
|
|
979
|
+
turnPanelEl.open = true
|
|
628
980
|
const summary = document.createElement("summary")
|
|
629
|
-
summary.addEventListener("click",
|
|
630
|
-
|
|
631
|
-
updateThinkingSummaryLabel()
|
|
981
|
+
summary.addEventListener("click", toggleTurnPanel)
|
|
982
|
+
turnPanelEl.appendChild(summary)
|
|
632
983
|
messages.insertBefore(
|
|
633
|
-
|
|
984
|
+
turnPanelEl,
|
|
634
985
|
typing.parentElement === messages ? typing : statusText,
|
|
635
986
|
)
|
|
636
987
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
988
|
+
cancelTurnPanelAnimation(turnPanelEl)
|
|
989
|
+
turnPanelEl.open = true
|
|
990
|
+
updateTurnPanelLabel()
|
|
991
|
+
return turnPanelEl
|
|
640
992
|
}
|
|
641
993
|
|
|
642
994
|
function clearPendingThinkingUpdate() {
|
|
@@ -655,34 +1007,31 @@
|
|
|
655
1007
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
656
1008
|
}
|
|
657
1009
|
|
|
658
|
-
function
|
|
1010
|
+
function resetTurnPanelAnimation(el) {
|
|
659
1011
|
el.style.height = ""
|
|
660
1012
|
el.style.overflow = ""
|
|
661
1013
|
}
|
|
662
1014
|
|
|
663
|
-
function
|
|
1015
|
+
function cancelTurnPanelAnimation(el) {
|
|
664
1016
|
if (!el) return
|
|
665
|
-
el.
|
|
666
|
-
|
|
1017
|
+
el._animationId = (el._animationId ?? 0) + 1
|
|
1018
|
+
resetTurnPanelAnimation(el)
|
|
667
1019
|
}
|
|
668
1020
|
|
|
669
|
-
function
|
|
1021
|
+
function toggleTurnPanel(event) {
|
|
670
1022
|
if (prefersReducedMotion()) return
|
|
671
1023
|
event.preventDefault()
|
|
672
1024
|
const el = event.currentTarget.parentElement
|
|
673
|
-
if (el.open)
|
|
674
|
-
else
|
|
1025
|
+
if (el.open) collapseTurnPanel(el)
|
|
1026
|
+
else expandTurnPanel(el)
|
|
675
1027
|
}
|
|
676
1028
|
|
|
677
1029
|
function renderThinkingStep(update) {
|
|
678
|
-
const { artifactId, text,
|
|
679
|
-
const panel =
|
|
680
|
-
const idx = thinkingStepEls.findIndex((
|
|
681
|
-
if (
|
|
682
|
-
thinkingTexts[idx] += text
|
|
683
|
-
thinkingStepEls[idx].innerHTML = md(thinkingTexts[idx])
|
|
684
|
-
} else if (artifactId === currentThinkingArtifactId && !currentThinkingClosed) {
|
|
685
|
-
thinkingTexts[idx] += "\n" + text
|
|
1030
|
+
const { artifactId, text, append } = update
|
|
1031
|
+
const panel = ensureTurnPanel()
|
|
1032
|
+
const idx = thinkingStepEls.findIndex((s) => s._artifactId === artifactId)
|
|
1033
|
+
if (idx !== -1) {
|
|
1034
|
+
thinkingTexts[idx] += append ? text : "\n" + text
|
|
686
1035
|
thinkingStepEls[idx].innerHTML = md(thinkingTexts[idx])
|
|
687
1036
|
} else {
|
|
688
1037
|
const step = document.createElement("div")
|
|
@@ -692,12 +1041,8 @@
|
|
|
692
1041
|
panel.appendChild(step)
|
|
693
1042
|
thinkingStepEls.push(step)
|
|
694
1043
|
thinkingTexts.push(text)
|
|
695
|
-
|
|
696
|
-
updateThinkingSummaryLabel()
|
|
697
|
-
currentThinkingArtifactId = artifactId
|
|
698
|
-
currentThinkingClosed = false
|
|
1044
|
+
updateTurnPanelLabel()
|
|
699
1045
|
}
|
|
700
|
-
if (lastChunk) currentThinkingClosed = true
|
|
701
1046
|
messages.scrollTop = messages.scrollHeight
|
|
702
1047
|
}
|
|
703
1048
|
|
|
@@ -726,24 +1071,112 @@
|
|
|
726
1071
|
)
|
|
727
1072
|
}
|
|
728
1073
|
|
|
1074
|
+
function escHtml(str) {
|
|
1075
|
+
return String(str)
|
|
1076
|
+
.replace(/&/g, "&")
|
|
1077
|
+
.replace(/</g, "<")
|
|
1078
|
+
.replace(/>/g, ">")
|
|
1079
|
+
.replace(/"/g, """)
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const TOOL_STATUS_ICON = { running: "⏳", done: "✓", error: "✗" }
|
|
1083
|
+
const TOOL_RESULT_TRUNCATE = 500
|
|
1084
|
+
|
|
1085
|
+
function updateToolCall(id, data) {
|
|
1086
|
+
let entry = toolCallsMap.get(id)
|
|
1087
|
+
if (!entry) {
|
|
1088
|
+
const el = document.createElement("details")
|
|
1089
|
+
el.className = "tool-call-item"
|
|
1090
|
+
el.innerHTML =
|
|
1091
|
+
`<summary>` +
|
|
1092
|
+
`<span class="tool-call-kind" hidden></span>` +
|
|
1093
|
+
`<span class="tool-call-label"></span>` +
|
|
1094
|
+
`<span class="tool-call-status"></span> ` +
|
|
1095
|
+
`</summary>` +
|
|
1096
|
+
`<div class="tool-call-detail-label" hidden>Args</div><pre class="tool-call-detail args" hidden></pre>` +
|
|
1097
|
+
`<div class="tool-call-detail-label" hidden>Result</div><pre class="tool-call-detail result" hidden></pre>`
|
|
1098
|
+
entry = { el, data: {} }
|
|
1099
|
+
toolCallsMap.set(id, entry)
|
|
1100
|
+
ensureTurnPanel().appendChild(el)
|
|
1101
|
+
} else {
|
|
1102
|
+
ensureTurnPanel()
|
|
1103
|
+
}
|
|
1104
|
+
Object.assign(entry.data, data)
|
|
1105
|
+
const { el } = entry
|
|
1106
|
+
const d = entry.data
|
|
1107
|
+
|
|
1108
|
+
el.querySelector(".tool-call-status").textContent = TOOL_STATUS_ICON[d.status] ?? "⏳"
|
|
1109
|
+
el.querySelector(".tool-call-label").textContent = d.label ?? d.name ?? ""
|
|
1110
|
+
if (d.kind) {
|
|
1111
|
+
const kindEl = el.querySelector(".tool-call-kind")
|
|
1112
|
+
kindEl.textContent = d.kind
|
|
1113
|
+
kindEl.className = `tool-call-kind kind-${d.kind}`
|
|
1114
|
+
kindEl.hidden = false
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const argsStr =
|
|
1118
|
+
d.args != null
|
|
1119
|
+
? typeof d.args === "string"
|
|
1120
|
+
? d.args
|
|
1121
|
+
: JSON.stringify(d.args, null, 2)
|
|
1122
|
+
: null
|
|
1123
|
+
let resultStr = null
|
|
1124
|
+
if (d.result != null) {
|
|
1125
|
+
const raw = typeof d.result === "string" ? d.result : JSON.stringify(d.result, null, 2)
|
|
1126
|
+
resultStr =
|
|
1127
|
+
raw.length > TOOL_RESULT_TRUNCATE
|
|
1128
|
+
? raw.slice(0, TOOL_RESULT_TRUNCATE) +
|
|
1129
|
+
`\n… (+${raw.length - TOOL_RESULT_TRUNCATE} chars)`
|
|
1130
|
+
: raw
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
if (argsStr != null) {
|
|
1134
|
+
const pre = el.querySelector(".args")
|
|
1135
|
+
pre.textContent = argsStr
|
|
1136
|
+
pre.hidden = false
|
|
1137
|
+
pre.previousElementSibling.hidden = false
|
|
1138
|
+
el.setAttribute("hasdetails", "")
|
|
1139
|
+
}
|
|
1140
|
+
if (resultStr != null) {
|
|
1141
|
+
const pre = el.querySelector(".result")
|
|
1142
|
+
pre.textContent = resultStr
|
|
1143
|
+
pre.hidden = false
|
|
1144
|
+
pre.previousElementSibling.hidden = false
|
|
1145
|
+
el.setAttribute("hasdetails", "")
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
messages.scrollTop = messages.scrollHeight
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function finalizeTurnPanel() {
|
|
1152
|
+
if (turnPanelEl?.open) {
|
|
1153
|
+
setTimeout(() => {
|
|
1154
|
+
if (turnPanelEl) collapseTurnPanel(turnPanelEl)
|
|
1155
|
+
}, 800)
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
729
1159
|
function promoteLatestThinkingToResponse(responseText) {
|
|
730
|
-
if (!
|
|
1160
|
+
if (!turnPanelEl || !latestThinkingMatches(responseText)) return null
|
|
731
1161
|
|
|
732
1162
|
thinkingTexts.pop()
|
|
733
1163
|
const responseEl = thinkingStepEls.pop()
|
|
734
|
-
thinkingStepCount = thinkingStepEls.length
|
|
735
1164
|
|
|
736
1165
|
responseEl.className = "msg agent"
|
|
737
1166
|
responseEl._promotedFromThinking = true
|
|
738
1167
|
responseEl.innerHTML = md(responseText)
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1168
|
+
const wrap = document.createElement("div")
|
|
1169
|
+
wrap.className = "msg-wrap"
|
|
1170
|
+
wrap.appendChild(responseEl)
|
|
1171
|
+
responseEl._wrap = wrap
|
|
1172
|
+
messages.insertBefore(wrap, typing.parentElement === messages ? typing : statusText)
|
|
1173
|
+
|
|
1174
|
+
if (thinkingStepEls.length === 0 && toolCallsMap.size === 0) {
|
|
1175
|
+
turnPanelEl.remove()
|
|
1176
|
+
turnPanelEl = null
|
|
744
1177
|
} else {
|
|
745
|
-
|
|
746
|
-
|
|
1178
|
+
updateTurnPanelLabel()
|
|
1179
|
+
collapseTurnPanel(turnPanelEl)
|
|
747
1180
|
}
|
|
748
1181
|
|
|
749
1182
|
messages.scrollTop = messages.scrollHeight
|
|
@@ -753,28 +1186,27 @@
|
|
|
753
1186
|
function finalizeThinking(beforeEl, responseText) {
|
|
754
1187
|
clearPendingThinkingUpdate()
|
|
755
1188
|
if (beforeEl?._promotedFromThinking) return
|
|
756
|
-
if (thinkingStepEls.length === 0 || !
|
|
1189
|
+
if (thinkingStepEls.length === 0 || !turnPanelEl) return
|
|
757
1190
|
// Stream labels every model turn as thinking until terminal response is
|
|
758
1191
|
// known. Its final turn is authoritative response, never a thinking step.
|
|
759
1192
|
thinkingTexts.pop()
|
|
760
1193
|
const finalResponse = thinkingStepEls.pop()
|
|
761
1194
|
finalResponse?.remove()
|
|
762
|
-
|
|
763
|
-
updateThinkingSummaryLabel()
|
|
1195
|
+
updateTurnPanelLabel()
|
|
764
1196
|
|
|
765
|
-
if (thinkingStepEls.length === 0) {
|
|
766
|
-
|
|
767
|
-
|
|
1197
|
+
if (thinkingStepEls.length === 0 && toolCallsMap.size === 0) {
|
|
1198
|
+
turnPanelEl.remove()
|
|
1199
|
+
turnPanelEl = null
|
|
768
1200
|
return
|
|
769
1201
|
}
|
|
770
1202
|
|
|
771
|
-
if (beforeEl &&
|
|
772
|
-
messages.insertBefore(
|
|
1203
|
+
if (beforeEl && turnPanelEl.nextSibling !== (beforeEl._wrap ?? beforeEl)) {
|
|
1204
|
+
messages.insertBefore(turnPanelEl, beforeEl._wrap ?? beforeEl)
|
|
773
1205
|
}
|
|
774
|
-
|
|
1206
|
+
collapseTurnPanel(turnPanelEl)
|
|
775
1207
|
}
|
|
776
1208
|
|
|
777
|
-
function
|
|
1209
|
+
function collapseTurnPanel(el) {
|
|
778
1210
|
if (!el || !el.open) return
|
|
779
1211
|
|
|
780
1212
|
if (prefersReducedMotion()) {
|
|
@@ -785,20 +1217,20 @@
|
|
|
785
1217
|
const summary = el.querySelector("summary")
|
|
786
1218
|
const startHeight = `${el.offsetHeight}px`
|
|
787
1219
|
const endHeight = `${summary?.offsetHeight ?? 0}px`
|
|
788
|
-
const animationId = (el.
|
|
789
|
-
el.
|
|
1220
|
+
const animationId = (el._animationId ?? 0) + 1
|
|
1221
|
+
el._animationId = animationId
|
|
790
1222
|
let finished = false
|
|
791
1223
|
|
|
792
1224
|
const finish = () => {
|
|
793
1225
|
el.removeEventListener("transitionend", finish)
|
|
794
|
-
if (el.
|
|
1226
|
+
if (el._animationId !== animationId) return
|
|
795
1227
|
if (finished) return
|
|
796
1228
|
finished = true
|
|
797
1229
|
el.open = false
|
|
798
|
-
|
|
1230
|
+
resetTurnPanelAnimation(el)
|
|
799
1231
|
}
|
|
800
1232
|
|
|
801
|
-
|
|
1233
|
+
resetTurnPanelAnimation(el)
|
|
802
1234
|
el.style.overflow = "hidden"
|
|
803
1235
|
el.style.height = startHeight
|
|
804
1236
|
requestAnimationFrame(() => {
|
|
@@ -808,7 +1240,7 @@
|
|
|
808
1240
|
setTimeout(finish, THINKING_SUMMARY_ANIMATION_MS + 80)
|
|
809
1241
|
}
|
|
810
1242
|
|
|
811
|
-
function
|
|
1243
|
+
function expandTurnPanel(el) {
|
|
812
1244
|
if (!el || el.open) return
|
|
813
1245
|
|
|
814
1246
|
if (prefersReducedMotion()) {
|
|
@@ -820,19 +1252,19 @@
|
|
|
820
1252
|
const summary = el.querySelector("summary")
|
|
821
1253
|
const startHeight = `${summary?.offsetHeight ?? 0}px`
|
|
822
1254
|
const endHeight = `${el.scrollHeight}px`
|
|
823
|
-
const animationId = (el.
|
|
824
|
-
el.
|
|
1255
|
+
const animationId = (el._animationId ?? 0) + 1
|
|
1256
|
+
el._animationId = animationId
|
|
825
1257
|
let finished = false
|
|
826
1258
|
|
|
827
1259
|
const finish = () => {
|
|
828
1260
|
el.removeEventListener("transitionend", finish)
|
|
829
|
-
if (el.
|
|
1261
|
+
if (el._animationId !== animationId) return
|
|
830
1262
|
if (finished) return
|
|
831
1263
|
finished = true
|
|
832
|
-
|
|
1264
|
+
resetTurnPanelAnimation(el)
|
|
833
1265
|
}
|
|
834
1266
|
|
|
835
|
-
|
|
1267
|
+
resetTurnPanelAnimation(el)
|
|
836
1268
|
el.style.overflow = "hidden"
|
|
837
1269
|
el.style.height = startHeight
|
|
838
1270
|
requestAnimationFrame(() => {
|
|
@@ -916,6 +1348,7 @@
|
|
|
916
1348
|
|
|
917
1349
|
activeTaskId = taskId
|
|
918
1350
|
setBusy(true)
|
|
1351
|
+
responseStartTime = performance.now()
|
|
919
1352
|
|
|
920
1353
|
const abort = new AbortController()
|
|
921
1354
|
activeAbort = abort
|
|
@@ -936,6 +1369,7 @@
|
|
|
936
1369
|
taskId,
|
|
937
1370
|
contextId,
|
|
938
1371
|
parts: [{ kind: "text", text: decision }],
|
|
1372
|
+
metadata: { "tool-status-update": { args: true, result: true } },
|
|
939
1373
|
},
|
|
940
1374
|
},
|
|
941
1375
|
}),
|
|
@@ -980,9 +1414,19 @@
|
|
|
980
1414
|
return
|
|
981
1415
|
}
|
|
982
1416
|
|
|
1417
|
+
// Token usage: non-streaming embeds in status message metadata; streaming passes via _tokenUsage
|
|
1418
|
+
const tokenUsage =
|
|
1419
|
+
result?._tokenUsage ?? result?.status?.message?.metadata?.["sap.cds.agents.token-usage"]
|
|
1420
|
+
const durationMs =
|
|
1421
|
+
responseStartTime != null ? Math.round(performance.now() - responseStartTime) : null
|
|
1422
|
+
|
|
983
1423
|
// If streaming already rendered the final text via lastChunk, skip.
|
|
984
1424
|
if (useStreaming && streamingText !== "") {
|
|
985
|
-
if (!thinkingFinalized) finalizeThinking(
|
|
1425
|
+
if (!thinkingFinalized) finalizeThinking(lastStreamingBubble, streamingText)
|
|
1426
|
+
if (tokenUsage && lastStreamingBubble)
|
|
1427
|
+
attachMsgMeta(lastStreamingBubble, tokenUsage, durationMs)
|
|
1428
|
+
finalizeTurnPanel()
|
|
1429
|
+
lastStreamingBubble = null
|
|
986
1430
|
streamingBubble = null
|
|
987
1431
|
streamingText = ""
|
|
988
1432
|
return
|
|
@@ -992,6 +1436,7 @@
|
|
|
992
1436
|
if (streamingBubble) {
|
|
993
1437
|
if (streamingText) streamingBubble.innerHTML = md(streamingText)
|
|
994
1438
|
finalizeThinking(streamingBubble, streamingText)
|
|
1439
|
+
finalizeTurnPanel()
|
|
995
1440
|
streamingBubble = null
|
|
996
1441
|
streamingText = ""
|
|
997
1442
|
return
|
|
@@ -1002,7 +1447,9 @@
|
|
|
1002
1447
|
const reply = partsToText(parts)
|
|
1003
1448
|
const replyEl =
|
|
1004
1449
|
promoteLatestThinkingToResponse(reply) || addMessage("agent", reply || "(no response)")
|
|
1450
|
+
if (tokenUsage) attachMsgMeta(replyEl, tokenUsage, durationMs)
|
|
1005
1451
|
finalizeThinking(replyEl, reply)
|
|
1452
|
+
finalizeTurnPanel()
|
|
1006
1453
|
}
|
|
1007
1454
|
|
|
1008
1455
|
function enqueueMessage() {
|
|
@@ -1042,19 +1489,19 @@
|
|
|
1042
1489
|
el.classList.remove("queued")
|
|
1043
1490
|
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
1044
1491
|
setBusy(true)
|
|
1492
|
+
responseStartTime = performance.now()
|
|
1045
1493
|
|
|
1046
1494
|
const abort = new AbortController()
|
|
1047
1495
|
activeAbort = abort
|
|
1048
1496
|
streamingBubble = null
|
|
1497
|
+
lastStreamingBubble = null
|
|
1049
1498
|
streamingText = ""
|
|
1050
1499
|
thinkingFinalized = false
|
|
1051
1500
|
clearPendingThinkingUpdate()
|
|
1052
1501
|
thinkingStepEls = []
|
|
1053
1502
|
thinkingTexts = []
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
currentThinkingArtifactId = null
|
|
1057
|
-
currentThinkingClosed = false
|
|
1503
|
+
turnPanelEl = null
|
|
1504
|
+
toolCallsMap = new Map()
|
|
1058
1505
|
|
|
1059
1506
|
const useStream = useStreaming
|
|
1060
1507
|
try {
|
|
@@ -1072,6 +1519,12 @@
|
|
|
1072
1519
|
messageId: crypto.randomUUID(),
|
|
1073
1520
|
...(contextId ? { contextId } : {}),
|
|
1074
1521
|
parts: [{ kind: "text", text }],
|
|
1522
|
+
metadata: {
|
|
1523
|
+
"tool-status-update": {
|
|
1524
|
+
args: true,
|
|
1525
|
+
result: true,
|
|
1526
|
+
},
|
|
1527
|
+
},
|
|
1075
1528
|
},
|
|
1076
1529
|
},
|
|
1077
1530
|
}),
|
|
@@ -1176,6 +1629,17 @@
|
|
|
1176
1629
|
return null
|
|
1177
1630
|
}
|
|
1178
1631
|
|
|
1632
|
+
if (event.artifact?.artifactId?.startsWith("tool-call-")) {
|
|
1633
|
+
const dataPart = (event.artifact?.parts ?? []).find(
|
|
1634
|
+
(p) => p.kind === "data" && p.data?.type === "tool-call",
|
|
1635
|
+
)
|
|
1636
|
+
if (dataPart) {
|
|
1637
|
+
const id = event.artifact.artifactId.slice("tool-call-".length)
|
|
1638
|
+
updateToolCall(id, dataPart.data)
|
|
1639
|
+
}
|
|
1640
|
+
return null
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1179
1643
|
if (event.artifact?.artifactId !== "response") return null
|
|
1180
1644
|
|
|
1181
1645
|
// A2A spec: append/lastChunk are event-level fields (siblings of `artifact`).
|
|
@@ -1194,7 +1658,9 @@
|
|
|
1194
1658
|
}
|
|
1195
1659
|
}
|
|
1196
1660
|
finalizeThinking(streamingBubble, text)
|
|
1661
|
+
finalizeTurnPanel()
|
|
1197
1662
|
thinkingFinalized = true
|
|
1663
|
+
lastStreamingBubble = streamingBubble
|
|
1198
1664
|
streamingBubble = null
|
|
1199
1665
|
streamingText = text // dedupe with handleResult
|
|
1200
1666
|
return null
|
|
@@ -1222,11 +1688,13 @@
|
|
|
1222
1688
|
|
|
1223
1689
|
// Status update with final=true means terminal state
|
|
1224
1690
|
if (event.kind === "status-update" && event.final) {
|
|
1691
|
+
const tokenUsage = event.metadata?.["sap.cds.agents.token-usage"]
|
|
1225
1692
|
return {
|
|
1226
1693
|
id: event.taskId || activeTaskId,
|
|
1227
1694
|
contextId: event.contextId || contextId,
|
|
1228
1695
|
status: event.status,
|
|
1229
1696
|
artifacts: event.artifacts,
|
|
1697
|
+
_tokenUsage: tokenUsage,
|
|
1230
1698
|
}
|
|
1231
1699
|
}
|
|
1232
1700
|
|