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

@ -129,6 +129,7 @@
"type-check": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "0.3.142",
"@ktx/llm": "workspace:*",
"@looker/sdk": "^26.8.0",
"@looker/sdk-node": "^26.8.0",

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,

132
pnpm-lock.yaml generated
View file

@ -312,6 +312,9 @@ importers:
packages/context:
dependencies:
'@anthropic-ai/claude-agent-sdk':
specifier: 0.3.142
version: 0.3.142(zod@4.4.3)
'@ktx/llm':
specifier: workspace:*
version: link:../llm
@ -475,6 +478,65 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.142':
resolution: {integrity: sha512-yBHOiRqJ8JcD9OAMGJALbypaD3u3K8hyUmcnZ+91AHJtymzWxuMkVi4IY1qp8L5jzkKeTnvYfCspzkbiHLuYWg==}
cpu: [arm64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.142':
resolution: {integrity: sha512-/a/bVMjvAl3gNzWiPIgynYktTYckTcp4YAacV/2F4Jd8XeCV0+DMQW7OFeR+3fnPcBg/8kcOAVYfLZXDExqO1w==}
cpu: [x64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.142':
resolution: {integrity: sha512-KZuwSupNJovnMJ7MZxjp1Qq0yu7rAmbzO4Zlmr3jtKDU95t2kgs3c6j4evzQDCgTQMlwH8QTSV4mItDGxlYEbg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.142':
resolution: {integrity: sha512-QKG553PSbIcQ5KLvnl2ekfy5lTyU3dW/X5fDQlRLv4YHNHnqf2o7scJ6eUdfaVTQdIZ+Pa7SNN3bsvVs4bNjQw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.142':
resolution: {integrity: sha512-QkDwLMsdYO7n/i1zPCt5YZIet5u+Eo07UpF9UX5yD7bnwRZKDe22L6LVVwiLLjeTO0fTz+uNY7w9/XOYQMlxUQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.142':
resolution: {integrity: sha512-o1QZmCNRL5BFTc14KEvT23Fxm1jNv0aa0e9T0OZUjua0oW8DRpri3HKvDEM36qEGWUOANBG7h6Ca/KNqxaTnYg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.142':
resolution: {integrity: sha512-x8lbY1m7E/BiFF0Gu/Mx9lkD/zW3vBr3viw0GYNuqY9GYHfLOX9+l9H8C+INeGzB4+ibG8+xD2pnRhWdQxuvUg==}
cpu: [arm64]
os: [win32]
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.142':
resolution: {integrity: sha512-NpNxdiCEUNjjwvBltpDnkgdjVQ+nRsALpfM1Pe4GhnYiOkTk/TvjMZUuA2qGh0F8KyF0FbqzUsi0uXIgojJT5w==}
cpu: [x64]
os: [win32]
'@anthropic-ai/claude-agent-sdk@0.3.142':
resolution: {integrity: sha512-k1xBon6ov0PT/vZNf+Z+SuAqmylGJU/+a+h/k04MW5cBbzOIwiVcGFRTGJ/qbY5pcboJbLtts/yBwSu9AvSipg==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^4.0.0
'@anthropic-ai/sdk@0.93.0':
resolution: {integrity: sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA==}
hasBin: true
peerDependencies:
zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
zod:
optional: true
'@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'}
@ -799,6 +861,10 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/runtime@7.29.2':
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
@ -3656,6 +3722,10 @@ packages:
json-bigint@1.0.0:
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
json-schema-to-ts@3.1.1:
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
engines: {node: '>=16'}
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
@ -4752,6 +4822,9 @@ packages:
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
ts-algebra@2.0.0:
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -5080,6 +5153,54 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.142':
optional: true
'@anthropic-ai/claude-agent-sdk@0.3.142(zod@4.4.3)':
dependencies:
'@anthropic-ai/sdk': 0.93.0(zod@4.4.3)
'@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
zod: 4.4.3
optionalDependencies:
'@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.142
'@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.142
'@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.142
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.142
'@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.142
'@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.142
'@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.142
'@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.142
transitivePeerDependencies:
- '@cfworker/json-schema'
- supports-color
'@anthropic-ai/sdk@0.93.0(zod@4.4.3)':
dependencies:
json-schema-to-ts: 3.1.1
optionalDependencies:
zod: 4.4.3
'@aws-crypto/crc32@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
@ -6002,6 +6123,8 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
'@babel/runtime@7.29.2': {}
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
@ -6443,6 +6566,7 @@ snapshots:
'@ktx/context@file:packages/context(js-yaml@4.1.1)':
dependencies:
'@anthropic-ai/claude-agent-sdk': 0.3.142(zod@4.4.3)
'@ktx/llm': file:packages/llm(zod@4.4.3)
'@looker/sdk': 26.8.0
'@looker/sdk-node': 26.8.0
@ -6468,6 +6592,7 @@ snapshots:
'@ktx/context@file:packages/context(js-yaml@4.1.1)(ws@8.20.0)':
dependencies:
'@anthropic-ai/claude-agent-sdk': 0.3.142(zod@4.4.3)
'@ktx/llm': file:packages/llm(ws@8.20.0)(zod@4.4.3)
'@looker/sdk': 26.8.0
'@looker/sdk-node': 26.8.0
@ -8884,6 +9009,11 @@ snapshots:
dependencies:
bignumber.js: 9.3.1
json-schema-to-ts@3.1.1:
dependencies:
'@babel/runtime': 7.29.2
ts-algebra: 2.0.0
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
@ -10439,6 +10569,8 @@ snapshots:
trough@2.2.0: {}
ts-algebra@2.0.0: {}
tslib@2.8.1: {}
tunnel-agent@0.6.0: