@deepagents/context 0.34.0 → 0.36.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
@@ -83,22 +83,6 @@ function assistantText(content, options) {
83
83
  parts: [{ type: "text", text: content }]
84
84
  });
85
85
  }
86
- var LAZY_ID = Symbol.for("@deepagents/context:lazy-id");
87
- function isLazyFragment(fragment2) {
88
- return LAZY_ID in fragment2;
89
- }
90
- function lastAssistantMessage(content) {
91
- return {
92
- name: "assistant",
93
- type: "message",
94
- persist: true,
95
- data: "content",
96
- [LAZY_ID]: {
97
- type: "last-assistant",
98
- content
99
- }
100
- };
101
- }
102
86
 
103
87
  // packages/context/src/lib/guardrail.ts
104
88
  function pass(part) {
@@ -149,13 +133,19 @@ var Agent = class _Agent {
149
133
  return generateText({
150
134
  abortSignal: config?.abortSignal,
151
135
  providerOptions: this.#options.providerOptions,
136
+ experimental_telemetry: this.#options.experimental_telemetry,
152
137
  model: this.#options.model,
153
138
  system: systemPrompt,
154
- messages: await convertToModelMessages(messages),
155
- stopWhen: stepCountIs(25),
139
+ messages: await convertToModelMessages(messages, {
140
+ ignoreIncompleteToolCalls: true
141
+ }),
142
+ stopWhen: stepCountIs(200),
156
143
  tools: this.#options.tools,
157
144
  experimental_context: contextVariables,
158
- experimental_repairToolCall: createRepairToolCall(this.#options.model),
145
+ experimental_repairToolCall: createRepairToolCall(
146
+ this.#options.model,
147
+ config?.abortSignal
148
+ ),
159
149
  toolChoice: this.#options.toolChoice,
160
150
  onStepFinish: (step) => {
161
151
  const toolCall = step.toolCalls.at(-1);
@@ -216,11 +206,17 @@ var Agent = class _Agent {
216
206
  return streamText({
217
207
  abortSignal: config?.abortSignal,
218
208
  providerOptions: this.#options.providerOptions,
209
+ experimental_telemetry: this.#options.experimental_telemetry,
219
210
  model,
220
211
  system: systemPrompt,
221
- messages: await convertToModelMessages(messages),
222
- experimental_repairToolCall: createRepairToolCall(model),
223
- stopWhen: stepCountIs(50),
212
+ messages: await convertToModelMessages(messages, {
213
+ ignoreIncompleteToolCalls: true
214
+ }),
215
+ experimental_repairToolCall: createRepairToolCall(
216
+ model,
217
+ config?.abortSignal
218
+ ),
219
+ stopWhen: stepCountIs(200),
224
220
  experimental_transform: config?.transform ?? smoothStream(),
225
221
  tools: this.#options.tools,
226
222
  experimental_context: contextVariables,
@@ -239,8 +235,8 @@ var Agent = class _Agent {
239
235
  * Wrap a StreamTextResult with guardrail protection on toUIMessageStream().
240
236
  *
241
237
  * When a guardrail fails:
242
- * 1. Accumulated text + feedback is appended as the assistant's self-correction
243
- * 2. The feedback is written to the output stream (user sees the correction)
238
+ * 1. The feedback is written to the output stream (user sees the correction)
239
+ * 2. A finish-step is emitted, triggering onStepFinish to persist the self-correction
244
240
  * 3. A new stream is started and the model continues from the correction
245
241
  */
246
242
  #wrapWithGuardrails(result, contextVariables, config) {
@@ -251,8 +247,18 @@ var Agent = class _Agent {
251
247
  }
252
248
  const originalToUIMessageStream = result.toUIMessageStream.bind(result);
253
249
  result.toUIMessageStream = (options) => {
250
+ const assistantMsgId = options?.generateMessageId?.();
251
+ let stepSaved = null;
254
252
  return createUIMessageStream({
255
- generateId: generateId2,
253
+ generateId: assistantMsgId ? () => assistantMsgId : generateId2,
254
+ onStepFinish: async ({ responseMessage }) => {
255
+ if (!stepSaved) return;
256
+ const normalizedMessage = assistantMsgId ? { ...responseMessage, id: assistantMsgId } : responseMessage;
257
+ context.set(assistant(normalizedMessage));
258
+ await context.save({ branch: false });
259
+ stepSaved.resolve();
260
+ stepSaved = null;
261
+ },
256
262
  execute: async ({ writer }) => {
257
263
  let currentResult = result;
258
264
  let attempt = 0;
@@ -267,7 +273,6 @@ var Agent = class _Agent {
267
273
  return;
268
274
  }
269
275
  attempt++;
270
- let accumulatedText = "";
271
276
  let guardrailFailed = false;
272
277
  let failureFeedback = "";
273
278
  const uiStream = currentResult === result ? originalToUIMessageStream(options) : currentResult.toUIMessageStream(options);
@@ -297,9 +302,6 @@ var Agent = class _Agent {
297
302
  writer.write({ type: "finish" });
298
303
  return;
299
304
  }
300
- if (checkResult.part.type === "text-delta") {
301
- accumulatedText += checkResult.part.delta;
302
- }
303
305
  writer.write(part);
304
306
  }
305
307
  if (!guardrailFailed) {
@@ -316,9 +318,9 @@ var Agent = class _Agent {
316
318
  return;
317
319
  }
318
320
  writeText(writer, failureFeedback);
319
- const selfCorrectionText = accumulatedText + " " + failureFeedback;
320
- context.set(lastAssistantMessage(selfCorrectionText));
321
- await context.save({ branch: false });
321
+ stepSaved = Promise.withResolvers();
322
+ writer.write({ type: "finish-step" });
323
+ await stepSaved.promise;
322
324
  currentResult = await this.#createRawStream(
323
325
  contextVariables,
324
326
  config
@@ -358,11 +360,17 @@ function structuredOutput(options) {
358
360
  const result = await generateText({
359
361
  abortSignal: config?.abortSignal,
360
362
  providerOptions: options.providerOptions,
363
+ experimental_telemetry: options.experimental_telemetry,
361
364
  model: options.model,
362
365
  system: systemPrompt,
363
- messages: await convertToModelMessages(messages),
364
- stopWhen: stepCountIs(25),
365
- experimental_repairToolCall: createRepairToolCall(options.model),
366
+ messages: await convertToModelMessages(messages, {
367
+ ignoreIncompleteToolCalls: true
368
+ }),
369
+ stopWhen: stepCountIs(200),
370
+ experimental_repairToolCall: createRepairToolCall(
371
+ options.model,
372
+ config?.abortSignal
373
+ ),
366
374
  experimental_context: contextVariables,
367
375
  output: Output.object({ schema: options.schema }),
368
376
  tools: options.tools
@@ -382,11 +390,17 @@ function structuredOutput(options) {
382
390
  return streamText({
383
391
  abortSignal: config?.abortSignal,
384
392
  providerOptions: options.providerOptions,
393
+ experimental_telemetry: options.experimental_telemetry,
385
394
  model: options.model,
386
395
  system: systemPrompt,
387
- experimental_repairToolCall: createRepairToolCall(options.model),
388
- messages: await convertToModelMessages(messages),
389
- stopWhen: stepCountIs(50),
396
+ experimental_repairToolCall: createRepairToolCall(
397
+ options.model,
398
+ config?.abortSignal
399
+ ),
400
+ messages: await convertToModelMessages(messages, {
401
+ ignoreIncompleteToolCalls: true
402
+ }),
403
+ stopWhen: stepCountIs(200),
390
404
  experimental_transform: config?.transform ?? smoothStream(),
391
405
  experimental_context: contextVariables,
392
406
  output: Output.object({ schema: options.schema }),
@@ -593,422 +607,77 @@ async function estimate(modelId, renderer, ...fragments) {
593
607
  // packages/context/src/lib/fragments/message/user.ts
594
608
  import { generateId as generateId3 } from "ai";
595
609
 
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;
610
+ // packages/context/src/lib/renderers/abstract.renderer.ts
611
+ import pluralize from "pluralize";
612
+ import { titlecase } from "stringcase";
613
+ var ContextRenderer = class {
614
+ options;
615
+ constructor(options = {}) {
616
+ this.options = options;
716
617
  }
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;
618
+ /**
619
+ * Check if data is a primitive (string, number, boolean).
620
+ */
621
+ isPrimitive(data) {
622
+ return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
723
623
  }
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;
624
+ /**
625
+ * Group fragments by name for groupFragments option.
626
+ */
627
+ groupByName(fragments) {
628
+ const groups = /* @__PURE__ */ new Map();
629
+ for (const fragment2 of fragments) {
630
+ const existing = groups.get(fragment2.name) ?? [];
631
+ existing.push(fragment2);
632
+ groups.set(fragment2.name, existing);
633
+ }
634
+ return groups;
635
+ }
636
+ /**
637
+ * Remove null/undefined from fragments and fragment data recursively.
638
+ * This protects renderers from nullish values and ensures they are ignored
639
+ * consistently across all output formats.
640
+ */
641
+ sanitizeFragments(fragments) {
642
+ const sanitized = [];
643
+ for (const fragment2 of fragments) {
644
+ const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
645
+ if (cleaned) {
646
+ sanitized.push(cleaned);
730
647
  }
731
- continue;
732
648
  }
733
- output += text.slice(cursor, range.start);
734
- cursor = range.end;
649
+ return sanitized;
735
650
  }
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);
651
+ sanitizeFragment(fragment2, seen) {
652
+ const data = this.sanitizeData(getFragmentData(fragment2), seen);
653
+ if (data == null) {
654
+ return null;
655
+ }
656
+ return {
657
+ ...fragment2,
658
+ data
659
+ };
748
660
  }
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];
661
+ sanitizeData(data, seen) {
662
+ if (data == null) {
663
+ return void 0;
754
664
  }
755
- const strippedText = stripTextByRanges(clonedPart.text, ranges);
756
- if (strippedText.length === 0) {
757
- return [];
665
+ if (isFragment(data)) {
666
+ return this.sanitizeFragment(data, seen) ?? void 0;
758
667
  }
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;
772
- }
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;
668
+ if (Array.isArray(data)) {
669
+ if (seen.has(data)) {
670
+ return void 0;
671
+ }
672
+ seen.add(data);
673
+ const cleaned = [];
674
+ for (const item of data) {
675
+ const sanitizedItem = this.sanitizeData(item, seen);
676
+ if (sanitizedItem != null) {
677
+ cleaned.push(sanitizedItem);
678
+ }
679
+ }
680
+ return cleaned;
1012
681
  }
1013
682
  if (isFragmentObject(data)) {
1014
683
  if (seen.has(data)) {
@@ -1814,27 +1483,391 @@ ${entries}`;
1814
1483
  }
1815
1484
  };
1816
1485
 
1817
- // packages/context/src/lib/store/store.ts
1818
- var ContextStore = class {
1819
- };
1486
+ // packages/context/src/lib/text.ts
1487
+ import { isTextUIPart } from "ai";
1488
+ function getTextParts(message2) {
1489
+ return message2.parts.filter(isTextUIPart).map((part) => part.text);
1490
+ }
1491
+ function extractPlainText(message2) {
1492
+ return getTextParts(message2).join(" ");
1493
+ }
1820
1494
 
1821
- // packages/context/src/lib/engine.ts
1822
- function estimateMessageContent(data) {
1823
- return typeof data === "string" ? data : JSON.stringify(data);
1495
+ // packages/context/src/lib/fragments/message/user.ts
1496
+ function everyNTurns(n) {
1497
+ return ({ turn }) => turn % n === 0;
1824
1498
  }
1825
- function isLanguageModelUsage(value) {
1826
- return typeof value === "object" && value !== null && "totalTokens" in value;
1499
+ function once() {
1500
+ return ({ turn }) => turn === 1;
1827
1501
  }
1828
- var ContextEngine = class {
1829
- /** Non-message fragments (role, hints, etc.) - not persisted in graph */
1830
- #fragments = [];
1831
- /** Pending message fragments to be added to graph */
1832
- #pendingMessages = [];
1833
- #store;
1834
- #chatId;
1835
- #userId;
1836
- #branchName;
1837
- #branch = null;
1502
+ function firstN(n) {
1503
+ return ({ turn }) => turn <= n;
1504
+ }
1505
+ function afterTurn(n) {
1506
+ return ({ turn }) => turn > n;
1507
+ }
1508
+ function and(...predicates) {
1509
+ return async (ctx) => {
1510
+ for (const it of predicates) {
1511
+ if (!await it(ctx)) return false;
1512
+ }
1513
+ return true;
1514
+ };
1515
+ }
1516
+ function or(...predicates) {
1517
+ return async (ctx) => {
1518
+ for (const it of predicates) {
1519
+ if (await it(ctx)) return true;
1520
+ }
1521
+ return false;
1522
+ };
1523
+ }
1524
+ function not(predicate) {
1525
+ return async (ctx) => !await predicate(ctx);
1526
+ }
1527
+ function contentIncludes(keywords) {
1528
+ const lower = keywords.map((k) => k.toLowerCase());
1529
+ return (ctx) => {
1530
+ const text = ctx.content.toLowerCase();
1531
+ return lower.some((kw) => text.includes(kw));
1532
+ };
1533
+ }
1534
+ function contentPattern(pattern) {
1535
+ return (ctx) => {
1536
+ pattern.lastIndex = 0;
1537
+ return pattern.test(ctx.content);
1538
+ };
1539
+ }
1540
+ function toDateParts(date, tz) {
1541
+ const parts = new Intl.DateTimeFormat("en-CA", {
1542
+ timeZone: tz,
1543
+ year: "numeric",
1544
+ month: "2-digit",
1545
+ day: "2-digit",
1546
+ hour: "2-digit",
1547
+ hourCycle: "h23"
1548
+ }).formatToParts(date);
1549
+ const get = (type) => parts.find((p) => p.type === type).value;
1550
+ return {
1551
+ year: get("year"),
1552
+ month: get("month"),
1553
+ day: get("day"),
1554
+ hour: get("hour")
1555
+ };
1556
+ }
1557
+ function temporalChanged(ctx, tz, getKey) {
1558
+ if (ctx.lastMessageAt === void 0) return true;
1559
+ const nowParts = toDateParts(/* @__PURE__ */ new Date(), tz);
1560
+ const prevParts = toDateParts(new Date(ctx.lastMessageAt), tz);
1561
+ return getKey(nowParts) !== getKey(prevParts);
1562
+ }
1563
+ function getSeason(month) {
1564
+ if (month >= 2 && month <= 4) return "Spring";
1565
+ if (month >= 5 && month <= 7) return "Summer";
1566
+ if (month >= 8 && month <= 10) return "Fall";
1567
+ return "Winter";
1568
+ }
1569
+ function isoWeekKey(parts) {
1570
+ const d = new Date(
1571
+ Date.UTC(
1572
+ parseInt(parts.year),
1573
+ parseInt(parts.month) - 1,
1574
+ parseInt(parts.day)
1575
+ )
1576
+ );
1577
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
1578
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
1579
+ const weekNo = Math.ceil(
1580
+ ((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
1581
+ );
1582
+ return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
1583
+ }
1584
+ function dayChanged(tz = "UTC") {
1585
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}`);
1586
+ }
1587
+ function hourChanged(tz = "UTC") {
1588
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}-${p.hour}`);
1589
+ }
1590
+ function monthChanged(tz = "UTC") {
1591
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}`);
1592
+ }
1593
+ function yearChanged(tz = "UTC") {
1594
+ return (ctx) => temporalChanged(ctx, tz, (p) => p.year);
1595
+ }
1596
+ function seasonChanged(tz = "UTC") {
1597
+ return (ctx) => temporalChanged(ctx, tz, (p) => getSeason(parseInt(p.month) - 1));
1598
+ }
1599
+ function weekChanged(tz = "UTC") {
1600
+ return (ctx) => temporalChanged(ctx, tz, isoWeekKey);
1601
+ }
1602
+ function isConditionalReminder(fragment2) {
1603
+ return fragment2.name === "reminder" && !!fragment2.metadata?.reminder;
1604
+ }
1605
+ function getConditionalReminder(fragment2) {
1606
+ return fragment2.metadata.reminder;
1607
+ }
1608
+ var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
1609
+ var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
1610
+ function getReminderRanges(metadata) {
1611
+ return metadata?.reminders ?? [];
1612
+ }
1613
+ function stripTextByRanges(text, ranges) {
1614
+ if (ranges.length === 0) {
1615
+ return text;
1616
+ }
1617
+ const normalized = ranges.map((range) => ({
1618
+ start: Math.max(0, Math.min(text.length, range.start)),
1619
+ end: Math.max(0, Math.min(text.length, range.end))
1620
+ })).filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
1621
+ if (normalized.length === 0) {
1622
+ return text;
1623
+ }
1624
+ let cursor = 0;
1625
+ let output = "";
1626
+ for (const range of normalized) {
1627
+ if (range.start < cursor) {
1628
+ if (range.end > cursor) {
1629
+ cursor = range.end;
1630
+ }
1631
+ continue;
1632
+ }
1633
+ output += text.slice(cursor, range.start);
1634
+ cursor = range.end;
1635
+ }
1636
+ output += text.slice(cursor);
1637
+ return output.trimEnd();
1638
+ }
1639
+ function stripReminders(message2) {
1640
+ const reminderRanges = getReminderRanges(
1641
+ isRecord(message2.metadata) ? message2.metadata : void 0
1642
+ );
1643
+ const rangesByPartIndex = /* @__PURE__ */ new Map();
1644
+ for (const range of reminderRanges) {
1645
+ const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
1646
+ partRanges.push({ start: range.start, end: range.end });
1647
+ rangesByPartIndex.set(range.partIndex, partRanges);
1648
+ }
1649
+ const strippedParts = message2.parts.flatMap((part, partIndex) => {
1650
+ const clonedPart = { ...part };
1651
+ const ranges = rangesByPartIndex.get(partIndex);
1652
+ if (clonedPart.type !== "text" || ranges === void 0) {
1653
+ return [clonedPart];
1654
+ }
1655
+ const strippedText = stripTextByRanges(clonedPart.text, ranges);
1656
+ if (strippedText.length === 0) {
1657
+ return [];
1658
+ }
1659
+ return [{ ...clonedPart, text: strippedText }];
1660
+ });
1661
+ const nextMessage = {
1662
+ ...message2,
1663
+ parts: strippedParts
1664
+ };
1665
+ if (isRecord(message2.metadata)) {
1666
+ const metadata = { ...message2.metadata };
1667
+ delete metadata.reminders;
1668
+ if (Object.keys(metadata).length > 0) {
1669
+ nextMessage.metadata = metadata;
1670
+ } else {
1671
+ delete nextMessage.metadata;
1672
+ }
1673
+ }
1674
+ return nextMessage;
1675
+ }
1676
+ function isRecord(value) {
1677
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1678
+ }
1679
+ function assertReminderText(text) {
1680
+ if (text.trim().length === 0) {
1681
+ throw new Error("Reminder text must not be empty");
1682
+ }
1683
+ }
1684
+ function formatTaggedReminder(text) {
1685
+ return `${SYSTEM_REMINDER_OPEN_TAG}${text}${SYSTEM_REMINDER_CLOSE_TAG}`;
1686
+ }
1687
+ function findLastTextPartIndex(message2) {
1688
+ for (let i = message2.parts.length - 1; i >= 0; i--) {
1689
+ if (message2.parts[i].type === "text") {
1690
+ return i;
1691
+ }
1692
+ }
1693
+ return void 0;
1694
+ }
1695
+ function ensureTextPart(message2) {
1696
+ const existingIndex = findLastTextPartIndex(message2);
1697
+ if (existingIndex !== void 0) {
1698
+ return existingIndex;
1699
+ }
1700
+ const reminderPart = {
1701
+ type: "text",
1702
+ text: ""
1703
+ };
1704
+ message2.parts.push(reminderPart);
1705
+ return message2.parts.length - 1;
1706
+ }
1707
+ function applyInlineReminder(message2, value) {
1708
+ const partIndex = ensureTextPart(message2);
1709
+ const textPart = message2.parts[partIndex];
1710
+ if (textPart.type !== "text") {
1711
+ throw new Error("Failed to resolve text part for inline reminder");
1712
+ }
1713
+ const reminderText = formatTaggedReminder(value);
1714
+ const start = textPart.text.length;
1715
+ const updatedText = `${textPart.text}${reminderText}`;
1716
+ message2.parts[partIndex] = { ...textPart, text: updatedText };
1717
+ return {
1718
+ id: generateId3(),
1719
+ text: value,
1720
+ partIndex,
1721
+ start,
1722
+ end: start + reminderText.length,
1723
+ mode: "inline"
1724
+ };
1725
+ }
1726
+ function applyPartReminder(message2, value) {
1727
+ const part = { type: "text", text: value };
1728
+ message2.parts.push(part);
1729
+ const partIndex = message2.parts.length - 1;
1730
+ return {
1731
+ id: generateId3(),
1732
+ text: value,
1733
+ partIndex,
1734
+ start: 0,
1735
+ end: value.length,
1736
+ mode: "part"
1737
+ };
1738
+ }
1739
+ function resolveReminderText(item, ctx) {
1740
+ return resolveReminder(item, ctx)?.text ?? "";
1741
+ }
1742
+ function normalizeReminderResolution(value) {
1743
+ if (typeof value === "string") {
1744
+ return value.trim().length === 0 ? null : { text: value };
1745
+ }
1746
+ if (value.text.trim().length === 0) {
1747
+ return null;
1748
+ }
1749
+ return value;
1750
+ }
1751
+ function resolveReminder(item, ctx) {
1752
+ const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
1753
+ const resolved = normalizeReminderResolution(resolvedText);
1754
+ if (!resolved) {
1755
+ return null;
1756
+ }
1757
+ const metadata = item.metadata || resolved.metadata ? {
1758
+ ...item.metadata ?? {},
1759
+ ...resolved.metadata ?? {}
1760
+ } : void 0;
1761
+ return metadata ? { ...resolved, metadata } : resolved;
1762
+ }
1763
+ async function resolveReminderAsync(item, ctx) {
1764
+ const text = await (typeof item.text === "function" ? item.text(ctx) : item.text);
1765
+ const resolved = normalizeReminderResolution(text);
1766
+ if (!resolved) return null;
1767
+ const metadata = item.metadata || resolved.metadata ? { ...item.metadata ?? {}, ...resolved.metadata ?? {} } : void 0;
1768
+ return metadata ? { ...resolved, metadata } : resolved;
1769
+ }
1770
+ function mergeMessageMetadata(message2, addedMetadata) {
1771
+ if (Object.keys(addedMetadata).length === 0) {
1772
+ return;
1773
+ }
1774
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1775
+ message2.metadata = { ...metadata, ...addedMetadata };
1776
+ }
1777
+ function applyReminderToMessage(message2, item, ctx) {
1778
+ const resolved = resolveReminder(item, ctx);
1779
+ if (!resolved) {
1780
+ return null;
1781
+ }
1782
+ if (resolved.metadata) {
1783
+ mergeMessageMetadata(message2, resolved.metadata);
1784
+ }
1785
+ return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
1786
+ }
1787
+ function mergeReminderMetadata(message2, addedReminders) {
1788
+ if (addedReminders.length === 0) return;
1789
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1790
+ const existing = Array.isArray(metadata.reminders) ? metadata.reminders : [];
1791
+ metadata.reminders = [...existing, ...addedReminders];
1792
+ message2.metadata = metadata;
1793
+ }
1794
+ function reminder(textOrFragment, options) {
1795
+ const text = isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
1796
+ if (typeof text === "string") {
1797
+ assertReminderText(text);
1798
+ }
1799
+ if (options && "when" in options && options.when) {
1800
+ return {
1801
+ name: "reminder",
1802
+ data: null,
1803
+ metadata: {
1804
+ reminder: {
1805
+ text,
1806
+ when: options.when,
1807
+ asPart: options.asPart ?? false
1808
+ }
1809
+ }
1810
+ };
1811
+ }
1812
+ return {
1813
+ text,
1814
+ asPart: options?.asPart ?? false
1815
+ };
1816
+ }
1817
+ function user(content, ...reminders) {
1818
+ const message2 = typeof content === "string" ? {
1819
+ id: generateId3(),
1820
+ role: "user",
1821
+ parts: [{ type: "text", text: content }]
1822
+ } : { ...content, role: "user", parts: [...content.parts] };
1823
+ if (reminders.length > 0) {
1824
+ const plainText = extractPlainText(message2);
1825
+ const added = [];
1826
+ for (const item of reminders) {
1827
+ const meta = applyReminderToMessage(message2, item, {
1828
+ content: plainText
1829
+ });
1830
+ if (meta) added.push(meta);
1831
+ }
1832
+ mergeReminderMetadata(message2, added);
1833
+ }
1834
+ return {
1835
+ id: message2.id,
1836
+ name: "user",
1837
+ type: "message",
1838
+ persist: true,
1839
+ codec: {
1840
+ decode() {
1841
+ return message2;
1842
+ },
1843
+ encode() {
1844
+ return message2;
1845
+ }
1846
+ }
1847
+ };
1848
+ }
1849
+
1850
+ // packages/context/src/lib/store/store.ts
1851
+ var ContextStore = class {
1852
+ };
1853
+
1854
+ // packages/context/src/lib/engine.ts
1855
+ function estimateMessageContent(data) {
1856
+ return typeof data === "string" ? data : JSON.stringify(data);
1857
+ }
1858
+ function isLanguageModelUsage(value) {
1859
+ return typeof value === "object" && value !== null && "totalTokens" in value;
1860
+ }
1861
+ var ContextEngine = class {
1862
+ /** Non-message fragments (role, hints, etc.) - not persisted in graph */
1863
+ #fragments = [];
1864
+ /** Pending message fragments to be added to graph */
1865
+ #pendingMessages = [];
1866
+ #store;
1867
+ #chatId;
1868
+ #userId;
1869
+ #branchName;
1870
+ #branch = null;
1838
1871
  #chatData = null;
1839
1872
  #initialized = false;
1840
1873
  /** Initial metadata to merge on first initialization */
@@ -1972,7 +2005,7 @@ var ContextEngine = class {
1972
2005
  let messageCount = 0;
1973
2006
  let lastMessageAt;
1974
2007
  let lastMessage;
1975
- let lastAssistantMessage2;
2008
+ let lastAssistantMessage;
1976
2009
  if (this.#branch?.headMessageId) {
1977
2010
  const chain = await this.#store.getMessageChain(
1978
2011
  this.#branch.headMessageId
@@ -1980,7 +2013,7 @@ var ContextEngine = class {
1980
2013
  for (const msg of chain) {
1981
2014
  messageCount++;
1982
2015
  if (msg.name === "assistant") {
1983
- lastAssistantMessage2 = msg.data;
2016
+ lastAssistantMessage = msg.data;
1984
2017
  }
1985
2018
  if (msg.name !== "user") {
1986
2019
  continue;
@@ -1999,7 +2032,7 @@ var ContextEngine = class {
1999
2032
  messageCount,
2000
2033
  lastMessageAt,
2001
2034
  lastMessage,
2002
- lastAssistantMessage: lastAssistantMessage2
2035
+ lastAssistantMessage
2003
2036
  };
2004
2037
  }
2005
2038
  async getTurnCount() {
@@ -2061,17 +2094,9 @@ var ContextEngine = class {
2061
2094
  messages.push(msg.data);
2062
2095
  }
2063
2096
  }
2064
- for (let i = 0; i < this.#pendingMessages.length; i++) {
2065
- const fragment2 = this.#pendingMessages[i];
2066
- if (isLazyFragment(fragment2)) {
2067
- this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
2068
- }
2069
- }
2070
2097
  for (const fragment2 of this.#pendingMessages) {
2071
2098
  if (!fragment2.codec) {
2072
- throw new Error(
2073
- `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before encode.`
2074
- );
2099
+ throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2075
2100
  }
2076
2101
  messages.push(fragment2.codec.encode());
2077
2102
  }
@@ -2100,12 +2125,6 @@ var ContextEngine = class {
2100
2125
  return { headMessageId: this.#branch?.headMessageId ?? void 0 };
2101
2126
  }
2102
2127
  const shouldBranch = options?.branch ?? true;
2103
- for (let i = 0; i < this.#pendingMessages.length; i++) {
2104
- const fragment2 = this.#pendingMessages[i];
2105
- if (isLazyFragment(fragment2)) {
2106
- this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
2107
- }
2108
- }
2109
2128
  if (shouldBranch) {
2110
2129
  for (const fragment2 of this.#pendingMessages) {
2111
2130
  if (fragment2.id) {
@@ -2135,7 +2154,7 @@ var ContextEngine = class {
2135
2154
  messageCount,
2136
2155
  lastMessageAt,
2137
2156
  lastMessage,
2138
- lastAssistantMessage: lastAssistantMessage2
2157
+ lastAssistantMessage
2139
2158
  } = await this.#getChainContext();
2140
2159
  const original = lastUserFragment.codec.encode();
2141
2160
  const plainText = extractPlainText(original);
@@ -2152,19 +2171,23 @@ var ContextEngine = class {
2152
2171
  branch: this.#branchName,
2153
2172
  elapsed,
2154
2173
  messageCount,
2155
- lastAssistantMessage: lastAssistantMessage2
2174
+ lastAssistantMessage
2156
2175
  };
2157
- const firedReminders = conditionalReminders.map(getConditionalReminder).filter((config) => config.when(whenCtx));
2176
+ const configs = conditionalReminders.map(getConditionalReminder);
2177
+ const whenResults = await Promise.all(
2178
+ configs.map((it) => it.when(whenCtx))
2179
+ );
2180
+ const firedReminders = configs.filter((_, i) => whenResults[i]);
2158
2181
  if (firedReminders.length > 0) {
2159
- const reminders = firedReminders.flatMap((rem) => {
2160
- const resolved = resolveReminder(rem, whenCtx);
2161
- if (!resolved) {
2162
- return [];
2163
- }
2182
+ const resolvedOrNull = await Promise.all(
2183
+ firedReminders.map((it) => resolveReminderAsync(it, whenCtx))
2184
+ );
2185
+ const reminders = resolvedOrNull.flatMap((resolved, i) => {
2186
+ if (!resolved) return [];
2164
2187
  return [
2165
2188
  {
2166
2189
  text: resolved.text,
2167
- asPart: rem.asPart,
2190
+ asPart: firedReminders[i].asPart,
2168
2191
  metadata: resolved.metadata
2169
2192
  }
2170
2193
  ];
@@ -2180,9 +2203,7 @@ var ContextEngine = class {
2180
2203
  const now = Date.now();
2181
2204
  for (const fragment2 of this.#pendingMessages) {
2182
2205
  if (!fragment2.codec) {
2183
- throw new Error(
2184
- `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before encode.`
2185
- );
2206
+ throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2186
2207
  }
2187
2208
  const msgId = fragment2.id ?? crypto.randomUUID();
2188
2209
  let msgParentId = parentId;
@@ -2209,39 +2230,6 @@ var ContextEngine = class {
2209
2230
  this.#pendingMessages = [];
2210
2231
  return { headMessageId: this.#activeBranch.headMessageId ?? void 0 };
2211
2232
  }
2212
- /**
2213
- * Resolve a lazy fragment by finding the appropriate ID.
2214
- */
2215
- async #resolveLazyFragment(fragment2) {
2216
- const lazy = fragment2[LAZY_ID];
2217
- if (lazy.type === "last-assistant") {
2218
- const lastId = await this.#getLastAssistantId();
2219
- return assistantText(lazy.content, { id: lastId ?? crypto.randomUUID() });
2220
- }
2221
- throw new Error(`Unknown lazy fragment type: ${lazy.type}`);
2222
- }
2223
- /**
2224
- * Find the most recent assistant message ID (pending or persisted).
2225
- */
2226
- async #getLastAssistantId() {
2227
- for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
2228
- const msg = this.#pendingMessages[i];
2229
- if (msg.name === "assistant" && !isLazyFragment(msg)) {
2230
- return msg.id;
2231
- }
2232
- }
2233
- if (this.#branch?.headMessageId) {
2234
- const chain = await this.#store.getMessageChain(
2235
- this.#branch.headMessageId
2236
- );
2237
- for (let i = chain.length - 1; i >= 0; i--) {
2238
- if (chain[i].name === "assistant") {
2239
- return chain[i].id;
2240
- }
2241
- }
2242
- }
2243
- return void 0;
2244
- }
2245
2233
  /**
2246
2234
  * Estimate token count and cost for the full context.
2247
2235
  *
@@ -7934,7 +7922,6 @@ export {
7934
7922
  DockerfileBuildError,
7935
7923
  DockerfileStrategy,
7936
7924
  InMemoryContextStore,
7937
- LAZY_ID,
7938
7925
  MarkdownRenderer,
7939
7926
  ModelsRegistry,
7940
7927
  MountPathError,
@@ -8002,10 +7989,8 @@ export {
8002
7989
  isDockerfileOptions,
8003
7990
  isFragment,
8004
7991
  isFragmentObject,
8005
- isLazyFragment,
8006
7992
  isMessageFragment,
8007
7993
  isRecord,
8008
- lastAssistantMessage,
8009
7994
  loadSkillMetadata,
8010
7995
  localeReminder,
8011
7996
  mergeMessageMetadata,
@@ -8032,6 +8017,7 @@ export {
8032
8017
  render,
8033
8018
  resetAdaptivePolling,
8034
8019
  resolveReminder,
8020
+ resolveReminderAsync,
8035
8021
  resolveReminderText,
8036
8022
  role,
8037
8023
  runGuardrailChain,