REFERENCE · @migor/agentia
公共 API 参考
全部导出都来自包根 @migor/agentia,零运行时依赖
(公共消息类型自有,与 @anthropic-ai/sdk 结构兼容但不需要安装它)——
可选能力(zod / Redis)与 MCP 桥一律 duck-typed;MCP 连接器
(stdio / StreamableHTTP)出厂自带,但只用标准库(node:child_process +
全局 fetch)—— 一样是零新增依赖。可观测面(Trace / TraceSink /
metricsSink / createOtlpExporter)与四类能力同级导出,
它不是另装的第三方追踪 SDK。
core · 数据模型
core 层是零依赖的数据模型与结构面:四类能力最终都编译成 AgentTool,一次 run 沉淀为一条 Trace。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| AgentTool<I, O> | { name; description; inputSchema; strict?; approval?: 'required'; run(input: I, ctx?: ToolRunContext) } | 模型可见工具的最小结构面。run 抛错被包成 is_error 的 tool_result 回给模型,run 不中断。approval: 'required'(HITL):每次调用先挂起等人工审批(回合级全有或全无),批准/拒绝经 AsyncRunner.approve / POST /tasks/:id/approve 到达后恢复。 |
| JsonSchema | { type; description?; properties?; required?; additionalProperties?; … } | 工具 input_schema 的 JSON Schema 子集(v1 裸 schema,不依赖 zod)。 |
| ModelClient | { messages.stream(params) → { on('text', cb); finalMessage() } } | engine 对模型端的最小结构面;消息形态为自有类型(与 Anthropic Messages API 逐字对齐),其他 provider 适配成同一形态。params 含可选 signal(取消传播)。 |
| ModelFallbackLink | { model: string; client?: ModelClient } | 模型 fallback 链的一环(fallbacks 选项的元素):client 缺省复用本次 run 的 client(同端点换模型是主用例);换环判定与重试共用 classifyError 的 retryable 位。 |
| MessageParam / ContentBlockParam / TextBlockParam / ImageBlockParam / ToolUseBlockParam / ToolResultBlockParam / Role / CacheControl / ToolParam | { role; content: string | ContentBlockParam[] } 等 | 请求侧消息类型族(自有公共类型,snake_case 与 Anthropic Messages API 逐字对齐、与 @anthropic-ai/sdk 结构兼容 —— 双向兼容由类型门禁钉住)。ContentBlockParam 联合含 { type: string } 兜底成员:厂商新块型原样携带,不丢内容。ToolParam 避让 @Tool 装饰器命名。 |
| Message / ContentBlock / TextBlock / ToolUseBlock / ThinkingBlock / MessageUsage | { id; type; role; content; model; stop_reason; stop_sequence; usage } 等 | 响应侧消息类型族(finalMessage() 的产物);MessageUsage 避让 trace 的 Usage 命名。 |
| ToolRunContext | { client; recorder; parentSpanId; signal?; priceOverrides?; onUnpricedModel?; toolTimeoutMs?; maxEventChars?; traceContent?; maxTotalTokens?; maxCostUsd?; approval?; deferUntil?; abandoned? } | engine 调用每个工具时注入的执行上下文,供子能力把嵌套 span 挂进当前 trace;signal 由工具自行决定是否尊重;approval(HITL)是本次调用的审批决定(仅审批工具在场,审计/分级授权用);deferUntil(at)(durable timer)请求「现在还不是时候,T 之后再问我」—— 本回合整批挂起、到点重跑,时刻必须是**将来**(否则当场抛错,见 AgentRunResult.wakeAt);abandoned 在引擎超时「放弃等待」时 abort —— 想真停的工具监听它(@SubAgent/@Skill 已这么做)。 |
| ApprovalDecision | { approved; reason?; decidedBy?; decidedAt?; requestedAt? } | 人工审批决定(HITL),以 tool_use_id 为键:RunAgentOptions.approvals / RunInvocationOptions.approvals / TaskRecord.approvals(随任务落库,重启不丢)。decidedAt 缺省由框架填;requestedAt 是挂起时刻(框架回填,trace 的 approval.decided 事件据此算 waitedMs)。 |
| TaskEvent | { eventId?; type; payload } | run 事件(事件投入口:AsyncRunner.signalTask / POST /tasks/:id/events,投进一条挂起的 run)。白名单形状:全是字符串 —— 外部永远不能构造消息块,引擎把它渲染成一条 user 文本消息注入消息流(续跑段 run 根记 task.event 事件留痕)。eventId 给了就按它幂等去重(重复投递 → 409;簿记随 TaskRecord.deliveredEventIds 落库,有界 FIFO);不给则重复投递 = 重复进历史。只对 suspended 生效(否则 409 / 不存在 404)。待注入缓冲有上限(64 条):满了 → 409 且不收下(本次事件没有被记录 —— 先让这条 run 跑起来)。 |
| RecorderBackend | begin / end / event / setAttribute / snapshot / usage | TraceRecorder 面向子能力的最小记账面(core 不依赖 engine)。usage() 是只读总量的廉价路径(不拷 spans)—— 预算护栏走它。 |
| Trace | { traceId; rootSpanId; spans: Span[]; status; totalUsage } | 一次 run == 一条 trace(traceId == runId);totalUsage 只累加 llm.turn span(capability 的 usage 是子孙聚合,仅供展示)。 |
| Span | { spanId; traceId; parentSpanId; kind; name; startedAt; endedAt?; status; error?; usage?; links?; attributes; events } | 调用树节点;capability 名为 ${capabilityType}:${capabilityName},llm.turn 名为模型 id。links 是跨 trace 链路(v1 只出现在 run 根,见 TraceContext);没记 link 的 span **没有这个键**(不是空数组)。 |
| Usage | { inputTokens; outputTokens; cacheReadTokens; cacheCreationTokens; costEstimate? } | span 聚合用量;成本由 token × 单价表估算。 |
| SpanKind | 'run' | 'capability' | 'llm.turn' | span 层级:整次运行 / 能力调用(仅 skill 与 subagent 建)/ 每次模型往返。普通工具与 @Prompt 只记 turn 上的事件,不建 span。 |
| CapabilityType | 'tool' | 'skill' | 'prompt' | 'subagent' | 四类能力,共用命名空间。 |
| SpanEvent | { time; name; body } | span 上的结构化事件(如 tool.input / tool.output / compaction / budget.exceeded)。 |
| SpanError | { type; message; retryable } | 错误分类;retryable 标记 429 / 5xx / 网络类可安全重试(重试逻辑消费它)。 |
| SpanStatus / SpanId / TraceId | 'ok' | 'error' / string / string | 基础类型别名。 |
| TraceContext / SpanLink | { traceId: string; spanId?: string } / { traceId; spanId? } | 跨进程 / 跨服务关联:调用方(网关 / 队列消费者 / 上游服务)把它自己的 span 标识作为 RunInvocationOptions.traceContext 传进来,本次 run 的根 span 记成一条 SpanLink(OTLP 导出即标准 span links)。不改 traceId == runId —— run 仍是自己的新树,上游是被链接而不是被继承,所以调用树永远自洽、因果仍可查。 |
| parseTraceparent | (value: string | null | undefined) → TraceContext | undefined | 解析 W3C traceparent 头(00-<32位trace>-<16位span>-01)成 TraceContext。非法 / 缺失 / 版本 ff / 全零 id / 位宽不符一律返回 undefined(不抛):链路是观测行为,不该把业务请求打成 400。createHttpHandler 在 POST /run 与 POST /tasks 上自动用它。 |
| currentTraceparent | () → string | undefined | 出站链路传播:当前调用期的 W3C traceparent(00-<32位trace>-<16位span>-00),带给出站请求,下游就能关联到具体 span(回合 / 能力调用粒度,而不是整次 run)。不在 run 内、或在 run 根 span 打开之前(contextInit / 记忆水合)返回 undefined;flags 恒 00(本框架不采样)。与 parseTraceparent 对称:一个解析、一个生成,共用同一份 id 投影。只给读取器 —— 框架不创建出站请求,注入那一行由宿主的 fetch / metadata 自己写。 |
| TraceSink | { export(trace) → void | Promise<void> } | trace 出口:run 收尾(成功 / 失败)都投递,sink 抛错被吞(吞之前落一条 console.warn,文案含「trace sink」)、不影响 run。OTLP 导出器、metricsSink() 与 jsonlTraceSink() 都天然满足它。 |
| TraceRecordEvent | { type: 'span.begin' | 'span.end' | 'span.event' | 'span.attribute' | 'span.link'; seq; … } | 增量记账出口的事件形状(onTraceEvent 的回调参数):run 进行中逐笔给,给「等不了收尾」的消费者(进度面板 / SSE / 异步任务流)。与 TraceSink 是两条缝,不是替代 —— sink 收尾拿整棵、本回调运行期逐笔给且不保证送达。载荷是增量 + 此刻的拷贝;按 seq 升序折回必须逐字等于收尾时的 trace。 |
| Score | { name; value; source?; comment? } | 质量评分(R7 质量闭环):LLM-judge / 人工标注 / eval 结论挂到 trace 上;约定 value 为 0–1(布尔结论用 0/1),source 记评分来源(eval 名 / 'human' / judge 模型 id)。 |
| attachScore | (trace: Trace, score: Score) → void | 把评分挂到 trace 根 span(一条 score 事件,body 即 Score);评分通常来自 run 之外,故走事件而非 span 字段。出口映射:OTLP 译为 gen_ai.evaluation.result,metricsSink 聚合为 agentia_score 指标族。 |
| validateJsonSchema | (schema: JsonSchema, input: unknown) → string | null | JSON Schema 子集校验(fromZod 接入时先走 zod),返回首条错误消息或 null。 |
| combineSignals | (...signals: (AbortSignal | undefined)[]) → AbortSignal | 合成多个中断源(调用方 / 超时 / 断连),任一触发即中止(Node 18 无 AbortSignal.any)。 |
| Blackboard / BlackboardKey / BlackboardValue / BlackboardSeed | interface Blackboard {} 等 | 黑板类型链路:声明合并 Blackboard 后,RunContext.get/set 与 run({ blackboard }) 种子获得键补全 + 拼写检查 + 值类型;不声明时键为 string、值为 unknown。 |
| TypedSchema<T> / SchemaType<S> / SchemaInput<S> | SchemaType<TypedSchema<T>> = T | schema 即单一事实来源:fromZod<T>() 返回 TypedSchema<T>,据此自动推导 result.typed 与 @Tool 方法入参类型(不匹配即编译期报错)。 |
| AGENTIA_VERSION | string | 框架版本号常量,反映已发布版本 —— 未发布窗口内会落后于 package.json(包版本先行,发布时同步)。 |
engine · 运行时内核
流式 manual loop:模型往返 + 递归工具调用,直到 end_turn / 循环上限 / submit_result 提交结构化结果;可取消、可重试、可按成本硬停。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| runAgent | (options: RunAgentOptions) → Promise<AgentRunResult> | 主循环入口;不注入 recorder 时内部新建,traceId 即 runId。 |
| RunAgentOptions | { messages; system?; tools?; model?; … } | 主循环入参(逐项见下表)。ExecuteRunOptions / RunAppOptions 在它之上叠加各自的字段。 |
| resolveDefaultModel | (over?: string) → string | 模型缺省解析:显式参数 > AGENTIA_MODEL 环境变量 > claude-opus-5。 |
| TraceRecorder | class:begin(kind, name, parent) / end(id, patch?) / event / setAttribute / snapshot(status) / usage() | span 记账器,traceId 随实例生成。usage() 与 snapshot().totalUsage 同口径但不拷 spans/events(预算护栏每回合判两次,走这条)。 |
| TraceLimits | { maxEvents? } | 记账的数量上限:整条 trace 的事件总数闸(与管正文长度的 maxEventChars 正交)。超限即停止记账,并在 run 根留两笔 trace.truncated —— 起点标记 {limit}(断点在这里)+ 收尾摘要 {droppedEvents, limit}(丢了多少),两笔同时进交付的 trace 与增量流,缺口位置可预测(尾巴)且有计数。缺省不设(全量记账是本框架的承诺);0 = 一条都不记(仍有计数)。坏值在 run 入口抛 TypeError。 |
| TraceRecorderOptions | { maxEvents? } | new TraceRecorder(opts) 的构造项(与 TraceLimits.maxEvents 同一个闸;闸门在构造期生效,没有「前几条没算进闸」的窗口)。 |
| classifyError | (e: unknown) → SpanError | SDK 异常归类:timeout / rate_limit / connection 三类都标 retryable;超时单列一类,便于按类型分流告警与重试策略。 |
| isAbortError | (e: unknown) → boolean | 判定异常是否为中断(name === 'AbortError');中断不重试、不冒泡。 |
| isSuccessStopReason | (reason: AgentStopReason) → boolean | 「正常收尾」的唯一尺子(end_turn / stop_sequence);run 状态机、trace 状态、子 agent 交回判定共用它。 |
| AgentRunResult | { trace; stopReason; finalText; iterations; error?; typed?; suspendedMessages?; pendingApprovals?; suspendedReason?; wakeAt?; eventsDelivered } | typed 为 resultSchema 校验通过的结构化结果(模型未提交则为 undefined)。stopReason === 'suspended'(挂起:等人工审批 / 等一个时刻)时 suspendedMessages 是完整消息历史(末尾为含未决 tool_use 的 assistant 消息)、pendingApprovals 是待决 tool_use_id 列表、suspendedReason 是 'approval' / 'timer'、wakeAt(仅 'timer')是目标时刻 —— 把消息历史连同 approvals 决定喂回 runAgent / app.run 即恢复执行(时间挂起由宿主到期喂回,见异步宿主的 resumePending)。eventsDelivered 表示本段是否把传入的 events 真注入了消息历史(宿主据此清/留 TaskRecord.pendingEvents)。 |
| AgentStopReason | 'end_turn' | 'stop_sequence' | 'max_tokens' | 'refusal' | 'pause_turn' | 'max_iterations' | 'aborted' | 'budget_exceeded' | 'tool_use_no_blocks' | 'suspended' | 'unknown_stop_reason' | 'error' | 循环终止原因。aborted(被取消)与 budget_exceeded(超预算)都算失败 —— ⚠️ 落库状态另按意图分:宿主 runner.cancel 触发的 aborted 落 RunStatus.cancelled(取消不是失败,但原因可查:error.type === 'aborted');runTimeoutMs 到点落 failed。suspended(挂起:等人工审批 / 等一个时刻)不算成功也不算失败(非终态,trace 记 ok),挂起原因见 AgentRunResult.suspendedReason。 |
| DEFAULT_RETRY / RetryOptions | { maxAttempts: 3; baseDelayMs; maxDelayMs; jitter; onRetry?; isRetryable? } | 模型请求重试缺省参数(指数退避 + 抖动);retry: false 关闭,单次 run 可覆盖。 |
| ContextPolicy | { budgetTokens?; beforeTurn(messages, { iteration, model }) → Promise<messages> } | 上下文预算策略面:每回合发送前可编辑 / 压缩消息。beforeTurn 里可能走网络(compaction 的 summarize 是模型调用),框架按尽力而为对待:抛错即本回合原样放行,不判死整条 run,但会在 run 根记一条 context.policy_failed 并打一条 console.warn(降级要看得见)。 |
| SystemParam / SystemTextBlock | string | SystemTextBlock[] | system 参数:纯文本,或带 cache_control breakpoint 的可缓存块数组。 |
| createBudgetGuard / BudgetGuard | (opts?: BudgetGuardOptions) → { check({ totalUsage }) → 'tokens' | 'cost' | null } | 成本硬管控底座(每回合记账后判);也可以只拿它自己记账。入参只收 totalUsage —— 传整份 Trace 也行(结构上满足),但引擎走的是 recorder.usage() 的廉价视图,不拷 spans。 |
| BudgetGuardOptions / BudgetSnapshot | { maxTotalTokens?; maxCostUsd?; onExceed? } / { kind; limit; actual; totalTokens; costUsd } | 超限快照同时记进 run 根的 budget.exceeded 事件并回调 onExceed。 |
| DEFAULT_PRICING / buildPricing | Record<string, ModelPricing> / (overrides?) → Record<string, ModelPricing> | 内置价格表(6 个 Claude 型号)与合并函数:priceOverrides 覆盖同名项或给非 Anthropic 模型定价(如 { 'deepseek-chat': { in: 0.27, out: 1.10 } })。非法单价 / 乘数在第一次 llm 调用之前抛错(turn 解析价格表时就校验,client 一次都不被调用)—— 不让它悄悄算出 NaN 成本、把 maxCostUsd 变成永不触发的摆设;⚠️ 经 runAgent 时这个错被收成 status: error 的 result,不抛给调用方。 |
| ModelPricing | { in: number; out: number; cacheRead?: number; cacheWrite?: number } | 模型单价(美元 / 1M tokens)。cacheRead / cacheWrite 是缓存读 / 写乘数(相对 in),缺省 0.1 / 1.25(官方 5 分钟档)。缓存写的缺省只对 5m 档正确 —— 用了 cache_control: { ttl: '1h' } 就写 2,否则成本低估 37.5%、maxCostUsd 迟触发;缓存读的逐模型例外(Opus 5.5 = 0.05、Fable 5.1 / Mythos 5.1 = 0.025)同理。 |
| mapWithConcurrency | (items, limit, fn) → Promise<R[]> | 有界并发 map,结果顺序与输入一致;limit 非正 / 非有限 = 不限。同回合并行工具的底座。 |
RunAgentOptions 选项
| 选项 | 类型 | 说明 |
|---|---|---|
| messages(必填) | MessageParam[] | 初始消息;由调用方给 user 起始消息。 |
| system | SystemParam | 顶层 system(SystemPrompt 产物);稳定内容放在第一个 breakpoint 前。 |
| tools | AgentTool[] | 主 agent 可调工具菜单(裸 JSON schema)。 |
| model | string | 缺省走 resolveDefaultModel。 |
| maxTokens | number | 流式请求的 max_tokens,给足避免中途截断。 |
| maxIterations | number | 循环安全上限,防止无限 tool 往返。 |
| client | ModelClient | 缺省经 createAnthropicClient() 创建(自研 fetch + SSE,读 ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL);多模型场景注入适配 client。 |
| fallbacks | ModelFallbackLink[] | 模型 fallback 链:主模型本回合最终失败且错误可换(rate_limit / server / timeout / connection)时按序换环重试本回合;每环独立 llm.turn span(成本归对模型),切换记 llm.fallback 事件。护栏:aborted 永不换、本回合吐过字不换、每回合从主环重新起;子 agent 不继承。 |
| traceContent | 'full' | opt-in 记录 assistant 文本:每回合模型文本落 llm.turn span 的 output.text 属性(过 maxEventChars 同一道截断闸);缺省不记。注意体积与脱敏面(出库前走脱敏配方)。 |
| labels | Record<string, string> | 归因标签(R8-P4):落 run 根的 labels.<key> 属性(trace 侧无基数问题;与框架自写的 source 审计正交)。键非空、值必须字符串,否则 run 入口抛 TypeError。进 metrics 是另一个开关:metricsSink({ labelKeys }) 显式点名(缺省一个都不上,防基数爆炸)。 |
| recorder | TraceRecorder | run 层复用注入;不注入则内部新建。 |
| onText | (delta: string) => void | 文本增量回调(终端 / SSE 用)。 |
| runName | string | 写入 trace 根 span 名。 |
| contextPolicy | ContextPolicy | 每回合发送前的上下文预算策略(compaction / context editing)。 |
| resultSchema | JsonSchema | 给出后追加隐藏工具 submit_result,模型提交校验通过的结果写入 result.typed 并结束循环。 |
| signal | AbortSignal | 中断信号:中止则取消在飞请求,run 以 stopReason='aborted' 收尾(不抛异常)。 |
| retry | RetryOptions | false | 模型请求重试策略;缺省开启(maxAttempts=3)。只重试「本次尝试未产出任何文本」的失败。 |
| maxTotalTokens | number | 整条 run(含子 agent)累计 token 上限;超限以 budget_exceeded 收尾(算失败)。不是硬实时(一回合记账完才判)。 |
| maxCostUsd | number | 累计成本上限(美元);依赖模型在价格表内(内置表或 priceOverrides),不在表里时成本恒 0、这条护栏不触发 —— 但失效不再静默:turn 上会记 usage.unpriced 事件、指标出 model_unpriced_turns_total、可回调 onUnpricedModel。 |
| priceOverrides | Record<string, ModelPricing> | 价格表覆盖/追加($/1M tokens):覆盖内置同名项,或给非 Anthropic 模型定价。透传给子 agent / skill 的子循环,不会出现「主 agent 有成本、子 agent 恒 0」。⚠️ 模型名精确匹配:不带日期的别名与带日期的快照是两个不同的键(claude-haiku-4-5 ≠ claude-haiku-4-5-20251001),要按你实际传的那个 id 定价。非法单价 / 非法乘数在第一次 llm 调用之前就失败:run 以 status: error 收口(错误记在 run 根 span、totalUsage 全 0、client 一次都不被调用)—— 不是抛异常给调用方。 |
| onUnpricedModel | (info: { model; spanId }) => void | 遇到价格表外的模型时回调(每个循环作用域内每模型一次);抛错被吞,不改变 run 结局(定价缺失是宿主配置问题)。用它接告警。 |
| toolTimeoutMs | number | 单工具执行超时(毫秒),缺省 0 = 不限。超时不杀 run:该条 tool_result 记 is_error。⚠️ 是「放弃等待」不是取消。 |
| maxToolConcurrency | number | 同一回合内并行工具上限;缺省 Infinity(全并行)。 |
| systemVersion | string | 写进 run 根 attribute system.version;走 app.run 时由 SystemPrompt({ version }) 自动带上。 |
| promptVersions | Record<string, string> | 菜单内 @Prompt 资产的版本表({ 能力名: 版本 }):写进 run 根 attribute prompts.versions(name@ver 逗号拼接、排序、空表不记);走 app.run 时由装配期收集自动带上。 |
| sessionId | string | 写进 run 根 attribute session.id(OTLP 导出时映射 gen_ai.conversation.id),多轮 run 由此按会话聚合(thread 维度);走 app.run / executeRun 时给了 session 就自动带上。 |
| approvals | Record<string, ApprovalDecision> | HITL 审批决定(以 tool_use_id 为键):恢复挂起的 run 时由宿主传入;手工续跑「assistant 结尾带 tool_use」的消息历史也可直接给。标了 approval: 'required' 的工具在这里没有决定 ⇒ 该回合整体挂起。 |
engine · 长上下文策略
预算驱动的上下文护栏:估算 ≤ 预算走快路径原样放行;超预算先 context editing(不调模型);仍超且有 summarize 才 compaction,并带滞回避免每回合反复压缩。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| createBudgetPolicy | (opts?: BudgetPolicyOptions) → ContextPolicy | 上述三段降级的开箱实现;框架不替你造 token,可注入按 /count_tokens 的 estimate。跨回合只对新增消息计 token(历史只追加时不重算前缀)。 |
| BudgetPolicyOptions | { budgetTokens?=60000; keepToolPairs?=1; keepRecent?=20; estimateTokens?; editBeforeCompact?=true; summarize?; compactEvery?=1 } | keepToolPairs 管 context editing(按对数),keepRecent 管 compaction(按条数);summarize 提供才允许 compaction。 |
| trimToolPairs | (messages, opts?: TrimOptions) → messages | context editing:丢弃超出 keepToolPairs(缺省 1 对)的旧 tool_use→tool_result 对;非严格交替历史整体放弃裁剪。 |
| traceToMessages | (trace, opts?: ReplayOptions) → MessageParam[] | trace 重放基底:把完成的 run 还原成可喂回模型的对话(角色交替合法、tool 对配对、末条为 user、可截断)。 |
| forkMessages | (trace, opts: ForkReplayOptions) → MessageParam[] | 分叉重放:在主循环第 atTurn 回合(0-based;直属 run 根的 llm.turn,子 agent 嵌套回合不算,与 harvest 同口径)之前截断,只重放分叉点前的真实历史,再拼上 append 的新消息(通常是改写过的新 user 消息)——「从第 N 回合换个问法重跑」的基底,喂回 app.run / runAgent。越界抛可读错误。与 traceToMessages 同一份有损边界(assistant 文本为标注占位、原始输入不入 trace),不是续跑。 |
| diffTraces | (a, b, opts?: TraceDiffOptions) → TraceDiff | trace diff(A/B 比对):同输入换模型 / 换 prompt 跑两条 run,比出 run 级 summary(status / totalUsage / 根 attributes —— 模型差第一眼就在 attributes.model)+ 逐 span 字段级差异。纯函数。配对键:llm.turn 忽略 name(name 是模型 id,按 name 配对会把两侧全报缺失),capability 按 kind:name;缺侧子树不下钻(一条缺侧记录代表整支);缺省忽略墙钟(ignoreTiming:false 时改比时长,绝对时间戳永不比)。 |
| TraceDiff / SpanDiff / DiffEntry | { equal; summary: DiffEntry[]; spans: SpanDiff[] } / { path; a?; b?; fields: DiffEntry[] } / { field; a; b } | diffTraces 的产物:summary 是第一眼视图,spans 只收有字段差或缺侧的 span(配平且全同的不出现);path 是配对路径(run:<name>/llm.turn#<i>/capability:<name>),字段差逐条 { field, a, b }。 |
| TraceDiffOptions / ForkReplayOptions | { ignoreTiming? } / ReplayOptions & { atTurn; append? } | ignoreTiming 缺省 true(A/B 不关心时序);atTurn 合法范围 0..主循环回合数-1,append 缺省不追加。分叉的 blackboard 种子由调用方经 RunInvocationOptions.blackboard 自带(trace 不记 blackboard)。 |
| compactMessages | (messages, opts: CompactOptions) → Promise<messages> | compaction:旧前缀经 summarize 摘要,只留最近 keepRecent(缺省 20 条)。 |
| defaultEstimateTokens | (text: string) → number | CJK 感知的 token 启发式估算(预算决策用,非精确记账)。 |
| estimateMessages | (messages, estimate?) → number | 整组消息 token 估算。每次调用都全量计算 —— 需要每回合反复估同一段历史时,用预算策略(内部走增量计数)而不是直接调它。 |
| renderMessages | (messages) → string | 消息渲染成纯文本,作摘要器输入。 |
| TrimOptions / CompactOptions / ReplayOptions | { keepToolPairs? } / { keepRecent?; summarize } / { includeToolIO?; maxEventChars? } | 上述函数的选项类型(注意单位不同:keepToolPairs 是对数,keepRecent 是条数)。 |
run · 生命周期与触发
一次 run 的完整生命周期(RunContext + recorder + 记忆水合/回写 + 会话历史),以及同步 RPC、异步任务、定时调度三类触发 —— 共用同一份输入契约,换宿主不换语义。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| executeRun | (options: ExecuteRunOptions) → Promise<{ run; result }> | run 生命周期入口:RunContext 作用域内跑 runAgent,finish / fail 收口,收尾投递 sinks。 |
| ExecuteRunOptions | RunAgentOptions & { idempotencyKey?; contextInit?; memory?; session?; rethrow?; sinks? } | memory:run 开始把 store.load(keys) 水合进 blackboard(用户种子优先),结束回写;session:对话历史读写(与前者正交)。 |
| Run | class:start() / finish(result) / fail(error) / toMeta() | run 状态机(queued → running → succeeded/failed);runId == recorder.traceId。 |
| RunContext | class:static current() / get(key) / set(key, value) / has / delete / keys | run 内 blackboard,AsyncLocalStorage 传播,不用全局单例。键类型随 Blackboard 声明合并收窄(未声明为 string)。 |
| withRunContext | (ctx: RunContext, fn) → T | Promise<T> | 在指定 ctx 作用域内执行 fn(返回类型随 fn:同步 fn 给 T)。 |
| SuspendedReason | 'approval' | 'timer' | 挂起原因:「等人工决定」(approval,HITL)与「等一个时刻」(timer,durable timer)。判据(approvalTimeoutMs 超时、approve 能否叫醒)落在原因上而不是状态上 —— 于是「时间挂起被审批超时提前叫醒」这类误伤在类型上写不出来。 |
| RunMeta / RunStatus | { runId; status; idempotencyKey?; createdAt; startedAt?; finishedAt?; error?; suspendedReason?; wakeAt? } / 'queued' | 'running' | 'suspended' | 'succeeded' | 'failed' | 'cancelled' | 运行记录与状态。suspended(挂起)是非终态:不占并发槽、resumePending 不捡、awaitTask 继续等;等什么由 suspendedReason('approval' = 等人工决定、'timer' = 等一个时刻)说,wakeAt 是后者的目标时刻。cancelled(被取消,宿主调 runner.cancel)是终态,与 failed 分开:取消不是失败(引擎侧 aborted 收尾的口径),运维读数不该把「人按的」与「跑挂的」混成一类;机制是同一条 abort signal,差别在意图 —— runTimeoutMs 超时仍然落 failed。 |
| SystemPrompt | class:new SystemPrompt({ version? });add(name, text, stable?) / add(section) / build({ cache? }) | 分段组装 system:stable 段进可缓存前缀打 ephemeral breakpoint,volatile 段放其后;version 自动写入 run 根 system.version。 |
| SystemPromptOptions / SystemSection | { version? } / { name; text; stable? } | 构造选项与段落;stable=false 不进可缓存前缀。 |
| SessionStore / InMemorySessionStore | interface:load(id) / append(id, messages) | 跨 run 的对话历史(append-only),与 MemoryStore(键值黑板)正交,可同时用;只有跑成功的轮次才回写。 |
| normalizeMessages | (input: RunInput | unknown) → MessageParam[] | 任意任务入参(string / messages / { prompt | text | messages })规范成 messages;空数组抛错。 |
| RunInput | string | MessageParam[] | { prompt?; text?; messages? } | transport 层接受的原始入参形态。 |
| RunSpec | { messages; options?; source? } | 一次任务的规范化入参;source 标记触发来源(sync / async / schedule:<id>)。 |
| RunInvocationOptions | { model?; fallbacks?; traceContent?; labels?; maxTokens?; maxIterations?; client?; onText?; signal?; blackboard?; contextPolicy?; retry?; idempotencyKey?; traceLimits?; onTraceEvent?; traceContext?; rethrow?; tools?; maxTotalTokens?; maxCostUsd?; priceOverrides?; toolTimeoutMs?; maxToolConcurrency?; maxEventChars?; approvals?; events?; sessionId? } | 单次 run 的调用参数,三类触发共用(不含不可序列化的 store / session —— 那几个只在程序内直调时可用)。fallbacks 是模型 fallback 链(环里的 client 是进程内对象,跨重启续跑只写 model 的环仍成立)。events 是挂起期间投递的 run 事件(随 TaskRecord 落库,续跑段由引擎注入消息流)。 |
| runSync / createSyncHandler | (app, input, opts?) / (app) → (input, opts?) => … | 同步 RPC:规范化入参后直接 app.run。 |
| AsyncRunner | new AsyncRunner(app, { client?, store?, concurrency?, maxQueued?, runTimeoutMs?, approvalTimeoutMs?, taskSinks? }):submit / approve(taskId, decisions, opts?) / signalTask(taskId, event) / cancel(taskId) / resumePending(opts?) / awaitTask(taskId, opts?) / drain(opts?) / inFlight / isDraining | 提交即返回任务记录、后台执行;同幂等键未失败任务 last-wins 去重。maxQueued(缺省 0 = 不限)是在等槽位的任务数上限 —— 超限 submit 抛 TaskQueueFullError、HTTP 回 503 + Retry-After(与 maxConcurrentRuns 同款;恢复路径不受闸,只挡新活)。approve(HITL)审批挂起任务:逐 tool_use_id 幂等(第一次决定赢),决定齐了先落库再恢复执行;只收该任务 pendingApprovals 里的 id(多一个 → 400、整批拒、记录不动)。signalTask(事件投入口)把 TaskEvent 投给挂起任务:只对 suspended 生效、先落库再派发、eventId 幂等去重(重复 → 409);待注入缓冲上限 64 条(满了 → 409、本次不收下)。approvalTimeoutMs(缺省 0 = 不限)是惰性审批超时:approve / poll / resumePending 读到过期挂起任务时自动全拒(「审批超时」)并重派,不起定时器。drain() = 拒新单 → 等在飞收尾(超时返回 false,未完成任务留在 store 里)。awaitTask() 等指定任务到终态:本进程写终态时事件唤醒,intervalMs(缺省 250ms)只作兜底轮询(终态由他进程写入的异步 store 才用得上)。 |
| AsyncRunnerOptions / ResumePendingOptions | { …; runTimeoutMs?; approvalTimeoutMs?; taskSinks?; onPersistError? } / { staleAfterMs? } | runTimeoutMs 到点真中止在飞 run;approvalTimeoutMs 是挂起审批的惰性超时(不起定时器);resumePending 靠 ownerId 跳过本进程记录(suspended 一律不捡 —— 挂起不是孤儿),staleAfterMs 只是判不了主人还在不在时的兜底启发式:能判的时候按租约判 —— ownerId 的主机名是本机就问那个 pid 还在不在(在的绝不抢、不在的立刻可抢 = 崩溃孤儿不等保鲜期),异主机 / 旧格式才退回「记录看起来够老了吗」;approvalTimeoutMs 只对 suspendedReason === 'approval' 成立(时间挂起不会被审批超时提前叫醒)。 |
| TaskSink | { onFinished(rec: TaskRecord) } | 任务完成回调:达终态后逐个 await 投递(抛错被吞),先通知再递减在飞计数。 |
| PersistFailureInfo | { record: TaskRecord; error: unknown; phase: 'initial' | 'outcome' } | 落库失败的形状(AsyncRunnerOptions.onPersistError 的回调参数)。此前这个失败被 #safeSave 吞掉且不留出口 ⇒ 宿主做不到它被告知要做的事。最贵的是 phase:'outcome':终态那次写出失败时库里停在 running,而 run 其实已跑完(副作用已发生)—— 重启后 resumePending 会把它当孤儿重跑,所以副作用工具必须自身幂等。与 createOtlpExporter({ onExportError }) 同因同形。 |
| AppCallable | { name; run(messages, opts?) } | 应用最小调用面。 |
| Scheduler / ScheduleHandle / ScheduleEveryOptions | new Scheduler(runner):every(intervalMs, input, opts?) / at(when, input, opts?) | 周期 / 定点触发,到点转为异步任务提交;返回 ScheduleHandle { id, cancel() };非法间隔 / 非法日期直接抛错。every 的 maxInFlight(缺省 1)是「同时未终态的任务数上限」,Infinity = 关闸门;0 / 负数在构造期抛错 —— 闸门判据 size >= maxInFlight 在 0 时恒真,周期任务会永不派发且不报错。 |
| TaskStore | interface:save / get / byIdempotency / list / clear(可选 listDue(before) / compact() / close()) | 任务记录存储面(同步或异步实现皆可,返回 MaybePromise);byIdempotency 为 last-wins 去重依据。listDue(到期索引,可选):只回「suspended 且 wakeAt ≤ before」的记录,AsyncRunner 的到期唤醒扫描有它走它、没有回退全表(语义不变);list 是真查询的 store(sqlite)值得实现,记录在内存的(memory / file)不必。 |
| TaskRecord | { taskId; status; idempotencyKey?; spec: RunSpec; runId?; ownerId?; approvals?; pendingApprovals?; pendingEvents?; deliveredEventIds?; createdAt; startedAt?; … } | 任务记录;完成后回填 runId(== traceId)。ownerId 是内部标识(p<pid>@<host>-<8 位十六进制>,2026-09-28 起带主机名)—— 续跑认领靠它判「主人还在不在」,宿主别解析它的格式。HITL:挂起时 spec.messages 存扩展历史(末尾是含未决 tool_use 的 assistant 消息),pendingApprovals 是待决 tool_use_id 列表,approvals 是已落库的决定(重启不丢)。事件投入口:pendingEvents 是挂起期间收到、待注入历史的事件(跑通后清掉),deliveredEventIds 是 eventId 幂等簿记(有界 FIFO)。 |
| InMemoryTaskStore | class(TaskStore):{ maxRecords? } | Map 实现:测试与缺省场景用;超上限从最旧已终态记录起淘汰(在飞记录永不淘汰)。 |
| FileTaskStore | new FileTaskStore(file) | JSONL 一行一快照落盘;宿主重启后 resumePending() 续跑 queued/running。 |
| SqliteTaskStore | new SqliteTaskStore(path) | node:sqlite 落库(WAL 读写不互斥),支持 ':memory:'。实现了可选的 listDue(before) 到期索引(派生列 wake_at + (status, wake_at) 索引;存量库构造期就地迁移并回填)—— 到期唤醒扫描 O(到期数) 而不是 O(全表)。 |
| RedisTaskStore | new RedisTaskStore(client, { prefix?, ttlSeconds? }) | duck-typed Redis 客户端(ioredis/node-redis 均可,可选 peer),TaskStore 方法支持异步。 |
| RedisLike / RedisTaskStoreOptions / RedisSetOptions | { get; set; del; expire?; keys | scanIterator } 等 | Redis 客户端的最小结构面与选项类型(框架不 import redis 包)。TTL 经 expire(key, seconds) 施加(两家客户端同名同形);set 只传两参 —— 尾参的选项形状两家相反,取任何一种都会在另一家上静默失效。 |
| MaybePromise<T> | T | Promise<T> | 同步 / 异步实现统一形态(调用方 await 兼容两种)。 |
run · 宿主与导出
同一份应用一行接入 HTTP(含鉴权缝、优雅停机、健康检查);trace 可导出到任何 OTLP 收集器或自建 sink;OpenAI 兼容端点一行接入多模型。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| createHttpHandler | (app: AppCallable, opts?: HttpHandlerOptions) → HttpHandler | node:http 接入:POST /run(同步 / SSE)、POST /tasks(异步)、GET /tasks/:id(轮询)、POST /tasks/:id/approve(HITL 审批:200 返回任务记录;404 不存在 / 409 状态不对 / 400 body 非法,或 decisions 里有不在本任务 pendingApprovals 里的 id(整批拒、记录一字不动);停机中仍可用)、POST /tasks/:id/events(事件投入口:白名单 { eventId?, type, payload } 投给挂起任务;404 / 409 / 400 / 413 同档;停机中仍可用)、GET /healthz。返回值可直接传给 http.createServer,另挂 drain() 与 runner。SSE 侧带背压保护:下游积压超过 sseMaxBufferedBytes 即收口该流并中止对应 run。 |
| HttpHandlerOptions | { runner?; authenticate?; metrics?; maxConcurrentRuns?=32; maxBodyBytes?=1MiB; exposeErrors?=false; sseMaxBufferedBytes?=8MiB } | authenticate 拦在入口、读 body 之前(除 /healthz 与 /metrics 外的所有路径);metrics 传 metricsSink()(或任意 { render() } / 返回字符串的闭包)即内建 GET /metrics(不鉴权,与 /healthz 同档);超并发回 503 + Retry-After;exposeErrors 关时 500 只回通用文案;sseMaxBufferedBytes 是 SSE 下游积压上限,超限即收口并中止对应 run(防「连得上但不读」的客户端吃光内存)。⚠️ maxBodyBytes 与 sseMaxBufferedBytes 都必须为正数(0 在构造期抛错):0 会让每个带 body 的请求都 413 / 每个流活不过一帧,要「不限」用 Infinity。 |
| HttpHandler | ((req, res) => Promise<void>) & { drain(opts?) → Promise<boolean>; runner } | 停机:drain() 拒新单 → 等在飞收尾 → 强制收口 SSE 流;未完成任务留在 store 里。活连接(SSE)不享受无限窗口:timeoutMs 缺省 0(一直等)时先收口再等 —— 否则那个等待没有终点。返回值如实分两种收口:切掉了在飞的 run(/run 的流,被 abort 收尾)即算没排干净(false);只关掉旁观者长连(/tasks/:id/stream)不损失工作,不影响返回值。 |
| HttpException | new HttpException(status, body) | 鉴权钩子里抛它即按其 status / body 回(想回 403 就抛 403);抛其它错误回 401 且原文只进服务端日志。 |
| HealthResponse | { ok; inFlight; uptimeMs; draining; suspended: { approval; timer; nextWakeAt } } | GET /healthz 的返回:不鉴权、停机中也回 200;ok 恒 true(能回响应即进程活着),就绪与否看 draining。suspended 是挂起读数(按原因分组的条数 + 最早的目标时刻,无时间挂起时 nextWakeAt 为 null)—— 口径是**本进程**看得见的记录,与 inFlight 同一张表。 |
| RunHttpResponse / TaskSubmitBody | { runId; …result } / { input; options?; idempotencyKey? } | HTTP 契约类型。 |
| serve | (handler, opts: ServeOptions) => Promise<Served> | 起监听:createServer + listen + 等真的在听才 resolve;port 是实际端口(传 0 时由 OS 分配);端口冲突 / 权限不足直接 reject(boot 阶段静默起不来比崩更难查)。 |
| ServeOptions / Served | { port; host?; backlog? } / { server; port; close(opts?) → Promise<boolean> } | serve 的选项与返回值。close 单独给出是为了「只要关服务器、不要 drain」的手动编排;返回是否在预算内关干净。 |
| gracefulShutdown | (opts: GracefulShutdownOptions) => () => Promise<boolean> | 停机编排:drain → 关服务器 + 收连接(预算耗尽强收)→ onShutdown(clean);顺序不可参数化(先关服务器会把在飞请求切断)。返回幂等的 stop()(并发调用共用同一个 in-flight promise)。signals 不传 = 一个信号都不订阅(框架不做进程级决策);第二次信号不吞 —— 给了 onSecondSignal 就交给你,没给就摘掉监听让 Node 默认行为接管。本原语不调 process.exit:退出码与「关池 / 冲 sink 之后再退」是宿主的编排。 |
| GracefulShutdownOptions | { server; drain; signals?; timeoutMs?=15000; onSecondSignal?; onShutdown? } | drain 返回 false = 没排干净(此后本进程不再推进任何任务 ⇒ 宿主必须退出);抛错按 false 处理但仍然关服务器。timeoutMs <= 0 读作「不限」,不是「立刻超时」。onShutdown(clean) 是留给 process.exit 的缝。 |
| registerDefaultTraceSink | (sink: TraceSink) → void | 注册全局默认 sink(createApp 构造期快照合并,已建应用不受后续注册影响)。 |
| createOtlpExporter | ({ endpoint, headers?, serviceName?, timeoutMs?, onExportError?, projectToolSpans? }) → { export(trace) → Promise<void> } | trace 导出到任意 OTLP/HTTP 收集器(缺省 service.name 为 'agentia');节点 span 展开成 attributes + events,零依赖。属性对齐 open-telemetry/semantic-conventions-genai @ 6fd0d763(2026-10-09;GenAI 约定自 v1.42 起移出语义约定主线,该仓无 tag、未版本化,故基准钉 commit)(additive:追加 gen_ai.* 键、保留旧键 —— 含 gen_ai.usage.cache_read/cache_write.input_tokens 与 gen_ai.conversation.compacted),score 事件译为 gen_ai.evaluation.result。projectToolSpans: true(默认关)把 tool.input/tool.output 事件对投影成 execute_tool child span。 |
| jsonlTraceSink | (options: { path: string }) → TraceSink | JSONL 文件 sink:一行一条裸 Trace 追加落盘 —— 产物就是 agentia report / agentia diff / agentia harvest 的输入文件。父目录不存在时构造期递归创建;追加不覆盖;写失败抛给调用方(由 flushSinks 吞掉并落 console.warn)。 |
| createOpenAIClient | (opts?: { apiKey?; baseURL?; fetchImpl?; stream? }) → ModelClient | OpenAI 兼容端点(含 DeepSeek 等)适配成 ModelClient:真流式、图片块转 image_url、转发 signal。stream: false 退回一次性。 |
| createAnthropicClient | (opts?: { apiKey?; baseURL?; … }) → ModelClient | 默认 ModelClient(Anthropic Messages API)。自定义只传 apiKey / baseURL(缺省走环境变量),不需要直接依赖厂商 SDK。 |
| AnthropicClientOptions | { apiKey?; baseURL?; … } | createAnthropicClient 的可配置项(其余参数原样透传 SDK)。 |
| MemoryStore | interface:load(keys) / save(entries) + 可选 loadWithRev / saveIfRev | 跨 run 记忆存储面(可同步或异步实现)。只有 load/save = last-write-wins:同 keys 的并发 run 会丢写且框架检测不到;要能发现就成对实现 loadWithRev + saveIfRev(CAS:冲突时框架不写 + 出声,不替你合并)。只实现一个是装配错误 ⇒ 入口抛 TypeError。 |
| InMemoryMemoryStore | class(MemoryStore) | Map 实现:进程内,无持久化。带版本号(整店一个单调计数,粒度粗 ⇒ 宁可多报冲突)。 |
| MemorySnapshot | interface:values / rev | loadWithRev 的返回:values 同 load;rev 是不透明版本句柄(框架不解释它、只在本 run 回写时原样递给 saveIfRev)。粒度由 store 定,越细误报越少。 |
| MemoryWriteResult | interface:committed / reason? | saveIfRev 的结果:只有 committed: true 才算真写进去了;false(reason: 'conflict')时一个字都不许写,框架据此打一条告警(「回写被拒」不再静默)。 |
integrations · MCP 与指标
外部系统适配层,零运行时依赖 —— MCP 桥与可选能力(zod / Redis)一律 duck-typed(与 RedisLike 同款范式);MCP 连接器出厂自带而只用标准库,见下。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| mcpTools | (client: McpClientLike, opts?: McpToolsOptions) → Promise<AgentTool[]> | 把 MCP server 的 tools/list 映射成框架工具,可直接进 createApp({ tools });与本地 @Tool 同池(同过中间件、同进重名查重)。 |
| McpClientLike | { listTools(); callTool(name, args, opts?) } | MCP client 的最小结构面:任意实现它的对象都能接,框架不 import MCP SDK;接官方 SDK / 远程 server / 自研传输走这条缝。callTool 失败请抛错(框架会包成 is_error)。opts 均可选:abandoned(裁判放弃等待的信号)与 SEP-414 的 traceparent(出站链路;出厂连接器把它放进 params._meta 裸键)。 |
| McpConnector | McpClientLike & { close() } | 两个出厂连接器的公共面。close() 幂等;stdio 侧返回即代表子进程已终止(SIGTERM → 宽限期 → SIGKILL 后仍等 'exit',不留孤儿);HTTP 尽力 DELETE 会话。关闭后再调用会抛可读错误。 |
| createStdioMcpConnector | (cmd: string[], opts?: StdioMcpConnectorOptions) → McpConnector | 出厂 stdio 连接器:spawn 子进程走换行分隔 JSON-RPC(initialize → notifications/initialized → tools/list / tools/call)。惰性(构造不 spawn);替你兜住三件只有连接器能做的事:spawn 失败的异步 'error' 事件、stdout 分帧、协议层 isError: true 转抛错。只用 node:child_process。 |
| createStreamableHttpMcpConnector | (url: string, opts?: StreamableHttpMcpConnectorOptions) → McpConnector | 出厂 StreamableHTTP 连接器:一个 endpoint POST JSON-RPC,application/json 与 text/event-stream 两种响应都接;记住 Mcp-Session-Id 并在后续请求回带、close() 尽力终止会话。会话过期自愈:带会话 id 收到 404 ⇒ 丢弃会话 → 重新握手 → 把这一次重试一次(只一次;onSessionExpired 可观测)。HTTP 失败带数值 status ⇒ 自动被归成可重试 / 不可重试。只用全局 fetch。 |
| StdioMcpConnectorOptions | { env?; cwd?; stderr?; clientInfo?; protocolVersion?; timeoutMs?=60000 } | timeoutMs 只作用于装配期(握手 + tools/list)—— 那两步没有任何别的裁判,server 卡住会让 createApp 永久挂起。 |
| StreamableHttpMcpConnectorOptions | { headers?; clientInfo?; protocolVersion?; timeoutMs?=60000; onSessionExpired?; fetchImpl? } | headers 放鉴权;onSessionExpired 在会话过期自愈时被调一次(自愈本身静默,靠它计数 / 告警);fetchImpl 是测试注入点(与 createOpenAIClient 同款)。 |
| McpToolInfo | { name; description?; inputSchema? } | tools/list 条目的结构面子集;inputSchema 已是 JSON Schema → 原样当 input_schema 用。 |
| McpToolsOptions | { prefix?; server?; timeoutMs?=60000 } | 缺省前缀 mcp_<server>_(没给 server 时 mcp_);timeoutMs 是兜底单次调用超时(放弃等待,非取消)—— 引擎设了 toolTimeoutMs 时本项不参与判定。 |
| MCP_DEFAULT_TIMEOUT_MS | 60000 | 桥的兜底单次调用超时(引擎设了 toolTimeoutMs 时不参与判定)。 |
| MCP_CLOSE_GRACE_MS | 2000 | close() 里 SIGTERM → SIGKILL 的宽限期。 |
| createMcpServer | (app: McpServerApp, opts: McpServerOptions) → McpServer | MCP 反向桥(R8-P5):把 app 的能力菜单(装配后、过中间件的那份)暴露成 MCP server —— Claude Code / Cursor 等任何 MCP 宿主可直接调。传输 stdio(换行分隔 JSON-RPC)或 http(StreamableHTTP:POST 收报文、回 application/json,initialize 铸 mcp-session-id 头但不校验;GET → 405、DELETE → 200;客户端断连中止该次调用的 signal)。范围只到 tools(initialize / tools/list / tools/call + ping)。每次 tools/call 造一棵 trace(run 根 mcp.tools/call + capability span + tool.input/tool.output 事件)投递 opts.sinks。只用标准库。 |
| McpServerApp | { tools: AgentTool[] } | 反向桥的 app 入参(鸭子类型):AgentApp 结构满足 —— app.tools 即装配后的菜单。 |
| McpServerOptions | { transport: 'stdio' | 'http'; host?; port?; path?='/mcp'; server?; client?; sinks?; traceLimits?; maxEventChars?; toolTimeoutMs?; auth?; name? } | client 缺省惰性走 createAnthropicClient()(首次 tools/call 才构造);server 挂进既有 http.Server(不替它 listen,close 只摘 handler);auth 是 http 侧鉴权钩子(读 body 之前、抛错即 401,与 HTTP 宿主同纪律;stdio 信任父进程);toolTimeoutMs 与引擎同款语义(超时 = 放弃等待,回 isError);traceLimits / maxEventChars 与引擎同款(每次 tools/call 那棵 trace 的事件数闸 / 事件正文截断上限,缺省与引擎一致)。 |
| McpServer | { url: string | undefined; ready: Promise<void>; close(): Promise<void> } | 反向桥句柄:url 是 http 模式的实际 endpoint(stdio 恒 undefined);close() 幂等、中止在飞调用的 signal;自有 server 才关,挂进来的只摘 handler。 |
| metricsSink | (opts?: MetricsSinkOptions) → MetricsSink | 进程内指标累加器,天然满足 TraceSink → createApp({ sinks: [metricsSink()] }) 即接入,零新出口。维度都从既有 trace 派生(不用埋点):run 级、能力级(每个 tool/skill/subagent 的调用数、失败数、耗时、token、成本 —— 工具读 turn 上的 tool.output 事件,skill/subagent 读 capability span)、模型级(按模型归因,另出 model_unpriced_turns_total 与 model_usage_missing_turns_total —— 后者是「上游没回 usage」,成本会**看起来**是 0,与「模型没定价」分开数)、评分级(agentia_score gauge + agentia_score_total counter,来自 run 根的 score 事件);另有 agentia_dropped_keys{kind="capability"|"model"|"score"} —— 被基数上限折叠的不同键数,恒定发三个样本(折叠不静默);开了 labelKeys 时另按每键加 kind="label:<key>"、并在组合数封顶时加 kind="label:combos"(未开那几行一个都没有 —— 不是 0,是那个维度压根没开)。 |
| MetricsSink | TraceSink & { snapshot(); render(); contentType?; flush(); stop(); reset() } | snapshot 给数值(含 capabilities / models / droppedCapabilities / droppedModels / droppedScores / exemplars),render 给文本(/metrics 直接回它),contentType 声明 render 产物的格式(随 export 模式走,内置路由读它发响应头),flush 主动导出一次(OTLP 模式),stop 停掉定时导出,reset 清空累计。被折叠的不同键数在 snapshot()(三个 dropped* 字段)与 render()(agentia_dropped_keys)两侧都可见。 |
| MetricsSinkOptions | { export?='prometheus' | 'openmetrics' | 'otlp'; endpoint?; intervalMs?=60000; resourceAttributes?; serviceName?; timeoutMs?; onExportError?; windowSize?=1024; prefix?='agentia_'; labelMode?='capability' | 'kind' | 'none'; maxCapabilities?=200; maxModels?=50; maxScores?=200; labelKeys?=[]; maxLabelValues?=100; maxLabelCombos?=200; buckets? } | tokens 口径 = 四类之和(与 BudgetGuard 一致)。时长给两种口径并存:histogram(*_bucket/_sum/_count,累积语义、可跨实例聚合)+ 窗口内精确分位(gauge、单实例好读)。export:'openmetrics' 出带 exemplars 的 OpenMetrics 文本(指标 ↔ trace 互跳);export:'otlp' 零依赖手写 OTLP/JSON,必须同时给 endpoint(不给就构造期抛错)。labelMode + maxCapabilities/maxModels/maxScores 给三个维度封顶(model 与评分键一样可能是无界键)——超限的键折叠进 __other__(各出口的 label 名分别为 capability / model / name),桶照常累加故总量不丢,丢的只是标签粒度。labelKeys(R8-P4 归因标签)显式点名 run 根 labels.* 里哪些键上指标标签(缺省一个都不上),每键相异值数由 maxLabelValues 封顶(超出归 __other__)。maxLabelValues 封的是每个键的值域,而进内存的是键的组合(叉乘:值域 100 配 3 个键就是一百万条)—— maxLabelCombos(缺省 200)给组合数封顶,超限的新组合折进一个全 __other__ 的桶(量照收、只丢标签粒度),被折的组合数见 snapshot().droppedLabelCombos 与 agentia_dropped_keys{kind="label:combos"};这道上限同样不可关(内存不变量要求每个新基数维度都有 cap,opt-in 挡不住「明知几千租户偏要上」)。单键配置下组合数 ≈ 值数,行为不变。 |
| MetricsSnapshot / CapabilityMetrics / ModelMetrics / RunLabelMetrics / ExemplarSnapshot | { runs; failed; latencyP50; latencyP95; tokens; costUsd; capabilities; models; scores; runLabels; droppedCapabilities; droppedModels; droppedScores; droppedLabelValues; droppedLabelCombos; exemplars } | capabilities 按能力 label 给 calls/errors/分位/tokens/costUsd(工具没有 token 语义,为 null);models 按模型给 turns/tokens/costUsd/unpricedTurns/usageMissingTurns/分位;scores 按评分维度(键 name@source)给最近一次值 / 条数 / 合计(来自 attachScore 的 score 事件);runLabels(R8-P4)按归因标签 combo(键 k=v 逗号连)给 runs/failed/tokens/costUsd —— 只有 labelKeys 点名的键才有。dropped* 是被折叠进 __other__ 的不同键数(各自最多记账 1024 个,满了以后是下界;droppedLabelValues 按 labelKey 分键计,droppedLabelCombos 是组合数上限折掉的那批)。exemplars(ExemplarSnapshot)是指标 ↔ trace 互跳:failed = 最近一次失败 run、slowest = 迄今最慢 run 的 traceId/spanId(OpenMetrics 与 OTLP 出口会挂上)。 |
| DEFAULT_BUCKETS | readonly number[] | 时长直方图的缺省桶边界(毫秒):25 / 50 / 100 / 250 / 500 / 1k / 2.5k / 5k / 10k / 30k。可用 buckets 覆盖(须严格升序)。 |
| buildRunReport | (trace: Trace) → RunReport | 调优报告(纯函数):能力按总耗时降序排行(calls / errors / total / max / tokens / cost)、模型归因、未定价模型清单 —— 回答「该拧哪个旋钮」。单条 run 样本少,故以 total / max 为主,不给分位。 |
| mergeRunReports | (reports: RunReport[]) → RunReport | 跨 run 汇总(分位只在多条 run 上才有统计意义):calls/errors/耗时累加,能力按新的总耗时重排,未定价模型取并集。 |
| renderRunReport | (report: RunReport) → string | 人类可读的纯文本表(CLI / 日志用);未定价模型在 cost 列显式标出。agentia report <trace.jsonl> 就是它的薄壳。 |
| RunReport / CapabilityReport | { traceId; status; durationMs; totalUsage; models; capabilities; unpricedModels; usageMissingModels; runs } | 报告结构;CapabilityReport 含 durations(原始样本,merge 靠它重算跨 run 分位)。 |
| ModelReport / DurationReport | { model; turns; tokens; tokensTotal; costUsd; unpricedTurns; usageMissingTurns; durationMs; durations } / { total; max; p50; p95 } | 模型归因与时长汇总(单 run 的 p50/p95 仅供参考)。 |
eval · 回归断言
把脚本化的模型往返变成一等能力,让「改 prompt / 换模型 / 加工具之后有没有回归」变成断言,而不是靠人眼。断言源就是既有的 Trace。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| scriptedClient | (steps: ScriptedStep[]) → ModelClient | 按脚本依次返回模型响应,并真的把文本块经 on('text') 吐出去(onText / SSE 链路按真实路径走)。步骤在 finalMessage 成功后才前进 → 抛错的步骤在重试时重放同一步。 |
| ScriptedStep | Record<string, unknown> | (params) => message | 一步脚本:直接给响应对象,或按本次请求参数现算(可 throw 模拟 429 / 网络失败)。脚本耗尽即抛错。 |
| defineEval | <T>(def: EvalDefinition<T>) → { name; run() → Promise<EvalReport> } | 定义「用例 + 断言」。run() 不抛(用例失败进报告,一次跑完能看到所有回归),只有应用建不起来才冒泡。 |
| EvalDefinition | { name; app; cases; expect } | app 一轮只建一次(用例间复用装配);expect 抛错 = 该用例失败。 |
| EvalCase | { name?; input; client; opts? } | 一个用例;opts 透传 app.run(注入 resultSchema 才能断言 result.typed)。 |
| EvalContext | { trace: Trace } | 断言上下文(与 result.trace 同一份)。 |
| EvalReport | { name; total; passed; failed; ok; cases: EvalCaseReport[] } | 报告;ok 全通过才 true(total=0 视为通过)。接 CI 只看 report.ok。 |
| EvalCaseReport | { name; ok; stopReason?; error?; trace? } | 失败 case 带完整 trace —— 排查回归就看它。 |
container · 显式 DI
显式 provider 注册,覆盖式语义(后注册覆盖先注册);resolve 单例缓存并做循环依赖检测;重新注册会传递失效下游缓存。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| Container | class:register(...providers) / has(token) / resolve<T>(token) | DI 容器;register 返回 this 可链式。 |
| Token | string | provider 注册键。 |
| Provider | ValueProvider | ClassProvider | FactoryProvider | 三种 provider 形态的联合。 |
| ValueProvider | { provide; useValue } | 直接给值。 |
| ClassProvider | { provide; useClass; deps? } | 类实例化;deps 按序注入构造器参数。 |
| FactoryProvider | { provide; useFactory; deps? } | 工厂函数;deps 与 useFactory 形参一一对应(形参类型自由,不会因逆变被拒)。 |
toolkit · 声明式能力与装配
装饰器声明四类能力,createApp 装配:DI 注册 → 扫描收集菜单 → 装配期静态校验(菜单查重、tools 引用存在性、toolSources 指向、DI 循环依赖)。
| 导出 | 签名摘要 | 说明 |
|---|---|---|
| createApp | (opts: AppOptions) → AgentApp;带 discover 时 → Promise<AgentApp> | 装配应用;目录发现走动态 import,故返回 Promise。 |
| AppOptions | { name?; providers?; modules?; discover?; system; model?; fallbacks?; traceContent?; labels?; maxTokens?; maxIterations?; contextPolicy?; retry?; maxTotalTokens?; maxCostUsd?; priceOverrides?; onUnpricedModel?; toolTimeoutMs?; maxToolConcurrency?; maxEventChars?; toolSources?; tools?; middleware?; sinks?; onTraceEvent?; traceLimits? } | system 必填:SystemPrompt 实例(自动打缓存)或拼好的 SystemParam。tools 是裸工具直进主菜单(MCP 桥等动态工具走这里,同过中间件与查重);sinks 是 trace 出口。priceOverrides / onUnpricedModel 是缺省成本配置(可被单次 run 覆盖)。 |
| AgentApp | class:run(messages, opts?: RunAppOptions) → Promise<AgentRunOutput>;tools;container | 装配产物;run 每执行一次从 SystemPrompt 重建 system,保证 volatile 新鲜。 |
| RunAppOptions | RunInvocationOptions & { system?; resultSchema?; session? } | 单次调用参数:可覆盖 system、给结构化结果 schema、传会话存储(session 不可序列化,故只在程序内直调时可用)。 |
| AgentRunOutput | { run: Run; result: AgentRunResult } | app.run 的返回。 |
| defineModule / AgentModule | (m: AgentModule) → AgentModule / { providers; middleware? } | 第三方能力包约定:providers 先于应用级注册(同 token 应用级覆盖),middleware 拼在更外层;defineModule 为 identity 函数。 |
| Tool | @Tool(spec: ToolSpec) 方法装饰器 | 类方法即工具:入参按 schema 先校验再执行。 |
| ToolSpec | { name?; description; schema: JsonSchema; strict?; approval?: 'required' } | name 缺省取方法名;strict 需 additionalProperties:false + required 齐全;schema 给 fromZod<T> 时方法签名会被校验;approval(HITL)声明后每次调用先挂起等人工审批。 |
| collectTools | (instance: object) → AgentTool[] | 沿原型链扫描 @Tool 方法并绑定实例。 |
| Skill | @Skill(spec: SkillSpec) 方法装饰器 | 方法体是确定性脚本;模型调用只在显式 ctx.llm() 时发生,记在 skill 自己的 capability span 下。 |
| SkillSpec | { name?; description; schema?; model?; maxTokens?; maxIterations?; tools? } | tools 为容器 provider token 列表(ctx.llm() 可调)。 |
| SkillContext | { model?; llm(opts: SkillLlmOptions) → Promise<SkillLlmResult> } | 受限子运行句柄:每次 llm() 在 capability span 下开一轮独立 agent 循环。 |
| SkillLlmOptions / SkillLlmResult | { prompt?; messages?; system?; model?; maxTokens?; maxIterations? } / { text; stopReason } | prompt 与 messages 二选一;system 接受 string 或 SystemPrompt。 |
| collectSkills / skillToTool | (instance) → SkillCapability[] / (capability, resolveTools) → AgentTool | 扫描 @Skill 方法 / 编译成主 agent 菜单项。 |
| SkillCapability | { name; description; inputSchema; spec; invoke(input, ctx) } | 含绑定实例的方法执行器。 |
| SubAgent | @SubAgent(spec: SubAgentSpec) 方法装饰器 | 方法体不执行:框架按 system 另起隔离循环,中间过程不外泄,只有最终报告回流。 |
| SubAgentSpec | { name?; description; schema; system; tools?; model?; maxTokens?; maxIterations?; resultSchema? } | system 支持 string / SystemPrompt / (task) => SystemParam(动态拼,框架不附加);tools 为 provider token 列表。引用成环在装配期即抛错(直接自引用,或经另一个 provider 绕回来都算):引用的解析是延迟求值的,环只在运行期成立,而 maxIterations 只限一回合的宽度、深度没有任何闸 ⇒ 一次自我调用就是无限递归。抛错信息给出环上的节点序列。 |
| collectSubAgents / subagentToTool | (instance) → SubAgentCapability[] / (capability, resolveTools) → AgentTool | 扫描 @SubAgent 方法 / 编译成 AgentTool(子循环复用 ctx.client 与 recorder)。 |
| SubAgentCapability | { name; description; inputSchema; spec } | 子代理能力元数据。 |
| Prompt | @Prompt(spec: PromptSpec) 方法装饰器 | 纯文本资产编译成无副作用的拉取型工具;每次调用重算(volatile 语义)。 |
| PromptSpec | { name?; description; schema?; version? } | description 写清何时该拉取,模型据此决定调用;schema 缺省空对象(无参资产);version 是资产版本号 —— 装配期随能力名收集,每次 run 落 run 根 prompts.versions attribute。 |
| collectPrompts | (instance: object) → AgentTool[] | 扫描 @Prompt 方法(static 方法亦可)。 |
| asset | (base: string | URL, rel: string) → string | asset(import.meta.url, './system.md') 相对能力目录读文本资产;每次调用现读不缓存。 |
| loadEnvFile | (options?: LoadEnvOptions) → Record<string, string> | 读一份 .env 进 process.env,返回本次真正生效的键。框架不自动读 .env —— 读哪个文件、什么时候读由宿主的启动代码决定(脚手架的装配模块 app.ts 已内置这一行)。文件不存在静默返回 {};真实环境变量优先,已定义的键不被覆盖(含空串)。 |
| LoadEnvOptions | { path?; override? } | path 缺省 .env(相对 cwd);override 缺省 false(让文件压过环境变量时置 true)。 |
| discoverProviders | (dir: string | string[]) → Promise<Provider[]> | 给一个目录或一组目录(数组顺序即装配顺序),扫各目录下的 <name>/index.ts:default export 为类 / Provider / Provider[],类以文件夹名为 token 注册;数组里任一目录不存在即报错,跨目录重名 token 留告警(装配期后者覆盖)。 |
| applyMiddleware | (tools: AgentTool[], middleware: CapabilityMiddleware[]) → AgentTool[] | 洋葱模型包裹整个菜单(链序 = 注册顺序,先注册最外层);链为空原样返回,零开销。 |
| CapabilityMiddleware | (call: CapabilityCall, next: CapabilityNext) => unknown | 能力调用前后横切(鉴权 / 限流 / 缓存 / 审计 / 配额);不调 next 即短路,抛错按能力失败处理。 |
| CapabilityCall / CapabilityNext | { capability; input; ctx? } / (input?) => unknown | 入参 schema 校验已过;next(newInput) 可改写入参;连调两次会抛错。 |
| fromZod | (jsonSchema: JsonSchema, zod: unknown) → JsonSchema | zod 可选接入(peer):校验走 zod safeParse(结构面识别,框架不 import zod),错误路径原样回给模型自我修正。 |