@springbrand/chat-client 0.1.3-alpha.3 → 0.1.3-alpha.31
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 +7 -3
- package/src/index.ts +3 -0
- package/src/use-universal-agent-chat.ts +217 -48
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.31",
|
|
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.42"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
package/src/chat-sessions.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { useAgent } from "agents/react";
|
|
3
3
|
|
|
4
|
-
interface InboxState<Session> {
|
|
4
|
+
export interface InboxState<Session> {
|
|
5
5
|
chats: Session[];
|
|
6
6
|
workspaceRev?: number;
|
|
7
7
|
}
|
|
@@ -33,8 +33,11 @@ export async function loadChatSessions<Session>(
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Browser client for the user Inbox. Routing and presentation stay with the host UI. */
|
|
36
|
-
export function useChatSessions<
|
|
37
|
-
|
|
36
|
+
export function useChatSessions<
|
|
37
|
+
Session,
|
|
38
|
+
State extends InboxState<Session> = InboxState<Session>,
|
|
39
|
+
>(options: ChatSessionsOptions = {}) {
|
|
40
|
+
const inbox = useAgent<State>({
|
|
38
41
|
agent: "Inbox",
|
|
39
42
|
basePath: CURRENT_INBOX_AGENT_PATH,
|
|
40
43
|
...(options.protocols === undefined ? {} : { protocols: options.protocols }),
|
|
@@ -93,6 +96,7 @@ export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
|
|
|
93
96
|
synced: inbox.state !== undefined || snapshot !== undefined,
|
|
94
97
|
connected: inbox.identified,
|
|
95
98
|
workspaceRev: inbox.state?.workspaceRev ?? 0,
|
|
99
|
+
inboxState: inbox.state,
|
|
96
100
|
createChat,
|
|
97
101
|
forkChat,
|
|
98
102
|
renameChat,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export {
|
|
2
|
+
UniversalAgentChatProvider,
|
|
2
3
|
useUniversalAgentChat,
|
|
3
4
|
type AgentToolRun,
|
|
5
|
+
type ChatConnectionStatus,
|
|
4
6
|
type ChatConnectionOptions,
|
|
5
7
|
type ChatRuntime,
|
|
6
8
|
} from "./use-universal-agent-chat";
|
|
@@ -21,4 +23,5 @@ export {
|
|
|
21
23
|
useChatSessions,
|
|
22
24
|
type ChatSessionsOptions,
|
|
23
25
|
type ChatSessions,
|
|
26
|
+
type InboxState,
|
|
24
27
|
} from "./chat-sessions";
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createElement,
|
|
4
|
+
Suspense,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
} from "react";
|
|
2
13
|
import { nanoid } from "nanoid";
|
|
3
14
|
import type { ChatStatus, FileUIPart, UIMessage } from "ai";
|
|
4
15
|
import type { AgentToolRunState } from "agents";
|
|
16
|
+
import type { AgentConnectionError } from "agents/client";
|
|
5
17
|
import { MessageType } from "agents/chat";
|
|
6
18
|
import { getAgentMessages, useAgentChat } from "agents/chat/react";
|
|
7
19
|
import { useAgent, useAgentToolEvents } from "agents/react";
|
|
8
20
|
import {
|
|
21
|
+
type ApprovalDecision,
|
|
9
22
|
type MessageDelivery,
|
|
10
23
|
type MessageDispatchReceipt,
|
|
24
|
+
type RequestedCapability,
|
|
11
25
|
type RuntimeAssemblyView,
|
|
12
26
|
type RuntimeConfigUpdateResult,
|
|
13
27
|
type RuntimeQueuedSubmission,
|
|
@@ -17,9 +31,17 @@ import { createSafeUIMessageAgentConnection } from "./ui-message-stream-guard";
|
|
|
17
31
|
import { CURRENT_INBOX_AGENT_PATH } from "./chat-sessions";
|
|
18
32
|
|
|
19
33
|
type ChatMessageMetadata = Record<string, unknown> & {
|
|
34
|
+
error?: string;
|
|
35
|
+
requestedCapabilities?: readonly RequestedCapability[];
|
|
36
|
+
turnId?: string;
|
|
20
37
|
turnStatus?: string;
|
|
21
38
|
};
|
|
22
39
|
type ChatMessage = UIMessage<ChatMessageMetadata>;
|
|
40
|
+
export type ChatConnectionStatus =
|
|
41
|
+
| "connecting"
|
|
42
|
+
| "connected"
|
|
43
|
+
| "rejected"
|
|
44
|
+
| "error";
|
|
23
45
|
type FailedDispatch = {
|
|
24
46
|
message: ChatMessage;
|
|
25
47
|
delivery: MessageDelivery;
|
|
@@ -82,12 +104,26 @@ type ChatTurn = {
|
|
|
82
104
|
export interface ChatRuntime {
|
|
83
105
|
messages: ChatMessage[];
|
|
84
106
|
status: ChatStatus;
|
|
107
|
+
connectionStatus: ChatConnectionStatus;
|
|
108
|
+
connectionError: AgentConnectionError | undefined;
|
|
85
109
|
runtimeLoad: ChatRuntimeLoadState | undefined;
|
|
86
110
|
isStreaming: boolean;
|
|
87
111
|
error: string | Error | undefined;
|
|
88
|
-
sendText: (
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
sendText: (
|
|
113
|
+
text: string,
|
|
114
|
+
files?: readonly FileUIPart[],
|
|
115
|
+
capabilities?: readonly RequestedCapability[],
|
|
116
|
+
) => Promise<boolean>;
|
|
117
|
+
steerText: (
|
|
118
|
+
text: string,
|
|
119
|
+
files?: readonly FileUIPart[],
|
|
120
|
+
capabilities?: readonly RequestedCapability[],
|
|
121
|
+
) => Promise<boolean>;
|
|
122
|
+
enqueueText: (
|
|
123
|
+
text: string,
|
|
124
|
+
files?: readonly FileUIPart[],
|
|
125
|
+
capabilities?: readonly RequestedCapability[],
|
|
126
|
+
) => Promise<boolean>;
|
|
91
127
|
steerQueued: (submissionId: string) => Promise<boolean>;
|
|
92
128
|
cancelQueued: (submissionId: string) => Promise<boolean>;
|
|
93
129
|
stop: () => Promise<void>;
|
|
@@ -101,6 +137,10 @@ export interface ChatRuntime {
|
|
|
101
137
|
toolCallId: string,
|
|
102
138
|
response: unknown,
|
|
103
139
|
) => Promise<boolean>;
|
|
140
|
+
decideApproval: (
|
|
141
|
+
executionId: string,
|
|
142
|
+
decision: ApprovalDecision,
|
|
143
|
+
) => Promise<{ ok: boolean }>;
|
|
104
144
|
/**
|
|
105
145
|
* 读取该 Session facet 当前真正装配出来的 Runtime。
|
|
106
146
|
*
|
|
@@ -110,6 +150,10 @@ export interface ChatRuntime {
|
|
|
110
150
|
* 装配未就绪还是连接断了,收敛成 `undefined` 会让调试面板无从显示原因。
|
|
111
151
|
*/
|
|
112
152
|
getRuntimeAssembly: () => Promise<RuntimeAssemblyView>;
|
|
153
|
+
getRuntimeBinding: <Binding = unknown>() => Promise<Binding>;
|
|
154
|
+
readChatMetadata: <Metadata = unknown>() => Promise<Metadata>;
|
|
155
|
+
renameChat: <Metadata = unknown>(title: string) => Promise<Metadata>;
|
|
156
|
+
rebindRuntime: <Input = unknown, Result = unknown>(input: Input) => Promise<Result>;
|
|
113
157
|
updateConfig: <Change = unknown>(
|
|
114
158
|
command: unknown,
|
|
115
159
|
) => Promise<RuntimeConfigUpdateResult<Change>>;
|
|
@@ -131,8 +175,13 @@ type SharedChatConnectionOptions = {
|
|
|
131
175
|
host?: string;
|
|
132
176
|
credentials?: RequestCredentials;
|
|
133
177
|
protocols?: string | string[];
|
|
178
|
+
onMessage?: (message: MessageEvent) => void;
|
|
134
179
|
inboxAgent?: string;
|
|
135
180
|
sessionAgent?: string;
|
|
181
|
+
/** Skip history loading when the caller has just created this Chat. */
|
|
182
|
+
skipInitialMessages?: boolean;
|
|
183
|
+
/** Host projection used only until the selected Session sends its first state. */
|
|
184
|
+
initialTurnActive?: boolean;
|
|
136
185
|
};
|
|
137
186
|
|
|
138
187
|
export type ChatConnectionOptions = SharedChatConnectionOptions & (
|
|
@@ -140,6 +189,57 @@ export type ChatConnectionOptions = SharedChatConnectionOptions & (
|
|
|
140
189
|
| { inboxName?: never; basePath?: string }
|
|
141
190
|
);
|
|
142
191
|
|
|
192
|
+
type SessionAgentConnection = ReturnType<typeof useAgent<RuntimeState>>;
|
|
193
|
+
type UniversalAgentChatContextValue = {
|
|
194
|
+
chatId: string;
|
|
195
|
+
connection: ChatConnectionOptions;
|
|
196
|
+
agent: SessionAgentConnection;
|
|
197
|
+
chatAgent: SessionAgentConnection;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const UniversalAgentChatContext = createContext<UniversalAgentChatContextValue | null>(null);
|
|
201
|
+
|
|
202
|
+
export function UniversalAgentChatProvider({
|
|
203
|
+
chatId,
|
|
204
|
+
connection = {},
|
|
205
|
+
fallback = null,
|
|
206
|
+
children,
|
|
207
|
+
}: {
|
|
208
|
+
chatId: string;
|
|
209
|
+
connection?: ChatConnectionOptions;
|
|
210
|
+
fallback?: ReactNode;
|
|
211
|
+
children: ReactNode;
|
|
212
|
+
}) {
|
|
213
|
+
const inboxAgent = connection.inboxAgent ?? "Inbox";
|
|
214
|
+
const agent = useAgent<RuntimeState>({
|
|
215
|
+
agent: inboxAgent,
|
|
216
|
+
...(connection.inboxName === undefined
|
|
217
|
+
? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
|
|
218
|
+
: { name: connection.inboxName }),
|
|
219
|
+
...(connection.host === undefined ? {} : { host: connection.host }),
|
|
220
|
+
...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
|
|
221
|
+
...(connection.onMessage === undefined ? {} : { onMessage: connection.onMessage }),
|
|
222
|
+
connectionTimeout: 6_000,
|
|
223
|
+
sub: [{
|
|
224
|
+
agent: connection.sessionAgent ?? "UniversalAgent",
|
|
225
|
+
name: chatId,
|
|
226
|
+
}],
|
|
227
|
+
});
|
|
228
|
+
const chatAgent = useMemo(
|
|
229
|
+
() => createSafeUIMessageAgentConnection(agent),
|
|
230
|
+
[agent],
|
|
231
|
+
);
|
|
232
|
+
const value = useMemo(
|
|
233
|
+
() => ({ chatId, connection, agent, chatAgent }),
|
|
234
|
+
[agent, chatAgent, chatId, connection],
|
|
235
|
+
);
|
|
236
|
+
return createElement(
|
|
237
|
+
UniversalAgentChatContext.Provider,
|
|
238
|
+
{ value },
|
|
239
|
+
createElement(Suspense, { fallback }, children),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
143
243
|
type ChatTurnTiming = {
|
|
144
244
|
chatId: string;
|
|
145
245
|
messageId: string;
|
|
@@ -195,10 +295,18 @@ function markChatTurnPhase(
|
|
|
195
295
|
function createChatMessage(
|
|
196
296
|
text: string,
|
|
197
297
|
files?: readonly FileUIPart[],
|
|
298
|
+
capabilities: readonly RequestedCapability[] = [],
|
|
198
299
|
): ChatMessage | null {
|
|
199
300
|
const normalizedText = text.trim();
|
|
200
301
|
if (!normalizedText && !files?.length) return null;
|
|
201
302
|
const createdAt = Date.now();
|
|
303
|
+
const seen = new Set<string>();
|
|
304
|
+
const requestedCapabilities = capabilities.filter((capability) => {
|
|
305
|
+
const key = `${capability.kind}:${capability.name}`;
|
|
306
|
+
if (seen.has(key)) return false;
|
|
307
|
+
seen.add(key);
|
|
308
|
+
return true;
|
|
309
|
+
});
|
|
202
310
|
return {
|
|
203
311
|
id: nanoid(),
|
|
204
312
|
role: "user",
|
|
@@ -212,6 +320,7 @@ function createChatMessage(
|
|
|
212
320
|
createdAt,
|
|
213
321
|
authorDisplayName: "You",
|
|
214
322
|
messageSource: "Web",
|
|
323
|
+
...(requestedCapabilities.length > 0 ? { requestedCapabilities } : {}),
|
|
215
324
|
},
|
|
216
325
|
};
|
|
217
326
|
}
|
|
@@ -258,10 +367,12 @@ function loadInitialMessages(
|
|
|
258
367
|
* sub 数组由客户端 kebab 化;服务端按 ctx.exports 反解回 CamelCase className,
|
|
259
368
|
* 与 Inbox.onBeforeSubAgent 的严格门卫(hasSubAgent)对齐。
|
|
260
369
|
*/
|
|
261
|
-
export function useUniversalAgentChat(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
370
|
+
export function useUniversalAgentChat(): ChatRuntime {
|
|
371
|
+
const context = useContext(UniversalAgentChatContext);
|
|
372
|
+
if (!context) {
|
|
373
|
+
throw new Error("useUniversalAgentChat must be used within UniversalAgentChatProvider");
|
|
374
|
+
}
|
|
375
|
+
const { chatId, connection, agent, chatAgent } = context;
|
|
265
376
|
const [optimisticMessages, setOptimisticMessages] =
|
|
266
377
|
useState<ChatMessage[]>([]);
|
|
267
378
|
const [optimisticQueued, setOptimisticQueued] =
|
|
@@ -270,24 +381,6 @@ export function useUniversalAgentChat(
|
|
|
270
381
|
const [canRetry, setCanRetry] = useState(false);
|
|
271
382
|
const failedDispatchRef = useRef<FailedDispatch | undefined>(undefined);
|
|
272
383
|
const activeSubmissionIdRef = useRef<string | undefined>(undefined);
|
|
273
|
-
const inboxAgent = connection.inboxAgent ?? "Inbox";
|
|
274
|
-
const agent = useAgent<RuntimeState>({
|
|
275
|
-
agent: inboxAgent,
|
|
276
|
-
...(connection.inboxName === undefined
|
|
277
|
-
? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
|
|
278
|
-
: { name: connection.inboxName }),
|
|
279
|
-
...(connection.host === undefined ? {} : { host: connection.host }),
|
|
280
|
-
...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
|
|
281
|
-
sub: [{
|
|
282
|
-
agent: connection.sessionAgent ?? "UniversalAgent",
|
|
283
|
-
name: chatId,
|
|
284
|
-
}],
|
|
285
|
-
});
|
|
286
|
-
const chatAgent = useMemo(
|
|
287
|
-
() => createSafeUIMessageAgentConnection(agent),
|
|
288
|
-
[agent],
|
|
289
|
-
);
|
|
290
|
-
|
|
291
384
|
// 轮询→推送:这条 chat 连接连的是 UniversalAgent facet,`agent.state` 即 facet 的
|
|
292
385
|
// AgentState 广播(useAgent 内部 useState,收到 cf_agent_state 即 re-render)。待批项
|
|
293
386
|
// 随它推来 —— 审批读侧复用这条已开的连接,不另开第二条 WebSocket。三态(原则 VII):
|
|
@@ -303,22 +396,27 @@ export function useUniversalAgentChat(
|
|
|
303
396
|
isStreaming,
|
|
304
397
|
error,
|
|
305
398
|
clearError,
|
|
399
|
+
stop: stopChat,
|
|
306
400
|
// autonomous responses 纪律(CF 文档采纳 2026-07-05):框架已提供三个流态标志,
|
|
307
401
|
// 透出供 UI 区分「服务端主动流 vs 用户发起流」+「恢复态」(与 stall 看门狗配套)。
|
|
308
402
|
isServerStreaming,
|
|
309
|
-
isRecovering,
|
|
403
|
+
isRecovering: sdkIsRecovering,
|
|
310
404
|
isToolContinuation,
|
|
311
|
-
connectionError,
|
|
405
|
+
connectionError: sdkConnectionError,
|
|
312
406
|
} = useAgentChat<RuntimeState, ChatMessage>({
|
|
313
407
|
agent: chatAgent,
|
|
314
|
-
getInitialMessages:
|
|
315
|
-
|
|
408
|
+
getInitialMessages: connection.skipInitialMessages
|
|
409
|
+
? null
|
|
410
|
+
: ({ url }) => loadInitialMessages(url, connection.credentials),
|
|
316
411
|
...(connection.credentials === undefined
|
|
317
412
|
? {}
|
|
318
413
|
: { credentials: connection.credentials }),
|
|
319
414
|
syncMessagesToServer: false,
|
|
320
415
|
throttle: 100,
|
|
321
416
|
});
|
|
417
|
+
// The SDK keeps its advisory recovery flag until terminal settlement; a
|
|
418
|
+
// live server stream proves the recovered Turn is executing again.
|
|
419
|
+
const isRecovering = sdkIsRecovering && !isServerStreaming;
|
|
322
420
|
const visibleMessages = useMemo(
|
|
323
421
|
() => mergeOptimisticMessages(messages, optimisticMessages),
|
|
324
422
|
[messages, optimisticMessages],
|
|
@@ -338,6 +436,18 @@ export function useUniversalAgentChat(
|
|
|
338
436
|
? "ready"
|
|
339
437
|
: sdkStatus;
|
|
340
438
|
const sdkError = stoppedByUser || hasTerminalMessage ? undefined : error;
|
|
439
|
+
const connectionError = sdkConnectionError ?? undefined;
|
|
440
|
+
const connectionStatus: ChatConnectionStatus = connectionError
|
|
441
|
+
? connectionError.code === 1008 ? "rejected" : "error"
|
|
442
|
+
: agent.identified ? "connected" : "connecting";
|
|
443
|
+
const runtimeError = dispatchError ?? sdkError ?? connectionError;
|
|
444
|
+
const runtimeErrorMessage = runtimeError instanceof Error
|
|
445
|
+
? runtimeError.message
|
|
446
|
+
: runtimeError;
|
|
447
|
+
const presentationError = latestTurnStatus === "error" &&
|
|
448
|
+
latestMessage?.metadata?.error === runtimeErrorMessage
|
|
449
|
+
? undefined
|
|
450
|
+
: runtimeError;
|
|
341
451
|
// RPC admissions bypass useChat's request lifecycle. Project the durable
|
|
342
452
|
// Turn here so presentation stays correct without a second send path.
|
|
343
453
|
const authoritativeStatus =
|
|
@@ -349,9 +459,12 @@ export function useUniversalAgentChat(
|
|
|
349
459
|
? "streaming"
|
|
350
460
|
: "submitted"
|
|
351
461
|
: normalizedSdkStatus;
|
|
462
|
+
const hydratingTurnActive = facetState === undefined &&
|
|
463
|
+
connection.initialTurnActive === true;
|
|
352
464
|
const status = dispatchError
|
|
353
465
|
? "error"
|
|
354
|
-
: optimisticMessages.length > 0
|
|
466
|
+
: (optimisticMessages.length > 0 || hydratingTurnActive) &&
|
|
467
|
+
authoritativeStatus === "ready"
|
|
355
468
|
? "submitted"
|
|
356
469
|
: authoritativeStatus;
|
|
357
470
|
const messagesRef = useRef(messages);
|
|
@@ -443,7 +556,8 @@ export function useUniversalAgentChat(
|
|
|
443
556
|
isToolContinuation;
|
|
444
557
|
const pendingLocalTurn = optimisticMessages.length > 0 &&
|
|
445
558
|
(status === "submitted" || status === "streaming");
|
|
446
|
-
const turnActive =
|
|
559
|
+
const turnActive = hydratingTurnActive ||
|
|
560
|
+
Boolean(turn?.activeSubmissionId) ||
|
|
447
561
|
pendingLocalTurn ||
|
|
448
562
|
(authoritativeTurn === undefined && sdkTurnActive);
|
|
449
563
|
const canSteer = turnActive &&
|
|
@@ -462,11 +576,25 @@ export function useUniversalAgentChat(
|
|
|
462
576
|
const dispatchMessage = useCallback(async (
|
|
463
577
|
message: ChatMessage,
|
|
464
578
|
delivery: MessageDelivery,
|
|
465
|
-
): Promise<MessageDispatchReceipt> =>
|
|
466
|
-
agentRef.current.call<MessageDispatchReceipt>(
|
|
579
|
+
): Promise<MessageDispatchReceipt> => {
|
|
580
|
+
const dispatch = () => agentRef.current.call<MessageDispatchReceipt>(
|
|
467
581
|
"dispatchMessage",
|
|
468
582
|
[message, delivery],
|
|
469
|
-
)
|
|
583
|
+
);
|
|
584
|
+
try {
|
|
585
|
+
return await dispatch();
|
|
586
|
+
} catch (error) {
|
|
587
|
+
// The server deduplicates dispatches by message.id, so replay only the
|
|
588
|
+
// transport failures that mean admission may already have succeeded.
|
|
589
|
+
const retryable = error instanceof Error && (
|
|
590
|
+
/^RPC call to dispatchMessage timed out after \d+ms$/u.test(error.message) ||
|
|
591
|
+
(error.message === "Connection closed" && agentRef.current.shouldReconnect)
|
|
592
|
+
);
|
|
593
|
+
if (!retryable) throw error;
|
|
594
|
+
await agentRef.current.ready;
|
|
595
|
+
return dispatch();
|
|
596
|
+
}
|
|
597
|
+
}, []);
|
|
470
598
|
|
|
471
599
|
const submitMessage = useCallback(async (
|
|
472
600
|
message: ChatMessage,
|
|
@@ -572,8 +700,9 @@ export function useUniversalAgentChat(
|
|
|
572
700
|
const sendText = useCallback(async (
|
|
573
701
|
text: string,
|
|
574
702
|
files?: readonly FileUIPart[],
|
|
703
|
+
capabilities?: readonly RequestedCapability[],
|
|
575
704
|
): Promise<boolean> => {
|
|
576
|
-
const message = createChatMessage(text, files);
|
|
705
|
+
const message = createChatMessage(text, files, capabilities);
|
|
577
706
|
return message
|
|
578
707
|
? submitMessage(message, "enqueue", "message")
|
|
579
708
|
: false;
|
|
@@ -582,21 +711,28 @@ export function useUniversalAgentChat(
|
|
|
582
711
|
const dispatchText = useCallback(async (
|
|
583
712
|
text: string,
|
|
584
713
|
files: readonly FileUIPart[] | undefined,
|
|
714
|
+
capabilities: readonly RequestedCapability[] | undefined,
|
|
585
715
|
delivery: MessageDelivery,
|
|
586
716
|
projection: "message" | "queue",
|
|
587
717
|
): Promise<boolean> => {
|
|
588
|
-
const message = createChatMessage(text, files);
|
|
718
|
+
const message = createChatMessage(text, files, capabilities);
|
|
589
719
|
return message ? submitMessage(message, delivery, projection) : false;
|
|
590
720
|
}, [submitMessage]);
|
|
591
721
|
|
|
592
722
|
const steerText = useCallback(
|
|
593
|
-
(
|
|
594
|
-
|
|
723
|
+
(
|
|
724
|
+
text: string,
|
|
725
|
+
files?: readonly FileUIPart[],
|
|
726
|
+
capabilities?: readonly RequestedCapability[],
|
|
727
|
+
) => dispatchText(text, files, capabilities, "steer", "message"),
|
|
595
728
|
[dispatchText],
|
|
596
729
|
);
|
|
597
730
|
const enqueueText = useCallback(
|
|
598
|
-
(
|
|
599
|
-
|
|
731
|
+
(
|
|
732
|
+
text: string,
|
|
733
|
+
files?: readonly FileUIPart[],
|
|
734
|
+
capabilities?: readonly RequestedCapability[],
|
|
735
|
+
) => dispatchText(text, files, capabilities, "enqueue", "queue"),
|
|
600
736
|
[dispatchText],
|
|
601
737
|
);
|
|
602
738
|
const steerQueued = useCallback(async (submissionId: string) => {
|
|
@@ -657,18 +793,18 @@ export function useUniversalAgentChat(
|
|
|
657
793
|
type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL,
|
|
658
794
|
id: requestId,
|
|
659
795
|
}));
|
|
796
|
+
await stopChat();
|
|
660
797
|
return;
|
|
661
798
|
}
|
|
662
|
-
|
|
663
|
-
?
|
|
799
|
+
await (submissionId
|
|
800
|
+
? agentRef.current.call<{ ok: boolean }>(
|
|
664
801
|
"cancelSubmissionById",
|
|
665
802
|
[submissionId, USER_STOP_REASON],
|
|
666
803
|
)
|
|
667
|
-
:
|
|
804
|
+
: agentRef.current.call<{ ok: boolean }>(
|
|
668
805
|
"stopTurn",
|
|
669
806
|
[undefined, USER_STOP_REASON],
|
|
670
|
-
);
|
|
671
|
-
if (!result.ok) setDispatchError("The active Turn could not be stopped");
|
|
807
|
+
));
|
|
672
808
|
} catch (cause) {
|
|
673
809
|
setDispatchError(
|
|
674
810
|
cause instanceof Error ? cause.message : String(cause),
|
|
@@ -677,6 +813,7 @@ export function useUniversalAgentChat(
|
|
|
677
813
|
}, [
|
|
678
814
|
authoritativeTurn?.activeRequestId,
|
|
679
815
|
authoritativeTurn?.activeSubmissionId,
|
|
816
|
+
stopChat,
|
|
680
817
|
]);
|
|
681
818
|
|
|
682
819
|
const retry = useCallback(() => {
|
|
@@ -705,6 +842,14 @@ export function useUniversalAgentChat(
|
|
|
705
842
|
return false;
|
|
706
843
|
}
|
|
707
844
|
}, []);
|
|
845
|
+
const decideApproval = useCallback(
|
|
846
|
+
(executionId: string, decision: ApprovalDecision) =>
|
|
847
|
+
agentRef.current.call<{ ok: boolean }>(
|
|
848
|
+
"decideApproval",
|
|
849
|
+
[executionId, decision],
|
|
850
|
+
),
|
|
851
|
+
[],
|
|
852
|
+
);
|
|
708
853
|
|
|
709
854
|
// 只读控制面:Runtime 尚未装配时由 Agent 侧 `ensureRuntimeReady` 负责等待,
|
|
710
855
|
// 这里不缓存结果 —— 换模型、加技能或 reload 之后调用方要拿到的是新装配。
|
|
@@ -712,6 +857,24 @@ export function useUniversalAgentChat(
|
|
|
712
857
|
() => agentRef.current.call<RuntimeAssemblyView>("getRuntimeAssembly", []),
|
|
713
858
|
[],
|
|
714
859
|
);
|
|
860
|
+
const getRuntimeBinding = useCallback(
|
|
861
|
+
<Binding,>() => agentRef.current.call<Binding>("getRuntimeBinding", []),
|
|
862
|
+
[],
|
|
863
|
+
);
|
|
864
|
+
const readChatMetadata = useCallback(
|
|
865
|
+
<Metadata,>() => agentRef.current.call<Metadata>("readChatMetadata", []),
|
|
866
|
+
[],
|
|
867
|
+
);
|
|
868
|
+
const renameChat = useCallback(
|
|
869
|
+
<Metadata,>(title: string) =>
|
|
870
|
+
agentRef.current.call<Metadata>("renameChat", [title]),
|
|
871
|
+
[],
|
|
872
|
+
);
|
|
873
|
+
const rebindRuntime = useCallback(
|
|
874
|
+
<Input, Result,>(input: Input) =>
|
|
875
|
+
agentRef.current.call<Result>("rebindRuntime", [input]),
|
|
876
|
+
[],
|
|
877
|
+
);
|
|
715
878
|
const updateConfig = useCallback(
|
|
716
879
|
<Change,>(command: unknown) =>
|
|
717
880
|
agentRef.current.call<RuntimeConfigUpdateResult<Change>>(
|
|
@@ -730,15 +893,21 @@ export function useUniversalAgentChat(
|
|
|
730
893
|
|
|
731
894
|
return {
|
|
732
895
|
respondToolInteraction,
|
|
896
|
+
decideApproval,
|
|
733
897
|
getRuntimeAssembly,
|
|
898
|
+
getRuntimeBinding,
|
|
899
|
+
readChatMetadata,
|
|
900
|
+
renameChat,
|
|
901
|
+
rebindRuntime,
|
|
734
902
|
updateConfig,
|
|
735
903
|
reloadRuntime,
|
|
736
904
|
messages: visibleMessages,
|
|
737
905
|
status,
|
|
906
|
+
connectionStatus,
|
|
907
|
+
connectionError,
|
|
738
908
|
runtimeLoad: facetState?.runtimeLoad,
|
|
739
909
|
isStreaming,
|
|
740
|
-
error:
|
|
741
|
-
connectionError ?? undefined,
|
|
910
|
+
error: presentationError,
|
|
742
911
|
sendText,
|
|
743
912
|
steerText,
|
|
744
913
|
enqueueText,
|