@springbrand/chat-client 0.1.3-alpha.1 → 0.1.3-alpha.11
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/package.json +3 -3
- package/src/chat-sessions.ts +0 -13
- package/src/use-universal-agent-chat.ts +124 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/chat-client",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
".": "./src/index.ts"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"agents": "^0.
|
|
16
|
+
"agents": "^0.20.1",
|
|
17
17
|
"ai": "^7.0.0",
|
|
18
18
|
"react": "^19.0.0"
|
|
19
19
|
},
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@types/react-dom": "^19.2.3",
|
|
26
26
|
"react-dom": "^19.2.7",
|
|
27
27
|
"typescript": "^7.0.2",
|
|
28
|
-
"@springbrand/agent-runtime": "0.
|
|
28
|
+
"@springbrand/agent-runtime": "0.2.0-alpha.19"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
package/src/chat-sessions.ts
CHANGED
|
@@ -88,18 +88,6 @@ export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
|
|
|
88
88
|
await inbox.call("archiveChat", [id, archived]);
|
|
89
89
|
}, [inbox]);
|
|
90
90
|
|
|
91
|
-
const reloadChatRuntime = useCallback(
|
|
92
|
-
async (chatId: string, userAgentId: string) => {
|
|
93
|
-
const result = await inbox.call<{ userAgentId: string } | null>(
|
|
94
|
-
"chatReloadRuntime",
|
|
95
|
-
[chatId, userAgentId],
|
|
96
|
-
);
|
|
97
|
-
if (!result) throw new Error("Session is unavailable");
|
|
98
|
-
return result;
|
|
99
|
-
},
|
|
100
|
-
[inbox],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
91
|
return {
|
|
104
92
|
chats: inbox.state?.chats ?? snapshot ?? [],
|
|
105
93
|
synced: inbox.state !== undefined || snapshot !== undefined,
|
|
@@ -111,7 +99,6 @@ export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
|
|
|
111
99
|
deleteChat,
|
|
112
100
|
pinChat,
|
|
113
101
|
archiveChat,
|
|
114
|
-
reloadChatRuntime,
|
|
115
102
|
};
|
|
116
103
|
}
|
|
117
104
|
|
|
@@ -8,6 +8,9 @@ import { useAgent, useAgentToolEvents } from "agents/react";
|
|
|
8
8
|
import {
|
|
9
9
|
type MessageDelivery,
|
|
10
10
|
type MessageDispatchReceipt,
|
|
11
|
+
type RequestedCapability,
|
|
12
|
+
type RuntimeAssemblyView,
|
|
13
|
+
type RuntimeConfigUpdateResult,
|
|
11
14
|
type RuntimeQueuedSubmission,
|
|
12
15
|
type RuntimeState,
|
|
13
16
|
} from "@springbrand/agent-runtime/contracts";
|
|
@@ -15,6 +18,8 @@ import { createSafeUIMessageAgentConnection } from "./ui-message-stream-guard";
|
|
|
15
18
|
import { CURRENT_INBOX_AGENT_PATH } from "./chat-sessions";
|
|
16
19
|
|
|
17
20
|
type ChatMessageMetadata = Record<string, unknown> & {
|
|
21
|
+
error?: string;
|
|
22
|
+
requestedCapabilities?: readonly RequestedCapability[];
|
|
18
23
|
turnStatus?: string;
|
|
19
24
|
};
|
|
20
25
|
type ChatMessage = UIMessage<ChatMessageMetadata>;
|
|
@@ -28,7 +33,7 @@ type ChatRuntimeLoadState =
|
|
|
28
33
|
| { status: "idle"; available: false }
|
|
29
34
|
| {
|
|
30
35
|
status: "loading";
|
|
31
|
-
phase: "config" | "
|
|
36
|
+
phase: "config" | "assembly" | "mcp" | "pi";
|
|
32
37
|
available: boolean;
|
|
33
38
|
startedAt: number;
|
|
34
39
|
updatedAt: number;
|
|
@@ -41,7 +46,7 @@ type ChatRuntimeLoadState =
|
|
|
41
46
|
}
|
|
42
47
|
| {
|
|
43
48
|
status: "error";
|
|
44
|
-
phase: "config" | "
|
|
49
|
+
phase: "config" | "assembly" | "mcp" | "pi";
|
|
45
50
|
available: boolean;
|
|
46
51
|
startedAt: number;
|
|
47
52
|
failedAt: number;
|
|
@@ -83,12 +88,47 @@ export interface ChatRuntime {
|
|
|
83
88
|
runtimeLoad: ChatRuntimeLoadState | undefined;
|
|
84
89
|
isStreaming: boolean;
|
|
85
90
|
error: string | Error | undefined;
|
|
86
|
-
sendText: (
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
sendText: (
|
|
92
|
+
text: string,
|
|
93
|
+
files?: readonly FileUIPart[],
|
|
94
|
+
capabilities?: readonly RequestedCapability[],
|
|
95
|
+
) => Promise<boolean>;
|
|
96
|
+
steerText: (
|
|
97
|
+
text: string,
|
|
98
|
+
files?: readonly FileUIPart[],
|
|
99
|
+
capabilities?: readonly RequestedCapability[],
|
|
100
|
+
) => Promise<boolean>;
|
|
101
|
+
enqueueText: (
|
|
102
|
+
text: string,
|
|
103
|
+
files?: readonly FileUIPart[],
|
|
104
|
+
capabilities?: readonly RequestedCapability[],
|
|
105
|
+
) => Promise<boolean>;
|
|
89
106
|
steerQueued: (submissionId: string) => Promise<boolean>;
|
|
90
107
|
cancelQueued: (submissionId: string) => Promise<boolean>;
|
|
91
108
|
stop: () => Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* 把用户对某张交互卡片的操作回写成那次工具调用的结果。
|
|
111
|
+
*
|
|
112
|
+
* 走的是这条已开的 WS 连接(`agent.call`),不是 HTTP —— ack 与随后流下来的
|
|
113
|
+
* `tool-output-available` chunk 同序到达,UI 状态不会倒挂。
|
|
114
|
+
*/
|
|
115
|
+
respondToolInteraction: (
|
|
116
|
+
toolCallId: string,
|
|
117
|
+
response: unknown,
|
|
118
|
+
) => Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* 读取该 Session facet 当前真正装配出来的 Runtime。
|
|
121
|
+
*
|
|
122
|
+
* 复用这条已开的 WS 连接,调用方不必为一个只读控制面方法另建 `useAgent`。
|
|
123
|
+
*
|
|
124
|
+
* 与其它动作不同,失败会原样抛出:这是读取而不是提交,调用方需要知道是
|
|
125
|
+
* 装配未就绪还是连接断了,收敛成 `undefined` 会让调试面板无从显示原因。
|
|
126
|
+
*/
|
|
127
|
+
getRuntimeAssembly: () => Promise<RuntimeAssemblyView>;
|
|
128
|
+
updateConfig: <Change = unknown>(
|
|
129
|
+
command: unknown,
|
|
130
|
+
) => Promise<RuntimeConfigUpdateResult<Change>>;
|
|
131
|
+
reloadRuntime: () => Promise<void>;
|
|
92
132
|
regenerate: () => void;
|
|
93
133
|
canRetry: boolean;
|
|
94
134
|
agentToolRuns: AgentToolRunState<ChatMessage["parts"][number]>[];
|
|
@@ -170,10 +210,18 @@ function markChatTurnPhase(
|
|
|
170
210
|
function createChatMessage(
|
|
171
211
|
text: string,
|
|
172
212
|
files?: readonly FileUIPart[],
|
|
213
|
+
capabilities: readonly RequestedCapability[] = [],
|
|
173
214
|
): ChatMessage | null {
|
|
174
215
|
const normalizedText = text.trim();
|
|
175
216
|
if (!normalizedText && !files?.length) return null;
|
|
176
217
|
const createdAt = Date.now();
|
|
218
|
+
const seen = new Set<string>();
|
|
219
|
+
const requestedCapabilities = capabilities.filter((capability) => {
|
|
220
|
+
const key = `${capability.kind}:${capability.name}`;
|
|
221
|
+
if (seen.has(key)) return false;
|
|
222
|
+
seen.add(key);
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
177
225
|
return {
|
|
178
226
|
id: nanoid(),
|
|
179
227
|
role: "user",
|
|
@@ -187,6 +235,7 @@ function createChatMessage(
|
|
|
187
235
|
createdAt,
|
|
188
236
|
authorDisplayName: "You",
|
|
189
237
|
messageSource: "Web",
|
|
238
|
+
...(requestedCapabilities.length > 0 ? { requestedCapabilities } : {}),
|
|
190
239
|
},
|
|
191
240
|
};
|
|
192
241
|
}
|
|
@@ -281,7 +330,7 @@ export function useUniversalAgentChat(
|
|
|
281
330
|
// autonomous responses 纪律(CF 文档采纳 2026-07-05):框架已提供三个流态标志,
|
|
282
331
|
// 透出供 UI 区分「服务端主动流 vs 用户发起流」+「恢复态」(与 stall 看门狗配套)。
|
|
283
332
|
isServerStreaming,
|
|
284
|
-
isRecovering,
|
|
333
|
+
isRecovering: sdkIsRecovering,
|
|
285
334
|
isToolContinuation,
|
|
286
335
|
connectionError,
|
|
287
336
|
} = useAgentChat<RuntimeState, ChatMessage>({
|
|
@@ -294,6 +343,9 @@ export function useUniversalAgentChat(
|
|
|
294
343
|
syncMessagesToServer: false,
|
|
295
344
|
throttle: 100,
|
|
296
345
|
});
|
|
346
|
+
// The SDK keeps its advisory recovery flag until terminal settlement; a
|
|
347
|
+
// live server stream proves the recovered Turn is executing again.
|
|
348
|
+
const isRecovering = sdkIsRecovering && !isServerStreaming;
|
|
297
349
|
const visibleMessages = useMemo(
|
|
298
350
|
() => mergeOptimisticMessages(messages, optimisticMessages),
|
|
299
351
|
[messages, optimisticMessages],
|
|
@@ -313,6 +365,14 @@ export function useUniversalAgentChat(
|
|
|
313
365
|
? "ready"
|
|
314
366
|
: sdkStatus;
|
|
315
367
|
const sdkError = stoppedByUser || hasTerminalMessage ? undefined : error;
|
|
368
|
+
const runtimeError = dispatchError ?? sdkError ?? connectionError ?? undefined;
|
|
369
|
+
const runtimeErrorMessage = runtimeError instanceof Error
|
|
370
|
+
? runtimeError.message
|
|
371
|
+
: runtimeError;
|
|
372
|
+
const presentationError = latestTurnStatus === "error" &&
|
|
373
|
+
latestMessage?.metadata?.error === runtimeErrorMessage
|
|
374
|
+
? undefined
|
|
375
|
+
: runtimeError;
|
|
316
376
|
// RPC admissions bypass useChat's request lifecycle. Project the durable
|
|
317
377
|
// Turn here so presentation stays correct without a second send path.
|
|
318
378
|
const authoritativeStatus =
|
|
@@ -547,8 +607,9 @@ export function useUniversalAgentChat(
|
|
|
547
607
|
const sendText = useCallback(async (
|
|
548
608
|
text: string,
|
|
549
609
|
files?: readonly FileUIPart[],
|
|
610
|
+
capabilities?: readonly RequestedCapability[],
|
|
550
611
|
): Promise<boolean> => {
|
|
551
|
-
const message = createChatMessage(text, files);
|
|
612
|
+
const message = createChatMessage(text, files, capabilities);
|
|
552
613
|
return message
|
|
553
614
|
? submitMessage(message, "enqueue", "message")
|
|
554
615
|
: false;
|
|
@@ -557,21 +618,28 @@ export function useUniversalAgentChat(
|
|
|
557
618
|
const dispatchText = useCallback(async (
|
|
558
619
|
text: string,
|
|
559
620
|
files: readonly FileUIPart[] | undefined,
|
|
621
|
+
capabilities: readonly RequestedCapability[] | undefined,
|
|
560
622
|
delivery: MessageDelivery,
|
|
561
623
|
projection: "message" | "queue",
|
|
562
624
|
): Promise<boolean> => {
|
|
563
|
-
const message = createChatMessage(text, files);
|
|
625
|
+
const message = createChatMessage(text, files, capabilities);
|
|
564
626
|
return message ? submitMessage(message, delivery, projection) : false;
|
|
565
627
|
}, [submitMessage]);
|
|
566
628
|
|
|
567
629
|
const steerText = useCallback(
|
|
568
|
-
(
|
|
569
|
-
|
|
630
|
+
(
|
|
631
|
+
text: string,
|
|
632
|
+
files?: readonly FileUIPart[],
|
|
633
|
+
capabilities?: readonly RequestedCapability[],
|
|
634
|
+
) => dispatchText(text, files, capabilities, "steer", "message"),
|
|
570
635
|
[dispatchText],
|
|
571
636
|
);
|
|
572
637
|
const enqueueText = useCallback(
|
|
573
|
-
(
|
|
574
|
-
|
|
638
|
+
(
|
|
639
|
+
text: string,
|
|
640
|
+
files?: readonly FileUIPart[],
|
|
641
|
+
capabilities?: readonly RequestedCapability[],
|
|
642
|
+
) => dispatchText(text, files, capabilities, "enqueue", "queue"),
|
|
575
643
|
[dispatchText],
|
|
576
644
|
);
|
|
577
645
|
const steerQueued = useCallback(async (submissionId: string) => {
|
|
@@ -664,13 +732,55 @@ export function useUniversalAgentChat(
|
|
|
664
732
|
);
|
|
665
733
|
}, [submitMessage]);
|
|
666
734
|
|
|
735
|
+
// 交互卡片的写回口。失败只返回 false —— 调用方(块组件)自己决定怎么提示;
|
|
736
|
+
// 权威状态永远来自 part 的 state,不来自这里的返回值。
|
|
737
|
+
const respondToolInteraction = useCallback(async (
|
|
738
|
+
toolCallId: string,
|
|
739
|
+
response: unknown,
|
|
740
|
+
): Promise<boolean> => {
|
|
741
|
+
try {
|
|
742
|
+
const receipt = await agentRef.current.call<{ ok: boolean }>(
|
|
743
|
+
"respondToolInteraction",
|
|
744
|
+
[toolCallId, response],
|
|
745
|
+
);
|
|
746
|
+
return receipt.ok;
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}, []);
|
|
751
|
+
|
|
752
|
+
// 只读控制面:Runtime 尚未装配时由 Agent 侧 `ensureRuntimeReady` 负责等待,
|
|
753
|
+
// 这里不缓存结果 —— 换模型、加技能或 reload 之后调用方要拿到的是新装配。
|
|
754
|
+
const getRuntimeAssembly = useCallback(
|
|
755
|
+
() => agentRef.current.call<RuntimeAssemblyView>("getRuntimeAssembly", []),
|
|
756
|
+
[],
|
|
757
|
+
);
|
|
758
|
+
const updateConfig = useCallback(
|
|
759
|
+
<Change,>(command: unknown) =>
|
|
760
|
+
agentRef.current.call<RuntimeConfigUpdateResult<Change>>(
|
|
761
|
+
"updateConfig",
|
|
762
|
+
[command],
|
|
763
|
+
),
|
|
764
|
+
[],
|
|
765
|
+
);
|
|
766
|
+
const reloadRuntime = useCallback(
|
|
767
|
+
() => agentRef.current.call<void>(
|
|
768
|
+
"reloadRuntime",
|
|
769
|
+
[undefined, { force: true }],
|
|
770
|
+
),
|
|
771
|
+
[],
|
|
772
|
+
);
|
|
773
|
+
|
|
667
774
|
return {
|
|
775
|
+
respondToolInteraction,
|
|
776
|
+
getRuntimeAssembly,
|
|
777
|
+
updateConfig,
|
|
778
|
+
reloadRuntime,
|
|
668
779
|
messages: visibleMessages,
|
|
669
780
|
status,
|
|
670
781
|
runtimeLoad: facetState?.runtimeLoad,
|
|
671
782
|
isStreaming,
|
|
672
|
-
error:
|
|
673
|
-
connectionError ?? undefined,
|
|
783
|
+
error: presentationError,
|
|
674
784
|
sendText,
|
|
675
785
|
steerText,
|
|
676
786
|
enqueueText,
|