@deepagents/context 0.30.0 → 0.31.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.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './lib/engine.ts';
7
7
  export * from './lib/estimate.ts';
8
8
  export * from './lib/fragments.ts';
9
9
  export * from './lib/fragments/domain.ts';
10
+ export * from './lib/fragments/message/environment-reminder.ts';
10
11
  export * from './lib/fragments/message/user.ts';
11
12
  export * from './lib/fragments/reasoning.ts';
12
13
  export * from './lib/fragments/user.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iDAAiD,CAAC;AAChE,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -426,7 +426,7 @@ import {
426
426
  import { z } from "zod";
427
427
 
428
428
  // packages/context/src/lib/engine.ts
429
- import { isTextUIPart as isTextUIPart2, validateUIMessages } from "ai";
429
+ import { validateUIMessages } from "ai";
430
430
  import { mergeWith } from "lodash-es";
431
431
 
432
432
  // packages/context/src/lib/estimate.ts
@@ -753,14 +753,45 @@ function applyPartReminder(message2, value) {
753
753
  };
754
754
  }
755
755
  function resolveReminderText(item, ctx) {
756
- return typeof item.text === "function" ? item.text(ctx) : item.text;
756
+ return resolveReminder(item, ctx)?.text ?? "";
757
+ }
758
+ function normalizeReminderResolution(value) {
759
+ if (typeof value === "string") {
760
+ return value.trim().length === 0 ? null : { text: value };
761
+ }
762
+ if (value.text.trim().length === 0) {
763
+ return null;
764
+ }
765
+ return value;
766
+ }
767
+ function resolveReminder(item, ctx) {
768
+ const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
769
+ const resolved = normalizeReminderResolution(resolvedText);
770
+ if (!resolved) {
771
+ return null;
772
+ }
773
+ const metadata = item.metadata || resolved.metadata ? {
774
+ ...item.metadata ?? {},
775
+ ...resolved.metadata ?? {}
776
+ } : void 0;
777
+ return metadata ? { ...resolved, metadata } : resolved;
778
+ }
779
+ function mergeMessageMetadata(message2, addedMetadata) {
780
+ if (Object.keys(addedMetadata).length === 0) {
781
+ return;
782
+ }
783
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
784
+ message2.metadata = { ...metadata, ...addedMetadata };
757
785
  }
758
786
  function applyReminderToMessage(message2, item, ctx) {
759
- const resolvedText = resolveReminderText(item, ctx);
760
- if (resolvedText.trim().length === 0) {
787
+ const resolved = resolveReminder(item, ctx);
788
+ if (!resolved) {
761
789
  return null;
762
790
  }
763
- return item.asPart ? applyPartReminder(message2, resolvedText) : applyInlineReminder(message2, resolvedText);
791
+ if (resolved.metadata) {
792
+ mergeMessageMetadata(message2, resolved.metadata);
793
+ }
794
+ return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
764
795
  }
765
796
  function mergeReminderMetadata(message2, addedReminders) {
766
797
  if (addedReminders.length === 0) return;
@@ -798,14 +829,14 @@ function user(content, ...reminders) {
798
829
  } : { ...content, role: "user", parts: [...content.parts] };
799
830
  if (reminders.length > 0) {
800
831
  const plainText = extractPlainText(message2);
801
- const addedReminders = [];
832
+ const added = [];
802
833
  for (const item of reminders) {
803
834
  const meta = applyReminderToMessage(message2, item, {
804
835
  content: plainText
805
836
  });
806
- if (meta) addedReminders.push(meta);
837
+ if (meta) added.push(meta);
807
838
  }
808
- mergeReminderMetadata(message2, addedReminders);
839
+ mergeReminderMetadata(message2, added);
809
840
  }
810
841
  return {
811
842
  id: message2.id,
@@ -1721,6 +1752,14 @@ var ContextEngine = class {
1721
1752
  #initialized = false;
1722
1753
  /** Initial metadata to merge on first initialization */
1723
1754
  #initialMetadata;
1755
+ get #activeBranch() {
1756
+ if (!this.#branch) {
1757
+ throw new Error(
1758
+ "Branch not initialized. Call #ensureInitialized() first."
1759
+ );
1760
+ }
1761
+ return this.#branch;
1762
+ }
1724
1763
  get #renderableFragments() {
1725
1764
  return this.#fragments.filter((f) => !isConditionalReminder(f));
1726
1765
  }
@@ -1757,7 +1796,13 @@ var ContextEngine = class {
1757
1796
  });
1758
1797
  this.#initialMetadata = void 0;
1759
1798
  }
1760
- this.#branch = await this.#store.getActiveBranch(this.#chatId);
1799
+ const branch = await this.#store.getActiveBranch(this.#chatId);
1800
+ if (!branch) {
1801
+ throw new Error(
1802
+ `Active branch not found for chat "${this.#chatId}" after upsertChat`
1803
+ );
1804
+ }
1805
+ this.#branch = branch;
1761
1806
  this.#initialized = true;
1762
1807
  }
1763
1808
  /**
@@ -1841,30 +1886,35 @@ var ContextEngine = class {
1841
1886
  };
1842
1887
  }
1843
1888
  /**
1844
- * Count user+assistant message pairs (turns) in the conversation.
1845
- * Includes persisted messages and pending messages.
1846
- * A turn is one user message paired with one assistant message.
1847
- * An unpaired trailing user message counts as the next turn.
1889
+ * Count user turns in the conversation and return the previous saved user message context.
1890
+ * Includes persisted messages and pending messages in the turn count.
1848
1891
  */
1849
- async getTurnCount() {
1892
+ async #getChainContext() {
1850
1893
  await this.#ensureInitialized();
1851
- let userCount = 0;
1894
+ let turn = 0;
1895
+ let lastMessageAt;
1896
+ let lastMessage;
1852
1897
  if (this.#branch?.headMessageId) {
1853
1898
  const chain = await this.#store.getMessageChain(
1854
1899
  this.#branch.headMessageId
1855
1900
  );
1856
1901
  for (const msg of chain) {
1857
- if (msg.name === "user") {
1858
- userCount++;
1902
+ if (msg.name !== "user") {
1903
+ continue;
1859
1904
  }
1905
+ turn++;
1906
+ lastMessageAt = msg.createdAt;
1907
+ lastMessage = msg.data;
1860
1908
  }
1861
1909
  }
1862
1910
  for (const fragment2 of this.#pendingMessages) {
1863
- if (fragment2.name === "user") {
1864
- userCount++;
1865
- }
1911
+ if (fragment2.name === "user") turn++;
1866
1912
  }
1867
- return userCount;
1913
+ return { turn, lastMessageAt, lastMessage };
1914
+ }
1915
+ async getTurnCount() {
1916
+ const { turn } = await this.#getChainContext();
1917
+ return turn;
1868
1918
  }
1869
1919
  /**
1870
1920
  * Add fragments to the context.
@@ -1935,37 +1985,6 @@ var ContextEngine = class {
1935
1985
  }
1936
1986
  messages.push(fragment2.codec.encode());
1937
1987
  }
1938
- const conditionalReminders = this.#fragments.filter(isConditionalReminder);
1939
- if (conditionalReminders.length > 0) {
1940
- const turn = await this.getTurnCount();
1941
- let lastUserIndex = -1;
1942
- for (let i = messages.length - 1; i >= 0; i--) {
1943
- if (messages[i].role === "user") {
1944
- lastUserIndex = i;
1945
- break;
1946
- }
1947
- }
1948
- if (lastUserIndex >= 0) {
1949
- const original = messages[lastUserIndex];
1950
- const message2 = {
1951
- ...original,
1952
- parts: [...original.parts]
1953
- };
1954
- const plainText = message2.parts.filter(isTextUIPart2).map((p) => p.text).join(" ");
1955
- const addedReminders = [];
1956
- for (const fragment2 of conditionalReminders) {
1957
- const config = getConditionalReminder(fragment2);
1958
- if (!config.when(turn)) continue;
1959
- const meta = applyReminderToMessage(message2, config, {
1960
- content: plainText,
1961
- turn
1962
- });
1963
- if (meta) addedReminders.push(meta);
1964
- }
1965
- mergeReminderMetadata(message2, addedReminders);
1966
- messages[lastUserIndex] = message2;
1967
- }
1968
- }
1969
1988
  return {
1970
1989
  systemPrompt,
1971
1990
  messages: messages.length === 0 ? [] : await validateUIMessages({ messages })
@@ -2009,7 +2028,49 @@ var ContextEngine = class {
2009
2028
  }
2010
2029
  }
2011
2030
  }
2012
- let parentId = this.#branch.headMessageId;
2031
+ const conditionalReminders = this.#fragments.filter(isConditionalReminder);
2032
+ if (conditionalReminders.length > 0) {
2033
+ let lastUserIndex = -1;
2034
+ for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
2035
+ if (this.#pendingMessages[i].name === "user") {
2036
+ lastUserIndex = i;
2037
+ break;
2038
+ }
2039
+ }
2040
+ if (lastUserIndex >= 0) {
2041
+ const lastUserFragment = this.#pendingMessages[lastUserIndex];
2042
+ if (lastUserFragment.codec) {
2043
+ const { turn, lastMessageAt, lastMessage } = await this.#getChainContext();
2044
+ const firedReminders = conditionalReminders.map(getConditionalReminder).filter((config) => config.when(turn));
2045
+ if (firedReminders.length > 0) {
2046
+ const original = lastUserFragment.codec.encode();
2047
+ const plainText = extractPlainText(original);
2048
+ const reminders = firedReminders.flatMap((rem) => {
2049
+ const resolved = resolveReminder(rem, {
2050
+ content: plainText,
2051
+ turn,
2052
+ lastMessageAt,
2053
+ lastMessage
2054
+ });
2055
+ if (!resolved) {
2056
+ return [];
2057
+ }
2058
+ return [
2059
+ {
2060
+ text: resolved.text,
2061
+ asPart: rem.asPart,
2062
+ metadata: resolved.metadata
2063
+ }
2064
+ ];
2065
+ });
2066
+ const recreated = user(original, ...reminders);
2067
+ recreated.id = lastUserFragment.id;
2068
+ this.#pendingMessages[lastUserIndex] = recreated;
2069
+ }
2070
+ }
2071
+ }
2072
+ }
2073
+ let parentId = this.#activeBranch.headMessageId;
2013
2074
  const now = Date.now();
2014
2075
  for (const fragment2 of this.#pendingMessages) {
2015
2076
  if (!fragment2.codec) {
@@ -2037,10 +2098,10 @@ var ContextEngine = class {
2037
2098
  await this.#store.addMessage(messageData);
2038
2099
  parentId = messageData.id;
2039
2100
  }
2040
- await this.#store.updateBranchHead(this.#branch.id, parentId);
2041
- this.#branch.headMessageId = parentId;
2101
+ await this.#store.updateBranchHead(this.#activeBranch.id, parentId);
2102
+ this.#activeBranch.headMessageId = parentId;
2042
2103
  this.#pendingMessages = [];
2043
- return { headMessageId: this.#branch.headMessageId ?? void 0 };
2104
+ return { headMessageId: this.#activeBranch.headMessageId ?? void 0 };
2044
2105
  }
2045
2106
  /**
2046
2107
  * Resolve a lazy fragment by finding the appropriate ID.
@@ -3814,6 +3875,159 @@ function fromFragment(fragment2, options) {
3814
3875
  throw new Error(`Fragment "${fragment2.name}" is missing codec`);
3815
3876
  }
3816
3877
 
3878
+ // packages/context/src/lib/fragments/message/environment-reminder.ts
3879
+ var ENVIRONMENT_REMINDER_METADATA_KEY = "environment";
3880
+ function getSeason(month) {
3881
+ if (month >= 2 && month <= 4) return "Spring";
3882
+ if (month >= 5 && month <= 7) return "Summer";
3883
+ if (month >= 8 && month <= 10) return "Fall";
3884
+ return "Winter";
3885
+ }
3886
+ function isRecord2(value) {
3887
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3888
+ }
3889
+ function buildEnvironmentSnapshot(now, options) {
3890
+ const { language, timeZone } = options;
3891
+ const currentDateTime = now.toLocaleString("en-CA", {
3892
+ timeZone,
3893
+ year: "numeric",
3894
+ month: "2-digit",
3895
+ day: "2-digit",
3896
+ hour: "2-digit",
3897
+ minute: "2-digit",
3898
+ second: "2-digit",
3899
+ hour12: false
3900
+ });
3901
+ const dayOfWeek = now.toLocaleString("default", {
3902
+ weekday: "long",
3903
+ timeZone
3904
+ });
3905
+ const month = now.toLocaleString("default", {
3906
+ month: "long",
3907
+ timeZone
3908
+ });
3909
+ const year = parseInt(
3910
+ now.toLocaleString("en-CA", { year: "numeric", timeZone }),
3911
+ 10
3912
+ );
3913
+ const monthIndex = parseInt(now.toLocaleString("en-CA", { month: "numeric", timeZone }), 10) - 1;
3914
+ return {
3915
+ timeZone,
3916
+ language,
3917
+ currentDateTime,
3918
+ dateKey: now.toLocaleDateString("en-CA", { timeZone }),
3919
+ dayOfWeek,
3920
+ month,
3921
+ year,
3922
+ season: getSeason(monthIndex),
3923
+ timestamp: now.getTime()
3924
+ };
3925
+ }
3926
+ function buildEnvironmentBlock(snapshot) {
3927
+ return `TimeZone is always in ${snapshot.timeZone}.
3928
+
3929
+ Current Date & Time: "${snapshot.currentDateTime}"
3930
+ Day of Week: ${snapshot.dayOfWeek}
3931
+ Month: ${snapshot.month}
3932
+ Year: ${snapshot.year}
3933
+ Timestamp: ${snapshot.timestamp}
3934
+ Language: ${snapshot.language}
3935
+ Season: ${snapshot.season}`;
3936
+ }
3937
+ function formatChange(previous, current) {
3938
+ const changes = [];
3939
+ if (previous.timeZone !== current.timeZone) {
3940
+ changes.push(`time zone: ${previous.timeZone} -> ${current.timeZone}`);
3941
+ }
3942
+ if (previous.language !== current.language) {
3943
+ changes.push(`language: ${previous.language} -> ${current.language}`);
3944
+ }
3945
+ if (previous.dateKey !== current.dateKey) {
3946
+ changes.push(`date: ${previous.dateKey} -> ${current.dateKey}`);
3947
+ }
3948
+ if (previous.dayOfWeek !== current.dayOfWeek) {
3949
+ changes.push(`day of week: ${previous.dayOfWeek} -> ${current.dayOfWeek}`);
3950
+ }
3951
+ if (previous.month !== current.month) {
3952
+ changes.push(`month: ${previous.month} -> ${current.month}`);
3953
+ }
3954
+ if (previous.year !== current.year) {
3955
+ changes.push(`year: ${previous.year} -> ${current.year}`);
3956
+ }
3957
+ if (previous.season !== current.season) {
3958
+ changes.push(`season: ${previous.season} -> ${current.season}`);
3959
+ }
3960
+ return changes;
3961
+ }
3962
+ function buildChangeSummary(previous, current) {
3963
+ if (!previous) {
3964
+ return "";
3965
+ }
3966
+ const changes = formatChange(previous, current);
3967
+ if (changes.length === 0) {
3968
+ return "";
3969
+ }
3970
+ return `Changes since last environment snapshot:
3971
+ - ${changes.join("\n- ")}
3972
+
3973
+ `;
3974
+ }
3975
+ function parseEnvironmentSnapshot(value) {
3976
+ if (!isRecord2(value)) {
3977
+ return null;
3978
+ }
3979
+ if (typeof value.timeZone !== "string" || typeof value.language !== "string" || typeof value.currentDateTime !== "string" || typeof value.dateKey !== "string" || typeof value.dayOfWeek !== "string" || typeof value.month !== "string" || typeof value.year !== "number" || typeof value.season !== "string" || typeof value.timestamp !== "number") {
3980
+ return null;
3981
+ }
3982
+ return {
3983
+ timeZone: value.timeZone,
3984
+ language: value.language,
3985
+ currentDateTime: value.currentDateTime,
3986
+ dateKey: value.dateKey,
3987
+ dayOfWeek: value.dayOfWeek,
3988
+ month: value.month,
3989
+ year: value.year,
3990
+ season: value.season,
3991
+ timestamp: value.timestamp
3992
+ };
3993
+ }
3994
+ function getEnvironmentSnapshot(message2) {
3995
+ const metadata = isRecord2(message2?.metadata) ? message2.metadata : null;
3996
+ if (!metadata) {
3997
+ return null;
3998
+ }
3999
+ const stored = metadata[ENVIRONMENT_REMINDER_METADATA_KEY];
4000
+ if (!isRecord2(stored) || stored.version !== 1) {
4001
+ return null;
4002
+ }
4003
+ return parseEnvironmentSnapshot(stored.snapshot);
4004
+ }
4005
+ function environmentReminder(options) {
4006
+ const getNow = options?.getNow ?? (() => /* @__PURE__ */ new Date());
4007
+ const language = options?.language ?? "English (US)";
4008
+ const timeZone = options?.timeZone ?? "UTC";
4009
+ return reminder(
4010
+ (ctx) => {
4011
+ const snapshot = buildEnvironmentSnapshot(getNow(), {
4012
+ language,
4013
+ timeZone
4014
+ });
4015
+ const previousSnapshot = getEnvironmentSnapshot(ctx.lastMessage);
4016
+ const summary = buildChangeSummary(previousSnapshot, snapshot);
4017
+ return {
4018
+ text: `${summary}${buildEnvironmentBlock(snapshot)}`,
4019
+ metadata: {
4020
+ [ENVIRONMENT_REMINDER_METADATA_KEY]: {
4021
+ version: 1,
4022
+ snapshot
4023
+ }
4024
+ }
4025
+ };
4026
+ },
4027
+ { when: everyNTurns(1) }
4028
+ );
4029
+ }
4030
+
3817
4031
  // packages/context/src/lib/fragments/reasoning.ts
3818
4032
  function reasoningFramework() {
3819
4033
  return [
@@ -7350,6 +7564,7 @@ export {
7350
7564
  DockerSandboxStrategy,
7351
7565
  DockerfileBuildError,
7352
7566
  DockerfileStrategy,
7567
+ ENVIRONMENT_REMINDER_METADATA_KEY,
7353
7568
  InMemoryContextStore,
7354
7569
  LAZY_ID,
7355
7570
  MarkdownRenderer,
@@ -7387,17 +7602,20 @@ export {
7387
7602
  defaultTokenizer,
7388
7603
  discoverSkillsInDirectory,
7389
7604
  encodeSerializedValue,
7605
+ environmentReminder,
7390
7606
  errorRecoveryGuardrail,
7391
7607
  estimate,
7392
7608
  everyNTurns,
7393
7609
  example,
7394
7610
  explain,
7611
+ extractPlainText,
7395
7612
  fail,
7396
7613
  firstN,
7397
7614
  fragment,
7398
7615
  fromFragment,
7399
7616
  generateChatTitle,
7400
7617
  getConditionalReminder,
7618
+ getEnvironmentSnapshot,
7401
7619
  getFragmentData,
7402
7620
  getModelsRegistry,
7403
7621
  getReminderRanges,
@@ -7414,6 +7632,7 @@ export {
7414
7632
  isMessageFragment,
7415
7633
  lastAssistantMessage,
7416
7634
  loadSkillMetadata,
7635
+ mergeMessageMetadata,
7417
7636
  mergeReminderMetadata,
7418
7637
  message,
7419
7638
  nextAdaptivePollingDelay,
@@ -7434,6 +7653,7 @@ export {
7434
7653
  reminder,
7435
7654
  render,
7436
7655
  resetAdaptivePolling,
7656
+ resolveReminder,
7437
7657
  resolveReminderText,
7438
7658
  role,
7439
7659
  runGuardrailChain,