@rei-standard/amsg-server 2.6.0-next.3 → 2.6.0-next.4
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-OVGQHTSE.mjs} +237 -48
- package/dist/{chunk-L7WMM33Q.cjs → chunk-REX5MCEZ.cjs} +237 -48
- package/dist/{cloudflare-DlLTdCp0.d.cts → cloudflare-sVIPkSsf.d.cts} +361 -51
- package/dist/{cloudflare-DlLTdCp0.d.ts → cloudflare-sVIPkSsf.d.ts} +361 -51
- 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;
|
|
@@ -551,18 +626,19 @@ 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 };
|
|
@@ -597,7 +673,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
597
673
|
contactName: decryptedPayload.contactName,
|
|
598
674
|
avatarUrl: decryptedPayload.avatarUrl || void 0,
|
|
599
675
|
charId: decryptedPayload.charId,
|
|
600
|
-
metadata: decryptedPayload.metadata
|
|
676
|
+
metadata: decryptedPayload.metadata,
|
|
677
|
+
scratch
|
|
601
678
|
});
|
|
602
679
|
const decision = await hooks.onLLMOutput(sessionCtx);
|
|
603
680
|
assertValidDecision(decision, { inlineToolCalls: true });
|
|
@@ -1439,6 +1516,9 @@ var UPDATABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
1439
1516
|
"created_at",
|
|
1440
1517
|
"updated_at"
|
|
1441
1518
|
]);
|
|
1519
|
+
function escapeLikePrefix(prefix) {
|
|
1520
|
+
return prefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
1521
|
+
}
|
|
1442
1522
|
var D1Adapter = class {
|
|
1443
1523
|
/** @param {{ prepare: (sql: string) => any }} db - Cloudflare D1 binding */
|
|
1444
1524
|
constructor(db) {
|
|
@@ -1620,6 +1700,10 @@ var D1Adapter = class {
|
|
|
1620
1700
|
* than the stored row (updatedAt strictly lower) is skipped; equal or
|
|
1621
1701
|
* newer overwrites. Values arrive pre-encrypted (the handler encrypts).
|
|
1622
1702
|
*
|
|
1703
|
+
* `cleanups` 是分块存储的清理项(见 lib/state-chunks.js):在同一 batch 里
|
|
1704
|
+
* 先于 upsert 执行,按 (namespace, key 前缀) 删掉旧写入留下的切片行;
|
|
1705
|
+
* `updated_at <= ?` 条件保证陈旧批次删不动更新写入的行。
|
|
1706
|
+
*
|
|
1623
1707
|
* Uses D1's batch() — one network round trip for the whole set (implicit
|
|
1624
1708
|
* transaction). The client calls this endpoint inside its few-seconds
|
|
1625
1709
|
* background window, so N sequential round trips could eat the whole
|
|
@@ -1628,36 +1712,44 @@ var D1Adapter = class {
|
|
|
1628
1712
|
*
|
|
1629
1713
|
* @param {string} userId
|
|
1630
1714
|
* @param {Array<{ namespace: string, key: string, value: string, updatedAt: number }>} entries
|
|
1631
|
-
* @
|
|
1715
|
+
* @param {Array<{ namespace: string, keyPrefix: string, updatedAt: number }>} [cleanups]
|
|
1716
|
+
* @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
|
|
1717
|
+
* `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
|
|
1632
1718
|
*/
|
|
1633
|
-
async upsertClientState(userId, entries) {
|
|
1719
|
+
async upsertClientState(userId, entries, cleanups = []) {
|
|
1634
1720
|
const UPSERT_SQL = `INSERT INTO client_state (user_id, namespace, key, value, updated_at)
|
|
1635
1721
|
VALUES (?, ?, ?, ?, ?)
|
|
1636
1722
|
ON CONFLICT (user_id, namespace, key) DO UPDATE SET
|
|
1637
1723
|
value = excluded.value,
|
|
1638
1724
|
updated_at = excluded.updated_at
|
|
1639
1725
|
WHERE excluded.updated_at >= client_state.updated_at`;
|
|
1726
|
+
const CLEANUP_SQL = `DELETE FROM client_state
|
|
1727
|
+
WHERE user_id = ? AND namespace = ? AND key LIKE ? ESCAPE '\\' AND updated_at <= ?`;
|
|
1728
|
+
const buildStatements = () => [
|
|
1729
|
+
...cleanups.map(
|
|
1730
|
+
(c) => this._db.prepare(CLEANUP_SQL).bind(userId, c.namespace, `${escapeLikePrefix(c.keyPrefix)}%`, c.updatedAt)
|
|
1731
|
+
),
|
|
1732
|
+
...entries.map(
|
|
1733
|
+
(entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt)
|
|
1734
|
+
)
|
|
1735
|
+
];
|
|
1640
1736
|
let results;
|
|
1641
1737
|
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);
|
|
1738
|
+
results = await this._db.batch(buildStatements());
|
|
1646
1739
|
} else {
|
|
1647
1740
|
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
|
-
);
|
|
1741
|
+
for (const stmt of buildStatements()) {
|
|
1742
|
+
results.push(await stmt.run());
|
|
1652
1743
|
}
|
|
1653
1744
|
}
|
|
1745
|
+
const outcomes = results.slice(cleanups.length).map((res) => res.meta.changes > 0);
|
|
1654
1746
|
let upserted = 0;
|
|
1655
1747
|
let skipped = 0;
|
|
1656
|
-
for (const
|
|
1657
|
-
if (
|
|
1748
|
+
for (const wrote of outcomes) {
|
|
1749
|
+
if (wrote) upserted++;
|
|
1658
1750
|
else skipped++;
|
|
1659
1751
|
}
|
|
1660
|
-
return { upserted, skipped };
|
|
1752
|
+
return { upserted, skipped, outcomes };
|
|
1661
1753
|
}
|
|
1662
1754
|
/**
|
|
1663
1755
|
* All entries of one namespace (values still encrypted).
|
|
@@ -1799,7 +1891,6 @@ function createVapidPublicKeyHandler(ctx) {
|
|
|
1799
1891
|
}
|
|
1800
1892
|
|
|
1801
1893
|
// src/server/handlers/client-state.js
|
|
1802
|
-
var MAX_STATE_VALUE_BYTES = 200 * 1024;
|
|
1803
1894
|
var MAX_STATE_ENTRIES_PER_REQUEST = 200;
|
|
1804
1895
|
var MAX_NAMESPACE_CHARS = 128;
|
|
1805
1896
|
var MAX_KEY_CHARS = 256;
|
|
@@ -1814,23 +1905,39 @@ function requireUserId(headers) {
|
|
|
1814
1905
|
if (!isValidUUIDv4(userId)) return { error: err(400, "INVALID_USER_ID_FORMAT", "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F") };
|
|
1815
1906
|
return { userId };
|
|
1816
1907
|
}
|
|
1817
|
-
function
|
|
1818
|
-
|
|
1908
|
+
function rejectEntry(entry, index, code, message, extra) {
|
|
1909
|
+
const rejection = { index, code, message, ...extra || {} };
|
|
1910
|
+
if (entry && typeof entry === "object") {
|
|
1911
|
+
if (typeof entry.namespace === "string") rejection.namespace = entry.namespace;
|
|
1912
|
+
if (typeof entry.key === "string") rejection.key = entry.key;
|
|
1913
|
+
}
|
|
1914
|
+
return rejection;
|
|
1915
|
+
}
|
|
1916
|
+
function validateEntry(entry, index, maxValueBytes) {
|
|
1917
|
+
if (!isPlainObject(entry)) {
|
|
1918
|
+
return rejectEntry(entry, index, "INVALID_STATE_ENTRY", `entries[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
1919
|
+
}
|
|
1819
1920
|
if (typeof entry.namespace !== "string" || !entry.namespace.trim() || entry.namespace.length > MAX_NAMESPACE_CHARS) {
|
|
1820
|
-
return
|
|
1921
|
+
return rejectEntry(entry, index, "INVALID_STATE_NAMESPACE", `entries[${index}].namespace \u5FC5\u987B\u662F 1-${MAX_NAMESPACE_CHARS} \u5B57\u7B26\u7684\u5B57\u7B26\u4E32`);
|
|
1922
|
+
}
|
|
1923
|
+
if (INTERNAL_STATE_CHAR_RE.test(entry.namespace)) {
|
|
1924
|
+
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
1925
|
}
|
|
1822
1926
|
if (typeof entry.key !== "string" || !entry.key.trim() || entry.key.length > MAX_KEY_CHARS) {
|
|
1823
|
-
return
|
|
1927
|
+
return rejectEntry(entry, index, "INVALID_STATE_KEY", `entries[${index}].key \u5FC5\u987B\u662F 1-${MAX_KEY_CHARS} \u5B57\u7B26\u7684\u5B57\u7B26\u4E32`);
|
|
1928
|
+
}
|
|
1929
|
+
if (INTERNAL_STATE_CHAR_RE.test(entry.key)) {
|
|
1930
|
+
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
1931
|
}
|
|
1825
1932
|
if (typeof entry.value !== "string") {
|
|
1826
|
-
return
|
|
1933
|
+
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
1934
|
}
|
|
1828
1935
|
const bytes = utf82.encode(entry.value).length;
|
|
1829
|
-
if (bytes >
|
|
1830
|
-
return
|
|
1936
|
+
if (bytes > maxValueBytes) {
|
|
1937
|
+
return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
|
|
1831
1938
|
}
|
|
1832
1939
|
if (!Number.isInteger(entry.updatedAt) || entry.updatedAt <= 0) {
|
|
1833
|
-
return
|
|
1940
|
+
return rejectEntry(entry, index, "INVALID_STATE_UPDATED_AT", `entries[${index}].updatedAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08epoch \u6BEB\u79D2\uFF09`);
|
|
1834
1941
|
}
|
|
1835
1942
|
return null;
|
|
1836
1943
|
}
|
|
@@ -1868,21 +1975,70 @@ function createClientStateHandler(ctx) {
|
|
|
1868
1975
|
if (entries.length > MAX_STATE_ENTRIES_PER_REQUEST) {
|
|
1869
1976
|
return err(400, "TOO_MANY_STATE_ENTRIES", `\u5355\u6B21\u6700\u591A ${MAX_STATE_ENTRIES_PER_REQUEST} \u6761`, { count: entries.length });
|
|
1870
1977
|
}
|
|
1978
|
+
const maxValueBytes = Number.isInteger(ctx.maxStateValueBytes) && ctx.maxStateValueBytes > 0 ? ctx.maxStateValueBytes : DEFAULT_MAX_STATE_VALUE_BYTES;
|
|
1979
|
+
const accepted = [];
|
|
1980
|
+
const rejected = [];
|
|
1871
1981
|
for (let i = 0; i < entries.length; i++) {
|
|
1872
|
-
const
|
|
1873
|
-
if (
|
|
1982
|
+
const rejection = validateEntry(entries[i], i, maxValueBytes);
|
|
1983
|
+
if (rejection) rejected.push(rejection);
|
|
1984
|
+
else accepted.push(entries[i]);
|
|
1874
1985
|
}
|
|
1875
1986
|
if (typeof db.upsertClientState !== "function") {
|
|
1876
1987
|
return err(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
1877
1988
|
}
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1989
|
+
const physicalRows = [];
|
|
1990
|
+
const cleanups = [];
|
|
1991
|
+
const rootRowIndexes = [];
|
|
1992
|
+
for (const entry of accepted) {
|
|
1993
|
+
cleanups.push({
|
|
1994
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
1995
|
+
keyPrefix: chunkKeyPrefixFor(entry.key),
|
|
1996
|
+
updatedAt: entry.updatedAt
|
|
1997
|
+
});
|
|
1998
|
+
rootRowIndexes.push(physicalRows.length);
|
|
1999
|
+
if (utf82.encode(entry.value).length <= STATE_CHUNK_SLICE_BYTES) {
|
|
2000
|
+
physicalRows.push({
|
|
2001
|
+
namespace: entry.namespace,
|
|
2002
|
+
key: entry.key,
|
|
2003
|
+
value: await encryptForStorage(entry.value, userKey),
|
|
2004
|
+
updatedAt: entry.updatedAt
|
|
2005
|
+
});
|
|
2006
|
+
} else {
|
|
2007
|
+
const slices = splitStateValue(entry.value);
|
|
2008
|
+
physicalRows.push({
|
|
2009
|
+
namespace: entry.namespace,
|
|
2010
|
+
key: entry.key,
|
|
2011
|
+
value: buildChunkedRootValue(slices.length),
|
|
2012
|
+
updatedAt: entry.updatedAt
|
|
2013
|
+
});
|
|
2014
|
+
const encryptedSlices = await Promise.all(slices.map((slice) => encryptForStorage(slice, userKey)));
|
|
2015
|
+
for (let c = 0; c < encryptedSlices.length; c++) {
|
|
2016
|
+
physicalRows.push({
|
|
2017
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
2018
|
+
key: chunkKeyFor(entry.key, c),
|
|
2019
|
+
value: encryptedSlices[c],
|
|
2020
|
+
updatedAt: entry.updatedAt
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
let upserted = 0;
|
|
2026
|
+
let skipped = 0;
|
|
2027
|
+
if (physicalRows.length > 0) {
|
|
2028
|
+
const result = await db.upsertClientState(userId, physicalRows, cleanups);
|
|
2029
|
+
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
2030
|
+
for (const rootIndex of rootRowIndexes) {
|
|
2031
|
+
if (result.outcomes[rootIndex]) upserted++;
|
|
2032
|
+
else skipped++;
|
|
2033
|
+
}
|
|
2034
|
+
} else {
|
|
2035
|
+
upserted = result.upserted;
|
|
2036
|
+
skipped = result.skipped;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const data = { upserted, skipped };
|
|
2040
|
+
if (rejected.length > 0) data.rejected = rejected;
|
|
2041
|
+
return { status: 200, body: { success: true, data } };
|
|
1886
2042
|
}
|
|
1887
2043
|
async function GET(url, headers) {
|
|
1888
2044
|
const tenantResult = await ctx.tenantManager.resolveTenant(headers);
|
|
@@ -1893,17 +2049,19 @@ function createClientStateHandler(ctx) {
|
|
|
1893
2049
|
const { userId } = gate;
|
|
1894
2050
|
const namespace = new URL(url, "https://dummy").searchParams.get("namespace") || "";
|
|
1895
2051
|
if (!namespace.trim()) return err(400, "NAMESPACE_REQUIRED", "\u5FC5\u987B\u63D0\u4F9B namespace \u67E5\u8BE2\u53C2\u6570");
|
|
2052
|
+
if (INTERNAL_STATE_CHAR_RE.test(namespace)) {
|
|
2053
|
+
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");
|
|
2054
|
+
}
|
|
1896
2055
|
if (typeof db.getClientState !== "function") {
|
|
1897
2056
|
return err(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
1898
2057
|
}
|
|
1899
2058
|
const userKey = await deriveUserEncryptionKey(userId, masterKey);
|
|
1900
2059
|
const rows = await db.getClientState(userId, namespace);
|
|
1901
|
-
const decrypted = await
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
value
|
|
1905
|
-
|
|
1906
|
-
})));
|
|
2060
|
+
const decrypted = await resolveClientStateEntries(
|
|
2061
|
+
rows,
|
|
2062
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace)),
|
|
2063
|
+
(value) => decryptFromStorage(value, userKey)
|
|
2064
|
+
);
|
|
1907
2065
|
const encryptedResponse = await encryptPayload({ namespace, entries: decrypted }, userKey);
|
|
1908
2066
|
return { status: 200, body: { success: true, encrypted: true, version: 1, data: encryptedResponse } };
|
|
1909
2067
|
}
|
|
@@ -1923,6 +2081,33 @@ function createClientStateHandler(ctx) {
|
|
|
1923
2081
|
return { PUT, GET, DELETE };
|
|
1924
2082
|
}
|
|
1925
2083
|
|
|
2084
|
+
// src/server/lib/version.js
|
|
2085
|
+
var SERVER_VERSION = true ? "2.6.0-next.4" : "0.0.0-dev";
|
|
2086
|
+
|
|
2087
|
+
// src/server/handlers/capabilities.js
|
|
2088
|
+
var SERVER_FEATURES = Object.freeze([
|
|
2089
|
+
"client-state",
|
|
2090
|
+
"client-state-chunking",
|
|
2091
|
+
"client-state-partial-failure",
|
|
2092
|
+
"agentic-hooks",
|
|
2093
|
+
"agentic-scratch",
|
|
2094
|
+
"vapid-public-key"
|
|
2095
|
+
]);
|
|
2096
|
+
function createCapabilitiesHandler(ctx) {
|
|
2097
|
+
async function GET(url, headers) {
|
|
2098
|
+
const effectiveHeaders = headers || url || {};
|
|
2099
|
+
const tenantResult = await ctx.tenantManager.resolveTenant(effectiveHeaders);
|
|
2100
|
+
if (!tenantResult.ok) {
|
|
2101
|
+
return tenantResult.error;
|
|
2102
|
+
}
|
|
2103
|
+
return {
|
|
2104
|
+
status: 200,
|
|
2105
|
+
body: { success: true, serverVersion: SERVER_VERSION, features: [...SERVER_FEATURES] }
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
return { GET };
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1926
2111
|
// src/server/single-user.js
|
|
1927
2112
|
function createSingleUserServer(config) {
|
|
1928
2113
|
if (!config || !config.db) throw new Error("[amsg-server single-user] config.db is required");
|
|
@@ -1946,7 +2131,8 @@ function createSingleUserServer(config) {
|
|
|
1946
2131
|
// the agentic path too.
|
|
1947
2132
|
hooks: config.hooks || null,
|
|
1948
2133
|
maxToolIterations: config.maxToolIterations,
|
|
1949
|
-
totalTimeoutMs: config.totalTimeoutMs
|
|
2134
|
+
totalTimeoutMs: config.totalTimeoutMs,
|
|
2135
|
+
maxStateValueBytes: config.maxStateValueBytes
|
|
1950
2136
|
};
|
|
1951
2137
|
return {
|
|
1952
2138
|
ctx,
|
|
@@ -1958,7 +2144,8 @@ function createSingleUserServer(config) {
|
|
|
1958
2144
|
cancelMessage: createCancelMessageHandler(ctx),
|
|
1959
2145
|
messages: createMessagesHandler(ctx),
|
|
1960
2146
|
vapidPublicKey: createVapidPublicKeyHandler(ctx),
|
|
1961
|
-
clientState: createClientStateHandler(ctx)
|
|
2147
|
+
clientState: createClientStateHandler(ctx),
|
|
2148
|
+
capabilities: createCapabilitiesHandler(ctx)
|
|
1962
2149
|
}
|
|
1963
2150
|
};
|
|
1964
2151
|
}
|
|
@@ -2224,6 +2411,8 @@ function createSingleUserCloudflareWorker(buildConfig) {
|
|
|
2224
2411
|
result = await server.handlers.cancelMessage.DELETE(url, headers);
|
|
2225
2412
|
} else if (method === "GET" && pathname.endsWith("/vapid-public-key")) {
|
|
2226
2413
|
result = await server.handlers.vapidPublicKey.GET(url, headers);
|
|
2414
|
+
} else if (method === "GET" && pathname.endsWith("/capabilities")) {
|
|
2415
|
+
result = await server.handlers.capabilities.GET(url, headers);
|
|
2227
2416
|
} else if (method === "PUT" && pathname.endsWith("/client-state")) {
|
|
2228
2417
|
result = await server.handlers.clientState.PUT(headers, await request.text());
|
|
2229
2418
|
} else if (method === "GET" && pathname.endsWith("/client-state")) {
|