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
|
@@ -85,7 +85,7 @@ declare const sessions: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
85
85
|
tableName: "sessions";
|
|
86
86
|
dataType: "string";
|
|
87
87
|
columnType: "SQLiteText";
|
|
88
|
-
data: "
|
|
88
|
+
data: "error" | "completed" | "active" | "waiting";
|
|
89
89
|
driverParam: string;
|
|
90
90
|
notNull: true;
|
|
91
91
|
hasDefault: true;
|
|
@@ -391,7 +391,7 @@ declare const toolExecutions: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
391
391
|
tableName: "tool_executions";
|
|
392
392
|
dataType: "string";
|
|
393
393
|
columnType: "SQLiteText";
|
|
394
|
-
data: "
|
|
394
|
+
data: "error" | "completed" | "pending" | "approved" | "rejected";
|
|
395
395
|
driverParam: string;
|
|
396
396
|
notNull: true;
|
|
397
397
|
hasDefault: true;
|
|
@@ -814,7 +814,7 @@ declare const terminals: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
814
814
|
tableName: "terminals";
|
|
815
815
|
dataType: "string";
|
|
816
816
|
columnType: "SQLiteText";
|
|
817
|
-
data: "
|
|
817
|
+
data: "error" | "running" | "stopped";
|
|
818
818
|
driverParam: string;
|
|
819
819
|
notNull: true;
|
|
820
820
|
hasDefault: true;
|
|
@@ -15,12 +15,12 @@ interface BashToolOptions {
|
|
|
15
15
|
}
|
|
16
16
|
declare function createBashTool(options: BashToolOptions): ai.Tool<{
|
|
17
17
|
background: boolean;
|
|
18
|
-
id?: string | undefined;
|
|
19
18
|
input?: string | undefined;
|
|
19
|
+
id?: string | undefined;
|
|
20
20
|
command?: string | undefined;
|
|
21
21
|
kill?: boolean | undefined;
|
|
22
22
|
tail?: number | undefined;
|
|
23
|
-
key?: "Enter" | "Escape" | "Up" | "Down" | "Left" | "Right" | "Tab" | "C-c" | "C-d" | "
|
|
23
|
+
key?: "y" | "Enter" | "Escape" | "Up" | "Down" | "Left" | "Right" | "Tab" | "C-c" | "C-d" | "n" | undefined;
|
|
24
24
|
}, {
|
|
25
25
|
success: boolean;
|
|
26
26
|
id: string;
|
|
@@ -66,7 +66,7 @@ declare function createBashTool(options: BashToolOptions): ai.Tool<{
|
|
|
66
66
|
id: string;
|
|
67
67
|
output: string;
|
|
68
68
|
exitCode: number;
|
|
69
|
-
status: "
|
|
69
|
+
status: "error" | "completed" | "running" | "stopped";
|
|
70
70
|
message?: undefined;
|
|
71
71
|
error?: undefined;
|
|
72
72
|
} | {
|
|
@@ -218,8 +218,8 @@ interface SearchToolOptions {
|
|
|
218
218
|
* Progress is streamed back to the UI so users can see exploration happening.
|
|
219
219
|
*/
|
|
220
220
|
declare function createSearchTool(options: SearchToolOptions): ai.Tool<{
|
|
221
|
-
query: string;
|
|
222
221
|
context: string;
|
|
222
|
+
query: string;
|
|
223
223
|
}, {
|
|
224
224
|
success: boolean;
|
|
225
225
|
error: string;
|
package/dist/server/index.js
CHANGED
|
@@ -2302,6 +2302,19 @@ import { z as z2 } from "zod";
|
|
|
2302
2302
|
import { exec as exec2 } from "child_process";
|
|
2303
2303
|
import { promisify as promisify2 } from "util";
|
|
2304
2304
|
|
|
2305
|
+
// src/utils/tokens.ts
|
|
2306
|
+
var CHARS_PER_TOKEN = 4;
|
|
2307
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
2308
|
+
function estimateTokens(text) {
|
|
2309
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
2310
|
+
}
|
|
2311
|
+
function estimateMessageTokens(messages) {
|
|
2312
|
+
return messages.reduce((total, msg) => {
|
|
2313
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
2314
|
+
return total + estimateTokens(content) + MESSAGE_OVERHEAD_TOKENS;
|
|
2315
|
+
}, 0);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2305
2318
|
// src/utils/truncate.ts
|
|
2306
2319
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
2307
2320
|
function truncateOutput(output, maxChars = MAX_OUTPUT_CHARS) {
|
|
@@ -6272,9 +6285,6 @@ ${conversationHistory}
|
|
|
6272
6285
|
Summary:`;
|
|
6273
6286
|
}
|
|
6274
6287
|
|
|
6275
|
-
// src/agent/context.ts
|
|
6276
|
-
init_config();
|
|
6277
|
-
|
|
6278
6288
|
// src/utils/sanitize-messages.ts
|
|
6279
6289
|
import { modelMessageSchema } from "ai";
|
|
6280
6290
|
function convertDatesToStrings(value) {
|
|
@@ -6411,79 +6421,237 @@ function sanitizeModelMessages(messages) {
|
|
|
6411
6421
|
return result;
|
|
6412
6422
|
}
|
|
6413
6423
|
|
|
6424
|
+
// src/agent/model-limits.ts
|
|
6425
|
+
var MODEL_LIMITS = {
|
|
6426
|
+
"anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6427
|
+
"anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6428
|
+
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6429
|
+
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6430
|
+
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6431
|
+
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6432
|
+
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6433
|
+
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
6434
|
+
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6435
|
+
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6436
|
+
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
6437
|
+
};
|
|
6438
|
+
var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
6439
|
+
var PREFIX_DEFAULTS = {
|
|
6440
|
+
"anthropic/": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6441
|
+
"google/": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6442
|
+
"openai/": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
6443
|
+
"xai/": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
6444
|
+
};
|
|
6445
|
+
function getModelLimits(modelId) {
|
|
6446
|
+
const normalized = modelId.trim().toLowerCase();
|
|
6447
|
+
const exact = MODEL_LIMITS[normalized];
|
|
6448
|
+
if (exact) return exact;
|
|
6449
|
+
for (const [prefix, limits] of Object.entries(PREFIX_DEFAULTS)) {
|
|
6450
|
+
if (normalized.startsWith(prefix)) return limits;
|
|
6451
|
+
}
|
|
6452
|
+
return DEFAULT_LIMITS;
|
|
6453
|
+
}
|
|
6454
|
+
var SUMMARIZATION_MODEL = "google/gemini-3-flash-preview";
|
|
6455
|
+
var SUMMARY_CHUNK_TOKENS = 3e4;
|
|
6456
|
+
var SUMMARY_BUDGET_RATIO = 0.15;
|
|
6457
|
+
|
|
6414
6458
|
// src/agent/context.ts
|
|
6459
|
+
var TOOL_OUTPUT_TRIM_CHARS = 400;
|
|
6460
|
+
var COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
6461
|
+
"read_file",
|
|
6462
|
+
"bash",
|
|
6463
|
+
"explore_agent",
|
|
6464
|
+
"code_graph"
|
|
6465
|
+
]);
|
|
6415
6466
|
var ContextManager = class {
|
|
6416
6467
|
sessionId;
|
|
6468
|
+
modelId;
|
|
6417
6469
|
maxContextChars;
|
|
6418
6470
|
keepRecentMessages;
|
|
6419
6471
|
autoSummarize;
|
|
6420
|
-
|
|
6472
|
+
summaries = [];
|
|
6421
6473
|
constructor(options) {
|
|
6422
6474
|
this.sessionId = options.sessionId;
|
|
6475
|
+
this.modelId = options.modelId;
|
|
6423
6476
|
this.maxContextChars = options.maxContextChars;
|
|
6424
6477
|
this.keepRecentMessages = options.keepRecentMessages;
|
|
6425
6478
|
this.autoSummarize = options.autoSummarize;
|
|
6426
6479
|
}
|
|
6427
6480
|
/**
|
|
6428
|
-
* Get messages for the current context
|
|
6429
|
-
* Returns ModelMessage[] that can be passed directly to streamText/generateText
|
|
6430
|
-
*
|
|
6431
|
-
* Includes self-repair: if messages from the database have been corrupted
|
|
6432
|
-
* (e.g., Date objects in tool outputs from parseDates), they are automatically
|
|
6433
|
-
* sanitized to conform to the AI SDK's ModelMessage schema.
|
|
6481
|
+
* Get messages for the current context, applying the three-phase pipeline.
|
|
6434
6482
|
*/
|
|
6435
6483
|
async getMessages() {
|
|
6436
|
-
let
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
if (this.autoSummarize
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6484
|
+
let messages = await messageQueries.getModelMessages(this.sessionId);
|
|
6485
|
+
messages = sanitizeModelMessages(messages);
|
|
6486
|
+
messages = this.compactOlderMessages(messages, this.keepRecentMessages);
|
|
6487
|
+
if (this.autoSummarize) {
|
|
6488
|
+
const { rollingTarget } = getModelLimits(this.modelId);
|
|
6489
|
+
const summaryBudget = Math.floor(rollingTarget * SUMMARY_BUDGET_RATIO);
|
|
6490
|
+
messages = await this.chunkSummarize(messages, rollingTarget);
|
|
6491
|
+
await this.rollSummaries(summaryBudget);
|
|
6492
|
+
}
|
|
6493
|
+
if (this.summaries.length > 0) {
|
|
6494
|
+
const summaryContent = this.summaries.join("\n\n---\n\n");
|
|
6495
|
+
messages = [
|
|
6444
6496
|
{
|
|
6445
6497
|
role: "system",
|
|
6446
6498
|
content: `[Previous conversation summary]
|
|
6447
|
-
${
|
|
6499
|
+
${summaryContent}`
|
|
6448
6500
|
},
|
|
6449
|
-
...
|
|
6501
|
+
...messages
|
|
6450
6502
|
];
|
|
6451
6503
|
}
|
|
6452
|
-
return
|
|
6504
|
+
return messages;
|
|
6453
6505
|
}
|
|
6506
|
+
// ---------------------------------------------------------------------------
|
|
6507
|
+
// Phase 1 – Compact
|
|
6508
|
+
// ---------------------------------------------------------------------------
|
|
6454
6509
|
/**
|
|
6455
|
-
*
|
|
6510
|
+
* Strip non-essential content from messages older than the most recent
|
|
6511
|
+
* `recentCount`. Operates in-memory only — does not touch the DB.
|
|
6456
6512
|
*/
|
|
6457
|
-
|
|
6458
|
-
if (messages.length <=
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
const
|
|
6462
|
-
const
|
|
6463
|
-
const
|
|
6464
|
-
|
|
6513
|
+
compactOlderMessages(messages, recentCount) {
|
|
6514
|
+
if (messages.length <= recentCount) return messages;
|
|
6515
|
+
const boundary = messages.length - recentCount;
|
|
6516
|
+
const olderMessages = messages.slice(0, boundary);
|
|
6517
|
+
const recentMessages = messages.slice(boundary);
|
|
6518
|
+
const compacted = [];
|
|
6519
|
+
for (const msg of olderMessages) {
|
|
6520
|
+
const processed = this.compactMessage(msg);
|
|
6521
|
+
if (processed) compacted.push(processed);
|
|
6522
|
+
}
|
|
6523
|
+
return [...compacted, ...recentMessages];
|
|
6524
|
+
}
|
|
6525
|
+
compactMessage(msg) {
|
|
6526
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
6527
|
+
const parts = [];
|
|
6528
|
+
for (const part of msg.content) {
|
|
6529
|
+
if (part.type === "tool-call" && part.toolName === "todo") continue;
|
|
6530
|
+
if (part.type === "tool-result" && part.toolName === "todo") continue;
|
|
6531
|
+
if (part.type === "reasoning" || part.type === "thinking") continue;
|
|
6532
|
+
if (part.type === "tool-result" && COMPACTABLE_TOOLS.has(part.toolName)) {
|
|
6533
|
+
parts.push(this.trimToolResult(part));
|
|
6534
|
+
continue;
|
|
6535
|
+
}
|
|
6536
|
+
parts.push(part);
|
|
6537
|
+
}
|
|
6538
|
+
if (parts.length === 0) return null;
|
|
6539
|
+
return { ...msg, content: parts };
|
|
6540
|
+
}
|
|
6541
|
+
trimToolResult(part) {
|
|
6542
|
+
const results = Array.isArray(part.result) ? part.result : [part.result];
|
|
6543
|
+
const trimmedResults = results.map((r) => {
|
|
6544
|
+
if (typeof r === "string" && r.length > TOOL_OUTPUT_TRIM_CHARS) {
|
|
6545
|
+
const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
|
|
6546
|
+
return r.slice(0, half) + `
|
|
6547
|
+
...[trimmed ${r.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
|
|
6548
|
+
` + r.slice(-half);
|
|
6549
|
+
}
|
|
6550
|
+
if (r && typeof r === "object" && typeof r.text === "string" && r.text.length > TOOL_OUTPUT_TRIM_CHARS) {
|
|
6551
|
+
const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
|
|
6552
|
+
return {
|
|
6553
|
+
...r,
|
|
6554
|
+
text: r.text.slice(0, half) + `
|
|
6555
|
+
...[trimmed ${r.text.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
|
|
6556
|
+
` + r.text.slice(-half)
|
|
6557
|
+
};
|
|
6558
|
+
}
|
|
6559
|
+
return r;
|
|
6560
|
+
});
|
|
6561
|
+
return {
|
|
6562
|
+
...part,
|
|
6563
|
+
result: Array.isArray(part.result) ? trimmedResults : trimmedResults[0]
|
|
6564
|
+
};
|
|
6565
|
+
}
|
|
6566
|
+
// ---------------------------------------------------------------------------
|
|
6567
|
+
// Phase 2 – Chunk-summarize
|
|
6568
|
+
// ---------------------------------------------------------------------------
|
|
6569
|
+
/**
|
|
6570
|
+
* While estimated tokens exceed `rollingTarget`, peel off the oldest
|
|
6571
|
+
* ~SUMMARY_CHUNK_TOKENS worth of messages, summarize them via the cheap
|
|
6572
|
+
* model, and prepend the summary.
|
|
6573
|
+
*/
|
|
6574
|
+
async chunkSummarize(messages, rollingTarget) {
|
|
6575
|
+
let totalTokens = estimateMessageTokens(messages);
|
|
6576
|
+
while (totalTokens > rollingTarget && messages.length > this.keepRecentMessages) {
|
|
6577
|
+
let chunkTokens = 0;
|
|
6578
|
+
let chunkEnd = 0;
|
|
6579
|
+
const maxChunkable = messages.length - this.keepRecentMessages;
|
|
6580
|
+
for (let i = 0; i < maxChunkable; i++) {
|
|
6581
|
+
const msgTokens = this.messageTokens(messages[i]);
|
|
6582
|
+
chunkTokens += msgTokens;
|
|
6583
|
+
chunkEnd = i + 1;
|
|
6584
|
+
if (chunkTokens >= SUMMARY_CHUNK_TOKENS) break;
|
|
6585
|
+
}
|
|
6586
|
+
if (chunkEnd === 0) break;
|
|
6587
|
+
const chunk = messages.slice(0, chunkEnd);
|
|
6588
|
+
const remaining = messages.slice(chunkEnd);
|
|
6589
|
+
const summary = await this.summarizeChunk(chunk);
|
|
6590
|
+
if (summary) {
|
|
6591
|
+
this.summaries.push(summary);
|
|
6592
|
+
console.log(
|
|
6593
|
+
`[Context] Summarized ${chunk.length} messages (~${chunkTokens} tokens) into ${estimateTokens(summary)} tokens`
|
|
6594
|
+
);
|
|
6595
|
+
}
|
|
6596
|
+
messages = remaining;
|
|
6597
|
+
totalTokens = estimateMessageTokens(messages);
|
|
6598
|
+
}
|
|
6599
|
+
return messages;
|
|
6600
|
+
}
|
|
6601
|
+
async summarizeChunk(chunk) {
|
|
6602
|
+
const historyText = chunk.map((msg) => {
|
|
6465
6603
|
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
6466
6604
|
return `[${msg.role}]: ${content}`;
|
|
6467
6605
|
}).join("\n\n");
|
|
6468
6606
|
try {
|
|
6469
|
-
const config = getConfig();
|
|
6470
|
-
const summaryPrompt = createSummaryPrompt(historyText);
|
|
6471
6607
|
const result = await generateText2({
|
|
6472
|
-
model: resolveModel(
|
|
6473
|
-
prompt:
|
|
6608
|
+
model: resolveModel(SUMMARIZATION_MODEL),
|
|
6609
|
+
prompt: createSummaryPrompt(historyText)
|
|
6474
6610
|
});
|
|
6475
|
-
|
|
6476
|
-
console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);
|
|
6477
|
-
return recentMessages;
|
|
6611
|
+
return result.text;
|
|
6478
6612
|
} catch (error) {
|
|
6479
|
-
console.error("[Context]
|
|
6480
|
-
return
|
|
6613
|
+
console.error("[Context] Chunk summarization failed:", error);
|
|
6614
|
+
return null;
|
|
6481
6615
|
}
|
|
6482
6616
|
}
|
|
6617
|
+
// ---------------------------------------------------------------------------
|
|
6618
|
+
// Phase 3 – Roll summaries
|
|
6619
|
+
// ---------------------------------------------------------------------------
|
|
6483
6620
|
/**
|
|
6484
|
-
*
|
|
6485
|
-
*
|
|
6621
|
+
* If accumulated summaries exceed `budget` tokens, re-summarize them
|
|
6622
|
+
* into a single condensed summary.
|
|
6486
6623
|
*/
|
|
6624
|
+
async rollSummaries(budget) {
|
|
6625
|
+
if (this.summaries.length <= 1) return;
|
|
6626
|
+
const totalSummaryTokens = this.summaries.reduce(
|
|
6627
|
+
(t, s) => t + estimateTokens(s),
|
|
6628
|
+
0
|
|
6629
|
+
);
|
|
6630
|
+
if (totalSummaryTokens <= budget) return;
|
|
6631
|
+
const combined = this.summaries.join("\n\n---\n\n");
|
|
6632
|
+
try {
|
|
6633
|
+
const result = await generateText2({
|
|
6634
|
+
model: resolveModel(SUMMARIZATION_MODEL),
|
|
6635
|
+
prompt: createSummaryPrompt(combined)
|
|
6636
|
+
});
|
|
6637
|
+
console.log(
|
|
6638
|
+
`[Context] Rolled ${this.summaries.length} summaries (${totalSummaryTokens} tokens) into ${estimateTokens(result.text)} tokens`
|
|
6639
|
+
);
|
|
6640
|
+
this.summaries = [result.text];
|
|
6641
|
+
} catch (error) {
|
|
6642
|
+
console.error("[Context] Summary rolling failed:", error);
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
// ---------------------------------------------------------------------------
|
|
6646
|
+
// Helpers
|
|
6647
|
+
// ---------------------------------------------------------------------------
|
|
6648
|
+
messageTokens(msg) {
|
|
6649
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
6650
|
+
return estimateTokens(content) + 4;
|
|
6651
|
+
}
|
|
6652
|
+
// ---------------------------------------------------------------------------
|
|
6653
|
+
// Public API (unchanged)
|
|
6654
|
+
// ---------------------------------------------------------------------------
|
|
6487
6655
|
async addUserMessage(content) {
|
|
6488
6656
|
const userMessage = {
|
|
6489
6657
|
role: "user",
|
|
@@ -6491,30 +6659,22 @@ ${this.summary}`
|
|
|
6491
6659
|
};
|
|
6492
6660
|
await messageQueries.create(this.sessionId, userMessage);
|
|
6493
6661
|
}
|
|
6494
|
-
/**
|
|
6495
|
-
* Add response messages from AI SDK directly
|
|
6496
|
-
* This is the preferred method - use result.response.messages from streamText/generateText
|
|
6497
|
-
*/
|
|
6498
6662
|
async addResponseMessages(messages) {
|
|
6499
6663
|
await messageQueries.addMany(this.sessionId, messages);
|
|
6500
6664
|
}
|
|
6501
|
-
/**
|
|
6502
|
-
* Get current context statistics
|
|
6503
|
-
*/
|
|
6504
6665
|
async getStats() {
|
|
6505
6666
|
const messages = await messageQueries.getModelMessages(this.sessionId);
|
|
6506
6667
|
return {
|
|
6507
6668
|
messageCount: messages.length,
|
|
6508
6669
|
contextChars: calculateContextSize(messages),
|
|
6509
|
-
|
|
6670
|
+
estimatedTokens: estimateMessageTokens(messages),
|
|
6671
|
+
hasSummary: this.summaries.length > 0,
|
|
6672
|
+
summaryCount: this.summaries.length
|
|
6510
6673
|
};
|
|
6511
6674
|
}
|
|
6512
|
-
/**
|
|
6513
|
-
* Clear all messages in the context
|
|
6514
|
-
*/
|
|
6515
6675
|
async clear() {
|
|
6516
6676
|
await messageQueries.deleteBySession(this.sessionId);
|
|
6517
|
-
this.
|
|
6677
|
+
this.summaries = [];
|
|
6518
6678
|
}
|
|
6519
6679
|
};
|
|
6520
6680
|
|
|
@@ -6582,6 +6742,7 @@ var Agent = class _Agent {
|
|
|
6582
6742
|
}
|
|
6583
6743
|
const context = new ContextManager({
|
|
6584
6744
|
sessionId: session.id,
|
|
6745
|
+
modelId: session.model || config.defaultModel,
|
|
6585
6746
|
maxContextChars: config.context?.maxChars || 2e5,
|
|
6586
6747
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
6587
6748
|
autoSummarize: config.context?.autoSummarize ?? true
|