@my-agent/react
v0.2.1
Published
`@my-agent/react` 是 React SDK 的运行时包,负责客户端、上下文、主题容器、钩子与绑定运行时的 Agent 组合层组件。纯展示原语由 `@my-agent/ui` 提供。
Readme
@my-agent/react
@my-agent/react 是 React SDK 的运行时包,负责客户端、上下文、主题容器、钩子与绑定运行时的 Agent 组合层组件。纯展示原语由 @my-agent/ui 提供。
当前导出
@my-agent/react:根入口,透出客户端、Provider 与组件公共 API。@my-agent/react/client:客户端、认证、错误与基础类型。@my-agent/react/providers:SdkRoot、ThreadProvider、useThreadContext、AgentProvider与运行时钩子。@my-agent/react/components:MessageList、PromptInput与消息渲染扩展能力。@my-agent/react/styles.css:React SDK 与底层展示原语共用的样式入口,宿主应用按需引入一次。
运行时职责
createAgentClient 负责连接 Agent API,并在发送请求时解析认证头。ThreadProvider 负责线程列表、活跃线程和创建线程;开启 autoCreate 后,没有可用线程时会自动创建一个新线程。AgentProvider 绑定当前线程并装配 AI SDK 对话运行时。MessageList 和 PromptInput 只依赖 AgentProvider 提供的运行时上下文。
线程历史来自 Mastra server 的标准 memory 接口。服务端根据认证后的 Mastra resourceId 隔离线程,客户端不会也不应该传入 resourceId。
匿名请求可以继续发送 baseUrl 下的 /chat 消息,但不能跨刷新恢复“当前用户线程”。需要恢复线程历史时,宿主应用必须提供能映射到稳定 Mastra resource 的认证信息。
使用方式
如果宿主应用只需要展示原语,请直接从 @my-agent/ui 引入。例如:
import { Button, Message } from "@my-agent/ui"需要接入 Agent runtime 时,使用客户端、线程 Provider、Agent Provider 与基础聊天组件组合:
import "@my-agent/react/styles.css"
import { createAgentClient } from "@my-agent/react/client"
import { MessageList, PromptInput } from "@my-agent/react/components"
import {
AgentProvider,
SdkRoot,
ThreadProvider,
useThreadContext,
} from "@my-agent/react/providers"
const client = createAgentClient({
baseUrl: "https://example.com/api",
})
function ChatRuntime() {
const { activeThreadId, isLoading } = useThreadContext()
if (isLoading) {
return <div>正在加载对话...</div>
}
if (!activeThreadId) {
return <div>暂无可用对话</div>
}
return (
<AgentProvider
agentId="generalAgent"
client={client}
threadId={activeThreadId}
>
<MessageList />
<PromptInput />
</AgentProvider>
)
}
export function App() {
return (
<SdkRoot>
<ThreadProvider agentId="generalAgent" autoCreate client={client}>
<ChatRuntime />
</ThreadProvider>
</SdkRoot>
)
}客户端工具
AgentProvider 可以接收 clientTools,让模型在对话中请求浏览器侧能力。客户端工具有几个约束:
- 工具名必须使用
client_命名空间,例如client_get_selection。不带此前缀的工具不会作为客户端工具声明或渲染器接入;实际请求仍会受服务端工具名规则约束。 baseUrl下的/chat只做协议边界校验:最多 20 个客户端工具,clientTools声明 JSON 序列化后不超过 50,000 字符,超过限制的请求会被拒绝。inputSchema必须是 JSON Schema object,用来描述模型传给工具的入参;根 schema 必须是type: "object"。schema 内部关键字会透传给 AI SDK,不由baseUrl下的/chat维护 JSON Schema 子集白名单。execute在浏览器中执行,返回值必须是 JSON 可序列化值,例如null、布尔值、数字、字符串、数组或普通对象。render是可选的,可以自定义工具消息在MessageList中的展示方式。createClientToolRenderers(clientTools)会把工具定义里的render转成MessageRendererProvider可识别的toolRenderers。toolRenderers按数组顺序匹配。消费者显式传入的渲染器放在createClientToolRenderers(clientTools)之前时,可以覆盖工具自带渲染器。
import "@my-agent/react/styles.css"
import type { ReactNode } from "react"
import { createAgentClient } from "@my-agent/react/client"
import {
createClientToolRenderers,
MessageList,
MessageRendererProvider,
PromptInput,
type ToolPart,
type ToolRenderer,
} from "@my-agent/react/components"
import {
AgentProvider,
SdkRoot,
ThreadProvider,
useThreadContext,
type ClientToolRegistry,
} from "@my-agent/react/providers"
const client = createAgentClient({
baseUrl: "https://example.com/api",
})
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null
}
function getSelectionText(part: ToolPart): string | undefined {
if (!("output" in part) || !isRecord(part.output)) {
return undefined
}
return typeof part.output.text === "string" ? part.output.text : undefined
}
const clientTools = {
client_get_selection: {
description: "读取用户当前选中的文本",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
execute() {
return {
text: window.getSelection()?.toString() ?? "",
}
},
render({ part }): ReactNode {
if (part.state === "input-streaming" || part.state === "input-available") {
return <div>正在读取浏览器选区...</div>
}
if (part.state === "output-error") {
const errorText =
"errorText" in part && typeof part.errorText === "string"
? part.errorText
: "未知错误"
return <div>读取浏览器选区失败:{errorText}</div>
}
const text = getSelectionText(part)
return (
<div>
已读取浏览器选区:{text === undefined || text === "" ? "(空)" : text}
</div>
)
},
},
} satisfies ClientToolRegistry
const toolRenderers: readonly ToolRenderer[] = [
{
id: "host:client_get_selection",
canRender: ({ toolName }) => toolName === "client_get_selection",
render: () => <div>宿主应用覆盖了选区工具展示</div>,
},
...createClientToolRenderers(clientTools),
]
function ChatRuntime() {
const { activeThreadId, isLoading } = useThreadContext()
if (isLoading) {
return <div>正在加载对话...</div>
}
if (!activeThreadId) {
return <div>暂无可用对话</div>
}
return (
<AgentProvider
agentId="generalAgent"
client={client}
clientTools={clientTools}
threadId={activeThreadId}
>
<MessageRendererProvider toolRenderers={toolRenderers}>
<MessageList />
<PromptInput />
</MessageRendererProvider>
</AgentProvider>
)
}
export function App() {
return (
<SdkRoot>
<ThreadProvider agentId="generalAgent" autoCreate client={client}>
<ChatRuntime />
</ThreadProvider>
</SdkRoot>
)
}构建
pnpm --filter @my-agent/react build