diff --git a/packages/cli/src/claude-code-prompt-caching.ts b/packages/cli/src/claude-code-prompt-caching.ts new file mode 100644 index 00000000..43a6596f --- /dev/null +++ b/packages/cli/src/claude-code-prompt-caching.ts @@ -0,0 +1,28 @@ +import type { KtxProjectLlmConfig } from '@ktx/context/project'; + +const CLAUDE_CODE_IGNORED_PROMPT_CACHING_FIELDS = [ + 'systemTtl', + 'toolsTtl', + 'historyTtl', + 'vertexFallbackTo5m', +] as const; + +export function ignoredClaudeCodePromptCachingFields(config: KtxProjectLlmConfig): string[] { + if (config.provider.backend !== 'claude-code' || !config.promptCaching) { + return []; + } + return CLAUDE_CODE_IGNORED_PROMPT_CACHING_FIELDS.filter((key) => key in config.promptCaching).map( + (key) => `llm.promptCaching.${key}`, + ); +} + +export function formatClaudeCodePromptCachingWarning(fields: string[]): string | null { + if (fields.length === 0) { + return null; + } + return `claude-code ignores ${fields.join(', ')} because the Claude Agent SDK does not expose KTX prompt-cache TTL, tool, or history markers.`; +} + +export function formatClaudeCodePromptCachingFix(): string { + return 'Remove those promptCaching fields or use anthropic, vertex, or gateway when those cache knobs are required.'; +} diff --git a/packages/cli/src/setup-models.test.ts b/packages/cli/src/setup-models.test.ts index 5c69e0fa..23a7277a 100644 --- a/packages/cli/src/setup-models.test.ts +++ b/packages/cli/src/setup-models.test.ts @@ -209,6 +209,44 @@ describe('setup Anthropic model step', () => { expect(authProbe).toHaveBeenCalledWith(expect.objectContaining({ projectDir: tempDir, model: 'sonnet' })); }); + it('warns during Claude Code setup when existing prompt-caching fields will be ignored', async () => { + await writeFile( + join(tempDir, 'ktx.yaml'), + [ + 'llm:', + ' provider:', + ' backend: anthropic', + ' models:', + ' default: claude-sonnet-4-6', + ' promptCaching:', + ' enabled: true', + ' systemTtl: 1h', + ' toolsTtl: 1h', + ' historyTtl: 5m', + '', + ].join('\n'), + 'utf-8', + ); + const io = makeIo(); + + const result = await runKtxSetupAnthropicModelStep( + { + projectDir: tempDir, + inputMode: 'disabled', + llmBackend: 'claude-code', + skipLlm: false, + }, + io.io, + { + claudeCodeAuthProbe: async () => ({ ok: true as const }), + }, + ); + + expect(result.status).toBe('ready'); + expect(io.stderr()).toContain('claude-code ignores llm.promptCaching.systemTtl'); + expect(io.stderr()).toContain('Claude Agent SDK does not expose KTX prompt-cache TTL, tool, or history markers'); + }); + it('returns from Anthropic credential Back to provider selection', async () => { const prompts = makePromptAdapter({ selectValues: ['anthropic', 'back', 'back'] }); diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index 87d0d3df..5232f4de 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -11,6 +11,10 @@ import { serializeKtxProjectConfig, } from '@ktx/context/project'; import { type KtxLlmConfig, type KtxLlmHealthCheckResult, runKtxLlmHealthCheck } from '@ktx/llm'; +import { + formatClaudeCodePromptCachingWarning, + ignoredClaudeCodePromptCachingFields, +} from './claude-code-prompt-caching.js'; import { createClackSpinner, type KtxCliSpinner } from './clack.js'; import type { KtxCliIo } from './cli-runtime.js'; import { withTextInputNavigation } from './prompt-navigation.js'; @@ -946,6 +950,14 @@ export async function runKtxSetupAnthropicModelStep( io.stderr.write(`${health.message}\n`); return { status: 'failed', projectDir: args.projectDir }; } + const warning = formatClaudeCodePromptCachingWarning( + ignoredClaudeCodePromptCachingFields( + buildProjectLlmConfig(project.config.llm, { backend: 'claude-code' }, model), + ), + ); + if (warning) { + io.stderr.write(`${warning}\n`); + } await persistLlmConfig(args.projectDir, { backend: 'claude-code' }, model); io.stdout.write(`│ LLM ready: yes (${model})\n`); return { status: 'ready', projectDir: args.projectDir }; diff --git a/packages/cli/src/status-project.ts b/packages/cli/src/status-project.ts index d527cb28..ea0aeb77 100644 --- a/packages/cli/src/status-project.ts +++ b/packages/cli/src/status-project.ts @@ -9,6 +9,11 @@ import type { KtxProjectLlmConfig, } from '@ktx/context/project'; import type { PostgresPgssProbeResult } from '@ktx/context/ingest'; +import { + formatClaudeCodePromptCachingFix, + formatClaudeCodePromptCachingWarning, + ignoredClaudeCodePromptCachingFields, +} from './claude-code-prompt-caching.js'; import type { DoctorCheck } from './doctor.js'; import { KTX_NEXT_STEP_DIRECT_COMMANDS } from './next-steps.js'; @@ -509,13 +514,6 @@ function buildPipelineStatus(config: KtxProjectConfig): PipelineStatus { }; } -function ignoredClaudeCodePromptCachingFields(config: KtxProjectLlmConfig): string[] { - if (config.provider.backend !== 'claude-code' || !config.promptCaching) { - return []; - } - return Object.keys(config.promptCaching).map((key) => `llm.promptCaching.${key}`); -} - function buildStorageStatus(config: KtxProjectConfig): StorageStatus { return { state: config.storage.state, @@ -603,11 +601,11 @@ function buildWarnings( }); } - const ignored = ignoredClaudeCodePromptCachingFields(config.llm); - if (ignored.length > 0) { + const warning = formatClaudeCodePromptCachingWarning(ignoredClaudeCodePromptCachingFields(config.llm)); + if (warning) { warnings.push({ - message: `claude-code ignores ${ignored.join(', ')} because the Claude Agent SDK does not expose KTX prompt-cache TTL, tool, or history markers.`, - fix: 'Remove those promptCaching fields or use anthropic, vertex, or gateway when those cache knobs are required.', + message: warning, + fix: formatClaudeCodePromptCachingFix(), }); }