From 79369fea6ccb586252ffcbcf3461c4ea3ac98854 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Fri, 15 May 2026 12:48:28 +0200 Subject: [PATCH] feat: add claude-code agent runner config --- packages/context/package.json | 1 + packages/context/src/project/config.test.ts | 47 +++++++ packages/context/src/project/config.ts | 19 ++- packages/context/src/project/index.ts | 1 + pnpm-lock.yaml | 132 ++++++++++++++++++++ 5 files changed, 199 insertions(+), 1 deletion(-) diff --git a/packages/context/package.json b/packages/context/package.json index 28ec5190..104b4e47 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -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", diff --git a/packages/context/src/project/config.test.ts b/packages/context/src/project/config.test.ts index 164282eb..5699d621 100644 --- a/packages/context/src/project/config.test.ts +++ b/packages/context/src/project/config.test.ts @@ -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 }>; + const llm = properties.llm as { properties?: Record }> }; + const agentRunner = llm.properties?.agentRunner as { properties?: Record }; + 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: diff --git a/packages/context/src/project/config.ts b/packages/context/src/project/config.ts index 178721c4..a19cb866 100644 --- a/packages/context/src/project/config.ts +++ b/packages/context/src/project/config.ts @@ -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; +export type KtxProjectAgentRunnerConfig = z.infer; export type KtxProjectLlmConfig = z.infer; export type KtxProjectLlmProviderConfig = z.infer; export type KtxProjectEmbeddingConfig = z.infer; @@ -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)}` }]; } diff --git a/packages/context/src/project/index.ts b/packages/context/src/project/index.ts index a0c08767..46c7906f 100644 --- a/packages/context/src/project/index.ts +++ b/packages/context/src/project/index.ts @@ -1,6 +1,7 @@ export type { KtxConfigIssue, KtxConfigValidation, + KtxProjectAgentRunnerConfig, KtxProjectConfig, KtxProjectConnectionConfig, KtxProjectEmbeddingConfig, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22e0c035..cd331dd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: