@migor/agentia 0.6.1 → 0.6.3
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 +134 -1
- package/README.md +1 -1
- package/dist/AGENTS.md +46 -8
- 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/sse.d.ts +14 -0
- package/dist/core/sse.js +45 -0
- package/dist/core/sse.js.map +1 -0
- package/dist/core/stats.d.ts +18 -0
- package/dist/core/stats.js +24 -0
- package/dist/core/stats.js.map +1 -0
- package/dist/core/text.d.ts +19 -0
- package/dist/core/text.js +8 -0
- package/dist/core/text.js.map +1 -0
- package/dist/core/timeout.d.ts +38 -0
- package/dist/core/timeout.js +70 -0
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool.d.ts +3 -3
- package/dist/core/trace.d.ts +55 -1
- package/dist/core/trace.js +43 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/budget.d.ts +3 -3
- package/dist/engine/concurrency.js +8 -1
- package/dist/engine/concurrency.js.map +1 -1
- package/dist/engine/loop.d.ts +22 -21
- package/dist/engine/loop.js +17 -1
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/retry.d.ts +17 -2
- package/dist/engine/retry.js +41 -22
- package/dist/engine/retry.js.map +1 -1
- package/dist/engine/spec.d.ts +24 -2
- package/dist/engine/spec.js +19 -4
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/trace-diff.d.ts +3 -3
- package/dist/engine/tracer.d.ts +6 -1
- package/dist/engine/tracer.js +17 -2
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/trimming.d.ts +3 -1
- package/dist/engine/trimming.js +32 -6
- package/dist/engine/trimming.js.map +1 -1
- package/dist/engine/turn.d.ts +26 -17
- package/dist/engine/turn.js +24 -15
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +10 -3
- package/dist/eval/defineEval.d.ts +3 -0
- package/dist/eval/defineEval.js +42 -12
- package/dist/eval/defineEval.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.js +32 -83
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/mcp.js +8 -4
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics.d.ts +35 -5
- package/dist/integrations/metrics.js +101 -39
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.d.ts +23 -0
- package/dist/integrations/openai.js +115 -52
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.d.ts +14 -1
- package/dist/integrations/otlp.js +26 -1
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/integrations/report.js +2 -14
- package/dist/integrations/report.js.map +1 -1
- package/dist/runtime/run.d.ts +22 -2
- package/dist/runtime/run.js +22 -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/env.js +43 -7
- package/dist/toolkit/env.js.map +1 -1
- package/dist/toolkit/middleware.d.ts +1 -1
- package/dist/toolkit/module.d.ts +10 -1
- package/dist/toolkit/module.js +44 -30
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.js +18 -4
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/toolkit/skill.d.ts +1 -1
- package/dist/toolkit/skill.js +3 -0
- package/dist/toolkit/skill.js.map +1 -1
- package/dist/toolkit/subagent.js +7 -0
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/transport/async.d.ts +4 -1
- package/dist/transport/async.js +35 -4
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http.d.ts +3 -3
- package/dist/transport/http.js +64 -8
- 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,137 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.6.3] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
> 本版主题:把第六轮 16 条的共同根因(「约定写在文档里、但没有门禁」)收口 ——
|
|
11
|
+
> 守卫注册表 + 三个新架构守卫 + 适配器对拍矩阵;`exactOptionalPropertyTypes` 全量迁移;
|
|
12
|
+
> OpenAI 适配器补齐客户端内层重试(与 anthropic 对称)。**无破坏性变更**。
|
|
13
|
+
|
|
14
|
+
### 新增(守卫基建 + 适配器对齐)
|
|
15
|
+
|
|
16
|
+
- **守卫注册表 `docs/guards.md`**:「哪类危险由谁守」的单源清单(§1 已挂守卫 → 保护的
|
|
17
|
+
不变量 → 退化后果;§2 待守缺口;§3 写法纪律),配套 PR 模板的「危险类自查 5 问」。
|
|
18
|
+
- **两个新架构守卫**:`tests/architecture/transport-errors.test.ts`(`integrations` 的
|
|
19
|
+
传输抛错必须带数值 `status`,否则 `classifyError` 判 unknown、重试层静默失效 ——
|
|
20
|
+
上线当天即抓到 `otlp.ts` 的同形漏网)与 `tests/architecture/tsconfig-strictness.test.ts`
|
|
21
|
+
(`exactOptionalPropertyTypes` / `strict` / `types:["node"]` 三个承重开关不得被关)。
|
|
22
|
+
另有元守卫 `tests/docs/guards-registry.test.ts` 防注册表本身腐化。
|
|
23
|
+
- **`OpenAIClientOptions.maxRetries`(缺省 2)**:OpenAI 适配器补齐**客户端内层重试**
|
|
24
|
+
(408/409/429/5xx + `retry-after` 尊重,与 `createAnthropicClient` 逐字对齐)——
|
|
25
|
+
此前同一个 429 在 anthropic 打 3 次网络请求、在 openai 只打 1 次(引擎层那一次)。
|
|
26
|
+
对称性由 `tests/integrations/adapter-parity.test.ts` 守住(一份场景表跑两侧 + 跨侧
|
|
27
|
+
对称断言)。
|
|
28
|
+
- **`exactOptionalPropertyTypes` 开启并完成迁移**(39 处 `error TS` 全清):结果/状态
|
|
29
|
+
记录改必填 `T | undefined`、内部管道 `?: T | undefined`、**公共入参签名不动**(调用点
|
|
30
|
+
条件展开或 `omitUndefined`)。「显式 undefined ≠ 不传」从此是类型级约束 ——
|
|
31
|
+
`retry: { maxAttempts: undefined }` 静默关重试这类写法在编译期就写不出来。
|
|
32
|
+
|
|
33
|
+
### 修复
|
|
34
|
+
|
|
35
|
+
- **`createOtlpExporter` 非 2xx 改抛 `OtlpExportError`**(带数值 `status`):此前裸
|
|
36
|
+
`Error` 会被 `classifyError` 判 `unknown` + 不可重试(与 OpenAI 适配器同形的病,
|
|
37
|
+
由新守卫抓到)。
|
|
38
|
+
|
|
39
|
+
### 重构
|
|
40
|
+
|
|
41
|
+
- 两条适配器的 client 层退避(`backoffMs` / `interruptibleSleep`)单源收进
|
|
42
|
+
`core/timeout.ts` —— 此前曾短暂存在 anthropic / openai 两份逐字副本;「不与引擎层
|
|
43
|
+
`backoffDelay` 合并」的例外仍在(±25%+retry-after vs ±20%,策略不同)。
|
|
44
|
+
|
|
45
|
+
## [0.6.2] - 2026-09-18
|
|
46
|
+
|
|
47
|
+
> 本版主题:第六轮全量 review 收口 —— 16 条「不报错地不干活」修复(含两处**语义变更**,见下)、
|
|
48
|
+
> eval 分数进指标的 `beforeFlush` 时序缝、metrics 三维度基数封顶、trace 跨进程入站关联。
|
|
49
|
+
> **无破坏性变更**:两处语义变更(OpenAI 流式截断判据、MCP 桥对显式 `toolTimeoutMs: 0` 的裁判)
|
|
50
|
+
> 都是「此前错误的行为被改正」,正常用法不受影响。
|
|
51
|
+
|
|
52
|
+
### 修复(第六轮全量 review:「不报错地不干活」一次收口)
|
|
53
|
+
|
|
54
|
+
- **OpenAI 兼容端点的引擎重试此前整体失效**:非 2xx 抛的是裸 `Error`(无结构化 `status`),
|
|
55
|
+
`classifyError` 一律归 `unknown + 不可重试` —— 兼容端点吃一个 429 就整轮 run 失败。
|
|
56
|
+
现在抛 `OpenAICompatApiError`(带数值 `status`,与 `AnthropicApiError` 同形;module 级导出,
|
|
57
|
+
不进公共面),429 → `rate_limit` 可重试;流内 `error` 分片(塞进 200 的流里的故障)按
|
|
58
|
+
`type`/`code` 反推 status(`rate_limit` / `insufficient_quota` / `too_many` → 429)。
|
|
59
|
+
- **OpenAI 流式截断不再被记成成功**(**语义变更**):终止判据从「累积为空」换成
|
|
60
|
+
「既无 `[DONE]` 也无 `finish_reason` ⇒ 上游故障」—— 此前截断发生在已吐出半句之后时,
|
|
61
|
+
半截输出被 `end_turn` 收尾上报。反向也对齐:正常终止但空的流若 `finish_reason=content_filter`
|
|
62
|
+
不再误抛,与非流式路径同样记 `refusal`。
|
|
63
|
+
- **Anthropic 适配器的 usage 合并不再被显式 `null` 清零**:网关型端点的 `message_delta`
|
|
64
|
+
带 `input_tokens: null` 时,浅合并会把 `message_start` 的真实计量抹掉,该回合 input/cache
|
|
65
|
+
token 与 `costEstimate` 归零(`maxCostUsd` 护栏随之失效)。现在 `mergeUsage` 跳过
|
|
66
|
+
`null`/`undefined` —— 缺值的语义是「保持已有值」,不是「清空已有值」。
|
|
67
|
+
- **`maxToolConcurrency` 的 (0,1) 小数不再静默丢弃全部工具**:`Math.floor(0.5) = 0` 曾意味着
|
|
68
|
+
零 worker、工具一次都不执行。现在正数一律至少 1 个 worker;run 根快照记**生效的整数**
|
|
69
|
+
(不限记 `'off'`,不再把 `NaN` / `-1` 写进 trace)。
|
|
70
|
+
- **`.env` 引号值 + 行内注释不再把字面引号写进值**:`A="sk-..." # prod` 曾被解析成含引号的
|
|
71
|
+
`"sk-..."`(每个请求 401,文件看上去完全正确)。引号判定改为「扫到闭合引号为止,其后只允许
|
|
72
|
+
空白或 `#` 注释」;未闭合 / 有残留回退未加引号分支,不猜。
|
|
73
|
+
- **`toolTimeoutMs` 现在透传给子 agent / skill 的子循环**:此前嵌套 run 里工具**永不超时**,
|
|
74
|
+
且 MCP 桥找不到引擎预算会另起 60s 兜底 —— 双计时器 + 双账本。
|
|
75
|
+
- **MCP 桥裁判判据改为 `!= null`**(**语义变更**):显式 `toolTimeoutMs: 0`(引擎表态「不限」)
|
|
76
|
+
时桥不再自作主张判 60s —— 说了不限就该不限。
|
|
77
|
+
- **异步任务的崩溃恢复不再可能重复派发**:`resumePending` 认领时**先落库再派发** —— 此前认领
|
|
78
|
+
只改内存,对 `list()` 返回反序列化新对象的 store(sqlite/redis),窗口内第二次扫描会把
|
|
79
|
+
同进程正在跑的任务再派发一遍(重复执行、重复副作用、重复花费)。
|
|
80
|
+
- **优雅停机不再可能永不返回**:`handler.drain({ timeoutMs })` 在 deadline 已过时直接返回
|
|
81
|
+
`false`,不再把「已到点」透传给 `0 = 不限` 的语义(SIGTERM 容器被强杀、在飞任务硬切)。
|
|
82
|
+
- **显式 `undefined` 不再覆盖重试缺省**:`{ maxAttempts: undefined }` 这类透传组装曾把重试
|
|
83
|
+
静默关闭(快照记 0,像用户主动关的)/ 让退避算出 `NaN`。现在显式 `undefined` 回落缺省。
|
|
84
|
+
- **静态 `@Prompt` 不再被同名实例方法静默撞掉**:静态扫描改为按**解析后的菜单名**去重,
|
|
85
|
+
实例↔静态真重名交给装配期抛「菜单能力重名」(对齐 spec §7「重名即抛」)。
|
|
86
|
+
- **`tool_use_no_blocks` 收尾现在带结构化 `error`**(`type:'agent_error'`)—— 此前该分支
|
|
87
|
+
`status:'failed'` 但 `result.error` 是 `undefined`,HTTP body / 任务记录里看不出为什么失败。
|
|
88
|
+
- **`POST /tasks` 的 store 落库故障不再回 400 + 内部原文**:新增 `TaskInputError`(module 级,
|
|
89
|
+
不进公共面)区分「调用方参数错」(400 + 原因)与「服务端故障」(500 + 走 `exposeErrors`
|
|
90
|
+
策略);读 body 期间开始停机的竞态回 503。
|
|
91
|
+
- **长上下文压缩不再切出孤儿 `tool_result`**:`compactMessages` 的切点校验换成「保留段工具
|
|
92
|
+
自洽」—— 非相邻工具对(tool_use 与 tool_result 中间隔着普通消息)此前会切出孤儿块、下一次
|
|
93
|
+
请求被 API 400;退无可退时照 `trimToolPairs` 先例整体放弃本次压缩。
|
|
94
|
+
|
|
95
|
+
### 新增(质量闭环收尾 + 指标基数封顶)
|
|
96
|
+
|
|
97
|
+
- **`beforeFlush(trace, result)`**(`RunAppOptions` / `ExecuteRunOptions`,可选):sinks 冲刷
|
|
98
|
+
**之前**的最后一笔 —— 「拿到 run 结果才判得出的结论」(典型是 `defineEval` 的 score)在这个
|
|
99
|
+
时点挂上,`metricsSink` 才聚合得到。**此前 eval 的 score 挂在冲刷之后,永远进不了
|
|
100
|
+
`agentia_score` 指标族**(usage-guide / roadmap 承诺的「eval → trace → 监控」链路是断的)。
|
|
101
|
+
宿主漏透传该字段时 `defineEval` 退回「跑完再断言」:断言照做,不误报全挂,只是分数进不了指标。
|
|
102
|
+
- **`metricsSink` 三个维度的键空间都封顶**:新增 `maxModels`(缺省 50)/ `maxScores`(缺省 200),
|
|
103
|
+
与 `maxCapabilities` 同口径 —— 超限键折叠进 `__other__`(**量不丢,只丢标签粒度**),
|
|
104
|
+
snapshot 新增 `droppedModels` / `droppedScores`(各自最多记账 1024 个不同键,满了以后是下界)。
|
|
105
|
+
此前 `models` / `scores` 无上限,与 usage-guide 承诺的内存上界不符。
|
|
106
|
+
|
|
107
|
+
### 重构(内部去重下沉 core,公共面不变)
|
|
108
|
+
|
|
109
|
+
- `sseLines` / `percentile` / `capabilityKindOf` / `textOf` / 可中断 `sleep` 各只剩一份
|
|
110
|
+
(`src/core/{sse,stats,trace,text,timeout}.ts`)—— `integrations` 只准依赖 `core`,core 是让
|
|
111
|
+
两处重复合一的唯一合法落点。`textOf(message, separator)` 三个调用点各传各的原值,行为零变化;
|
|
112
|
+
两份 backoff(引擎 ±20% 均匀抖动 vs client ±25% 且尊重 `retry-after`)**刻意不合并** ——
|
|
113
|
+
合并即改行为。
|
|
114
|
+
|
|
115
|
+
### 新增(trace 跨进程关联:`traceparent` → run 根 span links)
|
|
116
|
+
|
|
117
|
+
- **入站链路上下文**:`RunInvocationOptions.traceContext`(`{ traceId, spanId? }`)与 HTTP 请求头
|
|
118
|
+
`traceparent`(W3C)现在会记成 run 根 span 的一条 **`links`**(新类型 `SpanLink`),
|
|
119
|
+
`createOtlpExporter` 映射为 OTLP **span links** —— 于是「这条 run 是被谁触发的」在跨进程 / 跨服务
|
|
120
|
+
时也可查。**不改 `traceId == runId` 的 1:1 不变量**:run 仍是自洽的一棵新树,上游是被**链接**
|
|
121
|
+
而不是被**继承**成父 span(理由与取舍见 `docs/spec.md` §10 2026-09-17 ⑤)。
|
|
122
|
+
- 新增 `parseTraceparent(value)` 导出:把 `traceparent` 头解析成 `TraceContext`。
|
|
123
|
+
**非法 / 缺失 / 版本 `ff` / 全零 id / 位宽不符一律返回 `undefined`**(不抛)——
|
|
124
|
+
链路是观测行为,不该把业务请求打成 400。`createHttpHandler` 在 `POST /run` 与 `POST /tasks`
|
|
125
|
+
上自动用它;`POST /tasks` 的 body 里显式给的 `options.traceContext` 优先于该头。
|
|
126
|
+
- **异步宿主零改动即继承**:`traceContext` 随 `spec.options` 落进 `TaskRecord`,所以另一个进程
|
|
127
|
+
`resumePending` 续跑的那次 run 也带得上(队列消费者场景)。
|
|
128
|
+
- `TraceRecorder.addLink()` 记为公共能力;没记 link 的 span **没有 `links` 键**(不是空数组)。
|
|
129
|
+
- **已知边界(如实标注)**:只做**入站** —— 框架不生成出站 `traceparent`(运行中没有「当前 span」
|
|
130
|
+
可导出,硬造会给出假 spanId);link 只落 run 根,不自动跨进程传播(队列场景由调用方把
|
|
131
|
+
`traceContext` 传下去)。
|
|
132
|
+
|
|
133
|
+
### 文档
|
|
134
|
+
|
|
135
|
+
- `docs/usage-guide.md`:新增「跨进程关联」小节(含队列消费者配方与「只做入站」的边界)、
|
|
136
|
+
`app.run` 选项表补 `traceContext`、已知边界补一条;官网 API 页补 `TraceContext` / `SpanLink` /
|
|
137
|
+
`parseTraceparent` 三行并把 `Span.links` 写进签名。
|
|
138
|
+
|
|
8
139
|
## [0.6.1] - 2026-09-17
|
|
9
140
|
|
|
10
141
|
### 文档(对外文案不再暴露内部流程;使用说明按用途重排)
|
|
@@ -237,7 +368,9 @@
|
|
|
237
368
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
238
369
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
239
370
|
|
|
240
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.
|
|
371
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.3...HEAD
|
|
372
|
+
[0.6.3]: https://github.com/retrychx/agentia/releases/tag/v0.6.3
|
|
373
|
+
[0.6.2]: https://github.com/retrychx/agentia/releases/tag/v0.6.2
|
|
241
374
|
[0.6.1]: https://github.com/retrychx/agentia/releases/tag/v0.6.1
|
|
242
375
|
[0.6.0]: https://github.com/retrychx/agentia/releases/tag/v0.6.0
|
|
243
376
|
[0.5.0]: https://github.com/retrychx/agentia/releases/tag/v0.5.0
|
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.6.
|
|
33
|
+
> **版本**:`0.6.3`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
|
|
34
34
|
> `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
|
|
35
35
|
> [`examples/README.md`](examples/README.md) 的「依赖说明」。
|
|
36
36
|
|
package/dist/AGENTS.md
CHANGED
|
@@ -187,6 +187,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
187
187
|
| `client` | 注入 `ModelClient`(换 OpenAI 兼容端点等) |
|
|
188
188
|
| `onText` | 文本增量回调(SSE/终端) |
|
|
189
189
|
| `signal` | `AbortSignal`:中止则在飞请求被取消,run 以 `stopReason='aborted'` 收尾(算失败) |
|
|
190
|
+
| `traceContext` | 入站链路上下文 `{ traceId, spanId? }`:触发本次 run 的上游 span 记成 run 根的一条 `links`(不改 `traceId == runId`)。HTTP 宿主认 `traceparent` 头,自动填 —— 见 §6「跨进程关联」 |
|
|
190
191
|
| `blackboard` | 预置黑板种子(配 `Blackboard` 声明合并有键补全) |
|
|
191
192
|
| `contextPolicy` | 单次覆盖上下文策略 |
|
|
192
193
|
| `retry` | 单次覆盖重试策略:`false` 关闭,或 `{ maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参(只重试「本次尝试尚未产出文本」的可重试失败) |
|
|
@@ -203,6 +204,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
203
204
|
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** —— 同一棵调用树上口径一致。只影响**记账**,回给模型的 tool_result 永远完整 |
|
|
204
205
|
| `session` | 会话持久化 `{ store, id }`:run 前拼历史、成功收尾追加本轮(见 `SessionStore`) |
|
|
205
206
|
| `memory` | 跨 run 记忆 `{ store, keys }`:run 前水合进 blackboard(用户种子优先)、收尾写回;与 `session` 正交(见 `MemoryStore`) |
|
|
207
|
+
| `beforeFlush` | `(trace, result) => void \| Promise<void>`:**sinks 冲刷之前**的最后一笔(run 正常收尾后调一次,抛错被吞)。给「**拿到结果才判得出**的结论」用的缝 —— 典型是 `defineEval` 的 score:等 `app.run` 返回再 `attachScore`,`metricsSink` 早在导出那一刻聚完账,分数就永远进不了指标。读 trace 就够的判断不必用它,写进 sinks 里即可(见 §6 判官配方) |
|
|
206
208
|
|
|
207
209
|
返回 `AgentRunOutput`:`{ run, result }`。`result` 含 `trace` / `stopReason` / `finalText` / `iterations` / `error` / `typed`。
|
|
208
210
|
|
|
@@ -448,7 +450,7 @@ process.on('SIGTERM', async () => {
|
|
|
448
450
|
| `mapWithConcurrency` | 有界并发 map(结果保序);`maxToolConcurrency` 的底座,也可自用 |
|
|
449
451
|
|
|
450
452
|
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs` 到点同样是**真中止**(构造期校验:必须 ≥ 0 的**有限**数 —— NaN/Infinity 会被 `setTimeout` 钳到 1ms,等于每个任务立即超时,故直接抛错;要「不限」传 0 或不设)。
|
|
451
|
-
- **重试**:缺省自动重试可重试失败(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 })`)。
|
|
452
454
|
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error`);不带该头仍回一元 JSON。
|
|
453
455
|
- **工具超时 / 并发闸门**:`toolTimeoutMs` 超时**不杀 run**(该条 tool_result 记 `is_error`,模型可换路);`maxToolConcurrency` 给同回合的并行工具设上限(默认全并行)。⚠️ 超时 = **放弃等待**,`AgentTool.run` 没有 signal 参数,**副作用可能已发生** —— 想真停的工具请自行读 `ToolRunContext.signal`。**超时判定只有一个裁判**:`toolTimeoutMs` 是唯一判据 —— 工具自带的超时(如 MCP 桥的 `timeoutMs`)在设了本项时**不参与**判定;反过来说,工具自判的超时(抛 `code='timeout'` 的错误)与引擎判的记**同一类账**(`errorKind='timeout'`),并同样回 `is_error`。
|
|
454
456
|
|
|
@@ -536,7 +538,8 @@ trace 出去之后能干什么:指标、调用树面板、调优报告、生
|
|
|
536
538
|
|---|---|
|
|
537
539
|
| `TraceSink` | `{ export(trace) }`,run 收尾(成功/失败)都投递,抛错被吞 |
|
|
538
540
|
| `registerDefaultTraceSink` | 注册全局默认 sink(构造期快照合并) |
|
|
539
|
-
| `TraceRecorder` | 内存 recorder(一次 run
|
|
541
|
+
| `TraceRecorder` | 内存 recorder(一次 run 一个);`addLink(spanId, { traceId, spanId? })` 记一条跨 trace 链路(见 §6「跨进程关联」) |
|
|
542
|
+
| `parseTraceparent` | 解析 W3C `traceparent` 头 → `{ traceId, spanId? }`;**非法 / 缺头一律返回 `undefined`**(不抛、不打 400)—— 结果直接交给 `traceContext` 选项,见 §6「跨进程关联」 |
|
|
540
543
|
| `createOtlpExporter` | OTLP/JSON 导出,零依赖;选项 `OtlpExporterOptions`:`endpoint` / `headers` / `serviceName` / `timeoutMs`(单次导出超时,缺省 10000,非正数 = 不限 —— 裸 fetch 无超时,collector 半开连接会让 run 收尾永久挂起;超时按导出失败处理,不击穿 run) |
|
|
541
544
|
| `metricsSink` | 指标累加器(Prometheus 文本 / OTLP metrics),满足 `TraceSink` 即接入 —— 见 §6「指标」 |
|
|
542
545
|
| `buildRunReport` | 从一条 trace 生成**调优报告**(能力/模型的耗时、token、成本、错误率排行)—— 见 §6「调优报告」 |
|
|
@@ -558,6 +561,36 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
558
561
|
> `examples/observability/`。**完整的示例**(四类能力 + 三种触发 + 鉴权 + 全观测栈)在 `examples/complete/`;
|
|
559
562
|
> 最小可交付示例(Dockerfile + compose)在 `examples/deploy/`。
|
|
560
563
|
|
|
564
|
+
#### 跨进程关联(这条 run 是谁触发的)
|
|
565
|
+
|
|
566
|
+
一次 run 仍是一条**自洽**的 trace(`traceId == runId` 的 1:1 不变量不变),但可以用一条 **link**
|
|
567
|
+
把它接回上游 —— 触发它的那个 span(网关请求 / 队列消息 / 另一个服务)记在 run 根 span 的 `links` 上,
|
|
568
|
+
OTLP 导出时就是标准的 **span links**,后端能把因果边画出来。
|
|
569
|
+
|
|
570
|
+
**入站两种给法**:
|
|
571
|
+
|
|
572
|
+
| 场景 | 写法 |
|
|
573
|
+
|---|---|
|
|
574
|
+
| HTTP 宿主(网关 / 消费者通过 HTTP 打过来) | 请求头 `traceparent: 00-<32位trace>-<16位span>-01`(W3C)—— `createHttpHandler` 自动解析,`POST /run` 与 `POST /tasks` 都认;**畸形头静默当作没有上游**,不会打 400 |
|
|
575
|
+
| 程序内直接调 `app.run` / `runner.submit` | `options.traceContext: { traceId, spanId? }` —— 也可写 `parseTraceparent(头值)` 自己解析 |
|
|
576
|
+
|
|
577
|
+
**队列消费者(Kafka / RabbitMQ / SQS)的形态**:拿消息键当 `idempotencyKey`,把消息里带的
|
|
578
|
+
`traceparent` 一并传进去 —— 它随 `spec.options` 落进 `TaskRecord`,所以**另一个进程
|
|
579
|
+
`resumePending` 接着跑的那次 run 也带得上**,关联不断链:
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
// 消费者回调里(不要 await run 跑完:位移提交点与 run 终态不是一个时刻,重复靠幂等键兜)
|
|
583
|
+
runner.submit(msg.value, {
|
|
584
|
+
idempotencyKey: msg.key,
|
|
585
|
+
options: { traceContext: parseTraceparent(msg.headers.traceparent) },
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
异步任务可以在 `POST /tasks` 的 body 里显式给 `options.traceContext`,它优先于 `traceparent` 头。
|
|
590
|
+
|
|
591
|
+
> **只做入站**:框架**不生成**出站 `traceparent`(运行中没有「当前 span」这个概念,硬造一个会是假的
|
|
592
|
+
> spanId,比不做更糟)。要从 run 往外传,用结果里的 `runId`(== `traceId`)拼你自己的头,见 §7 已知边界。
|
|
593
|
+
|
|
561
594
|
#### 指标(从 trace 派生)
|
|
562
595
|
|
|
563
596
|
| API | 说明 |
|
|
@@ -602,6 +635,8 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
602
635
|
| `prefix` | 指标名前缀,缺省 `agentia_` |
|
|
603
636
|
| `labelMode` | 能力标签粒度:`'capability'`(缺省,`tool:search` 这种)/ `'kind'`(只按类型,基数极小)/ `'none'`(不产出能力指标) |
|
|
604
637
|
| `maxCapabilities` | 能力标签基数上限(缺省 200):超出后新能力归入 `capability="__other__"`(防标签爆炸);非正数抛错 |
|
|
638
|
+
| `maxModels` | 模型维度基数上限(缺省 50):超出后新模型归入 `model="__other__"` ——`model` 是 per-run 可覆盖的,上游把版本号拼进模型 id 时键会无界增长;非正数抛错 |
|
|
639
|
+
| `maxScores` | 评分维度基数上限(缺省 200):评分键是 `name@source`,eval 名带时间戳时同样无界;超出的归入 `name="__other__"`;非正数抛错 |
|
|
605
640
|
| `buckets` | 直方图桶边界(毫秒,严格升序);缺省 `DEFAULT_BUCKETS` |
|
|
606
641
|
|
|
607
642
|
#### `MetricsSink`(`metricsSink()` 的返回值)
|
|
@@ -609,15 +644,16 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
609
644
|
| 成员 | 说明 |
|
|
610
645
|
|---|---|
|
|
611
646
|
| `export` | `TraceSink` 的实现(run 收尾投递)—— 也是接进 `sinks` 的形状 |
|
|
612
|
-
| `snapshot` | `{ runs, failed, latencyP50, latencyP95, tokens, costUsd, capabilities, models, scores, droppedCapabilities }` |
|
|
647
|
+
| `snapshot` | `{ runs, failed, latencyP50, latencyP95, tokens, costUsd, capabilities, models, scores, droppedCapabilities, droppedModels, droppedScores }` |
|
|
613
648
|
| `render` | Prometheus 文本(`/metrics` 直接回它) |
|
|
614
649
|
| `flush` | 主动导出一次(`export:'otlp'` 时有意义;prometheus 模式为空操作) |
|
|
615
650
|
| `stop` | 停掉定时导出(进程收尾 / 测试用) |
|
|
616
|
-
| `reset` |
|
|
651
|
+
| `reset` | 清空累计(含能力 / 模型 / 评分三个维度,以及各自的基数配额) |
|
|
617
652
|
|
|
618
653
|
- `tokens` 口径 = **四类之和**(input + output + cacheRead + cacheCreation),与 `BudgetGuard` 一致;分项在 `render()` 里以 label 给出,不会丢。
|
|
619
654
|
- 分位是**窗口内精确值**(最近 rank 法),只反映最近 `windowSize` 条样本;**直方图计数是累积的**(全历史),两者语义不同、各有各的用处。
|
|
620
|
-
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` ——
|
|
655
|
+
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` —— 三个维度都由基数上限封顶(`maxCapabilities` / `maxModels` / `maxScores`),长跑宿主不会被拖住。
|
|
656
|
+
- 超上限的键折叠进 `__other__`:**丢的只是标签粒度,量不丢** —— `__other__` 桶照常累加,`snapshot()` 里各维度的总数仍然对得上。被折叠的**不同**键数见 `droppedCapabilities` / `droppedModels` / `droppedScores`(各自最多记账 1024 个键,满了以后是下界)。
|
|
621
657
|
- `costUsd` 依赖模型在价格表内(不在表里时不计、并计入 `unpricedTurns` 与 `usage.unpriced` 事件);根 span 未收尾(如失败路径的半截 trace)的 run 不进延迟样本。
|
|
622
658
|
|
|
623
659
|
#### 调用树面板(`agentia dev` 的本地面板 / 官网 Playground)
|
|
@@ -1000,10 +1036,10 @@ const callable = {
|
|
|
1000
1036
|
| 会话只存对话轮次 | `SessionStore` 存「用户输入 + 最终回复」,run 内部的 tool 往返**不进历史**(要完整过程用 `traceToMessages`);且只有**跑成功**的轮次才回写 |
|
|
1001
1037
|
| 同 session 并发 run 要自行串行化 | `SessionStore` 是 **append-only**:并发写不互相覆盖、不丢数据,但**不保证角色交替** —— 两个并发 run 共用同一 sessionId 时,各自追加的轮次可能交错成「连续两条 user」,下一轮 load 出来撞角色交替校验(400)。同一 session 的并发 run 请调用方自行串行化(每 session 一把锁 / 一条队列) |
|
|
1002
1038
|
| OpenAI 适配器听端点的话 | 请求发 `stream:true`,但**按响应形态解析**:端点回 JSON 就退回一次性(没有打字机效果),回 `event-stream` 才逐 token |
|
|
1003
|
-
| OpenAI 流式的上游故障按失败处理 |
|
|
1039
|
+
| OpenAI 流式的上游故障按失败处理 | 三种形态都**抛错**按失败处理:流中 `error` 分片(上游把故障塞进 200 的流;按 `type`/`code` 反推 status,限流能被引擎重试认出);**未收到 `[DONE]` 也无 `finish_reason`**(流被上游/代理截断 —— 哪怕已吐出半句、有累积文本,也按不完整响应抛错,不报 `end_turn`);正常终止却无任何文本与工具调用(与非流式空 `choices` 同一守卫)。**例外**:`finish_reason=content_filter` 的空流是合法 refusal,不抛 —— 与非流式路径同一个响应同一个结论 |
|
|
1004
1040
|
| MCP 只做 tools | `sampling`(server 反向请求模型)/ `resources` / `prompts` 原语不做;连接器(stdio / HTTP)不在框架内 |
|
|
1005
1041
|
| MCP 的协议层错误框架看不见 | `isError: true` 只有连接器能看见 —— 它必须转成抛错,否则模型收到的是一条「成功」的结果 |
|
|
1006
|
-
| MCP 超时同样是「不等了」 | 桥的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它只是**兜底** —— 引擎设了 `toolTimeoutMs`
|
|
1042
|
+
| MCP 超时同样是「不等了」 | 桥的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它只是**兜底** —— 引擎设了 `toolTimeoutMs` 时**不参与**判定(一次调用只有一个裁判;**显式 `toolTimeoutMs: 0` 也算设了** —— 那是引擎表态「不限」,桥不会再自作主张判 60s),两条路径**同判定、同账**(`errorKind='timeout'`) |
|
|
1007
1043
|
| MCP 名字可能被归一化 | 原名含 `-` / `.` / 空格 → 进菜单时变成 `_`;回调 server 用的仍是原名(`mcp.tool.<菜单名>` attribute 逐次可查;`mcp.tool` 是最近一次) |
|
|
1008
1044
|
| MCP 工具不能进 DI 容器 | 它没有 provider token,也不能被别的能力的 `tools` 引用(两种引用粒度都要先有 token) |
|
|
1009
1045
|
| 指标分位是窗口内精确值 | `*_last{quantile=...}` 只反映最近 `windowSize`(缺省 1024)条样本;要跨实例聚合请用直方图(`*_bucket` / `_sum` / `_count`,累积语义) |
|
|
@@ -1013,10 +1049,12 @@ const callable = {
|
|
|
1013
1049
|
| 工具没有 token/成本指标 | 工具是**你的代码**、本身不消耗 token,所以只产出调用数/失败数/耗时;token 与成本只对 `skill`/`subagent`(有 `capability` span)与模型维度产出 |
|
|
1014
1050
|
| `@Prompt` 没有能力指标 | 资产类能力不建 span、无独立耗时,故不出现在能力排行里(这是刻意的:硬凑一个假耗时会误导调优) |
|
|
1015
1051
|
| 能力标签有基数上限 | `labelMode:'capability'`(缺省)+ `maxCapabilities`(缺省 200),超出的能力归入 `capability="__other__"`;`snapshot().droppedCapabilities` 给出被归并的能力个数。要完整明细请用 `buildRunReport`(不设上限) |
|
|
1052
|
+
| 模型 / 评分维度也有基数上限 | `maxModels`(缺省 50)/ `maxScores`(缺省 200):`model` 与评分键(`name@source`)都可能是无界键(上游把版本号拼进模型 id、eval 名带时间戳),而每个模型键都持一份时长窗口 —— 光是能力封顶不够。折叠只丢标签粒度,`__other__` 桶照常累加,总数仍对得上;被折叠的不同键数见 `droppedModels` / `droppedScores` |
|
|
1016
1053
|
| 提示词版本只是标记 | 框架不存版本库、不回滚:`version` 只落 run 根 attribute;`system` 传已拼好的 `SystemParam` 时无版本可记 |
|
|
1017
1054
|
| `agentia harvest` 的产物是轨迹骨架 | trace **不记 assistant 文本**(llm.turn 只记 usage/事件),故 harvest 用例脚本里的 text 块是占位、预填 `expect` 是从原 trace 抄录的实际轨迹 —— 脚手架不是成品,人工核对后再进 CI(见 §6「线上 trace 回流」) |
|
|
1018
1055
|
| 分叉重放不是续跑 | `forkMessages` 与 `traceToMessages` / harvest **同源有损**:trace 不记 assistant 文本与 run 原始输入(重放里 assistant 是标注占位、首尾 user 是合成),也不记 blackboard(分叉种子经 `RunInvocationOptions.blackboard` 自带);它产出喂回 `app.run` 的 messages、起的是**新 run**,不是接着原 run 的循环位置跑 |
|
|
1019
1056
|
| 评分来自 run 之外 | `Score` 走 run 根 `score` **事件**而非 span 字段(评分通常在 run 跑完后才产生);`attachScore` 找不到根 span 时静默忽略,多次调用即多条事件(不同维度各记各的) |
|
|
1057
|
+
| 链路关联只做**入站** | `traceContext` / `traceparent` 头只把**上游**接进来(run 根的 `links`);框架**不生成**出站 `traceparent` —— 运行中没有「当前 span」可导出,硬造会给出假 spanId。要从 run 往外传,用 `result.trace.traceId`(== `runId`)自行拼头。另:link 只落在 run 根(子 span 不散),且**一进程内**不跨进程自动传播 —— 队列场景要自己把 `traceContext` 传下去(HTTP 头带走,或随 `TaskRecord.spec.options` 落库) |
|
|
1020
1058
|
| 配额不是框架子系统 | 只给缝(middleware + TraceSink + BudgetGuard),计数放哪(内存 / Redis / DB)与超限怎么办都是你的策略 |
|
|
1021
1059
|
| 人工介入只到「闸门」 | `middleware` 能 `await` 审批决策再放行;**跨进程挂起/续跑框架不做** —— `RunStatus` 无「待批准」态、循环位置不落库,`traceToMessages` 重放有损,不能拿它假装续跑(要跨重启审批请上工作流引擎)|
|
|
1022
1060
|
| 内容护栏不给实现 | 同「配额」:只给缝(入参包 `app.run` / 工具前 `middleware` / 出参包返回值或 `sinks`),策略(正则 / 分类器 / 外部 API)是你的 |
|
|
@@ -1048,5 +1086,5 @@ const callable = {
|
|
|
1048
1086
|
| MCP 调用「成功」但内容是错误文本 | 连接器没把协议层 `isError: true` 转成抛错(框架只认抛错) |
|
|
1049
1087
|
| eval 里模型调了不存在的工具 | 脚本里的工具名必须是**菜单里的名字**(MCP 工具是归一化后的 `mcp_<server>_<name>`) |
|
|
1050
1088
|
| `metricsSink` 的数字一直是 0 | 没接进 `createApp({ sinks })`(或 `registerDefaultTraceSink`)—— 它靠 run 收尾投递,不自己埋点 |
|
|
1051
|
-
| `metricsSink({ export: 'otlp' })` 构造期报错 | 没给 `endpoint` —— OTLP 导出必须知道往哪发,响亮失败好过静默不导出;补上 `endpoint`(如 `http://localhost:4318`)即可。`windowSize` / `maxCapabilities` 非正数、`buckets` 非严格升序同理是构造期配置校验 |
|
|
1089
|
+
| `metricsSink({ export: 'otlp' })` 构造期报错 | 没给 `endpoint` —— OTLP 导出必须知道往哪发,响亮失败好过静默不导出;补上 `endpoint`(如 `http://localhost:4318`)即可。`windowSize` / `maxCapabilities` / `maxModels` / `maxScores` 非正数、`buckets` 非严格升序同理是构造期配置校验 |
|
|
1052
1090
|
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— SSE 逐行分帧(**单源**)。
|
|
3
|
+
*
|
|
4
|
+
* Anthropic 与 OpenAI 兼容端点的事件流都是「一行一条 `data:`」(不需要处理跨行
|
|
5
|
+
* payload),两个适配器此前各写了一份**逐字相同**的 `sseLines`。合并的意义不止整洁:
|
|
6
|
+
* `finally` 里的 `reader.cancel()` 是「提前 break(abort / `[DONE]`)时底层流仍被回收」
|
|
7
|
+
* 的唯一保证 —— 这份代码在两处各存一份,漏改一处就是一处泄漏(读锁不释放,连接挂着)。
|
|
8
|
+
*
|
|
9
|
+
* 只做分帧,不解释协议:空行照常 yield,是不是 `data:` 前缀、要不要认 `[DONE]`
|
|
10
|
+
* 全由调用方判。
|
|
11
|
+
*
|
|
12
|
+
* core 是叶子层:本文件零 import(只用全局的 `ReadableStream` / `TextDecoder`)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function sseLines(body: ReadableStream<Uint8Array>): AsyncGenerator<string>;
|
package/dist/core/sse.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— SSE 逐行分帧(**单源**)。
|
|
3
|
+
*
|
|
4
|
+
* Anthropic 与 OpenAI 兼容端点的事件流都是「一行一条 `data:`」(不需要处理跨行
|
|
5
|
+
* payload),两个适配器此前各写了一份**逐字相同**的 `sseLines`。合并的意义不止整洁:
|
|
6
|
+
* `finally` 里的 `reader.cancel()` 是「提前 break(abort / `[DONE]`)时底层流仍被回收」
|
|
7
|
+
* 的唯一保证 —— 这份代码在两处各存一份,漏改一处就是一处泄漏(读锁不释放,连接挂着)。
|
|
8
|
+
*
|
|
9
|
+
* 只做分帧,不解释协议:空行照常 yield,是不是 `data:` 前缀、要不要认 `[DONE]`
|
|
10
|
+
* 全由调用方判。
|
|
11
|
+
*
|
|
12
|
+
* core 是叶子层:本文件零 import(只用全局的 `ReadableStream` / `TextDecoder`)。
|
|
13
|
+
*/
|
|
14
|
+
export async function* sseLines(body) {
|
|
15
|
+
const reader = body.getReader();
|
|
16
|
+
const decoder = new TextDecoder();
|
|
17
|
+
let buf = '';
|
|
18
|
+
try {
|
|
19
|
+
for (;;) {
|
|
20
|
+
const { done, value } = await reader.read();
|
|
21
|
+
if (done)
|
|
22
|
+
break;
|
|
23
|
+
buf += decoder.decode(value, { stream: true });
|
|
24
|
+
let idx = buf.indexOf('\n');
|
|
25
|
+
while (idx >= 0) {
|
|
26
|
+
yield buf.slice(0, idx).replace(/\r$/, '');
|
|
27
|
+
buf = buf.slice(idx + 1);
|
|
28
|
+
idx = buf.indexOf('\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// 末尾没有换行收尾的残行也要交出去(不是所有端点都补 \n)
|
|
32
|
+
if (buf)
|
|
33
|
+
yield buf.replace(/\r$/, '');
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
// 提前 break(abort / [DONE])时释放读锁,否则流不会被回收
|
|
37
|
+
try {
|
|
38
|
+
await reader.cancel();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* 已结束/已取消 */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/core/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAgC;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,IAAI,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;gBAChB,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC3C,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;gBACzB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,gCAAgC;QAChC,IAAI,GAAG;YAAE,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,CAAC;YAAS,CAAC;QACT,yCAAyC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,aAAa;QACf,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 统计小工具(**单源**)。
|
|
3
|
+
*
|
|
4
|
+
* `percentile` 此前在 `integrations/metrics.ts`(直方图窗口的分位,导出给看板)与
|
|
5
|
+
* `integrations/report.ts`(时长报告的 p50/p95)各有一份。两处口径**必须**一致
|
|
6
|
+
* (都取 Prometheus 语义的「最近 rank」),分开演化就会出现「看板 p95 ≠ 报告 p95」——
|
|
7
|
+
* 同一条 trace、两个数,用户无从判断该信哪个。
|
|
8
|
+
*
|
|
9
|
+
* 入参是**已排序**数组:排序成本留给调用方决定 —— metrics 每次要复制滑动窗口再排,
|
|
10
|
+
* report 只排一次复用给 p50/p95。本函数不替它们做这个选择。
|
|
11
|
+
*
|
|
12
|
+
* core 是叶子层:本文件零 import。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 分位数:取 `ceil(q · n)` 处的样本(Prometheus 的 quantile 语义 —— 不是线性插值)。
|
|
16
|
+
* 空数组返回 0;`rank` 钳到 `[1, n]`,所以 `q=0` 也返回最小样本,不会越界读 `-1`。
|
|
17
|
+
*/
|
|
18
|
+
export declare function percentile(sorted: readonly number[], q: number): number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 统计小工具(**单源**)。
|
|
3
|
+
*
|
|
4
|
+
* `percentile` 此前在 `integrations/metrics.ts`(直方图窗口的分位,导出给看板)与
|
|
5
|
+
* `integrations/report.ts`(时长报告的 p50/p95)各有一份。两处口径**必须**一致
|
|
6
|
+
* (都取 Prometheus 语义的「最近 rank」),分开演化就会出现「看板 p95 ≠ 报告 p95」——
|
|
7
|
+
* 同一条 trace、两个数,用户无从判断该信哪个。
|
|
8
|
+
*
|
|
9
|
+
* 入参是**已排序**数组:排序成本留给调用方决定 —— metrics 每次要复制滑动窗口再排,
|
|
10
|
+
* report 只排一次复用给 p50/p95。本函数不替它们做这个选择。
|
|
11
|
+
*
|
|
12
|
+
* core 是叶子层:本文件零 import。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 分位数:取 `ceil(q · n)` 处的样本(Prometheus 的 quantile 语义 —— 不是线性插值)。
|
|
16
|
+
* 空数组返回 0;`rank` 钳到 `[1, n]`,所以 `q=0` 也返回最小样本,不会越界读 `-1`。
|
|
17
|
+
*/
|
|
18
|
+
export function percentile(sorted, q) {
|
|
19
|
+
if (sorted.length === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
const rank = Math.ceil(q * sorted.length);
|
|
22
|
+
return sorted[Math.min(Math.max(rank, 1), sorted.length) - 1];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/core/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAyB,EAAE,CAAS;IAC7D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAE,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 消息文本读取(**单源**)。
|
|
3
|
+
*
|
|
4
|
+
* 合并三处同形实现:`integrations/anthropic.ts` 与 `integrations/openai.ts` 的
|
|
5
|
+
* 「非流式回落路径」各有一份 `join('')`,`engine/turn.ts` 有一份 `join('\n')`。
|
|
6
|
+
* 三份长得一样、只差一个连接符 —— 这不是巧合,是同一个语义在三处各自推导了一遍,
|
|
7
|
+
* 任何后续修正(例如「空文本块不该产生分隔符」)都只会落到其中一处。
|
|
8
|
+
*
|
|
9
|
+
* 为什么不放进 `core/message.ts`:那个文件**刻意**是「零依赖纯类型文件」
|
|
10
|
+
* (见其头注:编译后全部擦除、import 它零运行时成本)。为一个 8 行函数破掉这条性质
|
|
11
|
+
* 不划算 —— 类型在 `message.ts`、取值函数在这儿,两边各守一个职责。
|
|
12
|
+
*
|
|
13
|
+
* `separator` 是**调用方语义**,不是格式细节:
|
|
14
|
+
* - `''`(适配器):回落的原文本来就是一整段,拼回去要与流式累积的文本逐字一致;
|
|
15
|
+
* - `'\n'`(引擎):多文本块是模型分段的输出,拼成 `finalText` 要保住分段。
|
|
16
|
+
*/
|
|
17
|
+
import type { Message } from './message.js';
|
|
18
|
+
/** 取消息里的全部文本块,按 `separator` 连接(无文本块 → 空串) */
|
|
19
|
+
export declare function textOf(message: Pick<Message, 'content'>, separator: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/core/text.ts"],"names":[],"mappings":"AAkBA,6CAA6C;AAC7C,MAAM,UAAU,MAAM,CAAC,OAAiC,EAAE,SAAiB;IACzE,OAAO,OAAO,CAAC,OAAO;SACnB,MAAM,CAAC,CAAC,CAAC,EAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAChD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,IAAI,CAAC,SAAS,CAAC,CAAC;AACrB,CAAC"}
|
package/dist/core/timeout.d.ts
CHANGED
|
@@ -70,3 +70,41 @@ export declare function isTimeoutError(e: unknown): boolean;
|
|
|
70
70
|
* SSE 心跳、metrics 刷盘)成立,对**「等待的终点」**不成立:那正是调用方在等的东西。
|
|
71
71
|
*/
|
|
72
72
|
export declare function withTimeout<T>(p: Promise<T>, timeoutMs: number): Promise<T | typeof TIMED_OUT>;
|
|
73
|
+
/**
|
|
74
|
+
* 可被 signal 中断的 sleep(**单源**)。
|
|
75
|
+
*
|
|
76
|
+
* 合并两处逐字相同的实现:`engine/retry.ts` 的 `sleep`(重试退避期间收到取消就
|
|
77
|
+
* 不必再等)与 `integrations/anthropic.ts` 的 `interruptibleSleep`(client 层退避)。
|
|
78
|
+
* 那边此前自带一段「与 engine/retry.ts 的 sleep 同语义但不复用它 —— 分层约束:
|
|
79
|
+
* integrations 只能依赖 core」的注解:**正是那条约束把两份代码逼成了重复**,
|
|
80
|
+
* 所以下沉到 core 是让它们合一的唯一合法落点(见 spec §10 2026-09-17 ① 的单源化口径)。
|
|
81
|
+
*
|
|
82
|
+
* ⚠️ 「不合并退避计算器」的例外只针对**引擎层**:`engine/retry.ts` 的 `backoffDelay`
|
|
83
|
+
* 是 ±20% 均匀抖动、底数/上限来自 `RetryOptions`,与 client 层策略不同(见下方
|
|
84
|
+
* `backoffMs` 的注释)—— 合一就是改行为。两条 client 之间的逐字复制不在此列:
|
|
85
|
+
* 它们的 `backoffMs` 也已收进本文件。
|
|
86
|
+
*
|
|
87
|
+
* `abortMessage` 参数化而非统一:文案是**调用方语境**(引擎说「run 已被取消」,
|
|
88
|
+
* client 说「请求已被取消」),两者都会出现在用户眼前的报错里。为去重把两句话
|
|
89
|
+
* 改成一句,是拿可读性换整洁度。
|
|
90
|
+
*
|
|
91
|
+
* 抛的是 `name === 'AbortError'` 的错误(而非 `TimeoutError`):取消不是超时,
|
|
92
|
+
* `engine/errors.ts` 按 `name` 把它归进「已中止」,`loop` 据此以 `aborted` 收尾。
|
|
93
|
+
*/
|
|
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;
|