feat: add claude-code agent runner config

This commit is contained in:
Andrey Avtomonov 2026-05-15 12:48:28 +02:00
parent f64b5a92c8
commit 79369fea6c
5 changed files with 199 additions and 1 deletions

View file

@ -38,6 +38,9 @@ connections:
backend: 'none',
},
models: {},
agentRunner: {
backend: 'ai-sdk',
},
},
ingest: {
adapters: [],
@ -180,6 +183,50 @@ llm:
});
});
it('defaults the agent runner backend to ai-sdk', () => {
expect(buildDefaultKtxProjectConfig().llm.agentRunner).toEqual({
backend: 'ai-sdk',
});
});
it('accepts claude-code as an agent runner backend without enabling the global LLM provider', () => {
const config = parseKtxProjectConfig(`
llm:
agentRunner:
backend: claude-code
models:
default: claude-sonnet-4-6
`);
expect(config.llm.provider.backend).toBe('none');
expect(config.llm.agentRunner.backend).toBe('claude-code');
expect(config.llm.models.default).toBe('claude-sonnet-4-6');
});
it('rejects unknown agent runner backends with a scoped config issue', () => {
const result = validateKtxProjectConfig(`
llm:
agentRunner:
backend: subprocess
`);
expect(result.ok).toBe(false);
expect(result.ok ? [] : result.issues).toContainEqual({
path: 'llm.agentRunner',
message: 'Unsupported llm.agentRunner: subprocess',
});
});
it('includes agent runner backend values in the generated JSON schema', () => {
const schema = generateKtxProjectConfigJsonSchema();
const properties = schema.properties as Record<string, { properties?: Record<string, unknown> }>;
const llm = properties.llm as { properties?: Record<string, { properties?: Record<string, unknown> }> };
const agentRunner = llm.properties?.agentRunner as { properties?: Record<string, unknown> };
const backend = agentRunner.properties?.backend as { enum?: readonly string[] };
expect(backend.enum).toEqual(['ai-sdk', 'claude-code']);
});
it('parses gateway LLM, OpenAI scan embeddings, and sentence-transformers ingest embeddings', () => {
const config = parseKtxProjectConfig(`
llm:

View file

@ -4,6 +4,7 @@ import * as z from 'zod';
import { connectionConfigSchema } from './driver-schemas.js';
const KTX_LLM_BACKENDS = ['none', 'anthropic', 'vertex', 'gateway'] as const;
const KTX_AGENT_RUNNER_BACKENDS = ['ai-sdk', 'claude-code'] as const;
const KTX_EMBEDDING_BACKENDS = ['none', 'deterministic', 'openai', 'sentence-transformers'] as const;
const KTX_PROMPT_CACHE_TTLS = ['5m', '1h'] as const;
const KTX_ENRICHMENT_MODES = ['none', 'deterministic', 'llm'] as const;
@ -63,6 +64,17 @@ const promptCachingSchema = z
})
.describe('Prompt-caching tunables for Anthropic-compatible providers.');
const agentRunnerSchema = z
.strictObject({
backend: z
.enum(KTX_AGENT_RUNNER_BACKENDS)
.default('ai-sdk')
.describe(
'Agent-loop backend. "ai-sdk" uses the configured LLM provider; "claude-code" uses the local Claude Agent SDK session for agentic loops only.',
),
})
.describe('Agent runner backend selection for ingest and memory-agent loops.');
const llmSchema = z
.strictObject({
provider: llmProviderSchema.prefault({}).describe('LLM provider backend and credentials.'),
@ -71,8 +83,9 @@ const llmSchema = z
.default({})
.describe('Per-role model overrides keyed by KTX model role (e.g. "default", "triage"). Values are provider-specific model identifiers.'),
promptCaching: promptCachingSchema.optional().describe('Optional prompt-caching tunables.'),
agentRunner: agentRunnerSchema.prefault({}).describe('Agent runner backend selection for ingest and memory-agent loops.'),
})
.describe('LLM provider, per-role model overrides, and prompt-caching tunables.');
.describe('LLM provider, per-role model overrides, prompt-caching tunables, and agent-runner backend.');
const embeddingSchema = z
.strictObject({
@ -253,6 +266,7 @@ const ktxProjectConfigSchema = z
.describe('Configuration schema for KTX project files (ktx.yaml).');
export type KtxProjectConfig = z.infer<typeof ktxProjectConfigSchema>;
export type KtxProjectAgentRunnerConfig = z.infer<typeof agentRunnerSchema>;
export type KtxProjectLlmConfig = z.infer<typeof llmSchema>;
export type KtxProjectLlmProviderConfig = z.infer<typeof llmProviderSchema>;
export type KtxProjectEmbeddingConfig = z.infer<typeof embeddingSchema>;
@ -311,6 +325,9 @@ function formatIssue(issue: z.core.$ZodIssue, input: unknown): KtxConfigIssue[]
const lastSegment = issue.path[issue.path.length - 1];
if (lastSegment === 'backend' && (issue.code === 'invalid_value' || issue.code === 'invalid_type')) {
const value = valueAtPath(input, issue.path);
if (basePath === 'llm.agentRunner.backend') {
return [{ path: 'llm.agentRunner', message: `Unsupported llm.agentRunner: ${String(value)}` }];
}
return [{ path: basePath, message: `Unsupported ${basePath}: ${String(value)}` }];
}

View file

@ -1,6 +1,7 @@
export type {
KtxConfigIssue,
KtxConfigValidation,
KtxProjectAgentRunnerConfig,
KtxProjectConfig,
KtxProjectConnectionConfig,
KtxProjectEmbeddingConfig,