akm-cli 0.7.4 → 0.8.0-rc.10
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 +224 -1
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -511
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1093
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import { getDefaultLlmConfig } from "../core/config";
|
|
5
|
+
import { warn } from "../core/warn";
|
|
6
|
+
import { resolveAgentProfile, runAgent } from "../integrations/agent";
|
|
7
|
+
import { chatCompletion } from "./client";
|
|
8
|
+
/**
|
|
9
|
+
* Unified AI call: prefers the default agent profile, falls back to the
|
|
10
|
+
* default LLM profile. When neither is configured, returns a structured
|
|
11
|
+
* error pointing the user at `akm setup`.
|
|
12
|
+
*/
|
|
13
|
+
export async function callAi(config, prompt, opts = {}) {
|
|
14
|
+
const defaultAgentName = config.defaults?.agent;
|
|
15
|
+
if (defaultAgentName) {
|
|
16
|
+
try {
|
|
17
|
+
const profile = resolveAgentProfile(defaultAgentName, config.profiles?.agent?.[defaultAgentName]);
|
|
18
|
+
if (!profile) {
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: `Agent profile "${defaultAgentName}" is not built-in and has no \`bin\` override.`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const result = await runAgent(profile, prompt, {
|
|
25
|
+
stdio: opts.draftFilePath ? "interactive" : "captured",
|
|
26
|
+
parseOutput: "text",
|
|
27
|
+
timeoutMs: opts.timeoutMs,
|
|
28
|
+
});
|
|
29
|
+
if (!result.ok)
|
|
30
|
+
return { ok: false, error: result.error ?? result.reason ?? "agent failed" };
|
|
31
|
+
return { ok: true, content: result.stdout ?? "", path: "agent-cli" };
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return { ok: false, error: String(e) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const llmConfig = getDefaultLlmConfig(config);
|
|
38
|
+
if (llmConfig) {
|
|
39
|
+
if (opts.draftFilePath) {
|
|
40
|
+
warn("[akm] No agent CLI configured — falling back to LLM API. " +
|
|
41
|
+
"File-write contract unavailable; expecting JSON in stdout. " +
|
|
42
|
+
"Install an agent CLI and run `akm setup` for full functionality.");
|
|
43
|
+
}
|
|
44
|
+
const messages = [];
|
|
45
|
+
if (opts.systemPrompt)
|
|
46
|
+
messages.push({ role: "system", content: opts.systemPrompt });
|
|
47
|
+
messages.push({ role: "user", content: prompt });
|
|
48
|
+
try {
|
|
49
|
+
const content = await chatCompletion(llmConfig, messages, {
|
|
50
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
51
|
+
});
|
|
52
|
+
return { ok: true, content, path: "llm-http" };
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return { ok: false, error: String(e) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: "No AI connection configured. Run `akm setup` or set `defaults.agent`/`defaults.llm`.",
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/llm/client.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Low-level OpenAI-compatible chat completions client and capability probing.
|
|
3
6
|
*
|
|
@@ -8,6 +11,11 @@
|
|
|
8
11
|
* `llm.ts` re-exports everything from this module for backward compatibility.
|
|
9
12
|
*/
|
|
10
13
|
import { fetchWithTimeout } from "../core/common";
|
|
14
|
+
import { resolveSecret } from "../core/config";
|
|
15
|
+
import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse";
|
|
16
|
+
// Re-export shared parse utilities so existing importers of `client.ts` continue
|
|
17
|
+
// to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
|
|
18
|
+
export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse";
|
|
11
19
|
/** Maximum length of an LLM error response body included in thrown errors. */
|
|
12
20
|
const ERROR_BODY_MAX_LEN = 200;
|
|
13
21
|
/**
|
|
@@ -38,105 +46,88 @@ export function redactErrorBody(input) {
|
|
|
38
46
|
}
|
|
39
47
|
return out;
|
|
40
48
|
}
|
|
49
|
+
export class LlmCallError extends Error {
|
|
50
|
+
code;
|
|
51
|
+
statusCode;
|
|
52
|
+
constructor(message, code, statusCode) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.code = code;
|
|
55
|
+
this.statusCode = statusCode;
|
|
56
|
+
this.name = "LlmCallError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
41
59
|
export async function chatCompletion(config, messages, options) {
|
|
60
|
+
const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
|
|
42
61
|
const headers = { "Content-Type": "application/json" };
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
const resolvedKey = resolveSecret(config.apiKey);
|
|
63
|
+
if (resolvedKey) {
|
|
64
|
+
headers.Authorization = `Bearer ${resolvedKey}`;
|
|
65
|
+
}
|
|
66
|
+
// Only include max_tokens when explicitly set. The model/API knows its own
|
|
67
|
+
// limits; a hardcoded default creates silent truncation failures when the
|
|
68
|
+
// guess is wrong. Users who need a cap can set llm.maxTokens in config.
|
|
69
|
+
const resolvedMaxTokens = options?.maxTokens ?? config.maxTokens;
|
|
70
|
+
const responseFormat = options?.responseSchema && config.supportsJsonSchema
|
|
71
|
+
? { response_format: { type: "json_schema", json_schema: { schema: options.responseSchema, strict: true } } }
|
|
72
|
+
: {};
|
|
73
|
+
let response;
|
|
74
|
+
try {
|
|
75
|
+
response = await fetchWithTimeout(config.endpoint, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers,
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
model: config.model,
|
|
80
|
+
messages,
|
|
81
|
+
temperature: options?.temperature ?? config.temperature ?? 0.3,
|
|
82
|
+
...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
|
|
83
|
+
...responseFormat,
|
|
84
|
+
...(options?.enableThinking !== undefined
|
|
85
|
+
? { enable_thinking: options.enableThinking }
|
|
86
|
+
: config.enableThinking !== undefined
|
|
87
|
+
? { enable_thinking: config.enableThinking }
|
|
88
|
+
: {}),
|
|
89
|
+
...config.extraParams,
|
|
90
|
+
}),
|
|
91
|
+
}, timeoutMs, options?.signal);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// fetchWithTimeout throws a plain Error with a message containing
|
|
95
|
+
// "timed out" for AbortController-driven timeouts, or "aborted" for
|
|
96
|
+
// caller-driven cancellations. Map both to typed LlmCallError.
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
99
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
100
|
+
}
|
|
101
|
+
if (msg.includes("timed out")) {
|
|
102
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
103
|
+
}
|
|
104
|
+
throw new LlmCallError(`Network error: ${msg}`, "network_error");
|
|
45
105
|
}
|
|
46
|
-
const response = await fetchWithTimeout(config.endpoint, {
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers,
|
|
49
|
-
body: JSON.stringify({
|
|
50
|
-
model: config.model,
|
|
51
|
-
messages,
|
|
52
|
-
temperature: options?.temperature ?? config.temperature ?? 0.3,
|
|
53
|
-
max_tokens: options?.maxTokens ?? config.maxTokens ?? 512,
|
|
54
|
-
...config.extraParams,
|
|
55
|
-
}),
|
|
56
|
-
}, 30_000, options?.signal);
|
|
57
106
|
if (!response.ok) {
|
|
58
107
|
const rawBody = await response.text().catch(() => "");
|
|
59
108
|
const safeBody = redactErrorBody(rawBody);
|
|
60
|
-
|
|
109
|
+
const status = response.status;
|
|
110
|
+
if (status === 429) {
|
|
111
|
+
throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
|
|
112
|
+
}
|
|
113
|
+
if (status >= 500) {
|
|
114
|
+
throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
115
|
+
}
|
|
116
|
+
throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
61
117
|
}
|
|
62
118
|
const json = (await response.json());
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export function stripJsonFences(raw) {
|
|
67
|
-
return raw
|
|
68
|
-
.trim()
|
|
69
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
70
|
-
.replace(/^```(?:json)?\s*\n?/i, "")
|
|
71
|
-
.replace(/\n?```\s*$/i, "")
|
|
72
|
-
.trim();
|
|
73
|
-
}
|
|
74
|
-
/** Parse a possibly-fenced JSON response. Returns undefined if invalid. */
|
|
75
|
-
export function parseJsonResponse(raw) {
|
|
76
|
-
try {
|
|
77
|
-
return JSON.parse(stripJsonFences(raw));
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
119
|
+
const content = (json.choices?.[0]?.message?.content ?? "").trim();
|
|
120
|
+
const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
|
|
121
|
+
return content || reasoning;
|
|
82
122
|
}
|
|
83
123
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
124
|
+
* Strip `<think>` blocks, code fences, and escape control characters in JSON
|
|
125
|
+
* strings. Thin wrapper kept for backward compatibility with call sites that
|
|
126
|
+
* import `stripJsonFences` from this module. New code should prefer the
|
|
127
|
+
* granular helpers from `../core/parse`.
|
|
86
128
|
*/
|
|
87
|
-
export function
|
|
88
|
-
|
|
89
|
-
if (direct !== undefined)
|
|
90
|
-
return direct;
|
|
91
|
-
const text = stripJsonFences(raw);
|
|
92
|
-
let arrayFallback;
|
|
93
|
-
for (let start = 0; start < text.length; start++) {
|
|
94
|
-
const opener = text[start];
|
|
95
|
-
if (opener !== "{" && opener !== "[")
|
|
96
|
-
continue;
|
|
97
|
-
const closer = opener === "{" ? "}" : "]";
|
|
98
|
-
let depth = 0;
|
|
99
|
-
let inString = false;
|
|
100
|
-
let escaped = false;
|
|
101
|
-
for (let i = start; i < text.length; i++) {
|
|
102
|
-
const ch = text[i];
|
|
103
|
-
if (inString) {
|
|
104
|
-
if (escaped) {
|
|
105
|
-
escaped = false;
|
|
106
|
-
}
|
|
107
|
-
else if (ch === "\\") {
|
|
108
|
-
escaped = true;
|
|
109
|
-
}
|
|
110
|
-
else if (ch === '"') {
|
|
111
|
-
inString = false;
|
|
112
|
-
}
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (ch === '"') {
|
|
116
|
-
inString = true;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (ch === opener)
|
|
120
|
-
depth += 1;
|
|
121
|
-
if (ch === closer) {
|
|
122
|
-
depth -= 1;
|
|
123
|
-
if (depth === 0) {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(text.slice(start, i + 1));
|
|
126
|
-
if (!Array.isArray(parsed)) {
|
|
127
|
-
return parsed;
|
|
128
|
-
}
|
|
129
|
-
arrayFallback ??= parsed;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return arrayFallback;
|
|
129
|
+
export function stripJsonFences(raw) {
|
|
130
|
+
return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
|
|
140
131
|
}
|
|
141
132
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
142
133
|
/**
|
package/dist/llm/embedder.js
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* The implementation has been split into:
|
|
5
|
-
* - `./embedders/types` — `EmbeddingVector`, `Embedder`, `EmbeddingCheckResult`
|
|
6
|
-
* - `./embedders/local` — `LocalEmbedder`, `DEFAULT_LOCAL_MODEL`,
|
|
7
|
-
* `isTransformersAvailable`
|
|
8
|
-
* - `./embedders/remote` — `RemoteEmbedder`, `hasRemoteEndpoint`
|
|
9
|
-
* - `./embedders/cache` — LRU `embedCache`, `clearEmbeddingCache`,
|
|
10
|
-
* `embedCacheKey`
|
|
11
|
-
*
|
|
12
|
-
* This module wires them together: it picks the right implementation from the
|
|
13
|
-
* (optional) embedding config, applies the cache layer, and re-exports the
|
|
14
|
-
* existing public API so call sites (`db-search.ts`, `indexer.ts`, `db.ts`,
|
|
15
|
-
* `setup.ts`, `semantic-status.ts`, tests) keep working unmodified.
|
|
16
|
-
*
|
|
17
|
-
* Tests can construct fresh `LocalEmbedder` / `RemoteEmbedder` instances
|
|
18
|
-
* directly from their submodules to avoid module-level state pollution.
|
|
19
|
-
*/
|
|
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/.
|
|
20
4
|
import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache";
|
|
21
5
|
import { isTransformersAvailable, LocalEmbedder } from "./embedders/local";
|
|
22
6
|
import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote";
|
|
@@ -24,18 +8,25 @@ import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote";
|
|
|
24
8
|
export { clearEmbeddingCache } from "./embedders/cache";
|
|
25
9
|
export { DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedders/local";
|
|
26
10
|
// ── Singleton local embedder ────────────────────────────────────────────────
|
|
27
|
-
// `
|
|
28
|
-
// @huggingface/transformers pipeline is
|
|
29
|
-
// + WASM compilation) and is safe to
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
11
|
+
// `_localEmbedder` is an intentional module-level singleton but constructed
|
|
12
|
+
// lazily on first use. The underlying @huggingface/transformers pipeline is
|
|
13
|
+
// expensive to initialise (model download + WASM compilation) and is safe to
|
|
14
|
+
// share across calls because it is stateless once created. Deferring
|
|
15
|
+
// construction to first call keeps the module side-effect-free at import time,
|
|
16
|
+
// which matters for the test suite (single Bun process, ~120 test files).
|
|
17
|
+
let _localEmbedder;
|
|
18
|
+
function getLocalEmbedder() {
|
|
19
|
+
if (!_localEmbedder) {
|
|
20
|
+
_localEmbedder = new LocalEmbedder();
|
|
21
|
+
}
|
|
22
|
+
return _localEmbedder;
|
|
23
|
+
}
|
|
33
24
|
/**
|
|
34
25
|
* Reset the cached local embedder pipeline. Used by tests that want a fresh
|
|
35
26
|
* pipeline construction (e.g. to assert the dtype-fallback retry logic).
|
|
36
27
|
*/
|
|
37
28
|
export function resetLocalEmbedder() {
|
|
38
|
-
|
|
29
|
+
getLocalEmbedder().reset();
|
|
39
30
|
}
|
|
40
31
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
41
32
|
/**
|
|
@@ -54,7 +45,7 @@ export async function embed(text, embeddingConfig, signal) {
|
|
|
54
45
|
return cached;
|
|
55
46
|
const result = embeddingConfig && hasRemoteEndpoint(embeddingConfig)
|
|
56
47
|
? await new RemoteEmbedder(embeddingConfig).embed(text, signal)
|
|
57
|
-
: await
|
|
48
|
+
: await getLocalEmbedder().embed(text, signal);
|
|
58
49
|
setCachedEmbedding(key, result);
|
|
59
50
|
return result;
|
|
60
51
|
}
|
|
@@ -76,7 +67,7 @@ export async function embedBatch(texts, embeddingConfig, signal) {
|
|
|
76
67
|
if (signal?.aborted) {
|
|
77
68
|
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
78
69
|
}
|
|
79
|
-
results.push(await
|
|
70
|
+
results.push(await getLocalEmbedder().embedWithModel(text, localModel));
|
|
80
71
|
}
|
|
81
72
|
return results;
|
|
82
73
|
}
|
|
@@ -113,7 +104,7 @@ export async function checkEmbeddingAvailability(embeddingConfig) {
|
|
|
113
104
|
};
|
|
114
105
|
}
|
|
115
106
|
try {
|
|
116
|
-
await
|
|
107
|
+
await getLocalEmbedder().getPipeline(embeddingConfig?.localModel);
|
|
117
108
|
return { available: true };
|
|
118
109
|
}
|
|
119
110
|
catch (err) {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Caches query embeddings to avoid redundant computation for repeated
|
|
5
|
-
* queries. Uses a simple Map with LRU eviction (delete + re-insert to move
|
|
6
|
-
* an entry to the most-recently-used end).
|
|
7
|
-
*/
|
|
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/.
|
|
8
4
|
const EMBED_CACHE_MAX = 100;
|
|
9
5
|
const embedCache = new Map();
|
|
10
6
|
/**
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Local @huggingface/transformers embedder.
|
|
3
6
|
*
|
|
@@ -25,6 +28,31 @@ const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
|
|
|
25
28
|
function resolveLocalModelName(overrideModel) {
|
|
26
29
|
return overrideModel || DEFAULT_LOCAL_MODEL;
|
|
27
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect whether the current process is running from a Bun-compiled binary
|
|
33
|
+
* (i.e. `bun build --compile` produced a single executable). Bun marks the
|
|
34
|
+
* compiled binary with a synthesized `process.execPath` that ends in the
|
|
35
|
+
* binary name rather than `bun`, AND sets a flag we can probe.
|
|
36
|
+
*
|
|
37
|
+
* Used to gate the "install @huggingface/transformers" hint — that advice
|
|
38
|
+
* is impossible to follow from a single-binary install, so we replace it
|
|
39
|
+
* with the only working remediation (switch to npm/Bun install, or turn
|
|
40
|
+
* semantic search off). See #482.
|
|
41
|
+
*/
|
|
42
|
+
function isCompiledBinary() {
|
|
43
|
+
try {
|
|
44
|
+
const flag = Bun.embeddedFiles;
|
|
45
|
+
if (flag !== undefined)
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Bun not available (under Node tests, for example) — treat as not-binary.
|
|
50
|
+
}
|
|
51
|
+
const exec = (process.execPath || "").toLowerCase();
|
|
52
|
+
if (exec.endsWith("/akm") || exec.endsWith("\\akm.exe"))
|
|
53
|
+
return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
28
56
|
export class LocalEmbedder {
|
|
29
57
|
defaultModel;
|
|
30
58
|
/**
|
|
@@ -93,7 +121,20 @@ export class LocalEmbedder {
|
|
|
93
121
|
catch (importError) {
|
|
94
122
|
const msg = importError instanceof Error ? importError.message : String(importError);
|
|
95
123
|
if (/Cannot find module|MODULE_NOT_FOUND|Cannot resolve/i.test(msg)) {
|
|
96
|
-
|
|
124
|
+
// #482: the prebuilt binary build is invoked with
|
|
125
|
+
// `bun install --omit optional` (release.yml), so binary users
|
|
126
|
+
// can NEVER load @huggingface/transformers. Telling them to
|
|
127
|
+
// `bun add` it is a dead-end — there is no install target.
|
|
128
|
+
// Detect the binary execution path and give the only working
|
|
129
|
+
// remediation: switch to the npm/Bun install of akm-cli, or
|
|
130
|
+
// turn off semantic search.
|
|
131
|
+
const isBinary = isCompiledBinary();
|
|
132
|
+
const hint = isBinary
|
|
133
|
+
? "You are running the prebuilt akm binary, which cannot load optional native dependencies. " +
|
|
134
|
+
"To enable semantic search, install akm-cli via Bun: `curl -fsSL https://bun.sh/install | bash && bun install -g akm-cli`. " +
|
|
135
|
+
"To keep using the binary, set `semanticSearchMode: off` in your config and use keyword-only FTS."
|
|
136
|
+
: "Install it with: `bun add @huggingface/transformers` (or `npm install @huggingface/transformers`).";
|
|
137
|
+
throw new Error(`Semantic search requires @huggingface/transformers. ${hint}`);
|
|
97
138
|
}
|
|
98
139
|
throw new Error(`Failed to load embedding runtime: ${msg}. Check platform compatibility.`);
|
|
99
140
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* OpenAI-compatible remote embedder.
|
|
3
6
|
*
|
|
@@ -5,6 +8,7 @@
|
|
|
5
8
|
* vectors so the scoring pipeline's L2-to-cosine conversion is correct.
|
|
6
9
|
*/
|
|
7
10
|
import { fetchWithTimeout, isHttpUrl } from "../../core/common";
|
|
11
|
+
import { resolveSecret } from "../../core/config";
|
|
8
12
|
const DEFAULT_REMOTE_BATCH_SIZE = 100;
|
|
9
13
|
/** Cheap token estimator: 4 chars ≈ 1 token. Used in verbose logging and error messages. */
|
|
10
14
|
export function estimateTokenCount(text) {
|
|
@@ -12,14 +16,21 @@ export function estimateTokenCount(text) {
|
|
|
12
16
|
}
|
|
13
17
|
export class RemoteEmbedder {
|
|
14
18
|
config;
|
|
19
|
+
endpoint;
|
|
20
|
+
model;
|
|
15
21
|
constructor(config) {
|
|
16
22
|
this.config = config;
|
|
23
|
+
if (!config.endpoint || !config.model) {
|
|
24
|
+
throw new Error("RemoteEmbedder requires both endpoint and model on the embedding config.");
|
|
25
|
+
}
|
|
26
|
+
this.endpoint = config.endpoint;
|
|
27
|
+
this.model = config.model;
|
|
17
28
|
}
|
|
18
29
|
async embed(text, signal) {
|
|
19
30
|
const headers = this.buildHeaders();
|
|
20
31
|
const body = {
|
|
21
32
|
input: text,
|
|
22
|
-
model: this.
|
|
33
|
+
model: this.model,
|
|
23
34
|
};
|
|
24
35
|
if (this.config.dimension) {
|
|
25
36
|
body.dimensions = this.config.dimension;
|
|
@@ -28,7 +39,7 @@ export class RemoteEmbedder {
|
|
|
28
39
|
if (ollamaOpts) {
|
|
29
40
|
body.options = ollamaOpts;
|
|
30
41
|
}
|
|
31
|
-
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.
|
|
42
|
+
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.endpoint), {
|
|
32
43
|
method: "POST",
|
|
33
44
|
headers,
|
|
34
45
|
body: JSON.stringify(body),
|
|
@@ -40,7 +51,7 @@ export class RemoteEmbedder {
|
|
|
40
51
|
}
|
|
41
52
|
const json = (await response.json());
|
|
42
53
|
if (!json.data?.[0]?.embedding) {
|
|
43
|
-
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.
|
|
54
|
+
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
44
55
|
}
|
|
45
56
|
return l2Normalize(json.data[0].embedding);
|
|
46
57
|
}
|
|
@@ -55,7 +66,7 @@ export class RemoteEmbedder {
|
|
|
55
66
|
const batch = texts.slice(i, i + batchSize);
|
|
56
67
|
const body = {
|
|
57
68
|
input: batch,
|
|
58
|
-
model: this.
|
|
69
|
+
model: this.model,
|
|
59
70
|
};
|
|
60
71
|
if (this.config.dimension) {
|
|
61
72
|
body.dimensions = this.config.dimension;
|
|
@@ -63,7 +74,7 @@ export class RemoteEmbedder {
|
|
|
63
74
|
if (ollamaOpts) {
|
|
64
75
|
body.options = ollamaOpts;
|
|
65
76
|
}
|
|
66
|
-
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.
|
|
77
|
+
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.endpoint), {
|
|
67
78
|
method: "POST",
|
|
68
79
|
headers,
|
|
69
80
|
body: JSON.stringify(body),
|
|
@@ -75,7 +86,7 @@ export class RemoteEmbedder {
|
|
|
75
86
|
}
|
|
76
87
|
const json = (await response.json());
|
|
77
88
|
if (!json.data || json.data.length !== batch.length) {
|
|
78
|
-
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.
|
|
89
|
+
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
79
90
|
}
|
|
80
91
|
// Sort by index to guarantee correct order (OpenAI API doesn't guarantee order)
|
|
81
92
|
const sorted = [...json.data].sort((a, b) => a.index - b.index);
|
|
@@ -90,8 +101,9 @@ export class RemoteEmbedder {
|
|
|
90
101
|
}
|
|
91
102
|
buildHeaders() {
|
|
92
103
|
const headers = { "Content-Type": "application/json" };
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
const resolvedKey = resolveSecret(this.config.apiKey);
|
|
105
|
+
if (resolvedKey) {
|
|
106
|
+
headers.Authorization = `Bearer ${resolvedKey}`;
|
|
95
107
|
}
|
|
96
108
|
return headers;
|
|
97
109
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Pulled out of `embedder.ts` so concrete implementations (`local.ts`,
|
|
5
|
-
* `remote.ts`) and the cache layer can depend on a small, stable types
|
|
6
|
-
* module without dragging in the facade or a sibling implementation.
|
|
7
|
-
*/
|
|
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/.
|
|
8
4
|
/**
|
|
9
5
|
* Cosine similarity between two embedding vectors.
|
|
10
6
|
*
|