【免费下载链接】BrowserOS
🌐 The open-source Agentic browser; alternative to ChatGPT Atlas, Perplexity Comet, Dia.
本篇技术指南以 BrowserOS Agent 仓库内置的 AI SDK 技能参考文档(common-errors.md)为主体,系统梳理 Vercel AI SDK 从旧版到新版演进过程中的全部破坏性变更与废弃 API,涵盖 generateText、streamText、useChat、工具定义、类型化消息渲染等核心场景。读完本文,你将掌握在新版 AI SDK(ai@6.0.34+)下写出正确、可编译、类型安全的 Agent 客户端与服务端代码,并能在 typecheck 失败时第一时间定位废弃 API 的替代写法。
为什么需要这样一份错误速查表
AI SDK 的 API 演进速度极快,且废弃往往是"静默"的:旧参数依旧被接受,但类型标注已变化、语义已改变,甚至在新版本中直接移除。BrowserOS Agent 仓库在 SKILL.md 中对开发 Agent 提出了明确约束:
- 不要信任训练数据中的 AI SDK 知识:内置规则直接声明"Everything you know about the AI SDK is outdated or wrong"(关于 AI SDK 的一切既有认知都已过时或错误),要求开发者始终以
node_modules/ai/docs/与node_modules/ai/src/中的当前 API 为准; - typecheck 失败时优先查错误速查表:SKILL.md 明确要求"Before searching source code, grep Common Errors for the failing property or function name",即遇到编译错误先在本速查表中搜索报错属性名,多数类型错误都源于本文档记录的废弃 API;
- useChat 变化尤其剧烈:SKILL.md 特别提示"
useChathas changed significantly - check Common Errors before writing client code",写任何客户端代码前必须先过一遍本表。
这份 common-errors.md 就是仓库为 Agent 开发者准备的"避坑清单",按生成类 API、流式响应、前端 Hook、消息渲染四类场景组织。下文逐条展开,并补充仓库内的真实应用佐证。
一、文本与结构化生成 API 的迁移
1. maxTokens → maxOutputTokens
旧版 generateText / streamText 使用 maxTokens 限制输出 token 数,新版统一更名为 maxOutputTokens,与 maxInputTokens、maxTotalTokens 形成一套完整的预算控制体系:
// ❌ Incorrect
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
maxTokens: 512, // deprecated: use `maxOutputTokens` instead
prompt: 'Write a short story',
});
// ✅ Correct
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
maxOutputTokens: 512,
prompt: 'Write a short story',
});
2. maxSteps → stopWhen: stepCountIs(n)
maxSteps 控制 Agent 在工具调用循环中最多执行多少轮。新版将其泛化为 stopWhen 谓词机制,stepCountIs(n) 是其中最常用的内置谓词:
// ❌ Incorrect
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
tools: { weather },
maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead
prompt: 'What is the weather in NYC?',
});
// ✅ Correct
import { generateText, stepCountIs } from 'ai';
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
tools: { weather },
stopWhen: stepCountIs(5),
prompt: 'What is the weather in NYC?',
});
stopWhen 比固定步数更灵活——你可以传入自定义谓词函数,根据消息历史、工具执行结果等任意条件决定何时终止循环,stepCountIs 只是最常用的快捷方式。
3. 工具定义:parameters → inputSchema
工具定义中的参数声明字段从 parameters 更名为 inputSchema,语义上更贴近"这是模型的输入模式":
// ❌ Incorrect
const weatherTool = tool({
description: 'Get weather for a location',
parameters: z.object({
// deprecated: use `inputSchema` instead
location: z.string(),
}),
execute: async ({ location }) => ({ location, temp: 72 }),
});
// ✅ Correct
const weatherTool = tool({
description: 'Get weather for a location',
inputSchema: z.object({
location: z.string(),
}),
execute: async ({ location }) => ({ location, temp: 72 }),
});
这一约定在 BrowserOS Agent 的类型安全 Agent 参考文档(type-safe-agents.md)中被严格遵守:所有工具定义均使用 inputSchema: z.object({...}),配合 z.string().describe('City name') 为模型提供字段说明。
4. generateObject → generateText + output 选项
generateObject 整体废弃,统一收敛到 generateText 的 output 选项。结构化输出的方式从"独立函数 + schema"变为"生成文本 + 输出描述":
// ❌ Deprecated
import { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead
const result = await generateObject({
// deprecated function
model: 'anthropic/claude-opus-4.5',
schema: z.object({
// deprecated: use `Output.object({ schema })` instead
recipe: z.object({
name: z.string(),
ingredients: z.array(z.string()),
}),
}),
prompt: 'Generate a recipe for chocolate cake',
});
// ✅ Correct
import { generateText, Output } from 'ai';
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(z.string()),
}),
}),
}),
prompt: 'Generate a recipe for chocolate cake',
});
console.log(result.output); // typed object
迁移后 result.output 直接是经过 Zod schema 校验的强类型对象,无需再手动 JSON.parse。
5. 手动 JSON 解析 → generateText + output
过去常见的"让模型输出 JSON 字符串再手动 parse"的写法(脆弱且容易因模型输出多余文本而失败)同样是废弃模式。正确的做法是把 schema 交给 Output.object:
// ❌ Incorrect
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
prompt: `Extract the user info as JSON: { "name": string, "age": number }
Input: John is 25 years old`,
});
const parsed = JSON.parse(result.text);
// ✅ Correct
import { generateText, Output } from 'ai';
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
output: Output.object({
schema: z.object({
name: z.string(),
age: z.number(),
}),
}),
prompt: 'Extract the user info: John is 25 years old',
});
console.log(result.output); // { name: 'John', age: 25 }
新版由 SDK 内部保证输出严格符合 schema(含重试机制),result.output 的类型被推断为 { name: string; age: number }。
6. output 的其他形态:Output.array、Output.choice、Output.json
Output 命名空间不止 object,还提供另外三种结构化输出形态:
// Output.array - for generating arrays of items
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
output: Output.array({
element: z.object({
city: z.string(),
country: z.string(),
}),
}),
prompt: 'List 5 capital cities',
});
// Output.choice - for selecting from predefined options
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
output: Output.choice({
options: ['positive', 'negative', 'neutral'] as const,
}),
prompt: 'Classify the sentiment: I love this product!',
});
// Output.json - for untyped JSON output
const result = await generateText({
model: 'anthropic/claude-opus-4.5',
output: Output.json(),
prompt: 'Return some JSON data',
});
| 形态 | 用途 | 关键选项 |
|---|---|---|
Output.object | 强类型结构化对象 | schema: z.ZodType |
Output.array | 生成同构元素数组 | element: z.ZodType |
Output.choice | 从预定义候选中单选 | options: readonly string[](配合 as const 获得字面量类型) |
Output.json | 不校验结构的任意 JSON | 无 |
选择原则:数据形状固定用 object/array 换取类型安全;分类打标任务用 choice;只需要"合法 JSON"不在乎结构时用 json。
二、流式响应与 useChat 前端集成
7. toDataStreamResponse → toUIMessageStreamResponse
当前端使用 useChat 时,服务端流式响应的封装必须从 toDataStreamResponse() 换成 toUIMessageStreamResponse()。UI 消息流格式专为聊天 UI 组件设计,能正确处理消息状态(包括工具执行中间态、错误态等):
// ❌ Incorrect (when using useChat)
const result = streamText({
// config
});
return result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse
// ✅ Correct
const result = streamText({
// config
});
return result.toUIMessageStreamResponse();
8. useChat 不再托管输入框状态
这是 useChat 最显著的行为变更:输入状态(input、handleInputChange)已从 Hook 内部移除,必须由组件自己用 useState 管理;提交方式从 handleSubmit 改为显式的 sendMessage({ text });API 端点配置从 api 字符串改为 transport: new DefaultChatTransport({ api }):
// ❌ Deprecated
import { useChat } from '@ai-sdk/react';
export default function Page() {
const {
input, // deprecated: manage input state manually with useState
handleInputChange, // deprecated: use custom onChange handler
handleSubmit, // deprecated: use sendMessage() instead
} = useChat({
api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead
});
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
);
}
// ✅ Correct
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function Page() {
const [input, setInput] = useState('');
const { sendMessage } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const handleSubmit = e => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={e => setInput(e.target.value)} />
<button type="submit">Send</button>
</form>
);
}
BrowserOS 仓库的真实实践:BrowserOS Agent 扩展的聊天会话层在 chat-session.hooks.ts 中完全采用了新式写法——用 useRef 持有 new DefaultChatTransport<UIMessage>({...}) 单例,通过 prepareSendMessagesRequest 回调在发送前注入浏览器上下文(活动标签页、选中文案、启用的 MCP 服务器、用户系统提示词、历史会话等),再通过 prepareReconnectToStreamRequest 支持断线重连;useChat({ transport, onError, onFinish }) 只保留 messages、sendMessage、status、resumeStream、regenerate 等派生能力,输入框状态由调用方组件自行持有。这正是"状态外置 + transport 配置"迁移模式在生产级代码中的完整呈现。
三、类型化消息与工具 part 渲染
9. tool-invocation → tool-{toolName}(类型化工具 part)
useChat 渲染消息时,工具的 part.type 从通用字符串 'tool-invocation' 变为按工具名生成的类型化判别值 'tool-{toolName}'(如 'tool-getWeatherInformation')。同时属性名同步变更:part.args → part.input、part.result → part.output。part.type 作为判别联合的 discriminant,TypeScript 能据此为每个工具推断出对应的输入/输出类型:
// ❌ Incorrect - using generic tool-invocation
{
message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
case 'tool-invocation': // deprecated: use typed tool parts instead
return (
<pre key={`${message.id}-${i}`}>
{JSON.stringify(part.toolInvocation, null, 2)}
</pre>
);
}
});
}
// ✅ Correct - using typed tool parts (recommended)
{
message.parts.map(part => {
switch (part.type) {
case 'text':
return part.text;
case 'tool-askForConfirmation':
// handle askForConfirmation tool
break;
case 'tool-getWeatherInformation':
// handle getWeatherInformation tool
break;
}
});
}
// ✅ Alternative - using isToolUIPart as a catch-all
import { isToolUIPart } from 'ai';
{
message.parts.map(part => {
if (part.type === 'text') {
return part.text;
}
if (isToolUIPart(part)) {
// handle any tool part generically
return (
<div key={part.toolCallId}>
{part.toolName}: {part.state}
</div>
);
}
});
}
若要实现端到端类型安全,可进一步使用 InferAgentUIMessage<typeof agent> 从 Agent 定义推断 UIMessage 类型,并配合 UIToolInvocation<typeof tool> 将每个工具渲染拆成独立组件——完整模式见 type-safe-agents.md。
10. 状态依赖属性访问(TS18048 保护)
类型化 tool part 的 input 与 output 属性并非在所有状态下都可用:流式过程中 input 可能尚为 undefined,执行结束前 output 必为 undefined。TypeScript 会在未做状态检查时直接报 TS18048: 'part.input' is possibly 'undefined':
// ❌ Incorrect - input may be undefined during streaming
// TS18048: 'part.input' is possibly 'undefined'
if (part.type === 'tool-getWeather') {
const location = part.input.location;
}
// ✅ Correct - check for input-available or output-available
if (
part.type === 'tool-getWeather' &&
(part.state === 'input-available' || part.state === 'output-available')
) {
const location = part.input.location;
}
// ❌ Incorrect - output is only available after execution
// TS18048: 'part.output' is possibly 'undefined'
if (part.type === 'tool-getWeather') {
const weather = part.output;
}
// ✅ Correct - check for output-available
if (part.type === 'tool-getWeather' && part.state === 'output-available') {
const location = part.input.location;
const weather = part.output;
}
规则一句话:先检查 part.state,再访问 part.input / part.output,状态守卫同时充当类型收窄。
11. 三个逐项属性重命名
part.toolInvocation.args→part.input:
// ❌ Incorrect
if (part.type === 'tool-invocation') {
// deprecated: use `part.input` on typed tool parts instead
const location = part.toolInvocation.args.location;
}
// ✅ Correct
if (
part.type === 'tool-getWeather' &&
(part.state === 'input-available' || part.state === 'output-available')
) {
const location = part.input.location;
}
part.toolInvocation.result→part.output:
// ❌ Incorrect
if (part.type === 'tool-invocation') {
// deprecated: use `part.output` on typed tool parts instead
const weather = part.toolInvocation.result;
}
// ✅ Correct
if (part.type === 'tool-getWeather' && part.state === 'output-available') {
const weather = part.output;
}
part.toolInvocation.toolCallId→part.toolCallId:
// ❌ Incorrect
if (part.type === 'tool-invocation') {
// deprecated: use `part.toolCallId` on typed tool parts instead
const id = part.toolInvocation.toolCallId;
}
// ✅ Correct
if (part.type === 'tool-getWeather') {
const id = part.toolCallId;
}
12. 工具调用状态机重命名
toolInvocation.state 的三种旧状态值被整体重命名,语义更贴近"数据何时可见"而非"调用进行到哪一步":
| 旧值(deprecated) | 新值 | 含义 |
|---|---|---|
partial-call | input-streaming | 工具参数正在流式到达 |
call | input-available | 参数完整,工具待执行/执行中 |
result | output-available | 工具已执行完毕,输出可用 |
// ❌ Incorrect
switch (part.toolInvocation.state) {
case 'partial-call': // deprecated: use `input-streaming` instead
return <div>Loading...</div>;
case 'call': // deprecated: use `input-available` instead
return <div>Executing...</div>;
case 'result': // deprecated: use `output-available` instead
return <div>Done</div>;
}
// ✅ Correct
switch (part.state) {
case 'input-streaming':
return <div>Loading...</div>;
case 'input-available':
return <div>Executing...</div>;
case 'output-available':
return <div>Done</div>;
}
BrowserOS 仓库的真实实践:扩展 UI 的工具渲染组件 tool.tsx 将 ToolUIPart['state'] 的完整状态集映射为徽章与图标——input-streaming → Pending、input-available → Running、approval-requested → Awaiting Approval、approval-responded → Responded、output-available → Completed、output-error → Error、output-denied → Denied。可见新版状态机实际上包含七个状态(在 input-streaming 与 output-available 之间还插入了人工审批相关的 approval-requested / approval-responded 以及异常态 output-error / output-denied),本文速查表列出的是最常用的三个基础迁移对照,完整枚举以 ai 包导出的 ToolUIPart['state'] 类型为准。
13. addToolResult → addToolOutput
人工确认类工具(如审批流)在前端注入结果时,函数名从 addToolResult 改为 addToolOutput,参数同步更新:result → output,且新增必填的 tool 字段(工具名):
// ❌ Incorrect
addToolResult({
// deprecated: use `addToolOutput` instead
toolCallId: part.toolInvocation.toolCallId,
result: 'Yes, confirmed.', // deprecated: use `output` instead
});
// ✅ Correct
addToolOutput({
tool: 'askForConfirmation',
toolCallId: part.toolCallId,
output: 'Yes, confirmed.',
});
14. createAgentUIStreamResponse:messages → uiMessages
服务端返回 Agent 驱动的 UI 消息流时,入参字段名从 messages 改为 uiMessages,语义上明确区分"底层模型消息"与"面向 UI 渲染的消息":
// ❌ Incorrect
return createAgentUIStreamResponse({
agent: myAgent,
messages, // incorrect: use `uiMessages` instead
});
// ✅ Correct
return createAgentUIStreamResponse({
agent: myAgent,
uiMessages: messages,
});
四、在 BrowserOS Agent 中如何落地这套规范
BrowserOS Agent 的 Agent 服务端在 ai-sdk-agent.ts 中使用 ToolLoopAgent 构建 Agent 实例(该文件同时承载 ToolLoopAgent 的类型定义引用,reasoning-config.ts 还基于 ToolLoopAgent 的部分设置对象做按供应商的推理参数配置);客户端侧聊天与会话逻辑集中在 chat-session.hooks.ts,工具渲染组件位于 ai-elements/tool.tsx。将这些真实用法与本文速查表对照,可以得到一套可复用的排错工作流:
- 编译失败先查表:TypeScript 报错属性名若出现在本文档任一标题中(如
maxTokens、parameters、tool-invocation、handleInputChange),直接按对应小节迁移,无需再翻源码; - 结构从
Output走:任何"要模型返回 JSON"的需求,优先Output.object/Output.array/Output.choice,不要手动拼接 prompt 提示词加JSON.parse; - 客户端坚持新 Hook 形态:
useChat只做消息流消费,输入状态用useState外置,传输配置走DefaultChatTransport,提交走sendMessage; - 渲染工具 part 认准类型化判别:用
tool-{toolName}+part.state守卫 +part.input/part.output,需要通用处理时用isToolUIPart兜底; - 服务端流式响应统一
toUIMessageStreamResponse/createAgentUIStreamResponse({ uiMessages }),保证useChat前端能正确还原工具中间态与错误态。
结语
AI SDK 的本次大规模重构本质上是在做两件事:把"生成文本"与"结构化输出"统一到 generateText + Output 一条主线上,把"聊天 UI"收敛到类型化消息 part 与显式传输配置的 useChat 新形态上。common-errors.md 这份速查表的价值在于把散落在升级日志与类型错误里的迁移知识压缩成 14 条可直接对照的规则。在 BrowserOS Agent 仓库中,无论是编写新的聊天界面、接入新的 MCP 工具,还是排查 useChat 相关类型错误,都可以先按本文顺序自查一遍——多数问题在表格对照阶段即可解决。
【免费下载链接】BrowserOS
🌐 The open-source Agentic browser; alternative to ChatGPT Atlas, Perplexity Comet, Dia.
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



