@rei-standard/amsg-server 2.6.0-next.6 → 2.6.0-next.8
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 +26 -0
- package/dist/{chunk-WFFIWXM5.cjs → chunk-HQHRXSQA.cjs} +209 -79
- package/dist/{chunk-JODQV7MD.mjs → chunk-LEE5KQ7Y.mjs} +208 -78
- package/dist/{cloudflare-qy1YWQcp.d.cts → cloudflare-CH3dBl5b.d.cts} +367 -88
- package/dist/{cloudflare-qy1YWQcp.d.ts → cloudflare-CH3dBl5b.d.ts} +367 -88
- package/dist/cloudflare.cjs +10 -2
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.mjs +9 -1
- package/dist/index.cjs +19 -11
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +9 -1
- package/package.json +1 -1
|
@@ -443,6 +443,12 @@ function buildAiRequestBody(payload) {
|
|
|
443
443
|
} else if (!Array.isArray(payload.messages)) {
|
|
444
444
|
requestBody.temperature = 0.8;
|
|
445
445
|
}
|
|
446
|
+
if (Array.isArray(payload.tools) && payload.tools.length > 0) {
|
|
447
|
+
requestBody.tools = payload.tools;
|
|
448
|
+
if (payload.toolChoice !== void 0 && payload.toolChoice !== null) {
|
|
449
|
+
requestBody.tool_choice = payload.toolChoice;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
446
452
|
if (payload.maxTokens === void 0 || payload.maxTokens === null) {
|
|
447
453
|
return requestBody;
|
|
448
454
|
}
|
|
@@ -559,6 +565,79 @@ async function resolveClientStateEntries(rows, fetchChunkRows, decryptValue) {
|
|
|
559
565
|
return entries;
|
|
560
566
|
}
|
|
561
567
|
|
|
568
|
+
// src/server/lib/client-state-store.js
|
|
569
|
+
var utf82 = new TextEncoder();
|
|
570
|
+
var MAX_STATE_ENTRIES_PER_BATCH = 200;
|
|
571
|
+
var MAX_NAMESPACE_CHARS = 128;
|
|
572
|
+
var MAX_KEY_CHARS = 256;
|
|
573
|
+
function stateValueBytes(value) {
|
|
574
|
+
return utf82.encode(value).length;
|
|
575
|
+
}
|
|
576
|
+
async function writeClientStateEntries({ db, userId, userKey, entries }) {
|
|
577
|
+
const physicalRows = [];
|
|
578
|
+
const cleanups = [];
|
|
579
|
+
const rootRowIndexes = [];
|
|
580
|
+
let deleted = 0;
|
|
581
|
+
for (const entry of entries) {
|
|
582
|
+
cleanups.push({
|
|
583
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
584
|
+
keyPrefix: chunkKeyPrefixFor(entry.key),
|
|
585
|
+
updatedAt: entry.updatedAt
|
|
586
|
+
});
|
|
587
|
+
if (entry.value === null) {
|
|
588
|
+
cleanups.push({
|
|
589
|
+
namespace: entry.namespace,
|
|
590
|
+
key: entry.key,
|
|
591
|
+
updatedAt: entry.updatedAt
|
|
592
|
+
});
|
|
593
|
+
deleted++;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
rootRowIndexes.push(physicalRows.length);
|
|
597
|
+
if (stateValueBytes(entry.value) <= STATE_CHUNK_SLICE_BYTES) {
|
|
598
|
+
physicalRows.push({
|
|
599
|
+
namespace: entry.namespace,
|
|
600
|
+
key: entry.key,
|
|
601
|
+
value: await encryptForStorage(entry.value, userKey),
|
|
602
|
+
updatedAt: entry.updatedAt
|
|
603
|
+
});
|
|
604
|
+
} else {
|
|
605
|
+
const slices = splitStateValue(entry.value);
|
|
606
|
+
physicalRows.push({
|
|
607
|
+
namespace: entry.namespace,
|
|
608
|
+
key: entry.key,
|
|
609
|
+
value: buildChunkedRootValue(slices.length),
|
|
610
|
+
updatedAt: entry.updatedAt
|
|
611
|
+
});
|
|
612
|
+
const encryptedSlices = await Promise.all(slices.map((slice) => encryptForStorage(slice, userKey)));
|
|
613
|
+
for (let c = 0; c < encryptedSlices.length; c++) {
|
|
614
|
+
physicalRows.push({
|
|
615
|
+
namespace: chunkNamespaceFor(entry.namespace),
|
|
616
|
+
key: chunkKeyFor(entry.key, c),
|
|
617
|
+
value: encryptedSlices[c],
|
|
618
|
+
updatedAt: entry.updatedAt
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (physicalRows.length === 0 && cleanups.length === 0) {
|
|
624
|
+
return { upserted: 0, skipped: 0, deleted: 0 };
|
|
625
|
+
}
|
|
626
|
+
const result = await db.upsertClientState(userId, physicalRows, cleanups);
|
|
627
|
+
let upserted = 0;
|
|
628
|
+
let skipped = 0;
|
|
629
|
+
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
630
|
+
for (const rootIndex of rootRowIndexes) {
|
|
631
|
+
if (result.outcomes[rootIndex]) upserted++;
|
|
632
|
+
else skipped++;
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
upserted = result.upserted;
|
|
636
|
+
skipped = result.skipped;
|
|
637
|
+
}
|
|
638
|
+
return { upserted, skipped, deleted };
|
|
639
|
+
}
|
|
640
|
+
|
|
562
641
|
// src/server/lib/agentic-fire.js
|
|
563
642
|
var DEFAULT_MAX_TOOL_ITERATIONS = 5;
|
|
564
643
|
var DEFAULT_TOTAL_TIMEOUT_MS = 24e4;
|
|
@@ -594,11 +673,13 @@ function normalizeBeforeFireResult(result) {
|
|
|
594
673
|
return {
|
|
595
674
|
messages: result.messages,
|
|
596
675
|
maxToolIterations: result.maxToolIterations,
|
|
597
|
-
totalTimeoutMs: result.totalTimeoutMs
|
|
676
|
+
totalTimeoutMs: result.totalTimeoutMs,
|
|
677
|
+
tools: Array.isArray(result.tools) && result.tools.length > 0 ? result.tools : void 0,
|
|
678
|
+
toolChoice: result.toolChoice
|
|
598
679
|
};
|
|
599
680
|
}
|
|
600
681
|
throw new TypeError(
|
|
601
|
-
"AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs? } | { skip: true } | null"
|
|
682
|
+
"AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? } | { skip: true } | null"
|
|
602
683
|
);
|
|
603
684
|
}
|
|
604
685
|
function firstPositiveInt(values, fallback) {
|
|
@@ -632,11 +713,64 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
632
713
|
(value) => decryptFromStorage(value, userKey)
|
|
633
714
|
);
|
|
634
715
|
};
|
|
716
|
+
const maxStateValueBytes = Number.isInteger(ctx.maxStateValueBytes) && ctx.maxStateValueBytes > 0 ? ctx.maxStateValueBytes : DEFAULT_MAX_STATE_VALUE_BYTES;
|
|
717
|
+
const writeState = async (namespace, entries) => {
|
|
718
|
+
if (typeof namespace !== "string" || !namespace.trim()) {
|
|
719
|
+
throw new TypeError("writeState(namespace, entries) requires a non-empty string namespace");
|
|
720
|
+
}
|
|
721
|
+
if (namespace.length > MAX_NAMESPACE_CHARS || INTERNAL_STATE_CHAR_RE.test(namespace)) {
|
|
722
|
+
throw new TypeError(
|
|
723
|
+
`writeState: namespace \u5FC5\u987B\u662F 1-${MAX_NAMESPACE_CHARS} \u5B57\u7B26\u4E14\u4E0D\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (!Array.isArray(entries)) {
|
|
727
|
+
throw new TypeError("writeState(namespace, entries) requires an array of { key, value }");
|
|
728
|
+
}
|
|
729
|
+
if (entries.length === 0) return { upserted: 0, skipped: 0, deleted: 0 };
|
|
730
|
+
if (entries.length > MAX_STATE_ENTRIES_PER_BATCH) {
|
|
731
|
+
throw new RangeError(`writeState: \u5355\u6B21\u6700\u591A ${MAX_STATE_ENTRIES_PER_BATCH} \u6761\uFF0C\u6536\u5230 ${entries.length} \u6761`);
|
|
732
|
+
}
|
|
733
|
+
if (!ctx.db || typeof ctx.db.upsertClientState !== "function") {
|
|
734
|
+
throw new Error("AGENTIC_STATE_WRITE_UNSUPPORTED: \u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state \u5199\u5165");
|
|
735
|
+
}
|
|
736
|
+
const now = nowFn();
|
|
737
|
+
const normalized2 = entries.map((entry, index) => {
|
|
738
|
+
if (!entry || typeof entry !== "object") {
|
|
739
|
+
throw new TypeError(`writeState: entries[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
740
|
+
}
|
|
741
|
+
if (typeof entry.key !== "string" || !entry.key.trim() || entry.key.length > MAX_KEY_CHARS) {
|
|
742
|
+
throw new TypeError(`writeState: entries[${index}].key \u5FC5\u987B\u662F 1-${MAX_KEY_CHARS} \u5B57\u7B26\u7684\u5B57\u7B26\u4E32`);
|
|
743
|
+
}
|
|
744
|
+
if (INTERNAL_STATE_CHAR_RE.test(entry.key)) {
|
|
745
|
+
throw new TypeError(`writeState: entries[${index}].key \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`);
|
|
746
|
+
}
|
|
747
|
+
if (entry.value !== null && typeof entry.value !== "string") {
|
|
748
|
+
throw new TypeError(`writeState: entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09\uFF0C\u6216 null \u8868\u793A\u5220\u9664`);
|
|
749
|
+
}
|
|
750
|
+
if (typeof entry.value === "string") {
|
|
751
|
+
const bytes = stateValueBytes(entry.value);
|
|
752
|
+
if (bytes > maxStateValueBytes) {
|
|
753
|
+
throw new RangeError(`writeState: entries[${index}].value \u4E3A ${bytes} \u5B57\u8282\uFF0C\u8D85\u8FC7\u5355\u6761\u4E0A\u9650 ${maxStateValueBytes} \u5B57\u8282`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (entry.updatedAt !== void 0 && (!Number.isInteger(entry.updatedAt) || entry.updatedAt <= 0)) {
|
|
757
|
+
throw new TypeError(`writeState: entries[${index}].updatedAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08epoch \u6BEB\u79D2\uFF09`);
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
namespace,
|
|
761
|
+
key: entry.key,
|
|
762
|
+
value: entry.value,
|
|
763
|
+
updatedAt: entry.updatedAt ?? now
|
|
764
|
+
};
|
|
765
|
+
});
|
|
766
|
+
return writeClientStateEntries({ db: ctx.db, userId: task.user_id, userKey, entries: normalized2 });
|
|
767
|
+
};
|
|
635
768
|
const scratch = {};
|
|
636
769
|
const fireCtx = Object.freeze({
|
|
637
770
|
task: buildHookTask(task, decryptedPayload),
|
|
638
771
|
userId: task.user_id,
|
|
639
772
|
readState,
|
|
773
|
+
writeState,
|
|
640
774
|
now: new Date(nowFn()),
|
|
641
775
|
scratch
|
|
642
776
|
});
|
|
@@ -663,21 +797,29 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
663
797
|
}
|
|
664
798
|
const roundTimeoutMs = Math.max(1, Math.min(3e5, deadline - nowFn()));
|
|
665
799
|
const { response: llmResponse } = await callLlm(
|
|
666
|
-
{
|
|
800
|
+
{
|
|
801
|
+
...decryptedPayload,
|
|
802
|
+
messages,
|
|
803
|
+
...normalized.tools ? { tools: normalized.tools, toolChoice: normalized.toolChoice } : {}
|
|
804
|
+
},
|
|
667
805
|
{ requireContent: false, timeoutMs: roundTimeoutMs }
|
|
668
806
|
);
|
|
669
807
|
const assistantMessage = extractAssistantMessage(llmResponse);
|
|
670
808
|
messages = [...messages, assistantMessage];
|
|
671
|
-
const sessionCtx =
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
809
|
+
const sessionCtx = Object.freeze({
|
|
810
|
+
...buildSessionContext({
|
|
811
|
+
sessionId,
|
|
812
|
+
messages,
|
|
813
|
+
llmResponse,
|
|
814
|
+
iteration,
|
|
815
|
+
contactName: decryptedPayload.contactName,
|
|
816
|
+
avatarUrl: decryptedPayload.avatarUrl || void 0,
|
|
817
|
+
charId: decryptedPayload.charId,
|
|
818
|
+
metadata: decryptedPayload.metadata,
|
|
819
|
+
scratch
|
|
820
|
+
}),
|
|
821
|
+
readState,
|
|
822
|
+
writeState
|
|
681
823
|
});
|
|
682
824
|
const decision = await hooks.onLLMOutput(sessionCtx);
|
|
683
825
|
assertValidDecision(decision, { inlineToolCalls: true });
|
|
@@ -715,7 +857,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
715
857
|
content: `Tool execution failed: ${error?.message ?? String(error)}`
|
|
716
858
|
}));
|
|
717
859
|
}
|
|
718
|
-
const
|
|
860
|
+
const nativeCalls = Array.isArray(assistantMessage.tool_calls) ? assistantMessage.tool_calls : [];
|
|
861
|
+
const nativeIds = new Set(nativeCalls.map((tc) => tc && tc.id));
|
|
862
|
+
const synthesized = toolCalls.filter((tc) => !nativeIds.has(tc && tc.id));
|
|
863
|
+
const assistantWithTools = synthesized.length === 0 ? assistantMessage : { ...assistantMessage, tool_calls: [...nativeCalls, ...synthesized] };
|
|
719
864
|
messages = [...messages.slice(0, -1), assistantWithTools, ...toolResults];
|
|
720
865
|
}
|
|
721
866
|
throw new Error(`AGENTIC_LOOP_EXCEEDED: no finish/skip-push decision within ${maxToolIterations} LLM round(s)`);
|
|
@@ -1796,9 +1941,15 @@ var D1Adapter = class {
|
|
|
1796
1941
|
* than the stored row (updatedAt strictly lower) is skipped; equal or
|
|
1797
1942
|
* newer overwrites. Values arrive pre-encrypted (the handler encrypts).
|
|
1798
1943
|
*
|
|
1799
|
-
* `cleanups`
|
|
1800
|
-
*
|
|
1801
|
-
* `
|
|
1944
|
+
* `cleanups` 是删除项:在同一 batch 里先于 upsert 执行,`updated_at <= ?`
|
|
1945
|
+
* 条件保证陈旧批次删不动更新写入的行。两种形态——
|
|
1946
|
+
* - `{ namespace, keyPrefix, updatedAt }` 删 key 前缀下的所有行,用来清掉
|
|
1947
|
+
* 大值旧写入留下的切片行(见 lib/state-chunks.js);
|
|
1948
|
+
* - `{ namespace, key, updatedAt }` 删这一个 key,用来删整条状态(前缀会
|
|
1949
|
+
* 连带删掉同前缀的兄弟 key,删单条必须走精确匹配)。
|
|
1950
|
+
*
|
|
1951
|
+
* 删掉一整条状态就是这两种各来一条(切片行走前缀、根行走精确 key),
|
|
1952
|
+
* `entries` 传空数组即可——见 lib/client-state-store.js。
|
|
1802
1953
|
*
|
|
1803
1954
|
* Uses D1's batch() — one network round trip for the whole set (implicit
|
|
1804
1955
|
* transaction). The client calls this endpoint inside its few-seconds
|
|
@@ -1808,7 +1959,7 @@ var D1Adapter = class {
|
|
|
1808
1959
|
*
|
|
1809
1960
|
* @param {string} userId
|
|
1810
1961
|
* @param {Array<{ namespace: string, key: string, value: string, updatedAt: number }>} entries
|
|
1811
|
-
* @param {Array<{ namespace: string, keyPrefix
|
|
1962
|
+
* @param {Array<{ namespace: string, key?: string, keyPrefix?: string, updatedAt: number }>} [cleanups]
|
|
1812
1963
|
* @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
|
|
1813
1964
|
* `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
|
|
1814
1965
|
*/
|
|
@@ -1819,12 +1970,12 @@ var D1Adapter = class {
|
|
|
1819
1970
|
value = excluded.value,
|
|
1820
1971
|
updated_at = excluded.updated_at
|
|
1821
1972
|
WHERE excluded.updated_at >= client_state.updated_at`;
|
|
1822
|
-
const
|
|
1973
|
+
const CLEANUP_PREFIX_SQL = `DELETE FROM client_state
|
|
1823
1974
|
WHERE user_id = ? AND namespace = ? AND key LIKE ? ESCAPE '\\' AND updated_at <= ?`;
|
|
1975
|
+
const CLEANUP_KEY_SQL = `DELETE FROM client_state
|
|
1976
|
+
WHERE user_id = ? AND namespace = ? AND key = ? AND updated_at <= ?`;
|
|
1824
1977
|
const buildStatements = () => [
|
|
1825
|
-
...cleanups.map(
|
|
1826
|
-
(c) => this._db.prepare(CLEANUP_SQL).bind(userId, c.namespace, `${escapeLikePrefix(c.keyPrefix)}%`, c.updatedAt)
|
|
1827
|
-
),
|
|
1978
|
+
...cleanups.map((c) => typeof c.key === "string" ? this._db.prepare(CLEANUP_KEY_SQL).bind(userId, c.namespace, c.key, c.updatedAt) : this._db.prepare(CLEANUP_PREFIX_SQL).bind(userId, c.namespace, `${escapeLikePrefix(c.keyPrefix)}%`, c.updatedAt)),
|
|
1828
1979
|
...entries.map(
|
|
1829
1980
|
(entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt)
|
|
1830
1981
|
)
|
|
@@ -1987,10 +2138,7 @@ function createVapidPublicKeyHandler(ctx) {
|
|
|
1987
2138
|
}
|
|
1988
2139
|
|
|
1989
2140
|
// src/server/handlers/client-state.js
|
|
1990
|
-
var MAX_STATE_ENTRIES_PER_REQUEST =
|
|
1991
|
-
var MAX_NAMESPACE_CHARS = 128;
|
|
1992
|
-
var MAX_KEY_CHARS = 256;
|
|
1993
|
-
var utf82 = new TextEncoder();
|
|
2141
|
+
var MAX_STATE_ENTRIES_PER_REQUEST = MAX_STATE_ENTRIES_PER_BATCH;
|
|
1994
2142
|
function err(status, code, message, details) {
|
|
1995
2143
|
const error = details === void 0 ? { code, message } : { code, message, details };
|
|
1996
2144
|
return { status, body: { success: false, error } };
|
|
@@ -2028,7 +2176,7 @@ function validateEntry(entry, index, maxValueBytes) {
|
|
|
2028
2176
|
if (typeof entry.value !== "string") {
|
|
2029
2177
|
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`);
|
|
2030
2178
|
}
|
|
2031
|
-
const bytes =
|
|
2179
|
+
const bytes = stateValueBytes(entry.value);
|
|
2032
2180
|
if (bytes > maxValueBytes) {
|
|
2033
2181
|
return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
|
|
2034
2182
|
}
|
|
@@ -2082,56 +2230,7 @@ function createClientStateHandler(ctx) {
|
|
|
2082
2230
|
if (typeof db.upsertClientState !== "function") {
|
|
2083
2231
|
return err(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
2084
2232
|
}
|
|
2085
|
-
const
|
|
2086
|
-
const cleanups = [];
|
|
2087
|
-
const rootRowIndexes = [];
|
|
2088
|
-
for (const entry of accepted) {
|
|
2089
|
-
cleanups.push({
|
|
2090
|
-
namespace: chunkNamespaceFor(entry.namespace),
|
|
2091
|
-
keyPrefix: chunkKeyPrefixFor(entry.key),
|
|
2092
|
-
updatedAt: entry.updatedAt
|
|
2093
|
-
});
|
|
2094
|
-
rootRowIndexes.push(physicalRows.length);
|
|
2095
|
-
if (utf82.encode(entry.value).length <= STATE_CHUNK_SLICE_BYTES) {
|
|
2096
|
-
physicalRows.push({
|
|
2097
|
-
namespace: entry.namespace,
|
|
2098
|
-
key: entry.key,
|
|
2099
|
-
value: await encryptForStorage(entry.value, userKey),
|
|
2100
|
-
updatedAt: entry.updatedAt
|
|
2101
|
-
});
|
|
2102
|
-
} else {
|
|
2103
|
-
const slices = splitStateValue(entry.value);
|
|
2104
|
-
physicalRows.push({
|
|
2105
|
-
namespace: entry.namespace,
|
|
2106
|
-
key: entry.key,
|
|
2107
|
-
value: buildChunkedRootValue(slices.length),
|
|
2108
|
-
updatedAt: entry.updatedAt
|
|
2109
|
-
});
|
|
2110
|
-
const encryptedSlices = await Promise.all(slices.map((slice) => encryptForStorage(slice, userKey)));
|
|
2111
|
-
for (let c = 0; c < encryptedSlices.length; c++) {
|
|
2112
|
-
physicalRows.push({
|
|
2113
|
-
namespace: chunkNamespaceFor(entry.namespace),
|
|
2114
|
-
key: chunkKeyFor(entry.key, c),
|
|
2115
|
-
value: encryptedSlices[c],
|
|
2116
|
-
updatedAt: entry.updatedAt
|
|
2117
|
-
});
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
let upserted = 0;
|
|
2122
|
-
let skipped = 0;
|
|
2123
|
-
if (physicalRows.length > 0) {
|
|
2124
|
-
const result = await db.upsertClientState(userId, physicalRows, cleanups);
|
|
2125
|
-
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
2126
|
-
for (const rootIndex of rootRowIndexes) {
|
|
2127
|
-
if (result.outcomes[rootIndex]) upserted++;
|
|
2128
|
-
else skipped++;
|
|
2129
|
-
}
|
|
2130
|
-
} else {
|
|
2131
|
-
upserted = result.upserted;
|
|
2132
|
-
skipped = result.skipped;
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2233
|
+
const { upserted, skipped } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
|
|
2135
2234
|
const data = { upserted, skipped };
|
|
2136
2235
|
if (rejected.length > 0) data.rejected = rejected;
|
|
2137
2236
|
return { status: 200, body: { success: true, data } };
|
|
@@ -2178,7 +2277,7 @@ function createClientStateHandler(ctx) {
|
|
|
2178
2277
|
}
|
|
2179
2278
|
|
|
2180
2279
|
// src/server/lib/version.js
|
|
2181
|
-
var SERVER_VERSION = true ? "2.6.0-next.
|
|
2280
|
+
var SERVER_VERSION = true ? "2.6.0-next.8" : "0.0.0-dev";
|
|
2182
2281
|
|
|
2183
2282
|
// src/server/handlers/capabilities.js
|
|
2184
2283
|
var SERVER_FEATURES = Object.freeze([
|
|
@@ -2187,6 +2286,8 @@ var SERVER_FEATURES = Object.freeze([
|
|
|
2187
2286
|
"client-state-partial-failure",
|
|
2188
2287
|
"agentic-hooks",
|
|
2189
2288
|
"agentic-scratch",
|
|
2289
|
+
"agentic-write-state",
|
|
2290
|
+
"agentic-fire-tools",
|
|
2190
2291
|
"vapid-public-key"
|
|
2191
2292
|
]);
|
|
2192
2293
|
function createCapabilitiesHandler(ctx) {
|
|
@@ -2254,6 +2355,19 @@ var NONCE_INFO = utf8("Content-Encoding: nonce\0");
|
|
|
2254
2355
|
var VAPID_DEFAULT_TTL = 60;
|
|
2255
2356
|
var VAPID_TOKEN_LIFETIME = 12 * 3600;
|
|
2256
2357
|
var RECORD_SIZE = 4096;
|
|
2358
|
+
var WEB_PUSH_MAX_BODY_BYTES = 4096;
|
|
2359
|
+
var WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = 16 + 4 + 1 + 65 + 1 + 16;
|
|
2360
|
+
var MAX_PUSH_PAYLOAD_BYTES = WEB_PUSH_MAX_BODY_BYTES - WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES;
|
|
2361
|
+
var payloadEncoder = new TextEncoder();
|
|
2362
|
+
function measurePushPayload(payload) {
|
|
2363
|
+
const bytes = payloadEncoder.encode(typeof payload === "string" ? payload : String(payload)).length;
|
|
2364
|
+
return {
|
|
2365
|
+
bytes,
|
|
2366
|
+
maxBytes: MAX_PUSH_PAYLOAD_BYTES,
|
|
2367
|
+
remainingBytes: MAX_PUSH_PAYLOAD_BYTES - bytes,
|
|
2368
|
+
withinLimit: bytes <= MAX_PUSH_PAYLOAD_BYTES
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2257
2371
|
async function sendWebPush({ subscription, payload, vapid, ttl, fetch: fetchImpl }) {
|
|
2258
2372
|
if (!subscription || typeof subscription.endpoint !== "string") {
|
|
2259
2373
|
throw new Error("sendWebPush: invalid subscription");
|
|
@@ -2268,6 +2382,16 @@ async function sendWebPush({ subscription, payload, vapid, ttl, fetch: fetchImpl
|
|
|
2268
2382
|
if (typeof subscriptionKeys.p256dh !== "string" || typeof subscriptionKeys.auth !== "string") {
|
|
2269
2383
|
throw new Error("sendWebPush: subscription.keys.p256dh and .auth are required");
|
|
2270
2384
|
}
|
|
2385
|
+
const size = measurePushPayload(payload);
|
|
2386
|
+
if (!size.withinLimit) {
|
|
2387
|
+
const err2 = new Error(
|
|
2388
|
+
`sendWebPush: payload is ${size.bytes} bytes, over the ${MAX_PUSH_PAYLOAD_BYTES}-byte limit (push services cap the encrypted body at ${WEB_PUSH_MAX_BODY_BYTES} bytes; aes128gcm adds ${WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES} bytes)`
|
|
2389
|
+
);
|
|
2390
|
+
err2.code = "PUSH_PAYLOAD_TOO_LARGE";
|
|
2391
|
+
err2.bytes = size.bytes;
|
|
2392
|
+
err2.maxBytes = MAX_PUSH_PAYLOAD_BYTES;
|
|
2393
|
+
throw err2;
|
|
2394
|
+
}
|
|
2271
2395
|
const encryptedBody = await encryptPushPayload({
|
|
2272
2396
|
plaintext: utf8(payload),
|
|
2273
2397
|
uaPublicKey: base64UrlToBytes(subscriptionKeys.p256dh),
|
|
@@ -2542,6 +2666,8 @@ function createSingleUserCloudflareWorker(buildConfig) {
|
|
|
2542
2666
|
hooks: cfg.hooks || null,
|
|
2543
2667
|
maxToolIterations: cfg.maxToolIterations,
|
|
2544
2668
|
totalTimeoutMs: cfg.totalTimeoutMs,
|
|
2669
|
+
// hook 的 ctx.writeState() 用它判断单条 value 的上限,和 PUT /client-state 同一个配置。
|
|
2670
|
+
maxStateValueBytes: cfg.maxStateValueBytes,
|
|
2545
2671
|
// 任务占位租期(默认 10 分钟,随 totalTimeoutMs 抬高)。
|
|
2546
2672
|
claimLeaseMs: cfg.claimLeaseMs
|
|
2547
2673
|
});
|
|
@@ -2575,6 +2701,10 @@ export {
|
|
|
2575
2701
|
createMessagesHandler,
|
|
2576
2702
|
createD1Adapter,
|
|
2577
2703
|
createSingleUserServer,
|
|
2704
|
+
WEB_PUSH_MAX_BODY_BYTES,
|
|
2705
|
+
WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES,
|
|
2706
|
+
MAX_PUSH_PAYLOAD_BYTES,
|
|
2707
|
+
measurePushPayload,
|
|
2578
2708
|
createWebCryptoWebPush,
|
|
2579
2709
|
createSingleUserCloudflareWorker
|
|
2580
2710
|
};
|