@rei-standard/amsg-server 2.6.0-next.16 → 2.6.0-next.17
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 +50 -1
- package/dist/adapters/d1.d.ts +45 -0
- package/dist/adapters/interface.d.ts +33 -0
- package/dist/adapters/neon.d.ts +11 -0
- package/dist/adapters/pg-shared.d.ts +47 -0
- package/dist/adapters/pg.d.ts +11 -0
- package/dist/adapters/schema.d.ts +1 -0
- package/dist/adapters/schema.sqlite.d.ts +1 -0
- package/dist/{chunk-34I2YSWE.mjs → chunk-5FXVSC5O.mjs} +11 -0
- package/dist/{chunk-TFFBMYA2.cjs → chunk-E7OWP3VL.cjs} +41 -1
- package/dist/{chunk-H4V7OXHE.cjs → chunk-QHWGWKB4.cjs} +624 -153
- package/dist/{chunk-KVHR3RCU.mjs → chunk-QL6PMTIN.mjs} +629 -158
- package/dist/{chunk-OUPM4HAV.cjs → chunk-V6NZQ22S.cjs} +12 -1
- package/dist/{chunk-UYROU4L3.mjs → chunk-ZGGF4GMA.mjs} +41 -1
- package/dist/cloudflare.cjs +3 -3
- package/dist/cloudflare.mjs +2 -2
- package/dist/handlers/llm-credentials.d.ts +5 -0
- package/dist/index.cjs +49 -27
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +27 -5
- package/dist/lib/llm-credentials-store.d.ts +139 -0
- package/dist/lib/task-projection.d.ts +5 -0
- package/dist/{neon-G3G4TDAY.cjs → neon-U6TQFKMJ.cjs} +40 -16
- package/dist/{neon-GBUF4G76.mjs → neon-Y4KEZO5C.mjs} +26 -2
- package/dist/{pg-C3JZKBUM.cjs → pg-B324Q2R4.cjs} +36 -16
- package/dist/{pg-MRXP5MHJ.mjs → pg-GWJZBWAM.mjs} +22 -2
- package/dist/single-user.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,9 @@ const rei = await createReiServer({
|
|
|
47
47
|
// PUT /api/v1/push-subscription -> rei.handlers.pushSubscription.PUT
|
|
48
48
|
// GET /api/v1/push-subscription -> rei.handlers.pushSubscription.GET
|
|
49
49
|
// DELETE /api/v1/push-subscription -> rei.handlers.pushSubscription.DELETE
|
|
50
|
+
// PUT /api/v1/llm-credentials -> rei.handlers.llmCredentials.PUT
|
|
51
|
+
// GET /api/v1/llm-credentials -> rei.handlers.llmCredentials.GET
|
|
52
|
+
// DELETE /api/v1/llm-credentials -> rei.handlers.llmCredentials.DELETE
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
## 关于 `messageType: 'instant'`
|
|
@@ -160,6 +163,51 @@ const message = body.length <= remainingBytes ? body : body.slice(0, remainingBy
|
|
|
160
163
|
|
|
161
164
|
装不下的内容(长文、附件详情)建议走旁路:正文存进 `client_state`,push 里只带一个引用键,客户端上线后用 `GET /client-state` 取回。单用户 Worker 的 fire-time hook 用 `ctx.writeState()` 写,见 [`examples/cloudflare-single-user/README.md`](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/server/examples/cloudflare-single-user/README.md)。
|
|
162
165
|
|
|
166
|
+
## LLM 凭据(用户级)与 credRefs
|
|
167
|
+
|
|
168
|
+
LLM API 凭据(`apiUrl` / `apiKey` / `primaryModel`)有两种给法:
|
|
169
|
+
|
|
170
|
+
- **内联**:随排程请求带三件套,冻结进任务行(一直以来的方式,继续支持)。
|
|
171
|
+
- **引用**:凭据先用 `PUT /llm-credentials` 集中登记,排程 payload 里只带
|
|
172
|
+
`credRefs: { chat: '<credId>' }`。到点投递时按 credId 现读——换 Key 覆盖对应
|
|
173
|
+
行就够了,所有引用它的任务(包括角色在 fire 里给自己排的、客户端根本不知道
|
|
174
|
+
存在的那些)下次触发用的都是新凭据。
|
|
175
|
+
|
|
176
|
+
| 端点 | 语义 |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `PUT /llm-credentials` | 批量登记 / 覆盖。body =(加密后的)`{ credentials: [{ credId, value: { apiUrl, apiKey, primaryModel } }] }`,一批 ≤100 条,单用户 ≤500 行 |
|
|
179
|
+
| `GET /llm-credentials` | 对账清单 `{ credentials: [{ credId, updatedAt }] }`。**凭据本体永远不回传** |
|
|
180
|
+
| `DELETE /llm-credentials` | 删除。body =(加密后的)`{ credIds: [...] }` 或 `{ all: true }` |
|
|
181
|
+
|
|
182
|
+
客户端侧对应 `client.putLlmCredentials(credentials)` / `listLlmCredentials()` / `deleteLlmCredentials(opts)`。
|
|
183
|
+
|
|
184
|
+
`credId` 是客户端起名的**不透明字符串**(1–128 字符、不含控制字符),服务端不
|
|
185
|
+
解释语义。约定:`char:<charId>/<purpose>`(角色级)、`global/<purpose>`(全
|
|
186
|
+
局)。`credRefs` 的 purpose 键里只有 `chat` 由服务端消费(fire 时的主 LLM 调
|
|
187
|
+
用);其余 purpose(如情绪评估的副 API)归宿主 hook 侧,fire hook 的 ctx 上有
|
|
188
|
+
`resolveLlmCredential(credId)` 可按需取用(每次返回新对象;拿到就用,别挂到
|
|
189
|
+
ctx / metadata / push 上)。
|
|
190
|
+
|
|
191
|
+
配套的规则:
|
|
192
|
+
|
|
193
|
+
- 排程 / 更新时对 `credRefs` 里的**全部** credId 做存在性检查,缺的返回
|
|
194
|
+
`409 CREDENTIAL_NOT_FOUND` 并点名(先登记再排程)。
|
|
195
|
+
- `credRefs.chat` 与内联三件套在同一个请求里不能都传(`400`):chat 凭据只能
|
|
196
|
+
有一个来源。
|
|
197
|
+
- fire 时的解析顺序:`credRefs.chat` → 查表;行没了 → 退回任务里的内联三件套
|
|
198
|
+
(如有);都没有 → 本轮失败,`lastError` 记 `CREDENTIAL_MISSING`,走常规重
|
|
199
|
+
试——补传凭据后下一轮自愈。
|
|
200
|
+
- hook 的 `ctx.scheduleTask()` 自排任务时:父任务带 `credRefs` 就复制**引用**
|
|
201
|
+
(不复制凭据本体),换 Key 自动作用于整条自排链;存量内联任务照旧复制三件套。
|
|
202
|
+
- 任务投影(`GET /messages` / hook 的 `ctx.task`)带 `credRefs`(只是名字,不
|
|
203
|
+
是机密),客户端对账用;凭据本体照旧被白名单挡在外面。
|
|
204
|
+
- 数据库侧是 `llm_credentials` 表(`(user_id, cred_id)` 主键,`encrypted_value`
|
|
205
|
+
密文,时间戳 ISO8601 文本)。内置的 D1 / pg / neon 适配器都实现了,
|
|
206
|
+
`initSchema()` 会建表。自定义适配器要补 `upsertLlmCredentials` /
|
|
207
|
+
`getLlmCredentials` / `listLlmCredentials` / `deleteLlmCredentials` 四个方法,
|
|
208
|
+
缺任何一个这几个端点返回 501、带 `credRefs` 的排程也会被拒。
|
|
209
|
+
- 特性探测:`GET /capabilities` 的 `features` 含 `'llm-credentials'`。
|
|
210
|
+
|
|
163
211
|
## 读一条任务:列表 vs 单条
|
|
164
212
|
|
|
165
213
|
| 端点 | 给什么 |
|
|
@@ -173,12 +221,13 @@ const message = body.length <= remainingBytes ? body : body.slice(0, remainingBy
|
|
|
173
221
|
|
|
174
222
|
## 更新任务时能改哪些字段
|
|
175
223
|
|
|
176
|
-
`PUT /update-message` 的可写字段:`contactName` / `avatarUrl` / `userMessage` / `completePrompt` / `messages` / `nextSendAt` / `recurrenceType` / `tzId` / `metadata` / `maxTokens` / `temperature` / `splitPattern
|
|
224
|
+
`PUT /update-message` 的可写字段:`contactName` / `avatarUrl` / `userMessage` / `completePrompt` / `messages` / `nextSendAt` / `recurrenceType` / `tzId` / `metadata` / `maxTokens` / `temperature` / `splitPattern`、凭据三件套 `apiUrl` / `apiKey` / `primaryModel`,以及凭据引用 `credRefs`。
|
|
177
225
|
|
|
178
226
|
- `contactName` 必须是非空字符串(口径与排程时一致),空串 / `null` / 非字符串一律 `400`。用户给角色改了名之后,之前排的任务推送出来的通知标题(「来自 <contactName>」)靠它跟着改。
|
|
179
227
|
- `metadata` 是整体替换,不深合并——只改一个子字段的读-改-写流程见上一节。
|
|
180
228
|
- `avatarUrl` 显式传 `null` 是「不改」而不是「清空」(§6.2 的软清空策略要求非法头像被摘掉时保留旧头像,「摘掉」和「传了个 null」在这一层是同一件事)。
|
|
181
229
|
- 凭据三件套传 `null` 同样只是忽略:清掉任何一个,任务到点就发不出去。
|
|
230
|
+
- `credRefs` 是整体替换(语义同 `metadata`),同样做存在性检查;与内联三件套在同一个请求里混着传返回 `400`。给存量内联任务补 `credRefs` 时不动已存的三件套——那份留作 fire 时表行缺失的兜底。
|
|
182
231
|
- `pushSubscription` 不收(`400 PUSH_SUBSCRIPTION_NOT_ACCEPTED`),它是用户级的一份,走 `PUT /push-subscription`。
|
|
183
232
|
|
|
184
233
|
## 推送自带任务身份
|
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -237,6 +237,51 @@ export class D1Adapter {
|
|
|
237
237
|
* @returns {Promise<boolean>} true = 确实删掉了一行
|
|
238
238
|
*/
|
|
239
239
|
deletePushSubscription(userId: string): Promise<boolean>;
|
|
240
|
+
/**
|
|
241
|
+
* 批量 upsert 这个用户的凭据(value 已是密文,加密在上层)。已存在的行
|
|
242
|
+
* 覆盖 encrypted_value 并刷 updated_at,created_at 保留首次写入的时刻。
|
|
243
|
+
*
|
|
244
|
+
* @param {string} userId
|
|
245
|
+
* @param {Array<{ credId: string, encryptedValue: string }>} entries
|
|
246
|
+
* @returns {Promise<number>} 实际写入/覆盖的行数
|
|
247
|
+
*/
|
|
248
|
+
upsertLlmCredentials(userId: string, entries: Array<{
|
|
249
|
+
credId: string;
|
|
250
|
+
encryptedValue: string;
|
|
251
|
+
}>): Promise<number>;
|
|
252
|
+
/**
|
|
253
|
+
* 按 cred_id 批量读这个用户的凭据行(`encrypted_value` 是密文,解密在上
|
|
254
|
+
* 层)。排程时的存在性检查和 fire 时的解析共用这一个口。
|
|
255
|
+
*
|
|
256
|
+
* @param {string} userId
|
|
257
|
+
* @param {string[]} credIds
|
|
258
|
+
* @returns {Promise<Array<{ cred_id: string, encrypted_value: string, updated_at: string }>>}
|
|
259
|
+
*/
|
|
260
|
+
getLlmCredentials(userId: string, credIds: string[]): Promise<Array<{
|
|
261
|
+
cred_id: string;
|
|
262
|
+
encrypted_value: string;
|
|
263
|
+
updated_at: string;
|
|
264
|
+
}>>;
|
|
265
|
+
/**
|
|
266
|
+
* 这个用户名下所有凭据的对账清单(只有 cred_id 和 updated_at,密文不出
|
|
267
|
+
* 这个方法)。按 cred_id 排序,输出稳定。
|
|
268
|
+
*
|
|
269
|
+
* @param {string} userId
|
|
270
|
+
* @returns {Promise<Array<{ cred_id: string, updated_at: string }>>}
|
|
271
|
+
*/
|
|
272
|
+
listLlmCredentials(userId: string): Promise<Array<{
|
|
273
|
+
cred_id: string;
|
|
274
|
+
updated_at: string;
|
|
275
|
+
}>>;
|
|
276
|
+
/**
|
|
277
|
+
* 删凭据。`credIds` 传数组删指定那几行;传 null 删这个用户的全部
|
|
278
|
+
* (「清空云端数据」用)。
|
|
279
|
+
*
|
|
280
|
+
* @param {string} userId
|
|
281
|
+
* @param {string[]|null} credIds
|
|
282
|
+
* @returns {Promise<number>} 删掉的行数
|
|
283
|
+
*/
|
|
284
|
+
deleteLlmCredentials(userId: string, credIds?: string[] | null): Promise<number>;
|
|
240
285
|
/**
|
|
241
286
|
* 发送前把这一批 push 落进 outbox(一次 batch)。(user_id, message_id)
|
|
242
287
|
* 唯一:重试同一 occurrence 带着同一批 messageId 再来时更新 payload、不加
|
|
@@ -179,6 +179,39 @@ export type DbAdapter = {
|
|
|
179
179
|
* (建了也永远发不出去)。内置的 D1 / pg / neon 适配器都实现了。
|
|
180
180
|
*/
|
|
181
181
|
deletePushSubscription?: (userId: string) => Promise<boolean>;
|
|
182
|
+
/**
|
|
183
|
+
* 批量覆盖写这个用户的 LLM 凭据(`encryptedValue` 是密文,加密在上层)。
|
|
184
|
+
* 已存在的行覆盖 encrypted_value 并刷 updated_at。返回实际写入/覆盖的行数。
|
|
185
|
+
*/
|
|
186
|
+
upsertLlmCredentials?: (userId: string, entries: Array<{
|
|
187
|
+
credId: string;
|
|
188
|
+
encryptedValue: string;
|
|
189
|
+
}>) => Promise<number>;
|
|
190
|
+
/**
|
|
191
|
+
* 按 cred_id 批量读凭据行(密文原样返回)。排程时的存在性检查和 fire 时的
|
|
192
|
+
* 解析共用这一个口。
|
|
193
|
+
*/
|
|
194
|
+
getLlmCredentials?: (userId: string, credIds: string[]) => Promise<Array<{
|
|
195
|
+
cred_id: string;
|
|
196
|
+
encrypted_value: string;
|
|
197
|
+
updated_at: string;
|
|
198
|
+
}>>;
|
|
199
|
+
/**
|
|
200
|
+
* 这个用户名下所有凭据的对账清单(只有 cred_id 和 updated_at,密文不出这个
|
|
201
|
+
* 方法)。按 cred_id 排序。
|
|
202
|
+
*/
|
|
203
|
+
listLlmCredentials?: (userId: string) => Promise<Array<{
|
|
204
|
+
cred_id: string;
|
|
205
|
+
updated_at: string;
|
|
206
|
+
}>>;
|
|
207
|
+
/**
|
|
208
|
+
* 删凭据:数组删指定那几行,null 删全部。返回删掉的行数。
|
|
209
|
+
*
|
|
210
|
+
* llm_credentials 四个方法要么都实现、要么都不实现:缺任何一个,
|
|
211
|
+
* `PUT/GET/DELETE /llm-credentials` 返回 501,带 `credRefs` 的
|
|
212
|
+
* `POST /schedule-message` 也会被拒。内置的 D1 / pg / neon 适配器都实现了。
|
|
213
|
+
*/
|
|
214
|
+
deleteLlmCredentials?: (userId: string, credIds: string[] | null) => Promise<number>;
|
|
182
215
|
/**
|
|
183
216
|
* (可选)投递期间的租约续期(runScheduledTick 的心跳)。只在行仍是
|
|
184
217
|
* pending 且 lease_until 非空时生效——收尾放掉租约之后,迟到的心跳不会把
|
package/dist/adapters/neon.d.ts
CHANGED
|
@@ -68,4 +68,15 @@ export class NeonAdapter {
|
|
|
68
68
|
}>;
|
|
69
69
|
upsertPushSubscription(userId: any, encryptedSubscription: any, updatedAt: any): Promise<boolean>;
|
|
70
70
|
deletePushSubscription(userId: any): Promise<boolean>;
|
|
71
|
+
upsertLlmCredentials(userId: any, entries: any): Promise<number>;
|
|
72
|
+
getLlmCredentials(userId: any, credIds: any): Promise<{
|
|
73
|
+
cred_id: string;
|
|
74
|
+
encrypted_value: string;
|
|
75
|
+
updated_at: string;
|
|
76
|
+
}[]>;
|
|
77
|
+
listLlmCredentials(userId: any): Promise<{
|
|
78
|
+
cred_id: string;
|
|
79
|
+
updated_at: string;
|
|
80
|
+
}[]>;
|
|
81
|
+
deleteLlmCredentials(userId: any, credIds?: any): Promise<number>;
|
|
71
82
|
}
|
|
@@ -101,6 +101,53 @@ export function upsertPushSubscription(query: PgQuery, userId: string, encrypted
|
|
|
101
101
|
* @returns {Promise<boolean>} true = 确实删掉了一行
|
|
102
102
|
*/
|
|
103
103
|
export function deletePushSubscription(query: PgQuery, userId: string): Promise<boolean>;
|
|
104
|
+
/**
|
|
105
|
+
* 批量 upsert 这个用户的凭据(value 已是密文,加密在上层)。已存在的行覆盖
|
|
106
|
+
* encrypted_value 并刷 updated_at,created_at 保留首次写入的时刻。
|
|
107
|
+
*
|
|
108
|
+
* @param {PgQuery} query
|
|
109
|
+
* @param {string} userId
|
|
110
|
+
* @param {Array<{ credId: string, encryptedValue: string }>} entries
|
|
111
|
+
* @param {string} now - ISO8601 时间戳(与 SQLite 侧同口径的 TEXT)
|
|
112
|
+
* @returns {Promise<number>} 实际写入/覆盖的行数
|
|
113
|
+
*/
|
|
114
|
+
export function upsertLlmCredentials(query: PgQuery, userId: string, entries: Array<{
|
|
115
|
+
credId: string;
|
|
116
|
+
encryptedValue: string;
|
|
117
|
+
}>, now: string): Promise<number>;
|
|
118
|
+
/**
|
|
119
|
+
* 按 cred_id 批量读这个用户的凭据行(密文原样返回,解密在上层)。
|
|
120
|
+
*
|
|
121
|
+
* @param {PgQuery} query
|
|
122
|
+
* @param {string} userId
|
|
123
|
+
* @param {string[]} credIds
|
|
124
|
+
* @returns {Promise<Array<{ cred_id: string, encrypted_value: string, updated_at: string }>>}
|
|
125
|
+
*/
|
|
126
|
+
export function getLlmCredentials(query: PgQuery, userId: string, credIds: string[]): Promise<Array<{
|
|
127
|
+
cred_id: string;
|
|
128
|
+
encrypted_value: string;
|
|
129
|
+
updated_at: string;
|
|
130
|
+
}>>;
|
|
131
|
+
/**
|
|
132
|
+
* 这个用户名下所有凭据的对账清单(只有 cred_id 和 updated_at)。
|
|
133
|
+
*
|
|
134
|
+
* @param {PgQuery} query
|
|
135
|
+
* @param {string} userId
|
|
136
|
+
* @returns {Promise<Array<{ cred_id: string, updated_at: string }>>}
|
|
137
|
+
*/
|
|
138
|
+
export function listLlmCredentials(query: PgQuery, userId: string): Promise<Array<{
|
|
139
|
+
cred_id: string;
|
|
140
|
+
updated_at: string;
|
|
141
|
+
}>>;
|
|
142
|
+
/**
|
|
143
|
+
* 删凭据。`credIds` 传数组删指定那几行;传 null 删这个用户的全部。
|
|
144
|
+
*
|
|
145
|
+
* @param {PgQuery} query
|
|
146
|
+
* @param {string} userId
|
|
147
|
+
* @param {string[]|null} credIds
|
|
148
|
+
* @returns {Promise<number>} 删掉的行数
|
|
149
|
+
*/
|
|
150
|
+
export function deleteLlmCredentials(query: PgQuery, userId: string, credIds?: string[] | null): Promise<number>;
|
|
104
151
|
/**
|
|
105
152
|
* pg / neon 共用的 Postgres 查询实现。
|
|
106
153
|
*
|
package/dist/adapters/pg.d.ts
CHANGED
|
@@ -70,4 +70,15 @@ export class PgAdapter {
|
|
|
70
70
|
}>;
|
|
71
71
|
upsertPushSubscription(userId: any, encryptedSubscription: any, updatedAt: any): Promise<boolean>;
|
|
72
72
|
deletePushSubscription(userId: any): Promise<boolean>;
|
|
73
|
+
upsertLlmCredentials(userId: any, entries: any): Promise<number>;
|
|
74
|
+
getLlmCredentials(userId: any, credIds: any): Promise<{
|
|
75
|
+
cred_id: string;
|
|
76
|
+
encrypted_value: string;
|
|
77
|
+
updated_at: string;
|
|
78
|
+
}[]>;
|
|
79
|
+
listLlmCredentials(userId: any): Promise<{
|
|
80
|
+
cred_id: string;
|
|
81
|
+
updated_at: string;
|
|
82
|
+
}[]>;
|
|
83
|
+
deleteLlmCredentials(userId: any, credIds?: any): Promise<number>;
|
|
73
84
|
}
|
|
@@ -35,6 +35,7 @@ export const INDEXES: ({
|
|
|
35
35
|
critical: boolean;
|
|
36
36
|
})[];
|
|
37
37
|
export const PUSH_SUBSCRIPTION_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS push_subscriptions (\n user_id VARCHAR(255) PRIMARY KEY,\n subscription TEXT NOT NULL,\n updated_at BIGINT NOT NULL\n )\n";
|
|
38
|
+
export const LLM_CREDENTIALS_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS llm_credentials (\n user_id VARCHAR(255) NOT NULL,\n cred_id VARCHAR(128) NOT NULL,\n encrypted_value TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (user_id, cred_id)\n )\n";
|
|
38
39
|
export const VERIFY_TABLE_SQL: "\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_name = 'scheduled_messages'\n";
|
|
39
40
|
export const COLUMNS_SQL: "\n SELECT column_name, data_type, is_nullable\n FROM information_schema.columns\n WHERE table_schema = 'public'\n AND table_name = 'scheduled_messages'\n ORDER BY ordinal_position\n";
|
|
40
41
|
export const UPDATABLE_COLUMNS: Set<string>;
|
|
@@ -32,6 +32,7 @@ export const SQLITE_INDEXES: {
|
|
|
32
32
|
}[];
|
|
33
33
|
export const CLIENT_STATE_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS client_state (\n user_id TEXT NOT NULL,\n namespace TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (user_id, namespace, key)\n )\n";
|
|
34
34
|
export const PUSH_SUBSCRIPTION_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS push_subscriptions (\n user_id TEXT PRIMARY KEY,\n subscription TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n )\n";
|
|
35
|
+
export const LLM_CREDENTIALS_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS llm_credentials (\n user_id TEXT NOT NULL,\n cred_id TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (user_id, cred_id)\n )\n";
|
|
35
36
|
export const MESSAGE_OUTBOX_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS message_outbox (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id TEXT NOT NULL,\n message_id TEXT NOT NULL,\n task_uuid TEXT,\n session_id TEXT,\n message_index INTEGER,\n total_messages INTEGER,\n payload TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n delivered_at INTEGER,\n acked_at INTEGER,\n UNIQUE (user_id, message_id)\n )\n";
|
|
36
37
|
export const MESSAGE_OUTBOX_INDEX_SQL: "\n CREATE INDEX IF NOT EXISTS idx_outbox_unacked\n ON message_outbox (user_id, id)\n WHERE acked_at IS NULL\n";
|
|
37
38
|
/**
|
|
@@ -93,6 +93,16 @@ var PUSH_SUBSCRIPTION_TABLE_SQL = `
|
|
|
93
93
|
updated_at BIGINT NOT NULL
|
|
94
94
|
)
|
|
95
95
|
`;
|
|
96
|
+
var LLM_CREDENTIALS_TABLE_SQL = `
|
|
97
|
+
CREATE TABLE IF NOT EXISTS llm_credentials (
|
|
98
|
+
user_id VARCHAR(255) NOT NULL,
|
|
99
|
+
cred_id VARCHAR(128) NOT NULL,
|
|
100
|
+
encrypted_value TEXT NOT NULL,
|
|
101
|
+
created_at TEXT NOT NULL,
|
|
102
|
+
updated_at TEXT NOT NULL,
|
|
103
|
+
PRIMARY KEY (user_id, cred_id)
|
|
104
|
+
)
|
|
105
|
+
`;
|
|
96
106
|
var VERIFY_TABLE_SQL = `
|
|
97
107
|
SELECT table_name
|
|
98
108
|
FROM information_schema.tables
|
|
@@ -127,6 +137,7 @@ export {
|
|
|
127
137
|
MIGRATIONS,
|
|
128
138
|
INDEXES,
|
|
129
139
|
PUSH_SUBSCRIPTION_TABLE_SQL,
|
|
140
|
+
LLM_CREDENTIALS_TABLE_SQL,
|
|
130
141
|
VERIFY_TABLE_SQL,
|
|
131
142
|
COLUMNS_SQL,
|
|
132
143
|
UPDATABLE_COLUMNS
|
|
@@ -84,6 +84,46 @@ async function deletePushSubscription(query, userId) {
|
|
|
84
84
|
);
|
|
85
85
|
return rows.length > 0;
|
|
86
86
|
}
|
|
87
|
+
async function upsertLlmCredentials(query, userId, entries, now) {
|
|
88
|
+
let upserted = 0;
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const rows = await query(
|
|
91
|
+
`INSERT INTO llm_credentials (user_id, cred_id, encrypted_value, created_at, updated_at)
|
|
92
|
+
VALUES ($1, $2, $3, $4, $4)
|
|
93
|
+
ON CONFLICT (user_id, cred_id) DO UPDATE SET
|
|
94
|
+
encrypted_value = EXCLUDED.encrypted_value,
|
|
95
|
+
updated_at = EXCLUDED.updated_at
|
|
96
|
+
RETURNING cred_id`,
|
|
97
|
+
[userId, entry.credId, entry.encryptedValue, now]
|
|
98
|
+
);
|
|
99
|
+
if (rows.length > 0) upserted++;
|
|
100
|
+
}
|
|
101
|
+
return upserted;
|
|
102
|
+
}
|
|
103
|
+
async function getLlmCredentials(query, userId, credIds) {
|
|
104
|
+
if (!credIds || credIds.length === 0) return [];
|
|
105
|
+
return query(
|
|
106
|
+
`SELECT cred_id, encrypted_value, updated_at
|
|
107
|
+
FROM llm_credentials
|
|
108
|
+
WHERE user_id = $1 AND cred_id = ANY($2)`,
|
|
109
|
+
[userId, credIds]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
async function listLlmCredentials(query, userId) {
|
|
113
|
+
return query(
|
|
114
|
+
"SELECT cred_id, updated_at FROM llm_credentials WHERE user_id = $1 ORDER BY cred_id ASC",
|
|
115
|
+
[userId]
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
async function deleteLlmCredentials(query, userId, credIds = null) {
|
|
119
|
+
if (credIds !== null && credIds.length === 0) return 0;
|
|
120
|
+
const rows = credIds === null ? await query("DELETE FROM llm_credentials WHERE user_id = $1 RETURNING cred_id", [userId]) : await query("DELETE FROM llm_credentials WHERE user_id = $1 AND cred_id = ANY($2) RETURNING cred_id", [userId, credIds]);
|
|
121
|
+
return rows.length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
87
127
|
|
|
88
128
|
|
|
89
129
|
|
|
@@ -92,4 +132,4 @@ async function deletePushSubscription(query, userId) {
|
|
|
92
132
|
|
|
93
133
|
|
|
94
134
|
|
|
95
|
-
exports.claimTask = claimTask; exports.renewTaskLease = renewTaskLease; exports.getTaskStatusInfo = getTaskStatusInfo; exports.getPushSubscription = getPushSubscription; exports.upsertPushSubscription = upsertPushSubscription; exports.deletePushSubscription = deletePushSubscription;
|
|
135
|
+
exports.claimTask = claimTask; exports.renewTaskLease = renewTaskLease; exports.getTaskStatusInfo = getTaskStatusInfo; exports.getPushSubscription = getPushSubscription; exports.upsertPushSubscription = upsertPushSubscription; exports.deletePushSubscription = deletePushSubscription; exports.upsertLlmCredentials = upsertLlmCredentials; exports.getLlmCredentials = getLlmCredentials; exports.listLlmCredentials = listLlmCredentials; exports.deleteLlmCredentials = deleteLlmCredentials;
|