@migor/agentia 0.3.0 → 0.4.1
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/README.md +2 -2
- package/dist/AGENTS.md +59 -23
- package/dist/core/abort.d.ts +7 -0
- package/dist/core/abort.js +28 -2
- package/dist/core/abort.js.map +1 -1
- package/dist/core/schema.js +5 -3
- package/dist/core/schema.js.map +1 -1
- package/dist/core/tool.d.ts +18 -0
- package/dist/core/trace.d.ts +5 -1
- package/dist/engine/loop.d.ts +11 -0
- package/dist/engine/loop.js +122 -27
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/policy.js +5 -0
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/spec.d.ts +10 -0
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/tracer.js +13 -1
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/trimming.d.ts +7 -2
- package/dist/engine/trimming.js +20 -6
- package/dist/engine/trimming.js.map +1 -1
- package/dist/engine/types.d.ts +33 -5
- package/dist/engine/usage.d.ts +3 -3
- package/dist/engine/usage.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/mcp.js +9 -1
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics.d.ts +8 -2
- package/dist/integrations/metrics.js +120 -46
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.js +14 -1
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.d.ts +7 -0
- package/dist/integrations/otlp.js +5 -2
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/runtime/run.d.ts +7 -3
- package/dist/runtime/run.js +4 -0
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/session.d.ts +5 -0
- package/dist/runtime/session.js.map +1 -1
- package/dist/store/fsStore.d.ts +2 -1
- package/dist/store/fsStore.js +12 -5
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/redisStore.d.ts +23 -3
- package/dist/store/redisStore.js +14 -3
- package/dist/store/redisStore.js.map +1 -1
- package/dist/toolkit/asset.d.ts +5 -0
- package/dist/toolkit/asset.js +8 -0
- package/dist/toolkit/asset.js.map +1 -1
- package/dist/toolkit/collect.d.ts +6 -0
- package/dist/toolkit/collect.js +29 -4
- package/dist/toolkit/collect.js.map +1 -1
- package/dist/toolkit/discover.js +24 -7
- package/dist/toolkit/discover.js.map +1 -1
- package/dist/toolkit/env.js +5 -0
- package/dist/toolkit/env.js.map +1 -1
- package/dist/toolkit/module.d.ts +9 -1
- package/dist/toolkit/module.js +10 -1
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.d.ts +1 -1
- package/dist/toolkit/prompt.js +19 -12
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/toolkit/skill.js +5 -0
- package/dist/toolkit/skill.js.map +1 -1
- package/dist/toolkit/subagent.js +5 -0
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/transport/async.d.ts +2 -1
- package/dist/transport/async.js +19 -9
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http.d.ts +4 -4
- package/dist/transport/http.js +15 -2
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/scheduler.js +13 -1
- package/dist/transport/scheduler.js.map +1 -1
- package/package.json +3 -2
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.4.1`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
|
|
34
34
|
> `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
|
|
35
35
|
> [`examples/README.md`](examples/README.md) 的「依赖说明」。
|
|
36
36
|
|
|
@@ -339,7 +339,7 @@ npm run build # 编译框架(dist/)
|
|
|
339
339
|
npm run typecheck # 类型检查
|
|
340
340
|
npm run lint # lint + 格式检查(Biome)
|
|
341
341
|
npm test # 单元测试(node:test)
|
|
342
|
-
npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete
|
|
342
|
+
npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete 与 examples/deploy(含崩溃续跑)
|
|
343
343
|
```
|
|
344
344
|
|
|
345
345
|
> 注:本仓库的 `npm run dev`(tsx)前需先 `npm approve-scripts` 批准 esbuild/tsx 的 postinstall;
|
package/dist/AGENTS.md
CHANGED
|
@@ -133,7 +133,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
133
133
|
| `name` | 缺省取方法名 |
|
|
134
134
|
| `schema` | 模板化入参 schema;缺省空对象(无参资产) |
|
|
135
135
|
|
|
136
|
-
`@Prompt`
|
|
136
|
+
`@Prompt` **只支持方法形态**(标准装饰器下字段拿不到值/类引用)。实例方法与静态方法都**沿继承链**收集(父类的 `@Prompt` 资产子类自动带上)。方法体内用 `asset(import.meta.url, './x.md')` 读同目录长文本。
|
|
137
137
|
|
|
138
138
|
---
|
|
139
139
|
|
|
@@ -151,17 +151,19 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
151
151
|
| `model` | 缺省模型;不给则 `AGENTIA_MODEL` env,再回落 `claude-opus-5` |
|
|
152
152
|
| `maxTokens` | 缺省 `max_tokens` |
|
|
153
153
|
| `maxIterations` | 缺省循环上限 |
|
|
154
|
-
| `
|
|
154
|
+
| `retry` | 缺省模型请求重试策略(可被单次 run 覆盖):缺省**开启**(`DEFAULT_RETRY`:maxAttempts=3、指数退避 + 抖动);`false` 关闭 |
|
|
155
|
+
| `contextPolicy` | 上下文预算策略(`createBudgetPolicy(...)`);带状态的策略应实现 `forRun()` 按 run 隔离(见 §长上下文) |
|
|
155
156
|
| `toolSources` | 白名单:只把这些 provider 的能力放进主菜单 |
|
|
156
157
|
| `tools` | 直接追加到主菜单的**裸工具**(`AgentTool[]`):给「构造期才知道有哪些工具」的场合(典型:MCP 桥,见 §6)。与能力**同过中间件、同进重名查重**,不是旁路 |
|
|
157
158
|
| `middleware` | 能力调用中间件(洋葱链,链序 = 注册顺序) |
|
|
158
159
|
| `sinks` | trace 出口,run 收尾投递 |
|
|
159
|
-
| `maxTotalTokens` | 缺省成本硬管控:整条 run
|
|
160
|
+
| `maxTotalTokens` | 缺省成本硬管控:整条 run(**含子 agent / skill 子循环**,上限经 `ToolRunContext` 透传)累计 token 上限(可被单次 run 覆盖) |
|
|
160
161
|
| `maxCostUsd` | 缺省成本硬管控:累计成本(美元)上限(**依赖模型在价格表内**,见 `priceOverrides`;未定价模型会留 `usage.unpriced` 事件,所以「护栏有没有真的生效」看得见) |
|
|
161
162
|
| `priceOverrides` | 价格表覆盖/追加(`$/1M tokens`):覆盖内置同名项,或给非 Anthropic 模型定价(如 `{ 'deepseek-chat': { in: 0.27, out: 1.10 } }`)。**透传给子 agent/skill 的子循环** —— 不会「主 agent 有成本、子 agent 恒 0」。非法单价在 run 开始即抛错 |
|
|
162
163
|
| `onUnpricedModel` | 遇到价格表外的模型时回调(`{ model, spanId }`,每个循环作用域内每模型一次);抛错被吞,**不改变 run 结局**(定价缺失是宿主配置问题)。用它接告警 |
|
|
163
164
|
| `toolTimeoutMs` | 缺省单工具超时(毫秒);超时该条 tool_result 记 is_error,不杀 run |
|
|
164
165
|
| `maxToolConcurrency` | 缺省同回合并行工具上限;不设 = 不限(全并行) |
|
|
166
|
+
| `maxEventChars` | 缺省 trace 事件正文截断上限(可被单次 run 覆盖):数字 = 入参/出参统一用该上限,`false` = **不截断**(完整正文进 trace,面板里能展开看全文);不设 = 框架缺省 |
|
|
165
167
|
|
|
166
168
|
### `app.run(messages, opts?: RunAppOptions)`
|
|
167
169
|
|
|
@@ -173,18 +175,21 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
173
175
|
| `maxIterations` | 单次覆盖 |
|
|
174
176
|
| `client` | 注入 `ModelClient`(换 OpenAI 兼容端点等) |
|
|
175
177
|
| `onText` | 文本增量回调(SSE/终端) |
|
|
178
|
+
| `signal` | `AbortSignal`:中止则在飞请求被取消,run 以 `stopReason='aborted'` 收尾(算失败) |
|
|
176
179
|
| `blackboard` | 预置黑板种子(配 `Blackboard` 声明合并有键补全) |
|
|
177
180
|
| `contextPolicy` | 单次覆盖上下文策略 |
|
|
181
|
+
| `retry` | 单次覆盖重试策略:`false` 关闭,或 `{ maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参(只重试「本次尝试尚未产出文本」的可重试失败) |
|
|
178
182
|
| `idempotencyKey` | 幂等键(异步宿主的 at-least-once 去重依据) |
|
|
179
183
|
| `rethrow` | 硬失败是否抛出;缺省 `true`(异步宿主置 `false`,落 failed 记录而非冒泡) |
|
|
180
|
-
| `tools` |
|
|
184
|
+
| `tools` | 单次覆盖工具菜单(**同样过装配期那条中间件链**,不是旁路 —— 否则 per-run 覆盖就绕开了鉴权/限流/审计) |
|
|
181
185
|
| `resultSchema` | 结构化结果 schema;配 `fromZod<T>` 可让 `result.typed` 自动是 `T` |
|
|
182
|
-
| `maxTotalTokens` | 成本硬管控:整条 run
|
|
186
|
+
| `maxTotalTokens` | 成本硬管控:整条 run(**含子 agent / skill 子循环**,各级共享同一 recorder 的累计账单)累计 token 上限;超限以 `stopReason='budget_exceeded'` 收尾(**算失败**) |
|
|
183
187
|
| `maxCostUsd` | 成本硬管控:累计成本(美元)上限;模型不在价格表内时**不触发**(用 `priceOverrides` 定价,或用 `maxTotalTokens` 兜底) |
|
|
184
188
|
| `priceOverrides` | 单次覆盖价格表(`$/1M tokens`);语义同 `createApp` 的 `priceOverrides` |
|
|
185
189
|
| `onUnpricedModel` | 单次覆盖未定价回调(**是函数,因此不在 transport 的 `RunInvocationOptions` 里** —— 异步宿主不会替你传) |
|
|
186
190
|
| `toolTimeoutMs` | 单个工具执行超时(毫秒);超时该条 tool_result 记 is_error,run 继续 |
|
|
187
191
|
| `maxToolConcurrency` | 同回合并行工具上限;缺省不限 |
|
|
192
|
+
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** —— 同一棵调用树上口径一致。只影响**记账**,回给模型的 tool_result 永远完整 |
|
|
188
193
|
| `session` | 会话持久化 `{ store, id }`:run 前拼历史、成功收尾追加本轮(见 `SessionStore`) |
|
|
189
194
|
|
|
190
195
|
返回 `AgentRunOutput`:`{ run, result }`。`result` 含 `trace` / `stopReason` / `finalText` / `iterations` / `error` / `typed`。
|
|
@@ -306,7 +311,7 @@ const app = await createApp({ ... });
|
|
|
306
311
|
|
|
307
312
|
> ⚠️ **本机 export 过 `ANTHROPIC_API_KEY` 的人**(比如同时用 Claude Code):按上面的优先级,脚手架 `.env` 里的 key 会被**静默压住**。改了 `.env` 却「没生效」时,先 `echo $ANTHROPIC_API_KEY` 看看环境里是不是已经有一份。
|
|
308
313
|
|
|
309
|
-
解析规则(刻意窄,够用就好):`KEY=VALUE`,允许 `export ` 前缀与 `=` 两侧空白;`#` 整行注释;单引号内原样、双引号内认 `\n \r \t \" \\`;未加引号的值里 ` #`
|
|
314
|
+
解析规则(刻意窄,够用就好):`KEY=VALUE`,允许 `export ` 前缀与 `=` 两侧空白;`#` 整行注释;单引号内原样、双引号内认 `\n \r \t \" \\`;未加引号的值里 ` #` 起为行内注释。键名 `__proto__` **显式报错**(它会走原型 setter 被静默吞掉 —— 正是「以为配上了其实没配上」)。**不做变量展开、不合并续行**(需要就上专门的库);既不像 `KEY=VALUE` 又不是注释的行**直接报错并指出行号** —— 静默跳过等于让你以为「配上了其实没配上」。
|
|
310
315
|
|
|
311
316
|
### 宿主(换宿主不换语义)
|
|
312
317
|
|
|
@@ -321,7 +326,7 @@ const app = await createApp({ ... });
|
|
|
321
326
|
| `InMemoryTaskStore` | 内存任务存储(可设 `maxRecords` 做内存闸门) |
|
|
322
327
|
| `FileTaskStore` | JSONL 耐久存储(`compact()` 可压实日志) |
|
|
323
328
|
| `SqliteTaskStore` | `node:sqlite` 耐久存储(WAL + busy_timeout) |
|
|
324
|
-
| `RedisTaskStore` | duck-typed Redis 存储(可设 `ttlSeconds
|
|
329
|
+
| `RedisTaskStore` | duck-typed Redis 存储(可设 `ttlSeconds`);`RedisLike.set` 用**位置参数**形态 `set(key, value, 'EX', seconds)` —— ioredis 原生 / node-redis legacy 变参通吃(对象形态 `{EX}` 是 node-redis 独有,ioredis 会把它字符串化成 `"[object Object]"` 发出);不设 TTL 时只传两参 |
|
|
325
330
|
|
|
326
331
|
### HTTP 端点速查(`createHttpHandler` 的路由)
|
|
327
332
|
|
|
@@ -356,13 +361,14 @@ const handler = createHttpHandler(callable, { runner });
|
|
|
356
361
|
| `authenticate` | 入口鉴权钩子:**除 `/healthz` 与 `/metrics` 外所有路径**都过它,且在**读 body 之前**(未通过就不收 body)。正常返回即通过;抛 `HttpException` 按其 `status`/`body` 回;抛别的错误回 401,原文只进服务端日志。框架**不实现策略**(不读 env、不碰凭据) |
|
|
357
362
|
| `metrics` | 指标出口:给 `metricsSink()`(或任意 `{ render() }` / 返回字符串的闭包)后,`GET /metrics` 回它的 Prometheus 文本。**不鉴权**(拉取端在集群内网);要保护请放反代后面。不给则该路径 404。⚠️ 这只管**渲染** —— 数字要真的累计,必须把**同一个** sink 注册进 `createApp({ sinks: [metrics] })`(它靠 run 收尾投递,不自己埋点),否则 `/metrics` 恒为 0 **且不报错** |
|
|
358
363
|
| `maxBodyBytes` | 请求 body 上限(字节),超限回 413;缺省 1 MiB |
|
|
359
|
-
| `maxConcurrentRuns` | 同时在跑的 `POST /run` 上限,超限回 503 + `Retry-After`;缺省 32(传 `Infinity`
|
|
364
|
+
| `maxConcurrentRuns` | 同时在跑的 `POST /run` 上限,超限回 503 + `Retry-After`;缺省 32(传 `Infinity` 恢复无上限)。构造期校验:必须 > 0 或 Infinity —— NaN 会让闸门静默失效、0/负数会全部 503,故直接抛错 |
|
|
365
|
+
| `sseMaxBufferedBytes` | SSE 下游积压上限(字节,`res.writableLength` 超过即收口该 SSE 流并 **abort 对应 run** —— 客户端已经不消费了,继续逐 token 生成只是白烧 token);缺省 8 MiB |
|
|
360
366
|
| `exposeErrors` | 是否把内部异常原文回给调用方;缺省 `false`(细节只进服务端日志) |
|
|
361
367
|
| `runner` | 注入 `AsyncRunner`(共用 store / 并发上限 / `resumePending`);缺省内部 `new AsyncRunner(app)` |
|
|
362
368
|
|
|
363
369
|
返回值另外挂着两样(不影响 `(req,res)` 的调用形状):
|
|
364
370
|
|
|
365
|
-
- **`handler.drain(opts?)`** —— 优雅停机:拒新单(`POST /run` 与 `/tasks` → 503,`GET /tasks/:id` 仍可轮询)→ 等异步任务与在飞同步 run 收尾 → 强制收口仍开着的 SSE
|
|
371
|
+
- **`handler.drain(opts?)`** —— 优雅停机:拒新单(`POST /run` 与 `/tasks` → 503,`GET /tasks/:id` 仍可轮询)→ 等异步任务与在飞同步 run 收尾 → 强制收口仍开着的 SSE 流(**收口同时 abort 对应 run**,以 `stopReason='aborted'` 收尾 —— 只关流不中止会让 run 在后台继续烧 token)。返回是否排空干净;超时返回 `false`,**未完成的任务留在 store 里**,下次启动由 `resumePending` 续跑(不是丢弃)。`timeoutMs` 缺省 0 = 一直等。
|
|
366
372
|
**框架不订阅信号** —— `process.on('SIGTERM', () => handler.drain())` 是宿主的事(同「框架不读 env」)。
|
|
367
373
|
- **`handler.runner`** —— 内部 `AsyncRunner`,需要时手动控制(`resumePending` / `awaitTask` / `list`)。
|
|
368
374
|
|
|
@@ -402,7 +408,7 @@ process.on('SIGTERM', async () => {
|
|
|
402
408
|
| `isAbortError` | 判定异常是否为中断(`name === 'AbortError'`) |
|
|
403
409
|
| `mapWithConcurrency` | 有界并发 map(结果保序);`maxToolConcurrency` 的底座,也可自用 |
|
|
404
410
|
|
|
405
|
-
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs`
|
|
411
|
+
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs` 到点同样是**真中止**(构造期校验:必须 ≥ 0 的**有限**数 —— NaN/Infinity 会被 `setTimeout` 钳到 1ms,等于每个任务立即超时,故直接抛错;要「不限」传 0 或不设)。
|
|
406
412
|
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与 SDK 内置重试叠加 —— 建议二选一调(这里 `maxAttempts: 1` 或把 SDK 的 `maxRetries` 调小)。
|
|
407
413
|
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error`);不带该头仍回一元 JSON。
|
|
408
414
|
- **工具超时 / 并发闸门**:`toolTimeoutMs` 超时**不杀 run**(该条 tool_result 记 `is_error`,模型可换路);`maxToolConcurrency` 给同回合的并行工具设上限(默认全并行)。⚠️ 超时 = **放弃等待**,`AgentTool.run` 没有 signal 参数,**副作用可能已发生** —— 想真停的工具请自行读 `ToolRunContext.signal`。
|
|
@@ -414,7 +420,7 @@ process.on('SIGTERM', async () => {
|
|
|
414
420
|
| `TraceSink` | `{ export(trace) }`,run 收尾(成功/失败)都投递,抛错被吞 |
|
|
415
421
|
| `registerDefaultTraceSink` | 注册全局默认 sink(构造期快照合并) |
|
|
416
422
|
| `TraceRecorder` | 内存 recorder(一次 run 一个) |
|
|
417
|
-
| `createOtlpExporter` | OTLP/JSON
|
|
423
|
+
| `createOtlpExporter` | OTLP/JSON 导出,零依赖;选项 `OtlpExporterOptions`:`endpoint` / `headers` / `serviceName` / `timeoutMs`(单次导出超时,缺省 10000,非正数 = 不限 —— 裸 fetch 无超时,collector 半开连接会让 run 收尾永久挂起;超时按导出失败处理,不击穿 run) |
|
|
418
424
|
| `metricsSink` | 指标累加器(Prometheus 文本 / OTLP metrics),满足 `TraceSink` 即接入 —— 见 §6「指标」 |
|
|
419
425
|
| `buildRunReport` | 从一条 trace 生成**调优报告**(能力/模型的耗时、token、成本、错误率排行)—— 见 §6「调优报告」 |
|
|
420
426
|
|
|
@@ -430,7 +436,14 @@ process.on('SIGTERM', async () => {
|
|
|
430
436
|
| `createBudgetPolicy` | 预算策略:超预算先 `trimToolPairs` 编辑,再 `compactMessages` 压缩(带滞回) |
|
|
431
437
|
| `trimToolPairs` | context editing:丢旧 tool 对(按**对数**,`keepToolPairs`) |
|
|
432
438
|
| `compactMessages` | compaction:旧前缀做摘要(摘要器由你注入,框架不替你造 token) |
|
|
433
|
-
| `estimateMessages` |
|
|
439
|
+
| `estimateMessages` | 估算一组消息的 token(预算决策用,不是精确记账) |
|
|
440
|
+
| `defaultEstimateTokens` | 缺省的单文本估算函数(CJK 感知启发式:CJK ≈ 1.5 字/token、其余 ≈ 4 字符/token) |
|
|
441
|
+
| `renderMessages` | 把 messages 渲染成纯文本 —— 喂给你注入的 compaction 摘要器(`summarize`)用 |
|
|
442
|
+
|
|
443
|
+
**per-run 隔离(`ContextPolicy.forRun`)**:策略可能被配成应用级单例(`createApp({ contextPolicy })`)
|
|
444
|
+
被所有 run 复用。带状态的实现(滞回计数、token 缓存等)应实现可选的 `forRun(): ContextPolicy` ——
|
|
445
|
+
引擎在每条 run 开始时调一次,拿**本 run 专用**的实例(`createBudgetPolicy` 已实现它);
|
|
446
|
+
不实现的自定义策略按单例复用,状态跨 run(含并发 run)共享 —— 适合无状态策略,有状态请实现 `forRun`。
|
|
434
447
|
|
|
435
448
|
### 成本硬管控(**别与上面的上下文预算混为一谈**)
|
|
436
449
|
|
|
@@ -449,8 +462,8 @@ process.on('SIGTERM', async () => {
|
|
|
449
462
|
|
|
450
463
|
- 超限后 run 以 `stopReason='budget_exceeded'` 收尾(**算失败**),run 根记一条 `budget.exceeded` 事件(带 `{ kind, limit, actual, totalTokens, costUsd }`)。
|
|
451
464
|
- **不是硬实时**:一回合跑完才判,实际用量可能超上限一个回合的量。
|
|
452
|
-
- **模型自然收尾的那一回合超限不改判失败**(只留事件)—— 那次 run
|
|
453
|
-
-
|
|
465
|
+
- **模型自然收尾的那一回合超限不改判失败**(只留事件)—— 那次 run 的任务其实做完了,不该追认成失败。同理,超预算的回合仍照常处理 `submit_result`(纯内部的结构化提交、零副作用)—— 模型已把最终结果交出来,连同回合丢弃等于白烧这一回合。
|
|
466
|
+
- 预算是**整条 run(含各级子 agent / skill 子循环)**的口径:上限经 `ToolRunContext` 透传,各级循环共享同一 recorder 的累计账单、每回合各自检查。子循环超限以 `budget_exceeded` 收尾(该次能力调用记 `is_error`,capability span 上记 `budget.exceeded` 事件),主循环在下一回合**入口**拦住、不再发出新请求,整条 run 以 `budget_exceeded` 收尾。
|
|
454
467
|
|
|
455
468
|
```ts
|
|
456
469
|
const { result } = await app.run(messages, { maxTotalTokens: 200_000 });
|
|
@@ -505,7 +518,7 @@ if (result.stopReason === 'budget_exceeded') console.warn('这次 run 被预算
|
|
|
505
518
|
| `McpClientLike` | 最小结构面:`listTools()` + `callTool(name, args)`;框架**不 import** MCP SDK |
|
|
506
519
|
| `MCP_DEFAULT_TIMEOUT_MS` | 桥的缺省单次调用超时(60000 ms) |
|
|
507
520
|
|
|
508
|
-
- **名字**:`prefix + 归一化原名`(MCP 名里的 `-` / `.` / 空格 → `_
|
|
521
|
+
- **名字**:`prefix + 归一化原名`(MCP 名里的 `-` / `.` / 空格 → `_`)。归一化后**空名(原名不含任何 ASCII 字母/数字/下划线时产物为空,如全 emoji 名)/ 撞名 / 超 64 字符**一律**装配期抛错**(静默改名会得到一个调不回去的名字,比启动期报错难查得多)。
|
|
509
522
|
- **原名**:每次调用写进发起 turn 的 `mcp.tool` attribute —— 审计 / 回放要还原它才能回调 server。
|
|
510
523
|
- **入参 schema**:MCP 的 `inputSchema` 已是 JSON Schema → 原样透传,由 engine 的子集校验器在 `callTool` **之前**校验(非法入参根本不会发给 server,模型自己会改)。
|
|
511
524
|
- **失败**:`callTool` 抛错 → 该条 `tool_result` 记 `is_error`,**不杀 run**(与本地工具抛错同语义)。⚠️ **协议层的 `isError: true` 框架看不见** —— 连接器必须转成抛错,否则模型以为成功了。
|
|
@@ -580,7 +593,9 @@ if (!report.ok) console.error(report.cases.filter((c) => !c.ok));
|
|
|
580
593
|
时长同时给两种口径,**并存不冲突**:
|
|
581
594
|
|
|
582
595
|
- **histogram**(`*_bucket` / `*_sum` / `*_count`,累积语义)—— 抓取端可**跨实例任意聚合**;
|
|
583
|
-
- **窗口内精确分位**(`*{quantile="..."}` gauge
|
|
596
|
+
- **窗口内精确分位**(`*_last{quantile="..."}` gauge,如 `agentia_run_duration_ms_last`)—— 单实例排障时更好读。
|
|
597
|
+
分位 gauge 与 histogram **必须不同名**(同名指标只允许一种 TYPE,混发会被 expfmt 判硬错误、整次 scrape 失败),
|
|
598
|
+
故分位家族统一带 `_last` 后缀;capability / model 维度同理(`capability_duration_ms_last` / `model_duration_ms_last`)。
|
|
584
599
|
|
|
585
600
|
### `MetricsSinkOptions`(`metricsSink` 的选项)
|
|
586
601
|
|
|
@@ -590,6 +605,7 @@ if (!report.ok) console.error(report.cases.filter((c) => !c.ok));
|
|
|
590
605
|
| `endpoint` | OTLP 采集端基地址(如 `http://localhost:4318`);尾部斜杠会被去掉 |
|
|
591
606
|
| `intervalMs` | OTLP 导出间隔(毫秒,缺省 60000);`0` = 每次 run 收尾立即导出。定时器已 `unref()`,不阻止进程退出 |
|
|
592
607
|
| `resourceAttributes` / `serviceName` | OTLP resource 属性(`service.name` 缺省 `agentia`) |
|
|
608
|
+
| `timeoutMs` | OTLP 单次导出请求超时(毫秒,缺省 10000,非正数 = 不限)——collector 半开连接时兜底,`intervalMs: 0` 模式不被挂死;超时按导出失败处理 |
|
|
593
609
|
| `onExportError` | 导出失败回调(缺省吞掉 —— 观测失败不得击穿业务) |
|
|
594
610
|
| `windowSize` | 时长分位保留的样本数(环形窗口,缺省 1024,**run / 能力 / 模型各自独立**);非正数抛错 |
|
|
595
611
|
| `prefix` | 指标名前缀,缺省 `agentia_` |
|
|
@@ -613,6 +629,18 @@ if (!report.ok) console.error(report.cases.filter((c) => !c.ok));
|
|
|
613
629
|
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` —— 能力数由 `maxCapabilities` 封顶,长跑宿主不会被拖住。
|
|
614
630
|
- `costUsd` 依赖模型在价格表内(不在表里时不计、并计入 `unpricedTurns` 与 `usage.unpriced` 事件);根 span 未收尾(如失败路径的半截 trace)的 run 不进延迟样本。
|
|
615
631
|
|
|
632
|
+
### 调用树面板(`agentia dev` 的本地面板 / 官网 Playground)
|
|
633
|
+
|
|
634
|
+
同一份 `@migor/trace-view` 渲染器,**面板 / Playground / `report` 的能力排行三处共用**,不各写一套。
|
|
635
|
+
|
|
636
|
+
- **折叠态是「一行一件事」**:事件行(`tool.input` / `tool.output`)只显示省略号收敛的摘要。
|
|
637
|
+
- **点事件行展开看完整正文**(正文可选中复制),再点收起。展开的正文**默认仍与标签同一行**,
|
|
638
|
+
只有在放不下时才整段换到下一行 —— 官网 Playground 的 trace 列(约 320px)就属于放不下。
|
|
639
|
+
- **可展开的行右端常显一个 caret**(`▸` / `▾`):这一行能不能展开随时看得见,不必先悬停才发现。
|
|
640
|
+
- **展开只能展开 trace 里存着的正文** —— 想看到被截断掉的部分,得在**记账时**就别截:`maxEventChars: false`(见 §4)。缺省截到 2000 字符,展开了也只有那 2000 字符。
|
|
641
|
+
- 入参折叠态的摘要被砍到 62 字符(4 个键 / 每值 21 字符);**展开拿到的是原文**,不是那份摘要 ——
|
|
642
|
+
本地面板与官网 Playground 两个宿主都是如此(两个宿主都不该只喂摘要,否则点开什么都没多出来)。
|
|
643
|
+
|
|
616
644
|
### 调优报告(**哪个能力慢 / 贵 / 爱失败**)
|
|
617
645
|
|
|
618
646
|
指标回答「整体怎么样」,报告回答「**该拧哪个旋钮**」:
|
|
@@ -659,9 +687,13 @@ const app = await createApp({ /* … */ sinks: [jsonl] });
|
|
|
659
687
|
### 生效配置快照(「这条 run 用了哪套旋钮」)
|
|
660
688
|
|
|
661
689
|
每个 run 的**根 span** 都带一组 `config.*` attributes(`config.maxTokens` / `config.maxCostUsd` /
|
|
662
|
-
`config.retry.maxAttempts` / `config.contextPolicy.budgetTokens` / `config.priceOverrides`
|
|
663
|
-
|
|
690
|
+
`config.retry.maxAttempts` / `config.contextPolicy.budgetTokens` / `config.priceOverrides` /
|
|
691
|
+
`config.maxEventChars` …)。
|
|
692
|
+
带缺省值的那几项(`maxTokens` / `maxIterations` / `contextPolicy` / `retry`)**缺省值也记**——
|
|
693
|
+
「没配」与「配了缺省值」因此可区分;可选项(`toolTimeoutMs` / `maxToolConcurrency` /
|
|
694
|
+
`maxEventChars`)只在设了才记。换参数前后对比、复现线上行为都有据可查。
|
|
664
695
|
函数型选项(`summarize` / `confirm` 之类)只记「配没配」,不记函数体。
|
|
696
|
+
截断关掉时记的是 `'off'` 而不是 `false` —— 后者在日志/看板里会被读成「上限为 0」。
|
|
665
697
|
|
|
666
698
|
### 提示词版本化
|
|
667
699
|
|
|
@@ -780,7 +812,11 @@ const callable = {
|
|
|
780
812
|
| 历史畸形就放弃裁剪 | `trimToolPairs` 遇到非严格交替历史会整体放弃(宁可少裁,也不切出孤立 tool_use 让请求 400) |
|
|
781
813
|
| 缺省内存 store 不淘汰 | 长跑宿主请设 `InMemoryTaskStore({ maxRecords })` 或换 `FileTaskStore` / `SqliteTaskStore` |
|
|
782
814
|
| 能力引用是 provider 粒度 | 子 agent / skill 的 `tools` 写的是 **provider token**,不是单个工具名 |
|
|
815
|
+
| 能力名有格式校验 | 装饰器能力名(`name` 或缺省的方法名)必须匹配 `^[A-Za-z0-9_-]{1,64}$`(与 MCP 桥同口径),非法名在 `createApp` **装配期即抛错** —— 含空格/点/中文的名字会让模型 API 400,宁可在启动期拦住 |
|
|
816
|
+
| `discover` 入口会回落 | 能力目录里源码与编译产物并存(`index.ts` + `index.js`)时,首选 `.ts` 加载失败会**回落 `.js` 并 warn** —— 命中的可能是**陈旧编译产物**(刚改过源码时注意);全部候选都失败才抛错并列出各自原因 |
|
|
817
|
+
| `asset()` 的 rel 必须是相对路径 | 带 scheme(`file:` / `https:` …)的 rel 会让 `new URL(rel, base)` 整个忽略 base(「以为读了能力目录、实际读了别处」),显式抛错;`../` 越出能力目录是**有意放行**(共享资产如 `../../shared/x.md` 是合法用法) |
|
|
783
818
|
| 取消要传进客户端才有效 | 传 `signal` 后框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发);不转发 `signal` 的自定义 `ModelClient` 只能「放弃等待」(请求在后台跑完、产物丢弃) |
|
|
819
|
+
| 工具阶段的 abort 有盲区 | abort 只在三处被观察:**回合边界 / 在飞模型请求 / 重试退避 sleep**。没设 `toolTimeoutMs` 且工具挂死时,abort 之后 run 也不会返回(工具的 Promise 永不 settle)—— 挂死的工具要么设超时,要么自己读 `ToolRunContext.signal` |
|
|
784
820
|
| 观测失败被吞 | sink 抛错不影响 run(观测是辅助动作);同理记忆水合/回写失败也不击穿 run |
|
|
785
821
|
| 框架不自动读 .env | 除 `AGENTIA_MODEL`(缺省模型覆盖)与 `OPENAI_API_KEY`(OpenAI 适配器)外,框架自己不去翻环境变量,也不读 `.env`;要读就在启动代码里调 `loadEnvFile()`(脚手架已内置那行),**真实环境变量优先**于文件 |
|
|
786
822
|
| 鉴权只是缝 | 框架**不实现** token / JWT / 签名策略,也不碰凭据 env —— `authenticate` 只承诺「拦在入口、读 body 之前」;策略是宿主或反代的事 |
|
|
@@ -792,13 +828,15 @@ const callable = {
|
|
|
792
828
|
| `maxCostUsd` 依赖价格表 | 模型不在价格表内(且未用 `priceOverrides` 覆盖)时成本恒为 0,这条护栏**不触发** —— 要无条件兜底用 `maxTotalTokens`。**但失效不再静默**:turn 上会记 `usage.unpriced` 事件、指标有 `model_unpriced_turns_total`、可回调 `onUnpricedModel` |
|
|
793
829
|
| 工具超时**不取消**工具 | `AgentTool.run` 没有 signal 参数,超时只是「不等了」;副作用可能已发生。想真停请让工具自己读 `ToolRunContext.signal` |
|
|
794
830
|
| 会话只存对话轮次 | `SessionStore` 存「用户输入 + 最终回复」,run 内部的 tool 往返**不进历史**(要完整过程用 `traceToMessages`);且只有**跑成功**的轮次才回写 |
|
|
831
|
+
| 同 session 并发 run 要自行串行化 | `SessionStore` 是 **append-only**:并发写不互相覆盖、不丢数据,但**不保证角色交替** —— 两个并发 run 共用同一 sessionId 时,各自追加的轮次可能交错成「连续两条 user」,下一轮 load 出来撞角色交替校验(400)。同一 session 的并发 run 请调用方自行串行化(每 session 一把锁 / 一条队列) |
|
|
795
832
|
| OpenAI 适配器听端点的话 | 请求发 `stream:true`,但**按响应形态解析**:端点回 JSON 就退回一次性(没有打字机效果),回 `event-stream` 才逐 token |
|
|
833
|
+
| OpenAI 流式的上游故障不再装成功 | 流中 `error` 分片(上游把故障塞进 200 的流)与「流正常结束却无文本无 tool_calls」都**抛错**按失败处理 —— 不再静默映射成「成功空回复」(与非流式空 `choices` 的守卫同口径) |
|
|
796
834
|
| MCP 只做 tools | `sampling`(server 反向请求模型)/ `resources` / `prompts` 原语不做;连接器(stdio / HTTP)不在框架内 |
|
|
797
835
|
| MCP 的协议层错误框架看不见 | `isError: true` 只有连接器能看见 —— 它必须转成抛错,否则模型收到的是一条「成功」的结果 |
|
|
798
836
|
| MCP 超时同样是「不等了」 | 桥自带的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它与 engine 的 `toolTimeoutMs` **双重计时**,谁短谁生效 |
|
|
799
837
|
| MCP 名字可能被归一化 | 原名含 `-` / `.` / 空格 → 进菜单时变成 `_`;回调 server 用的仍是原名(`mcp.tool` attribute 里查得到) |
|
|
800
838
|
| MCP 工具不能进 DI 容器 | 它没有 provider token,也不能被别的能力的 `tools` 引用 —— 引用是 provider 粒度 |
|
|
801
|
-
| 指标分位是窗口内精确值 | `*{quantile=...}` 只反映最近 `windowSize`(缺省 1024)条样本;要跨实例聚合请用直方图(`*_bucket` / `_sum` / `_count`,累积语义) |
|
|
839
|
+
| 指标分位是窗口内精确值 | `*_last{quantile=...}` 只反映最近 `windowSize`(缺省 1024)条样本;要跨实例聚合请用直方图(`*_bucket` / `_sum` / `_count`,累积语义) |
|
|
802
840
|
| 指标是**进程内**累加 | 不做分布式聚合与持久化:多实例各算各的(直方图可相加),重启即清零。要长期保留请把 `render()` 抓走或用 `export:'otlp'` 推给采集端 |
|
|
803
841
|
| OTLP metrics 只推当前累计 | 按 `intervalMs` 周期导出**累积值**(CUMULATIVE),不做增量/背压;导出失败按 `onExportError` 处理(缺省吞掉,不重试、不阻塞 run) |
|
|
804
842
|
| `GET /metrics` 不鉴权 | 与 `/healthz` 同档(拉取端在集群内网)。要保护请放反代之后,或不传 `metrics` 选项自行在外层挂路由 |
|
|
@@ -837,7 +875,7 @@ const callable = {
|
|
|
837
875
|
| MCP 调用「成功」但内容是错误文本 | 连接器没把协议层 `isError: true` 转成抛错(框架只认抛错) |
|
|
838
876
|
| eval 里模型调了不存在的工具 | 脚本里的工具名必须是**菜单里的名字**(MCP 工具是归一化后的 `mcp_<server>_<name>`) |
|
|
839
877
|
| `metricsSink` 的数字一直是 0 | 没接进 `createApp({ sinks })`(或 `registerDefaultTraceSink`)—— 它靠 run 收尾投递,不自己埋点 |
|
|
840
|
-
|
|
|
878
|
+
| `metricsSink({ export: 'otlp' })` 构造期报错 | 没给 `endpoint` —— OTLP 导出必须知道往哪发,响亮失败好过静默不导出;补上 `endpoint`(如 `http://localhost:4318`)即可。`windowSize` / `maxCapabilities` 非正数、`buckets` 非严格升序同理是构造期配置校验 |
|
|
841
879
|
|
|
842
880
|
---
|
|
843
881
|
|
|
@@ -849,6 +887,4 @@ npm run typecheck:tests # 测试目录类型(含类型断言测试)
|
|
|
849
887
|
npm run test # 单测(node:test)
|
|
850
888
|
```
|
|
851
889
|
|
|
852
|
-
|
|
853
|
-
`examples/complete` 真起服务跑三种触发)、`npm run e2e:examples`(只跑后者)、
|
|
854
|
-
`npm run e2e:mcp`(真接一个 MCP server 走完「映射 → 菜单 → run」;无网时自动回落本地夹具 server)。
|
|
890
|
+
框架仓库另有这些门禁:`npm run typecheck:types`(针对构建产物的类型测试)、`npm run e2e`(三步链:CLI 端到端 + `examples/complete` 与 `examples/deploy` 真起服务)、`npm run e2e:examples` / `npm run e2e:deploy`(单跑对应一步)、`npm run e2e:mcp`(真接一个 MCP server 走完「映射 → 菜单 → run」;无网时自动回落本地夹具 server)。
|
package/dist/core/abort.d.ts
CHANGED
|
@@ -8,3 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** 合成多个中断源:任一已中止 / 后中止即中止;忽略 undefined;全空返回**永不中止**的 signal。 */
|
|
10
10
|
export declare function combineSignals(...signals: Array<AbortSignal | undefined>): AbortSignal;
|
|
11
|
+
/**
|
|
12
|
+
* run 正常收尾(没有任何源中止)时主动摘除 combineSignals 挂在各源上的监听器。
|
|
13
|
+
* 对非 combineSignals 产物 / 已清理的 signal 幂等空操作。
|
|
14
|
+
*
|
|
15
|
+
* 模块级 export(不进公共面):调用方(如 transport/async)在 run settle 后调用。
|
|
16
|
+
*/
|
|
17
|
+
export declare function releaseCombinedSignal(signal: AbortSignal): void;
|
package/dist/core/abort.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Node 18 没有 `AbortSignal.any`,这里手写兜底(Node 20.3+ 才有)。
|
|
8
8
|
*/
|
|
9
|
+
/** 合成 signal → 其「摘除全部源监听器」的清理函数(仅 combineSignals 多源分支的产物在表内) */
|
|
10
|
+
const cleanups = new WeakMap();
|
|
9
11
|
/** 合成多个中断源:任一已中止 / 后中止即中止;忽略 undefined;全空返回**永不中止**的 signal。 */
|
|
10
12
|
export function combineSignals(...signals) {
|
|
11
13
|
const real = signals.filter((s) => s !== undefined);
|
|
@@ -20,9 +22,33 @@ export function combineSignals(...signals) {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
if (!ac.signal.aborted) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
// 每个源一个具名监听(存进 map 供摘除):合成 signal 中止后,其余源上的监听器
|
|
26
|
+
// 就再也没用 —— 不摘掉的话,宿主级共享 signal(长寿)每跑一条 run 多挂一个,
|
|
27
|
+
// 累积 >10 触发 MaxListenersExceededWarning,闭包也跟着滞留。
|
|
28
|
+
const listeners = new Map();
|
|
29
|
+
const detach = () => {
|
|
30
|
+
for (const [s, l] of listeners)
|
|
31
|
+
s.removeEventListener('abort', l);
|
|
32
|
+
listeners.clear();
|
|
33
|
+
cleanups.delete(ac.signal);
|
|
34
|
+
};
|
|
35
|
+
for (const s of real) {
|
|
36
|
+
const l = () => ac.abort(s.reason);
|
|
37
|
+
listeners.set(s, l);
|
|
38
|
+
s.addEventListener('abort', l, { once: true });
|
|
39
|
+
}
|
|
40
|
+
ac.signal.addEventListener('abort', detach, { once: true });
|
|
41
|
+
cleanups.set(ac.signal, detach);
|
|
25
42
|
}
|
|
26
43
|
return ac.signal;
|
|
27
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* run 正常收尾(没有任何源中止)时主动摘除 combineSignals 挂在各源上的监听器。
|
|
47
|
+
* 对非 combineSignals 产物 / 已清理的 signal 幂等空操作。
|
|
48
|
+
*
|
|
49
|
+
* 模块级 export(不进公共面):调用方(如 transport/async)在 run settle 后调用。
|
|
50
|
+
*/
|
|
51
|
+
export function releaseCombinedSignal(signal) {
|
|
52
|
+
cleanups.get(signal)?.();
|
|
53
|
+
}
|
|
28
54
|
//# sourceMappingURL=abort.js.map
|
package/dist/core/abort.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/core/abort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAG,OAAuC;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACtE,yBAAyB;IACzB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IAEtC,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnB,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/core/abort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA2B,CAAC;AAExD,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAG,OAAuC;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACtE,yBAAyB;IACzB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IAEtC,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnB,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,+CAA+C;QAC/C,gDAAgD;QAChD,iDAAiD;QACjD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;QACrD,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS;gBAAE,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAClE,SAAS,CAAC,KAAK,EAAE,CAAC;YAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,GAAS,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACzC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,EAAE,CAAC,MAAM,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmB;IACvD,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;AAC3B,CAAC"}
|
package/dist/core/schema.js
CHANGED
|
@@ -57,19 +57,21 @@ function checkObject(schema, value, path) {
|
|
|
57
57
|
}
|
|
58
58
|
const obj = value;
|
|
59
59
|
for (const key of schema.required ?? []) {
|
|
60
|
-
|
|
60
|
+
// 用 Object.hasOwn 而非 `in`:后者会命中原型链,required:['constructor'] 就恒通过了
|
|
61
|
+
if (!Object.hasOwn(obj, key))
|
|
61
62
|
return `${path}: 缺少必需属性 "${key}"`;
|
|
62
63
|
}
|
|
63
64
|
const properties = schema.properties ?? {};
|
|
64
65
|
for (const [key, sub] of Object.entries(properties)) {
|
|
65
|
-
if (key
|
|
66
|
+
if (Object.hasOwn(obj, key)) {
|
|
66
67
|
const err = check(sub, obj[key], `${path}.${key}`);
|
|
67
68
|
if (err)
|
|
68
69
|
return err;
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
if (schema.additionalProperties === false) {
|
|
72
|
-
|
|
73
|
+
// 同上:`k in properties` 会把 toString 之类的原型键当成「已声明」放行
|
|
74
|
+
const extra = Object.keys(obj).filter((k) => !Object.hasOwn(properties, k));
|
|
73
75
|
if (extra.length > 0) {
|
|
74
76
|
return `${path}: 存在 schema 未声明的属性 ${extra.map((k) => `"${k}"`).join(', ')}`;
|
|
75
77
|
}
|
package/dist/core/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB,EAAE,KAAc;IACnE,0DAA0D;IAC1D,uEAAuE;IACvE,MAAM,EAAE,GAAI,MAAkC,CAAC,aAAa,CAAC;IAC7D,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAI,EAAwC,CAAC,KAAK,CAAC,CAAC;QAC7D,IAAI,GAAG;YAAE,OAAO,MAAM,GAAG,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,KAAK,CAAC,MAAkB,EAAE,KAAc,EAAE,IAAY;IAC7D,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAG,IAAI,uBAAuB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;IACzD,CAAC;IAED,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,KAAK,OAAO;YACV,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACzC,KAAK,QAAQ;YACX,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,kBAAkB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACrF,KAAK,QAAQ;YACX,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACxD,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,GAAG,IAAI,kBAAkB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,KAAK,SAAS;YACZ,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;gBACzD,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,GAAG,IAAI,mBAAmB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,KAAK,SAAS;YACZ,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,mBAAmB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACvF,KAAK,MAAM;YACT,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,gBAAgB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE;YACE,OAAO,IAAI,CAAC,CAAC,mBAAmB;IACpC,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,MAAkB,EAAE,KAAc,EAAE,IAAY;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,GAAG,IAAI,kBAAkB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/core/tool.d.ts
CHANGED
|
@@ -116,6 +116,24 @@ export interface ToolRunContext {
|
|
|
116
116
|
* 模型在子循环里会退化成"未定价"(成本恒 0,`maxCostUsd` 静默失效)。
|
|
117
117
|
*/
|
|
118
118
|
priceOverrides?: Record<string, ModelPricing>;
|
|
119
|
+
/**
|
|
120
|
+
* trace 事件正文的截断上限(见 `RunAgentOptions.maxEventChars`)。嵌套能力
|
|
121
|
+
* (@SubAgent / @Skill)拉起自己的 llm 循环时必须原样传下去,否则**同一棵调用树
|
|
122
|
+
* 上会出现两种截断口径** —— 主 agent 的工具结果看得见全文、子 agent 的却被截断,
|
|
123
|
+
* 而「子 agent 里的工具为什么失败」恰恰是最需要看全文的地方。
|
|
124
|
+
*
|
|
125
|
+
* 不设(undefined)= 子循环用各自缺省(不是"不截断")。
|
|
126
|
+
*/
|
|
127
|
+
maxEventChars?: number | false;
|
|
128
|
+
/**
|
|
129
|
+
* 成本硬管控(C1)透传:整条 run 累计 token 上限(见 `RunAgentOptions.maxTotalTokens`)。
|
|
130
|
+
* 嵌套能力(@SubAgent / @Skill)拉起自己的 llm 循环时必须原样传下去 —— 预算是
|
|
131
|
+
* **整条 run(含各级子 agent)** 的口径,子循环不拿到它就等于护栏在子循环期间离线
|
|
132
|
+
* (最坏可超一整个子 run 的用量)。各级循环共享同一 recorder,按同一份累计账单判断。
|
|
133
|
+
*/
|
|
134
|
+
maxTotalTokens?: number;
|
|
135
|
+
/** 成本硬管控(C1)透传:累计成本(美元)上限;同 maxTotalTokens 的透传语义 */
|
|
136
|
+
maxCostUsd?: number;
|
|
119
137
|
}
|
|
120
138
|
export interface AgentTool<I = unknown, O = unknown> {
|
|
121
139
|
/** 模型可见的唯一名(建议 snake_case) */
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -25,7 +25,11 @@ export interface SpanError {
|
|
|
25
25
|
/** 是否可安全重试(429/5xx/网络 vs 400/400类) */
|
|
26
26
|
retryable: boolean;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* 结构化事件(日志)。工具入参/出参**正文默认截断**(入参/成功出参 2000 字符、失败出参 1000),
|
|
30
|
+
* 完整正文需显式开启:`RunInvocationOptions.maxEventChars: false`(缺省关)。
|
|
31
|
+
* 脱敏**不在框架内** —— 那是 sink 缝外的事(spec §9.3),框架只保证出口形状。
|
|
32
|
+
*/
|
|
29
33
|
export interface SpanEvent {
|
|
30
34
|
time: number;
|
|
31
35
|
name: string;
|
package/dist/engine/loop.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export declare function runAgentScoped<S extends JsonSchema = JsonSchema>(opts:
|
|
|
47
47
|
toolTimeoutMs?: number;
|
|
48
48
|
/** 同回合并行工具上限;同 RunAgentOptions.maxToolConcurrency */
|
|
49
49
|
maxToolConcurrency?: number;
|
|
50
|
+
/** 事件正文截断上限;同 RunAgentOptions.maxEventChars */
|
|
51
|
+
maxEventChars?: number | false;
|
|
50
52
|
/** 价格表覆盖(F1):由发起它的能力从 ToolRunContext.priceOverrides 透传 */
|
|
51
53
|
priceOverrides?: Record<string, ModelPricing>;
|
|
52
54
|
/** 未定价模型回调(F2):由发起它的能力透传 */
|
|
@@ -54,6 +56,15 @@ export declare function runAgentScoped<S extends JsonSchema = JsonSchema>(opts:
|
|
|
54
56
|
model: string;
|
|
55
57
|
spanId: string;
|
|
56
58
|
}) => void;
|
|
59
|
+
/**
|
|
60
|
+
* 成本硬管控(C1):由发起它的能力从 ToolRunContext 透传 —— 预算是整条 run 的口径
|
|
61
|
+
* (各级循环共享同一 recorder,按同一份累计账单判断),子循环每回合同样检查;
|
|
62
|
+
* 子循环超限以 stopReason='budget_exceeded' 收尾,由能力层包成 is_error 回主循环,
|
|
63
|
+
* 主循环回合入口的预算检查随即将整条 run 停掉。
|
|
64
|
+
*/
|
|
65
|
+
maxTotalTokens?: number;
|
|
66
|
+
/** 成本硬管控(C1):累计成本(美元)上限;同 maxTotalTokens */
|
|
67
|
+
maxCostUsd?: number;
|
|
57
68
|
}): Promise<AgentLoopResult<SchemaType<S>>>;
|
|
58
69
|
/**
|
|
59
70
|
* 用 `next` 原地替换 `target` 的全部内容(保持数组引用不变 —— 循环各处持同一数组)。
|