sparkecoder 0.1.71 → 0.1.73

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 (94) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +212 -51
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +214 -53
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-CYNqPa6Z.d.ts → index-dbWF1hyW.d.ts} +57 -49
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +214 -53
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-C7Mm4Ykn.d.ts → schema-XcP0dedO.d.ts} +3 -3
  12. package/dist/{search-CVVfuBPZ.d.ts → search-CCffrVJE.d.ts} +4 -4
  13. package/dist/server/index.js +214 -53
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/skills/default/qa.md +53 -14
  16. package/dist/tools/index.d.ts +3 -3
  17. package/dist/tools/index.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/skills/default/qa.md +53 -14
  20. package/web/.next/BUILD_ID +1 -1
  21. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  23. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  24. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  25. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  75. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  83. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  84. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  85. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  86. /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
  87. /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
  88. /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_ssgManifest.js +0 -0
  89. /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
  90. /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
  91. /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_ssgManifest.js +0 -0
  92. /package/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
  93. /package/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
  94. /package/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import 'ai';
2
- import '../schema-C7Mm4Ykn.js';
3
- export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-CYNqPa6Z.js';
4
- import '../search-CVVfuBPZ.js';
2
+ import '../schema-XcP0dedO.js';
3
+ export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-dbWF1hyW.js';
4
+ import '../search-CCffrVJE.js';
5
5
  import 'drizzle-orm/sqlite-core';
6
6
  import 'zod';
@@ -1912,6 +1912,19 @@ import { z as z2 } from "zod";
1912
1912
  import { exec as exec2 } from "child_process";
1913
1913
  import { promisify as promisify2 } from "util";
1914
1914
 
1915
+ // src/utils/tokens.ts
1916
+ var CHARS_PER_TOKEN = 4;
1917
+ var MESSAGE_OVERHEAD_TOKENS = 4;
1918
+ function estimateTokens(text) {
1919
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
1920
+ }
1921
+ function estimateMessageTokens(messages) {
1922
+ return messages.reduce((total, msg) => {
1923
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1924
+ return total + estimateTokens(content) + MESSAGE_OVERHEAD_TOKENS;
1925
+ }, 0);
1926
+ }
1927
+
1915
1928
  // src/utils/truncate.ts
1916
1929
  var MAX_OUTPUT_CHARS = 1e4;
1917
1930
  function truncateOutput(output, maxChars = MAX_OUTPUT_CHARS) {
@@ -5698,9 +5711,6 @@ ${conversationHistory}
5698
5711
  Summary:`;
5699
5712
  }
5700
5713
 
5701
- // src/agent/context.ts
5702
- init_config();
5703
-
5704
5714
  // src/utils/sanitize-messages.ts
5705
5715
  import { modelMessageSchema } from "ai";
5706
5716
  function convertDatesToStrings(value) {
@@ -5837,79 +5847,237 @@ function sanitizeModelMessages(messages) {
5837
5847
  return result;
5838
5848
  }
5839
5849
 
5850
+ // src/agent/model-limits.ts
5851
+ var MODEL_LIMITS = {
5852
+ "anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
5853
+ "anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
5854
+ "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
5855
+ "anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
5856
+ "google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
5857
+ "google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
5858
+ "google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
5859
+ "openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
5860
+ "openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
5861
+ "openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
5862
+ "xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
5863
+ };
5864
+ var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
5865
+ var PREFIX_DEFAULTS = {
5866
+ "anthropic/": { contextWindow: 2e5, rollingTarget: 15e4 },
5867
+ "google/": { contextWindow: 1e6, rollingTarget: 15e4 },
5868
+ "openai/": { contextWindow: 128e3, rollingTarget: 78e3 },
5869
+ "xai/": { contextWindow: 131072, rollingTarget: 8e4 }
5870
+ };
5871
+ function getModelLimits(modelId) {
5872
+ const normalized = modelId.trim().toLowerCase();
5873
+ const exact = MODEL_LIMITS[normalized];
5874
+ if (exact) return exact;
5875
+ for (const [prefix, limits] of Object.entries(PREFIX_DEFAULTS)) {
5876
+ if (normalized.startsWith(prefix)) return limits;
5877
+ }
5878
+ return DEFAULT_LIMITS;
5879
+ }
5880
+ var SUMMARIZATION_MODEL = "google/gemini-3-flash-preview";
5881
+ var SUMMARY_CHUNK_TOKENS = 3e4;
5882
+ var SUMMARY_BUDGET_RATIO = 0.15;
5883
+
5840
5884
  // src/agent/context.ts
5885
+ var TOOL_OUTPUT_TRIM_CHARS = 400;
5886
+ var COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
5887
+ "read_file",
5888
+ "bash",
5889
+ "explore_agent",
5890
+ "code_graph"
5891
+ ]);
5841
5892
  var ContextManager = class {
5842
5893
  sessionId;
5894
+ modelId;
5843
5895
  maxContextChars;
5844
5896
  keepRecentMessages;
5845
5897
  autoSummarize;
5846
- summary = null;
5898
+ summaries = [];
5847
5899
  constructor(options) {
5848
5900
  this.sessionId = options.sessionId;
5901
+ this.modelId = options.modelId;
5849
5902
  this.maxContextChars = options.maxContextChars;
5850
5903
  this.keepRecentMessages = options.keepRecentMessages;
5851
5904
  this.autoSummarize = options.autoSummarize;
5852
5905
  }
5853
5906
  /**
5854
- * Get messages for the current context
5855
- * Returns ModelMessage[] that can be passed directly to streamText/generateText
5856
- *
5857
- * Includes self-repair: if messages from the database have been corrupted
5858
- * (e.g., Date objects in tool outputs from parseDates), they are automatically
5859
- * sanitized to conform to the AI SDK's ModelMessage schema.
5907
+ * Get messages for the current context, applying the three-phase pipeline.
5860
5908
  */
5861
5909
  async getMessages() {
5862
- let modelMessages = await messageQueries.getModelMessages(this.sessionId);
5863
- modelMessages = sanitizeModelMessages(modelMessages);
5864
- const contextSize = calculateContextSize(modelMessages);
5865
- if (this.autoSummarize && contextSize > this.maxContextChars) {
5866
- modelMessages = await this.summarizeContext(modelMessages);
5910
+ let messages = await messageQueries.getModelMessages(this.sessionId);
5911
+ messages = sanitizeModelMessages(messages);
5912
+ messages = this.compactOlderMessages(messages, this.keepRecentMessages);
5913
+ if (this.autoSummarize) {
5914
+ const { rollingTarget } = getModelLimits(this.modelId);
5915
+ const summaryBudget = Math.floor(rollingTarget * SUMMARY_BUDGET_RATIO);
5916
+ messages = await this.chunkSummarize(messages, rollingTarget);
5917
+ await this.rollSummaries(summaryBudget);
5867
5918
  }
5868
- if (this.summary) {
5869
- modelMessages = [
5919
+ if (this.summaries.length > 0) {
5920
+ const summaryContent = this.summaries.join("\n\n---\n\n");
5921
+ messages = [
5870
5922
  {
5871
5923
  role: "system",
5872
5924
  content: `[Previous conversation summary]
5873
- ${this.summary}`
5925
+ ${summaryContent}`
5874
5926
  },
5875
- ...modelMessages
5927
+ ...messages
5876
5928
  ];
5877
5929
  }
5878
- return modelMessages;
5930
+ return messages;
5879
5931
  }
5932
+ // ---------------------------------------------------------------------------
5933
+ // Phase 1 – Compact
5934
+ // ---------------------------------------------------------------------------
5880
5935
  /**
5881
- * Summarize older messages to reduce context size
5936
+ * Strip non-essential content from messages older than the most recent
5937
+ * `recentCount`. Operates in-memory only — does not touch the DB.
5882
5938
  */
5883
- async summarizeContext(messages) {
5884
- if (messages.length <= this.keepRecentMessages) {
5885
- return messages;
5939
+ compactOlderMessages(messages, recentCount) {
5940
+ if (messages.length <= recentCount) return messages;
5941
+ const boundary = messages.length - recentCount;
5942
+ const olderMessages = messages.slice(0, boundary);
5943
+ const recentMessages = messages.slice(boundary);
5944
+ const compacted = [];
5945
+ for (const msg of olderMessages) {
5946
+ const processed = this.compactMessage(msg);
5947
+ if (processed) compacted.push(processed);
5886
5948
  }
5887
- const splitIndex = messages.length - this.keepRecentMessages;
5888
- const oldMessages = messages.slice(0, splitIndex);
5889
- const recentMessages = messages.slice(splitIndex);
5890
- const historyText = oldMessages.map((msg) => {
5949
+ return [...compacted, ...recentMessages];
5950
+ }
5951
+ compactMessage(msg) {
5952
+ if (!Array.isArray(msg.content)) return msg;
5953
+ const parts = [];
5954
+ for (const part of msg.content) {
5955
+ if (part.type === "tool-call" && part.toolName === "todo") continue;
5956
+ if (part.type === "tool-result" && part.toolName === "todo") continue;
5957
+ if (part.type === "reasoning" || part.type === "thinking") continue;
5958
+ if (part.type === "tool-result" && COMPACTABLE_TOOLS.has(part.toolName)) {
5959
+ parts.push(this.trimToolResult(part));
5960
+ continue;
5961
+ }
5962
+ parts.push(part);
5963
+ }
5964
+ if (parts.length === 0) return null;
5965
+ return { ...msg, content: parts };
5966
+ }
5967
+ trimToolResult(part) {
5968
+ const results = Array.isArray(part.result) ? part.result : [part.result];
5969
+ const trimmedResults = results.map((r) => {
5970
+ if (typeof r === "string" && r.length > TOOL_OUTPUT_TRIM_CHARS) {
5971
+ const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
5972
+ return r.slice(0, half) + `
5973
+ ...[trimmed ${r.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
5974
+ ` + r.slice(-half);
5975
+ }
5976
+ if (r && typeof r === "object" && typeof r.text === "string" && r.text.length > TOOL_OUTPUT_TRIM_CHARS) {
5977
+ const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
5978
+ return {
5979
+ ...r,
5980
+ text: r.text.slice(0, half) + `
5981
+ ...[trimmed ${r.text.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
5982
+ ` + r.text.slice(-half)
5983
+ };
5984
+ }
5985
+ return r;
5986
+ });
5987
+ return {
5988
+ ...part,
5989
+ result: Array.isArray(part.result) ? trimmedResults : trimmedResults[0]
5990
+ };
5991
+ }
5992
+ // ---------------------------------------------------------------------------
5993
+ // Phase 2 – Chunk-summarize
5994
+ // ---------------------------------------------------------------------------
5995
+ /**
5996
+ * While estimated tokens exceed `rollingTarget`, peel off the oldest
5997
+ * ~SUMMARY_CHUNK_TOKENS worth of messages, summarize them via the cheap
5998
+ * model, and prepend the summary.
5999
+ */
6000
+ async chunkSummarize(messages, rollingTarget) {
6001
+ let totalTokens = estimateMessageTokens(messages);
6002
+ while (totalTokens > rollingTarget && messages.length > this.keepRecentMessages) {
6003
+ let chunkTokens = 0;
6004
+ let chunkEnd = 0;
6005
+ const maxChunkable = messages.length - this.keepRecentMessages;
6006
+ for (let i = 0; i < maxChunkable; i++) {
6007
+ const msgTokens = this.messageTokens(messages[i]);
6008
+ chunkTokens += msgTokens;
6009
+ chunkEnd = i + 1;
6010
+ if (chunkTokens >= SUMMARY_CHUNK_TOKENS) break;
6011
+ }
6012
+ if (chunkEnd === 0) break;
6013
+ const chunk = messages.slice(0, chunkEnd);
6014
+ const remaining = messages.slice(chunkEnd);
6015
+ const summary = await this.summarizeChunk(chunk);
6016
+ if (summary) {
6017
+ this.summaries.push(summary);
6018
+ console.log(
6019
+ `[Context] Summarized ${chunk.length} messages (~${chunkTokens} tokens) into ${estimateTokens(summary)} tokens`
6020
+ );
6021
+ }
6022
+ messages = remaining;
6023
+ totalTokens = estimateMessageTokens(messages);
6024
+ }
6025
+ return messages;
6026
+ }
6027
+ async summarizeChunk(chunk) {
6028
+ const historyText = chunk.map((msg) => {
5891
6029
  const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
5892
6030
  return `[${msg.role}]: ${content}`;
5893
6031
  }).join("\n\n");
5894
6032
  try {
5895
- const config = getConfig();
5896
- const summaryPrompt = createSummaryPrompt(historyText);
5897
6033
  const result = await generateText2({
5898
- model: resolveModel(config.defaultModel),
5899
- prompt: summaryPrompt
6034
+ model: resolveModel(SUMMARIZATION_MODEL),
6035
+ prompt: createSummaryPrompt(historyText)
5900
6036
  });
5901
- this.summary = result.text;
5902
- console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);
5903
- return recentMessages;
6037
+ return result.text;
5904
6038
  } catch (error) {
5905
- console.error("[Context] Failed to summarize:", error);
5906
- return recentMessages;
6039
+ console.error("[Context] Chunk summarization failed:", error);
6040
+ return null;
5907
6041
  }
5908
6042
  }
6043
+ // ---------------------------------------------------------------------------
6044
+ // Phase 3 – Roll summaries
6045
+ // ---------------------------------------------------------------------------
5909
6046
  /**
5910
- * Add a user message to the context
5911
- * Content can be a string or an array of content parts (for messages with images/files)
6047
+ * If accumulated summaries exceed `budget` tokens, re-summarize them
6048
+ * into a single condensed summary.
5912
6049
  */
6050
+ async rollSummaries(budget) {
6051
+ if (this.summaries.length <= 1) return;
6052
+ const totalSummaryTokens = this.summaries.reduce(
6053
+ (t, s) => t + estimateTokens(s),
6054
+ 0
6055
+ );
6056
+ if (totalSummaryTokens <= budget) return;
6057
+ const combined = this.summaries.join("\n\n---\n\n");
6058
+ try {
6059
+ const result = await generateText2({
6060
+ model: resolveModel(SUMMARIZATION_MODEL),
6061
+ prompt: createSummaryPrompt(combined)
6062
+ });
6063
+ console.log(
6064
+ `[Context] Rolled ${this.summaries.length} summaries (${totalSummaryTokens} tokens) into ${estimateTokens(result.text)} tokens`
6065
+ );
6066
+ this.summaries = [result.text];
6067
+ } catch (error) {
6068
+ console.error("[Context] Summary rolling failed:", error);
6069
+ }
6070
+ }
6071
+ // ---------------------------------------------------------------------------
6072
+ // Helpers
6073
+ // ---------------------------------------------------------------------------
6074
+ messageTokens(msg) {
6075
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
6076
+ return estimateTokens(content) + 4;
6077
+ }
6078
+ // ---------------------------------------------------------------------------
6079
+ // Public API (unchanged)
6080
+ // ---------------------------------------------------------------------------
5913
6081
  async addUserMessage(content) {
5914
6082
  const userMessage = {
5915
6083
  role: "user",
@@ -5917,30 +6085,22 @@ ${this.summary}`
5917
6085
  };
5918
6086
  await messageQueries.create(this.sessionId, userMessage);
5919
6087
  }
5920
- /**
5921
- * Add response messages from AI SDK directly
5922
- * This is the preferred method - use result.response.messages from streamText/generateText
5923
- */
5924
6088
  async addResponseMessages(messages) {
5925
6089
  await messageQueries.addMany(this.sessionId, messages);
5926
6090
  }
5927
- /**
5928
- * Get current context statistics
5929
- */
5930
6091
  async getStats() {
5931
6092
  const messages = await messageQueries.getModelMessages(this.sessionId);
5932
6093
  return {
5933
6094
  messageCount: messages.length,
5934
6095
  contextChars: calculateContextSize(messages),
5935
- hasSummary: this.summary !== null
6096
+ estimatedTokens: estimateMessageTokens(messages),
6097
+ hasSummary: this.summaries.length > 0,
6098
+ summaryCount: this.summaries.length
5936
6099
  };
5937
6100
  }
5938
- /**
5939
- * Clear all messages in the context
5940
- */
5941
6101
  async clear() {
5942
6102
  await messageQueries.deleteBySession(this.sessionId);
5943
- this.summary = null;
6103
+ this.summaries = [];
5944
6104
  }
5945
6105
  };
5946
6106
 
@@ -6026,6 +6186,7 @@ var Agent = class _Agent {
6026
6186
  }
6027
6187
  const context = new ContextManager({
6028
6188
  sessionId: session.id,
6189
+ modelId: session.model || config.defaultModel,
6029
6190
  maxContextChars: config.context?.maxChars || 2e5,
6030
6191
  keepRecentMessages: config.context?.keepRecentMessages || 10,
6031
6192
  autoSummarize: config.context?.autoSummarize ?? true