@migor/agentia 0.6.1 → 0.6.2
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 +96 -1
- package/README.md +1 -1
- package/dist/AGENTS.md +45 -7
- 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 +20 -0
- package/dist/core/timeout.js +40 -0
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/trace.d.ts +54 -0
- package/dist/core/trace.js +43 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/concurrency.js +8 -1
- package/dist/engine/concurrency.js.map +1 -1
- package/dist/engine/loop.js +16 -1
- package/dist/engine/loop.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 +22 -0
- package/dist/engine/spec.js +19 -4
- package/dist/engine/spec.js.map +1 -1
- 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 +8 -0
- package/dist/engine/turn.js +24 -15
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +7 -1
- 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 +35 -66
- 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 +13 -0
- package/dist/integrations/openai.js +62 -49
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.js +12 -0
- 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 +21 -1
- package/dist/runtime/run.js +17 -0
- package/dist/runtime/run.js.map +1 -1
- package/dist/toolkit/env.js +43 -7
- package/dist/toolkit/env.js.map +1 -1
- package/dist/toolkit/module.d.ts +10 -1
- package/dist/toolkit/module.js +6 -0
- 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.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 +3 -0
- package/dist/transport/async.js +28 -2
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http.js +58 -7
- package/dist/transport/http.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,100 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.6.2] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
> 本版主题:第六轮全量 review 收口 —— 16 条「不报错地不干活」修复(含两处**语义变更**,见下)、
|
|
11
|
+
> eval 分数进指标的 `beforeFlush` 时序缝、metrics 三维度基数封顶、trace 跨进程入站关联。
|
|
12
|
+
> **无破坏性变更**:两处语义变更(OpenAI 流式截断判据、MCP 桥对显式 `toolTimeoutMs: 0` 的裁判)
|
|
13
|
+
> 都是「此前错误的行为被改正」,正常用法不受影响。
|
|
14
|
+
|
|
15
|
+
### 修复(第六轮全量 review:「不报错地不干活」一次收口)
|
|
16
|
+
|
|
17
|
+
- **OpenAI 兼容端点的引擎重试此前整体失效**:非 2xx 抛的是裸 `Error`(无结构化 `status`),
|
|
18
|
+
`classifyError` 一律归 `unknown + 不可重试` —— 兼容端点吃一个 429 就整轮 run 失败。
|
|
19
|
+
现在抛 `OpenAICompatApiError`(带数值 `status`,与 `AnthropicApiError` 同形;module 级导出,
|
|
20
|
+
不进公共面),429 → `rate_limit` 可重试;流内 `error` 分片(塞进 200 的流里的故障)按
|
|
21
|
+
`type`/`code` 反推 status(`rate_limit` / `insufficient_quota` / `too_many` → 429)。
|
|
22
|
+
- **OpenAI 流式截断不再被记成成功**(**语义变更**):终止判据从「累积为空」换成
|
|
23
|
+
「既无 `[DONE]` 也无 `finish_reason` ⇒ 上游故障」—— 此前截断发生在已吐出半句之后时,
|
|
24
|
+
半截输出被 `end_turn` 收尾上报。反向也对齐:正常终止但空的流若 `finish_reason=content_filter`
|
|
25
|
+
不再误抛,与非流式路径同样记 `refusal`。
|
|
26
|
+
- **Anthropic 适配器的 usage 合并不再被显式 `null` 清零**:网关型端点的 `message_delta`
|
|
27
|
+
带 `input_tokens: null` 时,浅合并会把 `message_start` 的真实计量抹掉,该回合 input/cache
|
|
28
|
+
token 与 `costEstimate` 归零(`maxCostUsd` 护栏随之失效)。现在 `mergeUsage` 跳过
|
|
29
|
+
`null`/`undefined` —— 缺值的语义是「保持已有值」,不是「清空已有值」。
|
|
30
|
+
- **`maxToolConcurrency` 的 (0,1) 小数不再静默丢弃全部工具**:`Math.floor(0.5) = 0` 曾意味着
|
|
31
|
+
零 worker、工具一次都不执行。现在正数一律至少 1 个 worker;run 根快照记**生效的整数**
|
|
32
|
+
(不限记 `'off'`,不再把 `NaN` / `-1` 写进 trace)。
|
|
33
|
+
- **`.env` 引号值 + 行内注释不再把字面引号写进值**:`A="sk-..." # prod` 曾被解析成含引号的
|
|
34
|
+
`"sk-..."`(每个请求 401,文件看上去完全正确)。引号判定改为「扫到闭合引号为止,其后只允许
|
|
35
|
+
空白或 `#` 注释」;未闭合 / 有残留回退未加引号分支,不猜。
|
|
36
|
+
- **`toolTimeoutMs` 现在透传给子 agent / skill 的子循环**:此前嵌套 run 里工具**永不超时**,
|
|
37
|
+
且 MCP 桥找不到引擎预算会另起 60s 兜底 —— 双计时器 + 双账本。
|
|
38
|
+
- **MCP 桥裁判判据改为 `!= null`**(**语义变更**):显式 `toolTimeoutMs: 0`(引擎表态「不限」)
|
|
39
|
+
时桥不再自作主张判 60s —— 说了不限就该不限。
|
|
40
|
+
- **异步任务的崩溃恢复不再可能重复派发**:`resumePending` 认领时**先落库再派发** —— 此前认领
|
|
41
|
+
只改内存,对 `list()` 返回反序列化新对象的 store(sqlite/redis),窗口内第二次扫描会把
|
|
42
|
+
同进程正在跑的任务再派发一遍(重复执行、重复副作用、重复花费)。
|
|
43
|
+
- **优雅停机不再可能永不返回**:`handler.drain({ timeoutMs })` 在 deadline 已过时直接返回
|
|
44
|
+
`false`,不再把「已到点」透传给 `0 = 不限` 的语义(SIGTERM 容器被强杀、在飞任务硬切)。
|
|
45
|
+
- **显式 `undefined` 不再覆盖重试缺省**:`{ maxAttempts: undefined }` 这类透传组装曾把重试
|
|
46
|
+
静默关闭(快照记 0,像用户主动关的)/ 让退避算出 `NaN`。现在显式 `undefined` 回落缺省。
|
|
47
|
+
- **静态 `@Prompt` 不再被同名实例方法静默撞掉**:静态扫描改为按**解析后的菜单名**去重,
|
|
48
|
+
实例↔静态真重名交给装配期抛「菜单能力重名」(对齐 spec §7「重名即抛」)。
|
|
49
|
+
- **`tool_use_no_blocks` 收尾现在带结构化 `error`**(`type:'agent_error'`)—— 此前该分支
|
|
50
|
+
`status:'failed'` 但 `result.error` 是 `undefined`,HTTP body / 任务记录里看不出为什么失败。
|
|
51
|
+
- **`POST /tasks` 的 store 落库故障不再回 400 + 内部原文**:新增 `TaskInputError`(module 级,
|
|
52
|
+
不进公共面)区分「调用方参数错」(400 + 原因)与「服务端故障」(500 + 走 `exposeErrors`
|
|
53
|
+
策略);读 body 期间开始停机的竞态回 503。
|
|
54
|
+
- **长上下文压缩不再切出孤儿 `tool_result`**:`compactMessages` 的切点校验换成「保留段工具
|
|
55
|
+
自洽」—— 非相邻工具对(tool_use 与 tool_result 中间隔着普通消息)此前会切出孤儿块、下一次
|
|
56
|
+
请求被 API 400;退无可退时照 `trimToolPairs` 先例整体放弃本次压缩。
|
|
57
|
+
|
|
58
|
+
### 新增(质量闭环收尾 + 指标基数封顶)
|
|
59
|
+
|
|
60
|
+
- **`beforeFlush(trace, result)`**(`RunAppOptions` / `ExecuteRunOptions`,可选):sinks 冲刷
|
|
61
|
+
**之前**的最后一笔 —— 「拿到 run 结果才判得出的结论」(典型是 `defineEval` 的 score)在这个
|
|
62
|
+
时点挂上,`metricsSink` 才聚合得到。**此前 eval 的 score 挂在冲刷之后,永远进不了
|
|
63
|
+
`agentia_score` 指标族**(usage-guide / roadmap 承诺的「eval → trace → 监控」链路是断的)。
|
|
64
|
+
宿主漏透传该字段时 `defineEval` 退回「跑完再断言」:断言照做,不误报全挂,只是分数进不了指标。
|
|
65
|
+
- **`metricsSink` 三个维度的键空间都封顶**:新增 `maxModels`(缺省 50)/ `maxScores`(缺省 200),
|
|
66
|
+
与 `maxCapabilities` 同口径 —— 超限键折叠进 `__other__`(**量不丢,只丢标签粒度**),
|
|
67
|
+
snapshot 新增 `droppedModels` / `droppedScores`(各自最多记账 1024 个不同键,满了以后是下界)。
|
|
68
|
+
此前 `models` / `scores` 无上限,与 usage-guide 承诺的内存上界不符。
|
|
69
|
+
|
|
70
|
+
### 重构(内部去重下沉 core,公共面不变)
|
|
71
|
+
|
|
72
|
+
- `sseLines` / `percentile` / `capabilityKindOf` / `textOf` / 可中断 `sleep` 各只剩一份
|
|
73
|
+
(`src/core/{sse,stats,trace,text,timeout}.ts`)—— `integrations` 只准依赖 `core`,core 是让
|
|
74
|
+
两处重复合一的唯一合法落点。`textOf(message, separator)` 三个调用点各传各的原值,行为零变化;
|
|
75
|
+
两份 backoff(引擎 ±20% 均匀抖动 vs client ±25% 且尊重 `retry-after`)**刻意不合并** ——
|
|
76
|
+
合并即改行为。
|
|
77
|
+
|
|
78
|
+
### 新增(trace 跨进程关联:`traceparent` → run 根 span links)
|
|
79
|
+
|
|
80
|
+
- **入站链路上下文**:`RunInvocationOptions.traceContext`(`{ traceId, spanId? }`)与 HTTP 请求头
|
|
81
|
+
`traceparent`(W3C)现在会记成 run 根 span 的一条 **`links`**(新类型 `SpanLink`),
|
|
82
|
+
`createOtlpExporter` 映射为 OTLP **span links** —— 于是「这条 run 是被谁触发的」在跨进程 / 跨服务
|
|
83
|
+
时也可查。**不改 `traceId == runId` 的 1:1 不变量**:run 仍是自洽的一棵新树,上游是被**链接**
|
|
84
|
+
而不是被**继承**成父 span(理由与取舍见 `docs/spec.md` §10 2026-09-17 ⑤)。
|
|
85
|
+
- 新增 `parseTraceparent(value)` 导出:把 `traceparent` 头解析成 `TraceContext`。
|
|
86
|
+
**非法 / 缺失 / 版本 `ff` / 全零 id / 位宽不符一律返回 `undefined`**(不抛)——
|
|
87
|
+
链路是观测行为,不该把业务请求打成 400。`createHttpHandler` 在 `POST /run` 与 `POST /tasks`
|
|
88
|
+
上自动用它;`POST /tasks` 的 body 里显式给的 `options.traceContext` 优先于该头。
|
|
89
|
+
- **异步宿主零改动即继承**:`traceContext` 随 `spec.options` 落进 `TaskRecord`,所以另一个进程
|
|
90
|
+
`resumePending` 续跑的那次 run 也带得上(队列消费者场景)。
|
|
91
|
+
- `TraceRecorder.addLink()` 记为公共能力;没记 link 的 span **没有 `links` 键**(不是空数组)。
|
|
92
|
+
- **已知边界(如实标注)**:只做**入站** —— 框架不生成出站 `traceparent`(运行中没有「当前 span」
|
|
93
|
+
可导出,硬造会给出假 spanId);link 只落 run 根,不自动跨进程传播(队列场景由调用方把
|
|
94
|
+
`traceContext` 传下去)。
|
|
95
|
+
|
|
96
|
+
### 文档
|
|
97
|
+
|
|
98
|
+
- `docs/usage-guide.md`:新增「跨进程关联」小节(含队列消费者配方与「只做入站」的边界)、
|
|
99
|
+
`app.run` 选项表补 `traceContext`、已知边界补一条;官网 API 页补 `TraceContext` / `SpanLink` /
|
|
100
|
+
`parseTraceparent` 三行并把 `Span.links` 写进签名。
|
|
101
|
+
|
|
8
102
|
## [0.6.1] - 2026-09-17
|
|
9
103
|
|
|
10
104
|
### 文档(对外文案不再暴露内部流程;使用说明按用途重排)
|
|
@@ -237,7 +331,8 @@
|
|
|
237
331
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
238
332
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
239
333
|
|
|
240
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.
|
|
334
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.2...HEAD
|
|
335
|
+
[0.6.2]: https://github.com/retrychx/agentia/releases/tag/v0.6.2
|
|
241
336
|
[0.6.1]: https://github.com/retrychx/agentia/releases/tag/v0.6.1
|
|
242
337
|
[0.6.0]: https://github.com/retrychx/agentia/releases/tag/v0.6.0
|
|
243
338
|
[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.2`(`@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
|
|
|
@@ -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,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,23 @@ 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
|
+
* ⚠️ 只合并这个 sleep,**不合并退避计算器**:engine 是 ±20% 均匀抖动、client 是
|
|
83
|
+
* ±25% 且额外尊重 `retry-after`。函数体相同、策略不同 —— 合策略会真改行为。
|
|
84
|
+
*
|
|
85
|
+
* `abortMessage` 参数化而非统一:文案是**调用方语境**(引擎说「run 已被取消」,
|
|
86
|
+
* client 说「请求已被取消」),两者都会出现在用户眼前的报错里。为去重把两句话
|
|
87
|
+
* 改成一句,是拿可读性换整洁度。
|
|
88
|
+
*
|
|
89
|
+
* 抛的是 `name === 'AbortError'` 的错误(而非 `TimeoutError`):取消不是超时,
|
|
90
|
+
* `engine/errors.ts` 按 `name` 把它归进「已中止」,`loop` 据此以 `aborted` 收尾。
|
|
91
|
+
*/
|
|
92
|
+
export declare function interruptibleSleep(ms: number, signal?: AbortSignal, abortMessage?: string): Promise<void>;
|
package/dist/core/timeout.js
CHANGED
|
@@ -114,4 +114,44 @@ export async function withTimeout(p, timeoutMs) {
|
|
|
114
114
|
clearTimeout(timer);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* 可被 signal 中断的 sleep(**单源**)。
|
|
119
|
+
*
|
|
120
|
+
* 合并两处逐字相同的实现:`engine/retry.ts` 的 `sleep`(重试退避期间收到取消就
|
|
121
|
+
* 不必再等)与 `integrations/anthropic.ts` 的 `interruptibleSleep`(client 层退避)。
|
|
122
|
+
* 那边此前自带一段「与 engine/retry.ts 的 sleep 同语义但不复用它 —— 分层约束:
|
|
123
|
+
* integrations 只能依赖 core」的注解:**正是那条约束把两份代码逼成了重复**,
|
|
124
|
+
* 所以下沉到 core 是让它们合一的唯一合法落点(见 spec §10 2026-09-17 ① 的单源化口径)。
|
|
125
|
+
*
|
|
126
|
+
* ⚠️ 只合并这个 sleep,**不合并退避计算器**:engine 是 ±20% 均匀抖动、client 是
|
|
127
|
+
* ±25% 且额外尊重 `retry-after`。函数体相同、策略不同 —— 合策略会真改行为。
|
|
128
|
+
*
|
|
129
|
+
* `abortMessage` 参数化而非统一:文案是**调用方语境**(引擎说「run 已被取消」,
|
|
130
|
+
* client 说「请求已被取消」),两者都会出现在用户眼前的报错里。为去重把两句话
|
|
131
|
+
* 改成一句,是拿可读性换整洁度。
|
|
132
|
+
*
|
|
133
|
+
* 抛的是 `name === 'AbortError'` 的错误(而非 `TimeoutError`):取消不是超时,
|
|
134
|
+
* `engine/errors.ts` 按 `name` 把它归进「已中止」,`loop` 据此以 `aborted` 收尾。
|
|
135
|
+
*/
|
|
136
|
+
export function interruptibleSleep(ms, signal, abortMessage = '已被取消') {
|
|
137
|
+
if (ms <= 0)
|
|
138
|
+
return Promise.resolve();
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const abortError = () => Object.assign(new Error(abortMessage), { name: 'AbortError' });
|
|
141
|
+
// 已中止:立即 reject(此处 timer 尚未创建,绝不能去 clear)
|
|
142
|
+
if (signal?.aborted) {
|
|
143
|
+
reject(abortError());
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
function onAbort() {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
reject(abortError());
|
|
149
|
+
}
|
|
150
|
+
const timer = setTimeout(() => {
|
|
151
|
+
signal?.removeEventListener('abort', onAbort);
|
|
152
|
+
resolve();
|
|
153
|
+
}, ms);
|
|
154
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
117
157
|
//# 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"}
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/core/timeout.ts"],"names":[],"mappings":"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"}
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -35,6 +35,41 @@ export interface SpanEvent {
|
|
|
35
35
|
name: string;
|
|
36
36
|
body: unknown;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* 触发来源的链路上下文(跨进程 / 跨服务关联,spec §9.2)。
|
|
40
|
+
*
|
|
41
|
+
* 语义是**入站**的:调用方(HTTP 网关、队列消费者、上游服务)把它自己的 span 标识
|
|
42
|
+
* 传进来,本次 run 的根 span 会把它记成一条 `SpanLink` 指回去 —— 于是「这条 run 是被
|
|
43
|
+
* 谁触发的」在两个系统之间可查。
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ **不改变 `traceId == runId` 的 1:1 不变量**:run 仍是自己的一棵新树,上游只是被
|
|
46
|
+
* **链接**、不是被**继承**成父 span。所以一次 run 的调用树永远自洽(不依赖上游是否
|
|
47
|
+
* 还在、是否被采样掉),而因果关系仍然成立。
|
|
48
|
+
*/
|
|
49
|
+
export interface TraceContext {
|
|
50
|
+
/** 上游 trace id:32 位 hex(W3C traceparent)或 UUID(带 '-')两种形态都接受 */
|
|
51
|
+
traceId: string;
|
|
52
|
+
/** 上游 span id(16 位 hex);缺省表示只知道 trace 粒度、没有具体 span */
|
|
53
|
+
spanId?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 一条链路引用:指向**另一个** trace 里的 span(OTLP 的 span links 同语义)。
|
|
57
|
+
* v1 只在 run 根 span 上写入(来源是 `RunInvocationOptions.traceContext`)。
|
|
58
|
+
*/
|
|
59
|
+
export interface SpanLink {
|
|
60
|
+
traceId: TraceId;
|
|
61
|
+
spanId?: SpanId;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 解析 W3C `traceparent` 头(spec §9.2 的「header 语义」)。这是入站唯一的字符串形态
|
|
65
|
+
* —— 宿主从 HTTP 头 / 队列消息属性里取到它,直接交给 `RunInvocationOptions.traceContext`。
|
|
66
|
+
*
|
|
67
|
+
* **非法或缺失一律返回 `undefined`**(调用方据此当作「没有上游上下文」继续跑):
|
|
68
|
+
* 链路关联是观测行为,不该因为一个畸形头把业务请求打成 400。被拒的形态有:
|
|
69
|
+
* 版本 `ff`(W3C 保留为非法)、trace/span id 全零、位宽不符、大小写之外的畸形。
|
|
70
|
+
* 版本号非 `00` 时按 W3C「兼容未来」规则接受(只认前四段语义)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseTraceparent(value: string | null | undefined): TraceContext | undefined;
|
|
38
73
|
export interface Span {
|
|
39
74
|
spanId: SpanId;
|
|
40
75
|
traceId: TraceId;
|
|
@@ -45,6 +80,12 @@ export interface Span {
|
|
|
45
80
|
endedAt?: number;
|
|
46
81
|
status: SpanStatus;
|
|
47
82
|
error?: SpanError;
|
|
83
|
+
/**
|
|
84
|
+
* 跨 trace 的链路引用(v1 只出现在 run 根 span 上,见 `TraceContext`)。
|
|
85
|
+
* 缺席(而不是空数组)表示「没有上游上下文」—— 与 links 为空的 span 是同一件事,
|
|
86
|
+
* 不要据此区分「没传」和「传了但为空」。
|
|
87
|
+
*/
|
|
88
|
+
links?: SpanLink[];
|
|
48
89
|
/**
|
|
49
90
|
* usage —— 语义按 kind 区分:
|
|
50
91
|
* - `llm.turn`:该次模型往返的**自身计量**,是 Trace.totalUsage 的唯一来源;
|
|
@@ -83,6 +124,19 @@ export interface Score {
|
|
|
83
124
|
* 出口映射:OTLP 导出时译为 `gen_ai.evaluation.result` 事件,metricsSink 聚合为 score 指标族。
|
|
84
125
|
*/
|
|
85
126
|
export declare function attachScore(trace: Trace, score: Score): void;
|
|
127
|
+
/**
|
|
128
|
+
* 能力 span 的类型标签:`attributes.skill` 有值 → `'skill'`,`attributes.subagent` 有值
|
|
129
|
+
* → `'subagent'`,否则 `'capability'`(`SpanKind` 的第三个取值,也是 span 的 kind)。
|
|
130
|
+
*
|
|
131
|
+
* 放在本文件(而不是各出口里):`skill` / `subagent` 这两个 attribute 名是
|
|
132
|
+
* `toolkit/skill.ts`、`toolkit/subagent.ts` 与本类型之间的共同契约 —— 判定写在契约
|
|
133
|
+
* 定义处,两个出口(`metrics.ts` 的指标 label、`report.ts` 的能力排行)只消费结论。
|
|
134
|
+
* 此前两处各有一份同形实现,加起来是**同一件事的三个定义**。
|
|
135
|
+
*
|
|
136
|
+
* 返回 `string` 而非 `CapabilityType`:`'capability'` 不在 `CapabilityType`
|
|
137
|
+
* (那是「菜单四类能力」的口径)里,两者刻意不混。
|
|
138
|
+
*/
|
|
139
|
+
export declare function capabilityKindOf(span: Span): string;
|
|
86
140
|
/**
|
|
87
141
|
* trace 出口:run 收尾(成功或失败)后,框架把【完整 Trace】交给每个 sink。
|
|
88
142
|
* sink 抛错由框架吞掉,绝不影响 run 结果(与 memory 回写同款防护)。
|
package/dist/core/trace.js
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
* Agentia —— Trace / 调用树 类型草稿。
|
|
3
3
|
* 设计见 docs/spec.md §9。一次 run == 一条 trace(v1 中 traceId == runId)。
|
|
4
4
|
*/
|
|
5
|
+
/** W3C traceparent:`<2位版本>-<32位trace>-<16位span>-<2位flags>`,全小写 hex */
|
|
6
|
+
const TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
7
|
+
/**
|
|
8
|
+
* 解析 W3C `traceparent` 头(spec §9.2 的「header 语义」)。这是入站唯一的字符串形态
|
|
9
|
+
* —— 宿主从 HTTP 头 / 队列消息属性里取到它,直接交给 `RunInvocationOptions.traceContext`。
|
|
10
|
+
*
|
|
11
|
+
* **非法或缺失一律返回 `undefined`**(调用方据此当作「没有上游上下文」继续跑):
|
|
12
|
+
* 链路关联是观测行为,不该因为一个畸形头把业务请求打成 400。被拒的形态有:
|
|
13
|
+
* 版本 `ff`(W3C 保留为非法)、trace/span id 全零、位宽不符、大小写之外的畸形。
|
|
14
|
+
* 版本号非 `00` 时按 W3C「兼容未来」规则接受(只认前四段语义)。
|
|
15
|
+
*/
|
|
16
|
+
export function parseTraceparent(value) {
|
|
17
|
+
if (!value)
|
|
18
|
+
return undefined;
|
|
19
|
+
const m = TRACEPARENT_RE.exec(value.trim().toLowerCase());
|
|
20
|
+
if (!m)
|
|
21
|
+
return undefined;
|
|
22
|
+
const [, version, traceId, spanId] = m;
|
|
23
|
+
if (version === 'ff')
|
|
24
|
+
return undefined;
|
|
25
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(spanId))
|
|
26
|
+
return undefined;
|
|
27
|
+
return { traceId, spanId };
|
|
28
|
+
}
|
|
5
29
|
/**
|
|
6
30
|
* 把评分挂到 trace 根 span(一条 `score` 事件,body 即 Score)。
|
|
7
31
|
* trace 找不到根 span 时静默忽略(观测不击穿业务);多次调用即多条事件(不同维度各记各的)。
|
|
@@ -11,4 +35,23 @@ export function attachScore(trace, score) {
|
|
|
11
35
|
const root = trace.spans.find((s) => s.spanId === trace.rootSpanId);
|
|
12
36
|
root?.events.push({ time: Date.now(), name: 'score', body: score });
|
|
13
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* 能力 span 的类型标签:`attributes.skill` 有值 → `'skill'`,`attributes.subagent` 有值
|
|
40
|
+
* → `'subagent'`,否则 `'capability'`(`SpanKind` 的第三个取值,也是 span 的 kind)。
|
|
41
|
+
*
|
|
42
|
+
* 放在本文件(而不是各出口里):`skill` / `subagent` 这两个 attribute 名是
|
|
43
|
+
* `toolkit/skill.ts`、`toolkit/subagent.ts` 与本类型之间的共同契约 —— 判定写在契约
|
|
44
|
+
* 定义处,两个出口(`metrics.ts` 的指标 label、`report.ts` 的能力排行)只消费结论。
|
|
45
|
+
* 此前两处各有一份同形实现,加起来是**同一件事的三个定义**。
|
|
46
|
+
*
|
|
47
|
+
* 返回 `string` 而非 `CapabilityType`:`'capability'` 不在 `CapabilityType`
|
|
48
|
+
* (那是「菜单四类能力」的口径)里,两者刻意不混。
|
|
49
|
+
*/
|
|
50
|
+
export function capabilityKindOf(span) {
|
|
51
|
+
if (span.attributes.skill !== undefined)
|
|
52
|
+
return 'skill';
|
|
53
|
+
if (span.attributes.subagent !== undefined)
|
|
54
|
+
return 'subagent';
|
|
55
|
+
return 'capability';
|
|
56
|
+
}
|
|
14
57
|
//# sourceMappingURL=trace.js.map
|
package/dist/core/trace.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqEH,sEAAsE;AACtE,MAAM,cAAc,GAAG,6DAA6D,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAqDD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,KAAY;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC;IACpE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IACxD,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC9D,OAAO,YAAY,CAAC;AACtB,CAAC"}
|