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
package/dist/cli.js CHANGED
@@ -3080,6 +3080,19 @@ import { z as z2 } from "zod";
3080
3080
  import { exec as exec2 } from "child_process";
3081
3081
  import { promisify as promisify2 } from "util";
3082
3082
 
3083
+ // src/utils/tokens.ts
3084
+ var CHARS_PER_TOKEN = 4;
3085
+ var MESSAGE_OVERHEAD_TOKENS = 4;
3086
+ function estimateTokens(text) {
3087
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
3088
+ }
3089
+ function estimateMessageTokens(messages) {
3090
+ return messages.reduce((total, msg) => {
3091
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
3092
+ return total + estimateTokens(content) + MESSAGE_OVERHEAD_TOKENS;
3093
+ }, 0);
3094
+ }
3095
+
3083
3096
  // src/utils/truncate.ts
3084
3097
  var MAX_OUTPUT_CHARS = 1e4;
3085
3098
  function truncateOutput(output, maxChars = MAX_OUTPUT_CHARS) {
@@ -7050,9 +7063,6 @@ ${conversationHistory}
7050
7063
  Summary:`;
7051
7064
  }
7052
7065
 
7053
- // src/agent/context.ts
7054
- init_config();
7055
-
7056
7066
  // src/utils/sanitize-messages.ts
7057
7067
  import { modelMessageSchema } from "ai";
7058
7068
  function convertDatesToStrings(value) {
@@ -7189,79 +7199,237 @@ function sanitizeModelMessages(messages) {
7189
7199
  return result;
7190
7200
  }
7191
7201
 
7202
+ // src/agent/model-limits.ts
7203
+ var MODEL_LIMITS = {
7204
+ "anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
7205
+ "anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
7206
+ "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
7207
+ "anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
7208
+ "google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
7209
+ "google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
7210
+ "google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
7211
+ "openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
7212
+ "openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
7213
+ "openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
7214
+ "xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
7215
+ };
7216
+ var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
7217
+ var PREFIX_DEFAULTS = {
7218
+ "anthropic/": { contextWindow: 2e5, rollingTarget: 15e4 },
7219
+ "google/": { contextWindow: 1e6, rollingTarget: 15e4 },
7220
+ "openai/": { contextWindow: 128e3, rollingTarget: 78e3 },
7221
+ "xai/": { contextWindow: 131072, rollingTarget: 8e4 }
7222
+ };
7223
+ function getModelLimits(modelId) {
7224
+ const normalized = modelId.trim().toLowerCase();
7225
+ const exact = MODEL_LIMITS[normalized];
7226
+ if (exact) return exact;
7227
+ for (const [prefix, limits] of Object.entries(PREFIX_DEFAULTS)) {
7228
+ if (normalized.startsWith(prefix)) return limits;
7229
+ }
7230
+ return DEFAULT_LIMITS;
7231
+ }
7232
+ var SUMMARIZATION_MODEL = "google/gemini-3-flash-preview";
7233
+ var SUMMARY_CHUNK_TOKENS = 3e4;
7234
+ var SUMMARY_BUDGET_RATIO = 0.15;
7235
+
7192
7236
  // src/agent/context.ts
7237
+ var TOOL_OUTPUT_TRIM_CHARS = 400;
7238
+ var COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
7239
+ "read_file",
7240
+ "bash",
7241
+ "explore_agent",
7242
+ "code_graph"
7243
+ ]);
7193
7244
  var ContextManager = class {
7194
7245
  sessionId;
7246
+ modelId;
7195
7247
  maxContextChars;
7196
7248
  keepRecentMessages;
7197
7249
  autoSummarize;
7198
- summary = null;
7250
+ summaries = [];
7199
7251
  constructor(options) {
7200
7252
  this.sessionId = options.sessionId;
7253
+ this.modelId = options.modelId;
7201
7254
  this.maxContextChars = options.maxContextChars;
7202
7255
  this.keepRecentMessages = options.keepRecentMessages;
7203
7256
  this.autoSummarize = options.autoSummarize;
7204
7257
  }
7205
7258
  /**
7206
- * Get messages for the current context
7207
- * Returns ModelMessage[] that can be passed directly to streamText/generateText
7208
- *
7209
- * Includes self-repair: if messages from the database have been corrupted
7210
- * (e.g., Date objects in tool outputs from parseDates), they are automatically
7211
- * sanitized to conform to the AI SDK's ModelMessage schema.
7259
+ * Get messages for the current context, applying the three-phase pipeline.
7212
7260
  */
7213
7261
  async getMessages() {
7214
- let modelMessages = await messageQueries.getModelMessages(this.sessionId);
7215
- modelMessages = sanitizeModelMessages(modelMessages);
7216
- const contextSize = calculateContextSize(modelMessages);
7217
- if (this.autoSummarize && contextSize > this.maxContextChars) {
7218
- modelMessages = await this.summarizeContext(modelMessages);
7219
- }
7220
- if (this.summary) {
7221
- modelMessages = [
7262
+ let messages = await messageQueries.getModelMessages(this.sessionId);
7263
+ messages = sanitizeModelMessages(messages);
7264
+ messages = this.compactOlderMessages(messages, this.keepRecentMessages);
7265
+ if (this.autoSummarize) {
7266
+ const { rollingTarget } = getModelLimits(this.modelId);
7267
+ const summaryBudget = Math.floor(rollingTarget * SUMMARY_BUDGET_RATIO);
7268
+ messages = await this.chunkSummarize(messages, rollingTarget);
7269
+ await this.rollSummaries(summaryBudget);
7270
+ }
7271
+ if (this.summaries.length > 0) {
7272
+ const summaryContent = this.summaries.join("\n\n---\n\n");
7273
+ messages = [
7222
7274
  {
7223
7275
  role: "system",
7224
7276
  content: `[Previous conversation summary]
7225
- ${this.summary}`
7277
+ ${summaryContent}`
7226
7278
  },
7227
- ...modelMessages
7279
+ ...messages
7228
7280
  ];
7229
7281
  }
7230
- return modelMessages;
7282
+ return messages;
7231
7283
  }
7284
+ // ---------------------------------------------------------------------------
7285
+ // Phase 1 – Compact
7286
+ // ---------------------------------------------------------------------------
7232
7287
  /**
7233
- * Summarize older messages to reduce context size
7288
+ * Strip non-essential content from messages older than the most recent
7289
+ * `recentCount`. Operates in-memory only — does not touch the DB.
7234
7290
  */
7235
- async summarizeContext(messages) {
7236
- if (messages.length <= this.keepRecentMessages) {
7237
- return messages;
7238
- }
7239
- const splitIndex = messages.length - this.keepRecentMessages;
7240
- const oldMessages = messages.slice(0, splitIndex);
7241
- const recentMessages = messages.slice(splitIndex);
7242
- const historyText = oldMessages.map((msg) => {
7291
+ compactOlderMessages(messages, recentCount) {
7292
+ if (messages.length <= recentCount) return messages;
7293
+ const boundary = messages.length - recentCount;
7294
+ const olderMessages = messages.slice(0, boundary);
7295
+ const recentMessages = messages.slice(boundary);
7296
+ const compacted = [];
7297
+ for (const msg of olderMessages) {
7298
+ const processed = this.compactMessage(msg);
7299
+ if (processed) compacted.push(processed);
7300
+ }
7301
+ return [...compacted, ...recentMessages];
7302
+ }
7303
+ compactMessage(msg) {
7304
+ if (!Array.isArray(msg.content)) return msg;
7305
+ const parts = [];
7306
+ for (const part of msg.content) {
7307
+ if (part.type === "tool-call" && part.toolName === "todo") continue;
7308
+ if (part.type === "tool-result" && part.toolName === "todo") continue;
7309
+ if (part.type === "reasoning" || part.type === "thinking") continue;
7310
+ if (part.type === "tool-result" && COMPACTABLE_TOOLS.has(part.toolName)) {
7311
+ parts.push(this.trimToolResult(part));
7312
+ continue;
7313
+ }
7314
+ parts.push(part);
7315
+ }
7316
+ if (parts.length === 0) return null;
7317
+ return { ...msg, content: parts };
7318
+ }
7319
+ trimToolResult(part) {
7320
+ const results = Array.isArray(part.result) ? part.result : [part.result];
7321
+ const trimmedResults = results.map((r) => {
7322
+ if (typeof r === "string" && r.length > TOOL_OUTPUT_TRIM_CHARS) {
7323
+ const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
7324
+ return r.slice(0, half) + `
7325
+ ...[trimmed ${r.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
7326
+ ` + r.slice(-half);
7327
+ }
7328
+ if (r && typeof r === "object" && typeof r.text === "string" && r.text.length > TOOL_OUTPUT_TRIM_CHARS) {
7329
+ const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
7330
+ return {
7331
+ ...r,
7332
+ text: r.text.slice(0, half) + `
7333
+ ...[trimmed ${r.text.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
7334
+ ` + r.text.slice(-half)
7335
+ };
7336
+ }
7337
+ return r;
7338
+ });
7339
+ return {
7340
+ ...part,
7341
+ result: Array.isArray(part.result) ? trimmedResults : trimmedResults[0]
7342
+ };
7343
+ }
7344
+ // ---------------------------------------------------------------------------
7345
+ // Phase 2 – Chunk-summarize
7346
+ // ---------------------------------------------------------------------------
7347
+ /**
7348
+ * While estimated tokens exceed `rollingTarget`, peel off the oldest
7349
+ * ~SUMMARY_CHUNK_TOKENS worth of messages, summarize them via the cheap
7350
+ * model, and prepend the summary.
7351
+ */
7352
+ async chunkSummarize(messages, rollingTarget) {
7353
+ let totalTokens = estimateMessageTokens(messages);
7354
+ while (totalTokens > rollingTarget && messages.length > this.keepRecentMessages) {
7355
+ let chunkTokens = 0;
7356
+ let chunkEnd = 0;
7357
+ const maxChunkable = messages.length - this.keepRecentMessages;
7358
+ for (let i = 0; i < maxChunkable; i++) {
7359
+ const msgTokens = this.messageTokens(messages[i]);
7360
+ chunkTokens += msgTokens;
7361
+ chunkEnd = i + 1;
7362
+ if (chunkTokens >= SUMMARY_CHUNK_TOKENS) break;
7363
+ }
7364
+ if (chunkEnd === 0) break;
7365
+ const chunk = messages.slice(0, chunkEnd);
7366
+ const remaining = messages.slice(chunkEnd);
7367
+ const summary = await this.summarizeChunk(chunk);
7368
+ if (summary) {
7369
+ this.summaries.push(summary);
7370
+ console.log(
7371
+ `[Context] Summarized ${chunk.length} messages (~${chunkTokens} tokens) into ${estimateTokens(summary)} tokens`
7372
+ );
7373
+ }
7374
+ messages = remaining;
7375
+ totalTokens = estimateMessageTokens(messages);
7376
+ }
7377
+ return messages;
7378
+ }
7379
+ async summarizeChunk(chunk) {
7380
+ const historyText = chunk.map((msg) => {
7243
7381
  const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
7244
7382
  return `[${msg.role}]: ${content}`;
7245
7383
  }).join("\n\n");
7246
7384
  try {
7247
- const config = getConfig();
7248
- const summaryPrompt = createSummaryPrompt(historyText);
7249
7385
  const result = await generateText2({
7250
- model: resolveModel(config.defaultModel),
7251
- prompt: summaryPrompt
7386
+ model: resolveModel(SUMMARIZATION_MODEL),
7387
+ prompt: createSummaryPrompt(historyText)
7252
7388
  });
7253
- this.summary = result.text;
7254
- console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);
7255
- return recentMessages;
7389
+ return result.text;
7256
7390
  } catch (error) {
7257
- console.error("[Context] Failed to summarize:", error);
7258
- return recentMessages;
7391
+ console.error("[Context] Chunk summarization failed:", error);
7392
+ return null;
7259
7393
  }
7260
7394
  }
7395
+ // ---------------------------------------------------------------------------
7396
+ // Phase 3 – Roll summaries
7397
+ // ---------------------------------------------------------------------------
7261
7398
  /**
7262
- * Add a user message to the context
7263
- * Content can be a string or an array of content parts (for messages with images/files)
7399
+ * If accumulated summaries exceed `budget` tokens, re-summarize them
7400
+ * into a single condensed summary.
7264
7401
  */
7402
+ async rollSummaries(budget) {
7403
+ if (this.summaries.length <= 1) return;
7404
+ const totalSummaryTokens = this.summaries.reduce(
7405
+ (t, s) => t + estimateTokens(s),
7406
+ 0
7407
+ );
7408
+ if (totalSummaryTokens <= budget) return;
7409
+ const combined = this.summaries.join("\n\n---\n\n");
7410
+ try {
7411
+ const result = await generateText2({
7412
+ model: resolveModel(SUMMARIZATION_MODEL),
7413
+ prompt: createSummaryPrompt(combined)
7414
+ });
7415
+ console.log(
7416
+ `[Context] Rolled ${this.summaries.length} summaries (${totalSummaryTokens} tokens) into ${estimateTokens(result.text)} tokens`
7417
+ );
7418
+ this.summaries = [result.text];
7419
+ } catch (error) {
7420
+ console.error("[Context] Summary rolling failed:", error);
7421
+ }
7422
+ }
7423
+ // ---------------------------------------------------------------------------
7424
+ // Helpers
7425
+ // ---------------------------------------------------------------------------
7426
+ messageTokens(msg) {
7427
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
7428
+ return estimateTokens(content) + 4;
7429
+ }
7430
+ // ---------------------------------------------------------------------------
7431
+ // Public API (unchanged)
7432
+ // ---------------------------------------------------------------------------
7265
7433
  async addUserMessage(content) {
7266
7434
  const userMessage = {
7267
7435
  role: "user",
@@ -7269,30 +7437,22 @@ ${this.summary}`
7269
7437
  };
7270
7438
  await messageQueries.create(this.sessionId, userMessage);
7271
7439
  }
7272
- /**
7273
- * Add response messages from AI SDK directly
7274
- * This is the preferred method - use result.response.messages from streamText/generateText
7275
- */
7276
7440
  async addResponseMessages(messages) {
7277
7441
  await messageQueries.addMany(this.sessionId, messages);
7278
7442
  }
7279
- /**
7280
- * Get current context statistics
7281
- */
7282
7443
  async getStats() {
7283
7444
  const messages = await messageQueries.getModelMessages(this.sessionId);
7284
7445
  return {
7285
7446
  messageCount: messages.length,
7286
7447
  contextChars: calculateContextSize(messages),
7287
- hasSummary: this.summary !== null
7448
+ estimatedTokens: estimateMessageTokens(messages),
7449
+ hasSummary: this.summaries.length > 0,
7450
+ summaryCount: this.summaries.length
7288
7451
  };
7289
7452
  }
7290
- /**
7291
- * Clear all messages in the context
7292
- */
7293
7453
  async clear() {
7294
7454
  await messageQueries.deleteBySession(this.sessionId);
7295
- this.summary = null;
7455
+ this.summaries = [];
7296
7456
  }
7297
7457
  };
7298
7458
 
@@ -7360,6 +7520,7 @@ var Agent = class _Agent {
7360
7520
  }
7361
7521
  const context = new ContextManager({
7362
7522
  sessionId: session.id,
7523
+ modelId: session.model || config.defaultModel,
7363
7524
  maxContextChars: config.context?.maxChars || 2e5,
7364
7525
  keepRecentMessages: config.context?.keepRecentMessages || 10,
7365
7526
  autoSummarize: config.context?.autoSummarize ?? true