sparkecoder 0.1.72 → 0.1.74
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 +1 -1
- package/dist/agent/index.js +292 -57
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +294 -59
- package/dist/cli.js.map +1 -1
- package/dist/{index-Dm6wGcYv.d.ts → index-DT1l57s0.d.ts} +38 -24
- package/dist/index.d.ts +2 -2
- package/dist/index.js +294 -59
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +294 -59
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/qa.md +15 -11
- package/dist/tools/index.js +16 -6
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/qa.md +15 -11
- 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/{static/xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{xXtjibFdZ768DUoqjM6zS → static/_Xl7dVMD-6ghn4EberTUE}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{xXtjibFdZ768DUoqjM6zS → static/_Xl7dVMD-6ghn4EberTUE}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{xXtjibFdZ768DUoqjM6zS → static/_Xl7dVMD-6ghn4EberTUE}/_ssgManifest.js +0 -0
- /package/web/.next/static/{xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_buildManifest.js +0 -0
- /package/web/.next/static/{xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{xXtjibFdZ768DUoqjM6zS → _Xl7dVMD-6ghn4EberTUE}/_ssgManifest.js +0 -0
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'ai';
|
|
2
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-
|
|
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-DT1l57s0.js';
|
|
4
4
|
import '../search-CCffrVJE.js';
|
|
5
5
|
import 'drizzle-orm/sqlite-core';
|
|
6
6
|
import 'zod';
|
package/dist/agent/index.js
CHANGED
|
@@ -1794,9 +1794,8 @@ function createRemoteModel(modelId, config) {
|
|
|
1794
1794
|
});
|
|
1795
1795
|
if (!res.ok) {
|
|
1796
1796
|
const err = await res.json().catch(() => ({}));
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
);
|
|
1797
|
+
const detail = formatRemoteError(res.status, modelId, err);
|
|
1798
|
+
throw new Error(detail);
|
|
1800
1799
|
}
|
|
1801
1800
|
const result = await res.json();
|
|
1802
1801
|
return deserializeValue(result);
|
|
@@ -1813,9 +1812,8 @@ function createRemoteModel(modelId, config) {
|
|
|
1813
1812
|
});
|
|
1814
1813
|
if (!res.ok) {
|
|
1815
1814
|
const err = await res.json().catch(() => ({}));
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
);
|
|
1815
|
+
const detail = formatRemoteError(res.status, modelId, err);
|
|
1816
|
+
throw new Error(detail);
|
|
1819
1817
|
}
|
|
1820
1818
|
const reader = res.body.getReader();
|
|
1821
1819
|
const decoder = new TextDecoder();
|
|
@@ -1873,6 +1871,18 @@ function createRemoteModel(modelId, config) {
|
|
|
1873
1871
|
}
|
|
1874
1872
|
};
|
|
1875
1873
|
}
|
|
1874
|
+
function formatRemoteError(status, modelId, body) {
|
|
1875
|
+
const parts = [`Remote inference failed (${status}) for ${modelId}`];
|
|
1876
|
+
if (body.error) parts.push(body.error);
|
|
1877
|
+
if (body.details) {
|
|
1878
|
+
const d = body.details;
|
|
1879
|
+
if (d.type) parts.push(`type=${d.type}`);
|
|
1880
|
+
if (d.statusCode && d.statusCode !== status) parts.push(`upstream=${d.statusCode}`);
|
|
1881
|
+
if (d.cause) parts.push(`cause: ${d.cause}`);
|
|
1882
|
+
if (d.orderWarnings?.length) parts.push(`prompt issues: ${d.orderWarnings.join("; ")}`);
|
|
1883
|
+
}
|
|
1884
|
+
return parts.join(" \u2014 ");
|
|
1885
|
+
}
|
|
1876
1886
|
|
|
1877
1887
|
// src/agent/model.ts
|
|
1878
1888
|
init_config();
|
|
@@ -1912,6 +1922,19 @@ import { z as z2 } from "zod";
|
|
|
1912
1922
|
import { exec as exec2 } from "child_process";
|
|
1913
1923
|
import { promisify as promisify2 } from "util";
|
|
1914
1924
|
|
|
1925
|
+
// src/utils/tokens.ts
|
|
1926
|
+
var CHARS_PER_TOKEN = 4;
|
|
1927
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
1928
|
+
function estimateTokens(text) {
|
|
1929
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
1930
|
+
}
|
|
1931
|
+
function estimateMessageTokens(messages) {
|
|
1932
|
+
return messages.reduce((total, msg) => {
|
|
1933
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
1934
|
+
return total + estimateTokens(content) + MESSAGE_OVERHEAD_TOKENS;
|
|
1935
|
+
}, 0);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1915
1938
|
// src/utils/truncate.ts
|
|
1916
1939
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
1917
1940
|
function truncateOutput(output, maxChars = MAX_OUTPUT_CHARS) {
|
|
@@ -5698,9 +5721,6 @@ ${conversationHistory}
|
|
|
5698
5721
|
Summary:`;
|
|
5699
5722
|
}
|
|
5700
5723
|
|
|
5701
|
-
// src/agent/context.ts
|
|
5702
|
-
init_config();
|
|
5703
|
-
|
|
5704
5724
|
// src/utils/sanitize-messages.ts
|
|
5705
5725
|
import { modelMessageSchema } from "ai";
|
|
5706
5726
|
function convertDatesToStrings(value) {
|
|
@@ -5837,79 +5857,256 @@ function sanitizeModelMessages(messages) {
|
|
|
5837
5857
|
return result;
|
|
5838
5858
|
}
|
|
5839
5859
|
|
|
5860
|
+
// src/agent/model-limits.ts
|
|
5861
|
+
var MODEL_LIMITS = {
|
|
5862
|
+
"anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5863
|
+
"anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5864
|
+
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5865
|
+
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5866
|
+
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
5867
|
+
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
5868
|
+
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
5869
|
+
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
5870
|
+
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
5871
|
+
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5872
|
+
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
5873
|
+
};
|
|
5874
|
+
var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
5875
|
+
var PREFIX_DEFAULTS = {
|
|
5876
|
+
"anthropic/": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
5877
|
+
"google/": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
5878
|
+
"openai/": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
5879
|
+
"xai/": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
5880
|
+
};
|
|
5881
|
+
function getModelLimits(modelId) {
|
|
5882
|
+
const normalized = modelId.trim().toLowerCase();
|
|
5883
|
+
const exact = MODEL_LIMITS[normalized];
|
|
5884
|
+
if (exact) return exact;
|
|
5885
|
+
for (const [prefix, limits] of Object.entries(PREFIX_DEFAULTS)) {
|
|
5886
|
+
if (normalized.startsWith(prefix)) return limits;
|
|
5887
|
+
}
|
|
5888
|
+
return DEFAULT_LIMITS;
|
|
5889
|
+
}
|
|
5890
|
+
var SUMMARIZATION_MODEL = "google/gemini-3-flash-preview";
|
|
5891
|
+
var SUMMARY_CHUNK_TOKENS = 3e4;
|
|
5892
|
+
var SUMMARY_BUDGET_RATIO = 0.15;
|
|
5893
|
+
|
|
5840
5894
|
// src/agent/context.ts
|
|
5895
|
+
var TOOL_OUTPUT_TRIM_CHARS = 400;
|
|
5896
|
+
var COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
5897
|
+
"read_file",
|
|
5898
|
+
"bash",
|
|
5899
|
+
"explore_agent",
|
|
5900
|
+
"code_graph"
|
|
5901
|
+
]);
|
|
5841
5902
|
var ContextManager = class {
|
|
5842
5903
|
sessionId;
|
|
5904
|
+
modelId;
|
|
5843
5905
|
maxContextChars;
|
|
5844
5906
|
keepRecentMessages;
|
|
5845
5907
|
autoSummarize;
|
|
5846
|
-
|
|
5908
|
+
summaries = [];
|
|
5847
5909
|
constructor(options) {
|
|
5848
5910
|
this.sessionId = options.sessionId;
|
|
5911
|
+
this.modelId = options.modelId;
|
|
5849
5912
|
this.maxContextChars = options.maxContextChars;
|
|
5850
5913
|
this.keepRecentMessages = options.keepRecentMessages;
|
|
5851
5914
|
this.autoSummarize = options.autoSummarize;
|
|
5852
5915
|
}
|
|
5853
5916
|
/**
|
|
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.
|
|
5917
|
+
* Get messages for the current context, applying the three-phase pipeline.
|
|
5860
5918
|
*/
|
|
5861
5919
|
async getMessages() {
|
|
5862
|
-
let
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
if (this.autoSummarize
|
|
5866
|
-
|
|
5920
|
+
let messages = await messageQueries.getModelMessages(this.sessionId);
|
|
5921
|
+
messages = sanitizeModelMessages(messages);
|
|
5922
|
+
messages = this.compactOlderMessages(messages, this.keepRecentMessages);
|
|
5923
|
+
if (this.autoSummarize) {
|
|
5924
|
+
const { rollingTarget } = getModelLimits(this.modelId);
|
|
5925
|
+
const summaryBudget = Math.floor(rollingTarget * SUMMARY_BUDGET_RATIO);
|
|
5926
|
+
messages = await this.chunkSummarize(messages, rollingTarget);
|
|
5927
|
+
await this.rollSummaries(summaryBudget);
|
|
5867
5928
|
}
|
|
5868
|
-
if (this.
|
|
5869
|
-
|
|
5929
|
+
if (this.summaries.length > 0) {
|
|
5930
|
+
const summaryContent = this.summaries.join("\n\n---\n\n");
|
|
5931
|
+
messages = [
|
|
5870
5932
|
{
|
|
5871
5933
|
role: "system",
|
|
5872
5934
|
content: `[Previous conversation summary]
|
|
5873
|
-
${
|
|
5935
|
+
${summaryContent}`
|
|
5874
5936
|
},
|
|
5875
|
-
...
|
|
5937
|
+
...messages
|
|
5876
5938
|
];
|
|
5877
5939
|
}
|
|
5878
|
-
|
|
5940
|
+
messages = repairToolPairing(messages);
|
|
5941
|
+
return messages;
|
|
5942
|
+
}
|
|
5943
|
+
// ---------------------------------------------------------------------------
|
|
5944
|
+
// Phase 1 – Compact
|
|
5945
|
+
// ---------------------------------------------------------------------------
|
|
5946
|
+
/**
|
|
5947
|
+
* Strip non-essential content from messages older than the most recent
|
|
5948
|
+
* `recentCount`. Operates in-memory only — does not touch the DB.
|
|
5949
|
+
*
|
|
5950
|
+
* Tracks removed tool-call IDs so matching tool-results are also removed,
|
|
5951
|
+
* preventing orphaned tool_result blocks that providers reject.
|
|
5952
|
+
*/
|
|
5953
|
+
compactOlderMessages(messages, recentCount) {
|
|
5954
|
+
if (messages.length <= recentCount) return messages;
|
|
5955
|
+
const boundary = messages.length - recentCount;
|
|
5956
|
+
const olderMessages = messages.slice(0, boundary);
|
|
5957
|
+
const recentMessages = messages.slice(boundary);
|
|
5958
|
+
const removedToolCallIds = /* @__PURE__ */ new Set();
|
|
5959
|
+
const compacted = [];
|
|
5960
|
+
for (const msg of olderMessages) {
|
|
5961
|
+
const processed = this.compactMessage(msg, removedToolCallIds);
|
|
5962
|
+
if (processed) compacted.push(processed);
|
|
5963
|
+
}
|
|
5964
|
+
if (removedToolCallIds.size > 0) {
|
|
5965
|
+
const cleaned = [];
|
|
5966
|
+
for (const msg of compacted) {
|
|
5967
|
+
const result = stripOrphanedToolResults(msg, removedToolCallIds);
|
|
5968
|
+
if (result) cleaned.push(result);
|
|
5969
|
+
}
|
|
5970
|
+
return [...cleaned, ...recentMessages];
|
|
5971
|
+
}
|
|
5972
|
+
return [...compacted, ...recentMessages];
|
|
5973
|
+
}
|
|
5974
|
+
compactMessage(msg, removedToolCallIds) {
|
|
5975
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
5976
|
+
const parts = [];
|
|
5977
|
+
for (const part of msg.content) {
|
|
5978
|
+
if (part.type === "tool-call" && part.toolName === "todo") {
|
|
5979
|
+
if (part.toolCallId) removedToolCallIds.add(part.toolCallId);
|
|
5980
|
+
continue;
|
|
5981
|
+
}
|
|
5982
|
+
if (part.type === "tool-result" && part.toolName === "todo") {
|
|
5983
|
+
if (part.toolCallId) removedToolCallIds.add(part.toolCallId);
|
|
5984
|
+
continue;
|
|
5985
|
+
}
|
|
5986
|
+
if (part.type === "reasoning" || part.type === "thinking") continue;
|
|
5987
|
+
if (part.type === "tool-result" && COMPACTABLE_TOOLS.has(part.toolName)) {
|
|
5988
|
+
parts.push(this.trimToolResult(part));
|
|
5989
|
+
continue;
|
|
5990
|
+
}
|
|
5991
|
+
parts.push(part);
|
|
5992
|
+
}
|
|
5993
|
+
if (parts.length === 0) return null;
|
|
5994
|
+
return { ...msg, content: parts };
|
|
5995
|
+
}
|
|
5996
|
+
trimToolResult(part) {
|
|
5997
|
+
const results = Array.isArray(part.result) ? part.result : [part.result];
|
|
5998
|
+
const trimmedResults = results.map((r) => {
|
|
5999
|
+
if (typeof r === "string" && r.length > TOOL_OUTPUT_TRIM_CHARS) {
|
|
6000
|
+
const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
|
|
6001
|
+
return r.slice(0, half) + `
|
|
6002
|
+
...[trimmed ${r.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
|
|
6003
|
+
` + r.slice(-half);
|
|
6004
|
+
}
|
|
6005
|
+
if (r && typeof r === "object" && typeof r.text === "string" && r.text.length > TOOL_OUTPUT_TRIM_CHARS) {
|
|
6006
|
+
const half = Math.floor(TOOL_OUTPUT_TRIM_CHARS / 2);
|
|
6007
|
+
return {
|
|
6008
|
+
...r,
|
|
6009
|
+
text: r.text.slice(0, half) + `
|
|
6010
|
+
...[trimmed ${r.text.length - TOOL_OUTPUT_TRIM_CHARS} chars]...
|
|
6011
|
+
` + r.text.slice(-half)
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
return r;
|
|
6015
|
+
});
|
|
6016
|
+
return {
|
|
6017
|
+
...part,
|
|
6018
|
+
result: Array.isArray(part.result) ? trimmedResults : trimmedResults[0]
|
|
6019
|
+
};
|
|
5879
6020
|
}
|
|
6021
|
+
// ---------------------------------------------------------------------------
|
|
6022
|
+
// Phase 2 – Chunk-summarize
|
|
6023
|
+
// ---------------------------------------------------------------------------
|
|
5880
6024
|
/**
|
|
5881
|
-
*
|
|
6025
|
+
* While estimated tokens exceed `rollingTarget`, peel off the oldest
|
|
6026
|
+
* ~SUMMARY_CHUNK_TOKENS worth of messages, summarize them via the cheap
|
|
6027
|
+
* model, and prepend the summary.
|
|
5882
6028
|
*/
|
|
5883
|
-
async
|
|
5884
|
-
|
|
5885
|
-
|
|
6029
|
+
async chunkSummarize(messages, rollingTarget) {
|
|
6030
|
+
let totalTokens = estimateMessageTokens(messages);
|
|
6031
|
+
while (totalTokens > rollingTarget && messages.length > this.keepRecentMessages) {
|
|
6032
|
+
let chunkTokens = 0;
|
|
6033
|
+
let chunkEnd = 0;
|
|
6034
|
+
const maxChunkable = messages.length - this.keepRecentMessages;
|
|
6035
|
+
for (let i = 0; i < maxChunkable; i++) {
|
|
6036
|
+
const msgTokens = this.messageTokens(messages[i]);
|
|
6037
|
+
chunkTokens += msgTokens;
|
|
6038
|
+
chunkEnd = i + 1;
|
|
6039
|
+
if (chunkTokens >= SUMMARY_CHUNK_TOKENS) break;
|
|
6040
|
+
}
|
|
6041
|
+
if (chunkEnd === 0) break;
|
|
6042
|
+
const chunk = messages.slice(0, chunkEnd);
|
|
6043
|
+
const remaining = messages.slice(chunkEnd);
|
|
6044
|
+
const summary = await this.summarizeChunk(chunk);
|
|
6045
|
+
if (summary) {
|
|
6046
|
+
this.summaries.push(summary);
|
|
6047
|
+
console.log(
|
|
6048
|
+
`[Context] Summarized ${chunk.length} messages (~${chunkTokens} tokens) into ${estimateTokens(summary)} tokens`
|
|
6049
|
+
);
|
|
6050
|
+
}
|
|
6051
|
+
messages = remaining;
|
|
6052
|
+
totalTokens = estimateMessageTokens(messages);
|
|
5886
6053
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
const historyText =
|
|
6054
|
+
return messages;
|
|
6055
|
+
}
|
|
6056
|
+
async summarizeChunk(chunk) {
|
|
6057
|
+
const historyText = chunk.map((msg) => {
|
|
5891
6058
|
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
5892
6059
|
return `[${msg.role}]: ${content}`;
|
|
5893
6060
|
}).join("\n\n");
|
|
5894
6061
|
try {
|
|
5895
|
-
const config = getConfig();
|
|
5896
|
-
const summaryPrompt = createSummaryPrompt(historyText);
|
|
5897
6062
|
const result = await generateText2({
|
|
5898
|
-
model: resolveModel(
|
|
5899
|
-
prompt:
|
|
6063
|
+
model: resolveModel(SUMMARIZATION_MODEL),
|
|
6064
|
+
prompt: createSummaryPrompt(historyText)
|
|
5900
6065
|
});
|
|
5901
|
-
|
|
5902
|
-
console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);
|
|
5903
|
-
return recentMessages;
|
|
6066
|
+
return result.text;
|
|
5904
6067
|
} catch (error) {
|
|
5905
|
-
console.error("[Context]
|
|
5906
|
-
return
|
|
6068
|
+
console.error("[Context] Chunk summarization failed:", error);
|
|
6069
|
+
return null;
|
|
5907
6070
|
}
|
|
5908
6071
|
}
|
|
6072
|
+
// ---------------------------------------------------------------------------
|
|
6073
|
+
// Phase 3 – Roll summaries
|
|
6074
|
+
// ---------------------------------------------------------------------------
|
|
5909
6075
|
/**
|
|
5910
|
-
*
|
|
5911
|
-
*
|
|
6076
|
+
* If accumulated summaries exceed `budget` tokens, re-summarize them
|
|
6077
|
+
* into a single condensed summary.
|
|
5912
6078
|
*/
|
|
6079
|
+
async rollSummaries(budget) {
|
|
6080
|
+
if (this.summaries.length <= 1) return;
|
|
6081
|
+
const totalSummaryTokens = this.summaries.reduce(
|
|
6082
|
+
(t, s) => t + estimateTokens(s),
|
|
6083
|
+
0
|
|
6084
|
+
);
|
|
6085
|
+
if (totalSummaryTokens <= budget) return;
|
|
6086
|
+
const combined = this.summaries.join("\n\n---\n\n");
|
|
6087
|
+
try {
|
|
6088
|
+
const result = await generateText2({
|
|
6089
|
+
model: resolveModel(SUMMARIZATION_MODEL),
|
|
6090
|
+
prompt: createSummaryPrompt(combined)
|
|
6091
|
+
});
|
|
6092
|
+
console.log(
|
|
6093
|
+
`[Context] Rolled ${this.summaries.length} summaries (${totalSummaryTokens} tokens) into ${estimateTokens(result.text)} tokens`
|
|
6094
|
+
);
|
|
6095
|
+
this.summaries = [result.text];
|
|
6096
|
+
} catch (error) {
|
|
6097
|
+
console.error("[Context] Summary rolling failed:", error);
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
// ---------------------------------------------------------------------------
|
|
6101
|
+
// Helpers
|
|
6102
|
+
// ---------------------------------------------------------------------------
|
|
6103
|
+
messageTokens(msg) {
|
|
6104
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
6105
|
+
return estimateTokens(content) + 4;
|
|
6106
|
+
}
|
|
6107
|
+
// ---------------------------------------------------------------------------
|
|
6108
|
+
// Public API (unchanged)
|
|
6109
|
+
// ---------------------------------------------------------------------------
|
|
5913
6110
|
async addUserMessage(content) {
|
|
5914
6111
|
const userMessage = {
|
|
5915
6112
|
role: "user",
|
|
@@ -5917,32 +6114,69 @@ ${this.summary}`
|
|
|
5917
6114
|
};
|
|
5918
6115
|
await messageQueries.create(this.sessionId, userMessage);
|
|
5919
6116
|
}
|
|
5920
|
-
/**
|
|
5921
|
-
* Add response messages from AI SDK directly
|
|
5922
|
-
* This is the preferred method - use result.response.messages from streamText/generateText
|
|
5923
|
-
*/
|
|
5924
6117
|
async addResponseMessages(messages) {
|
|
5925
6118
|
await messageQueries.addMany(this.sessionId, messages);
|
|
5926
6119
|
}
|
|
5927
|
-
/**
|
|
5928
|
-
* Get current context statistics
|
|
5929
|
-
*/
|
|
5930
6120
|
async getStats() {
|
|
5931
6121
|
const messages = await messageQueries.getModelMessages(this.sessionId);
|
|
5932
6122
|
return {
|
|
5933
6123
|
messageCount: messages.length,
|
|
5934
6124
|
contextChars: calculateContextSize(messages),
|
|
5935
|
-
|
|
6125
|
+
estimatedTokens: estimateMessageTokens(messages),
|
|
6126
|
+
hasSummary: this.summaries.length > 0,
|
|
6127
|
+
summaryCount: this.summaries.length
|
|
5936
6128
|
};
|
|
5937
6129
|
}
|
|
5938
|
-
/**
|
|
5939
|
-
* Clear all messages in the context
|
|
5940
|
-
*/
|
|
5941
6130
|
async clear() {
|
|
5942
6131
|
await messageQueries.deleteBySession(this.sessionId);
|
|
5943
|
-
this.
|
|
6132
|
+
this.summaries = [];
|
|
5944
6133
|
}
|
|
5945
6134
|
};
|
|
6135
|
+
function stripOrphanedToolResults(msg, removedIds) {
|
|
6136
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
6137
|
+
const parts = msg.content.filter((part) => {
|
|
6138
|
+
if (part.type === "tool-result" && removedIds.has(part.toolCallId)) return false;
|
|
6139
|
+
if (part.type === "tool-call" && removedIds.has(part.toolCallId)) return false;
|
|
6140
|
+
return true;
|
|
6141
|
+
});
|
|
6142
|
+
if (parts.length === 0) return null;
|
|
6143
|
+
return { ...msg, content: parts };
|
|
6144
|
+
}
|
|
6145
|
+
function repairToolPairing(messages) {
|
|
6146
|
+
const toolCallIds = /* @__PURE__ */ new Set();
|
|
6147
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
6148
|
+
for (const msg of messages) {
|
|
6149
|
+
if (!Array.isArray(msg.content)) continue;
|
|
6150
|
+
for (const part of msg.content) {
|
|
6151
|
+
if (part.type === "tool-call" && part.toolCallId) toolCallIds.add(part.toolCallId);
|
|
6152
|
+
if (part.type === "tool-result" && part.toolCallId) toolResultIds.add(part.toolCallId);
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
const orphanedCalls = new Set([...toolCallIds].filter((id) => !toolResultIds.has(id)));
|
|
6156
|
+
const orphanedResults = new Set([...toolResultIds].filter((id) => !toolCallIds.has(id)));
|
|
6157
|
+
if (orphanedCalls.size === 0 && orphanedResults.size === 0) return messages;
|
|
6158
|
+
if (orphanedCalls.size > 0) {
|
|
6159
|
+
console.warn(`[tool-repair] Removing ${orphanedCalls.size} orphaned tool-call(s) with no matching result`);
|
|
6160
|
+
}
|
|
6161
|
+
if (orphanedResults.size > 0) {
|
|
6162
|
+
console.warn(`[tool-repair] Removing ${orphanedResults.size} orphaned tool-result(s) with no matching call`);
|
|
6163
|
+
}
|
|
6164
|
+
const repaired = [];
|
|
6165
|
+
for (const msg of messages) {
|
|
6166
|
+
if (!Array.isArray(msg.content)) {
|
|
6167
|
+
repaired.push(msg);
|
|
6168
|
+
continue;
|
|
6169
|
+
}
|
|
6170
|
+
const parts = msg.content.filter((part) => {
|
|
6171
|
+
if (part.type === "tool-call" && orphanedCalls.has(part.toolCallId)) return false;
|
|
6172
|
+
if (part.type === "tool-result" && orphanedResults.has(part.toolCallId)) return false;
|
|
6173
|
+
return true;
|
|
6174
|
+
});
|
|
6175
|
+
if (parts.length === 0) continue;
|
|
6176
|
+
repaired.push({ ...msg, content: parts });
|
|
6177
|
+
}
|
|
6178
|
+
return repaired;
|
|
6179
|
+
}
|
|
5946
6180
|
|
|
5947
6181
|
// src/utils/webhook.ts
|
|
5948
6182
|
async function sendWebhook(url, event) {
|
|
@@ -6026,6 +6260,7 @@ var Agent = class _Agent {
|
|
|
6026
6260
|
}
|
|
6027
6261
|
const context = new ContextManager({
|
|
6028
6262
|
sessionId: session.id,
|
|
6263
|
+
modelId: session.model || config.defaultModel,
|
|
6029
6264
|
maxContextChars: config.context?.maxChars || 2e5,
|
|
6030
6265
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
6031
6266
|
autoSummarize: config.context?.autoSummarize ?? true
|