Files
maka/packages/runtime/src/context-budget-policy.ts
T
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

150 lines
6.3 KiB
TypeScript

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { RuntimeExecutionConnection } from '@maka/core/llm-connections';
import { lookupModelMetadata } from '@maka/core/model-metadata';
import { relayModelProfile } from '@maka/core/model-thinking';
import type { ContextBudgetPolicy } from './context-budget.js';
import { finitePositive } from './context-budget-helpers.js';
export interface BuildDefaultContextBudgetPolicyOptions {
name?: string;
modelId?: string;
}
export function buildDefaultContextBudgetPolicy(
connection: RuntimeExecutionConnection,
options: BuildDefaultContextBudgetPolicyOptions = {},
): ContextBudgetPolicy {
const contextWindow = resolveSelectedModelContextWindow(connection, options.modelId);
const reserveTokens = defaultCompactReserveTokens(contextWindow);
const maxHistoryEstimatedTokens = defaultHistoryBudgetTokens(
connection,
contextWindow,
reserveTokens,
);
const surfaceName = (options.name ?? 'default-history-budget').replace(
/-default-history-budget$/,
'',
);
return {
name: options.name ?? 'default-history-budget',
...(maxHistoryEstimatedTokens !== undefined ? { maxHistoryEstimatedTokens } : {}),
staleToolResultPrune: {
enabled: true,
maxResultEstimatedTokens: 2_048,
minRecentTurnsFull: 2,
},
historyCompact: {
enabled: true,
highWaterName: `${surfaceName}-history-compact`,
midTurn: { enabled: true, reserveTokens },
},
activeToolResultPrune: {
enabled: true,
maxCurrentResultEstimatedTokens: 2_048,
minSupersededResultEstimatedTokens: 256,
minStepNumber: 1,
},
};
}
// Single owner of the compaction reserve default. The classic 16384 reserve
// assumed large-window models; on an 8K window it derived a 1-token history
// budget and a 1-token mid_turn high water — every multi-step turn ran the
// summarizer for a checkpoint the replay gate could never admit. The default
// is therefore bounded by the KNOWN window (a quarter of it, capped at 16384;
// peers bound the same way: opencode caps its buffer by the model's output
// limit, gemini-cli triggers at a window fraction). An unknown window keeps
// the classic constant.
function defaultCompactReserveTokens(contextWindow: number | undefined): number {
if (contextWindow === undefined) return 16_384;
return Math.min(16_384, Math.max(1, Math.floor(contextWindow / 4)));
}
function defaultHistoryBudgetTokens(
connection: RuntimeExecutionConnection,
contextWindow: number | undefined,
reserveTokens: number,
): number | undefined {
if (contextWindow !== undefined) {
return Math.max(1, contextWindow - reserveTokens);
}
if (connection.providerType === 'deepseek') return undefined;
return 32_000;
}
export function resolveSelectedModelContextWindow(
connection: RuntimeExecutionConnection,
modelId: string | undefined,
): number | undefined {
const selectedModelId = modelId ?? connection.defaultModel;
if (selectedModelId === undefined) return undefined;
const model = connection.models?.find((candidate) => candidate.id === selectedModelId);
// A model-facts pin is the cross-provider correction authority. It must win
// over the older relay-only declaration so catalog display and execution use
// the same window. Relay declarations retain their existing precedence when
// there is no facts pin for this field.
if (model?.factOverriddenFields?.includes('contextWindow')) {
return narrowestPositiveLimit(model.contextWindow, model.inputLimit);
}
// A user declaration outranks both the provider's /models report and
// generated metadata — mirrors the declared-vision precedence in
// model-metadata.ts. A declared context window is legal on any provider: it
// states a fact about the model, not a request shape (#1584).
const declared = relayModelProfile(connection, selectedModelId)?.contextWindow;
if (declared !== undefined) return declared;
const metadata = lookupModelMetadata(connection.providerType, selectedModelId);
// Provider/access-path facts outrank static metadata. Within one source,
// use the narrowest positive bound: models.dev's input limit can be lower
// than its total context window, while an access path can expose a narrower
// context window than the public catalog.
const modelLimit = narrowestPositiveLimit(model?.contextWindow, model?.inputLimit);
const metadataLimit = narrowestPositiveLimit(metadata.contextWindow, metadata.inputLimit);
return modelLimit ?? metadataLimit;
}
function narrowestPositiveLimit(...values: Array<number | undefined>): number | undefined {
const positiveValues = values.filter(
(value): value is number => typeof value === 'number' && Number.isFinite(value) && value > 0,
);
return positiveValues.length > 0 ? Math.min(...positiveValues) : undefined;
}
export interface ContextBudgetCapacity {
tokens: number;
source: 'selected_model' | 'policy_fallback';
}
export function resolveContextBudgetCapacity(
connection: RuntimeExecutionConnection,
modelId: string | undefined,
policy: ContextBudgetPolicy | undefined,
): ContextBudgetCapacity | undefined {
const selectedWindow = resolveSelectedModelContextWindow(connection, modelId);
if (selectedWindow !== undefined) {
return { tokens: selectedWindow, source: 'selected_model' };
}
const historyBudget = finitePositive(policy?.maxHistoryEstimatedTokens);
const reserveTokens = finitePositive(policy?.historyCompact?.midTurn?.reserveTokens);
if (historyBudget === undefined || reserveTokens === undefined) return undefined;
return { tokens: historyBudget + reserveTokens, source: 'policy_fallback' };
}