|
1 | 1 | import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js"; |
| 2 | +import { resolveContextTokensForModel } from "../../agents/context.js"; |
| 3 | +import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; |
| 4 | +import { |
| 5 | + OPENAI_CODEX_PROVIDER_ID, |
| 6 | + OPENAI_PROVIDER_ID, |
| 7 | + resolveContextConfigProviderForRuntime, |
| 8 | +} from "../../agents/openai-codex-routing.js"; |
| 9 | +import { normalizeProviderId } from "../../agents/provider-id.js"; |
2 | 10 | import type { OpenClawConfig } from "../../config/types.openclaw.js"; |
3 | 11 | import { logVerbose } from "../../globals.js"; |
4 | 12 | import { createLazyImportLoader } from "../../shared/lazy-promise.js"; |
@@ -75,6 +83,99 @@ function formatCompactionReason(reason?: string): string | undefined { |
75 | 83 | return text; |
76 | 84 | } |
77 | 85 |
|
| 86 | +function resolveManualCompactContextTokenBudget(params: { |
| 87 | + cfg: OpenClawConfig; |
| 88 | + provider?: string; |
| 89 | + model?: string; |
| 90 | + agentId: string; |
| 91 | + sessionKey: string; |
| 92 | + liveContextTokens?: number; |
| 93 | + persistedContextTokens?: number; |
| 94 | +}): number | undefined { |
| 95 | + const liveContextTokens = |
| 96 | + typeof params.liveContextTokens === "number" && |
| 97 | + Number.isFinite(params.liveContextTokens) && |
| 98 | + params.liveContextTokens > 0 |
| 99 | + ? Math.floor(params.liveContextTokens) |
| 100 | + : undefined; |
| 101 | + |
| 102 | + const model = normalizeOptionalString(params.model); |
| 103 | + const provider = normalizeOptionalString(params.provider); |
| 104 | + if (!model || !provider) { |
| 105 | + return liveContextTokens ?? resolvePersistedContextTokens(params.persistedContextTokens); |
| 106 | + } |
| 107 | + |
| 108 | + const harnessPolicy = resolveAgentHarnessPolicy({ |
| 109 | + provider, |
| 110 | + modelId: model, |
| 111 | + config: params.cfg, |
| 112 | + agentId: params.agentId, |
| 113 | + sessionKey: params.sessionKey, |
| 114 | + }); |
| 115 | + const contextConfigProvider = resolveContextConfigProviderForRuntime({ |
| 116 | + provider, |
| 117 | + runtimeId: harnessPolicy.runtime, |
| 118 | + }); |
| 119 | + const configuredContextTokens = resolveContextTokensForModel({ |
| 120 | + cfg: params.cfg, |
| 121 | + provider: contextConfigProvider, |
| 122 | + model: resolveManualCompactContextModelId({ |
| 123 | + provider, |
| 124 | + contextConfigProvider, |
| 125 | + model, |
| 126 | + }), |
| 127 | + allowAsyncLoad: false, |
| 128 | + }); |
| 129 | + if (typeof configuredContextTokens === "number" && configuredContextTokens > 0) { |
| 130 | + const configuredBudget = Math.floor(configuredContextTokens); |
| 131 | + return liveContextTokens !== undefined |
| 132 | + ? Math.min(liveContextTokens, configuredBudget) |
| 133 | + : configuredBudget; |
| 134 | + } |
| 135 | + |
| 136 | + if (liveContextTokens !== undefined) { |
| 137 | + return liveContextTokens; |
| 138 | + } |
| 139 | + |
| 140 | + return resolvePersistedContextTokens(params.persistedContextTokens); |
| 141 | +} |
| 142 | + |
| 143 | +function resolvePersistedContextTokens(value: number | undefined): number | undefined { |
| 144 | + return typeof value === "number" && Number.isFinite(value) && value > 0 |
| 145 | + ? Math.floor(value) |
| 146 | + : undefined; |
| 147 | +} |
| 148 | + |
| 149 | +function resolveManualCompactContextModelId(params: { |
| 150 | + provider: string; |
| 151 | + contextConfigProvider: string; |
| 152 | + model: string; |
| 153 | +}): string { |
| 154 | + const model = params.model.trim(); |
| 155 | + const slashIndex = model.indexOf("/"); |
| 156 | + if (slashIndex <= 0) { |
| 157 | + return model; |
| 158 | + } |
| 159 | + |
| 160 | + const modelProvider = normalizeProviderId(model.slice(0, slashIndex)); |
| 161 | + const selectedProvider = normalizeProviderId(params.provider); |
| 162 | + const contextConfigProvider = normalizeProviderId(params.contextConfigProvider); |
| 163 | + const modelId = model.slice(slashIndex + 1).trim(); |
| 164 | + if (!modelId) { |
| 165 | + return model; |
| 166 | + } |
| 167 | + |
| 168 | + if ( |
| 169 | + modelProvider === selectedProvider || |
| 170 | + modelProvider === contextConfigProvider || |
| 171 | + (modelProvider === OPENAI_PROVIDER_ID && contextConfigProvider === OPENAI_CODEX_PROVIDER_ID) |
| 172 | + ) { |
| 173 | + return modelId; |
| 174 | + } |
| 175 | + |
| 176 | + return model; |
| 177 | +} |
| 178 | + |
78 | 179 | export const handleCompactCommand: CommandHandler = async (params) => { |
79 | 180 | const compactRequested = |
80 | 181 | params.command.commandBodyNormalized === "/compact" || |
@@ -116,6 +217,15 @@ export const handleCompactCommand: CommandHandler = async (params) => { |
116 | 217 | agentId: sessionAgentId, |
117 | 218 | isGroup: params.isGroup, |
118 | 219 | }); |
| 220 | + const contextTokenBudget = resolveManualCompactContextTokenBudget({ |
| 221 | + cfg: params.cfg, |
| 222 | + provider: params.provider, |
| 223 | + model: params.model, |
| 224 | + agentId: sessionAgentId, |
| 225 | + sessionKey: params.sessionKey, |
| 226 | + liveContextTokens: params.contextTokens, |
| 227 | + persistedContextTokens: targetSessionEntry.contextTokens, |
| 228 | + }); |
119 | 229 | const result = await runtime.compactEmbeddedPiSession({ |
120 | 230 | sessionId, |
121 | 231 | sessionKey: params.sessionKey, |
@@ -143,6 +253,7 @@ export const handleCompactCommand: CommandHandler = async (params) => { |
143 | 253 | skillsSnapshot: targetSessionEntry.skillsSnapshot, |
144 | 254 | provider: params.provider, |
145 | 255 | model: params.model, |
| 256 | + contextTokenBudget, |
146 | 257 | agentHarnessId: |
147 | 258 | targetSessionEntry.sessionId === sessionId ? targetSessionEntry.agentHarnessId : undefined, |
148 | 259 | thinkLevel: params.resolvedThinkLevel ?? (await params.resolveDefaultThinkingLevel()), |
@@ -186,7 +297,7 @@ export const handleCompactCommand: CommandHandler = async (params) => { |
186 | 297 | tokensAfterCompaction ?? runtime.resolveFreshSessionTotalTokens(targetSessionEntry); |
187 | 298 | const contextSummary = runtime.formatContextUsageShort( |
188 | 299 | typeof totalTokens === "number" && totalTokens > 0 ? totalTokens : null, |
189 | | - params.contextTokens ?? targetSessionEntry.contextTokens ?? null, |
| 300 | + contextTokenBudget ?? null, |
190 | 301 | ); |
191 | 302 | const reason = formatCompactionReason(result.reason); |
192 | 303 | const line = reason |
|
0 commit comments