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.
@@ -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 abiMismatchRemedy(message) {
8264
- const ABI_MISMATCH_SHAPES = [
8265
- "did not self-register",
8266
- "NODE_MODULE_VERSION",
8267
- "was compiled against a different",
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(`${abiMismatchRemedy(raw) ?? MISSING_BINDING_REMEDY}
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
- const remedy = abiMismatchRemedy(raw);
8307
- if (!remedy)
8295
+ if (!raw.includes("better_sqlite3.node"))
8308
8296
  throw err;
8309
- throw new Error(`${remedy}
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, MISSING_BINDING_REMEDY;
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
- init_paths();
55587
- init_warn();
55570
+
55571
+ // src/core/npm-package.ts
55588
55572
  init_runtime();
55573
+ init_common();
55589
55574
 
55590
- // src/integrations/harnesses/opencode/runtime/tar.ts
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 abiMismatchRemedy(message) {
8263
- const ABI_MISMATCH_SHAPES = [
8264
- "did not self-register",
8265
- "NODE_MODULE_VERSION",
8266
- "was compiled against a different",
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(`${abiMismatchRemedy(raw) ?? MISSING_BINDING_REMEDY}
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
- const remedy = abiMismatchRemedy(raw);
8306
- if (!remedy)
8294
+ if (!raw.includes("better_sqlite3.node"))
8307
8295
  throw err;
8308
- throw new Error(`${remedy}
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, MISSING_BINDING_REMEDY;
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
- init_paths();
55585
- init_warn();
55568
+
55569
+ // src/core/npm-package.ts
55586
55570
  init_runtime();
55571
+ init_common();
55587
55572
 
55588
- // src/integrations/harnesses/opencode/runtime/tar.ts
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 setup wizard. The embedding
6
- * runtime (onnxruntime-node) is a normal package dependency; setup never
7
- * mutates the installed CLI. This module only prepares the model.
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
- `• Local embedding model: ${embedding?.localModel ?? DEFAULT_LOCAL_MODEL} (~${LOCAL_MODEL_APPROX_SIZE_MB} MB download on first use)`,
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 embedding model (${embedding?.localModel ?? DEFAULT_LOCAL_MODEL})...`);
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 embedding model downloaded and ready.");
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
  }
@@ -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
- : "local";
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?"