@migor/agentia 0.0.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.
Files changed (89) hide show
  1. package/README.md +171 -0
  2. package/dist/container/container.d.ts +40 -0
  3. package/dist/container/container.js +66 -0
  4. package/dist/container/container.js.map +1 -0
  5. package/dist/core/schema.d.ts +14 -0
  6. package/dist/core/schema.js +116 -0
  7. package/dist/core/schema.js.map +1 -0
  8. package/dist/core/tool.d.ts +60 -0
  9. package/dist/core/tool.js +2 -0
  10. package/dist/core/tool.js.map +1 -0
  11. package/dist/core/trace.d.ts +52 -0
  12. package/dist/core/trace.js +6 -0
  13. package/dist/core/trace.js.map +1 -0
  14. package/dist/engine/context.d.ts +47 -0
  15. package/dist/engine/context.js +146 -0
  16. package/dist/engine/context.js.map +1 -0
  17. package/dist/engine/errors.d.ts +3 -0
  18. package/dist/engine/errors.js +22 -0
  19. package/dist/engine/errors.js.map +1 -0
  20. package/dist/engine/loop.d.ts +33 -0
  21. package/dist/engine/loop.js +227 -0
  22. package/dist/engine/loop.js.map +1 -0
  23. package/dist/engine/policy.d.ts +30 -0
  24. package/dist/engine/policy.js +35 -0
  25. package/dist/engine/policy.js.map +1 -0
  26. package/dist/engine/tracer.d.ts +27 -0
  27. package/dist/engine/tracer.js +93 -0
  28. package/dist/engine/tracer.js.map +1 -0
  29. package/dist/engine/types.d.ts +58 -0
  30. package/dist/engine/types.js +2 -0
  31. package/dist/engine/types.js.map +1 -0
  32. package/dist/engine/usage.d.ts +5 -0
  33. package/dist/engine/usage.js +30 -0
  34. package/dist/engine/usage.js.map +1 -0
  35. package/dist/index.d.ts +42 -0
  36. package/dist/index.js +31 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/run/async.d.ts +64 -0
  39. package/dist/run/async.js +135 -0
  40. package/dist/run/async.js.map +1 -0
  41. package/dist/run/context.d.ts +16 -0
  42. package/dist/run/context.js +48 -0
  43. package/dist/run/context.js.map +1 -0
  44. package/dist/run/fsStore.d.ts +29 -0
  45. package/dist/run/fsStore.js +79 -0
  46. package/dist/run/fsStore.js.map +1 -0
  47. package/dist/run/run.d.ts +50 -0
  48. package/dist/run/run.js +98 -0
  49. package/dist/run/run.js.map +1 -0
  50. package/dist/run/scheduler.d.ts +27 -0
  51. package/dist/run/scheduler.js +85 -0
  52. package/dist/run/scheduler.js.map +1 -0
  53. package/dist/run/spec.d.ts +40 -0
  54. package/dist/run/spec.js +25 -0
  55. package/dist/run/spec.js.map +1 -0
  56. package/dist/run/store.d.ts +40 -0
  57. package/dist/run/store.js +28 -0
  58. package/dist/run/store.js.map +1 -0
  59. package/dist/run/systemPrompt.d.ts +25 -0
  60. package/dist/run/systemPrompt.js +41 -0
  61. package/dist/run/systemPrompt.js.map +1 -0
  62. package/dist/run/transport.d.ts +12 -0
  63. package/dist/run/transport.js +16 -0
  64. package/dist/run/transport.js.map +1 -0
  65. package/dist/run/types.d.ts +12 -0
  66. package/dist/run/types.js +2 -0
  67. package/dist/run/types.js.map +1 -0
  68. package/dist/toolkit/asset.d.ts +22 -0
  69. package/dist/toolkit/asset.js +26 -0
  70. package/dist/toolkit/asset.js.map +1 -0
  71. package/dist/toolkit/discover.d.ts +2 -0
  72. package/dist/toolkit/discover.js +61 -0
  73. package/dist/toolkit/discover.js.map +1 -0
  74. package/dist/toolkit/module.d.ts +74 -0
  75. package/dist/toolkit/module.js +114 -0
  76. package/dist/toolkit/module.js.map +1 -0
  77. package/dist/toolkit/prompt.d.ts +32 -0
  78. package/dist/toolkit/prompt.js +70 -0
  79. package/dist/toolkit/prompt.js.map +1 -0
  80. package/dist/toolkit/skill.d.ts +74 -0
  81. package/dist/toolkit/skill.js +125 -0
  82. package/dist/toolkit/skill.js.map +1 -0
  83. package/dist/toolkit/subagent.d.ts +54 -0
  84. package/dist/toolkit/subagent.js +120 -0
  85. package/dist/toolkit/subagent.js.map +1 -0
  86. package/dist/toolkit/tool.d.ts +36 -0
  87. package/dist/toolkit/tool.js +54 -0
  88. package/dist/toolkit/tool.js.map +1 -0
  89. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # Agentia
2
+
3
+ 声明式 agent 服务开发框架:TS 装饰器 + DI,主 agent 调度 `@Tool` / `@Skill` / `@SubAgent` / `@Prompt` 单元执行任务,产出结构化结果与调用树(trace)。
4
+
5
+ ## 环境准备
6
+
7
+ ```bash
8
+ export ANTHROPIC_API_KEY=sk-... # 或 ANTHROPIC_AUTH_TOKEN
9
+ # 可选:ANTHROPIC_BASE_URL(兼容端点)、AGENTIA_MODEL(缺省 claude-opus-5)
10
+ ```
11
+
12
+ ## 快速开始(CLI)
13
+
14
+ ```bash
15
+ npm i -g @migor/cli # 安装 CLI(提供 agentia 命令)
16
+ agentia create my-app # 脚手架新项目(依赖 @migor/agentia)
17
+ cd my-app && npm install
18
+ agentia g tool weather # 生成 units/weather/index.ts 并登记 units.ts
19
+ agentia g subagent doc-reviewer # 生成 units/doc-reviewer/{index.ts,system.md}
20
+ agentia g skill note-writer # 生成 units/note-writer/index.ts
21
+ agentia g prompt style-guide # 生成 units/style-guide/{index.ts,asset.md}
22
+ npm run dev # 运行 src/main.ts
23
+ ```
24
+
25
+ **目录约定**:`units/<name>/` 一单元一文件夹,`index.ts` default export 一个 provider 类,DI token 缺省 = 文件夹名;长文本放文件夹内 `.md`,代码里用 `asset(import.meta.url, './system.md')` 读取。
26
+
27
+ **装配两条路(可混用)**:
28
+
29
+ ```ts
30
+ // 1) 目录扫描:启动期扫描 units/*/ 自动装配(返回 Promise)
31
+ const app = await createApp({ name: 'my-app', discover: 'units', system });
32
+
33
+ // 2) 显式注册表:units.ts 由 CLI 自动维护
34
+ import { providers } from './units.js';
35
+ const app = createApp({ name: 'my-app', providers, system });
36
+ ```
37
+
38
+ ## 手写单元
39
+
40
+ ```ts
41
+ import { Tool, SubAgent, createApp, SystemPrompt, RunContext } from '@migor/agentia';
42
+
43
+ class WeatherTools {
44
+ // 工具名缺省取方法名(建议 snake_case);入参 = 模型按 schema 解析的结构化 input
45
+ @Tool({
46
+ description: '查询城市天气',
47
+ schema: {
48
+ type: 'object',
49
+ properties: { city: { type: 'string' } },
50
+ required: ['city'],
51
+ additionalProperties: false,
52
+ },
53
+ strict: true,
54
+ })
55
+ get_weather(input: { city: string }): string {
56
+ // 方法体内随时可读本次 run 的上下文(blackboard)
57
+ const token = RunContext.current()?.get<string>('authToken');
58
+ return `city=${input.city};token=${token ?? 'none'}`;
59
+ }
60
+ }
61
+
62
+ class Pipeline {
63
+ // 子 agent:独立循环 + 裁剪上下文,只有最终报告回流主上下文(方法体不执行)
64
+ @SubAgent({
65
+ name: 'reviewer',
66
+ description: '审查给定文档并输出书面评审意见',
67
+ schema: { type: 'object', properties: { doc: { type: 'string' } }, required: ['doc'], additionalProperties: false },
68
+ system: '你是评审 agent。结论必须以“审查通过/不通过”开头。',
69
+ tools: ['weather'], // 子 agent 自己的工具菜单(provider token)
70
+ })
71
+ reviewer(_input: { doc: string }): void {}
72
+ }
73
+
74
+ const app = createApp({
75
+ name: 'weather-app',
76
+ providers: [
77
+ { provide: 'weather', useClass: WeatherTools },
78
+ { provide: 'pipeline', useClass: Pipeline },
79
+ ],
80
+ system: new SystemPrompt().add('role', '你是天气助手。', true),
81
+ });
82
+
83
+ const { run, result } = await app.run(
84
+ [{ role: 'user', content: '上海天气如何?' }],
85
+ { blackboard: { authToken: 'sk-...' } }, // 预置本次 run 的上下文
86
+ );
87
+ console.log(result.finalText); // 最终文本
88
+ console.log(result.trace); // 调用树 + 每步 token/成本(traceId == runId)
89
+ ```
90
+
91
+ 另外两类单元:
92
+
93
+ ```ts
94
+ // @Skill:代码控制的流程,模型调用只发生在显式 ctx.llm()
95
+ class Notes {
96
+ @Skill({ description: '按主题整理要点' })
97
+ async note_writer(input: { topic: string }, ctx: SkillContext): Promise<string> {
98
+ const r = await ctx.llm({ prompt: `就「${input.topic}」给出三个要点` });
99
+ return r.text; // 返回值即产物,以 tool_result 交回主 agent
100
+ }
101
+ }
102
+
103
+ // @Prompt:纯文本资产,模型判定需要时拉取进上下文
104
+ class Assets {
105
+ @Prompt({ description: '品牌基调文案规范' })
106
+ style_guide(): string {
107
+ return asset(import.meta.url, './asset.md'); // 从单元文件夹读 .md
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## 触发方式
113
+
114
+ 同一份应用,三种触发任选:
115
+
116
+ ```ts
117
+ // 同步 RPC
118
+ const { run, result } = await app.run(messages);
119
+
120
+ // 异步任务(幂等键去重,失败可重提,可落盘续跑)
121
+ const runner = new AsyncRunner(app, { store: new FileTaskStore('./tasks.jsonl'), concurrency: 4 });
122
+ const task = runner.submit('总结一下今天的新闻', { idempotencyKey: 'daily-2026-09-10' });
123
+ await runner.awaitTask(task.taskId);
124
+
125
+ // 定时
126
+ const scheduler = new Scheduler(runner);
127
+ scheduler.every(60_000, '巡检一次', { idempotencyKey: 'patrol' });
128
+ ```
129
+
130
+ ## 长上下文预算
131
+
132
+ ```ts
133
+ import { createBudgetPolicy } from '@migor/agentia';
134
+
135
+ const app = createApp({
136
+ // ...
137
+ contextPolicy: createBudgetPolicy({
138
+ budgetTokens: 60_000, // 超预算先丢旧工具对,仍超且有 summarize 才压缩
139
+ summarize: (text) => mySummarizer(text), // 可选,框架不替你调模型
140
+ }),
141
+ });
142
+ ```
143
+
144
+ ## 常用 API
145
+
146
+ | 导出 | 用途 |
147
+ |---|---|
148
+ | `createApp` / `discoverProviders` | 装配应用 / 扫描单元目录 |
149
+ | `Tool` / `Skill` / `SubAgent` / `Prompt` | 四类单元装饰器 |
150
+ | `asset` | 读单元文件夹内的文本资产 |
151
+ | `SystemPrompt` | 拼装系统提示(自动打缓存 breakpoint) |
152
+ | `RunContext.current()` | 取本次 run 的 blackboard / runId |
153
+ | `AsyncRunner` / `Scheduler` / `runSync` | 异步任务 / 定时 / 同步触发 |
154
+ | `FileTaskStore` / `InMemoryTaskStore` | 任务记录存储(重启续跑用 File) |
155
+ | `createBudgetPolicy` | 长上下文预算护栏 |
156
+ | `runAgent` / `executeRun` | 裸引擎入口(不走装配) |
157
+
158
+ 完整导出见 [`src/index.ts`](src/index.ts),设计规格见 [`docs/spec.md`](docs/spec.md)。
159
+
160
+ ## 本仓库脚本
161
+
162
+ ```bash
163
+ npm install
164
+ npm run build # 编译框架(dist/)
165
+ npm run typecheck # 类型检查
166
+ npm run smoke:turn7 # 端到端:CLI 脚手架 → 目录发现/注册表装配 → mock run
167
+ npm run live # 真机:runAgent + 工具往返(需 API key)
168
+ npm run live:skill # 真机:@Skill ctx.llm()(需 API key)
169
+ ```
170
+
171
+ > 注:`npm run dev`(tsx)前需先 `npm approve-scripts` 批准 esbuild/tsx 的 postinstall。
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Agentia —— 最小显式 DI 容器(spec §4/§10:标准装饰器 + 显式 DI,不用
3
+ * experimentalDecorators/emitDecoratorMetadata/reflect-metadata)。
4
+ *
5
+ * NestJS 风格 provider 三态:useValue / useClass / useFactory(+deps)。
6
+ * 依赖以字符串 token 显式声明,靠工厂解析期惰性求值;无构造器参数反射。
7
+ * 容器按 app 粒度单例缓存;run 作用域的黑板/上下文不经过 DI,而由
8
+ * RunContext(AsyncLocalStorage) 在 executeRun 内注入(见 run/context.ts)。
9
+ */
10
+ export type Token = string;
11
+ export interface ValueProvider<T = unknown> {
12
+ provide: Token;
13
+ useValue: T;
14
+ }
15
+ export interface ClassProvider<T = unknown> {
16
+ provide: Token;
17
+ useClass: new (...args: never[]) => T;
18
+ /** 构造器参数对应的依赖 token(按序注入);无依赖可省略 */
19
+ deps?: Token[];
20
+ }
21
+ export interface FactoryProvider<T = unknown> {
22
+ provide: Token;
23
+ useFactory: (...deps: unknown[]) => T;
24
+ /** 与 useFactory 形参一一对应的依赖 token */
25
+ deps?: Token[];
26
+ }
27
+ export type Provider<T = unknown> = ValueProvider<T> | ClassProvider<T> | FactoryProvider<T>;
28
+ export declare function isProvider(p: unknown): p is Provider;
29
+ export declare class Container {
30
+ private readonly bindings;
31
+ private readonly cache;
32
+ /** 正在解析的 token 链,用于循环依赖检测 */
33
+ private resolving;
34
+ /** 覆盖式注册(后注册覆盖先注册;同一 app 内同 token 重复注册视为升级) */
35
+ register(...providers: Provider[]): this;
36
+ has(token: Token): boolean;
37
+ resolve<T = unknown>(token: Token): T;
38
+ /** 调试辅助 */
39
+ registered(): Token[];
40
+ }
@@ -0,0 +1,66 @@
1
+ export function isProvider(p) {
2
+ return (typeof p === 'object' &&
3
+ p !== null &&
4
+ typeof p.provide === 'string' &&
5
+ ('useValue' in p ||
6
+ 'useClass' in p ||
7
+ 'useFactory' in p));
8
+ }
9
+ export class Container {
10
+ bindings = new Map();
11
+ cache = new Map();
12
+ /** 正在解析的 token 链,用于循环依赖检测 */
13
+ resolving = [];
14
+ /** 覆盖式注册(后注册覆盖先注册;同一 app 内同 token 重复注册视为升级) */
15
+ register(...providers) {
16
+ for (const p of providers) {
17
+ if (!isProvider(p)) {
18
+ throw new Error(`非法 provider: ${JSON.stringify(p)}`);
19
+ }
20
+ this.bindings.set(p.provide, p);
21
+ }
22
+ return this;
23
+ }
24
+ has(token) {
25
+ return this.bindings.has(token);
26
+ }
27
+ resolve(token) {
28
+ // 用 has 判定命中:provider 值可以是 undefined,get 返回 undefined 不等于未缓存
29
+ if (this.cache.has(token))
30
+ return this.cache.get(token);
31
+ const cycleAt = this.resolving.indexOf(token);
32
+ if (cycleAt !== -1) {
33
+ const chain = [...this.resolving.slice(cycleAt), token].join(' -> ');
34
+ throw new Error(`循环依赖(DI): ${chain}`);
35
+ }
36
+ const binding = this.bindings.get(token);
37
+ if (!binding) {
38
+ throw new Error(`未注册的 provider: "${token}"`);
39
+ }
40
+ this.resolving.push(token);
41
+ let value;
42
+ try {
43
+ if ('useValue' in binding) {
44
+ value = binding.useValue;
45
+ }
46
+ else if ('useClass' in binding) {
47
+ const deps = (binding.deps ?? []).map((d) => this.resolve(d));
48
+ value = new binding.useClass(...deps);
49
+ }
50
+ else {
51
+ const deps = (binding.deps ?? []).map((d) => this.resolve(d));
52
+ value = binding.useFactory(...deps);
53
+ }
54
+ }
55
+ finally {
56
+ this.resolving.pop();
57
+ }
58
+ this.cache.set(token, value);
59
+ return value;
60
+ }
61
+ /** 调试辅助 */
62
+ registered() {
63
+ return [...this.bindings.keys()];
64
+ }
65
+ }
66
+ //# sourceMappingURL=container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/container/container.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { JsonSchema } from './tool.js';
2
+ /**
3
+ * Agentia —— 最小 JSON Schema 校验子集(spec:v1 裸 JSON Schema,不接 zod)。
4
+ *
5
+ * 用途:engine 在执行 tool.run 前校验模型给出的结构化 input;校验失败直接回
6
+ * is_error 的 tool_result(错误信息含路径,模型可自我修正),不进方法体。
7
+ *
8
+ * 覆盖子集:type(object/array/string/number/integer/boolean/null)、
9
+ * properties、required、additionalProperties:false、enum、items。
10
+ * 未覆盖的关键字(format/minimum/oneOf 等)一律放行 —— 护栏不是完整校验器。
11
+ *
12
+ * @returns 人类可读的错误描述(含路径);合法返回 null。
13
+ */
14
+ export declare function validateJsonSchema(schema: JsonSchema, input: unknown): string | null;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Agentia —— 最小 JSON Schema 校验子集(spec:v1 裸 JSON Schema,不接 zod)。
3
+ *
4
+ * 用途:engine 在执行 tool.run 前校验模型给出的结构化 input;校验失败直接回
5
+ * is_error 的 tool_result(错误信息含路径,模型可自我修正),不进方法体。
6
+ *
7
+ * 覆盖子集:type(object/array/string/number/integer/boolean/null)、
8
+ * properties、required、additionalProperties:false、enum、items。
9
+ * 未覆盖的关键字(format/minimum/oneOf 等)一律放行 —— 护栏不是完整校验器。
10
+ *
11
+ * @returns 人类可读的错误描述(含路径);合法返回 null。
12
+ */
13
+ export function validateJsonSchema(schema, input) {
14
+ return check(schema, input, '$');
15
+ }
16
+ function check(schema, value, path) {
17
+ const en = schema.enum;
18
+ if (Array.isArray(en) && !en.some((v) => deepEqual(v, value))) {
19
+ return `${path}: 值不在 enum 允许范围内(得到 ${preview(value)})`;
20
+ }
21
+ switch (schema.type) {
22
+ case 'object':
23
+ return checkObject(schema, value, path);
24
+ case 'array':
25
+ return checkArray(schema, value, path);
26
+ case 'string':
27
+ return typeof value === 'string' ? null : `${path}: 期望 string,得到 ${typeOf(value)}`;
28
+ case 'number':
29
+ return typeof value === 'number' && Number.isFinite(value)
30
+ ? null
31
+ : `${path}: 期望 number,得到 ${typeOf(value)}`;
32
+ case 'integer':
33
+ return typeof value === 'number' && Number.isInteger(value)
34
+ ? null
35
+ : `${path}: 期望 integer,得到 ${typeOf(value)}`;
36
+ case 'boolean':
37
+ return typeof value === 'boolean' ? null : `${path}: 期望 boolean,得到 ${typeOf(value)}`;
38
+ case 'null':
39
+ return value === null ? null : `${path}: 期望 null,得到 ${typeOf(value)}`;
40
+ default:
41
+ return null; // 未声明/未覆盖的 type 放行
42
+ }
43
+ }
44
+ function checkObject(schema, value, path) {
45
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
46
+ return `${path}: 期望 object,得到 ${typeOf(value)}`;
47
+ }
48
+ const obj = value;
49
+ for (const key of schema.required ?? []) {
50
+ if (!(key in obj))
51
+ return `${path}: 缺少必需属性 "${key}"`;
52
+ }
53
+ const properties = schema.properties ?? {};
54
+ for (const [key, sub] of Object.entries(properties)) {
55
+ if (key in obj) {
56
+ const err = check(sub, obj[key], `${path}.${key}`);
57
+ if (err)
58
+ return err;
59
+ }
60
+ }
61
+ if (schema.additionalProperties === false) {
62
+ const extra = Object.keys(obj).filter((k) => !(k in properties));
63
+ if (extra.length > 0) {
64
+ return `${path}: 存在 schema 未声明的属性 ${extra.map((k) => `"${k}"`).join(', ')}`;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function checkArray(schema, value, path) {
70
+ if (!Array.isArray(value))
71
+ return `${path}: 期望 array,得到 ${typeOf(value)}`;
72
+ const items = schema.items;
73
+ if (items && typeof items === 'object' && !Array.isArray(items)) {
74
+ for (let i = 0; i < value.length; i++) {
75
+ const err = check(items, value[i], `${path}[${i}]`);
76
+ if (err)
77
+ return err;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function typeOf(value) {
83
+ if (value === null)
84
+ return 'null';
85
+ if (Array.isArray(value))
86
+ return 'array';
87
+ return typeof value;
88
+ }
89
+ function preview(value) {
90
+ const s = stringifySafe(value);
91
+ return s.length > 80 ? `${s.slice(0, 80)}…` : s;
92
+ }
93
+ function stringifySafe(x) {
94
+ try {
95
+ return JSON.stringify(x) ?? String(x);
96
+ }
97
+ catch {
98
+ return String(x);
99
+ }
100
+ }
101
+ function deepEqual(a, b) {
102
+ if (a === b)
103
+ return true;
104
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
105
+ return false;
106
+ if (Array.isArray(a) !== Array.isArray(b))
107
+ return false;
108
+ if (Array.isArray(a) && Array.isArray(b)) {
109
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
110
+ }
111
+ const ka = Object.keys(a);
112
+ const kb = Object.keys(b);
113
+ return (ka.length === kb.length &&
114
+ ka.every((k) => deepEqual(a[k], b[k])));
115
+ }
116
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB,EAAE,KAAc;IACnE,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,IAAI,GAAG,CAAC;YAAE,OAAO,GAAG,IAAI,aAAa,GAAG,GAAG,CAAC;IACvD,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC;YACnD,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,oBAAoB,KAAK,KAAK,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,GAAG,IAAI,sBAAsB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9E,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,MAAkB,EAAE,KAAc,EAAE,IAAY;IAClE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,IAAI,iBAAiB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAmB,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;YAClE,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,OAAO,KAAK,CAAC;AACtB,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,CAAU;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,CAAU,EAAE,CAAU;IACvC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC7F,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IACpC,OAAO,CACL,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QACvB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CACb,SAAS,CAAE,CAA6B,CAAC,CAAC,CAAC,EAAG,CAA6B,CAAC,CAAC,CAAC,CAAC,CAChF,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type Anthropic from '@anthropic-ai/sdk';
2
+ import type { SpanError, SpanId, SpanKind, SpanStatus, Trace, Usage } from './trace.js';
3
+ /**
4
+ * Agentia —— 工具定义。
5
+ * Turn 0:v1 用裸 JSON Schema,不引 zod;装饰器 → schema 在 Turn 2 接入。
6
+ * Turn 3:run 执行体可接收第二参 ctx(ToolRunContext),供子 agent/嵌套单元把
7
+ * 自己的 llm.turn 递归挂进当前 trace(unit span 的父由 engine 给定)。
8
+ */
9
+ /** JSON Schema 对象子集,供工具的 input_schema */
10
+ export interface JsonSchema {
11
+ type: string;
12
+ description?: string;
13
+ properties?: Record<string, JsonSchema>;
14
+ required?: string[];
15
+ additionalProperties?: boolean;
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * engine 的 TraceRecorder 面向子单元的最小结构面(core 不依赖 engine)。
20
+ * 需要开嵌套 span 的单元(如 @SubAgent)通过 ctx.recorder 记账,
21
+ * 其余工具可完全忽略它。
22
+ */
23
+ export interface RecorderBackend {
24
+ readonly traceId: string;
25
+ begin(kind: SpanKind, name: string, parentSpanId: SpanId | null): SpanId;
26
+ end(id: SpanId, patch?: {
27
+ status?: SpanStatus;
28
+ error?: SpanError;
29
+ usage?: Usage;
30
+ }): void;
31
+ event(id: SpanId, name: string, body: unknown): void;
32
+ setAttribute(id: SpanId, key: string, value: string | number | boolean): void;
33
+ snapshot(status: SpanStatus): Trace;
34
+ }
35
+ /**
36
+ * engine 在调用每个工具时注入的执行上下文(spec §9.2:当前 span 句柄随调用传播,
37
+ * 不用全局单例,保证并行工具调用父子关系准确)。
38
+ * - recorder:整条 run 共享的 recorder(新子单元/子 agent 的 span 写它下面);
39
+ * - parentSpanId:发起本次调用的上层 span —— 子单元 span 应挂它下面;
40
+ * - client:与主循环同一注入(子 agent 独立循环复用它)。
41
+ */
42
+ export interface ToolRunContext {
43
+ client: Anthropic;
44
+ recorder: RecorderBackend;
45
+ parentSpanId: SpanId;
46
+ }
47
+ export interface AgentTool<I = unknown, O = unknown> {
48
+ /** 模型可见的唯一名(建议 snake_case) */
49
+ name: string;
50
+ description: string;
51
+ inputSchema: JsonSchema;
52
+ /** 开启严格参数校验(schema 需 additionalProperties:false + required) */
53
+ strict?: boolean;
54
+ /**
55
+ * 执行体。入参 = 模型按 schema 解析的结构化 input;
56
+ * ctx 由 engine 注入(含 recorder/父 span),需要开嵌套 span 的单元(子 agent)用,
57
+ * 普通工具可忽略。抛错会被包成 is_error 的 tool_result 回给模型,不中断 run。
58
+ */
59
+ run: (input: I, ctx?: ToolRunContext) => Promise<O> | O;
60
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/core/tool.ts"],"names":[],"mappings":""}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Agentia —— Trace / 调用树 类型草稿。
3
+ * 设计见 docs/spec.md §9。一次 run == 一条 trace(v1 中 traceId == runId)。
4
+ */
5
+ export type TraceId = string;
6
+ export type SpanId = string;
7
+ /** span 层级:run=整次运行;unit=对单个单元(tool/skill/prompt/subagent)的调用;llm.turn=unit 内每次模型往返 */
8
+ export type SpanKind = 'run' | 'unit' | 'llm.turn' | 'internal';
9
+ export type UnitType = 'tool' | 'skill' | 'prompt' | 'subagent';
10
+ export interface Usage {
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ cacheReadTokens: number;
14
+ cacheCreationTokens: number;
15
+ /** 估算成本(美元),由 token × 单价算出,随模型表更新 */
16
+ costEstimate?: number;
17
+ }
18
+ export type SpanStatus = 'ok' | 'error';
19
+ export interface SpanError {
20
+ type: string;
21
+ message: string;
22
+ /** 是否可安全重试(429/5xx/网络 vs 400/400类) */
23
+ retryable: boolean;
24
+ }
25
+ /** 结构化事件(日志)。工具入参/出参默认截断 + 脱敏,完整内容 opt-in */
26
+ export interface SpanEvent {
27
+ time: number;
28
+ name: string;
29
+ body: unknown;
30
+ }
31
+ export interface Span {
32
+ spanId: SpanId;
33
+ traceId: TraceId;
34
+ parentSpanId: SpanId | null;
35
+ kind: SpanKind;
36
+ name: string;
37
+ startedAt: number;
38
+ endedAt?: number;
39
+ status: SpanStatus;
40
+ error?: SpanError;
41
+ /** unit / llm.turn 的聚合用量 */
42
+ usage?: Usage;
43
+ attributes: Record<string, string | number | boolean>;
44
+ events: SpanEvent[];
45
+ }
46
+ export interface Trace {
47
+ traceId: TraceId;
48
+ rootSpanId: SpanId;
49
+ spans: Span[];
50
+ status: SpanStatus;
51
+ totalUsage: Usage;
52
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Agentia —— Trace / 调用树 类型草稿。
3
+ * 设计见 docs/spec.md §9。一次 run == 一条 trace(v1 中 traceId == runId)。
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,47 @@
1
+ import type Anthropic from '@anthropic-ai/sdk';
2
+ /**
3
+ * Agentia —— 长上下文策略的纯函数层(spec §5/§6:compaction / context editing 分清)。
4
+ *
5
+ * 三策略不混:
6
+ * - **context editing**(`trimToolPairs`):清旧 tool_use→tool_result 对,不掉内容、不额外调模型;
7
+ * - **compaction**(`compactMessages`):把旧消息前缀做**服务端摘要**(摘要器由上层注入,可走模型),
8
+ * 只保留最近 N 条 + 一段摘要 —— 新的消息数组仍保证角色交替合法;
9
+ * - **客户端剪裁** = Turn 3 子 agent(另件)。
10
+ *
11
+ * 不在此自研 token 计数黑名单:`estimateMessages` 只是预算策略的默认启发式
12
+ * (CJK 感知估算,明确标注为估算),上层可注入真实 count_tokens 结果。
13
+ */
14
+ /**
15
+ * 默认估算(仅预算决策用,非精确记账):CJK 字符按 ~1.5 字/token,
16
+ * 其余(ASCII/拉丁)按 ~4 字符/token。纯字符/4 对中文系统性高估,
17
+ * 会让预算护栏过早触发压缩。
18
+ */
19
+ export declare function defaultEstimateTokens(text: string): number;
20
+ export declare function isToolResultMessage(msg: Anthropic.MessageParam): boolean;
21
+ /** 整组消息的估算 token(含每条 role 开销)。 */
22
+ export declare function estimateMessages(messages: Anthropic.MessageParam[], estimate?: (text: string) => number): number;
23
+ /** 把消息渲染成可喂给摘要器的纯文本(role: content)。 */
24
+ export declare function renderMessages(messages: Anthropic.MessageParam[]): string;
25
+ export interface TrimOptions {
26
+ /** 保留最近几对 tool exchange;缺省 1 */
27
+ keepRecent?: number;
28
+ }
29
+ /**
30
+ * context editing:丢弃旧的 tool_use→tool_result 对(超出 keepRecent 的),
31
+ * 保留最近 N 对以及所有非工具消息。逐对整体移除,保角色交替合法。
32
+ * 返回原数组引用(若无需裁剪)或新数组。
33
+ */
34
+ export declare function trimToolPairs(messages: Anthropic.MessageParam[], opts?: TrimOptions): Anthropic.MessageParam[];
35
+ export interface CompactOptions {
36
+ /** 保留的最近消息条数;缺省 20 */
37
+ keepRecent?: number;
38
+ /** 摘要器:输入被弃旧前缀的渲染文本,返回摘要 */
39
+ summarize: (historyText: string) => string | Promise<string>;
40
+ }
41
+ /**
42
+ * compaction:把 messages 里“除最近 keepRecent 条”的旧前缀做摘要,替换成一段
43
+ * 摘要消息,尾部保留。产出保证:
44
+ * - 不拆散 (assistant tool_use → user tool_result) 对 —— cut 落到对中间时整体后移;
45
+ * - 角色交替合法 —— 尾段首条是普通 user 时摘要并入之;否则摘要以 user 角色放最前。
46
+ */
47
+ export declare function compactMessages(messages: Anthropic.MessageParam[], opts: CompactOptions): Promise<Anthropic.MessageParam[]>;