@archships/dim-plugin-auto-compact
Advanced tools
+6
-3
@@ -1,2 +0,2 @@ | ||
| import { ModelRef } from "@archships/dim-agent-sdk"; | ||
| import { ModelRef, SessionCompactorController } from "@archships/dim-agent-sdk"; | ||
| import { DimPlugin } from "@archships/dim-plugin-api"; | ||
@@ -11,3 +11,2 @@ | ||
| retainMessages?: number; | ||
| maxStateEntries?: number; | ||
| compaction?: { | ||
@@ -19,5 +18,9 @@ auto?: boolean; | ||
| } | ||
| declare function createAutoCompactPlugin(options?: AutoCompactPluginOptions): DimPlugin; | ||
| declare function createAutoCompactPlugin(): DimPlugin<'auto-compact', SessionCompactorController>; | ||
| declare function createAutoCompactPlugin(options: AutoCompactPluginOptions): DimPlugin<'auto-compact', SessionCompactorController>; | ||
| declare function createAutoCompactPlugin<TId extends string>(options: AutoCompactPluginOptions & { | ||
| id: TId; | ||
| }): DimPlugin<TId, SessionCompactorController>; | ||
| //#endregion | ||
| export { AutoCompactPluginOptions, createAutoCompactPlugin }; | ||
| //# sourceMappingURL=index.d.ts.map |
+215
-484
@@ -5,3 +5,2 @@ //#region src/index.ts | ||
| const DEFAULT_RETAIN_MESSAGES = 4; | ||
| const DEFAULT_MAX_STATE_ENTRIES = 5; | ||
| const DEFAULT_THRESHOLD_HEADROOM = 1024; | ||
@@ -16,4 +15,2 @@ const DEFAULT_RECENT_TOOL_OUTPUTS_TO_PRESERVE = 2; | ||
| const CONTINUATION_SUMMARY_TAG_FRAGMENT_PATTERN = /<\/?continuation_summary>?/g; | ||
| const SUMMARY_PROMPT_VERSION = "continuation-summary-v2"; | ||
| const COMPACTION_INJECTION_METADATA_KEY = "_dimCompactionInjection"; | ||
| const DEFAULT_SUMMARY_PROMPT = `You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include: | ||
@@ -50,7 +47,7 @@ | ||
| function createAutoCompactPlugin(options = {}) { | ||
| const normalized = normalizeOptions(options); | ||
| const resolved = resolveOptions(options); | ||
| return { | ||
| manifest: { | ||
| id: "auto-compact", | ||
| version: "0.1.0", | ||
| id: options.id ?? "auto-compact", | ||
| version: "0.2.0", | ||
| apiVersion: 1, | ||
@@ -64,15 +61,14 @@ permissions: { model: true }, | ||
| getConfig: async () => ({ | ||
| summaryModel: normalized.summaryModel ? { | ||
| provider: normalized.summaryModel.provider, | ||
| modelId: normalized.summaryModel.modelId | ||
| summaryModel: resolved.summaryModel ? { | ||
| provider: resolved.summaryModel.provider, | ||
| modelId: resolved.summaryModel.modelId | ||
| } : null, | ||
| maxSummaryTokens: normalized.maxSummaryTokens, | ||
| summaryInputMaxTokens: normalized.summaryInputMaxTokens, | ||
| summaryPrompt: normalized.summaryPrompt, | ||
| retainMessages: normalized.retainMessages, | ||
| maxStateEntries: normalized.maxStateEntries, | ||
| maxSummaryTokens: resolved.maxSummaryTokens, | ||
| summaryInputMaxTokens: resolved.summaryInputMaxTokens, | ||
| summaryPrompt: resolved.summaryPrompt, | ||
| retainMessages: resolved.retainMessages, | ||
| compaction: { | ||
| auto: normalized.compaction.auto, | ||
| prune: normalized.compaction.prune, | ||
| reserved: normalized.compaction.reserved | ||
| auto: resolved.compaction.auto, | ||
| prune: resolved.compaction.prune, | ||
| reserved: resolved.compaction.reserved | ||
| } | ||
@@ -82,211 +78,8 @@ }), | ||
| }, | ||
| hooks: [{ | ||
| descriptor: { name: "context.compact.before" }, | ||
| middleware: [async ({ payload, context }) => { | ||
| if (!context.sessionId) return; | ||
| if (payload.trigger === "threshold" && !normalized.compaction.auto) return; | ||
| const baseAttempt = createAttemptDiagnostics(payload, Date.now()); | ||
| const previous = readPluginState(await context.services.pluginState.get(context.sessionId)); | ||
| await persistPluginState(context.sessionId, context, { | ||
| ...previous, | ||
| lastAttempt: baseAttempt | ||
| }); | ||
| const summaryModel = normalized.summaryModel ?? context.status?.model; | ||
| const usageKind = payload.trigger === "manual" ? "manual_compaction" : "auto_compaction"; | ||
| if (!summaryModel) { | ||
| await persistPluginState(context.sessionId, context, { | ||
| ...previous, | ||
| lastAttempt: finalizeAttemptDiagnostics(baseAttempt, { | ||
| status: "skipped", | ||
| reasonCode: "missing_summary_model" | ||
| }) | ||
| }); | ||
| return; | ||
| } | ||
| const environmentText = buildAutoCompactEnvironmentMessage(context.status); | ||
| const plan = planCompaction(payload.messages, payload.cursor, normalized, Math.max(0, payload.contextWindow - payload.plannedOutput), payload.thresholdTokens, environmentText); | ||
| if (!plan) { | ||
| const stickyFailure = previous.lastAttempt?.status === "failed" && previous.lastAttempt.reasonCode === "post_summary_over_budget"; | ||
| await persistPluginState(context.sessionId, context, { | ||
| ...previous, | ||
| lastAttempt: stickyFailure ? { | ||
| ...previous.lastAttempt, | ||
| trigger: baseAttempt.trigger, | ||
| estimatedInputTokens: baseAttempt.estimatedInputTokens, | ||
| thresholdTokens: baseAttempt.thresholdTokens, | ||
| maxInputTokens: baseAttempt.maxInputTokens, | ||
| cursor: baseAttempt.cursor, | ||
| startedAt: baseAttempt.startedAt, | ||
| completedAt: Date.now(), | ||
| status: "failed", | ||
| reasonCode: "post_summary_over_budget", | ||
| reasonMessage: previous.lastAttempt?.reasonMessage ?? "Compaction summary was applied but the projected request is still over budget" | ||
| } : finalizeAttemptDiagnostics(baseAttempt, { | ||
| status: "skipped", | ||
| reasonCode: "nothing_to_compact" | ||
| }) | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| const summaryResult = await generateSummary({ | ||
| contextStatus: context.status, | ||
| summaryModel, | ||
| maxSummaryTokens: normalized.maxSummaryTokens, | ||
| summaryInputMaxTokens: normalized.summaryInputMaxTokens, | ||
| summaryPrompt: normalized.summaryPrompt, | ||
| existingSummary: payload.systemSegments[0], | ||
| systemSegments: payload.systemSegments, | ||
| compactedMessages: plan.compactedMessages, | ||
| pruneToolOutputs: normalized.compaction.prune, | ||
| sessionId: context.sessionId, | ||
| usageKind, | ||
| model: context.services.model, | ||
| logger: context.services.logger | ||
| }); | ||
| if (summaryResult.status === "empty_body_preserve_previous") { | ||
| await persistPluginState(context.sessionId, context, { | ||
| strategy: "handoff-v1", | ||
| summaryModel, | ||
| entries: previous.entries, | ||
| lastAttempt: finalizeAttemptDiagnostics(baseAttempt, { | ||
| status: "skipped", | ||
| reasonCode: "empty_summary_preserved_previous", | ||
| reasonMessage: "Auto compact summary normalized to an empty body and preserved the previous continuation summary", | ||
| compactedMessageCount: plan.compactedMessages.length, | ||
| retainedMessageCount: plan.retainedMessages.length | ||
| }) | ||
| }); | ||
| return; | ||
| } | ||
| if (summaryResult.status === "invalid_summary_contract") throw createCodedError("invalid_summary_contract", "Auto compact summary body cannot be empty"); | ||
| const plannedAttempt = finalizeAttemptDiagnostics(baseAttempt, { | ||
| status: "planned", | ||
| compactedMessageCount: plan.compactedMessages.length, | ||
| retainedMessageCount: plan.retainedMessages.length, | ||
| summaryNormalization: summaryResult.summaryNormalization | ||
| }); | ||
| await persistPluginState(context.sessionId, context, { | ||
| strategy: "handoff-v1", | ||
| summaryModel, | ||
| entries: previous.entries, | ||
| lastAttempt: plannedAttempt | ||
| }); | ||
| let compactionState; | ||
| try { | ||
| compactionState = await context.services.compaction.apply({ | ||
| sessionId: context.sessionId, | ||
| cursor: plan.keepStart, | ||
| systemSegments: [summaryResult.summary], | ||
| summary: summaryResult.summary, | ||
| reason: payload.trigger, | ||
| metadata: { | ||
| pluginId: "auto-compact", | ||
| promptVersion: SUMMARY_PROMPT_VERSION, | ||
| prune: normalized.compaction.prune, | ||
| reserved: normalized.compaction.reserved, | ||
| [COMPACTION_INJECTION_METADATA_KEY]: { | ||
| mode: "auto-user-context-before-anchor", | ||
| environmentText | ||
| }, | ||
| compactedMessageCount: plan.compactedMessages.length, | ||
| retainedMessageCount: plan.retainedMessages.length, | ||
| summaryInputTokens: summaryResult.summaryInputTokens, | ||
| summaryOutputTokensEstimate: summaryResult.summaryOutputTokensEstimate, | ||
| summaryChars: summaryResult.summaryChars, | ||
| summaryCallCount: summaryResult.summaryCallCount, | ||
| summaryChunkCount: summaryResult.summaryChunkCount, | ||
| summaryNormalization: summaryResult.summaryNormalization, | ||
| prunedToolOutputCount: summaryResult.prunedToolOutputCount, | ||
| preservedToolOutputCount: summaryResult.preservedToolOutputCount | ||
| } | ||
| }); | ||
| } catch (error) { | ||
| if (isCompactionOwnerError(error)) { | ||
| await persistPluginState(context.sessionId, context, { | ||
| strategy: "handoff-v1", | ||
| summaryModel, | ||
| entries: previous.entries, | ||
| lastAttempt: finalizeAttemptDiagnostics(plannedAttempt, { | ||
| status: "failed", | ||
| reasonCode: "compaction_owner_required", | ||
| reasonMessage: "Auto compact plugin requires compaction.ownerPluginId to be \"auto-compact\"" | ||
| }) | ||
| }); | ||
| const nextError = /* @__PURE__ */ new Error("Auto compact plugin requires compaction.ownerPluginId to be \"auto-compact\""); | ||
| nextError.code = "compaction_owner_required"; | ||
| throw nextError; | ||
| } | ||
| await persistPluginState(context.sessionId, context, { | ||
| strategy: "handoff-v1", | ||
| summaryModel, | ||
| entries: previous.entries, | ||
| lastAttempt: finalizeAttemptDiagnostics(plannedAttempt, { | ||
| status: "failed", | ||
| reasonCode: "compaction_apply_failed", | ||
| reasonMessage: readErrorMessage(error) | ||
| }) | ||
| }); | ||
| return; | ||
| } | ||
| const latestCheckpoint = compactionState.checkpoints.at(-1); | ||
| const nextEntry = { | ||
| checkpointId: latestCheckpoint?.id ?? `compact-${Date.now()}`, | ||
| createdAt: latestCheckpoint?.createdAt ?? Date.now() | ||
| }; | ||
| const persistedState = readPluginState(await context.services.pluginState.get(context.sessionId)); | ||
| await persistPluginState(context.sessionId, context, { | ||
| strategy: "handoff-v1", | ||
| summaryModel, | ||
| entries: [...persistedState.entries, nextEntry].slice(-normalized.maxStateEntries), | ||
| lastAttempt: persistedState.lastAttempt ? finalizeAttemptDiagnostics(persistedState.lastAttempt, { checkpointId: latestCheckpoint?.id }) : finalizeAttemptDiagnostics(plannedAttempt, { checkpointId: latestCheckpoint?.id }) | ||
| }); | ||
| } catch (error) { | ||
| if (isCompactionOwnerError(error)) throw error; | ||
| await persistPluginState(context.sessionId, context, { | ||
| ...previous, | ||
| lastAttempt: finalizeAttemptDiagnostics(baseAttempt, { | ||
| status: "failed", | ||
| reasonCode: readErrorCode(error) ?? "summary_generation_failed", | ||
| reasonMessage: readErrorMessage(error), | ||
| compactedMessageCount: plan.compactedMessages.length, | ||
| retainedMessageCount: plan.retainedMessages.length | ||
| }) | ||
| }); | ||
| return; | ||
| } | ||
| }] | ||
| }, { | ||
| descriptor: { | ||
| name: "notify.message", | ||
| when: { notificationType: "context.compacted" } | ||
| }, | ||
| observers: [async ({ payload, context }) => { | ||
| if (!context.sessionId) return; | ||
| const notificationStats = readCompactedNotificationStats(payload.notification.metadata); | ||
| if (!notificationStats || notificationStats.pluginId !== "auto-compact") return; | ||
| const previous = readPluginState(await context.services.pluginState.get(context.sessionId)); | ||
| const lastAttempt = previous.lastAttempt; | ||
| if (!lastAttempt || lastAttempt.status !== "planned" && lastAttempt.status !== "compacted") return; | ||
| if (lastAttempt.checkpointId && notificationStats.checkpointId && lastAttempt.checkpointId !== notificationStats.checkpointId) return; | ||
| const postSummaryFitsBudget = notificationStats.postSummaryFitsBudget; | ||
| await persistPluginState(context.sessionId, context, { | ||
| ...previous, | ||
| lastAttempt: finalizeAttemptDiagnostics(lastAttempt, { | ||
| status: postSummaryFitsBudget === false ? "failed" : "compacted", | ||
| ...postSummaryFitsBudget === false ? { | ||
| reasonCode: "post_summary_over_budget", | ||
| reasonMessage: "Compaction summary was applied but the projected request is still over budget" | ||
| } : {}, | ||
| ...notificationStats.checkpointId ? { checkpointId: notificationStats.checkpointId } : {}, | ||
| ...notificationStats.summaryNormalization ? { summaryNormalization: notificationStats.summaryNormalization } : {}, | ||
| ...typeof notificationStats.compactedMessageCount === "number" ? { compactedMessageCount: notificationStats.compactedMessageCount } : {}, | ||
| ...typeof notificationStats.retainedMessageCount === "number" ? { retainedMessageCount: notificationStats.retainedMessageCount } : {}, | ||
| ...typeof notificationStats.estimatedInputTokensAfter === "number" ? { estimatedInputTokensAfter: notificationStats.estimatedInputTokensAfter } : {}, | ||
| ...typeof notificationStats.estimatedSavedTokens === "number" ? { estimatedSavedTokens: notificationStats.estimatedSavedTokens } : {}, | ||
| ...typeof notificationStats.postSummaryFitsBudget === "boolean" ? { postSummaryFitsBudget: notificationStats.postSummaryFitsBudget } : {} | ||
| }) | ||
| }); | ||
| }] | ||
| }] | ||
| createSessionController: (controllerContext) => createAutoCompactController({ | ||
| options: resolved, | ||
| logger: context.services.logger, | ||
| model: context.services.model, | ||
| context: controllerContext | ||
| }) | ||
| }; | ||
@@ -296,10 +89,98 @@ } | ||
| } | ||
| function normalizeOptions(options) { | ||
| function createAutoCompactController(input) { | ||
| return { | ||
| readPlanningOptions() { | ||
| return { | ||
| auto: input.options.compaction.auto, | ||
| retainMessages: input.options.retainMessages, | ||
| reservedTokens: input.options.compaction.reserved, | ||
| summaryTokenReserve: input.options.maxSummaryTokens | ||
| }; | ||
| }, | ||
| async compact(request) { | ||
| const sessionStatus = input.context.getStatus(); | ||
| const summaryModel = input.options.summaryModel ?? sessionStatus.model; | ||
| if (!summaryModel) { | ||
| const result = { | ||
| status: "skipped", | ||
| reasonCode: "missing_summary_model", | ||
| reasonMessage: "Auto compact summary model is unavailable", | ||
| diagnostics: {} | ||
| }; | ||
| await persistPluginState(input.context, { | ||
| strategy: "controller-v2", | ||
| ...input.options.summaryModel ? { summaryModel: input.options.summaryModel } : {}, | ||
| lastResult: createPersistedResult(request, result) | ||
| }); | ||
| return result; | ||
| } | ||
| try { | ||
| const summary = await generateSummary({ | ||
| contextStatus: sessionStatus, | ||
| summaryModel, | ||
| maxSummaryTokens: input.options.maxSummaryTokens, | ||
| summaryInputMaxTokens: input.options.summaryInputMaxTokens, | ||
| summaryPrompt: input.options.summaryPrompt, | ||
| existingSystemSegments: request.plan.existingSystemSegments, | ||
| compactedMessages: request.plan.compactedMessages, | ||
| pruneToolOutputs: input.options.compaction.prune, | ||
| sessionId: input.context.sessionId, | ||
| usageKind: request.plan.trigger === "manual" ? "manual_compaction" : "auto_compaction", | ||
| model: input.model, | ||
| logger: input.logger | ||
| }); | ||
| const result = summary.status === "normalized" ? { | ||
| status: "compacted", | ||
| summary: summary.summary, | ||
| diagnostics: { | ||
| summaryNormalization: summary.summaryNormalization, | ||
| summaryInputTokens: summary.summaryInputTokens, | ||
| summaryOutputTokensEstimate: summary.summaryOutputTokensEstimate, | ||
| summaryChars: summary.summaryChars, | ||
| summaryCallCount: summary.summaryCallCount, | ||
| summaryChunkCount: summary.summaryChunkCount, | ||
| prunedToolOutputCount: summary.prunedToolOutputCount, | ||
| preservedToolOutputCount: summary.preservedToolOutputCount | ||
| } | ||
| } : summary.status === "empty_body_preserve_previous" ? { | ||
| status: "skipped", | ||
| reasonCode: "empty_summary_preserved_previous", | ||
| reasonMessage: "Summary generation produced an empty body and preserved the existing continuation summary", | ||
| diagnostics: {} | ||
| } : { | ||
| status: "failed", | ||
| reasonCode: "invalid_summary_contract", | ||
| reasonMessage: "Auto compact summary body cannot be empty", | ||
| diagnostics: {} | ||
| }; | ||
| await persistPluginState(input.context, { | ||
| strategy: "controller-v2", | ||
| summaryModel, | ||
| lastResult: createPersistedResult(request, result) | ||
| }); | ||
| return result; | ||
| } catch (error) { | ||
| const result = { | ||
| status: "failed", | ||
| reasonCode: readErrorCode(error) ?? "summary_generation_failed", | ||
| reasonMessage: readErrorMessage(error), | ||
| diagnostics: {} | ||
| }; | ||
| await persistPluginState(input.context, { | ||
| strategy: "controller-v2", | ||
| summaryModel, | ||
| lastResult: createPersistedResult(request, result) | ||
| }); | ||
| return result; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| function resolveOptions(options) { | ||
| return { | ||
| summaryModel: options.summaryModel, | ||
| maxSummaryTokens: normalizePositiveInteger(options.maxSummaryTokens, DEFAULT_MAX_SUMMARY_TOKENS), | ||
| summaryInputMaxTokens: Math.max(DEFAULT_SUMMARY_INPUT_SAFETY_MARGIN + 256, normalizePositiveInteger(options.summaryInputMaxTokens, DEFAULT_SUMMARY_INPUT_MAX_TOKENS)), | ||
| maxSummaryTokens: positiveIntegerOr(options.maxSummaryTokens, DEFAULT_MAX_SUMMARY_TOKENS), | ||
| summaryInputMaxTokens: Math.max(DEFAULT_SUMMARY_INPUT_SAFETY_MARGIN + 256, positiveIntegerOr(options.summaryInputMaxTokens, DEFAULT_SUMMARY_INPUT_MAX_TOKENS)), | ||
| summaryPrompt: options.summaryPrompt?.trim() || DEFAULT_SUMMARY_PROMPT, | ||
| retainMessages: Math.max(1, normalizePositiveInteger(options.retainMessages, DEFAULT_RETAIN_MESSAGES)), | ||
| maxStateEntries: Math.max(1, normalizePositiveInteger(options.maxStateEntries, DEFAULT_MAX_STATE_ENTRIES)), | ||
| retainMessages: Math.max(1, positiveIntegerOr(options.retainMessages, DEFAULT_RETAIN_MESSAGES)), | ||
| compaction: { | ||
@@ -312,41 +193,4 @@ auto: options.compaction?.auto ?? true, | ||
| } | ||
| function normalizePositiveInteger(value, fallback) { | ||
| if (!Number.isFinite(value) || value === void 0 || value <= 0) return fallback; | ||
| return Math.floor(value); | ||
| } | ||
| function planCompaction(messages, cursor, options, maxInputTokens, thresholdTokens, environmentText) { | ||
| const leadingSystemCount = countLeadingSystemMessages(messages); | ||
| const effectiveCursor = Math.max(cursor, leadingSystemCount); | ||
| const visibleMessages = messages.slice(effectiveCursor); | ||
| if (visibleMessages.length <= 1) return null; | ||
| const realUserIndices = findRealUserKeepStartCandidates(messages, effectiveCursor); | ||
| if (realUserIndices.length === 0) return null; | ||
| const effectiveThreshold = thresholdTokens > 0 ? thresholdTokens : maxInputTokens; | ||
| const thresholdHeadroom = effectiveThreshold > 1 ? Math.min(options.compaction.reserved, effectiveThreshold - 1) : 0; | ||
| const targetTokens = effectiveThreshold > 0 ? Math.max(1, effectiveThreshold - thresholdHeadroom) : 1; | ||
| const preferredKeepCount = Math.min(Math.max(options.retainMessages, 1), visibleMessages.length - 1); | ||
| let candidateIndex = selectInitialKeepStartCandidate(realUserIndices, messages.length - preferredKeepCount); | ||
| while (candidateIndex < realUserIndices.length) { | ||
| const keepStart = realUserIndices[candidateIndex]; | ||
| const compactedMessages = messages.slice(effectiveCursor, keepStart); | ||
| const retainedMessages = messages.slice(keepStart); | ||
| if (compactedMessages.length > 0 && estimateProjectedTokens(retainedMessages, options.maxSummaryTokens, environmentText) <= targetTokens) return { | ||
| keepStart, | ||
| compactedMessages, | ||
| retainedMessages | ||
| }; | ||
| candidateIndex += 1; | ||
| } | ||
| const keepStart = realUserIndices.at(-1); | ||
| if (keepStart === void 0) return null; | ||
| const compactedMessages = messages.slice(effectiveCursor, keepStart); | ||
| if (compactedMessages.length === 0) return null; | ||
| return { | ||
| keepStart, | ||
| compactedMessages, | ||
| retainedMessages: messages.slice(keepStart) | ||
| }; | ||
| } | ||
| async function generateSummary(input) { | ||
| const renderedInput = buildSummaryInput(input.systemSegments, input.compactedMessages, input.pruneToolOutputs, input.contextStatus); | ||
| const renderedInput = buildSummaryInput(input.existingSystemSegments, input.compactedMessages, input.pruneToolOutputs, input.contextStatus); | ||
| const summaryInputTokens = estimateSectionsTokens([...renderedInput.prefixSections, ...renderedInput.messageSections]); | ||
@@ -371,3 +215,3 @@ const summaryInputBudget = Math.max(256, input.summaryInputMaxTokens - DEFAULT_SUMMARY_INPUT_SAFETY_MARGIN); | ||
| } catch (error) { | ||
| if (readErrorCode(error) === "empty_summary_body") return input.existingSummary?.trim() ? { status: "empty_body_preserve_previous" } : { status: "invalid_summary_contract" }; | ||
| if (readErrorCode(error) === "empty_summary_body") return input.existingSystemSegments.join("\n\n").trim() ? { status: "empty_body_preserve_previous" } : { status: "invalid_summary_contract" }; | ||
| throw error; | ||
@@ -388,3 +232,3 @@ } | ||
| } | ||
| function buildSummaryInput(systemSegments, compactedMessages, pruneToolOutputs, status) { | ||
| function buildSummaryInput(existingSystemSegments, compactedMessages, pruneToolOutputs, status) { | ||
| const preservedToolOutputIds = buildPreservedToolOutputIds(compactedMessages, pruneToolOutputs); | ||
@@ -401,3 +245,3 @@ let prunedToolOutputCount = 0; | ||
| ].join("\n") : "- unavailable"].join("\n")), | ||
| createRenderedSummarySection("existing-summary", ["Existing compaction summary:", systemSegments.length > 0 ? systemSegments.join("\n\n") : "(none)"].join("\n")), | ||
| createRenderedSummarySection("existing-summary", ["Existing compaction summary:", existingSystemSegments.length > 0 ? existingSystemSegments.join("\n\n") : "(none)"].join("\n")), | ||
| createRenderedSummarySection("messages-heading", "Messages to compact:") | ||
@@ -418,3 +262,3 @@ ], | ||
| if (estimateTextTokens(combinedText) <= input.summaryInputBudget) { | ||
| const normalizedSummary = await streamSummaryText({ | ||
| const canonical = await streamSummaryText({ | ||
| sessionId: input.sessionId, | ||
@@ -431,4 +275,4 @@ summaryModel: input.summaryModel, | ||
| return { | ||
| summary: normalizedSummary.summary, | ||
| summaryNormalization: normalizedSummary.normalization, | ||
| summary: canonical.summary, | ||
| summaryNormalization: canonical.normalization, | ||
| summaryCallCount: 1, | ||
@@ -445,3 +289,3 @@ summaryChunkCount: 0 | ||
| for (const [index, chunk] of chunks.entries()) { | ||
| const normalizedSummary = await streamSummaryText({ | ||
| const canonical = await streamSummaryText({ | ||
| sessionId: input.sessionId, | ||
@@ -460,4 +304,4 @@ summaryModel: input.summaryModel, | ||
| summaryCallCount += 1; | ||
| summaryNormalization = combineSummaryNormalizationModes(summaryNormalization, normalizedSummary.normalization); | ||
| chunkSummarySections.push(createRenderedSummarySection(`chunk-summary-${index + 1}`, `Chunk summary ${index + 1} of ${chunks.length}\n${normalizedSummary.summary}`)); | ||
| summaryNormalization = mergeSummaryNormalizationModes(summaryNormalization, canonical.normalization); | ||
| chunkSummarySections.push(createRenderedSummarySection(`chunk-summary-${index + 1}`, `Chunk summary ${index + 1} of ${chunks.length}\n${canonical.summary}`)); | ||
| } | ||
@@ -472,3 +316,3 @@ const merged = await summarizeRenderedSections({ | ||
| summary: merged.summary, | ||
| summaryNormalization: combineSummaryNormalizationModes(summaryNormalization, merged.summaryNormalization), | ||
| summaryNormalization: mergeSummaryNormalizationModes(summaryNormalization, merged.summaryNormalization), | ||
| summaryCallCount: summaryCallCount + merged.summaryCallCount, | ||
@@ -500,5 +344,5 @@ summaryChunkCount: chunks.length + merged.summaryChunkCount | ||
| if (!text.trim()) throw createCodedError("empty_summary_body", "Auto compact summary body cannot be empty"); | ||
| const normalizedSummary = normalizeContinuationSummary(text); | ||
| if (normalizedSummary.status === "empty_body") throw createCodedError("empty_summary_body", "Auto compact summary body cannot be empty"); | ||
| if (normalizedSummary.normalization !== "plain_text") input.logger.emit({ | ||
| const canonical = canonicalizeContinuationSummary(text); | ||
| if (canonical.status === "empty_body") throw createCodedError("empty_summary_body", "Auto compact summary body cannot be empty"); | ||
| if (canonical.normalization !== "plain_text") input.logger.emit({ | ||
| level: "warn", | ||
@@ -510,3 +354,3 @@ source: "auto-compact", | ||
| summaryKind: input.summaryKind, | ||
| summaryNormalization: normalizedSummary.normalization, | ||
| summaryNormalization: canonical.normalization, | ||
| ...typeof input.chunkIndex === "number" ? { chunkIndex: input.chunkIndex } : {}, | ||
@@ -516,3 +360,3 @@ ...typeof input.chunkCount === "number" ? { chunkCount: input.chunkCount } : {} | ||
| }); | ||
| return normalizedSummary; | ||
| return canonical; | ||
| } | ||
@@ -602,62 +446,5 @@ function renderMessage(message, pruneToolOutputs, preservedToolOutputIds) { | ||
| } | ||
| function estimateProjectedTokens(messages, maxSummaryTokens, environmentText) { | ||
| return Math.max(1, estimateMessagesTokens(messages) + maxSummaryTokens + (environmentText ? estimateTextTokens(environmentText) + 8 : 0) + 32); | ||
| } | ||
| function estimateMessagesTokens(messages) { | ||
| return messages.reduce((total, message) => total + estimateMessageTokens(message), 0); | ||
| } | ||
| function estimateMessageTokens(message) { | ||
| const metadataChars = message.metadata ? JSON.stringify(message.metadata).length : 0; | ||
| const roleChars = message.role.length; | ||
| const contentChars = renderMessageText(message).length; | ||
| const structuredChars = message.role === "tool" && message.structuredContent ? JSON.stringify(message.structuredContent).length : 0; | ||
| const thinkingChars = "thinking" in message && typeof message.thinking === "string" ? message.thinking.length : 0; | ||
| return Math.max(1, Math.ceil((roleChars + metadataChars + contentChars + structuredChars + thinkingChars) / 4)); | ||
| } | ||
| function estimateTextTokens(text) { | ||
| return Math.max(1, Math.ceil(text.length / 4)); | ||
| } | ||
| function buildAutoCompactEnvironmentMessage(status) { | ||
| const model = status ? `${status.model.provider}/${status.model.modelId}` : "(unknown)"; | ||
| const cwd = status?.cwd ?? "(unknown)"; | ||
| const messageCount = status?.messageCount ?? 0; | ||
| const compactionCursor = status?.compaction.cursor ?? 0; | ||
| return [ | ||
| "[auto compact context]", | ||
| `cwd: ${cwd}`, | ||
| `model: ${model}`, | ||
| `messages: ${messageCount}`, | ||
| `cursor: ${compactionCursor}`, | ||
| "note: The next user message is the retained anchor query; older history is summarized below." | ||
| ].join("\n"); | ||
| } | ||
| function countLeadingSystemMessages(messages) { | ||
| let count = 0; | ||
| for (const message of messages) { | ||
| if (message.role !== "system") break; | ||
| count += 1; | ||
| } | ||
| return count; | ||
| } | ||
| function findRealUserKeepStartCandidates(messages, effectiveCursor) { | ||
| const candidates = []; | ||
| for (let index = effectiveCursor + 1; index < messages.length; index += 1) if (isRealUserMessage(messages[index])) candidates.push(index); | ||
| return candidates; | ||
| } | ||
| function selectInitialKeepStartCandidate(candidates, preferredStart) { | ||
| const preferredIndex = candidates.findIndex((candidate) => candidate >= preferredStart); | ||
| return preferredIndex >= 0 ? preferredIndex : Math.max(0, candidates.length - 1); | ||
| } | ||
| function isRealUserMessage(message) { | ||
| if (!message || message.role !== "user") return false; | ||
| return !isSyntheticUserWrapper(message.metadata); | ||
| } | ||
| function isSyntheticUserWrapper(metadata) { | ||
| if (!isRecord(metadata)) return false; | ||
| if (isRecord(metadata._subagentResult)) return true; | ||
| if (isRecord(metadata._dimToolContext)) return true; | ||
| const overflowCompacted = metadata._dimOverflowCompacted; | ||
| if (!isRecord(overflowCompacted)) return false; | ||
| return overflowCompacted.kind === "subagent_parent_commit"; | ||
| } | ||
| function createSyntheticMessage(role, text) { | ||
@@ -674,24 +461,3 @@ return { | ||
| } | ||
| function createAttemptDiagnostics(payload, startedAt) { | ||
| return { | ||
| trigger: payload.trigger, | ||
| status: "planned", | ||
| estimatedInputTokens: payload.estimatedInputTokens, | ||
| thresholdTokens: payload.thresholdTokens, | ||
| maxInputTokens: Math.max(0, payload.contextWindow - payload.plannedOutput), | ||
| cursor: payload.cursor, | ||
| startedAt | ||
| }; | ||
| } | ||
| function finalizeAttemptDiagnostics(attempt, input) { | ||
| return { | ||
| ...attempt, | ||
| ...input, | ||
| ...input.status && input.status !== "planned" ? { completedAt: input.completedAt ?? Date.now() } : input.completedAt !== void 0 ? { completedAt: input.completedAt } : {} | ||
| }; | ||
| } | ||
| function isRecord(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| function normalizeContinuationSummary(summary) { | ||
| function canonicalizeContinuationSummary(summary) { | ||
| const trimmed = summary.trim(); | ||
@@ -723,3 +489,3 @@ if (!trimmed) return { status: "empty_body" }; | ||
| } | ||
| function combineSummaryNormalizationModes(left, right) { | ||
| function mergeSummaryNormalizationModes(left, right) { | ||
| const rank = { | ||
@@ -732,10 +498,17 @@ plain_text: 0, | ||
| } | ||
| function createCodedError(code, message) { | ||
| const error = new Error(message); | ||
| error.code = code; | ||
| return error; | ||
| async function persistPluginState(context, state) { | ||
| await context.pluginState.replace(context.sessionId, { | ||
| version: 2, | ||
| data: serializePluginState(state), | ||
| updatedAt: Date.now() | ||
| }); | ||
| } | ||
| async function persistPluginState(sessionId, context, state) { | ||
| const data = { | ||
| strategy: "handoff-v1", | ||
| function readPluginState(entry) { | ||
| if (!entry || entry.version !== 2) return { strategy: "controller-v2" }; | ||
| if (!isAutoCompactPluginStateData(entry.data)) return { strategy: "controller-v2" }; | ||
| return structuredClone(entry.data); | ||
| } | ||
| function serializePluginState(state) { | ||
| return { | ||
| strategy: state.strategy, | ||
| ...state.summaryModel ? { summaryModel: { | ||
@@ -745,123 +518,84 @@ provider: state.summaryModel.provider, | ||
| } } : {}, | ||
| entries: state.entries.map((entry) => ({ | ||
| checkpointId: entry.checkpointId, | ||
| createdAt: entry.createdAt | ||
| })), | ||
| ...state.lastAttempt ? { lastAttempt: serializeAttemptDiagnostics(state.lastAttempt) } : {} | ||
| ...state.lastResult ? { lastResult: { | ||
| trigger: state.lastResult.trigger, | ||
| status: state.lastResult.status, | ||
| compactedMessageCount: state.lastResult.compactedMessageCount, | ||
| retainedMessageCount: state.lastResult.retainedMessageCount, | ||
| updatedAt: state.lastResult.updatedAt, | ||
| ...state.lastResult.reasonCode ? { reasonCode: state.lastResult.reasonCode } : {}, | ||
| ...state.lastResult.reasonMessage ? { reasonMessage: state.lastResult.reasonMessage } : {}, | ||
| ...state.lastResult.summaryNormalization ? { summaryNormalization: state.lastResult.summaryNormalization } : {}, | ||
| ...typeof state.lastResult.summaryInputTokens === "number" ? { summaryInputTokens: state.lastResult.summaryInputTokens } : {}, | ||
| ...typeof state.lastResult.summaryOutputTokensEstimate === "number" ? { summaryOutputTokensEstimate: state.lastResult.summaryOutputTokensEstimate } : {}, | ||
| ...typeof state.lastResult.summaryChars === "number" ? { summaryChars: state.lastResult.summaryChars } : {}, | ||
| ...typeof state.lastResult.summaryCallCount === "number" ? { summaryCallCount: state.lastResult.summaryCallCount } : {}, | ||
| ...typeof state.lastResult.summaryChunkCount === "number" ? { summaryChunkCount: state.lastResult.summaryChunkCount } : {}, | ||
| ...typeof state.lastResult.prunedToolOutputCount === "number" ? { prunedToolOutputCount: state.lastResult.prunedToolOutputCount } : {}, | ||
| ...typeof state.lastResult.preservedToolOutputCount === "number" ? { preservedToolOutputCount: state.lastResult.preservedToolOutputCount } : {} | ||
| } } : {} | ||
| }; | ||
| await context.services.pluginState.replace(sessionId, { | ||
| version: 1, | ||
| data, | ||
| updatedAt: Date.now() | ||
| }); | ||
| } | ||
| function readPluginState(entry) { | ||
| if (!entry || typeof entry.data !== "object" || entry.data === null) return { | ||
| strategy: "handoff-v1", | ||
| entries: [] | ||
| }; | ||
| const strategy = entry.data.strategy === "handoff-v1" ? "handoff-v1" : "handoff-v1"; | ||
| const summaryModel = readModelRef(entry.data.summaryModel); | ||
| const entries = Array.isArray(entry.data.entries) ? entry.data.entries.flatMap((value) => { | ||
| if (!value || typeof value !== "object" || Array.isArray(value)) return []; | ||
| const candidate = value; | ||
| const checkpointId = typeof candidate.checkpointId === "string" ? candidate.checkpointId : void 0; | ||
| const createdAt = typeof candidate.createdAt === "number" ? candidate.createdAt : void 0; | ||
| if (!checkpointId || !createdAt) return []; | ||
| return [{ | ||
| checkpointId, | ||
| createdAt | ||
| }]; | ||
| }) : []; | ||
| const lastAttempt = readAttemptDiagnostics(entry.data.lastAttempt); | ||
| function createPersistedResult(request, result) { | ||
| return { | ||
| strategy, | ||
| summaryModel, | ||
| entries, | ||
| ...lastAttempt ? { lastAttempt } : {} | ||
| trigger: request.plan.trigger, | ||
| status: result.status, | ||
| compactedMessageCount: request.plan.compactedMessages.length, | ||
| retainedMessageCount: request.plan.retainedMessages.length, | ||
| updatedAt: Date.now(), | ||
| ...result.reasonCode ? { reasonCode: result.reasonCode } : {}, | ||
| ...result.reasonMessage ? { reasonMessage: result.reasonMessage } : {}, | ||
| ...result.diagnostics.summaryNormalization ? { summaryNormalization: result.diagnostics.summaryNormalization } : {}, | ||
| ...typeof result.diagnostics.summaryInputTokens === "number" ? { summaryInputTokens: result.diagnostics.summaryInputTokens } : {}, | ||
| ...typeof result.diagnostics.summaryOutputTokensEstimate === "number" ? { summaryOutputTokensEstimate: result.diagnostics.summaryOutputTokensEstimate } : {}, | ||
| ...typeof result.diagnostics.summaryChars === "number" ? { summaryChars: result.diagnostics.summaryChars } : {}, | ||
| ...typeof result.diagnostics.summaryCallCount === "number" ? { summaryCallCount: result.diagnostics.summaryCallCount } : {}, | ||
| ...typeof result.diagnostics.summaryChunkCount === "number" ? { summaryChunkCount: result.diagnostics.summaryChunkCount } : {}, | ||
| ...typeof result.diagnostics.prunedToolOutputCount === "number" ? { prunedToolOutputCount: result.diagnostics.prunedToolOutputCount } : {}, | ||
| ...typeof result.diagnostics.preservedToolOutputCount === "number" ? { preservedToolOutputCount: result.diagnostics.preservedToolOutputCount } : {} | ||
| }; | ||
| } | ||
| function readModelRef(value) { | ||
| if (!value || typeof value !== "object" || Array.isArray(value)) return void 0; | ||
| const candidate = value; | ||
| if (typeof candidate.provider !== "string" || typeof candidate.modelId !== "string") return void 0; | ||
| return { | ||
| provider: candidate.provider, | ||
| modelId: candidate.modelId | ||
| }; | ||
| function positiveIntegerOr(value, fallback) { | ||
| if (!Number.isFinite(value) || value === void 0 || value <= 0) return fallback; | ||
| return Math.floor(value); | ||
| } | ||
| function readSummaryNormalizationMode(value) { | ||
| if (value === "plain_text" || value === "wrapped_block" || value === "extracted_block") return value; | ||
| if (value === "strict") return "wrapped_block"; | ||
| if (value === "wrapped_plain_text") return "plain_text"; | ||
| function isAutoCompactPluginStateData(value) { | ||
| if (!isRecord(value)) return false; | ||
| if (value.strategy !== "controller-v2") return false; | ||
| if (value.summaryModel !== void 0 && !isModelRef(value.summaryModel)) return false; | ||
| if (value.lastResult !== void 0 && !isAutoCompactPersistedResult(value.lastResult)) return false; | ||
| return true; | ||
| } | ||
| function readAttemptDiagnostics(value) { | ||
| if (!value || typeof value !== "object" || Array.isArray(value)) return void 0; | ||
| const candidate = value; | ||
| const trigger = candidate.trigger === "manual" || candidate.trigger === "threshold" ? candidate.trigger : void 0; | ||
| const status = candidate.status === "planned" || candidate.status === "skipped" || candidate.status === "compacted" || candidate.status === "failed" ? candidate.status : void 0; | ||
| const estimatedInputTokens = typeof candidate.estimatedInputTokens === "number" ? candidate.estimatedInputTokens : void 0; | ||
| const thresholdTokens = typeof candidate.thresholdTokens === "number" ? candidate.thresholdTokens : void 0; | ||
| const maxInputTokens = typeof candidate.maxInputTokens === "number" ? candidate.maxInputTokens : void 0; | ||
| const cursor = typeof candidate.cursor === "number" ? candidate.cursor : void 0; | ||
| const startedAt = typeof candidate.startedAt === "number" ? candidate.startedAt : void 0; | ||
| if (trigger === void 0 || status === void 0 || estimatedInputTokens === void 0 || thresholdTokens === void 0 || maxInputTokens === void 0 || cursor === void 0 || startedAt === void 0) return void 0; | ||
| return { | ||
| trigger, | ||
| status, | ||
| estimatedInputTokens, | ||
| thresholdTokens, | ||
| maxInputTokens, | ||
| cursor, | ||
| startedAt, | ||
| ...typeof candidate.reasonCode === "string" ? { reasonCode: candidate.reasonCode } : {}, | ||
| ...typeof candidate.reasonMessage === "string" ? { reasonMessage: candidate.reasonMessage } : {}, | ||
| ...readSummaryNormalizationMode(candidate.summaryNormalization) ? { summaryNormalization: readSummaryNormalizationMode(candidate.summaryNormalization) } : {}, | ||
| ...typeof candidate.completedAt === "number" ? { completedAt: candidate.completedAt } : {}, | ||
| ...typeof candidate.checkpointId === "string" ? { checkpointId: candidate.checkpointId } : {}, | ||
| ...typeof candidate.compactedMessageCount === "number" ? { compactedMessageCount: candidate.compactedMessageCount } : {}, | ||
| ...typeof candidate.retainedMessageCount === "number" ? { retainedMessageCount: candidate.retainedMessageCount } : {}, | ||
| ...typeof candidate.estimatedInputTokensAfter === "number" ? { estimatedInputTokensAfter: candidate.estimatedInputTokensAfter } : {}, | ||
| ...typeof candidate.estimatedSavedTokens === "number" ? { estimatedSavedTokens: candidate.estimatedSavedTokens } : {}, | ||
| ...typeof candidate.postSummaryFitsBudget === "boolean" ? { postSummaryFitsBudget: candidate.postSummaryFitsBudget } : {} | ||
| }; | ||
| function isAutoCompactPersistedResult(value) { | ||
| if (!isRecord(value)) return false; | ||
| if (value.trigger !== "manual" && value.trigger !== "threshold") return false; | ||
| if (value.status !== "compacted" && value.status !== "skipped" && value.status !== "failed") return false; | ||
| if (typeof value.compactedMessageCount !== "number") return false; | ||
| if (typeof value.retainedMessageCount !== "number") return false; | ||
| if (typeof value.updatedAt !== "number") return false; | ||
| if (value.reasonCode !== void 0 && typeof value.reasonCode !== "string") return false; | ||
| if (value.reasonMessage !== void 0 && typeof value.reasonMessage !== "string") return false; | ||
| if (value.summaryNormalization !== void 0 && value.summaryNormalization !== "plain_text" && value.summaryNormalization !== "wrapped_block" && value.summaryNormalization !== "extracted_block") return false; | ||
| if (value.summaryInputTokens !== void 0 && typeof value.summaryInputTokens !== "number") return false; | ||
| if (value.summaryOutputTokensEstimate !== void 0 && typeof value.summaryOutputTokensEstimate !== "number") return false; | ||
| if (value.summaryChars !== void 0 && typeof value.summaryChars !== "number") return false; | ||
| if (value.summaryCallCount !== void 0 && typeof value.summaryCallCount !== "number") return false; | ||
| if (value.summaryChunkCount !== void 0 && typeof value.summaryChunkCount !== "number") return false; | ||
| if (value.prunedToolOutputCount !== void 0 && typeof value.prunedToolOutputCount !== "number") return false; | ||
| if (value.preservedToolOutputCount !== void 0 && typeof value.preservedToolOutputCount !== "number") return false; | ||
| return true; | ||
| } | ||
| function serializeAttemptDiagnostics(attempt) { | ||
| return { | ||
| trigger: attempt.trigger, | ||
| status: attempt.status, | ||
| estimatedInputTokens: attempt.estimatedInputTokens, | ||
| thresholdTokens: attempt.thresholdTokens, | ||
| maxInputTokens: attempt.maxInputTokens, | ||
| cursor: attempt.cursor, | ||
| startedAt: attempt.startedAt, | ||
| ...typeof attempt.reasonCode === "string" ? { reasonCode: attempt.reasonCode } : {}, | ||
| ...typeof attempt.reasonMessage === "string" ? { reasonMessage: attempt.reasonMessage } : {}, | ||
| ...attempt.summaryNormalization ? { summaryNormalization: attempt.summaryNormalization } : {}, | ||
| ...typeof attempt.completedAt === "number" ? { completedAt: attempt.completedAt } : {}, | ||
| ...typeof attempt.checkpointId === "string" ? { checkpointId: attempt.checkpointId } : {}, | ||
| ...typeof attempt.compactedMessageCount === "number" ? { compactedMessageCount: attempt.compactedMessageCount } : {}, | ||
| ...typeof attempt.retainedMessageCount === "number" ? { retainedMessageCount: attempt.retainedMessageCount } : {}, | ||
| ...typeof attempt.estimatedInputTokensAfter === "number" ? { estimatedInputTokensAfter: attempt.estimatedInputTokensAfter } : {}, | ||
| ...typeof attempt.estimatedSavedTokens === "number" ? { estimatedSavedTokens: attempt.estimatedSavedTokens } : {}, | ||
| ...typeof attempt.postSummaryFitsBudget === "boolean" ? { postSummaryFitsBudget: attempt.postSummaryFitsBudget } : {} | ||
| }; | ||
| function isModelRef(value) { | ||
| return isRecord(value) && typeof value.provider === "string" && typeof value.modelId === "string"; | ||
| } | ||
| function readCompactedNotificationStats(value) { | ||
| if (!value || typeof value !== "object" || Array.isArray(value)) return null; | ||
| const candidate = value; | ||
| return { | ||
| ...typeof candidate.checkpointId === "string" ? { checkpointId: candidate.checkpointId } : {}, | ||
| ...typeof candidate.pluginId === "string" ? { pluginId: candidate.pluginId } : {}, | ||
| ...readSummaryNormalizationMode(candidate.summaryNormalization) ? { summaryNormalization: readSummaryNormalizationMode(candidate.summaryNormalization) } : {}, | ||
| ...typeof candidate.compactedMessageCount === "number" ? { compactedMessageCount: candidate.compactedMessageCount } : {}, | ||
| ...typeof candidate.retainedMessageCount === "number" ? { retainedMessageCount: candidate.retainedMessageCount } : {}, | ||
| ...typeof candidate.estimatedInputTokensAfter === "number" ? { estimatedInputTokensAfter: candidate.estimatedInputTokensAfter } : {}, | ||
| ...typeof candidate.estimatedSavedTokens === "number" ? { estimatedSavedTokens: candidate.estimatedSavedTokens } : {}, | ||
| ...typeof candidate.postSummaryFitsBudget === "boolean" ? { postSummaryFitsBudget: candidate.postSummaryFitsBudget } : {} | ||
| }; | ||
| function isRecord(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| } | ||
| function createCodedError(code, message) { | ||
| const error = new Error(message); | ||
| error.code = code; | ||
| return error; | ||
| } | ||
| function readErrorCode(error) { | ||
| if (typeof error !== "object" || error === null) return void 0; | ||
| return "code" in error && typeof error.code === "string" ? error.code : void 0; | ||
| if (!isRecord(error)) return void 0; | ||
| return typeof error.code === "string" ? error.code : void 0; | ||
| } | ||
@@ -871,5 +605,2 @@ function readErrorMessage(error) { | ||
| } | ||
| function isCompactionOwnerError(error) { | ||
| return typeof error === "object" && error !== null && "code" in error && error.code === "compaction_owner_required"; | ||
| } | ||
| //#endregion | ||
@@ -876,0 +607,0 @@ export { createAutoCompactPlugin }; |
+3
-3
| { | ||
| "name": "@archships/dim-plugin-auto-compact", | ||
| "version": "0.0.14", | ||
| "version": "0.0.15", | ||
| "description": "Official auto compaction plugin for dim-agent-sdk.", | ||
@@ -27,4 +27,4 @@ "homepage": "https://dimcode.dev/", | ||
| "devDependencies": { | ||
| "@archships/dim-plugin-api": "0.0.20", | ||
| "@archships/dim-agent-sdk": "0.0.53" | ||
| "@archships/dim-agent-sdk": "0.0.55", | ||
| "@archships/dim-plugin-api": "0.0.21" | ||
| }, | ||
@@ -31,0 +31,0 @@ "scripts": { |
+2
-2
@@ -19,3 +19,3 @@ # @archships/dim-plugin-auto-compact | ||
| - publish `ModelCapabilities.contextWindow` on the model adapter so the gate can derive `threshold = contextWindow − plannedOutput − contextWindow × safetyRatio` | ||
| - configure `compaction.ownerPluginId: 'auto-compact'` | ||
| - configure `compaction.compactorPluginId: 'auto-compact'` | ||
| - grant model permission to the plugin runtime | ||
@@ -44,3 +44,3 @@ | ||
| safetyRatio: 0.2, | ||
| ownerPluginId: 'auto-compact', | ||
| compactorPluginId: 'auto-compact', | ||
| }, | ||
@@ -47,0 +47,0 @@ }) |
Sorry, the diff of this file is too big to display
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
Found 1 instance in 1 package
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
Found 1 instance in 1 package
83841
-31.78%601
-30.68%