@cap-js/agents 0.9.2 → 0.9.4
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/hitl-decision-note-injector.js +18 -0
- package/lib/agents/middleware/hitl.js +2 -2
- package/lib/agents/middleware/index.js +11 -0
- package/lib/agents/middleware/quota-enforcer.js +9 -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/models/anthropic.js +27 -12
- package/lib/preview/chat.html +373 -66
- 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/message-handling.js +14 -0
- package/lib/utils/resilience.js +133 -0
- package/lib/utils/utils.js +17 -0
- package/package.json +26 -14
- package/srv/handlers/chat.js +278 -0
- package/srv/handlers/graph-executor/hitl.js +263 -0
- package/srv/handlers/graph-executor.js +100 -286
- package/srv/handlers/index.js +5 -1
- package/srv/handlers/mcp-tools.js +7 -45
- package/srv/handlers/sub-agent-tools.js +30 -12
- package/srv/handlers/tools.js +39 -5
- package/index.js +0 -0
- package/lib/agents/middleware/hitl-edit-note-injector.js +0 -18
- 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;
|
|
@@ -127,6 +115,7 @@
|
|
|
127
115
|
color: #1d3e5a;
|
|
128
116
|
border-bottom-left-radius: 3px;
|
|
129
117
|
border-left: 3px solid #537492;
|
|
118
|
+
white-space: pre-wrap;
|
|
130
119
|
}
|
|
131
120
|
.msg.approval::before {
|
|
132
121
|
content: "Action Required";
|
|
@@ -397,6 +386,58 @@
|
|
|
397
386
|
background: #0f2d45;
|
|
398
387
|
}
|
|
399
388
|
}
|
|
389
|
+
|
|
390
|
+
/* Thinking messages are streamed into an expanded <details> panel,
|
|
391
|
+
then collapsed once the final response arrives. */
|
|
392
|
+
.thinking-summary {
|
|
393
|
+
align-self: flex-start;
|
|
394
|
+
margin: 4px 0 8px 0;
|
|
395
|
+
font-size: 0.88em;
|
|
396
|
+
color: #666;
|
|
397
|
+
border-left: 2px solid #ccc;
|
|
398
|
+
padding-left: 10px;
|
|
399
|
+
overflow-wrap: anywhere;
|
|
400
|
+
}
|
|
401
|
+
.thinking-summary summary {
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
user-select: none;
|
|
404
|
+
color: #888;
|
|
405
|
+
list-style: none;
|
|
406
|
+
}
|
|
407
|
+
.thinking-summary summary::before {
|
|
408
|
+
content: "▶ ";
|
|
409
|
+
font-size: 0.8em;
|
|
410
|
+
}
|
|
411
|
+
.thinking-summary[open] summary::before {
|
|
412
|
+
content: "▼ ";
|
|
413
|
+
}
|
|
414
|
+
.thinking-step {
|
|
415
|
+
min-width: 0;
|
|
416
|
+
padding: 4px 0;
|
|
417
|
+
font-style: italic;
|
|
418
|
+
overflow-x: auto;
|
|
419
|
+
}
|
|
420
|
+
.thinking-step + .thinking-step {
|
|
421
|
+
border-top: 1px dashed #e0e0e0;
|
|
422
|
+
margin-top: 4px;
|
|
423
|
+
}
|
|
424
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
425
|
+
.thinking-summary {
|
|
426
|
+
transition: height 0.16s ease;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
@media (prefers-color-scheme: dark) {
|
|
430
|
+
.thinking-summary {
|
|
431
|
+
color: #999;
|
|
432
|
+
border-left-color: #444;
|
|
433
|
+
}
|
|
434
|
+
.thinking-summary summary {
|
|
435
|
+
color: #aaa;
|
|
436
|
+
}
|
|
437
|
+
.thinking-step + .thinking-step {
|
|
438
|
+
border-top-color: #333;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
400
441
|
</style>
|
|
401
442
|
<script src="marked.min.js"></script>
|
|
402
443
|
</head>
|
|
@@ -404,15 +445,6 @@
|
|
|
404
445
|
<header>
|
|
405
446
|
<a class="back" id="back" title="Back to index">←</a>
|
|
406
447
|
<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
448
|
<div class="status-dot" id="dot"></div>
|
|
417
449
|
</header>
|
|
418
450
|
|
|
@@ -459,16 +491,35 @@
|
|
|
459
491
|
const prompt = document.getElementById("prompt")
|
|
460
492
|
const sendBtn = document.getElementById("send")
|
|
461
493
|
const dot = document.getElementById("dot")
|
|
462
|
-
|
|
494
|
+
|
|
495
|
+
let useStreaming = false
|
|
496
|
+
const THINKING_STEP_BUFFER_MS = 100
|
|
497
|
+
const THINKING_SUMMARY_ANIMATION_MS = 160
|
|
463
498
|
|
|
464
499
|
let busy = false
|
|
465
500
|
let contextId = null
|
|
466
501
|
let pendingTaskId = null
|
|
467
502
|
let activeTaskId = null
|
|
468
503
|
let activeAbort = null
|
|
469
|
-
// Live streaming bubble: created on first token, updated incrementally
|
|
470
504
|
let streamingBubble = null
|
|
471
505
|
let streamingText = ""
|
|
506
|
+
let thinkingFinalized = false
|
|
507
|
+
let thinkingStepEls = [] // thinking steps inside the summary panel
|
|
508
|
+
let thinkingTexts = [] // accumulated text per thinking step
|
|
509
|
+
let thinkingStepCount = 0
|
|
510
|
+
let thinkingSummaryEl = null // <details> containing thinking steps
|
|
511
|
+
let currentThinkingArtifactId = null // artifactId of open thinking step
|
|
512
|
+
let currentThinkingClosed = false // true once lastChunk seen for current id
|
|
513
|
+
let pendingThinkingUpdate = null
|
|
514
|
+
let pendingThinkingTimer = null
|
|
515
|
+
|
|
516
|
+
// Auto-enable streaming when agent card advertises streaming capability
|
|
517
|
+
fetch(AGENT_URL + ".well-known/agent-card.json", { headers: authHeaders() })
|
|
518
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
519
|
+
.then((card) => {
|
|
520
|
+
if (card?.capabilities?.streaming) useStreaming = true
|
|
521
|
+
})
|
|
522
|
+
.catch(() => {})
|
|
472
523
|
|
|
473
524
|
const HISTORY_KEY = "chatHistory_" + AGENT_URL
|
|
474
525
|
let inputHistory = JSON.parse(localStorage.getItem(HISTORY_KEY) || "[]")
|
|
@@ -521,17 +572,249 @@
|
|
|
521
572
|
function addMessage(role, text) {
|
|
522
573
|
const el = document.createElement("div")
|
|
523
574
|
el.className = "msg " + role
|
|
524
|
-
if (role === "agent"
|
|
575
|
+
if (role === "agent" || role === "agent_thinking")
|
|
576
|
+
el.innerHTML = md(text, { sanitize: true })
|
|
525
577
|
else el.textContent = text
|
|
526
|
-
|
|
578
|
+
if (role === "agent_thinking") el.className = "msg agent_thinking"
|
|
579
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
527
580
|
messages.scrollTop = messages.scrollHeight
|
|
528
581
|
return el
|
|
529
582
|
}
|
|
530
583
|
|
|
584
|
+
function updateThinkingSummaryLabel() {
|
|
585
|
+
if (!thinkingSummaryEl) return
|
|
586
|
+
const summary = thinkingSummaryEl.querySelector("summary")
|
|
587
|
+
if (!summary) return
|
|
588
|
+
const count = thinkingStepCount
|
|
589
|
+
summary.textContent = `Thinking (${count} step${count === 1 ? "" : "s"})`
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function ensureThinkingSummary() {
|
|
593
|
+
if (!thinkingSummaryEl) {
|
|
594
|
+
thinkingSummaryEl = document.createElement("details")
|
|
595
|
+
thinkingSummaryEl.className = "thinking-summary"
|
|
596
|
+
thinkingSummaryEl.open = true
|
|
597
|
+
const summary = document.createElement("summary")
|
|
598
|
+
summary.addEventListener("click", toggleThinkingSummary)
|
|
599
|
+
thinkingSummaryEl.appendChild(summary)
|
|
600
|
+
updateThinkingSummaryLabel()
|
|
601
|
+
messages.insertBefore(
|
|
602
|
+
thinkingSummaryEl,
|
|
603
|
+
typing.parentElement === messages ? typing : statusText,
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
cancelThinkingSummaryAnimation(thinkingSummaryEl)
|
|
607
|
+
thinkingSummaryEl.open = true
|
|
608
|
+
return thinkingSummaryEl
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function clearPendingThinkingUpdate() {
|
|
612
|
+
if (pendingThinkingTimer) clearTimeout(pendingThinkingTimer)
|
|
613
|
+
pendingThinkingTimer = null
|
|
614
|
+
pendingThinkingUpdate = null
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function flushPendingThinkingUpdate() {
|
|
618
|
+
const pending = pendingThinkingUpdate
|
|
619
|
+
clearPendingThinkingUpdate()
|
|
620
|
+
if (pending) renderThinkingStep(pending)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function prefersReducedMotion() {
|
|
624
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function resetThinkingSummaryAnimation(el) {
|
|
628
|
+
el.style.height = ""
|
|
629
|
+
el.style.overflow = ""
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function cancelThinkingSummaryAnimation(el) {
|
|
633
|
+
if (!el) return
|
|
634
|
+
el._thinkingAnimationId = (el._thinkingAnimationId ?? 0) + 1
|
|
635
|
+
resetThinkingSummaryAnimation(el)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function toggleThinkingSummary(event) {
|
|
639
|
+
if (prefersReducedMotion()) return
|
|
640
|
+
event.preventDefault()
|
|
641
|
+
const el = event.currentTarget.parentElement
|
|
642
|
+
if (el.open) collapseThinkingSummary(el)
|
|
643
|
+
else expandThinkingSummary(el)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function renderThinkingStep(update) {
|
|
647
|
+
const { artifactId, text, lastChunk, append } = update
|
|
648
|
+
const panel = ensureThinkingSummary()
|
|
649
|
+
const idx = thinkingStepEls.findIndex((step) => step._artifactId === artifactId)
|
|
650
|
+
if (append && idx >= 0) {
|
|
651
|
+
thinkingTexts[idx] += text
|
|
652
|
+
thinkingStepEls[idx].innerHTML = md(thinkingTexts[idx])
|
|
653
|
+
} else if (artifactId === currentThinkingArtifactId && !currentThinkingClosed) {
|
|
654
|
+
thinkingTexts[idx] += "\n" + text
|
|
655
|
+
thinkingStepEls[idx].innerHTML = md(thinkingTexts[idx])
|
|
656
|
+
} else {
|
|
657
|
+
const step = document.createElement("div")
|
|
658
|
+
step.className = "thinking-step"
|
|
659
|
+
step._artifactId = artifactId
|
|
660
|
+
step.innerHTML = md(text)
|
|
661
|
+
panel.appendChild(step)
|
|
662
|
+
thinkingStepEls.push(step)
|
|
663
|
+
thinkingTexts.push(text)
|
|
664
|
+
thinkingStepCount = thinkingStepEls.length
|
|
665
|
+
updateThinkingSummaryLabel()
|
|
666
|
+
currentThinkingArtifactId = artifactId
|
|
667
|
+
currentThinkingClosed = false
|
|
668
|
+
}
|
|
669
|
+
if (lastChunk) currentThinkingClosed = true
|
|
670
|
+
messages.scrollTop = messages.scrollHeight
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function addThinkingStep(artifactId, text, lastChunk, append) {
|
|
674
|
+
if (
|
|
675
|
+
pendingThinkingUpdate &&
|
|
676
|
+
pendingThinkingUpdate.artifactId === artifactId &&
|
|
677
|
+
!pendingThinkingUpdate.lastChunk
|
|
678
|
+
) {
|
|
679
|
+
pendingThinkingUpdate.text += text
|
|
680
|
+
pendingThinkingUpdate.lastChunk = lastChunk
|
|
681
|
+
} else {
|
|
682
|
+
flushPendingThinkingUpdate()
|
|
683
|
+
pendingThinkingUpdate = { artifactId, text, lastChunk, append }
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (pendingThinkingTimer) clearTimeout(pendingThinkingTimer)
|
|
687
|
+
pendingThinkingTimer = setTimeout(flushPendingThinkingUpdate, THINKING_STEP_BUFFER_MS)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function latestThinkingMatches(responseText) {
|
|
691
|
+
return (
|
|
692
|
+
responseText &&
|
|
693
|
+
thinkingTexts.length > 0 &&
|
|
694
|
+
thinkingTexts[thinkingTexts.length - 1].trim() === responseText.trim()
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function promoteLatestThinkingToResponse(responseText) {
|
|
699
|
+
if (!thinkingSummaryEl || !latestThinkingMatches(responseText)) return null
|
|
700
|
+
|
|
701
|
+
thinkingTexts.pop()
|
|
702
|
+
const responseEl = thinkingStepEls.pop()
|
|
703
|
+
thinkingStepCount = thinkingStepEls.length
|
|
704
|
+
|
|
705
|
+
responseEl.className = "msg agent"
|
|
706
|
+
responseEl._promotedFromThinking = true
|
|
707
|
+
responseEl.innerHTML = md(responseText)
|
|
708
|
+
messages.insertBefore(responseEl, typing.parentElement === messages ? typing : statusText)
|
|
709
|
+
|
|
710
|
+
if (thinkingStepEls.length === 0) {
|
|
711
|
+
thinkingSummaryEl.remove()
|
|
712
|
+
thinkingSummaryEl = null
|
|
713
|
+
} else {
|
|
714
|
+
updateThinkingSummaryLabel()
|
|
715
|
+
collapseThinkingSummary(thinkingSummaryEl)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
messages.scrollTop = messages.scrollHeight
|
|
719
|
+
return responseEl
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function finalizeThinking(beforeEl, responseText) {
|
|
723
|
+
clearPendingThinkingUpdate()
|
|
724
|
+
if (beforeEl?._promotedFromThinking) return
|
|
725
|
+
if (thinkingStepEls.length === 0 || !thinkingSummaryEl) return
|
|
726
|
+
// Stream labels every model turn as thinking until terminal response is
|
|
727
|
+
// known. Its final turn is authoritative response, never a thinking step.
|
|
728
|
+
thinkingTexts.pop()
|
|
729
|
+
const finalResponse = thinkingStepEls.pop()
|
|
730
|
+
finalResponse?.remove()
|
|
731
|
+
thinkingStepCount = thinkingStepEls.length
|
|
732
|
+
updateThinkingSummaryLabel()
|
|
733
|
+
|
|
734
|
+
if (thinkingStepEls.length === 0) {
|
|
735
|
+
thinkingSummaryEl.remove()
|
|
736
|
+
thinkingSummaryEl = null
|
|
737
|
+
return
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (beforeEl && thinkingSummaryEl.nextSibling !== beforeEl) {
|
|
741
|
+
messages.insertBefore(thinkingSummaryEl, beforeEl)
|
|
742
|
+
}
|
|
743
|
+
collapseThinkingSummary(thinkingSummaryEl)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function collapseThinkingSummary(el) {
|
|
747
|
+
if (!el || !el.open) return
|
|
748
|
+
|
|
749
|
+
if (prefersReducedMotion()) {
|
|
750
|
+
el.open = false
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const summary = el.querySelector("summary")
|
|
755
|
+
const startHeight = `${el.offsetHeight}px`
|
|
756
|
+
const endHeight = `${summary?.offsetHeight ?? 0}px`
|
|
757
|
+
const animationId = (el._thinkingAnimationId ?? 0) + 1
|
|
758
|
+
el._thinkingAnimationId = animationId
|
|
759
|
+
let finished = false
|
|
760
|
+
|
|
761
|
+
const finish = () => {
|
|
762
|
+
el.removeEventListener("transitionend", finish)
|
|
763
|
+
if (el._thinkingAnimationId !== animationId) return
|
|
764
|
+
if (finished) return
|
|
765
|
+
finished = true
|
|
766
|
+
el.open = false
|
|
767
|
+
resetThinkingSummaryAnimation(el)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
resetThinkingSummaryAnimation(el)
|
|
771
|
+
el.style.overflow = "hidden"
|
|
772
|
+
el.style.height = startHeight
|
|
773
|
+
requestAnimationFrame(() => {
|
|
774
|
+
el.style.height = endHeight
|
|
775
|
+
})
|
|
776
|
+
el.addEventListener("transitionend", finish)
|
|
777
|
+
setTimeout(finish, THINKING_SUMMARY_ANIMATION_MS + 80)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function expandThinkingSummary(el) {
|
|
781
|
+
if (!el || el.open) return
|
|
782
|
+
|
|
783
|
+
if (prefersReducedMotion()) {
|
|
784
|
+
el.open = true
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
el.open = true
|
|
789
|
+
const summary = el.querySelector("summary")
|
|
790
|
+
const startHeight = `${summary?.offsetHeight ?? 0}px`
|
|
791
|
+
const endHeight = `${el.scrollHeight}px`
|
|
792
|
+
const animationId = (el._thinkingAnimationId ?? 0) + 1
|
|
793
|
+
el._thinkingAnimationId = animationId
|
|
794
|
+
let finished = false
|
|
795
|
+
|
|
796
|
+
const finish = () => {
|
|
797
|
+
el.removeEventListener("transitionend", finish)
|
|
798
|
+
if (el._thinkingAnimationId !== animationId) return
|
|
799
|
+
if (finished) return
|
|
800
|
+
finished = true
|
|
801
|
+
resetThinkingSummaryAnimation(el)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
resetThinkingSummaryAnimation(el)
|
|
805
|
+
el.style.overflow = "hidden"
|
|
806
|
+
el.style.height = startHeight
|
|
807
|
+
requestAnimationFrame(() => {
|
|
808
|
+
el.style.height = endHeight
|
|
809
|
+
})
|
|
810
|
+
el.addEventListener("transitionend", finish)
|
|
811
|
+
setTimeout(finish, THINKING_SUMMARY_ANIMATION_MS + 80)
|
|
812
|
+
}
|
|
813
|
+
|
|
531
814
|
function addApprovalPrompt(description) {
|
|
532
815
|
const el = document.createElement("div")
|
|
533
816
|
el.className = "msg approval"
|
|
534
|
-
el.
|
|
817
|
+
el.textContent = description || "The agent requires your approval to continue."
|
|
535
818
|
const actions = document.createElement("div")
|
|
536
819
|
actions.className = "approval-actions"
|
|
537
820
|
|
|
@@ -554,7 +837,7 @@
|
|
|
554
837
|
actions.appendChild(approveBtn)
|
|
555
838
|
actions.appendChild(rejectBtn)
|
|
556
839
|
el.appendChild(actions)
|
|
557
|
-
messages.insertBefore(el, typing)
|
|
840
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
558
841
|
messages.scrollTop = messages.scrollHeight
|
|
559
842
|
return el
|
|
560
843
|
}
|
|
@@ -566,6 +849,7 @@
|
|
|
566
849
|
function setBusy(val) {
|
|
567
850
|
busy = val
|
|
568
851
|
prompt.disabled = val
|
|
852
|
+
if (typing.parentElement !== messages) messages.insertBefore(typing, statusText)
|
|
569
853
|
typing.style.display = val ? "block" : "none"
|
|
570
854
|
dot.className = "status-dot" + (val ? " busy" : "")
|
|
571
855
|
if (val) {
|
|
@@ -579,9 +863,6 @@
|
|
|
579
863
|
activeTaskId = null
|
|
580
864
|
activeAbort = null
|
|
581
865
|
statusText.textContent = ""
|
|
582
|
-
// Reset streaming state on any transition to idle
|
|
583
|
-
streamingBubble = null
|
|
584
|
-
streamingText = ""
|
|
585
866
|
}
|
|
586
867
|
messages.scrollTop = messages.scrollHeight
|
|
587
868
|
}
|
|
@@ -595,8 +876,6 @@
|
|
|
595
876
|
|
|
596
877
|
const abort = new AbortController()
|
|
597
878
|
activeAbort = abort
|
|
598
|
-
streamingBubble = null
|
|
599
|
-
streamingText = ""
|
|
600
879
|
|
|
601
880
|
try {
|
|
602
881
|
const resp = await fetch(AGENT_URL, {
|
|
@@ -605,7 +884,7 @@
|
|
|
605
884
|
signal: abort.signal,
|
|
606
885
|
body: JSON.stringify({
|
|
607
886
|
jsonrpc: "2.0",
|
|
608
|
-
method:
|
|
887
|
+
method: useStreaming ? "message/stream" : "message/send",
|
|
609
888
|
id: crypto.randomUUID(),
|
|
610
889
|
params: {
|
|
611
890
|
message: {
|
|
@@ -619,7 +898,7 @@
|
|
|
619
898
|
}),
|
|
620
899
|
})
|
|
621
900
|
|
|
622
|
-
if (
|
|
901
|
+
if (useStreaming) {
|
|
623
902
|
await handleSSEResponse(resp)
|
|
624
903
|
} else {
|
|
625
904
|
const envelope = await resp.json()
|
|
@@ -648,12 +927,15 @@
|
|
|
648
927
|
pendingTaskId = taskId
|
|
649
928
|
const msgParts = result?.status?.message?.parts ?? []
|
|
650
929
|
const description = partsToText(msgParts)
|
|
930
|
+
flushPendingThinkingUpdate()
|
|
651
931
|
addApprovalPrompt(description)
|
|
652
932
|
return
|
|
653
933
|
}
|
|
654
934
|
|
|
655
|
-
// If streaming
|
|
656
|
-
if (
|
|
935
|
+
// If streaming already rendered the final text via lastChunk, skip.
|
|
936
|
+
if (useStreaming && streamingText !== "") {
|
|
937
|
+
if (!thinkingFinalized) finalizeThinking(streamingBubble, streamingText)
|
|
938
|
+
streamingBubble = null
|
|
657
939
|
streamingText = ""
|
|
658
940
|
return
|
|
659
941
|
}
|
|
@@ -661,6 +943,7 @@
|
|
|
661
943
|
// Clean up any open bubble (streaming was interrupted before lastChunk)
|
|
662
944
|
if (streamingBubble) {
|
|
663
945
|
if (streamingText) streamingBubble.innerHTML = md(streamingText)
|
|
946
|
+
finalizeThinking(streamingBubble, streamingText)
|
|
664
947
|
streamingBubble = null
|
|
665
948
|
streamingText = ""
|
|
666
949
|
return
|
|
@@ -669,7 +952,9 @@
|
|
|
669
952
|
const parts =
|
|
670
953
|
result?.parts ?? result?.status?.message?.parts ?? result?.artifacts?.[0]?.parts ?? []
|
|
671
954
|
const reply = partsToText(parts)
|
|
672
|
-
|
|
955
|
+
const replyEl =
|
|
956
|
+
promoteLatestThinkingToResponse(reply) || addMessage("agent", reply || "(no response)")
|
|
957
|
+
finalizeThinking(replyEl, reply)
|
|
673
958
|
}
|
|
674
959
|
|
|
675
960
|
async function send() {
|
|
@@ -694,8 +979,16 @@
|
|
|
694
979
|
activeAbort = abort
|
|
695
980
|
streamingBubble = null
|
|
696
981
|
streamingText = ""
|
|
697
|
-
|
|
698
|
-
|
|
982
|
+
thinkingFinalized = false
|
|
983
|
+
clearPendingThinkingUpdate()
|
|
984
|
+
thinkingStepEls = []
|
|
985
|
+
thinkingTexts = []
|
|
986
|
+
thinkingStepCount = 0
|
|
987
|
+
thinkingSummaryEl = null
|
|
988
|
+
currentThinkingArtifactId = null
|
|
989
|
+
currentThinkingClosed = false
|
|
990
|
+
|
|
991
|
+
const useStream = useStreaming
|
|
699
992
|
try {
|
|
700
993
|
const resp = await fetch(AGENT_URL, {
|
|
701
994
|
method: "POST",
|
|
@@ -804,44 +1097,58 @@
|
|
|
804
1097
|
return null
|
|
805
1098
|
}
|
|
806
1099
|
|
|
807
|
-
|
|
808
|
-
if (event.kind === "artifact-update" && streamToggle.checked) {
|
|
1100
|
+
if (event.kind === "artifact-update") {
|
|
809
1101
|
const parts = event.artifact?.parts ?? []
|
|
810
1102
|
const text = partsToText(parts)
|
|
1103
|
+
|
|
1104
|
+
if (event.artifact?.artifactId?.startsWith("thinking")) {
|
|
1105
|
+
const lastChunk = event.lastChunk ?? false
|
|
1106
|
+
if (text)
|
|
1107
|
+
addThinkingStep(event.artifact.artifactId, text, lastChunk, event.append ?? false)
|
|
1108
|
+
return null
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (event.artifact?.artifactId !== "response") return null
|
|
1112
|
+
|
|
811
1113
|
// A2A spec: append/lastChunk are event-level fields (siblings of `artifact`).
|
|
812
1114
|
const append = event.append ?? false
|
|
813
1115
|
const lastChunk = event.lastChunk ?? false
|
|
814
1116
|
|
|
815
1117
|
if (lastChunk) {
|
|
816
|
-
//
|
|
1118
|
+
// Terminal: current bubble = final answer. Replace with authoritative text.
|
|
817
1119
|
if (text) {
|
|
818
1120
|
if (streamingBubble) {
|
|
819
1121
|
streamingBubble.innerHTML = md(text)
|
|
820
1122
|
messages.scrollTop = messages.scrollHeight
|
|
821
1123
|
} else {
|
|
822
1124
|
typing.style.display = "none"
|
|
823
|
-
addMessage("agent", text)
|
|
1125
|
+
streamingBubble = promoteLatestThinkingToResponse(text) || addMessage("agent", text)
|
|
824
1126
|
}
|
|
825
1127
|
}
|
|
1128
|
+
finalizeThinking(streamingBubble, text)
|
|
1129
|
+
thinkingFinalized = true
|
|
826
1130
|
streamingBubble = null
|
|
827
|
-
streamingText = text //
|
|
1131
|
+
streamingText = text // dedupe with handleResult
|
|
828
1132
|
return null
|
|
829
1133
|
}
|
|
830
1134
|
|
|
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
|
-
}
|
|
1135
|
+
if (!text) return null
|
|
844
1136
|
|
|
1137
|
+
if (!append) {
|
|
1138
|
+
typing.style.display = "none"
|
|
1139
|
+
streamingBubble = addMessage("agent", text)
|
|
1140
|
+
streamingText = text
|
|
1141
|
+
} else if (streamingBubble) {
|
|
1142
|
+
// Subsequent token — accumulate into the current turn's bubble.
|
|
1143
|
+
streamingText += text
|
|
1144
|
+
streamingBubble.innerHTML = md(streamingText)
|
|
1145
|
+
} else {
|
|
1146
|
+
// Defensive: append:true with no bubble yet (unexpected). Open one.
|
|
1147
|
+
typing.style.display = "none"
|
|
1148
|
+
streamingBubble = addMessage("agent", text)
|
|
1149
|
+
streamingText = text
|
|
1150
|
+
}
|
|
1151
|
+
messages.scrollTop = messages.scrollHeight
|
|
845
1152
|
return null
|
|
846
1153
|
}
|
|
847
1154
|
|
|
@@ -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")
|