From 8aa9ab88439fac7b32a94f676169dbd748312f39 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Thu, 21 May 2026 01:43:19 +0200 Subject: [PATCH] fix(cli): wire sl search through resolveProjectEmbeddingProvider so semantic lane works --- packages/cli/src/commands/sl-commands.ts | 2 + packages/cli/src/index.test.ts | 8 +-- packages/cli/src/sl.test.ts | 70 ++++++++++++++++++++++-- packages/cli/src/sl.ts | 53 ++++++++++++------ 4 files changed, 105 insertions(+), 28 deletions(-) diff --git a/packages/cli/src/commands/sl-commands.ts b/packages/cli/src/commands/sl-commands.ts index 6a03eb67..a4cb644c 100644 --- a/packages/cli/src/commands/sl-commands.ts +++ b/packages/cli/src/commands/sl-commands.ts @@ -77,6 +77,7 @@ export function registerSlCommands(program: Command, context: KtxCliCommandConte connectionId: options.connectionId, output: options.output, json: options.json, + cliVersion: context.packageInfo.version, }); return; } @@ -88,6 +89,7 @@ export function registerSlCommands(program: Command, context: KtxCliCommandConte ...(options.limit !== undefined ? { limit: options.limit } : {}), output: options.output, json: options.json, + cliVersion: context.packageInfo.version, }); }, ); diff --git a/packages/cli/src/index.test.ts b/packages/cli/src/index.test.ts index 53a7b593..4f9939e2 100644 --- a/packages/cli/src/index.test.ts +++ b/packages/cli/src/index.test.ts @@ -244,7 +244,7 @@ describe('runKtxCli', () => { ), ).resolves.toBe(0); expect(sl).toHaveBeenCalledWith( - { + expect.objectContaining({ command: 'search', projectDir: tempDir, connectionId: 'warehouse', @@ -252,7 +252,7 @@ describe('runKtxCli', () => { limit: 5, json: true, output: undefined, - }, + }), searchIo.io, ); @@ -261,13 +261,13 @@ describe('runKtxCli', () => { runKtxCli(['--project-dir', tempDir, 'sl', '--connection-id', 'warehouse', '--json'], bareIo.io, { sl }), ).resolves.toBe(0); expect(sl).toHaveBeenLastCalledWith( - { + expect.objectContaining({ command: 'list', projectDir: tempDir, connectionId: 'warehouse', json: true, output: undefined, - }, + }), bareIo.io, ); diff --git a/packages/cli/src/sl.test.ts b/packages/cli/src/sl.test.ts index 6486e536..574420db 100644 --- a/packages/cli/src/sl.test.ts +++ b/packages/cli/src/sl.test.ts @@ -77,12 +77,24 @@ describe('runKtxSl', () => { expect(validateIo.stdout()).toContain('Valid semantic-layer source: warehouse/orders'); const listIo = makeIo(); - await expect(runKtxSl({ command: 'list', projectDir, connectionId: 'warehouse' }, listIo.io)).resolves.toBe(0); + await expect( + runKtxSl({ command: 'list', projectDir, connectionId: 'warehouse', cliVersion: '0.0.0-test' }, listIo.io), + ).resolves.toBe(0); expect(listIo.stdout()).toContain('warehouse\torders\tcolumns=1\tmeasures=0\tjoins=0'); const searchIo = makeIo(); await expect( - runKtxSl({ command: 'search', projectDir, connectionId: 'warehouse', query: 'order', json: true }, searchIo.io), + runKtxSl( + { + command: 'search', + projectDir, + connectionId: 'warehouse', + query: 'order', + json: true, + cliVersion: '0.0.0-test', + }, + searchIo.io, + ), ).resolves.toBe(0); expect(JSON.parse(searchIo.stdout())).toMatchObject({ kind: 'list', @@ -106,7 +118,14 @@ describe('runKtxSl', () => { const searchIo = makeIo(); await expect( runKtxSl( - { command: 'search', projectDir, connectionId: 'warehouse', query: 'order', output: 'pretty' }, + { + command: 'search', + projectDir, + connectionId: 'warehouse', + query: 'order', + output: 'pretty', + cliVersion: '0.0.0-test', + }, searchIo.io, ), ).resolves.toBe(0); @@ -136,7 +155,14 @@ describe('runKtxSl', () => { const listIo = makeIo(); await expect( runKtxSl( - { command: 'search', projectDir, connectionId: 'warehouse', query: 'paid', json: true }, + { + command: 'search', + projectDir, + connectionId: 'warehouse', + query: 'paid', + json: true, + cliVersion: '0.0.0-test', + }, listIo.io, ), ).resolves.toBe(0); @@ -575,7 +601,7 @@ joins: [] const listIo = makeIo(); const code = await runKtxSl( - { command: 'list', projectDir, connectionId: 'warehouse', output: 'json' }, + { command: 'list', projectDir, connectionId: 'warehouse', output: 'json', cliVersion: '0.0.0-test' }, listIo.io, ); expect(code).toBe(0); @@ -601,13 +627,45 @@ joins: [] }); }); + it('passes a managed-daemon-backed embedding service into the search', async () => { + const projectDir = join(tempDir, 'resolver-project'); + const project = await initKtxProject({ projectDir }); + const search = vi.fn(async () => []); + const searchIo = makeIo(); + await expect( + runKtxSl( + { + command: 'search', + projectDir: project.projectDir, + query: 'income', + cliVersion: '0.5.0', + json: true, + }, + searchIo.io, + { + loadProject: async () => project, + resolveEmbeddingProvider: async () => ({ + kind: 'managed-running', + provider: { id: 'fake' } as never, + baseUrl: 'http://127.0.0.1:51234', + }), + searchLocalSlSources: search, + }, + ), + ).resolves.toBe(0); + expect(search).toHaveBeenCalledWith( + project, + expect.objectContaining({ embeddingService: expect.any(Object) }), + ); + }); + it('emits sl list with grouping and Clack-style framing when output=pretty', async () => { const projectDir = join(tempDir, 'project'); await seedSlSource({ projectDir }); const listIo = makeIo(); const code = await runKtxSl( - { command: 'list', projectDir, connectionId: 'warehouse', output: 'pretty' }, + { command: 'list', projectDir, connectionId: 'warehouse', output: 'pretty', cliVersion: '0.0.0-test' }, listIo.io, ); expect(code).toBe(0); diff --git a/packages/cli/src/sl.ts b/packages/cli/src/sl.ts index 4049936a..5964d4e5 100644 --- a/packages/cli/src/sl.ts +++ b/packages/cli/src/sl.ts @@ -1,22 +1,22 @@ import { readFile } from 'node:fs/promises'; import { createDefaultLocalQueryExecutor, type KtxSqlQueryExecutorPort } from '@ktx/context/connections'; -import { - createLocalKtxEmbeddingProviderFromConfig, - KtxIngestEmbeddingPortAdapter, - type KtxEmbeddingPort, -} from '@ktx/context'; +import { KtxIngestEmbeddingPortAdapter, type KtxEmbeddingPort } from '@ktx/context'; import type { KtxSemanticLayerComputePort } from '@ktx/context/daemon'; import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project'; import { compileLocalSlQuery, listLocalSlSources, readLocalSlSource, - searchLocalSlSources, + searchLocalSlSources as defaultSearchLocalSlSources, validateLocalSlSource, type LocalSlSourceSearchResult, type LocalSlSourceSummary, type SemanticLayerQueryInput, } from '@ktx/context/sl'; +import { + resolveProjectEmbeddingProvider, + type EmbeddingProviderResolution, +} from './embedding-resolution.js'; import type { PrintListColumn } from './io/print-list.js'; import { createManagedPythonSemanticLayerComputePort, @@ -29,7 +29,14 @@ profileMark('module:sl'); type SlQueryFormat = 'json' | 'sql'; export type KtxSlArgs = - | { command: 'list'; projectDir: string; connectionId?: string; output?: string; json?: boolean } + | { + command: 'list'; + projectDir: string; + connectionId?: string; + output?: string; + json?: boolean; + cliVersion: string; + } | { command: 'search'; projectDir: string; @@ -38,6 +45,7 @@ export type KtxSlArgs = limit?: number; output?: string; json?: boolean; + cliVersion: string; } | { command: 'validate'; projectDir: string; connectionId: string; sourceName: string } | { @@ -60,8 +68,8 @@ interface KtxSlIo { interface KtxSlDeps { loadProject?: typeof loadKtxProject; - embeddingService?: KtxEmbeddingPort | null; - createEmbeddingProvider?: typeof createLocalKtxEmbeddingProviderFromConfig; + resolveEmbeddingProvider?: typeof resolveProjectEmbeddingProvider; + searchLocalSlSources?: typeof defaultSearchLocalSlSources; createSemanticLayerCompute?: () => KtxSemanticLayerComputePort; createManagedSemanticLayerCompute?: (options: { cliVersion: string; @@ -71,14 +79,15 @@ interface KtxSlDeps { createQueryExecutor?: () => KtxSqlQueryExecutorPort; } -function slSearchEmbeddingService(project: KtxLocalProject, deps: KtxSlDeps): KtxEmbeddingPort | null { - if ('embeddingService' in deps) { - return deps.embeddingService ?? null; +function resolutionToEmbeddingPort(resolution: EmbeddingProviderResolution): KtxEmbeddingPort | null { + if ( + resolution.kind === 'configured' || + resolution.kind === 'managed-running' || + resolution.kind === 'managed-started' + ) { + return new KtxIngestEmbeddingPortAdapter(resolution.provider); } - const provider = (deps.createEmbeddingProvider ?? createLocalKtxEmbeddingProviderFromConfig)( - project.config.ingest.embeddings, - ); - return provider ? new KtxIngestEmbeddingPortAdapter(provider) : null; + return null; } async function printSlSources(input: { @@ -188,10 +197,18 @@ export async function runKtxSl(args: KtxSlArgs, io: KtxSlIo = process, deps: Ktx return 0; } if (args.command === 'search') { - const sources = await searchLocalSlSources(project, { + const resolver = deps.resolveEmbeddingProvider ?? resolveProjectEmbeddingProvider; + const resolution = await resolver(project, { + mode: 'use-if-running', + cliVersion: args.cliVersion, + io, + }); + const embeddingService = resolutionToEmbeddingPort(resolution); + const search = deps.searchLocalSlSources ?? defaultSearchLocalSlSources; + const sources = await search(project, { connectionId: args.connectionId, query: args.query, - embeddingService: slSearchEmbeddingService(project, deps), + embeddingService, limit: args.limit, }); await printSlSources({