mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
fix(cli): resolve managed-embeddings daemon URL at project boundary
A clean `ktx setup` was failing verification because the managed local-embeddings daemon URL was passed library-side through `process.env[KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL]`, and the setup flow never wrote that variable. With no resolved URL the embedding provider was null, the deep scan emitted `scan_enrichment_backend_not_configured`, descriptions + embeddings stayed `skipped`, and the agent-readiness check exited 1. Replace the env-var indirection with CLI-side substitution at the project-load boundary. New `loadKtxCliProject` wraps `loadKtxProject`, ensures the managed daemon when `managed:local-embeddings` is present in `config.ingest.embeddings` or `config.scan.enrichment.embeddings`, and substitutes the resolved baseUrl into the in-memory config. Runtime entry points (scan, ingest, public-ingest, admin-reindex) use the new loader; setup-time persistence paths keep raw `loadKtxProject` so the on-disk `ktx.yaml` keeps the portable sentinel. Cleanup follows from the new design: drop `MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV`, remove the env-var lookup branch in `resolveSentenceTransformersBaseUrl`, drop the `env` field from `ManagedLocalEmbeddingsDaemon`, and collapse the manual daemon-ensure dance in `admin-reindex.ts`.
This commit is contained in:
parent
da6d05ed55
commit
38376eece4
12 changed files with 318 additions and 95 deletions
|
|
@ -1,18 +1,17 @@
|
|||
import {
|
||||
createLocalKtxEmbeddingProviderFromConfig,
|
||||
KtxIngestEmbeddingPortAdapter,
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
type KtxEmbeddingPort,
|
||||
} from '@ktx/context';
|
||||
import { reindexLocalIndexes, type ReindexScopeResult, type ReindexSummary } from '@ktx/context/index-sync';
|
||||
import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project';
|
||||
import { type KtxLocalProject } from '@ktx/context/project';
|
||||
import { Option, type Command } from '@commander-js/extra-typings';
|
||||
import { cancel, intro, log, note, outro } from '@clack/prompts';
|
||||
import type { KtxCliCommandContext } from './cli-program.js';
|
||||
import { loadKtxCliProject } from './cli-project.js';
|
||||
import type { KtxCliIo } from './cli-runtime.js';
|
||||
import { resolveOutputMode } from './io/mode.js';
|
||||
import { green, red, SYMBOLS } from './io/symbols.js';
|
||||
import { ensureManagedLocalEmbeddingsDaemon } from './managed-local-embeddings.js';
|
||||
|
||||
export interface KtxAdminReindexArgs {
|
||||
projectDir: string;
|
||||
|
|
@ -49,30 +48,11 @@ export function registerAdminReindexCommand(admin: Command, context: KtxCliComma
|
|||
});
|
||||
}
|
||||
|
||||
async function resolveReindexEmbeddingService(
|
||||
project: KtxLocalProject,
|
||||
args: KtxAdminReindexArgs,
|
||||
io: KtxCliIo,
|
||||
): Promise<KtxEmbeddingPort | null> {
|
||||
function resolveReindexEmbeddingService(project: KtxLocalProject): KtxEmbeddingPort | null {
|
||||
const config = project.config.ingest.embeddings;
|
||||
if (config.backend === 'none') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
config.backend === 'sentence-transformers' &&
|
||||
config.sentenceTransformers?.base_url === MANAGED_SENTENCE_TRANSFORMERS_BASE_URL
|
||||
) {
|
||||
const daemon = await ensureManagedLocalEmbeddingsDaemon({
|
||||
cliVersion: args.cliVersion,
|
||||
projectDir: project.projectDir,
|
||||
installPolicy: 'never',
|
||||
io,
|
||||
});
|
||||
const provider = createLocalKtxEmbeddingProviderFromConfig(config, { env: { ...process.env, ...daemon.env } });
|
||||
return provider ? new KtxIngestEmbeddingPortAdapter(provider) : null;
|
||||
}
|
||||
|
||||
const provider = createLocalKtxEmbeddingProviderFromConfig(config);
|
||||
return provider ? new KtxIngestEmbeddingPortAdapter(provider) : null;
|
||||
}
|
||||
|
|
@ -186,8 +166,13 @@ function renderReindexPretty(summary: ReindexSummary, io: KtxCliIo): void {
|
|||
|
||||
async function runKtxAdminReindex(args: KtxAdminReindexArgs, io: KtxCliIo = process): Promise<number> {
|
||||
try {
|
||||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
const embeddingService = await resolveReindexEmbeddingService(project, args, io);
|
||||
const project = await loadKtxCliProject({
|
||||
projectDir: args.projectDir,
|
||||
cliVersion: args.cliVersion,
|
||||
installPolicy: 'never',
|
||||
io,
|
||||
});
|
||||
const embeddingService = resolveReindexEmbeddingService(project);
|
||||
const summary = await reindexLocalIndexes(project, { force: args.force, embeddingService });
|
||||
const mode = resolveOutputMode({ explicit: args.output, json: args.json, io });
|
||||
|
||||
|
|
|
|||
182
packages/cli/src/cli-project.test.ts
Normal file
182
packages/cli/src/cli-project.test.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
||||
import { buildDefaultKtxProjectConfig, type KtxLocalProject, type KtxProjectConfig } from '@ktx/context/project';
|
||||
import {
|
||||
loadKtxCliProject,
|
||||
projectNeedsManagedLocalEmbeddings,
|
||||
substituteManagedLocalEmbeddingsUrl,
|
||||
} from './cli-project.js';
|
||||
import type { ManagedLocalEmbeddingsDaemon } from './managed-local-embeddings.js';
|
||||
|
||||
const RESOLVED_BASE_URL = 'http://127.0.0.1:51234';
|
||||
|
||||
function makeIo() {
|
||||
let stderr = '';
|
||||
return {
|
||||
io: {
|
||||
stdout: { write: (_chunk: string) => {} },
|
||||
stderr: {
|
||||
write: (chunk: string) => {
|
||||
stderr += chunk;
|
||||
},
|
||||
},
|
||||
},
|
||||
stderr: () => stderr,
|
||||
};
|
||||
}
|
||||
|
||||
function projectWithConfig(config: KtxProjectConfig): KtxLocalProject {
|
||||
return {
|
||||
projectDir: '/work/proj',
|
||||
configPath: '/work/proj/ktx.yaml',
|
||||
config,
|
||||
coreConfig: {} as KtxLocalProject['coreConfig'],
|
||||
git: {} as KtxLocalProject['git'],
|
||||
fileStore: {} as KtxLocalProject['fileStore'],
|
||||
};
|
||||
}
|
||||
|
||||
function withManagedIngestEmbedding(config: KtxProjectConfig): KtxProjectConfig {
|
||||
return {
|
||||
...config,
|
||||
ingest: {
|
||||
...config.ingest,
|
||||
embeddings: {
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
sentenceTransformers: { base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL, pathPrefix: '' },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function withManagedScanEnrichmentEmbedding(config: KtxProjectConfig): KtxProjectConfig {
|
||||
return {
|
||||
...config,
|
||||
scan: {
|
||||
...config.scan,
|
||||
enrichment: {
|
||||
...config.scan.enrichment,
|
||||
embeddings: {
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
sentenceTransformers: { base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL, pathPrefix: '' },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fakeDaemon: ManagedLocalEmbeddingsDaemon = {
|
||||
baseUrl: RESOLVED_BASE_URL,
|
||||
stdoutLog: '/work/proj/.ktx/runtime/daemon.stdout.log',
|
||||
stderrLog: '/work/proj/.ktx/runtime/daemon.stderr.log',
|
||||
};
|
||||
|
||||
describe('projectNeedsManagedLocalEmbeddings', () => {
|
||||
it('returns false when neither ingest nor scan embeddings reference the managed sentinel', () => {
|
||||
expect(projectNeedsManagedLocalEmbeddings(buildDefaultKtxProjectConfig())).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when ingest.embeddings uses the managed sentinel', () => {
|
||||
expect(projectNeedsManagedLocalEmbeddings(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when scan.enrichment.embeddings uses the managed sentinel', () => {
|
||||
expect(
|
||||
projectNeedsManagedLocalEmbeddings(withManagedScanEnrichmentEmbedding(buildDefaultKtxProjectConfig())),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('substituteManagedLocalEmbeddingsUrl', () => {
|
||||
it('rewrites the managed sentinel in both ingest.embeddings and scan.enrichment.embeddings', () => {
|
||||
const config = withManagedScanEnrichmentEmbedding(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
|
||||
const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
|
||||
expect(resolved.ingest.embeddings.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
|
||||
expect(resolved.scan.enrichment.embeddings?.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
|
||||
});
|
||||
|
||||
it('returns the input unchanged when no sentinel is present', () => {
|
||||
const config = buildDefaultKtxProjectConfig();
|
||||
const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
|
||||
expect(resolved.ingest.embeddings).toEqual(config.ingest.embeddings);
|
||||
expect(resolved.scan.enrichment.embeddings).toEqual(config.scan.enrichment.embeddings);
|
||||
});
|
||||
|
||||
it('does not touch non-sentinel sentence-transformers URLs', () => {
|
||||
const config: KtxProjectConfig = {
|
||||
...buildDefaultKtxProjectConfig(),
|
||||
ingest: {
|
||||
...buildDefaultKtxProjectConfig().ingest,
|
||||
embeddings: {
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
sentenceTransformers: { base_url: 'http://localhost:9999', pathPrefix: '' },
|
||||
},
|
||||
},
|
||||
};
|
||||
const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
|
||||
expect(resolved.ingest.embeddings.sentenceTransformers?.base_url).toBe('http://localhost:9999');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadKtxCliProject', () => {
|
||||
it('returns the project unchanged and does not start the daemon when no sentinel is present', async () => {
|
||||
const io = makeIo();
|
||||
const project = projectWithConfig(buildDefaultKtxProjectConfig());
|
||||
const loadProject = vi.fn(async () => project);
|
||||
const ensureLocalEmbeddings = vi.fn(async () => fakeDaemon);
|
||||
|
||||
const result = await loadKtxCliProject(
|
||||
{ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io },
|
||||
{ loadProject, ensureLocalEmbeddings },
|
||||
);
|
||||
|
||||
expect(result).toBe(project);
|
||||
expect(ensureLocalEmbeddings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('starts the daemon and substitutes the resolved URL when ingest.embeddings uses the sentinel', async () => {
|
||||
const io = makeIo();
|
||||
const project = projectWithConfig(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
|
||||
const loadProject = vi.fn(async () => project);
|
||||
const ensureLocalEmbeddings = vi.fn(async () => fakeDaemon);
|
||||
|
||||
const result = await loadKtxCliProject(
|
||||
{ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io },
|
||||
{ loadProject, ensureLocalEmbeddings },
|
||||
);
|
||||
|
||||
expect(ensureLocalEmbeddings).toHaveBeenCalledWith({
|
||||
cliVersion: '0.2.0',
|
||||
projectDir: '/work/proj',
|
||||
installPolicy: 'never',
|
||||
io: io.io,
|
||||
});
|
||||
expect(result.config.ingest.embeddings.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
|
||||
});
|
||||
|
||||
it('does not mutate process.env', async () => {
|
||||
const io = makeIo();
|
||||
const before = process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
|
||||
delete process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
|
||||
try {
|
||||
const project = projectWithConfig(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
|
||||
await loadKtxCliProject(
|
||||
{ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io },
|
||||
{ loadProject: vi.fn(async () => project), ensureLocalEmbeddings: vi.fn(async () => fakeDaemon) },
|
||||
);
|
||||
expect(process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL).toBeUndefined();
|
||||
} finally {
|
||||
if (before === undefined) {
|
||||
delete process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
|
||||
} else {
|
||||
process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL = before;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
91
packages/cli/src/cli-project.ts
Normal file
91
packages/cli/src/cli-project.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
||||
import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project';
|
||||
import type { KtxProjectConfig, KtxProjectEmbeddingConfig } from '@ktx/context/project';
|
||||
import type { KtxCliIo } from './cli-runtime.js';
|
||||
import {
|
||||
ensureManagedLocalEmbeddingsDaemon,
|
||||
type ManagedLocalEmbeddingsDaemon,
|
||||
} from './managed-local-embeddings.js';
|
||||
import type { KtxManagedPythonInstallPolicy } from './managed-python-command.js';
|
||||
|
||||
export interface LoadKtxCliProjectOptions {
|
||||
projectDir: string;
|
||||
cliVersion: string;
|
||||
installPolicy: KtxManagedPythonInstallPolicy;
|
||||
io: KtxCliIo;
|
||||
}
|
||||
|
||||
export interface LoadKtxCliProjectDeps {
|
||||
loadProject?: typeof loadKtxProject;
|
||||
ensureLocalEmbeddings?: (
|
||||
options: Parameters<typeof ensureManagedLocalEmbeddingsDaemon>[0],
|
||||
) => Promise<ManagedLocalEmbeddingsDaemon>;
|
||||
}
|
||||
|
||||
export async function loadKtxCliProject(
|
||||
options: LoadKtxCliProjectOptions,
|
||||
deps: LoadKtxCliProjectDeps = {},
|
||||
): Promise<KtxLocalProject> {
|
||||
const loadProject = deps.loadProject ?? loadKtxProject;
|
||||
const ensureLocalEmbeddings = deps.ensureLocalEmbeddings ?? ensureManagedLocalEmbeddingsDaemon;
|
||||
|
||||
const project = await loadProject({ projectDir: options.projectDir });
|
||||
if (!projectNeedsManagedLocalEmbeddings(project.config)) {
|
||||
return project;
|
||||
}
|
||||
|
||||
const daemon = await ensureLocalEmbeddings({
|
||||
cliVersion: options.cliVersion,
|
||||
projectDir: options.projectDir,
|
||||
installPolicy: options.installPolicy,
|
||||
io: options.io,
|
||||
});
|
||||
|
||||
return {
|
||||
...project,
|
||||
config: substituteManagedLocalEmbeddingsUrl(project.config, daemon.baseUrl),
|
||||
};
|
||||
}
|
||||
|
||||
export function projectNeedsManagedLocalEmbeddings(config: KtxProjectConfig): boolean {
|
||||
return (
|
||||
embeddingUsesManagedSentinel(config.ingest.embeddings) ||
|
||||
embeddingUsesManagedSentinel(config.scan.enrichment.embeddings)
|
||||
);
|
||||
}
|
||||
|
||||
export function substituteManagedLocalEmbeddingsUrl(
|
||||
config: KtxProjectConfig,
|
||||
baseUrl: string,
|
||||
): KtxProjectConfig {
|
||||
const ingestEmbeddings = rewriteManagedEmbeddingConfig(config.ingest.embeddings, baseUrl);
|
||||
const scanEnrichmentEmbeddings = rewriteManagedEmbeddingConfig(config.scan.enrichment.embeddings, baseUrl);
|
||||
return {
|
||||
...config,
|
||||
ingest: { ...config.ingest, embeddings: ingestEmbeddings },
|
||||
scan: {
|
||||
...config.scan,
|
||||
enrichment: { ...config.scan.enrichment, embeddings: scanEnrichmentEmbeddings },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function embeddingUsesManagedSentinel(embedding: KtxProjectEmbeddingConfig | undefined): boolean {
|
||||
return embedding?.sentenceTransformers?.base_url === MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
|
||||
}
|
||||
|
||||
function rewriteManagedEmbeddingConfig<T extends KtxProjectEmbeddingConfig | undefined>(
|
||||
embedding: T,
|
||||
baseUrl: string,
|
||||
): T {
|
||||
if (!embedding || !embeddingUsesManagedSentinel(embedding)) {
|
||||
return embedding;
|
||||
}
|
||||
return {
|
||||
...embedding,
|
||||
sentenceTransformers: {
|
||||
...embedding.sentenceTransformers,
|
||||
base_url: baseUrl,
|
||||
},
|
||||
} as T;
|
||||
}
|
||||
|
|
@ -18,7 +18,8 @@ import {
|
|||
sanitizeMemoryFlowError,
|
||||
} from '@ktx/context/ingest';
|
||||
import type { KtxSqlQueryExecutorPort } from '@ktx/context/connections';
|
||||
import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project';
|
||||
import { type KtxLocalProject } from '@ktx/context/project';
|
||||
import { loadKtxCliProject } from './cli-project.js';
|
||||
import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
|
||||
import { readIngestReportSnapshotFile } from './ingest-report-file.js';
|
||||
import { createCliOperationalLogger } from './io/logger.js';
|
||||
|
|
@ -529,7 +530,7 @@ function assertReportMatchesReplayId(report: IngestReportSnapshot, requestedId:
|
|||
}
|
||||
|
||||
async function readStoredIngestReport(
|
||||
project: Awaited<ReturnType<typeof loadKtxProject>>,
|
||||
project: KtxLocalProject,
|
||||
runId: string | undefined,
|
||||
): Promise<IngestReportSnapshot | null> {
|
||||
return runId ? await getLocalIngestStatus(project, runId) : await getLatestLocalIngestStatus(project);
|
||||
|
|
@ -681,7 +682,14 @@ export async function runKtxIngest(
|
|||
deps: KtxIngestDeps = {},
|
||||
): Promise<number> {
|
||||
try {
|
||||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
const cliVersion = args.command === 'run' ? args.cliVersion : undefined;
|
||||
const runtimeInstallPolicy = args.command === 'run' ? args.runtimeInstallPolicy : undefined;
|
||||
const project = await loadKtxCliProject({
|
||||
projectDir: args.projectDir,
|
||||
cliVersion: cliVersion ?? '0.0.0-private',
|
||||
installPolicy: runtimeInstallPolicy ?? 'never',
|
||||
io,
|
||||
});
|
||||
const env = deps.env ?? process.env;
|
||||
if (args.command === 'run') {
|
||||
const ingestProject =
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
||||
} from '@ktx/context';
|
||||
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
||||
import {
|
||||
ensureManagedLocalEmbeddingsDaemon,
|
||||
managedLocalEmbeddingHealthConfig,
|
||||
|
|
@ -152,9 +149,6 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
|
|||
baseUrl: 'http://127.0.0.1:61234',
|
||||
stdoutLog: '/work/proj/.ktx/runtime/daemon.stdout.log',
|
||||
stderrLog: '/work/proj/.ktx/runtime/daemon.stderr.log',
|
||||
env: {
|
||||
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: 'http://127.0.0.1:61234',
|
||||
},
|
||||
});
|
||||
|
||||
expect(ensureRuntime).toHaveBeenCalledWith({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
||||
} from '@ktx/context';
|
||||
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
||||
import type { KtxProjectEmbeddingConfig } from '@ktx/context/project';
|
||||
import type { KtxEmbeddingConfig } from '@ktx/llm';
|
||||
import type { KtxCliIo } from './cli-runtime.js';
|
||||
|
|
@ -16,7 +13,6 @@ export interface ManagedLocalEmbeddingsDaemon {
|
|||
baseUrl: string;
|
||||
stdoutLog: string;
|
||||
stderrLog: string;
|
||||
env: Record<typeof MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV, string>;
|
||||
}
|
||||
|
||||
export interface ManagedLocalEmbeddingsOptions {
|
||||
|
|
@ -95,8 +91,5 @@ export async function ensureManagedLocalEmbeddingsDaemon(
|
|||
baseUrl: daemon.baseUrl,
|
||||
stdoutLog: daemon.state.stdoutLog,
|
||||
stderrLog: daemon.state.stderrLog,
|
||||
env: {
|
||||
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: daemon.baseUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { type KtxLocalProject, type KtxProjectConnectionConfig, loadKtxProject } from '@ktx/context/project';
|
||||
import { type KtxLocalProject, type KtxProjectConnectionConfig } from '@ktx/context/project';
|
||||
import { loadKtxCliProject } from './cli-project.js';
|
||||
import type { KtxProgressPort } from '@ktx/context/scan';
|
||||
import type { KtxCliIo } from './index.js';
|
||||
import type { KtxIngestArgs, KtxIngestDeps, KtxIngestProgressUpdate } from './ingest.js';
|
||||
|
|
@ -90,7 +91,7 @@ export type KtxPublicIngestProject = Pick<KtxLocalProject, 'projectDir' | 'confi
|
|||
type KtxPublicIngestPhaseKey = 'database-schema' | 'query-history' | 'source-ingest';
|
||||
|
||||
export interface KtxPublicIngestDeps {
|
||||
loadProject?: (options: Parameters<typeof loadKtxProject>[0]) => Promise<KtxPublicIngestProject>;
|
||||
loadProject?: (options: { projectDir: string }) => Promise<KtxPublicIngestProject>;
|
||||
runScan?: (args: KtxScanArgs, io: KtxCliIo, deps?: KtxScanDeps) => Promise<number>;
|
||||
runIngest?: (args: KtxIngestArgs, io: KtxCliIo, deps?: KtxIngestDeps) => Promise<number>;
|
||||
runContextBuild?: (
|
||||
|
|
@ -867,7 +868,15 @@ export async function runKtxPublicIngest(
|
|||
io: KtxCliIo,
|
||||
deps: KtxPublicIngestDeps = {},
|
||||
): Promise<number> {
|
||||
const loadProject = deps.loadProject ?? loadKtxProject;
|
||||
const loadProject =
|
||||
deps.loadProject ??
|
||||
((options: { projectDir: string }) =>
|
||||
loadKtxCliProject({
|
||||
projectDir: options.projectDir,
|
||||
cliVersion: args.cliVersion ?? '0.0.0-private',
|
||||
installPolicy: args.runtimeInstallPolicy ?? 'never',
|
||||
io,
|
||||
}));
|
||||
const project = await loadProject({ projectDir: args.projectDir });
|
||||
if (shouldUseForegroundContextBuildView(args, io)) {
|
||||
const plan = buildPublicIngestPlan(project, args);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { loadKtxProject } from '@ktx/context/project';
|
||||
import {
|
||||
type KtxProgressPort,
|
||||
type KtxScanMode,
|
||||
|
|
@ -6,6 +5,7 @@ import {
|
|||
type KtxScanWarning,
|
||||
runLocalScan,
|
||||
} from '@ktx/context/scan';
|
||||
import { loadKtxCliProject } from './cli-project.js';
|
||||
import type { KtxCliIo } from './index.js';
|
||||
import { createKtxCliLocalIngestAdapters } from './local-adapters.js';
|
||||
import { createKtxCliScanConnector } from './local-scan-connectors.js';
|
||||
|
|
@ -313,7 +313,12 @@ export function createCliScanProgress(
|
|||
|
||||
export async function runKtxScan(args: KtxScanArgs, io: KtxCliIo = process, deps: KtxScanDeps = {}): Promise<number> {
|
||||
try {
|
||||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
const project = await loadKtxCliProject({
|
||||
projectDir: args.projectDir,
|
||||
cliVersion: args.cliVersion ?? '0.0.0-private',
|
||||
installPolicy: args.runtimeInstallPolicy ?? 'never',
|
||||
io,
|
||||
});
|
||||
const managedDaemon = managedDaemonOptionsForScanRun(args, deps.runtimeIo ?? io);
|
||||
const connector =
|
||||
args.mode !== 'structural' || args.detectRelationships
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export {
|
|||
} from './debug-request-recorder.js';
|
||||
export {
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
||||
createLocalKtxEmbeddingProviderFromConfig,
|
||||
createLocalKtxLlmProviderFromConfig,
|
||||
createLocalKtxLlmRuntimeFromConfig,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
} from '../project/config.js';
|
||||
import {
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
||||
createLocalKtxEmbeddingProviderFromConfig,
|
||||
createLocalKtxLlmProviderFromConfig,
|
||||
resolveLocalKtxEmbeddingConfig,
|
||||
|
|
@ -152,32 +151,7 @@ describe('local KTX embedding config', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('resolves managed sentence-transformers config from the CLI-provided daemon URL', () => {
|
||||
const config: KtxProjectEmbeddingConfig = {
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
sentenceTransformers: {
|
||||
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
||||
pathPrefix: '',
|
||||
},
|
||||
batchSize: 32,
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveLocalKtxEmbeddingConfig(config, {
|
||||
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: 'http://127.0.0.1:61234',
|
||||
}),
|
||||
).toEqual({
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
sentenceTransformers: { baseURL: 'http://127.0.0.1:61234', pathPrefix: '' },
|
||||
batchSize: 32,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for managed sentence-transformers when no daemon URL is available', () => {
|
||||
it('returns null when sentence-transformers base_url is still the unresolved managed sentinel', () => {
|
||||
const config: KtxProjectEmbeddingConfig = {
|
||||
backend: 'sentence-transformers',
|
||||
model: 'all-MiniLM-L6-v2',
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ interface LocalConfigDeps {
|
|||
}
|
||||
|
||||
export const MANAGED_SENTENCE_TRANSFORMERS_BASE_URL = 'managed:local-embeddings';
|
||||
export const MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV = 'KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL';
|
||||
|
||||
function resolveOptional(value: string | undefined, env: NodeJS.ProcessEnv): string | undefined {
|
||||
return resolveKtxConfigReference(value, env) || undefined;
|
||||
|
|
@ -141,19 +140,6 @@ export function createLocalKtxLlmRuntimeFromConfig(
|
|||
return (deps.createAiSdkRuntime ?? ((runtimeDeps) => new AiSdkKtxLlmRuntime(runtimeDeps)))({ llmProvider });
|
||||
}
|
||||
|
||||
function resolveSentenceTransformersBaseUrl(
|
||||
value: string | undefined,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): string | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === MANAGED_SENTENCE_TRANSFORMERS_BASE_URL) {
|
||||
return resolveOptional(`env:${MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV}`, env);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function resolveLocalKtxEmbeddingConfig(
|
||||
config: KtxProjectEmbeddingConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
|
|
@ -162,8 +148,8 @@ export function resolveLocalKtxEmbeddingConfig(
|
|||
return null;
|
||||
}
|
||||
if (config.backend === 'sentence-transformers') {
|
||||
const baseURL = resolveSentenceTransformersBaseUrl(config.sentenceTransformers?.base_url, env);
|
||||
if (!baseURL) {
|
||||
const baseURL = config.sentenceTransformers?.base_url;
|
||||
if (!baseURL || baseURL === MANAGED_SENTENCE_TRANSFORMERS_BASE_URL) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -132,9 +132,6 @@ describe('@ktx/context package exports', () => {
|
|||
expect(root.assertSearchBackendCapabilities).toBeTypeOf('function');
|
||||
expect(root.createLocalKtxEmbeddingProviderFromConfig).toBeTypeOf('function');
|
||||
expect(root.MANAGED_SENTENCE_TRANSFORMERS_BASE_URL).toBe('managed:local-embeddings');
|
||||
expect(root.MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV).toBe(
|
||||
'KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL',
|
||||
);
|
||||
expect(agent).toBeDefined();
|
||||
expect(agent.AgentRunnerService).toBeTypeOf('function');
|
||||
expect(root.AgentRunnerService).toBeTypeOf('function');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue