akm-cli 0.9.23 → 0.9.25-alpha.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 +339 -0
- package/dist/commands/health/checks.js +10 -11
- package/dist/commands/improve/consolidate/pair-pass.js +3 -2
- package/dist/commands/improve/consolidate.js +3 -2
- package/dist/commands/improve/execution.js +4 -10
- package/dist/commands/improve/extract-prompt.js +4 -4
- package/dist/commands/improve/extract.js +10 -13
- package/dist/commands/improve/improve-cli.js +32 -33
- package/dist/commands/improve/improve-strategies.js +49 -43
- package/dist/commands/improve/improve-usage-report.js +8 -17
- package/dist/commands/improve/preparation.js +3 -1
- package/dist/commands/improve/reflect.js +132 -177
- package/dist/commands/improve/stage.js +69 -18
- package/dist/commands/proposal/drain.js +19 -7
- package/dist/commands/proposal/proposal-cli.js +1 -5
- package/dist/commands/proposal/propose-cli.js +2 -2
- package/dist/commands/proposal/propose.js +80 -83
- package/dist/commands/remember.js +3 -3
- package/dist/commands/sources/schema-repair.js +1 -1
- package/dist/core/config/config-schema.js +37 -60
- package/dist/core/config/engine-semantics.js +15 -11
- package/dist/core/config/schema/engines.js +33 -15
- package/dist/core/config/schema/improve-processes.js +2 -2
- package/dist/core/improve-result.js +3 -3
- package/dist/core/structured.js +10 -0
- package/dist/execution/source.js +14 -0
- package/dist/indexer/passes/memory-inference.js +2 -1
- package/dist/integrations/agent/builder-shared.js +15 -0
- package/dist/integrations/agent/config.js +2 -0
- package/dist/integrations/agent/engine-resolution.js +16 -31
- package/dist/integrations/agent/execution.js +46 -21
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/model-map.js +16 -15
- package/dist/integrations/agent/prompts.js +53 -76
- package/dist/integrations/agent/request-lowering.js +20 -9
- package/dist/integrations/agent/runner-dispatch.js +103 -4
- package/dist/integrations/agent/runner.js +8 -2
- package/dist/integrations/harnesses/aider/agent-builder.js +11 -23
- package/dist/integrations/harnesses/aider/index.js +0 -5
- package/dist/integrations/harnesses/amazonq/agent-builder.js +10 -21
- package/dist/integrations/harnesses/amazonq/index.js +0 -5
- package/dist/integrations/harnesses/claude/agent-builder.js +61 -38
- package/dist/integrations/harnesses/claude/index.js +0 -14
- package/dist/integrations/harnesses/codex/agent-builder.js +4 -4
- package/dist/integrations/harnesses/codex/index.js +0 -4
- package/dist/integrations/harnesses/copilot/agent-builder.js +4 -13
- package/dist/integrations/harnesses/copilot/index.js +2 -7
- package/dist/integrations/harnesses/gemini/agent-builder.js +4 -13
- package/dist/integrations/harnesses/gemini/index.js +0 -5
- package/dist/integrations/harnesses/ids.js +18 -10
- package/dist/integrations/harnesses/opencode/agent-builder.js +52 -10
- package/dist/integrations/harnesses/opencode/index.js +0 -8
- package/dist/integrations/harnesses/opencode/model-config.js +80 -0
- package/dist/integrations/harnesses/opencode/model-work-agent.js +80 -0
- package/dist/integrations/harnesses/opencode-sdk/harness.js +6 -7
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +168 -24
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -21
- package/dist/integrations/harnesses/openhands/index.js +0 -5
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -21
- package/dist/integrations/harnesses/pi/index.js +0 -5
- package/dist/llm/client.js +5 -0
- package/dist/llm/feature-gate.js +10 -4
- package/dist/llm/index-passes.js +3 -6
- package/dist/llm/memory-infer.js +6 -5
- package/dist/llm/structured-call.js +33 -11
- package/dist/scripts/akm-migrate-node.js +298 -204
- package/dist/scripts/akm-migrate.js +298 -204
- package/dist/workflows/exec/step-work.js +6 -5
- package/dist/workflows/freeze/step-values.js +1 -1
- package/docs/reference/cli.md +34 -14
- package/docs/reference/configuration.md +171 -12
- package/docs/reference/workflow-schema.md +13 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +36 -0
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* enforced at save time via `superRefine` on the top-level schema.
|
|
40
40
|
*/
|
|
41
41
|
import { z } from "zod";
|
|
42
|
-
import {
|
|
42
|
+
import { HARNESS_MODEL_WORK_IDS } from "../../integrations/harnesses/ids.js";
|
|
43
|
+
import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_ENGINE_PROCESSES } from "./engine-semantics.js";
|
|
43
44
|
import { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
|
|
44
45
|
import { EnginesSchema } from "./schema/engines.js";
|
|
45
46
|
import { ExecutionPolicyConfigSchema } from "./schema/execution.js";
|
|
@@ -222,13 +223,32 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
222
223
|
message: "engine does not name a configured engine",
|
|
223
224
|
});
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
// One rule for every key unattended model work reads its engine from: the
|
|
227
|
+
// engine must confine the model-work tool policy (an LLM, or an agent whose
|
|
228
|
+
// harness does), because that work runs over generated content with no one
|
|
229
|
+
// watching.
|
|
230
|
+
const confining = [...HARNESS_MODEL_WORK_IDS];
|
|
231
|
+
const confiningPlatforms = `${confining.slice(0, -1).join(", ")} or ${confining.at(-1)}`;
|
|
232
|
+
const modelWorkEngine = (name, path) => {
|
|
233
|
+
if (!name)
|
|
234
|
+
return;
|
|
235
|
+
const engine = config.engines?.[name];
|
|
236
|
+
if (!engine) {
|
|
237
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path, message: "engine does not name a configured engine" });
|
|
238
|
+
}
|
|
239
|
+
else if (engine.kind !== "llm" && !HARNESS_MODEL_WORK_IDS.has(engine.platform)) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: z.ZodIssueCode.custom,
|
|
242
|
+
path,
|
|
243
|
+
message: `engine "${name}" (platform ${engine.platform}) cannot confine the model-work tool policy, which unattended model work requires. Use an LLM engine, or an agent engine on ${confiningPlatforms}.`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
modelWorkEngine(config.defaults?.llmEngine, ["defaults", "llmEngine"]);
|
|
248
|
+
for (const [passName, pass] of Object.entries(config.index ?? {})) {
|
|
249
|
+
const engine = pass?.engine;
|
|
250
|
+
if (typeof engine === "string")
|
|
251
|
+
modelWorkEngine(engine, ["index", passName, "engine"]);
|
|
232
252
|
}
|
|
233
253
|
const workflowJudge = config.workflow?.judgeEngine;
|
|
234
254
|
if (workflowJudge && !config.engines?.[workflowJudge]) {
|
|
@@ -256,68 +276,25 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
256
276
|
});
|
|
257
277
|
}
|
|
258
278
|
for (const [strategyName, strategy] of Object.entries(config.improve?.strategies ?? {})) {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const engine = config.engines?.[strategyEngine];
|
|
262
|
-
if (!engine || engine.kind !== "llm") {
|
|
263
|
-
ctx.addIssue({
|
|
264
|
-
code: z.ZodIssueCode.custom,
|
|
265
|
-
path: ["improve", "strategies", strategyName, "engine"],
|
|
266
|
-
message: engine ? "strategy engine must be an LLM engine" : "engine does not name a configured engine",
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
279
|
+
const strategyPath = ["improve", "strategies", strategyName];
|
|
280
|
+
modelWorkEngine(strategy.engine, [...strategyPath, "engine"]);
|
|
270
281
|
for (const [processName, process] of Object.entries(strategy.processes ?? {})) {
|
|
271
282
|
const processConfig = process;
|
|
272
|
-
const
|
|
273
|
-
if (processConfig.engine &&
|
|
283
|
+
const processPath = [...strategyPath, "processes", processName];
|
|
284
|
+
if (processConfig.engine && !IMPROVE_ENGINE_PROCESSES.includes(processName)) {
|
|
274
285
|
ctx.addIssue({
|
|
275
286
|
code: z.ZodIssueCode.custom,
|
|
276
|
-
path: [
|
|
287
|
+
path: [...processPath, "engine"],
|
|
277
288
|
message: `${processName} does not dispatch an engine`,
|
|
278
289
|
});
|
|
279
290
|
}
|
|
280
291
|
else {
|
|
281
|
-
|
|
282
|
-
if (processEngine && capability === "llm") {
|
|
283
|
-
const engine = config.engines?.[processEngine];
|
|
284
|
-
if (!engine || engine.kind !== "llm") {
|
|
285
|
-
ctx.addIssue({
|
|
286
|
-
code: z.ZodIssueCode.custom,
|
|
287
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "engine"],
|
|
288
|
-
message: engine ? `${processName} requires an LLM engine` : "engine does not name a configured engine",
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
else if (processConfig.engine && capability === "runner" && !config.engines?.[processConfig.engine]) {
|
|
293
|
-
ctx.addIssue({
|
|
294
|
-
code: z.ZodIssueCode.custom,
|
|
295
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "engine"],
|
|
296
|
-
message: "engine does not name a configured engine",
|
|
297
|
-
});
|
|
298
|
-
}
|
|
292
|
+
modelWorkEngine(processConfig.engine, [...processPath, "engine"]);
|
|
299
293
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const engine = config.engines?.[judgmentEngine];
|
|
303
|
-
if (!engine) {
|
|
304
|
-
ctx.addIssue({
|
|
305
|
-
code: z.ZodIssueCode.custom,
|
|
306
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "judgment", "engine"],
|
|
307
|
-
message: "engine does not name a configured engine",
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
const gateEngine = processConfig.qualityGate?.engine;
|
|
312
|
-
if (gateEngine && config.engines?.[gateEngine]?.kind !== "llm") {
|
|
313
|
-
ctx.addIssue({
|
|
314
|
-
code: z.ZodIssueCode.custom,
|
|
315
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "qualityGate", "engine"],
|
|
316
|
-
message: config.engines?.[gateEngine]
|
|
317
|
-
? "a quality-gate judge must be an LLM engine"
|
|
318
|
-
: "engine does not name a configured engine",
|
|
319
|
-
});
|
|
294
|
+
if (processConfig.judgment?.enabled === true) {
|
|
295
|
+
modelWorkEngine(processConfig.judgment.engine, [...processPath, "judgment", "engine"]);
|
|
320
296
|
}
|
|
297
|
+
modelWorkEngine(processConfig.qualityGate?.engine, [...processPath, "qualityGate", "engine"]);
|
|
321
298
|
}
|
|
322
299
|
}
|
|
323
300
|
// #464.a: defaultWriteTarget must name a configured source. 0.9.0 (spec
|
|
@@ -11,14 +11,18 @@ export const BUILTIN_IMPROVE_STRATEGY_NAMES = [
|
|
|
11
11
|
"reflect-distill",
|
|
12
12
|
"proactive-maintenance",
|
|
13
13
|
];
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The improve processes that use an engine. Triage's engine is its judgment's;
|
|
16
|
+
* each of the others makes the process's own model calls.
|
|
17
|
+
*/
|
|
18
|
+
export const IMPROVE_ENGINE_PROCESSES = [
|
|
19
|
+
"reflect",
|
|
20
|
+
"distill",
|
|
21
|
+
"consolidate",
|
|
22
|
+
"memoryInference",
|
|
23
|
+
"extract",
|
|
24
|
+
"validation",
|
|
25
|
+
"triage",
|
|
26
|
+
];
|
|
27
|
+
/** Every improve process, in plan order: the engine processes and `proactiveMaintenance`, which uses none. */
|
|
28
|
+
export const IMPROVE_PROCESS_NAMES = [...IMPROVE_ENGINE_PROCESSES, "proactiveMaintenance"];
|
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
// `config-types`, which type-derives from this barrel via
|
|
14
14
|
// `typeof import("./config-schema")` — routing through config-types would mint
|
|
15
15
|
// a config-schema ↔ config-types type cycle that collapses inference.
|
|
16
|
-
import { HARNESS_AGENT_DISPATCH_IDS, VALID_HARNESS_IDS } from "../../../integrations/harnesses/ids.js";
|
|
16
|
+
import { HARNESS_AGENT_DISPATCH_IDS, harnessInferenceKeys, VALID_HARNESS_IDS, } from "../../../integrations/harnesses/ids.js";
|
|
17
17
|
import { WORKFLOW_MAX_TIMEOUT_MS } from "../../../workflows/resource-limits.js";
|
|
18
18
|
import { chatCompletionsEndpoint, ExtraParamsSchema, engineName, nonEmptyString, positiveInt, symbolicOrWarnApiKey, } from "./primitives.js";
|
|
19
19
|
/**
|
|
@@ -105,6 +105,20 @@ const LlmEngineSchema = z
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
+
/**
|
|
109
|
+
* The inference fields an agent engine may set: the ones its platform
|
|
110
|
+
* translates (`harnesses/ids.ts`). An asset's or a caller's inference reaches
|
|
111
|
+
* every engine and reports what the engine does not translate as a lowering
|
|
112
|
+
* notice; an engine the operator configures for a platform names only what
|
|
113
|
+
* that platform carries, so the rest is an error here.
|
|
114
|
+
*/
|
|
115
|
+
const AGENT_INFERENCE_KEYS = [
|
|
116
|
+
"temperature",
|
|
117
|
+
"maxTokens",
|
|
118
|
+
"contextLength",
|
|
119
|
+
"enableThinking",
|
|
120
|
+
"reasoningEffort",
|
|
121
|
+
];
|
|
108
122
|
const AgentEngineSchema = z
|
|
109
123
|
.object({
|
|
110
124
|
kind: z.literal("agent"),
|
|
@@ -117,26 +131,30 @@ const AgentEngineSchema = z
|
|
|
117
131
|
model: nonEmptyString.optional(),
|
|
118
132
|
timeoutMs: timeoutMsField,
|
|
119
133
|
llmEngine: engineName.optional(),
|
|
134
|
+
temperature: z.number().finite().optional(),
|
|
135
|
+
maxTokens: positiveInt.optional(),
|
|
136
|
+
contextLength: positiveInt.optional(),
|
|
137
|
+
enableThinking: z.boolean().optional(),
|
|
138
|
+
reasoningEffort: nonEmptyString.optional(),
|
|
120
139
|
})
|
|
121
140
|
.passthrough()
|
|
122
141
|
.superRefine((value, ctx) => {
|
|
123
|
-
for (const key of [
|
|
124
|
-
"provider",
|
|
125
|
-
"endpoint",
|
|
126
|
-
"apiKey",
|
|
127
|
-
"apiKeyFile",
|
|
128
|
-
"temperature",
|
|
129
|
-
"maxTokens",
|
|
130
|
-
"concurrency",
|
|
131
|
-
"extraParams",
|
|
132
|
-
"contextLength",
|
|
133
|
-
"enableThinking",
|
|
134
|
-
"reasoningEffort",
|
|
135
|
-
"modelAliases",
|
|
136
|
-
]) {
|
|
142
|
+
for (const key of ["provider", "endpoint", "apiKey", "apiKeyFile", "concurrency", "extraParams", "modelAliases"]) {
|
|
137
143
|
if (key in value)
|
|
138
144
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [key], message: `${key} is not valid on an agent engine` });
|
|
139
145
|
}
|
|
146
|
+
const translated = harnessInferenceKeys(value.platform);
|
|
147
|
+
for (const key of AGENT_INFERENCE_KEYS) {
|
|
148
|
+
if (key in value && !translated.includes(key)) {
|
|
149
|
+
ctx.addIssue({
|
|
150
|
+
code: z.ZodIssueCode.custom,
|
|
151
|
+
path: [key],
|
|
152
|
+
message: `${key} is not valid on a ${value.platform} engine: ${translated.length > 0
|
|
153
|
+
? `the platform translates only ${translated.join(", ")}`
|
|
154
|
+
: "the platform translates no inference fields"}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
140
158
|
if (value.platform !== "opencode-sdk" && value.llmEngine !== undefined) {
|
|
141
159
|
ctx.addIssue({
|
|
142
160
|
code: z.ZodIssueCode.custom,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* verbatim from the former `config-schema.ts` monolith — no behavior change.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import {
|
|
9
|
+
import { IMPROVE_PROCESS_NAMES } from "../engine-semantics.js";
|
|
10
10
|
import { engineName, LlmInvocationOverridesSchema, nonEmptyString, positiveInt } from "./primitives.js";
|
|
11
11
|
// ── Improve profile / process ──────────────────────────────────────────────
|
|
12
12
|
//
|
|
@@ -319,7 +319,7 @@ const ImproveProfileProcessesSchema = z
|
|
|
319
319
|
});
|
|
320
320
|
}
|
|
321
321
|
for (const [name, process] of Object.entries(val)) {
|
|
322
|
-
if (!(name
|
|
322
|
+
if (!IMPROVE_PROCESS_NAMES.includes(name) &&
|
|
323
323
|
!RETIRED_PROCESS_NAMES.has(name) &&
|
|
324
324
|
process !== null &&
|
|
325
325
|
typeof process === "object" &&
|
|
@@ -3,7 +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 { isRecord } from "./common.js";
|
|
6
|
-
import {
|
|
6
|
+
import { IMPROVE_PROCESS_NAMES } from "./config/engine-semantics.js";
|
|
7
7
|
const COMMON_FIELDS = [
|
|
8
8
|
"schemaVersion",
|
|
9
9
|
"ok",
|
|
@@ -192,11 +192,11 @@ function validateProactivePlan(value) {
|
|
|
192
192
|
fail("plan.proactive.selected must equal plan.proactive.selectedRefs.length");
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
/** #947 — plan.processes: one row per
|
|
195
|
+
/** #947 — plan.processes: one row per IMPROVE_PROCESS_NAMES name, plus an optional "triage.judgment" row. */
|
|
196
196
|
function validateProcessRoutingRows(value) {
|
|
197
197
|
if (!Array.isArray(value))
|
|
198
198
|
fail("plan.processes must be an array");
|
|
199
|
-
const canonicalNames =
|
|
199
|
+
const canonicalNames = IMPROVE_PROCESS_NAMES;
|
|
200
200
|
const engineKinds = new Set(["llm", "agent", "sdk"]);
|
|
201
201
|
const seen = new Set();
|
|
202
202
|
for (const row of value) {
|
package/dist/core/structured.js
CHANGED
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
* jittered retry; `runAgent` has timeout/abort semantics).
|
|
25
25
|
*/
|
|
26
26
|
import { parseEmbeddedJsonResponse } from "./parse.js";
|
|
27
|
+
/**
|
|
28
|
+
* Append the one structured-output instruction to a prompt. Agent lowering
|
|
29
|
+
* appends it to every schema-bearing request, alongside a harness's native
|
|
30
|
+
* channel where one exists (codex `--output-schema`); the workflow engine
|
|
31
|
+
* appends it for a direct-LLM unit. Resumed workflow runs depend on these
|
|
32
|
+
* exact bytes, so do not reword it.
|
|
33
|
+
*/
|
|
34
|
+
export function withSchemaInstruction(prompt, schema) {
|
|
35
|
+
return `${prompt}\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(schema)}`;
|
|
36
|
+
}
|
|
27
37
|
function defaultFeedback(failure) {
|
|
28
38
|
if (failure.reason === "parse_error") {
|
|
29
39
|
return "Your previous response contained no parseable JSON. Respond with ONLY a JSON value that matches the requested schema — no prose, no code fences.";
|
package/dist/execution/source.js
CHANGED
|
@@ -5,6 +5,20 @@ import { cloneExecutionJson, cloneExecutionJsonObject } from "./json.js";
|
|
|
5
5
|
export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
|
|
6
6
|
/** Current internal adapter identifiers are lowercase kebab-case registry keys. */
|
|
7
7
|
export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
8
|
+
/**
|
|
9
|
+
* The model-work tool policy: unattended model work (improve, the judges,
|
|
10
|
+
* index passes, remember) may read, edit only inside the dispatch's own
|
|
11
|
+
* scratch working directory, and run `akm search` and `akm show`. The stash
|
|
12
|
+
* stays read-only to it. A transport grants what it can confine and refuses
|
|
13
|
+
* the policy at build when it can confine nothing; an LLM has no tools.
|
|
14
|
+
*/
|
|
15
|
+
export const MODEL_WORK_TOOLS = Object.freeze(["read", "edit", "akm search", "akm show"]);
|
|
16
|
+
/** Whether a selection names the model-work tool policy. */
|
|
17
|
+
export function isModelWorkTools(tools) {
|
|
18
|
+
return (Array.isArray(tools) &&
|
|
19
|
+
tools.length === MODEL_WORK_TOOLS.length &&
|
|
20
|
+
tools.every((tool, index) => tool === MODEL_WORK_TOOLS[index]));
|
|
21
|
+
}
|
|
8
22
|
function requireRecord(value, path) {
|
|
9
23
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
10
24
|
throw new TypeError(`${path} must be an object`);
|
|
@@ -48,6 +48,7 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
48
48
|
import { warn } from "../../core/warn.js";
|
|
49
49
|
import { beginWriteProvenance, recordWrittenPath } from "../../core/write-provenance.js";
|
|
50
50
|
import { writeAssetToSource } from "../../core/write-source.js";
|
|
51
|
+
import { runnerLlmConnection } from "../../integrations/agent/runner.js";
|
|
51
52
|
import { assertRunnerCredentials } from "../../integrations/agent/runner-dispatch.js";
|
|
52
53
|
import { isProcessEnabled } from "../../llm/feature-gate.js";
|
|
53
54
|
import { resolveIndexPassExecution } from "../../llm/index-passes.js";
|
|
@@ -283,7 +284,7 @@ async function runMemoryInferencePassBody(ctx, provenance) {
|
|
|
283
284
|
}),
|
|
284
285
|
// Caller-set connection concurrency or 1: `resolveLlmEngineUse` does
|
|
285
286
|
// not forward `engines.<name>.concurrency`, so config cannot raise this.
|
|
286
|
-
llmRunner
|
|
287
|
+
runnerLlmConnection(llmRunner)?.concurrency ?? 1);
|
|
287
288
|
if (configFailure)
|
|
288
289
|
throw configFailure;
|
|
289
290
|
for (let i = 0; i < perRecordResults.length; i++) {
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
export function resolveDispatchModel(request, _profile, _platform) {
|
|
6
6
|
return request.model;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The model an engine's own `args` select, as `--model X` or `--model=X` (the
|
|
10
|
+
* last one wins), for a builder that writes its own argv in place of those args.
|
|
11
|
+
*/
|
|
12
|
+
export function modelFromArgs(args) {
|
|
13
|
+
let model;
|
|
14
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
15
|
+
const arg = args[index];
|
|
16
|
+
if (arg === "--model")
|
|
17
|
+
model = args[index + 1];
|
|
18
|
+
else if (arg?.startsWith("--model="))
|
|
19
|
+
model = arg.slice("--model=".length);
|
|
20
|
+
}
|
|
21
|
+
return model;
|
|
22
|
+
}
|
|
8
23
|
/**
|
|
9
24
|
* Normalize a toolPolicy value to a comma-separated string suitable for a
|
|
10
25
|
* CLI flag. Structured policy objects are JSON-serialized.
|
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
export const DEFAULT_AGENT_TIMEOUT_MS = null;
|
|
6
6
|
/** Default hard timeout for direct LLM calls when no engine/use override exists. */
|
|
7
7
|
export const DEFAULT_LLM_TIMEOUT_MS = 600_000;
|
|
8
|
+
/** Default bound on model work (structured calls, judgments) on every runner kind. */
|
|
9
|
+
export const DEFAULT_MODEL_WORK_TIMEOUT_MS = 600_000;
|
|
@@ -10,10 +10,10 @@ import { SECRET_STORE_REFERENCE_PATTERN } from "../../core/config/schema/primiti
|
|
|
10
10
|
import { ConfigError } from "../../core/errors.js";
|
|
11
11
|
import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
|
|
12
12
|
import { collectSensitiveValues } from "../../core/redaction.js";
|
|
13
|
-
import { warn } from "../../core/warn.js";
|
|
14
13
|
import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
|
|
15
14
|
import { getHarness } from "../harnesses/index.js";
|
|
16
|
-
import {
|
|
15
|
+
import { DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
16
|
+
import { engineModelAndInference } from "./model-map.js";
|
|
17
17
|
import { getBuiltinAgentProfile, OPENCODE_SDK_SERVER_BIN } from "./profiles.js";
|
|
18
18
|
const LLM_CONNECTION_FIELDS = [
|
|
19
19
|
"provider",
|
|
@@ -217,29 +217,15 @@ function rawLlmConnection(engine) {
|
|
|
217
217
|
}
|
|
218
218
|
return connection;
|
|
219
219
|
}
|
|
220
|
-
|
|
220
|
+
/** Resolve one selected LLM engine and overlays without materializing credentials. */
|
|
221
|
+
export function resolveLlmEngineUse(config, layers) {
|
|
221
222
|
const name = selectedEngineName(config, layers, true);
|
|
222
223
|
if (!name) {
|
|
223
|
-
if (options.optional)
|
|
224
|
-
return undefined;
|
|
225
224
|
throw new ConfigError("No LLM engine is selected. Set defaults.llmEngine or specify engine.", "LLM_NOT_CONFIGURED");
|
|
226
225
|
}
|
|
227
226
|
const engine = configuredEngine(name, config);
|
|
228
|
-
if (engine.kind !== "llm")
|
|
229
|
-
|
|
230
|
-
const fallbackEngine = fallbackName ? configuredEngine(fallbackName, config) : undefined;
|
|
231
|
-
if (!fallbackEngine || fallbackEngine.kind !== "llm") {
|
|
232
|
-
if (options.optional)
|
|
233
|
-
return undefined;
|
|
234
|
-
throw new ConfigError(fallbackName
|
|
235
|
-
? `Engine "${name}" is not an LLM engine, and its llmEngine fallback "${fallbackName}" is not one either.`
|
|
236
|
-
: `Engine "${name}" is not an LLM engine, and has no llmEngine fallback configured.`, "INVALID_CONFIG_FILE");
|
|
237
|
-
}
|
|
238
|
-
warn(`[akm] Engine "${name}" is an agent engine, not an LLM engine; using its llmEngine "${fallbackName}" instead.`);
|
|
239
|
-
return options.optional
|
|
240
|
-
? resolveLlmEngineUse(config, [{ engine: fallbackName }], { optional: true })
|
|
241
|
-
: resolveLlmEngineUse(config, [{ engine: fallbackName }]);
|
|
242
|
-
}
|
|
227
|
+
if (engine.kind !== "llm")
|
|
228
|
+
throw new ConfigError(`Engine "${name}" is not an LLM engine.`, "INVALID_CONFIG_FILE");
|
|
243
229
|
let connection = rawLlmConnection(engine);
|
|
244
230
|
for (const layer of layers) {
|
|
245
231
|
if (layer.llm)
|
|
@@ -288,6 +274,7 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
288
274
|
const platform = harness.id;
|
|
289
275
|
const sdk = platform === "opencode-sdk";
|
|
290
276
|
const builtin = getBuiltinAgentProfile(platform);
|
|
277
|
+
const { inference } = engineModelAndInference(engine);
|
|
291
278
|
const profile = {
|
|
292
279
|
name,
|
|
293
280
|
platform,
|
|
@@ -300,20 +287,18 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
300
287
|
parseOutput: "text",
|
|
301
288
|
...(engine.workspace ? { workspace: path.resolve(engine.workspace) } : {}),
|
|
302
289
|
...(engine.model ? { model: engine.model } : {}),
|
|
290
|
+
...(inference ? { inference } : {}),
|
|
303
291
|
};
|
|
292
|
+
// An engine that sets no timeoutMs leaves it unset, so a caller's own default
|
|
293
|
+
// (model work's 600 s) can apply; a dispatch with none runs unbounded.
|
|
304
294
|
const ownTimeout = Object.hasOwn(engine, "timeoutMs") ? (engine.timeoutMs ?? null) : undefined;
|
|
305
295
|
if (!sdk) {
|
|
306
|
-
return {
|
|
307
|
-
kind: "agent",
|
|
308
|
-
engine: name,
|
|
309
|
-
profile,
|
|
310
|
-
timeoutMs: ownTimeout !== undefined ? ownTimeout : DEFAULT_AGENT_TIMEOUT_MS,
|
|
311
|
-
};
|
|
296
|
+
return { kind: "agent", engine: name, profile, ...(ownTimeout !== undefined ? { timeoutMs: ownTimeout } : {}) };
|
|
312
297
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
298
|
+
// The fallback connection is the engine's own `llmEngine` and nothing else. `defaults.llmEngine`
|
|
299
|
+
// is the default engine for model work, not a connection every SDK engine borrows: with no
|
|
300
|
+
// `llmEngine`, opencode resolves provider, model and auth from its own configuration.
|
|
301
|
+
const fallback = engine.llmEngine ? resolveLlmEngineUse(config, [{ engine: engine.llmEngine }]) : undefined;
|
|
317
302
|
return {
|
|
318
303
|
kind: "sdk",
|
|
319
304
|
engine: name,
|
|
@@ -327,7 +312,7 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
327
312
|
fallbackTimeoutMs: fallback.timeoutMs,
|
|
328
313
|
}
|
|
329
314
|
: {}),
|
|
330
|
-
|
|
315
|
+
...(ownTimeout !== undefined ? { timeoutMs: ownTimeout } : fallback ? { timeoutMs: fallback.timeoutMs } : {}),
|
|
331
316
|
};
|
|
332
317
|
}
|
|
333
318
|
/** Resolve a configured engine name to its runner: an LLM connection, a spawned agent, or the SDK. */
|
|
@@ -6,9 +6,9 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
6
6
|
import { DURATION_UNITS, parseDuration } from "../../core/time.js";
|
|
7
7
|
import { EXECUTION_MAX_TIMEOUT_MS } from "../../execution/limits.js";
|
|
8
8
|
import { createInlineResolvedCommand, createResolvedExecutionRequest, decodeResolvedExecutionRequest, } from "../../execution/resolved-request.js";
|
|
9
|
-
import { cloneToolSelection, isPortableExecutionAgentSelector, } from "../../execution/source.js";
|
|
9
|
+
import { cloneToolSelection, isModelWorkTools, isPortableExecutionAgentSelector, } from "../../execution/source.js";
|
|
10
10
|
import { getHarness } from "../harnesses/index.js";
|
|
11
|
-
import {
|
|
11
|
+
import { DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
12
12
|
import { FALLBACK_ENGINE_NAME, fallbackEngineConfig, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, } from "./engine-fallback.js";
|
|
13
13
|
import { configuredEngine, resolveEngine } from "./engine-resolution.js";
|
|
14
14
|
import { engineModelAndInference, loadModelMap, resolveModelMapAlias } from "./model-map.js";
|
|
@@ -62,21 +62,14 @@ function engineDefaults(name, engine, config) {
|
|
|
62
62
|
if (engine.platform !== "opencode-sdk") {
|
|
63
63
|
return { kind: "agent", platform: engine.platform, modelMapKey: engine.platform, values };
|
|
64
64
|
}
|
|
65
|
-
// An SDK engine runs its
|
|
66
|
-
|
|
65
|
+
// An SDK engine runs its own `llmEngine`'s model/inference/timeout unless it sets its own. With no
|
|
66
|
+
// `llmEngine` it has no fallback, and opencode picks the model: `defaults.llmEngine` is not borrowed.
|
|
67
|
+
const fallbackName = engine.llmEngine;
|
|
67
68
|
const fallback = fallbackName && config.engines && Object.hasOwn(config.engines, fallbackName)
|
|
68
69
|
? config.engines[fallbackName]
|
|
69
70
|
: undefined;
|
|
70
71
|
if (fallback?.kind !== "llm" || !fallbackName) {
|
|
71
|
-
return {
|
|
72
|
-
kind: "sdk",
|
|
73
|
-
platform: "opencode-sdk",
|
|
74
|
-
modelMapKey: "opencode-sdk",
|
|
75
|
-
values: {
|
|
76
|
-
...values,
|
|
77
|
-
timeout: has(values, "timeout") ? values.timeout : DEFAULT_AGENT_TIMEOUT_MS,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
72
|
+
return { kind: "sdk", platform: "opencode-sdk", modelMapKey: "opencode-sdk", values };
|
|
80
73
|
}
|
|
81
74
|
const inherited = engineModelAndInference(fallback);
|
|
82
75
|
return {
|
|
@@ -85,8 +78,11 @@ function engineDefaults(name, engine, config) {
|
|
|
85
78
|
modelMapKey: own.model === undefined ? fallbackName : "opencode-sdk",
|
|
86
79
|
values: {
|
|
87
80
|
...(inherited.model !== undefined ? { model: inherited.model } : {}),
|
|
88
|
-
...(inherited.inference !== undefined ? { inference: inherited.inference } : {}),
|
|
89
81
|
...values,
|
|
82
|
+
// The engine's own inference is added over the fallback's, field by field.
|
|
83
|
+
...(inherited.inference !== undefined || own.inference !== undefined
|
|
84
|
+
? { inference: { ...inherited.inference, ...own.inference } }
|
|
85
|
+
: {}),
|
|
90
86
|
timeout: Object.hasOwn(engine, "timeoutMs")
|
|
91
87
|
? (engine.timeoutMs ?? null)
|
|
92
88
|
: Object.hasOwn(fallback, "timeoutMs")
|
|
@@ -114,7 +110,10 @@ function runnerDefaults(runner) {
|
|
|
114
110
|
const platform = runner.profile.platform ?? runner.profile.name;
|
|
115
111
|
const fallback = runner.kind === "sdk" ? runner.fallbackConnection : undefined;
|
|
116
112
|
const model = runner.profile.model ?? fallback?.model;
|
|
117
|
-
const
|
|
113
|
+
const fallbackInference = fallback ? inferenceOf(fallback) : undefined;
|
|
114
|
+
const inference = fallbackInference !== undefined || runner.profile.inference !== undefined
|
|
115
|
+
? { ...fallbackInference, ...runner.profile.inference }
|
|
116
|
+
: undefined;
|
|
118
117
|
return {
|
|
119
118
|
kind: runner.kind,
|
|
120
119
|
platform,
|
|
@@ -187,10 +186,17 @@ function requestedToolNames(tools) {
|
|
|
187
186
|
return undefined;
|
|
188
187
|
return Object.keys(policy).filter((tool) => policy[tool] === true);
|
|
189
188
|
}
|
|
190
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Assets may only narrow the host's `execution.allowedTools`; without a config
|
|
191
|
+
* nothing is allowed. The model-work policy is akm's own and always allowed:
|
|
192
|
+
* it confines an engine more tightly than leaving tools unset does.
|
|
193
|
+
*/
|
|
191
194
|
function authorizeTools(tools, config) {
|
|
192
195
|
if (!hasToolSelection(tools))
|
|
193
196
|
return { status: "not-required" };
|
|
197
|
+
if (isModelWorkTools(tools)) {
|
|
198
|
+
return { status: "allowed", reason: "The model-work tool policy is akm's own.", policy: { id: "model-work" } };
|
|
199
|
+
}
|
|
194
200
|
if (!config) {
|
|
195
201
|
return {
|
|
196
202
|
status: "denied",
|
|
@@ -239,14 +245,16 @@ function applyRequest(base, request) {
|
|
|
239
245
|
...timeout,
|
|
240
246
|
};
|
|
241
247
|
}
|
|
242
|
-
const { model: _model, workspace: _workspace, ...profile } = base.profile;
|
|
248
|
+
const { model: _model, workspace: _workspace, inference: ownInference, ...profile } = base.profile;
|
|
243
249
|
const workspace = request.runtime.workspace;
|
|
250
|
+
const inference = Object.hasOwn(request, "inference") ? request.inference : ownInference;
|
|
244
251
|
const next = {
|
|
245
252
|
...base,
|
|
246
253
|
profile: {
|
|
247
254
|
...profile,
|
|
248
255
|
...(model !== undefined ? { model } : {}),
|
|
249
256
|
...(typeof workspace === "string" ? { workspace } : {}),
|
|
257
|
+
...(inference ? { inference } : {}),
|
|
250
258
|
},
|
|
251
259
|
...timeout,
|
|
252
260
|
};
|
|
@@ -260,14 +268,29 @@ function applyRequest(base, request) {
|
|
|
260
268
|
}
|
|
261
269
|
return next;
|
|
262
270
|
}
|
|
263
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Reasoning effort has one word in a request: `reasoningEffort`, which is what
|
|
273
|
+
* engines, opencode and the LLM request body call it. `effort` is the same
|
|
274
|
+
* setting as a `models.json` alias or an asset's `effort:` frontmatter spells
|
|
275
|
+
* it, and becomes `reasoningEffort` here, in the one place every layer's
|
|
276
|
+
* inference is merged, so the nearest layer wins whichever word it used. When
|
|
277
|
+
* one inference object has both, `reasoningEffort` wins.
|
|
278
|
+
*/
|
|
279
|
+
function withReasoningEffort(inference) {
|
|
280
|
+
if (!Object.hasOwn(inference, "effort"))
|
|
281
|
+
return inference;
|
|
282
|
+
const { effort, ...rest } = inference;
|
|
283
|
+
return Object.hasOwn(rest, "reasoningEffort") ? rest : { ...rest, reasoningEffort: effort };
|
|
284
|
+
}
|
|
285
|
+
function mergeInference(current, layerInference, source, provenance) {
|
|
264
286
|
provenance["/inference"] = source;
|
|
265
|
-
if (
|
|
287
|
+
if (layerInference === null) {
|
|
266
288
|
for (const key of Object.keys(provenance))
|
|
267
289
|
if (key.startsWith("/inference/"))
|
|
268
290
|
delete provenance[key];
|
|
269
291
|
return null;
|
|
270
292
|
}
|
|
293
|
+
const next = withReasoningEffort(layerInference);
|
|
271
294
|
for (const key of Object.keys(next)) {
|
|
272
295
|
provenance[`/inference/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`] = source;
|
|
273
296
|
}
|
|
@@ -415,7 +438,8 @@ function buildLlm(request, runner, options) {
|
|
|
415
438
|
if (typeof request.agent === "string" && request.persona === null) {
|
|
416
439
|
throw new ConfigError(`The direct LLM transport cannot consume native agent selector ${JSON.stringify(request.agent)}.`, "INVALID_CONFIG_FILE");
|
|
417
440
|
}
|
|
418
|
-
|
|
441
|
+
// An LLM has no tools, which already meets the model-work policy.
|
|
442
|
+
if (hasToolSelection(request.tools) && !isModelWorkTools(request.tools)) {
|
|
419
443
|
throw new ConfigError("The direct LLM transport cannot enforce the resolved tool policy.", "INVALID_CONFIG_FILE");
|
|
420
444
|
}
|
|
421
445
|
const notices = [...request.notices];
|
|
@@ -427,8 +451,9 @@ function buildLlm(request, runner, options) {
|
|
|
427
451
|
skip(`inference.${key}`);
|
|
428
452
|
}
|
|
429
453
|
const chatOptions = {};
|
|
454
|
+
// Sent unless the engine opts out; chatCompletion drops it once if the provider rejects it.
|
|
430
455
|
if (request.outputSchema) {
|
|
431
|
-
if (runner.connection.supportsJsonSchema
|
|
456
|
+
if (runner.connection.supportsJsonSchema !== false)
|
|
432
457
|
chatOptions.responseSchema = request.outputSchema;
|
|
433
458
|
else
|
|
434
459
|
skip("outputSchema");
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
export { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
|
|
5
5
|
export { _setAgentDetectForTests, defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
|
|
6
6
|
export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
|
|
7
|
-
export { buildProposePrompt, buildReflectPrompt, buildSchemaRepairPrompt,
|
|
7
|
+
export { buildProposePrompt, buildReflectPrompt, buildSchemaRepairPrompt, parseAgentProposalPayload, } from "./prompts.js";
|