akm-cli 0.9.14 → 0.9.15-beta.1
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 +397 -0
- package/STABILITY.md +6 -3
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
- package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
- package/dist/assets/tasks/core/improve.yml +1 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
- package/dist/cli/retired-commands.js +0 -1
- package/dist/cli/shared.js +9 -0
- package/dist/cli/unknown-flags.js +1 -0
- package/dist/cli.js +3 -2
- package/dist/commands/config-cli.js +85 -3
- package/dist/commands/env/env-cli.js +1 -42
- package/dist/commands/env/env.js +1 -1
- package/dist/commands/env/secret-cli.js +1 -2
- package/dist/commands/health/checks.js +357 -63
- package/dist/commands/health/engine-usage.js +45 -0
- package/dist/commands/health/improve-metrics.js +18 -0
- package/dist/commands/health/llm-usage.js +41 -1
- package/dist/commands/health/plugin-staleness.js +7 -3
- package/dist/commands/health/version-drift.js +93 -0
- package/dist/commands/health/windows.js +3 -1
- package/dist/commands/health.js +44 -9
- package/dist/commands/improve/consolidate/chunking.js +4 -2
- package/dist/commands/improve/improve-cli.js +99 -5
- package/dist/commands/improve/improve-report.js +154 -0
- package/dist/commands/improve/improve-result-file.js +45 -33
- package/dist/commands/improve/improve-strategies.js +133 -3
- package/dist/commands/improve/improve-usage-report.js +182 -0
- package/dist/commands/improve/improve.js +40 -3
- package/dist/commands/improve/locks.js +27 -78
- package/dist/commands/improve/planner.js +1 -0
- package/dist/commands/improve/preparation.js +9 -1
- package/dist/commands/improve/reflect.js +44 -4
- package/dist/commands/models-cli.js +50 -1
- package/dist/commands/proposal/repository.js +8 -3
- package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
- package/dist/commands/proposal/validators/proposal-validators.js +24 -0
- package/dist/commands/read/search-cli.js +38 -2
- package/dist/commands/read/show.js +103 -4
- package/dist/commands/sources/info.js +5 -1
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/stash-cli.js +31 -0
- package/dist/commands/tasks/tasks-cli.js +49 -2
- package/dist/commands/workflow-cli.js +86 -12
- package/dist/core/asset/markdown-fragments.js +35 -0
- package/dist/core/config/config-schema.js +14 -0
- package/dist/core/config/config.js +302 -24
- package/dist/core/env-secret-ref.js +58 -5
- package/dist/core/errors.js +30 -0
- package/dist/core/improve-result.js +51 -0
- package/dist/core/loopback.js +17 -0
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +96 -0
- package/dist/core/sensitive-marker-path.js +19 -0
- package/dist/core/state-db.js +74 -14
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-writer-lock.js +40 -1
- package/dist/indexer/index-written-assets.js +21 -1
- package/dist/indexer/indexer.js +18 -17
- package/dist/indexer/materialize-embeddings.js +282 -32
- package/dist/indexer/search/db-search.js +49 -2
- package/dist/integrations/agent/engine-resolution.js +96 -6
- package/dist/integrations/agent/execution-definitions.js +6 -15
- package/dist/integrations/agent/execution-lowering.js +6 -1
- package/dist/integrations/agent/execution-preparation.js +1 -1
- package/dist/integrations/agent/model-map.js +123 -20
- package/dist/integrations/agent/prompts.js +40 -8
- package/dist/integrations/agent/runner-dispatch.js +9 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +8 -3
- package/dist/llm/embedder.js +20 -8
- package/dist/llm/embedders/local.js +10 -2
- package/dist/llm/embedders/remote.js +188 -21
- package/dist/output/shapes/helpers.js +38 -2
- package/dist/output/shapes/models-list.js +16 -0
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/shapes.js +4 -0
- package/dist/output/text/command-format.js +29 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/improve-report.js +27 -0
- package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
- package/dist/output/text/show-format.js +4 -0
- package/dist/output/text.js +4 -0
- package/dist/scripts/akm-migrate-node.js +24798 -21732
- package/dist/scripts/akm-migrate.js +23408 -20343
- package/dist/storage/repositories/improve-runs-repository.js +34 -0
- package/dist/storage/repositories/index-fts-repository.js +49 -6
- package/dist/storage/repositories/index-vec-repository.js +30 -0
- package/dist/storage/repositories/workflow-runs-repository.js +55 -18
- package/dist/tasks/backends/cron.js +14 -7
- package/dist/tasks/run/run-workflow-task.js +16 -0
- package/dist/workflows/exec/child-workflow.js +2 -2
- package/dist/workflows/exec/dispatch-redaction.js +21 -9
- package/dist/workflows/exec/run-workflow.js +6 -5
- package/dist/workflows/runtime/runs.js +33 -5
- package/docs/migration/release-notes/0.9.15.md +52 -0
- package/docs/migration/release-notes/README.md +4 -0
- package/docs/reference/cli.md +245 -29
- package/docs/reference/configuration.md +180 -19
- package/docs/reference/data-and-telemetry.md +8 -0
- package/docs/reference/tasks.md +16 -1
- package/docs/reference/workflow-schema.md +5 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +8 -0
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { resolveSecret } from "../../core/config/config.js";
|
|
7
8
|
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
9
|
+
import { SECRET_STORE_REFERENCE_PATTERN } from "../../core/config/schema/primitives.js";
|
|
8
10
|
import { ConfigError } from "../../core/errors.js";
|
|
9
11
|
import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
|
|
10
12
|
import { collectSensitiveValues } from "../../core/redaction.js";
|
|
11
13
|
import { warn } from "../../core/warn.js";
|
|
14
|
+
import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
|
|
12
15
|
import { getHarness } from "../harnesses/index.js";
|
|
13
16
|
import { DEFAULT_AGENT_TIMEOUT_MS, DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
14
17
|
import { getBuiltinAgentProfile } from "./profiles.js";
|
|
@@ -70,6 +73,21 @@ export function lookupApiKeyFileValue(filePath) {
|
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort, non-throwing read of a secret-store-backed credential's
|
|
78
|
+
* current value (#953), for redaction inventories and health probes that
|
|
79
|
+
* must never fail just because a value collector ran ahead of the real
|
|
80
|
+
* dispatch — an unresolvable reference is reported by
|
|
81
|
+
* {@link resolveLlmCredentialValue} at the actual call.
|
|
82
|
+
*/
|
|
83
|
+
export function lookupApiKeySecretRefValue(ref) {
|
|
84
|
+
try {
|
|
85
|
+
return resolveSecret(ref, resolveSecretFromStore);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
73
91
|
function selectedEngineName(config, layers, llmOnly) {
|
|
74
92
|
for (let index = layers.length - 1; index >= 0; index--) {
|
|
75
93
|
const layer = layers[index];
|
|
@@ -94,9 +112,14 @@ function resolveCredential(name, engine, config) {
|
|
|
94
112
|
const apiKey = ownValue(engine, "apiKey");
|
|
95
113
|
if (apiKey !== undefined) {
|
|
96
114
|
const explicit = envName(apiKey);
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
if (explicit)
|
|
116
|
+
return { names: [explicit], required: true };
|
|
117
|
+
// #953: a secret-store reference has no env descriptor — resolved
|
|
118
|
+
// separately onto `ResolvedLlmUse.apiKeySecretRef` — mirroring how
|
|
119
|
+
// apiKeyFile above is its own credential source.
|
|
120
|
+
if (SECRET_STORE_REFERENCE_PATTERN.test(apiKey))
|
|
121
|
+
return undefined;
|
|
122
|
+
throw new ConfigError(`Engine "${name}" has an invalid symbolic apiKey reference.`, "INVALID_CONFIG_FILE");
|
|
100
123
|
}
|
|
101
124
|
// #905: an explicit apiKeyFile is its own credential source — resolved
|
|
102
125
|
// separately onto `ResolvedLlmUse.apiKeyFile` — so it does not also fall
|
|
@@ -142,11 +165,66 @@ export function resolveCredentialFromEnv(credential, envSource = process.env) {
|
|
|
142
165
|
* so a whole operation observes one stable credential value instead of
|
|
143
166
|
* re-reading the file on every dispatch within it.
|
|
144
167
|
*/
|
|
145
|
-
export function resolveLlmCredentialValue(engine, credential, apiKeyFile, envSource = process.env) {
|
|
168
|
+
export function resolveLlmCredentialValue(engine, credential, apiKeyFile, apiKeySecretRef, envSource = process.env) {
|
|
146
169
|
const envValue = resolveCredentialFromEnv(credential, envSource);
|
|
147
170
|
if (envValue !== undefined)
|
|
148
171
|
return envValue;
|
|
149
|
-
|
|
172
|
+
if (apiKeyFile !== undefined)
|
|
173
|
+
return readApiKeyFile(engine, apiKeyFile);
|
|
174
|
+
// #953: a secret-store reference is the last fallback tier, reusing the
|
|
175
|
+
// same resolveSecret() helper llm/client.ts and embedders/remote.ts call
|
|
176
|
+
// directly — throws SECRET_REFERENCE_UNRESOLVED naming only the reference.
|
|
177
|
+
return apiKeySecretRef !== undefined ? resolveSecret(apiKeySecretRef, resolveSecretFromStore) : undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Non-throwing credential-presence check for `akm health`, the
|
|
181
|
+
* improve-strategy probe (#953), and improve's own plan builder (#957): an env
|
|
182
|
+
* value is present, or a file-backed credential is readable and non-empty, or
|
|
183
|
+
* a secret-store reference resolves. Never reads env/disk/store speculatively
|
|
184
|
+
* beyond what's needed to answer "is something here", and never throws on a
|
|
185
|
+
* broken/missing source — that is reported by {@link resolveLlmCredentialValue}
|
|
186
|
+
* at the real dispatch.
|
|
187
|
+
*
|
|
188
|
+
* On failure, `reference` and `reason` name WHICH env var / file / secret
|
|
189
|
+
* reference is missing (never its value) — the operator's own shell often
|
|
190
|
+
* passes the same check a scheduler's stripped-down environment fails, so the
|
|
191
|
+
* caller needs to say which reference is the problem. Callers that must never
|
|
192
|
+
* name the reference (`akm health`'s evidence/message) use only `.available`.
|
|
193
|
+
*/
|
|
194
|
+
export function describeLlmCredentialAvailability(resolved, env = process.env) {
|
|
195
|
+
if (resolved.credential?.required) {
|
|
196
|
+
if (resolved.credential.names.some((name) => Boolean(env[name]?.trim())))
|
|
197
|
+
return { available: true };
|
|
198
|
+
const reference = `$${resolved.credential.names[0]}`;
|
|
199
|
+
return { available: false, reference, reason: `${reference} is not set in this environment` };
|
|
200
|
+
}
|
|
201
|
+
if (resolved.apiKeyFile !== undefined) {
|
|
202
|
+
if (lookupApiKeyFileValue(resolved.apiKeyFile) !== undefined)
|
|
203
|
+
return { available: true };
|
|
204
|
+
return {
|
|
205
|
+
available: false,
|
|
206
|
+
reference: resolved.apiKeyFile,
|
|
207
|
+
reason: `apiKeyFile ${resolved.apiKeyFile} is missing or empty`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (resolved.apiKeySecretRef !== undefined) {
|
|
211
|
+
if (lookupApiKeySecretRefValue(resolved.apiKeySecretRef) !== undefined)
|
|
212
|
+
return { available: true };
|
|
213
|
+
return {
|
|
214
|
+
available: false,
|
|
215
|
+
reference: resolved.apiKeySecretRef,
|
|
216
|
+
reason: `${resolved.apiKeySecretRef} did not resolve from the secret store`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return { available: true };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Thin boolean wrapper over {@link describeLlmCredentialAvailability} for
|
|
223
|
+
* callers (`akm health`'s engine-reachability probes) that only need a
|
|
224
|
+
* yes/no answer and never surface the reference.
|
|
225
|
+
*/
|
|
226
|
+
export function isLlmCredentialAvailable(resolved, env = process.env) {
|
|
227
|
+
return describeLlmCredentialAvailability(resolved, env).available;
|
|
150
228
|
}
|
|
151
229
|
/** Collect materialized engine credentials for output and persistence redaction. */
|
|
152
230
|
export function collectEngineCredentialValues(config, envSource = process.env) {
|
|
@@ -168,6 +246,13 @@ export function collectEngineCredentialValues(config, envSource = process.env) {
|
|
|
168
246
|
if (value)
|
|
169
247
|
values.add(value);
|
|
170
248
|
}
|
|
249
|
+
// #953: secret-store-backed credential — best-effort for the same reason.
|
|
250
|
+
const apiKey = ownValue(engine, "apiKey");
|
|
251
|
+
if (apiKey !== undefined && SECRET_STORE_REFERENCE_PATTERN.test(apiKey)) {
|
|
252
|
+
const value = lookupApiKeySecretRefValue(apiKey);
|
|
253
|
+
if (value)
|
|
254
|
+
values.add(value);
|
|
255
|
+
}
|
|
171
256
|
}
|
|
172
257
|
return collectSensitiveValues(values);
|
|
173
258
|
}
|
|
@@ -235,11 +320,14 @@ export function resolveLlmEngineUse(config, layers, options = {}) {
|
|
|
235
320
|
delete connection[key];
|
|
236
321
|
}
|
|
237
322
|
const apiKeyFile = ownValue(engine, "apiKeyFile");
|
|
323
|
+
const apiKeyRaw = ownValue(engine, "apiKey");
|
|
324
|
+
const apiKeySecretRef = apiKeyRaw !== undefined && SECRET_STORE_REFERENCE_PATTERN.test(apiKeyRaw) ? apiKeyRaw : undefined;
|
|
238
325
|
return {
|
|
239
326
|
engine: name,
|
|
240
327
|
connection: sterileRecord(connection),
|
|
241
328
|
credential: resolveCredential(name, engine, config),
|
|
242
329
|
...(apiKeyFile !== undefined ? { apiKeyFile: expandHomePath(apiKeyFile) } : {}),
|
|
330
|
+
...(apiKeySecretRef !== undefined ? { apiKeySecretRef } : {}),
|
|
243
331
|
timeoutMs: effectiveTimeout(engine, layers, DEFAULT_LLM_TIMEOUT_MS),
|
|
244
332
|
};
|
|
245
333
|
}
|
|
@@ -270,7 +358,7 @@ export function materializeLlmConnectionWithCredential(resolved, credentialValue
|
|
|
270
358
|
* engine has no env descriptor.
|
|
271
359
|
*/
|
|
272
360
|
export function materializeLlmConnection(resolved, envSource = process.env) {
|
|
273
|
-
return materializeLlmConnectionWithCredential(resolved, resolveLlmCredentialValue(resolved.engine, resolved.credential, resolved.apiKeyFile, envSource));
|
|
361
|
+
return materializeLlmConnectionWithCredential(resolved, resolveLlmCredentialValue(resolved.engine, resolved.credential, resolved.apiKeyFile, resolved.apiKeySecretRef, envSource));
|
|
274
362
|
}
|
|
275
363
|
function lowerAgentEngine(name, engine, config) {
|
|
276
364
|
const harness = getHarness(engine.platform);
|
|
@@ -319,6 +407,7 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
319
407
|
fallbackConnection: fallback.connection,
|
|
320
408
|
...(fallback.credential ? { fallbackCredential: fallback.credential } : {}),
|
|
321
409
|
...(fallback.apiKeyFile ? { fallbackApiKeyFile: fallback.apiKeyFile } : {}),
|
|
410
|
+
...(fallback.apiKeySecretRef ? { fallbackApiKeySecretRef: fallback.apiKeySecretRef } : {}),
|
|
322
411
|
fallbackTimeoutMs: fallback.timeoutMs,
|
|
323
412
|
}
|
|
324
413
|
: {}),
|
|
@@ -340,6 +429,7 @@ export function resolveEngine(name, config) {
|
|
|
340
429
|
connection: resolved.connection,
|
|
341
430
|
...(resolved.credential ? { credential: resolved.credential } : {}),
|
|
342
431
|
...(resolved.apiKeyFile ? { apiKeyFile: resolved.apiKeyFile } : {}),
|
|
432
|
+
...(resolved.apiKeySecretRef ? { apiKeySecretRef: resolved.apiKeySecretRef } : {}),
|
|
343
433
|
timeoutMs: resolved.timeoutMs,
|
|
344
434
|
};
|
|
345
435
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { cloneExecutionJsonObject } from "../../execution/json.js";
|
|
5
5
|
import { DEFAULT_AGENT_TIMEOUT_MS, DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
6
|
+
import { engineModelAndInference } from "./model-map.js";
|
|
6
7
|
import { OPENCODE_SDK_SERVER_BIN } from "./profiles.js";
|
|
7
8
|
/**
|
|
8
9
|
* Canonical engine-setting marker: an SDK without its own model selected the
|
|
@@ -22,25 +23,15 @@ function withoutUndefined(value) {
|
|
|
22
23
|
}
|
|
23
24
|
function engineDefaults(engine) {
|
|
24
25
|
const defaults = {};
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const { model, inference } = engineModelAndInference(engine);
|
|
27
|
+
if (model !== undefined)
|
|
28
|
+
defaults.model = model;
|
|
27
29
|
if (own(engine, "timeoutMs"))
|
|
28
30
|
defaults.timeout = engine.timeoutMs;
|
|
29
31
|
if (engine.kind === "agent" && own(engine, "workspace"))
|
|
30
32
|
defaults.workspace = engine.workspace;
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
temperature: ownValue(engine, "temperature"),
|
|
34
|
-
maxTokens: ownValue(engine, "maxTokens"),
|
|
35
|
-
supportsJsonSchema: ownValue(engine, "supportsJsonSchema"),
|
|
36
|
-
extraParams: ownValue(engine, "extraParams"),
|
|
37
|
-
contextLength: ownValue(engine, "contextLength"),
|
|
38
|
-
enableThinking: ownValue(engine, "enableThinking"),
|
|
39
|
-
reasoningEffort: ownValue(engine, "reasoningEffort"),
|
|
40
|
-
});
|
|
41
|
-
if (Object.keys(inference).length > 0)
|
|
42
|
-
defaults.inference = inference;
|
|
43
|
-
}
|
|
33
|
+
if (inference !== undefined)
|
|
34
|
+
defaults.inference = inference;
|
|
44
35
|
return Object.freeze(defaults);
|
|
45
36
|
}
|
|
46
37
|
function configuredSdkFallback(engine, config) {
|
|
@@ -210,13 +210,16 @@ function snapshotRunnerSpec(input, options = {}) {
|
|
|
210
210
|
if (own(cloned, "timeoutMs"))
|
|
211
211
|
validateTimeout(cloned.timeoutMs, "execution runner material.timeoutMs");
|
|
212
212
|
if (kind === "llm") {
|
|
213
|
-
assertKeys(cloned, ["kind", "engine", "connection", "credential", "apiKeyFile", "timeoutMs"], "execution runner material");
|
|
213
|
+
assertKeys(cloned, ["kind", "engine", "connection", "credential", "apiKeyFile", "apiKeySecretRef", "timeoutMs"], "execution runner material");
|
|
214
214
|
validateConnection(cloned.connection, "execution runner material.connection", !options.allowMissingLlmModel);
|
|
215
215
|
if (own(cloned, "credential"))
|
|
216
216
|
validateCredential(cloned.credential, "execution runner material.credential");
|
|
217
217
|
// #905: a path, not the secret itself — as safe to freeze as `credential`'s
|
|
218
218
|
// env-var name.
|
|
219
219
|
validateOptionalString(cloned, "apiKeyFile", "execution runner material");
|
|
220
|
+
// #953: a `secret://<name>` reference, not the secret itself — same
|
|
221
|
+
// freeze-safety rationale as apiKeyFile above.
|
|
222
|
+
validateOptionalString(cloned, "apiKeySecretRef", "execution runner material");
|
|
220
223
|
}
|
|
221
224
|
else if (kind === "agent") {
|
|
222
225
|
assertKeys(cloned, ["kind", "engine", "profile", "timeoutMs"], "execution runner material");
|
|
@@ -230,6 +233,7 @@ function snapshotRunnerSpec(input, options = {}) {
|
|
|
230
233
|
"fallbackConnection",
|
|
231
234
|
"fallbackCredential",
|
|
232
235
|
"fallbackApiKeyFile",
|
|
236
|
+
"fallbackApiKeySecretRef",
|
|
233
237
|
"fallbackTimeoutMs",
|
|
234
238
|
"timeoutMs",
|
|
235
239
|
], "execution runner material");
|
|
@@ -241,6 +245,7 @@ function snapshotRunnerSpec(input, options = {}) {
|
|
|
241
245
|
validateCredential(cloned.fallbackCredential, "execution runner material.fallbackCredential");
|
|
242
246
|
}
|
|
243
247
|
validateOptionalString(cloned, "fallbackApiKeyFile", "execution runner material");
|
|
248
|
+
validateOptionalString(cloned, "fallbackApiKeySecretRef", "execution runner material");
|
|
244
249
|
if (own(cloned, "fallbackTimeoutMs")) {
|
|
245
250
|
validateTimeout(cloned.fallbackTimeoutMs, "execution runner material.fallbackTimeoutMs");
|
|
246
251
|
}
|
|
@@ -54,7 +54,7 @@ export function prepareResolvedExecution(options) {
|
|
|
54
54
|
...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}),
|
|
55
55
|
...(options.current ? { current: options.current } : {}),
|
|
56
56
|
engines: executionEngineDefinitionsFromConfig(config),
|
|
57
|
-
modelMap: options.modelMap ?? loadModelMap().map,
|
|
57
|
+
modelMap: options.modelMap ?? loadModelMap({ engines: config.engines }).map,
|
|
58
58
|
invocationKind: options.invocationKind,
|
|
59
59
|
...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
|
|
60
60
|
});
|
|
@@ -69,13 +69,25 @@ function parseProfileLayer(value, source, jsonPath) {
|
|
|
69
69
|
if (typeof value === "string")
|
|
70
70
|
return requireNonemptyString(value, source, jsonPath);
|
|
71
71
|
const record = requireRecord(value, source, jsonPath);
|
|
72
|
-
assertOnlyKeys(record, ["model", "inference"], source, jsonPath);
|
|
73
|
-
if (!Object.hasOwn(record, "model") && !Object.hasOwn(record, "inference")) {
|
|
74
|
-
invalid(source, jsonPath, "structured profile must contain model and/or
|
|
72
|
+
assertOnlyKeys(record, ["model", "inference", "engine"], source, jsonPath);
|
|
73
|
+
if (!Object.hasOwn(record, "model") && !Object.hasOwn(record, "inference") && !Object.hasOwn(record, "engine")) {
|
|
74
|
+
invalid(source, jsonPath, "structured profile must contain model, inference, and/or engine");
|
|
75
|
+
}
|
|
76
|
+
if (Object.hasOwn(record, "model") && Object.hasOwn(record, "engine")) {
|
|
77
|
+
invalid(source, jsonPath, "model and engine cannot both be set; engine is an indirection for the model value");
|
|
75
78
|
}
|
|
76
79
|
const out = {};
|
|
77
80
|
if (Object.hasOwn(record, "model"))
|
|
78
81
|
out.model = requireNonemptyString(record.model, source, `${jsonPath}.model`);
|
|
82
|
+
if (Object.hasOwn(record, "engine")) {
|
|
83
|
+
const rawEngine = requireNonemptyString(record.engine, source, `${jsonPath}.engine`);
|
|
84
|
+
const engine = rawEngine.toLowerCase();
|
|
85
|
+
assertSafeMapKey(engine, source, `${jsonPath}.engine`);
|
|
86
|
+
if (!ENGINE_KEY_PATTERN.test(engine)) {
|
|
87
|
+
invalid(source, `${jsonPath}.engine`, "engine reference must be lowercase kebab-case");
|
|
88
|
+
}
|
|
89
|
+
out.engine = engine;
|
|
90
|
+
}
|
|
79
91
|
if (Object.hasOwn(record, "inference")) {
|
|
80
92
|
if (record.inference === null)
|
|
81
93
|
out.inference = null;
|
|
@@ -161,10 +173,21 @@ function mergeProfiles(base, overlay) {
|
|
|
161
173
|
const out = {};
|
|
162
174
|
if (base?.model !== undefined)
|
|
163
175
|
out.model = base.model;
|
|
176
|
+
if (base?.engine !== undefined)
|
|
177
|
+
out.engine = base.engine;
|
|
164
178
|
if (base && Object.hasOwn(base, "inference"))
|
|
165
179
|
out.inference = base.inference;
|
|
166
|
-
|
|
180
|
+
// A literal `model` and an `engine` indirection are mutually exclusive within
|
|
181
|
+
// one layer (enforced at parse time); across layers, whichever the nearer
|
|
182
|
+
// layer sets replaces the other so the merged profile keeps that invariant.
|
|
183
|
+
if (next.model !== undefined) {
|
|
167
184
|
out.model = next.model;
|
|
185
|
+
delete out.engine;
|
|
186
|
+
}
|
|
187
|
+
if (next.engine !== undefined) {
|
|
188
|
+
out.engine = next.engine;
|
|
189
|
+
delete out.model;
|
|
190
|
+
}
|
|
168
191
|
if (Object.hasOwn(next, "inference")) {
|
|
169
192
|
out.inference =
|
|
170
193
|
next.inference !== null && next.inference !== undefined && isJsonObject(base?.inference)
|
|
@@ -173,14 +196,50 @@ function mergeProfiles(base, overlay) {
|
|
|
173
196
|
}
|
|
174
197
|
return Object.freeze(out);
|
|
175
198
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Derive the `{ model, inference }` a model-map profile borrows from a
|
|
201
|
+
* configured engine (#946). Shared verbatim by
|
|
202
|
+
* `execution-definitions.ts`'s own execution-defaults derivation
|
|
203
|
+
* (`engineDefaults`) so the two paths cannot silently diverge. `model` is
|
|
204
|
+
* copied verbatim from the engine's own config value; it must already be
|
|
205
|
+
* meaningful for the model-map column's platform (akm does not translate
|
|
206
|
+
* between an engine's connection and an agent platform's own provider
|
|
207
|
+
* registry). Only `kind: "llm"` engines contribute inference defaults — an
|
|
208
|
+
* agent-kind engine's schema carries no temperature/thinking fields.
|
|
209
|
+
*/
|
|
210
|
+
export function engineModelAndInference(engine) {
|
|
211
|
+
const out = {};
|
|
212
|
+
if (Object.hasOwn(engine, "model") && engine.model !== undefined)
|
|
213
|
+
out.model = engine.model;
|
|
214
|
+
if (engine.kind === "llm") {
|
|
215
|
+
const inference = {};
|
|
216
|
+
if (Object.hasOwn(engine, "temperature"))
|
|
217
|
+
inference.temperature = engine.temperature;
|
|
218
|
+
if (Object.hasOwn(engine, "maxTokens"))
|
|
219
|
+
inference.maxTokens = engine.maxTokens;
|
|
220
|
+
if (Object.hasOwn(engine, "supportsJsonSchema"))
|
|
221
|
+
inference.supportsJsonSchema = engine.supportsJsonSchema;
|
|
222
|
+
if (Object.hasOwn(engine, "extraParams"))
|
|
223
|
+
inference.extraParams = engine.extraParams;
|
|
224
|
+
if (Object.hasOwn(engine, "contextLength"))
|
|
225
|
+
inference.contextLength = engine.contextLength;
|
|
226
|
+
if (Object.hasOwn(engine, "enableThinking"))
|
|
227
|
+
inference.enableThinking = engine.enableThinking;
|
|
228
|
+
if (Object.hasOwn(engine, "reasoningEffort"))
|
|
229
|
+
inference.reasoningEffort = engine.reasoningEffort;
|
|
230
|
+
if (Object.keys(inference).length > 0)
|
|
231
|
+
out.inference = inference;
|
|
232
|
+
}
|
|
233
|
+
return Object.freeze(out);
|
|
234
|
+
}
|
|
235
|
+
/** Overlay user fields over installed fields, per (alias, column), without resolving `engine` indirection. */
|
|
236
|
+
function mergeRawProfileLayers(installed, user) {
|
|
178
237
|
const aliases = new Map();
|
|
179
238
|
const apply = (layer) => {
|
|
180
|
-
for (const [alias,
|
|
239
|
+
for (const [alias, layerEngines] of Object.entries(layer.aliases)) {
|
|
181
240
|
const mergedEngines = aliases.get(alias) ?? new Map();
|
|
182
|
-
for (const [
|
|
183
|
-
mergedEngines.set(
|
|
241
|
+
for (const [engineKey, profile] of Object.entries(layerEngines)) {
|
|
242
|
+
mergedEngines.set(engineKey, mergeProfiles(mergedEngines.get(engineKey), profile));
|
|
184
243
|
}
|
|
185
244
|
aliases.set(alias, mergedEngines);
|
|
186
245
|
}
|
|
@@ -188,14 +247,55 @@ export function mergeModelMapLayers(installed, user) {
|
|
|
188
247
|
apply(installed);
|
|
189
248
|
if (user)
|
|
190
249
|
apply(user);
|
|
250
|
+
return aliases;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The overlaid-but-unresolved profile per (alias, column), before any
|
|
254
|
+
* `engine` indirection is expanded. `akm models list` (#946) uses this to
|
|
255
|
+
* report whether a column resolves through a literal `model` or an `engine`
|
|
256
|
+
* reference — information {@link mergeModelMapLayers} collapses away once it
|
|
257
|
+
* performs the final resolution.
|
|
258
|
+
*/
|
|
259
|
+
export function mergedModelMapProfiles(installed, user) {
|
|
260
|
+
const aliases = mergeRawProfileLayers(installed, user);
|
|
261
|
+
const out = [];
|
|
262
|
+
for (const [alias, engineProfiles] of aliases)
|
|
263
|
+
out.push([alias, freezeRecord(engineProfiles)]);
|
|
264
|
+
return freezeRecord(out);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Overlay user fields over installed fields, resolve any `engine` indirection
|
|
268
|
+
* against configured engines, then enforce usable merged profiles.
|
|
269
|
+
*/
|
|
270
|
+
export function mergeModelMapLayers(installed, user, engines) {
|
|
271
|
+
const aliases = mergeRawProfileLayers(installed, user);
|
|
191
272
|
const resolvedAliases = [];
|
|
192
|
-
for (const [alias,
|
|
273
|
+
for (const [alias, engineProfiles] of aliases) {
|
|
193
274
|
const resolvedEngines = [];
|
|
194
|
-
for (const [
|
|
195
|
-
|
|
196
|
-
|
|
275
|
+
for (const [engineKey, profile] of engineProfiles) {
|
|
276
|
+
let model = profile.model;
|
|
277
|
+
let inference = Object.hasOwn(profile, "inference") ? profile.inference : undefined;
|
|
278
|
+
if (model === undefined && profile.engine !== undefined) {
|
|
279
|
+
const target = ownValue(engines, profile.engine);
|
|
280
|
+
if (target === undefined) {
|
|
281
|
+
invalid("merged models.json", `$.aliases.${alias}.${engineKey}.engine`, `references unknown engine ${JSON.stringify(profile.engine)}; a usable model is required after overlay`);
|
|
282
|
+
}
|
|
283
|
+
const expansion = engineModelAndInference(target);
|
|
284
|
+
if (expansion.model === undefined) {
|
|
285
|
+
invalid("merged models.json", `$.aliases.${alias}.${engineKey}.engine`, `engine ${JSON.stringify(profile.engine)} has no usable model; a usable model is required after overlay`);
|
|
286
|
+
}
|
|
287
|
+
model = expansion.model;
|
|
288
|
+
inference =
|
|
289
|
+
inference === undefined
|
|
290
|
+
? expansion.inference
|
|
291
|
+
: inference !== null && isJsonObject(expansion.inference)
|
|
292
|
+
? mergeJsonValue(expansion.inference, inference)
|
|
293
|
+
: inference;
|
|
197
294
|
}
|
|
198
|
-
|
|
295
|
+
if (model === undefined) {
|
|
296
|
+
invalid("merged models.json", `$.aliases.${alias}.${engineKey}.model`, "a usable model is required after overlay");
|
|
297
|
+
}
|
|
298
|
+
resolvedEngines.push([engineKey, Object.freeze(inference !== undefined ? { model, inference } : { model })]);
|
|
199
299
|
}
|
|
200
300
|
resolvedAliases.push([alias, freezeRecord(resolvedEngines)]);
|
|
201
301
|
}
|
|
@@ -314,15 +414,18 @@ export function readInstalledModelMapText(options = {}) {
|
|
|
314
414
|
}
|
|
315
415
|
throw new ConfigError(`Installed models.json is missing from this AKM installation (checked: ${candidates.join(", ")}).`, "INVALID_CONFIG_FILE", "Reinstall AKM so its dist/assets/models.json package asset is restored.");
|
|
316
416
|
}
|
|
317
|
-
/**
|
|
318
|
-
export function
|
|
417
|
+
/** Read and parse the installed authority plus the optional operator overlay, without resolving them. */
|
|
418
|
+
export function loadModelMapLayers(options = {}) {
|
|
319
419
|
const installed = parseModelMapLayer(readInstalledModelMapText(options), "installed models.json");
|
|
320
420
|
const userPath = userModelMapPath(options.env);
|
|
321
421
|
const userText = readModelMapFile(userPath, "User models.json", true);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
422
|
+
const user = userText !== undefined ? parseModelMapLayer(userText, `user models.json (${userPath})`) : undefined;
|
|
423
|
+
return Object.freeze({ installed, ...(user ? { user } : {}), userPath, userStatus: user ? "loaded" : "absent" });
|
|
424
|
+
}
|
|
425
|
+
/** Load the installed authority plus the optional operator overlay. */
|
|
426
|
+
export function loadModelMap(options = {}) {
|
|
427
|
+
const { installed, user, userPath, userStatus } = loadModelMapLayers(options);
|
|
428
|
+
return Object.freeze({ map: mergeModelMapLayers(installed, user, options.engines), userPath, userStatus });
|
|
326
429
|
}
|
|
327
430
|
function targetExistsError(target, detail) {
|
|
328
431
|
return new UsageError(`${detail}: ${target}`, "RESOURCE_ALREADY_EXISTS", "Move the existing target aside, then retry. Use --overwrite only for a stable regular file you intend to replace.");
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* `frontmatter` is optional — the proposal queue parses it from `content`
|
|
26
26
|
* during validation. We carry it through if the agent supplies it.
|
|
27
27
|
*/
|
|
28
|
+
import reflectFeedbackFraming from "../../assets/prompts/reflect-feedback-framing.md" with { type: "text" };
|
|
28
29
|
import reflectLlmFramedContract from "../../assets/prompts/reflect-llm-framed-contract.md" with { type: "text" };
|
|
29
30
|
import reflectLlmSchemaContract from "../../assets/prompts/reflect-llm-schema-contract.md" with { type: "text" };
|
|
30
31
|
import reflectOutputRepair from "../../assets/prompts/reflect-output-repair.md" with { type: "text" };
|
|
@@ -56,6 +57,25 @@ function hintForType(type) {
|
|
|
56
57
|
function knownTypeList() {
|
|
57
58
|
return [...placementTypes()].sort().join(", ");
|
|
58
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Default cap (in characters) on the asset content injected into
|
|
62
|
+
* {@link buildReflectPrompt}, and the floor a caller-supplied
|
|
63
|
+
* {@link ReflectPromptInput.contentBudgetChars} is never allowed to go below.
|
|
64
|
+
* Exists to stay well under OS ARG_MAX when the prompt is passed as a CLI
|
|
65
|
+
* argument to opencode/claude — large assets (wiki snapshots, long runbooks)
|
|
66
|
+
* would otherwise trigger E2BIG on posix_spawn. Agent/SDK runners always use
|
|
67
|
+
* this flat value; the direct-LLM path can raise it per #952 (see the reflect
|
|
68
|
+
* dispatch site in `src/commands/improve/reflect.ts`).
|
|
69
|
+
*/
|
|
70
|
+
export const REFLECT_CONTENT_CAP = 12_000;
|
|
71
|
+
/**
|
|
72
|
+
* Marker appended to truncated asset content when it exceeds the active
|
|
73
|
+
* content budget (#952). Exported so `sanitizeReflectPayload` can detect a
|
|
74
|
+
* model that echoed this notice back into its rewrite instead of proposing
|
|
75
|
+
* real content, and so the output contracts can reference the exact string
|
|
76
|
+
* to forbid.
|
|
77
|
+
*/
|
|
78
|
+
export const REFLECT_TRUNCATION_MARKER = "... [truncated — focus on the visible portion]";
|
|
59
79
|
/**
|
|
60
80
|
* Common envelope every prompt asks the agent to honour when NO draft file
|
|
61
81
|
* path is available. The wrapper code uses `JSON.parse(stdout)` to extract
|
|
@@ -87,6 +107,7 @@ function fileWriteContract(draftFilePath) {
|
|
|
87
107
|
`Write the complete improved asset content to: ${draftFilePath}`,
|
|
88
108
|
"Use your file-editing tools to create or overwrite that file.",
|
|
89
109
|
"Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
|
|
110
|
+
`Never include the text "${REFLECT_TRUNCATION_MARKER}" or any other content from outside the provided asset content in the file you write.`,
|
|
90
111
|
"When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
|
|
91
112
|
"`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
|
|
92
113
|
" • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
|
|
@@ -124,10 +145,14 @@ export function reflectLlmResponseContract(mode, targetScoped) {
|
|
|
124
145
|
.replace("{{FIELD_RULE}}", targetScoped
|
|
125
146
|
? "The response has exactly the required fields `content`, `confidence`, and `frontmatterPatch`; do not echo `ref` or arbitrary `frontmatter`."
|
|
126
147
|
: "The response has exactly the required fields `ref`, `content`, `confidence`, and `frontmatterPatch`; `ref` must identify the selected asset.")
|
|
148
|
+
.replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
|
|
127
149
|
.trim();
|
|
128
150
|
}
|
|
129
151
|
const refLine = targetScoped ? "" : "AKM_REFLECT_REF: <selected asset ref>\n";
|
|
130
|
-
return reflectLlmFramedContract
|
|
152
|
+
return reflectLlmFramedContract
|
|
153
|
+
.replace("{{REF_LINE}}", refLine)
|
|
154
|
+
.replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
|
|
155
|
+
.trim();
|
|
131
156
|
}
|
|
132
157
|
export function buildReflectOutputRepairPrompt(mode, targetScoped) {
|
|
133
158
|
return reflectOutputRepair.replace("{{OUTPUT_CONTRACT}}", reflectLlmResponseContract(mode, targetScoped)).trim();
|
|
@@ -194,6 +219,10 @@ export function buildReflectPrompt(input) {
|
|
|
194
219
|
}
|
|
195
220
|
// Change 3 & 4 — feedback moved before asset content; missing else branch added
|
|
196
221
|
if (input.feedback && input.feedback.length > 0) {
|
|
222
|
+
// #952 — feedback lines are unverified reports, not verified facts. Without
|
|
223
|
+
// this caveat, models fabricated whole new sections asserting whatever a
|
|
224
|
+
// feedback line claimed (invented incident dates, ports, disk layouts).
|
|
225
|
+
sections.push(reflectFeedbackFraming.trim());
|
|
197
226
|
sections.push("Recent feedback / signals:");
|
|
198
227
|
for (const line of input.feedback)
|
|
199
228
|
sections.push(`- ${line}`);
|
|
@@ -238,17 +267,20 @@ export function buildReflectPrompt(input) {
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
if (input.assetContent?.trim()) {
|
|
241
|
-
// Cap
|
|
242
|
-
// passed as a CLI argument to opencode/claude
|
|
243
|
-
// long runbooks) would otherwise trigger
|
|
244
|
-
|
|
270
|
+
// Cap defaults to REFLECT_CONTENT_CAP (12 000 chars) to stay well under OS
|
|
271
|
+
// ARG_MAX when the prompt is passed as a CLI argument to opencode/claude
|
|
272
|
+
// — large assets (wiki snapshots, long runbooks) would otherwise trigger
|
|
273
|
+
// E2BIG on posix_spawn. Direct-LLM callers never touch argv, so they may
|
|
274
|
+
// pass a larger, context-aware `contentBudgetChars` (#952); it is never
|
|
275
|
+
// allowed below the flat floor, which the caller enforces before calling in.
|
|
276
|
+
const contentCap = input.contentBudgetChars ?? REFLECT_CONTENT_CAP;
|
|
245
277
|
const body = input.assetContent.trimEnd();
|
|
246
|
-
const truncated = body.length >
|
|
278
|
+
const truncated = body.length > contentCap;
|
|
247
279
|
sections.push(truncated
|
|
248
|
-
? `Current asset content (first ${
|
|
280
|
+
? `Current asset content (first ${contentCap} chars — full asset is ${body.length} chars):`
|
|
249
281
|
: "Current asset content (verbatim):");
|
|
250
282
|
sections.push("```");
|
|
251
|
-
sections.push(truncated ? `${body.slice(0,
|
|
283
|
+
sections.push(truncated ? `${body.slice(0, contentCap)}\n${REFLECT_TRUNCATION_MARKER}` : body);
|
|
252
284
|
sections.push("```");
|
|
253
285
|
}
|
|
254
286
|
else if (input.ref) {
|
|
@@ -20,7 +20,7 @@ import { assertNever } from "../../core/assert.js";
|
|
|
20
20
|
import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveText, redactSensitiveValue, } from "../../core/redaction.js";
|
|
21
21
|
import { spawnEnvNamesFor } from "../../core/spawn-env.js";
|
|
22
22
|
import { closeServer as disposeOpencodeSdkServers, opencodeSdkServerEnvironmentNames, runOpencodeSdk, } from "../harnesses/opencode-sdk/sdk-runner.js";
|
|
23
|
-
import { lookupApiKeyFileValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
|
|
23
|
+
import { lookupApiKeyFileValue, lookupApiKeySecretRefValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
|
|
24
24
|
import { materializeLlmRunnerConnection, materializeLlmRunnerConnectionWithCredential, } from "./runner.js";
|
|
25
25
|
import { runAgent } from "./spawn.js";
|
|
26
26
|
const liveRunnerDispatchLeases = new WeakMap();
|
|
@@ -134,10 +134,10 @@ export function acquireRunnerDispatchLease(spec, envSource = process.env) {
|
|
|
134
134
|
},
|
|
135
135
|
});
|
|
136
136
|
const primaryCredential = spec.kind === "llm"
|
|
137
|
-
? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, credentialSource)
|
|
137
|
+
? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, spec.apiKeySecretRef, credentialSource)
|
|
138
138
|
: undefined;
|
|
139
139
|
const fallbackCredential = spec.kind === "sdk"
|
|
140
|
-
? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, credentialSource)
|
|
140
|
+
? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, spec.fallbackApiKeySecretRef, credentialSource)
|
|
141
141
|
: undefined;
|
|
142
142
|
const handle = Object.create(null);
|
|
143
143
|
Object.defineProperty(handle, "toJSON", {
|
|
@@ -229,6 +229,12 @@ export function collectDispatchSensitiveValues(spec, opts, envSource = opts.envS
|
|
|
229
229
|
add(lookupApiKeyFileValue(spec.apiKeyFile));
|
|
230
230
|
if (spec.kind === "sdk" && spec.fallbackApiKeyFile)
|
|
231
231
|
add(lookupApiKeyFileValue(spec.fallbackApiKeyFile));
|
|
232
|
+
// #953: a secret-store-backed credential is read at dispatch too — same
|
|
233
|
+
// best-effort scrub-set inclusion rationale.
|
|
234
|
+
if (spec.kind === "llm" && spec.apiKeySecretRef)
|
|
235
|
+
add(lookupApiKeySecretRefValue(spec.apiKeySecretRef));
|
|
236
|
+
if (spec.kind === "sdk" && spec.fallbackApiKeySecretRef)
|
|
237
|
+
add(lookupApiKeySecretRefValue(spec.fallbackApiKeySecretRef));
|
|
232
238
|
if (spec.kind !== "llm") {
|
|
233
239
|
for (const value of Object.values(spec.profile.env ?? {}))
|
|
234
240
|
add(value);
|
|
@@ -9,6 +9,7 @@ export function materializeLlmRunnerConnection(runner) {
|
|
|
9
9
|
connection: runner.connection,
|
|
10
10
|
...(runner.credential ? { credential: runner.credential } : {}),
|
|
11
11
|
...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
|
|
12
|
+
...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
|
|
12
13
|
timeoutMs: runner.timeoutMs ?? null,
|
|
13
14
|
});
|
|
14
15
|
}
|
|
@@ -19,6 +20,7 @@ export function materializeLlmRunnerConnectionWithCredential(runner, credentialV
|
|
|
19
20
|
connection: runner.connection,
|
|
20
21
|
...(runner.credential ? { credential: runner.credential } : {}),
|
|
21
22
|
...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
|
|
23
|
+
...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
|
|
22
24
|
timeoutMs: runner.timeoutMs ?? null,
|
|
23
25
|
}, credentialValue);
|
|
24
26
|
}
|
package/dist/llm/client.js
CHANGED
|
@@ -290,12 +290,17 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
|
|
|
290
290
|
},
|
|
291
291
|
}
|
|
292
292
|
: {};
|
|
293
|
+
// #949: which of these two wire forms a backend honors is a fact about the
|
|
294
|
+
// backend (and any gateway in front of it), not about akm's own `provider`
|
|
295
|
+
// label — a llama.cpp build honors chat_template_kwargs, a bare
|
|
296
|
+
// `enable_thinking` is honored by nothing observed, and a gateway
|
|
297
|
+
// (freellmapi, Bifrost) can drop either one depending on how it was built.
|
|
298
|
+
// Send both whenever thinking is explicitly resolved so the same engine
|
|
299
|
+
// block keeps working across a direct vhost or any gateway in front of it.
|
|
293
300
|
const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
|
|
294
301
|
const thinkingParams = resolvedEnableThinking === undefined
|
|
295
302
|
? {}
|
|
296
|
-
:
|
|
297
|
-
? { chat_template_kwargs: { enable_thinking: resolvedEnableThinking } }
|
|
298
|
-
: { enable_thinking: resolvedEnableThinking };
|
|
303
|
+
: { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
|
|
299
304
|
const reasoningEffortParams = config.reasoningEffort === undefined ? {} : { reasoning_effort: config.reasoningEffort };
|
|
300
305
|
const requestBody = JSON.stringify({
|
|
301
306
|
model: config.model,
|