akm-cli 0.9.1 → 0.9.2-alpha.2

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.
Files changed (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -15,15 +15,16 @@
15
15
  * This module is intentionally tiny and stateless so tests can stub it via
16
16
  * `mock.module("../src/llm/graph-extract", ...)` without hitting a network.
17
17
  *
18
- * The LLM connection comes from the selected named engine. Callers obtain it
19
- * via `resolveIndexPassLLM("graph", config)` and pass it straight through.
18
+ * The symbolic LLM runner comes from the current index-pass execution
19
+ * resolution and is passed straight through.
20
20
  */
21
21
  import systemPromptTemplate from "../assets/prompts/graph-extract-system.md" with { type: "text" };
22
22
  import userPromptTemplate from "../assets/prompts/graph-extract-user-prompt.md" with { type: "text" };
23
23
  import { toErrorMessage } from "../core/common.js";
24
+ import { ConfigError } from "../core/errors.js";
24
25
  import { parseEmbeddedJsonResponse } from "../core/parse.js";
25
26
  import { warn, warnVerbose } from "../core/warn.js";
26
- import { chatCompletion, isContextSizeError } from "./client.js";
27
+ import { isContextSizeError } from "./client.js";
27
28
  import { tryLlmFeature } from "./feature-gate.js";
28
29
  import { callStructured } from "./structured-call.js";
29
30
  /**
@@ -447,8 +448,24 @@ function buildBatchUserPrompt(bodies) {
447
448
  `- The array MUST have exactly ${count} elements — one placeholder per asset even if empty.\n\n` +
448
449
  assetBlocks);
449
450
  }
450
- function formatContextHint(llmConfig) {
451
- return llmConfig.contextLength ? `, configured contextLength=${llmConfig.contextLength}` : "";
451
+ function formatContextHint(llmRunner) {
452
+ return llmRunner.connection.contextLength ? `, configured contextLength=${llmRunner.connection.contextLength}` : "";
453
+ }
454
+ /** Dispatch one raw graph prompt through the common resolved-request adapter. */
455
+ async function callGraphLlm(runner, messages, request, lease, onNotices) {
456
+ return callStructured({
457
+ feature: "graph_extraction",
458
+ runner,
459
+ ...(lease ? { lease } : {}),
460
+ messages,
461
+ request,
462
+ onNotices,
463
+ parse: (raw) => raw ?? "",
464
+ onError: (_cls, error) => {
465
+ throw error;
466
+ },
467
+ fallback: "",
468
+ });
452
469
  }
453
470
  /**
454
471
  * Parse and validate a single item from the batch response array.
@@ -457,6 +474,21 @@ function formatContextHint(llmConfig) {
457
474
  function parseBatchItem(raw) {
458
475
  return parseGraphExtraction(raw);
459
476
  }
477
+ function applySuccessfulBatchResults(results, batchResult, nonEmptyBodies, nonEmptyIndices, batchState) {
478
+ if (batchState)
479
+ batchState.nonArrayBatchFailures = 0;
480
+ if (batchResult.length > nonEmptyBodies.length) {
481
+ warn(`graph extraction (batch): response had ${batchResult.length} items for ${nonEmptyBodies.length} assets; ` +
482
+ `ignoring ${batchResult.length - nonEmptyBodies.length} extra item(s).`);
483
+ }
484
+ for (let j = 0; j < nonEmptyBodies.length; j++) {
485
+ const originalIndex = nonEmptyIndices[j];
486
+ if (originalIndex === undefined)
487
+ continue;
488
+ if (j < batchResult.length)
489
+ results[originalIndex] = parseBatchItem(batchResult[j]);
490
+ }
491
+ }
460
492
  /**
461
493
  * Extract entities and relations from multiple asset bodies in a single LLM
462
494
  * call (batched graph extraction).
@@ -474,13 +506,13 @@ function parseBatchItem(raw) {
474
506
  * Routes through `tryLlmFeature("graph_extraction", ...)` so the feature gate
475
507
  * and onFallback hook are honoured uniformly.
476
508
  *
477
- * @param llmConfig - LLM connection configuration.
509
+ * @param llmRunner - Symbolic LLM runner selected through shared execution lowering.
478
510
  * @param bodies - Asset body strings to process in one batch.
479
511
  * @param signal - Optional AbortSignal for cancellation.
480
512
  * @param akmConfig - Full AKM config (for feature-gate checks).
481
513
  * @param onFallback - Optional fallback event sink.
482
514
  */
483
- export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfig, onFallback, options = {}) {
515
+ export async function extractGraphFromBodies(llmRunner, bodies, signal, akmConfig, onFallback, options = {}) {
484
516
  const empty = () => ({ entities: [], relations: [] });
485
517
  const batchState = normalizeBatchState(options.batchState);
486
518
  // Degenerate case: no bodies → empty array (not an error).
@@ -488,7 +520,7 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
488
520
  return [];
489
521
  // Single body: delegate to the single-asset path for identical behaviour.
490
522
  if (bodies.length === 1) {
491
- const result = await extractGraphFromBody(llmConfig, bodies[0] ?? "", signal, akmConfig, onFallback, options);
523
+ const result = await extractGraphFromBody(llmRunner, bodies[0] ?? "", signal, akmConfig, onFallback, options);
492
524
  return [result];
493
525
  }
494
526
  // Filter out bodies that are empty so we don't waste tokens, but keep
@@ -511,13 +543,13 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
511
543
  }
512
544
  if (oversizedIndices.length > 0) {
513
545
  await Promise.all(oversizedIndices.map(async (index) => {
514
- results[index] = await extractGraphFromBody(llmConfig, bodies[index] ?? "", signal, akmConfig, onFallback, options);
546
+ results[index] = await extractGraphFromBody(llmRunner, bodies[index] ?? "", signal, akmConfig, onFallback, options);
515
547
  }));
516
548
  }
517
549
  if (nonEmptyBodies.length === 0)
518
550
  return results;
519
551
  if (batchState?.batchingDisabled) {
520
- return Promise.all(bodies.map((body) => extractGraphFromBody(llmConfig, body, signal, akmConfig, onFallback, options)));
552
+ return Promise.all(bodies.map((body) => extractGraphFromBody(llmRunner, body, signal, akmConfig, onFallback, options)));
521
553
  }
522
554
  const systemPrompt = buildBatchSystemPrompt();
523
555
  const userPrompt = buildBatchUserPrompt(nonEmptyBodies);
@@ -527,19 +559,19 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
527
559
  }
528
560
  let batchContextError = false;
529
561
  let nonArrayResponse = false;
530
- const batchResult = await tryLlmFeature("graph_extraction", akmConfig, async () => {
562
+ const batchOutcome = await tryLlmFeature("graph_extraction", akmConfig, async () => {
531
563
  try {
532
- const raw = await chatCompletion(llmConfig, [
564
+ const raw = await callGraphLlm(llmRunner, [
533
565
  { role: "system", content: systemPrompt },
534
566
  { role: "user", content: userPrompt },
535
567
  ], {
536
568
  temperature: 0.1,
537
- timeoutMs: llmConfig.timeoutMs,
569
+ timeoutMs: llmRunner.timeoutMs,
538
570
  signal,
539
571
  onRetryAttempt: () => bumpTelemetry(options.telemetry, "retryAttempts"),
540
- });
572
+ }, options.lease, options.onNotices);
541
573
  if (!raw)
542
- return null;
574
+ return { kind: "value", value: null };
543
575
  // Array-preferring salvage (#635): the batch contract is a top-level
544
576
  // JSON array. A leading/example `{…}` object in the response must not
545
577
  // mask a valid `[…]` array as a false "non-array" failure.
@@ -549,10 +581,10 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
549
581
  // (#635). Many genuine non-array responses recover when the model is
550
582
  // told explicitly to emit only the raw array.
551
583
  bumpTelemetry(options.telemetry, "retryAttempts");
552
- const retryRaw = await chatCompletion(llmConfig, [
584
+ const retryRaw = await callGraphLlm(llmRunner, [
553
585
  { role: "system", content: buildBatchRetrySystemPrompt() },
554
586
  { role: "user", content: userPrompt },
555
- ], { temperature: 0, timeoutMs: llmConfig.timeoutMs, signal });
587
+ ], { temperature: 0, timeoutMs: llmRunner.timeoutMs, signal }, options.lease, options.onNotices);
556
588
  parsed = retryRaw ? parseEmbeddedJsonResponse(retryRaw, { expect: "array" }) : undefined;
557
589
  }
558
590
  if (!Array.isArray(parsed)) {
@@ -566,51 +598,42 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
566
598
  }
567
599
  warn(`graph extraction (batch): LLM response was not a JSON array for ${nonEmptyBodies.length} asset(s) ` +
568
600
  `even after a stricter retry; will fall back per-asset. ` +
569
- `promptChars=${userPrompt.length}${formatContextHint(llmConfig)}`);
570
- return null;
601
+ `promptChars=${userPrompt.length}${formatContextHint(llmRunner)}`);
602
+ return { kind: "value", value: null };
571
603
  }
572
- return parsed;
604
+ return { kind: "value", value: parsed };
573
605
  }
574
606
  catch (err) {
607
+ if (err instanceof ConfigError)
608
+ return { kind: "config-error", error: err };
575
609
  const errMsg = toErrorMessage(err);
576
610
  if (isContextSizeError(errMsg)) {
577
611
  batchContextError = true;
578
612
  bumpTelemetry(options.telemetry, "contextBatchRetries");
579
613
  warn(`graph extraction (batch): context size exceeded for ${nonEmptyBodies.length} asset(s); ` +
580
- `skipping batch. promptChars=${userPrompt.length}${formatContextHint(llmConfig)}`);
614
+ `skipping batch. promptChars=${userPrompt.length}${formatContextHint(llmRunner)}`);
581
615
  }
582
616
  else {
583
617
  warn(`graph extraction (batch) failed for ${nonEmptyBodies.length} asset(s); ` +
584
- `promptChars=${userPrompt.length}${formatContextHint(llmConfig)}: ${errMsg}`);
618
+ `promptChars=${userPrompt.length}${formatContextHint(llmRunner)}: ${errMsg}`);
585
619
  }
586
- return null;
620
+ return { kind: "value", value: null };
587
621
  }
588
- }, null, {
589
- timeoutMs: llmConfig.timeoutMs,
622
+ }, { kind: "value", value: null }, {
623
+ timeoutMs: llmRunner.timeoutMs,
590
624
  onFallback,
591
625
  });
626
+ if (batchOutcome.kind === "config-error")
627
+ throw batchOutcome.error;
628
+ const batchResult = batchOutcome.value;
592
629
  // Map successful batch results back to their original indices.
593
630
  if (batchResult !== null) {
594
- if (batchState)
595
- batchState.nonArrayBatchFailures = 0;
596
- if (batchResult.length > nonEmptyBodies.length) {
597
- warn(`graph extraction (batch): response had ${batchResult.length} items for ${nonEmptyBodies.length} assets; ` +
598
- `ignoring ${batchResult.length - nonEmptyBodies.length} extra item(s).`);
599
- }
600
- for (let j = 0; j < nonEmptyBodies.length; j++) {
601
- const originalIndex = nonEmptyIndices[j];
602
- if (originalIndex === undefined)
603
- continue;
604
- if (j < batchResult.length) {
605
- results[originalIndex] = parseBatchItem(batchResult[j]);
606
- }
607
- // j >= batchResult.length → partial failure; handled below.
608
- }
631
+ applySuccessfulBatchResults(results, batchResult, nonEmptyBodies, nonEmptyIndices, batchState);
609
632
  }
610
633
  if (batchContextError && nonEmptyBodies.length > 1) {
611
634
  const splitAt = Math.ceil(nonEmptyBodies.length / 2);
612
- const left = await extractGraphFromBodies(llmConfig, nonEmptyBodies.slice(0, splitAt), signal, akmConfig, onFallback, options);
613
- const right = await extractGraphFromBodies(llmConfig, nonEmptyBodies.slice(splitAt), signal, akmConfig, onFallback, options);
635
+ const left = await extractGraphFromBodies(llmRunner, nonEmptyBodies.slice(0, splitAt), signal, akmConfig, onFallback, options);
636
+ const right = await extractGraphFromBodies(llmRunner, nonEmptyBodies.slice(splitAt), signal, akmConfig, onFallback, options);
614
637
  const combined = [...left, ...right];
615
638
  for (let j = 0; j < nonEmptyIndices.length; j++) {
616
639
  const origIdx = nonEmptyIndices[j];
@@ -642,7 +665,7 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
642
665
  }
643
666
  await Promise.all(fallbackIndices.map(async (origIdx) => {
644
667
  const body = bodies[origIdx] ?? "";
645
- results[origIdx] = await extractGraphFromBody(llmConfig, body, signal, akmConfig, onFallback, options);
668
+ results[origIdx] = await extractGraphFromBody(llmRunner, body, signal, akmConfig, onFallback, options);
646
669
  }));
647
670
  }
648
671
  else if (batchContextError) {
@@ -664,7 +687,7 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
664
687
  * Routes through `tryLlmFeature("graph_extraction", ...)` so the feature gate
665
688
  * and onFallback hook are honoured uniformly (Fix C5).
666
689
  */
667
- export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, onFallback, options = {}) {
690
+ export async function extractGraphFromBody(llmRunner, body, signal, akmConfig, onFallback, options = {}) {
668
691
  const empty = (reason, status) => ({
669
692
  entities: [],
670
693
  relations: [],
@@ -682,7 +705,7 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
682
705
  if (chunked.chunks.length > 1) {
683
706
  const chunkResults = [];
684
707
  for (const chunk of chunked.chunks) {
685
- chunkResults.push(await extractGraphFromBody(llmConfig, chunk, signal, akmConfig, onFallback, options));
708
+ chunkResults.push(await extractGraphFromBody(llmRunner, chunk, signal, akmConfig, onFallback, options));
686
709
  }
687
710
  const merged = mergeGraphExtractions(chunkResults);
688
711
  merged.truncationCount = (merged.truncationCount ?? 0) + chunked.truncationCount;
@@ -692,17 +715,19 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
692
715
  return callStructured({
693
716
  feature: "graph_extraction",
694
717
  akmConfig,
695
- config: llmConfig,
718
+ runner: llmRunner,
719
+ ...(options.lease ? { lease: options.lease } : {}),
696
720
  messages: [
697
721
  { role: "system", content: SYSTEM_PROMPT },
698
722
  { role: "user", content: userPrompt },
699
723
  ],
700
724
  request: {
701
725
  temperature: 0.1,
702
- timeoutMs: llmConfig.timeoutMs,
726
+ timeoutMs: llmRunner.timeoutMs,
703
727
  signal,
704
728
  onRetryAttempt: () => bumpTelemetry(options.telemetry, "retryAttempts"),
705
729
  },
730
+ onNotices: options.onNotices,
706
731
  parse: (raw) => {
707
732
  if (!raw)
708
733
  return empty();
@@ -724,18 +749,18 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
724
749
  const errMsg = toErrorMessage(err);
725
750
  if (cls === "context_limit") {
726
751
  bumpTelemetry(options.telemetry, "failureCount");
727
- warn(`graph extraction: context size exceeded for asset; promptChars=${userPrompt.length}${formatContextHint(llmConfig)}. ` +
752
+ warn(`graph extraction: context size exceeded for asset; promptChars=${userPrompt.length}${formatContextHint(llmRunner)}. ` +
728
753
  `Consider increasing llm.contextLength in config.json.`);
729
754
  return empty("context_limit", "failed");
730
755
  }
731
756
  else if (cls === "html") {
732
757
  bumpTelemetry(options.telemetry, "htmlErrorCount");
733
- warn(`graph extraction: provider returned HTML instead of JSON for asset; promptChars=${userPrompt.length}${formatContextHint(llmConfig)}: ${errMsg}`);
758
+ warn(`graph extraction: provider returned HTML instead of JSON for asset; promptChars=${userPrompt.length}${formatContextHint(llmRunner)}: ${errMsg}`);
734
759
  return empty("llm_error", "failed");
735
760
  }
736
761
  else {
737
762
  bumpTelemetry(options.telemetry, "failureCount");
738
- warn(`graph extraction failed for asset; promptChars=${userPrompt.length}${formatContextHint(llmConfig)}: ${errMsg}`);
763
+ warn(`graph extraction failed for asset; promptChars=${userPrompt.length}${formatContextHint(llmRunner)}: ${errMsg}`);
739
764
  return empty("llm_error", "failed");
740
765
  }
741
766
  },
@@ -1,16 +1,54 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { materializeLlmConnection, resolveLlmEngineUse } from "../integrations/agent/engine-resolution.js";
4
+ import { ConfigError } from "../core/errors.js";
5
+ import { cloneExecutionJsonObject } from "../execution/json.js";
6
+ import { lowerResolvedExecutionRequest } from "../integrations/agent/execution-lowering.js";
7
+ import { prepareInlineExecution } from "../integrations/agent/inline-execution.js";
8
+ const NO_LOWERING_NOTICES = Object.freeze([]);
9
+ function own(value, key) {
10
+ return value !== undefined && Object.hasOwn(value, key);
11
+ }
12
+ /** Adapt one index invocation layer into the shared execution vocabulary. */
13
+ function indexExecutionDefaults(layer) {
14
+ if (!layer)
15
+ return {};
16
+ return {
17
+ ...(own(layer, "engine") ? { engine: layer.engine } : {}),
18
+ ...(own(layer, "model") ? { model: layer.model } : {}),
19
+ ...(own(layer, "timeoutMs") ? { timeout: layer.timeoutMs } : {}),
20
+ ...(own(layer, "llm") && layer.llm !== undefined
21
+ ? { inference: cloneExecutionJsonObject(layer.llm, "index pass LLM inference") }
22
+ : {}),
23
+ };
24
+ }
5
25
  /**
6
26
  * Resolve standalone index passes from the index section only. Improve
7
27
  * strategies own improve-triggered calls and are intentionally not consulted.
8
28
  */
9
- export function resolveIndexPassLLM(passName, config) {
29
+ export function resolveIndexPassExecution(passName, config) {
10
30
  const pass = config.index?.[passName];
11
31
  if (pass?.enabled === false)
12
- return undefined;
32
+ return Object.freeze({ runner: undefined, notices: NO_LOWERING_NOTICES });
13
33
  const defaults = config.index?.defaults;
14
- const resolved = resolveLlmEngineUse(config, [defaults ?? {}, pass ?? {}], { optional: true });
15
- return resolved ? materializeLlmConnection(resolved) : undefined;
34
+ const fallbackLlmEngine = config.defaults?.llmEngine;
35
+ const selectedEngine = pass?.engine ?? defaults?.engine ?? fallbackLlmEngine;
36
+ if (!selectedEngine)
37
+ return Object.freeze({ runner: undefined, notices: NO_LOWERING_NOTICES });
38
+ const invocationDefaults = {
39
+ ...indexExecutionDefaults(defaults),
40
+ ...(!own(defaults, "engine") && fallbackLlmEngine ? { engine: fallbackLlmEngine } : {}),
41
+ };
42
+ const prepared = prepareInlineExecution({
43
+ content: "",
44
+ config,
45
+ invocationKind: "direct",
46
+ invocationDefaults,
47
+ current: indexExecutionDefaults(pass),
48
+ });
49
+ const lowered = lowerResolvedExecutionRequest(prepared.request, prepared.config);
50
+ if (lowered.runner.kind !== "llm") {
51
+ throw new ConfigError(`Index pass ${JSON.stringify(passName)} requires an LLM engine; ${JSON.stringify(selectedEngine)} is not one.`, "INVALID_CONFIG_FILE");
52
+ }
53
+ return Object.freeze({ runner: lowered.runner, notices: lowered.notices });
16
54
  }
@@ -13,8 +13,8 @@
13
13
  * This module is intentionally tiny and stateless so tests can stub it via
14
14
  * `mock.module("../src/llm/memory-infer", ...)` without hitting a network.
15
15
  *
16
- * The LLM connection comes from the selected named engine. Callers obtain it
17
- * via `resolveIndexPassLLM("memory", config)` and pass it straight through.
16
+ * The symbolic LLM runner comes from the current index-pass execution
17
+ * resolution and is passed straight through.
18
18
  */
19
19
  import memoryInferSystemPrompt from "../assets/prompts/memory-infer-system.md" with { type: "text" };
20
20
  import memoryInferUserPrompt from "../assets/prompts/memory-infer-user.md" with { type: "text" };
@@ -37,7 +37,7 @@ const PROMPT_PLACEHOLDERS = new Set([
37
37
  ]);
38
38
  /**
39
39
  * Strict JSON Schema for the derived-memory payload. Sent to providers that
40
- * opt in via `ChatCompletionConfig.supportsJsonSchema = true`; the client
40
+ * opt in via `runner.connection.supportsJsonSchema = true`; the client
41
41
  * silently drops the schema for providers that don't.
42
42
  *
43
43
  * Extends the responseSchema lift (PR 1, asset-writers-investigation §5) to
@@ -70,7 +70,7 @@ const DERIVED_MEMORY_JSON_SCHEMA = {
70
70
  * feature gate, error classification, and onFallback hook are honoured uniformly
71
71
  * (Fix C5).
72
72
  */
73
- export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akmConfig, onFallback, telemetry, onRetryAttempt) {
73
+ export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akmConfig, onFallback, telemetry, onRetryAttempt, onNotices, lease) {
74
74
  const trimmedBody = body.trim();
75
75
  if (!trimmedBody)
76
76
  return undefined;
@@ -84,18 +84,20 @@ export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akm
84
84
  return callStructured({
85
85
  feature: "memory_inference",
86
86
  akmConfig,
87
- config: llmConfig,
87
+ runner: llmRunner,
88
+ ...(lease ? { lease } : {}),
88
89
  messages: [
89
90
  { role: "system", content: SYSTEM_PROMPT },
90
91
  { role: "user", content: userPrompt },
91
92
  ],
92
93
  request: {
93
94
  temperature: 0.1,
94
- timeoutMs: llmConfig.timeoutMs,
95
+ timeoutMs: llmRunner.timeoutMs,
95
96
  signal,
96
97
  responseSchema: DERIVED_MEMORY_JSON_SCHEMA,
97
98
  onRetryAttempt,
98
99
  },
100
+ onNotices,
99
101
  parse: (raw) => {
100
102
  if (!raw)
101
103
  return undefined;
@@ -22,7 +22,7 @@ const SYSTEM_PROMPT = metadataEnhanceSystemPrompt;
22
22
  * `akmConfig` is `undefined` the gate is bypassed entirely: the LLM call runs
23
23
  * unconditionally and errors propagate to direct callers such as tests.
24
24
  */
25
- export async function enhanceMetadata(config, entry, fileContent, signal, akmConfig) {
25
+ export async function enhanceMetadata(runner, entry, fileContent, signal, akmConfig, onNotices, lease) {
26
26
  const contextParts = [`Name: ${entry.name}`, `Type: ${entry.type}`];
27
27
  if (entry.description)
28
28
  contextParts.push(`Current description: ${entry.description}`);
@@ -51,12 +51,14 @@ Return ONLY the JSON object, no explanation.`;
51
51
  const outcome = await callStructured({
52
52
  feature: "metadata_enhance",
53
53
  akmConfig,
54
- config,
54
+ runner,
55
+ ...(lease ? { lease } : {}),
55
56
  messages: [
56
57
  { role: "system", content: SYSTEM_PROMPT },
57
58
  { role: "user", content: userPrompt },
58
59
  ],
59
- request: { signal, timeoutMs: config.timeoutMs },
60
+ request: { signal, timeoutMs: runner.timeoutMs },
61
+ onNotices,
60
62
  parse: (raw) => {
61
63
  const parsed = raw ? parseJsonResponse(raw) : undefined;
62
64
  const metadata = {};
@@ -1,8 +1,11 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { chatCompletion, isContextSizeError, LlmCallError, } from "./client.js";
5
- import { tryLlmFeature } from "./feature-gate.js";
4
+ import { ConfigError } from "../core/errors.js";
5
+ import { acquireLoweredExecutionDispatchLease, dispatchLoweredExecutionRequest, lowerResolvedExecutionRequestWithRunner, } from "../integrations/agent/execution-lowering.js";
6
+ import { prepareInlineExecutionWithRunner } from "../integrations/agent/inline-execution.js";
7
+ import { isContextSizeError, LlmCallError } from "./client.js";
8
+ import { isLlmFeatureEnabled, tryLlmFeature, } from "./feature-gate.js";
6
9
  /**
7
10
  * Classify a thrown LLM error into one of the three buckets. This is the single
8
11
  * home for the `isContextSizeError -> html -> other` ladder that was previously
@@ -16,42 +19,136 @@ export function classifyLlmError(err) {
16
19
  return "html";
17
20
  return "other";
18
21
  }
22
+ function own(value, key) {
23
+ return value !== undefined && Object.hasOwn(value, key);
24
+ }
25
+ /** @internal Exact request-to-cascade projection, exported for presence-semantics contracts. */
26
+ export function resolveStructuredCurrent(current, request) {
27
+ const out = current ? { ...current } : {};
28
+ const requestHasInference = own(request, "temperature") || own(request, "maxTokens") || own(request, "enableThinking");
29
+ const baseInference = current?.inference && typeof current.inference === "object" && !Array.isArray(current.inference)
30
+ ? { ...current.inference }
31
+ : {};
32
+ const inference = { ...baseInference };
33
+ if (own(request, "temperature") && request?.temperature !== undefined)
34
+ inference.temperature = request.temperature;
35
+ if (own(request, "maxTokens") && request?.maxTokens !== undefined)
36
+ inference.maxTokens = request.maxTokens;
37
+ if (own(request, "enableThinking") && request?.enableThinking !== undefined) {
38
+ inference.enableThinking = request.enableThinking;
39
+ }
40
+ if (Object.keys(inference).length > 0 || requestHasInference)
41
+ out.inference = inference;
42
+ else if (current?.inference === null)
43
+ out.inference = null;
44
+ if (own(request, "responseSchema") && request?.responseSchema !== undefined) {
45
+ out.outputSchema = request.responseSchema;
46
+ }
47
+ if (own(request, "timeoutMs"))
48
+ out.timeout = request?.timeoutMs ?? null;
49
+ return Object.keys(out).length > 0 ? out : undefined;
50
+ }
51
+ function requireTerminalUserMessage(messages) {
52
+ const terminal = messages.at(-1);
53
+ if (!terminal || terminal.role !== "user") {
54
+ throw new TypeError("callStructured messages must end with the terminal user command");
55
+ }
56
+ return {
57
+ content: terminal.content,
58
+ conversation: messages.slice(0, -1).map((message) => ({ role: message.role, content: message.content })),
59
+ };
60
+ }
61
+ function dispatchFailure(result) {
62
+ const message = result.error ?? result.stderr ?? result.reason ?? "LLM dispatch failed";
63
+ return result.llmErrorCode ? new LlmCallError(message, result.llmErrorCode) : new Error(message);
64
+ }
65
+ /**
66
+ * Validate one already-selected symbolic LLM runner before an operation makes
67
+ * any durable mutation. Callers must apply their feature/authorization gates
68
+ * first. Acquisition crosses the canonical prepare -> lower -> lease boundary,
69
+ * so required credentials are snapshotted by the same central authority as a
70
+ * real call without contacting the provider.
71
+ */
72
+ export async function preflightStructuredLlmRunner(runner) {
73
+ const prepared = prepareInlineExecutionWithRunner({
74
+ content: "Validate the selected LLM runner before operation dispatch.",
75
+ runner,
76
+ invocationKind: "direct",
77
+ });
78
+ const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
79
+ return acquireLoweredExecutionDispatchLease(lowered);
80
+ }
19
81
  export async function callStructured(opts) {
20
- const { feature, akmConfig, enabled, config, messages, request, parse, onError, fallback, onFallback } = opts;
21
- const chat = request?.chat ?? chatCompletion;
22
- // Forward each option key ONLY when present on `request` — see the
23
- // key-presence semantics note on CallStructuredRequest: for `timeoutMs`,
24
- // present-but-undefined means "explicitly disabled" while absent means
25
- // "use the default", both in the feature-gate wrapper and the transport.
26
- const has = (key) => request !== undefined && Object.hasOwn(request, key);
27
- const chatOptions = {
28
- ...(has("temperature") ? { temperature: request?.temperature } : {}),
29
- ...(has("timeoutMs") ? { timeoutMs: request?.timeoutMs } : {}),
30
- ...(has("signal") ? { signal: request?.signal } : {}),
31
- ...(has("responseSchema") ? { responseSchema: request?.responseSchema } : {}),
32
- ...(has("maxTokens") ? { maxTokens: request?.maxTokens } : {}),
33
- ...(has("enableThinking") ? { enableThinking: request?.enableThinking } : {}),
34
- ...(has("onRetryAttempt") ? { onRetryAttempt: request?.onRetryAttempt } : {}),
82
+ const { feature, akmConfig, enabled, messages, request, parse, onError, fallback, onFallback } = opts;
83
+ // A disabled feature owns a true no-work path: it does not need a runner,
84
+ // messages, authorization, lowering, or provider state. Some commands keep
85
+ // their runner optional precisely because a disabled feature must fall back
86
+ // before execution planning begins.
87
+ if (akmConfig !== undefined && !isLlmFeatureEnabled(akmConfig, feature, enabled)) {
88
+ return tryLlmFeature(feature, akmConfig, async () => fallback, fallback, {
89
+ ...(own(request, "timeoutMs") ? { timeoutMs: request?.timeoutMs } : {}),
90
+ ...(enabled !== undefined ? { enabled } : {}),
91
+ onFallback,
92
+ });
93
+ }
94
+ const runner = opts.runner;
95
+ if (!runner)
96
+ throw new TypeError("callStructured requires a resolved LLM runner");
97
+ const terminal = requireTerminalUserMessage(messages);
98
+ const prepareInvocation = () => {
99
+ const current = resolveStructuredCurrent(opts.current, request);
100
+ const prepared = prepareInlineExecutionWithRunner({
101
+ content: terminal.content,
102
+ conversation: terminal.conversation,
103
+ runner,
104
+ invocationKind: "direct",
105
+ ...(current ? { current } : {}),
106
+ ...(opts.authorizeTools ? { authorizeTools: opts.authorizeTools } : {}),
107
+ });
108
+ const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
109
+ opts.onNotices?.(lowered.notices);
110
+ return async () => {
111
+ const result = await dispatchLoweredExecutionRequest(lowered, {
112
+ ...(opts.lease ? { lease: opts.lease } : {}),
113
+ ...(request?.chat ? { chat: request.chat } : {}),
114
+ ...(request?.onRetryAttempt ? { onRetryAttempt: request.onRetryAttempt } : {}),
115
+ ...(own(request, "signal") ? { runOptions: { signal: request?.signal } } : {}),
116
+ });
117
+ if (!result.ok)
118
+ throw dispatchFailure(result);
119
+ return parse(result.stdout);
120
+ };
35
121
  };
36
122
  // UNGATED: run the chat+parse directly. Errors propagate — no `onError`
37
123
  // funnel — matching the pre-gate behaviour of direct callers.
38
124
  if (akmConfig === undefined) {
39
- const raw = await chat(config, messages, chatOptions);
40
- return parse(raw);
125
+ return prepareInvocation()();
41
126
  }
127
+ // On an enabled path, preparation/lowering happen OUTSIDE tryLlmFeature so
128
+ // authorization and invalid-config failures remain hard failures instead of
129
+ // being mistaken for provider fallbacks.
130
+ const invoke = prepareInvocation();
42
131
  // GATED: run through `tryLlmFeature`. A throw inside is classified ONCE and
43
132
  // routed to `onError`; `tryLlmFeature` returns `fallback` on disablement/timeout.
44
- return tryLlmFeature(feature, akmConfig, async () => {
133
+ const outcome = await tryLlmFeature(feature, akmConfig, async () => {
45
134
  try {
46
- const raw = await chat(config, messages, chatOptions);
47
- return parse(raw);
135
+ return { kind: "value", value: await invoke() };
48
136
  }
49
137
  catch (err) {
50
- return onError(classifyLlmError(err), err);
138
+ // Credential materialization remains dispatch-owned, so a missing
139
+ // required symbolic credential can surface here. Preserve config
140
+ // failures as hard pre-provider errors instead of sending them through
141
+ // a leaf's provider/runtime fallback policy.
142
+ if (err instanceof ConfigError)
143
+ return { kind: "config-error", error: err };
144
+ return { kind: "value", value: onError(classifyLlmError(err), err) };
51
145
  }
52
- }, fallback, {
53
- ...(has("timeoutMs") ? { timeoutMs: request?.timeoutMs } : {}),
146
+ }, { kind: "value", value: fallback }, {
147
+ ...(own(request, "timeoutMs") ? { timeoutMs: request?.timeoutMs } : {}),
54
148
  ...(enabled !== undefined ? { enabled } : {}),
55
149
  onFallback,
56
150
  });
151
+ if (outcome.kind === "config-error")
152
+ throw outcome.error;
153
+ return outcome.value;
57
154
  }
@@ -25,7 +25,7 @@ const EXEMPT_COMMANDS = new Set([
25
25
  // Emits shell completion script source for eval.
26
26
  "completions",
27
27
  // `migrate status`/`apply` used to be exempt here too: `runMigrationTool`
28
- // (src/commands/migration-tool.ts) spawns the standalone
28
+ // (src/commands/migration-tool.ts) spawns the task-only standalone
29
29
  // `scripts/akm-migrate.ts` tool, which always emitted its own fixed JSON
30
30
  // shape and never consulted `--format`. `src/commands/migrate-cli.ts` now
31
31
  // parses that child's final result line and renders it through the normal
@@ -27,31 +27,15 @@ const HTML_RENDERER_REGISTRY = createCommandRegistry();
27
27
  export function registerMdRenderer(command, handler) {
28
28
  MD_RENDERER_REGISTRY.register(command, handler);
29
29
  }
30
- /** Register a batch of Markdown renderers in iteration order. */
31
- export function registerMdRenderers(entries) {
32
- MD_RENDERER_REGISTRY.registerAll(entries);
33
- }
34
30
  /** Look up a registered Markdown renderer, or `undefined` when unregistered. */
35
31
  export function getMdRendererHandler(command) {
36
32
  return MD_RENDERER_REGISTRY.get(command);
37
33
  }
38
- /** Remove a previously-registered Markdown renderer. Test-only utility. */
39
- export function deregisterMdRenderer(command) {
40
- MD_RENDERER_REGISTRY.deregister(command);
41
- }
42
34
  /** Register an HTML renderer for a command name. */
43
35
  export function registerHtmlRenderer(command, handler) {
44
36
  HTML_RENDERER_REGISTRY.register(command, handler);
45
37
  }
46
- /** Register a batch of HTML renderers in iteration order. */
47
- export function registerHtmlRenderers(entries) {
48
- HTML_RENDERER_REGISTRY.registerAll(entries);
49
- }
50
38
  /** Look up a registered HTML renderer, or `undefined` when unregistered. */
51
39
  export function getHtmlRendererHandler(command) {
52
40
  return HTML_RENDERER_REGISTRY.get(command);
53
41
  }
54
- /** Remove a previously-registered HTML renderer. Test-only utility. */
55
- export function deregisterHtmlRenderer(command) {
56
- HTML_RENDERER_REGISTRY.deregister(command);
57
- }