akm-cli 0.7.4 → 0.8.0-rc.3
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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +86 -0
- package/dist/cli.js +1223 -650
- package/dist/commands/agent-dispatch.js +107 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +812 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +224 -39
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -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 +3 -30
- package/dist/commands/improve.js +1161 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +291 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +145 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/vault-key-rules.js +67 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +106 -43
- package/dist/commands/reflect.js +167 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +135 -55
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +173 -87
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +100 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +240 -127
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +0 -6
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +731 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +418 -59
- package/dist/indexer/ensure-index.js +133 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +480 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/matchers.js +124 -160
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +196 -197
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/builders.js +109 -0
- package/dist/integrations/agent/config.js +203 -3
- package/dist/integrations/agent/index.js +5 -2
- package/dist/integrations/agent/model-aliases.js +63 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +158 -34
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -309
- package/dist/output/renderers.js +226 -257
- package/dist/output/shapes.js +109 -96
- package/dist/output/text.js +274 -36
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/registry/resolve.js +8 -16
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/filesystem.js +16 -23
- package/dist/sources/providers/git.js +45 -4
- package/dist/sources/providers/website.js +15 -22
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -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 +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +5 -2
- 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 +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified AI call adapter: prefers `config.agent` (agent CLI shell-out),
|
|
3
|
+
* falls back to `config.llm` (HTTP chat-completions).
|
|
4
|
+
*
|
|
5
|
+
* NOT for use by background indexer passes — those call `chatCompletion`
|
|
6
|
+
* directly to avoid the agent-CLI overhead and to stay on the HTTP path that
|
|
7
|
+
* the indexer was designed around.
|
|
8
|
+
*/
|
|
9
|
+
import { warn } from "../core/warn";
|
|
10
|
+
import { resolveAgentProfile, runAgent } from "../integrations/agent";
|
|
11
|
+
import { chatCompletion } from "./client";
|
|
12
|
+
/**
|
|
13
|
+
* Unified AI call: prefers `config.agent` (agent CLI), falls back to
|
|
14
|
+
* `config.llm` (HTTP). When neither is configured, returns a structured
|
|
15
|
+
* error pointing the user at `akm setup`.
|
|
16
|
+
*
|
|
17
|
+
* NOT for use by background indexer passes — those call `chatCompletion`
|
|
18
|
+
* directly.
|
|
19
|
+
*/
|
|
20
|
+
export async function callAi(config, prompt, opts = {}) {
|
|
21
|
+
if (config.agent) {
|
|
22
|
+
try {
|
|
23
|
+
const defaultName = config.agent.default;
|
|
24
|
+
if (!defaultName) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: "No default agent profile configured. Set `agent.default` in config.json or run `akm setup`.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const profile = resolveAgentProfile(defaultName, config.agent.profiles?.[defaultName]);
|
|
31
|
+
if (!profile) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: `Agent profile "${defaultName}" is not built-in and has no \`bin\` override.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const result = await runAgent(profile, prompt, {
|
|
38
|
+
stdio: opts.draftFilePath ? "interactive" : "captured",
|
|
39
|
+
parseOutput: "text",
|
|
40
|
+
timeoutMs: opts.timeoutMs,
|
|
41
|
+
});
|
|
42
|
+
if (!result.ok)
|
|
43
|
+
return { ok: false, error: result.error ?? result.reason ?? "agent failed" };
|
|
44
|
+
return { ok: true, content: result.stdout ?? "", path: "agent-cli" };
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return { ok: false, error: String(e) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (config.llm) {
|
|
51
|
+
if (opts.draftFilePath) {
|
|
52
|
+
warn("[akm] No agent CLI configured — falling back to LLM API. " +
|
|
53
|
+
"File-write contract unavailable; expecting JSON in stdout. " +
|
|
54
|
+
"Install an agent CLI and run `akm setup` for full functionality.");
|
|
55
|
+
}
|
|
56
|
+
const messages = [];
|
|
57
|
+
if (opts.systemPrompt)
|
|
58
|
+
messages.push({ role: "system", content: opts.systemPrompt });
|
|
59
|
+
messages.push({ role: "user", content: prompt });
|
|
60
|
+
try {
|
|
61
|
+
const content = await chatCompletion(config.llm, messages, {
|
|
62
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
63
|
+
});
|
|
64
|
+
return { ok: true, content, path: "llm-http" };
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
return { ok: false, error: String(e) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: "No AI connection configured. Run `akm setup` or set `agent` or `llm` in your config.",
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/llm/client.js
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* `llm.ts` re-exports everything from this module for backward compatibility.
|
|
9
9
|
*/
|
|
10
10
|
import { fetchWithTimeout } from "../core/common";
|
|
11
|
+
import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse";
|
|
12
|
+
// Re-export shared parse utilities so existing importers of `client.ts` continue
|
|
13
|
+
// to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
|
|
14
|
+
export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse";
|
|
11
15
|
/** Maximum length of an LLM error response body included in thrown errors. */
|
|
12
16
|
const ERROR_BODY_MAX_LEN = 200;
|
|
13
17
|
/**
|
|
@@ -38,105 +42,78 @@ export function redactErrorBody(input) {
|
|
|
38
42
|
}
|
|
39
43
|
return out;
|
|
40
44
|
}
|
|
45
|
+
export class LlmCallError extends Error {
|
|
46
|
+
code;
|
|
47
|
+
statusCode;
|
|
48
|
+
constructor(message, code, statusCode) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.statusCode = statusCode;
|
|
52
|
+
this.name = "LlmCallError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
41
55
|
export async function chatCompletion(config, messages, options) {
|
|
56
|
+
const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
|
|
42
57
|
const headers = { "Content-Type": "application/json" };
|
|
43
58
|
if (config.apiKey) {
|
|
44
59
|
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
45
60
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
// Only include max_tokens when explicitly set. The model/API knows its own
|
|
62
|
+
// limits; a hardcoded default creates silent truncation failures when the
|
|
63
|
+
// guess is wrong. Users who need a cap can set llm.maxTokens in config.
|
|
64
|
+
const resolvedMaxTokens = options?.maxTokens ?? config.maxTokens;
|
|
65
|
+
let response;
|
|
66
|
+
try {
|
|
67
|
+
response = await fetchWithTimeout(config.endpoint, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers,
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
model: config.model,
|
|
72
|
+
messages,
|
|
73
|
+
temperature: options?.temperature ?? config.temperature ?? 0.3,
|
|
74
|
+
...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
|
|
75
|
+
...config.extraParams,
|
|
76
|
+
}),
|
|
77
|
+
}, timeoutMs, options?.signal);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// fetchWithTimeout throws a plain Error with a message containing
|
|
81
|
+
// "timed out" for AbortController-driven timeouts, or "aborted" for
|
|
82
|
+
// caller-driven cancellations. Map both to typed LlmCallError.
|
|
83
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
84
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
85
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
86
|
+
}
|
|
87
|
+
if (msg.includes("timed out")) {
|
|
88
|
+
throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
|
|
89
|
+
}
|
|
90
|
+
throw new LlmCallError(`Network error: ${msg}`, "network_error");
|
|
91
|
+
}
|
|
57
92
|
if (!response.ok) {
|
|
58
93
|
const rawBody = await response.text().catch(() => "");
|
|
59
94
|
const safeBody = redactErrorBody(rawBody);
|
|
60
|
-
|
|
95
|
+
const status = response.status;
|
|
96
|
+
if (status === 429) {
|
|
97
|
+
throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
|
|
98
|
+
}
|
|
99
|
+
if (status >= 500) {
|
|
100
|
+
throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
101
|
+
}
|
|
102
|
+
throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
61
103
|
}
|
|
62
104
|
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
|
-
}
|
|
105
|
+
const content = (json.choices?.[0]?.message?.content ?? "").trim();
|
|
106
|
+
const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
|
|
107
|
+
return content || reasoning;
|
|
82
108
|
}
|
|
83
109
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
110
|
+
* Strip `<think>` blocks, code fences, and escape control characters in JSON
|
|
111
|
+
* strings. Thin wrapper kept for backward compatibility with call sites that
|
|
112
|
+
* import `stripJsonFences` from this module. New code should prefer the
|
|
113
|
+
* granular helpers from `../core/parse`.
|
|
86
114
|
*/
|
|
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;
|
|
115
|
+
export function stripJsonFences(raw) {
|
|
116
|
+
return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
|
|
140
117
|
}
|
|
141
118
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
142
119
|
/**
|
package/dist/llm/feature-gate.js
CHANGED
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
* The seam is intentionally tiny:
|
|
9
9
|
*
|
|
10
10
|
* - `isLlmFeatureEnabled(config, feature)` — pure predicate, no side
|
|
11
|
-
* effects, no I/O. Returns `true`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* opts in.
|
|
11
|
+
* effects, no I/O. Returns `true` when the feature flag is explicitly
|
|
12
|
+
* `true`, or when the feature has a non-false default (currently
|
|
13
|
+
* `graph_extraction`).
|
|
15
14
|
* - `tryLlmFeature(feature, config, fn, fallback, opts?)` — single-call
|
|
16
15
|
* wrapper that runs `fn()` only when the gate is open, enforces a hard
|
|
17
|
-
* timeout (default
|
|
18
|
-
* on disablement, throw, or timeout. The wrapper
|
|
19
|
-
* transparent for any given (gate-state, fn-result)
|
|
20
|
-
* state is mutated.
|
|
16
|
+
* timeout (default 600s — overridable per call via `opts.timeoutMs`),
|
|
17
|
+
* and returns `fallback` on disablement, throw, or timeout. The wrapper
|
|
18
|
+
* is referentially transparent for any given (gate-state, fn-result)
|
|
19
|
+
* pair: no module state is mutated.
|
|
21
20
|
*
|
|
22
21
|
* Statelessness invariant (v1 spec §14.4): nothing in this module holds
|
|
23
22
|
* state across calls. There are no caches, no module-level singletons, no
|
|
@@ -29,18 +28,30 @@
|
|
|
29
28
|
/**
|
|
30
29
|
* Pure predicate: is the named feature gate explicitly enabled in `config`?
|
|
31
30
|
*
|
|
32
|
-
* Returns `false` when
|
|
33
|
-
*
|
|
34
|
-
* - the `features` block is missing,
|
|
35
|
-
* - the key is absent (defaults are `false`),
|
|
36
|
-
* - the key is set to `false`.
|
|
31
|
+
* Returns `false` only when the key is explicitly set to `false`, or when
|
|
32
|
+
* the key is absent and its default is `false`.
|
|
37
33
|
*/
|
|
34
|
+
const FEATURE_DEFAULTS = {
|
|
35
|
+
memory_inference: true,
|
|
36
|
+
graph_extraction: true,
|
|
37
|
+
};
|
|
38
38
|
export function isLlmFeatureEnabled(config, feature) {
|
|
39
|
-
|
|
39
|
+
const configured = config?.llm?.features?.[feature];
|
|
40
|
+
if (configured === true)
|
|
41
|
+
return true;
|
|
42
|
+
if (configured === false)
|
|
40
43
|
return false;
|
|
41
|
-
return
|
|
44
|
+
return FEATURE_DEFAULTS[feature] === true;
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Default hard timeout for every bounded in-tree LLM call. Set to 10 minutes
|
|
48
|
+
* (600 000 ms) — generous enough for a slow local model on a single-threaded
|
|
49
|
+
* server. Override per-call via `TryLlmFeatureOptions.timeoutMs`.
|
|
50
|
+
*
|
|
51
|
+
* Do NOT reduce this default without a documented user-facing reason — local
|
|
52
|
+
* model users need the headroom.
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
44
55
|
/**
|
|
45
56
|
* Run `fn()` only if `isLlmFeatureEnabled(config, feature)` is `true`. On
|
|
46
57
|
* disablement, throw, or timeout, return `fallback` (or — if it is a
|