[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
524
infrastructure/ai/harness/contextManager.ts
Normal file
524
infrastructure/ai/harness/contextManager.ts
Normal file
@@ -0,0 +1,524 @@
|
||||
import type { ModelMessage } from 'ai';
|
||||
import type { ChatMessage, AIPermissionMode } from '../types';
|
||||
import { isStepHandleNoticeMessage } from './agentEventAdapter';
|
||||
import {
|
||||
DEFAULT_CONTEXT_WINDOW_TOKENS,
|
||||
DEFAULT_PROTECT_RECENT_MESSAGES,
|
||||
findSafeCompactionSplitIndex,
|
||||
keepRecentContextMessages,
|
||||
prepareContextCompaction,
|
||||
} from '../contextCompaction';
|
||||
import { compressMessagesForRequestTooLargeRetry } from '../requestPayloadCompression';
|
||||
import {
|
||||
computeCompactionThreshold,
|
||||
computeTotalInputTokens,
|
||||
DEFAULT_MAX_OUTPUT_TOKENS,
|
||||
shouldCompactByBudget,
|
||||
} from './contextBudget';
|
||||
import { estimateModelMessagesTokensWithKind } from './tokenEstimator';
|
||||
import { pruneStaleToolContext } from './staleContextPruner';
|
||||
import type { PrepareStepContextInput } from './turnDrivers/types';
|
||||
import type {
|
||||
AgentEventListener,
|
||||
CompactionTrace,
|
||||
ContextPrepareResult,
|
||||
ContextPrepareTrigger,
|
||||
ExternalBridgeHistoryMessage,
|
||||
} from './types';
|
||||
import { buildExternalBridgeContextMessages } from './externalBridgeContext';
|
||||
import { repairToolMessageIntegrity } from './toolMessageIntegrity';
|
||||
import { pruneFirstModelMessage } from './compactionPruner';
|
||||
|
||||
export interface PrepareTurnContextInput {
|
||||
messages: ModelMessage[];
|
||||
backend: 'catty' | 'external-bridge';
|
||||
contextWindow?: number;
|
||||
reservedTokens?: number;
|
||||
maxOutputTokens?: number;
|
||||
trigger: ContextPrepareTrigger;
|
||||
protectRecentMessages?: number;
|
||||
force?: boolean;
|
||||
compressForRequestTooLargeRetry?: boolean;
|
||||
summarize?: (messagesToSummarize: ModelMessage[]) => Promise<string>;
|
||||
onEvent?: AgentEventListener;
|
||||
sessionId?: string;
|
||||
chatSessionId?: string;
|
||||
providerId?: string | null;
|
||||
reinjection?: PostCompactReinjection;
|
||||
}
|
||||
|
||||
export interface PostCompactReinjection {
|
||||
permissionMode?: AIPermissionMode;
|
||||
sessionScopeSummary?: string;
|
||||
sessionStateText?: string;
|
||||
userGoal?: string;
|
||||
pendingToolHandleIds?: string[];
|
||||
}
|
||||
|
||||
function emitCompactionEvent(
|
||||
onEvent: AgentEventListener | undefined,
|
||||
input: {
|
||||
sessionId?: string;
|
||||
chatSessionId?: string;
|
||||
backend: PrepareTurnContextInput['backend'];
|
||||
},
|
||||
trace: CompactionTrace,
|
||||
): void {
|
||||
if (!onEvent || !input.sessionId) return;
|
||||
onEvent({
|
||||
id: `compaction-${Date.now()}`,
|
||||
type: 'compaction',
|
||||
sessionId: input.sessionId,
|
||||
chatSessionId: input.chatSessionId,
|
||||
backend: input.backend === 'catty' ? 'catty' : 'external-sdk',
|
||||
timestamp: Date.now(),
|
||||
trace,
|
||||
});
|
||||
}
|
||||
|
||||
function emitCompactionStart(
|
||||
onEvent: AgentEventListener | undefined,
|
||||
input: {
|
||||
sessionId?: string;
|
||||
chatSessionId?: string;
|
||||
backend: PrepareTurnContextInput['backend'];
|
||||
},
|
||||
trigger: ContextPrepareTrigger,
|
||||
): void {
|
||||
if (!onEvent || !input.sessionId) return;
|
||||
onEvent({
|
||||
id: `compaction-start-${Date.now()}`,
|
||||
type: 'compaction_start',
|
||||
sessionId: input.sessionId,
|
||||
chatSessionId: input.chatSessionId,
|
||||
backend: input.backend === 'catty' ? 'catty' : 'external-sdk',
|
||||
timestamp: Date.now(),
|
||||
trigger,
|
||||
});
|
||||
}
|
||||
|
||||
function applyTypedMessageCompression(messages: ModelMessage[]): {
|
||||
messages: ModelMessage[];
|
||||
didAdjust: boolean;
|
||||
} {
|
||||
const compressed = compressMessagesForRequestTooLargeRetry(messages);
|
||||
return { messages: compressed.messages, didAdjust: compressed.didAdjust };
|
||||
}
|
||||
|
||||
function buildReinjectionMessages(reinjection?: PostCompactReinjection): ModelMessage[] {
|
||||
if (!reinjection) return [];
|
||||
const lines: string[] = ['[Netcatty session context — preserved after compaction]'];
|
||||
if (reinjection.permissionMode) {
|
||||
lines.push(`Permission mode: ${reinjection.permissionMode}`);
|
||||
}
|
||||
if (reinjection.sessionStateText) {
|
||||
lines.push(reinjection.sessionStateText);
|
||||
}
|
||||
if (reinjection.sessionScopeSummary) {
|
||||
lines.push(reinjection.sessionScopeSummary);
|
||||
}
|
||||
if (reinjection.userGoal) {
|
||||
lines.push(`Current user goal: ${reinjection.userGoal}`);
|
||||
}
|
||||
if (reinjection.pendingToolHandleIds?.length) {
|
||||
lines.push(`Unresolved tool output handles: ${reinjection.pendingToolHandleIds.join(', ')}`);
|
||||
}
|
||||
if (lines.length <= 1) return [];
|
||||
return [{
|
||||
role: 'user',
|
||||
content: lines.join('\n'),
|
||||
}];
|
||||
}
|
||||
|
||||
function buildCompactionTrace(input: {
|
||||
trigger: ContextPrepareTrigger;
|
||||
tokensBefore: number;
|
||||
tokensAfter: number;
|
||||
messagesBefore: number;
|
||||
messagesAfter: number;
|
||||
compressedMessageCount: number;
|
||||
retainedTailCount: number;
|
||||
summaryLength?: number;
|
||||
didTypedCompression: boolean;
|
||||
didLlmSummarize: boolean;
|
||||
did413Fallback: boolean;
|
||||
estimatorKind?: CompactionTrace['estimatorKind'];
|
||||
}): CompactionTrace {
|
||||
return {
|
||||
trigger: input.trigger,
|
||||
estimatedTokensBefore: input.tokensBefore,
|
||||
estimatedTokensAfter: input.tokensAfter,
|
||||
messagesBefore: input.messagesBefore,
|
||||
messagesAfter: input.messagesAfter,
|
||||
compressedMessageCount: input.compressedMessageCount,
|
||||
retainedTailCount: input.retainedTailCount,
|
||||
summaryLength: input.summaryLength,
|
||||
didTypedCompression: input.didTypedCompression,
|
||||
didLlmSummarize: input.didLlmSummarize,
|
||||
did413Fallback: input.did413Fallback,
|
||||
estimatorKind: input.estimatorKind,
|
||||
};
|
||||
}
|
||||
|
||||
function isContextUnderBudgetPressure(input: {
|
||||
messages: ModelMessage[];
|
||||
contextWindow: number;
|
||||
maxOutputTokens: number;
|
||||
providerId?: string | null;
|
||||
reservedTokens?: number;
|
||||
force?: boolean;
|
||||
trigger?: ContextPrepareTrigger;
|
||||
}): boolean {
|
||||
if (input.force || input.trigger === '413-retry' || input.trigger === 'force') {
|
||||
return true;
|
||||
}
|
||||
return shouldCompactByBudget({
|
||||
messages: input.messages,
|
||||
contextWindow: input.contextWindow,
|
||||
maxOutputTokens: input.maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
}
|
||||
|
||||
export async function prepareTurnContext(
|
||||
input: PrepareTurnContextInput,
|
||||
): Promise<ContextPrepareResult> {
|
||||
const contextWindow = input.contextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS;
|
||||
const protectRecent = input.protectRecentMessages ?? DEFAULT_PROTECT_RECENT_MESSAGES;
|
||||
const maxOutputTokens = input.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
||||
const messagesBeforeCount = input.messages.length;
|
||||
const integrity = repairToolMessageIntegrity(input.messages);
|
||||
|
||||
const underBudgetPressure = isContextUnderBudgetPressure({
|
||||
messages: integrity.messages,
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
force: input.force,
|
||||
trigger: input.trigger,
|
||||
});
|
||||
const stale = pruneStaleToolContext(integrity.messages, {
|
||||
underBudgetPressure,
|
||||
});
|
||||
let working = stale.messages;
|
||||
let didAdjust = integrity.didAdjust || stale.didAdjust;
|
||||
|
||||
const tokensBeforeResult = estimateModelMessagesTokensWithKind({
|
||||
messages: working,
|
||||
providerId: input.providerId,
|
||||
});
|
||||
const tokensBefore = tokensBeforeResult.tokens;
|
||||
const estimatorKind = tokensBeforeResult.estimatorKind;
|
||||
|
||||
let didTypedCompression = false;
|
||||
let didLlmSummarize = false;
|
||||
let did413Fallback = false;
|
||||
let summaryLength: number | undefined;
|
||||
let compressedMessageCount = 0;
|
||||
let retainedTailCount = working.length;
|
||||
|
||||
if (input.compressForRequestTooLargeRetry || input.trigger === '413-retry') {
|
||||
const typed = applyTypedMessageCompression(working);
|
||||
working = typed.messages;
|
||||
didTypedCompression = typed.didAdjust;
|
||||
did413Fallback = typed.didAdjust;
|
||||
didAdjust = didAdjust || typed.didAdjust;
|
||||
} else {
|
||||
const typed = applyTypedMessageCompression(working);
|
||||
if (typed.didAdjust) {
|
||||
working = typed.messages;
|
||||
didTypedCompression = true;
|
||||
didAdjust = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.summarize) {
|
||||
const compacted = await prepareContextCompaction({
|
||||
messages: working,
|
||||
contextWindow,
|
||||
reservedTokens: input.reservedTokens ?? 0,
|
||||
thresholdRatio: input.force || input.trigger === 'force' ? 0 : undefined,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
protectRecentMessages: protectRecent,
|
||||
summarize: input.summarize,
|
||||
});
|
||||
|
||||
if (compacted.didCompact) {
|
||||
working = compacted.messages;
|
||||
didLlmSummarize = true;
|
||||
didAdjust = true;
|
||||
summaryLength = compacted.summary?.length;
|
||||
compressedMessageCount = Math.max(0, messagesBeforeCount - protectRecent);
|
||||
retainedTailCount = protectRecent;
|
||||
} else if (input.force || input.trigger === '413-retry' || input.trigger === 'force') {
|
||||
working = keepRecentContextMessages(working, protectRecent);
|
||||
didAdjust = true;
|
||||
retainedTailCount = working.length;
|
||||
}
|
||||
} else if (input.force || input.trigger === '413-retry' || input.trigger === 'force') {
|
||||
working = keepRecentContextMessages(working, protectRecent);
|
||||
didAdjust = true;
|
||||
retainedTailCount = working.length;
|
||||
}
|
||||
|
||||
const reinjection = buildReinjectionMessages(input.reinjection);
|
||||
if (reinjection.length > 0 && didAdjust) {
|
||||
const reinjectionTokens = estimateModelMessagesTokensWithKind({
|
||||
messages: reinjection,
|
||||
providerId: input.providerId,
|
||||
}).tokens;
|
||||
const finalGuard = applyStepBudgetGuard(working, {
|
||||
contextWindow,
|
||||
reservedTokens: (input.reservedTokens ?? 0) + reinjectionTokens,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
protectRecentMessages: protectRecent,
|
||||
});
|
||||
working = [...reinjection, ...finalGuard.messages];
|
||||
didAdjust = didAdjust || finalGuard.didAdjust;
|
||||
}
|
||||
|
||||
const tokensAfter = estimateModelMessagesTokensWithKind({
|
||||
messages: working,
|
||||
providerId: input.providerId,
|
||||
}).tokens;
|
||||
|
||||
if (didAdjust) {
|
||||
const trace = buildCompactionTrace({
|
||||
trigger: input.trigger,
|
||||
tokensBefore,
|
||||
tokensAfter,
|
||||
messagesBefore: messagesBeforeCount,
|
||||
messagesAfter: working.length,
|
||||
compressedMessageCount,
|
||||
retainedTailCount,
|
||||
summaryLength,
|
||||
didTypedCompression,
|
||||
didLlmSummarize,
|
||||
did413Fallback,
|
||||
estimatorKind,
|
||||
});
|
||||
emitCompactionEvent(input.onEvent, input, trace);
|
||||
return { messages: working, didAdjust: true, trace };
|
||||
}
|
||||
|
||||
return { messages: working, didAdjust: false };
|
||||
}
|
||||
|
||||
export function buildExternalBridgeContext(
|
||||
messages: ChatMessage[],
|
||||
): ExternalBridgeHistoryMessage[] {
|
||||
return buildExternalBridgeContextMessages(messages);
|
||||
}
|
||||
|
||||
export function extractLatestUserGoal(messages: ModelMessage[] | ChatMessage[]): string | undefined {
|
||||
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||
const message = messages[i];
|
||||
if (message.role !== 'user') continue;
|
||||
const content = typeof message.content === 'string'
|
||||
? message.content.trim()
|
||||
: '';
|
||||
if (content && !content.startsWith('[Netcatty session context')) return content.slice(0, 500);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function applyStepBudgetGuard(
|
||||
messages: ModelMessage[],
|
||||
input: {
|
||||
contextWindow: number;
|
||||
reservedTokens: number;
|
||||
maxOutputTokens: number;
|
||||
providerId?: string | null;
|
||||
protectRecentMessages: number;
|
||||
},
|
||||
): { messages: ModelMessage[]; didAdjust: boolean; didTypedCompression: boolean } {
|
||||
const threshold = computeCompactionThreshold({
|
||||
contextWindow: input.contextWindow,
|
||||
maxOutputTokens: input.maxOutputTokens,
|
||||
});
|
||||
const total = computeTotalInputTokens({
|
||||
messages,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
if (total < threshold) {
|
||||
return { messages, didAdjust: false, didTypedCompression: false };
|
||||
}
|
||||
|
||||
const splitAt = findSafeCompactionSplitIndex(messages, input.protectRecentMessages);
|
||||
const head = messages.slice(0, splitAt);
|
||||
const tail = messages.slice(splitAt);
|
||||
const compressedHead = applyTypedMessageCompression(head);
|
||||
let next = [...compressedHead.messages, ...tail];
|
||||
let didAdjust = compressedHead.didAdjust;
|
||||
|
||||
let afterTotal = computeTotalInputTokens({
|
||||
messages: next,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
if (afterTotal >= threshold && splitAt > 0) {
|
||||
next = keepRecentContextMessages(next, input.protectRecentMessages);
|
||||
didAdjust = true;
|
||||
afterTotal = computeTotalInputTokens({
|
||||
messages: next,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
}
|
||||
while (afterTotal >= threshold && next.length > 2) {
|
||||
const pruned = pruneFirstModelMessage(next);
|
||||
if (pruned.length === next.length) break;
|
||||
next = pruned;
|
||||
didAdjust = true;
|
||||
afterTotal = computeTotalInputTokens({
|
||||
messages: next,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
messages: next,
|
||||
didAdjust: didAdjust || next.length !== messages.length,
|
||||
didTypedCompression: compressedHead.didAdjust,
|
||||
};
|
||||
}
|
||||
|
||||
/** Step-level typed pruning — no LLM summarize (reserved for pre-turn / 413). */
|
||||
export async function prepareStepContext(
|
||||
input: PrepareStepContextInput,
|
||||
): Promise<ContextPrepareResult & { runtimeContext?: import('./cattyRuntimeContext').CattyRuntimeContext }> {
|
||||
const contextWindow = input.contextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS;
|
||||
const maxOutputTokens = input.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
||||
const protectRecent = input.protectRecentMessages ?? DEFAULT_PROTECT_RECENT_MESSAGES;
|
||||
const messagesBeforeCount = input.messages.length;
|
||||
const integrity = repairToolMessageIntegrity(input.messages);
|
||||
|
||||
const underBudgetPressure = isContextUnderBudgetPressure({
|
||||
messages: integrity.messages,
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
reservedTokens: input.reservedTokens,
|
||||
});
|
||||
const stale = pruneStaleToolContext(integrity.messages, {
|
||||
underBudgetPressure,
|
||||
});
|
||||
let working = stale.messages;
|
||||
|
||||
const typed = compressMessagesForRequestTooLargeRetry(working);
|
||||
working = typed.messages;
|
||||
|
||||
const pendingHandles = input.toolOutputStore?.listPendingHandles(input.chatSessionId ?? input.sessionId) ?? [];
|
||||
let didHandleNotice = false;
|
||||
if (pendingHandles.length > 0 && input.stepNumber > 0) {
|
||||
working = working.filter((message) => {
|
||||
if (message.role !== 'user') return true;
|
||||
const content = typeof message.content === 'string' ? message.content : '';
|
||||
return !isStepHandleNoticeMessage(content);
|
||||
});
|
||||
const notice: ModelMessage = {
|
||||
role: 'user',
|
||||
content: `[step ${input.stepNumber}] Tool output handles available: ${pendingHandles.map(h => h.id).join(', ')}`,
|
||||
};
|
||||
working = [notice, ...working];
|
||||
didHandleNotice = true;
|
||||
}
|
||||
|
||||
const budgetGuard = applyStepBudgetGuard(working, {
|
||||
contextWindow,
|
||||
reservedTokens: input.reservedTokens ?? 0,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
protectRecentMessages: protectRecent,
|
||||
});
|
||||
working = budgetGuard.messages;
|
||||
if (didHandleNotice && pendingHandles.length > 0) {
|
||||
const hasHandleNotice = working.some(
|
||||
(message) => message.role === 'user' && isStepHandleNoticeMessage(message.content),
|
||||
);
|
||||
if (!hasHandleNotice) {
|
||||
working = [{
|
||||
role: 'user',
|
||||
content: `[step ${input.stepNumber}] Tool output handles available: ${pendingHandles.map(h => h.id).join(', ')}`,
|
||||
}, ...working];
|
||||
}
|
||||
}
|
||||
if (didHandleNotice) {
|
||||
const notices = working.filter(
|
||||
message => message.role === 'user' && isStepHandleNoticeMessage(message.content),
|
||||
);
|
||||
const body = working.filter(
|
||||
message => !(message.role === 'user' && isStepHandleNoticeMessage(message.content)),
|
||||
);
|
||||
const noticeTokens = estimateModelMessagesTokensWithKind({
|
||||
messages: notices,
|
||||
providerId: input.providerId,
|
||||
}).tokens;
|
||||
const finalGuard = applyStepBudgetGuard(body, {
|
||||
contextWindow,
|
||||
reservedTokens: (input.reservedTokens ?? 0) + noticeTokens,
|
||||
maxOutputTokens,
|
||||
providerId: input.providerId,
|
||||
protectRecentMessages: protectRecent,
|
||||
});
|
||||
working = [...notices, ...finalGuard.messages];
|
||||
budgetGuard.didAdjust = budgetGuard.didAdjust || finalGuard.didAdjust;
|
||||
}
|
||||
const didBudgetAdjust = integrity.didAdjust || stale.didAdjust || typed.didAdjust || budgetGuard.didAdjust;
|
||||
const didAdjust = didBudgetAdjust || didHandleNotice;
|
||||
|
||||
const before = estimateModelMessagesTokensWithKind({
|
||||
messages: input.messages,
|
||||
providerId: input.providerId,
|
||||
});
|
||||
const after = estimateModelMessagesTokensWithKind({
|
||||
messages: working,
|
||||
providerId: input.providerId,
|
||||
});
|
||||
|
||||
const trace = didBudgetAdjust ? buildCompactionTrace({
|
||||
trigger: 'step',
|
||||
tokensBefore: before.tokens,
|
||||
tokensAfter: after.tokens,
|
||||
messagesBefore: messagesBeforeCount,
|
||||
messagesAfter: working.length,
|
||||
compressedMessageCount: Math.max(0, messagesBeforeCount - working.length),
|
||||
retainedTailCount: working.length,
|
||||
didTypedCompression: typed.didAdjust || budgetGuard.didTypedCompression,
|
||||
didLlmSummarize: false,
|
||||
did413Fallback: false,
|
||||
estimatorKind: before.estimatorKind,
|
||||
}) : undefined;
|
||||
|
||||
if (trace && didBudgetAdjust && input.onEvent && input.sessionId) {
|
||||
emitCompactionStart(input.onEvent, {
|
||||
sessionId: input.sessionId,
|
||||
chatSessionId: input.chatSessionId,
|
||||
backend: 'catty',
|
||||
}, 'step');
|
||||
emitCompactionEvent(input.onEvent, {
|
||||
sessionId: input.sessionId,
|
||||
chatSessionId: input.chatSessionId,
|
||||
backend: 'catty',
|
||||
}, trace);
|
||||
}
|
||||
|
||||
const runtimeContext = {
|
||||
...input.runtimeContext,
|
||||
...(trace ? { lastCompaction: trace, lastStepAdjusted: didBudgetAdjust } : {}),
|
||||
...(didAdjust ? { lastStepAdjusted: true } : {}),
|
||||
};
|
||||
|
||||
return {
|
||||
messages: working,
|
||||
didAdjust,
|
||||
trace,
|
||||
runtimeContext,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user