@rei-standard/amsg-server 2.6.0-next.30 → 2.6.0-next.32
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 +47 -0
- package/dist/adapters/d1.d.ts +9 -0
- package/dist/adapters/interface.d.ts +4 -2
- package/dist/adapters/neon.d.ts +17 -0
- package/dist/adapters/pg-shared.d.ts +33 -0
- package/dist/adapters/pg.d.ts +17 -0
- package/dist/{chunk-VBDBORLR.mjs → chunk-INNZZFGJ.mjs} +228 -105
- package/dist/{chunk-E4657Y5X.cjs → chunk-KZT3YC62.cjs} +225 -102
- package/dist/{chunk-E7OWP3VL.cjs → chunk-TKDISKCF.cjs} +29 -1
- package/dist/{chunk-ZGGF4GMA.mjs → chunk-XKUWMYVF.mjs} +28 -0
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.mjs +1 -1
- package/dist/index.cjs +28 -27
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +4 -3
- package/dist/lib/message-processor.d.ts +9 -11
- package/dist/lib/retry-policy.d.ts +11 -0
- package/dist/lib/run-tick.d.ts +1 -1
- package/dist/{neon-ZIMHALYI.mjs → neon-GKLT4UY3.mjs} +18 -2
- package/dist/{neon-KP2CPA57.cjs → neon-XHPJZESV.cjs} +28 -12
- package/dist/{pg-RZPQGXR3.cjs → pg-FHXQ6BJP.cjs} +27 -12
- package/dist/{pg-QGC2XHUT.mjs → pg-J4YXRQP5.mjs} +17 -2
- package/dist/single-user.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -703,6 +703,26 @@ LLM 这一条只认上游**答复了、并且拒了**的那几种状态码:Key
|
|
|
703
703
|
|
|
704
704
|
`GET /capabilities` 的 features 里对应 `llm-permanent-errors`、`redeliver-committed-batch`、`hook-usage-total`、`max-delivery-retries`。
|
|
705
705
|
|
|
706
|
+
## 按任务限制生成重试(`maxGenerationRetries`)
|
|
707
|
+
|
|
708
|
+
用户主动触发的回复可以第一次生成失败就结束,定时任务则保留默认退避。工厂配置和 `runScheduledTick` / `runTask` 支持 `maxGenerationRetries`,值可以是非负整数,也可以是同步函数:
|
|
709
|
+
|
|
710
|
+
```js
|
|
711
|
+
createSingleUserCloudflareWorker((env) => ({
|
|
712
|
+
// 这里的 interactive 是宿主自己约定的 metadata,库不认识业务类型。
|
|
713
|
+
maxGenerationRetries: (task) => task.metadata?.interactive ? 0 : undefined,
|
|
714
|
+
// ...其余配置
|
|
715
|
+
}));
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
回调收到与 `serializeBy` 相同的安全任务视图(含 metadata,不含 API Key 等凭据),每次尝试解析一次。返回 `undefined` 继承 `maxDeliveryRetries`(默认 3),返回 0 禁止本轮失败后重新生成;显式数值单独控制生成重试上限。不配置时行为不变,不需要修改任务 payload 或迁移数据库,已有任务也生效。所有服务端工厂和旧的 UUID 即时入口都透传这个配置。
|
|
719
|
+
|
|
720
|
+
这里的“生成阶段”以**最终回复整批进入 outbox**为边界:前置 hook、模型、工具循环、组装和落库失败都使用生成上限;没有 outbox 的适配器发生推送失败时仍可能需要重新生成,因此也使用生成上限。完整批次已入 outbox 后的推送失败改用 `maxDeliveryRetries`,补投不会再次调用模型。hook 的独立 `emitResult` 不等于最终回复提交。
|
|
721
|
+
|
|
722
|
+
`onFireSettled` 新增 `willRetry` 与 `failureStage`:失败时前者为本次的重试决策,后者为 `'generation'` 或 `'delivery'`;成功、跳过、未接管时两者均为 `null`。宿主可以用 `info.status === 'failed' && info.willRetry === false && !info.outboxed` 即时显示生成失败,无需修改错误对象或猜测重试次数。此回执发生在调度器写任务状态之前,`willRetry` 描述同源策略决策,不保证后续数据库写入成功;取消导致的失败不会重试。已有永久性错误判定仍然生效。
|
|
723
|
+
|
|
724
|
+
策略回调抛错或返回非法值时,不调用生成 hook / 模型,以 `GENERATION_RETRY_POLICY_INVALID` 记录配置错误并沿默认投递上限退避,避免一次坏部署立即作废所有任务;此时尚未进入 fire,因此不触发 `onFireSettled`。
|
|
725
|
+
|
|
706
726
|
## 同一分组的任务不并发(`serializeBy`)
|
|
707
727
|
|
|
708
728
|
同一个角色可能有好几条定时任务。撞在一起并发跑的话,用户一口气收到两条互不知情的消息;宿主在 hook 里维护的「我刚才说过什么」台账通常是读进内存 → 改 → 整份写回,两条各改各的再写回,后写的必然盖掉前面那条。
|
|
@@ -920,3 +940,30 @@ VERCEL_PROTECTION_BYPASS=YOUR_BYPASS_KEY
|
|
|
920
940
|
- [SW 包 README](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/sw/README.md)
|
|
921
941
|
- [API 技术规范](https://github.com/Tosd0/ReiStandard/blob/main/standards/active-messaging-api.md)
|
|
922
942
|
- [Service Worker 规范](https://github.com/Tosd0/ReiStandard/blob/main/standards/service-worker-specification.md)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
## 取消正在执行的生成与工具
|
|
946
|
+
|
|
947
|
+
任务被 `DELETE /cancel-message` 取消或被新任务顶替后,租约心跳发现任务已经失效,会中断正在等待的模型请求,阻止新的模型轮次、工具阶段和推送。`leaseHeartbeatMs` 默认仍为 30000;需要及时停止的交互应用可配置为 1000(每个正在执行的任务每秒续租一次)。这是跨请求的数据库检测,延迟取决于心跳和数据库响应,不承诺瞬时中断。自定义适配器需要支持 `claimTask` / `renewTaskLease`;关掉心跳时也关掉了这条主动检测路径。
|
|
948
|
+
|
|
949
|
+
`onBeforeFire`、`onLLMOutput`、`executeToolCalls` 的 context 以及 `onFireSettled` 回执新增:
|
|
950
|
+
|
|
951
|
+
- `signal: AbortSignal`:传给工具内部的 `fetch`,与工具自己的超时信号合并。
|
|
952
|
+
- `isCancelled(): boolean`:当前是否已收到取消信号。
|
|
953
|
+
- `throwIfCancelled(): void`:已取消时抛出 `code: 'TASK_CANCELLED'` 的错误。每个副作用开始前检查,工具 catch 中也要先调用它,以免取消被转成普通工具失败后继续执行。
|
|
954
|
+
|
|
955
|
+
```js
|
|
956
|
+
async function executeToolCalls(calls, ctx) {
|
|
957
|
+
const results = [];
|
|
958
|
+
for (const call of calls) {
|
|
959
|
+
ctx.throwIfCancelled();
|
|
960
|
+
const response = await fetch(toolUrl(call), { signal: ctx.signal });
|
|
961
|
+
results.push({ tool_call_id: call.id, role: 'tool', content: await response.text() });
|
|
962
|
+
}
|
|
963
|
+
return results;
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
取消收尾使用 `onFireSettled({ status: 'cancelled', willRetry: false, error, ... })`,不是生成失败。应用应在这里释放资源、结算已发生的用量,避免发出失败提示或把未送达内容记为已回复。取消时已经开始写入的 outbox 批次会在写完后检查信号并撤掉未投递部分;已经发到客户端的消息仍由客户端处理。应用需要记录停止的任务身份,拦住迟到推送、补收以及尚未显示的本地内容。已完成的外部副作用无法撤销;不支持 AbortSignal 的工具也需要自己在执行步骤之间检查取消。
|
|
968
|
+
|
|
969
|
+
执行阶段的 `ctx.writeState`、`scheduleTask`、`cancelTask`、`renewTask` 和 `emitResult` 会拒绝取消后新发起的操作。`onFireSettled` 的 `info.writeState` 特意仍可用,供宿主释放锁、保存账单等收尾;它不能用于继续生成阶段的业务动作。已经开始的数据库写入不支持回滚,宿主仍需处理并发状态版本。
|
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -110,6 +110,15 @@ export class D1Adapter {
|
|
|
110
110
|
status: string;
|
|
111
111
|
} | null>;
|
|
112
112
|
updateTaskById(taskId: any, updates: any): Promise<any>;
|
|
113
|
+
/**
|
|
114
|
+
* 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
|
|
115
|
+
*
|
|
116
|
+
* 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
|
|
117
|
+
* 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
|
|
118
|
+
* 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
|
|
119
|
+
* ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
|
|
120
|
+
* 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
|
|
121
|
+
*/
|
|
113
122
|
updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<{
|
|
114
123
|
uuid: any;
|
|
115
124
|
updated_at: string;
|
|
@@ -302,8 +302,10 @@ export type DbAdapter = {
|
|
|
302
302
|
last_error: string | null;
|
|
303
303
|
} | null>;
|
|
304
304
|
/**
|
|
305
|
-
*
|
|
306
|
-
* supersedesUuid
|
|
305
|
+
* (可选)建新任务的同时取消旧的那条,两件事一起成败(POST /schedule-message
|
|
306
|
+
* 的 supersedesUuid)。内置的 D1 / pg / neon 都实现了。不实现时 handler 退回
|
|
307
|
+
* 两步:先建新、后删旧——删旧失败最坏是两条都留着,客户端重试一次就能收拾,
|
|
308
|
+
* 而反过来(先删后建)建新失败就把旧任务白删了,找不回来。
|
|
307
309
|
*/
|
|
308
310
|
createTaskSuperseding?: (params: InsertTaskParams, supersedesUuid: string) => Promise<TaskRow & {
|
|
309
311
|
superseded: boolean;
|
package/dist/adapters/neon.d.ts
CHANGED
|
@@ -39,6 +39,14 @@ export class NeonAdapter {
|
|
|
39
39
|
}>;
|
|
40
40
|
dropSchema(): Promise<void>;
|
|
41
41
|
createTask(params: any): Promise<Record<string, any>>;
|
|
42
|
+
createTaskSuperseding(params: any, supersedesUuid: any): Promise<{
|
|
43
|
+
id: number;
|
|
44
|
+
uuid: string;
|
|
45
|
+
next_send_at: any;
|
|
46
|
+
status: string;
|
|
47
|
+
created_at: any;
|
|
48
|
+
superseded: boolean;
|
|
49
|
+
}>;
|
|
42
50
|
getTaskByUuid(uuid: any, userId: any): Promise<Record<string, any>>;
|
|
43
51
|
getTaskByUuidOnly(uuid: any): Promise<Record<string, any>>;
|
|
44
52
|
/**
|
|
@@ -52,6 +60,15 @@ export class NeonAdapter {
|
|
|
52
60
|
status: string;
|
|
53
61
|
} | null>;
|
|
54
62
|
updateTaskById(taskId: any, updates: any): Promise<Record<string, any>>;
|
|
63
|
+
/**
|
|
64
|
+
* 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
|
|
65
|
+
*
|
|
66
|
+
* 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
|
|
67
|
+
* 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
|
|
68
|
+
* 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
|
|
69
|
+
* ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
|
|
70
|
+
* 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
|
|
71
|
+
*/
|
|
55
72
|
updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<Record<string, any>>;
|
|
56
73
|
deleteTaskById(taskId: any): Promise<boolean>;
|
|
57
74
|
deleteTaskByUuid(uuid: any, userId: any): Promise<boolean>;
|
|
@@ -59,6 +59,39 @@ export function claimTask(query: PgQuery, taskId: number, expectedNextSendAt: st
|
|
|
59
59
|
* @returns {Promise<boolean>} true = 续上了
|
|
60
60
|
*/
|
|
61
61
|
export function renewTaskLease(query: PgQuery, taskId: number, leaseUntil: string | Date): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* 建新任务的同时把被顶替的旧任务删掉(POST /schedule-message 带
|
|
64
|
+
* supersedesUuid 时走这条)。
|
|
65
|
+
*
|
|
66
|
+
* 删旧和建新必须一起成败:分成两次查询的话,建新那步失败(uuid 撞了、连接
|
|
67
|
+
* 一时抖了)时旧任务已经删掉了,接口却回失败——客户端以为旧任务还在,实际
|
|
68
|
+
* 上它已经没了,没有任何办法找回来。
|
|
69
|
+
*
|
|
70
|
+
* 一条语句解决,不用显式事务:数据修改型 CTE 里的 DELETE 一定会执行到底,
|
|
71
|
+
* 而 INSERT 抛错时整条语句一起回滚(pg 与 neon 的 HTTP 驱动都是单语句一个
|
|
72
|
+
* 隐式事务,用法完全一致)。superseded 由 DELETE 到底删没删到行推出来。
|
|
73
|
+
*
|
|
74
|
+
* @param {PgQuery} query
|
|
75
|
+
* @param {{ user_id: string, uuid: string, encrypted_payload: string,
|
|
76
|
+
* next_send_at: string|Date, message_type: string }} params - 新任务
|
|
77
|
+
* @param {string} supersedesUuid - 要顶替掉的旧任务 uuid(同一个用户名下)
|
|
78
|
+
* @returns {Promise<{ id: number, uuid: string, next_send_at: any, status: string,
|
|
79
|
+
* created_at: any, superseded: boolean }|null>}
|
|
80
|
+
*/
|
|
81
|
+
export function createTaskSuperseding(query: PgQuery, params: {
|
|
82
|
+
user_id: string;
|
|
83
|
+
uuid: string;
|
|
84
|
+
encrypted_payload: string;
|
|
85
|
+
next_send_at: string | Date;
|
|
86
|
+
message_type: string;
|
|
87
|
+
}, supersedesUuid: string): Promise<{
|
|
88
|
+
id: number;
|
|
89
|
+
uuid: string;
|
|
90
|
+
next_send_at: any;
|
|
91
|
+
status: string;
|
|
92
|
+
created_at: any;
|
|
93
|
+
superseded: boolean;
|
|
94
|
+
} | null>;
|
|
62
95
|
/**
|
|
63
96
|
* 状态 + 失败摘要(GET /message 用它把「为什么失败」透给已失败的行)。
|
|
64
97
|
*
|
package/dist/adapters/pg.d.ts
CHANGED
|
@@ -35,6 +35,14 @@ export class PgAdapter {
|
|
|
35
35
|
}>;
|
|
36
36
|
dropSchema(): Promise<void>;
|
|
37
37
|
createTask(params: any): Promise<any[]>;
|
|
38
|
+
createTaskSuperseding(params: any, supersedesUuid: any): Promise<{
|
|
39
|
+
id: number;
|
|
40
|
+
uuid: string;
|
|
41
|
+
next_send_at: any;
|
|
42
|
+
status: string;
|
|
43
|
+
created_at: any;
|
|
44
|
+
superseded: boolean;
|
|
45
|
+
}>;
|
|
38
46
|
getTaskByUuid(uuid: any, userId: any): Promise<any[]>;
|
|
39
47
|
getTaskByUuidOnly(uuid: any): Promise<any[]>;
|
|
40
48
|
/**
|
|
@@ -48,6 +56,15 @@ export class PgAdapter {
|
|
|
48
56
|
status: string;
|
|
49
57
|
} | null>;
|
|
50
58
|
updateTaskById(taskId: any, updates: any): Promise<any[]>;
|
|
59
|
+
/**
|
|
60
|
+
* 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
|
|
61
|
+
*
|
|
62
|
+
* 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
|
|
63
|
+
* 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
|
|
64
|
+
* 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
|
|
65
|
+
* ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
|
|
66
|
+
* 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
|
|
67
|
+
*/
|
|
51
68
|
updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<any[]>;
|
|
52
69
|
deleteTaskById(taskId: any): Promise<boolean>;
|
|
53
70
|
deleteTaskByUuid(uuid: any, userId: any): Promise<boolean>;
|