@deepagents/context 0.12.0 → 0.13.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/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // packages/context/src/lib/engine.ts
2
+ import { mergeWith } from "lodash-es";
3
+
1
4
  // packages/context/src/lib/estimate.ts
2
5
  import { encode } from "gpt-tokenizer";
3
6
  var defaultTokenizer = {
@@ -222,7 +225,7 @@ function message(content) {
222
225
  } : content;
223
226
  return {
224
227
  id: message2.id,
225
- name: "message",
228
+ name: message2.role,
226
229
  data: "content",
227
230
  type: "message",
228
231
  persist: true,
@@ -244,6 +247,22 @@ function assistantText(content, options) {
244
247
  parts: [{ type: "text", text: content }]
245
248
  });
246
249
  }
250
+ var LAZY_ID = Symbol("lazy-id");
251
+ function isLazyFragment(fragment2) {
252
+ return LAZY_ID in fragment2;
253
+ }
254
+ function lastAssistantMessage(content) {
255
+ return {
256
+ name: "assistant",
257
+ type: "message",
258
+ persist: true,
259
+ data: "content",
260
+ [LAZY_ID]: {
261
+ type: "last-assistant",
262
+ content
263
+ }
264
+ };
265
+ }
247
266
 
248
267
  // packages/context/src/lib/renderers/abstract.renderer.ts
249
268
  import pluralize from "pluralize";
@@ -1038,6 +1057,8 @@ var ContextEngine = class {
1038
1057
  #branch = null;
1039
1058
  #chatData = null;
1040
1059
  #initialized = false;
1060
+ /** Initial metadata to merge on first initialization */
1061
+ #initialMetadata;
1041
1062
  constructor(options) {
1042
1063
  if (!options.chatId) {
1043
1064
  throw new Error("chatId is required");
@@ -1049,6 +1070,7 @@ var ContextEngine = class {
1049
1070
  this.#chatId = options.chatId;
1050
1071
  this.#userId = options.userId;
1051
1072
  this.#branchName = "main";
1073
+ this.#initialMetadata = options.metadata;
1052
1074
  }
1053
1075
  /**
1054
1076
  * Initialize the chat and branch if they don't exist.
@@ -1061,6 +1083,15 @@ var ContextEngine = class {
1061
1083
  id: this.#chatId,
1062
1084
  userId: this.#userId
1063
1085
  });
1086
+ if (this.#initialMetadata) {
1087
+ this.#chatData = await this.#store.updateChat(this.#chatId, {
1088
+ metadata: {
1089
+ ...this.#chatData.metadata,
1090
+ ...this.#initialMetadata
1091
+ }
1092
+ });
1093
+ this.#initialMetadata = void 0;
1094
+ }
1064
1095
  this.#branch = await this.#store.getActiveBranch(this.#chatId);
1065
1096
  this.#initialized = true;
1066
1097
  }
@@ -1208,6 +1239,12 @@ var ContextEngine = class {
1208
1239
  if (this.#pendingMessages.length === 0) {
1209
1240
  return;
1210
1241
  }
1242
+ for (let i = 0; i < this.#pendingMessages.length; i++) {
1243
+ const fragment2 = this.#pendingMessages[i];
1244
+ if (isLazyFragment(fragment2)) {
1245
+ this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1246
+ }
1247
+ }
1211
1248
  let parentId = this.#branch.headMessageId;
1212
1249
  const now = Date.now();
1213
1250
  for (const fragment2 of this.#pendingMessages) {
@@ -1227,6 +1264,39 @@ var ContextEngine = class {
1227
1264
  this.#branch.headMessageId = parentId;
1228
1265
  this.#pendingMessages = [];
1229
1266
  }
1267
+ /**
1268
+ * Resolve a lazy fragment by finding the appropriate ID.
1269
+ */
1270
+ async #resolveLazyFragment(fragment2) {
1271
+ const lazy = fragment2[LAZY_ID];
1272
+ if (lazy.type === "last-assistant") {
1273
+ const lastId = await this.#getLastAssistantId();
1274
+ return assistantText(lazy.content, { id: lastId ?? crypto.randomUUID() });
1275
+ }
1276
+ throw new Error(`Unknown lazy fragment type: ${lazy.type}`);
1277
+ }
1278
+ /**
1279
+ * Find the most recent assistant message ID (pending or persisted).
1280
+ */
1281
+ async #getLastAssistantId() {
1282
+ for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
1283
+ const msg = this.#pendingMessages[i];
1284
+ if (msg.name === "assistant" && !isLazyFragment(msg)) {
1285
+ return msg.id;
1286
+ }
1287
+ }
1288
+ if (this.#branch?.headMessageId) {
1289
+ const chain = await this.#store.getMessageChain(
1290
+ this.#branch.headMessageId
1291
+ );
1292
+ for (let i = chain.length - 1; i >= 0; i--) {
1293
+ if (chain[i].name === "assistant") {
1294
+ return chain[i].id;
1295
+ }
1296
+ }
1297
+ }
1298
+ return void 0;
1299
+ }
1230
1300
  /**
1231
1301
  * Estimate token count and cost for the full context.
1232
1302
  *
@@ -1495,6 +1565,36 @@ var ContextEngine = class {
1495
1565
  }
1496
1566
  this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
1497
1567
  }
1568
+ /**
1569
+ * Track token usage for the current chat.
1570
+ * Accumulates usage metrics in chat.metadata.usage.
1571
+ *
1572
+ * @param usage - Token usage from AI SDK (LanguageModelUsage)
1573
+ *
1574
+ * @example
1575
+ * ```ts
1576
+ * // In onFinish callback
1577
+ * const usage = await result.totalUsage;
1578
+ * await context.trackUsage(usage);
1579
+ * ```
1580
+ */
1581
+ async trackUsage(usage) {
1582
+ await this.#ensureInitialized();
1583
+ const freshChatData = await this.#store.getChat(this.#chatId);
1584
+ const currentUsage = freshChatData?.metadata?.usage ?? {};
1585
+ const updatedUsage = mergeWith(
1586
+ {},
1587
+ currentUsage,
1588
+ usage,
1589
+ (a, b) => typeof a === "number" || typeof b === "number" ? (a ?? 0) + (b ?? 0) : void 0
1590
+ );
1591
+ this.#chatData = await this.#store.updateChat(this.#chatId, {
1592
+ metadata: {
1593
+ ...freshChatData?.metadata,
1594
+ usage: updatedUsage
1595
+ }
1596
+ });
1597
+ }
1498
1598
  /**
1499
1599
  * Consolidate context fragments (no-op for now).
1500
1600
  *
@@ -1506,6 +1606,35 @@ var ContextEngine = class {
1506
1606
  consolidate() {
1507
1607
  return void 0;
1508
1608
  }
1609
+ /**
1610
+ * Extract skill path mappings from available_skills fragments.
1611
+ * Returns array of { host, sandbox } for mounting in sandbox filesystem.
1612
+ *
1613
+ * Reads the original `paths` configuration stored in fragment metadata
1614
+ * by the skills() fragment helper.
1615
+ *
1616
+ * @example
1617
+ * ```ts
1618
+ * const context = new ContextEngine({ store, chatId, userId })
1619
+ * .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
1620
+ *
1621
+ * const mounts = context.getSkillMounts();
1622
+ * // [{ host: './skills', sandbox: '/skills' }]
1623
+ * ```
1624
+ */
1625
+ getSkillMounts() {
1626
+ const mounts = [];
1627
+ for (const fragment2 of this.#fragments) {
1628
+ if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
1629
+ for (const mapping of fragment2.metadata.paths) {
1630
+ if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
1631
+ mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+ return mounts;
1637
+ }
1509
1638
  /**
1510
1639
  * Inspect the full context state for debugging.
1511
1640
  * Returns a JSON-serializable object with context information.
@@ -1814,9 +1943,10 @@ var errorRecoveryGuardrail = {
1814
1943
  "My response format was invalid. Let me try again with a properly formatted response."
1815
1944
  );
1816
1945
  }
1946
+ console.dir({ part }, { depth: null });
1817
1947
  return logAndFail(
1818
1948
  "Unknown error",
1819
- `An error occurred: ${errorText.slice(0, 100)}. Let me try a different approach.`
1949
+ `An error occurred: ${errorText}. Let me try a different approach.`
1820
1950
  );
1821
1951
  }
1822
1952
  };
@@ -2642,9 +2772,13 @@ function discoverSkillsInDirectory(directory) {
2642
2772
 
2643
2773
  // packages/context/src/lib/skills/fragments.ts
2644
2774
  function skills(options) {
2775
+ const pathMapping = /* @__PURE__ */ new Map();
2776
+ for (const { host, sandbox } of options.paths) {
2777
+ pathMapping.set(host, sandbox);
2778
+ }
2645
2779
  const skillsMap = /* @__PURE__ */ new Map();
2646
- for (const dir of options.paths) {
2647
- const discovered = discoverSkillsInDirectory(dir);
2780
+ for (const { host } of options.paths) {
2781
+ const discovered = discoverSkillsInDirectory(host);
2648
2782
  for (const skill of discovered) {
2649
2783
  skillsMap.set(skill.name, skill);
2650
2784
  }
@@ -2659,14 +2793,26 @@ function skills(options) {
2659
2793
  (s) => !options.exclude.includes(s.name)
2660
2794
  );
2661
2795
  }
2662
- const skillFragments = filteredSkills.map((skill) => ({
2663
- name: "skill",
2664
- data: {
2665
- name: skill.name,
2666
- path: skill.skillMdPath,
2667
- description: skill.description
2796
+ const skillFragments = filteredSkills.map((skill) => {
2797
+ const originalPath = skill.skillMdPath;
2798
+ let sandboxPath = originalPath;
2799
+ for (const [host, sandbox] of pathMapping) {
2800
+ if (originalPath.startsWith(host)) {
2801
+ const relativePath = originalPath.slice(host.length);
2802
+ sandboxPath = sandbox + relativePath;
2803
+ break;
2804
+ }
2668
2805
  }
2669
- }));
2806
+ return {
2807
+ name: "skill",
2808
+ data: {
2809
+ name: skill.name,
2810
+ path: sandboxPath,
2811
+ description: skill.description
2812
+ },
2813
+ metadata: { originalPath }
2814
+ };
2815
+ });
2670
2816
  return {
2671
2817
  name: "available_skills",
2672
2818
  data: [
@@ -2675,7 +2821,11 @@ function skills(options) {
2675
2821
  data: SKILLS_INSTRUCTIONS
2676
2822
  },
2677
2823
  ...skillFragments
2678
- ]
2824
+ ],
2825
+ metadata: {
2826
+ paths: options.paths
2827
+ // Store original path mappings for getSkillMounts()
2828
+ }
2679
2829
  };
2680
2830
  }
2681
2831
  var SKILLS_INSTRUCTIONS = `When a user's request matches one of the skills listed below, read the skill's SKILL.md file to get detailed instructions before proceeding. Skills provide specialized knowledge and workflows for specific tasks.
@@ -2870,12 +3020,20 @@ var SqliteContextStore = class extends ContextStore {
2870
3020
  }
2871
3021
  async listChats(options) {
2872
3022
  const params = [];
2873
- let whereClause = "";
3023
+ const whereClauses = [];
2874
3024
  let limitClause = "";
2875
3025
  if (options?.userId) {
2876
- whereClause = "WHERE c.userId = ?";
3026
+ whereClauses.push("c.userId = ?");
2877
3027
  params.push(options.userId);
2878
3028
  }
3029
+ if (options?.metadata) {
3030
+ whereClauses.push(`json_extract(c.metadata, '$.' || ?) = ?`);
3031
+ params.push(options.metadata.key);
3032
+ params.push(
3033
+ typeof options.metadata.value === "boolean" ? options.metadata.value ? 1 : 0 : options.metadata.value
3034
+ );
3035
+ }
3036
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
2879
3037
  if (options?.limit !== void 0) {
2880
3038
  limitClause = " LIMIT ?";
2881
3039
  params.push(options.limit);
@@ -2889,6 +3047,7 @@ var SqliteContextStore = class extends ContextStore {
2889
3047
  c.id,
2890
3048
  c.userId,
2891
3049
  c.title,
3050
+ c.metadata,
2892
3051
  c.createdAt,
2893
3052
  c.updatedAt,
2894
3053
  COUNT(DISTINCT m.id) as messageCount,
@@ -2904,6 +3063,7 @@ var SqliteContextStore = class extends ContextStore {
2904
3063
  id: row.id,
2905
3064
  userId: row.userId,
2906
3065
  title: row.title ?? void 0,
3066
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
2907
3067
  messageCount: row.messageCount,
2908
3068
  branchCount: row.branchCount,
2909
3069
  createdAt: row.createdAt,
@@ -2935,9 +3095,16 @@ var SqliteContextStore = class extends ContextStore {
2935
3095
  async addMessage(message2) {
2936
3096
  this.#db.prepare(
2937
3097
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
2938
- VALUES (?, ?, ?, ?, ?, ?, ?)
3098
+ VALUES (
3099
+ ?1,
3100
+ ?2,
3101
+ CASE WHEN ?3 = ?1 THEN (SELECT parentId FROM messages WHERE id = ?1) ELSE ?3 END,
3102
+ ?4,
3103
+ ?5,
3104
+ ?6,
3105
+ ?7
3106
+ )
2939
3107
  ON CONFLICT(id) DO UPDATE SET
2940
- parentId = excluded.parentId,
2941
3108
  name = excluded.name,
2942
3109
  type = excluded.type,
2943
3110
  data = excluded.data`
@@ -3487,13 +3654,9 @@ var Agent = class _Agent {
3487
3654
  writer.write({ type: "finish" });
3488
3655
  return;
3489
3656
  }
3490
- writer.write({
3491
- type: "text-delta",
3492
- id: generateId2(),
3493
- delta: ` ${failureFeedback}`
3494
- });
3657
+ writeText(writer, failureFeedback);
3495
3658
  const selfCorrectionText = accumulatedText + " " + failureFeedback;
3496
- context.set(assistantText(selfCorrectionText));
3659
+ context.set(lastAssistantMessage(selfCorrectionText));
3497
3660
  await context.save();
3498
3661
  currentResult = await this.#createRawStream(
3499
3662
  contextVariables,
@@ -3598,6 +3761,22 @@ var repairToolCall = async ({
3598
3761
  });
3599
3762
  return { ...toolCall, input: JSON.stringify(output) };
3600
3763
  };
3764
+ function writeText(writer, text) {
3765
+ const feedbackPartId = generateId2();
3766
+ writer.write({
3767
+ id: feedbackPartId,
3768
+ type: "text-start"
3769
+ });
3770
+ writer.write({
3771
+ id: feedbackPartId,
3772
+ type: "text-delta",
3773
+ delta: ` ${text}`
3774
+ });
3775
+ writer.write({
3776
+ id: feedbackPartId,
3777
+ type: "text-end"
3778
+ });
3779
+ }
3601
3780
 
3602
3781
  // packages/context/src/lib/render.ts
3603
3782
  function render(tag, ...fragments) {
@@ -3622,6 +3801,7 @@ export {
3622
3801
  DockerfileBuildError,
3623
3802
  DockerfileStrategy,
3624
3803
  InMemoryContextStore,
3804
+ LAZY_ID,
3625
3805
  MarkdownRenderer,
3626
3806
  ModelsRegistry,
3627
3807
  MountPathError,
@@ -3658,7 +3838,9 @@ export {
3658
3838
  isDockerfileOptions,
3659
3839
  isFragment,
3660
3840
  isFragmentObject,
3841
+ isLazyFragment,
3661
3842
  isMessageFragment,
3843
+ lastAssistantMessage,
3662
3844
  loadSkillMetadata,
3663
3845
  message,
3664
3846
  parseFrontmatter,