@springbrand/chat-client 0.1.3-alpha.2 → 0.1.3-alpha.20
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 -16
- package/src/index.ts +3 -0
- package/src/use-universal-agent-chat.ts +250 -35
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.20",
|
|
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.27"
|
|
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 }),
|
|
@@ -88,30 +91,18 @@ export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
|
|
|
88
91
|
await inbox.call("archiveChat", [id, archived]);
|
|
89
92
|
}, [inbox]);
|
|
90
93
|
|
|
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
94
|
return {
|
|
104
95
|
chats: inbox.state?.chats ?? snapshot ?? [],
|
|
105
96
|
synced: inbox.state !== undefined || snapshot !== undefined,
|
|
106
97
|
connected: inbox.identified,
|
|
107
98
|
workspaceRev: inbox.state?.workspaceRev ?? 0,
|
|
99
|
+
inboxState: inbox.state,
|
|
108
100
|
createChat,
|
|
109
101
|
forkChat,
|
|
110
102
|
renameChat,
|
|
111
103
|
deleteChat,
|
|
112
104
|
pinChat,
|
|
113
105
|
archiveChat,
|
|
114
|
-
reloadChatRuntime,
|
|
115
106
|
};
|
|
116
107
|
}
|
|
117
108
|
|
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,29 @@
|
|
|
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,
|
|
25
|
+
type RuntimeAssemblyView,
|
|
26
|
+
type RuntimeConfigUpdateResult,
|
|
11
27
|
type RuntimeQueuedSubmission,
|
|
12
28
|
type RuntimeState,
|
|
13
29
|
} from "@springbrand/agent-runtime/contracts";
|
|
@@ -15,9 +31,16 @@ import { createSafeUIMessageAgentConnection } from "./ui-message-stream-guard";
|
|
|
15
31
|
import { CURRENT_INBOX_AGENT_PATH } from "./chat-sessions";
|
|
16
32
|
|
|
17
33
|
type ChatMessageMetadata = Record<string, unknown> & {
|
|
34
|
+
error?: string;
|
|
35
|
+
requestedCapabilities?: readonly RequestedCapability[];
|
|
18
36
|
turnStatus?: string;
|
|
19
37
|
};
|
|
20
38
|
type ChatMessage = UIMessage<ChatMessageMetadata>;
|
|
39
|
+
export type ChatConnectionStatus =
|
|
40
|
+
| "connecting"
|
|
41
|
+
| "connected"
|
|
42
|
+
| "rejected"
|
|
43
|
+
| "error";
|
|
21
44
|
type FailedDispatch = {
|
|
22
45
|
message: ChatMessage;
|
|
23
46
|
delivery: MessageDelivery;
|
|
@@ -28,7 +51,7 @@ type ChatRuntimeLoadState =
|
|
|
28
51
|
| { status: "idle"; available: false }
|
|
29
52
|
| {
|
|
30
53
|
status: "loading";
|
|
31
|
-
phase: "config" | "
|
|
54
|
+
phase: "config" | "assembly" | "mcp" | "pi";
|
|
32
55
|
available: boolean;
|
|
33
56
|
startedAt: number;
|
|
34
57
|
updatedAt: number;
|
|
@@ -41,7 +64,7 @@ type ChatRuntimeLoadState =
|
|
|
41
64
|
}
|
|
42
65
|
| {
|
|
43
66
|
status: "error";
|
|
44
|
-
phase: "config" | "
|
|
67
|
+
phase: "config" | "assembly" | "mcp" | "pi";
|
|
45
68
|
available: boolean;
|
|
46
69
|
startedAt: number;
|
|
47
70
|
failedAt: number;
|
|
@@ -80,15 +103,60 @@ type ChatTurn = {
|
|
|
80
103
|
export interface ChatRuntime {
|
|
81
104
|
messages: ChatMessage[];
|
|
82
105
|
status: ChatStatus;
|
|
106
|
+
connectionStatus: ChatConnectionStatus;
|
|
107
|
+
connectionError: AgentConnectionError | undefined;
|
|
83
108
|
runtimeLoad: ChatRuntimeLoadState | undefined;
|
|
84
109
|
isStreaming: boolean;
|
|
85
110
|
error: string | Error | undefined;
|
|
86
|
-
sendText: (
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
sendText: (
|
|
112
|
+
text: string,
|
|
113
|
+
files?: readonly FileUIPart[],
|
|
114
|
+
capabilities?: readonly RequestedCapability[],
|
|
115
|
+
) => Promise<boolean>;
|
|
116
|
+
steerText: (
|
|
117
|
+
text: string,
|
|
118
|
+
files?: readonly FileUIPart[],
|
|
119
|
+
capabilities?: readonly RequestedCapability[],
|
|
120
|
+
) => Promise<boolean>;
|
|
121
|
+
enqueueText: (
|
|
122
|
+
text: string,
|
|
123
|
+
files?: readonly FileUIPart[],
|
|
124
|
+
capabilities?: readonly RequestedCapability[],
|
|
125
|
+
) => Promise<boolean>;
|
|
89
126
|
steerQueued: (submissionId: string) => Promise<boolean>;
|
|
90
127
|
cancelQueued: (submissionId: string) => Promise<boolean>;
|
|
91
128
|
stop: () => Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* 把用户对某张交互卡片的操作回写成那次工具调用的结果。
|
|
131
|
+
*
|
|
132
|
+
* 走的是这条已开的 WS 连接(`agent.call`),不是 HTTP —— ack 与随后流下来的
|
|
133
|
+
* `tool-output-available` chunk 同序到达,UI 状态不会倒挂。
|
|
134
|
+
*/
|
|
135
|
+
respondToolInteraction: (
|
|
136
|
+
toolCallId: string,
|
|
137
|
+
response: unknown,
|
|
138
|
+
) => Promise<boolean>;
|
|
139
|
+
decideApproval: (
|
|
140
|
+
executionId: string,
|
|
141
|
+
decision: ApprovalDecision,
|
|
142
|
+
) => Promise<{ ok: boolean }>;
|
|
143
|
+
/**
|
|
144
|
+
* 读取该 Session facet 当前真正装配出来的 Runtime。
|
|
145
|
+
*
|
|
146
|
+
* 复用这条已开的 WS 连接,调用方不必为一个只读控制面方法另建 `useAgent`。
|
|
147
|
+
*
|
|
148
|
+
* 与其它动作不同,失败会原样抛出:这是读取而不是提交,调用方需要知道是
|
|
149
|
+
* 装配未就绪还是连接断了,收敛成 `undefined` 会让调试面板无从显示原因。
|
|
150
|
+
*/
|
|
151
|
+
getRuntimeAssembly: () => Promise<RuntimeAssemblyView>;
|
|
152
|
+
getRuntimeBinding: <Binding = unknown>() => Promise<Binding>;
|
|
153
|
+
readChatMetadata: <Metadata = unknown>() => Promise<Metadata>;
|
|
154
|
+
renameChat: <Metadata = unknown>(title: string) => Promise<Metadata>;
|
|
155
|
+
rebindRuntime: <Input = unknown, Result = unknown>(input: Input) => Promise<Result>;
|
|
156
|
+
updateConfig: <Change = unknown>(
|
|
157
|
+
command: unknown,
|
|
158
|
+
) => Promise<RuntimeConfigUpdateResult<Change>>;
|
|
159
|
+
reloadRuntime: () => Promise<void>;
|
|
92
160
|
regenerate: () => void;
|
|
93
161
|
canRetry: boolean;
|
|
94
162
|
agentToolRuns: AgentToolRunState<ChatMessage["parts"][number]>[];
|
|
@@ -108,6 +176,8 @@ type SharedChatConnectionOptions = {
|
|
|
108
176
|
protocols?: string | string[];
|
|
109
177
|
inboxAgent?: string;
|
|
110
178
|
sessionAgent?: string;
|
|
179
|
+
/** Host projection used only until the selected Session sends its first state. */
|
|
180
|
+
initialTurnActive?: boolean;
|
|
111
181
|
};
|
|
112
182
|
|
|
113
183
|
export type ChatConnectionOptions = SharedChatConnectionOptions & (
|
|
@@ -115,6 +185,51 @@ export type ChatConnectionOptions = SharedChatConnectionOptions & (
|
|
|
115
185
|
| { inboxName?: never; basePath?: string }
|
|
116
186
|
);
|
|
117
187
|
|
|
188
|
+
type SessionAgentConnection = ReturnType<typeof useAgent<RuntimeState>>;
|
|
189
|
+
type UniversalAgentChatContextValue = {
|
|
190
|
+
chatId: string;
|
|
191
|
+
connection: ChatConnectionOptions;
|
|
192
|
+
agent: SessionAgentConnection;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const UniversalAgentChatContext = createContext<UniversalAgentChatContextValue | null>(null);
|
|
196
|
+
|
|
197
|
+
export function UniversalAgentChatProvider({
|
|
198
|
+
chatId,
|
|
199
|
+
connection = {},
|
|
200
|
+
fallback = null,
|
|
201
|
+
children,
|
|
202
|
+
}: {
|
|
203
|
+
chatId: string;
|
|
204
|
+
connection?: ChatConnectionOptions;
|
|
205
|
+
fallback?: ReactNode;
|
|
206
|
+
children: ReactNode;
|
|
207
|
+
}) {
|
|
208
|
+
const inboxAgent = connection.inboxAgent ?? "Inbox";
|
|
209
|
+
const agent = useAgent<RuntimeState>({
|
|
210
|
+
agent: inboxAgent,
|
|
211
|
+
...(connection.inboxName === undefined
|
|
212
|
+
? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
|
|
213
|
+
: { name: connection.inboxName }),
|
|
214
|
+
...(connection.host === undefined ? {} : { host: connection.host }),
|
|
215
|
+
...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
|
|
216
|
+
connectionTimeout: 6_000,
|
|
217
|
+
sub: [{
|
|
218
|
+
agent: connection.sessionAgent ?? "UniversalAgent",
|
|
219
|
+
name: chatId,
|
|
220
|
+
}],
|
|
221
|
+
});
|
|
222
|
+
const value = useMemo(
|
|
223
|
+
() => ({ chatId, connection, agent }),
|
|
224
|
+
[agent, chatId, connection],
|
|
225
|
+
);
|
|
226
|
+
return createElement(
|
|
227
|
+
UniversalAgentChatContext.Provider,
|
|
228
|
+
{ value },
|
|
229
|
+
createElement(Suspense, { fallback }, children),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
118
233
|
type ChatTurnTiming = {
|
|
119
234
|
chatId: string;
|
|
120
235
|
messageId: string;
|
|
@@ -170,10 +285,18 @@ function markChatTurnPhase(
|
|
|
170
285
|
function createChatMessage(
|
|
171
286
|
text: string,
|
|
172
287
|
files?: readonly FileUIPart[],
|
|
288
|
+
capabilities: readonly RequestedCapability[] = [],
|
|
173
289
|
): ChatMessage | null {
|
|
174
290
|
const normalizedText = text.trim();
|
|
175
291
|
if (!normalizedText && !files?.length) return null;
|
|
176
292
|
const createdAt = Date.now();
|
|
293
|
+
const seen = new Set<string>();
|
|
294
|
+
const requestedCapabilities = capabilities.filter((capability) => {
|
|
295
|
+
const key = `${capability.kind}:${capability.name}`;
|
|
296
|
+
if (seen.has(key)) return false;
|
|
297
|
+
seen.add(key);
|
|
298
|
+
return true;
|
|
299
|
+
});
|
|
177
300
|
return {
|
|
178
301
|
id: nanoid(),
|
|
179
302
|
role: "user",
|
|
@@ -187,6 +310,7 @@ function createChatMessage(
|
|
|
187
310
|
createdAt,
|
|
188
311
|
authorDisplayName: "You",
|
|
189
312
|
messageSource: "Web",
|
|
313
|
+
...(requestedCapabilities.length > 0 ? { requestedCapabilities } : {}),
|
|
190
314
|
},
|
|
191
315
|
};
|
|
192
316
|
}
|
|
@@ -233,10 +357,12 @@ function loadInitialMessages(
|
|
|
233
357
|
* sub 数组由客户端 kebab 化;服务端按 ctx.exports 反解回 CamelCase className,
|
|
234
358
|
* 与 Inbox.onBeforeSubAgent 的严格门卫(hasSubAgent)对齐。
|
|
235
359
|
*/
|
|
236
|
-
export function useUniversalAgentChat(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
360
|
+
export function useUniversalAgentChat(): ChatRuntime {
|
|
361
|
+
const context = useContext(UniversalAgentChatContext);
|
|
362
|
+
if (!context) {
|
|
363
|
+
throw new Error("useUniversalAgentChat must be used within UniversalAgentChatProvider");
|
|
364
|
+
}
|
|
365
|
+
const { chatId, connection, agent } = context;
|
|
240
366
|
const [optimisticMessages, setOptimisticMessages] =
|
|
241
367
|
useState<ChatMessage[]>([]);
|
|
242
368
|
const [optimisticQueued, setOptimisticQueued] =
|
|
@@ -245,19 +371,6 @@ export function useUniversalAgentChat(
|
|
|
245
371
|
const [canRetry, setCanRetry] = useState(false);
|
|
246
372
|
const failedDispatchRef = useRef<FailedDispatch | undefined>(undefined);
|
|
247
373
|
const activeSubmissionIdRef = useRef<string | undefined>(undefined);
|
|
248
|
-
const inboxAgent = connection.inboxAgent ?? "Inbox";
|
|
249
|
-
const agent = useAgent<RuntimeState>({
|
|
250
|
-
agent: inboxAgent,
|
|
251
|
-
...(connection.inboxName === undefined
|
|
252
|
-
? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
|
|
253
|
-
: { name: connection.inboxName }),
|
|
254
|
-
...(connection.host === undefined ? {} : { host: connection.host }),
|
|
255
|
-
...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
|
|
256
|
-
sub: [{
|
|
257
|
-
agent: connection.sessionAgent ?? "UniversalAgent",
|
|
258
|
-
name: chatId,
|
|
259
|
-
}],
|
|
260
|
-
});
|
|
261
374
|
const chatAgent = useMemo(
|
|
262
375
|
() => createSafeUIMessageAgentConnection(agent),
|
|
263
376
|
[agent],
|
|
@@ -281,9 +394,9 @@ export function useUniversalAgentChat(
|
|
|
281
394
|
// autonomous responses 纪律(CF 文档采纳 2026-07-05):框架已提供三个流态标志,
|
|
282
395
|
// 透出供 UI 区分「服务端主动流 vs 用户发起流」+「恢复态」(与 stall 看门狗配套)。
|
|
283
396
|
isServerStreaming,
|
|
284
|
-
isRecovering,
|
|
397
|
+
isRecovering: sdkIsRecovering,
|
|
285
398
|
isToolContinuation,
|
|
286
|
-
connectionError,
|
|
399
|
+
connectionError: sdkConnectionError,
|
|
287
400
|
} = useAgentChat<RuntimeState, ChatMessage>({
|
|
288
401
|
agent: chatAgent,
|
|
289
402
|
getInitialMessages: ({ url }) =>
|
|
@@ -294,6 +407,9 @@ export function useUniversalAgentChat(
|
|
|
294
407
|
syncMessagesToServer: false,
|
|
295
408
|
throttle: 100,
|
|
296
409
|
});
|
|
410
|
+
// The SDK keeps its advisory recovery flag until terminal settlement; a
|
|
411
|
+
// live server stream proves the recovered Turn is executing again.
|
|
412
|
+
const isRecovering = sdkIsRecovering && !isServerStreaming;
|
|
297
413
|
const visibleMessages = useMemo(
|
|
298
414
|
() => mergeOptimisticMessages(messages, optimisticMessages),
|
|
299
415
|
[messages, optimisticMessages],
|
|
@@ -313,6 +429,18 @@ export function useUniversalAgentChat(
|
|
|
313
429
|
? "ready"
|
|
314
430
|
: sdkStatus;
|
|
315
431
|
const sdkError = stoppedByUser || hasTerminalMessage ? undefined : error;
|
|
432
|
+
const connectionError = sdkConnectionError ?? undefined;
|
|
433
|
+
const connectionStatus: ChatConnectionStatus = connectionError
|
|
434
|
+
? connectionError.code === 1008 ? "rejected" : "error"
|
|
435
|
+
: agent.identified ? "connected" : "connecting";
|
|
436
|
+
const runtimeError = dispatchError ?? sdkError ?? connectionError;
|
|
437
|
+
const runtimeErrorMessage = runtimeError instanceof Error
|
|
438
|
+
? runtimeError.message
|
|
439
|
+
: runtimeError;
|
|
440
|
+
const presentationError = latestTurnStatus === "error" &&
|
|
441
|
+
latestMessage?.metadata?.error === runtimeErrorMessage
|
|
442
|
+
? undefined
|
|
443
|
+
: runtimeError;
|
|
316
444
|
// RPC admissions bypass useChat's request lifecycle. Project the durable
|
|
317
445
|
// Turn here so presentation stays correct without a second send path.
|
|
318
446
|
const authoritativeStatus =
|
|
@@ -324,9 +452,12 @@ export function useUniversalAgentChat(
|
|
|
324
452
|
? "streaming"
|
|
325
453
|
: "submitted"
|
|
326
454
|
: normalizedSdkStatus;
|
|
455
|
+
const hydratingTurnActive = facetState === undefined &&
|
|
456
|
+
connection.initialTurnActive === true;
|
|
327
457
|
const status = dispatchError
|
|
328
458
|
? "error"
|
|
329
|
-
: optimisticMessages.length > 0
|
|
459
|
+
: (optimisticMessages.length > 0 || hydratingTurnActive) &&
|
|
460
|
+
authoritativeStatus === "ready"
|
|
330
461
|
? "submitted"
|
|
331
462
|
: authoritativeStatus;
|
|
332
463
|
const messagesRef = useRef(messages);
|
|
@@ -418,7 +549,8 @@ export function useUniversalAgentChat(
|
|
|
418
549
|
isToolContinuation;
|
|
419
550
|
const pendingLocalTurn = optimisticMessages.length > 0 &&
|
|
420
551
|
(status === "submitted" || status === "streaming");
|
|
421
|
-
const turnActive =
|
|
552
|
+
const turnActive = hydratingTurnActive ||
|
|
553
|
+
Boolean(turn?.activeSubmissionId) ||
|
|
422
554
|
pendingLocalTurn ||
|
|
423
555
|
(authoritativeTurn === undefined && sdkTurnActive);
|
|
424
556
|
const canSteer = turnActive &&
|
|
@@ -547,8 +679,9 @@ export function useUniversalAgentChat(
|
|
|
547
679
|
const sendText = useCallback(async (
|
|
548
680
|
text: string,
|
|
549
681
|
files?: readonly FileUIPart[],
|
|
682
|
+
capabilities?: readonly RequestedCapability[],
|
|
550
683
|
): Promise<boolean> => {
|
|
551
|
-
const message = createChatMessage(text, files);
|
|
684
|
+
const message = createChatMessage(text, files, capabilities);
|
|
552
685
|
return message
|
|
553
686
|
? submitMessage(message, "enqueue", "message")
|
|
554
687
|
: false;
|
|
@@ -557,21 +690,28 @@ export function useUniversalAgentChat(
|
|
|
557
690
|
const dispatchText = useCallback(async (
|
|
558
691
|
text: string,
|
|
559
692
|
files: readonly FileUIPart[] | undefined,
|
|
693
|
+
capabilities: readonly RequestedCapability[] | undefined,
|
|
560
694
|
delivery: MessageDelivery,
|
|
561
695
|
projection: "message" | "queue",
|
|
562
696
|
): Promise<boolean> => {
|
|
563
|
-
const message = createChatMessage(text, files);
|
|
697
|
+
const message = createChatMessage(text, files, capabilities);
|
|
564
698
|
return message ? submitMessage(message, delivery, projection) : false;
|
|
565
699
|
}, [submitMessage]);
|
|
566
700
|
|
|
567
701
|
const steerText = useCallback(
|
|
568
|
-
(
|
|
569
|
-
|
|
702
|
+
(
|
|
703
|
+
text: string,
|
|
704
|
+
files?: readonly FileUIPart[],
|
|
705
|
+
capabilities?: readonly RequestedCapability[],
|
|
706
|
+
) => dispatchText(text, files, capabilities, "steer", "message"),
|
|
570
707
|
[dispatchText],
|
|
571
708
|
);
|
|
572
709
|
const enqueueText = useCallback(
|
|
573
|
-
(
|
|
574
|
-
|
|
710
|
+
(
|
|
711
|
+
text: string,
|
|
712
|
+
files?: readonly FileUIPart[],
|
|
713
|
+
capabilities?: readonly RequestedCapability[],
|
|
714
|
+
) => dispatchText(text, files, capabilities, "enqueue", "queue"),
|
|
575
715
|
[dispatchText],
|
|
576
716
|
);
|
|
577
717
|
const steerQueued = useCallback(async (submissionId: string) => {
|
|
@@ -664,13 +804,88 @@ export function useUniversalAgentChat(
|
|
|
664
804
|
);
|
|
665
805
|
}, [submitMessage]);
|
|
666
806
|
|
|
807
|
+
// 交互卡片的写回口。失败只返回 false —— 调用方(块组件)自己决定怎么提示;
|
|
808
|
+
// 权威状态永远来自 part 的 state,不来自这里的返回值。
|
|
809
|
+
const respondToolInteraction = useCallback(async (
|
|
810
|
+
toolCallId: string,
|
|
811
|
+
response: unknown,
|
|
812
|
+
): Promise<boolean> => {
|
|
813
|
+
try {
|
|
814
|
+
const receipt = await agentRef.current.call<{ ok: boolean }>(
|
|
815
|
+
"respondToolInteraction",
|
|
816
|
+
[toolCallId, response],
|
|
817
|
+
);
|
|
818
|
+
return receipt.ok;
|
|
819
|
+
} catch {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
}, []);
|
|
823
|
+
const decideApproval = useCallback(
|
|
824
|
+
(executionId: string, decision: ApprovalDecision) =>
|
|
825
|
+
agentRef.current.call<{ ok: boolean }>(
|
|
826
|
+
"decideApproval",
|
|
827
|
+
[executionId, decision],
|
|
828
|
+
),
|
|
829
|
+
[],
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// 只读控制面:Runtime 尚未装配时由 Agent 侧 `ensureRuntimeReady` 负责等待,
|
|
833
|
+
// 这里不缓存结果 —— 换模型、加技能或 reload 之后调用方要拿到的是新装配。
|
|
834
|
+
const getRuntimeAssembly = useCallback(
|
|
835
|
+
() => agentRef.current.call<RuntimeAssemblyView>("getRuntimeAssembly", []),
|
|
836
|
+
[],
|
|
837
|
+
);
|
|
838
|
+
const getRuntimeBinding = useCallback(
|
|
839
|
+
<Binding,>() => agentRef.current.call<Binding>("getRuntimeBinding", []),
|
|
840
|
+
[],
|
|
841
|
+
);
|
|
842
|
+
const readChatMetadata = useCallback(
|
|
843
|
+
<Metadata,>() => agentRef.current.call<Metadata>("readChatMetadata", []),
|
|
844
|
+
[],
|
|
845
|
+
);
|
|
846
|
+
const renameChat = useCallback(
|
|
847
|
+
<Metadata,>(title: string) =>
|
|
848
|
+
agentRef.current.call<Metadata>("renameChat", [title]),
|
|
849
|
+
[],
|
|
850
|
+
);
|
|
851
|
+
const rebindRuntime = useCallback(
|
|
852
|
+
<Input, Result,>(input: Input) =>
|
|
853
|
+
agentRef.current.call<Result>("rebindRuntime", [input]),
|
|
854
|
+
[],
|
|
855
|
+
);
|
|
856
|
+
const updateConfig = useCallback(
|
|
857
|
+
<Change,>(command: unknown) =>
|
|
858
|
+
agentRef.current.call<RuntimeConfigUpdateResult<Change>>(
|
|
859
|
+
"updateConfig",
|
|
860
|
+
[command],
|
|
861
|
+
),
|
|
862
|
+
[],
|
|
863
|
+
);
|
|
864
|
+
const reloadRuntime = useCallback(
|
|
865
|
+
() => agentRef.current.call<void>(
|
|
866
|
+
"reloadRuntime",
|
|
867
|
+
[undefined, { force: true }],
|
|
868
|
+
),
|
|
869
|
+
[],
|
|
870
|
+
);
|
|
871
|
+
|
|
667
872
|
return {
|
|
873
|
+
respondToolInteraction,
|
|
874
|
+
decideApproval,
|
|
875
|
+
getRuntimeAssembly,
|
|
876
|
+
getRuntimeBinding,
|
|
877
|
+
readChatMetadata,
|
|
878
|
+
renameChat,
|
|
879
|
+
rebindRuntime,
|
|
880
|
+
updateConfig,
|
|
881
|
+
reloadRuntime,
|
|
668
882
|
messages: visibleMessages,
|
|
669
883
|
status,
|
|
884
|
+
connectionStatus,
|
|
885
|
+
connectionError,
|
|
670
886
|
runtimeLoad: facetState?.runtimeLoad,
|
|
671
887
|
isStreaming,
|
|
672
|
-
error:
|
|
673
|
-
connectionError ?? undefined,
|
|
888
|
+
error: presentationError,
|
|
674
889
|
sendText,
|
|
675
890
|
steerText,
|
|
676
891
|
enqueueText,
|