@blade-hq/agent-react 2610.0.0-beta.29 → 2610.0.0-beta.30

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/README.md CHANGED
@@ -158,6 +158,8 @@ await replay.exitToAutonomous() // 退出回放,之后真的运
158
158
 
159
159
  `ChatView` 的普通展示模式不显示只供诊断的项目说明、可用能力和当前工作环境。需要自建诊断界面时,可直接使用独立的 `ContextCard`,属性见 `ContextCardProps`;状态到文案的映射来自 agent-client 的 `getContextDisplayState`,不需要接入方重复判断。底层数据类型为 `ContextProjectionData` / `ContextProjectionFields`,展示结果为 `ContextDisplayState`,并保留 `ContextAction`、`ContextSourceInfo` 和 `contextProjectionData` 给 headless 消费方。
160
160
 
161
+ 记忆引用提示可通过 `MemoryRefsHint` 展示,`collectMemoryRefs` 用于从一轮消息中聚合引用,保证宿主自定义消息布局与 SDK 默认界面保持一致。
162
+
161
163
  `onFollowupInteraction` 使用 `FollowupInteractionEvent`,覆盖下一步建议展示/采纳、成果展示/打开/下载以及结果评分。SDK 不内置 PostHog 等分析厂商;宿主回调抛错也不会中断用户点击或下载。
162
164
  文件成果以文件名为文本的标准下载链接展示;Vue / 纯 HTML 使用的 `<blade-chat>` 与 `ChatView` 行为一致。
163
165
 
@@ -1,4 +1,4 @@
1
- import type { AskUserAnswerData, ChatMessage } from "@blade-hq/agent-client";
1
+ import type { AskUserAnswerData, ChatMessage, MemoryRefInfo } from "@blade-hq/agent-client";
2
2
  import { type ToolCallRenderer } from "./ToolCallBlock";
3
3
  interface Props {
4
4
  messages: ChatMessage[];
@@ -23,4 +23,8 @@ export declare function getExecutionDurationMs({ messages, isStreaming, now, }:
23
23
  }): number;
24
24
  /** 一轮助手回复:执行过程默认折叠,最终正文始终显示在摘要下方。 */
25
25
  export declare function AssistantTurnBlock({ messages, isStreaming, askAnswers, onAnswer, sessionStatus, toolCallRenderer, sessionId, }: Props): import("react/jsx-runtime").JSX.Element;
26
+ export declare function collectMemoryRefs(messages: ChatMessage[]): MemoryRefInfo[];
27
+ export declare function MemoryRefsHint({ refs }: {
28
+ refs: MemoryRefInfo[];
29
+ }): import("react/jsx-runtime").JSX.Element;
26
30
  export {};
@@ -38,6 +38,7 @@ declare const BladeAgent: {
38
38
  sortComputers: typeof agentClient.sortComputers;
39
39
  ModelsResource: typeof agentClient.ModelsResource;
40
40
  buildMessageContent: typeof agentClient.buildMessageContent;
41
+ chatErrorForDisplay: typeof agentClient.chatErrorForDisplay;
41
42
  contentPreview: typeof agentClient.contentPreview;
42
43
  extractTextAttachments: typeof agentClient.extractTextAttachments;
43
44
  getFileParts: typeof agentClient.getFileParts;
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export type { ReplayMismatchPromptProps } from "./components/ReplayMismatchPromp
24
24
  export type { ToolCallRenderer } from "./components/ToolCallBlock";
25
25
  export { ContextCard } from "./components/ContextCard";
26
26
  export type { ContextCardProps } from "./components/ContextCard";
27
+ export { collectMemoryRefs, MemoryRefsHint } from "./components/AssistantTurnBlock";
27
28
  export { isAgentComputerCommand, isAgentComputerToolCall, classifyAgentComputerLaunchOutcome, } from "./lib/agent-computer-command";
28
29
  export type { AgentComputerLaunchOutcome } from "./lib/agent-computer-command";
29
30
  export * from "@blade-hq/agent-client";
package/dist/index.js CHANGED
@@ -1221,6 +1221,9 @@ function ReplayMismatchPrompt({ mismatch, className }) {
1221
1221
  );
1222
1222
  }
1223
1223
 
1224
+ // src/components/ChatSurface.tsx
1225
+ import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
1226
+
1224
1227
  // src/components/ChatInput.tsx
1225
1228
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1226
1229
  function isImeCompositionKey(event) {
@@ -2708,6 +2711,32 @@ function getLastContentMessage(messages) {
2708
2711
  }
2709
2712
  return null;
2710
2713
  }
2714
+ function getOrderedMessageParts(message, toolCalls) {
2715
+ const blocks = message.blocks ?? [];
2716
+ if (!blocks.some((block) => block.type === "text") || !blocks.some((block) => block.type === "tool_use")) return [];
2717
+ const toolsById = new Map(toolCalls.map((toolCall) => [toolCall.id, toolCall]));
2718
+ const seenToolIds = /* @__PURE__ */ new Set();
2719
+ const parts = [];
2720
+ for (const [index, block] of blocks.entries()) {
2721
+ if (block.type === "text" && block.content != null && block.content !== "") {
2722
+ const content = Array.isArray(block.content) ? block.content : String(block.content);
2723
+ parts.push({ type: "text", key: `text-${index}`, content });
2724
+ }
2725
+ if (block.type !== "tool_use" || !block.tool_call_id) continue;
2726
+ const toolCall = toolsById.get(block.tool_call_id);
2727
+ if (!toolCall) continue;
2728
+ seenToolIds.add(toolCall.id);
2729
+ const previous = parts[parts.length - 1];
2730
+ if (previous?.type === "tools") previous.toolCalls.push(toolCall);
2731
+ else parts.push({ type: "tools", key: `tools-${index}`, toolCalls: [toolCall] });
2732
+ }
2733
+ const missingTools = toolCalls.filter((toolCall) => !seenToolIds.has(toolCall.id));
2734
+ if (seenToolIds.size === 0) return [];
2735
+ if (missingTools.length > 0) {
2736
+ parts.push({ type: "tools", key: "tools-missing", toolCalls: missingTools });
2737
+ }
2738
+ return parts;
2739
+ }
2711
2740
  function findLatestReasoningMessageIndex(messages) {
2712
2741
  for (let index = messages.length - 1; index >= 0; index -= 1) {
2713
2742
  if (messages[index].reasoning) return index;
@@ -2883,6 +2912,13 @@ function AssistantTurnBlock({
2883
2912
  (message) => message.status === "failed" && !hasRenderableMessageContent(message)
2884
2913
  );
2885
2914
  const finalMessage = getLastContentMessage(messages);
2915
+ const turnToolCalls = messages.flatMap((message) => message.tool_calls ?? []);
2916
+ const finalOrderedParts = finalMessage ? getOrderedMessageParts(
2917
+ finalMessage,
2918
+ (finalMessage.tool_calls ?? []).filter(
2919
+ (toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion"
2920
+ )
2921
+ ) : [];
2886
2922
  const hasExecutionProcess = messages.some(
2887
2923
  (message) => message.reasoning || (message.tool_calls?.length ?? 0) > 0
2888
2924
  );
@@ -2921,6 +2957,7 @@ function AssistantTurnBlock({
2921
2957
  return () => window.clearInterval(timer);
2922
2958
  }, [hasLiveStartTime, isStreaming]);
2923
2959
  const liveExecutionDurationMs = isStreaming ? getExecutionDurationMs({ messages, isStreaming, now: clock }) : executionDurationMs;
2960
+ const memoryRefs = collectMemoryRefs(messages);
2924
2961
  if (!hasExecutionProcess) {
2925
2962
  return /* @__PURE__ */ jsxs9(
2926
2963
  "div",
@@ -2928,6 +2965,7 @@ function AssistantTurnBlock({
2928
2965
  "aria-busy": isStreaming || void 0,
2929
2966
  className: "blade-chat-assistant-turn flex flex-col gap-3",
2930
2967
  children: [
2968
+ memoryRefs.length > 0 ? /* @__PURE__ */ jsx11(MemoryRefsHint, { refs: memoryRefs }) : null,
2931
2969
  hasInterrupted && /* @__PURE__ */ jsx11("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
2932
2970
  hasFailedWithoutContent && /* @__PURE__ */ jsx11("div", { className: "ml-4 w-fit rounded-full border border-red-500/30 bg-red-500/10 px-2.5 py-1 text-[10px] font-medium text-red-400", children: "\u751F\u6210\u5931\u8D25" }),
2933
2971
  messages.map((message, index) => {
@@ -2957,6 +2995,7 @@ function AssistantTurnBlock({
2957
2995
  "aria-busy": isStreaming || void 0,
2958
2996
  className: "blade-chat-assistant-turn flex flex-col gap-3",
2959
2997
  children: [
2998
+ memoryRefs.length > 0 ? /* @__PURE__ */ jsx11(MemoryRefsHint, { refs: memoryRefs }) : null,
2960
2999
  hasInterrupted && /* @__PURE__ */ jsx11("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
2961
3000
  hasFailedWithoutContent && /* @__PURE__ */ jsx11("div", { className: "ml-4 w-fit rounded-full border border-red-500/30 bg-red-500/10 px-2.5 py-1 text-[10px] font-medium text-red-400", children: "\u751F\u6210\u5931\u8D25" }),
2962
3001
  /* @__PURE__ */ jsxs9("div", { className: "flex w-full items-start gap-2.5", children: [
@@ -3013,6 +3052,7 @@ function AssistantTurnBlock({
3013
3052
  const toolCalls = (message.tool_calls ?? []).filter(
3014
3053
  (toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion"
3015
3054
  );
3055
+ const orderedParts = getOrderedMessageParts(message, toolCalls);
3016
3056
  const showReasoning = !!message.reasoning && isStreaming && index === latestReasoningIndex;
3017
3057
  return /* @__PURE__ */ jsxs9(
3018
3058
  "div",
@@ -3020,7 +3060,21 @@ function AssistantTurnBlock({
3020
3060
  className: "flex flex-col gap-3",
3021
3061
  children: [
3022
3062
  showReasoning && message.reasoning ? /* @__PURE__ */ jsx11(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }) : null,
3023
- hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx11(
3063
+ orderedParts.length > 0 ? orderedParts.map(
3064
+ (part) => part.type === "text" ? /* @__PURE__ */ jsx11(
3065
+ AssistantMessageContent,
3066
+ {
3067
+ message: { ...message, content: part.content, tool_calls: turnToolCalls },
3068
+ sessionId,
3069
+ streaming: streamingThis,
3070
+ compact: true
3071
+ },
3072
+ part.key
3073
+ ) : /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-0.5", children: part.toolCalls.map((toolCall) => {
3074
+ const custom = toolCallRenderer?.(toolCall);
3075
+ return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx11("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(ExecutionToolRow, { toolCall }, toolCall.id);
3076
+ }) }, part.key)
3077
+ ) : hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx11(
3024
3078
  AssistantMessageContent,
3025
3079
  {
3026
3080
  message,
@@ -3029,7 +3083,7 @@ function AssistantTurnBlock({
3029
3083
  compact: true
3030
3084
  }
3031
3085
  ) : null,
3032
- toolCalls.length > 0 ? /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
3086
+ orderedParts.length === 0 && toolCalls.length > 0 ? /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
3033
3087
  const custom = toolCallRenderer?.(toolCall);
3034
3088
  return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx11("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(ExecutionToolRow, { toolCall }, toolCall.id);
3035
3089
  }) }) : null
@@ -3038,7 +3092,7 @@ function AssistantTurnBlock({
3038
3092
  message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
3039
3093
  );
3040
3094
  }) }) : null,
3041
- finalMessage ? /* @__PURE__ */ jsx11("div", { className: "ml-10", children: /* @__PURE__ */ jsx11(
3095
+ finalMessage && (effectiveMode === "compact" || finalOrderedParts.length === 0) ? /* @__PURE__ */ jsx11("div", { className: "ml-10", children: /* @__PURE__ */ jsx11(
3042
3096
  AssistantMessageContent,
3043
3097
  {
3044
3098
  message: finalMessage,
@@ -3063,6 +3117,33 @@ function AssistantTurnBlock({
3063
3117
  }
3064
3118
  );
3065
3119
  }
3120
+ function collectMemoryRefs(messages) {
3121
+ const refs = /* @__PURE__ */ new Map();
3122
+ for (const message of messages) {
3123
+ for (const ref of message.memory_refs ?? []) if (!refs.has(ref.id)) refs.set(ref.id, ref);
3124
+ }
3125
+ return [...refs.values()];
3126
+ }
3127
+ function MemoryRefsHint({ refs }) {
3128
+ const [expanded, setExpanded] = useState10(false);
3129
+ const label = refs.some((ref) => ref.skill_name) ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
3130
+ return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
3131
+ /* @__PURE__ */ jsxs9("button", { type: "button", onClick: () => setExpanded((value) => !value), className: "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[hsl(var(--primary)/0.22)] bg-[hsl(var(--primary)/0.07)] px-3 text-xs font-medium text-[hsl(var(--primary))]", children: [
3132
+ /* @__PURE__ */ jsx11(BookOpen, { size: 12 }),
3133
+ /* @__PURE__ */ jsxs9("span", { children: [
3134
+ label,
3135
+ "\uFF08",
3136
+ refs.length,
3137
+ "\uFF09"
3138
+ ] }),
3139
+ /* @__PURE__ */ jsx11(ChevronRight, { size: 10, className: cn("transition-transform", expanded && "rotate-90") })
3140
+ ] }),
3141
+ expanded ? /* @__PURE__ */ jsx11("div", { className: "mt-2 flex flex-col gap-2 rounded-xl border border-[hsl(var(--border)/0.8)] bg-[hsl(var(--muted)/0.28)] p-2.5", children: refs.map((ref) => /* @__PURE__ */ jsxs9("div", { className: "rounded-lg border border-[hsl(var(--border)/0.55)] bg-[hsl(var(--background)/0.72)] px-3 py-2.5 text-xs", children: [
3142
+ /* @__PURE__ */ jsx11("p", { className: "line-clamp-2 break-words leading-5", children: ref.content_preview }),
3143
+ ref.skill_name ? /* @__PURE__ */ jsx11("span", { className: "mt-1 inline-flex text-[10px] text-[hsl(var(--primary))]", children: ref.skill_name }) : null
3144
+ ] }, ref.id)) }) : null
3145
+ ] });
3146
+ }
3066
3147
  function AssistantMessageContent({
3067
3148
  message,
3068
3149
  sessionId,
@@ -3565,7 +3646,12 @@ function PostChatFollowupBlock({
3565
3646
  }
3566
3647
 
3567
3648
  // src/components/UserMessageBubble.tsx
3568
- import { getFileParts as getFileParts2, getImageParts as getImageParts2, getTextContent as getTextContent2 } from "@blade-hq/agent-client";
3649
+ import {
3650
+ chatErrorForDisplay,
3651
+ getFileParts as getFileParts2,
3652
+ getImageParts as getImageParts2,
3653
+ getTextContent as getTextContent2
3654
+ } from "@blade-hq/agent-client";
3569
3655
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3570
3656
  function isUserMessage(message) {
3571
3657
  return message.role === "user";
@@ -3610,8 +3696,8 @@ function ErrorMessageBlock({
3610
3696
  message,
3611
3697
  className
3612
3698
  }) {
3613
- const text = getTextContent2(message.content);
3614
- return /* @__PURE__ */ jsx14("div", { className: cn("blade-chat-error-row flex justify-center", className), children: /* @__PURE__ */ jsx14("div", { className: "blade-chat-error-block max-w-[85%] border-l-[3px] border-[hsl(var(--border))] px-4 py-1 text-sm leading-7 text-[hsl(var(--muted-foreground))]", children: text }) });
3699
+ const text = chatErrorForDisplay(getTextContent2(message.content));
3700
+ return /* @__PURE__ */ jsx14("div", { className: cn("blade-chat-error-row flex min-w-0 justify-start", className), children: /* @__PURE__ */ jsx14("div", { className: "blade-chat-error-block min-w-0 max-w-full whitespace-pre-wrap break-words border-l-[3px] border-[hsl(var(--border))] px-3 py-1 text-left text-sm leading-7 text-[hsl(var(--muted-foreground))] [overflow-wrap:anywhere]", children: text }) });
3615
3701
  }
3616
3702
 
3617
3703
  // src/components/MessageList.tsx
@@ -3987,7 +4073,7 @@ function ChatSurface({
3987
4073
  banner,
3988
4074
  errorMessage && /* @__PURE__ */ jsxs14("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
3989
4075
  /* @__PURE__ */ jsx16(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
3990
- /* @__PURE__ */ jsx16("span", { children: errorMessage })
4076
+ /* @__PURE__ */ jsx16("span", { className: "min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]", children: chatErrorForDisplay2(errorMessage) })
3991
4077
  ] }),
3992
4078
  slots?.header,
3993
4079
  /* @__PURE__ */ jsx16(
@@ -4557,9 +4643,11 @@ export {
4557
4643
  ContextCard,
4558
4644
  LlmChat,
4559
4645
  MarkdownContent,
4646
+ MemoryRefsHint,
4560
4647
  ReplayBar,
4561
4648
  ReplayMismatchPrompt,
4562
4649
  classifyAgentComputerLaunchOutcome,
4650
+ collectMemoryRefs,
4563
4651
  isAgentComputerCommand,
4564
4652
  isAgentComputerToolCall,
4565
4653
  useAgentSession,