@migor/agentia 0.2.1 → 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.
- package/LICENSE +21 -0
- package/README.en.md +144 -0
- package/README.md +202 -76
- package/dist/AGENTS.md +811 -0
- package/dist/container/container.d.ts +10 -2
- package/dist/container/container.js +27 -3
- package/dist/container/container.js.map +1 -1
- package/dist/core/abort.d.ts +10 -0
- package/dist/core/abort.js +28 -0
- package/dist/core/abort.js.map +1 -0
- package/dist/core/blackboard.d.ts +37 -0
- package/dist/core/blackboard.js +2 -0
- package/dist/core/blackboard.js.map +1 -0
- package/dist/core/json.d.ts +2 -0
- package/dist/core/json.js +4 -0
- package/dist/core/json.js.map +1 -1
- package/dist/core/run.d.ts +18 -0
- package/dist/core/run.js +2 -0
- package/dist/core/run.js.map +1 -0
- package/dist/core/schema.js +2 -2
- package/dist/core/schema.js.map +1 -1
- package/dist/core/tool.d.ts +62 -7
- package/dist/core/trace.d.ts +20 -4
- package/dist/engine/budget.d.ts +49 -0
- package/dist/engine/budget.js +30 -0
- package/dist/engine/budget.js.map +1 -0
- package/dist/engine/concurrency.d.ts +43 -0
- package/dist/engine/concurrency.js +95 -0
- package/dist/engine/concurrency.js.map +1 -0
- package/dist/engine/errors.d.ts +2 -0
- package/dist/engine/errors.js +7 -0
- package/dist/engine/errors.js.map +1 -1
- package/dist/engine/loop.d.ts +35 -10
- package/dist/engine/loop.js +336 -66
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/policy.d.ts +9 -1
- package/dist/engine/policy.js +8 -5
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/replay.d.ts +8 -5
- package/dist/engine/replay.js +30 -29
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/retry.d.ts +47 -0
- package/dist/engine/retry.js +51 -0
- package/dist/engine/retry.js.map +1 -0
- package/dist/engine/spec.d.ts +61 -0
- package/dist/{run → engine}/spec.js +4 -1
- package/dist/engine/spec.js.map +1 -0
- package/dist/engine/tracer.d.ts +9 -0
- package/dist/engine/tracer.js +57 -1
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/{context.d.ts → trimming.d.ts} +19 -20
- package/dist/engine/trimming.js +238 -0
- package/dist/engine/trimming.js.map +1 -0
- package/dist/engine/types.d.ts +109 -13
- package/dist/engine/types.js +9 -1
- package/dist/engine/types.js.map +1 -1
- package/dist/engine/usage.d.ts +28 -2
- package/dist/engine/usage.js +39 -6
- package/dist/engine/usage.js.map +1 -1
- package/dist/eval/defineEval.d.ts +73 -0
- package/dist/eval/defineEval.js +44 -0
- package/dist/eval/defineEval.js.map +1 -0
- package/dist/eval/scripted.d.ts +26 -0
- package/dist/eval/scripted.js +43 -0
- package/dist/eval/scripted.js.map +1 -0
- package/dist/index.d.ts +60 -35
- package/dist/index.js +38 -20
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.d.ts +53 -0
- package/dist/integrations/anthropic.js +24 -0
- package/dist/integrations/anthropic.js.map +1 -0
- package/dist/integrations/mcp.d.ts +79 -0
- package/dist/integrations/mcp.js +108 -0
- package/dist/integrations/mcp.js.map +1 -0
- package/dist/integrations/metrics.d.ts +131 -0
- package/dist/integrations/metrics.js +488 -0
- package/dist/integrations/metrics.js.map +1 -0
- package/dist/{run → integrations}/openai.d.ts +12 -5
- package/dist/integrations/openai.js +386 -0
- package/dist/integrations/openai.js.map +1 -0
- package/dist/integrations/otlp.js.map +1 -0
- package/dist/integrations/report.d.ts +78 -0
- package/dist/integrations/report.js +257 -0
- package/dist/integrations/report.js.map +1 -0
- package/dist/runtime/context.d.ts +25 -0
- package/dist/{run → runtime}/context.js +8 -1
- package/dist/runtime/context.js.map +1 -0
- package/dist/{run → runtime}/memory.d.ts +1 -1
- package/dist/{run → runtime}/memory.js +3 -1
- package/dist/runtime/memory.js.map +1 -0
- package/dist/{run → runtime}/run.d.ts +22 -5
- package/dist/runtime/run.js +218 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/session.d.ts +27 -0
- package/dist/runtime/session.js +16 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/{run → runtime}/spec.d.ts +16 -2
- package/dist/runtime/spec.js +28 -0
- package/dist/runtime/spec.js.map +1 -0
- package/dist/{run → runtime}/systemPrompt.d.ts +12 -0
- package/dist/{run → runtime}/systemPrompt.js +16 -3
- package/dist/runtime/systemPrompt.js.map +1 -0
- package/dist/{run → runtime}/types.js.map +1 -1
- package/dist/{run → store}/fsStore.d.ts +15 -0
- package/dist/{run → store}/fsStore.js +43 -1
- package/dist/store/fsStore.js.map +1 -0
- package/dist/{run → store}/redisStore.d.ts +19 -1
- package/dist/{run → store}/redisStore.js +33 -6
- package/dist/store/redisStore.js.map +1 -0
- package/dist/{run → store}/sqliteStore.js +22 -1
- package/dist/store/sqliteStore.js.map +1 -0
- package/dist/{run → store}/store.d.ts +25 -3
- package/dist/store/store.js +63 -0
- package/dist/store/store.js.map +1 -0
- package/dist/toolkit/asset.d.ts +7 -6
- package/dist/toolkit/asset.js +7 -6
- package/dist/toolkit/asset.js.map +1 -1
- package/dist/toolkit/collect.d.ts +20 -7
- package/dist/toolkit/collect.js +21 -7
- package/dist/toolkit/collect.js.map +1 -1
- package/dist/toolkit/discover.d.ts +21 -1
- package/dist/toolkit/discover.js +59 -12
- package/dist/toolkit/discover.js.map +1 -1
- package/dist/toolkit/middleware.d.ts +10 -10
- package/dist/toolkit/middleware.js +17 -4
- package/dist/toolkit/middleware.js.map +1 -1
- package/dist/toolkit/module.d.ts +100 -34
- package/dist/toolkit/module.js +112 -36
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.d.ts +4 -6
- package/dist/toolkit/prompt.js +11 -10
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/toolkit/skill.d.ts +14 -16
- package/dist/toolkit/skill.js +32 -25
- package/dist/toolkit/skill.js.map +1 -1
- package/dist/toolkit/subagent.d.ts +10 -12
- package/dist/toolkit/subagent.js +23 -21
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/toolkit/tool.d.ts +19 -11
- package/dist/toolkit/tool.js +11 -8
- package/dist/toolkit/tool.js.map +1 -1
- package/dist/toolkit/zod.d.ts +4 -3
- package/dist/toolkit/zod.js +2 -1
- package/dist/toolkit/zod.js.map +1 -1
- package/dist/transport/async.d.ts +133 -0
- package/dist/transport/async.js +415 -0
- package/dist/transport/async.js.map +1 -0
- package/dist/transport/http.d.ts +146 -0
- package/dist/transport/http.js +387 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/{run → transport}/scheduler.d.ts +9 -1
- package/dist/transport/scheduler.js +133 -0
- package/dist/transport/scheduler.js.map +1 -0
- package/dist/transport/sse.d.ts +43 -0
- package/dist/transport/sse.js +51 -0
- package/dist/transport/sse.js.map +1 -0
- package/dist/{run → transport}/transport.d.ts +1 -1
- package/dist/{run → transport}/transport.js +1 -1
- package/dist/transport/transport.js.map +1 -0
- package/package.json +24 -9
- package/dist/engine/context.js +0 -146
- package/dist/engine/context.js.map +0 -1
- package/dist/run/async.d.ts +0 -59
- package/dist/run/async.js +0 -201
- package/dist/run/async.js.map +0 -1
- package/dist/run/context.d.ts +0 -16
- package/dist/run/context.js.map +0 -1
- package/dist/run/fsStore.js.map +0 -1
- package/dist/run/http.d.ts +0 -47
- package/dist/run/http.js +0 -119
- package/dist/run/http.js.map +0 -1
- package/dist/run/memory.js.map +0 -1
- package/dist/run/openai.js +0 -181
- package/dist/run/openai.js.map +0 -1
- package/dist/run/otlp.js.map +0 -1
- package/dist/run/redisStore.js.map +0 -1
- package/dist/run/run.js +0 -129
- package/dist/run/run.js.map +0 -1
- package/dist/run/scheduler.js +0 -85
- package/dist/run/scheduler.js.map +0 -1
- package/dist/run/spec.js.map +0 -1
- package/dist/run/sqliteStore.js.map +0 -1
- package/dist/run/store.js +0 -28
- package/dist/run/store.js.map +0 -1
- package/dist/run/systemPrompt.js.map +0 -1
- package/dist/run/transport.js.map +0 -1
- /package/dist/{run → integrations}/otlp.d.ts +0 -0
- /package/dist/{run → integrations}/otlp.js +0 -0
- /package/dist/{run → runtime}/types.d.ts +0 -0
- /package/dist/{run → runtime}/types.js +0 -0
- /package/dist/{run → store}/sqliteStore.d.ts +0 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { classifyError } from '../engine/errors.js';
|
|
3
|
+
import { normalizeMessages } from '../engine/spec.js';
|
|
4
|
+
import { InMemoryTaskStore, isThenable, nextTaskId } from '../store/store.js';
|
|
5
|
+
import { combineSignals } from '../core/abort.js';
|
|
6
|
+
export class AsyncRunner {
|
|
7
|
+
app;
|
|
8
|
+
store;
|
|
9
|
+
/** 本进程标识:写进认领的 TaskRecord.ownerId,供 resumePending 区分他我 */
|
|
10
|
+
ownerId;
|
|
11
|
+
client;
|
|
12
|
+
concurrency;
|
|
13
|
+
runTimeoutMs;
|
|
14
|
+
running = 0;
|
|
15
|
+
waitQueue = [];
|
|
16
|
+
/** 已受理但未达终态的任务数(queued + running)—— /healthz 与 drain 共用 */
|
|
17
|
+
active = 0;
|
|
18
|
+
draining = false;
|
|
19
|
+
drainWaiters = [];
|
|
20
|
+
/** 任务终态唤醒表:taskId → 等待者。仅覆盖**本进程**写终态(他进程写靠兜底轮询) */
|
|
21
|
+
#taskWaiters = new Map();
|
|
22
|
+
taskSinks;
|
|
23
|
+
constructor(app, opts = {}) {
|
|
24
|
+
this.app = app;
|
|
25
|
+
this.store = opts.store ?? new InMemoryTaskStore();
|
|
26
|
+
this.client = opts.client;
|
|
27
|
+
this.taskSinks = opts.taskSinks ?? [];
|
|
28
|
+
this.concurrency = opts.concurrency ?? Number.POSITIVE_INFINITY;
|
|
29
|
+
if (!(this.concurrency > 0)) {
|
|
30
|
+
throw new Error(`concurrency 必须为正数,收到 ${opts.concurrency}`);
|
|
31
|
+
}
|
|
32
|
+
this.runTimeoutMs = opts.runTimeoutMs ?? 0;
|
|
33
|
+
if (this.runTimeoutMs < 0) {
|
|
34
|
+
throw new Error(`runTimeoutMs 不能为负,收到 ${opts.runTimeoutMs}`);
|
|
35
|
+
}
|
|
36
|
+
this.ownerId = `p${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 提交一次异步任务。入参可为 string / messages / {prompt|text|messages}。
|
|
40
|
+
* 幂等键存在且上一任务未失败 → 直接返回既有记录(去重);失败的同键可产生新任务。
|
|
41
|
+
* 同步门面:同步 store 下立即去重并返回快照;异步 store 下 save 在后台完成、
|
|
42
|
+
* 去重推迟到执行前(见 #execute),返回新建任务的提交时刻快照。
|
|
43
|
+
*/
|
|
44
|
+
submit(input, opts = {}) {
|
|
45
|
+
// 停机中不接单(drain 之后);宿主据此回 503。放在最前:连入参规整都省了。
|
|
46
|
+
if (this.draining) {
|
|
47
|
+
throw new Error('runner 正在优雅停机,不再接受新任务');
|
|
48
|
+
}
|
|
49
|
+
const messages = normalizeMessages(input);
|
|
50
|
+
if (opts.idempotencyKey) {
|
|
51
|
+
const existing = this.store.byIdempotency(opts.idempotencyKey);
|
|
52
|
+
if (isThenable(existing)) {
|
|
53
|
+
// 异步 store 返回 Promise —— 同步门面无法 await,去重交给 #execute。
|
|
54
|
+
// 但**必须订阅它**:byIdempotency 的 reject(Redis 抖动等)若无人处理就是
|
|
55
|
+
// unhandledRejection(Node ≥15 默认终止宿主进程)。这里只做「查不到既有记录」
|
|
56
|
+
// 处理,拒绝即视为无记录,交 #execute 的去重兜底。
|
|
57
|
+
existing.catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
else if (existing && existing.status !== 'failed') {
|
|
60
|
+
return { ...existing }; // at-least-once 去重:不重复执行
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const rec = {
|
|
64
|
+
taskId: nextTaskId(),
|
|
65
|
+
status: 'queued',
|
|
66
|
+
idempotencyKey: opts.idempotencyKey,
|
|
67
|
+
spec: { messages, options: opts.options, source: opts.source ?? 'async' },
|
|
68
|
+
createdAt: Date.now(),
|
|
69
|
+
ownerId: this.ownerId,
|
|
70
|
+
};
|
|
71
|
+
const saved = this.store.save(rec);
|
|
72
|
+
if (isThenable(saved)) {
|
|
73
|
+
// 异步落库失败:尽力把任务标记为 failed 重存,避免静默吞错 / unhandled rejection。
|
|
74
|
+
// 但只在任务**尚未被 #execute 推进**时改判:初始 save 的 reject 可能迟到,
|
|
75
|
+
// 那时 run 已跑完并写了成功终态,无条件覆写会把成功翻成失败(落库终态与真实结果相反)。
|
|
76
|
+
saved.catch((e) => {
|
|
77
|
+
if (rec.status !== 'queued' || rec.finishedAt !== undefined)
|
|
78
|
+
return;
|
|
79
|
+
rec.status = 'failed';
|
|
80
|
+
rec.error = classifyError(e);
|
|
81
|
+
rec.finishedAt = Date.now();
|
|
82
|
+
void this.#safeSave(rec);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
void this.#execute(rec);
|
|
86
|
+
// 返回浅拷贝:记录会被后台状态机原地推进,调用方拿到的是提交时刻的快照
|
|
87
|
+
return { ...rec };
|
|
88
|
+
}
|
|
89
|
+
/** 查任务当前记录(异步 store 下返回 Promise,调用方 await) */
|
|
90
|
+
poll(taskId) {
|
|
91
|
+
return this.store.get(taskId);
|
|
92
|
+
}
|
|
93
|
+
byIdempotency(key) {
|
|
94
|
+
return this.store.byIdempotency(key);
|
|
95
|
+
}
|
|
96
|
+
list() {
|
|
97
|
+
return this.store.list();
|
|
98
|
+
}
|
|
99
|
+
/** 已受理但未达终态的任务数(queued + running)—— 健康检查与 drain 共用同一口径 */
|
|
100
|
+
get inFlight() {
|
|
101
|
+
return this.active;
|
|
102
|
+
}
|
|
103
|
+
/** 是否已进入优雅停机(drain 之后为 true)—— HTTP 宿主据此对新单回 503 */
|
|
104
|
+
get isDraining() {
|
|
105
|
+
return this.draining;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 优雅停机:停止接单(此后 `submit` 抛错),等待已受理任务排空(或超时)。
|
|
109
|
+
*
|
|
110
|
+
* 返回是否排空干净:超时仍返回 `false`,**未完成的任务留在 store 里**,下次启动由
|
|
111
|
+
* `resumePending` 续跑(所以 drain 不是「丢弃」,是「不再往前推」)。
|
|
112
|
+
* - `timeoutMs` 缺省 0 = 一直等。
|
|
113
|
+
* - 等待的是**所有已受理**的任务(queued 的也在内),不只是正在占槽位的那些。
|
|
114
|
+
*/
|
|
115
|
+
async drain(opts = {}) {
|
|
116
|
+
this.draining = true;
|
|
117
|
+
const timeoutMs = opts.timeoutMs ?? 0;
|
|
118
|
+
if (this.active === 0)
|
|
119
|
+
return true;
|
|
120
|
+
const drained = new Promise((resolve) => this.drainWaiters.push(() => resolve(true)));
|
|
121
|
+
if (timeoutMs <= 0)
|
|
122
|
+
return drained;
|
|
123
|
+
let timer;
|
|
124
|
+
try {
|
|
125
|
+
return await Promise.race([
|
|
126
|
+
drained,
|
|
127
|
+
new Promise((resolve) => {
|
|
128
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
129
|
+
// ⚠️ 不 unref:`drain()` 返回的正是这个 false —— 计时器的触发就是「调用方的 await 得以结束」
|
|
130
|
+
// 的条件。unref 过它 ⇒ 空事件循环下进程先退出,停机等待没有任何结论
|
|
131
|
+
// (见 `tests/timeoutLiveness.test.ts` 与 spec §10 ④)。
|
|
132
|
+
}),
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
if (timer)
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** 任务终态唤醒:在 #execute 的统一出口调用,覆盖成功 / 失败 / 采纳既有结果各路径 */
|
|
141
|
+
#notifyTaskDone(taskId) {
|
|
142
|
+
const waiters = this.#taskWaiters.get(taskId);
|
|
143
|
+
if (!waiters || waiters.length === 0)
|
|
144
|
+
return;
|
|
145
|
+
this.#taskWaiters.delete(taskId);
|
|
146
|
+
for (const w of waiters)
|
|
147
|
+
w();
|
|
148
|
+
}
|
|
149
|
+
/** 等「本进程把该任务写到终态」或被 timeoutMs 兜底唤醒(二者先到先返回) */
|
|
150
|
+
#waitTaskDone(taskId, timeoutMs) {
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
let settled = false;
|
|
153
|
+
const finish = () => {
|
|
154
|
+
if (settled)
|
|
155
|
+
return;
|
|
156
|
+
settled = true;
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
const waiters = this.#taskWaiters.get(taskId);
|
|
159
|
+
if (waiters) {
|
|
160
|
+
const i = waiters.indexOf(finish);
|
|
161
|
+
if (i >= 0)
|
|
162
|
+
waiters.splice(i, 1);
|
|
163
|
+
if (waiters.length === 0)
|
|
164
|
+
this.#taskWaiters.delete(taskId);
|
|
165
|
+
}
|
|
166
|
+
resolve();
|
|
167
|
+
};
|
|
168
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
169
|
+
const waiters = this.#taskWaiters.get(taskId);
|
|
170
|
+
if (waiters)
|
|
171
|
+
waiters.push(finish);
|
|
172
|
+
else
|
|
173
|
+
this.#taskWaiters.set(taskId, [finish]);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** 排空通知:只在确无在飞任务时唤醒等待者(drain 的唯一出口) */
|
|
177
|
+
#notifyDrained() {
|
|
178
|
+
if (this.active !== 0 || this.drainWaiters.length === 0)
|
|
179
|
+
return;
|
|
180
|
+
const waiters = this.drainWaiters.splice(0, this.drainWaiters.length);
|
|
181
|
+
for (const w of waiters)
|
|
182
|
+
w();
|
|
183
|
+
}
|
|
184
|
+
/** 等到任务终态;超时抛错。 */
|
|
185
|
+
async awaitTask(taskId, opts = {}) {
|
|
186
|
+
const timeoutMs = opts.timeoutMs ?? 30_000;
|
|
187
|
+
// intervalMs 现在只是**兜底轮询**间隔,不是主路径:本进程把任务写到终态会主动唤醒
|
|
188
|
+
// (见 #notifyTaskDone)。默认从 5ms 放宽到 250ms —— 旧实现每 5ms 读一次 store,
|
|
189
|
+
// 等 30s 就是约 6000 次读(SQLite/Redis 下是 6000 次往返)。用异步 store 且终态由
|
|
190
|
+
// **他进程**写入时唤不醒,才靠这个间隔兜底。
|
|
191
|
+
const intervalMs = opts.intervalMs ?? 250;
|
|
192
|
+
const deadline = Date.now() + timeoutMs;
|
|
193
|
+
for (;;) {
|
|
194
|
+
const rec = await this.store.get(taskId);
|
|
195
|
+
if (!rec)
|
|
196
|
+
throw new Error(`task 不存在: ${taskId}`);
|
|
197
|
+
if (rec.status === 'succeeded' || rec.status === 'failed')
|
|
198
|
+
return rec;
|
|
199
|
+
const left = deadline - Date.now();
|
|
200
|
+
if (left <= 0)
|
|
201
|
+
throw new Error(`task ${taskId} 等待超时(${rec.status})`);
|
|
202
|
+
await this.#waitTaskDone(taskId, Math.min(intervalMs, left));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 宿主重启续跑:把 store 里 queued | running 的记录重新派发执行
|
|
207
|
+
* (running 视为进程中断)。返回重派数量(异步 store 下返回 Promise<number>)。
|
|
208
|
+
* 幂等键去重照常生效。
|
|
209
|
+
*
|
|
210
|
+
* 多进程共用一个 store 时靠 `ownerId` 区分他我:
|
|
211
|
+
* - 本进程的记录一律跳过(它还在本进程内存里跑,重派 = 跑两遍);
|
|
212
|
+
* - `staleAfterMs > 0` 时,startedAt/createdAt 距今不足该值的他进程记录也跳过
|
|
213
|
+
* (大概正被那个进程执行)。缺省 0 = 不判断、一律重派(单进程旧语义)。
|
|
214
|
+
*/
|
|
215
|
+
resumePending(opts = {}) {
|
|
216
|
+
const listed = this.store.list();
|
|
217
|
+
const staleAfterMs = opts.staleAfterMs ?? 0;
|
|
218
|
+
if (isThenable(listed)) {
|
|
219
|
+
return listed.then((recs) => this.#redispatch(recs, staleAfterMs));
|
|
220
|
+
}
|
|
221
|
+
return this.#redispatch(listed, staleAfterMs);
|
|
222
|
+
}
|
|
223
|
+
#redispatch(recs, staleAfterMs) {
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
const pending = recs.filter((r) => {
|
|
226
|
+
if (r.status !== 'queued' && r.status !== 'running')
|
|
227
|
+
return false;
|
|
228
|
+
if (r.ownerId === this.ownerId)
|
|
229
|
+
return false; // 自己的一定还活着
|
|
230
|
+
if (staleAfterMs > 0 && r.ownerId !== undefined) {
|
|
231
|
+
const since = r.startedAt ?? r.createdAt;
|
|
232
|
+
if (now - since < staleAfterMs)
|
|
233
|
+
return false; // 他进程刚起的,别抢
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
for (const rec of pending) {
|
|
238
|
+
rec.status = 'queued'; // 重新入队,由 #execute 统一推进
|
|
239
|
+
rec.ownerId = this.ownerId; // 认领:此后本进程的记录不再被(自己)重派
|
|
240
|
+
void this.#execute(rec);
|
|
241
|
+
}
|
|
242
|
+
return pending.length;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 在飞计数包裹层:#executeInner 是状态机主体,这里只负责 active 计数与排空通知。
|
|
246
|
+
* 计数在**同步段**(第一个 await 之前)自增 —— 所以 `void this.#execute(rec)` 一返回,
|
|
247
|
+
* 该任务就已经计入了,drain 不会漏掉「刚 submit、还没开始跑」的任务。
|
|
248
|
+
*/
|
|
249
|
+
async #execute(rec) {
|
|
250
|
+
this.active++;
|
|
251
|
+
try {
|
|
252
|
+
await this.#executeInner(rec);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
// 通知在飞递减**之前**:drain() 返回时保证「任务已终态 + 回调已发完」。
|
|
256
|
+
// 内层 finally 保证回调万一抛错(理论上被吞掉)也不泄漏在飞计数。
|
|
257
|
+
try {
|
|
258
|
+
await this.#notifySinks(rec);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
this.active--;
|
|
262
|
+
// 任务已达终态并落库 → 唤醒 awaitTask 的等待者(放在递减之后,语义与 drain 一致)
|
|
263
|
+
this.#notifyTaskDone(rec.taskId);
|
|
264
|
+
this.#notifyDrained();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** 通知任务完成回调。逐个 await,**sink 抛错被吞** —— 回调失败不得影响任务状态。 */
|
|
269
|
+
async #notifySinks(rec) {
|
|
270
|
+
if (this.taskSinks.length === 0)
|
|
271
|
+
return;
|
|
272
|
+
// 传快照:回调拿到的是「此刻的终态」,之后记录再被改动不会串进回调持有的引用
|
|
273
|
+
const snapshot = { ...rec };
|
|
274
|
+
for (const sink of this.taskSinks) {
|
|
275
|
+
try {
|
|
276
|
+
await sink.onFinished(snapshot);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
/* 回调失败不影响任务状态(同 trace sink / memory 回写的防护) */
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async #executeInner(rec) {
|
|
284
|
+
// 顶层容错:异步 store(网络客户端)任何一处 reject 都不得逃逸成
|
|
285
|
+
// unhandled rejection(Node ≥15 默认终止进程)——任务标记失败尽力落库。
|
|
286
|
+
try {
|
|
287
|
+
// 异步 store 的幂等去重在此补齐(submit 同步门面无法 await):
|
|
288
|
+
// 同键已有 succeeded 记录 → 采纳其结果,不重复执行;queued/running 不采纳 ——
|
|
289
|
+
// 重复执行本就是 at-least-once 允许的行为。同步 store 下 submit 已完成去重,
|
|
290
|
+
// 这里查到的一般是 rec 自身(taskId 相同,直接放行)。
|
|
291
|
+
// 注意(load-bearing):该去重依赖 store.save 先写记录、后写 idem 索引的顺序
|
|
292
|
+
// (见 redisStore 头注释)——索引指向自身时上面的 taskId 相等判断放行。
|
|
293
|
+
if (rec.idempotencyKey) {
|
|
294
|
+
const existing = await this.store.byIdempotency(rec.idempotencyKey);
|
|
295
|
+
if (existing && existing.taskId !== rec.taskId && existing.status === 'succeeded') {
|
|
296
|
+
rec.status = 'succeeded';
|
|
297
|
+
rec.runId = existing.runId;
|
|
298
|
+
rec.result = existing.result;
|
|
299
|
+
rec.error = existing.error;
|
|
300
|
+
rec.finishedAt = Date.now();
|
|
301
|
+
// 采纳既有结果:落库失败也不该把一次已知成功的任务翻成 failed
|
|
302
|
+
await this.#safeSave(rec);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
await this.#acquireSlot();
|
|
307
|
+
try {
|
|
308
|
+
rec.status = 'running';
|
|
309
|
+
rec.startedAt = Date.now();
|
|
310
|
+
await this.store.save(rec);
|
|
311
|
+
try {
|
|
312
|
+
// runTimeoutMs 到点即 abort(对尊重 signal 的客户端是真中止);与调用方
|
|
313
|
+
// 可能传入的 signal 合成,任一触发都中止本次 run。
|
|
314
|
+
const timeoutAc = new AbortController();
|
|
315
|
+
const callOpts = {
|
|
316
|
+
...(rec.spec.options ?? {}),
|
|
317
|
+
client: rec.spec.options?.client ?? this.client,
|
|
318
|
+
idempotencyKey: rec.idempotencyKey,
|
|
319
|
+
rethrow: false, // 硬失败也以 failed 记录落库
|
|
320
|
+
signal: combineSignals(rec.spec.options?.signal, timeoutAc.signal),
|
|
321
|
+
};
|
|
322
|
+
const out = await this.#raceTimeout(this.app.run(rec.spec.messages, callOpts), rec.taskId, () => timeoutAc.abort());
|
|
323
|
+
rec.runId = out.run.runId;
|
|
324
|
+
rec.status = out.run.status;
|
|
325
|
+
rec.result = out.result;
|
|
326
|
+
rec.error = out.result.error;
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
rec.error = classifyError(e);
|
|
330
|
+
rec.status = 'failed';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
rec.finishedAt = Date.now();
|
|
335
|
+
// 落库失败不遮罩、槽位必须释放:释放放在内层 finally,即便落库实现抛错也必达
|
|
336
|
+
try {
|
|
337
|
+
await this.#safeSave(rec);
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
this.#releaseSlot();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
rec.status = 'failed';
|
|
346
|
+
rec.error = classifyError(e);
|
|
347
|
+
rec.finishedAt = Date.now();
|
|
348
|
+
await this.#safeSave(rec);
|
|
349
|
+
console.error(`[agentia] task ${rec.taskId} 执行异常:`, e);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 尽力落库:**先包成 Promise 再挂 catch**。
|
|
354
|
+
*
|
|
355
|
+
* 同步 store(FileTaskStore 的 writeFileSync、node:sqlite)的 save 是**同步抛错**的。
|
|
356
|
+
* 若写成 `Promise.resolve(this.store.save(rec)).catch(...)`,`this.store.save(rec)`
|
|
357
|
+
* 会在 `Promise.resolve` 之前求值并同步抛出 —— 异常逃出 finally(跳过 #releaseSlot,
|
|
358
|
+
* 并发槽位永久泄漏),再被外层 catch 里同一写法抛第二次,最终逃出 #execute 变成
|
|
359
|
+
* unhandled rejection(Node ≥15 默认终止宿主进程)。
|
|
360
|
+
*/
|
|
361
|
+
async #safeSave(rec) {
|
|
362
|
+
try {
|
|
363
|
+
await Promise.resolve().then(() => this.store.save(rec));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// 落库失败不遮罩主流程:任务结果仍在内存记录里可见
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 超时竞速(runTimeoutMs > 0 时):超时先 `onTimeout()`(abort 在飞请求)再 reject
|
|
371
|
+
* → 任务按 failed 落库、槽位回收。尊重 signal 的客户端会被真中止;不尊重者只是
|
|
372
|
+
* 停止等待(race 已订阅该 Promise,其后续 reject 不会变成 unhandled rejection)。
|
|
373
|
+
* timer 必须 clear(否则每次任务都留一个定时器)。
|
|
374
|
+
*/
|
|
375
|
+
async #raceTimeout(p, taskId, onTimeout) {
|
|
376
|
+
if (this.runTimeoutMs <= 0)
|
|
377
|
+
return p;
|
|
378
|
+
let timer;
|
|
379
|
+
try {
|
|
380
|
+
return await Promise.race([
|
|
381
|
+
p,
|
|
382
|
+
new Promise((_, reject) => {
|
|
383
|
+
timer = setTimeout(() => {
|
|
384
|
+
onTimeout?.();
|
|
385
|
+
reject(new Error(`task ${taskId} 执行超时(${this.runTimeoutMs}ms)`));
|
|
386
|
+
}, this.runTimeoutMs);
|
|
387
|
+
// ⚠️ 不 unref:这个 reject 是 `awaitTask` / 调用方 await 的终点(spec §10 ④)。
|
|
388
|
+
// 见 `tests/timeoutLiveness.test.ts`。
|
|
389
|
+
}),
|
|
390
|
+
]);
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
if (timer)
|
|
394
|
+
clearTimeout(timer);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/** 并发槽位:超限则排队等待(任务记录保持 queued,由 store 可见) */
|
|
398
|
+
#acquireSlot() {
|
|
399
|
+
if (this.running < this.concurrency) {
|
|
400
|
+
this.running++;
|
|
401
|
+
return Promise.resolve();
|
|
402
|
+
}
|
|
403
|
+
return new Promise((resolve) => this.waitQueue.push(resolve));
|
|
404
|
+
}
|
|
405
|
+
#releaseSlot() {
|
|
406
|
+
const next = this.waitQueue.shift();
|
|
407
|
+
if (next) {
|
|
408
|
+
next(); // 槽位直接移交给等待者,running 计数不变
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
this.running--;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/transport/async.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { SpanError, Trace } from '../core/trace.js';
|
|
3
|
+
import type { AgentStopReason } from '../engine/types.js';
|
|
4
|
+
import { AsyncRunner } from './async.js';
|
|
5
|
+
import type { AppCallable } from './async.js';
|
|
6
|
+
import type { RunInvocationOptions } from '../engine/spec.js';
|
|
7
|
+
import type { RunStatus } from '../core/run.js';
|
|
8
|
+
/**
|
|
9
|
+
* Agentia —— HTTP 宿主(spec §6.6:换宿主不换语义,roadmap R3)。
|
|
10
|
+
*
|
|
11
|
+
* 只产出一个 (req, res) handler,不做 listen —— 交给用户
|
|
12
|
+
* `http.createServer(createHttpHandler(app)).listen(...)`,可挂进任意 Node HTTP 框架。
|
|
13
|
+
*
|
|
14
|
+
* 端点契约(全部 JSON):
|
|
15
|
+
* - POST /run 同步 RPC。body = RunInput(string / messages / {prompt|text|messages}),
|
|
16
|
+
* 走 normalizeMessages 规整后同步执行;200 { runId, status, stopReason,
|
|
17
|
+
* finalText, typed?, trace, error? }。status=failed 也照返 200
|
|
18
|
+
* (rethrow:false 语义:硬失败以 error 字段返回,不用 HTTP 错误码表达);
|
|
19
|
+
* 输入无法规整为 messages → 400 { error };
|
|
20
|
+
* 并发 run 超 maxConcurrentRuns → 503 + Retry-After(见该选项)。
|
|
21
|
+
* - POST /tasks 异步任务。body { input, idempotencyKey?, options? } →
|
|
22
|
+
* AsyncRunner.submit → 202 TaskRecord(queued,幂等键去重照常生效)。
|
|
23
|
+
* - GET /tasks/<id> 轮询任务记录 → 200 TaskRecord;不存在 → 404。
|
|
24
|
+
* - GET /healthz 健康检查 → 200 { ok, inFlight, uptimeMs, draining };**不鉴权**
|
|
25
|
+
* (探针不该带凭据)。停机中仍回 200(进程活着),就绪与否看 draining。
|
|
26
|
+
*
|
|
27
|
+
* 方法不符 405;路径不符 404;body 非法 JSON 400。runner 缺省内部 new AsyncRunner(app)。
|
|
28
|
+
*
|
|
29
|
+
* 鉴权(B1):配了 `authenticate` 时,**除 /healthz 外的所有路径**先过钩子,且必须在
|
|
30
|
+
* 读 body 之前 —— 未通过即回错误并断开连接,**不接收 body**(省资源)。框架只给缝:
|
|
31
|
+
* token / JWT / 签名策略是宿主或反代的事(框架不读 env、不碰凭据)。
|
|
32
|
+
*
|
|
33
|
+
* 优雅停机(B2):`handler.drain()` 拒新单(POST /run 与 /tasks → 503)、等异步任务与
|
|
34
|
+
* 在飞同步 run 收尾、强制收口仍开着的 SSE 流;`GET /tasks/<id>` 停机中照常可轮询
|
|
35
|
+
* (否则调用方拿不到在飞任务的结果)。
|
|
36
|
+
*/
|
|
37
|
+
/** POST /run 的响应形态 */
|
|
38
|
+
export interface RunHttpResponse {
|
|
39
|
+
runId: string;
|
|
40
|
+
status: RunStatus;
|
|
41
|
+
stopReason: AgentStopReason;
|
|
42
|
+
finalText: string;
|
|
43
|
+
/** 结构化结果(R2 起应用可携带;无则省略) */
|
|
44
|
+
typed?: unknown;
|
|
45
|
+
trace: Trace;
|
|
46
|
+
error?: SpanError;
|
|
47
|
+
}
|
|
48
|
+
/** POST /tasks 的请求体形态 */
|
|
49
|
+
export interface TaskSubmitBody {
|
|
50
|
+
input: unknown;
|
|
51
|
+
idempotencyKey?: string;
|
|
52
|
+
options?: RunInvocationOptions;
|
|
53
|
+
}
|
|
54
|
+
export interface HttpHandlerOptions {
|
|
55
|
+
/** 异步任务宿主;缺省 new AsyncRunner(app)(InMemoryTaskStore) */
|
|
56
|
+
runner?: AsyncRunner;
|
|
57
|
+
/** 请求 body 上限(字节),超限回 413;缺省 1 MiB */
|
|
58
|
+
maxBodyBytes?: number;
|
|
59
|
+
/**
|
|
60
|
+
* 同时在执行的 POST /run 上限;缺省 32,超限回 503 + `Retry-After`。
|
|
61
|
+
* 传 `Infinity` 恢复无上限(旧行为)。**行为变更**:旧版本无此闸门。
|
|
62
|
+
* 闸门针对同步 RPC —— 每次请求都会真跑一次 run,不设上限时少量并发请求
|
|
63
|
+
* 就能把宿主压垮(上游模型并发也一起打满);异步走 POST /tasks(runner 自带上限)。
|
|
64
|
+
*/
|
|
65
|
+
maxConcurrentRuns?: number;
|
|
66
|
+
/**
|
|
67
|
+
* 是否把内部异常原文回给调用方;缺省 false。
|
|
68
|
+
* false 时 500 只回通用文案,细节走 `console.error` —— 否则
|
|
69
|
+
* `ECONNREFUSED 10.0.0.7:6379` 这类内部拓扑会回给未鉴权的调用方。
|
|
70
|
+
*/
|
|
71
|
+
exposeErrors?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* SSE 下游积压上限(字节):`res.writableLength` 超过它即收口该 SSE 流(见 `sseWriter`)。
|
|
74
|
+
* 缺省 8 MiB —— 正常客户端远达不到,实际只拦「连得上但不读」的消费者。
|
|
75
|
+
*
|
|
76
|
+
* **收口会连带中止对应的 run**:客户端已经不消费了,继续逐 token 生成只是白花模型钱,
|
|
77
|
+
* 同时把内存堆高。要高限额传更大的值;要记录/告警请自行在 `sseWriter` 之上包一层 sink。
|
|
78
|
+
*/
|
|
79
|
+
sseMaxBufferedBytes?: number;
|
|
80
|
+
/**
|
|
81
|
+
* 入口鉴权钩子。请求进入时调用,**除 /healthz 外所有路径**都过它,且**在读 body 之前**
|
|
82
|
+
* (未通过就不接收 body,省资源)。
|
|
83
|
+
* - 正常返回(任意值)→ 视为通过。返回值框架不转交:要 per-request 上下文请在钩子自己的
|
|
84
|
+
* 闭包里存(避免为「暂时没有消费点」的东西发明传递通道);
|
|
85
|
+
* - 抛出 `HttpException` → 按其 `status` / `body` 回响应(想回 403 就抛 `status: 403`);
|
|
86
|
+
* - 抛出其它错误 → 回 401 `{ error: '未通过鉴权' }`,原文只进服务端日志
|
|
87
|
+
* (与 `exposeErrors` 同理:不把内部拓扑回给未鉴权的调用方)。
|
|
88
|
+
*
|
|
89
|
+
* 框架**不实现策略**(token / JWT / 签名都不做)—— 那是宿主或反代的事(框架不读 env、
|
|
90
|
+
* 不碰凭据)。为什么不做成 middleware:middleware 拦的是**能力调用**(run 内部),
|
|
91
|
+
* 而鉴权要拦的是 **run 入口**,且必须早于 body 读取。
|
|
92
|
+
*/
|
|
93
|
+
authenticate?: (req: IncomingMessage) => unknown | Promise<unknown>;
|
|
94
|
+
/**
|
|
95
|
+
* 指标出口(G4):提供后 `GET /metrics` 输出其 Prometheus 文本(`text/plain; version=0.0.4`)。
|
|
96
|
+
* 通常直接传 `metricsSink()`(它有 `render()`)。
|
|
97
|
+
*
|
|
98
|
+
* 与 `/healthz` 同档处理:**不鉴权**、停机中仍可拉(拉取端在集群内网)。要保护它,
|
|
99
|
+
* 请放到反代之后,或不要传这个选项、自己在 handler 外层挂路由。
|
|
100
|
+
*
|
|
101
|
+
* 框架只给缝:它不知道指标从哪来 —— 传 sink、传读快照的闭包都行。
|
|
102
|
+
*/
|
|
103
|
+
metrics?: {
|
|
104
|
+
render(): string;
|
|
105
|
+
} | (() => string);
|
|
106
|
+
}
|
|
107
|
+
/** GET /healthz 的响应形态 */
|
|
108
|
+
export interface HealthResponse {
|
|
109
|
+
/** 恒为 true:能回这个响应就说明进程活着(停机中也是 true,就绪与否看 draining) */
|
|
110
|
+
ok: true;
|
|
111
|
+
/**
|
|
112
|
+
* 在飞工作量 = **正在处理的同步 run**(并发闸门计数,含 SSE 流)
|
|
113
|
+
* + **已受理未完成的异步任务**(queued + running)。
|
|
114
|
+
* 与 `drain()` 等的范围一致 —— 这样健康检查和停机判断看的是同一个数。
|
|
115
|
+
*/
|
|
116
|
+
inFlight: number;
|
|
117
|
+
/** 本 handler 创建至今的毫秒数 */
|
|
118
|
+
uptimeMs: number;
|
|
119
|
+
/** 是否已进入优雅停机(drain 之后)—— 负载均衡据此摘流量 */
|
|
120
|
+
draining: boolean;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 鉴权钩子可抛出的异常:显式携带 HTTP 状态与响应体。
|
|
124
|
+
* 钩子抛别的错误一律按 401 处理(设计 F4:与「工具抛错即 is_error」的既有风格一致)。
|
|
125
|
+
*/
|
|
126
|
+
export declare class HttpException extends Error {
|
|
127
|
+
readonly status: number;
|
|
128
|
+
readonly body: unknown;
|
|
129
|
+
constructor(status: number, body?: unknown, message?: string);
|
|
130
|
+
}
|
|
131
|
+
/** createHttpHandler 的产物:可直接传给 http.createServer,另带停机控制面 */
|
|
132
|
+
export interface HttpHandler {
|
|
133
|
+
(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* 优雅停机:拒新单(POST /run、/tasks → 503)→ 等异步任务与在飞同步 run 收尾
|
|
136
|
+
* (或超时)→ 强制收口仍开着的 SSE 流。返回是否排空干净(超时/仍有在飞为 false)。
|
|
137
|
+
* 未完成的任务仍在 store 里,下次启动由 `resumePending` 续跑。
|
|
138
|
+
* 框架**不订阅 SIGTERM**:`process.on('SIGTERM', () => handler.drain())` 是宿主的事。
|
|
139
|
+
*/
|
|
140
|
+
drain(opts?: {
|
|
141
|
+
timeoutMs?: number;
|
|
142
|
+
}): Promise<boolean>;
|
|
143
|
+
/** 内部 AsyncRunner —— 需要时手动控制(resumePending / awaitTask / list 等) */
|
|
144
|
+
readonly runner: AsyncRunner;
|
|
145
|
+
}
|
|
146
|
+
export declare function createHttpHandler(app: AppCallable, opts?: HttpHandlerOptions): HttpHandler;
|