agents 0.10.0 → 0.10.2

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.
@@ -1,6 +1,6 @@
1
1
  import { MessageType } from "../../../types.js";
2
2
  import { m as estimateStringTokens, p as estimateMessageTokens, t as COMPACTION_PREFIX } from "../../../compaction-helpers-BPE1_ziA.js";
3
- import { jsonSchema } from "ai";
3
+ import { z } from "zod";
4
4
  //#region src/experimental/memory/session/search.ts
5
5
  /**
6
6
  * Check if a provider is a SearchProvider (has a `search` method).
@@ -175,22 +175,6 @@ var R2SkillProvider = class {
175
175
  //#endregion
176
176
  //#region src/experimental/memory/session/context.ts
177
177
  /**
178
- * Context Block Management
179
- *
180
- * Persistent key-value blocks (MEMORY, USER, SOUL, etc.) that are:
181
- * - Loaded from their providers at init
182
- * - Frozen into a snapshot when toSystemPrompt() is called
183
- * - Updated via setBlock() which writes to the provider immediately
184
- * but does NOT update the frozen snapshot (preserves LLM prefix cache)
185
- * - Re-snapshotted on next toSystemPrompt() call
186
- *
187
- * Provider type determines behavior:
188
- * - ContextProvider (get only) → readonly block in system prompt
189
- * - WritableContextProvider (get+set) → writable via set_context tool
190
- * - SkillProvider (get+load+set?) → metadata in prompt, load_context tool
191
- * - SearchProvider (get+search+set?) → searchable via search_context tool
192
- */
193
- /**
194
178
  * Check if a provider is writable (has a `set` method).
195
179
  */
196
180
  function isWritableProvider(provider) {
@@ -204,9 +188,18 @@ var ContextBlocks = class {
204
188
  this.blocks = /* @__PURE__ */ new Map();
205
189
  this.snapshot = null;
206
190
  this.loaded = false;
191
+ this._loadedSkills = /* @__PURE__ */ new Set();
192
+ this._onUnloadSkill = null;
207
193
  this.configs = configs;
208
194
  this.promptStore = promptStore ?? null;
209
195
  }
196
+ /**
197
+ * Register a callback invoked when a skill is unloaded.
198
+ * Session uses this to update the stored tool result message.
199
+ */
200
+ setUnloadCallback(cb) {
201
+ this._onUnloadSkill = cb;
202
+ }
210
203
  isLoaded() {
211
204
  return this.loaded;
212
205
  }
@@ -235,6 +228,57 @@ var ContextBlocks = class {
235
228
  this.loaded = true;
236
229
  }
237
230
  /**
231
+ * Dynamically register a new context block after initialization.
232
+ * Used by extensions to contribute context at runtime.
233
+ *
234
+ * If blocks have already been loaded, the new block's provider is
235
+ * initialized and loaded immediately. The snapshot is NOT updated
236
+ * automatically — call `refreshSystemPrompt()` to rebuild.
237
+ */
238
+ async addBlock(config) {
239
+ if (!this.loaded) await this.load();
240
+ if (this.configs.some((c) => c.label === config.label)) throw new Error(`Block "${config.label}" already exists`);
241
+ this.configs.push(config);
242
+ if (config.provider?.init) config.provider.init(config.label);
243
+ const content = config.provider ? await config.provider.get() ?? "" : "";
244
+ const skill = config.provider ? isSkillProvider(config.provider) : false;
245
+ const searchable = config.provider ? isSearchProvider(config.provider) : false;
246
+ const writable = config.provider ? isWritableProvider(config.provider) || skill && !!config.provider.set || searchable && !!config.provider.set : false;
247
+ const block = {
248
+ label: config.label,
249
+ description: config.description,
250
+ content,
251
+ tokens: estimateStringTokens(content),
252
+ maxTokens: config.maxTokens,
253
+ writable,
254
+ isSkill: skill,
255
+ isSearchable: searchable
256
+ };
257
+ this.blocks.set(config.label, block);
258
+ return block;
259
+ }
260
+ /**
261
+ * Remove a dynamically registered context block.
262
+ * Used during extension unload cleanup.
263
+ *
264
+ * Returns true if the block existed and was removed.
265
+ * The snapshot is NOT updated automatically — call
266
+ * `refreshSystemPrompt()` to rebuild.
267
+ *
268
+ * Note: loaded skills for this block are cleaned up from the
269
+ * tracking set but the skill unload callback is NOT fired
270
+ * (history reclamation is skipped — appropriate for full
271
+ * extension removal).
272
+ */
273
+ removeBlock(label) {
274
+ const idx = this.configs.findIndex((c) => c.label === label);
275
+ if (idx === -1) return false;
276
+ this.configs.splice(idx, 1);
277
+ this.blocks.delete(label);
278
+ for (const id of this._loadedSkills) if (id.startsWith(`${label}:`)) this._loadedSkills.delete(id);
279
+ return true;
280
+ }
281
+ /**
238
282
  * Get a block by label.
239
283
  */
240
284
  getBlock(label) {
@@ -297,7 +341,39 @@ var ContextBlocks = class {
297
341
  if (!this.loaded) await this.load();
298
342
  const config = this.configs.find((c) => c.label === label);
299
343
  if (!config?.provider || !isSkillProvider(config.provider)) throw new Error(`Block "${label}" is not a skill provider`);
300
- return config.provider.load(key);
344
+ const content = await config.provider.load(key);
345
+ if (content !== null) this._loadedSkills.add(`${label}:${key}`);
346
+ return content;
347
+ }
348
+ /**
349
+ * Unload a previously loaded skill. Updates the stored tool result
350
+ * message via the unload callback (set by Session).
351
+ */
352
+ unloadSkill(label, key) {
353
+ const id = `${label}:${key}`;
354
+ if (!this._loadedSkills.has(id)) return false;
355
+ this._loadedSkills.delete(id);
356
+ this._onUnloadSkill?.(label, key);
357
+ return true;
358
+ }
359
+ /**
360
+ * Get the set of currently loaded skill keys (as "label:key" strings).
361
+ */
362
+ getLoadedSkillKeys() {
363
+ return this._loadedSkills;
364
+ }
365
+ /**
366
+ * Restore loaded skill tracking from a set of "label:key" strings.
367
+ * Used by Session to reconstruct state after hibernation.
368
+ */
369
+ restoreLoadedSkills(skillIds) {
370
+ this._loadedSkills = new Set(skillIds);
371
+ }
372
+ /**
373
+ * Clear all loaded skill tracking. Called when messages are cleared.
374
+ */
375
+ clearSkillState() {
376
+ this._loadedSkills.clear();
301
377
  }
302
378
  /**
303
379
  * Index a search entry within a searchable block.
@@ -474,7 +550,7 @@ var ContextBlocks = class {
474
550
  };
475
551
  toolSet.set_context = {
476
552
  description: `Write to a context block. Available blocks:\n${blockDescriptions.join("\n")}\n\nWrites are durable and persist across sessions.`,
477
- inputSchema: jsonSchema({
553
+ inputSchema: z.fromJSONSchema({
478
554
  type: "object",
479
555
  properties,
480
556
  required
@@ -505,7 +581,7 @@ var ContextBlocks = class {
505
581
  const skillLabels = this.getSkillLabels();
506
582
  toolSet.load_context = {
507
583
  description: "Load a document from a skill block by key. Available skill blocks: " + skillLabels.map((l) => `"${l}"`).join(", ") + ". Check the system prompt for available keys.",
508
- inputSchema: jsonSchema({
584
+ inputSchema: z.fromJSONSchema({
509
585
  type: "object",
510
586
  properties: {
511
587
  label: {
@@ -528,12 +604,35 @@ var ContextBlocks = class {
528
604
  }
529
605
  }
530
606
  };
607
+ const loadedList = [...this._loadedSkills];
608
+ toolSet.unload_context = {
609
+ description: "Unload a previously loaded skill to free context space. The skill remains available for re-loading." + (loadedList.length > 0 ? " Currently loaded: " + loadedList.join(", ") + "." : " No skills currently loaded."),
610
+ inputSchema: z.fromJSONSchema({
611
+ type: "object",
612
+ properties: {
613
+ label: {
614
+ type: "string",
615
+ enum: skillLabels,
616
+ description: "Skill block label"
617
+ },
618
+ key: {
619
+ type: "string",
620
+ description: "Skill key to unload"
621
+ }
622
+ },
623
+ required: ["label", "key"]
624
+ }),
625
+ execute: async ({ label, key }) => {
626
+ if (!this.unloadSkill(label, key)) return `Skill "${key}" is not currently loaded in "${label}".`;
627
+ return `Unloaded "${key}" from ${label}. Context reclaimed.`;
628
+ }
629
+ };
531
630
  }
532
631
  if (hasSearch) {
533
632
  const searchLabels = this.getSearchLabels();
534
633
  toolSet.search_context = {
535
634
  description: "Search for information in a searchable context block. Available searchable blocks: " + searchLabels.map((l) => `"${l}"`).join(", ") + ".",
536
- inputSchema: jsonSchema({
635
+ inputSchema: z.fromJSONSchema({
537
636
  type: "object",
538
637
  properties: {
539
638
  label: {
@@ -957,8 +1056,68 @@ var Session = class Session {
957
1056
  } else if (this._cachedPrompt) promptStore = this._cachedPrompt;
958
1057
  this.storage = new AgentSessionProvider(this._agent, this._sessionId);
959
1058
  this.context = new ContextBlocks(configs, promptStore);
1059
+ this.context.setUnloadCallback((label, key) => {
1060
+ this._reclaimLoadedSkill(label, key);
1061
+ });
1062
+ this._restoreLoadedSkills();
960
1063
  this._ready = true;
961
1064
  }
1065
+ /**
1066
+ * Reconstruct which skills are loaded by scanning conversation history
1067
+ * for load_context tool results that haven't been unloaded.
1068
+ * Called during init to survive hibernation/eviction.
1069
+ */
1070
+ _restoreLoadedSkills() {
1071
+ const history = this.storage.getHistory();
1072
+ const loaded = /* @__PURE__ */ new Set();
1073
+ for (const msg of history) {
1074
+ if (msg.role !== "assistant") continue;
1075
+ for (const part of msg.parts) if (part.toolName === "load_context" && part.state === "output-available") {
1076
+ const input = part.input;
1077
+ if (input?.label && input?.key) {
1078
+ const id = `${input.label}:${input.key}`;
1079
+ if (typeof part.output === "string" && part.output.startsWith("[skill unloaded:")) loaded.delete(id);
1080
+ else loaded.add(id);
1081
+ }
1082
+ } else if (part.toolName === "unload_context" && part.state === "output-available") {
1083
+ const input = part.input;
1084
+ if (input?.label && input?.key) loaded.delete(`${input.label}:${input.key}`);
1085
+ }
1086
+ }
1087
+ if (loaded.size > 0) this.context.restoreLoadedSkills(loaded);
1088
+ }
1089
+ /**
1090
+ * Replace a load_context tool result in conversation history
1091
+ * with a short marker to reclaim context space.
1092
+ */
1093
+ _reclaimLoadedSkill(label, key) {
1094
+ const history = this.storage.getHistory();
1095
+ for (let i = history.length - 1; i >= 0; i--) {
1096
+ const msg = history[i];
1097
+ if (msg.role !== "assistant") continue;
1098
+ let changed = false;
1099
+ const newParts = msg.parts.map((part) => {
1100
+ if (part.toolName === "load_context" && part.state === "output-available") {
1101
+ const input = part.input;
1102
+ if (input?.label === label && input?.key === key) {
1103
+ changed = true;
1104
+ return {
1105
+ ...part,
1106
+ output: `[skill unloaded: ${key}]`
1107
+ };
1108
+ }
1109
+ }
1110
+ return part;
1111
+ });
1112
+ if (changed) {
1113
+ this.storage.updateMessage({
1114
+ ...msg,
1115
+ parts: newParts
1116
+ });
1117
+ return;
1118
+ }
1119
+ }
1120
+ }
962
1121
  getHistory(leafId) {
963
1122
  this._ensureReady();
964
1123
  return this.storage.getHistory(leafId);
@@ -1020,6 +1179,7 @@ var Session = class Session {
1020
1179
  clearMessages() {
1021
1180
  this._ensureReady();
1022
1181
  this.storage.clearMessages();
1182
+ this.context.clearSkillState();
1023
1183
  this._emitStatus("idle");
1024
1184
  }
1025
1185
  addCompaction(summary, fromMessageId, toMessageId) {
@@ -1079,6 +1239,60 @@ var Session = class Session {
1079
1239
  this._ensureReady();
1080
1240
  return this.context.appendToBlock(label, content);
1081
1241
  }
1242
+ /**
1243
+ * Dynamically register a new context block after session initialization.
1244
+ * Used by extensions to contribute context blocks at runtime.
1245
+ *
1246
+ * The block's provider is initialized and loaded immediately.
1247
+ * Call `refreshSystemPrompt()` afterward to include the new block
1248
+ * in the system prompt.
1249
+ *
1250
+ * Note: When called without a provider, auto-wires to SQLite via
1251
+ * AgentContextProvider. Requires the session to have been created
1252
+ * via `Session.create(agent)` (not the direct constructor).
1253
+ */
1254
+ async addContext(label, options) {
1255
+ this._ensureReady();
1256
+ const opts = options ?? {};
1257
+ let provider = opts.provider;
1258
+ if (!provider) {
1259
+ const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1260
+ provider = new AgentContextProvider(this._agent, key);
1261
+ }
1262
+ return this.context.addBlock({
1263
+ label,
1264
+ description: opts.description,
1265
+ maxTokens: opts.maxTokens,
1266
+ provider
1267
+ });
1268
+ }
1269
+ /**
1270
+ * Remove a dynamically registered context block.
1271
+ * Used during extension unload cleanup.
1272
+ *
1273
+ * Returns true if the block existed and was removed.
1274
+ * Call `refreshSystemPrompt()` afterward to rebuild the prompt
1275
+ * without the removed block.
1276
+ */
1277
+ removeContext(label) {
1278
+ this._ensureReady();
1279
+ return this.context.removeBlock(label);
1280
+ }
1281
+ /**
1282
+ * Unload a previously loaded skill, reclaiming context space.
1283
+ * The tool result in conversation history is replaced with a short marker.
1284
+ */
1285
+ unloadSkill(label, key) {
1286
+ this._ensureReady();
1287
+ return this.context.unloadSkill(label, key);
1288
+ }
1289
+ /**
1290
+ * Get currently loaded skill keys (as "label:key" strings).
1291
+ */
1292
+ getLoadedSkillKeys() {
1293
+ this._ensureReady();
1294
+ return this.context.getLoadedSkillKeys();
1295
+ }
1082
1296
  async freezeSystemPrompt() {
1083
1297
  this._ensureReady();
1084
1298
  return this.context.freezeSystemPrompt();
@@ -1101,14 +1315,12 @@ var Session = class Session {
1101
1315
  //#endregion
1102
1316
  //#region src/experimental/memory/session/manager.ts
1103
1317
  var SessionManager = class SessionManager {
1104
- constructor(agent, options = {}) {
1105
- this._maxContextMessages = 100;
1318
+ constructor(agent, _options = {}) {
1106
1319
  this._pending = [];
1107
1320
  this._sessions = /* @__PURE__ */ new Map();
1108
1321
  this._tableReady = false;
1109
1322
  this._ready = false;
1110
1323
  this.agent = agent;
1111
- this._maxContextMessages = options.maxContextMessages ?? 100;
1112
1324
  this._ready = true;
1113
1325
  this._ensureTable();
1114
1326
  }
@@ -1121,7 +1333,7 @@ var SessionManager = class SessionManager {
1121
1333
  * .withContext("soul", { provider: { get: async () => "You are helpful." } })
1122
1334
  * .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
1123
1335
  * .withCachedPrompt()
1124
- * .maxContextMessages(50);
1336
+ * .compactAfter(100_000);
1125
1337
  *
1126
1338
  * // Each getSession(id) auto-creates namespaced providers:
1127
1339
  * // memory key: "memory_<sessionId>"
@@ -1132,7 +1344,6 @@ var SessionManager = class SessionManager {
1132
1344
  static create(agent) {
1133
1345
  const mgr = Object.create(SessionManager.prototype);
1134
1346
  mgr.agent = agent;
1135
- mgr._maxContextMessages = 100;
1136
1347
  mgr._pending = [];
1137
1348
  mgr._compactionFn = null;
1138
1349
  mgr._tokenThreshold = void 0;
@@ -1152,10 +1363,6 @@ var SessionManager = class SessionManager {
1152
1363
  this._cachedPrompt = provider ?? true;
1153
1364
  return this;
1154
1365
  }
1155
- maxContextMessages(count) {
1156
- this._maxContextMessages = count;
1157
- return this;
1158
- }
1159
1366
  /**
1160
1367
  * Register a compaction function propagated to all sessions.
1161
1368
  * Called by `Session.compact()` to compress message history.
@@ -1347,9 +1554,6 @@ var SessionManager = class SessionManager {
1347
1554
  this._touch(info.id);
1348
1555
  return info;
1349
1556
  }
1350
- needsCompaction(sessionId) {
1351
- return this.getSession(sessionId).getPathLength() > this._maxContextMessages;
1352
- }
1353
1557
  addCompaction(sessionId, summary, fromId, toId) {
1354
1558
  return this.getSession(sessionId).addCompaction(summary, fromId, toId);
1355
1559
  }
@@ -1411,7 +1615,7 @@ var SessionManager = class SessionManager {
1411
1615
  tools() {
1412
1616
  return { session_search: {
1413
1617
  description: "Search past conversations for relevant context. Searches across all sessions.",
1414
- inputSchema: jsonSchema({
1618
+ inputSchema: z.fromJSONSchema({
1415
1619
  type: "object",
1416
1620
  properties: { query: {
1417
1621
  type: "string",