@blade-hq/agent-kit 1.0.27 → 1.0.28
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/dist/{chunk-X2NJKKXR.js → chunk-BGNKHPCD.js} +213 -40
- package/dist/chunk-BGNKHPCD.js.map +1 -0
- package/dist/{chunk-M72OL7ZJ.js → chunk-HCQDDDOX.js} +2 -2
- package/dist/{chunk-TE6G6NVX.js → chunk-OGHUIKCY.js} +3 -3
- package/dist/{chunk-C2FKVGJU.js → chunk-PYYDXV2A.js} +2 -2
- package/dist/{chunk-JU5SL4HP.js → chunk-TVWC3PED.js} +2 -2
- package/dist/{chunk-SBASG3M2.js → chunk-UWNQ4PXK.js} +4 -1101
- package/dist/chunk-UWNQ4PXK.js.map +1 -0
- package/dist/{chunk-34GDDOWY.js → chunk-YJ6BX3FU.js} +1379 -31
- package/dist/chunk-YJ6BX3FU.js.map +1 -0
- package/dist/client/blade-client.d.ts +2 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +3 -1
- package/dist/client/resources/app-dev-templates.d.ts +93 -0
- package/dist/client/resources/models.d.ts +3 -0
- package/dist/{react → client/shared}/projection/builder.d.ts +1 -1
- package/dist/{react → client/shared}/projection/helpers.d.ts +1 -1
- package/dist/client/shared/projection/history.d.ts +12 -0
- package/dist/{react → client/shared}/projection/index.d.ts +1 -0
- package/dist/{react → client/shared}/projection/state.d.ts +1 -1
- package/dist/client/types/index.d.ts +2 -2
- package/dist/client/types/rest.d.ts +1010 -29
- package/dist/client/types/socket-events.d.ts +18 -132
- package/dist/react/api/app-dev-templates.d.ts +5 -0
- package/dist/react/api/model-config.d.ts +68 -0
- package/dist/react/api/models.d.ts +2 -0
- package/dist/react/api/published-apps.js +3 -3
- package/dist/react/api/sessions.js +2 -2
- package/dist/react/components/chat/ChatView.d.ts +3 -1
- package/dist/react/components/chat/MessageList.d.ts +3 -1
- package/dist/react/components/chat/index.js +5 -5
- package/dist/react/components/chat/scroll-anchor.d.ts +8 -0
- package/dist/react/components/plan/index.js +4 -4
- package/dist/react/components/session/index.js +3 -3
- package/dist/react/components/workspace/index.js +3 -3
- package/dist/react/hooks/use-model-preferences.d.ts +1 -0
- package/dist/react/hooks/use-resolved-model.d.ts +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +87 -7
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-34GDDOWY.js.map +0 -1
- package/dist/chunk-SBASG3M2.js.map +0 -1
- package/dist/chunk-X2NJKKXR.js.map +0 -1
- /package/dist/{chunk-M72OL7ZJ.js.map → chunk-HCQDDDOX.js.map} +0 -0
- /package/dist/{chunk-TE6G6NVX.js.map → chunk-OGHUIKCY.js.map} +0 -0
- /package/dist/{chunk-C2FKVGJU.js.map → chunk-PYYDXV2A.js.map} +0 -0
- /package/dist/{chunk-JU5SL4HP.js.map → chunk-TVWC3PED.js.map} +0 -0
|
@@ -71,6 +71,30 @@ async function readErrorDetail(response) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// src/client/resources/app-dev-templates.ts
|
|
75
|
+
var AppDevTemplatesResource = class {
|
|
76
|
+
constructor(client) {
|
|
77
|
+
this.client = client;
|
|
78
|
+
}
|
|
79
|
+
client;
|
|
80
|
+
list(params) {
|
|
81
|
+
const query = new URLSearchParams();
|
|
82
|
+
if (typeof params?.offset === "number") query.set("offset", String(params.offset));
|
|
83
|
+
if (typeof params?.limit === "number") query.set("limit", String(params.limit));
|
|
84
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
85
|
+
return this.client.json("GET", `/api/app-dev/templates${suffix}`);
|
|
86
|
+
}
|
|
87
|
+
get(slug) {
|
|
88
|
+
return this.client.json("GET", `/api/app-dev/templates/${encodeURIComponent(slug)}`);
|
|
89
|
+
}
|
|
90
|
+
getMediaUrl(slug, path) {
|
|
91
|
+
const encodedPath = path.split("/").map((part) => encodeURIComponent(part)).join("/");
|
|
92
|
+
return this.client.buildAuthedUrl(
|
|
93
|
+
`/api/app-dev/templates/${encodeURIComponent(slug)}/media/${encodedPath}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
74
98
|
// src/client/resources/auth.ts
|
|
75
99
|
var AuthResource = class {
|
|
76
100
|
constructor(client) {
|
|
@@ -382,10 +406,12 @@ var ModelOption = type({
|
|
|
382
406
|
id: "string",
|
|
383
407
|
label: "string",
|
|
384
408
|
"inputModalities?": "string[]",
|
|
385
|
-
"supportsImage?": "boolean"
|
|
409
|
+
"supportsImage?": "boolean",
|
|
410
|
+
"thinkingAvailable?": "boolean"
|
|
386
411
|
});
|
|
387
412
|
var ModelsConfig = type({
|
|
388
413
|
default: "string",
|
|
414
|
+
"thinkingAvailable?": "boolean",
|
|
389
415
|
models: ModelOption.array()
|
|
390
416
|
});
|
|
391
417
|
var ModelsResource = class {
|
|
@@ -506,6 +532,1326 @@ var RegistryResource = class {
|
|
|
506
532
|
}
|
|
507
533
|
};
|
|
508
534
|
|
|
535
|
+
// src/client/shared/projection/helpers.ts
|
|
536
|
+
function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
537
|
+
if (!toolCallId) return;
|
|
538
|
+
let found = false;
|
|
539
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
540
|
+
if (state.toolCalls[i].id !== toolCallId) continue;
|
|
541
|
+
const tc = state.toolCalls[i];
|
|
542
|
+
state.toolCalls[i] = {
|
|
543
|
+
...tc,
|
|
544
|
+
tool_name: toolName || tc.tool_name,
|
|
545
|
+
display_name: displayName || tc.display_name,
|
|
546
|
+
arguments: args || tc.arguments
|
|
547
|
+
};
|
|
548
|
+
found = true;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
if (!found) {
|
|
552
|
+
state.toolCalls.push({
|
|
553
|
+
id: toolCallId,
|
|
554
|
+
tool_name: toolName,
|
|
555
|
+
display_name: displayName,
|
|
556
|
+
arguments: args,
|
|
557
|
+
status: "pending"
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
let blockFound = false;
|
|
561
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
562
|
+
const b = state.blocks[i];
|
|
563
|
+
if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
|
|
564
|
+
state.blocks[i] = {
|
|
565
|
+
...b,
|
|
566
|
+
content: args || b.content,
|
|
567
|
+
tool_name: toolName || b.tool_name,
|
|
568
|
+
display_name: displayName || b.display_name
|
|
569
|
+
};
|
|
570
|
+
blockFound = true;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
if (!blockFound) {
|
|
574
|
+
state.blocks.push({
|
|
575
|
+
type: "tool_use",
|
|
576
|
+
content: args,
|
|
577
|
+
tool_call_id: toolCallId,
|
|
578
|
+
tool_name: toolName || null,
|
|
579
|
+
display_name: displayName || null
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function applyToolResult(state, toolCallId, result, status, durationMs, sourceLoop) {
|
|
584
|
+
let toolName = "";
|
|
585
|
+
let displayName = "";
|
|
586
|
+
let finalResult = result;
|
|
587
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
588
|
+
const tc = state.toolCalls[i];
|
|
589
|
+
if (tc.id !== toolCallId) continue;
|
|
590
|
+
if (typeof result === "string") {
|
|
591
|
+
finalResult = maybeEnrichAskUserResult(tc.tool_name, result, sourceLoop);
|
|
592
|
+
}
|
|
593
|
+
const update = { status, result: finalResult };
|
|
594
|
+
if (durationMs != null) update.duration_ms = durationMs;
|
|
595
|
+
state.toolCalls[i] = { ...tc, ...update };
|
|
596
|
+
toolName = tc.tool_name;
|
|
597
|
+
displayName = tc.display_name;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
upsertToolResultBlock(state, toolCallId, finalResult, toolName, displayName);
|
|
601
|
+
}
|
|
602
|
+
function appendToolResultDelta(state, toolCallId, delta) {
|
|
603
|
+
if (!toolCallId || !delta) return;
|
|
604
|
+
let toolName = "";
|
|
605
|
+
let displayName = "";
|
|
606
|
+
let currentResult = "";
|
|
607
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
608
|
+
const tc = state.toolCalls[i];
|
|
609
|
+
if (tc.id !== toolCallId) continue;
|
|
610
|
+
toolName = tc.tool_name;
|
|
611
|
+
displayName = tc.display_name;
|
|
612
|
+
if (typeof tc.result === "string") currentResult = tc.result;
|
|
613
|
+
state.toolCalls[i] = { ...tc, result: currentResult + delta };
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
for (const block of state.blocks) {
|
|
617
|
+
if (block.type !== "tool_result" || block.tool_call_id !== toolCallId) continue;
|
|
618
|
+
if (typeof block.content === "string") currentResult = block.content;
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
upsertToolResultBlock(state, toolCallId, currentResult + delta, toolName, displayName);
|
|
622
|
+
}
|
|
623
|
+
function upsertToolResultBlock(state, toolCallId, content, toolName, displayName) {
|
|
624
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
625
|
+
const block = state.blocks[i];
|
|
626
|
+
if (block.type !== "tool_result" || block.tool_call_id !== toolCallId) continue;
|
|
627
|
+
state.blocks[i] = {
|
|
628
|
+
...block,
|
|
629
|
+
content,
|
|
630
|
+
tool_name: toolName || block.tool_name,
|
|
631
|
+
display_name: displayName || block.display_name
|
|
632
|
+
};
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
state.blocks.push({
|
|
636
|
+
type: "tool_result",
|
|
637
|
+
content,
|
|
638
|
+
tool_call_id: toolCallId,
|
|
639
|
+
tool_name: toolName || null,
|
|
640
|
+
display_name: displayName || null
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
function appendTextBlock(state, blockType, content) {
|
|
644
|
+
if (content == null || content === "") return;
|
|
645
|
+
const text = String(content);
|
|
646
|
+
const lastBlock = state.blocks[state.blocks.length - 1];
|
|
647
|
+
if (lastBlock && lastBlock.type === blockType && typeof lastBlock.content === "string") {
|
|
648
|
+
state.blocks[state.blocks.length - 1] = {
|
|
649
|
+
...lastBlock,
|
|
650
|
+
content: lastBlock.content + text
|
|
651
|
+
};
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
state.blocks.push({ type: blockType, content: text });
|
|
655
|
+
}
|
|
656
|
+
function appendToolCallArguments(state, toolCallId, delta) {
|
|
657
|
+
let newArgs = "";
|
|
658
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
659
|
+
const tc = state.toolCalls[i];
|
|
660
|
+
if (tc.id === toolCallId) {
|
|
661
|
+
newArgs = tc.arguments + delta;
|
|
662
|
+
state.toolCalls[i] = { ...tc, arguments: newArgs };
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (!newArgs) return;
|
|
667
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
668
|
+
const b = state.blocks[i];
|
|
669
|
+
if (b.type === "tool_use" && b.tool_call_id === toolCallId) {
|
|
670
|
+
state.blocks[i] = { ...b, content: newArgs };
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function maybeEnrichAskUserResult(toolName, result, sourceLoop) {
|
|
676
|
+
if (!sourceLoop) return result;
|
|
677
|
+
const normalized = toolName.includes(":") ? toolName.split(":").pop() : toolName;
|
|
678
|
+
if (normalized !== "AskUserQuestion") return result;
|
|
679
|
+
try {
|
|
680
|
+
const payload = JSON.parse(result);
|
|
681
|
+
if (typeof payload !== "object" || payload === null) return result;
|
|
682
|
+
payload.source_loop = sourceLoop;
|
|
683
|
+
return JSON.stringify(payload);
|
|
684
|
+
} catch {
|
|
685
|
+
return result;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function buildSystemNotificationTurn(sequence, turnId, loopId, notificationType, status, title, detail, metadata) {
|
|
689
|
+
return {
|
|
690
|
+
id: turnId,
|
|
691
|
+
sequence,
|
|
692
|
+
turn_id: turnId,
|
|
693
|
+
loop_id: loopId,
|
|
694
|
+
kind: "message",
|
|
695
|
+
role: "system",
|
|
696
|
+
status: "completed",
|
|
697
|
+
blocks: [
|
|
698
|
+
{
|
|
699
|
+
type: "system_notification",
|
|
700
|
+
content: {
|
|
701
|
+
notification_type: notificationType,
|
|
702
|
+
status,
|
|
703
|
+
title,
|
|
704
|
+
detail,
|
|
705
|
+
metadata
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
],
|
|
709
|
+
tool_calls: [],
|
|
710
|
+
model: null,
|
|
711
|
+
usage: null,
|
|
712
|
+
duration_ms: 0
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function buildSystemNotificationBlock(notificationType, status, title, detail, metadata) {
|
|
716
|
+
return {
|
|
717
|
+
type: "system_notification",
|
|
718
|
+
content: {
|
|
719
|
+
notification_type: notificationType,
|
|
720
|
+
status,
|
|
721
|
+
title,
|
|
722
|
+
detail,
|
|
723
|
+
metadata
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function buildMarkerTurn(sequence, turnId, loopId, blockType, content) {
|
|
728
|
+
return {
|
|
729
|
+
id: turnId,
|
|
730
|
+
sequence,
|
|
731
|
+
turn_id: turnId,
|
|
732
|
+
loop_id: loopId,
|
|
733
|
+
kind: "message",
|
|
734
|
+
role: "assistant",
|
|
735
|
+
status: "completed",
|
|
736
|
+
blocks: [{ type: blockType, content }],
|
|
737
|
+
tool_calls: [],
|
|
738
|
+
model: null,
|
|
739
|
+
usage: null,
|
|
740
|
+
duration_ms: 0
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function buildCompactionTurn(sequence, compactionId, loopId, status, data) {
|
|
744
|
+
const tokensBefore = asInt(data.tokens_before);
|
|
745
|
+
const tokensAfter = asInt(data.tokens_after);
|
|
746
|
+
let savedRatio = null;
|
|
747
|
+
if (tokensBefore && tokensBefore > 0 && tokensAfter != null) {
|
|
748
|
+
savedRatio = 1 - tokensAfter / tokensBefore;
|
|
749
|
+
} else {
|
|
750
|
+
savedRatio = asFloat(data.saved_ratio);
|
|
751
|
+
}
|
|
752
|
+
let archivedCount = asInt(data.archived_count);
|
|
753
|
+
if (archivedCount == null && Array.isArray(data.archived_files)) {
|
|
754
|
+
archivedCount = data.archived_files.length;
|
|
755
|
+
}
|
|
756
|
+
const content = data.content;
|
|
757
|
+
const summaryFull = compactionSummaryFull(content);
|
|
758
|
+
const summaryPreview = summaryFull ? summaryFull.split("\n").map((l) => l.trim()).filter(Boolean).join(" ").slice(0, 200) : null;
|
|
759
|
+
return {
|
|
760
|
+
id: `compaction:${compactionId}`,
|
|
761
|
+
sequence,
|
|
762
|
+
turn_id: `compaction:${compactionId}`,
|
|
763
|
+
loop_id: loopId,
|
|
764
|
+
kind: "compaction",
|
|
765
|
+
role: "system",
|
|
766
|
+
status,
|
|
767
|
+
blocks: [],
|
|
768
|
+
tool_calls: [],
|
|
769
|
+
model: null,
|
|
770
|
+
usage: null,
|
|
771
|
+
duration_ms: 0,
|
|
772
|
+
compaction_id: compactionId,
|
|
773
|
+
summary_preview: summaryPreview,
|
|
774
|
+
summary_full: summaryFull,
|
|
775
|
+
archived_count: archivedCount,
|
|
776
|
+
tokens_before: tokensBefore,
|
|
777
|
+
tokens_after: tokensAfter,
|
|
778
|
+
saved_ratio: savedRatio,
|
|
779
|
+
trigger: asTrigger(data.trigger),
|
|
780
|
+
failure_reason: typeof data.reason === "string" && data.reason ? data.reason : null,
|
|
781
|
+
fallback_applied: typeof data.fallback_applied === "boolean" ? data.fallback_applied : null
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function compactionSummaryFull(content) {
|
|
785
|
+
const text = String(content ?? "");
|
|
786
|
+
if (!text) return null;
|
|
787
|
+
const cleaned = text.replace("<compaction-summary>", "").replace("</compaction-summary>", "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
788
|
+
return cleaned || null;
|
|
789
|
+
}
|
|
790
|
+
function asInt(value) {
|
|
791
|
+
if (typeof value === "boolean") return null;
|
|
792
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
function asFloat(value) {
|
|
796
|
+
if (typeof value === "boolean") return null;
|
|
797
|
+
if (typeof value === "number") return value;
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
function asTrigger(value) {
|
|
801
|
+
if (value === "auto" || value === "manual" || value === "forced_retry") return value;
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function setToolCallStatus(turn, toolCallId, status, result, pendingQuestionRef) {
|
|
805
|
+
if (!turn || !toolCallId) return;
|
|
806
|
+
const toolCalls = "toolCalls" in turn ? turn.toolCalls : turn.tool_calls;
|
|
807
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
808
|
+
if (toolCalls[i].id !== toolCallId) continue;
|
|
809
|
+
toolCalls[i] = {
|
|
810
|
+
...toolCalls[i],
|
|
811
|
+
status,
|
|
812
|
+
result,
|
|
813
|
+
pending_question_ref: pendingQuestionRef
|
|
814
|
+
};
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function applyAskUserPauseToTurn(turn, pauseToolData, toolCallId, childLoopName, description, displayNameResolver) {
|
|
819
|
+
if (!turn || !toolCallId) return;
|
|
820
|
+
const sourceLoop = childLoopName ? { loop_name: childLoopName, description } : null;
|
|
821
|
+
const toolName = String(pauseToolData.name ?? "AskUserQuestion");
|
|
822
|
+
const args = JSON.stringify(pauseToolData.arguments ?? {});
|
|
823
|
+
const result = askUserResultFromPausePayload(pauseToolData, sourceLoop);
|
|
824
|
+
const isTurnState = "toolCalls" in turn;
|
|
825
|
+
const toolCalls = isTurnState ? turn.toolCalls : turn.tool_calls;
|
|
826
|
+
const blocks = turn.blocks;
|
|
827
|
+
const displayName = displayNameResolver(toolName);
|
|
828
|
+
let found = false;
|
|
829
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
830
|
+
if (toolCalls[i].id !== toolCallId) continue;
|
|
831
|
+
toolCalls[i] = {
|
|
832
|
+
...toolCalls[i],
|
|
833
|
+
tool_name: toolName || toolCalls[i].tool_name,
|
|
834
|
+
display_name: displayName || toolCalls[i].display_name,
|
|
835
|
+
arguments: args || toolCalls[i].arguments
|
|
836
|
+
};
|
|
837
|
+
found = true;
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
if (!found) {
|
|
841
|
+
toolCalls.push({
|
|
842
|
+
id: toolCallId,
|
|
843
|
+
tool_name: toolName,
|
|
844
|
+
display_name: displayName,
|
|
845
|
+
arguments: args,
|
|
846
|
+
status: "pending"
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
let blockFound = false;
|
|
850
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
851
|
+
if (blocks[i].type !== "tool_use" || blocks[i].tool_call_id !== toolCallId) continue;
|
|
852
|
+
blocks[i] = { ...blocks[i], content: args, tool_name: toolName, display_name: displayName };
|
|
853
|
+
blockFound = true;
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
if (!blockFound) {
|
|
857
|
+
blocks.push({
|
|
858
|
+
type: "tool_use",
|
|
859
|
+
content: args,
|
|
860
|
+
tool_call_id: toolCallId,
|
|
861
|
+
tool_name: toolName,
|
|
862
|
+
display_name: displayName
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
setToolCallStatus(turn, toolCallId, "awaiting_answer", result, null);
|
|
866
|
+
let resultBlockFound = false;
|
|
867
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
868
|
+
if (blocks[i].type !== "tool_result" || blocks[i].tool_call_id !== toolCallId) continue;
|
|
869
|
+
blocks[i] = { ...blocks[i], content: result };
|
|
870
|
+
resultBlockFound = true;
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
if (!resultBlockFound) {
|
|
874
|
+
blocks.push({
|
|
875
|
+
type: "tool_result",
|
|
876
|
+
content: result,
|
|
877
|
+
tool_call_id: toolCallId,
|
|
878
|
+
tool_name: toolName,
|
|
879
|
+
display_name: displayName
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function applyChildPauseToParent(parentTurn, parentToolCallId, childLoopName, childToolCallId, description, displayNameResolver) {
|
|
884
|
+
if (!parentTurn || !parentToolCallId) return;
|
|
885
|
+
const toolCalls = "toolCalls" in parentTurn ? parentTurn.toolCalls : parentTurn.tool_calls;
|
|
886
|
+
const blocks = parentTurn.blocks;
|
|
887
|
+
const displayName = displayNameResolver("Agent");
|
|
888
|
+
let found = false;
|
|
889
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
890
|
+
if (toolCalls[i].id !== parentToolCallId) continue;
|
|
891
|
+
toolCalls[i] = {
|
|
892
|
+
...toolCalls[i],
|
|
893
|
+
tool_name: "Agent",
|
|
894
|
+
display_name: displayName || toolCalls[i].display_name
|
|
895
|
+
};
|
|
896
|
+
found = true;
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
if (!found) {
|
|
900
|
+
toolCalls.push({
|
|
901
|
+
id: parentToolCallId,
|
|
902
|
+
tool_name: "Agent",
|
|
903
|
+
display_name: displayName,
|
|
904
|
+
arguments: "",
|
|
905
|
+
status: "pending"
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
let blockFound = false;
|
|
909
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
910
|
+
if (blocks[i].type !== "tool_use" || blocks[i].tool_call_id !== parentToolCallId) continue;
|
|
911
|
+
blockFound = true;
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
if (!blockFound) {
|
|
915
|
+
blocks.push({
|
|
916
|
+
type: "tool_use",
|
|
917
|
+
content: "",
|
|
918
|
+
tool_call_id: parentToolCallId,
|
|
919
|
+
tool_name: "Agent",
|
|
920
|
+
display_name: displayName
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
setToolCallStatus(parentTurn, parentToolCallId, "awaiting_answer", null, {
|
|
924
|
+
child_loop_name: childLoopName,
|
|
925
|
+
child_tool_call_id: childToolCallId,
|
|
926
|
+
description: description || null
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
function askUserResultFromPausePayload(pauseToolData, sourceLoop) {
|
|
930
|
+
const args = typeof pauseToolData.arguments === "object" && pauseToolData.arguments !== null ? pauseToolData.arguments : {};
|
|
931
|
+
const payload = { questions: args.questions ?? [] };
|
|
932
|
+
if (sourceLoop) payload.source_loop = sourceLoop;
|
|
933
|
+
return JSON.stringify(payload);
|
|
934
|
+
}
|
|
935
|
+
function sourceLoopFor(loopId, loopDescriptions) {
|
|
936
|
+
if (loopId === "root") return null;
|
|
937
|
+
const description = loopDescriptions.get(loopId);
|
|
938
|
+
if (!description) return null;
|
|
939
|
+
return { loop_name: loopId, description };
|
|
940
|
+
}
|
|
941
|
+
function normalizeChildPause(pausePayload) {
|
|
942
|
+
const rawPtd = pausePayload.pause_tool_data;
|
|
943
|
+
const pauseToolData = typeof rawPtd === "object" && rawPtd !== null ? { ...rawPtd } : { ...pausePayload };
|
|
944
|
+
let sourceLoop = pauseToolData.source_loop;
|
|
945
|
+
if (typeof sourceLoop !== "object" || sourceLoop === null) {
|
|
946
|
+
sourceLoop = pausePayload.source_loop;
|
|
947
|
+
}
|
|
948
|
+
const childLoopName = String(pausePayload.child_loop_name ?? "") || (typeof sourceLoop === "object" && sourceLoop ? String(sourceLoop.name ?? "") : "");
|
|
949
|
+
const childToolCallId = String(
|
|
950
|
+
pausePayload.child_pause_tool_call_id ?? pauseToolData.tool_call_id ?? ""
|
|
951
|
+
);
|
|
952
|
+
const description = typeof sourceLoop === "object" && sourceLoop ? String(sourceLoop.description ?? "") : "";
|
|
953
|
+
if (childLoopName && description) {
|
|
954
|
+
pauseToolData.source_loop = { name: childLoopName, description };
|
|
955
|
+
}
|
|
956
|
+
return { pauseToolData, childLoopName, childToolCallId, description };
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/react/schemas/message.ts
|
|
960
|
+
function inferToolStatus(resultStr) {
|
|
961
|
+
if (resultStr.includes('"Cancelled by user"')) return "cancelled";
|
|
962
|
+
try {
|
|
963
|
+
const parsed = JSON.parse(resultStr);
|
|
964
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
965
|
+
if ("error" in parsed && parsed.error) return "error";
|
|
966
|
+
const nested = parsed.result;
|
|
967
|
+
if (typeof nested === "object" && nested !== null && "error" in nested && nested.error) {
|
|
968
|
+
return "error";
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
return "done";
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/client/shared/projection/state.ts
|
|
977
|
+
function createTurnState(turnId, loopId, opts = {}) {
|
|
978
|
+
return {
|
|
979
|
+
turnId,
|
|
980
|
+
loopId,
|
|
981
|
+
role: "assistant",
|
|
982
|
+
model: opts.model ?? null,
|
|
983
|
+
startedAt: Date.now(),
|
|
984
|
+
contextWindow: opts.contextWindow ?? 0,
|
|
985
|
+
blocks: [],
|
|
986
|
+
toolCalls: [],
|
|
987
|
+
usage: null,
|
|
988
|
+
memoryRefs: opts.memoryRefs ?? null,
|
|
989
|
+
parentForkToolCallId: null
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function snapshot(state, sequence, status) {
|
|
993
|
+
return {
|
|
994
|
+
id: state.turnId,
|
|
995
|
+
sequence,
|
|
996
|
+
turn_id: state.turnId,
|
|
997
|
+
loop_id: state.loopId,
|
|
998
|
+
kind: "message",
|
|
999
|
+
role: state.role,
|
|
1000
|
+
status,
|
|
1001
|
+
blocks: [...state.blocks],
|
|
1002
|
+
tool_calls: [...state.toolCalls],
|
|
1003
|
+
model: state.model,
|
|
1004
|
+
usage: state.usage ? { ...state.usage } : null,
|
|
1005
|
+
duration_ms: Math.max(0, Date.now() - state.startedAt),
|
|
1006
|
+
started_at: new Date(state.startedAt).toISOString(),
|
|
1007
|
+
context_window: state.contextWindow,
|
|
1008
|
+
memory_refs: state.memoryRefs,
|
|
1009
|
+
parent_fork_tool_call_id: state.parentForkToolCallId
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
var PAUSE_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
1013
|
+
function toolStatusFromResult(result) {
|
|
1014
|
+
const text = typeof result === "string" ? result : Array.isArray(result) ? result.filter(
|
|
1015
|
+
(p) => typeof p === "object" && p !== null && p.type === "text"
|
|
1016
|
+
).map((p) => p.text ?? "").join("\n") : String(result ?? "");
|
|
1017
|
+
return inferToolStatus(text);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/client/shared/projection/builder.ts
|
|
1021
|
+
var ClientProjectionBuilder = class {
|
|
1022
|
+
activeTurns = /* @__PURE__ */ new Map();
|
|
1023
|
+
lastTurnIds = /* @__PURE__ */ new Map();
|
|
1024
|
+
latestTurns = /* @__PURE__ */ new Map();
|
|
1025
|
+
loopDescriptions = /* @__PURE__ */ new Map();
|
|
1026
|
+
pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1027
|
+
pendingMemoryRefs = /* @__PURE__ */ new Map();
|
|
1028
|
+
activeCompactions = /* @__PURE__ */ new Map();
|
|
1029
|
+
seq = 0;
|
|
1030
|
+
syntheticCounter = 0;
|
|
1031
|
+
displayNameResolver;
|
|
1032
|
+
constructor(displayNameResolver) {
|
|
1033
|
+
this.displayNameResolver = displayNameResolver ?? ((n) => n);
|
|
1034
|
+
}
|
|
1035
|
+
processBatch(events) {
|
|
1036
|
+
const lastUpsertByTurn = /* @__PURE__ */ new Map();
|
|
1037
|
+
const nonUpserts = [];
|
|
1038
|
+
for (const event of events) {
|
|
1039
|
+
const updates = this.processEvent(event);
|
|
1040
|
+
if (!updates) continue;
|
|
1041
|
+
for (const u of updates) {
|
|
1042
|
+
if (u.kind === "upsert") {
|
|
1043
|
+
lastUpsertByTurn.set(u.turn.turn_id, u);
|
|
1044
|
+
} else {
|
|
1045
|
+
nonUpserts.push(u);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return [...nonUpserts, ...lastUpsertByTurn.values()];
|
|
1050
|
+
}
|
|
1051
|
+
processEvent(event) {
|
|
1052
|
+
const payload = event.payload;
|
|
1053
|
+
const loopId = String(payload.loop_name ?? "root");
|
|
1054
|
+
switch (event.type) {
|
|
1055
|
+
case "memory:inject:done":
|
|
1056
|
+
return this.onMemoryInject(loopId, payload);
|
|
1057
|
+
case "memory:inject:none":
|
|
1058
|
+
return null;
|
|
1059
|
+
case "loop:turn":
|
|
1060
|
+
return this.onLoopTurn(loopId, payload);
|
|
1061
|
+
case "mode:change":
|
|
1062
|
+
return this.onModeChange(loopId, payload);
|
|
1063
|
+
case "plan:status:update":
|
|
1064
|
+
return this.onPlanStatusUpdate(loopId, payload);
|
|
1065
|
+
case "workspace:changed":
|
|
1066
|
+
return this.onWorkspaceChanged(loopId, payload);
|
|
1067
|
+
case "agent:start":
|
|
1068
|
+
return this.onAgentStart(loopId, payload);
|
|
1069
|
+
case "bg:started":
|
|
1070
|
+
return this.onBgStarted(loopId, payload);
|
|
1071
|
+
case "bg:tasks_completed":
|
|
1072
|
+
return this.onBgTasksCompleted(loopId, payload);
|
|
1073
|
+
case "loop:ask_user_answer":
|
|
1074
|
+
return this.onAskUserAnswer(loopId, payload);
|
|
1075
|
+
case "user:message":
|
|
1076
|
+
return this.onUserMessage(loopId, payload);
|
|
1077
|
+
}
|
|
1078
|
+
if (event.type.startsWith("compaction:"))
|
|
1079
|
+
return this.onCompaction(event.type, loopId, payload);
|
|
1080
|
+
const state = this.activeTurns.get(loopId);
|
|
1081
|
+
if (!state) {
|
|
1082
|
+
if (event.type === "chat:end") {
|
|
1083
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
if (event.type === "agent:end") return this.onAgentEnd(null, loopId, payload);
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
switch (event.type) {
|
|
1090
|
+
case "llm:thinking:delta":
|
|
1091
|
+
return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
|
|
1092
|
+
case "llm:content:delta":
|
|
1093
|
+
return this.onDelta(state, "text", "", String(payload.content ?? ""));
|
|
1094
|
+
case "llm:tool_call:created":
|
|
1095
|
+
return this.onToolCallCreated(state, payload);
|
|
1096
|
+
case "llm:response:done":
|
|
1097
|
+
return this.onResponseDone(state, loopId, payload);
|
|
1098
|
+
case "tool:result:delta":
|
|
1099
|
+
return this.onToolResultDelta(state, payload);
|
|
1100
|
+
case "tool:result:done":
|
|
1101
|
+
return this.onToolResultDone(state, loopId, payload);
|
|
1102
|
+
case "loop:tool_ui":
|
|
1103
|
+
return this.onToolUi(state, payload);
|
|
1104
|
+
case "loop:tool_bridge":
|
|
1105
|
+
return this.onToolBridge(state, payload);
|
|
1106
|
+
case "tool:approval:required":
|
|
1107
|
+
return this.onToolApprovalRequired(state, payload);
|
|
1108
|
+
case "loop:child_pause":
|
|
1109
|
+
return this.onChildPause(state, loopId, payload);
|
|
1110
|
+
case "chat:end":
|
|
1111
|
+
return this.onChatEnd(state, loopId, payload);
|
|
1112
|
+
case "agent:end":
|
|
1113
|
+
return this.onAgentEnd(state, loopId, payload);
|
|
1114
|
+
default:
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
reset() {
|
|
1119
|
+
this.activeTurns.clear();
|
|
1120
|
+
this.lastTurnIds.clear();
|
|
1121
|
+
this.latestTurns.clear();
|
|
1122
|
+
this.loopDescriptions.clear();
|
|
1123
|
+
this.pendingChildPauses.clear();
|
|
1124
|
+
this.pendingMemoryRefs.clear();
|
|
1125
|
+
this.activeCompactions.clear();
|
|
1126
|
+
this.seq = 0;
|
|
1127
|
+
this.syntheticCounter = 0;
|
|
1128
|
+
}
|
|
1129
|
+
seedSequence(maxSeq) {
|
|
1130
|
+
if (maxSeq > this.seq) {
|
|
1131
|
+
this.seq = maxSeq;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// --- Event handlers ---
|
|
1135
|
+
onMemoryInject(loopId, payload) {
|
|
1136
|
+
const refs = payload.memory_refs;
|
|
1137
|
+
const turnId = String(payload.turn_id ?? "");
|
|
1138
|
+
if (Array.isArray(refs) && refs.length > 0) {
|
|
1139
|
+
this.pendingMemoryRefs.set(loopId, {
|
|
1140
|
+
turnId,
|
|
1141
|
+
refs: refs.map((r) => ({
|
|
1142
|
+
id: Number(r.id),
|
|
1143
|
+
content_preview: String(r.content_preview ?? ""),
|
|
1144
|
+
skill_name: r.skill_name != null ? String(r.skill_name) : null
|
|
1145
|
+
}))
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
onLoopTurn(loopId, payload) {
|
|
1151
|
+
let turnId = String(payload.turn_id ?? this.nextTurnId(`turn:${loopId}`));
|
|
1152
|
+
let memoryRefs = null;
|
|
1153
|
+
const pending = this.pendingMemoryRefs.get(loopId);
|
|
1154
|
+
if (pending) {
|
|
1155
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1156
|
+
if (pending.turnId) turnId = pending.turnId;
|
|
1157
|
+
memoryRefs = pending.refs;
|
|
1158
|
+
}
|
|
1159
|
+
const rawCw = payload.context_window;
|
|
1160
|
+
const contextWindow = typeof rawCw === "number" && rawCw > 0 ? Math.trunc(rawCw) : 0;
|
|
1161
|
+
const state = createTurnState(turnId, loopId, {
|
|
1162
|
+
model: optStr(payload.model),
|
|
1163
|
+
contextWindow,
|
|
1164
|
+
memoryRefs
|
|
1165
|
+
});
|
|
1166
|
+
this.activeTurns.set(loopId, state);
|
|
1167
|
+
this.lastTurnIds.set(loopId, turnId);
|
|
1168
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1169
|
+
this.latestTurns.set(loopId, turn);
|
|
1170
|
+
return [{ kind: "upsert", turn }];
|
|
1171
|
+
}
|
|
1172
|
+
onModeChange(loopId, payload) {
|
|
1173
|
+
const content = {};
|
|
1174
|
+
if (payload.from != null && payload.from !== "") content.from = String(payload.from);
|
|
1175
|
+
if (payload.to != null && payload.to !== "") content.to = String(payload.to);
|
|
1176
|
+
const turn = buildMarkerTurn(
|
|
1177
|
+
this.nextSeq(),
|
|
1178
|
+
this.nextTurnId("mode_change"),
|
|
1179
|
+
loopId,
|
|
1180
|
+
"mode_change",
|
|
1181
|
+
content
|
|
1182
|
+
);
|
|
1183
|
+
return [{ kind: "upsert", turn }];
|
|
1184
|
+
}
|
|
1185
|
+
onPlanStatusUpdate(loopId, payload) {
|
|
1186
|
+
let statuses = payload.statuses;
|
|
1187
|
+
if (typeof statuses !== "object" || statuses === null) {
|
|
1188
|
+
statuses = payload._statuses ?? {};
|
|
1189
|
+
}
|
|
1190
|
+
const turn = buildMarkerTurn(
|
|
1191
|
+
this.nextSeq(),
|
|
1192
|
+
this.nextTurnId("plan_status"),
|
|
1193
|
+
loopId,
|
|
1194
|
+
"plan_status",
|
|
1195
|
+
{ plan_yaml: payload.plan_yaml ?? "", statuses }
|
|
1196
|
+
);
|
|
1197
|
+
return [{ kind: "upsert", turn }];
|
|
1198
|
+
}
|
|
1199
|
+
onWorkspaceChanged(loopId, payload) {
|
|
1200
|
+
const updates = [
|
|
1201
|
+
{
|
|
1202
|
+
kind: "workspace_changed",
|
|
1203
|
+
loopId,
|
|
1204
|
+
toolCallId: optStr(payload.tool_call_id) ?? void 0,
|
|
1205
|
+
toolName: toolNameFromPayload(payload) ?? void 0
|
|
1206
|
+
}
|
|
1207
|
+
];
|
|
1208
|
+
const state = this.activeTurns.get(loopId);
|
|
1209
|
+
if (state) {
|
|
1210
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1211
|
+
this.latestTurns.set(loopId, turn);
|
|
1212
|
+
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1213
|
+
updates.push({ kind: "upsert", turn });
|
|
1214
|
+
}
|
|
1215
|
+
return updates;
|
|
1216
|
+
}
|
|
1217
|
+
onAgentStart(loopId, payload) {
|
|
1218
|
+
const description = optStr(payload.description);
|
|
1219
|
+
if (description) this.loopDescriptions.set(loopId, description);
|
|
1220
|
+
const turn = buildSystemNotificationTurn(
|
|
1221
|
+
this.nextSeq(),
|
|
1222
|
+
this.nextTurnId("agent:start"),
|
|
1223
|
+
loopId,
|
|
1224
|
+
"agent:start",
|
|
1225
|
+
"running",
|
|
1226
|
+
`Agent: ${payload.description ?? payload.skill_id ?? "agent"}`,
|
|
1227
|
+
optStr(payload.prompt),
|
|
1228
|
+
{
|
|
1229
|
+
skill_id: optStr(payload.skill_id),
|
|
1230
|
+
description: optStr(payload.description),
|
|
1231
|
+
parent_fork_tool_call_id: optStr(payload.parent_fork_tool_call_id)
|
|
1232
|
+
}
|
|
1233
|
+
);
|
|
1234
|
+
return [{ kind: "upsert", turn }];
|
|
1235
|
+
}
|
|
1236
|
+
onBgStarted(loopId, payload) {
|
|
1237
|
+
const taskId = optStr(payload.task_id) ?? "";
|
|
1238
|
+
const command = optStr(payload.command);
|
|
1239
|
+
const description = optStr(payload.description);
|
|
1240
|
+
const turn = buildSystemNotificationTurn(
|
|
1241
|
+
this.nextSeq(),
|
|
1242
|
+
this.nextTurnId("bg:started"),
|
|
1243
|
+
loopId,
|
|
1244
|
+
"bg:started",
|
|
1245
|
+
"running",
|
|
1246
|
+
description ?? `Background: ${taskId || "task"}`,
|
|
1247
|
+
command,
|
|
1248
|
+
{ task_id: taskId, command, description }
|
|
1249
|
+
);
|
|
1250
|
+
return [{ kind: "upsert", turn }];
|
|
1251
|
+
}
|
|
1252
|
+
onBgTasksCompleted(loopId, payload) {
|
|
1253
|
+
const taskIds = Array.isArray(payload.task_ids) ? payload.task_ids : [];
|
|
1254
|
+
const count = typeof payload.count === "number" ? payload.count : taskIds.length;
|
|
1255
|
+
const detail = taskIds.map(String).join(", ");
|
|
1256
|
+
const turn = buildSystemNotificationTurn(
|
|
1257
|
+
this.nextSeq(),
|
|
1258
|
+
this.nextTurnId("bg:tasks_completed"),
|
|
1259
|
+
loopId,
|
|
1260
|
+
"bg:tasks_completed",
|
|
1261
|
+
"done",
|
|
1262
|
+
`Background tasks completed (${count})`,
|
|
1263
|
+
detail || null,
|
|
1264
|
+
{ task_ids: taskIds, count }
|
|
1265
|
+
);
|
|
1266
|
+
return [{ kind: "upsert", turn }];
|
|
1267
|
+
}
|
|
1268
|
+
onAskUserAnswer(loopId, payload) {
|
|
1269
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1270
|
+
const pending = this.pendingChildPauses.get(toolCallId);
|
|
1271
|
+
const parentLoopId = pending?.parentLoopName;
|
|
1272
|
+
if (pending) {
|
|
1273
|
+
this.pendingChildPauses.delete(toolCallId);
|
|
1274
|
+
const childTurn = this.activeTurns.get(pending.childLoopName) ?? this.latestTurns.get(pending.childLoopName);
|
|
1275
|
+
setToolCallStatus(childTurn, pending.childToolCallId, "done", null, null);
|
|
1276
|
+
const parentTurn = this.activeTurns.get(pending.parentLoopName) ?? this.latestTurns.get(pending.parentLoopName);
|
|
1277
|
+
setToolCallStatus(parentTurn, pending.parentToolCallId, "done", null, null);
|
|
1278
|
+
}
|
|
1279
|
+
const directTurn = this.activeTurns.get(loopId) ?? this.latestTurns.get(loopId);
|
|
1280
|
+
setToolCallStatus(directTurn, toolCallId, "done", null, null);
|
|
1281
|
+
return this.collectLoopUpdates(loopId, parentLoopId);
|
|
1282
|
+
}
|
|
1283
|
+
onUserMessage(_loopId, payload) {
|
|
1284
|
+
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
1285
|
+
const displayContent = payload.display_content ?? payload.content ?? "";
|
|
1286
|
+
const turn = {
|
|
1287
|
+
id: entryId,
|
|
1288
|
+
sequence: this.nextSeq(),
|
|
1289
|
+
turn_id: entryId,
|
|
1290
|
+
loop_id: "root",
|
|
1291
|
+
kind: "message",
|
|
1292
|
+
role: "user",
|
|
1293
|
+
status: "completed",
|
|
1294
|
+
blocks: [{ type: "text", content: displayContent }],
|
|
1295
|
+
tool_calls: [],
|
|
1296
|
+
model: null,
|
|
1297
|
+
usage: null,
|
|
1298
|
+
duration_ms: 0
|
|
1299
|
+
};
|
|
1300
|
+
return [{ kind: "upsert", turn }];
|
|
1301
|
+
}
|
|
1302
|
+
// --- Delta / streaming events ---
|
|
1303
|
+
onDelta(state, blockType, _toolCallId, content) {
|
|
1304
|
+
if (!content) return null;
|
|
1305
|
+
appendTextBlock(state, blockType, content);
|
|
1306
|
+
return this.syncPatch(state);
|
|
1307
|
+
}
|
|
1308
|
+
onToolCallCreated(state, payload) {
|
|
1309
|
+
if ("arguments_delta" in payload) {
|
|
1310
|
+
const delta = String(payload.arguments_delta ?? "");
|
|
1311
|
+
const toolCallId2 = String(payload.id ?? "");
|
|
1312
|
+
if (delta && toolCallId2) {
|
|
1313
|
+
appendToolCallArguments(state, toolCallId2, delta);
|
|
1314
|
+
return this.syncPatch(state);
|
|
1315
|
+
}
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
const toolCallId = String(payload.id ?? "");
|
|
1319
|
+
const toolName = toolNameFromPayload(payload);
|
|
1320
|
+
const displayName = String(payload.display_name ?? "") || this.displayNameResolver(toolName);
|
|
1321
|
+
const args = toolArgsFromPayload(payload);
|
|
1322
|
+
upsertToolCall(state, toolCallId, toolName, displayName, args);
|
|
1323
|
+
return this.syncPatch(state);
|
|
1324
|
+
}
|
|
1325
|
+
onResponseDone(state, loopId, payload) {
|
|
1326
|
+
const model = optStr(payload.model);
|
|
1327
|
+
if (model) state.model = model;
|
|
1328
|
+
if (typeof payload.usage === "object" && payload.usage !== null) {
|
|
1329
|
+
state.usage = { ...payload.usage };
|
|
1330
|
+
}
|
|
1331
|
+
const toolCalls = payload.tool_calls;
|
|
1332
|
+
if (Array.isArray(toolCalls)) {
|
|
1333
|
+
for (const raw of toolCalls) {
|
|
1334
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
1335
|
+
const tc = raw;
|
|
1336
|
+
const fn = tc.function ?? {};
|
|
1337
|
+
const toolName = String(fn.name ?? "");
|
|
1338
|
+
const displayName = String(tc.display_name ?? "") || this.displayNameResolver(toolName);
|
|
1339
|
+
upsertToolCall(
|
|
1340
|
+
state,
|
|
1341
|
+
String(tc.id ?? ""),
|
|
1342
|
+
toolName,
|
|
1343
|
+
displayName,
|
|
1344
|
+
String(fn.arguments ?? "")
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (state.toolCalls.length === 0) {
|
|
1349
|
+
return this.finalize(loopId, "completed");
|
|
1350
|
+
}
|
|
1351
|
+
return this.syncPatch(state);
|
|
1352
|
+
}
|
|
1353
|
+
onToolResultDone(state, loopId, payload) {
|
|
1354
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1355
|
+
const content = payload.content ?? "";
|
|
1356
|
+
const status = toolStatusFromResult(content);
|
|
1357
|
+
const rawDuration = payload.duration_ms;
|
|
1358
|
+
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1359
|
+
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1360
|
+
applyToolResult(state, toolCallId, content, status, durationMs, sl);
|
|
1361
|
+
if (this.shouldFinalizeAfterToolResults(state)) {
|
|
1362
|
+
return this.finalize(loopId, "completed");
|
|
1363
|
+
}
|
|
1364
|
+
return this.syncPatch(state);
|
|
1365
|
+
}
|
|
1366
|
+
onToolResultDelta(state, payload) {
|
|
1367
|
+
appendToolResultDelta(
|
|
1368
|
+
state,
|
|
1369
|
+
String(payload.tool_call_id ?? ""),
|
|
1370
|
+
String(payload.content ?? "")
|
|
1371
|
+
);
|
|
1372
|
+
return this.syncPatch(state);
|
|
1373
|
+
}
|
|
1374
|
+
onToolUi(state, payload) {
|
|
1375
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1376
|
+
const ui = payload.ui;
|
|
1377
|
+
if (toolCallId && typeof ui === "object" && ui !== null) {
|
|
1378
|
+
state.blocks.push({
|
|
1379
|
+
type: "tool_ui",
|
|
1380
|
+
content: ui,
|
|
1381
|
+
tool_call_id: toolCallId
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
return this.syncPatch(state);
|
|
1385
|
+
}
|
|
1386
|
+
onToolBridge(state, payload) {
|
|
1387
|
+
const bridge = payload.bridge;
|
|
1388
|
+
if (typeof bridge !== "object" || bridge === null) return null;
|
|
1389
|
+
state.blocks.push({
|
|
1390
|
+
type: "tool_bridge",
|
|
1391
|
+
content: bridge,
|
|
1392
|
+
tool_call_id: String(payload.tool_call_id ?? "") || null
|
|
1393
|
+
});
|
|
1394
|
+
return this.syncPatch(state);
|
|
1395
|
+
}
|
|
1396
|
+
onToolApprovalRequired(state, payload) {
|
|
1397
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1398
|
+
const toolName = String(payload.tool_name ?? "");
|
|
1399
|
+
const displayName = this.displayNameResolver(toolName);
|
|
1400
|
+
upsertToolCall(state, toolCallId, toolName, displayName, "");
|
|
1401
|
+
return this.syncPatch(state);
|
|
1402
|
+
}
|
|
1403
|
+
onChildPause(state, loopId, payload) {
|
|
1404
|
+
const pauseToolData = payload.pause_tool_data;
|
|
1405
|
+
if (typeof pauseToolData !== "object" || pauseToolData === null)
|
|
1406
|
+
return this.syncPatch(state);
|
|
1407
|
+
const ptd = pauseToolData;
|
|
1408
|
+
const sourceLoop = ptd.source_loop;
|
|
1409
|
+
if (typeof sourceLoop === "object" && sourceLoop) {
|
|
1410
|
+
const slName = String(sourceLoop.name ?? "");
|
|
1411
|
+
const slDesc = optStr(sourceLoop.description);
|
|
1412
|
+
if (slName && slDesc) this.loopDescriptions.set(slName, slDesc);
|
|
1413
|
+
}
|
|
1414
|
+
const { childLoopName, childToolCallId, description } = normalizeChildPause(payload);
|
|
1415
|
+
if (!childToolCallId) return this.syncPatch(state);
|
|
1416
|
+
const existing = this.pendingChildPauses.get(childToolCallId);
|
|
1417
|
+
const parentLoopName = String(
|
|
1418
|
+
payload.parent_loop_name ?? existing?.parentLoopName ?? "root"
|
|
1419
|
+
);
|
|
1420
|
+
const parentToolCallId = String(
|
|
1421
|
+
payload.parent_fork_tool_call_id ?? existing?.parentToolCallId ?? ""
|
|
1422
|
+
);
|
|
1423
|
+
const resolvedChildLoopName = String(payload.child_loop_name ?? "") || existing?.childLoopName || childLoopName || loopId;
|
|
1424
|
+
this.pendingChildPauses.set(childToolCallId, {
|
|
1425
|
+
parentLoopName,
|
|
1426
|
+
parentToolCallId,
|
|
1427
|
+
childLoopName: resolvedChildLoopName,
|
|
1428
|
+
childToolCallId,
|
|
1429
|
+
description: description || existing?.description || ""
|
|
1430
|
+
});
|
|
1431
|
+
applyAskUserPauseToTurn(
|
|
1432
|
+
state,
|
|
1433
|
+
ptd,
|
|
1434
|
+
childToolCallId,
|
|
1435
|
+
resolvedChildLoopName,
|
|
1436
|
+
this.pendingChildPauses.get(childToolCallId).description,
|
|
1437
|
+
this.displayNameResolver
|
|
1438
|
+
);
|
|
1439
|
+
const parentTurn = this.activeTurns.get(parentLoopName) ?? this.latestTurns.get(parentLoopName);
|
|
1440
|
+
if (parentTurn) {
|
|
1441
|
+
applyChildPauseToParent(
|
|
1442
|
+
parentTurn,
|
|
1443
|
+
parentToolCallId,
|
|
1444
|
+
resolvedChildLoopName,
|
|
1445
|
+
childToolCallId,
|
|
1446
|
+
this.pendingChildPauses.get(childToolCallId).description,
|
|
1447
|
+
this.displayNameResolver
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
return this.collectLoopUpdates(loopId, parentLoopName);
|
|
1451
|
+
}
|
|
1452
|
+
onChatEnd(state, loopId, payload) {
|
|
1453
|
+
const status = String(payload.status ?? "completed");
|
|
1454
|
+
const pauseToolData = payload.pause_tool_data;
|
|
1455
|
+
if (typeof pauseToolData === "object" && pauseToolData !== null) {
|
|
1456
|
+
const ptd = pauseToolData;
|
|
1457
|
+
const pauseToolName = String(ptd.name ?? "");
|
|
1458
|
+
const normalized = pauseToolName.includes(":") ? pauseToolName.split(":").pop() : pauseToolName;
|
|
1459
|
+
const sourceLoop = ptd.source_loop;
|
|
1460
|
+
if (typeof sourceLoop === "object" && sourceLoop) {
|
|
1461
|
+
const slName = String(sourceLoop.name ?? "");
|
|
1462
|
+
const slDesc = optStr(sourceLoop.description);
|
|
1463
|
+
if (slName && slDesc) this.loopDescriptions.set(slName, slDesc);
|
|
1464
|
+
if (status === "paused" && slName) {
|
|
1465
|
+
applyChildPauseToParent(
|
|
1466
|
+
state,
|
|
1467
|
+
String(ptd.parent_fork_tool_call_id ?? ""),
|
|
1468
|
+
slName,
|
|
1469
|
+
String(ptd.tool_call_id ?? ""),
|
|
1470
|
+
slDesc ?? "",
|
|
1471
|
+
this.displayNameResolver
|
|
1472
|
+
);
|
|
1473
|
+
const childTurn = this.activeTurns.get(slName);
|
|
1474
|
+
if (childTurn) {
|
|
1475
|
+
applyAskUserPauseToTurn(
|
|
1476
|
+
childTurn,
|
|
1477
|
+
ptd,
|
|
1478
|
+
String(ptd.tool_call_id ?? ""),
|
|
1479
|
+
slName,
|
|
1480
|
+
slDesc ?? "",
|
|
1481
|
+
this.displayNameResolver
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
} else if (status === "paused" && normalized === "AskUserQuestion") {
|
|
1486
|
+
applyAskUserPauseToTurn(
|
|
1487
|
+
state,
|
|
1488
|
+
ptd,
|
|
1489
|
+
String(ptd.tool_call_id ?? ""),
|
|
1490
|
+
"",
|
|
1491
|
+
"",
|
|
1492
|
+
this.displayNameResolver
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const finalStatus = { completed: "completed", paused: "paused", failed: "failed", interrupted: "interrupted" }[status];
|
|
1497
|
+
if (!finalStatus) return null;
|
|
1498
|
+
if (!this.activeTurns.has(loopId)) {
|
|
1499
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1500
|
+
}
|
|
1501
|
+
return this.finalize(loopId, finalStatus);
|
|
1502
|
+
}
|
|
1503
|
+
onAgentEnd(state, loopId, payload) {
|
|
1504
|
+
const ok = payload.ok !== false;
|
|
1505
|
+
const status = ok ? "done" : "error";
|
|
1506
|
+
const title = `Agent: ${payload.description ?? payload.skill_id ?? "agent"}`;
|
|
1507
|
+
const detail = optStr(payload.error);
|
|
1508
|
+
const metadata = {
|
|
1509
|
+
skill_id: optStr(payload.skill_id),
|
|
1510
|
+
description: optStr(payload.description),
|
|
1511
|
+
parent_fork_tool_call_id: optStr(payload.parent_fork_tool_call_id),
|
|
1512
|
+
ok
|
|
1513
|
+
};
|
|
1514
|
+
if (state) {
|
|
1515
|
+
state.blocks.push(buildSystemNotificationBlock("agent:end", status, title, detail, metadata));
|
|
1516
|
+
return this.finalize(loopId, ok ? "completed" : "failed");
|
|
1517
|
+
}
|
|
1518
|
+
const turn = buildSystemNotificationTurn(
|
|
1519
|
+
this.nextSeq(),
|
|
1520
|
+
this.nextTurnId("agent:end"),
|
|
1521
|
+
loopId,
|
|
1522
|
+
"agent:end",
|
|
1523
|
+
status,
|
|
1524
|
+
title,
|
|
1525
|
+
detail,
|
|
1526
|
+
metadata
|
|
1527
|
+
);
|
|
1528
|
+
return [{ kind: "upsert", turn }];
|
|
1529
|
+
}
|
|
1530
|
+
onCompaction(eventType, loopId, payload) {
|
|
1531
|
+
const compactionId = String(payload.compaction_id ?? "");
|
|
1532
|
+
if (!compactionId) return null;
|
|
1533
|
+
const current = {
|
|
1534
|
+
...this.activeCompactions.get(compactionId) ?? {},
|
|
1535
|
+
...payload,
|
|
1536
|
+
loop_id: loopId
|
|
1537
|
+
};
|
|
1538
|
+
let status;
|
|
1539
|
+
switch (eventType) {
|
|
1540
|
+
case "compaction:start":
|
|
1541
|
+
status = "streaming";
|
|
1542
|
+
this.activeCompactions.set(compactionId, current);
|
|
1543
|
+
break;
|
|
1544
|
+
case "compaction:end":
|
|
1545
|
+
status = "completed";
|
|
1546
|
+
this.activeCompactions.delete(compactionId);
|
|
1547
|
+
break;
|
|
1548
|
+
case "compaction:abort":
|
|
1549
|
+
status = "interrupted";
|
|
1550
|
+
this.activeCompactions.delete(compactionId);
|
|
1551
|
+
break;
|
|
1552
|
+
case "compaction:error":
|
|
1553
|
+
status = "failed";
|
|
1554
|
+
this.activeCompactions.delete(compactionId);
|
|
1555
|
+
break;
|
|
1556
|
+
default:
|
|
1557
|
+
status = "streaming";
|
|
1558
|
+
this.activeCompactions.set(compactionId, current);
|
|
1559
|
+
}
|
|
1560
|
+
const turn = buildCompactionTurn(this.nextSeq(), compactionId, loopId, status, current);
|
|
1561
|
+
return [{ kind: "upsert", turn }];
|
|
1562
|
+
}
|
|
1563
|
+
// --- Internal helpers ---
|
|
1564
|
+
syncPatch(state) {
|
|
1565
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1566
|
+
this.latestTurns.set(state.loopId, turn);
|
|
1567
|
+
this.lastTurnIds.set(state.loopId, turn.turn_id);
|
|
1568
|
+
return [{ kind: "upsert", turn }];
|
|
1569
|
+
}
|
|
1570
|
+
finalize(loopId, status) {
|
|
1571
|
+
const state = this.activeTurns.get(loopId);
|
|
1572
|
+
if (!state) return null;
|
|
1573
|
+
this.activeTurns.delete(loopId);
|
|
1574
|
+
const turn = snapshot(state, this.nextSeq(), status);
|
|
1575
|
+
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1576
|
+
this.latestTurns.set(loopId, turn);
|
|
1577
|
+
return [{ kind: "upsert", turn }];
|
|
1578
|
+
}
|
|
1579
|
+
shouldFinalizeAfterToolResults(state) {
|
|
1580
|
+
if (state.toolCalls.length === 0) return true;
|
|
1581
|
+
if (state.toolCalls.some((tc) => tc.status === "pending")) return false;
|
|
1582
|
+
return !state.toolCalls.some((tc) => {
|
|
1583
|
+
const normalized = tc.tool_name.includes(":") ? tc.tool_name.split(":").pop() : tc.tool_name;
|
|
1584
|
+
return PAUSE_TOOL_NAMES.has(normalized);
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
collectLoopUpdates(...loopIds) {
|
|
1588
|
+
const updates = [];
|
|
1589
|
+
for (const loopId of loopIds) {
|
|
1590
|
+
if (!loopId) continue;
|
|
1591
|
+
const state = this.activeTurns.get(loopId);
|
|
1592
|
+
if (state) {
|
|
1593
|
+
updates.push(...this.syncPatch(state));
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const proj = this.latestTurns.get(loopId);
|
|
1597
|
+
if (proj) {
|
|
1598
|
+
const updated = { ...proj, sequence: this.nextSeq() };
|
|
1599
|
+
this.latestTurns.set(loopId, updated);
|
|
1600
|
+
updates.push({ kind: "upsert", turn: updated });
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return updates;
|
|
1604
|
+
}
|
|
1605
|
+
nextSeq() {
|
|
1606
|
+
return ++this.seq;
|
|
1607
|
+
}
|
|
1608
|
+
nextTurnId(prefix) {
|
|
1609
|
+
this.syntheticCounter++;
|
|
1610
|
+
const hex = Math.random().toString(16).slice(2, 10);
|
|
1611
|
+
return `${prefix}:${this.syntheticCounter}:${hex}`;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
function optStr(value) {
|
|
1615
|
+
if (value == null || value === "") return null;
|
|
1616
|
+
return String(value);
|
|
1617
|
+
}
|
|
1618
|
+
function toolNameFromPayload(payload) {
|
|
1619
|
+
const fn = payload.function;
|
|
1620
|
+
if (typeof fn === "object" && fn !== null) {
|
|
1621
|
+
return String(fn.name ?? "");
|
|
1622
|
+
}
|
|
1623
|
+
return String(payload.tool_name ?? "");
|
|
1624
|
+
}
|
|
1625
|
+
function toolArgsFromPayload(payload) {
|
|
1626
|
+
const fn = payload.function;
|
|
1627
|
+
if (typeof fn === "object" && fn !== null) {
|
|
1628
|
+
return String(fn.arguments ?? "");
|
|
1629
|
+
}
|
|
1630
|
+
return "";
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/client/shared/projection/history.ts
|
|
1634
|
+
function projectHistory(entries) {
|
|
1635
|
+
const turns = [];
|
|
1636
|
+
const toolOwners = /* @__PURE__ */ new Map();
|
|
1637
|
+
const latestAssistantByLoop = /* @__PURE__ */ new Map();
|
|
1638
|
+
const pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1639
|
+
let sequence = 0;
|
|
1640
|
+
for (const entry of entries) {
|
|
1641
|
+
const kind = String(entry.kind ?? "");
|
|
1642
|
+
const loopId = String(entry.loop_name ?? "root");
|
|
1643
|
+
const id = String(entry.id ?? `history-${sequence + 1}`);
|
|
1644
|
+
if (kind === "message" && isRecord2(entry.message)) {
|
|
1645
|
+
const message = entry.message;
|
|
1646
|
+
const role = String(message.role ?? "");
|
|
1647
|
+
if (role === "tool") {
|
|
1648
|
+
const toolCallId = String(message.tool_call_id ?? "");
|
|
1649
|
+
const owner = toolOwners.get(toolCallId);
|
|
1650
|
+
const toolCall = owner?.tool_calls.find((item) => item.id === toolCallId);
|
|
1651
|
+
if (owner && toolCall) {
|
|
1652
|
+
toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
|
|
1653
|
+
toolCall.result = message.content ?? "";
|
|
1654
|
+
owner.blocks.push({
|
|
1655
|
+
type: "tool_result",
|
|
1656
|
+
content: message.content ?? "",
|
|
1657
|
+
tool_call_id: toolCallId
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (role !== "user" && role !== "assistant" && role !== "system") continue;
|
|
1663
|
+
const toolCalls = role === "assistant" ? buildToolCalls(message.tool_calls) : [];
|
|
1664
|
+
const blocks = buildBlocks(message, toolCalls);
|
|
1665
|
+
const turn = {
|
|
1666
|
+
id,
|
|
1667
|
+
sequence: ++sequence,
|
|
1668
|
+
turn_id: id,
|
|
1669
|
+
loop_id: loopId,
|
|
1670
|
+
kind: "message",
|
|
1671
|
+
role,
|
|
1672
|
+
status: message._error ? "failed" : "completed",
|
|
1673
|
+
blocks,
|
|
1674
|
+
tool_calls: toolCalls,
|
|
1675
|
+
model: stringOrNull(message.model),
|
|
1676
|
+
usage: isRecord2(message._usage) ? { ...message._usage } : null,
|
|
1677
|
+
duration_ms: numberOrZero(message._duration_ms),
|
|
1678
|
+
started_at: typeof entry.timestamp === "string" ? entry.timestamp : void 0
|
|
1679
|
+
};
|
|
1680
|
+
turns.push(turn);
|
|
1681
|
+
if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
|
|
1682
|
+
for (const toolCall of toolCalls) toolOwners.set(toolCall.id, turn);
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
if (kind === "mode_change") {
|
|
1686
|
+
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
1687
|
+
} else if (kind === "plan_status") {
|
|
1688
|
+
const data = entry.data ?? {};
|
|
1689
|
+
turns.push(
|
|
1690
|
+
markerTurn(id, ++sequence, loopId, "plan_status", {
|
|
1691
|
+
plan_yaml: data.plan_yaml ?? "",
|
|
1692
|
+
statuses: data.statuses ?? data._statuses ?? {}
|
|
1693
|
+
})
|
|
1694
|
+
);
|
|
1695
|
+
} else if (kind === "tool_ui" || kind === "tool_bridge") {
|
|
1696
|
+
const data = entry.data ?? {};
|
|
1697
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
1698
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
1699
|
+
const content = kind === "tool_ui" ? data.ui : data.bridge;
|
|
1700
|
+
if (owner && content !== void 0) {
|
|
1701
|
+
owner.blocks.push({
|
|
1702
|
+
type: kind,
|
|
1703
|
+
content,
|
|
1704
|
+
tool_call_id: toolCallId || null
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
} else if (kind === "compaction" || kind === "tool_result_archive") {
|
|
1708
|
+
const data = entry.data ?? {};
|
|
1709
|
+
const compactionId = String(data.compaction_id ?? entry.id ?? `history-${sequence + 1}`);
|
|
1710
|
+
let content = isRecord2(entry.message) ? entry.message.content : void 0;
|
|
1711
|
+
if (kind === "tool_result_archive") {
|
|
1712
|
+
const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
|
|
1713
|
+
content = `<compaction-summary>
|
|
1714
|
+
\u5DF2\u5F52\u6863 ${archivedCount} \u4E2A\u5927\u578B\u5DE5\u5177\u7ED3\u679C\u3002
|
|
1715
|
+
</compaction-summary>`;
|
|
1716
|
+
}
|
|
1717
|
+
turns.push(
|
|
1718
|
+
buildCompactionTurn(++sequence, compactionId, loopId, "completed", {
|
|
1719
|
+
...data,
|
|
1720
|
+
content
|
|
1721
|
+
})
|
|
1722
|
+
);
|
|
1723
|
+
} else if (kind === "child_pause") {
|
|
1724
|
+
const data = entry.data ?? {};
|
|
1725
|
+
const sourceLoop = isRecord2(data.source_loop) ? data.source_loop : {};
|
|
1726
|
+
const childLoopId = String(data.child_loop_name ?? sourceLoop.name ?? "");
|
|
1727
|
+
const childToolCallId = String(data.child_pause_tool_call_id ?? "");
|
|
1728
|
+
const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
|
|
1729
|
+
const pauseToolData = isRecord2(data.pause_tool_data) ? data.pause_tool_data : {};
|
|
1730
|
+
const description = String(sourceLoop.description ?? "");
|
|
1731
|
+
applyAskUserPauseToTurn(
|
|
1732
|
+
latestAssistantByLoop.get(childLoopId),
|
|
1733
|
+
pauseToolData,
|
|
1734
|
+
childToolCallId,
|
|
1735
|
+
childLoopId,
|
|
1736
|
+
description,
|
|
1737
|
+
(name) => name
|
|
1738
|
+
);
|
|
1739
|
+
applyChildPauseToParent(
|
|
1740
|
+
latestAssistantByLoop.get(loopId),
|
|
1741
|
+
parentToolCallId,
|
|
1742
|
+
childLoopId,
|
|
1743
|
+
childToolCallId,
|
|
1744
|
+
description,
|
|
1745
|
+
(name) => name
|
|
1746
|
+
);
|
|
1747
|
+
if (childToolCallId) {
|
|
1748
|
+
pendingChildPauses.set(childToolCallId, {
|
|
1749
|
+
childLoopId,
|
|
1750
|
+
parentLoopId: loopId,
|
|
1751
|
+
parentToolCallId
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
} else if (kind === "ask_user_answer") {
|
|
1755
|
+
const data = entry.data ?? {};
|
|
1756
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
1757
|
+
const pending = pendingChildPauses.get(toolCallId);
|
|
1758
|
+
if (pending) {
|
|
1759
|
+
setToolCallStatus(latestAssistantByLoop.get(pending.childLoopId), toolCallId, "done", null, null);
|
|
1760
|
+
setToolCallStatus(
|
|
1761
|
+
latestAssistantByLoop.get(pending.parentLoopId),
|
|
1762
|
+
pending.parentToolCallId,
|
|
1763
|
+
"done",
|
|
1764
|
+
null,
|
|
1765
|
+
null
|
|
1766
|
+
);
|
|
1767
|
+
pendingChildPauses.delete(toolCallId);
|
|
1768
|
+
} else {
|
|
1769
|
+
setToolCallStatus(latestAssistantByLoop.get(loopId), toolCallId, "done", null, null);
|
|
1770
|
+
}
|
|
1771
|
+
turns.push(
|
|
1772
|
+
markerTurn(
|
|
1773
|
+
id,
|
|
1774
|
+
++sequence,
|
|
1775
|
+
loopId,
|
|
1776
|
+
"ask_user_answer",
|
|
1777
|
+
data,
|
|
1778
|
+
toolCallId || null
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return turns;
|
|
1784
|
+
}
|
|
1785
|
+
function buildToolCalls(value) {
|
|
1786
|
+
if (!Array.isArray(value)) return [];
|
|
1787
|
+
return value.filter(isRecord2).map((raw) => {
|
|
1788
|
+
const fn = isRecord2(raw.function) ? raw.function : {};
|
|
1789
|
+
const name = String(fn.name ?? "");
|
|
1790
|
+
return {
|
|
1791
|
+
id: String(raw.id ?? ""),
|
|
1792
|
+
tool_name: name,
|
|
1793
|
+
display_name: String(raw.display_name ?? name),
|
|
1794
|
+
arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
|
|
1795
|
+
status: "running",
|
|
1796
|
+
result: null
|
|
1797
|
+
};
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
function buildBlocks(message, toolCalls) {
|
|
1801
|
+
const stored = message._blocks;
|
|
1802
|
+
const blocks = [];
|
|
1803
|
+
if (Array.isArray(stored)) {
|
|
1804
|
+
for (const raw of stored) {
|
|
1805
|
+
if (!isRecord2(raw)) continue;
|
|
1806
|
+
const type3 = String(raw.type ?? "");
|
|
1807
|
+
if (type3 === "thinking") blocks.push({ type: type3, content: raw.thinking ?? raw.content ?? "" });
|
|
1808
|
+
if (type3 === "text") blocks.push({ type: type3, content: raw.text ?? raw.content ?? "" });
|
|
1809
|
+
}
|
|
1810
|
+
} else if (message.content !== void 0 && message.content !== "") {
|
|
1811
|
+
blocks.push({ type: "text", content: message.content });
|
|
1812
|
+
}
|
|
1813
|
+
for (const toolCall of toolCalls) {
|
|
1814
|
+
blocks.push({
|
|
1815
|
+
type: "tool_use",
|
|
1816
|
+
content: { name: toolCall.tool_name, arguments: toolCall.arguments },
|
|
1817
|
+
tool_call_id: toolCall.id
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
return blocks;
|
|
1821
|
+
}
|
|
1822
|
+
function markerTurn(id, sequence, loopId, type3, content, toolCallId = null) {
|
|
1823
|
+
return {
|
|
1824
|
+
id,
|
|
1825
|
+
sequence,
|
|
1826
|
+
turn_id: id,
|
|
1827
|
+
loop_id: loopId,
|
|
1828
|
+
kind: type3,
|
|
1829
|
+
role: "system",
|
|
1830
|
+
status: "completed",
|
|
1831
|
+
blocks: [{ type: type3, content, tool_call_id: toolCallId }],
|
|
1832
|
+
tool_calls: [],
|
|
1833
|
+
model: null,
|
|
1834
|
+
usage: null,
|
|
1835
|
+
duration_ms: 0
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
function isRecord2(value) {
|
|
1839
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1840
|
+
}
|
|
1841
|
+
function stringOrNull(value) {
|
|
1842
|
+
return typeof value === "string" ? value : null;
|
|
1843
|
+
}
|
|
1844
|
+
function numberOrZero(value) {
|
|
1845
|
+
return typeof value === "number" ? value : 0;
|
|
1846
|
+
}
|
|
1847
|
+
function findLatestAssistant(turns, loopId) {
|
|
1848
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
1849
|
+
const turn = turns[index];
|
|
1850
|
+
if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
|
|
1851
|
+
}
|
|
1852
|
+
return void 0;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
509
1855
|
// src/client/resources/runs.ts
|
|
510
1856
|
var SYSTEM_ERROR_CHAT_END_GRACE_MS2 = 1e3;
|
|
511
1857
|
var RunsResource = class {
|
|
@@ -524,6 +1870,7 @@ var RunsResource = class {
|
|
|
524
1870
|
let started = false;
|
|
525
1871
|
let chatRunId;
|
|
526
1872
|
let systemErrorTimeout = null;
|
|
1873
|
+
const projectionBuilder = new ClientProjectionBuilder();
|
|
527
1874
|
const timeout = setTimeout(() => {
|
|
528
1875
|
fail(new Error(`run timed out after ${timeoutMs}ms`));
|
|
529
1876
|
}, timeoutMs);
|
|
@@ -534,9 +1881,7 @@ var RunsResource = class {
|
|
|
534
1881
|
systemErrorTimeout = null;
|
|
535
1882
|
}
|
|
536
1883
|
socket.off("chat:start", onChatStart);
|
|
537
|
-
socket.off("turn:
|
|
538
|
-
socket.off("turn:patch", onTurnPatch);
|
|
539
|
-
socket.off("turn:end", onTurnEnd);
|
|
1884
|
+
socket.off("turn:events", onTurnEvents);
|
|
540
1885
|
socket.off("chat:end", onChatEnd);
|
|
541
1886
|
socket.off("system:error", onSystemError);
|
|
542
1887
|
if (sendStop && !stopSent) {
|
|
@@ -574,17 +1919,13 @@ var RunsResource = class {
|
|
|
574
1919
|
started = true;
|
|
575
1920
|
events.push({ type: "chat:start", payload });
|
|
576
1921
|
}
|
|
577
|
-
function
|
|
578
|
-
if (!isSessionEvent(payload, sessionId)) return;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
function onTurnEnd(payload) {
|
|
586
|
-
if (!isSessionEvent(payload, sessionId)) return;
|
|
587
|
-
events.push({ type: "turn:end", payload });
|
|
1922
|
+
function onTurnEvents(payload) {
|
|
1923
|
+
if (!isSessionEvent(payload, sessionId) || !payload.events?.length) return;
|
|
1924
|
+
for (const update of projectionBuilder.processBatch(payload.events)) {
|
|
1925
|
+
if (update.kind === "upsert") {
|
|
1926
|
+
events.push({ type: "turn:end", payload: { ...update.turn, session_id: sessionId } });
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
588
1929
|
}
|
|
589
1930
|
function onSystemError(payload) {
|
|
590
1931
|
if (!isSessionEvent(payload, sessionId)) return;
|
|
@@ -613,12 +1954,10 @@ var RunsResource = class {
|
|
|
613
1954
|
finish(payload);
|
|
614
1955
|
}
|
|
615
1956
|
socket.on("chat:start", onChatStart);
|
|
616
|
-
socket.on("turn:
|
|
617
|
-
socket.on("turn:patch", onTurnPatch);
|
|
618
|
-
socket.on("turn:end", onTurnEnd);
|
|
1957
|
+
socket.on("turn:events", onTurnEvents);
|
|
619
1958
|
socket.on("chat:end", onChatEnd);
|
|
620
1959
|
socket.on("system:error", onSystemError);
|
|
621
|
-
socket.emit("session:subscribe", { session_id: sessionId, event_format: "
|
|
1960
|
+
socket.emit("session:subscribe", { session_id: sessionId, event_format: "raw" });
|
|
622
1961
|
socket.emit("chat:send", buildChatSendPayload(sessionId, message, options));
|
|
623
1962
|
});
|
|
624
1963
|
}
|
|
@@ -645,16 +1984,16 @@ function buildChatSendPayload(sessionId, message, options) {
|
|
|
645
1984
|
}
|
|
646
1985
|
function collectConfigSnapshot(events) {
|
|
647
1986
|
const chatStart = events.find((event) => {
|
|
648
|
-
if (!
|
|
649
|
-
return
|
|
1987
|
+
if (!isRecord3(event) || event.type !== "chat:start") return false;
|
|
1988
|
+
return isRecord3(event.payload);
|
|
650
1989
|
});
|
|
651
|
-
if (!
|
|
1990
|
+
if (!isRecord3(chatStart) || !isRecord3(chatStart.payload)) {
|
|
652
1991
|
return {};
|
|
653
1992
|
}
|
|
654
1993
|
return chatStart.payload;
|
|
655
1994
|
}
|
|
656
1995
|
function errorMessageFromResult2(result) {
|
|
657
|
-
if (
|
|
1996
|
+
if (isRecord3(result) && typeof result.err_msg === "string" && result.err_msg) {
|
|
658
1997
|
return result.err_msg;
|
|
659
1998
|
}
|
|
660
1999
|
return null;
|
|
@@ -662,7 +2001,7 @@ function errorMessageFromResult2(result) {
|
|
|
662
2001
|
function isSessionEvent(payload, sessionId) {
|
|
663
2002
|
return !payload.session_id || payload.session_id === sessionId;
|
|
664
2003
|
}
|
|
665
|
-
function
|
|
2004
|
+
function isRecord3(value) {
|
|
666
2005
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
667
2006
|
}
|
|
668
2007
|
function ensureSocketConnected2(socket, timeoutMs, detail) {
|
|
@@ -834,17 +2173,22 @@ var SessionsResource = class {
|
|
|
834
2173
|
revokeShare(sessionId, token) {
|
|
835
2174
|
return this.client.json("DELETE", `/api/sessions/${sessionId}/share/${encodeURIComponent(token)}`);
|
|
836
2175
|
}
|
|
837
|
-
getSharedSession(token) {
|
|
838
|
-
|
|
2176
|
+
async getSharedSession(token) {
|
|
2177
|
+
const entries = await this.client.jsonFromInit(`/api/share/${encodeURIComponent(token)}`, {
|
|
839
2178
|
credentials: "omit",
|
|
840
2179
|
headers: { Accept: "application/json" }
|
|
841
2180
|
});
|
|
2181
|
+
return projectHistory(entries);
|
|
842
2182
|
}
|
|
843
2183
|
getSessionTasks(sessionId) {
|
|
844
2184
|
return this.client.json("GET", `/api/sessions/${sessionId}/tasks`);
|
|
845
2185
|
}
|
|
846
|
-
getSessionTurns(sessionId) {
|
|
847
|
-
|
|
2186
|
+
async getSessionTurns(sessionId) {
|
|
2187
|
+
const entries = await this.client.json(
|
|
2188
|
+
"GET",
|
|
2189
|
+
`/api/sessions/${sessionId}/messages`
|
|
2190
|
+
);
|
|
2191
|
+
return projectHistory(entries);
|
|
848
2192
|
}
|
|
849
2193
|
getSessionContextStats(sessionId, init) {
|
|
850
2194
|
return this.client.jsonFromInit(`/api/sessions/${sessionId}/context-stats`, init);
|
|
@@ -1113,7 +2457,7 @@ var SolutionsResource = class {
|
|
|
1113
2457
|
}
|
|
1114
2458
|
};
|
|
1115
2459
|
function toPreparedSolution(payload) {
|
|
1116
|
-
const data =
|
|
2460
|
+
const data = isRecord4(payload) ? payload : {};
|
|
1117
2461
|
const roles = Array.isArray(data.roles) ? data.roles.filter((role) => typeof role === "string") : [];
|
|
1118
2462
|
return {
|
|
1119
2463
|
kind: "prepared",
|
|
@@ -1127,7 +2471,7 @@ function stringField(data, key) {
|
|
|
1127
2471
|
const value = data[key];
|
|
1128
2472
|
return typeof value === "string" ? value : null;
|
|
1129
2473
|
}
|
|
1130
|
-
function
|
|
2474
|
+
function isRecord4(value) {
|
|
1131
2475
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1132
2476
|
}
|
|
1133
2477
|
function parseContentDispositionFilename(header) {
|
|
@@ -1233,6 +2577,7 @@ var BladeClient = class {
|
|
|
1233
2577
|
storeSocketTokenResolver = null;
|
|
1234
2578
|
options;
|
|
1235
2579
|
apiKeys;
|
|
2580
|
+
appDevTemplates;
|
|
1236
2581
|
auth;
|
|
1237
2582
|
envBuckets;
|
|
1238
2583
|
gis;
|
|
@@ -1253,6 +2598,7 @@ var BladeClient = class {
|
|
|
1253
2598
|
baseUrl: normalizeBaseUrl(options.baseUrl)
|
|
1254
2599
|
};
|
|
1255
2600
|
this.apiKeys = new ApiKeysResource(this);
|
|
2601
|
+
this.appDevTemplates = new AppDevTemplatesResource(this);
|
|
1256
2602
|
this.auth = new AuthResource(this);
|
|
1257
2603
|
this.envBuckets = new EnvBucketsResource(this);
|
|
1258
2604
|
this.gis = new GisResource(this);
|
|
@@ -1532,6 +2878,7 @@ function parseXhrHeaders(rawHeaders) {
|
|
|
1532
2878
|
export {
|
|
1533
2879
|
BladeApiError,
|
|
1534
2880
|
ApiKeysResource,
|
|
2881
|
+
AppDevTemplatesResource,
|
|
1535
2882
|
AuthResource,
|
|
1536
2883
|
GisResource,
|
|
1537
2884
|
HeadlessError,
|
|
@@ -1544,6 +2891,7 @@ export {
|
|
|
1544
2891
|
REGISTRY_PREFIX,
|
|
1545
2892
|
normalizeResource,
|
|
1546
2893
|
RegistryResource,
|
|
2894
|
+
ClientProjectionBuilder,
|
|
1547
2895
|
RunsResource,
|
|
1548
2896
|
ScenariosResource,
|
|
1549
2897
|
SessionsResource,
|
|
@@ -1553,4 +2901,4 @@ export {
|
|
|
1553
2901
|
createSocket,
|
|
1554
2902
|
BladeClient
|
|
1555
2903
|
};
|
|
1556
|
-
//# sourceMappingURL=chunk-
|
|
2904
|
+
//# sourceMappingURL=chunk-YJ6BX3FU.js.map
|