@deepagents/context 0.34.0 → 0.35.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
@@ -151,8 +151,10 @@ var Agent = class _Agent {
151
151
  providerOptions: this.#options.providerOptions,
152
152
  model: this.#options.model,
153
153
  system: systemPrompt,
154
- messages: await convertToModelMessages(messages),
155
- stopWhen: stepCountIs(25),
154
+ messages: await convertToModelMessages(messages, {
155
+ ignoreIncompleteToolCalls: true
156
+ }),
157
+ stopWhen: stepCountIs(200),
156
158
  tools: this.#options.tools,
157
159
  experimental_context: contextVariables,
158
160
  experimental_repairToolCall: createRepairToolCall(this.#options.model),
@@ -218,9 +220,11 @@ var Agent = class _Agent {
218
220
  providerOptions: this.#options.providerOptions,
219
221
  model,
220
222
  system: systemPrompt,
221
- messages: await convertToModelMessages(messages),
223
+ messages: await convertToModelMessages(messages, {
224
+ ignoreIncompleteToolCalls: true
225
+ }),
222
226
  experimental_repairToolCall: createRepairToolCall(model),
223
- stopWhen: stepCountIs(50),
227
+ stopWhen: stepCountIs(200),
224
228
  experimental_transform: config?.transform ?? smoothStream(),
225
229
  tools: this.#options.tools,
226
230
  experimental_context: contextVariables,
@@ -360,8 +364,10 @@ function structuredOutput(options) {
360
364
  providerOptions: options.providerOptions,
361
365
  model: options.model,
362
366
  system: systemPrompt,
363
- messages: await convertToModelMessages(messages),
364
- stopWhen: stepCountIs(25),
367
+ messages: await convertToModelMessages(messages, {
368
+ ignoreIncompleteToolCalls: true
369
+ }),
370
+ stopWhen: stepCountIs(200),
365
371
  experimental_repairToolCall: createRepairToolCall(options.model),
366
372
  experimental_context: contextVariables,
367
373
  output: Output.object({ schema: options.schema }),
@@ -385,8 +391,10 @@ function structuredOutput(options) {
385
391
  model: options.model,
386
392
  system: systemPrompt,
387
393
  experimental_repairToolCall: createRepairToolCall(options.model),
388
- messages: await convertToModelMessages(messages),
389
- stopWhen: stepCountIs(50),
394
+ messages: await convertToModelMessages(messages, {
395
+ ignoreIncompleteToolCalls: true
396
+ }),
397
+ stopWhen: stepCountIs(200),
390
398
  experimental_transform: config?.transform ?? smoothStream(),
391
399
  experimental_context: contextVariables,
392
400
  output: Output.object({ schema: options.schema }),
@@ -593,438 +601,93 @@ async function estimate(modelId, renderer, ...fragments) {
593
601
  // packages/context/src/lib/fragments/message/user.ts
594
602
  import { generateId as generateId3 } from "ai";
595
603
 
596
- // packages/context/src/lib/text.ts
597
- import { isTextUIPart } from "ai";
598
- function getTextParts(message2) {
599
- return message2.parts.filter(isTextUIPart).map((part) => part.text);
600
- }
601
- function extractPlainText(message2) {
602
- return getTextParts(message2).join(" ");
603
- }
604
-
605
- // packages/context/src/lib/fragments/message/user.ts
606
- function everyNTurns(n) {
607
- return ({ turn }) => turn % n === 0;
608
- }
609
- function once() {
610
- return ({ turn }) => turn === 1;
611
- }
612
- function firstN(n) {
613
- return ({ turn }) => turn <= n;
614
- }
615
- function afterTurn(n) {
616
- return ({ turn }) => turn > n;
617
- }
618
- function and(...predicates) {
619
- return (ctx) => predicates.every((p) => p(ctx));
620
- }
621
- function or(...predicates) {
622
- return (ctx) => predicates.some((p) => p(ctx));
623
- }
624
- function not(predicate) {
625
- return (ctx) => !predicate(ctx);
626
- }
627
- function contentIncludes(keywords) {
628
- const lower = keywords.map((k) => k.toLowerCase());
629
- return (ctx) => {
630
- const text = ctx.content.toLowerCase();
631
- return lower.some((kw) => text.includes(kw));
632
- };
633
- }
634
- function contentPattern(pattern) {
635
- return (ctx) => {
636
- pattern.lastIndex = 0;
637
- return pattern.test(ctx.content);
638
- };
639
- }
640
- function toDateParts(date, tz) {
641
- const parts = new Intl.DateTimeFormat("en-CA", {
642
- timeZone: tz,
643
- year: "numeric",
644
- month: "2-digit",
645
- day: "2-digit",
646
- hour: "2-digit",
647
- hourCycle: "h23"
648
- }).formatToParts(date);
649
- const get = (type) => parts.find((p) => p.type === type).value;
650
- return {
651
- year: get("year"),
652
- month: get("month"),
653
- day: get("day"),
654
- hour: get("hour")
655
- };
656
- }
657
- function temporalChanged(ctx, tz, getKey) {
658
- if (ctx.lastMessageAt === void 0) return true;
659
- const nowParts = toDateParts(/* @__PURE__ */ new Date(), tz);
660
- const prevParts = toDateParts(new Date(ctx.lastMessageAt), tz);
661
- return getKey(nowParts) !== getKey(prevParts);
662
- }
663
- function getSeason(month) {
664
- if (month >= 2 && month <= 4) return "Spring";
665
- if (month >= 5 && month <= 7) return "Summer";
666
- if (month >= 8 && month <= 10) return "Fall";
667
- return "Winter";
668
- }
669
- function isoWeekKey(parts) {
670
- const d = new Date(
671
- Date.UTC(
672
- parseInt(parts.year),
673
- parseInt(parts.month) - 1,
674
- parseInt(parts.day)
675
- )
676
- );
677
- d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
678
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
679
- const weekNo = Math.ceil(
680
- ((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
681
- );
682
- return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
683
- }
684
- function dayChanged(tz = "UTC") {
685
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}`);
686
- }
687
- function hourChanged(tz = "UTC") {
688
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}-${p.hour}`);
689
- }
690
- function monthChanged(tz = "UTC") {
691
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}`);
692
- }
693
- function yearChanged(tz = "UTC") {
694
- return (ctx) => temporalChanged(ctx, tz, (p) => p.year);
695
- }
696
- function seasonChanged(tz = "UTC") {
697
- return (ctx) => temporalChanged(ctx, tz, (p) => getSeason(parseInt(p.month) - 1));
698
- }
699
- function weekChanged(tz = "UTC") {
700
- return (ctx) => temporalChanged(ctx, tz, isoWeekKey);
701
- }
702
- function isConditionalReminder(fragment2) {
703
- return fragment2.name === "reminder" && !!fragment2.metadata?.reminder;
704
- }
705
- function getConditionalReminder(fragment2) {
706
- return fragment2.metadata.reminder;
707
- }
708
- var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
709
- var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
710
- function getReminderRanges(metadata) {
711
- return metadata?.reminders ?? [];
712
- }
713
- function stripTextByRanges(text, ranges) {
714
- if (ranges.length === 0) {
715
- return text;
604
+ // packages/context/src/lib/renderers/abstract.renderer.ts
605
+ import pluralize from "pluralize";
606
+ import { titlecase } from "stringcase";
607
+ var ContextRenderer = class {
608
+ options;
609
+ constructor(options = {}) {
610
+ this.options = options;
716
611
  }
717
- const normalized = ranges.map((range) => ({
718
- start: Math.max(0, Math.min(text.length, range.start)),
719
- end: Math.max(0, Math.min(text.length, range.end))
720
- })).filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
721
- if (normalized.length === 0) {
722
- return text;
612
+ /**
613
+ * Check if data is a primitive (string, number, boolean).
614
+ */
615
+ isPrimitive(data) {
616
+ return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
723
617
  }
724
- let cursor = 0;
725
- let output = "";
726
- for (const range of normalized) {
727
- if (range.start < cursor) {
728
- if (range.end > cursor) {
729
- cursor = range.end;
618
+ /**
619
+ * Group fragments by name for groupFragments option.
620
+ */
621
+ groupByName(fragments) {
622
+ const groups = /* @__PURE__ */ new Map();
623
+ for (const fragment2 of fragments) {
624
+ const existing = groups.get(fragment2.name) ?? [];
625
+ existing.push(fragment2);
626
+ groups.set(fragment2.name, existing);
627
+ }
628
+ return groups;
629
+ }
630
+ /**
631
+ * Remove null/undefined from fragments and fragment data recursively.
632
+ * This protects renderers from nullish values and ensures they are ignored
633
+ * consistently across all output formats.
634
+ */
635
+ sanitizeFragments(fragments) {
636
+ const sanitized = [];
637
+ for (const fragment2 of fragments) {
638
+ const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
639
+ if (cleaned) {
640
+ sanitized.push(cleaned);
730
641
  }
731
- continue;
732
642
  }
733
- output += text.slice(cursor, range.start);
734
- cursor = range.end;
643
+ return sanitized;
735
644
  }
736
- output += text.slice(cursor);
737
- return output.trimEnd();
738
- }
739
- function stripReminders(message2) {
740
- const reminderRanges = getReminderRanges(
741
- isRecord(message2.metadata) ? message2.metadata : void 0
742
- );
743
- const rangesByPartIndex = /* @__PURE__ */ new Map();
744
- for (const range of reminderRanges) {
745
- const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
746
- partRanges.push({ start: range.start, end: range.end });
747
- rangesByPartIndex.set(range.partIndex, partRanges);
645
+ sanitizeFragment(fragment2, seen) {
646
+ const data = this.sanitizeData(getFragmentData(fragment2), seen);
647
+ if (data == null) {
648
+ return null;
649
+ }
650
+ return {
651
+ ...fragment2,
652
+ data
653
+ };
748
654
  }
749
- const strippedParts = message2.parts.flatMap((part, partIndex) => {
750
- const clonedPart = { ...part };
751
- const ranges = rangesByPartIndex.get(partIndex);
752
- if (clonedPart.type !== "text" || ranges === void 0) {
753
- return [clonedPart];
655
+ sanitizeData(data, seen) {
656
+ if (data == null) {
657
+ return void 0;
754
658
  }
755
- const strippedText = stripTextByRanges(clonedPart.text, ranges);
756
- if (strippedText.length === 0) {
757
- return [];
659
+ if (isFragment(data)) {
660
+ return this.sanitizeFragment(data, seen) ?? void 0;
758
661
  }
759
- return [{ ...clonedPart, text: strippedText }];
760
- });
761
- const nextMessage = {
762
- ...message2,
763
- parts: strippedParts
764
- };
765
- if (isRecord(message2.metadata)) {
766
- const metadata = { ...message2.metadata };
767
- delete metadata.reminders;
768
- if (Object.keys(metadata).length > 0) {
769
- nextMessage.metadata = metadata;
770
- } else {
771
- delete nextMessage.metadata;
662
+ if (Array.isArray(data)) {
663
+ if (seen.has(data)) {
664
+ return void 0;
665
+ }
666
+ seen.add(data);
667
+ const cleaned = [];
668
+ for (const item of data) {
669
+ const sanitizedItem = this.sanitizeData(item, seen);
670
+ if (sanitizedItem != null) {
671
+ cleaned.push(sanitizedItem);
672
+ }
673
+ }
674
+ return cleaned;
772
675
  }
773
- }
774
- return nextMessage;
775
- }
776
- function isRecord(value) {
777
- return typeof value === "object" && value !== null && !Array.isArray(value);
778
- }
779
- function assertReminderText(text) {
780
- if (text.trim().length === 0) {
781
- throw new Error("Reminder text must not be empty");
782
- }
783
- }
784
- function formatTaggedReminder(text) {
785
- return `${SYSTEM_REMINDER_OPEN_TAG}${text}${SYSTEM_REMINDER_CLOSE_TAG}`;
786
- }
787
- function findLastTextPartIndex(message2) {
788
- for (let i = message2.parts.length - 1; i >= 0; i--) {
789
- if (message2.parts[i].type === "text") {
790
- return i;
791
- }
792
- }
793
- return void 0;
794
- }
795
- function ensureTextPart(message2) {
796
- const existingIndex = findLastTextPartIndex(message2);
797
- if (existingIndex !== void 0) {
798
- return existingIndex;
799
- }
800
- const reminderPart = {
801
- type: "text",
802
- text: ""
803
- };
804
- message2.parts.push(reminderPart);
805
- return message2.parts.length - 1;
806
- }
807
- function applyInlineReminder(message2, value) {
808
- const partIndex = ensureTextPart(message2);
809
- const textPart = message2.parts[partIndex];
810
- if (textPart.type !== "text") {
811
- throw new Error("Failed to resolve text part for inline reminder");
812
- }
813
- const reminderText = formatTaggedReminder(value);
814
- const start = textPart.text.length;
815
- const updatedText = `${textPart.text}${reminderText}`;
816
- message2.parts[partIndex] = { ...textPart, text: updatedText };
817
- return {
818
- id: generateId3(),
819
- text: value,
820
- partIndex,
821
- start,
822
- end: start + reminderText.length,
823
- mode: "inline"
824
- };
825
- }
826
- function applyPartReminder(message2, value) {
827
- const part = { type: "text", text: value };
828
- message2.parts.push(part);
829
- const partIndex = message2.parts.length - 1;
830
- return {
831
- id: generateId3(),
832
- text: value,
833
- partIndex,
834
- start: 0,
835
- end: value.length,
836
- mode: "part"
837
- };
838
- }
839
- function resolveReminderText(item, ctx) {
840
- return resolveReminder(item, ctx)?.text ?? "";
841
- }
842
- function normalizeReminderResolution(value) {
843
- if (typeof value === "string") {
844
- return value.trim().length === 0 ? null : { text: value };
845
- }
846
- if (value.text.trim().length === 0) {
847
- return null;
848
- }
849
- return value;
850
- }
851
- function resolveReminder(item, ctx) {
852
- const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
853
- const resolved = normalizeReminderResolution(resolvedText);
854
- if (!resolved) {
855
- return null;
856
- }
857
- const metadata = item.metadata || resolved.metadata ? {
858
- ...item.metadata ?? {},
859
- ...resolved.metadata ?? {}
860
- } : void 0;
861
- return metadata ? { ...resolved, metadata } : resolved;
862
- }
863
- function mergeMessageMetadata(message2, addedMetadata) {
864
- if (Object.keys(addedMetadata).length === 0) {
865
- return;
866
- }
867
- const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
868
- message2.metadata = { ...metadata, ...addedMetadata };
869
- }
870
- function applyReminderToMessage(message2, item, ctx) {
871
- const resolved = resolveReminder(item, ctx);
872
- if (!resolved) {
873
- return null;
874
- }
875
- if (resolved.metadata) {
876
- mergeMessageMetadata(message2, resolved.metadata);
877
- }
878
- return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
879
- }
880
- function mergeReminderMetadata(message2, addedReminders) {
881
- if (addedReminders.length === 0) return;
882
- const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
883
- const existing = Array.isArray(metadata.reminders) ? metadata.reminders : [];
884
- metadata.reminders = [...existing, ...addedReminders];
885
- message2.metadata = metadata;
886
- }
887
- function reminder(text, options) {
888
- if (typeof text === "string") {
889
- assertReminderText(text);
890
- }
891
- if (options && "when" in options && options.when) {
892
- return {
893
- name: "reminder",
894
- metadata: {
895
- reminder: {
896
- text,
897
- when: options.when,
898
- asPart: options.asPart ?? false
899
- }
900
- }
901
- };
902
- }
903
- return {
904
- text,
905
- asPart: options?.asPart ?? false
906
- };
907
- }
908
- function user(content, ...reminders) {
909
- const message2 = typeof content === "string" ? {
910
- id: generateId3(),
911
- role: "user",
912
- parts: [{ type: "text", text: content }]
913
- } : { ...content, role: "user", parts: [...content.parts] };
914
- if (reminders.length > 0) {
915
- const plainText = extractPlainText(message2);
916
- const added = [];
917
- for (const item of reminders) {
918
- const meta = applyReminderToMessage(message2, item, {
919
- content: plainText
920
- });
921
- if (meta) added.push(meta);
922
- }
923
- mergeReminderMetadata(message2, added);
924
- }
925
- return {
926
- id: message2.id,
927
- name: "user",
928
- type: "message",
929
- persist: true,
930
- codec: {
931
- decode() {
932
- return message2;
933
- },
934
- encode() {
935
- return message2;
936
- }
937
- }
938
- };
939
- }
940
-
941
- // packages/context/src/lib/renderers/abstract.renderer.ts
942
- import pluralize from "pluralize";
943
- import { titlecase } from "stringcase";
944
- var ContextRenderer = class {
945
- options;
946
- constructor(options = {}) {
947
- this.options = options;
948
- }
949
- /**
950
- * Check if data is a primitive (string, number, boolean).
951
- */
952
- isPrimitive(data) {
953
- return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
954
- }
955
- /**
956
- * Group fragments by name for groupFragments option.
957
- */
958
- groupByName(fragments) {
959
- const groups = /* @__PURE__ */ new Map();
960
- for (const fragment2 of fragments) {
961
- const existing = groups.get(fragment2.name) ?? [];
962
- existing.push(fragment2);
963
- groups.set(fragment2.name, existing);
964
- }
965
- return groups;
966
- }
967
- /**
968
- * Remove null/undefined from fragments and fragment data recursively.
969
- * This protects renderers from nullish values and ensures they are ignored
970
- * consistently across all output formats.
971
- */
972
- sanitizeFragments(fragments) {
973
- const sanitized = [];
974
- for (const fragment2 of fragments) {
975
- const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
976
- if (cleaned) {
977
- sanitized.push(cleaned);
978
- }
979
- }
980
- return sanitized;
981
- }
982
- sanitizeFragment(fragment2, seen) {
983
- const data = this.sanitizeData(getFragmentData(fragment2), seen);
984
- if (data == null) {
985
- return null;
986
- }
987
- return {
988
- ...fragment2,
989
- data
990
- };
991
- }
992
- sanitizeData(data, seen) {
993
- if (data == null) {
994
- return void 0;
995
- }
996
- if (isFragment(data)) {
997
- return this.sanitizeFragment(data, seen) ?? void 0;
998
- }
999
- if (Array.isArray(data)) {
1000
- if (seen.has(data)) {
1001
- return void 0;
1002
- }
1003
- seen.add(data);
1004
- const cleaned = [];
1005
- for (const item of data) {
1006
- const sanitizedItem = this.sanitizeData(item, seen);
1007
- if (sanitizedItem != null) {
1008
- cleaned.push(sanitizedItem);
1009
- }
1010
- }
1011
- return cleaned;
1012
- }
1013
- if (isFragmentObject(data)) {
1014
- if (seen.has(data)) {
1015
- return void 0;
1016
- }
1017
- seen.add(data);
1018
- const cleaned = {};
1019
- for (const [key, value] of Object.entries(data)) {
1020
- const sanitizedValue = this.sanitizeData(value, seen);
1021
- if (sanitizedValue != null) {
1022
- cleaned[key] = sanitizedValue;
1023
- }
1024
- }
1025
- return cleaned;
1026
- }
1027
- return data;
676
+ if (isFragmentObject(data)) {
677
+ if (seen.has(data)) {
678
+ return void 0;
679
+ }
680
+ seen.add(data);
681
+ const cleaned = {};
682
+ for (const [key, value] of Object.entries(data)) {
683
+ const sanitizedValue = this.sanitizeData(value, seen);
684
+ if (sanitizedValue != null) {
685
+ cleaned[key] = sanitizedValue;
686
+ }
687
+ }
688
+ return cleaned;
689
+ }
690
+ return data;
1028
691
  }
1029
692
  /**
1030
693
  * Template method - dispatches value to appropriate handler.
@@ -1814,16 +1477,380 @@ ${entries}`;
1814
1477
  }
1815
1478
  };
1816
1479
 
1817
- // packages/context/src/lib/store/store.ts
1818
- var ContextStore = class {
1819
- };
1820
-
1821
- // packages/context/src/lib/engine.ts
1822
- function estimateMessageContent(data) {
1823
- return typeof data === "string" ? data : JSON.stringify(data);
1480
+ // packages/context/src/lib/text.ts
1481
+ import { isTextUIPart } from "ai";
1482
+ function getTextParts(message2) {
1483
+ return message2.parts.filter(isTextUIPart).map((part) => part.text);
1824
1484
  }
1825
- function isLanguageModelUsage(value) {
1826
- return typeof value === "object" && value !== null && "totalTokens" in value;
1485
+ function extractPlainText(message2) {
1486
+ return getTextParts(message2).join(" ");
1487
+ }
1488
+
1489
+ // packages/context/src/lib/fragments/message/user.ts
1490
+ function everyNTurns(n) {
1491
+ return ({ turn }) => turn % n === 0;
1492
+ }
1493
+ function once() {
1494
+ return ({ turn }) => turn === 1;
1495
+ }
1496
+ function firstN(n) {
1497
+ return ({ turn }) => turn <= n;
1498
+ }
1499
+ function afterTurn(n) {
1500
+ return ({ turn }) => turn > n;
1501
+ }
1502
+ function and(...predicates) {
1503
+ return async (ctx) => {
1504
+ for (const it of predicates) {
1505
+ if (!await it(ctx)) return false;
1506
+ }
1507
+ return true;
1508
+ };
1509
+ }
1510
+ function or(...predicates) {
1511
+ return async (ctx) => {
1512
+ for (const it of predicates) {
1513
+ if (await it(ctx)) return true;
1514
+ }
1515
+ return false;
1516
+ };
1517
+ }
1518
+ function not(predicate) {
1519
+ return async (ctx) => !await predicate(ctx);
1520
+ }
1521
+ function contentIncludes(keywords) {
1522
+ const lower = keywords.map((k) => k.toLowerCase());
1523
+ return (ctx) => {
1524
+ const text = ctx.content.toLowerCase();
1525
+ return lower.some((kw) => text.includes(kw));
1526
+ };
1527
+ }
1528
+ function contentPattern(pattern) {
1529
+ return (ctx) => {
1530
+ pattern.lastIndex = 0;
1531
+ return pattern.test(ctx.content);
1532
+ };
1533
+ }
1534
+ function toDateParts(date, tz) {
1535
+ const parts = new Intl.DateTimeFormat("en-CA", {
1536
+ timeZone: tz,
1537
+ year: "numeric",
1538
+ month: "2-digit",
1539
+ day: "2-digit",
1540
+ hour: "2-digit",
1541
+ hourCycle: "h23"
1542
+ }).formatToParts(date);
1543
+ const get = (type) => parts.find((p) => p.type === type).value;
1544
+ return {
1545
+ year: get("year"),
1546
+ month: get("month"),
1547
+ day: get("day"),
1548
+ hour: get("hour")
1549
+ };
1550
+ }
1551
+ function temporalChanged(ctx, tz, getKey) {
1552
+ if (ctx.lastMessageAt === void 0) return true;
1553
+ const nowParts = toDateParts(/* @__PURE__ */ new Date(), tz);
1554
+ const prevParts = toDateParts(new Date(ctx.lastMessageAt), tz);
1555
+ return getKey(nowParts) !== getKey(prevParts);
1556
+ }
1557
+ function getSeason(month) {
1558
+ if (month >= 2 && month <= 4) return "Spring";
1559
+ if (month >= 5 && month <= 7) return "Summer";
1560
+ if (month >= 8 && month <= 10) return "Fall";
1561
+ return "Winter";
1562
+ }
1563
+ function isoWeekKey(parts) {
1564
+ const d = new Date(
1565
+ Date.UTC(
1566
+ parseInt(parts.year),
1567
+ parseInt(parts.month) - 1,
1568
+ parseInt(parts.day)
1569
+ )
1570
+ );
1571
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
1572
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
1573
+ const weekNo = Math.ceil(
1574
+ ((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
1575
+ );
1576
+ return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
1577
+ }
1578
+ function dayChanged(tz = "UTC") {
1579
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}`);
1580
+ }
1581
+ function hourChanged(tz = "UTC") {
1582
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}-${p.hour}`);
1583
+ }
1584
+ function monthChanged(tz = "UTC") {
1585
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}`);
1586
+ }
1587
+ function yearChanged(tz = "UTC") {
1588
+ return (ctx) => temporalChanged(ctx, tz, (p) => p.year);
1589
+ }
1590
+ function seasonChanged(tz = "UTC") {
1591
+ return (ctx) => temporalChanged(ctx, tz, (p) => getSeason(parseInt(p.month) - 1));
1592
+ }
1593
+ function weekChanged(tz = "UTC") {
1594
+ return (ctx) => temporalChanged(ctx, tz, isoWeekKey);
1595
+ }
1596
+ function isConditionalReminder(fragment2) {
1597
+ return fragment2.name === "reminder" && !!fragment2.metadata?.reminder;
1598
+ }
1599
+ function getConditionalReminder(fragment2) {
1600
+ return fragment2.metadata.reminder;
1601
+ }
1602
+ var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
1603
+ var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
1604
+ function getReminderRanges(metadata) {
1605
+ return metadata?.reminders ?? [];
1606
+ }
1607
+ function stripTextByRanges(text, ranges) {
1608
+ if (ranges.length === 0) {
1609
+ return text;
1610
+ }
1611
+ const normalized = ranges.map((range) => ({
1612
+ start: Math.max(0, Math.min(text.length, range.start)),
1613
+ end: Math.max(0, Math.min(text.length, range.end))
1614
+ })).filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
1615
+ if (normalized.length === 0) {
1616
+ return text;
1617
+ }
1618
+ let cursor = 0;
1619
+ let output = "";
1620
+ for (const range of normalized) {
1621
+ if (range.start < cursor) {
1622
+ if (range.end > cursor) {
1623
+ cursor = range.end;
1624
+ }
1625
+ continue;
1626
+ }
1627
+ output += text.slice(cursor, range.start);
1628
+ cursor = range.end;
1629
+ }
1630
+ output += text.slice(cursor);
1631
+ return output.trimEnd();
1632
+ }
1633
+ function stripReminders(message2) {
1634
+ const reminderRanges = getReminderRanges(
1635
+ isRecord(message2.metadata) ? message2.metadata : void 0
1636
+ );
1637
+ const rangesByPartIndex = /* @__PURE__ */ new Map();
1638
+ for (const range of reminderRanges) {
1639
+ const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
1640
+ partRanges.push({ start: range.start, end: range.end });
1641
+ rangesByPartIndex.set(range.partIndex, partRanges);
1642
+ }
1643
+ const strippedParts = message2.parts.flatMap((part, partIndex) => {
1644
+ const clonedPart = { ...part };
1645
+ const ranges = rangesByPartIndex.get(partIndex);
1646
+ if (clonedPart.type !== "text" || ranges === void 0) {
1647
+ return [clonedPart];
1648
+ }
1649
+ const strippedText = stripTextByRanges(clonedPart.text, ranges);
1650
+ if (strippedText.length === 0) {
1651
+ return [];
1652
+ }
1653
+ return [{ ...clonedPart, text: strippedText }];
1654
+ });
1655
+ const nextMessage = {
1656
+ ...message2,
1657
+ parts: strippedParts
1658
+ };
1659
+ if (isRecord(message2.metadata)) {
1660
+ const metadata = { ...message2.metadata };
1661
+ delete metadata.reminders;
1662
+ if (Object.keys(metadata).length > 0) {
1663
+ nextMessage.metadata = metadata;
1664
+ } else {
1665
+ delete nextMessage.metadata;
1666
+ }
1667
+ }
1668
+ return nextMessage;
1669
+ }
1670
+ function isRecord(value) {
1671
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1672
+ }
1673
+ function assertReminderText(text) {
1674
+ if (text.trim().length === 0) {
1675
+ throw new Error("Reminder text must not be empty");
1676
+ }
1677
+ }
1678
+ function formatTaggedReminder(text) {
1679
+ return `${SYSTEM_REMINDER_OPEN_TAG}${text}${SYSTEM_REMINDER_CLOSE_TAG}`;
1680
+ }
1681
+ function findLastTextPartIndex(message2) {
1682
+ for (let i = message2.parts.length - 1; i >= 0; i--) {
1683
+ if (message2.parts[i].type === "text") {
1684
+ return i;
1685
+ }
1686
+ }
1687
+ return void 0;
1688
+ }
1689
+ function ensureTextPart(message2) {
1690
+ const existingIndex = findLastTextPartIndex(message2);
1691
+ if (existingIndex !== void 0) {
1692
+ return existingIndex;
1693
+ }
1694
+ const reminderPart = {
1695
+ type: "text",
1696
+ text: ""
1697
+ };
1698
+ message2.parts.push(reminderPart);
1699
+ return message2.parts.length - 1;
1700
+ }
1701
+ function applyInlineReminder(message2, value) {
1702
+ const partIndex = ensureTextPart(message2);
1703
+ const textPart = message2.parts[partIndex];
1704
+ if (textPart.type !== "text") {
1705
+ throw new Error("Failed to resolve text part for inline reminder");
1706
+ }
1707
+ const reminderText = formatTaggedReminder(value);
1708
+ const start = textPart.text.length;
1709
+ const updatedText = `${textPart.text}${reminderText}`;
1710
+ message2.parts[partIndex] = { ...textPart, text: updatedText };
1711
+ return {
1712
+ id: generateId3(),
1713
+ text: value,
1714
+ partIndex,
1715
+ start,
1716
+ end: start + reminderText.length,
1717
+ mode: "inline"
1718
+ };
1719
+ }
1720
+ function applyPartReminder(message2, value) {
1721
+ const part = { type: "text", text: value };
1722
+ message2.parts.push(part);
1723
+ const partIndex = message2.parts.length - 1;
1724
+ return {
1725
+ id: generateId3(),
1726
+ text: value,
1727
+ partIndex,
1728
+ start: 0,
1729
+ end: value.length,
1730
+ mode: "part"
1731
+ };
1732
+ }
1733
+ function resolveReminderText(item, ctx) {
1734
+ return resolveReminder(item, ctx)?.text ?? "";
1735
+ }
1736
+ function normalizeReminderResolution(value) {
1737
+ if (typeof value === "string") {
1738
+ return value.trim().length === 0 ? null : { text: value };
1739
+ }
1740
+ if (value.text.trim().length === 0) {
1741
+ return null;
1742
+ }
1743
+ return value;
1744
+ }
1745
+ function resolveReminder(item, ctx) {
1746
+ const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
1747
+ const resolved = normalizeReminderResolution(resolvedText);
1748
+ if (!resolved) {
1749
+ return null;
1750
+ }
1751
+ const metadata = item.metadata || resolved.metadata ? {
1752
+ ...item.metadata ?? {},
1753
+ ...resolved.metadata ?? {}
1754
+ } : void 0;
1755
+ return metadata ? { ...resolved, metadata } : resolved;
1756
+ }
1757
+ async function resolveReminderAsync(item, ctx) {
1758
+ const text = await (typeof item.text === "function" ? item.text(ctx) : item.text);
1759
+ const resolved = normalizeReminderResolution(text);
1760
+ if (!resolved) return null;
1761
+ const metadata = item.metadata || resolved.metadata ? { ...item.metadata ?? {}, ...resolved.metadata ?? {} } : void 0;
1762
+ return metadata ? { ...resolved, metadata } : resolved;
1763
+ }
1764
+ function mergeMessageMetadata(message2, addedMetadata) {
1765
+ if (Object.keys(addedMetadata).length === 0) {
1766
+ return;
1767
+ }
1768
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1769
+ message2.metadata = { ...metadata, ...addedMetadata };
1770
+ }
1771
+ function applyReminderToMessage(message2, item, ctx) {
1772
+ const resolved = resolveReminder(item, ctx);
1773
+ if (!resolved) {
1774
+ return null;
1775
+ }
1776
+ if (resolved.metadata) {
1777
+ mergeMessageMetadata(message2, resolved.metadata);
1778
+ }
1779
+ return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
1780
+ }
1781
+ function mergeReminderMetadata(message2, addedReminders) {
1782
+ if (addedReminders.length === 0) return;
1783
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1784
+ const existing = Array.isArray(metadata.reminders) ? metadata.reminders : [];
1785
+ metadata.reminders = [...existing, ...addedReminders];
1786
+ message2.metadata = metadata;
1787
+ }
1788
+ function reminder(textOrFragment, options) {
1789
+ const text = isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
1790
+ if (typeof text === "string") {
1791
+ assertReminderText(text);
1792
+ }
1793
+ if (options && "when" in options && options.when) {
1794
+ return {
1795
+ name: "reminder",
1796
+ data: null,
1797
+ metadata: {
1798
+ reminder: {
1799
+ text,
1800
+ when: options.when,
1801
+ asPart: options.asPart ?? false
1802
+ }
1803
+ }
1804
+ };
1805
+ }
1806
+ return {
1807
+ text,
1808
+ asPart: options?.asPart ?? false
1809
+ };
1810
+ }
1811
+ function user(content, ...reminders) {
1812
+ const message2 = typeof content === "string" ? {
1813
+ id: generateId3(),
1814
+ role: "user",
1815
+ parts: [{ type: "text", text: content }]
1816
+ } : { ...content, role: "user", parts: [...content.parts] };
1817
+ if (reminders.length > 0) {
1818
+ const plainText = extractPlainText(message2);
1819
+ const added = [];
1820
+ for (const item of reminders) {
1821
+ const meta = applyReminderToMessage(message2, item, {
1822
+ content: plainText
1823
+ });
1824
+ if (meta) added.push(meta);
1825
+ }
1826
+ mergeReminderMetadata(message2, added);
1827
+ }
1828
+ return {
1829
+ id: message2.id,
1830
+ name: "user",
1831
+ type: "message",
1832
+ persist: true,
1833
+ codec: {
1834
+ decode() {
1835
+ return message2;
1836
+ },
1837
+ encode() {
1838
+ return message2;
1839
+ }
1840
+ }
1841
+ };
1842
+ }
1843
+
1844
+ // packages/context/src/lib/store/store.ts
1845
+ var ContextStore = class {
1846
+ };
1847
+
1848
+ // packages/context/src/lib/engine.ts
1849
+ function estimateMessageContent(data) {
1850
+ return typeof data === "string" ? data : JSON.stringify(data);
1851
+ }
1852
+ function isLanguageModelUsage(value) {
1853
+ return typeof value === "object" && value !== null && "totalTokens" in value;
1827
1854
  }
1828
1855
  var ContextEngine = class {
1829
1856
  /** Non-message fragments (role, hints, etc.) - not persisted in graph */
@@ -2154,17 +2181,21 @@ var ContextEngine = class {
2154
2181
  messageCount,
2155
2182
  lastAssistantMessage: lastAssistantMessage2
2156
2183
  };
2157
- const firedReminders = conditionalReminders.map(getConditionalReminder).filter((config) => config.when(whenCtx));
2184
+ const configs = conditionalReminders.map(getConditionalReminder);
2185
+ const whenResults = await Promise.all(
2186
+ configs.map((it) => it.when(whenCtx))
2187
+ );
2188
+ const firedReminders = configs.filter((_, i) => whenResults[i]);
2158
2189
  if (firedReminders.length > 0) {
2159
- const reminders = firedReminders.flatMap((rem) => {
2160
- const resolved = resolveReminder(rem, whenCtx);
2161
- if (!resolved) {
2162
- return [];
2163
- }
2190
+ const resolvedOrNull = await Promise.all(
2191
+ firedReminders.map((it) => resolveReminderAsync(it, whenCtx))
2192
+ );
2193
+ const reminders = resolvedOrNull.flatMap((resolved, i) => {
2194
+ if (!resolved) return [];
2164
2195
  return [
2165
2196
  {
2166
2197
  text: resolved.text,
2167
- asPart: rem.asPart,
2198
+ asPart: firedReminders[i].asPart,
2168
2199
  metadata: resolved.metadata
2169
2200
  }
2170
2201
  ];
@@ -8032,6 +8063,7 @@ export {
8032
8063
  render,
8033
8064
  resetAdaptivePolling,
8034
8065
  resolveReminder,
8066
+ resolveReminderAsync,
8035
8067
  resolveReminderText,
8036
8068
  role,
8037
8069
  runGuardrailChain,