@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/browser.js CHANGED
@@ -69,22 +69,6 @@ function assistantText(content, options) {
69
69
  parts: [{ type: "text", text: content }]
70
70
  });
71
71
  }
72
- var LAZY_ID = Symbol.for("@deepagents/context:lazy-id");
73
- function isLazyFragment(fragment2) {
74
- return LAZY_ID in fragment2;
75
- }
76
- function lastAssistantMessage(content) {
77
- return {
78
- name: "assistant",
79
- type: "message",
80
- persist: true,
81
- data: "content",
82
- [LAZY_ID]: {
83
- type: "last-assistant",
84
- content
85
- }
86
- };
87
- }
88
72
 
89
73
  // packages/context/src/lib/codec/serialized-codec.ts
90
74
  function encodeSerializedValue(value) {
@@ -902,460 +886,93 @@ async function estimate(modelId, renderer, ...fragments) {
902
886
  // packages/context/src/lib/fragments/message/user.ts
903
887
  import { generateId as generateId2 } from "ai";
904
888
 
905
- // packages/context/src/lib/text.ts
906
- import { isTextUIPart } from "ai";
907
- function getTextParts(message2) {
908
- return message2.parts.filter(isTextUIPart).map((part) => part.text);
909
- }
910
- function extractPlainText(message2) {
911
- return getTextParts(message2).join(" ");
912
- }
913
-
914
- // packages/context/src/lib/fragments/message/user.ts
915
- function everyNTurns(n) {
916
- return ({ turn }) => turn % n === 0;
917
- }
918
- function once() {
919
- return ({ turn }) => turn === 1;
920
- }
921
- function firstN(n) {
922
- return ({ turn }) => turn <= n;
923
- }
924
- function afterTurn(n) {
925
- return ({ turn }) => turn > n;
926
- }
927
- function and(...predicates) {
928
- return (ctx) => predicates.every((p) => p(ctx));
929
- }
930
- function or(...predicates) {
931
- return (ctx) => predicates.some((p) => p(ctx));
932
- }
933
- function not(predicate) {
934
- return (ctx) => !predicate(ctx);
935
- }
936
- function contentIncludes(keywords) {
937
- const lower = keywords.map((k) => k.toLowerCase());
938
- return (ctx) => {
939
- const text = ctx.content.toLowerCase();
940
- return lower.some((kw) => text.includes(kw));
941
- };
942
- }
943
- function contentPattern(pattern) {
944
- return (ctx) => {
945
- pattern.lastIndex = 0;
946
- return pattern.test(ctx.content);
947
- };
948
- }
949
- function toDateParts(date, tz) {
950
- const parts = new Intl.DateTimeFormat("en-CA", {
951
- timeZone: tz,
952
- year: "numeric",
953
- month: "2-digit",
954
- day: "2-digit",
955
- hour: "2-digit",
956
- hourCycle: "h23"
957
- }).formatToParts(date);
958
- const get = (type) => parts.find((p) => p.type === type).value;
959
- return {
960
- year: get("year"),
961
- month: get("month"),
962
- day: get("day"),
963
- hour: get("hour")
964
- };
965
- }
966
- function temporalChanged(ctx, tz, getKey) {
967
- if (ctx.lastMessageAt === void 0) return true;
968
- const nowParts = toDateParts(/* @__PURE__ */ new Date(), tz);
969
- const prevParts = toDateParts(new Date(ctx.lastMessageAt), tz);
970
- return getKey(nowParts) !== getKey(prevParts);
971
- }
972
- function getSeason(month) {
973
- if (month >= 2 && month <= 4) return "Spring";
974
- if (month >= 5 && month <= 7) return "Summer";
975
- if (month >= 8 && month <= 10) return "Fall";
976
- return "Winter";
977
- }
978
- function isoWeekKey(parts) {
979
- const d = new Date(
980
- Date.UTC(
981
- parseInt(parts.year),
982
- parseInt(parts.month) - 1,
983
- parseInt(parts.day)
984
- )
985
- );
986
- d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
987
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
988
- const weekNo = Math.ceil(
989
- ((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
990
- );
991
- return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
992
- }
993
- function dayChanged(tz = "UTC") {
994
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}`);
995
- }
996
- function hourChanged(tz = "UTC") {
997
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}-${p.hour}`);
998
- }
999
- function monthChanged(tz = "UTC") {
1000
- return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}`);
1001
- }
1002
- function yearChanged(tz = "UTC") {
1003
- return (ctx) => temporalChanged(ctx, tz, (p) => p.year);
1004
- }
1005
- function seasonChanged(tz = "UTC") {
1006
- return (ctx) => temporalChanged(ctx, tz, (p) => getSeason(parseInt(p.month) - 1));
1007
- }
1008
- function weekChanged(tz = "UTC") {
1009
- return (ctx) => temporalChanged(ctx, tz, isoWeekKey);
1010
- }
1011
- function isConditionalReminder(fragment2) {
1012
- return fragment2.name === "reminder" && !!fragment2.metadata?.reminder;
1013
- }
1014
- function getConditionalReminder(fragment2) {
1015
- return fragment2.metadata.reminder;
1016
- }
1017
- var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
1018
- var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
1019
- function getReminderRanges(metadata) {
1020
- return metadata?.reminders ?? [];
1021
- }
1022
- function stripTextByRanges(text, ranges) {
1023
- if (ranges.length === 0) {
1024
- return text;
889
+ // packages/context/src/lib/renderers/abstract.renderer.ts
890
+ import pluralize from "pluralize";
891
+ import { titlecase } from "stringcase";
892
+ var ContextRenderer = class {
893
+ options;
894
+ constructor(options = {}) {
895
+ this.options = options;
1025
896
  }
1026
- const normalized = ranges.map((range) => ({
1027
- start: Math.max(0, Math.min(text.length, range.start)),
1028
- end: Math.max(0, Math.min(text.length, range.end))
1029
- })).filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
1030
- if (normalized.length === 0) {
1031
- return text;
897
+ /**
898
+ * Check if data is a primitive (string, number, boolean).
899
+ */
900
+ isPrimitive(data) {
901
+ return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
1032
902
  }
1033
- let cursor = 0;
1034
- let output = "";
1035
- for (const range of normalized) {
1036
- if (range.start < cursor) {
1037
- if (range.end > cursor) {
1038
- cursor = range.end;
903
+ /**
904
+ * Group fragments by name for groupFragments option.
905
+ */
906
+ groupByName(fragments) {
907
+ const groups = /* @__PURE__ */ new Map();
908
+ for (const fragment2 of fragments) {
909
+ const existing = groups.get(fragment2.name) ?? [];
910
+ existing.push(fragment2);
911
+ groups.set(fragment2.name, existing);
912
+ }
913
+ return groups;
914
+ }
915
+ /**
916
+ * Remove null/undefined from fragments and fragment data recursively.
917
+ * This protects renderers from nullish values and ensures they are ignored
918
+ * consistently across all output formats.
919
+ */
920
+ sanitizeFragments(fragments) {
921
+ const sanitized = [];
922
+ for (const fragment2 of fragments) {
923
+ const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
924
+ if (cleaned) {
925
+ sanitized.push(cleaned);
1039
926
  }
1040
- continue;
1041
927
  }
1042
- output += text.slice(cursor, range.start);
1043
- cursor = range.end;
928
+ return sanitized;
1044
929
  }
1045
- output += text.slice(cursor);
1046
- return output.trimEnd();
1047
- }
1048
- function stripReminders(message2) {
1049
- const reminderRanges = getReminderRanges(
1050
- isRecord(message2.metadata) ? message2.metadata : void 0
1051
- );
1052
- const rangesByPartIndex = /* @__PURE__ */ new Map();
1053
- for (const range of reminderRanges) {
1054
- const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
1055
- partRanges.push({ start: range.start, end: range.end });
1056
- rangesByPartIndex.set(range.partIndex, partRanges);
930
+ sanitizeFragment(fragment2, seen) {
931
+ const data = this.sanitizeData(getFragmentData(fragment2), seen);
932
+ if (data == null) {
933
+ return null;
934
+ }
935
+ return {
936
+ ...fragment2,
937
+ data
938
+ };
1057
939
  }
1058
- const strippedParts = message2.parts.flatMap((part, partIndex) => {
1059
- const clonedPart = { ...part };
1060
- const ranges = rangesByPartIndex.get(partIndex);
1061
- if (clonedPart.type !== "text" || ranges === void 0) {
1062
- return [clonedPart];
940
+ sanitizeData(data, seen) {
941
+ if (data == null) {
942
+ return void 0;
1063
943
  }
1064
- const strippedText = stripTextByRanges(clonedPart.text, ranges);
1065
- if (strippedText.length === 0) {
1066
- return [];
944
+ if (isFragment(data)) {
945
+ return this.sanitizeFragment(data, seen) ?? void 0;
1067
946
  }
1068
- return [{ ...clonedPart, text: strippedText }];
1069
- });
1070
- const nextMessage = {
1071
- ...message2,
1072
- parts: strippedParts
1073
- };
1074
- if (isRecord(message2.metadata)) {
1075
- const metadata = { ...message2.metadata };
1076
- delete metadata.reminders;
1077
- if (Object.keys(metadata).length > 0) {
1078
- nextMessage.metadata = metadata;
1079
- } else {
1080
- delete nextMessage.metadata;
947
+ if (Array.isArray(data)) {
948
+ if (seen.has(data)) {
949
+ return void 0;
950
+ }
951
+ seen.add(data);
952
+ const cleaned = [];
953
+ for (const item of data) {
954
+ const sanitizedItem = this.sanitizeData(item, seen);
955
+ if (sanitizedItem != null) {
956
+ cleaned.push(sanitizedItem);
957
+ }
958
+ }
959
+ return cleaned;
960
+ }
961
+ if (isFragmentObject(data)) {
962
+ if (seen.has(data)) {
963
+ return void 0;
964
+ }
965
+ seen.add(data);
966
+ const cleaned = {};
967
+ for (const [key, value] of Object.entries(data)) {
968
+ const sanitizedValue = this.sanitizeData(value, seen);
969
+ if (sanitizedValue != null) {
970
+ cleaned[key] = sanitizedValue;
971
+ }
972
+ }
973
+ return cleaned;
1081
974
  }
1082
- }
1083
- return nextMessage;
1084
- }
1085
- function isRecord(value) {
1086
- return typeof value === "object" && value !== null && !Array.isArray(value);
1087
- }
1088
- function assertReminderText(text) {
1089
- if (text.trim().length === 0) {
1090
- throw new Error("Reminder text must not be empty");
1091
- }
1092
- }
1093
- function formatTaggedReminder(text) {
1094
- return `${SYSTEM_REMINDER_OPEN_TAG}${text}${SYSTEM_REMINDER_CLOSE_TAG}`;
1095
- }
1096
- function findLastTextPartIndex(message2) {
1097
- for (let i = message2.parts.length - 1; i >= 0; i--) {
1098
- if (message2.parts[i].type === "text") {
1099
- return i;
1100
- }
1101
- }
1102
- return void 0;
1103
- }
1104
- function ensureTextPart(message2) {
1105
- const existingIndex = findLastTextPartIndex(message2);
1106
- if (existingIndex !== void 0) {
1107
- return existingIndex;
1108
- }
1109
- const reminderPart = {
1110
- type: "text",
1111
- text: ""
1112
- };
1113
- message2.parts.push(reminderPart);
1114
- return message2.parts.length - 1;
1115
- }
1116
- function applyInlineReminder(message2, value) {
1117
- const partIndex = ensureTextPart(message2);
1118
- const textPart = message2.parts[partIndex];
1119
- if (textPart.type !== "text") {
1120
- throw new Error("Failed to resolve text part for inline reminder");
1121
- }
1122
- const reminderText = formatTaggedReminder(value);
1123
- const start = textPart.text.length;
1124
- const updatedText = `${textPart.text}${reminderText}`;
1125
- message2.parts[partIndex] = { ...textPart, text: updatedText };
1126
- return {
1127
- id: generateId2(),
1128
- text: value,
1129
- partIndex,
1130
- start,
1131
- end: start + reminderText.length,
1132
- mode: "inline"
1133
- };
1134
- }
1135
- function applyPartReminder(message2, value) {
1136
- const part = { type: "text", text: value };
1137
- message2.parts.push(part);
1138
- const partIndex = message2.parts.length - 1;
1139
- return {
1140
- id: generateId2(),
1141
- text: value,
1142
- partIndex,
1143
- start: 0,
1144
- end: value.length,
1145
- mode: "part"
1146
- };
1147
- }
1148
- function resolveReminderText(item, ctx) {
1149
- return resolveReminder(item, ctx)?.text ?? "";
1150
- }
1151
- function normalizeReminderResolution(value) {
1152
- if (typeof value === "string") {
1153
- return value.trim().length === 0 ? null : { text: value };
1154
- }
1155
- if (value.text.trim().length === 0) {
1156
- return null;
1157
- }
1158
- return value;
1159
- }
1160
- function resolveReminder(item, ctx) {
1161
- const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
1162
- const resolved = normalizeReminderResolution(resolvedText);
1163
- if (!resolved) {
1164
- return null;
1165
- }
1166
- const metadata = item.metadata || resolved.metadata ? {
1167
- ...item.metadata ?? {},
1168
- ...resolved.metadata ?? {}
1169
- } : void 0;
1170
- return metadata ? { ...resolved, metadata } : resolved;
1171
- }
1172
- function mergeMessageMetadata(message2, addedMetadata) {
1173
- if (Object.keys(addedMetadata).length === 0) {
1174
- return;
1175
- }
1176
- const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1177
- message2.metadata = { ...metadata, ...addedMetadata };
1178
- }
1179
- function applyReminderToMessage(message2, item, ctx) {
1180
- const resolved = resolveReminder(item, ctx);
1181
- if (!resolved) {
1182
- return null;
1183
- }
1184
- if (resolved.metadata) {
1185
- mergeMessageMetadata(message2, resolved.metadata);
1186
- }
1187
- return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
1188
- }
1189
- function mergeReminderMetadata(message2, addedReminders) {
1190
- if (addedReminders.length === 0) return;
1191
- const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
1192
- const existing = Array.isArray(metadata.reminders) ? metadata.reminders : [];
1193
- metadata.reminders = [...existing, ...addedReminders];
1194
- message2.metadata = metadata;
1195
- }
1196
- function reminder(text, options) {
1197
- if (typeof text === "string") {
1198
- assertReminderText(text);
1199
- }
1200
- if (options && "when" in options && options.when) {
1201
- return {
1202
- name: "reminder",
1203
- metadata: {
1204
- reminder: {
1205
- text,
1206
- when: options.when,
1207
- asPart: options.asPart ?? false
1208
- }
1209
- }
1210
- };
1211
- }
1212
- return {
1213
- text,
1214
- asPart: options?.asPart ?? false
1215
- };
1216
- }
1217
- function user(content, ...reminders) {
1218
- const message2 = typeof content === "string" ? {
1219
- id: generateId2(),
1220
- role: "user",
1221
- parts: [{ type: "text", text: content }]
1222
- } : { ...content, role: "user", parts: [...content.parts] };
1223
- if (reminders.length > 0) {
1224
- const plainText = extractPlainText(message2);
1225
- const added = [];
1226
- for (const item of reminders) {
1227
- const meta = applyReminderToMessage(message2, item, {
1228
- content: plainText
1229
- });
1230
- if (meta) added.push(meta);
1231
- }
1232
- mergeReminderMetadata(message2, added);
1233
- }
1234
- return {
1235
- id: message2.id,
1236
- name: "user",
1237
- type: "message",
1238
- persist: true,
1239
- codec: {
1240
- decode() {
1241
- return message2;
1242
- },
1243
- encode() {
1244
- return message2;
1245
- }
1246
- }
1247
- };
1248
- }
1249
-
1250
- // packages/context/src/lib/guardrail.ts
1251
- function pass(part) {
1252
- return { type: "pass", part };
1253
- }
1254
- function fail(feedback) {
1255
- return { type: "fail", feedback };
1256
- }
1257
- function stop(part) {
1258
- return { type: "stop", part };
1259
- }
1260
- function runGuardrailChain(part, guardrails, context) {
1261
- let currentPart = part;
1262
- for (const guardrail2 of guardrails) {
1263
- const result = guardrail2.handle(currentPart, context);
1264
- if (result.type === "fail" || result.type === "stop") {
1265
- return result;
1266
- }
1267
- currentPart = result.part;
1268
- }
1269
- return pass(currentPart);
1270
- }
1271
-
1272
- // packages/context/src/lib/renderers/abstract.renderer.ts
1273
- import pluralize from "pluralize";
1274
- import { titlecase } from "stringcase";
1275
- var ContextRenderer = class {
1276
- options;
1277
- constructor(options = {}) {
1278
- this.options = options;
1279
- }
1280
- /**
1281
- * Check if data is a primitive (string, number, boolean).
1282
- */
1283
- isPrimitive(data) {
1284
- return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
1285
- }
1286
- /**
1287
- * Group fragments by name for groupFragments option.
1288
- */
1289
- groupByName(fragments) {
1290
- const groups = /* @__PURE__ */ new Map();
1291
- for (const fragment2 of fragments) {
1292
- const existing = groups.get(fragment2.name) ?? [];
1293
- existing.push(fragment2);
1294
- groups.set(fragment2.name, existing);
1295
- }
1296
- return groups;
1297
- }
1298
- /**
1299
- * Remove null/undefined from fragments and fragment data recursively.
1300
- * This protects renderers from nullish values and ensures they are ignored
1301
- * consistently across all output formats.
1302
- */
1303
- sanitizeFragments(fragments) {
1304
- const sanitized = [];
1305
- for (const fragment2 of fragments) {
1306
- const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
1307
- if (cleaned) {
1308
- sanitized.push(cleaned);
1309
- }
1310
- }
1311
- return sanitized;
1312
- }
1313
- sanitizeFragment(fragment2, seen) {
1314
- const data = this.sanitizeData(getFragmentData(fragment2), seen);
1315
- if (data == null) {
1316
- return null;
1317
- }
1318
- return {
1319
- ...fragment2,
1320
- data
1321
- };
1322
- }
1323
- sanitizeData(data, seen) {
1324
- if (data == null) {
1325
- return void 0;
1326
- }
1327
- if (isFragment(data)) {
1328
- return this.sanitizeFragment(data, seen) ?? void 0;
1329
- }
1330
- if (Array.isArray(data)) {
1331
- if (seen.has(data)) {
1332
- return void 0;
1333
- }
1334
- seen.add(data);
1335
- const cleaned = [];
1336
- for (const item of data) {
1337
- const sanitizedItem = this.sanitizeData(item, seen);
1338
- if (sanitizedItem != null) {
1339
- cleaned.push(sanitizedItem);
1340
- }
1341
- }
1342
- return cleaned;
1343
- }
1344
- if (isFragmentObject(data)) {
1345
- if (seen.has(data)) {
1346
- return void 0;
1347
- }
1348
- seen.add(data);
1349
- const cleaned = {};
1350
- for (const [key, value] of Object.entries(data)) {
1351
- const sanitizedValue = this.sanitizeData(value, seen);
1352
- if (sanitizedValue != null) {
1353
- cleaned[key] = sanitizedValue;
1354
- }
1355
- }
1356
- return cleaned;
1357
- }
1358
- return data;
975
+ return data;
1359
976
  }
1360
977
  /**
1361
978
  * Template method - dispatches value to appropriate handler.
@@ -2076,74 +1693,460 @@ ${nested}`);
2076
1693
  if (this.isPrimitive(data)) {
2077
1694
  return `${this.#pad(ctx.depth)}${fragment2.name}: ${this.#formatValue(data)}`;
2078
1695
  }
2079
- if (isFragment(data)) {
2080
- const child = this.renderFragment(data, {
2081
- ...ctx,
2082
- depth: ctx.depth + 1
2083
- });
2084
- return `${this.#pad(ctx.depth)}${fragment2.name}:
2085
- ${child}`;
1696
+ if (isFragment(data)) {
1697
+ const child = this.renderFragment(data, {
1698
+ ...ctx,
1699
+ depth: ctx.depth + 1
1700
+ });
1701
+ return `${this.#pad(ctx.depth)}${fragment2.name}:
1702
+ ${child}`;
1703
+ }
1704
+ if (Array.isArray(data)) {
1705
+ return this.#renderArrayField(fragment2.name, data, ctx.depth);
1706
+ }
1707
+ if (isFragmentObject(data)) {
1708
+ const entries = this.#renderObjectEntries(data, ctx.depth + 1);
1709
+ if (!entries) {
1710
+ return `${this.#pad(ctx.depth)}${fragment2.name}:`;
1711
+ }
1712
+ return `${this.#pad(ctx.depth)}${fragment2.name}:
1713
+ ${entries}`;
1714
+ }
1715
+ return `${this.#pad(ctx.depth)}${fragment2.name}:`;
1716
+ }
1717
+ renderPrimitive(key, value, ctx) {
1718
+ return `${this.#pad(ctx.depth)}${key}: ${this.#formatValue(value)}`;
1719
+ }
1720
+ renderArray(key, items, ctx) {
1721
+ return this.#renderArrayField(key, items, ctx.depth);
1722
+ }
1723
+ renderObject(key, obj, ctx) {
1724
+ const entries = this.#renderObjectEntries(obj, ctx.depth + 1);
1725
+ if (!entries) {
1726
+ return `${this.#pad(ctx.depth)}${key}:`;
1727
+ }
1728
+ return `${this.#pad(ctx.depth)}${key}:
1729
+ ${entries}`;
1730
+ }
1731
+ #pad(depth) {
1732
+ return " ".repeat(depth);
1733
+ }
1734
+ #needsQuoting(value) {
1735
+ if (value === "") return true;
1736
+ if (value !== value.trim()) return true;
1737
+ if (["true", "false", "null"].includes(value.toLowerCase())) return true;
1738
+ if (/^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value)) return true;
1739
+ if (/[:\\"'[\]{}|,\t\n\r]/.test(value)) return true;
1740
+ if (value.startsWith("-")) return true;
1741
+ return false;
1742
+ }
1743
+ #escape(value) {
1744
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1745
+ }
1746
+ #canonicalizeNumber(n) {
1747
+ if (!Number.isFinite(n)) return "null";
1748
+ if (Object.is(n, -0)) return "0";
1749
+ return String(n);
1750
+ }
1751
+ #formatValue(value) {
1752
+ if (value === null) return "null";
1753
+ if (typeof value === "boolean") return String(value);
1754
+ if (typeof value === "number") return this.#canonicalizeNumber(value);
1755
+ if (typeof value === "string") {
1756
+ if (this.#needsQuoting(value)) {
1757
+ return `"${this.#escape(value)}"`;
1758
+ }
1759
+ return value;
1760
+ }
1761
+ return `"${this.#escape(JSON.stringify(value))}"`;
1762
+ }
1763
+ };
1764
+
1765
+ // packages/context/src/lib/text.ts
1766
+ import { isTextUIPart } from "ai";
1767
+ function getTextParts(message2) {
1768
+ return message2.parts.filter(isTextUIPart).map((part) => part.text);
1769
+ }
1770
+ function extractPlainText(message2) {
1771
+ return getTextParts(message2).join(" ");
1772
+ }
1773
+
1774
+ // packages/context/src/lib/fragments/message/user.ts
1775
+ function everyNTurns(n) {
1776
+ return ({ turn }) => turn % n === 0;
1777
+ }
1778
+ function once() {
1779
+ return ({ turn }) => turn === 1;
1780
+ }
1781
+ function firstN(n) {
1782
+ return ({ turn }) => turn <= n;
1783
+ }
1784
+ function afterTurn(n) {
1785
+ return ({ turn }) => turn > n;
1786
+ }
1787
+ function and(...predicates) {
1788
+ return async (ctx) => {
1789
+ for (const it of predicates) {
1790
+ if (!await it(ctx)) return false;
1791
+ }
1792
+ return true;
1793
+ };
1794
+ }
1795
+ function or(...predicates) {
1796
+ return async (ctx) => {
1797
+ for (const it of predicates) {
1798
+ if (await it(ctx)) return true;
1799
+ }
1800
+ return false;
1801
+ };
1802
+ }
1803
+ function not(predicate) {
1804
+ return async (ctx) => !await predicate(ctx);
1805
+ }
1806
+ function contentIncludes(keywords) {
1807
+ const lower = keywords.map((k) => k.toLowerCase());
1808
+ return (ctx) => {
1809
+ const text = ctx.content.toLowerCase();
1810
+ return lower.some((kw) => text.includes(kw));
1811
+ };
1812
+ }
1813
+ function contentPattern(pattern) {
1814
+ return (ctx) => {
1815
+ pattern.lastIndex = 0;
1816
+ return pattern.test(ctx.content);
1817
+ };
1818
+ }
1819
+ function toDateParts(date, tz) {
1820
+ const parts = new Intl.DateTimeFormat("en-CA", {
1821
+ timeZone: tz,
1822
+ year: "numeric",
1823
+ month: "2-digit",
1824
+ day: "2-digit",
1825
+ hour: "2-digit",
1826
+ hourCycle: "h23"
1827
+ }).formatToParts(date);
1828
+ const get = (type) => parts.find((p) => p.type === type).value;
1829
+ return {
1830
+ year: get("year"),
1831
+ month: get("month"),
1832
+ day: get("day"),
1833
+ hour: get("hour")
1834
+ };
1835
+ }
1836
+ function temporalChanged(ctx, tz, getKey) {
1837
+ if (ctx.lastMessageAt === void 0) return true;
1838
+ const nowParts = toDateParts(/* @__PURE__ */ new Date(), tz);
1839
+ const prevParts = toDateParts(new Date(ctx.lastMessageAt), tz);
1840
+ return getKey(nowParts) !== getKey(prevParts);
1841
+ }
1842
+ function getSeason(month) {
1843
+ if (month >= 2 && month <= 4) return "Spring";
1844
+ if (month >= 5 && month <= 7) return "Summer";
1845
+ if (month >= 8 && month <= 10) return "Fall";
1846
+ return "Winter";
1847
+ }
1848
+ function isoWeekKey(parts) {
1849
+ const d = new Date(
1850
+ Date.UTC(
1851
+ parseInt(parts.year),
1852
+ parseInt(parts.month) - 1,
1853
+ parseInt(parts.day)
1854
+ )
1855
+ );
1856
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
1857
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
1858
+ const weekNo = Math.ceil(
1859
+ ((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
1860
+ );
1861
+ return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
1862
+ }
1863
+ function dayChanged(tz = "UTC") {
1864
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}`);
1865
+ }
1866
+ function hourChanged(tz = "UTC") {
1867
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}-${p.day}-${p.hour}`);
1868
+ }
1869
+ function monthChanged(tz = "UTC") {
1870
+ return (ctx) => temporalChanged(ctx, tz, (p) => `${p.year}-${p.month}`);
1871
+ }
1872
+ function yearChanged(tz = "UTC") {
1873
+ return (ctx) => temporalChanged(ctx, tz, (p) => p.year);
1874
+ }
1875
+ function seasonChanged(tz = "UTC") {
1876
+ return (ctx) => temporalChanged(ctx, tz, (p) => getSeason(parseInt(p.month) - 1));
1877
+ }
1878
+ function weekChanged(tz = "UTC") {
1879
+ return (ctx) => temporalChanged(ctx, tz, isoWeekKey);
1880
+ }
1881
+ function isConditionalReminder(fragment2) {
1882
+ return fragment2.name === "reminder" && !!fragment2.metadata?.reminder;
1883
+ }
1884
+ function getConditionalReminder(fragment2) {
1885
+ return fragment2.metadata.reminder;
1886
+ }
1887
+ var SYSTEM_REMINDER_OPEN_TAG = "<system-reminder>";
1888
+ var SYSTEM_REMINDER_CLOSE_TAG = "</system-reminder>";
1889
+ function getReminderRanges(metadata) {
1890
+ return metadata?.reminders ?? [];
1891
+ }
1892
+ function stripTextByRanges(text, ranges) {
1893
+ if (ranges.length === 0) {
1894
+ return text;
1895
+ }
1896
+ const normalized = ranges.map((range) => ({
1897
+ start: Math.max(0, Math.min(text.length, range.start)),
1898
+ end: Math.max(0, Math.min(text.length, range.end))
1899
+ })).filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
1900
+ if (normalized.length === 0) {
1901
+ return text;
1902
+ }
1903
+ let cursor = 0;
1904
+ let output = "";
1905
+ for (const range of normalized) {
1906
+ if (range.start < cursor) {
1907
+ if (range.end > cursor) {
1908
+ cursor = range.end;
1909
+ }
1910
+ continue;
1911
+ }
1912
+ output += text.slice(cursor, range.start);
1913
+ cursor = range.end;
1914
+ }
1915
+ output += text.slice(cursor);
1916
+ return output.trimEnd();
1917
+ }
1918
+ function stripReminders(message2) {
1919
+ const reminderRanges = getReminderRanges(
1920
+ isRecord(message2.metadata) ? message2.metadata : void 0
1921
+ );
1922
+ const rangesByPartIndex = /* @__PURE__ */ new Map();
1923
+ for (const range of reminderRanges) {
1924
+ const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
1925
+ partRanges.push({ start: range.start, end: range.end });
1926
+ rangesByPartIndex.set(range.partIndex, partRanges);
1927
+ }
1928
+ const strippedParts = message2.parts.flatMap((part, partIndex) => {
1929
+ const clonedPart = { ...part };
1930
+ const ranges = rangesByPartIndex.get(partIndex);
1931
+ if (clonedPart.type !== "text" || ranges === void 0) {
1932
+ return [clonedPart];
1933
+ }
1934
+ const strippedText = stripTextByRanges(clonedPart.text, ranges);
1935
+ if (strippedText.length === 0) {
1936
+ return [];
2086
1937
  }
2087
- if (Array.isArray(data)) {
2088
- return this.#renderArrayField(fragment2.name, data, ctx.depth);
1938
+ return [{ ...clonedPart, text: strippedText }];
1939
+ });
1940
+ const nextMessage = {
1941
+ ...message2,
1942
+ parts: strippedParts
1943
+ };
1944
+ if (isRecord(message2.metadata)) {
1945
+ const metadata = { ...message2.metadata };
1946
+ delete metadata.reminders;
1947
+ if (Object.keys(metadata).length > 0) {
1948
+ nextMessage.metadata = metadata;
1949
+ } else {
1950
+ delete nextMessage.metadata;
2089
1951
  }
2090
- if (isFragmentObject(data)) {
2091
- const entries = this.#renderObjectEntries(data, ctx.depth + 1);
2092
- if (!entries) {
2093
- return `${this.#pad(ctx.depth)}${fragment2.name}:`;
2094
- }
2095
- return `${this.#pad(ctx.depth)}${fragment2.name}:
2096
- ${entries}`;
1952
+ }
1953
+ return nextMessage;
1954
+ }
1955
+ function isRecord(value) {
1956
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1957
+ }
1958
+ function assertReminderText(text) {
1959
+ if (text.trim().length === 0) {
1960
+ throw new Error("Reminder text must not be empty");
1961
+ }
1962
+ }
1963
+ function formatTaggedReminder(text) {
1964
+ return `${SYSTEM_REMINDER_OPEN_TAG}${text}${SYSTEM_REMINDER_CLOSE_TAG}`;
1965
+ }
1966
+ function findLastTextPartIndex(message2) {
1967
+ for (let i = message2.parts.length - 1; i >= 0; i--) {
1968
+ if (message2.parts[i].type === "text") {
1969
+ return i;
2097
1970
  }
2098
- return `${this.#pad(ctx.depth)}${fragment2.name}:`;
2099
1971
  }
2100
- renderPrimitive(key, value, ctx) {
2101
- return `${this.#pad(ctx.depth)}${key}: ${this.#formatValue(value)}`;
1972
+ return void 0;
1973
+ }
1974
+ function ensureTextPart(message2) {
1975
+ const existingIndex = findLastTextPartIndex(message2);
1976
+ if (existingIndex !== void 0) {
1977
+ return existingIndex;
2102
1978
  }
2103
- renderArray(key, items, ctx) {
2104
- return this.#renderArrayField(key, items, ctx.depth);
1979
+ const reminderPart = {
1980
+ type: "text",
1981
+ text: ""
1982
+ };
1983
+ message2.parts.push(reminderPart);
1984
+ return message2.parts.length - 1;
1985
+ }
1986
+ function applyInlineReminder(message2, value) {
1987
+ const partIndex = ensureTextPart(message2);
1988
+ const textPart = message2.parts[partIndex];
1989
+ if (textPart.type !== "text") {
1990
+ throw new Error("Failed to resolve text part for inline reminder");
2105
1991
  }
2106
- renderObject(key, obj, ctx) {
2107
- const entries = this.#renderObjectEntries(obj, ctx.depth + 1);
2108
- if (!entries) {
2109
- return `${this.#pad(ctx.depth)}${key}:`;
2110
- }
2111
- return `${this.#pad(ctx.depth)}${key}:
2112
- ${entries}`;
1992
+ const reminderText = formatTaggedReminder(value);
1993
+ const start = textPart.text.length;
1994
+ const updatedText = `${textPart.text}${reminderText}`;
1995
+ message2.parts[partIndex] = { ...textPart, text: updatedText };
1996
+ return {
1997
+ id: generateId2(),
1998
+ text: value,
1999
+ partIndex,
2000
+ start,
2001
+ end: start + reminderText.length,
2002
+ mode: "inline"
2003
+ };
2004
+ }
2005
+ function applyPartReminder(message2, value) {
2006
+ const part = { type: "text", text: value };
2007
+ message2.parts.push(part);
2008
+ const partIndex = message2.parts.length - 1;
2009
+ return {
2010
+ id: generateId2(),
2011
+ text: value,
2012
+ partIndex,
2013
+ start: 0,
2014
+ end: value.length,
2015
+ mode: "part"
2016
+ };
2017
+ }
2018
+ function resolveReminderText(item, ctx) {
2019
+ return resolveReminder(item, ctx)?.text ?? "";
2020
+ }
2021
+ function normalizeReminderResolution(value) {
2022
+ if (typeof value === "string") {
2023
+ return value.trim().length === 0 ? null : { text: value };
2113
2024
  }
2114
- #pad(depth) {
2115
- return " ".repeat(depth);
2025
+ if (value.text.trim().length === 0) {
2026
+ return null;
2116
2027
  }
2117
- #needsQuoting(value) {
2118
- if (value === "") return true;
2119
- if (value !== value.trim()) return true;
2120
- if (["true", "false", "null"].includes(value.toLowerCase())) return true;
2121
- if (/^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value)) return true;
2122
- if (/[:\\"'[\]{}|,\t\n\r]/.test(value)) return true;
2123
- if (value.startsWith("-")) return true;
2124
- return false;
2028
+ return value;
2029
+ }
2030
+ function resolveReminder(item, ctx) {
2031
+ const resolvedText = typeof item.text === "function" ? item.text(ctx) : item.text;
2032
+ const resolved = normalizeReminderResolution(resolvedText);
2033
+ if (!resolved) {
2034
+ return null;
2125
2035
  }
2126
- #escape(value) {
2127
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
2036
+ const metadata = item.metadata || resolved.metadata ? {
2037
+ ...item.metadata ?? {},
2038
+ ...resolved.metadata ?? {}
2039
+ } : void 0;
2040
+ return metadata ? { ...resolved, metadata } : resolved;
2041
+ }
2042
+ async function resolveReminderAsync(item, ctx) {
2043
+ const text = await (typeof item.text === "function" ? item.text(ctx) : item.text);
2044
+ const resolved = normalizeReminderResolution(text);
2045
+ if (!resolved) return null;
2046
+ const metadata = item.metadata || resolved.metadata ? { ...item.metadata ?? {}, ...resolved.metadata ?? {} } : void 0;
2047
+ return metadata ? { ...resolved, metadata } : resolved;
2048
+ }
2049
+ function mergeMessageMetadata(message2, addedMetadata) {
2050
+ if (Object.keys(addedMetadata).length === 0) {
2051
+ return;
2128
2052
  }
2129
- #canonicalizeNumber(n) {
2130
- if (!Number.isFinite(n)) return "null";
2131
- if (Object.is(n, -0)) return "0";
2132
- return String(n);
2053
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
2054
+ message2.metadata = { ...metadata, ...addedMetadata };
2055
+ }
2056
+ function applyReminderToMessage(message2, item, ctx) {
2057
+ const resolved = resolveReminder(item, ctx);
2058
+ if (!resolved) {
2059
+ return null;
2133
2060
  }
2134
- #formatValue(value) {
2135
- if (value === null) return "null";
2136
- if (typeof value === "boolean") return String(value);
2137
- if (typeof value === "number") return this.#canonicalizeNumber(value);
2138
- if (typeof value === "string") {
2139
- if (this.#needsQuoting(value)) {
2140
- return `"${this.#escape(value)}"`;
2061
+ if (resolved.metadata) {
2062
+ mergeMessageMetadata(message2, resolved.metadata);
2063
+ }
2064
+ return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
2065
+ }
2066
+ function mergeReminderMetadata(message2, addedReminders) {
2067
+ if (addedReminders.length === 0) return;
2068
+ const metadata = isRecord(message2.metadata) ? { ...message2.metadata } : {};
2069
+ const existing = Array.isArray(metadata.reminders) ? metadata.reminders : [];
2070
+ metadata.reminders = [...existing, ...addedReminders];
2071
+ message2.metadata = metadata;
2072
+ }
2073
+ function reminder(textOrFragment, options) {
2074
+ const text = isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
2075
+ if (typeof text === "string") {
2076
+ assertReminderText(text);
2077
+ }
2078
+ if (options && "when" in options && options.when) {
2079
+ return {
2080
+ name: "reminder",
2081
+ data: null,
2082
+ metadata: {
2083
+ reminder: {
2084
+ text,
2085
+ when: options.when,
2086
+ asPart: options.asPart ?? false
2087
+ }
2141
2088
  }
2142
- return value;
2089
+ };
2090
+ }
2091
+ return {
2092
+ text,
2093
+ asPart: options?.asPart ?? false
2094
+ };
2095
+ }
2096
+ function user(content, ...reminders) {
2097
+ const message2 = typeof content === "string" ? {
2098
+ id: generateId2(),
2099
+ role: "user",
2100
+ parts: [{ type: "text", text: content }]
2101
+ } : { ...content, role: "user", parts: [...content.parts] };
2102
+ if (reminders.length > 0) {
2103
+ const plainText = extractPlainText(message2);
2104
+ const added = [];
2105
+ for (const item of reminders) {
2106
+ const meta = applyReminderToMessage(message2, item, {
2107
+ content: plainText
2108
+ });
2109
+ if (meta) added.push(meta);
2143
2110
  }
2144
- return `"${this.#escape(JSON.stringify(value))}"`;
2111
+ mergeReminderMetadata(message2, added);
2145
2112
  }
2146
- };
2113
+ return {
2114
+ id: message2.id,
2115
+ name: "user",
2116
+ type: "message",
2117
+ persist: true,
2118
+ codec: {
2119
+ decode() {
2120
+ return message2;
2121
+ },
2122
+ encode() {
2123
+ return message2;
2124
+ }
2125
+ }
2126
+ };
2127
+ }
2128
+
2129
+ // packages/context/src/lib/guardrail.ts
2130
+ function pass(part) {
2131
+ return { type: "pass", part };
2132
+ }
2133
+ function fail(feedback) {
2134
+ return { type: "fail", feedback };
2135
+ }
2136
+ function stop(part) {
2137
+ return { type: "stop", part };
2138
+ }
2139
+ function runGuardrailChain(part, guardrails, context) {
2140
+ let currentPart = part;
2141
+ for (const guardrail2 of guardrails) {
2142
+ const result = guardrail2.handle(currentPart, context);
2143
+ if (result.type === "fail" || result.type === "stop") {
2144
+ return result;
2145
+ }
2146
+ currentPart = result.part;
2147
+ }
2148
+ return pass(currentPart);
2149
+ }
2147
2150
 
2148
2151
  // packages/context/src/lib/render.ts
2149
2152
  function render(tag, ...fragments) {
@@ -2302,7 +2305,6 @@ function visualizeGraph(data) {
2302
2305
  export {
2303
2306
  ContextRenderer,
2304
2307
  ContextStore,
2305
- LAZY_ID,
2306
2308
  MarkdownRenderer,
2307
2309
  ModelsRegistry,
2308
2310
  StreamStore,
@@ -2345,10 +2347,8 @@ export {
2345
2347
  isConditionalReminder,
2346
2348
  isFragment,
2347
2349
  isFragmentObject,
2348
- isLazyFragment,
2349
2350
  isMessageFragment,
2350
2351
  isRecord,
2351
- lastAssistantMessage,
2352
2352
  mergeMessageMetadata,
2353
2353
  mergeReminderMetadata,
2354
2354
  message,
@@ -2366,6 +2366,7 @@ export {
2366
2366
  reminder,
2367
2367
  render,
2368
2368
  resolveReminder,
2369
+ resolveReminderAsync,
2369
2370
  resolveReminderText,
2370
2371
  role,
2371
2372
  runGuardrailChain,