@migor/agentia 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +153 -1
- package/README.en.md +4 -1
- package/README.md +23 -1
- package/dist/AGENTS.md +53 -9
- package/dist/core/object.d.ts +21 -0
- package/dist/core/object.js +27 -0
- package/dist/core/object.js.map +1 -0
- package/dist/core/run.d.ts +11 -4
- package/dist/core/timeout.d.ts +20 -2
- package/dist/core/timeout.js +32 -2
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool.d.ts +3 -3
- package/dist/core/trace.d.ts +1 -1
- package/dist/engine/budget.d.ts +3 -3
- package/dist/engine/loop.d.ts +22 -21
- package/dist/engine/loop.js +1 -0
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/spec.d.ts +2 -2
- package/dist/engine/trace-diff.d.ts +3 -3
- package/dist/engine/turn.d.ts +18 -17
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.js +26 -27
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/mcp.d.ts +107 -10
- package/dist/integrations/mcp.js +459 -0
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics.d.ts +15 -0
- package/dist/integrations/metrics.js +40 -1
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.d.ts +10 -0
- package/dist/integrations/openai.js +78 -3
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.d.ts +14 -1
- package/dist/integrations/otlp.js +14 -1
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/runtime/run.d.ts +1 -1
- package/dist/runtime/run.js +5 -2
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/systemPrompt.d.ts +1 -1
- package/dist/runtime/systemPrompt.js.map +1 -1
- package/dist/store/store.d.ts +7 -7
- package/dist/toolkit/middleware.d.ts +1 -1
- package/dist/toolkit/module.js +44 -36
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/skill.d.ts +1 -1
- package/dist/transport/async.d.ts +6 -1
- package/dist/transport/async.js +39 -4
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http.d.ts +3 -3
- package/dist/transport/http.js +10 -5
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/scheduler.js +2 -2
- package/dist/transport/scheduler.js.map +1 -1
- package/dist/transport/sse.d.ts +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,156 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
### 变更
|
|
11
|
+
|
|
12
|
+
> 本版两个主题:**MCP 连接器出厂自带**(新增公共 API:`createStdioMcpConnector` /
|
|
13
|
+
> `createStreamableHttpMcpConnector` / `McpConnector` / `MCP_CLOSE_GRACE_MS`),
|
|
14
|
+
> 以及一轮复审与「已知边界」收口。**两处行为变更,都不需要使用者改代码**:
|
|
15
|
+
> ① StreamableHTTP 会话过期从「抛错、需重建连接器」变成**自愈**(多了个可选的
|
|
16
|
+
> `onSessionExpired` 钩子);② `close()` 从「到点即返回」变成「返回即子进程已终止」。
|
|
17
|
+
> **无破坏性变更** ⇒ 不需要迁移动作。
|
|
18
|
+
|
|
19
|
+
### 修复(MCP 连接器两条「已知边界」收掉)
|
|
20
|
+
|
|
21
|
+
- **StreamableHTTP 会话过期不再需要人工重建连接器**(`404` 自愈)。带会话 id 收到 `404` 的语义是
|
|
22
|
+
「这个会话我不认识」⇒ **该请求没有被 server 执行** ⇒ 连接器丢会话、重新握手、把**这一次**重试一次
|
|
23
|
+
(**只一次**,第二次再 404 直接抛,不循环)—— 这也是 MCP 规范对客户端的要求。
|
|
24
|
+
此前按不可重试的 `api` 错抛出,长跑宿主的会话一过期就得**重建整个连接器**。
|
|
25
|
+
自愈本身是静默的,所以给了 **`onSessionExpired`** 钩子:不挂它就没人知道恢复发生过 ——
|
|
26
|
+
「静默恢复」和「静默失效」在监控上看不出区别。
|
|
27
|
+
- **`close()` 现在保证返回时子进程已终止**(stdio)。此前「到点即 resolve」:SIGTERM → 等
|
|
28
|
+
`MCP_CLOSE_GRACE_MS` → SIGKILL **并立刻返回**,此刻子进程往往还在(未回收)—— 调用方以为
|
|
29
|
+
进程没了,实际留下一个孤儿。现在 SIGKILL 之后**继续等真正的 `'exit'`**(SIGKILL 不可被捕获,
|
|
30
|
+
该事件必达)。
|
|
31
|
+
- 验证:`tests/integrations/mcpConnector.test.ts` 27 → 31 例;
|
|
32
|
+
**变异电池 6/6 全部被抓到、0 漏网**(删自愈分支 / 丢掉 `sessionId !== null` 前置 / 重试透传
|
|
33
|
+
`allowReinit`(无限重试)/ 去掉 `onSessionExpired` / `close()` 恢复不等 reap / 夹具不再忽略
|
|
34
|
+
SIGTERM ⇒ 证明那条用例真在测 SIGKILL 路径)。
|
|
35
|
+
|
|
36
|
+
### 修复(官网手写数字:补上真正没被守的那几个)
|
|
37
|
+
|
|
38
|
+
- `api.html` 的 `0 个运行时依赖` / `4 类能力` 与 `index.html` 首屏的 `4 类能力` / `0 个运行时依赖` /
|
|
39
|
+
`3 类触发` **此前没有任何断言** —— 加一个运行时依赖、增删一类能力或触发宿主,页面会继续写旧数字
|
|
40
|
+
而没人拦。现在逐条对源码核(`package.json` 的 `dependencies` 数 / 四个能力装饰器 / 三个传输宿主)。
|
|
41
|
+
变异电池 3/3 会咬(改成 1 / 5 / 4 各判红一次)。
|
|
42
|
+
⚠️ 顺带**更正一处过度声明**:`210 个导出` 与 `9 个层次` **本来就有守卫**
|
|
43
|
+
(`api-page.test.ts` 已有:前者对 `src/index.ts` 导出数、后者对页面 section 数)——
|
|
44
|
+
它们从来不是缺口。首屏 `1:1 run ↔ trace`(真守卫在 `traceLink.test.ts`)与 `0 反射`
|
|
45
|
+
(策略声明,无法从源码计数推导)**刻意不推导**,已在 `guards.md` §2「待守」登记。
|
|
46
|
+
|
|
47
|
+
### 新增(MCP 连接器**出厂自带**:stdio + StreamableHTTP)
|
|
48
|
+
|
|
49
|
+
- **`createStdioMcpConnector(cmd, opts?)`** / **`createStreamableHttpMcpConnector(url, opts?)`**
|
|
50
|
+
—— MCP 的两种传输现在随框架发布。此前只有 `mcpTools()` 那条 duck-typed 桥,**连接器要自己写**:
|
|
51
|
+
官方示例只有 `scripts/e2e-mcp.ts` 里一份 94 行的最小参考(还是内联在脚本里的私有副本)。
|
|
52
|
+
公共面另加 `McpConnector`(`McpClientLike` + `close()`)与 `MCP_CLOSE_GRACE_MS`。
|
|
53
|
+
**只用标准库**(`node:child_process` + 全局 `fetch`)⇒ 不新增任何第三方依赖,「零运行时依赖」不变;
|
|
54
|
+
也**没有**放宽「第三方 SDK 对用户不可见」—— 一个 MCP SDK 都没 import,`McpClientLike` 这条缝原样保留
|
|
55
|
+
(接官方 SDK / 远程 server / 自研传输照旧走它)。
|
|
56
|
+
- **本条反转了 2026-09-11 的 F7**(「连接器放独立可选包 `@migor/mcp`」)。那个包**从未发布**,
|
|
57
|
+
而仓库里有 5 处注释把它当既成事实引用(含 `src/index.ts` 的公共面注释)—— 现在全部改正。
|
|
58
|
+
为什么反:F7 的理由「守住零运行时依赖」不成立(该口径 = 不依赖**第三方包**;`spawn` / `fetch` 都是
|
|
59
|
+
标准库),且 `store/` 的三层形状里「只用标准库的平台能力」一律内置(`FileTaskStore` / `SqliteTaskStore` /
|
|
60
|
+
HTTP 宿主),独立包才是那个例外。完整论证与可逆性判据见 `docs/spec.md` §10 **2026-09-18 ⑨**。
|
|
61
|
+
- **连接器替你兜住三件只有它能做的事**(此前只活在 `scripts/e2e-mcp.ts` 那段内联副本里,无门禁守着):
|
|
62
|
+
① spawn 失败的 `'error'` 是**异步事件**,不接住就是未捕获异常(真实宿主进程直接崩,没有 try/catch
|
|
63
|
+
接得住);② stdout 必须按 `\n` **攒包**(一条报文可能跨多个 chunk);③ **协议层 `isError: true`
|
|
64
|
+
转成抛错** —— 否则模型收到一条「成功」的结果、trace 也把这次失败的调用记成成功。
|
|
65
|
+
- **连接器的 `timeoutMs` 只管装配期**(握手 + `tools/list`):那两步此前**没有任何裁判**,server 卡住会让
|
|
66
|
+
`createApp` 永久挂起;`callTool` 的裁判仍是引擎 / 桥(延续「一次调用只有一个裁判」)。
|
|
67
|
+
- **已知边界**:StreamableHTTP 会话过期(带会话 id 收到 `404`)**不自动重握手**,按不可重试的 `api` 错抛出;
|
|
68
|
+
`close()` 幂等且**有界**(SIGTERM → 2000 ms 后 SIGKILL),但**不保证等到子进程被 reap**。
|
|
69
|
+
- 验证:新增 `tests/integrations/mcpConnector.test.ts` 27 例(stdio 侧起**真子进程**);
|
|
70
|
+
**变异电池 9/9 全部被抓到、0 漏网**;`npm run e2e:mcp` 改为走出厂连接器后真第三方 server 全绿 ——
|
|
71
|
+
此前那条端到端证明测的是它自己那份私有副本,现在测的是用户拿到的东西。
|
|
72
|
+
|
|
73
|
+
### 修复(第七轮复审收口:三处「不报错地不干活」)
|
|
74
|
+
|
|
75
|
+
- **Anthropic 适配器:流被截断 / 空流现在抛带 `status` 的错误**(`AnthropicApiError(500)`)。
|
|
76
|
+
此前两处抛裸 `Error` ⇒ `classifyError` 判 `unknown` + `retryable:false`:上游故障被记成「模型的
|
|
77
|
+
协议问题」(排障方向被带偏),且**引擎层重试一次都不会发生**。更糟的是「已吐出半句之后断流」——
|
|
78
|
+
内容非空使旧判据(`!started`)不触发,`stop_reason: null` 落 `unknown_stop_reason`(同样不可重试)。
|
|
79
|
+
现在按 openai 侧同款判据:**既无 `message_stop`、也无 `message_delta` 的 `stop_reason` ⇒ 抛 500 可重试**。
|
|
80
|
+
- **OpenAI 适配器:流内 error 分片的 status 反推分三档**(此前只把限流判 429、其余一律 500 + 可重试)。
|
|
81
|
+
`invalid_request_error` / `context_length_exceeded` / `model_not_found` / 鉴权 / `content_filter` 这类
|
|
82
|
+
**改配置才有救**的 4xx 病因现在判 400 且**不可重试**(此前白重试 3 次、trace 记成 `server` 而非 `api`)。
|
|
83
|
+
- **OpenAI 适配器:非流式路径的「200 + 空补全」不再记成成功**。`choices[0].message.content = null`
|
|
84
|
+
+ `finish_reason: 'stop'` 此前映射成「空文本 + end_turn」成功收尾,而同一响应在**流式**路径上会被判
|
|
85
|
+
失败 —— 两条路径结论相反,且与模块头「上游故障绝不映射成成功」相反。`content_filter` 的合法空回复
|
|
86
|
+
仍豁免(与流式同款例外)。
|
|
87
|
+
- **`AsyncRunner.resumePending` 补重入闸**:并发/重入调用现在**共享同一次扫描的结果**(返回在飞那个
|
|
88
|
+
Promise,而不是谎报 0)。「先落库再派发」只堵住了**串行**重扫 —— 认领是异步的,两个并发调用都在任一
|
|
89
|
+
`save` 落地前 `list()` 到旧快照,`ownerId` 过滤双双失效 ⇒ 同一任务被派发两次(副作用与花费翻倍)。
|
|
90
|
+
- **`metricsSink`:基数折叠在 `/metrics` 上可见** —— 新增 gauge 家族
|
|
91
|
+
`agentia_dropped_keys{kind="capability"|"model"|"score"}`(Prometheus 与 OTLP 两侧都出,恒定发三个样本)。
|
|
92
|
+
此前 `droppedCapabilities/Models/Scores` 只存在于 `snapshot()`,而文档让用户把 `metricsSink()` 接到
|
|
93
|
+
`GET /metrics`(只消费 `render()`)⇒ Prometheus-only 的部署**完全看不见折叠发生**(静默丢失);
|
|
94
|
+
对照:同一份文件对「算不出成本的 turn」专门发了 `model_unpriced_turns_total`。
|
|
95
|
+
- **`metrics.ts` 的 OTLP 导出失败改抛 `MetricsExportError`(带数值 `status`)**:裸 `Error` 被判
|
|
96
|
+
`unknown`,宿主在 `onExportError` 里拿不到 status,无法区分「collector 拒收(4xx,改配置)」与
|
|
97
|
+
「collector 挂了(5xx,等它回来)」。
|
|
98
|
+
|
|
99
|
+
### 修复(守卫自身的洞 —— 本版主题是「把没门禁的约定收口」,而守卫自己有洞)
|
|
100
|
+
|
|
101
|
+
- **`tests/architecture/transport-errors.test.ts` 的注释剥离会瘫痪整份文件的扫描**(严重):
|
|
102
|
+
旧实现把每行「截到 `//` 为止」,而 `//` 会出现在**字符串里**(最典型是 URL)。截断会切掉该行闭合的
|
|
103
|
+
`)` 与反引号 ⇒ `bareErrorThrows` 的括号配平一路吃到文件末尾 ⇒ **那一行之后的每一处抛错都再也扫不到、
|
|
104
|
+
且全程不报错**。实测 `src/integrations/metrics.ts`:8 处裸抛错只剩 2 处可见,被吞掉的正好包括
|
|
105
|
+
`OTLP metrics 导出失败: HTTP ${res.status}`(本守卫存在的理由)。改为「整行丢弃注释行」,
|
|
106
|
+
并以合成样本复现该机制作回归钉(旧实现下会红)。
|
|
107
|
+
- `tests/docs/guards-registry.test.ts` 的**条目密度下限由 8 提到 24**(实测 32):旧下限意味着删掉
|
|
108
|
+
注册表 §1.1–§1.3 整整三节仍会绿 —— 防「抽词器退化」的护栏同时替「整节被删」放了行。
|
|
109
|
+
|
|
110
|
+
### 仍未做(如实标注,下一轮)
|
|
111
|
+
|
|
112
|
+
- `tests/integrations/adapter-parity.test.ts` 的矩阵**结构上测不到「缺省 `maxRetries` 对称」**:
|
|
113
|
+
`make(maxRetries: number)` 是必填、所有场景都显式 `a.make(2)` ⇒ 把 openai 的缺省改成 0 仍全绿。
|
|
114
|
+
而该矩阵被造出来正是为守这件事(修法:加一条走缺省的场景)。
|
|
115
|
+
- `maxToolConcurrency` 是否该随 `ToolRunContext` 透传给嵌套能力(`docs/guards.md` §2 已登记的
|
|
116
|
+
「手写转发列表不得漏字段」形状,历史事故 = `runAgentScoped` 漏 `toolTimeoutMs`)—— 待口径判定:
|
|
117
|
+
转发,或在 `types.ts` 注明「只作用本层循环」。
|
|
118
|
+
|
|
119
|
+
## [Unreleased]
|
|
120
|
+
|
|
121
|
+
## [0.6.3] - 2026-09-18
|
|
122
|
+
|
|
123
|
+
> 本版主题:把第六轮 16 条的共同根因(「约定写在文档里、但没有门禁」)收口 ——
|
|
124
|
+
> 守卫注册表 + 三个新架构守卫 + 适配器对拍矩阵;`exactOptionalPropertyTypes` 全量迁移;
|
|
125
|
+
> OpenAI 适配器补齐客户端内层重试(与 anthropic 对称)。**无破坏性变更**。
|
|
126
|
+
|
|
127
|
+
### 新增(守卫基建 + 适配器对齐)
|
|
128
|
+
|
|
129
|
+
- **守卫注册表 `docs/guards.md`**:「哪类危险由谁守」的单源清单(§1 已挂守卫 → 保护的
|
|
130
|
+
不变量 → 退化后果;§2 待守缺口;§3 写法纪律),配套 PR 模板的「危险类自查 5 问」。
|
|
131
|
+
- **两个新架构守卫**:`tests/architecture/transport-errors.test.ts`(`integrations` 的
|
|
132
|
+
传输抛错必须带数值 `status`,否则 `classifyError` 判 unknown、重试层静默失效 ——
|
|
133
|
+
上线当天即抓到 `otlp.ts` 的同形漏网)与 `tests/architecture/tsconfig-strictness.test.ts`
|
|
134
|
+
(`exactOptionalPropertyTypes` / `strict` / `types:["node"]` 三个承重开关不得被关)。
|
|
135
|
+
另有元守卫 `tests/docs/guards-registry.test.ts` 防注册表本身腐化。
|
|
136
|
+
- **`OpenAIClientOptions.maxRetries`(缺省 2)**:OpenAI 适配器补齐**客户端内层重试**
|
|
137
|
+
(408/409/429/5xx + `retry-after` 尊重,与 `createAnthropicClient` 逐字对齐)——
|
|
138
|
+
此前同一个 429 在 anthropic 打 3 次网络请求、在 openai 只打 1 次(引擎层那一次)。
|
|
139
|
+
对称性由 `tests/integrations/adapter-parity.test.ts` 守住(一份场景表跑两侧 + 跨侧
|
|
140
|
+
对称断言)。
|
|
141
|
+
- **`exactOptionalPropertyTypes` 开启并完成迁移**(39 处 `error TS` 全清):结果/状态
|
|
142
|
+
记录改必填 `T | undefined`、内部管道 `?: T | undefined`、**公共入参签名不动**(调用点
|
|
143
|
+
条件展开或 `omitUndefined`)。「显式 undefined ≠ 不传」从此是类型级约束 ——
|
|
144
|
+
`retry: { maxAttempts: undefined }` 静默关重试这类写法在编译期就写不出来。
|
|
145
|
+
|
|
146
|
+
### 修复
|
|
147
|
+
|
|
148
|
+
- **`createOtlpExporter` 非 2xx 改抛 `OtlpExportError`**(带数值 `status`):此前裸
|
|
149
|
+
`Error` 会被 `classifyError` 判 `unknown` + 不可重试(与 OpenAI 适配器同形的病,
|
|
150
|
+
由新守卫抓到)。
|
|
151
|
+
|
|
152
|
+
### 重构
|
|
153
|
+
|
|
154
|
+
- 两条适配器的 client 层退避(`backoffMs` / `interruptibleSleep`)单源收进
|
|
155
|
+
`core/timeout.ts` —— 此前曾短暂存在 anthropic / openai 两份逐字副本;「不与引擎层
|
|
156
|
+
`backoffDelay` 合并」的例外仍在(±25%+retry-after vs ±20%,策略不同)。
|
|
157
|
+
|
|
8
158
|
## [0.6.2] - 2026-09-18
|
|
9
159
|
|
|
10
160
|
> 本版主题:第六轮全量 review 收口 —— 16 条「不报错地不干活」修复(含两处**语义变更**,见下)、
|
|
@@ -331,7 +481,9 @@
|
|
|
331
481
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
332
482
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
333
483
|
|
|
334
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.
|
|
484
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.7.0...HEAD
|
|
485
|
+
[0.7.0]: https://github.com/retrychx/agentia/releases/tag/v0.7.0
|
|
486
|
+
[0.6.3]: https://github.com/retrychx/agentia/releases/tag/v0.6.3
|
|
335
487
|
[0.6.2]: https://github.com/retrychx/agentia/releases/tag/v0.6.2
|
|
336
488
|
[0.6.1]: https://github.com/retrychx/agentia/releases/tag/v0.6.1
|
|
337
489
|
[0.6.0]: https://github.com/retrychx/agentia/releases/tag/v0.6.0
|
package/README.en.md
CHANGED
|
@@ -124,7 +124,10 @@ One run == one trace (`traceId === runId`), **built in from turn 0** — not a b
|
|
|
124
124
|
- **Models** — `createAnthropicClient()` (default) and `createOpenAIClient()` for any OpenAI-compatible
|
|
125
125
|
endpoint (DeepSeek, **Ollama**, gateways). You never need to touch a vendor SDK: pass
|
|
126
126
|
`createAnthropicClient({ baseURL })` / `createOpenAIClient({ baseURL })`.
|
|
127
|
-
- **MCP** — `
|
|
127
|
+
- **MCP** — `createStdioMcpConnector()` / `createStreamableHttpMcpConnector()` ship with the framework
|
|
128
|
+
(stdlib only: `node:child_process` + global `fetch`); `mcpTools()` bridges the server's tools into the
|
|
129
|
+
menu. The framework never imports the MCP SDK, and the `McpClientLike` seam stays open
|
|
130
|
+
(bring the official SDK / a remote server / your own transport).
|
|
128
131
|
- **Memory** — `memory: { store, keys }` for cross-run state; task stores: memory / JSONL / SQLite / Redis
|
|
129
132
|
- **Evals** — `scriptedClient` + `defineEval` turn mocked runs into a first-class diagnostic loop
|
|
130
133
|
- **Platforms** — send traces to Langfuse / Phoenix / etc. via OTLP or a tiny custom `TraceSink`
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ $EDITOR my-app/.env # 填 ANTHROPIC_API_KEY(也可直接 expo
|
|
|
30
30
|
> 框架**不自动**读 `.env`:脚手架 `src/main.ts` 首行的 `loadEnvFile()` 负责把它读进 `process.env`。
|
|
31
31
|
> 真实环境变量优先(CI / docker / 命令行永远赢过文件),`loadEnvFile({ override: true })` 才反过来。
|
|
32
32
|
|
|
33
|
-
> **版本**:`0.
|
|
33
|
+
> **版本**:`0.7.0`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
|
|
34
34
|
> `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
|
|
35
35
|
> [`examples/README.md`](examples/README.md) 的「依赖说明」。
|
|
36
36
|
|
|
@@ -283,6 +283,28 @@ import { createHttpHandler } from '@migor/agentia';
|
|
|
283
283
|
createServer(createHttpHandler(app, { runner })).listen(8080);
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
### MCP(连接器出厂自带)
|
|
287
|
+
|
|
288
|
+
MCP server 的工具映射成菜单项 —— 连接器(stdio / StreamableHTTP)**随框架发布**,只用标准库(`node:child_process` + 全局 `fetch`),不新增第三方依赖:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { createApp, createStdioMcpConnector, createStreamableHttpMcpConnector, mcpTools } from '@migor/agentia';
|
|
292
|
+
|
|
293
|
+
// stdio:spawn 一个 MCP server 子进程
|
|
294
|
+
const mcp = createStdioMcpConnector(['uvx', 'mcp-server-time']);
|
|
295
|
+
const tools = await mcpTools(mcp, { server: 'time' }); // → mcp_time_get_current_time …
|
|
296
|
+
|
|
297
|
+
// 远程 server:StreamableHTTP(一个 endpoint,鉴权走 headers)
|
|
298
|
+
const remote = createStreamableHttpMcpConnector('https://mcp.example/mcp', {
|
|
299
|
+
headers: { authorization: 'Bearer …' },
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// 与本地 @Tool 同池:同过中间件链、同进重名查重
|
|
303
|
+
createApp({ system, providers: [...], tools });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`McpClientLike` 这条缝仍然在:接官方 SDK / 远程 server / 自研传输时实现 `listTools()` + `callTool()` 两个方法即可(框架**不 import** MCP SDK)。
|
|
307
|
+
|
|
286
308
|
### 多模型(OpenAI 兼容端点)
|
|
287
309
|
|
|
288
310
|
```ts
|
package/dist/AGENTS.md
CHANGED
|
@@ -450,7 +450,7 @@ process.on('SIGTERM', async () => {
|
|
|
450
450
|
| `mapWithConcurrency` | 有界并发 map(结果保序);`maxToolConcurrency` 的底座,也可自用 |
|
|
451
451
|
|
|
452
452
|
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs` 到点同样是**真中止**(构造期校验:必须 ≥ 0 的**有限**数 —— NaN/Infinity 会被 `setTimeout` 钳到 1ms,等于每个任务立即超时,故直接抛错;要「不限」传 0 或不设)。
|
|
453
|
-
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与底层 client
|
|
453
|
+
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与底层 client 的**内置重试**叠加 —— **两条内置适配器口径一致**(`createAnthropicClient` / `createOpenAIClient` 都有 `maxRetries`,缺省 2,重试同一状态码集合 408/409/429/5xx)—— 建议二选一调(这里 `maxAttempts: 1`,或 `<适配器>({ maxRetries: 0 })`)。
|
|
454
454
|
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error`);不带该头仍回一元 JSON。
|
|
455
455
|
- **工具超时 / 并发闸门**:`toolTimeoutMs` 超时**不杀 run**(该条 tool_result 记 `is_error`,模型可换路);`maxToolConcurrency` 给同回合的并行工具设上限(默认全并行)。⚠️ 超时 = **放弃等待**,`AgentTool.run` 没有 signal 参数,**副作用可能已发生** —— 想真停的工具请自行读 `ToolRunContext.signal`。**超时判定只有一个裁判**:`toolTimeoutMs` 是唯一判据 —— 工具自带的超时(如 MCP 桥的 `timeoutMs`)在设了本项时**不参与**判定;反过来说,工具自判的超时(抛 `code='timeout'` 的错误)与引擎判的记**同一类账**(`errorKind='timeout'`),并同样回 `is_error`。
|
|
456
456
|
|
|
@@ -606,6 +606,8 @@ runner.submit(msg.value, {
|
|
|
606
606
|
来自 `capability` span。**`@Prompt` 不建 span、无独立耗时,因此不产出能力指标**(如实缺省,不硬凑)。
|
|
607
607
|
- **模型级** —— 按模型(`llm.turn` 的 span name)归因 turn 数 / token / 成本 / 耗时,并单独给出
|
|
608
608
|
`model_unpriced_turns_total`(算不出成本的 turn 数 —— **成本护栏失效的显式信号**)。
|
|
609
|
+
同理,**标签基数折叠不是静默的**:`agentia_dropped_keys{kind="capability"|"model"|"score"}`
|
|
610
|
+
(恒定发三个样本,即使为 0 —— 「0 → N」这个变化本身就是要告警的信号)。
|
|
609
611
|
- **评分级** —— 来自 run 根 span 的 `score` 事件(`attachScore` 写入):
|
|
610
612
|
`agentia_score{name,source}` gauge 记**最近一次**值(分数不是累加量),`agentia_score_total{name,source}` counter 记条数;
|
|
611
613
|
`snapshot().scores` 以 `name@source` 为键(source 缺省时裸 name)暴露 `{ value, count, sum }`(平均 = sum/count),
|
|
@@ -654,6 +656,8 @@ runner.submit(msg.value, {
|
|
|
654
656
|
- 分位是**窗口内精确值**(最近 rank 法),只反映最近 `windowSize` 条样本;**直方图计数是累积的**(全历史),两者语义不同、各有各的用处。
|
|
655
657
|
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` —— 三个维度都由基数上限封顶(`maxCapabilities` / `maxModels` / `maxScores`),长跑宿主不会被拖住。
|
|
656
658
|
- 超上限的键折叠进 `__other__`:**丢的只是标签粒度,量不丢** —— `__other__` 桶照常累加,`snapshot()` 里各维度的总数仍然对得上。被折叠的**不同**键数见 `droppedCapabilities` / `droppedModels` / `droppedScores`(各自最多记账 1024 个键,满了以后是下界)。
|
|
659
|
+
**同样的数在 `/metrics` 上也看得见**(`render()`):`agentia_dropped_keys{kind=…}` —— 只看 Prometheus
|
|
660
|
+
不看 `snapshot()` 的部署不会漏掉折叠。
|
|
657
661
|
- `costUsd` 依赖模型在价格表内(不在表里时不计、并计入 `unpricedTurns` 与 `usage.unpriced` 事件);根 span 未收尾(如失败路径的半截 trace)的 run 不进延迟样本。
|
|
658
662
|
|
|
659
663
|
#### 调用树面板(`agentia dev` 的本地面板 / 官网 Playground)
|
|
@@ -748,25 +752,40 @@ const app = await createApp({ /* … */ sinks: [jsonl] });
|
|
|
748
752
|
| `diffTraces` | 两条 trace 的 A/B 比对(prompt / 模型实验):run 级 summary + 逐 span 字段差;纯函数,llm.turn 配对**忽略模型名**,缺省忽略墙钟 |
|
|
749
753
|
| `applyMiddleware` | 手动包裹配置菜单(装配层已自动做) |
|
|
750
754
|
|
|
751
|
-
#### MCP
|
|
755
|
+
#### MCP 桥与连接器(MCP 是「工具来源」,不是新机制)
|
|
752
756
|
|
|
753
757
|
| API | 说明 |
|
|
754
758
|
|---|---|
|
|
755
759
|
| `mcpTools` | 把 MCP server 的 `tools/list` 映射成框架 `AgentTool[]`(进 `createApp({ tools })`) |
|
|
756
760
|
| `McpClientLike` | 最小结构面:`listTools()` + `callTool(name, args)`;框架**不 import** MCP SDK |
|
|
761
|
+
| `createStdioMcpConnector` | **出厂 stdio 连接器**:spawn 一个 MCP server 子进程,走换行分隔 JSON-RPC(只用 `node:child_process`) |
|
|
762
|
+
| `createStreamableHttpMcpConnector` | **出厂 StreamableHTTP 连接器**:一个 endpoint POST JSON-RPC;`application/json` 与 `text/event-stream` 两种响应都接(只用全局 `fetch`) |
|
|
763
|
+
| `McpConnector` | 两个连接器的公共面 = `McpClientLike` + `close()`。自己写传输时实现 `McpClientLike` 即可,不必碰它 |
|
|
757
764
|
| `MCP_DEFAULT_TIMEOUT_MS` | 桥的**兜底**单次调用超时(60000 ms)—— 引擎设了 `toolTimeoutMs` 时**不参与**判定 |
|
|
765
|
+
| `MCP_CLOSE_GRACE_MS` | `close()` 里 SIGTERM → SIGKILL 的宽限期(2000 ms) |
|
|
758
766
|
|
|
759
767
|
- **名字**:`prefix + 归一化原名`(MCP 名里的 `-` / `.` / 空格 → `_`)。归一化后**空名(原名不含任何 ASCII 字母/数字/下划线时产物为空,如全 emoji 名)/ 撞名 / 超 64 字符**一律**装配期抛错**(静默改名会得到一个调不回去的名字,比启动期报错难查得多)。
|
|
760
768
|
- **原名**:每次调用写进发起 turn 的两条 attribute —— `mcp.tool.<菜单名>`(每次调用各一条,并行调用互不覆盖,审计 / 回放靠它把菜单名还原成 server 认识的原名)与 `mcp.tool`(本次 turn **最近一次**的原名,兼容既有查询)。
|
|
761
769
|
- **入参 schema**:MCP 的 `inputSchema` 已是 JSON Schema → 原样透传,由 engine 的子集校验器在 `callTool` **之前**校验(非法入参根本不会发给 server,模型自己会改)。
|
|
762
|
-
- **失败**:`callTool` 抛错 → 该条 `tool_result` 记 `is_error`,**不杀 run**(与本地工具抛错同语义)。⚠️ **协议层的 `isError: true` 框架看不见** ——
|
|
770
|
+
- **失败**:`callTool` 抛错 → 该条 `tool_result` 记 `is_error`,**不杀 run**(与本地工具抛错同语义)。⚠️ **协议层的 `isError: true` 框架看不见** —— 连接器必须转成抛错,否则模型以为成功了(出厂连接器已代你处理)。
|
|
763
771
|
- **超时**:**只有一个裁判**。引擎设了 `toolTimeoutMs` 时,桥的 `timeoutMs`(缺省 `MCP_DEFAULT_TIMEOUT_MS` = 60000)**不参与判定**;它只在「桥脱离引擎单用」或「引擎没设 `toolTimeoutMs`」时作为兜底。两条路径共用同一判定(`core/timeout.ts`:**看实测耗时,不看竞速**),超时都记 `errorKind='timeout'` + `is_error` 回模型、**不杀 run**。你自己写的工具要报超时,抛一个 `code === 'timeout'` 的错误即可(不必 import 框架的类)。
|
|
764
|
-
-
|
|
772
|
+
- **连接器出厂自带,但仍只给缝**:两个连接器只用标准库(`node:child_process` + 全局 `fetch`)⇒ **不新增任何第三方依赖**。要接官方 SDK / 远程 server / 自研传输,实现 `McpClientLike` 两个方法即可(同 `RedisLike` 的形状)。
|
|
773
|
+
- **连接器替你兜住三件只有它能做的事**:① spawn 失败(命令不存在 → `ENOENT`)是**异步 `'error'` 事件**,不接住会把宿主进程带崩;② stdout 的分帧 —— 一条报文可能跨多个 chunk;③ **协议层 `isError: true` 转成抛错**。
|
|
774
|
+
- **连接器的 `timeoutMs` 只管装配期**(握手 + `tools/list`):那两步**没有任何别的裁判**,server 卡住会让 `createApp` 永久挂起;`callTool` 的裁判仍是引擎 / 桥(一次调用只有一个)。
|
|
765
775
|
|
|
766
776
|
```ts
|
|
767
|
-
//
|
|
768
|
-
const
|
|
769
|
-
const tools = await mcpTools(
|
|
777
|
+
// 出厂连接器:stdio(spawn 子进程)
|
|
778
|
+
const mcp = createStdioMcpConnector(['uvx', 'mcp-server-time']);
|
|
779
|
+
const tools = await mcpTools(mcp, { server: 'time' }); // → mcp_time_get_current_time …
|
|
780
|
+
|
|
781
|
+
// 远程 server:StreamableHTTP
|
|
782
|
+
const remote = createStreamableHttpMcpConnector('https://mcp.example/mcp', {
|
|
783
|
+
headers: { authorization: 'Bearer …' },
|
|
784
|
+
});
|
|
785
|
+
const remoteTools = await mcpTools(remote, { server: 'remote' });
|
|
786
|
+
|
|
787
|
+
// 也可以用你自己的传输:任意实现了 listTools/callTool 的对象都能接(duck-typed,无需继承)
|
|
788
|
+
const mine = await mcpTools(myOwnConnector, { server: 'time' });
|
|
770
789
|
|
|
771
790
|
// 与本地 @Tool 同池:同过中间件链、同进重名查重
|
|
772
791
|
const app = createApp({ system, providers: [...], tools });
|
|
@@ -780,6 +799,28 @@ const app = createApp({ system, providers: [...], tools });
|
|
|
780
799
|
| `server` | server 标识,只用于拼缺省前缀(不会发给 server) |
|
|
781
800
|
| `timeoutMs` | 单次 `callTool` 超时(毫秒);缺省 60000,非正数 = 不限 |
|
|
782
801
|
|
|
802
|
+
#### `StdioMcpConnectorOptions`(`createStdioMcpConnector` 的选项)
|
|
803
|
+
|
|
804
|
+
| 字段 | 说明 |
|
|
805
|
+
|---|---|
|
|
806
|
+
| `env` | 追加 / 覆盖的环境变量(缺省继承 `process.env`) |
|
|
807
|
+
| `cwd` | 子进程工作目录 |
|
|
808
|
+
| `stderr` | 子进程 stderr 去向:`'inherit'`(缺省,server 日志直通终端)或 `'ignore'` |
|
|
809
|
+
| `clientInfo` | `initialize` 握手要发的 `{ name, version }`(协议要求存在) |
|
|
810
|
+
| `protocolVersion` | 请求的协议版本,缺省 `2024-11-05` |
|
|
811
|
+
| `timeoutMs` | **装配期**超时(握手 + `tools/list`),缺省 60000,非正数 = 不限 |
|
|
812
|
+
|
|
813
|
+
#### `StreamableHttpMcpConnectorOptions`(`createStreamableHttpMcpConnector` 的选项)
|
|
814
|
+
|
|
815
|
+
| 字段 | 说明 |
|
|
816
|
+
|---|---|
|
|
817
|
+
| `headers` | 附加请求头(鉴权等);会覆盖缺省的 `content-type` / `accept` 同名字段 |
|
|
818
|
+
| `clientInfo` | 同 stdio |
|
|
819
|
+
| `protocolVersion` | 请求的协议版本,缺省 `2024-11-05`;协商结果以 server 回的为准 |
|
|
820
|
+
| `timeoutMs` | **装配期**超时(握手 + `tools/list`),缺省 60000,非正数 = 不限 |
|
|
821
|
+
| `onSessionExpired` | 会话过期自愈时被调一次(见「已知边界」)—— 要计数 / 告警 / 打日志就挂它 |
|
|
822
|
+
| `fetchImpl` | 注入 `fetch`(测试用;缺省全局 `fetch`,与 `createOpenAIClient` 同款) |
|
|
823
|
+
|
|
783
824
|
#### evals(把 mockClient 提升为一等能力)
|
|
784
825
|
|
|
785
826
|
| API | 说明 |
|
|
@@ -1037,9 +1078,12 @@ const callable = {
|
|
|
1037
1078
|
| 同 session 并发 run 要自行串行化 | `SessionStore` 是 **append-only**:并发写不互相覆盖、不丢数据,但**不保证角色交替** —— 两个并发 run 共用同一 sessionId 时,各自追加的轮次可能交错成「连续两条 user」,下一轮 load 出来撞角色交替校验(400)。同一 session 的并发 run 请调用方自行串行化(每 session 一把锁 / 一条队列) |
|
|
1038
1079
|
| OpenAI 适配器听端点的话 | 请求发 `stream:true`,但**按响应形态解析**:端点回 JSON 就退回一次性(没有打字机效果),回 `event-stream` 才逐 token |
|
|
1039
1080
|
| OpenAI 流式的上游故障按失败处理 | 三种形态都**抛错**按失败处理:流中 `error` 分片(上游把故障塞进 200 的流;按 `type`/`code` 反推 status,限流能被引擎重试认出);**未收到 `[DONE]` 也无 `finish_reason`**(流被上游/代理截断 —— 哪怕已吐出半句、有累积文本,也按不完整响应抛错,不报 `end_turn`);正常终止却无任何文本与工具调用(与非流式空 `choices` 同一守卫)。**例外**:`finish_reason=content_filter` 的空流是合法 refusal,不抛 —— 与非流式路径同一个响应同一个结论 |
|
|
1040
|
-
| MCP 只做 tools | `sampling`(server 反向请求模型)/ `resources` / `prompts`
|
|
1041
|
-
| MCP 的协议层错误框架看不见 | `isError: true` 只有连接器能看见 ——
|
|
1081
|
+
| MCP 只做 tools | `sampling`(server 反向请求模型)/ `resources` / `prompts` 原语不做;出厂连接器同样只做 `tools/list` + `tools/call` |
|
|
1082
|
+
| MCP 的协议层错误框架看不见 | `isError: true` 只有连接器能看见 —— 它必须转成抛错,否则模型收到的是一条「成功」的结果(出厂连接器已代你处理) |
|
|
1042
1083
|
| MCP 超时同样是「不等了」 | 桥的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它只是**兜底** —— 引擎设了 `toolTimeoutMs` 时**不参与**判定(一次调用只有一个裁判;**显式 `toolTimeoutMs: 0` 也算设了** —— 那是引擎表态「不限」,桥不会再自作主张判 60s),两条路径**同判定、同账**(`errorKind='timeout'`) |
|
|
1084
|
+
| MCP 连接器的超时只管装配期 | 连接器自带的 `timeoutMs` 只作用于**握手 + `tools/list`**(那两步**没有任何别的裁判** —— server 卡住会让 `createApp` 永久挂起);`callTool` 仍是引擎 / 桥那一个裁判 |
|
|
1085
|
+
| MCP 连接的 `close()` 保证子进程已终止 | stdio 先 `SIGTERM`、`MCP_CLOSE_GRACE_MS`(2000 ms)后 `SIGKILL`,然后**等真正的 `'exit'`** —— **返回即代表进程已被回收**(此前到点即返回,会留孤儿进程而调用方无从知晓);HTTP 尽力 `DELETE` 会话(server 不认也无所谓) |
|
|
1086
|
+
| StreamableHTTP 会话过期**自愈** | 带会话 id 收到 `404` = 会话已终止、**该请求未被 server 执行** ⇒ 丢会话 → 重新握手 → 把**这一次**重试一次(**只一次**,不再循环)。自愈本身是静默的 ⇒ 用 `onSessionExpired` 去计数 / 告警,否则它和「静默失效」在监控上看不出区别。`404` **之外**的失败仍按 `classifyError` 分流抛出,不重试 |
|
|
1043
1087
|
| MCP 名字可能被归一化 | 原名含 `-` / `.` / 空格 → 进菜单时变成 `_`;回调 server 用的仍是原名(`mcp.tool.<菜单名>` attribute 逐次可查;`mcp.tool` 是最近一次) |
|
|
1044
1088
|
| MCP 工具不能进 DI 容器 | 它没有 provider token,也不能被别的能力的 `tools` 引用(两种引用粒度都要先有 token) |
|
|
1045
1089
|
| 指标分位是窗口内精确值 | `*_last{quantile=...}` 只反映最近 `windowSize`(缺省 1024)条样本;要跨实例聚合请用直方图(`*_bucket` / `_sum` / `_count`,累积语义) |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 对象工具(core 叶子,零依赖)。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 去掉值为 `undefined` 的键,**只过滤 undefined**(`null` / `0` / `''` / `false` 一律保留)。
|
|
6
|
+
*
|
|
7
|
+
* 为什么需要它:`tsconfig` 开了 `exactOptionalPropertyTypes` 之后,`{foo: x}`(`x: T | undefined`)
|
|
8
|
+
* **不再是**合法的 `foo?: T` —— 显式传 `undefined` 与「不传这个键」被区分开了(这正是那个开关
|
|
9
|
+
* 要守的东西:`retry.ts` 的「显式 undefined 覆盖缺省」事故)。框架内部把「可能 undefined 的
|
|
10
|
+
* 变量」拼进可选参数对象时(如 `AgentApp.run` 把 per-run 覆盖转交给 `executeRun`),
|
|
11
|
+
* 用它把 undefined 键摘掉,类型上就能安全赋给 `foo?: T`。
|
|
12
|
+
*
|
|
13
|
+
* 返回类型里每个键的值都排除了 `undefined`,所以赋给可选属性是**类型准确**的
|
|
14
|
+
* (不是 `as any` 式的谎言;运行时确实过滤了)。
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* executeRun({ messages, ...omitUndefined({ model: opts.model, client: opts.client }) })
|
|
18
|
+
*/
|
|
19
|
+
export declare function omitUndefined<T extends object>(o: T): {
|
|
20
|
+
[K in keyof T]?: Exclude<T[K], undefined>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 对象工具(core 叶子,零依赖)。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 去掉值为 `undefined` 的键,**只过滤 undefined**(`null` / `0` / `''` / `false` 一律保留)。
|
|
6
|
+
*
|
|
7
|
+
* 为什么需要它:`tsconfig` 开了 `exactOptionalPropertyTypes` 之后,`{foo: x}`(`x: T | undefined`)
|
|
8
|
+
* **不再是**合法的 `foo?: T` —— 显式传 `undefined` 与「不传这个键」被区分开了(这正是那个开关
|
|
9
|
+
* 要守的东西:`retry.ts` 的「显式 undefined 覆盖缺省」事故)。框架内部把「可能 undefined 的
|
|
10
|
+
* 变量」拼进可选参数对象时(如 `AgentApp.run` 把 per-run 覆盖转交给 `executeRun`),
|
|
11
|
+
* 用它把 undefined 键摘掉,类型上就能安全赋给 `foo?: T`。
|
|
12
|
+
*
|
|
13
|
+
* 返回类型里每个键的值都排除了 `undefined`,所以赋给可选属性是**类型准确**的
|
|
14
|
+
* (不是 `as any` 式的谎言;运行时确实过滤了)。
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* executeRun({ messages, ...omitUndefined({ model: opts.model, client: opts.client }) })
|
|
18
|
+
*/
|
|
19
|
+
export function omitUndefined(o) {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [k, v] of Object.entries(o)) {
|
|
22
|
+
if (v !== undefined)
|
|
23
|
+
out[k] = v;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=object.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object.js","sourceRoot":"","sources":["../../src/core/object.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAC3B,CAAI;IAEJ,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAoD,CAAC;AAC9D,CAAC"}
|
package/dist/core/run.d.ts
CHANGED
|
@@ -7,12 +7,19 @@ import type { SpanError } from './trace.js';
|
|
|
7
7
|
* (此前 `store → runtime` 是未声明的兄弟层依赖,见 AGENTS.md 分层约定)。
|
|
8
8
|
*/
|
|
9
9
|
export type RunStatus = 'queued' | 'running' | 'succeeded' | 'failed';
|
|
10
|
+
/**
|
|
11
|
+
* run 的运行记录(供持久化 / 读取)。字段**必填但可为 undefined**:它们是框架在 run
|
|
12
|
+
* 生命周期各阶段**总是写进对象**的状态(`toMeta()` 一次构造全量),「缺省」在这里不是
|
|
13
|
+
* 一个有意义的语义 —— 与「可选入参」不同。这条区分由 tsconfig 的
|
|
14
|
+
* `exactOptionalPropertyTypes` 强制:可选入参保持 `?: T`(调用点不许显式传 undefined),
|
|
15
|
+
* 而状态/结果记录写成必填 `T | undefined`(字段在场、值可能没有)。
|
|
16
|
+
*/
|
|
10
17
|
export interface RunMeta {
|
|
11
18
|
runId: string;
|
|
12
19
|
status: RunStatus;
|
|
13
|
-
idempotencyKey
|
|
20
|
+
idempotencyKey: string | undefined;
|
|
14
21
|
createdAt: number;
|
|
15
|
-
startedAt
|
|
16
|
-
finishedAt
|
|
17
|
-
error
|
|
22
|
+
startedAt: number | undefined;
|
|
23
|
+
finishedAt: number | undefined;
|
|
24
|
+
error: SpanError | undefined;
|
|
18
25
|
}
|
package/dist/core/timeout.d.ts
CHANGED
|
@@ -79,8 +79,10 @@ export declare function withTimeout<T>(p: Promise<T>, timeoutMs: number): Promis
|
|
|
79
79
|
* integrations 只能依赖 core」的注解:**正是那条约束把两份代码逼成了重复**,
|
|
80
80
|
* 所以下沉到 core 是让它们合一的唯一合法落点(见 spec §10 2026-09-17 ① 的单源化口径)。
|
|
81
81
|
*
|
|
82
|
-
* ⚠️
|
|
83
|
-
* ±
|
|
82
|
+
* ⚠️ 「不合并退避计算器」的例外只针对**引擎层**:`engine/retry.ts` 的 `backoffDelay`
|
|
83
|
+
* 是 ±20% 均匀抖动、底数/上限来自 `RetryOptions`,与 client 层策略不同(见下方
|
|
84
|
+
* `backoffMs` 的注释)—— 合一就是改行为。两条 client 之间的逐字复制不在此列:
|
|
85
|
+
* 它们的 `backoffMs` 也已收进本文件。
|
|
84
86
|
*
|
|
85
87
|
* `abortMessage` 参数化而非统一:文案是**调用方语境**(引擎说「run 已被取消」,
|
|
86
88
|
* client 说「请求已被取消」),两者都会出现在用户眼前的报错里。为去重把两句话
|
|
@@ -90,3 +92,19 @@ export declare function withTimeout<T>(p: Promise<T>, timeoutMs: number): Promis
|
|
|
90
92
|
* `engine/errors.ts` 按 `name` 把它归进「已中止」,`loop` 据此以 `aborted` 收尾。
|
|
91
93
|
*/
|
|
92
94
|
export declare function interruptibleSleep(ms: number, signal?: AbortSignal, abortMessage?: string): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* client 层重试的退避毫秒(两条内置适配器**共用**的单源):`retry-after`(秒数或
|
|
97
|
+
* HTTP-date)优先;否则指数退避 `min(500 × 2^(attempt-1), 8000)` ±25% 抖动
|
|
98
|
+
* (attempt 从 1 起 = 第一次重试)。
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ **不要**与 `engine/retry.ts` 的 `backoffDelay` 合并(2026-09-17 去重时明确留下的
|
|
101
|
+
* 例外 —— 那条例外针对的是**引擎层**,不是这里):两者形似而策略不同,合一就是改行为。
|
|
102
|
+
* 本函数 ±25% 固定抖动、且**优先尊重 `retry-after`**(限流窗口是上游说了算,框架不该
|
|
103
|
+
* 拿自己的指数曲线去猜);那个是 ±jitter(缺省 ±20%)的均匀抖动、底数与上限来自
|
|
104
|
+
* `RetryOptions`,且它在引擎层(client 放弃之后的兜底重试)。
|
|
105
|
+
*
|
|
106
|
+
* 本函数此前在 `anthropic.ts` 与 `openai.ts` 各存一份逐字相同的副本 —— 「不合并」的
|
|
107
|
+
* 例外从来不覆盖 client↔client 的逐字复制(引擎那条理由不适用于同层),两份相同代码
|
|
108
|
+
* 只会各自漂移。
|
|
109
|
+
*/
|
|
110
|
+
export declare function backoffMs(attempt: number, retryAfter: string | null): number;
|
package/dist/core/timeout.js
CHANGED
|
@@ -123,8 +123,10 @@ export async function withTimeout(p, timeoutMs) {
|
|
|
123
123
|
* integrations 只能依赖 core」的注解:**正是那条约束把两份代码逼成了重复**,
|
|
124
124
|
* 所以下沉到 core 是让它们合一的唯一合法落点(见 spec §10 2026-09-17 ① 的单源化口径)。
|
|
125
125
|
*
|
|
126
|
-
* ⚠️
|
|
127
|
-
* ±
|
|
126
|
+
* ⚠️ 「不合并退避计算器」的例外只针对**引擎层**:`engine/retry.ts` 的 `backoffDelay`
|
|
127
|
+
* 是 ±20% 均匀抖动、底数/上限来自 `RetryOptions`,与 client 层策略不同(见下方
|
|
128
|
+
* `backoffMs` 的注释)—— 合一就是改行为。两条 client 之间的逐字复制不在此列:
|
|
129
|
+
* 它们的 `backoffMs` 也已收进本文件。
|
|
128
130
|
*
|
|
129
131
|
* `abortMessage` 参数化而非统一:文案是**调用方语境**(引擎说「run 已被取消」,
|
|
130
132
|
* client 说「请求已被取消」),两者都会出现在用户眼前的报错里。为去重把两句话
|
|
@@ -154,4 +156,32 @@ export function interruptibleSleep(ms, signal, abortMessage = '已被取消') {
|
|
|
154
156
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
155
157
|
});
|
|
156
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* client 层重试的退避毫秒(两条内置适配器**共用**的单源):`retry-after`(秒数或
|
|
161
|
+
* HTTP-date)优先;否则指数退避 `min(500 × 2^(attempt-1), 8000)` ±25% 抖动
|
|
162
|
+
* (attempt 从 1 起 = 第一次重试)。
|
|
163
|
+
*
|
|
164
|
+
* ⚠️ **不要**与 `engine/retry.ts` 的 `backoffDelay` 合并(2026-09-17 去重时明确留下的
|
|
165
|
+
* 例外 —— 那条例外针对的是**引擎层**,不是这里):两者形似而策略不同,合一就是改行为。
|
|
166
|
+
* 本函数 ±25% 固定抖动、且**优先尊重 `retry-after`**(限流窗口是上游说了算,框架不该
|
|
167
|
+
* 拿自己的指数曲线去猜);那个是 ±jitter(缺省 ±20%)的均匀抖动、底数与上限来自
|
|
168
|
+
* `RetryOptions`,且它在引擎层(client 放弃之后的兜底重试)。
|
|
169
|
+
*
|
|
170
|
+
* 本函数此前在 `anthropic.ts` 与 `openai.ts` 各存一份逐字相同的副本 —— 「不合并」的
|
|
171
|
+
* 例外从来不覆盖 client↔client 的逐字复制(引擎那条理由不适用于同层),两份相同代码
|
|
172
|
+
* 只会各自漂移。
|
|
173
|
+
*/
|
|
174
|
+
export function backoffMs(attempt, retryAfter) {
|
|
175
|
+
if (retryAfter) {
|
|
176
|
+
const secs = Number(retryAfter);
|
|
177
|
+
if (Number.isFinite(secs))
|
|
178
|
+
return Math.max(0, Math.round(secs * 1000));
|
|
179
|
+
const at = Date.parse(retryAfter);
|
|
180
|
+
if (Number.isFinite(at))
|
|
181
|
+
return Math.max(0, at - Date.now());
|
|
182
|
+
// 解析不了就回落指数退避
|
|
183
|
+
}
|
|
184
|
+
const base = Math.min(500 * 2 ** Math.max(0, attempt - 1), 8000);
|
|
185
|
+
return Math.round(base * (0.75 + Math.random() * 0.5));
|
|
186
|
+
}
|
|
157
187
|
//# sourceMappingURL=timeout.js.map
|
package/dist/core/timeout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/core/timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sCAAsC;AACtC,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,GAAc,SAAS,CAAC;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,CAAU;IACvC,IAAI,CAAC,YAAY,YAAY;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,CAAuC,CAAC;IAClD,OAAO,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,CAAa,EACb,SAAiB;IAEjB,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,gDAAgD;IAChD,8CAA8C;IAC9C,qDAAqD;IACrD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CACpB,CAAC,CAAC,EAAE,EAAE;QACJ,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;QACJ,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,CAAC,CAAC;IACV,CAAC,CACF,CAAC;IACF,IAAI,KAAiC,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC7B,OAAO;YACP,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,EAAE;gBACxC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;gBACxD,mDAAmD;gBACnD,0CAA0C;YAC5C,CAAC,CAAC;SACH,CAAC,CAAC;QACH,oCAAoC;QACpC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,OAAO,SAAS,GAAG,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;IAC9D,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/core/timeout.ts"],"names":[],"mappings":"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"}
|
package/dist/core/tool.d.ts
CHANGED
|
@@ -47,9 +47,9 @@ export interface RecorderBackend {
|
|
|
47
47
|
readonly traceId: string;
|
|
48
48
|
begin(kind: SpanKind, name: string, parentSpanId: SpanId | null): SpanId;
|
|
49
49
|
end(id: SpanId, patch?: {
|
|
50
|
-
status?: SpanStatus;
|
|
51
|
-
error?: SpanError;
|
|
52
|
-
usage?: Usage;
|
|
50
|
+
status?: SpanStatus | undefined;
|
|
51
|
+
error?: SpanError | undefined;
|
|
52
|
+
usage?: Usage | undefined;
|
|
53
53
|
}): void;
|
|
54
54
|
event(id: SpanId, name: string, body: unknown): void;
|
|
55
55
|
setAttribute(id: SpanId, key: string, value: string | number | boolean): void;
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface Usage {
|
|
|
16
16
|
cacheReadTokens: number;
|
|
17
17
|
cacheCreationTokens: number;
|
|
18
18
|
/** 估算成本(美元),由 token × 单价算出,随模型表更新 */
|
|
19
|
-
costEstimate?: number;
|
|
19
|
+
costEstimate?: number | undefined;
|
|
20
20
|
}
|
|
21
21
|
export type SpanStatus = 'ok' | 'error';
|
|
22
22
|
export interface SpanError {
|
package/dist/engine/budget.d.ts
CHANGED
|
@@ -25,18 +25,18 @@ export interface BudgetSnapshot {
|
|
|
25
25
|
}
|
|
26
26
|
export interface BudgetGuardOptions {
|
|
27
27
|
/** 整条 run(含子 agent)的累计 token 上限 */
|
|
28
|
-
maxTotalTokens?: number;
|
|
28
|
+
maxTotalTokens?: number | undefined;
|
|
29
29
|
/**
|
|
30
30
|
* 累计成本上限(美元)。**依赖 span 级 usage 的 `costEstimate`** ——
|
|
31
31
|
* 模型不在 `engine/usage.ts` 的价格表里时成本恒为 0,这条护栏**永远不触发**;
|
|
32
32
|
* 要无条件兜底请用 `maxTotalTokens`。同理,缓存 token 的单价是近似值(见 usage.ts)。
|
|
33
33
|
*/
|
|
34
|
-
maxCostUsd?: number;
|
|
34
|
+
maxCostUsd?: number | undefined;
|
|
35
35
|
/**
|
|
36
36
|
* 超限时的回调(在 `check` 返回前调用)。**抛错会冒泡进引擎** ——
|
|
37
37
|
* 想记日志/告警请自行保证不抛(引擎侧装配的那条已经做了 try 包裹)。
|
|
38
38
|
*/
|
|
39
|
-
onExceed?: (snapshot: BudgetSnapshot) => void;
|
|
39
|
+
onExceed?: ((snapshot: BudgetSnapshot) => void) | undefined;
|
|
40
40
|
}
|
|
41
41
|
export interface BudgetGuard {
|
|
42
42
|
/** 每回合记账完成后调用;超限返回 `'tokens' | 'cost'`,否则 `null` */
|