agents 0.14.5 → 0.16.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/dist/{agent-tool-types-V25Z_HcX.d.ts → agent-tool-types-NofdbL9X.d.ts} +182 -112
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-C-9s151X.d.ts → agent-tools-DLquv-dp.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/browser/ai.d.ts +126 -7
- package/dist/browser/ai.js +73 -29
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +81 -69
- package/dist/browser/index.js +3 -2
- package/dist/browser/tanstack-ai.d.ts +13 -7
- package/dist/browser/tanstack-ai.js +18 -19
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/chat/index.d.ts +111 -5
- package/dist/chat/index.js +207 -35
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/{classPrivateFieldGet2-Beqsfu2Z.js → classPrivateFieldGet2-CZ7QjTXN.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-B5ko1s2R.js → classPrivateMethodInitSpec-D-0__zd9.js} +2 -2
- package/dist/client.d.ts +19 -2
- package/dist/client.js +31 -11
- package/dist/client.js.map +1 -1
- package/dist/{compaction-helpers-BEUILPss.d.ts → compaction-helpers-DVcu5lPN.d.ts} +91 -12
- package/dist/connector-D6yYzYHg.js +1080 -0
- package/dist/connector-D6yYzYHg.js.map +1 -0
- package/dist/connector-DXursxV5.d.ts +340 -0
- package/dist/experimental/memory/session/index.d.ts +75 -12
- package/dist/experimental/memory/session/index.js +226 -21
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +2 -2
- package/dist/{index-CPe1OtI0.d.ts → index-B7IbEeze.d.ts} +32 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +116 -45
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +262 -487
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/react.d.ts +12 -1
- package/dist/react.js +101 -30
- package/dist/react.js.map +1 -1
- package/dist/{retries-CF_HKSlJ.d.ts → retries-CwlpAGet.d.ts} +35 -5
- package/dist/retries.d.ts +9 -5
- package/dist/retries.js +87 -1
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/index.js +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +10 -10
- package/dist/shared-4CAYLCTO.d.ts +0 -34
- package/dist/shared-wyII629d.js +0 -432
- package/dist/shared-wyII629d.js.map +0 -1
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
A as
|
|
2
|
+
A as StoredCompaction,
|
|
3
3
|
C as isSearchProvider,
|
|
4
|
-
D as
|
|
5
|
-
E as
|
|
6
|
-
F as
|
|
7
|
-
I as
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
D as RecentHistoryResult,
|
|
5
|
+
E as HistoryRowStat,
|
|
6
|
+
F as SessionMessagePart,
|
|
7
|
+
I as SessionOptions,
|
|
8
|
+
L as SessionTokenCounter,
|
|
9
|
+
M as CompactContext,
|
|
10
|
+
N as CompactionErrorHandler,
|
|
11
|
+
O as SearchResult,
|
|
12
|
+
P as SessionMessage,
|
|
13
|
+
R as SessionTokenCounterInput,
|
|
12
14
|
S as SearchProvider,
|
|
13
15
|
T as SqlProvider,
|
|
14
16
|
_ as isWritableProvider,
|
|
15
17
|
b as isSkillProvider,
|
|
16
18
|
g as WritableContextProvider,
|
|
17
19
|
h as ContextProvider,
|
|
18
|
-
j as
|
|
19
|
-
k as
|
|
20
|
+
j as CompactAfterOptions,
|
|
21
|
+
k as SessionProvider,
|
|
20
22
|
m as ContextConfig,
|
|
21
23
|
p as ContextBlock,
|
|
22
24
|
r as CompactResult,
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
w as AgentSessionProvider,
|
|
25
27
|
x as AgentSearchProvider,
|
|
26
28
|
y as SkillProvider
|
|
27
|
-
} from "../../../compaction-helpers-
|
|
29
|
+
} from "../../../compaction-helpers-DVcu5lPN.js";
|
|
28
30
|
import { ToolSet } from "ai";
|
|
29
31
|
|
|
30
32
|
//#region src/experimental/memory/session/session.d.ts
|
|
@@ -151,8 +153,31 @@ declare class Session {
|
|
|
151
153
|
* for load_context tool results that haven't been unloaded.
|
|
152
154
|
* Runs once per init to survive hibernation / eviction, including for
|
|
153
155
|
* async SessionProviders (e.g. Postgres) where we must `await` history.
|
|
156
|
+
*
|
|
157
|
+
* Skipped entirely when no skill-capable provider is configured —
|
|
158
|
+
* `load_context` results can only exist when a skill block was registered,
|
|
159
|
+
* and the scan would otherwise read the whole transcript on every wake,
|
|
160
|
+
* bypassing byte-budgeted hydration (#1710). A skill block added later via
|
|
161
|
+
* `addContext()` triggers the scan at that point instead.
|
|
154
162
|
*/
|
|
155
163
|
private _restoreLoadedSkills;
|
|
164
|
+
private _skillScanRan;
|
|
165
|
+
/**
|
|
166
|
+
* Scan stored history for load/unload_context tool results and restore
|
|
167
|
+
* the loaded-skill tracking set.
|
|
168
|
+
*
|
|
169
|
+
* Memory-bounded when the provider supports `getHistoryRowStats`: rows
|
|
170
|
+
* are enumerated without content, then only assistant rows are fetched
|
|
171
|
+
* and scanned ONE AT A TIME — peak memory is a single message instead of
|
|
172
|
+
* the whole transcript. Falls back to a full `getHistory()` read for
|
|
173
|
+
* providers without row stats (e.g. Postgres).
|
|
174
|
+
*
|
|
175
|
+
* Note: the bounded path scans raw path rows, so `load_context` results
|
|
176
|
+
* inside compacted ranges are still seen (the full-read path hides them
|
|
177
|
+
* behind compaction overlays). That superset is intentional — the stored
|
|
178
|
+
* tool result still exists and can be reclaimed by `unloadSkill`.
|
|
179
|
+
*/
|
|
180
|
+
private _scanHistoryForLoadedSkills;
|
|
156
181
|
/**
|
|
157
182
|
* Reclaim context-window tokens consumed by a previously loaded skill.
|
|
158
183
|
*
|
|
@@ -170,6 +195,30 @@ declare class Session {
|
|
|
170
195
|
*/
|
|
171
196
|
private _reclaimLoadedSkill;
|
|
172
197
|
getHistory(leafId?: string | null): Promise<SessionMessage[]>;
|
|
198
|
+
private _warnedNoRecentHistorySupport;
|
|
199
|
+
/**
|
|
200
|
+
* Byte-budgeted read of the most recent messages on the active branch
|
|
201
|
+
* path (always at least the leaf message, and at least
|
|
202
|
+
* `minRecentMessages` when the path is long enough). Lets hosts hydrate
|
|
203
|
+
* a bounded window instead of the full transcript so wake-time memory
|
|
204
|
+
* scales with the budget rather than total session history (#1710).
|
|
205
|
+
*
|
|
206
|
+
* Falls back to a full (untruncated) read when the provider doesn't
|
|
207
|
+
* implement `getRecentHistory`. The fallback reports honest metadata
|
|
208
|
+
* (`truncated: false` and the real serialized size) and warns once so a
|
|
209
|
+
* host relying on the budget knows it is not being enforced.
|
|
210
|
+
*/
|
|
211
|
+
getRecentHistory(
|
|
212
|
+
maxContentBytes: number,
|
|
213
|
+
minRecentMessages?: number
|
|
214
|
+
): Promise<RecentHistoryResult>;
|
|
215
|
+
/**
|
|
216
|
+
* Per-row stored sizes for the active branch path (root → leaf) WITHOUT
|
|
217
|
+
* loading message content, or `null` when the provider doesn't support it.
|
|
218
|
+
* Lets hosts find oversized rows (e.g. inline base64 media) and process
|
|
219
|
+
* them one at a time with bounded memory.
|
|
220
|
+
*/
|
|
221
|
+
getHistoryRowStats(): Promise<HistoryRowStat[] | null>;
|
|
173
222
|
getMessage(id: string): Promise<SessionMessage | null>;
|
|
174
223
|
getLatestLeaf(): Promise<SessionMessage | null>;
|
|
175
224
|
getBranches(messageId: string): Promise<SessionMessage[]>;
|
|
@@ -186,6 +235,18 @@ declare class Session {
|
|
|
186
235
|
): Promise<void>;
|
|
187
236
|
private _appendMessage;
|
|
188
237
|
updateMessage(message: SessionMessage): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* @internal
|
|
240
|
+
* Rewrite a stored message WITHOUT the public-write side effects: no
|
|
241
|
+
* token-estimate status broadcast (which reads the FULL history) and no
|
|
242
|
+
* auto-compaction check. For framework maintenance passes that rewrite
|
|
243
|
+
* many rows with bounded memory — e.g. media eviction (#1710) — where the
|
|
244
|
+
* per-row full-history estimate would reintroduce the memory pressure the
|
|
245
|
+
* pass exists to remove. The message-change listener still fires so a
|
|
246
|
+
* cache-owning host stays coherent. Application code should use
|
|
247
|
+
* `updateMessage`.
|
|
248
|
+
*/
|
|
249
|
+
internal_rewriteMessage(message: SessionMessage): Promise<void>;
|
|
189
250
|
deleteMessages(messageIds: string[]): Promise<void>;
|
|
190
251
|
clearMessages(): Promise<void>;
|
|
191
252
|
addCompaction(
|
|
@@ -576,6 +637,7 @@ export {
|
|
|
576
637
|
type ContextBlock,
|
|
577
638
|
type ContextConfig,
|
|
578
639
|
type ContextProvider,
|
|
640
|
+
type HistoryRowStat,
|
|
579
641
|
type PgClientLike,
|
|
580
642
|
type PostgresClient,
|
|
581
643
|
type PostgresConnection,
|
|
@@ -583,6 +645,7 @@ export {
|
|
|
583
645
|
PostgresSearchProvider,
|
|
584
646
|
PostgresSessionProvider,
|
|
585
647
|
R2SkillProvider,
|
|
648
|
+
type RecentHistoryResult,
|
|
586
649
|
type SearchProvider,
|
|
587
650
|
type SearchResult,
|
|
588
651
|
Session,
|
|
@@ -482,6 +482,14 @@ var ContextBlocks = class {
|
|
|
482
482
|
return Array.from(this.blocks.values()).some((b) => b.isSkill);
|
|
483
483
|
}
|
|
484
484
|
/**
|
|
485
|
+
* Check whether any CONFIGURED provider is skill-capable, without
|
|
486
|
+
* requiring `load()` to have run. Used by Session to decide whether the
|
|
487
|
+
* init-time loaded-skill restore scan is needed at all.
|
|
488
|
+
*/
|
|
489
|
+
hasSkillCapableConfigs() {
|
|
490
|
+
return this.configs.some((c) => c.provider !== void 0 && isSkillProvider(c.provider));
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
485
493
|
* Get skill block labels.
|
|
486
494
|
*/
|
|
487
495
|
getSkillLabels() {
|
|
@@ -708,6 +716,22 @@ var ContextBlocks = class {
|
|
|
708
716
|
};
|
|
709
717
|
//#endregion
|
|
710
718
|
//#region src/experimental/memory/session/providers/agent.ts
|
|
719
|
+
/**
|
|
720
|
+
* Bounds for each content-hydration query on a history path.
|
|
721
|
+
*
|
|
722
|
+
* Message rows can be up to ~1.8MB each (see ROW_MAX_BYTES in agents/chat),
|
|
723
|
+
* so content is fetched in bounded batches rather than one statement to keep
|
|
724
|
+
* any per-statement buffering in the SQLite layer small. In workerd the
|
|
725
|
+
* SQLite allocator shares the isolate's memory budget with the JS heap —
|
|
726
|
+
* oversized transient result sets surface as SQLITE_NOMEM (#1710).
|
|
727
|
+
*
|
|
728
|
+
* Chunks are bounded by BOTH row count and cumulative stored bytes (sizes
|
|
729
|
+
* come from the path row stats, so no content is read to compute them).
|
|
730
|
+
* Without the byte bound, 50 near-cap rows could still materialize ~90MB
|
|
731
|
+
* in a single statement.
|
|
732
|
+
*/
|
|
733
|
+
const HISTORY_CONTENT_CHUNK_SIZE = 50;
|
|
734
|
+
const HISTORY_CONTENT_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
711
735
|
var AgentSessionProvider = class {
|
|
712
736
|
/**
|
|
713
737
|
* @param agent - Agent or any object with a `sql` tagged template method
|
|
@@ -772,29 +796,47 @@ var AgentSessionProvider = class {
|
|
|
772
796
|
}
|
|
773
797
|
getHistory(leafId) {
|
|
774
798
|
this.ensureTable();
|
|
775
|
-
const leaf = leafId ? this.
|
|
776
|
-
SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
|
|
777
|
-
`[0] : this.latestLeafRow();
|
|
799
|
+
const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
|
|
778
800
|
if (!leaf) return [];
|
|
779
|
-
const
|
|
780
|
-
WITH RECURSIVE path AS (
|
|
781
|
-
SELECT *, 0 as depth FROM assistant_messages WHERE id = ${leaf.id}
|
|
782
|
-
UNION ALL
|
|
783
|
-
SELECT m.*, p.depth + 1 FROM assistant_messages m
|
|
784
|
-
JOIN path p ON m.id = p.parent_id
|
|
785
|
-
WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
|
|
786
|
-
)
|
|
787
|
-
SELECT content FROM path ORDER BY depth DESC
|
|
788
|
-
`;
|
|
789
|
-
const messages = this.parseRows(path);
|
|
801
|
+
const messages = this.messagesByPathStats(this.pathRowStats(leaf.id));
|
|
790
802
|
const compactions = this.getCompactions();
|
|
791
803
|
if (compactions.length === 0) return messages;
|
|
792
804
|
return this.applyCompactions(messages, compactions);
|
|
793
805
|
}
|
|
806
|
+
getRecentHistory(leafId, maxContentBytes, minRecentMessages = 1) {
|
|
807
|
+
this.ensureTable();
|
|
808
|
+
const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
|
|
809
|
+
if (!leaf) return {
|
|
810
|
+
messages: [],
|
|
811
|
+
truncated: false,
|
|
812
|
+
totalContentBytes: 0
|
|
813
|
+
};
|
|
814
|
+
const stats = this.pathRowStats(leaf.id);
|
|
815
|
+
const totalContentBytes = stats.reduce((sum, row) => sum + row.bytes, 0);
|
|
816
|
+
const minRecent = Math.max(1, Math.floor(minRecentMessages));
|
|
817
|
+
let start = stats.length - 1;
|
|
818
|
+
let used = stats[start]?.bytes ?? 0;
|
|
819
|
+
while (start > 0 && (stats.length - start < minRecent || used + stats[start - 1].bytes <= maxContentBytes)) {
|
|
820
|
+
start--;
|
|
821
|
+
used += stats[start].bytes;
|
|
822
|
+
}
|
|
823
|
+
const messages = this.messagesByPathStats(stats.slice(start));
|
|
824
|
+
const compactions = this.getCompactions();
|
|
825
|
+
return {
|
|
826
|
+
messages: compactions.length === 0 ? messages : this.applyCompactions(messages, compactions),
|
|
827
|
+
truncated: start > 0,
|
|
828
|
+
totalContentBytes
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
getHistoryRowStats(leafId) {
|
|
832
|
+
this.ensureTable();
|
|
833
|
+
const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
|
|
834
|
+
return leaf ? this.pathRowStats(leaf.id) : [];
|
|
835
|
+
}
|
|
794
836
|
getLatestLeaf() {
|
|
795
837
|
this.ensureTable();
|
|
796
838
|
const row = this.latestLeafRow();
|
|
797
|
-
return row ? this.
|
|
839
|
+
return row ? this.getMessage(row.id) : null;
|
|
798
840
|
}
|
|
799
841
|
getBranches(messageId) {
|
|
800
842
|
this.ensureTable();
|
|
@@ -910,12 +952,80 @@ var AgentSessionProvider = class {
|
|
|
910
952
|
}
|
|
911
953
|
latestLeafRow() {
|
|
912
954
|
return this.agent.sql`
|
|
913
|
-
SELECT m.id
|
|
955
|
+
SELECT m.id FROM assistant_messages m
|
|
914
956
|
LEFT JOIN assistant_messages c ON c.parent_id = m.id AND c.session_id = ${this.sessionId}
|
|
915
957
|
WHERE c.id IS NULL AND m.session_id = ${this.sessionId}
|
|
916
958
|
ORDER BY m.created_at DESC, m.rowid DESC LIMIT 1
|
|
917
959
|
`[0] ?? null;
|
|
918
960
|
}
|
|
961
|
+
leafRowById(leafId) {
|
|
962
|
+
return this.agent.sql`
|
|
963
|
+
SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
|
|
964
|
+
`[0] ?? null;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* The active branch path as (id, role, content size) rows, root → leaf.
|
|
968
|
+
*
|
|
969
|
+
* Recurses over (id, parent_id) only. Carrying `content` through the
|
|
970
|
+
* recursive queue AND the ORDER BY sorter materializes the entire
|
|
971
|
+
* transcript several times over inside SQLite's allocator, which in
|
|
972
|
+
* workerd shares the isolate's memory budget with the JS heap — large
|
|
973
|
+
* media-heavy sessions then fail with SQLITE_NOMEM on wake (#1710).
|
|
974
|
+
* Content is fetched separately in bounded chunks (`messagesByPathStats`).
|
|
975
|
+
*/
|
|
976
|
+
pathRowStats(leafId) {
|
|
977
|
+
return this.agent.sql`
|
|
978
|
+
WITH RECURSIVE path(id, parent_id, depth) AS (
|
|
979
|
+
SELECT id, parent_id, 0 FROM assistant_messages WHERE id = ${leafId}
|
|
980
|
+
UNION ALL
|
|
981
|
+
SELECT m.id, m.parent_id, p.depth + 1 FROM assistant_messages m
|
|
982
|
+
JOIN path p ON m.id = p.parent_id
|
|
983
|
+
WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
|
|
984
|
+
)
|
|
985
|
+
SELECT path.id AS id, am.role AS role, LENGTH(CAST(am.content AS BLOB)) AS bytes
|
|
986
|
+
FROM path JOIN assistant_messages am ON am.id = path.id
|
|
987
|
+
ORDER BY path.depth DESC
|
|
988
|
+
`;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Fetch and parse message content for an ordered list of path rows.
|
|
992
|
+
*
|
|
993
|
+
* Content is read in chunks bounded by both row count and cumulative
|
|
994
|
+
* stored bytes (no ORDER BY — SQLite streams rows without materializing
|
|
995
|
+
* the result set) and reassembled in path order. Rows that fail to parse
|
|
996
|
+
* are skipped, matching previous behavior.
|
|
997
|
+
*/
|
|
998
|
+
messagesByPathStats(rows) {
|
|
999
|
+
const contentById = /* @__PURE__ */ new Map();
|
|
1000
|
+
const fetchChunk = (ids) => {
|
|
1001
|
+
const fetched = this.agent.sql`
|
|
1002
|
+
SELECT id, content FROM assistant_messages
|
|
1003
|
+
WHERE session_id = ${this.sessionId}
|
|
1004
|
+
AND id IN (SELECT value FROM json_each(${JSON.stringify(ids)}))
|
|
1005
|
+
`;
|
|
1006
|
+
for (const row of fetched) contentById.set(row.id, row.content);
|
|
1007
|
+
};
|
|
1008
|
+
let chunk = [];
|
|
1009
|
+
let chunkBytes = 0;
|
|
1010
|
+
for (const row of rows) {
|
|
1011
|
+
if (chunk.length > 0 && (chunk.length >= HISTORY_CONTENT_CHUNK_SIZE || chunkBytes + row.bytes > HISTORY_CONTENT_CHUNK_BYTES)) {
|
|
1012
|
+
fetchChunk(chunk);
|
|
1013
|
+
chunk = [];
|
|
1014
|
+
chunkBytes = 0;
|
|
1015
|
+
}
|
|
1016
|
+
chunk.push(row.id);
|
|
1017
|
+
chunkBytes += row.bytes;
|
|
1018
|
+
}
|
|
1019
|
+
if (chunk.length > 0) fetchChunk(chunk);
|
|
1020
|
+
const result = [];
|
|
1021
|
+
for (const row of rows) {
|
|
1022
|
+
const content = contentById.get(row.id);
|
|
1023
|
+
if (content === void 0) continue;
|
|
1024
|
+
const msg = this.parse(content);
|
|
1025
|
+
if (msg) result.push(msg);
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
919
1029
|
indexFTS(message) {
|
|
920
1030
|
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
921
1031
|
this.deleteFTS(message.id);
|
|
@@ -1023,6 +1133,8 @@ var Session = class Session {
|
|
|
1023
1133
|
constructor(storage, options) {
|
|
1024
1134
|
this._warnedCompactionNoOp = false;
|
|
1025
1135
|
this._ready = false;
|
|
1136
|
+
this._skillScanRan = false;
|
|
1137
|
+
this._warnedNoRecentHistorySupport = false;
|
|
1026
1138
|
this.storage = storage;
|
|
1027
1139
|
this.context = new ContextBlocks(options?.context ?? [], options?.promptStore);
|
|
1028
1140
|
this._tokenCounter = options?.tokenCounter;
|
|
@@ -1171,12 +1283,37 @@ var Session = class Session {
|
|
|
1171
1283
|
* for load_context tool results that haven't been unloaded.
|
|
1172
1284
|
* Runs once per init to survive hibernation / eviction, including for
|
|
1173
1285
|
* async SessionProviders (e.g. Postgres) where we must `await` history.
|
|
1286
|
+
*
|
|
1287
|
+
* Skipped entirely when no skill-capable provider is configured —
|
|
1288
|
+
* `load_context` results can only exist when a skill block was registered,
|
|
1289
|
+
* and the scan would otherwise read the whole transcript on every wake,
|
|
1290
|
+
* bypassing byte-budgeted hydration (#1710). A skill block added later via
|
|
1291
|
+
* `addContext()` triggers the scan at that point instead.
|
|
1174
1292
|
*/
|
|
1175
1293
|
async _restoreLoadedSkills() {
|
|
1176
|
-
|
|
1294
|
+
if (!this.context.hasSkillCapableConfigs()) return;
|
|
1295
|
+
await this._scanHistoryForLoadedSkills();
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Scan stored history for load/unload_context tool results and restore
|
|
1299
|
+
* the loaded-skill tracking set.
|
|
1300
|
+
*
|
|
1301
|
+
* Memory-bounded when the provider supports `getHistoryRowStats`: rows
|
|
1302
|
+
* are enumerated without content, then only assistant rows are fetched
|
|
1303
|
+
* and scanned ONE AT A TIME — peak memory is a single message instead of
|
|
1304
|
+
* the whole transcript. Falls back to a full `getHistory()` read for
|
|
1305
|
+
* providers without row stats (e.g. Postgres).
|
|
1306
|
+
*
|
|
1307
|
+
* Note: the bounded path scans raw path rows, so `load_context` results
|
|
1308
|
+
* inside compacted ranges are still seen (the full-read path hides them
|
|
1309
|
+
* behind compaction overlays). That superset is intentional — the stored
|
|
1310
|
+
* tool result still exists and can be reclaimed by `unloadSkill`.
|
|
1311
|
+
*/
|
|
1312
|
+
async _scanHistoryForLoadedSkills() {
|
|
1313
|
+
this._skillScanRan = true;
|
|
1177
1314
|
const loaded = /* @__PURE__ */ new Set();
|
|
1178
|
-
|
|
1179
|
-
if (msg.role !== "assistant")
|
|
1315
|
+
const scanMessage = (msg) => {
|
|
1316
|
+
if (msg.role !== "assistant") return;
|
|
1180
1317
|
for (const part of msg.parts) if (part.toolName === "load_context" && part.state === "output-available") {
|
|
1181
1318
|
const input = part.input;
|
|
1182
1319
|
if (input?.label && input?.key) {
|
|
@@ -1188,7 +1325,15 @@ var Session = class Session {
|
|
|
1188
1325
|
const input = part.input;
|
|
1189
1326
|
if (input?.label && input?.key) loaded.delete(`${input.label}:${input.key}`);
|
|
1190
1327
|
}
|
|
1191
|
-
}
|
|
1328
|
+
};
|
|
1329
|
+
if (this.storage.getHistoryRowStats) {
|
|
1330
|
+
const stats = await this.storage.getHistoryRowStats();
|
|
1331
|
+
for (const row of stats) {
|
|
1332
|
+
if (row.role !== "assistant") continue;
|
|
1333
|
+
const msg = await this.storage.getMessage(row.id);
|
|
1334
|
+
if (msg) scanMessage(msg);
|
|
1335
|
+
}
|
|
1336
|
+
} else for (const msg of await this.storage.getHistory()) scanMessage(msg);
|
|
1192
1337
|
if (loaded.size > 0) this.context.restoreLoadedSkills(loaded);
|
|
1193
1338
|
}
|
|
1194
1339
|
/**
|
|
@@ -1238,6 +1383,45 @@ var Session = class Session {
|
|
|
1238
1383
|
await this._ensureRestored();
|
|
1239
1384
|
return this.storage.getHistory(leafId);
|
|
1240
1385
|
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Byte-budgeted read of the most recent messages on the active branch
|
|
1388
|
+
* path (always at least the leaf message, and at least
|
|
1389
|
+
* `minRecentMessages` when the path is long enough). Lets hosts hydrate
|
|
1390
|
+
* a bounded window instead of the full transcript so wake-time memory
|
|
1391
|
+
* scales with the budget rather than total session history (#1710).
|
|
1392
|
+
*
|
|
1393
|
+
* Falls back to a full (untruncated) read when the provider doesn't
|
|
1394
|
+
* implement `getRecentHistory`. The fallback reports honest metadata
|
|
1395
|
+
* (`truncated: false` and the real serialized size) and warns once so a
|
|
1396
|
+
* host relying on the budget knows it is not being enforced.
|
|
1397
|
+
*/
|
|
1398
|
+
async getRecentHistory(maxContentBytes, minRecentMessages = 1) {
|
|
1399
|
+
await this._ensureRestored();
|
|
1400
|
+
if (this.storage.getRecentHistory) return this.storage.getRecentHistory(null, maxContentBytes, minRecentMessages);
|
|
1401
|
+
if (!this._warnedNoRecentHistorySupport) {
|
|
1402
|
+
this._warnedNoRecentHistorySupport = true;
|
|
1403
|
+
console.warn("[Session] The configured SessionProvider does not implement getRecentHistory; the requested byte budget cannot be enforced and the FULL history was loaded. Implement getRecentHistory (and getHistoryRowStats) on the provider to bound hydration.");
|
|
1404
|
+
}
|
|
1405
|
+
const messages = await this.storage.getHistory();
|
|
1406
|
+
let totalContentBytes = 0;
|
|
1407
|
+
for (const message of messages) totalContentBytes += JSON.stringify(message).length;
|
|
1408
|
+
return {
|
|
1409
|
+
messages,
|
|
1410
|
+
truncated: false,
|
|
1411
|
+
totalContentBytes
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Per-row stored sizes for the active branch path (root → leaf) WITHOUT
|
|
1416
|
+
* loading message content, or `null` when the provider doesn't support it.
|
|
1417
|
+
* Lets hosts find oversized rows (e.g. inline base64 media) and process
|
|
1418
|
+
* them one at a time with bounded memory.
|
|
1419
|
+
*/
|
|
1420
|
+
async getHistoryRowStats() {
|
|
1421
|
+
await this._ensureRestored();
|
|
1422
|
+
if (!this.storage.getHistoryRowStats) return null;
|
|
1423
|
+
return this.storage.getHistoryRowStats();
|
|
1424
|
+
}
|
|
1241
1425
|
async getMessage(id) {
|
|
1242
1426
|
await this._ensureRestored();
|
|
1243
1427
|
return this.storage.getMessage(id);
|
|
@@ -1351,6 +1535,25 @@ var Session = class Session {
|
|
|
1351
1535
|
message
|
|
1352
1536
|
});
|
|
1353
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* @internal
|
|
1540
|
+
* Rewrite a stored message WITHOUT the public-write side effects: no
|
|
1541
|
+
* token-estimate status broadcast (which reads the FULL history) and no
|
|
1542
|
+
* auto-compaction check. For framework maintenance passes that rewrite
|
|
1543
|
+
* many rows with bounded memory — e.g. media eviction (#1710) — where the
|
|
1544
|
+
* per-row full-history estimate would reintroduce the memory pressure the
|
|
1545
|
+
* pass exists to remove. The message-change listener still fires so a
|
|
1546
|
+
* cache-owning host stays coherent. Application code should use
|
|
1547
|
+
* `updateMessage`.
|
|
1548
|
+
*/
|
|
1549
|
+
async internal_rewriteMessage(message) {
|
|
1550
|
+
await this._ensureRestored();
|
|
1551
|
+
await this.storage.updateMessage(message);
|
|
1552
|
+
await this._notifyMessagesChanged({
|
|
1553
|
+
type: "update",
|
|
1554
|
+
message
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1354
1557
|
async deleteMessages(messageIds) {
|
|
1355
1558
|
await this._ensureRestored();
|
|
1356
1559
|
await this.storage.deleteMessages(messageIds);
|
|
@@ -1452,12 +1655,14 @@ var Session = class Session {
|
|
|
1452
1655
|
const key = this._sessionId ? `${label}_${this._sessionId}` : label;
|
|
1453
1656
|
provider = new AgentContextProvider(this._agent, key);
|
|
1454
1657
|
}
|
|
1455
|
-
|
|
1658
|
+
const block = await this.context.addBlock({
|
|
1456
1659
|
label,
|
|
1457
1660
|
description: opts.description,
|
|
1458
1661
|
maxTokens: opts.maxTokens,
|
|
1459
1662
|
provider
|
|
1460
1663
|
});
|
|
1664
|
+
if (block.isSkill && !this._skillScanRan) await this._scanHistoryForLoadedSkills();
|
|
1665
|
+
return block;
|
|
1461
1666
|
}
|
|
1462
1667
|
/**
|
|
1463
1668
|
* Remove a dynamically registered context block.
|