@deepagents/context 0.38.0 → 0.39.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.
Files changed (32) hide show
  1. package/dist/browser.js +7 -0
  2. package/dist/browser.js.map +2 -2
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +393 -126
  6. package/dist/index.js.map +4 -4
  7. package/dist/lib/chat.d.ts +19 -5
  8. package/dist/lib/chat.d.ts.map +1 -1
  9. package/dist/lib/engine.d.ts +44 -2
  10. package/dist/lib/engine.d.ts.map +1 -1
  11. package/dist/lib/fragments/message/user.d.ts +1 -0
  12. package/dist/lib/fragments/message/user.d.ts.map +1 -1
  13. package/dist/lib/fragments/reminders/combinators.d.ts +23 -0
  14. package/dist/lib/fragments/reminders/combinators.d.ts.map +1 -1
  15. package/dist/lib/fragments/reminders/message-predicates.d.ts +10 -0
  16. package/dist/lib/fragments/reminders/message-predicates.d.ts.map +1 -0
  17. package/dist/lib/fragments/reminders/temporal/elapsed.d.ts +3 -0
  18. package/dist/lib/fragments/reminders/temporal/elapsed.d.ts.map +1 -0
  19. package/dist/lib/fragments/reminders/temporal/index.d.ts +1 -0
  20. package/dist/lib/fragments/reminders/temporal/index.d.ts.map +1 -1
  21. package/dist/lib/fragments/reminders/tool-predicates.d.ts +17 -0
  22. package/dist/lib/fragments/reminders/tool-predicates.d.ts.map +1 -0
  23. package/dist/lib/fragments/reminders/usage-predicates.d.ts +3 -0
  24. package/dist/lib/fragments/reminders/usage-predicates.d.ts.map +1 -0
  25. package/dist/lib/fragments.d.ts +2 -0
  26. package/dist/lib/fragments.d.ts.map +1 -1
  27. package/dist/lib/title.d.ts +16 -7
  28. package/dist/lib/title.d.ts.map +1 -1
  29. package/dist/lib/tracing/exporter.d.ts.map +1 -1
  30. package/dist/lib/tracing/index.js +16 -1
  31. package/dist/lib/tracing/index.js.map +2 -2
  32. package/package.json +14 -13
package/dist/index.js CHANGED
@@ -199,6 +199,12 @@ function assistant(message2) {
199
199
  }
200
200
  };
201
201
  }
202
+ function toMessageFragment(item) {
203
+ if (isFragment(item) && isMessageFragment(item)) {
204
+ return item;
205
+ }
206
+ return message(item);
207
+ }
202
208
  function message(content) {
203
209
  const message2 = typeof content === "string" ? {
204
210
  id: generateId(),
@@ -1396,27 +1402,27 @@ import {
1396
1402
  } from "ai";
1397
1403
  import chalk from "chalk";
1398
1404
  function createRepairToolCall(model, abortSignal) {
1399
- return async ({ toolCall, tools, inputSchema, error }) => {
1405
+ return async ({ toolCall: toolCall2, tools, inputSchema, error }) => {
1400
1406
  if (NoSuchToolError.isInstance(error)) {
1401
1407
  return null;
1402
1408
  }
1403
1409
  console.log(
1404
- `Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall.toolName}`
1410
+ `Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall2.toolName}`
1405
1411
  );
1406
- const tool3 = tools[toolCall.toolName];
1412
+ const tool3 = tools[toolCall2.toolName];
1407
1413
  const { output } = await generateText({
1408
1414
  abortSignal,
1409
1415
  model,
1410
1416
  output: Output.object({ schema: tool3.inputSchema }),
1411
1417
  prompt: [
1412
- `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
1413
- JSON.stringify(toolCall.input),
1418
+ `The model tried to call the tool "${toolCall2.toolName}" with the following inputs:`,
1419
+ JSON.stringify(toolCall2.input),
1414
1420
  `The tool accepts the following schema:`,
1415
- JSON.stringify(inputSchema(toolCall)),
1421
+ JSON.stringify(inputSchema(toolCall2)),
1416
1422
  "Please fix the inputs."
1417
1423
  ].join("\n")
1418
1424
  });
1419
- return { ...toolCall, input: JSON.stringify(output) };
1425
+ return { ...toolCall2, input: JSON.stringify(output) };
1420
1426
  };
1421
1427
  }
1422
1428
 
@@ -1464,10 +1470,10 @@ var Agent = class _Agent {
1464
1470
  ),
1465
1471
  toolChoice: this.#options.toolChoice,
1466
1472
  onStepFinish: (step) => {
1467
- const toolCall = step.toolCalls.at(-1);
1468
- if (toolCall) {
1473
+ const toolCall2 = step.toolCalls.at(-1);
1474
+ if (toolCall2) {
1469
1475
  console.log(
1470
- `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1476
+ `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall2.toolName}(${JSON.stringify(toolCall2.input)})`
1471
1477
  );
1472
1478
  }
1473
1479
  }
@@ -1538,10 +1544,10 @@ var Agent = class _Agent {
1538
1544
  experimental_context: contextVariables,
1539
1545
  toolChoice: this.#options.toolChoice,
1540
1546
  onStepFinish: (step) => {
1541
- const toolCall = step.toolCalls.at(-1);
1542
- if (toolCall) {
1547
+ const toolCall2 = step.toolCalls.at(-1);
1548
+ if (toolCall2) {
1543
1549
  console.log(
1544
- `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1550
+ `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall2.toolName}(${JSON.stringify(toolCall2.input)})`
1545
1551
  );
1546
1552
  }
1547
1553
  }
@@ -1869,7 +1875,6 @@ import {
1869
1875
  NoSuchToolError as NoSuchToolError2,
1870
1876
  ToolCallRepairError,
1871
1877
  createUIMessageStream as createUIMessageStream2,
1872
- generateId as generateId4,
1873
1878
  isToolUIPart
1874
1879
  } from "ai";
1875
1880
 
@@ -1877,7 +1882,10 @@ import {
1877
1882
  import { z as z2 } from "zod";
1878
1883
 
1879
1884
  // packages/context/src/lib/engine.ts
1880
- import { validateUIMessages } from "ai";
1885
+ import {
1886
+ generateId as generateId4,
1887
+ validateUIMessages
1888
+ } from "ai";
1881
1889
  import { mergeWith } from "lodash-es";
1882
1890
 
1883
1891
  // packages/context/src/lib/estimate.ts
@@ -2585,6 +2593,9 @@ function estimateMessageContent(data) {
2585
2593
  function isLanguageModelUsage(value) {
2586
2594
  return typeof value === "object" && value !== null && "totalTokens" in value;
2587
2595
  }
2596
+ function isEmptyAssistantPlaceholder(message2) {
2597
+ return message2.role === "assistant" && message2.parts.length === 0;
2598
+ }
2588
2599
  var ContextEngine = class _ContextEngine {
2589
2600
  /** Non-message fragments (role, hints, etc.) - not persisted in graph */
2590
2601
  #fragments = [];
@@ -2733,6 +2744,7 @@ var ContextEngine = class _ContextEngine {
2733
2744
  let lastMessageAt;
2734
2745
  let lastMessage;
2735
2746
  let lastAssistantMessage;
2747
+ const lastAssistantMessages = [];
2736
2748
  if (this.#branch?.headMessageId) {
2737
2749
  const chain = await this.#store.getMessageChain(
2738
2750
  this.#branch.headMessageId
@@ -2740,7 +2752,9 @@ var ContextEngine = class _ContextEngine {
2740
2752
  for (const msg of chain) {
2741
2753
  messageCount++;
2742
2754
  if (msg.name === "assistant") {
2743
- lastAssistantMessage = msg.data;
2755
+ const assistantMsg = msg.data;
2756
+ lastAssistantMessage = assistantMsg;
2757
+ lastAssistantMessages.push(assistantMsg);
2744
2758
  }
2745
2759
  if (msg.name !== "user") {
2746
2760
  continue;
@@ -2759,13 +2773,128 @@ var ContextEngine = class _ContextEngine {
2759
2773
  messageCount,
2760
2774
  lastMessageAt,
2761
2775
  lastMessage,
2762
- lastAssistantMessage
2776
+ lastAssistantMessage,
2777
+ lastAssistantMessages
2763
2778
  };
2764
2779
  }
2765
2780
  async getTurnCount() {
2766
2781
  const { turn } = await this.#getChainContext();
2767
2782
  return turn;
2768
2783
  }
2784
+ async firstUserMessage() {
2785
+ await this.#ensureInitialized();
2786
+ if (this.#branch?.headMessageId) {
2787
+ const chain = await this.#store.getMessageChain(
2788
+ this.#branch.headMessageId
2789
+ );
2790
+ for (const msg of chain) {
2791
+ if (msg.name === "user") return msg.data;
2792
+ }
2793
+ }
2794
+ for (const fragment2 of this.#pendingMessages) {
2795
+ if (fragment2.name !== "user") continue;
2796
+ if (!fragment2.codec) {
2797
+ throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2798
+ }
2799
+ return fragment2.codec.encode();
2800
+ }
2801
+ return void 0;
2802
+ }
2803
+ /**
2804
+ * Return the head of the conversation — pending tail or persisted branch head.
2805
+ *
2806
+ * Includes empty assistant placeholders (use this for id-lookup, not for
2807
+ * building model prompts — see `getMessages()` for prompt-ready output).
2808
+ *
2809
+ * @throws if the pending tail is missing an id (programming error).
2810
+ */
2811
+ async headMessage() {
2812
+ await this.#ensureInitialized();
2813
+ if (this.#pendingMessages.length > 0) {
2814
+ const tail = this.#pendingMessages[this.#pendingMessages.length - 1];
2815
+ if (!tail.id) {
2816
+ throw new Error(
2817
+ `headMessage: pending fragment "${tail.name}" is missing id`
2818
+ );
2819
+ }
2820
+ return { id: tail.id, name: tail.name };
2821
+ }
2822
+ if (this.#branch?.headMessageId) {
2823
+ const msg = await this.#store.getMessage(this.#branch.headMessageId);
2824
+ if (msg) return { id: msg.id, name: msg.name };
2825
+ }
2826
+ return void 0;
2827
+ }
2828
+ /**
2829
+ * Return the model-ready conversation: persisted chain plus pending fragments,
2830
+ * with empty assistant placeholders filtered out.
2831
+ *
2832
+ * For id-lookup use `headMessage()` instead — that one keeps placeholders.
2833
+ */
2834
+ async getMessages() {
2835
+ await this.#ensureInitialized();
2836
+ const messages = [];
2837
+ if (this.#branch?.headMessageId) {
2838
+ const chain = await this.#store.getMessageChain(
2839
+ this.#branch.headMessageId
2840
+ );
2841
+ for (const msg of chain) {
2842
+ const data = msg.data;
2843
+ if (isEmptyAssistantPlaceholder(data)) continue;
2844
+ messages.push(data);
2845
+ }
2846
+ }
2847
+ for (const fragment2 of this.#pendingMessages) {
2848
+ if (!fragment2.codec) {
2849
+ throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2850
+ }
2851
+ const encoded = fragment2.codec.encode();
2852
+ if (isEmptyAssistantPlaceholder(encoded)) continue;
2853
+ messages.push(encoded);
2854
+ }
2855
+ return messages.length === 0 ? [] : validateUIMessages({ messages });
2856
+ }
2857
+ /**
2858
+ * Advance the conversation by one turn. Required setup before `chat()`.
2859
+ *
2860
+ * - User input → persists the message AND appends an empty assistant
2861
+ * placeholder reserving the id of the next streamed response.
2862
+ * - Assistant input (tool-resume / continuation) → persists in-place
2863
+ * (`branch: false`), reusing the input's id.
2864
+ *
2865
+ * Always leaves the chain head as an assistant fragment, satisfying chat()'s
2866
+ * precondition.
2867
+ *
2868
+ * @returns the assistant id that will receive the streamed response — useful
2869
+ * for telemetry, optimistic UI, or correlating logs before the stream starts.
2870
+ * @throws if assistant input is missing an id.
2871
+ *
2872
+ * @example
2873
+ * ```ts
2874
+ * const assistantId = await context.continue(user('hi'));
2875
+ * const stream = await chat(agent); // streams into assistantId
2876
+ * ```
2877
+ */
2878
+ async continue(input) {
2879
+ const fragment2 = toMessageFragment(input);
2880
+ const isAssistantUpdate = fragment2.name === "assistant";
2881
+ let assistantId;
2882
+ if (isAssistantUpdate) {
2883
+ if (!fragment2.id) {
2884
+ throw new Error("continue: assistant input is missing id");
2885
+ }
2886
+ assistantId = fragment2.id;
2887
+ this.set(fragment2);
2888
+ } else {
2889
+ assistantId = generateId4();
2890
+ this.set(
2891
+ fragment2,
2892
+ assistant({ id: assistantId, role: "assistant", parts: [] })
2893
+ );
2894
+ }
2895
+ await this.save({ branch: !isAssistantUpdate });
2896
+ return assistantId;
2897
+ }
2769
2898
  /**
2770
2899
  * Add fragments to the context.
2771
2900
  *
@@ -2812,25 +2941,8 @@ var ContextEngine = class _ContextEngine {
2812
2941
  async resolve(options) {
2813
2942
  await this.#ensureInitialized();
2814
2943
  const systemPrompt = options.renderer.render(this.#renderableFragments);
2815
- const messages = [];
2816
- if (this.#branch?.headMessageId) {
2817
- const chain = await this.#store.getMessageChain(
2818
- this.#branch.headMessageId
2819
- );
2820
- for (const msg of chain) {
2821
- messages.push(msg.data);
2822
- }
2823
- }
2824
- for (const fragment2 of this.#pendingMessages) {
2825
- if (!fragment2.codec) {
2826
- throw new Error(`Fragment "${fragment2.name}" is missing codec.`);
2827
- }
2828
- messages.push(fragment2.codec.encode());
2829
- }
2830
- return {
2831
- systemPrompt,
2832
- messages: messages.length === 0 ? [] : await validateUIMessages({ messages })
2833
- };
2944
+ const messages = await this.getMessages();
2945
+ return { systemPrompt, messages };
2834
2946
  }
2835
2947
  /**
2836
2948
  * Save pending messages to the graph.
@@ -2881,7 +2993,8 @@ var ContextEngine = class _ContextEngine {
2881
2993
  messageCount,
2882
2994
  lastMessageAt,
2883
2995
  lastMessage,
2884
- lastAssistantMessage
2996
+ lastAssistantMessage,
2997
+ lastAssistantMessages
2885
2998
  } = await this.#getChainContext();
2886
2999
  const original = lastUserFragment.codec.encode();
2887
3000
  const plainText = extractPlainText(original);
@@ -2899,7 +3012,8 @@ var ContextEngine = class _ContextEngine {
2899
3012
  branch: this.#branchName,
2900
3013
  elapsed,
2901
3014
  messageCount,
2902
- lastAssistantMessage
3015
+ lastAssistantMessage,
3016
+ lastAssistantMessages
2903
3017
  };
2904
3018
  const configs = conditionalReminders.map(
2905
3019
  (it) => it.metadata.reminder
@@ -3746,55 +3860,75 @@ Examples:
3746
3860
  - "hi" -> New Conversation
3747
3861
  - "debug my python code" -> Python Debugging`;
3748
3862
  var titleSchema = z2.object({ title: z2.string() });
3749
- function extractText(message2) {
3750
- const cleaned = stripReminders(message2);
3751
- const textPart = cleaned.parts.find((p) => p.type === "text");
3752
- return textPart && "text" in textPart ? textPart.text : "";
3753
- }
3754
- function truncateTitle(text) {
3755
- if (!text) return "New Chat";
3756
- return text.length > 100 ? text.slice(0, 100) + "..." : text;
3757
- }
3758
- function staticChatTitle(message2) {
3759
- return truncateTitle(extractText(message2));
3760
- }
3761
- async function generateChatTitle(options) {
3762
- const text = extractText(options.message);
3763
- const fallback = truncateTitle(text);
3764
- if (!text) return fallback;
3765
- const store2 = new InMemoryContextStore();
3766
- const context = new ContextEngine({
3767
- store: store2,
3768
- chatId: crypto.randomUUID(),
3769
- userId: "system"
3770
- });
3771
- context.set(role(TITLE_PROMPT), user(text));
3772
- try {
3863
+ var TitleGenerator = class {
3864
+ #context;
3865
+ constructor(options) {
3866
+ this.#context = options.context;
3867
+ }
3868
+ async ensure(options) {
3869
+ const msg = await this.#firstUntitledUser();
3870
+ if (!msg) return null;
3871
+ try {
3872
+ const title = await this.#generateTitle(msg, options);
3873
+ return this.#applyTitle(title, "llm");
3874
+ } catch (error) {
3875
+ console.warn(
3876
+ "TitleGenerator: LLM title generation failed, falling back to static.",
3877
+ error
3878
+ );
3879
+ return this.#applyTitle(this.#staticTitle(msg), "static");
3880
+ }
3881
+ }
3882
+ async ensureStatic() {
3883
+ const msg = await this.#firstUntitledUser();
3884
+ if (!msg) return null;
3885
+ return this.#applyTitle(this.#staticTitle(msg), "static");
3886
+ }
3887
+ #staticTitle(message2) {
3888
+ return this.#truncateTitle(this.#extractText(message2));
3889
+ }
3890
+ async #generateTitle(message2, options) {
3891
+ const text = this.#extractText(message2);
3892
+ if (!text) {
3893
+ throw new Error(
3894
+ "Cannot generate chat title: message has no text content."
3895
+ );
3896
+ }
3897
+ const store2 = new InMemoryContextStore();
3898
+ const context = new ContextEngine({
3899
+ store: store2,
3900
+ chatId: crypto.randomUUID(),
3901
+ userId: "system"
3902
+ });
3903
+ context.set(role(TITLE_PROMPT), user(text));
3773
3904
  const { title } = await structuredOutput({
3774
3905
  context,
3775
3906
  model: options.model,
3776
3907
  schema: titleSchema
3777
3908
  }).generate({}, { abortSignal: options.abortSignal });
3778
- return title || fallback;
3779
- } catch (error) {
3780
- console.warn("Title generation failed, using fallback:", error);
3781
- return fallback;
3909
+ if (!title) {
3910
+ throw new Error("Title generation returned an empty string.");
3911
+ }
3912
+ return title;
3782
3913
  }
3783
- }
3784
-
3785
- // packages/context/src/lib/chat.ts
3786
- function toMessageFragment(item) {
3787
- if (isFragment(item) && isMessageFragment(item)) {
3788
- return item;
3914
+ #extractText(message2) {
3915
+ return extractPlainText(stripReminders(message2));
3789
3916
  }
3790
- return message(item);
3791
- }
3792
- function chatMessageToUIMessage(item) {
3793
- if (isFragment(item) && isMessageFragment(item)) {
3794
- return item.codec.encode();
3917
+ #truncateTitle(text) {
3918
+ if (!text) return "New Chat";
3919
+ return text.length > 100 ? text.slice(0, 100) + "..." : text;
3795
3920
  }
3796
- return item;
3797
- }
3921
+ async #applyTitle(title, source) {
3922
+ await this.#context.updateChat({ title });
3923
+ return { title, source };
3924
+ }
3925
+ async #firstUntitledUser() {
3926
+ if (this.#context.chat?.title) return void 0;
3927
+ return this.#context.firstUserMessage();
3928
+ }
3929
+ };
3930
+
3931
+ // packages/context/src/lib/chat.ts
3798
3932
  var defaultChatMessageMetadata = ({
3799
3933
  part
3800
3934
  }) => {
@@ -3806,7 +3940,7 @@ var defaultChatMessageMetadata = ({
3806
3940
  }
3807
3941
  return void 0;
3808
3942
  };
3809
- async function chat(agent2, messages, options) {
3943
+ async function chat(agent2, options = {}) {
3810
3944
  const context = agent2.context;
3811
3945
  const sandbox = agent2.sandbox;
3812
3946
  if (!context) {
@@ -3814,53 +3948,36 @@ async function chat(agent2, messages, options) {
3814
3948
  "Agent is missing a context. Provide context when creating the agent."
3815
3949
  );
3816
3950
  }
3817
- if (messages.length === 0) {
3818
- throw new Error("messages must not be empty");
3819
- }
3820
- const lastItem = messages[messages.length - 1];
3821
- const lastFragment = toMessageFragment(lastItem);
3822
- const lastUIMessage = chatMessageToUIMessage(lastItem);
3823
- let assistantMsgId;
3824
- if (lastUIMessage.role === "assistant") {
3825
- context.set(lastFragment);
3826
- await context.save({ branch: false });
3827
- assistantMsgId = lastUIMessage.id;
3828
- } else {
3829
- context.set(lastFragment);
3830
- await context.save();
3831
- assistantMsgId = generateId4();
3832
- }
3833
- const uiMessages = messages.map(chatMessageToUIMessage);
3834
- let title = null;
3835
- if (!context.chat?.title) {
3836
- const firstUserMsg = uiMessages.find((m) => m.role === "user");
3837
- if (firstUserMsg) {
3838
- if (options?.generateTitle && agent2.model) {
3839
- title = await generateChatTitle({
3840
- message: firstUserMsg,
3841
- model: agent2.model,
3842
- abortSignal: options?.abortSignal
3843
- });
3844
- } else {
3845
- title = staticChatTitle(firstUserMsg);
3846
- }
3847
- await context.updateChat({ title });
3848
- }
3951
+ const head = await context.headMessage();
3952
+ if (head?.name !== "assistant") {
3953
+ throw new Error(
3954
+ "chat: expected an assistant message at head. Call context.continue(input) before chat()."
3955
+ );
3849
3956
  }
3850
- const streamContextVariables = options?.contextVariables === void 0 ? {} : options.contextVariables;
3851
- const result = await agent2.stream(streamContextVariables, {
3852
- transform: options?.transform,
3853
- abortSignal: options?.abortSignal
3854
- });
3957
+ const assistantMsgId = head.id;
3958
+ const uiMessages = await context.getMessages();
3959
+ const streamContextVariables = options.contextVariables === void 0 ? {} : options.contextVariables;
3960
+ const [title, result] = await Promise.all([
3961
+ makeTitle({
3962
+ context,
3963
+ model: agent2.model,
3964
+ generateTitle: options.generateTitle,
3965
+ abortSignal: options.abortSignal
3966
+ }),
3967
+ agent2.stream(streamContextVariables, {
3968
+ transform: options.transform,
3969
+ abortSignal: options.abortSignal
3970
+ })
3971
+ ]);
3855
3972
  const uiStream = result.toUIMessageStream({
3856
- onError: options?.onError ?? formatChatError,
3973
+ onError: options.onError ?? formatChatError,
3857
3974
  sendStart: true,
3858
3975
  sendFinish: true,
3859
3976
  sendReasoning: true,
3860
3977
  sendSources: true,
3861
3978
  originalMessages: uiMessages,
3862
3979
  generateMessageId: () => assistantMsgId,
3863
- messageMetadata: options?.messageMetadata ?? defaultChatMessageMetadata
3980
+ messageMetadata: options.messageMetadata ?? defaultChatMessageMetadata
3864
3981
  });
3865
3982
  return createUIMessageStream2({
3866
3983
  originalMessages: uiMessages,
@@ -3883,7 +4000,7 @@ async function chat(agent2, messages, options) {
3883
4000
  }
3884
4001
  const drained = sandbox.drainFileEvents?.() ?? [];
3885
4002
  const fileEvents = isAborted ? [] : drained;
3886
- const finalMetadata = await options?.finalAssistantMetadata?.(normalizedMessage);
4003
+ const finalMetadata = await options.finalAssistantMetadata?.(normalizedMessage);
3887
4004
  const mergedMetadata = {
3888
4005
  ...normalizedMessage.metadata ?? {},
3889
4006
  ...fileEvents.length > 0 ? { fileEvents } : {},
@@ -3920,9 +4037,7 @@ function sanitizeAbortedParts(parts) {
3920
4037
  sanitized.push(part);
3921
4038
  continue;
3922
4039
  }
3923
- if (part.state === "input-streaming") {
3924
- continue;
3925
- }
4040
+ if (part.state === "input-streaming") continue;
3926
4041
  sanitized.push({
3927
4042
  ...part,
3928
4043
  state: "output-error",
@@ -3947,6 +4062,19 @@ function formatChatError(error) {
3947
4062
  }
3948
4063
  return JSON.stringify(error);
3949
4064
  }
4065
+ async function makeTitle(options) {
4066
+ const titler = new TitleGenerator({ context: options.context });
4067
+ if (options.generateTitle && !options.model) {
4068
+ console.warn(
4069
+ "chat: generateTitle=true but agent.model is unset; using static title."
4070
+ );
4071
+ }
4072
+ const result = options.generateTitle && options.model ? await titler.ensure({
4073
+ model: options.model,
4074
+ abortSignal: options.abortSignal
4075
+ }) : await titler.ensureStatic();
4076
+ return result?.title ?? null;
4077
+ }
3950
4078
 
3951
4079
  // packages/context/src/lib/fragments/user.ts
3952
4080
  function identity(input) {
@@ -4393,6 +4521,32 @@ function or(...predicates) {
4393
4521
  function not(predicate) {
4394
4522
  return async (ctx) => !await predicate(ctx);
4395
4523
  }
4524
+ function withinLastN(n, predicate) {
4525
+ return async (ctx) => {
4526
+ if (n <= 0) return false;
4527
+ const candidates = (ctx.lastAssistantMessages ?? []).slice(-n);
4528
+ for (const message2 of candidates) {
4529
+ if (await predicate({ ...ctx, lastAssistantMessage: message2 })) {
4530
+ return true;
4531
+ }
4532
+ }
4533
+ return false;
4534
+ };
4535
+ }
4536
+ function everyOfLastN(n, predicate) {
4537
+ return async (ctx) => {
4538
+ if (n <= 0) return false;
4539
+ const history = ctx.lastAssistantMessages ?? [];
4540
+ if (history.length < n) return false;
4541
+ const candidates = history.slice(-n);
4542
+ for (const message2 of candidates) {
4543
+ if (!await predicate({ ...ctx, lastAssistantMessage: message2 })) {
4544
+ return false;
4545
+ }
4546
+ }
4547
+ return true;
4548
+ };
4549
+ }
4396
4550
 
4397
4551
  // packages/context/src/lib/fragments/reminders/content-predicates.ts
4398
4552
  function contentMatches(topics, options) {
@@ -4418,6 +4572,37 @@ function contentPattern(pattern) {
4418
4572
  };
4419
4573
  }
4420
4574
 
4575
+ // packages/context/src/lib/fragments/reminders/message-predicates.ts
4576
+ function assertCountSpec(spec) {
4577
+ const hasEq = spec.eq !== void 0;
4578
+ const hasRange = spec.gte !== void 0 || spec.lte !== void 0;
4579
+ if (!hasEq && !hasRange) {
4580
+ throw new Error("CountSpec must include at least one of gte/lte/eq");
4581
+ }
4582
+ if (hasEq && hasRange) {
4583
+ throw new Error("CountSpec.eq cannot be combined with gte/lte");
4584
+ }
4585
+ }
4586
+ function checkCount(count, spec) {
4587
+ if (spec.eq !== void 0) return count === spec.eq;
4588
+ if (spec.gte !== void 0 && count < spec.gte) return false;
4589
+ if (spec.lte !== void 0 && count > spec.lte) return false;
4590
+ return true;
4591
+ }
4592
+ function lastAssistantLength(spec) {
4593
+ assertCountSpec(spec);
4594
+ return (ctx) => {
4595
+ const message2 = ctx.lastAssistantMessage;
4596
+ const text = message2 ? extractPlainText(message2) : "";
4597
+ return checkCount(text.length, spec);
4598
+ };
4599
+ }
4600
+
4601
+ // packages/context/src/lib/fragments/reminders/temporal/elapsed.ts
4602
+ function elapsedExceeds(ms) {
4603
+ return (ctx) => (ctx.elapsed ?? 0) >= ms;
4604
+ }
4605
+
4421
4606
  // packages/context/src/lib/fragments/reminders/temporal/predicates.ts
4422
4607
  var LOCALE_METADATA_KEY = "locale";
4423
4608
  function getLocaleFromMessage(message2) {
@@ -4689,6 +4874,73 @@ function temporalReminder(options) {
4689
4874
  ];
4690
4875
  }
4691
4876
 
4877
+ // packages/context/src/lib/fragments/reminders/tool-predicates.ts
4878
+ import { isStaticToolUIPart } from "ai";
4879
+ var COMPLETED_STATES = /* @__PURE__ */ new Set([
4880
+ "input-available",
4881
+ "output-available",
4882
+ "output-error"
4883
+ ]);
4884
+ function matchesName(spec, name) {
4885
+ return typeof spec === "function" ? spec(name) : spec === name;
4886
+ }
4887
+ function toolNameOf(part) {
4888
+ return part.type.slice("tool-".length);
4889
+ }
4890
+ function toolPartsOf(message2) {
4891
+ if (!message2) return [];
4892
+ return message2.parts.filter(isStaticToolUIPart);
4893
+ }
4894
+ function toolCall(options) {
4895
+ return (ctx) => {
4896
+ const parts = toolPartsOf(ctx.lastAssistantMessage);
4897
+ return parts.some((part) => {
4898
+ if (options.state) {
4899
+ if (part.state !== options.state) return false;
4900
+ } else if (!COMPLETED_STATES.has(part.state)) {
4901
+ return false;
4902
+ }
4903
+ if (options.name !== void 0 && !matchesName(options.name, toolNameOf(part))) {
4904
+ return false;
4905
+ }
4906
+ if (options.input && !options.input(part.input)) {
4907
+ return false;
4908
+ }
4909
+ if (options.output) {
4910
+ if (part.state !== "output-available") return false;
4911
+ if (!options.output(part.output))
4912
+ return false;
4913
+ }
4914
+ if (options.errorText) {
4915
+ if (part.state !== "output-error") return false;
4916
+ const text = part.errorText ?? "";
4917
+ if (!options.errorText(text)) return false;
4918
+ }
4919
+ return true;
4920
+ });
4921
+ };
4922
+ }
4923
+ function toolCalled(name) {
4924
+ return toolCall({ name });
4925
+ }
4926
+ function toolFailed(name) {
4927
+ return toolCall({ name, state: "output-error" });
4928
+ }
4929
+ function anyToolCalled() {
4930
+ return (ctx) => toolPartsOf(ctx.lastAssistantMessage).some(
4931
+ (part) => COMPLETED_STATES.has(part.state)
4932
+ );
4933
+ }
4934
+ function toolCallCount(name, spec) {
4935
+ assertCountSpec(spec);
4936
+ return (ctx) => {
4937
+ const count = toolPartsOf(ctx.lastAssistantMessage).filter(
4938
+ (part) => COMPLETED_STATES.has(part.state) && matchesName(name, toolNameOf(part))
4939
+ ).length;
4940
+ return checkCount(count, spec);
4941
+ };
4942
+ }
4943
+
4692
4944
  // packages/context/src/lib/fragments/reminders/turn-predicates.ts
4693
4945
  function everyNTurns(n) {
4694
4946
  return ({ turn }) => turn % n === 0;
@@ -4703,6 +4955,11 @@ function afterTurn(n) {
4703
4955
  return ({ turn }) => turn > n;
4704
4956
  }
4705
4957
 
4958
+ // packages/context/src/lib/fragments/reminders/usage-predicates.ts
4959
+ function usageExceeds(totalTokens) {
4960
+ return (ctx) => (ctx.usage?.totalTokens ?? 0) >= totalTokens;
4961
+ }
4962
+
4706
4963
  // packages/context/src/lib/fragments/socratic.ts
4707
4964
  function socraticPrompting() {
4708
4965
  return [
@@ -9211,6 +9468,7 @@ export {
9211
9468
  SqliteStreamStore,
9212
9469
  StreamManager,
9213
9470
  StreamStore,
9471
+ TitleGenerator,
9214
9472
  TomlRenderer,
9215
9473
  ToonRenderer,
9216
9474
  XmlRenderer,
@@ -9223,18 +9481,20 @@ export {
9223
9481
  alias,
9224
9482
  analogy,
9225
9483
  and,
9484
+ anyToolCalled,
9226
9485
  applyInlineReminder,
9227
9486
  applyPartReminder,
9228
9487
  applyReminderToMessage,
9229
9488
  asStaticWordPartText,
9230
9489
  asStaticWordText,
9490
+ assertCountSpec,
9231
9491
  assistant,
9232
9492
  assistantText,
9233
9493
  buildSchemaSubcommand,
9234
9494
  buildSubcommandRepair,
9235
9495
  chainHooks,
9236
9496
  chat,
9237
- chatMessageToUIMessage,
9497
+ checkCount,
9238
9498
  clarification,
9239
9499
  classifies,
9240
9500
  contentIncludes,
@@ -9257,10 +9517,12 @@ export {
9257
9517
  defaultTokenizer,
9258
9518
  defineSubcommandGroup,
9259
9519
  discoverSkillsInDirectory,
9520
+ elapsedExceeds,
9260
9521
  encodeSerializedValue,
9261
9522
  errorRecoveryGuardrail,
9262
9523
  estimate,
9263
9524
  everyNTurns,
9525
+ everyOfLastN,
9264
9526
  example,
9265
9527
  executorContext,
9266
9528
  explain,
@@ -9272,7 +9534,6 @@ export {
9272
9534
  formatResponse,
9273
9535
  fragment,
9274
9536
  fromFragment,
9275
- generateChatTitle,
9276
9537
  getFragmentData,
9277
9538
  getLocaleFromMessage,
9278
9539
  getModelsRegistry,
@@ -9291,6 +9552,7 @@ export {
9291
9552
  isInstallable,
9292
9553
  isMessageFragment,
9293
9554
  isRecord,
9555
+ lastAssistantLength,
9294
9556
  loadSkillMetadata,
9295
9557
  localeReminder,
9296
9558
  mapGenerateErrorToCode,
@@ -9335,7 +9597,6 @@ export {
9335
9597
  skillsReminder,
9336
9598
  socraticPrompting,
9337
9599
  soul,
9338
- staticChatTitle,
9339
9600
  stop,
9340
9601
  stripQuoteArtifacts,
9341
9602
  stripReminders,
@@ -9347,13 +9608,19 @@ export {
9347
9608
  timeReminder,
9348
9609
  toFragment,
9349
9610
  toMessageFragment,
9611
+ toolCall,
9612
+ toolCallCount,
9613
+ toolCalled,
9614
+ toolFailed,
9350
9615
  uploadSkills,
9616
+ usageExceeds,
9351
9617
  useAgentOsSandbox,
9352
9618
  useBashMeta,
9353
9619
  useSandbox,
9354
9620
  user,
9355
9621
  visualizeGraph,
9356
9622
  weekChanged,
9623
+ withinLastN,
9357
9624
  workflow,
9358
9625
  yearChanged,
9359
9626
  yearReminder