mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-19 23:01:32 +00:00
Store file lists in BranchSummaryEntry.details for cumulative tracking
- BranchSummaryResult now returns readFiles and modifiedFiles separately
- BranchSummaryDetails type for details: { readFiles, modifiedFiles }
- branchWithSummary accepts optional details parameter
- Collect files from existing branch_summary.details when preparing entries
- Files accumulate across nested branch summaries
This commit is contained in:
parent
04f2fcf004
commit
4ef3325cec
4 changed files with 111 additions and 95 deletions
|
|
@ -3835,13 +3835,13 @@ export const MODELS = {
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text"],
|
input: ["text"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.39,
|
input: 0.35,
|
||||||
output: 1.9,
|
output: 1.5,
|
||||||
cacheRead: 0,
|
cacheRead: 0,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
},
|
},
|
||||||
contextWindow: 204800,
|
contextWindow: 202752,
|
||||||
maxTokens: 204800,
|
maxTokens: 65536,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"z-ai/glm-4.6:exacto": {
|
"z-ai/glm-4.6:exacto": {
|
||||||
id: "z-ai/glm-4.6:exacto",
|
id: "z-ai/glm-4.6:exacto",
|
||||||
|
|
@ -6104,9 +6104,9 @@ export const MODELS = {
|
||||||
contextWindow: 32768,
|
contextWindow: 32768,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"anthropic/claude-3.5-haiku": {
|
"anthropic/claude-3.5-haiku-20241022": {
|
||||||
id: "anthropic/claude-3.5-haiku",
|
id: "anthropic/claude-3.5-haiku-20241022",
|
||||||
name: "Anthropic: Claude 3.5 Haiku",
|
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)",
|
||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "openrouter",
|
provider: "openrouter",
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
|
@ -6121,9 +6121,9 @@ export const MODELS = {
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"anthropic/claude-3.5-haiku-20241022": {
|
"anthropic/claude-3.5-haiku": {
|
||||||
id: "anthropic/claude-3.5-haiku-20241022",
|
id: "anthropic/claude-3.5-haiku",
|
||||||
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)",
|
name: "Anthropic: Claude 3.5 Haiku",
|
||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "openrouter",
|
provider: "openrouter",
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
|
@ -6359,6 +6359,23 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 16384,
|
maxTokens: 16384,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"meta-llama/llama-3.1-8b-instruct": {
|
||||||
|
id: "meta-llama/llama-3.1-8b-instruct",
|
||||||
|
name: "Meta: Llama 3.1 8B Instruct",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0.02,
|
||||||
|
output: 0.03,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 16384,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"meta-llama/llama-3.1-405b-instruct": {
|
"meta-llama/llama-3.1-405b-instruct": {
|
||||||
id: "meta-llama/llama-3.1-405b-instruct",
|
id: "meta-llama/llama-3.1-405b-instruct",
|
||||||
name: "Meta: Llama 3.1 405B Instruct",
|
name: "Meta: Llama 3.1 405B Instruct",
|
||||||
|
|
@ -6393,23 +6410,6 @@ export const MODELS = {
|
||||||
contextWindow: 131072,
|
contextWindow: 131072,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"meta-llama/llama-3.1-8b-instruct": {
|
|
||||||
id: "meta-llama/llama-3.1-8b-instruct",
|
|
||||||
name: "Meta: Llama 3.1 8B Instruct",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text"],
|
|
||||||
cost: {
|
|
||||||
input: 0.02,
|
|
||||||
output: 0.03,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 131072,
|
|
||||||
maxTokens: 16384,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"mistralai/mistral-nemo": {
|
"mistralai/mistral-nemo": {
|
||||||
id: "mistralai/mistral-nemo",
|
id: "mistralai/mistral-nemo",
|
||||||
name: "Mistral: Mistral Nemo",
|
name: "Mistral: Mistral Nemo",
|
||||||
|
|
@ -6546,6 +6546,23 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"openai/gpt-4o-2024-05-13": {
|
||||||
|
id: "openai/gpt-4o-2024-05-13",
|
||||||
|
name: "OpenAI: GPT-4o (2024-05-13)",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"],
|
||||||
|
cost: {
|
||||||
|
input: 5,
|
||||||
|
output: 15,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-4o": {
|
"openai/gpt-4o": {
|
||||||
id: "openai/gpt-4o",
|
id: "openai/gpt-4o",
|
||||||
name: "OpenAI: GPT-4o",
|
name: "OpenAI: GPT-4o",
|
||||||
|
|
@ -6580,23 +6597,6 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-4o-2024-05-13": {
|
|
||||||
id: "openai/gpt-4o-2024-05-13",
|
|
||||||
name: "OpenAI: GPT-4o (2024-05-13)",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text", "image"],
|
|
||||||
cost: {
|
|
||||||
input: 5,
|
|
||||||
output: 15,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 128000,
|
|
||||||
maxTokens: 4096,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"meta-llama/llama-3-70b-instruct": {
|
"meta-llama/llama-3-70b-instruct": {
|
||||||
id: "meta-llama/llama-3-70b-instruct",
|
id: "meta-llama/llama-3-70b-instruct",
|
||||||
name: "Meta: Llama 3 70B Instruct",
|
name: "Meta: Llama 3 70B Instruct",
|
||||||
|
|
@ -6835,23 +6835,6 @@ export const MODELS = {
|
||||||
contextWindow: 8191,
|
contextWindow: 8191,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-3.5-turbo": {
|
|
||||||
id: "openai/gpt-3.5-turbo",
|
|
||||||
name: "OpenAI: GPT-3.5 Turbo",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text"],
|
|
||||||
cost: {
|
|
||||||
input: 0.5,
|
|
||||||
output: 1.5,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 16385,
|
|
||||||
maxTokens: 4096,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"openai/gpt-4": {
|
"openai/gpt-4": {
|
||||||
id: "openai/gpt-4",
|
id: "openai/gpt-4",
|
||||||
name: "OpenAI: GPT-4",
|
name: "OpenAI: GPT-4",
|
||||||
|
|
@ -6869,6 +6852,23 @@ export const MODELS = {
|
||||||
contextWindow: 8191,
|
contextWindow: 8191,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"openai/gpt-3.5-turbo": {
|
||||||
|
id: "openai/gpt-3.5-turbo",
|
||||||
|
name: "OpenAI: GPT-3.5 Turbo",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0.5,
|
||||||
|
output: 1.5,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 16385,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"openrouter/auto": {
|
"openrouter/auto": {
|
||||||
id: "openrouter/auto",
|
id: "openrouter/auto",
|
||||||
name: "OpenRouter: Auto Router",
|
name: "OpenRouter: Auto Router",
|
||||||
|
|
|
||||||
|
|
@ -1644,6 +1644,7 @@ export class AgentSession {
|
||||||
|
|
||||||
// Run default summarizer if needed
|
// Run default summarizer if needed
|
||||||
let summaryText: string | undefined;
|
let summaryText: string | undefined;
|
||||||
|
let summaryDetails: unknown;
|
||||||
if (options.summarize && entriesToSummarize.length > 0 && !hookSummary) {
|
if (options.summarize && entriesToSummarize.length > 0 && !hookSummary) {
|
||||||
const model = this.model!;
|
const model = this.model!;
|
||||||
const apiKey = await this._modelRegistry.getApiKey(model);
|
const apiKey = await this._modelRegistry.getApiKey(model);
|
||||||
|
|
@ -1666,8 +1667,13 @@ export class AgentSession {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
summaryText = result.summary;
|
summaryText = result.summary;
|
||||||
|
summaryDetails = {
|
||||||
|
readFiles: result.readFiles || [],
|
||||||
|
modifiedFiles: result.modifiedFiles || [],
|
||||||
|
};
|
||||||
} else if (hookSummary) {
|
} else if (hookSummary) {
|
||||||
summaryText = hookSummary.summary;
|
summaryText = hookSummary.summary;
|
||||||
|
summaryDetails = hookSummary.details;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the new leaf position based on target type
|
// Determine the new leaf position based on target type
|
||||||
|
|
@ -1698,7 +1704,7 @@ export class AgentSession {
|
||||||
let summaryEntry: BranchSummaryEntry | undefined;
|
let summaryEntry: BranchSummaryEntry | undefined;
|
||||||
if (summaryText) {
|
if (summaryText) {
|
||||||
// Create summary at target position (can be null for root)
|
// Create summary at target position (can be null for root)
|
||||||
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText);
|
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails);
|
||||||
summaryEntry = this.sessionManager.getEntry(summaryId) as BranchSummaryEntry;
|
summaryEntry = this.sessionManager.getEntry(summaryId) as BranchSummaryEntry;
|
||||||
} else if (newLeafId === null) {
|
} else if (newLeafId === null) {
|
||||||
// No summary, navigating to root - reset leaf
|
// No summary, navigating to root - reset leaf
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,18 @@ import { estimateTokens } from "./compaction.js";
|
||||||
|
|
||||||
export interface BranchSummaryResult {
|
export interface BranchSummaryResult {
|
||||||
summary?: string;
|
summary?: string;
|
||||||
|
readFiles?: string[];
|
||||||
|
modifiedFiles?: string[];
|
||||||
aborted?: boolean;
|
aborted?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Details stored in BranchSummaryEntry.details for file tracking */
|
||||||
|
export interface BranchSummaryDetails {
|
||||||
|
readFiles: string[];
|
||||||
|
modifiedFiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileOperations {
|
export interface FileOperations {
|
||||||
read: Set<string>;
|
read: Set<string>;
|
||||||
written: Set<string>;
|
written: Set<string>;
|
||||||
|
|
@ -183,6 +191,10 @@ function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperation
|
||||||
* Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.
|
* Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.
|
||||||
* This ensures we keep the most recent context when the branch is too long.
|
* This ensures we keep the most recent context when the branch is too long.
|
||||||
*
|
*
|
||||||
|
* Also collects file operations from:
|
||||||
|
* - Tool calls in assistant messages
|
||||||
|
* - Existing branch_summary entries' details (for cumulative tracking)
|
||||||
|
*
|
||||||
* @param entries - Entries in chronological order
|
* @param entries - Entries in chronological order
|
||||||
* @param tokenBudget - Maximum tokens to include (0 = no limit)
|
* @param tokenBudget - Maximum tokens to include (0 = no limit)
|
||||||
*/
|
*/
|
||||||
|
|
@ -195,13 +207,30 @@ export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numbe
|
||||||
};
|
};
|
||||||
let totalTokens = 0;
|
let totalTokens = 0;
|
||||||
|
|
||||||
// Walk from newest to oldest to prioritize recent context
|
// First pass: collect file ops from ALL entries (even if they don't fit in token budget)
|
||||||
|
// This ensures we capture cumulative file tracking from nested branch summaries
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.type === "branch_summary" && entry.details) {
|
||||||
|
const details = entry.details as BranchSummaryDetails;
|
||||||
|
if (Array.isArray(details.readFiles)) {
|
||||||
|
for (const f of details.readFiles) fileOps.read.add(f);
|
||||||
|
}
|
||||||
|
if (Array.isArray(details.modifiedFiles)) {
|
||||||
|
// Modified files go into both edited and written for proper deduplication
|
||||||
|
for (const f of details.modifiedFiles) {
|
||||||
|
fileOps.edited.add(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: walk from newest to oldest, adding messages until token budget
|
||||||
for (let i = entries.length - 1; i >= 0; i--) {
|
for (let i = entries.length - 1; i >= 0; i--) {
|
||||||
const entry = entries[i];
|
const entry = entries[i];
|
||||||
const message = getMessageFromEntry(entry);
|
const message = getMessageFromEntry(entry);
|
||||||
if (!message) continue;
|
if (!message) continue;
|
||||||
|
|
||||||
// Extract file ops from assistant messages
|
// Extract file ops from assistant messages (tool calls)
|
||||||
extractFileOpsFromMessage(message, fileOps);
|
extractFileOpsFromMessage(message, fileOps);
|
||||||
|
|
||||||
const tokens = estimateTokens(message);
|
const tokens = estimateTokens(message);
|
||||||
|
|
@ -238,32 +267,6 @@ const BRANCH_SUMMARY_PROMPT = `Summarize this conversation branch concisely for
|
||||||
|
|
||||||
Be brief and focused on what matters for future reference.`;
|
Be brief and focused on what matters for future reference.`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Format file operations as a static section to append to summary.
|
|
||||||
*/
|
|
||||||
function formatFileOperations(fileOps: FileOperations): string {
|
|
||||||
// Combine edited and written into "modified"
|
|
||||||
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
|
||||||
|
|
||||||
// Read-only = read but not modified
|
|
||||||
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
|
||||||
|
|
||||||
const sections: string[] = [];
|
|
||||||
|
|
||||||
if (readOnly.length > 0) {
|
|
||||||
sections.push(`<read-files>\n${readOnly.join("\n")}\n</read-files>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modified.size > 0) {
|
|
||||||
const files = [...modified].sort();
|
|
||||||
sections.push(`<modified-files>\n${files.join("\n")}\n</modified-files>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sections.length === 0) return "";
|
|
||||||
|
|
||||||
return `\n\n${sections.join("\n\n")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert messages to text for the summarization prompt.
|
* Convert messages to text for the summarization prompt.
|
||||||
*/
|
*/
|
||||||
|
|
@ -358,13 +361,19 @@ export async function generateBranchSummary(
|
||||||
return { error: response.errorMessage || "Summarization failed" };
|
return { error: response.errorMessage || "Summarization failed" };
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = response.content
|
const summary = response.content
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
||||||
.map((c) => c.text)
|
.map((c) => c.text)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
// Append static file operations section
|
// Compute file lists for details
|
||||||
summary += formatFileOperations(fileOps);
|
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
||||||
|
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
||||||
|
const modifiedFiles = [...modified].sort();
|
||||||
|
|
||||||
return { summary: summary || "No summary generated" };
|
return {
|
||||||
|
summary: summary || "No summary generated",
|
||||||
|
readFiles: readOnly,
|
||||||
|
modifiedFiles,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -872,7 +872,7 @@ export class SessionManager {
|
||||||
* Same as branch(), but also appends a branch_summary entry that captures
|
* Same as branch(), but also appends a branch_summary entry that captures
|
||||||
* context from the abandoned conversation path.
|
* context from the abandoned conversation path.
|
||||||
*/
|
*/
|
||||||
branchWithSummary(branchFromId: string | null, summary: string): string {
|
branchWithSummary(branchFromId: string | null, summary: string, details?: unknown): string {
|
||||||
if (branchFromId !== null && !this.byId.has(branchFromId)) {
|
if (branchFromId !== null && !this.byId.has(branchFromId)) {
|
||||||
throw new Error(`Entry ${branchFromId} not found`);
|
throw new Error(`Entry ${branchFromId} not found`);
|
||||||
}
|
}
|
||||||
|
|
@ -884,6 +884,7 @@ export class SessionManager {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
fromId: branchFromId ?? "root",
|
fromId: branchFromId ?? "root",
|
||||||
summary,
|
summary,
|
||||||
|
details,
|
||||||
};
|
};
|
||||||
this._appendEntry(entry);
|
this._appendEntry(entry);
|
||||||
return entry.id;
|
return entry.id;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue