@cap-js/agents 0.9.2 → 0.9.3
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/README.md +2 -0
- package/cds-plugin.js +2 -2
- package/lib/agents/middleware/content-filter.js +3 -2
- package/lib/agents/middleware/index.js +11 -0
- package/lib/agents/middleware/remote-mcp.js +62 -0
- package/lib/agents/middleware/tool-wrap.js +52 -0
- package/lib/compile.js +6 -2
- package/lib/eval/Judge.js +239 -0
- package/lib/eval/eval-describe.js +69 -0
- package/lib/eval/eval-run.js +147 -0
- package/lib/eval/index.js +6 -0
- package/lib/eval/metrics.js +53 -0
- package/lib/eval/span-collector.js +50 -0
- package/lib/index.js +2 -1
- package/lib/models/aicore.js +23 -6
- package/lib/preview/chat.html +368 -65
- package/lib/protocol/agent-card.js +6 -2
- package/lib/sidecar.js +1 -1
- package/lib/telemetry/chat-tracing.js +39 -8
- package/lib/telemetry/mlflow/credentials.js +79 -0
- package/lib/telemetry/mlflow/evaluation.js +38 -0
- package/lib/telemetry/mlflow/exporter/DatabricksExporter.js +75 -0
- package/lib/telemetry/mlflow/exporter/MlflowExporter.js +115 -0
- package/lib/telemetry/mlflow/exporter/index.js +23 -0
- package/lib/telemetry/mlflow/index.js +16 -0
- package/lib/telemetry/mlflow/prompts.js +123 -0
- package/lib/telemetry/mlflow/tracing.js +264 -0
- package/lib/telemetry/tool-tracing.js +27 -54
- package/lib/telemetry/tracing.js +3 -1
- package/lib/utils/markdown.js +1 -11
- package/lib/utils/resilience.js +133 -0
- package/lib/utils/utils.js +17 -0
- package/package.json +23 -14
- package/srv/handlers/chat.js +278 -0
- package/srv/handlers/graph-executor.js +43 -37
- package/srv/handlers/index.js +3 -0
- package/srv/handlers/mcp-tools.js +7 -45
- package/srv/handlers/sub-agent-tools.js +8 -1
- package/srv/handlers/tools.js +9 -5
- package/index.js +0 -0
- package/lib/telemetry/mlflow.js +0 -290
- /package/{index.cds → srv/entities.cds} +0 -0
package/lib/preview/chat.html
CHANGED
|
@@ -41,26 +41,6 @@
|
|
|
41
41
|
header .back:hover {
|
|
42
42
|
color: #fff;
|
|
43
43
|
}
|
|
44
|
-
.header-toggle {
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
gap: 6px;
|
|
48
|
-
font-size: 0.78rem;
|
|
49
|
-
color: #c8def0;
|
|
50
|
-
cursor: pointer;
|
|
51
|
-
user-select: none;
|
|
52
|
-
flex-shrink: 0;
|
|
53
|
-
}
|
|
54
|
-
.header-toggle[hidden] {
|
|
55
|
-
display: none;
|
|
56
|
-
}
|
|
57
|
-
.header-toggle input[type="checkbox"] {
|
|
58
|
-
accent-color: #7fbce8;
|
|
59
|
-
width: 14px;
|
|
60
|
-
height: 14px;
|
|
61
|
-
cursor: pointer;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
44
|
header .status-dot {
|
|
65
45
|
width: 8px;
|
|
66
46
|
height: 8px;
|
|
@@ -93,13 +73,21 @@
|
|
|
93
73
|
gap: 14px;
|
|
94
74
|
}
|
|
95
75
|
|
|
96
|
-
.msg
|
|
76
|
+
.msg,
|
|
77
|
+
.thinking-summary {
|
|
97
78
|
max-width: 75%;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.msg {
|
|
98
83
|
padding: 10px 14px;
|
|
99
84
|
border-radius: 12px;
|
|
100
85
|
line-height: 1.5;
|
|
101
86
|
font-size: 90%;
|
|
102
87
|
word-break: break-word;
|
|
88
|
+
transition:
|
|
89
|
+
background 0.15s,
|
|
90
|
+
color 0.15s;
|
|
103
91
|
}
|
|
104
92
|
.msg.user {
|
|
105
93
|
align-self: flex-end;
|
|
@@ -397,6 +385,58 @@
|
|
|
397
385
|
background: #0f2d45;
|
|
398
386
|
}
|
|
399
387
|
}
|
|
388
|
+
|
|
389
|
+
/* Thinking messages are streamed into an expanded <details> panel,
|
|
390
|
+
then collapsed once the final response arrives. */
|
|
391
|
+
.thinking-summary {
|
|
392
|
+
align-self: flex-start;
|
|
393
|
+
margin: 4px 0 8px 0;
|
|
394
|
+
font-size: 0.88em;
|
|
395
|
+
color: #666;
|
|
396
|
+
border-left: 2px solid #ccc;
|
|
397
|
+
padding-left: 10px;
|
|
398
|
+
overflow-wrap: anywhere;
|
|
399
|
+
}
|
|
400
|
+
.thinking-summary summary {
|
|
401
|
+
cursor: pointer;
|
|
402
|
+
user-select: none;
|
|
403
|
+
color: #888;
|
|
404
|
+
list-style: none;
|
|
405
|
+
}
|
|
406
|
+
.thinking-summary summary::before {
|
|
407
|
+
content: "▶ ";
|
|
408
|
+
font-size: 0.8em;
|
|
409
|
+
}
|
|
410
|
+
.thinking-summary[open] summary::before {
|
|
411
|
+
content: "▼ ";
|
|
412
|
+
}
|
|
413
|
+
.thinking-step {
|
|
414
|
+
min-width: 0;
|
|
415
|
+
padding: 4px 0;
|
|
416
|
+
font-style: italic;
|
|
417
|
+
overflow-x: auto;
|
|
418
|
+
}
|
|
419
|
+
.thinking-step + .thinking-step {
|
|
420
|
+
border-top: 1px dashed #e0e0e0;
|
|
421
|
+
margin-top: 4px;
|
|
422
|
+
}
|
|
423
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
424
|
+
.thinking-summary {
|
|
425
|
+
transition: height 0.16s ease;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
@media (prefers-color-scheme: dark) {
|
|
429
|
+
.thinking-summary {
|
|
430
|
+
color: #999;
|
|
431
|
+
border-left-color: #444;
|
|
432
|
+
}
|
|
433
|
+
.thinking-summary summary {
|
|
434
|
+
color: #aaa;
|
|
435
|
+
}
|
|
436
|
+
.thinking-step + .thinking-step {
|
|
437
|
+
border-top-color: #333;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
400
440
|
</style>
|
|
401
441
|
<script src="marked.min.js"></script>
|
|
402
442
|
</head>
|
|
@@ -404,15 +444,6 @@
|
|
|
404
444
|
<header>
|
|
405
445
|
<a class="back" id="back" title="Back to index">←</a>
|
|
406
446
|
<h1>{{agentName}}</h1>
|
|
407
|
-
<label
|
|
408
|
-
class="header-toggle"
|
|
409
|
-
title="Stream tokens as they arrive"
|
|
410
|
-
style="margin-left: auto"
|
|
411
|
-
hidden
|
|
412
|
-
>
|
|
413
|
-
<input type="checkbox" id="streamToggle" hidden />
|
|
414
|
-
Streaming
|
|
415
|
-
</label>
|
|
416
447
|
<div class="status-dot" id="dot"></div>
|
|
417
448
|
</header>
|
|
418
449
|
|
|
@@ -459,16 +490,35 @@
|
|
|
459
490
|
const prompt = document.getElementById("prompt")
|
|
460
491
|
const sendBtn = document.getElementById("send")
|
|
461
492
|
const dot = document.getElementById("dot")
|
|
462
|
-
|
|
493
|
+
|
|
494
|
+
let useStreaming = false
|
|
495
|
+
const THINKING_STEP_BUFFER_MS = 100
|
|
496
|
+
const THINKING_SUMMARY_ANIMATION_MS = 160
|
|
463
497
|
|
|
464
498
|
let busy = false
|
|
465
499
|
let contextId = null
|
|
466
500
|
let pendingTaskId = null
|
|
467
501
|
let activeTaskId = null
|
|
468
502
|
let activeAbort = null
|
|
469
|
-
// Live streaming bubble: created on first token, updated incrementally
|
|
470
503
|
let streamingBubble = null
|
|
471
504
|
let streamingText = ""
|
|
505
|
+
let thinkingFinalized = false
|
|
506
|
+
let thinkingStepEls = [] // thinking steps inside the summary panel
|
|
507
|
+
let thinkingTexts = [] // accumulated text per thinking step
|
|
508
|
+
let thinkingStepCount = 0
|
|
509
|
+
let thinkingSummaryEl = null // <details> containing thinking steps
|
|
510
|
+
let currentThinkingArtifactId = null // artifactId of open thinking step
|
|
511
|
+
let currentThinkingClosed = false // true once lastChunk seen for current id
|
|
512
|
+
let pendingThinkingUpdate = null
|
|
513
|
+
let pendingThinkingTimer = null
|
|
514
|
+
|
|
515
|
+
// Auto-enable streaming when agent card advertises streaming capability
|
|
516
|
+
fetch(AGENT_URL + ".well-known/agent-card.json", { headers: authHeaders() })
|
|
517
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
518
|
+
.then((card) => {
|
|
519
|
+
if (card?.capabilities?.streaming) useStreaming = true
|
|
520
|
+
})
|
|
521
|
+
.catch(() => {})
|
|
472
522
|
|
|
473
523
|
const HISTORY_KEY = "chatHistory_" + AGENT_URL
|
|
474
524
|
let inputHistory = JSON.parse(localStorage.getItem(HISTORY_KEY) || "[]")
|
|
@@ -521,13 +571,243 @@
|
|
|
521
571
|
function addMessage(role, text) {
|
|
522
572
|
const el = document.createElement("div")
|
|
523
573
|
el.className = "msg " + role
|
|
524
|
-
if (role === "agent"
|
|
574
|
+
if (role === "agent" || role === "agent_thinking")
|
|
575
|
+
el.innerHTML = md(text, { sanitize: true })
|
|
525
576
|
else el.textContent = text
|
|
526
|
-
|
|
577
|
+
if (role === "agent_thinking") el.className = "msg agent_thinking"
|
|
578
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
527
579
|
messages.scrollTop = messages.scrollHeight
|
|
528
580
|
return el
|
|
529
581
|
}
|
|
530
582
|
|
|
583
|
+
function updateThinkingSummaryLabel() {
|
|
584
|
+
if (!thinkingSummaryEl) return
|
|
585
|
+
const summary = thinkingSummaryEl.querySelector("summary")
|
|
586
|
+
if (!summary) return
|
|
587
|
+
const count = thinkingStepCount
|
|
588
|
+
summary.textContent = `Thinking (${count} step${count === 1 ? "" : "s"})`
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function ensureThinkingSummary() {
|
|
592
|
+
if (!thinkingSummaryEl) {
|
|
593
|
+
thinkingSummaryEl = document.createElement("details")
|
|
594
|
+
thinkingSummaryEl.className = "thinking-summary"
|
|
595
|
+
thinkingSummaryEl.open = true
|
|
596
|
+
const summary = document.createElement("summary")
|
|
597
|
+
summary.addEventListener("click", toggleThinkingSummary)
|
|
598
|
+
thinkingSummaryEl.appendChild(summary)
|
|
599
|
+
updateThinkingSummaryLabel()
|
|
600
|
+
messages.insertBefore(
|
|
601
|
+
thinkingSummaryEl,
|
|
602
|
+
typing.parentElement === messages ? typing : statusText,
|
|
603
|
+
)
|
|
604
|
+
}
|
|
605
|
+
cancelThinkingSummaryAnimation(thinkingSummaryEl)
|
|
606
|
+
thinkingSummaryEl.open = true
|
|
607
|
+
return thinkingSummaryEl
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function clearPendingThinkingUpdate() {
|
|
611
|
+
if (pendingThinkingTimer) clearTimeout(pendingThinkingTimer)
|
|
612
|
+
pendingThinkingTimer = null
|
|
613
|
+
pendingThinkingUpdate = null
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function flushPendingThinkingUpdate() {
|
|
617
|
+
const pending = pendingThinkingUpdate
|
|
618
|
+
clearPendingThinkingUpdate()
|
|
619
|
+
if (pending) renderThinkingStep(pending.artifactId, pending.text, pending.lastChunk)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function prefersReducedMotion() {
|
|
623
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function resetThinkingSummaryAnimation(el) {
|
|
627
|
+
el.style.height = ""
|
|
628
|
+
el.style.overflow = ""
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function cancelThinkingSummaryAnimation(el) {
|
|
632
|
+
if (!el) return
|
|
633
|
+
el._thinkingAnimationId = (el._thinkingAnimationId ?? 0) + 1
|
|
634
|
+
resetThinkingSummaryAnimation(el)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function toggleThinkingSummary(event) {
|
|
638
|
+
if (prefersReducedMotion()) return
|
|
639
|
+
event.preventDefault()
|
|
640
|
+
const el = event.currentTarget.parentElement
|
|
641
|
+
if (el.open) collapseThinkingSummary(el)
|
|
642
|
+
else expandThinkingSummary(el)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function renderThinkingStep(artifactId, text, lastChunk) {
|
|
646
|
+
const panel = ensureThinkingSummary()
|
|
647
|
+
const isNewStep = artifactId !== currentThinkingArtifactId || currentThinkingClosed
|
|
648
|
+
if (isNewStep) {
|
|
649
|
+
const step = document.createElement("div")
|
|
650
|
+
step.className = "thinking-step"
|
|
651
|
+
step._artifactId = artifactId
|
|
652
|
+
step.innerHTML = md(text)
|
|
653
|
+
panel.appendChild(step)
|
|
654
|
+
thinkingStepEls.push(step)
|
|
655
|
+
thinkingTexts.push(text)
|
|
656
|
+
thinkingStepCount = thinkingStepEls.length
|
|
657
|
+
updateThinkingSummaryLabel()
|
|
658
|
+
currentThinkingArtifactId = artifactId
|
|
659
|
+
currentThinkingClosed = false
|
|
660
|
+
} else {
|
|
661
|
+
// Append to current open step for this artifactId
|
|
662
|
+
const idx = thinkingStepEls.length - 1
|
|
663
|
+
thinkingTexts[idx] += text
|
|
664
|
+
thinkingStepEls[idx].innerHTML = md(thinkingTexts[idx])
|
|
665
|
+
}
|
|
666
|
+
if (lastChunk) currentThinkingClosed = true
|
|
667
|
+
messages.scrollTop = messages.scrollHeight
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function addThinkingStep(artifactId, text, lastChunk) {
|
|
671
|
+
if (
|
|
672
|
+
pendingThinkingUpdate &&
|
|
673
|
+
pendingThinkingUpdate.artifactId === artifactId &&
|
|
674
|
+
!pendingThinkingUpdate.lastChunk
|
|
675
|
+
) {
|
|
676
|
+
pendingThinkingUpdate.text += text
|
|
677
|
+
pendingThinkingUpdate.lastChunk = lastChunk
|
|
678
|
+
} else {
|
|
679
|
+
flushPendingThinkingUpdate()
|
|
680
|
+
pendingThinkingUpdate = { artifactId, text, lastChunk }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (pendingThinkingTimer) clearTimeout(pendingThinkingTimer)
|
|
684
|
+
pendingThinkingTimer = setTimeout(flushPendingThinkingUpdate, THINKING_STEP_BUFFER_MS)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function latestThinkingMatches(responseText) {
|
|
688
|
+
return (
|
|
689
|
+
responseText &&
|
|
690
|
+
thinkingTexts.length > 0 &&
|
|
691
|
+
thinkingTexts[thinkingTexts.length - 1].trim() === responseText.trim()
|
|
692
|
+
)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function promoteLatestThinkingToResponse(responseText) {
|
|
696
|
+
if (!thinkingSummaryEl || !latestThinkingMatches(responseText)) return null
|
|
697
|
+
|
|
698
|
+
thinkingTexts.pop()
|
|
699
|
+
const responseEl = thinkingStepEls.pop()
|
|
700
|
+
thinkingStepCount = thinkingStepEls.length
|
|
701
|
+
|
|
702
|
+
responseEl.className = "msg agent"
|
|
703
|
+
responseEl._promotedFromThinking = true
|
|
704
|
+
responseEl.innerHTML = md(responseText)
|
|
705
|
+
messages.insertBefore(responseEl, typing.parentElement === messages ? typing : statusText)
|
|
706
|
+
|
|
707
|
+
if (thinkingStepEls.length === 0) {
|
|
708
|
+
thinkingSummaryEl.remove()
|
|
709
|
+
thinkingSummaryEl = null
|
|
710
|
+
} else {
|
|
711
|
+
updateThinkingSummaryLabel()
|
|
712
|
+
collapseThinkingSummary(thinkingSummaryEl)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
messages.scrollTop = messages.scrollHeight
|
|
716
|
+
return responseEl
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function finalizeThinking(beforeEl, responseText) {
|
|
720
|
+
clearPendingThinkingUpdate()
|
|
721
|
+
if (beforeEl?._promotedFromThinking) return
|
|
722
|
+
if (thinkingStepEls.length === 0 || !thinkingSummaryEl) return
|
|
723
|
+
// Stream labels every model turn as thinking until terminal response is
|
|
724
|
+
// known. Its final turn is authoritative response, never a thinking step.
|
|
725
|
+
thinkingTexts.pop()
|
|
726
|
+
const finalResponse = thinkingStepEls.pop()
|
|
727
|
+
finalResponse?.remove()
|
|
728
|
+
thinkingStepCount = thinkingStepEls.length
|
|
729
|
+
updateThinkingSummaryLabel()
|
|
730
|
+
|
|
731
|
+
if (thinkingStepEls.length === 0) {
|
|
732
|
+
thinkingSummaryEl.remove()
|
|
733
|
+
thinkingSummaryEl = null
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (beforeEl && thinkingSummaryEl.nextSibling !== beforeEl) {
|
|
738
|
+
messages.insertBefore(thinkingSummaryEl, beforeEl)
|
|
739
|
+
}
|
|
740
|
+
collapseThinkingSummary(thinkingSummaryEl)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function collapseThinkingSummary(el) {
|
|
744
|
+
if (!el || !el.open) return
|
|
745
|
+
|
|
746
|
+
if (prefersReducedMotion()) {
|
|
747
|
+
el.open = false
|
|
748
|
+
return
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const summary = el.querySelector("summary")
|
|
752
|
+
const startHeight = `${el.offsetHeight}px`
|
|
753
|
+
const endHeight = `${summary?.offsetHeight ?? 0}px`
|
|
754
|
+
const animationId = (el._thinkingAnimationId ?? 0) + 1
|
|
755
|
+
el._thinkingAnimationId = animationId
|
|
756
|
+
let finished = false
|
|
757
|
+
|
|
758
|
+
const finish = () => {
|
|
759
|
+
el.removeEventListener("transitionend", finish)
|
|
760
|
+
if (el._thinkingAnimationId !== animationId) return
|
|
761
|
+
if (finished) return
|
|
762
|
+
finished = true
|
|
763
|
+
el.open = false
|
|
764
|
+
resetThinkingSummaryAnimation(el)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
resetThinkingSummaryAnimation(el)
|
|
768
|
+
el.style.overflow = "hidden"
|
|
769
|
+
el.style.height = startHeight
|
|
770
|
+
requestAnimationFrame(() => {
|
|
771
|
+
el.style.height = endHeight
|
|
772
|
+
})
|
|
773
|
+
el.addEventListener("transitionend", finish)
|
|
774
|
+
setTimeout(finish, THINKING_SUMMARY_ANIMATION_MS + 80)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function expandThinkingSummary(el) {
|
|
778
|
+
if (!el || el.open) return
|
|
779
|
+
|
|
780
|
+
if (prefersReducedMotion()) {
|
|
781
|
+
el.open = true
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
el.open = true
|
|
786
|
+
const summary = el.querySelector("summary")
|
|
787
|
+
const startHeight = `${summary?.offsetHeight ?? 0}px`
|
|
788
|
+
const endHeight = `${el.scrollHeight}px`
|
|
789
|
+
const animationId = (el._thinkingAnimationId ?? 0) + 1
|
|
790
|
+
el._thinkingAnimationId = animationId
|
|
791
|
+
let finished = false
|
|
792
|
+
|
|
793
|
+
const finish = () => {
|
|
794
|
+
el.removeEventListener("transitionend", finish)
|
|
795
|
+
if (el._thinkingAnimationId !== animationId) return
|
|
796
|
+
if (finished) return
|
|
797
|
+
finished = true
|
|
798
|
+
resetThinkingSummaryAnimation(el)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
resetThinkingSummaryAnimation(el)
|
|
802
|
+
el.style.overflow = "hidden"
|
|
803
|
+
el.style.height = startHeight
|
|
804
|
+
requestAnimationFrame(() => {
|
|
805
|
+
el.style.height = endHeight
|
|
806
|
+
})
|
|
807
|
+
el.addEventListener("transitionend", finish)
|
|
808
|
+
setTimeout(finish, THINKING_SUMMARY_ANIMATION_MS + 80)
|
|
809
|
+
}
|
|
810
|
+
|
|
531
811
|
function addApprovalPrompt(description) {
|
|
532
812
|
const el = document.createElement("div")
|
|
533
813
|
el.className = "msg approval"
|
|
@@ -554,7 +834,7 @@
|
|
|
554
834
|
actions.appendChild(approveBtn)
|
|
555
835
|
actions.appendChild(rejectBtn)
|
|
556
836
|
el.appendChild(actions)
|
|
557
|
-
messages.insertBefore(el, typing)
|
|
837
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
558
838
|
messages.scrollTop = messages.scrollHeight
|
|
559
839
|
return el
|
|
560
840
|
}
|
|
@@ -566,6 +846,7 @@
|
|
|
566
846
|
function setBusy(val) {
|
|
567
847
|
busy = val
|
|
568
848
|
prompt.disabled = val
|
|
849
|
+
if (typing.parentElement !== messages) messages.insertBefore(typing, statusText)
|
|
569
850
|
typing.style.display = val ? "block" : "none"
|
|
570
851
|
dot.className = "status-dot" + (val ? " busy" : "")
|
|
571
852
|
if (val) {
|
|
@@ -579,9 +860,6 @@
|
|
|
579
860
|
activeTaskId = null
|
|
580
861
|
activeAbort = null
|
|
581
862
|
statusText.textContent = ""
|
|
582
|
-
// Reset streaming state on any transition to idle
|
|
583
|
-
streamingBubble = null
|
|
584
|
-
streamingText = ""
|
|
585
863
|
}
|
|
586
864
|
messages.scrollTop = messages.scrollHeight
|
|
587
865
|
}
|
|
@@ -595,8 +873,6 @@
|
|
|
595
873
|
|
|
596
874
|
const abort = new AbortController()
|
|
597
875
|
activeAbort = abort
|
|
598
|
-
streamingBubble = null
|
|
599
|
-
streamingText = ""
|
|
600
876
|
|
|
601
877
|
try {
|
|
602
878
|
const resp = await fetch(AGENT_URL, {
|
|
@@ -605,7 +881,7 @@
|
|
|
605
881
|
signal: abort.signal,
|
|
606
882
|
body: JSON.stringify({
|
|
607
883
|
jsonrpc: "2.0",
|
|
608
|
-
method:
|
|
884
|
+
method: useStreaming ? "message/stream" : "message/send",
|
|
609
885
|
id: crypto.randomUUID(),
|
|
610
886
|
params: {
|
|
611
887
|
message: {
|
|
@@ -619,7 +895,7 @@
|
|
|
619
895
|
}),
|
|
620
896
|
})
|
|
621
897
|
|
|
622
|
-
if (
|
|
898
|
+
if (useStreaming) {
|
|
623
899
|
await handleSSEResponse(resp)
|
|
624
900
|
} else {
|
|
625
901
|
const envelope = await resp.json()
|
|
@@ -648,12 +924,15 @@
|
|
|
648
924
|
pendingTaskId = taskId
|
|
649
925
|
const msgParts = result?.status?.message?.parts ?? []
|
|
650
926
|
const description = partsToText(msgParts)
|
|
927
|
+
flushPendingThinkingUpdate()
|
|
651
928
|
addApprovalPrompt(description)
|
|
652
929
|
return
|
|
653
930
|
}
|
|
654
931
|
|
|
655
|
-
// If streaming
|
|
656
|
-
if (
|
|
932
|
+
// If streaming already rendered the final text via lastChunk, skip.
|
|
933
|
+
if (useStreaming && streamingText !== "") {
|
|
934
|
+
if (!thinkingFinalized) finalizeThinking(streamingBubble, streamingText)
|
|
935
|
+
streamingBubble = null
|
|
657
936
|
streamingText = ""
|
|
658
937
|
return
|
|
659
938
|
}
|
|
@@ -661,6 +940,7 @@
|
|
|
661
940
|
// Clean up any open bubble (streaming was interrupted before lastChunk)
|
|
662
941
|
if (streamingBubble) {
|
|
663
942
|
if (streamingText) streamingBubble.innerHTML = md(streamingText)
|
|
943
|
+
finalizeThinking(streamingBubble, streamingText)
|
|
664
944
|
streamingBubble = null
|
|
665
945
|
streamingText = ""
|
|
666
946
|
return
|
|
@@ -669,7 +949,9 @@
|
|
|
669
949
|
const parts =
|
|
670
950
|
result?.parts ?? result?.status?.message?.parts ?? result?.artifacts?.[0]?.parts ?? []
|
|
671
951
|
const reply = partsToText(parts)
|
|
672
|
-
|
|
952
|
+
const replyEl =
|
|
953
|
+
promoteLatestThinkingToResponse(reply) || addMessage("agent", reply || "(no response)")
|
|
954
|
+
finalizeThinking(replyEl, reply)
|
|
673
955
|
}
|
|
674
956
|
|
|
675
957
|
async function send() {
|
|
@@ -694,8 +976,16 @@
|
|
|
694
976
|
activeAbort = abort
|
|
695
977
|
streamingBubble = null
|
|
696
978
|
streamingText = ""
|
|
697
|
-
|
|
698
|
-
|
|
979
|
+
thinkingFinalized = false
|
|
980
|
+
clearPendingThinkingUpdate()
|
|
981
|
+
thinkingStepEls = []
|
|
982
|
+
thinkingTexts = []
|
|
983
|
+
thinkingStepCount = 0
|
|
984
|
+
thinkingSummaryEl = null
|
|
985
|
+
currentThinkingArtifactId = null
|
|
986
|
+
currentThinkingClosed = false
|
|
987
|
+
|
|
988
|
+
const useStream = useStreaming
|
|
699
989
|
try {
|
|
700
990
|
const resp = await fetch(AGENT_URL, {
|
|
701
991
|
method: "POST",
|
|
@@ -804,44 +1094,57 @@
|
|
|
804
1094
|
return null
|
|
805
1095
|
}
|
|
806
1096
|
|
|
807
|
-
|
|
808
|
-
if (event.kind === "artifact-update" && streamToggle.checked) {
|
|
1097
|
+
if (event.kind === "artifact-update") {
|
|
809
1098
|
const parts = event.artifact?.parts ?? []
|
|
810
1099
|
const text = partsToText(parts)
|
|
1100
|
+
|
|
1101
|
+
if (event.artifact?.artifactId?.startsWith("thinking")) {
|
|
1102
|
+
const lastChunk = event.lastChunk ?? false
|
|
1103
|
+
if (text) addThinkingStep(event.artifact.artifactId, text, lastChunk)
|
|
1104
|
+
return null
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (event.artifact?.artifactId !== "response") return null
|
|
1108
|
+
|
|
811
1109
|
// A2A spec: append/lastChunk are event-level fields (siblings of `artifact`).
|
|
812
1110
|
const append = event.append ?? false
|
|
813
1111
|
const lastChunk = event.lastChunk ?? false
|
|
814
1112
|
|
|
815
1113
|
if (lastChunk) {
|
|
816
|
-
//
|
|
1114
|
+
// Terminal: current bubble = final answer. Replace with authoritative text.
|
|
817
1115
|
if (text) {
|
|
818
1116
|
if (streamingBubble) {
|
|
819
1117
|
streamingBubble.innerHTML = md(text)
|
|
820
1118
|
messages.scrollTop = messages.scrollHeight
|
|
821
1119
|
} else {
|
|
822
1120
|
typing.style.display = "none"
|
|
823
|
-
addMessage("agent", text)
|
|
1121
|
+
streamingBubble = promoteLatestThinkingToResponse(text) || addMessage("agent", text)
|
|
824
1122
|
}
|
|
825
1123
|
}
|
|
1124
|
+
finalizeThinking(streamingBubble, text)
|
|
1125
|
+
thinkingFinalized = true
|
|
826
1126
|
streamingBubble = null
|
|
827
|
-
streamingText = text //
|
|
1127
|
+
streamingText = text // dedupe with handleResult
|
|
828
1128
|
return null
|
|
829
1129
|
}
|
|
830
1130
|
|
|
831
|
-
if (text)
|
|
832
|
-
if (!append || !streamingBubble) {
|
|
833
|
-
// First token — create a live bubble, hide typing indicator
|
|
834
|
-
typing.style.display = "none"
|
|
835
|
-
streamingBubble = addMessage("agent", text)
|
|
836
|
-
streamingText = text
|
|
837
|
-
} else {
|
|
838
|
-
// Subsequent token — accumulate and re-render markdown
|
|
839
|
-
streamingText += text
|
|
840
|
-
streamingBubble.innerHTML = md(streamingText)
|
|
841
|
-
messages.scrollTop = messages.scrollHeight
|
|
842
|
-
}
|
|
843
|
-
}
|
|
1131
|
+
if (!text) return null
|
|
844
1132
|
|
|
1133
|
+
if (!append) {
|
|
1134
|
+
typing.style.display = "none"
|
|
1135
|
+
streamingBubble = addMessage("agent", text)
|
|
1136
|
+
streamingText = text
|
|
1137
|
+
} else if (streamingBubble) {
|
|
1138
|
+
// Subsequent token — accumulate into the current turn's bubble.
|
|
1139
|
+
streamingText += text
|
|
1140
|
+
streamingBubble.innerHTML = md(streamingText)
|
|
1141
|
+
} else {
|
|
1142
|
+
// Defensive: append:true with no bubble yet (unexpected). Open one.
|
|
1143
|
+
typing.style.display = "none"
|
|
1144
|
+
streamingBubble = addMessage("agent", text)
|
|
1145
|
+
streamingText = text
|
|
1146
|
+
}
|
|
1147
|
+
messages.scrollTop = messages.scrollHeight
|
|
845
1148
|
return null
|
|
846
1149
|
}
|
|
847
1150
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import cds from "@sap/cds"
|
|
2
2
|
import { createRequire } from "node:module"
|
|
3
3
|
const { path } = cds.utils
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getDescription,
|
|
6
|
+
getFilteredEntities,
|
|
7
|
+
getFilteredActions,
|
|
8
|
+
slugified,
|
|
9
|
+
} from "../utils/utils.js"
|
|
5
10
|
import {
|
|
6
11
|
scanSkills,
|
|
7
12
|
parseAgentMetadata,
|
|
8
13
|
parseAgentCardMd,
|
|
9
14
|
parseAgentCardFile,
|
|
10
|
-
slugified,
|
|
11
15
|
} from "../utils/markdown.js"
|
|
12
16
|
|
|
13
17
|
const require = createRequire(import.meta.url)
|
package/lib/sidecar.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import cds from "@sap/cds"
|
|
2
2
|
import A2AProtocolAdapter, { authContext } from "./index.js"
|
|
3
|
-
import { slugified } from "./utils/
|
|
3
|
+
import { slugified } from "./utils/utils.js"
|
|
4
4
|
import registerDefaultAgentHandlers from "../srv/handlers/index.js"
|
|
5
5
|
|
|
6
6
|
const LOG = cds.log("a2a")
|