akm-cli 0.10.261014-beta → 0.10.261015-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +7 -8
- package/SECURITY.md +3 -3
- package/dist/cli.js +2 -2
- package/dist/commands/health/semantic-search-check.js +2 -2
- package/dist/core/npm-package.js +89 -0
- package/dist/{integrations/harnesses/opencode/runtime → core}/tar.js +25 -16
- package/dist/integrations/harnesses/opencode/runtime/binary.js +8 -75
- package/dist/integrations/harnesses/opencode/runtime/index.js +5 -1
- package/dist/llm/embedders/local.js +23 -58
- package/dist/llm/embedders/onnx-runtime.js +121 -0
- package/dist/scripts/akm-migrate-node.js +15 -28
- package/dist/scripts/akm-migrate.js +15 -28
- package/dist/setup/semantic-assets.js +9 -6
- package/dist/setup/setup.js +56 -2
- package/dist/setup/steps/embeddings.js +21 -4
- package/dist/storage/database.js +17 -57
- package/docs/architecture/akm-core-principles.md +3 -2
- package/docs/architecture/internals/health-advisories.md +1 -1
- package/docs/architecture/internals/storage-locations.md +1 -0
- package/docs/architecture/testing/testing-workflow.md +21 -12
- package/docs/guides/getting-started.md +20 -18
- package/docs/guides/recipes/headless-install.md +13 -8
- package/docs/migration/v0.9-to-v0.10.md +17 -0
- package/docs/reference/cli.md +20 -9
- package/docs/reference/configuration.md +37 -15
- package/package.json +3 -6
- package/dist/standalone/onnxruntime-library.js +0 -52
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* ONNX Runtime for the local embedder: onnxruntime-node's N-API binding and the
|
|
6
|
+
* library it links, kept in akm's cache and loaded from there. `akm setup`
|
|
7
|
+
* fetches them when local embeddings are chosen, and the first local embedding
|
|
8
|
+
* fetches them when setup did not.
|
|
9
|
+
*
|
|
10
|
+
* akm does not depend on the onnxruntime-node package. Its JavaScript loads the
|
|
11
|
+
* binding from a path inside the package, which a compiled standalone binary can
|
|
12
|
+
* only satisfy by writing an embedded copy to the temp directory, and its install
|
|
13
|
+
* script downloads CUDA providers akm never uses. Here the binding is loaded by
|
|
14
|
+
* absolute path with the library beside it in the same folder, where the system
|
|
15
|
+
* loader finds it (`$ORIGIN` on Linux, `@loader_path` on macOS).
|
|
16
|
+
*/
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { toErrorMessage } from "../../core/common.js";
|
|
21
|
+
import { fetchNpmPackageFiles, NPM_REGISTRY } from "../../core/npm-package.js";
|
|
22
|
+
import { getCacheDir } from "../../core/paths.js";
|
|
23
|
+
import { info } from "../../core/warn.js";
|
|
24
|
+
/**
|
|
25
|
+
* The onnxruntime-node release whose binding akm loads, and the only one it supports. akm calls the binding through
|
|
26
|
+
* the contract between that package's own JavaScript and its addon, which no release documents or promises and which
|
|
27
|
+
* moves between releases (after 1.24.3, `loadModel` returns a promise and `initOrtOnce` takes a third argument). What
|
|
28
|
+
* akm relies on: `initOrtOnce(logLevel, Tensor)`; `new InferenceSession()`; `loadModel(path, options)`;
|
|
29
|
+
* `inputMetadata` and `outputMetadata` (`{ name }[]`); `run(feeds, fetches)`, which reads a tensor as
|
|
30
|
+
* `{ type, data, dims, location }` and makes each result with `new Tensor(type, data, dims)`. To move it: change this
|
|
31
|
+
* version, check that contract in the new release's `lib/binding.ts` and `lib/backend.ts`, check the library names in
|
|
32
|
+
* {@link LIBRARIES}, and run the reference-vector test in `tests/integration/semantic-search-e2e.test.ts`, the gate for
|
|
33
|
+
* any bump.
|
|
34
|
+
*/
|
|
35
|
+
export const ONNXRUNTIME_VERSION = "1.24.3";
|
|
36
|
+
/**
|
|
37
|
+
* The platforms akm offers local embeddings on, each with the library the binding links there. Every other platform
|
|
38
|
+
* has keyword search and remote embeddings. A platform is listed once a real run has verified it; Linux means glibc.
|
|
39
|
+
*/
|
|
40
|
+
const LIBRARIES = {
|
|
41
|
+
"linux-x64": "libonnxruntime.so.1",
|
|
42
|
+
"darwin-arm64": `libonnxruntime.${ONNXRUNTIME_VERSION}.dylib`,
|
|
43
|
+
};
|
|
44
|
+
const BINDING = "onnxruntime_binding.node";
|
|
45
|
+
/** This machine as `<platform>-<arch>`, Linux without glibc as `linux-musl-<arch>`. */
|
|
46
|
+
function thisPlatform() {
|
|
47
|
+
const header = process.report?.getReport()?.header;
|
|
48
|
+
const musl = process.platform === "linux" && !header?.glibcVersionRuntime;
|
|
49
|
+
return `${process.platform}${musl ? "-musl" : ""}-${process.arch}`;
|
|
50
|
+
}
|
|
51
|
+
/** Whether akm offers local embeddings on this machine. */
|
|
52
|
+
export function hasLocalEmbeddings() {
|
|
53
|
+
return thisPlatform() in LIBRARIES;
|
|
54
|
+
}
|
|
55
|
+
/** What a machine without local embeddings is told, wherever it is told. */
|
|
56
|
+
export function noLocalEmbeddingsReason() {
|
|
57
|
+
return `Local embeddings are not available on ${thisPlatform()}; choose a remote endpoint or keyword search.`;
|
|
58
|
+
}
|
|
59
|
+
/** A tensor as the binding reads one and makes its results. */
|
|
60
|
+
export class OnnxTensor {
|
|
61
|
+
type;
|
|
62
|
+
data;
|
|
63
|
+
dims;
|
|
64
|
+
location = "cpu";
|
|
65
|
+
constructor(type, data, dims) {
|
|
66
|
+
this.type = type;
|
|
67
|
+
this.data = data;
|
|
68
|
+
this.dims = dims;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Fetch the binding and its library into `directory`, naming the package, its URL and the folder when it cannot. */
|
|
72
|
+
async function fetchOnnxRuntime(directory) {
|
|
73
|
+
const library = LIBRARIES[thisPlatform()];
|
|
74
|
+
if (!library)
|
|
75
|
+
throw new Error(noLocalEmbeddingsReason());
|
|
76
|
+
const source = `bin/napi-v6/${process.platform}/${process.arch}`;
|
|
77
|
+
try {
|
|
78
|
+
info(`Fetching ONNX Runtime ${ONNXRUNTIME_VERSION} for local embeddings (about 86 MB, once) into ${directory}.`);
|
|
79
|
+
// The library first: a binding in the folder means its library is there too.
|
|
80
|
+
await fetchNpmPackageFiles("onnxruntime-node", ONNXRUNTIME_VERSION, directory, [
|
|
81
|
+
{ member: `package/${source}/${library}`, file: library },
|
|
82
|
+
{ member: `package/${source}/${BINDING}`, file: BINDING },
|
|
83
|
+
]);
|
|
84
|
+
return directory;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Could not fetch ONNX Runtime ${ONNXRUNTIME_VERSION} for local embeddings: package onnxruntime-node from ${NPM_REGISTRY}/onnxruntime-node/${ONNXRUNTIME_VERSION} into ${directory}: ${toErrorMessage(error)}. ` +
|
|
88
|
+
`Run \`akm setup --yes\` with network access, or copy ${BINDING} and ${library} from that package's ${source}/ into that folder.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The binding, loaded and initialised from `<cache>/onnxruntime/<version>-<platform>-<arch>/`, where it and its library
|
|
93
|
+
* are fetched first when the binding is not there.
|
|
94
|
+
*/
|
|
95
|
+
async function loadBinding() {
|
|
96
|
+
const directory = path.join(getCacheDir(), "onnxruntime", `${ONNXRUNTIME_VERSION}-${thisPlatform()}`);
|
|
97
|
+
if (!fs.existsSync(path.join(directory, BINDING)))
|
|
98
|
+
await fetchOnnxRuntime(directory);
|
|
99
|
+
const loaded = createRequire(import.meta.url)(path.join(directory, BINDING));
|
|
100
|
+
// 3: errors only.
|
|
101
|
+
loaded.initOrtOnce(3, OnnxTensor);
|
|
102
|
+
return loaded;
|
|
103
|
+
}
|
|
104
|
+
/** Once per process, however many callers ask meanwhile; a failure is not kept, so the next caller tries again. */
|
|
105
|
+
let loading;
|
|
106
|
+
/** The runtime, ready to load models: fetched first when it is not in the cache, then loaded. */
|
|
107
|
+
export async function loadOnnxRuntime() {
|
|
108
|
+
loading ??= loadBinding().catch((error) => {
|
|
109
|
+
loading = undefined;
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
const loaded = await loading;
|
|
113
|
+
return {
|
|
114
|
+
createSession(modelPath) {
|
|
115
|
+
const session = new loaded.InferenceSession();
|
|
116
|
+
session.loadModel(modelPath, { executionProviders: ["cpu"], logSeverityLevel: 3 });
|
|
117
|
+
const outputs = Object.fromEntries(session.outputMetadata.map(({ name }) => [name, null]));
|
|
118
|
+
return { inputNames: session.inputMetadata.map(({ name }) => name), run: (feeds) => session.run(feeds, outputs) };
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -8260,22 +8260,11 @@ function loadBunSqlite() {
|
|
|
8260
8260
|
}
|
|
8261
8261
|
return bunSqliteModule;
|
|
8262
8262
|
}
|
|
8263
|
-
function
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
"
|
|
8268
|
-
"invalid ELF header"
|
|
8269
|
-
];
|
|
8270
|
-
if (!ABI_MISMATCH_SHAPES.some((shape) => message.includes(shape)))
|
|
8271
|
-
return;
|
|
8272
|
-
return `akm could not load 'better-sqlite3': its native binding was built for a different
|
|
8273
|
-
` + `Node.js version than the one now running (this Node is ABI ${process.versions.modules}).
|
|
8274
|
-
` + `This is what happens when Node is upgraded after akm is installed. It is NOT a
|
|
8275
|
-
` + `broken install, and reinstalling akm is not required.
|
|
8276
|
-
` + ` Fix: npm rebuild better-sqlite3 # in akm's install directory
|
|
8277
|
-
` + ` Or: npm install -g akm-cli # reinstall, rebuilding against this Node
|
|
8278
|
-
` + " Or: run akm under Bun, whose built-in SQLite driver needs no native binding.";
|
|
8263
|
+
function missingBindingRemedy() {
|
|
8264
|
+
return `akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
8265
|
+
` + ` • Its package carries prebuilt bindings for Linux and macOS on x64 and arm64 and for
|
|
8266
|
+
` + ` Windows on x64 and arm64; this machine is ${process.platform} ${process.arch}.
|
|
8267
|
+
` + " • Reinstall akm (`npm install -g akm-cli`) if the package is missing (a global\n" + " `npm i -g better-sqlite3` will NOT be resolved — Node loads it from akm's own node_modules).\n" + " • Or run akm under Bun, which has a built-in SQLite driver and needs no native binding.";
|
|
8279
8268
|
}
|
|
8280
8269
|
function loadBetterSqlite3() {
|
|
8281
8270
|
if (!betterSqlite3Ctor) {
|
|
@@ -8284,7 +8273,7 @@ function loadBetterSqlite3() {
|
|
|
8284
8273
|
mod = nodeRequire2("better-sqlite3");
|
|
8285
8274
|
} catch (err) {
|
|
8286
8275
|
const raw = err instanceof Error ? err.message : String(err);
|
|
8287
|
-
throw new Error(`${
|
|
8276
|
+
throw new Error(`${missingBindingRemedy()}
|
|
8288
8277
|
Underlying load error: ${raw}`);
|
|
8289
8278
|
}
|
|
8290
8279
|
betterSqlite3Ctor = mod.default ?? mod;
|
|
@@ -8303,10 +8292,9 @@ function openNodeDatabase(path8, opts) {
|
|
|
8303
8292
|
db = opts ? new BetterSqlite3(path8, options) : new BetterSqlite3(path8);
|
|
8304
8293
|
} catch (err) {
|
|
8305
8294
|
const raw = err instanceof Error ? err.message : String(err);
|
|
8306
|
-
|
|
8307
|
-
if (!remedy)
|
|
8295
|
+
if (!raw.includes("better_sqlite3.node"))
|
|
8308
8296
|
throw err;
|
|
8309
|
-
throw new Error(`${
|
|
8297
|
+
throw new Error(`${missingBindingRemedy()}
|
|
8310
8298
|
Underlying error: ${raw}`);
|
|
8311
8299
|
}
|
|
8312
8300
|
return {
|
|
@@ -8321,14 +8309,10 @@ function openNodeDatabase(path8, opts) {
|
|
|
8321
8309
|
close: db.close.bind(db)
|
|
8322
8310
|
};
|
|
8323
8311
|
}
|
|
8324
|
-
var isBun2, nodeRequire2, bunSqliteModule, betterSqlite3Ctor
|
|
8312
|
+
var isBun2, nodeRequire2, bunSqliteModule, betterSqlite3Ctor;
|
|
8325
8313
|
var init_database = __esm(() => {
|
|
8326
8314
|
isBun2 = !!process.versions?.bun;
|
|
8327
8315
|
nodeRequire2 = createRequire3(import.meta.url);
|
|
8328
|
-
MISSING_BINDING_REMEDY = `akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
8329
|
-
` + ` • Reinstall akm with a working C/C++ build toolchain so its optional
|
|
8330
|
-
` + " 'better-sqlite3' native binding builds (a global `npm i -g better-sqlite3`\n" + ` will NOT be resolved — Node loads it from akm's own node_modules).
|
|
8331
|
-
` + " • Or run akm under Bun, which has a built-in SQLite driver and needs no native build.";
|
|
8332
8316
|
});
|
|
8333
8317
|
|
|
8334
8318
|
// src/core/time.ts
|
|
@@ -55583,11 +55567,12 @@ init_session_log_v2();
|
|
|
55583
55567
|
|
|
55584
55568
|
// src/integrations/harnesses/opencode/runtime/binary.ts
|
|
55585
55569
|
init_common();
|
|
55586
|
-
|
|
55587
|
-
|
|
55570
|
+
|
|
55571
|
+
// src/core/npm-package.ts
|
|
55588
55572
|
init_runtime();
|
|
55573
|
+
init_common();
|
|
55589
55574
|
|
|
55590
|
-
// src/
|
|
55575
|
+
// src/core/tar.ts
|
|
55591
55576
|
class ByteReader {
|
|
55592
55577
|
#chunks;
|
|
55593
55578
|
#pending = Buffer.alloc(0);
|
|
@@ -55627,6 +55612,8 @@ class ByteReader {
|
|
|
55627
55612
|
}
|
|
55628
55613
|
|
|
55629
55614
|
// src/integrations/harnesses/opencode/runtime/binary.ts
|
|
55615
|
+
init_paths();
|
|
55616
|
+
init_warn();
|
|
55630
55617
|
var PACKAGES = new Set([
|
|
55631
55618
|
"@opencode/cli-linux-x64",
|
|
55632
55619
|
"@opencode/cli-linux-x64-baseline",
|
|
@@ -8259,22 +8259,11 @@ function loadBunSqlite() {
|
|
|
8259
8259
|
}
|
|
8260
8260
|
return bunSqliteModule;
|
|
8261
8261
|
}
|
|
8262
|
-
function
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
"
|
|
8267
|
-
"invalid ELF header"
|
|
8268
|
-
];
|
|
8269
|
-
if (!ABI_MISMATCH_SHAPES.some((shape) => message.includes(shape)))
|
|
8270
|
-
return;
|
|
8271
|
-
return `akm could not load 'better-sqlite3': its native binding was built for a different
|
|
8272
|
-
` + `Node.js version than the one now running (this Node is ABI ${process.versions.modules}).
|
|
8273
|
-
` + `This is what happens when Node is upgraded after akm is installed. It is NOT a
|
|
8274
|
-
` + `broken install, and reinstalling akm is not required.
|
|
8275
|
-
` + ` Fix: npm rebuild better-sqlite3 # in akm's install directory
|
|
8276
|
-
` + ` Or: npm install -g akm-cli # reinstall, rebuilding against this Node
|
|
8277
|
-
` + " Or: run akm under Bun, whose built-in SQLite driver needs no native binding.";
|
|
8262
|
+
function missingBindingRemedy() {
|
|
8263
|
+
return `akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
8264
|
+
` + ` \u2022 Its package carries prebuilt bindings for Linux and macOS on x64 and arm64 and for
|
|
8265
|
+
` + ` Windows on x64 and arm64; this machine is ${process.platform} ${process.arch}.
|
|
8266
|
+
` + " \u2022 Reinstall akm (`npm install -g akm-cli`) if the package is missing (a global\n" + " `npm i -g better-sqlite3` will NOT be resolved \u2014 Node loads it from akm's own node_modules).\n" + " \u2022 Or run akm under Bun, which has a built-in SQLite driver and needs no native binding.";
|
|
8278
8267
|
}
|
|
8279
8268
|
function loadBetterSqlite3() {
|
|
8280
8269
|
if (!betterSqlite3Ctor) {
|
|
@@ -8283,7 +8272,7 @@ function loadBetterSqlite3() {
|
|
|
8283
8272
|
mod = nodeRequire2("better-sqlite3");
|
|
8284
8273
|
} catch (err) {
|
|
8285
8274
|
const raw = err instanceof Error ? err.message : String(err);
|
|
8286
|
-
throw new Error(`${
|
|
8275
|
+
throw new Error(`${missingBindingRemedy()}
|
|
8287
8276
|
Underlying load error: ${raw}`);
|
|
8288
8277
|
}
|
|
8289
8278
|
betterSqlite3Ctor = mod.default ?? mod;
|
|
@@ -8302,10 +8291,9 @@ function openNodeDatabase(path8, opts) {
|
|
|
8302
8291
|
db = opts ? new BetterSqlite3(path8, options) : new BetterSqlite3(path8);
|
|
8303
8292
|
} catch (err) {
|
|
8304
8293
|
const raw = err instanceof Error ? err.message : String(err);
|
|
8305
|
-
|
|
8306
|
-
if (!remedy)
|
|
8294
|
+
if (!raw.includes("better_sqlite3.node"))
|
|
8307
8295
|
throw err;
|
|
8308
|
-
throw new Error(`${
|
|
8296
|
+
throw new Error(`${missingBindingRemedy()}
|
|
8309
8297
|
Underlying error: ${raw}`);
|
|
8310
8298
|
}
|
|
8311
8299
|
return {
|
|
@@ -8320,14 +8308,10 @@ function openNodeDatabase(path8, opts) {
|
|
|
8320
8308
|
close: db.close.bind(db)
|
|
8321
8309
|
};
|
|
8322
8310
|
}
|
|
8323
|
-
var isBun2, nodeRequire2, bunSqliteModule, betterSqlite3Ctor
|
|
8311
|
+
var isBun2, nodeRequire2, bunSqliteModule, betterSqlite3Ctor;
|
|
8324
8312
|
var init_database = __esm(() => {
|
|
8325
8313
|
isBun2 = !!process.versions?.bun;
|
|
8326
8314
|
nodeRequire2 = createRequire2(import.meta.url);
|
|
8327
|
-
MISSING_BINDING_REMEDY = `akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
8328
|
-
` + ` \u2022 Reinstall akm with a working C/C++ build toolchain so its optional
|
|
8329
|
-
` + " 'better-sqlite3' native binding builds (a global `npm i -g better-sqlite3`\n" + ` will NOT be resolved \u2014 Node loads it from akm's own node_modules).
|
|
8330
|
-
` + " \u2022 Or run akm under Bun, which has a built-in SQLite driver and needs no native build.";
|
|
8331
8315
|
});
|
|
8332
8316
|
|
|
8333
8317
|
// src/core/time.ts
|
|
@@ -55581,11 +55565,12 @@ init_session_log_v2();
|
|
|
55581
55565
|
|
|
55582
55566
|
// src/integrations/harnesses/opencode/runtime/binary.ts
|
|
55583
55567
|
init_common();
|
|
55584
|
-
|
|
55585
|
-
|
|
55568
|
+
|
|
55569
|
+
// src/core/npm-package.ts
|
|
55586
55570
|
init_runtime();
|
|
55571
|
+
init_common();
|
|
55587
55572
|
|
|
55588
|
-
// src/
|
|
55573
|
+
// src/core/tar.ts
|
|
55589
55574
|
class ByteReader {
|
|
55590
55575
|
#chunks;
|
|
55591
55576
|
#pending = Buffer.alloc(0);
|
|
@@ -55625,6 +55610,8 @@ class ByteReader {
|
|
|
55625
55610
|
}
|
|
55626
55611
|
|
|
55627
55612
|
// src/integrations/harnesses/opencode/runtime/binary.ts
|
|
55613
|
+
init_paths();
|
|
55614
|
+
init_warn();
|
|
55628
55615
|
var PACKAGES = new Set([
|
|
55629
55616
|
"@opencode/cli-linux-x64",
|
|
55630
55617
|
"@opencode/cli-linux-x64-baseline",
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* Semantic-search asset preparation for the
|
|
6
|
-
* runtime (onnxruntime-node
|
|
7
|
-
*
|
|
5
|
+
* Semantic-search asset preparation for setup. For the built-in local embedder
|
|
6
|
+
* that is its runtime (onnxruntime-node's binding and library) and its model,
|
|
7
|
+
* both fetched into akm's cache; for an embedding endpoint, a check that it
|
|
8
|
+
* answers.
|
|
8
9
|
*/
|
|
9
10
|
import * as p from "../cli/clack.js";
|
|
10
11
|
import { isHttpUrl } from "../core/common.js";
|
|
11
12
|
import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL } from "../llm/embedder.js";
|
|
12
13
|
import { effectiveEmbedding } from "../llm/embedders/connection.js";
|
|
14
|
+
import { ONNXRUNTIME_VERSION } from "../llm/embedders/onnx-runtime.js";
|
|
13
15
|
// Approximate first-download size of the default local model (DEFAULT_LOCAL_MODEL), used in the setup note.
|
|
14
16
|
const LOCAL_MODEL_APPROX_SIZE_MB = 127;
|
|
15
17
|
export function isRemoteEmbeddingConfig(embedding) {
|
|
@@ -23,7 +25,8 @@ export function describeSemanticSearchAssets(embedding) {
|
|
|
23
25
|
return [`• Embedding endpoint: ${embedding?.provider ?? "custom"} / ${embedding?.model} (no local model download)`];
|
|
24
26
|
}
|
|
25
27
|
return [
|
|
26
|
-
`•
|
|
28
|
+
`• ONNX Runtime ${ONNXRUNTIME_VERSION}, the local embedder's runtime (~86 MB download, once)`,
|
|
29
|
+
`• Local embedding model: ${embedding?.localModel ?? DEFAULT_LOCAL_MODEL} (~${LOCAL_MODEL_APPROX_SIZE_MB} MB download, once)`,
|
|
27
30
|
];
|
|
28
31
|
}
|
|
29
32
|
export async function prepareSemanticSearchAssets(config) {
|
|
@@ -32,7 +35,7 @@ export async function prepareSemanticSearchAssets(config) {
|
|
|
32
35
|
const spin = p.spinner();
|
|
33
36
|
spin.start(remote
|
|
34
37
|
? "Checking remote embedding endpoint..."
|
|
35
|
-
: `Downloading local
|
|
38
|
+
: `Downloading the local embedder's runtime and model (${embedding?.localModel ?? DEFAULT_LOCAL_MODEL})...`);
|
|
36
39
|
const result = await checkEmbeddingAvailability(embedding);
|
|
37
40
|
if (!result.available) {
|
|
38
41
|
spin.stop("Semantic-search assets could not be prepared.");
|
|
@@ -50,6 +53,6 @@ export async function prepareSemanticSearchAssets(config) {
|
|
|
50
53
|
return { ok: false, reason: "local-model-download", message: result.message };
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
|
-
spin.stop(remote ? "Remote embedding endpoint is ready." : "Local
|
|
56
|
+
spin.stop(remote ? "Remote embedding endpoint is ready." : "Local embedder's runtime and model downloaded and ready.");
|
|
54
57
|
return { ok: true };
|
|
55
58
|
}
|
package/dist/setup/setup.js
CHANGED
|
@@ -19,7 +19,9 @@ import path from "node:path";
|
|
|
19
19
|
import { isDeepStrictEqual } from "node:util";
|
|
20
20
|
import * as p from "../cli/clack.js";
|
|
21
21
|
import { akmInit } from "../commands/sources/init.js";
|
|
22
|
+
import { targetRows } from "../commands/targets.js";
|
|
22
23
|
import { deriveBundleIds } from "../core/bundle-id.js";
|
|
24
|
+
import { toErrorMessage } from "../core/common.js";
|
|
23
25
|
import { bundleEntryToSourceEntry, DEFAULT_CONFIG, defaultConnection, loadUserConfig, mutateConfigWithPrecommit, parseAndValidateConfigText, primaryBundlePath, validateCompleteConfig, } from "../core/config/config.js";
|
|
24
26
|
import { readConfigText } from "../core/config/config-io.js";
|
|
25
27
|
import { listTopLevelConfigKeys } from "../core/config/config-schema.js";
|
|
@@ -30,6 +32,8 @@ import { getConfigPath, getDefaultStashDir, isTransientStashPath } from "../core
|
|
|
30
32
|
import { warn, warnOnce } from "../core/warn.js";
|
|
31
33
|
import { akmIndex } from "../indexer/indexer.js";
|
|
32
34
|
import { readLockfile } from "../integrations/lockfile.js";
|
|
35
|
+
import { checkEmbeddingAvailability } from "../llm/embedder.js";
|
|
36
|
+
import { effectiveEmbedding } from "../llm/embedders/connection.js";
|
|
33
37
|
import { detectEnvironment, renderDetectionSummary } from "./detect.js";
|
|
34
38
|
import { bail, prompt } from "./prompt.js";
|
|
35
39
|
import { prepareSemanticSearchAssets } from "./semantic-assets.js";
|
|
@@ -305,6 +309,50 @@ function finalizeSetupDraft(draft) {
|
|
|
305
309
|
delete finalized.defaultBundle;
|
|
306
310
|
return finalized;
|
|
307
311
|
}
|
|
312
|
+
/** Where a process of `config` runs, as `akm info` and `akm health` say it. */
|
|
313
|
+
function processRow(config, process) {
|
|
314
|
+
return targetRows({ config }).find((row) => row.type === "process" && row.process === process);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* What `--yes` and `--from` do for local embeddings once the config is saved: fetch the local embedder's runtime and
|
|
318
|
+
* model and load them. Undefined when the saved config does not embed locally.
|
|
319
|
+
*/
|
|
320
|
+
async function prepareLocalEmbeddings(config, noProbe) {
|
|
321
|
+
if (processRow(config, "embedding")?.kind !== "local")
|
|
322
|
+
return undefined;
|
|
323
|
+
if (noProbe)
|
|
324
|
+
return "skipped";
|
|
325
|
+
const result = await checkEmbeddingAvailability(effectiveEmbedding(config));
|
|
326
|
+
if (result.available)
|
|
327
|
+
return "ready";
|
|
328
|
+
warn(`[akm setup] Semantic search is blocked: ${result.message}`);
|
|
329
|
+
return `blocked: ${result.message}`;
|
|
330
|
+
}
|
|
331
|
+
/** The wizard's offer to fetch what akm's own agent runtime runs, when the saved config runs agent work there. */
|
|
332
|
+
async function offerAgentRuntimeFetch(config) {
|
|
333
|
+
if (processRow(config, "agents")?.kind !== "connection")
|
|
334
|
+
return;
|
|
335
|
+
const { agentRuntime } = await import("../integrations/agent/runtime.js");
|
|
336
|
+
const pending = agentRuntime.pendingFetch();
|
|
337
|
+
if (pending === undefined)
|
|
338
|
+
return;
|
|
339
|
+
const fetchNow = await prompt(() => p.confirm({
|
|
340
|
+
message: `Agent work runs on akm's own runtime. Fetch ${pending} for it now, rather than on the first agent run?`,
|
|
341
|
+
initialValue: true,
|
|
342
|
+
}));
|
|
343
|
+
if (!fetchNow)
|
|
344
|
+
return;
|
|
345
|
+
const spin = p.spinner();
|
|
346
|
+
spin.start(`Fetching ${pending}...`);
|
|
347
|
+
try {
|
|
348
|
+
await agentRuntime.fetch();
|
|
349
|
+
spin.stop("akm's own agent runtime is ready.");
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
spin.stop("akm's own agent runtime could not be fetched.");
|
|
353
|
+
p.log.warn(`${toErrorMessage(error)}\nThe first agent run tries again.`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
308
356
|
const NOTHING_DETECTED = {
|
|
309
357
|
harnesses: [],
|
|
310
358
|
localServers: [],
|
|
@@ -538,6 +586,8 @@ export async function runSetupWizard(opts) {
|
|
|
538
586
|
// Non-interactive setup paths never reach this interactive-only step.
|
|
539
587
|
p.log.step("Scheduled Tasks");
|
|
540
588
|
await stepScheduledTasks();
|
|
589
|
+
if (opts?.noProbe !== true)
|
|
590
|
+
await offerAgentRuntimeFetch(savedConfig);
|
|
541
591
|
if (semanticSearchMode.mode === "auto") {
|
|
542
592
|
if (semanticSearchMode.prepareAssets) {
|
|
543
593
|
const ready = await prepareSemanticSearchAssets(savedConfig);
|
|
@@ -624,12 +674,13 @@ export async function runSetupWithDefaults(opts) {
|
|
|
624
674
|
const { config, credentials } = await applyNonInteractiveDefaults(current, stashDir, opts);
|
|
625
675
|
const finalConfig = finalizeSetupDraft(config);
|
|
626
676
|
validateCompleteConfig(finalConfig);
|
|
627
|
-
const { precommit: initResult } = await saveSetupConfig(current, finalConfig, async () => {
|
|
677
|
+
const { config: savedConfig, precommit: initResult } = await saveSetupConfig(current, finalConfig, async () => {
|
|
628
678
|
storeCredentials(credentials);
|
|
629
679
|
if (opts.noInit)
|
|
630
680
|
return undefined;
|
|
631
681
|
return akmInit({ dir: stashDir, setDefault: true, persistConfig: false });
|
|
632
682
|
});
|
|
683
|
+
const semanticAssets = await prepareLocalEmbeddings(savedConfig, opts.noProbe);
|
|
633
684
|
return {
|
|
634
685
|
configPath: getConfigPath(),
|
|
635
686
|
bundleDir: stashDir,
|
|
@@ -637,6 +688,7 @@ export async function runSetupWithDefaults(opts) {
|
|
|
637
688
|
written: true,
|
|
638
689
|
fields: Object.keys(finalConfig).filter((k) => finalConfig[k] !== undefined),
|
|
639
690
|
runs: describeRuns(finalConfig),
|
|
691
|
+
...(semanticAssets ? { semanticAssets } : {}),
|
|
640
692
|
};
|
|
641
693
|
}
|
|
642
694
|
/**
|
|
@@ -736,12 +788,13 @@ export async function runSetupFromConfig(opts) {
|
|
|
736
788
|
const finalizedMerged = finalizeSetupDraft(merged);
|
|
737
789
|
// Reject an invalid merged config before touching the stash.
|
|
738
790
|
validateCompleteConfig(finalizedMerged);
|
|
739
|
-
const { precommit: initResult } = await saveSetupConfig(current, finalizedMerged, async () => {
|
|
791
|
+
const { config: savedConfig, precommit: initResult } = await saveSetupConfig(current, finalizedMerged, async () => {
|
|
740
792
|
storeCredentials(credentials);
|
|
741
793
|
if (opts.noInit)
|
|
742
794
|
return undefined;
|
|
743
795
|
return akmInit({ dir: stashDir, setDefault: true, persistConfig: false });
|
|
744
796
|
});
|
|
797
|
+
const semanticAssets = await prepareLocalEmbeddings(savedConfig, opts.noProbe);
|
|
745
798
|
return {
|
|
746
799
|
configPath: getConfigPath(),
|
|
747
800
|
bundleDir: stashDir,
|
|
@@ -749,6 +802,7 @@ export async function runSetupFromConfig(opts) {
|
|
|
749
802
|
written: true,
|
|
750
803
|
fields: Object.keys(incoming).filter((k) => incoming[k] !== undefined),
|
|
751
804
|
runs: describeRuns(finalizedMerged),
|
|
805
|
+
...(semanticAssets ? { semanticAssets } : {}),
|
|
752
806
|
};
|
|
753
807
|
}
|
|
754
808
|
// ── Setup --from <file> bootstrap helper ────────────────────────────────────
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
5
|
* Setup step 4: embeddings. A model the connection serves, akm's built-in local
|
|
6
|
-
* embedder, another endpoint, or off (which is semantic search off).
|
|
6
|
+
* embedder, another endpoint, or off (which is semantic search off). The local
|
|
7
|
+
* embedder is offered only where akm has local embeddings
|
|
8
|
+
* (`llm/embedders/onnx-runtime.ts`); everywhere else the choice is an endpoint
|
|
9
|
+
* or keyword search.
|
|
7
10
|
*/
|
|
8
11
|
import * as p from "../../cli/clack.js";
|
|
9
12
|
import { defaultConnection, } from "../../core/config/config.js";
|
|
@@ -11,6 +14,7 @@ import { apiKeyReference, resolveApiKey, storedCredential } from "../../core/con
|
|
|
11
14
|
import { sameEndpoint } from "../../core/endpoint-url.js";
|
|
12
15
|
import { UsageError } from "../../core/errors.js";
|
|
13
16
|
import { redactUrlCredentials } from "../../core/redaction.js";
|
|
17
|
+
import { hasLocalEmbeddings, noLocalEmbeddingsReason } from "../../llm/embedders/onnx-runtime.js";
|
|
14
18
|
import { isEmbeddingModel } from "../../llm/model-list.js";
|
|
15
19
|
import { prompt } from "../prompt.js";
|
|
16
20
|
import { describeSemanticSearchAssets, isRemoteEmbeddingConfig } from "../semantic-assets.js";
|
|
@@ -45,6 +49,10 @@ function warnOfDroppedKey(current, endpoint, keyGiven, interactive) {
|
|
|
45
49
|
}
|
|
46
50
|
function fromFlags(input) {
|
|
47
51
|
const { flags } = input;
|
|
52
|
+
if (flags.embedding === "local" && !hasLocalEmbeddings()) {
|
|
53
|
+
warnUser(`${noLocalEmbeddingsReason()} --embedding local is ignored: semantic search stays off.`, false);
|
|
54
|
+
return { kind: "off" };
|
|
55
|
+
}
|
|
48
56
|
if (flags.embedding === "off" || flags.embedding === "local")
|
|
49
57
|
return { kind: flags.embedding };
|
|
50
58
|
const model = flags.embedding;
|
|
@@ -81,9 +89,12 @@ async function askSource(input) {
|
|
|
81
89
|
const hasServing = servingConnection(input) !== undefined;
|
|
82
90
|
const heldModel = hasServing && current.embedding?.endpoint === undefined ? current.embedding?.model : undefined;
|
|
83
91
|
const connectionModels = [...new Set([...(heldModel ? [heldModel] : []), ...served])];
|
|
92
|
+
const local = hasLocalEmbeddings();
|
|
93
|
+
if (!local)
|
|
94
|
+
p.log.info(noLocalEmbeddingsReason());
|
|
84
95
|
const options = [
|
|
85
96
|
...connectionModels.map((model) => ({ value: `model:${model}`, label: model, hint: "served by the connection" })),
|
|
86
|
-
{ value: "local", label: "Built-in local embedder", hint: "no server needed" },
|
|
97
|
+
...(local ? [{ value: "local", label: "Built-in local embedder", hint: "no server needed" }] : []),
|
|
87
98
|
{ value: "endpoint", label: "Another endpoint" },
|
|
88
99
|
{ value: "off", label: "Off", hint: "keyword search only" },
|
|
89
100
|
];
|
|
@@ -93,7 +104,9 @@ async function askSource(input) {
|
|
|
93
104
|
? `model:${heldModel}`
|
|
94
105
|
: served[0]
|
|
95
106
|
? `model:${served[0]}`
|
|
96
|
-
:
|
|
107
|
+
: local
|
|
108
|
+
? "local"
|
|
109
|
+
: "off";
|
|
97
110
|
const choice = await prompt(() => p.select({ message: "Which embedding source should akm use?", options, initialValue }));
|
|
98
111
|
if (choice === "local" || choice === "off")
|
|
99
112
|
return { kind: choice };
|
|
@@ -104,7 +117,7 @@ async function askSource(input) {
|
|
|
104
117
|
...(current.embedding?.endpoint ? { initialValue: current.embedding.endpoint } : {}),
|
|
105
118
|
});
|
|
106
119
|
if (endpoint === undefined)
|
|
107
|
-
return { kind: "local" };
|
|
120
|
+
return { kind: local ? "local" : "off" };
|
|
108
121
|
// A key belongs to the endpoint it was given for, so the current one is offered for that endpoint alone.
|
|
109
122
|
const unchangedEndpoint = current.embedding?.endpoint !== undefined && sameEndpoint(current.embedding.endpoint, endpoint);
|
|
110
123
|
const held = unchangedEndpoint
|
|
@@ -141,6 +154,10 @@ export async function stepEmbeddings(input) {
|
|
|
141
154
|
return { answer, semantic: { mode: "off", prepareAssets: false } };
|
|
142
155
|
const after = embeddingAfter(input, answer);
|
|
143
156
|
p.note(describeSemanticSearchAssets(after).join("\n"), "Semantic Search Assets");
|
|
157
|
+
if (!input.probe) {
|
|
158
|
+
p.log.info("Offline run: semantic search is not checked or prepared now; the first `akm index` does both.");
|
|
159
|
+
return { answer, semantic: { mode: "auto", prepareAssets: false } };
|
|
160
|
+
}
|
|
144
161
|
const prepareAssets = await prompt(() => p.confirm({
|
|
145
162
|
message: isRemoteEmbeddingConfig(after)
|
|
146
163
|
? "Check the embedding endpoint and verify semantic search now?"
|