@cap-js/agents 0.9.1 → 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 -1
- package/cds-plugin.js +2 -2
- package/lib/agents/markdown/backends/outputs-backend.js +16 -12
- package/lib/agents/markdown/backends/readonly-backend.js +48 -0
- package/lib/agents/markdown/backends/uploads-backend.js +30 -13
- package/lib/agents/markdown/deep-agent.js +15 -26
- 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 +4 -3
- package/lib/models/aicore.js +23 -6
- package/lib/preview/chat.html +394 -69
- package/lib/protocol/agent-card.js +6 -2
- package/lib/protocol/persistence/checkpoint-saver.js +4 -1
- package/lib/protocol/persistence/cleanup.js +84 -0
- package/lib/sidecar.js +1 -1
- package/lib/telemetry/chat-tracing.js +61 -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 +24 -15
- package/{index.cds → srv/entities.cds} +11 -0
- package/srv/handlers/chat.js +278 -0
- package/srv/handlers/graph-executor.js +48 -37
- package/srv/handlers/index.js +8 -0
- package/srv/handlers/mcp-tools.js +11 -47
- package/srv/handlers/sub-agent-tools.js +12 -3
- package/srv/handlers/tools.js +9 -5
- package/index.js +0 -0
- package/lib/telemetry/mlflow.js +0 -290
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
|
|
|
@@ -429,7 +460,29 @@
|
|
|
429
460
|
<script>
|
|
430
461
|
const AGENT_URL = window.location.pathname.replace(/\/preview\/?$/, "") + "/"
|
|
431
462
|
const AUTH_HEADER = document.querySelector('meta[name="a2a-auth"]')?.content || ""
|
|
432
|
-
|
|
463
|
+
|
|
464
|
+
const fetchCsrfToken = async () => {
|
|
465
|
+
if (document.csrfToken === undefined) {
|
|
466
|
+
const res = await fetch("/", {
|
|
467
|
+
method: "HEAD",
|
|
468
|
+
headers: { "x-csrf-token": "fetch", ...(await authHeaders({ includeCsrf: false })) },
|
|
469
|
+
})
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
throw new Error(`Could not fetch CSRF token ${res.status}: ${res.statusText}`)
|
|
472
|
+
}
|
|
473
|
+
document.csrfToken = res.headers.get("x-csrf-token")
|
|
474
|
+
}
|
|
475
|
+
return document.csrfToken
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const authHeaders = async ({ includeCsrf = true } = {}) => {
|
|
479
|
+
const headers = AUTH_HEADER ? { Authorization: AUTH_HEADER } : {}
|
|
480
|
+
if (!includeCsrf) return headers
|
|
481
|
+
const csrfToken = await fetchCsrfToken()
|
|
482
|
+
if (csrfToken) headers["x-csrf-token"] = csrfToken
|
|
483
|
+
return headers
|
|
484
|
+
}
|
|
485
|
+
|
|
433
486
|
document.getElementById("back").addEventListener("click", () => history.back())
|
|
434
487
|
const messages = document.getElementById("messages")
|
|
435
488
|
const typing = document.getElementById("typing")
|
|
@@ -437,16 +490,35 @@
|
|
|
437
490
|
const prompt = document.getElementById("prompt")
|
|
438
491
|
const sendBtn = document.getElementById("send")
|
|
439
492
|
const dot = document.getElementById("dot")
|
|
440
|
-
|
|
493
|
+
|
|
494
|
+
let useStreaming = false
|
|
495
|
+
const THINKING_STEP_BUFFER_MS = 100
|
|
496
|
+
const THINKING_SUMMARY_ANIMATION_MS = 160
|
|
441
497
|
|
|
442
498
|
let busy = false
|
|
443
499
|
let contextId = null
|
|
444
500
|
let pendingTaskId = null
|
|
445
501
|
let activeTaskId = null
|
|
446
502
|
let activeAbort = null
|
|
447
|
-
// Live streaming bubble: created on first token, updated incrementally
|
|
448
503
|
let streamingBubble = null
|
|
449
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(() => {})
|
|
450
522
|
|
|
451
523
|
const HISTORY_KEY = "chatHistory_" + AGENT_URL
|
|
452
524
|
let inputHistory = JSON.parse(localStorage.getItem(HISTORY_KEY) || "[]")
|
|
@@ -499,13 +571,243 @@
|
|
|
499
571
|
function addMessage(role, text) {
|
|
500
572
|
const el = document.createElement("div")
|
|
501
573
|
el.className = "msg " + role
|
|
502
|
-
if (role === "agent"
|
|
574
|
+
if (role === "agent" || role === "agent_thinking")
|
|
575
|
+
el.innerHTML = md(text, { sanitize: true })
|
|
503
576
|
else el.textContent = text
|
|
504
|
-
|
|
577
|
+
if (role === "agent_thinking") el.className = "msg agent_thinking"
|
|
578
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
505
579
|
messages.scrollTop = messages.scrollHeight
|
|
506
580
|
return el
|
|
507
581
|
}
|
|
508
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
|
+
|
|
509
811
|
function addApprovalPrompt(description) {
|
|
510
812
|
const el = document.createElement("div")
|
|
511
813
|
el.className = "msg approval"
|
|
@@ -532,7 +834,7 @@
|
|
|
532
834
|
actions.appendChild(approveBtn)
|
|
533
835
|
actions.appendChild(rejectBtn)
|
|
534
836
|
el.appendChild(actions)
|
|
535
|
-
messages.insertBefore(el, typing)
|
|
837
|
+
messages.insertBefore(el, typing.parentElement === messages ? typing : statusText)
|
|
536
838
|
messages.scrollTop = messages.scrollHeight
|
|
537
839
|
return el
|
|
538
840
|
}
|
|
@@ -544,6 +846,7 @@
|
|
|
544
846
|
function setBusy(val) {
|
|
545
847
|
busy = val
|
|
546
848
|
prompt.disabled = val
|
|
849
|
+
if (typing.parentElement !== messages) messages.insertBefore(typing, statusText)
|
|
547
850
|
typing.style.display = val ? "block" : "none"
|
|
548
851
|
dot.className = "status-dot" + (val ? " busy" : "")
|
|
549
852
|
if (val) {
|
|
@@ -557,9 +860,6 @@
|
|
|
557
860
|
activeTaskId = null
|
|
558
861
|
activeAbort = null
|
|
559
862
|
statusText.textContent = ""
|
|
560
|
-
// Reset streaming state on any transition to idle
|
|
561
|
-
streamingBubble = null
|
|
562
|
-
streamingText = ""
|
|
563
863
|
}
|
|
564
864
|
messages.scrollTop = messages.scrollHeight
|
|
565
865
|
}
|
|
@@ -573,17 +873,15 @@
|
|
|
573
873
|
|
|
574
874
|
const abort = new AbortController()
|
|
575
875
|
activeAbort = abort
|
|
576
|
-
streamingBubble = null
|
|
577
|
-
streamingText = ""
|
|
578
876
|
|
|
579
877
|
try {
|
|
580
878
|
const resp = await fetch(AGENT_URL, {
|
|
581
879
|
method: "POST",
|
|
582
|
-
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
880
|
+
headers: { "Content-Type": "application/json", ...(await authHeaders()) },
|
|
583
881
|
signal: abort.signal,
|
|
584
882
|
body: JSON.stringify({
|
|
585
883
|
jsonrpc: "2.0",
|
|
586
|
-
method:
|
|
884
|
+
method: useStreaming ? "message/stream" : "message/send",
|
|
587
885
|
id: crypto.randomUUID(),
|
|
588
886
|
params: {
|
|
589
887
|
message: {
|
|
@@ -597,7 +895,7 @@
|
|
|
597
895
|
}),
|
|
598
896
|
})
|
|
599
897
|
|
|
600
|
-
if (
|
|
898
|
+
if (useStreaming) {
|
|
601
899
|
await handleSSEResponse(resp)
|
|
602
900
|
} else {
|
|
603
901
|
const envelope = await resp.json()
|
|
@@ -626,12 +924,15 @@
|
|
|
626
924
|
pendingTaskId = taskId
|
|
627
925
|
const msgParts = result?.status?.message?.parts ?? []
|
|
628
926
|
const description = partsToText(msgParts)
|
|
927
|
+
flushPendingThinkingUpdate()
|
|
629
928
|
addApprovalPrompt(description)
|
|
630
929
|
return
|
|
631
930
|
}
|
|
632
931
|
|
|
633
|
-
// If streaming
|
|
634
|
-
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
|
|
635
936
|
streamingText = ""
|
|
636
937
|
return
|
|
637
938
|
}
|
|
@@ -639,6 +940,7 @@
|
|
|
639
940
|
// Clean up any open bubble (streaming was interrupted before lastChunk)
|
|
640
941
|
if (streamingBubble) {
|
|
641
942
|
if (streamingText) streamingBubble.innerHTML = md(streamingText)
|
|
943
|
+
finalizeThinking(streamingBubble, streamingText)
|
|
642
944
|
streamingBubble = null
|
|
643
945
|
streamingText = ""
|
|
644
946
|
return
|
|
@@ -647,7 +949,9 @@
|
|
|
647
949
|
const parts =
|
|
648
950
|
result?.parts ?? result?.status?.message?.parts ?? result?.artifacts?.[0]?.parts ?? []
|
|
649
951
|
const reply = partsToText(parts)
|
|
650
|
-
|
|
952
|
+
const replyEl =
|
|
953
|
+
promoteLatestThinkingToResponse(reply) || addMessage("agent", reply || "(no response)")
|
|
954
|
+
finalizeThinking(replyEl, reply)
|
|
651
955
|
}
|
|
652
956
|
|
|
653
957
|
async function send() {
|
|
@@ -672,12 +976,20 @@
|
|
|
672
976
|
activeAbort = abort
|
|
673
977
|
streamingBubble = null
|
|
674
978
|
streamingText = ""
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
677
989
|
try {
|
|
678
990
|
const resp = await fetch(AGENT_URL, {
|
|
679
991
|
method: "POST",
|
|
680
|
-
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
992
|
+
headers: { "Content-Type": "application/json", ...(await authHeaders()) },
|
|
681
993
|
signal: abort.signal,
|
|
682
994
|
body: JSON.stringify({
|
|
683
995
|
jsonrpc: "2.0",
|
|
@@ -782,44 +1094,57 @@
|
|
|
782
1094
|
return null
|
|
783
1095
|
}
|
|
784
1096
|
|
|
785
|
-
|
|
786
|
-
if (event.kind === "artifact-update" && streamToggle.checked) {
|
|
1097
|
+
if (event.kind === "artifact-update") {
|
|
787
1098
|
const parts = event.artifact?.parts ?? []
|
|
788
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
|
+
|
|
789
1109
|
// A2A spec: append/lastChunk are event-level fields (siblings of `artifact`).
|
|
790
1110
|
const append = event.append ?? false
|
|
791
1111
|
const lastChunk = event.lastChunk ?? false
|
|
792
1112
|
|
|
793
1113
|
if (lastChunk) {
|
|
794
|
-
//
|
|
1114
|
+
// Terminal: current bubble = final answer. Replace with authoritative text.
|
|
795
1115
|
if (text) {
|
|
796
1116
|
if (streamingBubble) {
|
|
797
1117
|
streamingBubble.innerHTML = md(text)
|
|
798
1118
|
messages.scrollTop = messages.scrollHeight
|
|
799
1119
|
} else {
|
|
800
1120
|
typing.style.display = "none"
|
|
801
|
-
addMessage("agent", text)
|
|
1121
|
+
streamingBubble = promoteLatestThinkingToResponse(text) || addMessage("agent", text)
|
|
802
1122
|
}
|
|
803
1123
|
}
|
|
1124
|
+
finalizeThinking(streamingBubble, text)
|
|
1125
|
+
thinkingFinalized = true
|
|
804
1126
|
streamingBubble = null
|
|
805
|
-
streamingText = text //
|
|
1127
|
+
streamingText = text // dedupe with handleResult
|
|
806
1128
|
return null
|
|
807
1129
|
}
|
|
808
1130
|
|
|
809
|
-
if (text)
|
|
810
|
-
if (!append || !streamingBubble) {
|
|
811
|
-
// First token — create a live bubble, hide typing indicator
|
|
812
|
-
typing.style.display = "none"
|
|
813
|
-
streamingBubble = addMessage("agent", text)
|
|
814
|
-
streamingText = text
|
|
815
|
-
} else {
|
|
816
|
-
// Subsequent token — accumulate and re-render markdown
|
|
817
|
-
streamingText += text
|
|
818
|
-
streamingBubble.innerHTML = md(streamingText)
|
|
819
|
-
messages.scrollTop = messages.scrollHeight
|
|
820
|
-
}
|
|
821
|
-
}
|
|
1131
|
+
if (!text) return null
|
|
822
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
|
|
823
1148
|
return null
|
|
824
1149
|
}
|
|
825
1150
|
|
|
@@ -856,7 +1181,7 @@
|
|
|
856
1181
|
if (taskId) {
|
|
857
1182
|
fetch(AGENT_URL, {
|
|
858
1183
|
method: "POST",
|
|
859
|
-
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
1184
|
+
headers: { "Content-Type": "application/json", ...(await authHeaders()) },
|
|
860
1185
|
body: JSON.stringify({
|
|
861
1186
|
jsonrpc: "2.0",
|
|
862
1187
|
method: "tasks/cancel",
|
|
@@ -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)
|
|
@@ -151,6 +151,7 @@ export class CdsCheckpointSaver extends BaseCheckpointSaver {
|
|
|
151
151
|
const thread_id = config.configurable?.thread_id
|
|
152
152
|
const checkpoint_ns = config.configurable?.checkpoint_ns ?? ""
|
|
153
153
|
const parent_checkpoint_id = config.configurable?.checkpoint_id
|
|
154
|
+
const task_id = config.configurable?._taskId || cds.context?.["agent.task.id"] || null
|
|
154
155
|
|
|
155
156
|
if (!thread_id) {
|
|
156
157
|
throw new Error('Missing required "thread_id" in config.configurable')
|
|
@@ -165,12 +166,14 @@ export class CdsCheckpointSaver extends BaseCheckpointSaver {
|
|
|
165
166
|
checkpoint_ns,
|
|
166
167
|
checkpoint_id: checkpoint.id,
|
|
167
168
|
parent_checkpoint_id,
|
|
169
|
+
task_id,
|
|
168
170
|
checkpoint: toUtf8String(serializedCheckpoint),
|
|
169
171
|
metadata: toUtf8String(serializedMetadata),
|
|
170
172
|
createdBy: resolveUserId(),
|
|
171
173
|
})
|
|
172
174
|
|
|
173
|
-
LOG._trace &&
|
|
175
|
+
LOG._trace &&
|
|
176
|
+
LOG.debug("Checkpoint saved", { thread_id, checkpoint_id: checkpoint.id, task_id })
|
|
174
177
|
|
|
175
178
|
return {
|
|
176
179
|
configurable: {
|