@migor/agentia 0.2.0 → 0.2.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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +144 -0
  3. package/README.md +202 -76
  4. package/dist/AGENTS.md +811 -0
  5. package/dist/container/container.d.ts +10 -2
  6. package/dist/container/container.js +27 -3
  7. package/dist/container/container.js.map +1 -1
  8. package/dist/core/abort.d.ts +10 -0
  9. package/dist/core/abort.js +28 -0
  10. package/dist/core/abort.js.map +1 -0
  11. package/dist/core/blackboard.d.ts +37 -0
  12. package/dist/core/blackboard.js +2 -0
  13. package/dist/core/blackboard.js.map +1 -0
  14. package/dist/core/json.d.ts +2 -0
  15. package/dist/core/json.js +4 -0
  16. package/dist/core/json.js.map +1 -1
  17. package/dist/core/run.d.ts +18 -0
  18. package/dist/core/run.js +2 -0
  19. package/dist/core/run.js.map +1 -0
  20. package/dist/core/schema.js +2 -2
  21. package/dist/core/schema.js.map +1 -1
  22. package/dist/core/tool.d.ts +62 -7
  23. package/dist/core/trace.d.ts +20 -4
  24. package/dist/engine/budget.d.ts +49 -0
  25. package/dist/engine/budget.js +30 -0
  26. package/dist/engine/budget.js.map +1 -0
  27. package/dist/engine/concurrency.d.ts +43 -0
  28. package/dist/engine/concurrency.js +95 -0
  29. package/dist/engine/concurrency.js.map +1 -0
  30. package/dist/engine/errors.d.ts +2 -0
  31. package/dist/engine/errors.js +7 -0
  32. package/dist/engine/errors.js.map +1 -1
  33. package/dist/engine/loop.d.ts +35 -10
  34. package/dist/engine/loop.js +336 -66
  35. package/dist/engine/loop.js.map +1 -1
  36. package/dist/engine/policy.d.ts +9 -1
  37. package/dist/engine/policy.js +8 -5
  38. package/dist/engine/policy.js.map +1 -1
  39. package/dist/engine/replay.d.ts +10 -4
  40. package/dist/engine/replay.js +57 -27
  41. package/dist/engine/replay.js.map +1 -1
  42. package/dist/engine/retry.d.ts +47 -0
  43. package/dist/engine/retry.js +51 -0
  44. package/dist/engine/retry.js.map +1 -0
  45. package/dist/engine/spec.d.ts +61 -0
  46. package/dist/{run → engine}/spec.js +4 -1
  47. package/dist/engine/spec.js.map +1 -0
  48. package/dist/engine/tracer.d.ts +9 -0
  49. package/dist/engine/tracer.js +57 -1
  50. package/dist/engine/tracer.js.map +1 -1
  51. package/dist/engine/{context.d.ts → trimming.d.ts} +19 -20
  52. package/dist/engine/trimming.js +238 -0
  53. package/dist/engine/trimming.js.map +1 -0
  54. package/dist/engine/types.d.ts +109 -13
  55. package/dist/engine/types.js +9 -1
  56. package/dist/engine/types.js.map +1 -1
  57. package/dist/engine/usage.d.ts +28 -2
  58. package/dist/engine/usage.js +39 -6
  59. package/dist/engine/usage.js.map +1 -1
  60. package/dist/eval/defineEval.d.ts +73 -0
  61. package/dist/eval/defineEval.js +44 -0
  62. package/dist/eval/defineEval.js.map +1 -0
  63. package/dist/eval/scripted.d.ts +26 -0
  64. package/dist/eval/scripted.js +43 -0
  65. package/dist/eval/scripted.js.map +1 -0
  66. package/dist/index.d.ts +60 -35
  67. package/dist/index.js +38 -20
  68. package/dist/index.js.map +1 -1
  69. package/dist/integrations/anthropic.d.ts +53 -0
  70. package/dist/integrations/anthropic.js +24 -0
  71. package/dist/integrations/anthropic.js.map +1 -0
  72. package/dist/integrations/mcp.d.ts +79 -0
  73. package/dist/integrations/mcp.js +108 -0
  74. package/dist/integrations/mcp.js.map +1 -0
  75. package/dist/integrations/metrics.d.ts +131 -0
  76. package/dist/integrations/metrics.js +488 -0
  77. package/dist/integrations/metrics.js.map +1 -0
  78. package/dist/{run → integrations}/openai.d.ts +12 -5
  79. package/dist/integrations/openai.js +386 -0
  80. package/dist/integrations/openai.js.map +1 -0
  81. package/dist/integrations/otlp.js.map +1 -0
  82. package/dist/integrations/report.d.ts +78 -0
  83. package/dist/integrations/report.js +257 -0
  84. package/dist/integrations/report.js.map +1 -0
  85. package/dist/runtime/context.d.ts +25 -0
  86. package/dist/{run → runtime}/context.js +8 -1
  87. package/dist/runtime/context.js.map +1 -0
  88. package/dist/{run → runtime}/memory.d.ts +1 -1
  89. package/dist/{run → runtime}/memory.js +3 -1
  90. package/dist/runtime/memory.js.map +1 -0
  91. package/dist/{run → runtime}/run.d.ts +22 -5
  92. package/dist/runtime/run.js +218 -0
  93. package/dist/runtime/run.js.map +1 -0
  94. package/dist/runtime/session.d.ts +27 -0
  95. package/dist/runtime/session.js +16 -0
  96. package/dist/runtime/session.js.map +1 -0
  97. package/dist/{run → runtime}/spec.d.ts +16 -2
  98. package/dist/runtime/spec.js +28 -0
  99. package/dist/runtime/spec.js.map +1 -0
  100. package/dist/{run → runtime}/systemPrompt.d.ts +12 -0
  101. package/dist/{run → runtime}/systemPrompt.js +16 -3
  102. package/dist/runtime/systemPrompt.js.map +1 -0
  103. package/dist/{run → runtime}/types.js.map +1 -1
  104. package/dist/{run → store}/fsStore.d.ts +15 -0
  105. package/dist/{run → store}/fsStore.js +43 -1
  106. package/dist/store/fsStore.js.map +1 -0
  107. package/dist/{run → store}/redisStore.d.ts +23 -1
  108. package/dist/{run → store}/redisStore.js +33 -6
  109. package/dist/store/redisStore.js.map +1 -0
  110. package/dist/{run → store}/sqliteStore.js +22 -1
  111. package/dist/store/sqliteStore.js.map +1 -0
  112. package/dist/{run → store}/store.d.ts +25 -3
  113. package/dist/store/store.js +63 -0
  114. package/dist/store/store.js.map +1 -0
  115. package/dist/toolkit/asset.d.ts +7 -6
  116. package/dist/toolkit/asset.js +7 -6
  117. package/dist/toolkit/asset.js.map +1 -1
  118. package/dist/toolkit/collect.d.ts +20 -7
  119. package/dist/toolkit/collect.js +21 -7
  120. package/dist/toolkit/collect.js.map +1 -1
  121. package/dist/toolkit/discover.d.ts +21 -1
  122. package/dist/toolkit/discover.js +59 -12
  123. package/dist/toolkit/discover.js.map +1 -1
  124. package/dist/toolkit/middleware.d.ts +10 -10
  125. package/dist/toolkit/middleware.js +17 -4
  126. package/dist/toolkit/middleware.js.map +1 -1
  127. package/dist/toolkit/module.d.ts +100 -34
  128. package/dist/toolkit/module.js +112 -36
  129. package/dist/toolkit/module.js.map +1 -1
  130. package/dist/toolkit/prompt.d.ts +4 -6
  131. package/dist/toolkit/prompt.js +11 -10
  132. package/dist/toolkit/prompt.js.map +1 -1
  133. package/dist/toolkit/skill.d.ts +14 -16
  134. package/dist/toolkit/skill.js +32 -25
  135. package/dist/toolkit/skill.js.map +1 -1
  136. package/dist/toolkit/subagent.d.ts +10 -12
  137. package/dist/toolkit/subagent.js +23 -21
  138. package/dist/toolkit/subagent.js.map +1 -1
  139. package/dist/toolkit/tool.d.ts +19 -11
  140. package/dist/toolkit/tool.js +11 -8
  141. package/dist/toolkit/tool.js.map +1 -1
  142. package/dist/toolkit/zod.d.ts +4 -3
  143. package/dist/toolkit/zod.js +2 -1
  144. package/dist/toolkit/zod.js.map +1 -1
  145. package/dist/transport/async.d.ts +133 -0
  146. package/dist/transport/async.js +415 -0
  147. package/dist/transport/async.js.map +1 -0
  148. package/dist/transport/http.d.ts +146 -0
  149. package/dist/transport/http.js +387 -0
  150. package/dist/transport/http.js.map +1 -0
  151. package/dist/{run → transport}/scheduler.d.ts +9 -1
  152. package/dist/transport/scheduler.js +133 -0
  153. package/dist/transport/scheduler.js.map +1 -0
  154. package/dist/transport/sse.d.ts +43 -0
  155. package/dist/transport/sse.js +51 -0
  156. package/dist/transport/sse.js.map +1 -0
  157. package/dist/{run → transport}/transport.d.ts +1 -1
  158. package/dist/{run → transport}/transport.js +1 -1
  159. package/dist/transport/transport.js.map +1 -0
  160. package/package.json +24 -9
  161. package/dist/engine/context.js +0 -146
  162. package/dist/engine/context.js.map +0 -1
  163. package/dist/run/async.d.ts +0 -59
  164. package/dist/run/async.js +0 -185
  165. package/dist/run/async.js.map +0 -1
  166. package/dist/run/context.d.ts +0 -16
  167. package/dist/run/context.js.map +0 -1
  168. package/dist/run/fsStore.js.map +0 -1
  169. package/dist/run/http.d.ts +0 -47
  170. package/dist/run/http.js +0 -119
  171. package/dist/run/http.js.map +0 -1
  172. package/dist/run/memory.js.map +0 -1
  173. package/dist/run/openai.js +0 -181
  174. package/dist/run/openai.js.map +0 -1
  175. package/dist/run/otlp.js.map +0 -1
  176. package/dist/run/redisStore.js.map +0 -1
  177. package/dist/run/run.js +0 -129
  178. package/dist/run/run.js.map +0 -1
  179. package/dist/run/scheduler.js +0 -85
  180. package/dist/run/scheduler.js.map +0 -1
  181. package/dist/run/spec.js.map +0 -1
  182. package/dist/run/sqliteStore.js.map +0 -1
  183. package/dist/run/store.js +0 -28
  184. package/dist/run/store.js.map +0 -1
  185. package/dist/run/systemPrompt.js.map +0 -1
  186. package/dist/run/transport.js.map +0 -1
  187. /package/dist/{run → integrations}/otlp.d.ts +0 -0
  188. /package/dist/{run → integrations}/otlp.js +0 -0
  189. /package/dist/{run → runtime}/types.d.ts +0 -0
  190. /package/dist/{run → runtime}/types.js +0 -0
  191. /package/dist/{run → store}/sqliteStore.d.ts +0 -0
@@ -0,0 +1,387 @@
1
+ import { AsyncRunner } from './async.js';
2
+ import { sseWriter } from './sse.js';
3
+ import { normalizeMessages } from '../engine/spec.js';
4
+ /**
5
+ * 鉴权钩子可抛出的异常:显式携带 HTTP 状态与响应体。
6
+ * 钩子抛别的错误一律按 401 处理(设计 F4:与「工具抛错即 is_error」的既有风格一致)。
7
+ */
8
+ export class HttpException extends Error {
9
+ status;
10
+ body;
11
+ constructor(status, body, message) {
12
+ super(message ?? `HTTP ${status}`);
13
+ this.name = 'HttpException';
14
+ this.status = status;
15
+ this.body = body ?? { error: `HTTP ${status}` };
16
+ }
17
+ }
18
+ /** body 上限缺省值:1 MiB —— 本宿主只接 messages JSON,正常远小于此 */
19
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
20
+ /** POST /run 并发上限缺省值:32 —— 够单机跑满,又不至于让上游被单宿主打爆 */
21
+ const DEFAULT_MAX_CONCURRENT_RUNS = 32;
22
+ /** 503 建议重试间隔(秒):同步 run 通常秒级,给 1s 足够错峰 */
23
+ const RETRY_AFTER_SECONDS = '1';
24
+ function sendJson(res, status, body) {
25
+ const payload = JSON.stringify(body);
26
+ res.writeHead(status, {
27
+ 'content-type': 'application/json; charset=utf-8',
28
+ 'content-length': Buffer.byteLength(payload),
29
+ });
30
+ res.end(payload);
31
+ }
32
+ /** 405 统一响应(方法不符):文案与其余错误一致用中文,并带 Allow 头。 */
33
+ function methodNotAllowed(res, method, allowed) {
34
+ res.setHeader('allow', allowed);
35
+ sendJson(res, 405, { error: `方法 ${method} 不被允许,请用 ${allowed}` });
36
+ }
37
+ /** Prometheus 文本响应(G4):抓取端按 text/plain; version=0.0.4 解析 */
38
+ function sendPrometheus(res, body) {
39
+ res.writeHead(200, {
40
+ 'content-type': 'text/plain; version=0.0.4; charset=utf-8',
41
+ 'content-length': Buffer.byteLength(body),
42
+ 'cache-control': 'no-store',
43
+ });
44
+ res.end(body);
45
+ }
46
+ /** 把 app.run 的产物收成 HTTP 响应体(JSON 与 SSE 的 run.end 共用同一形状) */
47
+ function toHttpBody(out) {
48
+ return {
49
+ runId: out.run.runId,
50
+ status: out.run.status,
51
+ stopReason: out.result.stopReason,
52
+ finalText: out.result.finalText,
53
+ typed: out.result.typed,
54
+ trace: out.result.trace,
55
+ error: out.result.error,
56
+ };
57
+ }
58
+ /**
59
+ * 读取 body 全文。两道兜底(原实现两者皆无):
60
+ * - **maxBytes 上限**:超限立即停止累积并 resolve 成哨兵(调用方回 413)——
61
+ * 否则一个未鉴权的大 body 就能把宿主内存打满;
62
+ * - **close 兜底**:客户端中途断开时既不会有 `end` 也不会有 `error`(Node 发 `aborted`/`close`),
63
+ * Promise 永不 settle → 每个半截请求漏一个 handler 与一份 buffer。
64
+ */
65
+ function readBody(req, maxBytes) {
66
+ return new Promise((resolve) => {
67
+ const chunks = [];
68
+ let size = 0;
69
+ let done = false;
70
+ const cleanup = () => {
71
+ req.off('data', onData);
72
+ req.off('end', onEnd);
73
+ req.off('error', onAbort);
74
+ req.off('close', onAbort);
75
+ };
76
+ const finish = (r) => {
77
+ if (done)
78
+ return; // end 之后仍会来一次 close
79
+ done = true;
80
+ cleanup();
81
+ resolve(r);
82
+ };
83
+ const onData = (c) => {
84
+ size += c.length;
85
+ if (size > maxBytes) {
86
+ finish({ ok: false, reason: 'too-large' });
87
+ return;
88
+ }
89
+ chunks.push(c);
90
+ };
91
+ const onEnd = () => finish({ ok: true, raw: Buffer.concat(chunks).toString('utf8') });
92
+ const onAbort = () => finish({ ok: false, reason: 'aborted' });
93
+ req.on('data', onData);
94
+ req.on('end', onEnd);
95
+ req.on('error', onAbort);
96
+ req.on('close', onAbort);
97
+ });
98
+ }
99
+ function errMessage(e) {
100
+ return e instanceof Error ? e.message : String(e);
101
+ }
102
+ /**
103
+ * 轮询等待条件成立,直到 deadline(`Infinity` = 一直等)。返回是否等到了。
104
+ * 用于 drain 里等同步 run 收尾 —— 事件驱动没有「在飞数变 0」的回调,轮询足够。
105
+ */
106
+ async function waitUntil(cond, deadline) {
107
+ while (!cond()) {
108
+ if (Date.now() >= deadline)
109
+ return false;
110
+ await new Promise((r) => setTimeout(r, 5));
111
+ }
112
+ return true;
113
+ }
114
+ /**
115
+ * 鉴权未通过(B1)。`HttpException` 按其 status/body 回;其它错误回 401 通用文案,
116
+ * 原文只进日志(除非 exposeErrors)。
117
+ *
118
+ * **连接处理**:这里在读到 body 之前就回了响应,请求体没被消费 —— 连接不能复用,
119
+ * 否则残留字节会被当成下一个请求(与 413 同理)。故 `req.complete` 为假时显式
120
+ * `connection: close`,让客户端尽早停止上传(这也正是「省资源」的落点)。
121
+ */
122
+ function sendUnauthorized(req, res, e, exposeErrors) {
123
+ // 在读到 body 之前就回了响应:请求体没被消费 → 连接不能复用
124
+ // (残留字节会被当成下一个请求,与 413 同理)。这也正是「不收 body 省资源」的落点。
125
+ if (!req.complete)
126
+ res.setHeader('connection', 'close');
127
+ if (e instanceof HttpException) {
128
+ sendJson(res, e.status, e.body);
129
+ return;
130
+ }
131
+ if (exposeErrors) {
132
+ sendJson(res, 401, { error: errMessage(e) });
133
+ return;
134
+ }
135
+ console.error('[agentia:http] 鉴权钩子异常:', e);
136
+ sendJson(res, 401, { error: '未通过鉴权' });
137
+ }
138
+ /** 503:停机中不再接单(在读 body 之前就拒,省一次传输) */
139
+ function sendShuttingDown(req, res) {
140
+ // 同 sendUnauthorized:body 未消费 → 连接不可复用
141
+ if (!req.complete)
142
+ res.setHeader('connection', 'close');
143
+ res.setHeader('retry-after', RETRY_AFTER_SECONDS);
144
+ sendJson(res, 503, { error: '服务正在优雅停机,不再接受新任务' });
145
+ }
146
+ export function createHttpHandler(app, opts = {}) {
147
+ const runner = opts.runner ?? new AsyncRunner(app);
148
+ const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
149
+ const maxConcurrentRuns = opts.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
150
+ const exposeErrors = opts.exposeErrors ?? false;
151
+ const sseMaxBufferedBytes = opts.sseMaxBufferedBytes;
152
+ const authenticate = opts.authenticate;
153
+ const metricsProvider = opts.metrics;
154
+ const startedAt = Date.now();
155
+ let inFlightRuns = 0;
156
+ let draining = false;
157
+ /** 仍开着的 SSE 流的收口函数 —— drain 时强制 close,否则长连会把进程吊住 */
158
+ const openSse = new Set();
159
+ const sendInternalError = (res, e) => {
160
+ if (exposeErrors) {
161
+ sendJson(res, 500, { error: errMessage(e) });
162
+ return;
163
+ }
164
+ // 细节只进服务端日志,响应里不回内部拓扑
165
+ console.error('[agentia:http] 请求处理异常:', e);
166
+ sendJson(res, 500, { error: '内部错误' });
167
+ };
168
+ /** 优雅停机(B2):拒新单 → 等异步任务与在飞同步 run → 强制收口 SSE。 */
169
+ const drain = async (drainOpts = {}) => {
170
+ draining = true; // 先拒新单,再等存量
171
+ const timeoutMs = drainOpts.timeoutMs ?? 0;
172
+ const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;
173
+ const remaining = () => deadline === Number.POSITIVE_INFINITY ? 0 : Math.max(0, deadline - Date.now());
174
+ // 异步任务:排在 waitQueue 里的也一并等(它们的记录在 store 里,超时未跑完则下次启动续跑)
175
+ const tasksDrained = await runner.drain(deadline === Number.POSITIVE_INFINITY ? {} : { timeoutMs: remaining() });
176
+ // 同步 /run(含 SSE 流)也在 inFlightRuns 里计数 —— 同样给到 deadline
177
+ const runsDrained = inFlightRuns === 0 || (await waitUntil(() => inFlightRuns === 0, deadline));
178
+ // 收口:超时仍挂着的 SSE 流强制关闭(其 run 因 res 'close' 中止,stopReason='aborted')
179
+ for (const close of [...openSse])
180
+ close();
181
+ return tasksDrained && runsDrained;
182
+ };
183
+ const handler = async (req, res) => {
184
+ const method = req.method ?? 'GET';
185
+ const pathname = (req.url ?? '/').split('?')[0];
186
+ try {
187
+ // 健康检查:不鉴权(探针带不了凭据)、停机中也回(探针要先能问到才有意义)
188
+ if (pathname === '/healthz') {
189
+ if (method !== 'GET') {
190
+ methodNotAllowed(res, method, 'GET');
191
+ return;
192
+ }
193
+ sendJson(res, 200, {
194
+ ok: true,
195
+ inFlight: inFlightRuns + runner.inFlight,
196
+ uptimeMs: Date.now() - startedAt,
197
+ draining,
198
+ });
199
+ return;
200
+ }
201
+ // 指标:与 /healthz 同档(不鉴权、停机中仍可拉 —— 抓取端在集群内网)
202
+ if (pathname === '/metrics' && metricsProvider) {
203
+ if (method !== 'GET') {
204
+ methodNotAllowed(res, method, 'GET');
205
+ return;
206
+ }
207
+ sendPrometheus(res, typeof metricsProvider === 'function' ? metricsProvider() : metricsProvider.render());
208
+ return;
209
+ }
210
+ // 鉴权缝:必须在读 body 之前(未通过即断开,body 一个字节都不收)
211
+ if (authenticate) {
212
+ try {
213
+ await authenticate(req);
214
+ }
215
+ catch (e) {
216
+ sendUnauthorized(req, res, e, exposeErrors);
217
+ return;
218
+ }
219
+ }
220
+ if (pathname === '/run') {
221
+ if (method !== 'POST') {
222
+ methodNotAllowed(res, method, 'POST');
223
+ return;
224
+ }
225
+ if (draining) {
226
+ sendShuttingDown(req, res);
227
+ return;
228
+ }
229
+ const input = await parseJsonBody(req, res, maxBodyBytes);
230
+ if (input === PARSE_FAILED)
231
+ return;
232
+ let messages;
233
+ try {
234
+ messages = normalizeMessages(input);
235
+ }
236
+ catch (e) {
237
+ sendJson(res, 400, { error: errMessage(e) });
238
+ return;
239
+ }
240
+ // 并发闸门:body 已读完(连接可复用),只是暂时不给跑 —— 回 503 让调用方退避重试
241
+ if (inFlightRuns >= maxConcurrentRuns) {
242
+ res.setHeader('retry-after', RETRY_AFTER_SECONDS);
243
+ sendJson(res, 503, {
244
+ error: `并发 run 已达上限 ${maxConcurrentRuns},请稍后重试`,
245
+ });
246
+ return;
247
+ }
248
+ inFlightRuns++;
249
+ // 客户端中途断开 → 中止本次 run(省 token)。res 'close' 正常结束也会触发,
250
+ // 故以 writableEnded 区分:只有响应还没写完才算「断开」。
251
+ const runAc = new AbortController();
252
+ const onClose = () => {
253
+ if (!res.writableEnded)
254
+ runAc.abort();
255
+ };
256
+ res.once('close', onClose);
257
+ // 内容协商:`Accept: text/event-stream` → SSE 逐帧下发;否则一元 JSON(旧行为逐字不变)
258
+ const wantsSse = String(req.headers.accept ?? '').includes('text/event-stream');
259
+ try {
260
+ if (wantsSse) {
261
+ const sse = sseWriter(res, {
262
+ maxBufferedBytes: sseMaxBufferedBytes,
263
+ // 下游积压超限 → 收口并中止本次 run(见 sseWriter 的背压说明)
264
+ onBackpressure: () => runAc.abort(),
265
+ });
266
+ const heartbeat = setInterval(() => sse.comment('ping'), 15_000);
267
+ heartbeat.unref?.();
268
+ // 登记收口函数:drain 时强制关闭(SSE 是长连,不关会把进程吊住)
269
+ const closeSse = () => {
270
+ clearInterval(heartbeat);
271
+ sse.close();
272
+ };
273
+ openSse.add(closeSse);
274
+ try {
275
+ // rethrow:false —— 与 AsyncRunner 对齐:硬失败也以 run.end 下发(status/error 字段)
276
+ const out = await app.run(messages, {
277
+ rethrow: false,
278
+ signal: runAc.signal,
279
+ onText: (delta) => sse.event('text.delta', { text: delta }),
280
+ });
281
+ sse.event('run.end', toHttpBody(out));
282
+ }
283
+ catch (e) {
284
+ // 流已开(200 与头已发出)→ 只能以 error 事件收尾,不能再改 HTTP 状态码
285
+ sse.event('error', { message: errMessage(e) });
286
+ }
287
+ finally {
288
+ openSse.delete(closeSse);
289
+ closeSse();
290
+ }
291
+ return;
292
+ }
293
+ // rethrow:false —— 与 AsyncRunner 对齐:硬失败也以 status/error 字段返回 200
294
+ const out = await app.run(messages, { rethrow: false, signal: runAc.signal });
295
+ sendJson(res, 200, toHttpBody(out));
296
+ }
297
+ finally {
298
+ res.off('close', onClose);
299
+ inFlightRuns--;
300
+ }
301
+ return;
302
+ }
303
+ if (pathname === '/tasks') {
304
+ if (method !== 'POST') {
305
+ methodNotAllowed(res, method, 'POST');
306
+ return;
307
+ }
308
+ // 停机中不再接单(含直接 drain 了 runner 的情况);GET /tasks/<id> 不受影响
309
+ if (draining || runner.isDraining) {
310
+ sendShuttingDown(req, res);
311
+ return;
312
+ }
313
+ const body = await parseJsonBody(req, res, maxBodyBytes);
314
+ if (body === PARSE_FAILED)
315
+ return;
316
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
317
+ sendJson(res, 400, { error: 'body 需为 { input, idempotencyKey?, options? }' });
318
+ return;
319
+ }
320
+ const submitBody = body;
321
+ let rec;
322
+ try {
323
+ rec = runner.submit(submitBody.input, {
324
+ idempotencyKey: submitBody.idempotencyKey,
325
+ options: submitBody.options,
326
+ source: 'http',
327
+ });
328
+ }
329
+ catch (e) {
330
+ sendJson(res, 400, { error: errMessage(e) });
331
+ return;
332
+ }
333
+ sendJson(res, 202, rec);
334
+ return;
335
+ }
336
+ if (pathname.startsWith('/tasks/')) {
337
+ if (method !== 'GET') {
338
+ methodNotAllowed(res, method, 'GET');
339
+ return;
340
+ }
341
+ let taskId;
342
+ try {
343
+ taskId = decodeURIComponent(pathname.slice('/tasks/'.length));
344
+ }
345
+ catch {
346
+ // 残缺的 % 转义会抛 URIError —— 是调用方的输入问题(400),不是服务端 500
347
+ sendJson(res, 400, { error: 'taskId 不是合法的 URL 编码' });
348
+ return;
349
+ }
350
+ const rec = await runner.poll(taskId); // MaybePromise:异步 store 下必须 await
351
+ if (!rec) {
352
+ sendJson(res, 404, { error: `task 不存在: ${taskId}` });
353
+ return;
354
+ }
355
+ sendJson(res, 200, rec);
356
+ return;
357
+ }
358
+ sendJson(res, 404, { error: `路径不存在: ${pathname}` });
359
+ }
360
+ catch (e) {
361
+ sendInternalError(res, e);
362
+ }
363
+ };
364
+ // 把停机控制面挂在 handler 上(不破坏 (req, res) 的调用形状)
365
+ return Object.assign(handler, { drain, runner });
366
+ }
367
+ const PARSE_FAILED = Symbol('parse-failed');
368
+ /** 读取并解析 JSON body;失败时直接回错误响应并返回哨兵(连接已断则无响应可回)。 */
369
+ async function parseJsonBody(req, res, maxBytes) {
370
+ const body = await readBody(req, maxBytes);
371
+ if (!body.ok) {
372
+ if (body.reason === 'too-large') {
373
+ // body 未读完就回响应:连接不能复用,否则残留字节会被当成下一个请求
374
+ res.setHeader('connection', 'close');
375
+ sendJson(res, 413, { error: `请求 body 超过上限 ${maxBytes} 字节` });
376
+ }
377
+ return PARSE_FAILED; // aborted:客户端已走,写了也没人收
378
+ }
379
+ try {
380
+ return body.raw ? JSON.parse(body.raw) : undefined;
381
+ }
382
+ catch {
383
+ sendJson(res, 400, { error: '请求 body 不是合法 JSON' });
384
+ return PARSE_FAILED;
385
+ }
386
+ }
387
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { AsyncRunner } from './async.js';
2
- import type { RunInvocationOptions } from './spec.js';
2
+ import type { RunInvocationOptions } from '../engine/spec.js';
3
3
  export interface ScheduleEveryOptions {
4
4
  /** 任务级调用参数 */
5
5
  options?: RunInvocationOptions;
@@ -7,6 +7,12 @@ export interface ScheduleEveryOptions {
7
7
  idempotencyPrefix?: string;
8
8
  /** 覆盖触发来源标记 */
9
9
  source?: string;
10
+ /**
11
+ * 同时未终态的任务数上限;缺省 1(上一片没跑完就跳过本次 tick)。
12
+ * 没有它时 `every(1000)` + 60s 任务会每个 tick 都 submit 一次,任务无上限堆积。
13
+ * 传 `Infinity` 关闭闸门(旧行为)。'at' 单发天然不受影响。
14
+ */
15
+ maxInFlight?: number;
10
16
  }
11
17
  export interface ScheduleHandle {
12
18
  id: string;
@@ -23,5 +29,7 @@ export declare class Scheduler {
23
29
  cancel(id: string): void;
24
30
  stop(): void;
25
31
  get active(): number;
32
+ /** 清掉已到终态的追踪项(异步 store 下 poll 返回 Promise,到货后再清) */
33
+ private pruneInFlight;
26
34
  private dispatch;
27
35
  }
@@ -0,0 +1,133 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isThenable } from '../store/store.js';
3
+ export class Scheduler {
4
+ runner;
5
+ jobs = new Map();
6
+ constructor(runner) {
7
+ this.runner = runner;
8
+ }
9
+ /** 周期触发(毫秒间隔)。首次触发在 intervalMs 之后。 */
10
+ every(intervalMs, input, opts = {}) {
11
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
12
+ // setInterval(0) 会退化成「尽快重复」的空转循环,把事件循环打满(Node 会把
13
+ // 0 钳到 1ms 但仍是每毫秒一次的忙轮询)。这是配置错误,直接报错。
14
+ throw new Error(`Scheduler.every 的 intervalMs 必须为正有限数,收到 ${intervalMs}`);
15
+ }
16
+ const id = randomUUID();
17
+ const timer = setInterval(() => {
18
+ const job = this.jobs.get(id);
19
+ if (job)
20
+ this.dispatch(job);
21
+ }, intervalMs);
22
+ if (typeof timer === 'object' && 'unref' in timer)
23
+ timer.unref?.();
24
+ this.jobs.set(id, {
25
+ id,
26
+ kind: 'every',
27
+ timer,
28
+ intervalMs,
29
+ input,
30
+ options: opts.options,
31
+ source: opts.source,
32
+ prefix: opts.idempotencyPrefix,
33
+ inFlight: new Set(),
34
+ maxInFlight: opts.maxInFlight ?? 1,
35
+ });
36
+ return { id, cancel: () => this.cancel(id) };
37
+ }
38
+ /** 单次触发(指定时刻)。 */
39
+ at(when, input, opts = {}) {
40
+ if (!(when instanceof Date) || !Number.isFinite(when.getTime())) {
41
+ // 非法日期 → delay 为 NaN → setTimeout(fn, NaN) 会立即触发且无任何提示
42
+ throw new Error(`Scheduler.at 需要一个合法 Date(非 Invalid Date),收到 ${String(when)}`);
43
+ }
44
+ const id = randomUUID();
45
+ const delay = Math.max(0, when.getTime() - Date.now());
46
+ const timer = setTimeout(() => {
47
+ const job = this.jobs.get(id);
48
+ this.jobs.delete(id);
49
+ if (job)
50
+ this.dispatch(job);
51
+ }, delay);
52
+ // 与 every() 一致:定时器不阻止进程退出(宿主 stop() 仍可显式取消)
53
+ if (typeof timer === 'object' && 'unref' in timer)
54
+ timer.unref?.();
55
+ this.jobs.set(id, {
56
+ id,
57
+ kind: 'at',
58
+ timer,
59
+ input,
60
+ options: opts.options,
61
+ source: opts.source,
62
+ prefix: opts.idempotencyPrefix,
63
+ });
64
+ return { id, cancel: () => this.cancel(id) };
65
+ }
66
+ cancel(id) {
67
+ const job = this.jobs.get(id);
68
+ if (!job)
69
+ return;
70
+ clearTimeout(job.timer);
71
+ this.jobs.delete(id);
72
+ }
73
+ stop() {
74
+ for (const id of [...this.jobs.keys()])
75
+ this.cancel(id);
76
+ }
77
+ get active() {
78
+ return this.jobs.size;
79
+ }
80
+ /** 清掉已到终态的追踪项(异步 store 下 poll 返回 Promise,到货后再清) */
81
+ pruneInFlight(inFlight) {
82
+ const forget = (taskId) => inFlight.delete(taskId);
83
+ for (const taskId of inFlight) {
84
+ const rec = this.runner.poll(taskId);
85
+ if (isThenable(rec)) {
86
+ void rec
87
+ .then((r) => {
88
+ if (!r || r.status === 'succeeded' || r.status === 'failed')
89
+ forget(taskId);
90
+ })
91
+ .catch(() => forget(taskId)); // 查不到就别再挡住后续 tick
92
+ continue;
93
+ }
94
+ const r = rec;
95
+ if (!r || r.status === 'succeeded' || r.status === 'failed')
96
+ forget(taskId);
97
+ }
98
+ }
99
+ dispatch(job) {
100
+ // maxInFlight 闸门(仅周期任务):上一片还在跑就跳过本次 tick —— 否则
101
+ // every(1000) + 60s 任务会每个 tick 都派发,任务无上限堆积。
102
+ // Infinity = 闸门关闭:也不追踪(否则 inFlight 集合随 tick 无界增长)。
103
+ const maxInFlight = job.maxInFlight ?? 1;
104
+ const track = maxInFlight === Number.POSITIVE_INFINITY ? undefined : job.inFlight;
105
+ if (track) {
106
+ this.pruneInFlight(track);
107
+ if (track.size >= maxInFlight)
108
+ return;
109
+ }
110
+ // 'at' 单发:有 prefix 则用固定键(同次重复提交去重);周期任务用窗口键
111
+ const idempotencyKey = job.prefix
112
+ ? job.kind === 'every'
113
+ ? `${job.prefix}:${Math.floor(Date.now() / (job.intervalMs ?? 1))}`
114
+ : job.prefix
115
+ : undefined;
116
+ try {
117
+ const rec = this.runner.submit(job.input, {
118
+ idempotencyKey,
119
+ options: job.options,
120
+ source: job.source ?? `schedule:${job.id.slice(0, 8)}`,
121
+ });
122
+ // 追踪未终态的派发(去重命中已有终态记录时不计入)
123
+ if (track && (rec.status === 'queued' || rec.status === 'running')) {
124
+ track.add(rec.taskId);
125
+ }
126
+ }
127
+ catch (e) {
128
+ // 调度触发不应崩掉宿主进程:入参非法等以 console 形式暴露
129
+ console.error(`[agentia:scheduler] ${job.id} 触发失败`, e?.message ?? e);
130
+ }
131
+ }
132
+ }
133
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/transport/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ /**
3
+ * Agentia —— SSE(Server-Sent Events)写出器(零依赖)。
4
+ *
5
+ * 帧格式:`event: <name>\ndata: <json>\n\n`;注释帧 `: <text>\n\n` 用作心跳
6
+ * (长时间无数据时中间代理会掐连接)。
7
+ *
8
+ * 语义约束(调用方必须知道):**一旦写出响应头,HTTP 状态码就定了**(200)。
9
+ * 之后的错误只能以 `error` 事件表达,不能再改成 4xx/5xx —— 所以流开之前的
10
+ * 校验(方法 / 并发闸门 / body 解析)必须先做,那些用普通 HTTP 状态码回。
11
+ *
12
+ * **背压**:`res.write()` 在 socket 发送缓冲写满时返回 `false`,此时 Node 仍会把
13
+ * 后续数据留在内存里排队 —— 一个「连得上但不读」的客户端能凭一条 SSE 连接把宿主
14
+ * 内存吃光(逐 token 下发时尤其快)。所以这里盯着 `res.writableLength`,超过
15
+ * `maxBufferedBytes` 即收口:不再无限缓冲,并回调 `onBackpressure`(宿主据此中止
16
+ * 对应 run,别继续为空耗 token)。做的是**有界缓冲 + 超限收口**,不是静默丢帧 ——
17
+ * 丢帧会让客户端拿到看起来正常、实则残缺的输出。
18
+ */
19
+ export interface SseWriter {
20
+ /** 写一个事件帧(data 走 JSON.stringify);已关闭或积压超限时为 no-op */
21
+ event(name: string, data: unknown): void;
22
+ /** 写一个注释帧(心跳) */
23
+ comment(text: string): void;
24
+ /** 结束响应;之后再 event/comment 都是 no-op */
25
+ close(): void;
26
+ /** 响应是否已关闭 */
27
+ readonly closed: boolean;
28
+ }
29
+ export interface SseWriterOptions {
30
+ /**
31
+ * 下游积压上限(字节),按 `res.writableLength` 计;缺省 8 MiB。
32
+ * 正常客户端远达不到,实际只拦「连上但不读」的病态消费者。
33
+ */
34
+ maxBufferedBytes?: number;
35
+ /** 因积压超限而收口时回调(收口**先于**回调发生,回调抛错不影响收口) */
36
+ onBackpressure?: (info: {
37
+ bufferedBytes: number;
38
+ limitBytes: number;
39
+ }) => void;
40
+ }
41
+ /** 缺省积压上限:8 MiB —— 一条 8 MiB 都没被读走的流,已经不是「慢」而是「断了」 */
42
+ export declare const SSE_DEFAULT_MAX_BUFFERED_BYTES: number;
43
+ export declare function sseWriter(res: ServerResponse, opts?: SseWriterOptions): SseWriter;