From c4e9324ea0bb3a14c11996616489ca41cffa83c6 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Thu, 21 May 2026 01:38:20 +0200 Subject: [PATCH] feat(cli): add tryUseManagedLocalEmbeddingsDaemon for read-only callers --- .../cli/src/managed-local-embeddings.test.ts | 92 +++++++++++++++++++ packages/cli/src/managed-local-embeddings.ts | 34 ++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/managed-local-embeddings.test.ts b/packages/cli/src/managed-local-embeddings.test.ts index dd35bc2f..0a8b6386 100644 --- a/packages/cli/src/managed-local-embeddings.test.ts +++ b/packages/cli/src/managed-local-embeddings.test.ts @@ -4,9 +4,11 @@ import { ensureManagedLocalEmbeddingsDaemon, managedLocalEmbeddingHealthConfig, managedLocalEmbeddingProjectConfig, + tryUseManagedLocalEmbeddingsDaemon, } from './managed-local-embeddings.js'; import type { ManagedPythonCommandRuntime } from './managed-python-command.js'; import type { ManagedPythonDaemonStartResult } from './managed-python-daemon.js'; +import type { ManagedPythonDaemonLayout } from './managed-python-runtime.js'; function makeIo() { let stdout = ''; @@ -181,3 +183,93 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => { expect(io.stderr()).toContain('Using KTX daemon: http://127.0.0.1:61234'); }); }); + +describe('tryUseManagedLocalEmbeddingsDaemon', () => { + it('returns the daemon when one is running and healthy', async () => { + const readStatus = vi.fn(async () => ({ + kind: 'running' as const, + detail: 'ok', + layout: {} as ManagedPythonDaemonLayout, + state: { + schemaVersion: 1 as const, + pid: 123, + host: '127.0.0.1' as const, + port: 4321, + version: '0.5.0', + features: ['local-embeddings' as const], + startedAt: '2026-05-21T00:00:00Z', + stdoutLog: '/tmp/stdout.log', + stderrLog: '/tmp/stderr.log', + }, + baseUrl: 'http://127.0.0.1:4321', + })); + const result = await tryUseManagedLocalEmbeddingsDaemon({ + cliVersion: '0.5.0', + projectDir: '/work/proj', + readStatus, + }); + expect(result).toEqual({ + baseUrl: 'http://127.0.0.1:4321', + stdoutLog: '/tmp/stdout.log', + stderrLog: '/tmp/stderr.log', + }); + expect(readStatus).toHaveBeenCalledWith({ + cliVersion: '0.5.0', + projectDir: '/work/proj', + }); + }); + + it('returns null when no daemon state exists', async () => { + const readStatus = vi.fn(async () => ({ + kind: 'stopped' as const, + detail: 'no state', + layout: {} as ManagedPythonDaemonLayout, + })); + const result = await tryUseManagedLocalEmbeddingsDaemon({ + cliVersion: '0.5.0', + projectDir: '/work/proj', + readStatus, + }); + expect(result).toBeNull(); + }); + + it('returns null when daemon is stale', async () => { + const readStatus = vi.fn(async () => ({ + kind: 'stale' as const, + detail: 'process gone', + layout: {} as ManagedPythonDaemonLayout, + })); + const result = await tryUseManagedLocalEmbeddingsDaemon({ + cliVersion: '0.5.0', + projectDir: '/work/proj', + readStatus, + }); + expect(result).toBeNull(); + }); + + it('rejects daemons that do not advertise local-embeddings', async () => { + const readStatus = vi.fn(async () => ({ + kind: 'running' as const, + detail: 'ok', + layout: {} as ManagedPythonDaemonLayout, + state: { + schemaVersion: 1 as const, + pid: 123, + host: '127.0.0.1' as const, + port: 4321, + version: '0.5.0', + features: ['core' as const], + startedAt: '2026-05-21T00:00:00Z', + stdoutLog: '/tmp/stdout.log', + stderrLog: '/tmp/stderr.log', + }, + baseUrl: 'http://127.0.0.1:4321', + })); + const result = await tryUseManagedLocalEmbeddingsDaemon({ + cliVersion: '0.5.0', + projectDir: '/work/proj', + readStatus, + }); + expect(result).toBeNull(); + }); +}); diff --git a/packages/cli/src/managed-local-embeddings.ts b/packages/cli/src/managed-local-embeddings.ts index 04baaf5f..5bdfbedc 100644 --- a/packages/cli/src/managed-local-embeddings.ts +++ b/packages/cli/src/managed-local-embeddings.ts @@ -7,7 +7,12 @@ import { type KtxManagedPythonInstallPolicy, type ManagedPythonCommandRuntime, } from './managed-python-command.js'; -import { startManagedPythonDaemon, type ManagedPythonDaemonStartResult } from './managed-python-daemon.js'; +import { + readManagedPythonDaemonStatus, + startManagedPythonDaemon, + type ManagedPythonDaemonStartResult, + type ManagedPythonDaemonStatus, +} from './managed-python-daemon.js'; export interface ManagedLocalEmbeddingsDaemon { baseUrl: string; @@ -93,3 +98,30 @@ export async function ensureManagedLocalEmbeddingsDaemon( stderrLog: daemon.state.stderrLog, }; } + +export interface TryUseManagedLocalEmbeddingsOptions { + cliVersion: string; + projectDir: string; + readStatus?: typeof readManagedPythonDaemonStatus; +} + +export async function tryUseManagedLocalEmbeddingsDaemon( + options: TryUseManagedLocalEmbeddingsOptions, +): Promise { + const readStatus = options.readStatus ?? readManagedPythonDaemonStatus; + const status: ManagedPythonDaemonStatus = await readStatus({ + cliVersion: options.cliVersion, + projectDir: options.projectDir, + }); + if (status.kind !== 'running') { + return null; + } + if (!status.state.features.includes('local-embeddings')) { + return null; + } + return { + baseUrl: status.baseUrl, + stdoutLog: status.state.stdoutLog, + stderrLog: status.state.stderrLog, + }; +}