@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 +519 -518
- package/dist/browser.js.map +4 -4
- package/dist/index.js +516 -530
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts +2 -0
- package/dist/lib/agent.d.ts.map +1 -1
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/fragments/message/user.d.ts +57 -13
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- package/dist/lib/fragments.d.ts +0 -40
- package/dist/lib/fragments.d.ts.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
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(
|
|
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
|
-
|
|
223
|
-
|
|
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.
|
|
243
|
-
* 2.
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
await
|
|
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
|
-
|
|
365
|
-
|
|
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(
|
|
388
|
-
|
|
389
|
-
|
|
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/
|
|
597
|
-
import
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
734
|
-
cursor = range.end;
|
|
649
|
+
return sanitized;
|
|
735
650
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
return [];
|
|
665
|
+
if (isFragment(data)) {
|
|
666
|
+
return this.sanitizeFragment(data, seen) ?? void 0;
|
|
758
667
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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/
|
|
1818
|
-
|
|
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/
|
|
1822
|
-
function
|
|
1823
|
-
return
|
|
1495
|
+
// packages/context/src/lib/fragments/message/user.ts
|
|
1496
|
+
function everyNTurns(n) {
|
|
1497
|
+
return ({ turn }) => turn % n === 0;
|
|
1824
1498
|
}
|
|
1825
|
-
function
|
|
1826
|
-
return
|
|
1499
|
+
function once() {
|
|
1500
|
+
return ({ turn }) => turn === 1;
|
|
1827
1501
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2174
|
+
lastAssistantMessage
|
|
2156
2175
|
};
|
|
2157
|
-
const
|
|
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
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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:
|
|
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,
|