mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
feat: wire claude-code agent runner backend
This commit is contained in:
parent
eb90d2f32c
commit
3de32c43a1
16 changed files with 229 additions and 21 deletions
|
|
@ -250,6 +250,17 @@ canonical revenue reporting.
|
|||
|
||||
Together, these four pillars give agents enough context to produce analytics artifacts that match what your team would produce - not just syntactically valid SQL, but the right query for the question.
|
||||
|
||||
## Agent runner backends
|
||||
|
||||
KTX separates the global LLM provider from the agent runner. The global
|
||||
provider powers non-agent calls such as scan enrichment and relationship
|
||||
proposals. The agent runner powers curated tool loops used by ingest and memory
|
||||
capture.
|
||||
|
||||
Use `llm.agentRunner.backend: claude-code` when you want those curated loops to
|
||||
run through the Claude Agent SDK. KTX registers only its stage-specific MCP
|
||||
tools for the session and disables Claude Code built-in tools for that backend.
|
||||
|
||||
## How KTX compares
|
||||
|
||||
KTX is a context layer with an agent-native semantic layer at its core. MetricFlow, Cube, and Malloy model metrics, dimensions, joins, and generated SQL. KTX covers that semantic-layer work, then adds the context agents need to use and maintain it: wiki pages, schema scans, provenance, ingestion, validation, and agent-facing CLI commands.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ completed setup steps and resumes from the remaining work.
|
|||
KTX uses a Claude model for ingest agents that turn schemas, SQL, BI metadata,
|
||||
and documents into semantic-layer sources and wiki context.
|
||||
|
||||
Setup supports two LLM provider paths:
|
||||
Setup supports two hosted LLM provider paths:
|
||||
|
||||
| Provider | Use when | Credential model |
|
||||
|----------|----------|------------------|
|
||||
|
|
@ -78,6 +78,28 @@ Setup checks the selected model before saving. Anthropic API setup fetches live
|
|||
Claude model choices when possible and falls back to bundled defaults if model
|
||||
discovery is unavailable.
|
||||
|
||||
### Use a local Claude Code session for ingest agents
|
||||
|
||||
KTX can run ingest and memory-agent loops through your local Claude Code
|
||||
session. This affects only agentic loops; scan enrichment, page triage, and
|
||||
relationship proposals still use `llm.provider`.
|
||||
|
||||
```bash
|
||||
ktx setup --llm-backend claude-code --anthropic-model claude-sonnet-4-6
|
||||
```
|
||||
|
||||
The generated `ktx.yaml` uses:
|
||||
|
||||
```yaml
|
||||
llm:
|
||||
provider:
|
||||
backend: none
|
||||
agentRunner:
|
||||
backend: claude-code
|
||||
models:
|
||||
default: claude-sonnet-4-6
|
||||
```
|
||||
|
||||
## Step 3: Configure embeddings
|
||||
|
||||
KTX uses embeddings for semantic search over semantic-layer sources, wiki
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"type-check": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.142",
|
||||
"@clack/prompts": "1.4.0",
|
||||
"@commander-js/extra-typings": "14.0.0",
|
||||
"@ktx/connector-bigquery": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function embeddingBackend(value: string): 'openai' | 'sentence-transformers' {
|
|||
}
|
||||
|
||||
function llmBackend(value: string): KtxSetupLlmBackend {
|
||||
if (value === 'anthropic' || value === 'vertex') {
|
||||
if (value === 'anthropic' || value === 'vertex' || value === 'claude-code') {
|
||||
return value;
|
||||
}
|
||||
throw new InvalidArgumentError(`invalid choice '${value}'`);
|
||||
|
|
@ -361,12 +361,18 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (options.llmBackend === 'vertex' && (options.anthropicApiKeyEnv || options.anthropicApiKeyFile)) {
|
||||
if (
|
||||
(options.llmBackend === 'vertex' || options.llmBackend === 'claude-code') &&
|
||||
(options.anthropicApiKeyEnv || options.anthropicApiKeyFile)
|
||||
) {
|
||||
context.io.stderr.write('Anthropic API key flags are only valid with --llm-backend anthropic.\n');
|
||||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (options.llmBackend === 'anthropic' && (options.vertexProject || options.vertexLocation)) {
|
||||
if (
|
||||
(options.llmBackend === 'anthropic' || options.llmBackend === 'claude-code') &&
|
||||
(options.vertexProject || options.vertexLocation)
|
||||
) {
|
||||
context.io.stderr.write('Vertex AI flags are only valid with --llm-backend vertex.\n');
|
||||
context.setExitCode(1);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -913,6 +913,7 @@ describe('runContextBuild', () => {
|
|||
llm: {
|
||||
provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } }, // pragma: allowlist secret
|
||||
models: { default: 'gpt-test' },
|
||||
agentRunner: { backend: 'ai-sdk' },
|
||||
},
|
||||
scan: {
|
||||
...projectWithConnections({ warehouse: { driver: 'postgres' } }).config.scan,
|
||||
|
|
|
|||
|
|
@ -1074,6 +1074,41 @@ describe('runKtxCli', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('dispatches Claude Code setup flags to the setup runner', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
[
|
||||
'--project-dir',
|
||||
tempDir,
|
||||
'setup',
|
||||
'--no-input',
|
||||
'--llm-backend',
|
||||
'claude-code',
|
||||
'--anthropic-model',
|
||||
'claude-sonnet-4-6',
|
||||
],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.0.0-private',
|
||||
llmBackend: 'claude-code',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
}),
|
||||
setupIo.io,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects conflicting Anthropic credential setup flags', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ export class CliLookerSlWritingAgentRunner extends AgentRunnerService {
|
|||
verifiedIdentifiers: ['prod-warehouse', 'public.orders'],
|
||||
unverifiedIdentifiers: [],
|
||||
},
|
||||
{ toolCallId: 'cli-looker-verification-ledger', messages: [] },
|
||||
{ toolCallId: 'cli-looker-verification-ledger' },
|
||||
);
|
||||
const slWrite = params.toolSet.sl_write_source;
|
||||
if (!slWrite?.execute) {
|
||||
|
|
@ -292,10 +292,13 @@ export class CliLookerSlWritingAgentRunner extends AgentRunnerService {
|
|||
measures: [{ name: 'total_revenue', expr: 'sum(revenue)' }],
|
||||
},
|
||||
},
|
||||
{ toolCallId: 'cli-looker-sl-write', messages: [] },
|
||||
{ toolCallId: 'cli-looker-sl-write' },
|
||||
);
|
||||
if (!result.structured.success) {
|
||||
throw new Error(result.markdown);
|
||||
const structured =
|
||||
result && typeof result === 'object' && 'structured' in result ? result.structured : undefined;
|
||||
if (!structured || typeof structured !== 'object' || !('success' in structured) || structured.success !== true) {
|
||||
const message = result && typeof result === 'object' && 'markdown' in result ? result.markdown : String(result);
|
||||
throw new Error(typeof message === 'string' ? message : 'sl_write_source failed');
|
||||
}
|
||||
}
|
||||
return { stopReason: 'natural' as const };
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ async function writeReadyProject(projectDir: string, overrides: ReadyProjectOver
|
|||
llm: {
|
||||
provider: { backend: 'anthropic' },
|
||||
models: { default: 'claude-sonnet-4-6' },
|
||||
agentRunner: { backend: 'ai-sdk' },
|
||||
},
|
||||
ingest: {
|
||||
...defaults.ingest,
|
||||
|
|
|
|||
|
|
@ -975,6 +975,26 @@ describe('setup Anthropic model step', () => {
|
|||
expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).not.toContain('completed_steps:');
|
||||
});
|
||||
|
||||
it('writes claude-code agent runner config when requested as the LLM backend', async () => {
|
||||
const result = await runKtxSetupAnthropicModelStep(
|
||||
{
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
llmBackend: 'claude-code',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
makeIo().io,
|
||||
{ claudeCodeAuthProbe: vi.fn(async () => ({ ok: true as const })) },
|
||||
);
|
||||
|
||||
expect(result.status).toBe('ready');
|
||||
const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8'));
|
||||
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('returns back without writing config when Back is selected', async () => {
|
||||
const prompts = makePromptAdapter({ credentialChoice: 'back' });
|
||||
const result = await runKtxSetupAnthropicModelStep(
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export interface AnthropicModelChoice {
|
|||
recommended: boolean;
|
||||
}
|
||||
|
||||
export type KtxSetupLlmBackend = 'anthropic' | 'vertex';
|
||||
export type KtxSetupLlmBackend = 'anthropic' | 'vertex' | 'claude-code';
|
||||
|
||||
export interface KtxSetupModelPromptAdapter {
|
||||
select(options: { message: string; options: KtxSetupPromptOption[] }): Promise<string>;
|
||||
|
|
@ -68,6 +68,7 @@ export interface KtxSetupModelDeps {
|
|||
prompts?: KtxSetupModelPromptAdapter;
|
||||
listModels?: (apiKey: string) => Promise<AnthropicModelChoice[]>;
|
||||
healthCheck?: (config: KtxLlmConfig) => Promise<KtxLlmHealthCheckResult>;
|
||||
claudeCodeAuthProbe?: () => Promise<{ ok: true } | { ok: false; message: string }>;
|
||||
readGcloudProject?: () => Promise<string | undefined>;
|
||||
listGcloudProjects?: () => Promise<GcloudProjectChoice[]>;
|
||||
spinner?: () => KtxCliSpinner;
|
||||
|
|
@ -238,6 +239,9 @@ export async function fetchAnthropicModels(
|
|||
}
|
||||
|
||||
export function isKtxSetupLlmConfigReady(config: KtxProjectLlmConfig): boolean {
|
||||
if (config.agentRunner?.backend === 'claude-code') {
|
||||
return Boolean(config.models.default);
|
||||
}
|
||||
let resolved: KtxLlmConfig | null;
|
||||
try {
|
||||
resolved = resolveLocalKtxLlmConfig(config, process.env);
|
||||
|
|
@ -274,6 +278,7 @@ function buildProjectLlmConfig(
|
|||
},
|
||||
models: { ...existing.models, default: model },
|
||||
promptCaching: { ...(existing.promptCaching ?? {}), enabled: true, vertexFallbackTo5m: true },
|
||||
agentRunner: { backend: 'ai-sdk' },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +289,7 @@ function buildProjectLlmConfig(
|
|||
},
|
||||
models: { ...existing.models, default: model },
|
||||
promptCaching: { ...(existing.promptCaching ?? {}), enabled: true },
|
||||
agentRunner: { backend: 'ai-sdk' },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -305,6 +311,30 @@ function buildVertexHealthConfig(vertex: { project?: string; location: string },
|
|||
};
|
||||
}
|
||||
|
||||
async function defaultClaudeCodeAuthProbe(): Promise<{ ok: true } | { ok: false; message: string }> {
|
||||
const { query } = await import('@anthropic-ai/claude-agent-sdk');
|
||||
const session = query({
|
||||
prompt: '',
|
||||
options: {
|
||||
tools: [],
|
||||
settingSources: [],
|
||||
skills: [],
|
||||
allowedTools: [],
|
||||
disallowedTools: ['Bash', 'Read', 'Edit', 'Write', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Task'],
|
||||
permissionMode: 'dontAsk',
|
||||
maxTurns: 1,
|
||||
},
|
||||
});
|
||||
try {
|
||||
await session.accountInfo();
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
type LlmHealthProvider = 'Anthropic API' | 'Vertex AI';
|
||||
|
||||
function llmHealthCheckStartText(provider: LlmHealthProvider, model: string): string {
|
||||
|
|
@ -483,13 +513,18 @@ async function chooseBackend(
|
|||
options: [
|
||||
{ value: 'anthropic', label: 'Anthropic API' },
|
||||
{ value: 'vertex', label: 'Google Vertex AI for Anthropic Claude' },
|
||||
{ value: 'claude-code', label: 'Claude Code local session (agent runner only)' },
|
||||
{ value: 'back', label: 'Back' },
|
||||
],
|
||||
});
|
||||
if (choice === 'back') {
|
||||
return { status: 'back' };
|
||||
}
|
||||
return { status: 'ready', backend: choice === 'vertex' ? 'vertex' : 'anthropic', prompted: true };
|
||||
return {
|
||||
status: 'ready',
|
||||
backend: choice === 'vertex' ? 'vertex' : choice === 'claude-code' ? 'claude-code' : 'anthropic',
|
||||
prompted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveProvidedVertexRef(
|
||||
|
|
@ -826,6 +861,21 @@ async function persistLlmConfig(
|
|||
await markKtxSetupStateStepComplete(projectDir, 'llm');
|
||||
}
|
||||
|
||||
async function persistClaudeCodeAgentRunnerConfig(projectDir: string, model: string): Promise<void> {
|
||||
const project = await loadKtxProject({ projectDir });
|
||||
const nextConfig: KtxProjectConfig = {
|
||||
...project.config,
|
||||
llm: {
|
||||
...project.config.llm,
|
||||
provider: { backend: 'none' },
|
||||
models: { ...project.config.llm.models, default: model },
|
||||
agentRunner: { backend: 'claude-code' },
|
||||
},
|
||||
};
|
||||
await writeFile(project.configPath, serializeKtxProjectConfig(nextConfig), 'utf-8');
|
||||
await markKtxSetupStateStepComplete(projectDir, 'llm');
|
||||
}
|
||||
|
||||
function buildInteractiveRetryArgs(args: KtxSetupModelArgs, backend?: KtxSetupLlmBackend): KtxSetupModelArgs {
|
||||
return {
|
||||
projectDir: args.projectDir,
|
||||
|
|
@ -874,6 +924,18 @@ export async function runKtxSetupAnthropicModelStep(
|
|||
? ({ ...attemptArgs, llmBackend: backendChoice.backend, showPromptInstructions: false } satisfies KtxSetupModelArgs)
|
||||
: attemptArgs;
|
||||
|
||||
if (backendChoice.backend === 'claude-code') {
|
||||
const model = backendArgs.anthropicModel ?? 'claude-sonnet-4-6';
|
||||
const probe = await (deps.claudeCodeAuthProbe ?? defaultClaudeCodeAuthProbe)();
|
||||
if (!probe.ok) {
|
||||
io.stderr.write(`Claude Code authentication check failed: ${probe.message}\n`);
|
||||
return { status: 'failed', projectDir: args.projectDir };
|
||||
}
|
||||
await persistClaudeCodeAgentRunnerConfig(args.projectDir, model);
|
||||
io.stdout.write(`│ LLM ready: yes (Claude Code agent runner, ${model})\n`);
|
||||
return { status: 'ready', projectDir: args.projectDir };
|
||||
}
|
||||
|
||||
if (backendChoice.backend === 'vertex') {
|
||||
const vertex = await chooseVertexConfig(backendArgs, io, deps);
|
||||
if (vertex.status === 'back' && backendChoice.prompted) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
agentToolOutputToText,
|
||||
assertAgentToolSet,
|
||||
type AgentToolDefinition,
|
||||
type AgentToolSet,
|
||||
} from './agent-tool.js';
|
||||
import type { AgentRunnerPort, RunLoopParams, RunLoopResult, RunLoopStopReason } from './agent-runner.service.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { AgentRunnerService } from '../agent/index.js';
|
|||
import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js';
|
||||
import { createLocalBundleIngestRuntime } from './local-bundle-runtime.js';
|
||||
import { createLocalBundleIngestRuntime, resolveAgentRunnerForTest } from './local-bundle-runtime.js';
|
||||
|
||||
type RuntimeWithConnectionDeps = {
|
||||
deps: {
|
||||
|
|
@ -55,7 +55,7 @@ describe('createLocalBundleIngestRuntime', () => {
|
|||
}),
|
||||
).toThrow(
|
||||
[
|
||||
'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner.',
|
||||
'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway; llm.agentRunner.backend: claude-code; or an injected agentRunner.',
|
||||
`Configure an Anthropic provider, then rerun ingest:`,
|
||||
` ktx setup --project-dir ${project.projectDir} --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`,
|
||||
].join('\n'),
|
||||
|
|
@ -84,6 +84,18 @@ describe('createLocalBundleIngestRuntime', () => {
|
|||
await mkdir(runtime.storage.resolveUploadDir('job-1'), { recursive: true });
|
||||
});
|
||||
|
||||
it('constructs a Claude Agent SDK runner when llm.agentRunner.backend is claude-code', () => {
|
||||
project.config.llm = {
|
||||
provider: { backend: 'none' },
|
||||
models: { default: 'claude-sonnet-4-6' },
|
||||
agentRunner: { backend: 'claude-code' },
|
||||
};
|
||||
|
||||
const resolved = resolveAgentRunnerForTest({ project, adapters: [] });
|
||||
|
||||
expect(resolved.agentRunner.constructor.name).toBe('ClaudeAgentSdkRunnerService');
|
||||
});
|
||||
|
||||
it('exposes canonical warehouse connection types to local ingest SL tools', async () => {
|
||||
project.config.connections.warehouse = {
|
||||
driver: 'postgres',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|||
import type { KtxLlmProvider } from '@ktx/llm';
|
||||
import YAML from 'yaml';
|
||||
import type { AgentRunnerPort, AgentToolSet } from '../agent/index.js';
|
||||
import { AgentRunnerService as DefaultAgentRunnerService } from '../agent/index.js';
|
||||
import { AgentRunnerService as DefaultAgentRunnerService, ClaudeAgentSdkRunnerService } from '../agent/index.js';
|
||||
import { localConnectionInfoFromConfig, type KtxSqlQueryExecutorPort } from '../connections/index.js';
|
||||
import type { KtxEmbeddingPort, KtxLogger } from '../core/index.js';
|
||||
import { noopLogger, SessionWorktreeService } from '../core/index.js';
|
||||
|
|
@ -570,7 +570,7 @@ function nextLocalJobId(): string {
|
|||
|
||||
function localIngestLlmProviderGuardMessage(projectDir: string): string {
|
||||
return [
|
||||
'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner.',
|
||||
'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway; llm.agentRunner.backend: claude-code; or an injected agentRunner.',
|
||||
'Configure an Anthropic provider, then rerun ingest:',
|
||||
` ktx setup --project-dir ${projectDir} --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`,
|
||||
].join('\n');
|
||||
|
|
@ -587,6 +587,17 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): {
|
|||
return { agentRunner: options.agentRunner, ...(llmProvider ? { llmProvider } : {}) };
|
||||
}
|
||||
|
||||
if (options.project.config.llm.agentRunner.backend === 'claude-code') {
|
||||
return {
|
||||
agentRunner: new ClaudeAgentSdkRunnerService({
|
||||
projectDir: options.project.projectDir,
|
||||
modelSlots: options.project.config.llm.models,
|
||||
logger: options.logger ?? noopLogger,
|
||||
}),
|
||||
...(llmProvider ? { llmProvider } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!llmProvider) {
|
||||
throw new Error(localIngestLlmProviderGuardMessage(options.project.projectDir));
|
||||
}
|
||||
|
|
@ -603,6 +614,8 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): {
|
|||
};
|
||||
}
|
||||
|
||||
export const resolveAgentRunnerForTest = resolveAgentRunner;
|
||||
|
||||
export function createLocalBundleIngestRuntime(
|
||||
options: CreateLocalBundleIngestRuntimeOptions,
|
||||
): LocalBundleIngestRuntime {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,17 @@ describe('createLocalProjectMemoryCapture', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('constructs local memory capture with Claude Agent SDK runner config', async () => {
|
||||
const project = await initKtxProject({ projectDir: tempDir });
|
||||
project.config.llm = {
|
||||
provider: { backend: 'none' },
|
||||
models: { default: 'claude-sonnet-4-6' },
|
||||
agentRunner: { backend: 'claude-code' },
|
||||
};
|
||||
|
||||
expect(() => createLocalProjectMemoryCapture(project)).not.toThrow();
|
||||
});
|
||||
|
||||
it('captures a semantic-layer source for a named local connection id', async () => {
|
||||
const project = await initKtxProject({ projectDir: tempDir });
|
||||
project.config.connections.warehouse = { driver: 'postgres' };
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { join } from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import type { KtxLlmProvider } from '@ktx/llm';
|
||||
import YAML from 'yaml';
|
||||
import { AgentRunnerService, type AgentRunnerPort } from '../agent/index.js';
|
||||
import { AgentRunnerService, ClaudeAgentSdkRunnerService, type AgentRunnerPort } from '../agent/index.js';
|
||||
import { localConnectionInfoFromConfig } from '../connections/index.js';
|
||||
import type { KtxEmbeddingPort, KtxFileStorePort, KtxFileWriteResult } from '../core/index.js';
|
||||
import { type KtxLogger, noopLogger, SessionWorktreeService } from '../core/index.js';
|
||||
|
|
@ -104,10 +104,16 @@ export function createLocalProjectMemoryCapture(
|
|||
});
|
||||
const agentRunner =
|
||||
options.agentRunner ??
|
||||
new AgentRunnerService({
|
||||
llmProvider: requireLlmProvider(llmProvider),
|
||||
logger,
|
||||
});
|
||||
(project.config.llm.agentRunner.backend === 'claude-code'
|
||||
? new ClaudeAgentSdkRunnerService({
|
||||
projectDir: project.projectDir,
|
||||
modelSlots: project.config.llm.models,
|
||||
logger,
|
||||
})
|
||||
: new AgentRunnerService({
|
||||
llmProvider: requireLlmProvider(llmProvider),
|
||||
logger,
|
||||
}));
|
||||
const memoryAgent = new MemoryAgentService({
|
||||
settings: {
|
||||
knowledge: { userScopedKnowledgeEnabled: false },
|
||||
|
|
@ -145,7 +151,9 @@ export function createLocalProjectMemoryCapture(
|
|||
|
||||
function requireLlmProvider(provider: KtxLlmProvider | null | undefined): KtxLlmProvider {
|
||||
if (!provider) {
|
||||
throw new Error('createLocalProjectMemoryCapture requires llm.provider.backend or an injected agentRunner');
|
||||
throw new Error(
|
||||
'createLocalProjectMemoryCapture requires llm.provider.backend, llm.agentRunner.backend: claude-code, or an injected agentRunner',
|
||||
);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -73,6 +73,9 @@ importers:
|
|||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
'@anthropic-ai/claude-agent-sdk':
|
||||
specifier: 0.3.142
|
||||
version: 0.3.142(zod@4.4.3)
|
||||
'@clack/prompts':
|
||||
specifier: 1.4.0
|
||||
version: 1.4.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue