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.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +212 -51
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +214 -53
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-CYNqPa6Z.d.ts → index-dbWF1hyW.d.ts} +57 -49
- package/dist/index.d.ts +5 -5
- package/dist/index.js +214 -53
- package/dist/index.js.map +1 -1
- package/dist/{schema-C7Mm4Ykn.d.ts → schema-XcP0dedO.d.ts} +3 -3
- package/dist/{search-CVVfuBPZ.d.ts → search-CCffrVJE.d.ts} +4 -4
- package/dist/server/index.js +214 -53
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/qa.md +53 -14
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/qa.md +53 -14
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_ssgManifest.js +0 -0
- /package/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_buildManifest.js +0 -0
- /package/web/.next/static/{CvXz-9ALpAXX-GsCxrOdt → 2xYE9FvjZmf0tU0NF5Jvj}/_clientMiddlewareManifest.json +0 -0
- /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
|
-
|
|
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
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
if (this.autoSummarize
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
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
|
-
${
|
|
7277
|
+
${summaryContent}`
|
|
7226
7278
|
},
|
|
7227
|
-
...
|
|
7279
|
+
...messages
|
|
7228
7280
|
];
|
|
7229
7281
|
}
|
|
7230
|
-
return
|
|
7282
|
+
return messages;
|
|
7231
7283
|
}
|
|
7284
|
+
// ---------------------------------------------------------------------------
|
|
7285
|
+
// Phase 1 – Compact
|
|
7286
|
+
// ---------------------------------------------------------------------------
|
|
7232
7287
|
/**
|
|
7233
|
-
*
|
|
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
|
-
|
|
7236
|
-
if (messages.length <=
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
const
|
|
7240
|
-
const
|
|
7241
|
-
const
|
|
7242
|
-
|
|
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(
|
|
7251
|
-
prompt:
|
|
7386
|
+
model: resolveModel(SUMMARIZATION_MODEL),
|
|
7387
|
+
prompt: createSummaryPrompt(historyText)
|
|
7252
7388
|
});
|
|
7253
|
-
|
|
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]
|
|
7258
|
-
return
|
|
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
|
-
*
|
|
7263
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|