@rei-standard/amsg-server 2.6.0-next.3 → 2.6.0-next.5
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/dist/{chunk-PPPWETND.mjs → chunk-ES2HUXYZ.mjs} +249 -50
- package/dist/{chunk-L7WMM33Q.cjs → chunk-IFI4EWDW.cjs} +249 -50
- package/dist/{cloudflare-DlLTdCp0.d.cts → cloudflare-DM_Njij8.d.cts} +387 -56
- package/dist/{cloudflare-DlLTdCp0.d.ts → cloudflare-DM_Njij8.d.ts} +387 -56
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.mjs +1 -1
- package/dist/index.cjs +11 -11
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
|
@@ -485,6 +485,81 @@ import {
|
|
|
485
485
|
extractAssistantMessage,
|
|
486
486
|
extractToolCallsFromDecision
|
|
487
487
|
} from "@rei-standard/amsg-shared";
|
|
488
|
+
|
|
489
|
+
// src/server/lib/state-chunks.js
|
|
490
|
+
import { chunkReasoningByUtf8Bytes } from "@rei-standard/amsg-shared";
|
|
491
|
+
var STATE_CHUNK_SLICE_BYTES = 200 * 1024;
|
|
492
|
+
var DEFAULT_MAX_STATE_VALUE_BYTES = 5 * 1024 * 1024;
|
|
493
|
+
var INTERNAL_STATE_CHAR_RE = /[\u0000-\u001f]/;
|
|
494
|
+
var SEP = "";
|
|
495
|
+
var CHUNK_NS_PREFIX = `${SEP}amsg-chunks${SEP}`;
|
|
496
|
+
var ROOT_MARKER_PREFIX = `${SEP}amsg-chunked${SEP}v1${SEP}`;
|
|
497
|
+
function chunkNamespaceFor(namespace) {
|
|
498
|
+
return CHUNK_NS_PREFIX + namespace;
|
|
499
|
+
}
|
|
500
|
+
function chunkKeyFor(key, index) {
|
|
501
|
+
return `${key}${SEP}${index}`;
|
|
502
|
+
}
|
|
503
|
+
function chunkKeyPrefixFor(key) {
|
|
504
|
+
return `${key}${SEP}`;
|
|
505
|
+
}
|
|
506
|
+
function buildChunkedRootValue(chunkCount) {
|
|
507
|
+
return `${ROOT_MARKER_PREFIX}${chunkCount}`;
|
|
508
|
+
}
|
|
509
|
+
function parseChunkedRootCount(value) {
|
|
510
|
+
if (typeof value !== "string" || !value.startsWith(ROOT_MARKER_PREFIX)) return null;
|
|
511
|
+
const raw = value.slice(ROOT_MARKER_PREFIX.length);
|
|
512
|
+
if (!/^[1-9][0-9]*$/.test(raw)) return null;
|
|
513
|
+
return Number(raw);
|
|
514
|
+
}
|
|
515
|
+
function splitStateValue(value) {
|
|
516
|
+
return chunkReasoningByUtf8Bytes(value, STATE_CHUNK_SLICE_BYTES);
|
|
517
|
+
}
|
|
518
|
+
async function resolveClientStateEntries(rows, fetchChunkRows, decryptValue) {
|
|
519
|
+
let chunkMap = null;
|
|
520
|
+
const loadChunks = async () => {
|
|
521
|
+
if (chunkMap === null) {
|
|
522
|
+
const chunkRows = await fetchChunkRows();
|
|
523
|
+
chunkMap = new Map(chunkRows.map((row) => [row.key, row]));
|
|
524
|
+
}
|
|
525
|
+
return chunkMap;
|
|
526
|
+
};
|
|
527
|
+
const entries = [];
|
|
528
|
+
for (const row of rows) {
|
|
529
|
+
const count = parseChunkedRootCount(row.value);
|
|
530
|
+
if (count === null) {
|
|
531
|
+
entries.push({
|
|
532
|
+
namespace: row.namespace,
|
|
533
|
+
key: row.key,
|
|
534
|
+
value: await decryptValue(row.value),
|
|
535
|
+
updatedAt: row.updated_at
|
|
536
|
+
});
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const map = await loadChunks();
|
|
540
|
+
const chunkRows = [];
|
|
541
|
+
let intact = true;
|
|
542
|
+
for (let i = 0; i < count; i++) {
|
|
543
|
+
const chunk = map.get(chunkKeyFor(row.key, i));
|
|
544
|
+
if (!chunk || chunk.updated_at !== row.updated_at) {
|
|
545
|
+
intact = false;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
chunkRows.push(chunk);
|
|
549
|
+
}
|
|
550
|
+
if (!intact) continue;
|
|
551
|
+
const parts = await Promise.all(chunkRows.map((chunk) => decryptValue(chunk.value)));
|
|
552
|
+
entries.push({
|
|
553
|
+
namespace: row.namespace,
|
|
554
|
+
key: row.key,
|
|
555
|
+
value: parts.join(""),
|
|
556
|
+
updatedAt: row.updated_at
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return entries;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/server/lib/agentic-fire.js
|
|
488
563
|
var DEFAULT_MAX_TOOL_ITERATIONS = 5;
|
|
489
564
|
var DEFAULT_TOTAL_TIMEOUT_MS = 24e4;
|
|
490
565
|
var SLEEP_BETWEEN_MESSAGES_MS = 1500;
|
|
@@ -523,7 +598,7 @@ function normalizeBeforeFireResult(result) {
|
|
|
523
598
|
};
|
|
524
599
|
}
|
|
525
600
|
throw new TypeError(
|
|
526
|
-
"AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs? } | null"
|
|
601
|
+
"AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs? } | { skip: true } | null"
|
|
527
602
|
);
|
|
528
603
|
}
|
|
529
604
|
function firstPositiveInt(values, fallback) {
|
|
@@ -551,21 +626,25 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
551
626
|
}
|
|
552
627
|
if (!ctx.db || typeof ctx.db.getClientState !== "function") return [];
|
|
553
628
|
const rows = await ctx.db.getClientState(task.user_id, namespace);
|
|
554
|
-
return
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
value
|
|
558
|
-
|
|
559
|
-
})));
|
|
629
|
+
return resolveClientStateEntries(
|
|
630
|
+
rows,
|
|
631
|
+
() => ctx.db.getClientState(task.user_id, chunkNamespaceFor(namespace)),
|
|
632
|
+
(value) => decryptFromStorage(value, userKey)
|
|
633
|
+
);
|
|
560
634
|
};
|
|
635
|
+
const scratch = {};
|
|
561
636
|
const fireCtx = Object.freeze({
|
|
562
637
|
task: buildHookTask(task, decryptedPayload),
|
|
563
638
|
userId: task.user_id,
|
|
564
639
|
readState,
|
|
565
|
-
now: new Date(nowFn())
|
|
640
|
+
now: new Date(nowFn()),
|
|
641
|
+
scratch
|
|
566
642
|
});
|
|
567
643
|
const before = await hooks.onBeforeFire(fireCtx);
|
|
568
644
|
if (before == null) return { handled: false };
|
|
645
|
+
if (typeof before === "object" && before.skip === true) {
|
|
646
|
+
return { handled: true, result: { success: true, messagesSent: 0, status: "skipped", iterations: 0 } };
|
|
647
|
+
}
|
|
569
648
|
const normalized = normalizeBeforeFireResult(before);
|
|
570
649
|
const maxToolIterations = firstPositiveInt(
|
|
571
650
|
[normalized.maxToolIterations, ctx.maxToolIterations],
|
|
@@ -597,7 +676,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
597
676
|
contactName: decryptedPayload.contactName,
|
|
598
677
|
avatarUrl: decryptedPayload.avatarUrl || void 0,
|
|
599
678
|
charId: decryptedPayload.charId,
|
|
600
|
-
metadata: decryptedPayload.metadata
|
|
679
|
+
metadata: decryptedPayload.metadata,
|
|
680
|
+
scratch
|
|
601
681
|
});
|
|
602
682
|
const decision = await hooks.onLLMOutput(sessionCtx);
|
|
603
683
|
assertValidDecision(decision, { inlineToolCalls: true });
|
|
@@ -1339,7 +1419,14 @@ function createMessagesHandler(ctx) {
|
|
|
1339
1419
|
status: task.status,
|
|
1340
1420
|
retryCount: task.retry_count,
|
|
1341
1421
|
createdAt: task.created_at,
|
|
1342
|
-
updatedAt: task.updated_at
|
|
1422
|
+
updatedAt: task.updated_at,
|
|
1423
|
+
// Character ownership / client-side task identity, pulled from the
|
|
1424
|
+
// scheduling host's metadata so it can filter tasks by character
|
|
1425
|
+
// (contactName can collide across characters). Only these two
|
|
1426
|
+
// metadata fields are surfaced — the rest of metadata may hold
|
|
1427
|
+
// host-private data and stays server-side. Absent → null.
|
|
1428
|
+
charId: decrypted.metadata?.charId ?? null,
|
|
1429
|
+
clientTaskId: decrypted.metadata?.amsgClientTaskId ?? null
|
|
1343
1430
|
};
|
|
1344
1431
|
}));
|
|
1345
1432
|
const responsePayload = {
|
|
@@ -1439,6 +1526,9 @@ var UPDATABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
1439
1526
|
"created_at",
|
|
1440
1527
|
"updated_at"
|
|
1441
1528
|
]);
|
|
1529
|
+
function escapeLikePrefix(prefix) {
|
|
1530
|
+
return prefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
1531
|
+
}
|
|
1442
1532
|
var D1Adapter = class {
|
|
1443
1533
|
/** @param {{ prepare: (sql: string) => any }} db - Cloudflare D1 binding */
|
|
1444
1534
|
constructor(db) {
|
|
@@ -1620,6 +1710,10 @@ var D1Adapter = class {
|
|
|
1620
1710
|
* than the stored row (updatedAt strictly lower) is skipped; equal or
|
|
1621
1711
|
* newer overwrites. Values arrive pre-encrypted (the handler encrypts).
|
|
1622
1712
|
*
|
|
1713
|
+
* `cleanups` 是分块存储的清理项(见 lib/state-chunks.js):在同一 batch 里
|
|
1714
|
+
* 先于 upsert 执行,按 (namespace, key 前缀) 删掉旧写入留下的切片行;
|
|
1715
|
+
* `updated_at <= ?` 条件保证陈旧批次删不动更新写入的行。
|
|
1716
|
+
*
|
|
1623
1717
|
* Uses D1's batch() — one network round trip for the whole set (implicit
|
|
1624
1718
|
* transaction). The client calls this endpoint inside its few-seconds
|
|
1625
1719
|
* background window, so N sequential round trips could eat the whole
|
|
@@ -1628,36 +1722,44 @@ var D1Adapter = class {
|
|
|
1628
1722
|
*
|
|
1629
1723
|
* @param {string} userId
|
|
1630
1724
|
* @param {Array<{ namespace: string, key: string, value: string, updatedAt: number }>} entries
|
|
1631
|
-
* @
|
|
1725
|
+
* @param {Array<{ namespace: string, keyPrefix: string, updatedAt: number }>} [cleanups]
|
|
1726
|
+
* @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
|
|
1727
|
+
* `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
|
|
1632
1728
|
*/
|
|
1633
|
-
async upsertClientState(userId, entries) {
|
|
1729
|
+
async upsertClientState(userId, entries, cleanups = []) {
|
|
1634
1730
|
const UPSERT_SQL = `INSERT INTO client_state (user_id, namespace, key, value, updated_at)
|
|
1635
1731
|
VALUES (?, ?, ?, ?, ?)
|
|
1636
1732
|
ON CONFLICT (user_id, namespace, key) DO UPDATE SET
|
|
1637
1733
|
value = excluded.value,
|
|
1638
1734
|
updated_at = excluded.updated_at
|
|
1639
1735
|
WHERE excluded.updated_at >= client_state.updated_at`;
|
|
1736
|
+
const CLEANUP_SQL = `DELETE FROM client_state
|
|
1737
|
+
WHERE user_id = ? AND namespace = ? AND key LIKE ? ESCAPE '\\' AND updated_at <= ?`;
|
|
1738
|
+
const buildStatements = () => [
|
|
1739
|
+
...cleanups.map(
|
|
1740
|
+
(c) => this._db.prepare(CLEANUP_SQL).bind(userId, c.namespace, `${escapeLikePrefix(c.keyPrefix)}%`, c.updatedAt)
|
|
1741
|
+
),
|
|
1742
|
+
...entries.map(
|
|
1743
|
+
(entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt)
|
|
1744
|
+
)
|
|
1745
|
+
];
|
|
1640
1746
|
let results;
|
|
1641
1747
|
if (typeof this._db.batch === "function") {
|
|
1642
|
-
|
|
1643
|
-
(entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt)
|
|
1644
|
-
);
|
|
1645
|
-
results = await this._db.batch(statements);
|
|
1748
|
+
results = await this._db.batch(buildStatements());
|
|
1646
1749
|
} else {
|
|
1647
1750
|
results = [];
|
|
1648
|
-
for (const
|
|
1649
|
-
results.push(
|
|
1650
|
-
await this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt).run()
|
|
1651
|
-
);
|
|
1751
|
+
for (const stmt of buildStatements()) {
|
|
1752
|
+
results.push(await stmt.run());
|
|
1652
1753
|
}
|
|
1653
1754
|
}
|
|
1755
|
+
const outcomes = results.slice(cleanups.length).map((res) => res.meta.changes > 0);
|
|
1654
1756
|
let upserted = 0;
|
|
1655
1757
|
let skipped = 0;
|
|
1656
|
-
for (const
|
|
1657
|
-
if (
|
|
1758
|
+
for (const wrote of outcomes) {
|
|
1759
|
+
if (wrote) upserted++;
|
|
1658
1760
|
else skipped++;
|
|
1659
1761
|
}
|
|
1660
|
-
return { upserted, skipped };
|
|
1762
|
+
return { upserted, skipped, outcomes };
|
|
1661
1763
|
}
|
|
1662
1764
|
/**
|
|
1663
1765
|
* All entries of one namespace (values still encrypted).
|
|
@@ -1799,7 +1901,6 @@ function createVapidPublicKeyHandler(ctx) {
|
|
|
1799
1901
|
}
|
|
1800
1902
|
|
|
1801
1903
|
// src/server/handlers/client-state.js
|
|
1802
|
-
var MAX_STATE_VALUE_BYTES = 200 * 1024;
|
|
1803
1904
|
var MAX_STATE_ENTRIES_PER_REQUEST = 200;
|
|
1804
1905
|
var MAX_NAMESPACE_CHARS = 128;
|
|
1805
1906
|
var MAX_KEY_CHARS = 256;
|
|
@@ -1814,23 +1915,39 @@ function requireUserId(headers) {
|
|
|
1814
1915
|
if (!isValidUUIDv4(userId)) return { error: err(400, "INVALID_USER_ID_FORMAT", "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F") };
|
|
1815
1916
|
return { userId };
|
|
1816
1917
|
}
|
|
1817
|
-
function
|
|
1818
|
-
|
|
1918
|
+
function rejectEntry(entry, index, code, message, extra) {
|
|
1919
|
+
const rejection = { index, code, message, ...extra || {} };
|
|
1920
|
+
if (entry && typeof entry === "object") {
|
|
1921
|
+
if (typeof entry.namespace === "string") rejection.namespace = entry.namespace;
|
|
1922
|
+
if (typeof entry.key === "string") rejection.key = entry.key;
|
|
1923
|
+
}
|
|
1924
|
+
return rejection;
|
|
1925
|
+
}
|
|
1926
|
+
function validateEntry(entry, index, maxValueBytes) {
|
|
1927
|
+
if (!isPlainObject(entry)) {
|
|
1928
|
+
return rejectEntry(entry, index, "INVALID_STATE_ENTRY", `entries[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
1929
|
+
}
|
|
1819
1930
|
if (typeof entry.namespace !== "string" || !entry.namespace.trim() || entry.namespace.length > MAX_NAMESPACE_CHARS) {
|
|
1820
|
-
return
|
|
1931
|
+
return rejectEntry(entry, index, "INVALID_STATE_NAMESPACE", `entries[${index}].namespace \u5FC5\u987B\u662F 1-${MAX_NAMESPACE_CHARS} \u5B57\u7B26\u7684\u5B57\u7B26\u4E32`);
|
|
1932
|
+
}
|
|
1933
|
+
if (INTERNAL_STATE_CHAR_RE.test(entry.namespace)) {
|
|
1934
|
+
return rejectEntry(entry, index, "INVALID_STATE_NAMESPACE", `entries[${index}].namespace \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`);
|
|
1821
1935
|
}
|
|
1822
1936
|
if (typeof entry.key !== "string" || !entry.key.trim() || entry.key.length > MAX_KEY_CHARS) {
|
|
1823
|
-
return
|
|
1937
|
+
return rejectEntry(entry, index, "INVALID_STATE_KEY", `entries[${index}].key \u5FC5\u987B\u662F 1-${MAX_KEY_CHARS} \u5B57\u7B26\u7684\u5B57\u7B26\u4E32`);
|
|
1938
|
+
}
|
|
1939
|
+
if (INTERNAL_STATE_CHAR_RE.test(entry.key)) {
|
|
1940
|
+
return rejectEntry(entry, index, "INVALID_STATE_KEY", `entries[${index}].key \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`);
|
|
1824
1941
|
}
|
|
1825
1942
|
if (typeof entry.value !== "string") {
|
|
1826
|
-
return
|
|
1943
|
+
return rejectEntry(entry, index, "INVALID_STATE_VALUE", `entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09`);
|
|
1827
1944
|
}
|
|
1828
1945
|
const bytes = utf82.encode(entry.value).length;
|
|
1829
|
-
if (bytes >
|
|
1830
|
-
return
|
|
1946
|
+
if (bytes > maxValueBytes) {
|
|
1947
|
+
return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
|
|
1831
1948
|
}
|
|
1832
1949
|
if (!Number.isInteger(entry.updatedAt) || entry.updatedAt <= 0) {
|
|
1833
|
-
return
|
|
1950
|
+
return rejectEntry(entry, index, "INVALID_STATE_UPDATED_AT", `entries[${index}].updatedAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08epoch \u6BEB\u79D2\uFF09`);
|
|
1834
1951
|
}
|
|
1835
1952
|
return null;
|
|
1836
1953
|
}
|
|
@@ -1868,21 +1985,70 @@ function createClientStateHandler(ctx) {
|
|
|
1868
1985
|
if (entries.length > MAX_STATE_ENTRIES_PER_REQUEST) {
|
|
1869
1986
|
return err(400, "TOO_MANY_STATE_ENTRIES", `\u5355\u6B21\u6700\u591A ${MAX_STATE_ENTRIES_PER_REQUEST} \u6761`, { count: entries.length });
|
|
1870
1987
|
}
|
|
1988
|
+
const maxValueBytes = Number.isInteger(ctx.maxStateValueBytes) && ctx.maxStateValueBytes > 0 ? ctx.maxStateValueBytes : DEFAULT_MAX_STATE_VALUE_BYTES;
|
|
1989
|
+
const accepted = [];
|
|
1990
|
+
const rejected = [];
|
|
1871
1991
|
for (let i = 0; i < entries.length; i++) {
|
|
1872
|
-
const
|
|
1873
|
-
if (
|
|
1992
|
+
const rejection = validateEntry(entries[i], i, maxValueBytes);
|
|
1993
|
+
if (rejection) rejected.push(rejection);
|
|
1994
|
+
else accepted.push(entries[i]);
|
|
1874
1995
|
}
|
|
1875
1996
|
if (typeof db.upsertClientState !== "function") {
|
|
1876
1997
|
return err(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
1877
1998
|
}
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1999
|
+
const physicalRows = [];
|
|
2000
|
+
const cleanups = [];
|
|
2001
|
+
const rootRowIndexes = [];
|
|
2002
|
+
for (const entry of accepted) {
|
|
2003
|
+
cleanups.push({
|
|
2004
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
2005
|
+
keyPrefix: chunkKeyPrefixFor(entry.key),
|
|
2006
|
+
updatedAt: entry.updatedAt
|
|
2007
|
+
});
|
|
2008
|
+
rootRowIndexes.push(physicalRows.length);
|
|
2009
|
+
if (utf82.encode(entry.value).length <= STATE_CHUNK_SLICE_BYTES) {
|
|
2010
|
+
physicalRows.push({
|
|
2011
|
+
namespace: entry.namespace,
|
|
2012
|
+
key: entry.key,
|
|
2013
|
+
value: await encryptForStorage(entry.value, userKey),
|
|
2014
|
+
updatedAt: entry.updatedAt
|
|
2015
|
+
});
|
|
2016
|
+
} else {
|
|
2017
|
+
const slices = splitStateValue(entry.value);
|
|
2018
|
+
physicalRows.push({
|
|
2019
|
+
namespace: entry.namespace,
|
|
2020
|
+
key: entry.key,
|
|
2021
|
+
value: buildChunkedRootValue(slices.length),
|
|
2022
|
+
updatedAt: entry.updatedAt
|
|
2023
|
+
});
|
|
2024
|
+
const encryptedSlices = await Promise.all(slices.map((slice) => encryptForStorage(slice, userKey)));
|
|
2025
|
+
for (let c = 0; c < encryptedSlices.length; c++) {
|
|
2026
|
+
physicalRows.push({
|
|
2027
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
2028
|
+
key: chunkKeyFor(entry.key, c),
|
|
2029
|
+
value: encryptedSlices[c],
|
|
2030
|
+
updatedAt: entry.updatedAt
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
let upserted = 0;
|
|
2036
|
+
let skipped = 0;
|
|
2037
|
+
if (physicalRows.length > 0) {
|
|
2038
|
+
const result = await db.upsertClientState(userId, physicalRows, cleanups);
|
|
2039
|
+
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
2040
|
+
for (const rootIndex of rootRowIndexes) {
|
|
2041
|
+
if (result.outcomes[rootIndex]) upserted++;
|
|
2042
|
+
else skipped++;
|
|
2043
|
+
}
|
|
2044
|
+
} else {
|
|
2045
|
+
upserted = result.upserted;
|
|
2046
|
+
skipped = result.skipped;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const data = { upserted, skipped };
|
|
2050
|
+
if (rejected.length > 0) data.rejected = rejected;
|
|
2051
|
+
return { status: 200, body: { success: true, data } };
|
|
1886
2052
|
}
|
|
1887
2053
|
async function GET(url, headers) {
|
|
1888
2054
|
const tenantResult = await ctx.tenantManager.resolveTenant(headers);
|
|
@@ -1893,17 +2059,19 @@ function createClientStateHandler(ctx) {
|
|
|
1893
2059
|
const { userId } = gate;
|
|
1894
2060
|
const namespace = new URL(url, "https://dummy").searchParams.get("namespace") || "";
|
|
1895
2061
|
if (!namespace.trim()) return err(400, "NAMESPACE_REQUIRED", "\u5FC5\u987B\u63D0\u4F9B namespace \u67E5\u8BE2\u53C2\u6570");
|
|
2062
|
+
if (INTERNAL_STATE_CHAR_RE.test(namespace)) {
|
|
2063
|
+
return err(400, "INVALID_STATE_NAMESPACE", "namespace \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09");
|
|
2064
|
+
}
|
|
1896
2065
|
if (typeof db.getClientState !== "function") {
|
|
1897
2066
|
return err(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
1898
2067
|
}
|
|
1899
2068
|
const userKey = await deriveUserEncryptionKey(userId, masterKey);
|
|
1900
2069
|
const rows = await db.getClientState(userId, namespace);
|
|
1901
|
-
const decrypted = await
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
value
|
|
1905
|
-
|
|
1906
|
-
})));
|
|
2070
|
+
const decrypted = await resolveClientStateEntries(
|
|
2071
|
+
rows,
|
|
2072
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace)),
|
|
2073
|
+
(value) => decryptFromStorage(value, userKey)
|
|
2074
|
+
);
|
|
1907
2075
|
const encryptedResponse = await encryptPayload({ namespace, entries: decrypted }, userKey);
|
|
1908
2076
|
return { status: 200, body: { success: true, encrypted: true, version: 1, data: encryptedResponse } };
|
|
1909
2077
|
}
|
|
@@ -1923,6 +2091,33 @@ function createClientStateHandler(ctx) {
|
|
|
1923
2091
|
return { PUT, GET, DELETE };
|
|
1924
2092
|
}
|
|
1925
2093
|
|
|
2094
|
+
// src/server/lib/version.js
|
|
2095
|
+
var SERVER_VERSION = true ? "2.6.0-next.5" : "0.0.0-dev";
|
|
2096
|
+
|
|
2097
|
+
// src/server/handlers/capabilities.js
|
|
2098
|
+
var SERVER_FEATURES = Object.freeze([
|
|
2099
|
+
"client-state",
|
|
2100
|
+
"client-state-chunking",
|
|
2101
|
+
"client-state-partial-failure",
|
|
2102
|
+
"agentic-hooks",
|
|
2103
|
+
"agentic-scratch",
|
|
2104
|
+
"vapid-public-key"
|
|
2105
|
+
]);
|
|
2106
|
+
function createCapabilitiesHandler(ctx) {
|
|
2107
|
+
async function GET(url, headers) {
|
|
2108
|
+
const effectiveHeaders = headers || url || {};
|
|
2109
|
+
const tenantResult = await ctx.tenantManager.resolveTenant(effectiveHeaders);
|
|
2110
|
+
if (!tenantResult.ok) {
|
|
2111
|
+
return tenantResult.error;
|
|
2112
|
+
}
|
|
2113
|
+
return {
|
|
2114
|
+
status: 200,
|
|
2115
|
+
body: { success: true, serverVersion: SERVER_VERSION, features: [...SERVER_FEATURES] }
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
return { GET };
|
|
2119
|
+
}
|
|
2120
|
+
|
|
1926
2121
|
// src/server/single-user.js
|
|
1927
2122
|
function createSingleUserServer(config) {
|
|
1928
2123
|
if (!config || !config.db) throw new Error("[amsg-server single-user] config.db is required");
|
|
@@ -1946,7 +2141,8 @@ function createSingleUserServer(config) {
|
|
|
1946
2141
|
// the agentic path too.
|
|
1947
2142
|
hooks: config.hooks || null,
|
|
1948
2143
|
maxToolIterations: config.maxToolIterations,
|
|
1949
|
-
totalTimeoutMs: config.totalTimeoutMs
|
|
2144
|
+
totalTimeoutMs: config.totalTimeoutMs,
|
|
2145
|
+
maxStateValueBytes: config.maxStateValueBytes
|
|
1950
2146
|
};
|
|
1951
2147
|
return {
|
|
1952
2148
|
ctx,
|
|
@@ -1958,7 +2154,8 @@ function createSingleUserServer(config) {
|
|
|
1958
2154
|
cancelMessage: createCancelMessageHandler(ctx),
|
|
1959
2155
|
messages: createMessagesHandler(ctx),
|
|
1960
2156
|
vapidPublicKey: createVapidPublicKeyHandler(ctx),
|
|
1961
|
-
clientState: createClientStateHandler(ctx)
|
|
2157
|
+
clientState: createClientStateHandler(ctx),
|
|
2158
|
+
capabilities: createCapabilitiesHandler(ctx)
|
|
1962
2159
|
}
|
|
1963
2160
|
};
|
|
1964
2161
|
}
|
|
@@ -2224,6 +2421,8 @@ function createSingleUserCloudflareWorker(buildConfig) {
|
|
|
2224
2421
|
result = await server.handlers.cancelMessage.DELETE(url, headers);
|
|
2225
2422
|
} else if (method === "GET" && pathname.endsWith("/vapid-public-key")) {
|
|
2226
2423
|
result = await server.handlers.vapidPublicKey.GET(url, headers);
|
|
2424
|
+
} else if (method === "GET" && pathname.endsWith("/capabilities")) {
|
|
2425
|
+
result = await server.handlers.capabilities.GET(url, headers);
|
|
2227
2426
|
} else if (method === "PUT" && pathname.endsWith("/client-state")) {
|
|
2228
2427
|
result = await server.handlers.clientState.PUT(headers, await request.text());
|
|
2229
2428
|
} else if (method === "GET" && pathname.endsWith("/client-state")) {
|