akm-cli 0.7.5 → 0.8.0-rc.6
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/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
- package/README.md +20 -4
- 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.js +1995 -551
- 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 +1531 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +990 -75
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +400 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +77 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-profiles.js +146 -0
- package/dist/commands/improve-result-file.js +103 -0
- package/dist/commands/improve.js +2175 -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/index.js +183 -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/vault-key-rules.js +139 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +66 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1119 -73
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +144 -25
- 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 +438 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/commands/vault.js +130 -77
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +7 -0
- package/dist/core/asset-registry.js +7 -16
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +22 -0
- package/dist/core/common.js +157 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +625 -0
- package/dist/core/config-schema.js +501 -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 +327 -987
- package/dist/core/errors.js +40 -19
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +3 -6
- 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 +326 -14
- package/dist/core/proposal-quality-validators.js +364 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +498 -42
- package/dist/core/state-db.js +927 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/warn.js +62 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +152 -253
- package/dist/indexer/db.js +933 -103
- package/dist/indexer/ensure-index.js +64 -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 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +506 -291
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +148 -160
- package/dist/indexer/memory-inference.js +99 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +255 -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 +5 -16
- 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 +150 -74
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +68 -0
- package/dist/integrations/session-logs/providers/claude-code.js +59 -0
- package/dist/integrations/session-logs/providers/opencode.js +55 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +72 -124
- package/dist/llm/embedder.js +3 -19
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +3 -0
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +89 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +9 -23
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +281 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +5 -318
- package/dist/output/context.js +3 -0
- package/dist/output/renderers.js +223 -256
- package/dist/output/shapes.js +150 -105
- package/dist/output/text.js +318 -30
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +3 -0
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +70 -49
- 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 +17307 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -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 +7 -5
- 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 +211 -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 +140 -10
- 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 +62 -91
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +9 -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.5.md +2 -2
- 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 +20 -8
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/templates/wiki-templates.js +0 -100
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,143 +46,83 @@ 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) {
|
|
42
60
|
const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
|
|
43
61
|
const headers = { "Content-Type": "application/json" };
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
const resolvedKey = resolveSecret(config.apiKey);
|
|
63
|
+
if (resolvedKey) {
|
|
64
|
+
headers.Authorization = `Bearer ${resolvedKey}`;
|
|
46
65
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
...config.extraParams,
|
|
85
|
+
}),
|
|
86
|
+
}, timeoutMs, options?.signal);
|
|
62
87
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
71
|
-
.replace(/^```(?:json)?\s*\n?/i, "")
|
|
72
|
-
.replace(/\n?```\s*$/i, "")
|
|
73
|
-
.trim();
|
|
74
|
-
let out = "";
|
|
75
|
-
let inString = false;
|
|
76
|
-
let escaped = false;
|
|
77
|
-
for (let i = 0; i < repaired.length; i++) {
|
|
78
|
-
const ch = repaired[i];
|
|
79
|
-
if (escaped) {
|
|
80
|
-
out += ch;
|
|
81
|
-
escaped = false;
|
|
82
|
-
continue;
|
|
88
|
+
catch (err) {
|
|
89
|
+
// fetchWithTimeout throws a plain Error with a message containing
|
|
90
|
+
// "timed out" for AbortController-driven timeouts, or "aborted" for
|
|
91
|
+
// caller-driven cancellations. Map both to typed LlmCallError.
|
|
92
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
93
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
94
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
83
95
|
}
|
|
84
|
-
if (
|
|
85
|
-
out
|
|
86
|
-
escaped = true;
|
|
87
|
-
continue;
|
|
96
|
+
if (msg.includes("timed out")) {
|
|
97
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
88
98
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
throw new LlmCallError(`Network error: ${msg}`, "network_error");
|
|
100
|
+
}
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const rawBody = await response.text().catch(() => "");
|
|
103
|
+
const safeBody = redactErrorBody(rawBody);
|
|
104
|
+
const status = response.status;
|
|
105
|
+
if (status === 429) {
|
|
106
|
+
throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
|
|
93
107
|
}
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
out += "\\n";
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (ch === "\r") {
|
|
100
|
-
out += "\\r";
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (ch === "\t") {
|
|
104
|
-
out += "\\t";
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
108
|
+
if (status >= 500) {
|
|
109
|
+
throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
107
110
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return out;
|
|
111
|
-
}
|
|
112
|
-
/** Parse a possibly-fenced JSON response. Returns undefined if invalid. */
|
|
113
|
-
export function parseJsonResponse(raw) {
|
|
114
|
-
try {
|
|
115
|
-
return JSON.parse(stripJsonFences(raw));
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return undefined;
|
|
111
|
+
throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
119
112
|
}
|
|
113
|
+
const json = (await response.json());
|
|
114
|
+
const content = (json.choices?.[0]?.message?.content ?? "").trim();
|
|
115
|
+
const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
|
|
116
|
+
return content || reasoning;
|
|
120
117
|
}
|
|
121
118
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* Strip `<think>` blocks, code fences, and escape control characters in JSON
|
|
120
|
+
* strings. Thin wrapper kept for backward compatibility with call sites that
|
|
121
|
+
* import `stripJsonFences` from this module. New code should prefer the
|
|
122
|
+
* granular helpers from `../core/parse`.
|
|
124
123
|
*/
|
|
125
|
-
export function
|
|
126
|
-
|
|
127
|
-
if (direct !== undefined)
|
|
128
|
-
return direct;
|
|
129
|
-
const text = stripJsonFences(raw);
|
|
130
|
-
let arrayFallback;
|
|
131
|
-
for (let start = 0; start < text.length; start++) {
|
|
132
|
-
const opener = text[start];
|
|
133
|
-
if (opener !== "{" && opener !== "[")
|
|
134
|
-
continue;
|
|
135
|
-
const closer = opener === "{" ? "}" : "]";
|
|
136
|
-
let depth = 0;
|
|
137
|
-
let inString = false;
|
|
138
|
-
let escaped = false;
|
|
139
|
-
for (let i = start; i < text.length; i++) {
|
|
140
|
-
const ch = text[i];
|
|
141
|
-
if (inString) {
|
|
142
|
-
if (escaped) {
|
|
143
|
-
escaped = false;
|
|
144
|
-
}
|
|
145
|
-
else if (ch === "\\") {
|
|
146
|
-
escaped = true;
|
|
147
|
-
}
|
|
148
|
-
else if (ch === '"') {
|
|
149
|
-
inString = false;
|
|
150
|
-
}
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (ch === '"') {
|
|
154
|
-
inString = true;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (ch === opener)
|
|
158
|
-
depth += 1;
|
|
159
|
-
if (ch === closer) {
|
|
160
|
-
depth -= 1;
|
|
161
|
-
if (depth === 0) {
|
|
162
|
-
try {
|
|
163
|
-
const parsed = JSON.parse(text.slice(start, i + 1));
|
|
164
|
-
if (!Array.isArray(parsed)) {
|
|
165
|
-
return parsed;
|
|
166
|
-
}
|
|
167
|
-
arrayFallback ??= parsed;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return arrayFallback;
|
|
124
|
+
export function stripJsonFences(raw) {
|
|
125
|
+
return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
|
|
178
126
|
}
|
|
179
127
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
180
128
|
/**
|
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";
|
|
@@ -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
|
* 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
|
*
|
package/dist/llm/feature-gate.js
CHANGED
|
@@ -1,63 +1,55 @@
|
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Every bounded in-tree LLM call site in akm is addressed by exactly one
|
|
5
|
-
* feature key under `llm.features.*`. This module is the single seam call
|
|
6
|
-
* sites use to ask "should I run?" and "if I run and fail, what do I return?"
|
|
7
|
-
*
|
|
8
|
-
* The seam is intentionally tiny:
|
|
9
|
-
*
|
|
10
|
-
* - `isLlmFeatureEnabled(config, feature)` — pure predicate, no side
|
|
11
|
-
* effects, no I/O. Returns `true` only when the feature flag is the
|
|
12
|
-
* literal boolean `true` in config. Defaults are `false` per v1
|
|
13
|
-
* spec §14 — adding a flag to the schema is a non-event until the user
|
|
14
|
-
* opts in.
|
|
15
|
-
* - `tryLlmFeature(feature, config, fn, fallback, opts?)` — single-call
|
|
16
|
-
* wrapper that runs `fn()` only when the gate is open, enforces a hard
|
|
17
|
-
* timeout (default 30s — overridable per call), and returns `fallback`
|
|
18
|
-
* on disablement, throw, or timeout. The wrapper is referentially
|
|
19
|
-
* transparent for any given (gate-state, fn-result) pair: no module
|
|
20
|
-
* state is mutated.
|
|
21
|
-
*
|
|
22
|
-
* Statelessness invariant (v1 spec §14.4): nothing in this module holds
|
|
23
|
-
* state across calls. There are no caches, no module-level singletons, no
|
|
24
|
-
* persistent connections. The architecture seam test
|
|
25
|
-
* (`tests/architecture/llm-stateless-seam.test.ts`) does not currently
|
|
26
|
-
* inspect this file but the same rule applies — keep all exports as pure
|
|
27
|
-
* functions.
|
|
5
|
+
* For each feature key, return the effective enabled state by reading the
|
|
6
|
+
* new 0.8.0 config shape. Defaults match the legacy `LlmFeatureFlags` docstrings.
|
|
28
7
|
*/
|
|
8
|
+
// Defaults below mirror the legacy LlmFeatureFlags docstrings so existing
|
|
9
|
+
// behaviour is preserved when a config is silent on a flag.
|
|
10
|
+
const FEATURE_LOCATION = {
|
|
11
|
+
// Legacy default: false → memory_consolidation only runs when explicitly enabled
|
|
12
|
+
// (either via the user's improve profile or the built-in `default` profile).
|
|
13
|
+
memory_consolidation: (cfg) => cfg.profiles?.improve?.default?.processes?.consolidate?.enabled ?? false,
|
|
14
|
+
// Legacy default: false
|
|
15
|
+
feedback_distillation: (cfg) => cfg.profiles?.improve?.default?.processes?.feedbackDistillation?.enabled ?? false,
|
|
16
|
+
// Legacy default: true
|
|
17
|
+
memory_inference: (cfg) => cfg.profiles?.improve?.default?.processes?.memoryInference?.enabled ?? true,
|
|
18
|
+
// Legacy default: true
|
|
19
|
+
graph_extraction: (cfg) => cfg.profiles?.improve?.default?.processes?.graphExtraction?.enabled ?? true,
|
|
20
|
+
// Legacy default: false
|
|
21
|
+
metadata_enhance: (cfg) => cfg.index?.metadataEnhance?.enabled ?? false,
|
|
22
|
+
// Legacy default: false
|
|
23
|
+
curate_rerank: (cfg) => cfg.search?.curateRerank?.enabled ?? false,
|
|
24
|
+
// Legacy default: false
|
|
25
|
+
lesson_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.distill?.qualityGate?.enabled ?? false,
|
|
26
|
+
// Legacy default: false
|
|
27
|
+
proposal_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.reflect?.qualityGate?.enabled ?? false,
|
|
28
|
+
// Legacy default: false
|
|
29
|
+
memory_contradiction_detection: (cfg) => cfg.profiles?.improve?.default?.processes?.consolidate?.contradictionDetection?.enabled ?? false,
|
|
30
|
+
};
|
|
29
31
|
/**
|
|
30
|
-
* Pure predicate: is the named feature gate
|
|
32
|
+
* Pure predicate: is the named feature gate enabled in `config`?
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* - the `features` block is missing,
|
|
35
|
-
* - the key is absent (defaults are `false`),
|
|
36
|
-
* - the key is set to `false`.
|
|
34
|
+
* Reads from the unified 0.8.0 config shape. Defaults follow the legacy
|
|
35
|
+
* `LlmFeatureFlags` docstring defaults.
|
|
37
36
|
*/
|
|
38
37
|
export function isLlmFeatureEnabled(config, feature) {
|
|
39
|
-
if (!config
|
|
38
|
+
if (!config)
|
|
39
|
+
return false;
|
|
40
|
+
const resolver = FEATURE_LOCATION[feature];
|
|
41
|
+
if (!resolver)
|
|
40
42
|
return false;
|
|
41
|
-
return config
|
|
43
|
+
return resolver(config);
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Default hard timeout for every bounded in-tree LLM call.
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
44
49
|
/**
|
|
45
50
|
* Run `fn()` only if `isLlmFeatureEnabled(config, feature)` is `true`. On
|
|
46
51
|
* disablement, throw, or timeout, return `fallback` (or — if it is a
|
|
47
52
|
* thunk — the value produced by calling it).
|
|
48
|
-
*
|
|
49
|
-
* The fallback may be a value or a synchronous/async function returning a
|
|
50
|
-
* value. The thunk form lets call sites encode "run the deterministic
|
|
51
|
-
* pipeline" without paying for it in the success path:
|
|
52
|
-
*
|
|
53
|
-
* ```ts
|
|
54
|
-
* const ranked = await tryLlmFeature(
|
|
55
|
-
* "curate_rerank",
|
|
56
|
-
* config,
|
|
57
|
-
* () => llmRerank(candidates),
|
|
58
|
-
* () => deterministicRerank(candidates),
|
|
59
|
-
* );
|
|
60
|
-
* ```
|
|
61
53
|
*/
|
|
62
54
|
export async function tryLlmFeature(feature, config, fn, fallback, opts) {
|
|
63
55
|
const resolveFallback = async () => typeof fallback === "function" ? await fallback() : fallback;
|
|
@@ -79,6 +71,55 @@ export async function tryLlmFeature(feature, config, fn, fallback, opts) {
|
|
|
79
71
|
return resolveFallback();
|
|
80
72
|
}
|
|
81
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Section-agnostic process gate. After the 0.8.0 migration, the canonical
|
|
76
|
+
* accessor is the `FEATURE_LOCATION` map above; this helper exists so older
|
|
77
|
+
* call sites that knew the (section, processName) pair don't all need to
|
|
78
|
+
* relearn the new mapping.
|
|
79
|
+
*
|
|
80
|
+
* For unknown (section, processName) pairs the result is `false`.
|
|
81
|
+
*/
|
|
82
|
+
export function isProcessEnabled(section, processName, config) {
|
|
83
|
+
if (!config)
|
|
84
|
+
return false;
|
|
85
|
+
// index.metadataEnhance / index.stalenessDetection are first-class new-shape entries.
|
|
86
|
+
if (section === "index") {
|
|
87
|
+
if (processName === "metadata_enhance" || processName === "metadataEnhance") {
|
|
88
|
+
return config.index?.metadataEnhance?.enabled ?? true;
|
|
89
|
+
}
|
|
90
|
+
if (processName === "staleness_detection" || processName === "stalenessDetection") {
|
|
91
|
+
return config.index?.stalenessDetection?.enabled ?? false;
|
|
92
|
+
}
|
|
93
|
+
if (processName === "memory_inference" || processName === "memoryInference") {
|
|
94
|
+
return isLlmFeatureEnabled(config, "memory_inference");
|
|
95
|
+
}
|
|
96
|
+
if (processName === "graph_extraction" || processName === "graphExtraction") {
|
|
97
|
+
return isLlmFeatureEnabled(config, "graph_extraction");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (section === "search" && (processName === "curate_rerank" || processName === "curateRerank")) {
|
|
101
|
+
return config.search?.curateRerank?.enabled ?? false;
|
|
102
|
+
}
|
|
103
|
+
if (section === "improve") {
|
|
104
|
+
const processes = config.profiles?.improve?.default?.processes;
|
|
105
|
+
const entry = processes?.[processName];
|
|
106
|
+
if (entry && typeof entry.enabled === "boolean")
|
|
107
|
+
return entry.enabled;
|
|
108
|
+
// Fallback to default-enabled state for known processes.
|
|
109
|
+
switch (processName) {
|
|
110
|
+
case "reflect":
|
|
111
|
+
case "distill":
|
|
112
|
+
case "consolidate":
|
|
113
|
+
case "memoryInference":
|
|
114
|
+
case "graphExtraction":
|
|
115
|
+
case "feedbackDistillation":
|
|
116
|
+
return true;
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
82
123
|
/** Specific error class so call sites and the wrapper can tell timeouts apart from generic throws. */
|
|
83
124
|
export class LlmFeatureTimeoutError extends Error {
|
|
84
125
|
feature;
|