@springbrand/chat-client 0.1.3-alpha.1 → 0.1.3-alpha.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/chat-client",
3
- "version": "0.1.3-alpha.1",
3
+ "version": "0.1.3-alpha.10",
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.19.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.1.3-alpha.2"
28
+ "@springbrand/agent-runtime": "0.2.0-alpha.15"
29
29
  },
30
30
  "scripts": {
31
31
  "typecheck": "tsc --noEmit"
@@ -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,7 @@ 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
+ requestedCapabilities?: readonly RequestedCapability[];
18
22
  turnStatus?: string;
19
23
  };
20
24
  type ChatMessage = UIMessage<ChatMessageMetadata>;
@@ -28,7 +32,7 @@ type ChatRuntimeLoadState =
28
32
  | { status: "idle"; available: false }
29
33
  | {
30
34
  status: "loading";
31
- phase: "config" | "plugins" | "mcp" | "pi";
35
+ phase: "config" | "assembly" | "mcp" | "pi";
32
36
  available: boolean;
33
37
  startedAt: number;
34
38
  updatedAt: number;
@@ -41,7 +45,7 @@ type ChatRuntimeLoadState =
41
45
  }
42
46
  | {
43
47
  status: "error";
44
- phase: "config" | "plugins" | "mcp" | "pi";
48
+ phase: "config" | "assembly" | "mcp" | "pi";
45
49
  available: boolean;
46
50
  startedAt: number;
47
51
  failedAt: number;
@@ -83,12 +87,47 @@ export interface ChatRuntime {
83
87
  runtimeLoad: ChatRuntimeLoadState | undefined;
84
88
  isStreaming: boolean;
85
89
  error: string | Error | undefined;
86
- sendText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
87
- steerText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
88
- enqueueText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
90
+ sendText: (
91
+ text: string,
92
+ files?: readonly FileUIPart[],
93
+ capabilities?: readonly RequestedCapability[],
94
+ ) => Promise<boolean>;
95
+ steerText: (
96
+ text: string,
97
+ files?: readonly FileUIPart[],
98
+ capabilities?: readonly RequestedCapability[],
99
+ ) => Promise<boolean>;
100
+ enqueueText: (
101
+ text: string,
102
+ files?: readonly FileUIPart[],
103
+ capabilities?: readonly RequestedCapability[],
104
+ ) => Promise<boolean>;
89
105
  steerQueued: (submissionId: string) => Promise<boolean>;
90
106
  cancelQueued: (submissionId: string) => Promise<boolean>;
91
107
  stop: () => Promise<void>;
108
+ /**
109
+ * 把用户对某张交互卡片的操作回写成那次工具调用的结果。
110
+ *
111
+ * 走的是这条已开的 WS 连接(`agent.call`),不是 HTTP —— ack 与随后流下来的
112
+ * `tool-output-available` chunk 同序到达,UI 状态不会倒挂。
113
+ */
114
+ respondToolInteraction: (
115
+ toolCallId: string,
116
+ response: unknown,
117
+ ) => Promise<boolean>;
118
+ /**
119
+ * 读取该 Session facet 当前真正装配出来的 Runtime。
120
+ *
121
+ * 复用这条已开的 WS 连接,调用方不必为一个只读控制面方法另建 `useAgent`。
122
+ *
123
+ * 与其它动作不同,失败会原样抛出:这是读取而不是提交,调用方需要知道是
124
+ * 装配未就绪还是连接断了,收敛成 `undefined` 会让调试面板无从显示原因。
125
+ */
126
+ getRuntimeAssembly: () => Promise<RuntimeAssemblyView>;
127
+ updateConfig: <Change = unknown>(
128
+ command: unknown,
129
+ ) => Promise<RuntimeConfigUpdateResult<Change>>;
130
+ reloadRuntime: () => Promise<void>;
92
131
  regenerate: () => void;
93
132
  canRetry: boolean;
94
133
  agentToolRuns: AgentToolRunState<ChatMessage["parts"][number]>[];
@@ -170,10 +209,18 @@ function markChatTurnPhase(
170
209
  function createChatMessage(
171
210
  text: string,
172
211
  files?: readonly FileUIPart[],
212
+ capabilities: readonly RequestedCapability[] = [],
173
213
  ): ChatMessage | null {
174
214
  const normalizedText = text.trim();
175
215
  if (!normalizedText && !files?.length) return null;
176
216
  const createdAt = Date.now();
217
+ const seen = new Set<string>();
218
+ const requestedCapabilities = capabilities.filter((capability) => {
219
+ const key = `${capability.kind}:${capability.name}`;
220
+ if (seen.has(key)) return false;
221
+ seen.add(key);
222
+ return true;
223
+ });
177
224
  return {
178
225
  id: nanoid(),
179
226
  role: "user",
@@ -187,6 +234,7 @@ function createChatMessage(
187
234
  createdAt,
188
235
  authorDisplayName: "You",
189
236
  messageSource: "Web",
237
+ ...(requestedCapabilities.length > 0 ? { requestedCapabilities } : {}),
190
238
  },
191
239
  };
192
240
  }
@@ -281,7 +329,7 @@ export function useUniversalAgentChat(
281
329
  // autonomous responses 纪律(CF 文档采纳 2026-07-05):框架已提供三个流态标志,
282
330
  // 透出供 UI 区分「服务端主动流 vs 用户发起流」+「恢复态」(与 stall 看门狗配套)。
283
331
  isServerStreaming,
284
- isRecovering,
332
+ isRecovering: sdkIsRecovering,
285
333
  isToolContinuation,
286
334
  connectionError,
287
335
  } = useAgentChat<RuntimeState, ChatMessage>({
@@ -294,6 +342,9 @@ export function useUniversalAgentChat(
294
342
  syncMessagesToServer: false,
295
343
  throttle: 100,
296
344
  });
345
+ // The SDK keeps its advisory recovery flag until terminal settlement; a
346
+ // live server stream proves the recovered Turn is executing again.
347
+ const isRecovering = sdkIsRecovering && !isServerStreaming;
297
348
  const visibleMessages = useMemo(
298
349
  () => mergeOptimisticMessages(messages, optimisticMessages),
299
350
  [messages, optimisticMessages],
@@ -547,8 +598,9 @@ export function useUniversalAgentChat(
547
598
  const sendText = useCallback(async (
548
599
  text: string,
549
600
  files?: readonly FileUIPart[],
601
+ capabilities?: readonly RequestedCapability[],
550
602
  ): Promise<boolean> => {
551
- const message = createChatMessage(text, files);
603
+ const message = createChatMessage(text, files, capabilities);
552
604
  return message
553
605
  ? submitMessage(message, "enqueue", "message")
554
606
  : false;
@@ -557,21 +609,28 @@ export function useUniversalAgentChat(
557
609
  const dispatchText = useCallback(async (
558
610
  text: string,
559
611
  files: readonly FileUIPart[] | undefined,
612
+ capabilities: readonly RequestedCapability[] | undefined,
560
613
  delivery: MessageDelivery,
561
614
  projection: "message" | "queue",
562
615
  ): Promise<boolean> => {
563
- const message = createChatMessage(text, files);
616
+ const message = createChatMessage(text, files, capabilities);
564
617
  return message ? submitMessage(message, delivery, projection) : false;
565
618
  }, [submitMessage]);
566
619
 
567
620
  const steerText = useCallback(
568
- (text: string, files?: readonly FileUIPart[]) =>
569
- dispatchText(text, files, "steer", "message"),
621
+ (
622
+ text: string,
623
+ files?: readonly FileUIPart[],
624
+ capabilities?: readonly RequestedCapability[],
625
+ ) => dispatchText(text, files, capabilities, "steer", "message"),
570
626
  [dispatchText],
571
627
  );
572
628
  const enqueueText = useCallback(
573
- (text: string, files?: readonly FileUIPart[]) =>
574
- dispatchText(text, files, "enqueue", "queue"),
629
+ (
630
+ text: string,
631
+ files?: readonly FileUIPart[],
632
+ capabilities?: readonly RequestedCapability[],
633
+ ) => dispatchText(text, files, capabilities, "enqueue", "queue"),
575
634
  [dispatchText],
576
635
  );
577
636
  const steerQueued = useCallback(async (submissionId: string) => {
@@ -664,7 +723,50 @@ export function useUniversalAgentChat(
664
723
  );
665
724
  }, [submitMessage]);
666
725
 
726
+ // 交互卡片的写回口。失败只返回 false —— 调用方(块组件)自己决定怎么提示;
727
+ // 权威状态永远来自 part 的 state,不来自这里的返回值。
728
+ const respondToolInteraction = useCallback(async (
729
+ toolCallId: string,
730
+ response: unknown,
731
+ ): Promise<boolean> => {
732
+ try {
733
+ const receipt = await agentRef.current.call<{ ok: boolean }>(
734
+ "respondToolInteraction",
735
+ [toolCallId, response],
736
+ );
737
+ return receipt.ok;
738
+ } catch {
739
+ return false;
740
+ }
741
+ }, []);
742
+
743
+ // 只读控制面:Runtime 尚未装配时由 Agent 侧 `ensureRuntimeReady` 负责等待,
744
+ // 这里不缓存结果 —— 换模型、加技能或 reload 之后调用方要拿到的是新装配。
745
+ const getRuntimeAssembly = useCallback(
746
+ () => agentRef.current.call<RuntimeAssemblyView>("getRuntimeAssembly", []),
747
+ [],
748
+ );
749
+ const updateConfig = useCallback(
750
+ <Change,>(command: unknown) =>
751
+ agentRef.current.call<RuntimeConfigUpdateResult<Change>>(
752
+ "updateConfig",
753
+ [command],
754
+ ),
755
+ [],
756
+ );
757
+ const reloadRuntime = useCallback(
758
+ () => agentRef.current.call<void>(
759
+ "reloadRuntime",
760
+ [undefined, { force: true }],
761
+ ),
762
+ [],
763
+ );
764
+
667
765
  return {
766
+ respondToolInteraction,
767
+ getRuntimeAssembly,
768
+ updateConfig,
769
+ reloadRuntime,
668
770
  messages: visibleMessages,
669
771
  status,
670
772
  runtimeLoad: facetState?.runtimeLoad,