Code Mode 输出
声明的输出契约¶
OpenClaw 工具可以为放在 AgentToolResult.details 中的结构化值声明 outputSchema。这对 Code Mode 和 Tool Search 很有用;它不是 provider 原生的工具响应 schema,也不会改变工具的直接暴露方式。
对于使用 defineToolPlugin 创建的工具,请在 parameters 旁边声明 schema:
import { Type } from "typebox";
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
const Shipment = Type.Object(
{
id: Type.String(),
paid: Type.Boolean(),
tons: Type.Number(),
},
{ additionalProperties: false },
);
export default defineToolPlugin({
id: "shipping",
name: "Shipping",
description: "Shipment tools.",
tools: (tool) => [
tool({
name: "shipping_list",
description: "List shipments.",
parameters: Type.Object({}),
outputSchema: Type.Array(Shipment),
execute: async () => loadShipments(),
}),
],
});
对于 api.registerTool(...) 或工厂工具,请将相同的 outputSchema 属性放到返回的 AnyAgentTool 对象上。
当前内置契约包括 agents_list、agents_wait、apply_patch、automations、conversations_list、conversations_send、conversations_turn、edit、openclaw、process、read、screen、sessions_history、sessions_list、sessions_search、sessions_send、session_status、suggest_task、terminal、web_fetch 和 web_search。
automations 声明了调度器状态、分页任务摘要、完整任务、运行历史和操作结果。当活动运行仍拥有其会话时,成功的移除可以包含 sessionCleanup: "pending"。任务已被移除;会话清理会在该运行结束时进行。
process 声明了会话列表、轮询与日志输出、输入确认以及失败情况。在组合结果之前,请通过 API.read("tools/automations.d.ts") 或 API.read("tools/process.d.ts") 阅读它们的声明。这些声明根据输入 action 选择输出:automations({ action: "list" }) 返回任务页,而 action: "status" 返回调度器状态。进程列表仍会包含其真实的失败结果;在读取 result.sessions 之前,请检查 result.status === "failed"。声明式与普通自动化创建仍是 add 操作下两种不同的可能结果。
精确透传工具可以复用其所属协议的 schema,而不必复制仅面向模型的契约。例如,会话工具暴露了与 conversations.list、conversations.send 和 conversations.turn 所使用的相同的 Gateway 结果 schema;web_fetch 拥有一个工具本地 schema,其提示暴露稳定的元数据、文本、缓存状态和嵌套 spill 元数据;web_search 将其精确规范化的 results/answer/error/raw 联合类型声明为完整的快速索引提示。文件系统契约返回结构化的读取文本、图像、截断以及可选未找到结果;显式编辑返回变更状态以及 diff/patch 数据;apply-patch 返回路径摘要。当规范的每日笔记(memory/YYYY-MM-DD.md)缺失时,即使省略了 optional,也会返回可选的 not_found 结果;除非显式提供 optional: true,其他缺失路径会抛出异常。当快速索引声明了这些字段时,一个 cell 即可组合发现与交付,而无需单独的检查轮次:
const listed = await conversations_list({ query: "build bot" });
const target = listed.conversations.find((item) => item.label === "Build bot");
if (!target) throw new Error("conversation not found");
return await conversations_send({
conversationRef: target.conversationRef,
message: "Build finished.",
});
嵌套调用仍使用正常的工具策略、钩子和审批。如果完整契约是精确的,但对有界快速索引来说过大,它仍可通过可调用句柄的 describe() 获得,箭头保持为 -> ?。
完整的原生工具声明也可按需通过 API.list("tools") 和 API.read("tools/<callableName>.d.ts") 获取,并使用与发现过程相同的最终可调用名称。这些声明是根据有效输入 schema 和可信输出 schema 生成的,而不是来自缩短后的快速索引。原生列表条目包含路径;API.read 生成文件后,bytes 才可用。文件不会被主动注入到每个 guest VM 中。未知输出和不支持的 schema 叶子节点保留为 unknown;客户端 schema 不会被提升为可信声明。对于 TypeScript 无法表达的约束,运行时验证仍然是事实来源。原生声明仅当有效 schema 接受运行时规范化所用的空对象时,才允许省略输入参数;真正必需的输入仍然必需。
已知输出声明描述的是完整且规范化后的工具值。程序数据准入会拒绝过大的回复,而不是用成功的截断标记来替代。声明具有独立的大小、深度和遍历边界;当这些边界需要未知类型时,请使用 describe() 获取原始 schema。读取声明不会执行工具,也不会对 cell 进行类型检查;它们用于指导 agent 的 JavaScript 组合。
契约规则是严格的:
- 描述精确的、JSON 兼容的
details值,而不是渲染后的content块或 provider 信封。 - 包含所有不会抛出的成功或错误变体。当工具没有稳定的结构化结果时,省略
outputSchema。 - 使用
{ additionalProperties: false }闭合对象层,以提供完整的快速索引提示。开放、过大或其他不完整的 schema 仍可通过句柄describe()获取,但不能启用单轮字段使用。 - OpenClaw 会在运行工具之前编译 schema,然后在常规工具钩子之后、catalog 调用返回之前验证最终的
details。无效 schema 无法运行工具;不匹配会失败,且不会打印该值。 - 紧凑提示是确定且有界的。当紧凑提示不足时,句柄
describe()会暴露完整可信 schema。 - 已安装的插件代码已经是可信的本地代码。远程 MCP 和客户端元数据仍然不可信,不能选择加入这些快速索引提示。
插件编写细节请参阅工具插件。
MCP 目录条目保留在生成的 MCP 命名空间下。面向任务的 catalog.search(...) 同样返回 MCP 句柄,这些句柄调用相同的命名空间路径并标识其声明文件。MCP 条目不会出现在裸全局变量、catalog.all() 和受信任的快速索引中。TypeScript 风格的声明文件可通过只读的 API 虚拟文件接口获取,因此代理无需将 MCP schema 添加到提示词中即可检查 MCP 签名:
const files = await API.list("mcp");
const githubApi = await API.read("mcp/github.d.ts");
const issue = await MCP.github.createIssue({
owner: "openclaw",
repo: "openclaw",
title: "Investigate gateway logs",
});
const snapshot = await MCP.chromeDevtools.takeSnapshot({ output: "markdown" });
const resource = await MCP.docs.resources.read({ uri: "memo://one" });
const prompt = await MCP.docs.prompts.get({
name: "brief",
arguments: { topic: "release" },
});
API.read("mcp/<server>.d.ts") 返回从 MCP 工具元数据推断出的紧凑声明:
interface McpToolResult {
content: unknown[];
structuredContent?: unknown;
isError?: boolean;
}
interface McpResourcesListResult {
resources: unknown[];
nextCursor?: string;
}
interface McpResourcesReadResult {
contents: unknown[];
}
interface McpPromptsListResult {
prompts: unknown[];
nextCursor?: string;
}
interface McpPromptsGetResult {
messages: unknown[];
description?: string;
}
declare namespace MCP.github {
/** Return this TypeScript-style API header. */
function $api(toolName?: string, options?: { schema?: boolean }): Promise<McpApiHeader>;
/**
* Create a GitHub issue.
* @param owner Repository owner
* @param repo Repository name
* @param title Issue title
*/
function createIssue(input: {
owner: string;
repo: string;
title: string;
body?: string;
}): Promise<McpToolResult>;
}
字典输入保留其值类型。可空枚举和标记为 nullable: true 的字段包含 null,除非显式枚举将其排除。带默认值的顶层字段可在调用中省略。这些声明近似于 JSON Schema;对于 TypeScript 无法表达的约束,可使用 MCP.<server>.$api("<tool>", { schema: true }) 检查原始 schema。
MCP 工具调用返回其原始的 JSON 安全内容块,包括块注解和块级 _meta,以及在提供时包含的顶层 structuredContent 和 isError。顶层 MCP _meta 和私有应用元数据永远不会进入 guest 环境。带有 isError: true 的 MCP 应用失败仍会作为结果解析,因此 guest 代码可以检查并从中恢复。资源和提示词操作则返回其原生 MCP 形状:resources.list() 返回 resources,resources.read() 返回 contents,prompts.list() 返回 prompts,prompts.get() 返回 messages 以及可选的 description。
声明文件是虚拟的,不会写入工作区或状态目录。对于每次代码模式的 exec 调用,OpenClaw 会构建运行作用域内的工具目录,保留可见的 MCP 条目,渲染 mcp/index.d.ts 以及每个可见服务器对应的 mcp/<server>.d.ts,并将这个小型只读表注入所选执行器的 worker 中。Guest 代码只能看到 API 对象:API.list(prefix?) 返回文件元数据,API.read(path) 返回所选的声明内容。未知路径和 ./.. 段会被拒绝。
这使大型 MCP schema 不会进入模型提示词:代理从 exec 工具描述中得知虚拟 API 的存在,只读取所需的声明文件,然后使用一个对象参数调用 MCP.<server>.<tool>()。MCP.<server>.$api() 仍可作为程序内单工具 schema 响应的内联回退方案。
Guest 运行时永远不会直接看到宿主对象。输入和输出以 JSON 兼容值的形式跨桥传输,并带有显式大小上限。
传递给 results.save 的工具参数和值必须能序列化为 JSON。BigInt、循环引用和抛出异常的序列化钩子会使受影响的调用失败,而不是静默替换其数据。捕获错误并显式转换该值;已有的保存结果保持不变。
输入相关的输出¶
输出依赖于字符串输入属性的工具可以使用标准 TypeBox 或 JSON Schema API 为其现有的 outputSchema 添加注解。从相同的变体中派生联合类型和映射:
import { Type } from "typebox";
const variants = Object.entries({
list: Type.Object({ items: Type.Array(Type.String()) }, { additionalProperties: false }),
status: Type.Object({ ready: Type.Boolean() }, { additionalProperties: false }),
});
const outputSchema = Type.Union(
variants.map(([, schema]) => schema),
{
"x-openclaw-input-discriminator": {
version: 1,
inputProperty: "action",
mapping: Object.fromEntries(variants.map(([value], index) => [value, index])),
},
},
);
将此 schema 赋值给工具现有的 outputSchema 属性。每个分支必须包含该输入值的所有非抛出结果,包括失败情况。完整的联合类型和选择器来自这些相同的分支。该注解不会改变输入验证,也不授权任何操作。
生成的声明会为字面量输入选择对应的结果。输入值的联合类型返回其结果的联合类型。缺失、宽泛或未映射的值保留完整的输出联合类型。该属性可以使用任意名称;action 是 automations 和 process 使用的约定。
目录执行会在分派前编译完整的 schema。当钩子更改选择器时,结果必须满足实际准备好的输入分支以及原始调用方声明的分支。兼容的重写和默认值/别名准备仍然有效;不兼容的结果无法到达按原始操作类型化的代码。根级 JSON Schema 约束保持不变。包含 $ref、$dynamicRef 或 $recursiveRef 的 schema,或超出有界结构检查范围的 schema,保留其原始的伞形验证和声明。这保留了递归引用语义。生成的注释会标识这种保守回退。操作声明同样共享原始的 32,768 字符输出限额;超大的特化声明回退到有界的伞形声明。这些回退不影响特定于操作的 automations 和 process 契约。
序列化模式包含普通的 anyOf 分支,以及带有 { version: 1, inputProperty, mapping } 的注解 x-openclaw-input-discriminator。该映射将字符串值与分支索引关联。请将联合类型与映射保持在一起,不要独立更新其中任何一项。SDK 静态元数据和目录描述保留该注解。不支持该注解的宿主保留普通联合类型。支持的宿主会在执行工具之前拒绝格式错误的注解和不支持的版本。不支持的声明形式仍保持为 unknown;该注解不会绕过模式检查或类型限制。
输出 API¶
text(value)将人类可读的输出追加到output数组中。json(value)在 JSON 兼容序列化后,将一个结构化输出项追加到数组中。- 客户代码的最终返回值会成为
completed结果中的value。
在发出异步值或将包含它们的数组或普通对象返回之前,请先等待它们。未等待的 Promise 会以诊断字符串的形式出现,其中包含 await 和 Promise.all 的使用建议。例如,使用
return await Promise.all(handles.map((tool) => tool.describe())); 来返回工具
描述。输出序列化不会替你等待嵌套的 Promise。
被处理的 Error 值在 text(...)、json(...)、返回的数组或普通对象中保留其 name、message 以及 JSON 兼容的可枚举自定义字段。不会调用错误特有的 toJSON 方法。这包括来自 Promise.allSettled(...) 的拒绝原因。处理错误不会使单元格失败;未捕获的错误仍会产生失败结果。
返回值以及 json(...) 输出保留字面 JSON 键,例如 __proto__。数值类型的类型化数组在带索引的 JSON 对象中保留其数字元素;当你需要 JSON 数组时,请使用 Array.from(...)。最终返回值不会调用自定义 toJSON 方法。请显式转换特殊值,例如返回 date.toISOString() 来得到日期字符串。
最终值的转换在单元格内执行。属性 getter 创建的输出和工具调用在单元格完成之前遵循普通的落定与挂起规则。
嵌套工具数据和模型可见输出有各自的限制。成功的桥接回复会以其完整的规范化 JSON 值到达客户方,或者其 promise 会以一个可捕获的程序数据资源错误拒绝。传输层绝不会以表示成功的截断标记作为替代。这也适用于目录发现以及整个适用的技能说明:要么完整,要么明确拒绝。
每个单元格都有一个聚合的待处理回复收件箱,容量为 min(memoryLimitBytes, maxSnapshotBytes) 个 UTF-8 编码字节:默认为 10 MiB,受现有配置上限约束最多为 256 MiB。成功的值和有界的工具错误在落定时消耗此容量,然后才进行保留。该容量横跨内联执行以及每一次等待;在宿主和工作进程释放已送达的回复后可重复使用,而不是累积的分页配额。饱和时,一个固定的、有界的失败诊断信息仍可用,且不保留工具数据;这些控制回复受待处理调用槽位的限制。取消和过期会关闭准入并释放未送达的回复。
这是附加的逻辑宿主数据容量,而不是总 RSS 限制,也不保证大数据可以挂起。执行器内存限制和 QuickJS 整个 VM 快照限制仍然适用;工作进程交接和 JSON 转换可能会临时保留额外的副本。在准入错误之后,请缩小请求范围或进行分页。
输出顺序与客户调用一致。累积的客户输出以及最终值或失败诊断信息在所有等待期间仍共享同一个 maxOutputBytes 序列化 UTF-8 预算。过大的错误会保留其首要原因并以 [error truncated] 结尾;截断不会把失败变成成功。对于超出此预算的成功发出或返回的输出,OpenClaw 会返回一个有界值,包含 truncated: true、一个 UTF-8 安全的 prefix、omittedBytes 以及关于用更窄的参数重新运行的指导。将该标记视为成功的部分结果:缩小搜索范围、分页、选择更少的文件,或返回更小的投影。不可序列化的值会被转换为普通字符串或错误;不支持二进制值。图像和文件通过普通的 OpenClaw 工具传输,而不是通过代码模式桥接。
当后续单元格需要完整数据时,请返回 await results.save(value),而不是发出该值。有界引用预览与完整的已保存 JSON 是分开的;results.load(id) 允许后续代码选择更小的投影而无需重新获取。有关限制和智能体运行生命周期,请参阅跨单元格重用数据。
交互式的 exec/wait 在最终显示投影会被截断时,也会自动保留一个过大的最终对象或数组。已保存的结果使用 value: { truncated: true, reference, guidance },其中描述符与 results.save 返回的相同。当发出的输出竞争空间时,其标识仍保持完整;预览文本和采样形状可能会缩小。如果连标识都无法容纳,新的保存会被释放,completed 结果会说明保留不可用。容量或数据容量失败同样保留原始的成功截断语义,而不会驱逐更早的引用。小值、普通字符串、发出的输出、失败、无头执行以及重启安全的单元格保持其普通的输出行为。
标记前缀和省略字节数描述的是规范化之后的原始紧凑 JSON,包括数组括号、分隔符和 JSON 转义。普通输出是增量式送达的。未变化的累积摘要不会被重复;新的输出或改变的最终值/错误预留可以产生对同一原始输出的替换摘要。
面向模型的 exec 和 wait 结果也符合生效模型的每个结果上下文和持久化限制。OpenClaw 会预留完整的结果信封,包括状态、续传、诊断、遥测和 JSON 格式化;在从保留的原始来源投射输出之前,使用相同的紧凑表示进行预算适配和投递。来自网络的结果保留不受信任内容封装器及其更小的内容限制。这些限制不会减少嵌套工具的字节配额。无模型上下文的无头执行和低层控制保留其仅字节配额(对于来自网络的控制输出,保留现有的安全封装器限制)。
这保护新产生的结果;它不是归档 JSON 保证。后续的聚合缩减、缓存 TTL 修剪以及重放到较小模型中,仍可能缩短或替换历史工具文本。已发送的结果在普通续传期间保持不变。常规工具保留其自身的文本和图像格式:声明的输出模式描述 details,而非模型可见文本。文件读取生产者会在相同的模型限制内预留其精确的分页页脚,超大的技能指令会被拒绝,而不是被静默地部分提供。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw