diff --git a/src/query.ts b/src/query.ts index 1b1a6d574..280c63758 100644 --- a/src/query.ts +++ b/src/query.ts @@ -127,6 +127,12 @@ import { isLangfuseEnabled, } from './services/langfuse/index.js' import { getAPIProvider } from './utils/model/providers.js' +import { + createCacheWarningMessage, + getCacheThreshold, + isCacheWarningEnabled, + shouldShowCacheWarning, +} from './utils/cacheWarning.js' /* eslint-disable @typescript-eslint/no-require-imports */ const snipModule = feature('HISTORY_SNIP') @@ -1137,6 +1143,32 @@ async function* queryLoop( return { reason: 'model_error', error } } + // 检测缓存命中率并在需要时 yield 警告消息 + // 必须在 executePostSamplingHooks 之前执行,确保警告消息在工具结果之前显示 + if ( + assistantMessages.length > 0 && + !toolUseContext.options.isNonInteractiveSession + ) { + const lastAssistant = assistantMessages.at(-1) + const usage = lastAssistant?.message?.usage as + | { + input_tokens: number + cache_creation_input_tokens: number + cache_read_input_tokens: number + } + | undefined + if (usage && isCacheWarningEnabled()) { + const warningInfo = shouldShowCacheWarning( + usage, + querySource, + getCacheThreshold(), + ) + if (warningInfo) { + yield createCacheWarningMessage(warningInfo) + } + } + } + // Execute post-sampling hooks after model response is complete if (assistantMessages.length > 0) { void executePostSamplingHooks( diff --git a/src/utils/cacheWarning.ts b/src/utils/cacheWarning.ts index 294673f0c..04aab21c1 100644 --- a/src/utils/cacheWarning.ts +++ b/src/utils/cacheWarning.ts @@ -42,6 +42,14 @@ export function getCacheThreshold(): number { : DEFAULT_CACHE_THRESHOLD } +/** + * 检查缓存警告是否启用。默认 true。 + */ +export function isCacheWarningEnabled(): boolean { + const settings = getInitialSettings() + return settings.cacheWarningEnabled ?? true +} + /** * 计算缓存命中率 * 返回值范围 0-100,null 表示无有效数据 diff --git a/src/utils/settings/types.ts b/src/utils/settings/types.ts index 1c9dbf5e8..ba47bc142 100644 --- a/src/utils/settings/types.ts +++ b/src/utils/settings/types.ts @@ -1074,6 +1074,21 @@ export const SettingsSchema = lazySchema(() => 'Only applies to User, Project, and Local memory types (Managed/policy files cannot be excluded). ' + 'Examples: "/home/user/monorepo/CLAUDE.md", "**/code/CLAUDE.md", "**/some-dir/.claude/rules/**"', ), + cacheThreshold: z + .number() + .int() + .min(0) + .max(100) + .optional() + .describe( + 'Prompt cache hit rate threshold (0-100). Warnings shown when cache hit rate falls below this percentage. Default: 80.', + ), + cacheWarningEnabled: z + .boolean() + .optional() + .describe( + 'Whether to show cache hit rate warnings in the message flow when the rate falls below cacheThreshold. Default: true.', + ), pluginTrustMessage: z .string() .optional()