feat(cli): install local embedding runtime on demand

This commit is contained in:
digua committed 2026-08-01 13:19:41 +08:00
1 parent 93fc26fb83
commit f96fef7b46
29 files changed
+741 -55

No files matched your search

+3 -1
View File
@@ -17,13 +17,14 @@ import { registerQueryCommands } from './query/register'
import { registerManifestCommand } from './query/manifest'
import { registerImportCommand } from './import/command'
import { registerValidateCommand } from './validate/command'
import { registerRuntimeCommand } from './semantic-index/runtime-command'
const program = new Command()
program.name('chatlab').description('ChatLab - Chat history analysis tool').version(getVersion(), '-v, --version')
program.hook('preAction', async (_thisCommand, actionCommand) => {
if (actionCommand.name() === 'update') return
if (actionCommand.name() === 'update' || actionCommand.parent?.name() === 'runtime') return
const { checkForUpdatesInteractive } = await import('./update-checker')
await checkForUpdatesInteractive()
})
@@ -53,6 +54,7 @@ registerManifestCommand(program, getVersion())
registerImportCommand(program)
registerValidateCommand(program)
registerRuntimeCommand(program)
program
.command('formats')
+2
View File
@@ -34,6 +34,7 @@ import { createCliRunAgentStream } from '../ai/agent-stream-runner'
import { initSync, cleanupSync } from '../sync'
import { resolveCliPath } from '../paths'
import { assertCliDataDirCompatible } from '../runtime-compat'
import { resolveCliLocalEmbeddingRuntimeConfig } from '../semantic-index/local-runtime'
let server: FastifyInstance | null = null
let dbManager: DatabaseManager | null = null
@@ -169,6 +170,7 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{
pathProvider,
runtime,
nativeBinding,
localEmbeddingRuntime: resolveCliLocalEmbeddingRuntimeConfig(pathProvider.getAiDataDir()),
workerEntryUrl: import.meta.url.endsWith('.ts')
? undefined
: new URL('./semantic-index-worker.mjs', import.meta.url),
@@ -0,0 +1,23 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import test from 'node:test'
import { LOCAL_EMBEDDING_RUNTIME_DIR_ENV, resolveCliLocalEmbeddingRuntimeConfig } from './local-runtime'
test('uses a platform-specific managed runtime below the CLI AI directory by default', () => {
assert.deepEqual(resolveCliLocalEmbeddingRuntimeConfig('/tmp/chatlab-ai', {}), {
baseDir: path.join('/tmp/chatlab-ai', 'runtime', 'local-embedding'),
installMode: 'auto',
})
})
test('treats an explicit runtime directory as preinstalled', () => {
assert.deepEqual(
resolveCliLocalEmbeddingRuntimeConfig('/tmp/chatlab-ai', {
[LOCAL_EMBEDDING_RUNTIME_DIR_ENV]: '/opt/chatlab/local-embedding',
}),
{
baseDir: '/opt/chatlab/local-embedding',
installMode: 'preinstalled',
}
)
})
@@ -0,0 +1,34 @@
import path from 'node:path'
import { createLocalEmbeddingRuntimeManager, type LocalEmbeddingRuntimeConfig } from '@openchatlab/node-runtime'
export const LOCAL_EMBEDDING_RUNTIME_DIR_ENV = 'CHATLAB_LOCAL_EMBEDDING_RUNTIME_DIR'
export function resolveCliLocalEmbeddingRuntimeConfig(
aiDataDir: string,
env: NodeJS.ProcessEnv = process.env
): LocalEmbeddingRuntimeConfig {
const preinstalledDir = env[LOCAL_EMBEDDING_RUNTIME_DIR_ENV]?.trim()
if (preinstalledDir) {
return {
baseDir: path.resolve(preinstalledDir),
installMode: 'preinstalled',
}
}
return {
baseDir: path.join(aiDataDir, 'runtime', 'local-embedding'),
installMode: 'auto',
}
}
export async function installCliLocalEmbeddingRuntime(
aiDataDir: string,
env: NodeJS.ProcessEnv = process.env
): Promise<string> {
const config = resolveCliLocalEmbeddingRuntimeConfig(aiDataDir, env)
const manager = createLocalEmbeddingRuntimeManager({
...config,
// The build-time installer populates a directory that normal app startup treats as read-only.
installMode: 'auto',
})
return await manager.ensureInstalled()
}
@@ -0,0 +1,20 @@
import path from 'node:path'
import os from 'node:os'
import type { Command } from 'commander'
import { installCliLocalEmbeddingRuntime } from './local-runtime'
const LOCAL_EMBEDDING_COMPONENT = 'local-embedding'
export function registerRuntimeCommand(program: Command): void {
program
.command('runtime', { hidden: true })
.command('install <component>', { hidden: true })
.action(async (component: string) => {
if (component !== LOCAL_EMBEDDING_COMPONENT) {
throw new Error(`Unknown ChatLab runtime component: ${component}`)
}
const aiDataDir = path.join(os.homedir(), '.chatlab', 'ai')
const runtimeDir = await installCliLocalEmbeddingRuntime(aiDataDir)
console.log(`Local embedding runtime installed: ${runtimeDir}`)
})
}