@rei-standard/amsg-server 3.0.0 → 3.1.0-next.0
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 +110 -11
- package/dist/adapters/d1.d.ts +3 -3
- package/dist/adapters/interface.d.ts +6 -3
- package/dist/{chunk-YBDQJVFQ.cjs → chunk-3NIYNYZY.cjs} +221 -83
- package/dist/{chunk-RL7V7VSS.mjs → chunk-6TCZEACV.mjs} +174 -36
- package/dist/cloudflare.cjs +4 -2
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.mjs +3 -1
- package/dist/index.cjs +27 -25
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +3 -1
- package/dist/lib/agentic-fire.d.ts +1 -0
- package/dist/lib/attachments.d.ts +39 -0
- package/dist/lib/client-state-store.d.ts +6 -4
- package/dist/lib/state-accessors.d.ts +12 -8
- package/dist/lib/task-projection.d.ts +2 -2
- package/package.json +1 -1
|
@@ -428,6 +428,9 @@ function taskPayloadByteLength(serializedPayload) {
|
|
|
428
428
|
return taskPayloadEncoder.encode(serializedPayload).length;
|
|
429
429
|
}
|
|
430
430
|
function validateScheduleMessagePayload(payload) {
|
|
431
|
+
if (payload.taskInput != null && (typeof payload.taskInput !== "object" || Array.isArray(payload.taskInput))) {
|
|
432
|
+
return { valid: false, errorCode: "INVALID_TASK_INPUT", errorMessage: "taskInput must be an object or null" };
|
|
433
|
+
}
|
|
431
434
|
if (!payload.contactName || typeof payload.contactName !== "string") {
|
|
432
435
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { missingFields: ["contactName"] } };
|
|
433
436
|
}
|
|
@@ -982,6 +985,40 @@ function createWebCryptoWebPush(vapid = {}, { ttl = SCHEDULED_DEFAULT_TTL } = {}
|
|
|
982
985
|
};
|
|
983
986
|
}
|
|
984
987
|
|
|
988
|
+
// src/server/lib/attachments.js
|
|
989
|
+
var ATTACHMENT_NAMESPACE = "amsg:attachments";
|
|
990
|
+
var ATTACHMENT_RETENTION_DAYS = 30;
|
|
991
|
+
var MAX_ATTACHMENT_TTL_MS = ATTACHMENT_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
992
|
+
function createAttachmentStore({ task, occurrenceMs, writeState, now }) {
|
|
993
|
+
return async function storeAttachment(value, { ttlMs = MAX_ATTACHMENT_TTL_MS, owner, kind = "attachment", ownerGeneration } = {}) {
|
|
994
|
+
if (typeof value !== "string") throw new TypeError("storeAttachment value must be a serialized string");
|
|
995
|
+
if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0 || ttlMs > MAX_ATTACHMENT_TTL_MS) {
|
|
996
|
+
throw new RangeError("storeAttachment ttlMs must be between 1 millisecond and 30 days");
|
|
997
|
+
}
|
|
998
|
+
if (typeof task.uuid !== "string" || !task.uuid || !Number.isFinite(occurrenceMs)) {
|
|
999
|
+
throw new TypeError("storeAttachment requires a task UUID and a valid occurrence");
|
|
1000
|
+
}
|
|
1001
|
+
const at = now();
|
|
1002
|
+
const ref = { version: (
|
|
1003
|
+
/** @type {1} */
|
|
1004
|
+
1
|
|
1005
|
+
), namespace: ATTACHMENT_NAMESPACE, key: randomUUID(), taskUuid: task.uuid, occurrenceMs, expiresAt: at + ttlMs };
|
|
1006
|
+
let result;
|
|
1007
|
+
try {
|
|
1008
|
+
result = await writeState(ATTACHMENT_NAMESPACE, [{ key: ref.key, value: JSON.stringify({ ...ref, owner, kind, ownerGeneration, value }), updatedAt: at, owner, kind, ownerGeneration }]);
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
if (error?.code === "AGENTIC_STATE_WRITE_UNSUPPORTED") {
|
|
1011
|
+
throw new DeploymentConfigError("ATTACHMENT_STORAGE_UNSUPPORTED: attachments require a client_state adapter (built-in support: D1)", { code: "ATTACHMENT_STORAGE_UNSUPPORTED" });
|
|
1012
|
+
}
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
if (result.upserted !== 1 || result.skipped !== 0) {
|
|
1016
|
+
throw new Error("ATTACHMENT_WRITE_INCOMPLETE: attachment was not stored");
|
|
1017
|
+
}
|
|
1018
|
+
return ref;
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
985
1022
|
// src/server/lib/agentic-fire.js
|
|
986
1023
|
import {
|
|
987
1024
|
assertValidDecision,
|
|
@@ -1192,6 +1229,7 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1192
1229
|
let upserted = 0;
|
|
1193
1230
|
let skipped = 0;
|
|
1194
1231
|
let deleted = 0;
|
|
1232
|
+
let unknown = 0;
|
|
1195
1233
|
const skippedEntries = [];
|
|
1196
1234
|
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
1197
1235
|
for (let i = 0; i < rootRowIndexes.length; i++) {
|
|
@@ -1203,9 +1241,10 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1203
1241
|
skippedEntries.push({ namespace: entry.namespace, key: entry.key });
|
|
1204
1242
|
}
|
|
1205
1243
|
}
|
|
1244
|
+
} else if (result.upserted === physicalRows.length && result.skipped === 0) {
|
|
1245
|
+
upserted = rootRowIndexes.length;
|
|
1206
1246
|
} else {
|
|
1207
|
-
|
|
1208
|
-
skipped = result.skipped;
|
|
1247
|
+
unknown = rootRowIndexes.length;
|
|
1209
1248
|
}
|
|
1210
1249
|
const cleanupOutcomes = Array.isArray(result.cleanupOutcomes) && result.cleanupOutcomes.length === cleanups.length ? result.cleanupOutcomes : null;
|
|
1211
1250
|
for (const { cleanupIndex, entry } of deletions) {
|
|
@@ -1216,22 +1255,26 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1216
1255
|
deleted++;
|
|
1217
1256
|
}
|
|
1218
1257
|
}
|
|
1219
|
-
return { upserted, skipped, deleted, skippedEntries };
|
|
1258
|
+
return { upserted, skipped, deleted, skippedEntries, ...unknown > 0 ? { unknown } : {} };
|
|
1220
1259
|
}
|
|
1221
1260
|
|
|
1222
1261
|
// src/server/lib/state-accessors.js
|
|
1223
1262
|
function createStateAccessors({ db, userId, userKey, maxStateValueBytes, now }) {
|
|
1224
1263
|
const nowFn = typeof now === "function" ? now : Date.now;
|
|
1225
1264
|
const valueCeiling = Number.isInteger(maxStateValueBytes) && maxStateValueBytes > 0 ? maxStateValueBytes : DEFAULT_MAX_STATE_VALUE_BYTES;
|
|
1226
|
-
const readState = async (namespace) => {
|
|
1265
|
+
const readState = async (namespace, { key } = {}) => {
|
|
1227
1266
|
if (typeof namespace !== "string" || !namespace.trim()) {
|
|
1228
1267
|
throw new TypeError("readState(namespace) requires a non-empty string");
|
|
1229
1268
|
}
|
|
1269
|
+
if (key !== void 0 && (typeof key !== "string" || !key.trim() || key.length > MAX_KEY_CHARS || INTERNAL_STATE_CHAR_RE.test(key))) {
|
|
1270
|
+
throw new TypeError(`readState key must be a non-empty string of at most ${MAX_KEY_CHARS} characters without control characters`);
|
|
1271
|
+
}
|
|
1230
1272
|
if (!db || typeof db.getClientState !== "function") return [];
|
|
1231
|
-
const rows = await db.getClientState(userId, namespace);
|
|
1273
|
+
const rows = await db.getClientState(userId, namespace, key === void 0 ? void 0 : { key });
|
|
1232
1274
|
return resolveClientStateEntries(
|
|
1233
|
-
|
|
1234
|
-
() =>
|
|
1275
|
+
// 老适配器可能忽略第三个参数,仍在解密前过滤根行,保持返回契约。
|
|
1276
|
+
key === void 0 ? rows : rows.filter((row) => row.key === key),
|
|
1277
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace), key === void 0 ? void 0 : { keyPrefix: chunkKeyPrefixFor(key) }),
|
|
1235
1278
|
(value) => decryptFromStorage(value, userKey)
|
|
1236
1279
|
);
|
|
1237
1280
|
};
|
|
@@ -1287,6 +1330,9 @@ function createStateAccessors({ db, userId, userKey, maxStateValueBytes, now })
|
|
|
1287
1330
|
namespace,
|
|
1288
1331
|
key: entry.key,
|
|
1289
1332
|
value: entry.value,
|
|
1333
|
+
owner: entry.owner,
|
|
1334
|
+
kind: entry.kind,
|
|
1335
|
+
ownerGeneration: entry.ownerGeneration,
|
|
1290
1336
|
updatedAt: entry.updatedAt ?? at,
|
|
1291
1337
|
...entry.version !== void 0 ? { version: entry.version } : {}
|
|
1292
1338
|
};
|
|
@@ -1618,7 +1664,7 @@ function projectTask(row, decryptedPayload, options = {}) {
|
|
|
1618
1664
|
// 凭据引用只是名字(cred_id),本体在 llm_credentials 表里,凭据字段本身
|
|
1619
1665
|
// 照旧被白名单挡在外面。
|
|
1620
1666
|
credRefs: payload.credRefs ?? null,
|
|
1621
|
-
// 整份 metadata
|
|
1667
|
+
// 整份 metadata 由 includeMetadata 控制,单条查询与 hook 回执会带。
|
|
1622
1668
|
...options.includeMetadata ? { metadata: payload.metadata ?? null } : {},
|
|
1623
1669
|
// 上一次没发出去的原因。reason 'stale' 表示错过触发时刻太久被判定不再补
|
|
1624
1670
|
// 发;其余是投递失败的错误信息。没有记录 → null。
|
|
@@ -1709,6 +1755,9 @@ function normalizeBeforeFireResult(result) {
|
|
|
1709
1755
|
return { messages: result };
|
|
1710
1756
|
}
|
|
1711
1757
|
if (result && typeof result === "object" && Array.isArray(result.messages)) {
|
|
1758
|
+
if (result.allowedToolNames !== void 0 && (!Array.isArray(result.allowedToolNames) || result.allowedToolNames.some((name) => typeof name !== "string" || !name.trim()))) {
|
|
1759
|
+
throw markPermanent(new TypeError("allowedToolNames must be an array of non-empty strings"), "AGENTIC_BAD_BEFORE_FIRE");
|
|
1760
|
+
}
|
|
1712
1761
|
return {
|
|
1713
1762
|
messages: result.messages,
|
|
1714
1763
|
maxToolIterations: result.maxToolIterations,
|
|
@@ -1717,7 +1766,8 @@ function normalizeBeforeFireResult(result) {
|
|
|
1717
1766
|
// 用真值。真正决定请求体带不带 tools 的规则住在 llm.js 的
|
|
1718
1767
|
// buildAiRequestBody(那边有测试钉着),这层只是不把空数组往下传。
|
|
1719
1768
|
tools: Array.isArray(result.tools) && result.tools.length > 0 ? result.tools : void 0,
|
|
1720
|
-
toolChoice: result.toolChoice
|
|
1769
|
+
toolChoice: result.toolChoice,
|
|
1770
|
+
allowedToolNames: result.allowedToolNames === void 0 ? void 0 : new Set(result.allowedToolNames)
|
|
1721
1771
|
};
|
|
1722
1772
|
}
|
|
1723
1773
|
throw markPermanent(new TypeError(BAD_BEFORE_FIRE_MESSAGE), "AGENTIC_BAD_BEFORE_FIRE");
|
|
@@ -1769,6 +1819,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1769
1819
|
const sessionId = task.id != null ? `sess_task_${task.id}${occurrenceSuffix(task)}` : `sess_${randomUUID()}`;
|
|
1770
1820
|
const messageIdBase = task.id != null ? `msg_task_${task.id}${occurrenceSuffix(task)}` : `msg_${randomUUID()}`;
|
|
1771
1821
|
const occurrenceMs = occurrenceMsOf(task);
|
|
1822
|
+
const storeAttachment = createAttachmentStore({ task, occurrenceMs, writeState, now: nowFn });
|
|
1823
|
+
const storeSettledAttachment = createAttachmentStore({ task, occurrenceMs, writeState: writeSettledState, now: nowFn });
|
|
1772
1824
|
const { emitResult } = createResultEmitter({
|
|
1773
1825
|
db: ctx.db,
|
|
1774
1826
|
task,
|
|
@@ -1791,17 +1843,37 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1791
1843
|
const row = await ctx.db.getTaskByUuid(uuid, task.user_id);
|
|
1792
1844
|
if (!row) return null;
|
|
1793
1845
|
const payload = JSON.parse(await decryptFromStorage(row.encrypted_payload, userKey));
|
|
1794
|
-
return projectTask(row, payload);
|
|
1846
|
+
return projectTask(row, payload, { includeMetadata: true });
|
|
1795
1847
|
} catch (error) {
|
|
1796
1848
|
console.warn("[amsg-server] scheduleTask: \u8BFB\u53D6\u649E\u8F66\u4EFB\u52A1\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
|
|
1797
1849
|
return null;
|
|
1798
1850
|
}
|
|
1799
1851
|
}
|
|
1852
|
+
const listTasks = async (options = {}) => {
|
|
1853
|
+
throwIfCancelled();
|
|
1854
|
+
const { status = "pending", limit = 100, offset = 0, includeMetadata = true } = options;
|
|
1855
|
+
if (!["pending", "sent", "failed", "all"].includes(status) || !Number.isInteger(limit) || limit < 1 || limit > 100 || !Number.isInteger(offset) || offset < 0) {
|
|
1856
|
+
throw new TypeError("listTasks requires a valid status, limit 1-100, and non-negative offset");
|
|
1857
|
+
}
|
|
1858
|
+
if (typeof ctx.db?.listTasks !== "function") {
|
|
1859
|
+
throw new DeploymentConfigError("AGENTIC_TASK_VIEW_UNSUPPORTED: adapter has no listTasks", { code: "AGENTIC_TASK_VIEW_UNSUPPORTED" });
|
|
1860
|
+
}
|
|
1861
|
+
const { tasks, total } = await ctx.db.listTasks(task.user_id, { status, limit, offset });
|
|
1862
|
+
const projected = await Promise.all(tasks.map(async (row) => projectTask(
|
|
1863
|
+
row,
|
|
1864
|
+
JSON.parse(await decryptFromStorage(row.encrypted_payload, userKey)),
|
|
1865
|
+
{ includeMetadata }
|
|
1866
|
+
)));
|
|
1867
|
+
return { tasks: projected, pagination: { total, limit, offset, hasMore: offset + tasks.length < total } };
|
|
1868
|
+
};
|
|
1800
1869
|
const scheduleTask = async (options) => {
|
|
1801
1870
|
throwIfCancelled();
|
|
1802
1871
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1803
1872
|
throw new TypeError("scheduleTask(options) \u9700\u8981\u4E00\u4E2A\u5BF9\u8C61\uFF0C\u81F3\u5C11\u5305\u542B { firstSendTime }");
|
|
1804
1873
|
}
|
|
1874
|
+
if (options.taskInput != null && (typeof options.taskInput !== "object" || Array.isArray(options.taskInput))) {
|
|
1875
|
+
throw new TypeError("scheduleTask: taskInput must be an object or null");
|
|
1876
|
+
}
|
|
1805
1877
|
if (typeof options.firstSendTime !== "string" || !options.firstSendTime.trim()) {
|
|
1806
1878
|
throw new RangeError("scheduleTask: firstSendTime \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u662F ISO 8601 \u5B57\u7B26\u4E32");
|
|
1807
1879
|
}
|
|
@@ -1896,7 +1968,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1896
1968
|
// 与凭据同列的投递配置:中转的非标准参数(thinking 之类)跟着任务走,
|
|
1897
1969
|
// 自排的后续任务不该突然掉档。
|
|
1898
1970
|
llmExtraBody: decryptedPayload.llmExtraBody ?? null,
|
|
1899
|
-
metadata
|
|
1971
|
+
metadata,
|
|
1972
|
+
taskInput: options.taskInput ?? null
|
|
1900
1973
|
};
|
|
1901
1974
|
const serializedTaskData = JSON.stringify(fullTaskData);
|
|
1902
1975
|
const sizeError = validateTaskPayloadSize(serializedTaskData);
|
|
@@ -1950,7 +2023,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1950
2023
|
created: true,
|
|
1951
2024
|
id: created.id ?? null,
|
|
1952
2025
|
uuid: created.uuid ?? uuid,
|
|
1953
|
-
nextSendAt: created.next_send_at ?? nextSendAt
|
|
2026
|
+
nextSendAt: created.next_send_at ?? nextSendAt,
|
|
2027
|
+
task: projectTask({ status: "pending", next_send_at: nextSendAt, ...created }, fullTaskData, { includeMetadata: true })
|
|
1954
2028
|
};
|
|
1955
2029
|
};
|
|
1956
2030
|
const cancelTask = async (uuid) => {
|
|
@@ -2018,7 +2092,11 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2018
2092
|
const stillPending = await ctx.db.getTaskByUuid(uuid, task.user_id);
|
|
2019
2093
|
return { renewed: false, reason: stillPending ? "in_flight" : "not_found" };
|
|
2020
2094
|
}
|
|
2021
|
-
return { renewed: true, uuid, nextSendAt: nextSendAtIso
|
|
2095
|
+
return { renewed: true, uuid, nextSendAt: nextSendAtIso, task: projectTask(
|
|
2096
|
+
{ ...row, next_send_at: nextSendAtIso, retry_count: 0 },
|
|
2097
|
+
payload,
|
|
2098
|
+
{ includeMetadata: true }
|
|
2099
|
+
) };
|
|
2022
2100
|
};
|
|
2023
2101
|
const resolveLlmCredential2 = async (credId) => {
|
|
2024
2102
|
if (typeof credId !== "string" || !credId.trim()) {
|
|
@@ -2035,7 +2113,9 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2035
2113
|
readState,
|
|
2036
2114
|
writeState,
|
|
2037
2115
|
emitResult,
|
|
2116
|
+
storeAttachment,
|
|
2038
2117
|
scheduleTask,
|
|
2118
|
+
listTasks,
|
|
2039
2119
|
cancelTask,
|
|
2040
2120
|
renewTask,
|
|
2041
2121
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2068,8 +2148,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2068
2148
|
readState,
|
|
2069
2149
|
writeState,
|
|
2070
2150
|
emitResult,
|
|
2151
|
+
storeAttachment,
|
|
2071
2152
|
scratch,
|
|
2072
2153
|
scheduleTask,
|
|
2154
|
+
listTasks,
|
|
2073
2155
|
cancelTask,
|
|
2074
2156
|
renewTask,
|
|
2075
2157
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2128,7 +2210,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2128
2210
|
scratch,
|
|
2129
2211
|
readState,
|
|
2130
2212
|
writeState: writeSettledState,
|
|
2131
|
-
emitResult
|
|
2213
|
+
emitResult,
|
|
2214
|
+
storeAttachment: storeSettledAttachment
|
|
2132
2215
|
});
|
|
2133
2216
|
}
|
|
2134
2217
|
}
|
|
@@ -2143,8 +2226,10 @@ async function runFireChain({
|
|
|
2143
2226
|
readState,
|
|
2144
2227
|
writeState,
|
|
2145
2228
|
emitResult,
|
|
2229
|
+
storeAttachment,
|
|
2146
2230
|
scratch,
|
|
2147
2231
|
scheduleTask,
|
|
2232
|
+
listTasks,
|
|
2148
2233
|
cancelTask,
|
|
2149
2234
|
renewTask,
|
|
2150
2235
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2243,15 +2328,35 @@ async function runFireChain({
|
|
|
2243
2328
|
readState,
|
|
2244
2329
|
writeState,
|
|
2245
2330
|
emitResult,
|
|
2331
|
+
storeAttachment,
|
|
2246
2332
|
scheduleTask,
|
|
2333
|
+
listTasks,
|
|
2247
2334
|
cancelTask,
|
|
2248
2335
|
renewTask,
|
|
2249
2336
|
resolveLlmCredential: resolveLlmCredential2
|
|
2250
2337
|
});
|
|
2251
2338
|
const decision = await hooks.onLLMOutput(sessionCtx);
|
|
2252
2339
|
ctx.throwIfCancelled();
|
|
2340
|
+
const rawCalls = decision?.decision === "tool-request" ? extractToolCallsFromDecision(decision) : [];
|
|
2341
|
+
const validId = (call) => typeof call?.id === "string" && call.id.trim().length > 0;
|
|
2342
|
+
const nativeCalls = Array.isArray(assistantMessage.tool_calls) ? assistantMessage.tool_calls : [];
|
|
2343
|
+
const usedIds = new Set([...rawCalls, ...nativeCalls].filter(validId).map((call) => call.id));
|
|
2344
|
+
const malformedIds = /* @__PURE__ */ new Set();
|
|
2345
|
+
const toolCalls = rawCalls.map((call, index) => {
|
|
2346
|
+
let id = validId(call) ? call.id : `call_${iteration + 1}_${index + 1}`;
|
|
2347
|
+
if (!validId(call)) {
|
|
2348
|
+
while (usedIds.has(id)) id += "_";
|
|
2349
|
+
usedIds.add(id);
|
|
2350
|
+
}
|
|
2351
|
+
if (typeof call?.function?.name !== "string" || !call.function.name.trim()) {
|
|
2352
|
+
malformedIds.add(id);
|
|
2353
|
+
return { id, type: "function", function: { name: "invalid_tool_call", arguments: "{}" } };
|
|
2354
|
+
}
|
|
2355
|
+
return { ...call, id };
|
|
2356
|
+
});
|
|
2357
|
+
const validationDecision = decision?.decision === "tool-request" && Array.isArray(decision.toolCalls) && decision.toolCalls.length > 0 ? { ...decision, toolCalls } : decision;
|
|
2253
2358
|
try {
|
|
2254
|
-
assertValidDecision(
|
|
2359
|
+
assertValidDecision(validationDecision, { inlineToolCalls: true });
|
|
2255
2360
|
} catch (error) {
|
|
2256
2361
|
throw markPermanent(error, "AGENTIC_BAD_DECISION");
|
|
2257
2362
|
}
|
|
@@ -2282,14 +2387,19 @@ async function runFireChain({
|
|
|
2282
2387
|
});
|
|
2283
2388
|
return { handled: true, result: { success: true, messagesSent, status: "finished", iterations: iteration + 1 } };
|
|
2284
2389
|
}
|
|
2285
|
-
const toolCalls = extractToolCallsFromDecision(decision);
|
|
2286
2390
|
if (toolCalls.length === 0) {
|
|
2287
2391
|
throw taggedRetryableError(
|
|
2288
2392
|
"AGENTIC_EMPTY_TOOL_REQUEST: tool-request decision carried no toolCalls (neither decision.toolCalls nor pushPayloads[].toolCalls)",
|
|
2289
2393
|
"AGENTIC_EMPTY_TOOL_REQUEST"
|
|
2290
2394
|
);
|
|
2291
2395
|
}
|
|
2292
|
-
|
|
2396
|
+
const permittedCalls = toolCalls.filter((call) => !malformedIds.has(call.id) && (normalized.allowedToolNames === void 0 || normalized.allowedToolNames.has(call.function.name)));
|
|
2397
|
+
const deniedResults = toolCalls.filter((call) => !permittedCalls.includes(call)).map((call) => ({
|
|
2398
|
+
role: "tool",
|
|
2399
|
+
tool_call_id: call.id,
|
|
2400
|
+
content: JSON.stringify({ error: malformedIds.has(call.id) ? { code: "INVALID_TOOL_CALL", message: "Tool call requires a function name" } : { code: "TOOL_NOT_ALLOWED", message: "Tool execution is not allowed for this fire" } })
|
|
2401
|
+
}));
|
|
2402
|
+
if (permittedCalls.length > 0 && typeof hooks.executeToolCalls !== "function") {
|
|
2293
2403
|
throw new DeploymentConfigError(
|
|
2294
2404
|
"AGENTIC_CONFIG_ERROR: onLLMOutput returned tool-request but hooks.executeToolCalls is not configured",
|
|
2295
2405
|
{ code: "AGENTIC_CONFIG_ERROR" }
|
|
@@ -2301,7 +2411,7 @@ async function runFireChain({
|
|
|
2301
2411
|
let toolResults;
|
|
2302
2412
|
try {
|
|
2303
2413
|
ctx.throwIfCancelled();
|
|
2304
|
-
toolResults = await hooks.executeToolCalls(
|
|
2414
|
+
toolResults = permittedCalls.length > 0 ? await hooks.executeToolCalls(permittedCalls, sessionCtx) : [];
|
|
2305
2415
|
ctx.throwIfCancelled();
|
|
2306
2416
|
if (!Array.isArray(toolResults)) {
|
|
2307
2417
|
throw new TypeError('executeToolCalls must resolve to an array of { tool_call_id, role: "tool", content }');
|
|
@@ -2309,16 +2419,17 @@ async function runFireChain({
|
|
|
2309
2419
|
} catch (error) {
|
|
2310
2420
|
ctx.throwIfCancelled();
|
|
2311
2421
|
if (isTaskCancelledError(error)) throw error;
|
|
2312
|
-
toolResults =
|
|
2422
|
+
toolResults = permittedCalls.map((toolCall) => ({
|
|
2313
2423
|
tool_call_id: toolCall && typeof toolCall === "object" && typeof toolCall.id === "string" ? toolCall.id : "",
|
|
2314
2424
|
role: "tool",
|
|
2315
2425
|
content: `Tool execution failed: ${error?.message ?? String(error)}`
|
|
2316
2426
|
}));
|
|
2317
2427
|
}
|
|
2318
|
-
|
|
2319
|
-
const
|
|
2320
|
-
const
|
|
2321
|
-
const
|
|
2428
|
+
toolResults = [...toolResults, ...deniedResults];
|
|
2429
|
+
const declared = nativeCalls.filter(validId).map((call) => toolCalls.find((item) => item.id === call.id) || call);
|
|
2430
|
+
const nativeIds = new Set(declared.map((call) => call.id));
|
|
2431
|
+
const synthesized = toolCalls.filter((call) => !nativeIds.has(call.id));
|
|
2432
|
+
const assistantWithTools = { ...assistantMessage, tool_calls: [...declared, ...synthesized] };
|
|
2322
2433
|
messages = [...messages.slice(0, -1), assistantWithTools, ...toolResults];
|
|
2323
2434
|
}
|
|
2324
2435
|
throw taggedRetryableError(
|
|
@@ -3392,6 +3503,9 @@ function createScheduleMessageHandler(ctx) {
|
|
|
3392
3503
|
owner: payload.owner,
|
|
3393
3504
|
kind: payload.kind,
|
|
3394
3505
|
ownerGeneration: payload.ownerGeneration,
|
|
3506
|
+
// Input is persisted with the task in the same create/supersede transaction.
|
|
3507
|
+
// Shared credentials remain references and continue to resolve at fire time.
|
|
3508
|
+
taskInput: payload.taskInput ?? null,
|
|
3395
3509
|
metadata: payload.metadata || {}
|
|
3396
3510
|
};
|
|
3397
3511
|
const serializedTaskData = JSON.stringify(fullTaskData);
|
|
@@ -3599,7 +3713,7 @@ async function runScheduledTick(ctx) {
|
|
|
3599
3713
|
}
|
|
3600
3714
|
async function cleanupExpiredClientState(ctx) {
|
|
3601
3715
|
if (typeof ctx.db.cleanupClientState !== "function") return;
|
|
3602
|
-
const targets = planClientStateCleanup(ctx.clientStateTtl, Date.now());
|
|
3716
|
+
const targets = planClientStateCleanup({ [ATTACHMENT_NAMESPACE]: ATTACHMENT_RETENTION_DAYS, ...ctx.clientStateTtl }, Date.now());
|
|
3603
3717
|
if (targets.length === 0) return;
|
|
3604
3718
|
try {
|
|
3605
3719
|
await ctx.db.cleanupClientState(targets);
|
|
@@ -4259,6 +4373,9 @@ function createUpdateMessageHandler(ctx) {
|
|
|
4259
4373
|
if (!isPlainObject(updates)) {
|
|
4260
4374
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF" } } };
|
|
4261
4375
|
}
|
|
4376
|
+
if (Object.prototype.hasOwnProperty.call(updates, "taskInput")) {
|
|
4377
|
+
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "taskInput \u53EA\u80FD\u5728\u521B\u5EFA\u6216\u66FF\u6362\u4EFB\u52A1\u65F6\u8BBE\u7F6E", details: { invalidFields: ["taskInput"] } } } };
|
|
4378
|
+
}
|
|
4262
4379
|
if (updates.nextSendAt && !isValidISO8601(updates.nextSendAt)) {
|
|
4263
4380
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["nextSendAt"] } } } };
|
|
4264
4381
|
}
|
|
@@ -6175,24 +6292,33 @@ var D1Adapter = class {
|
|
|
6175
6292
|
return result;
|
|
6176
6293
|
}
|
|
6177
6294
|
/**
|
|
6178
|
-
*
|
|
6295
|
+
* Entries of one namespace (values still encrypted); optional exact/prefix selection.
|
|
6179
6296
|
*
|
|
6180
6297
|
* @param {string} userId
|
|
6181
6298
|
* @param {string} namespace
|
|
6182
6299
|
* @returns {Promise<Array<{ namespace: string, key: string, value: string, updated_at: number }>>}
|
|
6183
6300
|
*/
|
|
6184
|
-
async getClientState(userId, namespace) {
|
|
6301
|
+
async getClientState(userId, namespace, options = {}) {
|
|
6302
|
+
const params = [userId, namespace];
|
|
6303
|
+
let selector = "";
|
|
6304
|
+
if (options.key !== void 0) {
|
|
6305
|
+
selector = " AND key = ?";
|
|
6306
|
+
params.push(options.key);
|
|
6307
|
+
} else if (options.keyPrefix !== void 0) {
|
|
6308
|
+
selector = " AND key >= ? AND key < ?";
|
|
6309
|
+
params.push(options.keyPrefix, prefixRangeEnd(options.keyPrefix));
|
|
6310
|
+
}
|
|
6185
6311
|
const res = await this._db.prepare(
|
|
6186
6312
|
`SELECT namespace, key, value, updated_at
|
|
6187
6313
|
FROM client_state
|
|
6188
|
-
WHERE user_id = ? AND namespace =
|
|
6314
|
+
WHERE user_id = ? AND namespace = ?${selector}
|
|
6189
6315
|
ORDER BY key ASC`
|
|
6190
|
-
).bind(
|
|
6316
|
+
).bind(...params).all();
|
|
6191
6317
|
return res.results || [];
|
|
6192
6318
|
}
|
|
6193
6319
|
/**
|
|
6194
6320
|
* 例行清理:把指定命名空间下太久没更新的条目删掉(run-tick 每跳顺手调,
|
|
6195
|
-
*
|
|
6321
|
+
* 附件默认保留 30 天,宿主 `clientStateTtl` 可覆盖或配置其他命名空间)。
|
|
6196
6322
|
*
|
|
6197
6323
|
* 不限用户——「这个命名空间只留最近 N 天」是命名空间级的约定,单用户部署
|
|
6198
6324
|
* 下也就是这一个用户的行。指令由 lib/client-state-store.js 的
|
|
@@ -6924,8 +7050,9 @@ function createClientStateHandler(ctx) {
|
|
|
6924
7050
|
if (typeof db.upsertClientState !== "function") {
|
|
6925
7051
|
return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
6926
7052
|
}
|
|
6927
|
-
const { upserted, skipped, deleted, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
|
|
7053
|
+
const { upserted, skipped, deleted, skippedEntries, unknown } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
|
|
6928
7054
|
const data = { upserted, skipped };
|
|
7055
|
+
if (unknown > 0) data.unknown = unknown;
|
|
6929
7056
|
if (deleted > 0) data.deleted = deleted;
|
|
6930
7057
|
if (skippedEntries.length > 0) data.skippedEntries = skippedEntries;
|
|
6931
7058
|
if (rejected.length > 0) data.rejected = rejected;
|
|
@@ -6938,7 +7065,12 @@ function createClientStateHandler(ctx) {
|
|
|
6938
7065
|
const gate = requireUserId(headers);
|
|
6939
7066
|
if (gate.error) return gate.error;
|
|
6940
7067
|
const { userId } = gate;
|
|
6941
|
-
const
|
|
7068
|
+
const query = new URL(url, "https://dummy").searchParams;
|
|
7069
|
+
const namespace = query.get("namespace") || "";
|
|
7070
|
+
const key = query.get("key");
|
|
7071
|
+
if (key !== null && (!key.trim() || key.length > MAX_KEY_CHARS || INTERNAL_STATE_CHAR_RE.test(key))) {
|
|
7072
|
+
return err3(400, "INVALID_STATE_KEY", "key must be non-empty, at most 256 characters, without control characters");
|
|
7073
|
+
}
|
|
6942
7074
|
if (!namespace.trim()) return err3(400, "NAMESPACE_REQUIRED", "\u5FC5\u987B\u63D0\u4F9B namespace \u67E5\u8BE2\u53C2\u6570");
|
|
6943
7075
|
if (INTERNAL_STATE_CHAR_RE.test(namespace)) {
|
|
6944
7076
|
return err3(400, "INVALID_STATE_NAMESPACE", "namespace \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09");
|
|
@@ -6947,10 +7079,10 @@ function createClientStateHandler(ctx) {
|
|
|
6947
7079
|
return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
6948
7080
|
}
|
|
6949
7081
|
const userKey = await deriveUserEncryptionKey(userId, masterKey);
|
|
6950
|
-
const rows = await db.getClientState(userId, namespace);
|
|
7082
|
+
const rows = await db.getClientState(userId, namespace, key === null ? void 0 : { key });
|
|
6951
7083
|
const decrypted = await resolveClientStateEntries(
|
|
6952
|
-
rows,
|
|
6953
|
-
() => db.getClientState(userId, chunkNamespaceFor(namespace)),
|
|
7084
|
+
key === null ? rows : rows.filter((row) => row.key === key),
|
|
7085
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace), key === null ? void 0 : { keyPrefix: chunkKeyPrefixFor(key) }),
|
|
6954
7086
|
(value) => decryptFromStorage(value, userKey)
|
|
6955
7087
|
);
|
|
6956
7088
|
const encryptedResponse = await encryptPayload({ namespace, entries: decrypted }, userKey);
|
|
@@ -7028,7 +7160,7 @@ function createClientStateNamespacesHandler(ctx) {
|
|
|
7028
7160
|
}
|
|
7029
7161
|
|
|
7030
7162
|
// src/server/lib/version.js
|
|
7031
|
-
var SERVER_VERSION = true ? "3.0.0" : "0.0.0-dev";
|
|
7163
|
+
var SERVER_VERSION = true ? "3.1.0-next.0" : "0.0.0-dev";
|
|
7032
7164
|
|
|
7033
7165
|
// src/server/handlers/capabilities.js
|
|
7034
7166
|
var SERVER_FEATURES = Object.freeze([
|
|
@@ -7144,7 +7276,12 @@ var SERVER_FEATURES = Object.freeze([
|
|
|
7144
7276
|
"max-generation-retries",
|
|
7145
7277
|
// onBeforeFire 认 { defer: { afterMs } }:这次不生成,过一会儿再来问(不占重试
|
|
7146
7278
|
// 次数);onFireSettled 的 status 多一种 'deferred',带 retryAfter。
|
|
7147
|
-
"before-fire-defer"
|
|
7279
|
+
"before-fire-defer",
|
|
7280
|
+
"task-input",
|
|
7281
|
+
"client-state-key",
|
|
7282
|
+
"agentic-list-tasks",
|
|
7283
|
+
"agentic-tool-allowlist",
|
|
7284
|
+
"attachments"
|
|
7148
7285
|
]);
|
|
7149
7286
|
function createCapabilitiesHandler(ctx) {
|
|
7150
7287
|
async function GET(url, headers) {
|
|
@@ -8718,6 +8855,7 @@ export {
|
|
|
8718
8855
|
timingSafeEqualBytes,
|
|
8719
8856
|
deriveUserEncryptionKey,
|
|
8720
8857
|
decryptPayload,
|
|
8858
|
+
encryptPayload,
|
|
8721
8859
|
encryptForStorage,
|
|
8722
8860
|
decryptFromStorage,
|
|
8723
8861
|
CRED_ID_MAX_LENGTH,
|
package/dist/cloudflare.cjs
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
var _chunk3NIYNYZYcjs = require('./chunk-3NIYNYZY.cjs');
|
|
32
33
|
require('./chunk-7NMQFTDJ.cjs');
|
|
33
34
|
|
|
34
35
|
|
|
@@ -60,4 +61,5 @@ require('./chunk-7NMQFTDJ.cjs');
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
exports.DEFAULT_MAX_DELIVERY_RETRIES = _chunk3NIYNYZYcjs.DEFAULT_MAX_DELIVERY_RETRIES; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunk3NIYNYZYcjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_DEFER_AFTER_MS = _chunk3NIYNYZYcjs.MAX_DEFER_AFTER_MS; exports.MAX_PUSH_PAYLOAD_BYTES = _chunk3NIYNYZYcjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunk3NIYNYZYcjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunk3NIYNYZYcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunk3NIYNYZYcjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunk3NIYNYZYcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunk3NIYNYZYcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunk3NIYNYZYcjs.advanceOccurrence; exports.createD1Adapter = _chunk3NIYNYZYcjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunk3NIYNYZYcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunk3NIYNYZYcjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunk3NIYNYZYcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunk3NIYNYZYcjs.decryptFromStorage; exports.decryptPayload = _chunk3NIYNYZYcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunk3NIYNYZYcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunk3NIYNYZYcjs.encryptForStorage; exports.encryptPayload = _chunk3NIYNYZYcjs.encryptPayload; exports.ensureSchema = _chunk3NIYNYZYcjs.ensureSchema; exports.getSchemaVersion = _chunk3NIYNYZYcjs.getSchemaVersion; exports.isNonRetryableError = _chunk3NIYNYZYcjs.isNonRetryableError; exports.isValidTimeZoneId = _chunk3NIYNYZYcjs.isValidTimeZoneId; exports.measurePushPayload = _chunk3NIYNYZYcjs.measurePushPayload; exports.nextFutureOccurrence = _chunk3NIYNYZYcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunk3NIYNYZYcjs.planNextOccurrence; exports.readRequestBody = _chunk3NIYNYZYcjs.readRequestBody; exports.runScheduledTick = _chunk3NIYNYZYcjs.runScheduledTick; exports.runTask = _chunk3NIYNYZYcjs.runTask; exports.summarizeErrorCause = _chunk3NIYNYZYcjs.summarizeErrorCause;
|
package/dist/cloudflare.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export { getSchemaVersion, ensureSchema, SCHEMA_VERSION } from "./lib/schema-ver
|
|
|
8
8
|
export { summarizeErrorCause, NonRetryableError, isNonRetryableError } from "./lib/errors.js";
|
|
9
9
|
export { isValidTimeZoneId, advanceOccurrence, nextFutureOccurrence, planNextOccurrence } from "./lib/recurrence.js";
|
|
10
10
|
export { readRequestBody, DEFAULT_MAX_REQUEST_BODY_BYTES } from "./lib/request.js";
|
|
11
|
-
export { deriveUserEncryptionKey, decryptPayload, encryptForStorage, decryptFromStorage } from "./lib/encryption.js";
|
|
11
|
+
export { deriveUserEncryptionKey, decryptPayload, encryptPayload, encryptForStorage, decryptFromStorage } from "./lib/encryption.js";
|
package/dist/cloudflare.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
decryptPayload,
|
|
18
18
|
deriveUserEncryptionKey,
|
|
19
19
|
encryptForStorage,
|
|
20
|
+
encryptPayload,
|
|
20
21
|
ensureSchema,
|
|
21
22
|
getSchemaVersion,
|
|
22
23
|
isNonRetryableError,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
runScheduledTick,
|
|
29
30
|
runTask,
|
|
30
31
|
summarizeErrorCause
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-6TCZEACV.mjs";
|
|
32
33
|
import "./chunk-GN44PST5.mjs";
|
|
33
34
|
export {
|
|
34
35
|
DEFAULT_MAX_DELIVERY_RETRIES,
|
|
@@ -49,6 +50,7 @@ export {
|
|
|
49
50
|
decryptPayload,
|
|
50
51
|
deriveUserEncryptionKey,
|
|
51
52
|
encryptForStorage,
|
|
53
|
+
encryptPayload,
|
|
52
54
|
ensureSchema,
|
|
53
55
|
getSchemaVersion,
|
|
54
56
|
isNonRetryableError,
|