跳转到内容

输入关键词开始搜索文档。

Agent API 参考

最后更新 查看 Markdown 源文件

本页文档记录了智能体模块契约、@flue/runtime 的所有导出(加上 /node 和 /routing 子路径)中用于从外部地址、运行或服务智能体的部分,以及用于编写智能体挂载资源的 defineTool/defineSkill/defineSubagent 辅助函数。智能体函数在渲染过程中调用的钩子——useModel、useTool、usePersistentState 以及事件钩子——在Agent Hooks API 中有文档记录。

@flue/runtime 接口的其他部分有自己的参考页面:

  • 错误——错误类和传输类别。
  • 事件——observe()、instrument()、FlueEvent 和观察类型。
  • Provider API——providers 配置、setProvider() 和 cloudflareBindingProvider()。
  • 沙盒适配器 API——SandboxFactory、Sandbox、SandboxDriver、bash()、sandboxFromDriver() 以及每个工具的工厂函数(createReadTool 等)。
  • 数据持久化 API——PersistenceAdapter 和存储契约。
  • 流式协议——ConversationStreamChunk 背后的对话有线协议。

智能体函数

type AgentFunction<TProps = void> = TProps extends void
  ? () => string | undefined | void
  : (props: TProps) => string | undefined | void;

type Agent = AgentFunction<AgentProps> & AgentStatics;

智能体是一个普通同步函数。函数体中调用的钩子附加功能(模型、工具、技能、状态);返回的字符串是指令文档。Agent 是可寻址单元:本页上每个接受智能体的 API——dispatch()、init()、getAgentInstance()、createAgentRouter()、start()——都接受函数值本身。

  • 函数必须同步返回。返回 promise 会抛出 [flue] Agent functions must be synchronous.。异步工作属于工具、事件钩子回调和资源工厂。
  • 返回值必须是字符串或 undefined。返回其他任何内容都会抛出异常。没有 return 语句的函数体(纯工具智能体)是合法的。
  • 指令文档按调用顺序组合:首先是返回的字符串,然后是每个 useInstruction() 的贡献,用空行连接。作者负责所有格式。
  • 运行时在每次模型调用前重新渲染函数。从钩子读取的值是该渲染时刻的快照;参见渲染和钩子规则。
  • 渲染永不嵌套。直接调用另一个智能体函数的智能体函数会抛出 [flue] Re-entrant agent render.。共享行为通过自定义钩子组合;委托通过 useSubagent() 进行。
  • 在一次渲染中声明两个同名的工具、技能、子智能体或状态名称会抛出异常(重复的工具名称会抛出 ToolNameConflictError)。

AgentProps

interface AgentProps {
  id: string;
}

运行时传递给根智能体函数的属性——其路由数据。

  • id — 此智能体实例的 id:智能体对话 URL 中的 :id 段、dispatch()/init() 调用的 id,或传递给 flue run 的 --id。在实例的整个生命周期内保持不变。
  • 只有根智能体函数接收属性。子智能体的智能体函数以无参数方式调用;要共享值请显式地闭包捕获。
  • 在没有实例的空渲染(测试和工具中的直接渲染)中,读取 props.id 会抛出异常。
  • 当 id 编码了多个结构化事实时,请将它们作为 initialData 传递,并使用 useInitialData() 读取,而不是解析 id。

'use agent' 指令

'use agent';

export function TriageAgent() {
  // ...
}

注册标记的是模块,而不是函数:'use agent' 是文件顶部的一个纯字符串字面量,在导入和任何其他语句之前。在构建时,Flue 扫描项目中的标记模块,并将每个具有大写名称的导出函数注册为智能体。一个模块可以导出多个智能体。注册使智能体可寻址——dispatch() 和 init() 根据注册集解析目标——并且与 HTTP 暴露是分开的,后者是一个显式的 createAgentRouter() 挂载。

智能体的持久标识是用于键控其对话存储(以及 Cloudflare 上的 Durable Object 类)的 slug。它按以下顺序解析:

  1. 构建时绑定的标识——'use agent' 转换在构建时将标识捕获为字符串字面量,因此压缩不会损坏它。
  2. agentName 静态属性。
  3. 函数自身的 name——在没有压缩器运行的插件免费上下文中(flue run、单元测试、start() 脚本)是安全的。
  • 标识必须匹配 /^[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*$/(导出为 AGENT_IDENTITY_PATTERN):PascalCase 函数名或 kebab-case 覆盖。特别是不能有 : 和前导数字。无效标识在注册时会抛出异常。
  • 跨注册集的重复标识会抛出异常。将同一函数值注册到两个标识下会抛出异常。
  • __flueBindAgentModule() 和 AgentIdentityBinding 类型是为构建转换生成的代码导出的。它们不是公共 API;不要调用它们。

该指令是构建时契约。在构建的应用程序之外,start() 显式注册智能体。

智能体静态属性

interface AgentStatics {
  agentName?: string;
  initialData?: v.GenericSchema;
  durability?: DurabilityConfig;
}

平台无需运行函数即可读取的智能体契约部分。所有都是可选的普通属性,在声明后赋值:

export function IssueTriage() {
  /* ... */
}
IssueTriage.agentName = 'issue-triage';
IssueTriage.initialData = v.object({ issue: v.pipe(v.number(), v.integer()) });
IssueTriage.durability = { maxAttempts: 5, timeoutMs: 7_200_000 };
  • agentName — 持久标识覆盖。赋值后将存储标识与源级函数名解耦(重命名函数后不需要数据迁移)。必须匹配 AGENT_IDENTITY_PATTERN;无效值在标识解析时会抛出异常。在 'use agent' 模块中,值必须是字符串字面量——构建目标在运行任何用户代码之前从中派生持久标识。
  • initialData — 用于实例创建数据的 Valibot 模式。仅在实例首次接触时同步验证一次,在任何持久化内容被接受之前;不匹配(包括缺失,除非模式接受 undefined)会拒绝创建发送。模式解析的输出是记录的内容,也是 useInitialData() 返回的内容。如果没有模式,则不加类型地记录创建者发送的任何内容。
  • durability — 提交重试策略(见下文)。是静态属性而不是钩子,因为平台在函数_未_运行时应用它,包括在智能体自身渲染中崩溃后。与 agentName 不同,值不必是字面量——在赋值表达式中表达环境相关的策略:Fn.durability = process.env.CI ? { timeoutMs: 60_000 } : { timeoutMs: 3_600_000 }。

DurabilityConfig

interface DurabilityConfig {
  maxAttempts?: number;
  timeoutMs?: number;
}
  • maxAttempts — 提交被终止为失败之前的最大总尝试次数(SubmissionRetryExhaustedError 结算)。初始运行计为第一次尝试;每次重新运行提交的中断消耗另一次。正整数。默认 10。
  • timeoutMs — 单个提交的最大挂钟毫秒数,从第一次尝试开始计时;超过此时间的提交将被中止并结算为失败(SubmissionTimeoutError)。回合边界连接和 useAgentFinish 延续不会扩展它。截止时间以协作方式检查——在每次回合之前和恢复工作之前,而不是在提供者调用期间抢占——因此挂起的提供者调用可能超过它;该情况由尝试预算覆盖,而不是此检查。正整数。默认 3_600_000(一小时)。
  • 未知字段在验证时会抛出异常。没有静态属性时,应用上述存储默认值。

参见持久性了解恢复模型。

DeliveredMessage

type DeliveredMessage =
  | { kind: 'user'; body: string; attachments?: DeliveredAttachment[] }
  | {
      kind: 'signal';
      type: string;
      body: string;
      attributes?: Record<string, string>;
      tagName?: string;
    };

type DeliveredMessageInput = string | DeliveredMessage;

type DeliveredAttachment = (PromptImage | PromptDocument) & { filename?: string };

interface PromptDocument {
  type: 'document';
  data: string; // base64
  mimeType: string; // 'application/pdf'
}

每个传递接口的统一输入形状:dispatch()、init() 句柄、useDispatchMessage() 和直接 HTTP 提示(其有线主体是此形状的逐字副本)。在所有接受 DeliveredMessageInput 的地方,纯字符串是 { kind: 'user', body } 的简写。

  • kind: 'user' — a direct user talking to the assistant. Produces a canonical user_message record and projects with purpose: 'user' in the conversation. attachments carries images for vision-capable models — { type: 'image', data, mimeType, filename? } — and documents — { type: 'document', data, mimeType: 'application/pdf', filename? }. data is base64 and capped at 14 MiB of base64 characters per attachment (14 * 1024 * 1024). Documents are forwarded to the model as native document content (for PDFs the provider sees extracted text plus a rendered image of every page) rather than text extracted or pages rasterized in your code. Native document input is supported on Anthropic Messages, Google Generative AI / Vertex, and OpenAI (and Azure OpenAI) Responses models; on any other model API the document is replaced in model context with a text placeholder saying it was omitted (and the runtime logs a one-time warning per API). Other document MIME types are rejected with invalid_request (400).
  • kind: 'signal' — everything beyond a direct 1:1 exchange, and the right shape for most channels: each participant’s or system’s activity, with sender identity and structured metadata in attributes and the content in body. Signals render into model context as an XML-tagged block, not a chat turn.
    • type — caller-defined event type, e.g. 'slack.message'. Non-empty. Framework-reserved types are rejected at admission on every transport.
    • body — a plain string. JSON-stringify structured payloads yourself.
    • attributes — a string-to-string map, rendered alongside the body.
    • tagName — overrides the XML tag the signal renders as; defaults to signal. Because it is rendered unescaped, it must be a valid XML name: letters, digits, _, -, ., not starting with a digit, -, or ..
  • A malformed message throws the stable InvalidRequestError (invalid_request) — the same validation on every transport. See Errors.
  • Agent code reads the delivered message with useDelivery().

dispatch()

function dispatch(agent: Agent, request: AgentDispatchRequest): Promise<DispatchReceipt>;

interface AgentDispatchRequest {
  id: string;
  message: DeliveredMessageInput;
  initialData?: unknown;
  uid?: string | null;
}

interface DispatchReceipt {
  submissionId: string;
  acceptedAt: string;
  uid: string;
}

向一个智能体实例发出一个消息的即发即忘传递。在运行时接受并排队输入后解析——它不等待模型处理或回复。要读取已结算的回复,请使用 init() 句柄 并将回执传递给其 read()。

请求字段:

  • id — 目标实例 id。必需,非空。实例在首次接触时创建;没有单独的创建步骤。
  • message — 传递的消息。在准入时快照。
  • initialData — 实例创建数据,仅当此发送创建实例时查阅:根据智能体的 initialData 模式静态属性(如果声明)验证一次,并记录一次,可通过 useInitialData() 永久读取。当发送继续现有实例时静默忽略——配合 uid: null 使用则报错。不能与字符串 uid 组合;组合在验证时被拒绝,在任何持久化内容发生之前(该条件禁止创建,因此种子永远无法应用)。
  • uid — 发送条件;参见条件发送。

回执字段:

  • submissionId — 此已接受传递的生成标识符。
  • acceptedAt — 开始准入时分配的 ISO 时间戳。
  • uid — 联系的化身的 uid:当此发送创建实例时在出生时铸造,当继续实例时回显。

行为和错误:

  • 目标必须是当前应用程序的注册智能体('use agent' 导出或 start() 条目)。未注册的函数会拒绝;非函数的第一个参数会抛出 InvalidRequestError。
  • 在运行时配置之前调用 dispatch() 会拒绝——在 Flue 构建的服务器内,运行时是自动配置的;独立脚本首先调用 start()。
  • 缺少 id 会抛出人类可读的 Error;格式错误的 message 会抛出 InvalidRequestError。
  • 向繁忙实例发送的派发在下一个回合边界加入实时响应;向空闲实例发送的派发会唤醒新的响应。错过实时响应的传递作为其自己的提交从持久队列运行——它们永远不会丢失。派发的活动属于继续的实例,并与对它的直接 HTTP 提示共享一个接受顺序。
  • 目标差异。 在 Cloudflare 上,派发持久地将工作接纳到目标智能体的 Durable Object,并可能在中断后重试处理。在 Node 上,传递持久性遵循配置的持久适配器:默认的内存中存储仅限进程生命周期,而持久适配器在重启后保持已接受的派发,并在替换进程上协调它们。在两个目标上,处理都是至少一次——设计外部副作用为幂等。

条件发送

发送是条件请求,实例 uid 扮演 ETag 的角色:

  • 省略 uid — 无条件:继续实例,或创建它。
  • uid: '<string>' — 仅继续具有此 uid 的化身。缺失的实例或不匹配的 uid 在准入时以 AgentInstanceNotFoundError(agent_instance_not_found,HTTP 404)拒绝;不会发生任何持久化操作。不能与 initialData 组合。
  • uid: null — 仅在没有实例存在时创建。现有实例在准入时以 AgentInstanceExistsError(agent_instance_exists,HTTP 409)拒绝。

AgentInstanceExistsError 携带现有实例的 uid,因此调用者可以在不重试的情况下进行条件发送:

const uid = await getAgentInstance(agent, id).then(r => r?.uid);
if (!uid) { /* 不存在 */ }

init()

function init(agent: Agent, options?: InitOptions): AgentInstanceHandle;

interface InitOptions {
  id?: string;
  uid?: string | null;
}

interface AgentInstanceHandle {
  readonly id: string;
  dispatch(request: string | AgentHandleDispatchRequest): Promise<DispatchReceipt>;
  read(target: string | DispatchReceipt, options?: AgentReadOptions): Promise<AgentReply>;
  abort(): Promise<void>;
}

type AgentHandleDispatchRequest = Omit<AgentDispatchRequest, 'id' | 'uid'>;
// = { message: DeliveredMessageInput; initialData?: unknown }

interface AgentReadOptions {
  onEvent?: (chunk: ConversationStreamChunk) => void;
  signal?: AbortSignal;
}

一个智能体实例的编程客户端。该句柄是一个_地址_,而不是资源:init() 本身不创建任何内容,也不执行任何 I/O——实例在首次接触时创建,就像任何其他传递一样,并且运行时在使用句柄时解析,因此在模块作用域调用 init() 是安全的。

init() 选项:

  • id — 实例地址。省略以铸造新的唯一 id(此运行的临时实例);传递稳定的 id 以寻址实例,以便后续发送可以再次找到。空的或非字符串的 id 会抛出异常;非函数的 agent 会抛出 InvalidRequestError。
  • uid — 句柄首次接触的发送条件,语义与 dispatch() 相同。在发送的回执之后,句柄固定它接触的化身,后续发送继续它。

handle.dispatch() 通过与所有其他传输相同的派发队列传递一个消息——每个钩子都像在其他地方一样触发——并在准入时以持久的 DispatchReceipt 解析,与顶层 dispatch() 相同的契约。其有效载荷是顶层请求减去句柄拥有的 id/uid;纯字符串是 { message } 的简写。

  • 不是字符串且不携带 message 属性的有效载荷会抛出异常。携带 id 或 uid 的有效载荷会抛出异常——将它们传递给 init(agent, { id, uid })。
  • 准入时条件在此处浮现:失败的发送条件在任何持久化操作发生之前以 AgentInstanceNotFoundError 或 AgentInstanceExistsError 拒绝。

handle.read() 等待一个提交的结算并以 AgentReply 解析。目标是派发回执,或纯提交 id——对于派发,其 submissionId;回执形式还将联系的化身的 uid 带到回复中。

  • 可重新附加。 结算和回复是持久的对话记录,因此读取可以在任何进程的任何时间工作:很久以前结算的提交立即解析,再次读取同一提交返回相同的回复。在崩溃后持久化的回执——例如,作为工作流步骤的持久结果——是重试所需的一切;在 dispatch() 和 read() 之间,内存中没有承载内容。
  • 对一个实例的并发传递序列化,或在回合边界加入实时响应;加入的传递读取响应它的合并回复。
  • 当提交结算为 failed 或 aborted 时,以 AgentRunError 拒绝。
  • onEvent 接收每个投影的对话块,因为它被持久记录——与 Flue Agent SDK 的更新视图读取的相同 ConversationStreamChunk 投影。
  • signal 停止读取:调用以信号的原因拒绝。取消读取是纯本地的——提交继续运行并保持可读。要持久停止智能体的工作,请调用 abort()。
  • 寻址到不存在实例的读取会在每个目标上以 AgentInstanceNotFoundError 拒绝——读取等待结算,而从未被联系的实例没有要结算的内容。在现有实例上,无信号的读取无限期等待结算,运行时的恢复不变量保证每个已接纳的提交;从未存在的提交 id 是编程错误,读取无法检测——id 来自回执。

handle.abort() 请求实例工作的持久中止——正在运行的头部和其后的每个排队提交。在意图记录后解析;不同的 aborted 结算异步到达,实时的 read() 观察到它并以 AgentRunError 结果 'aborted' 拒绝。

所有三个动词在进程配置了 Flue 运行时的任何地方工作:Flue 服务器内(例如 app.ts 中的 cron 回调)、start() 之后的独立脚本中、flue run 下,以及部署的 Cloudflare Worker 中——包括 Workflow 步骤,其中回执和已结算的回复各自成为步骤的持久结果。在配置任何运行时之前使用,调用会以与 dispatch() 相同的配置错误拒绝。

AgentReply

interface AgentReply {
  text: string;
  data: Record<string, unknown[]>;
  metadata?: Record<string, unknown>;
  uid?: string;
  submissionId: string;
}
  • text — 提交产生的最终助手文本;没有时为 ''。
  • data — 响应期间编写的命名客户端数据部分(useDataWriter),按部分名称键控,每个都是按顺序的写入数组。
  • metadata — 智能体编写的响应元数据(useResponseStart/useResponseFinish),如果存在。
  • uid — 联系的化身的 uid(已知时存在;当此发送创建实例时铸造)。
  • submissionId — 已结算提交的 id。

AgentRunError

class AgentRunError extends Error {
  readonly outcome: 'failed' | 'aborted';
  readonly submissionId: string;
}

read() 的拒绝,其提交结算为 failed 或 aborted。结算的底层错误(如果已记录)作为 cause 附加。

getAgentInstance()

function getAgentInstance(agent: Agent, id: string): Promise<AgentInstanceInfo | null>;

interface AgentInstanceInfo {
  id: string;
  uid?: string;
}

按 id 查找智能体实例:不存在实例时为 null,否则为其信息,包括可用作发送条件的 uid。uid 仅在实例的出生记录尚未落地时(物化中)缺失。

大多数调用者永远不需要这个——无条件发送不需要 uid 即可工作,创建发送在其回执上返回新的 uid,失败的 uid: null 条件在其错误详情中交还现有的 uid。当未创建实例的代码想要在不先尝试发送的情况下进行条件发送时使用它。与 dispatch() 相同的注册、运行时配置和参数验证错误适用。

start()

import { start } from '@flue/runtime/node';

function start(options: StartOptions): Promise<Flue>;

interface StartOptions {
  agents: readonly StartAgentEntry[];
  db?: PersistenceAdapter;
  env?: Record<string, string | undefined>;
  providers?: readonly Provider[];
}

type StartAgentEntry = Agent | StartAgentConfig;

interface StartAgentConfig {
  agent: Agent;
  name?: string;
}

interface Flue {
  stop(): Promise<void>;
  [Symbol.asyncDispose](): Promise<void>;
}

在生成的服务器入口之外运行 Flue 的 Node 引导——独立脚本和测试套件。它镜像构建服务器在启动时所做的(智能体注册、持久性、持久提交协调器),没有 HTTP 接口。start() 解析后,init()、dispatch() 和 getAgentInstance() 的工作方式与在服务器内完全相同。

  • agents — 此运行时服务的智能体。必需,非空。每个条目是一个智能体函数,或当需要标识覆盖时为 { agent, name }(测试中的内联或匿名函数)。标识从条目的 name 解析,否则从智能体自身的标识(agentName 静态属性,否则函数名)——永不按位置,因此重新排序数组不能重新分配对话。没有 agentName 也没有 name 的匿名函数会抛出异常。

  • db — 持久性。默认为内存中 SQLite(进程生命周期——退出后什么都不保留)。传递适配器,例如 sqlite('./run.db') 来自 @flue/runtime/node,以跨运行保持对话。参见数据持久化 API 了解适配器契约。

  • env — 运行时环境(提供者凭据和其他绑定)。默认为 process.env。

  • providers — 此运行时注册的 Pi 提供者,替换默认集。省略则注册每个 Pi 内置,与 flue run 相同;空数组则不注册任何。传递内置工厂、createProvider(...) 自定义或测试中的假提供者的 .provider:

    import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
    
    await start({
      agents: [Reporter],
      providers: [anthropicProvider()], // only anthropic/* specifiers resolve
    });

    省略时,默认注册跳过任何已注册的 ID,因此在 start() 之前进行的 setProvider() 调用会覆盖相同 ID 的内置,无论顺序如何。显式给出时,providers 无条件注册——此处的条目覆盖在 start() 之前设置的相同 ID 提供者,因为 setProvider() 总是按 ID 替换。

  • 返回 Flue 句柄。stop() 排空正在进行的工作,然后断开持久性连接;异步处理符号使 await using flue = await start(...) 自动清理。

  • 一个进程最多持有一个 Flue 运行时。当运行时已配置时 start() 会抛出异常——在 Flue 服务器内,直接调用 init()/dispatch()。

@flue/runtime/node 还导出 local() 沙盒工厂和 sqlite() 持久适配器;两者都在 Node.js 目标指南的参考部分中记录。

createAgentRouter()

import { createAgentRouter } from '@flue/runtime/routing';

function createAgentRouter(agent: Agent): Hono;

构建可挂载的 Hono 子应用程序,服务于一个智能体的 HTTP 接口。在编写的 app.ts 路由映射中挂载它:app.route('/agents/support', createAgentRouter(Support))。路由,相对于挂载点:

  • POST /:id — 发送提示。主体是 DeliveredMessage(可选携带顶层 initialData 和 uid 同级);在准入时响应 202。
  • GET | HEAD /:id — 对话流读取(流式协议)。
  • POST /:id/abort — 中止实例的所有进行中和排队的工作。
  • GET /:id/attachments/:attachmentId — 附件字节下载。

契约:

  • 纯工厂:没有副作用,没有选项;调用任意次数并在任意路径挂载结果。挂载路径仅用于路由——对话由智能体的持久标识键控,永不按 URL。
  • 处理程序在请求时解析运行时,因此在引导注册完成之前创建路由器是可以的;没有配置运行时服务的请求会报错。
  • 当智能体的标识无法解析(没有 agentName 静态属性的匿名函数)或无效时,在创建时抛出异常。
  • 已知路径上的不匹配方法呈现规范的 405 信封(MethodNotAllowedError);错误通过公共传输错误信封呈现。
  • 路由器不携带认证。挂载是暴露决策;在宿主应用程序中组合认证和其他中间件(app.use('/agents/support/*', auth))。参见路由。
  • 返回的应用程序暴露 .fetch,因此它也可以挂载在任何基于 fetch 的服务器框架中。

渠道有并行工厂 createChannelRouter(routes)(从 @flue/runtime 导出),它以相同方式服务渠道包的声明式 routes 数组(ChannelRouteDefinition[])——渠道包将其包装为 channel.route()。参见渠道。

Fetchable

import type { Fetchable } from '@flue/runtime/routing';

interface Fetchable {
  fetch(request: Request, env?: unknown, ctx?: unknown): Response | Promise<Response>;
}

编写的应用程序入口的默认导出的结构契约。任何暴露兼容 fetch() 的对象都满足它,包括 new Hono() 实例。在 Cloudflare 上,env 包含绑定,ctx 是 ExecutionContext;在 Node 上,env 包含 Hono 的 Node 适配器绑定,ctx 是 undefined。

Harness

interface FlueHarness {
  readonly name: string;
  prompt<S extends v.GenericSchema>(
    text: string,
    options: PromptOptions<S> & { result: S },
  ): CallHandle<PromptResultResponse<v.InferOutput<S>>>;
  prompt(text: string, options?: PromptOptions): CallHandle<PromptResponse>;
  compact(): Promise<void>;
  readonly sandbox: Sandbox;
}

运行时运行器拥有的已初始化智能体环境——传递给 harness: true 工具的 run 和 useAgentStart/useAgentFinish 上下文的接口。无法直接构造;harness 仅存在于智能体会话内,作用域为接收它的调用。

harness.prompt()

在 harness 自己的临时对话中运行模型操作——与智能体的公共对话分开,永不显示给客户端。在一个 harness 内的重复调用继续该对话(一次一个活跃操作),因此后续提示可以看到早期调用建立的内容。对话可以通过 task 工具委托给智能体声明的子智能体。Harness 调用计入委托深度上限,它们打开的子对话保留在父对话上以供检查。

传递 options.result(Valibot 模式)以要求验证的结构化数据:模型必须调用框架注入的 finish 工具,其参数根据模式验证,并且调用以 PromptResultResponse 解析而不是自由格式文本。当模型放弃或耗尽后续尝试时,调用以 ResultUnavailableError 拒绝。

interface PromptOptions<S extends v.GenericSchema | undefined = undefined> {
  result?: S;
  tools?: ToolDefinition[];
  model?: string;
  thinkingLevel?: ThinkingLevel;
  signal?: AbortSignal;
  images?: PromptImage[];
  documents?: PromptDocument[];
}
  • result — require validated structured data and resolve with response.data.
  • tools — additional model-callable tools for this operation only.
  • model — model specifier override ('provider-id/model-id') for this operation. Defaults to the agent’s useModel declaration.
  • thinkingLevel — reasoning-effort override for this call. See ThinkingLevel.
  • signal — external abort signal, merged with the handle’s own.
  • images — inline images attached to the operation’s user message (PromptImage re-exports pi-ai’s ImageContent: { type: 'image', data, mimeType }). Requires a vision-capable model.
  • documents — inline documents attached to the operation’s user message: { type: 'document', data, mimeType: 'application/pdf' }, with data base64. Forwarded as native document content, like a document DeliveredAttachment. Native document input is supported on Anthropic Messages, Google Generative AI / Vertex, and OpenAI (and Azure OpenAI) Responses models; on any other model API the document is replaced in model context with a text placeholder saying it was omitted (and the runtime logs a one-time warning per API). An unsupported mimeType throws.
interface PromptResponse {
  text: string;
  usage: PromptUsage;
  model: PromptModel;
}

interface PromptResultResponse<T> {
  data: T;
  usage: PromptUsage;
  model: PromptModel;
}

interface PromptModel {
  provider: string;
  id: string;
}

interface PromptUsage {
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  totalTokens: number;
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    total: number;
  };
}

PromptUsage 聚合操作派发的每个 LLM 调用:每个助手回合、结果提取重试以及调用触发的任何压缩摘要。cost 从模型的每百万令牌费率表计算;对于内置注册表,费率镜像已发布的提供者定价(主要商业提供者为美元),而自定义注册的模型可能使用其他单位。PromptModel 命名为主回合选择的模型。超出中止的操作失败以类型化的 FlueError 子类(例如 SessionBusyError)拒绝;参见错误。

CallHandle

interface CallHandle<T> extends Promise<T> {
  readonly signal: AbortSignal;
  abort(reason?: unknown): void;
}

prompt() 返回的可等待句柄。中止——通过 abort() 或 options.signal——以标准 AbortError(DOMException)拒绝等待的值。signal 在来自任一来源的中止时触发,因此传递给调用的工具可以观察取消。harness.sandbox.exec() 以相同方式取消——传递其 options.signal——因为它解析的是纯 Promise,而不是 CallHandle。

harness.compact()

立即触发 harness 对话的压缩。当没有内容可压缩时作为无操作解析;当摘要失败或中止时拒绝,并在对话上有另一个操作进行时以 SessionBusyError 拒绝。这压缩 harness 的临时对话——智能体的主对话根据其 CompactionConfig 自动压缩。

harness.sandbox

智能体的环境本身:从智能体的 useSandbox() 声明解析的实时 Sandbox。一个对象承载整个接口——exec()、文件动词(readFile、readFileBuffer、writeFile、stat、readdir、exists、mkdir、rm)、cwd 和 resolvePath()。在未声明沙盒的智能体中访问它会抛出 [flue] This agent has no sandbox. ...——可能在无沙盒智能体中运行的工具不应触碰它。完整的 Sandbox 契约在沙盒适配器 API中记录。

  • 对它的操作永不记录在对话中。模型有自己的工具用于它应该推理的文件系统工作;harness.sandbox 用于模型不应看到的管道。
  • writeFile 在每种沙盒模式中创建缺失的父目录。
  • 相对路径根据智能体的 cwd 解析(设置时为 useSandbox(factory, { cwd }),否则为适配器默认值);使用绝对路径以跨适配器可移植。resolvePath() 根据 cwd 解析相对路径而不触及文件系统。
  • 沙盒是异构的:适配器可能不支持每个通用动词(它在无法的地方抛出)并可能用其原生接口丰富返回的对象。适配器包提供运行时检查的访问器以缩小到该接口,例如 Cloudflare Computer 的 computerWorkspace(harness.sandbox)。
  • 它是实时 getter,而不是快照:条件 useSandbox() 可能在回合边界交换环境,此属性跟随。如果智能体交换环境,不要跨回合边界缓存返回的引用。

defineTool()

function defineTool<...>(options: {
  name: string;
  description: string;
  input?: ToolInputSchema;   // Valibot schema; top-level object
  output?: ToolOutputSchema; // Valibot schema
  harness?: boolean;
  durable?: boolean;
  timeoutMs?: number; // milliseconds
  annotations?: McpToolAnnotations;
  run(context: ToolContext<...>): ToolRunEnvelope<Output> | string | void | Promise<ToolRunEnvelope<Output> | string | void>;
}): ToolDefinition;

类型和验证辅助函数:它验证定义并返回冻结的版本,因此错误的定义在模块加载时失败,而不是在第一次渲染时。也可以从更轻量的 @flue/runtime/tool 入口导入,用于纯工具模块。智能体通过 useTool() 每次渲染挂载返回值。

  • name、description — 必需的非空字符串。描述是面向模型的目录行。
  • input — 调用参数的 Valibot 模式。必须是顶层对象模式(模型发送 JSON 对象);其他任何内容都会抛出异常。当存在时,解析的输出作为 context.data 到达,由推断类型化。当不存在时,工具不接收 data 属性,调用者的参数被忽略。
  • output — 返回值的 Valibot 模式。当存在时,运行时在记录之前通过它解析返回值;不匹配会抛出 ToolOutputValidationError,产生 undefined 的模式会抛出 ToolOutputSerializationError。
  • harness、durable — 能力标志,详见下文。当存在时必须是布尔值。
  • timeoutMs — 调用执行的可选限制,以毫秒为单位。到期时,harness 中止工具的 context.signal 并以 ToolTimeoutError 结算调用(模型看到的工具错误,因此对话继续);提交的持久性超时仍然是外部后盾。参见有界工具。
  • annotations — 可选的 MCP 兼容工具提示:title、readOnlyHint、destructiveHint、idempotentHint 和 openWorldHint。createMcpConnection() 从每个服务器 tools/list 条目复制这些;包装器可以保留它们,应用程序代码可以在决定调用是否需要批准时使用它们。运行时不会根据这些提示改变执行。除非你信任提供它们的 MCP 服务器,否则将它们视为不受信任。
  • run — 实现。可以是异步的,返回 ToolRunEnvelope — { output?, terminate? }。output 是工具的结果:它必须是 JSON 可序列化的,快照为 JSON 兼容数据,然后为模型 JSON 字符串化;不可序列化的输出会抛出 ToolOutputSerializationError。返回纯 string 是 { output: <string> } 的简写,返回 nothing(void)仅在未声明 output 模式时允许,作为 null 到达模型;任何其他纯返回——普通对象、数组、数字、布尔值或 null——会抛出异常,告诉你将其包装为 { output: <value> }。terminate: true 在当前工具批次结算后结束智能体的回合,与 finish/give_up 使用的相同循环结束契约——多工具批次仅在其每个结果都终止时结束回合,抛出的工具永不终止,并且标志记录在工具的规范结果上,因此终止在批次提交和提交结算之间的崩溃中存活。在 run 内部抛出会记录模型看到的工具错误;它不会使提交失败。
  • 未能通过 input 模式的参数在调用 run 之前抛出 ToolInputValidationError;模型接收验证失败作为工具结果,并可能重试。

错误类在错误中记录。教学材料请参见工具指南。

ToolContext — 传递给工具 run 的上下文:

type ToolContext<Input, Harness, Durable> = {
  readonly toolCallId: string;
  readonly signal?: AbortSignal;
  readonly log: FlueLogger;
} & {
  readonly data: v.InferOutput<Input>; // when `input` is declared
} & {
  readonly harness: FlueHarness; // when `harness: true`
} & {
  readonly step: ToolStep; // when `durable: true`
};

interface FlueLogger {
  info(message: string, attributes?: Record<string, unknown>): void;
  warn(message: string, attributes?: Record<string, unknown>): void;
  error(message: string, attributes?: Record<string, unknown>): void;
}
  • toolCallId — 正在执行的工具调用的 id;与调用的 tool_start/tool 事件及其工具结果消息携带的 id 相同,因此持久副作用和观察者可以通过 id 关联。在没有模型回合的独立运行中合成。
  • signal — 调用的中止信号。
  • log — 长时间运行工具的进度日志。行作为归属于此调用的 log 事件发射到对话流中;它们不是工具结果的一部分,模型永远看不到它们。
  • data — 由 input 模式解析的调用参数。仅当声明 input 时存在。
  • harness — 智能体的运行时接口。仅对 harness: true 工具存在。参见 Harness。
  • step — 持久步骤接口(ToolStep,见下文)。仅对 durable: true 工具存在。

辅助类型 ToolInput<TTool> 和 ToolOutput<TTool> 从工具的定义中提取工具的推断参数和输出类型。

工具标志。

  • harness: true — run 接收 harness,这是智能体环境(harness.sandbox)和模型(harness.prompt())的唯一接口。Harness 调用作用域为工具调用,计入委托深度上限,并保留它们打开的任何子对话。Harness 工具仅在智能体会话内运行,永不独立。没有该标志的工具是其数据的纯函数,无法访问运行时。
  • durable: true — run 接收 step,运行中的每个副作用预计通过 step.do(...)。作为交换,被中断的调用在恢复时重新执行——已完成的步骤重放其记录的值而不是再次运行——而不是像普通工具一样以未知结果错误结算。参见持久工具和 step.do。
  • timeoutMs: <ms> — 每次调用的执行限制。当截止时间到期时,harness 中止工具的 context.signal 并以 ToolTimeoutError 结算调用——模型看到 Tool \"<name>\" timed out after <ms>ms 作为工具的错误结果,对话继续;被放弃的运行的延迟结算被丢弃,主机中止仍然作为中止到达,而不是超时。

标志可组合:durable: true, harness: true 工具同时接收 step 和 harness(将 harness.prompt(...) 包装在步骤中以避免恢复时重新提示)。

ToolStep — durable: true 工具接收的持久步骤接口:

interface ToolStep {
  do<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
}
  • do(name, fn) 为此工具调用运行一次 fn。返回值在 do 解析之前持久记录;同一工具调用的重新执行返回记录的值而不调用 fn。
  • 精确一次记录,至少一次执行:在 fn 完成和记录落地之间的崩溃会在恢复时重新执行 fn。使每个步骤幂等。
  • 值必须是 JSON 可序列化的,并且应保持较小——将大工件存储在沙盒中并记录指针。不可序列化的值会抛出异常。
  • 名称标识逻辑工作:确定性地派生它们(`upsert:${id}`)。需要非空字符串,在一次调用内重用名称会抛出异常。
  • 在智能会话之外没有持久性——直接测试持久工具的 run 意味着提供你自己的 step 桩。

defineSkill()

function defineSkill(definition: SkillDefinition): SkillDefinition;

在代码中声明内联技能。在 defineTool() 模式中的类型和验证辅助函数:它验证定义并返回冻结的版本——这里不发生打包。运行时将定义打包为与 SKILL.md 导入懒惰产生的相同形状,第一次需要技能时;defineSkill 自己写入前置元数据,因此 instructions 保持纯 markdown。无效定义在模块加载时抛出 SkillDefinitionValidationError 并带有字段级问题。智能体通过 useSkill() 挂载结果。

SkillDefinition

interface SkillDefinition {
  readonly name: string;
  readonly description: string;
  readonly instructions: string;
  readonly license?: string;
  readonly compatibility?: string;
  readonly metadata?: Readonly<Record<string, string>>;
  readonly allowedTools?: string;
  readonly files?: Readonly<Record<string, string | Uint8Array>>;
}

等同于技能目录:instructions 是 SKILL.md 主体,files 承载支持资源,就像目录导入一样。

  • name — 仅小写 ASCII 字母、数字和单连字符;最多 64 个字符。必需。
  • description — 目录行:技能做什么以及何时使用它。最多 1024 个字符。必需。
  • instructions — 技能的内容,在激活时加载。必需,非空——技能就是其内容,因此没有指令的定义会被拒绝,而不是作为空目录行挂载。
  • license、compatibility — 记录在打包的前置元数据中的可选字符串(compatibility 最多 500 个字符)。
  • metadata — 记录在前置元数据中的字符串到字符串映射。
  • allowedTools — 技能作者首选的空格分隔工具名称(在 Agent Skills 规范中实验性)。为兼容性而接受;Flue 不强制执行它。
  • files — 按相对于技能根的路径键控的支持资源。路径必须是安全的相对路径(没有前导 /,没有 ./.. 段,没有反斜杠)并且不能是 SKILL.md 本身。内容是字符串或 Uint8Array。

defineSubagent()

function defineSubagent(definition: SubagentDefinition): SubagentDefinition;

验证定义并返回冻结的版本,因此错误的定义在模块加载时失败,而不是在第一次渲染时。返回的对象是可导出单元——定义一次委托,从任何智能体通过 useSubagent(...) 挂载。每次挂载的覆盖干净地展开:useSubagent({ ...issueClassifier, model: 'anthropic/claude-haiku-4-5' })。

SubagentDefinition

interface SubagentDefinition {
  name: string;
  description: string;
  agent: AgentFunction;
  model?: string;
  thinkingLevel?: ThinkingLevel;
}
  • name — 模型用于在 task 工具上选择此委托的目录名称。必需,非空。
  • description — 目录行;模型如何决定何时委托。必需,非空。
  • agent — 定义委托整个世界的智能体函数。必需。
  • model — 模型说明符覆盖。省略时继承父回合的模型。
  • thinkingLevel — 推理努力覆盖。省略时继承。

GeneralSubagent

const GeneralSubagent: SubagentDefinition;

一个空白的通用委托,准备用 useSubagent(GeneralSubagent) 声明。它的智能体函数故意为空:子智能体获取共享环境的工具、从其 cwd 发现的文件系统上下文以及父级的模型——没有父级的指令、工具、技能或子智能体。以框架保留名称 flue-general 注册。参见通用委托。

defineMcpConnection()

function defineMcpConnection(definition: McpConnectionDefinition): McpConnectionDefinition;

声明可重用的 MCP 连接。在 defineTool() 模式中的类型辅助函数:它验证定义并返回冻结的版本,因此错误的定义在模块加载时失败,而不是在第一次渲染时。返回的对象是可导出单元——定义一次服务器,从任何智能体通过 useMcpConnection(...) 挂载,它也接受相同的内联对象。每次挂载的覆盖干净地展开:useMcpConnection({ ...linear, tools: ['create_issue'] })。

McpConnectionDefinition

type McpTransport = 'streamable-http' | 'sse';
type McpAuth = string | (() => string | Promise<string>);

interface McpConnectionDefinition {
  name: string;
  url: string | URL;
  transport?: McpTransport;
  auth?: McpAuth;
  headers?: HeadersInit;
  requestInit?: RequestInit;
  fetch?: typeof fetch;
  timeoutMs?: number;
  resetTimeoutOnProgress?: boolean;
  tools?: string[];
  optional?: boolean;
}

一个 MCP 服务器,作为 defineMcpConnection(...)、useMcpConnection(...) 和 createMcpConnection(...) 消费它。

  • name — 服务器的 mcp__<server>__ 工具命名空间。必需,非空。
  • url — MCP 服务器端点。必需;字符串必须解析为绝对 URL。
  • transport — 默认为 'streamable-http'(现代可流式 HTTP)。对旧版 MCP 服务器使用 'sse'。
  • auth — 持有者凭据,在每个请求上作为 Authorization: Bearer <token> 发送。函数在每个请求时重新解析,用于每个用户和轮换凭据;在 401 时,传输重新解析一次并重试。参见认证。
  • headers — 合并到每个传输请求中的静态额外头部(设置覆盖 requestInit 头部)。对于凭据,首选 auth。
  • requestInit — 额外的传输请求配置。
  • fetch — 传输的自定义 fetch 实现。
  • timeoutMs — 每个请求的超时,以毫秒为单位。默认为 MCP SDK 默认值(60 秒)。
  • resetTimeoutOnProgress — 每当服务器发送进度通知时重置每个请求的超时。默认 false。
  • tools — 要适配的工具的允许列表,按服务器自己的工具名称,按此顺序。未知的、重复的和任务必需的名称会拒绝连接。
  • optional — 让智能体在解析失败时没有此服务器运行。默认 false:失败的连接在模型运行之前使提交失败。使用 optional: true 时,任何解析失败改为为提交挂载零工具——作为 resources 信号通知模型,作为 log 级别警告事件通知观察者——下次提交重试。
  • 未知字段会抛出异常;格式错误的值也会,带有冒犯字段名称。

createMcpConnection()

function createMcpConnection(definition: McpConnectionDefinition): Promise<McpConnection>;

interface McpConnection {
  name: string;
  tools: ToolDefinition[];
  close(): Promise<void>;
}

连接到由 McpConnectionDefinition 描述的远程 MCP 服务器——defineMcpConnection(...) 返回和 useMcpConnection(...) 挂载的相同对象——并将其列出的工具适配为普通的 Flue ToolDefinition 值,准备通过 useTool() 挂载。这是 useMcpConnection() 底层的低级函数——当受信任的应用程序代码应该拥有连接并直接处理工具定义时使用它;否则声明钩子。

函数是异步的,因此调用者拥有的连接通常在模块作用域使用顶层 await 制作——仅限 Node 目标:Cloudflare Workers 禁止在全局作用域进行网络 I/O,其模块图在顶层连接的 Worker 启动失败(违规在 vite dev 下不浮现,仅在 wrangler dev/部署时)。在 Cloudflare 上,使用 useMcpConnection()。

定义字段在 McpConnectionDefinition 中记录。连接契约:

  • 适配的工具名称采用 mcp__<server>__<tool> 形式;[A-Za-z0-9_-] 之外的字符被替换为下划线。重复的适配名称会拒绝连接。
  • 适配的描述携带服务器自己的工具描述(当服务器提供不同的描述时还有 Title:)。仅当清理改变了名称部分时才详细说明原始工具和服务器名称——否则适配的名称已经编码了两者。
  • 适配的定义保留服务器的 annotations 对象,包括 readOnlyHint、destructiveHint、idempotentHint 和 openWorldHint,因此受信任的应用程序代码可以在挂载或包装工具之前检查提示。MCP 注释是服务器提供的提示,而不是安全边界;仅当你信任服务器时才根据它们设置基础批准策略。
  • 工具发现遵循 tools/list 分页;重复的游标会抛出异常。需要基于任务执行的工具被跳过并带有控制台警告(允许列表一个是错误)。
  • auth 在每个请求之前解析;401 重新解析一次并重试,因此应用程序已经刷新的凭据可以就地恢复。
  • 工具结果的内容被展平为文本供模型使用;具有 isError 的结果成为工具错误。当服务器声明输出模式时,MCP 客户端根据它验证结构化内容,不匹配是错误。
  • 适配的定义是完整的——不要用 defineTool() 包装它们。
  • close() 关闭底层的 MCP 客户端连接;在应用程序关闭期间调用它。在任何连接或发现失败时,在错误传播之前关闭客户端。(钩子声明的连接由运行时拥有——运行时关闭它们。)

动态资源

工具、技能和子智能体可以有条件地声明,因此模型可以使用的集合在渲染之间变化。运行时永不重写模型已经读取的呈现接口——系统提示的技能目录和 task 工具的名册在持久基线快照上保持冻结,因此技能或子智能体翻转永不无效化提供者的提示缓存。相反,每个渲染的声明集与最后叙述的快照进行差异比较,更改作为信号附加到对话中。自定义工具更改还重写请求的原生工具数组,这会使缓存无效,除非工具是由已完成的工具调用添加的;当前的第一方 Anthropic 模型(不包括 Haiku)在转录中的位置加载此类添加。激活和委托总是根据实时集解析:activate_skill 和 task 工具的 agent 参数接受纯字符串名称,未知名称返回列出当前可用内容的事实性未命中。本节是这些框架编写信号的契约。