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
@@ -1,30 +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 path from "node:path";
5
4
  import { isContainedRelativePath } from "../../core/common.js";
6
5
  import { UsageError } from "../../core/errors.js";
7
- import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
8
- import { HARNESS_BY_ID } from "../../integrations/harnesses/index.js";
9
6
  import { parseReference } from "../program/expressions.js";
10
7
  import { PROGRAM_PARAM_NAME_PATTERN, PROGRAM_RETRY_REASONS, PROGRAM_STEP_ID_PATTERN } from "../program/schema.js";
11
- import { jsonBytes, utf8Bytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_ENGINES, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_INSTRUCTION_BYTES, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_PLAN_BYTES, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_STEPS, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
12
- /** The only executable persisted workflow plan format. */
13
- export const WORKFLOW_IR_VERSION = 3;
14
- /**
15
- * The {@link IrRuntimeKind} a frozen engine dispatches as — the value journaled
16
- * in `workflow_run_units.runner` and read back by `status --units`.
17
- *
18
- * Lives here, beside the union, because the mapping is a property OF the union:
19
- * spelled at each call site instead, a kind added to `FrozenEngineSnapshot`
20
- * would type-check against whichever site happens to end with a default and be
21
- * silently mis-journaled. Callers supply their own answer for the ABSENCE of an
22
- * engine, which genuinely differs — a unit without one is an `exec` unit, while
23
- * a gate without one is an llm judge.
24
- */
25
- export function engineRuntimeKind(engine) {
26
- return engine.kind === "llm" ? "llm" : engine.runnerKind;
27
- }
8
+ import { jsonBytes, utf8Bytes, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_INSTRUCTION_BYTES, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_PLAN_BYTES, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_STEPS, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
28
9
  // Shared dispatch-significant bounds now live in `../resource-limits` so the
29
10
  // parser, the published JSON Schema, and this decoder enforce identical
30
11
  // values. Re-exported here for existing importers (e.g. `commands/workflow-cli.ts`).
@@ -32,16 +13,16 @@ export { WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_RETRIES
32
13
  export const WORKFLOW_MAX_UNITS = WORKFLOW_MAX_MAP_EXPANSION;
33
14
  const MAX_LIST_ITEMS = 1024;
34
15
  const MAX_STRING_LENGTH = 1_000_000;
35
- const ENGINE_NAME_PATTERN = WORKFLOW_ENGINE_NAME_PATTERN;
36
- /** Strictly decode persisted v3 data before it can drive a workflow. */
37
- export function decodeWorkflowPlanV3(input, hooks = {}) {
38
- if (!isRecord(input) || input.irVersion !== WORKFLOW_IR_VERSION)
39
- fail("irVersion must be 3");
16
+ /** Validate the current durable execution graph. */
17
+ export function validateWorkflowPlanStructure(input, options, hooks = {}) {
18
+ if (!isRecord(input) || input.irVersion !== options.expectedVersion) {
19
+ fail(`irVersion must be ${options.expectedVersion}`);
20
+ }
40
21
  assertJson(input);
41
22
  if (jsonBytes(input) > WORKFLOW_MAX_PLAN_BYTES)
42
23
  fail("plan exceeds the 2 MiB resource limit");
43
24
  const plan = input;
44
- assertKeys(input, ["irVersion", "title", "params", "paramSchemas", "budget", "execution", "steps"], "plan");
25
+ assertKeys(input, ["irVersion", "title", "params", "paramSchemas", "budget", "execution", "steps", ...(options.planExtraKeys ?? [])], "plan");
45
26
  assertString(plan.title, "title");
46
27
  validateParams(plan.params, plan.paramSchemas);
47
28
  validateBudget(plan.budget);
@@ -51,15 +32,7 @@ export function decodeWorkflowPlanV3(input, hooks = {}) {
51
32
  plan.execution.maxConcurrency > WORKFLOW_MAX_CONCURRENCY) {
52
33
  fail(`execution.maxConcurrency must be an integer from 1 through ${WORKFLOW_MAX_CONCURRENCY}`);
53
34
  }
54
- assertKeys(plan.execution, ["maxConcurrency", "engines"], "execution");
55
- if (!isRecord(plan.execution.engines))
56
- fail("execution.engines must be an object");
57
- const engines = plan.execution.engines;
58
- if (Object.keys(engines).length > WORKFLOW_MAX_ENGINES)
59
- fail(`execution.engines exceeds ${WORKFLOW_MAX_ENGINES} entries`);
60
- const references = new Set();
61
- for (const [key, engine] of Object.entries(engines))
62
- validateEngine(key, engine, references, hooks);
35
+ assertKeys(plan.execution, ["maxConcurrency"], "execution");
63
36
  if (!Array.isArray(plan.steps) || plan.steps.length === 0 || plan.steps.length > WORKFLOW_MAX_STEPS)
64
37
  fail("steps must contain 1 through 256 entries");
65
38
  const stepIds = new Set();
@@ -81,10 +54,10 @@ export function decodeWorkflowPlanV3(input, hooks = {}) {
81
54
  if (step.outputSchema !== undefined)
82
55
  validateSchema(step.outputSchema, `step ${step.stepId} outputSchema`);
83
56
  if (step.root)
84
- validateNode(step.root, step.stepId, references, nodeIds, hooks);
57
+ validateNode(step.root, step.stepId, nodeIds, options.unitExtraKeys ?? []);
85
58
  if (step.route)
86
59
  validateRoute(step.route, step.stepId);
87
- validateGate(step.gate, step.stepId, references, nodeIds, hooks);
60
+ validateGate(step.gate, step.stepId, nodeIds, options.gateExtraKeys ?? []);
88
61
  }
89
62
  const stepIndex = new Map(plan.steps.map((step, index) => [step.stepId, index]));
90
63
  for (const [index, step] of plan.steps.entries()) {
@@ -99,136 +72,8 @@ export function decodeWorkflowPlanV3(input, hooks = {}) {
99
72
  }
100
73
  validateStepExpressions(step, index, stepIndex);
101
74
  }
102
- for (const name of references) {
103
- if (!engines[name])
104
- fail(`engine reference ${name} is not in execution.engines`);
105
- }
106
- for (const name of Object.keys(engines))
107
- if (!references.has(name))
108
- fail(`engine ${name} is not referenced`);
109
- for (const engine of Object.values(engines)) {
110
- if (engine.kind !== "agent")
111
- continue;
112
- if (engine.fallbackLlmEngine !== null && engines[engine.fallbackLlmEngine]?.kind !== "llm")
113
- fail(`SDK engine ${engine.name} fallback must name an LLM engine`);
114
- }
115
- for (const step of plan.steps) {
116
- if (step.root)
117
- assertUnitEngineCompatibility(step.root, engines);
118
- if (step.gate.judge) {
119
- const judge = engines[step.gate.judge.engine];
120
- if (!judge)
121
- fail(`gate ${step.gate.id} references an unavailable engine`);
122
- if (judge.kind === "llm" && step.gate.judge.model === null)
123
- fail(`LLM gate ${step.gate.id} has no exact model`);
124
- if (judge.kind === "agent" && step.gate.judge.llm !== undefined)
125
- fail(`agent gate ${step.gate.id} cannot carry LLM invocation settings`);
126
- }
127
- }
128
- return plan;
129
75
  }
130
- function validateEngine(key, engine, references, hooks) {
131
- if (!ENGINE_NAME_PATTERN.test(key) ||
132
- key.length > WORKFLOW_MAX_ENGINE_NAME_LENGTH ||
133
- !isRecord(engine) ||
134
- engine.name !== key ||
135
- (engine.kind !== "llm" && engine.kind !== "agent"))
136
- fail("catalog keys must equal a valid snapshot name");
137
- if (engine.kind === "llm") {
138
- assertKeys(engine, [
139
- "name",
140
- "kind",
141
- "provider",
142
- "endpoint",
143
- "model",
144
- "credential",
145
- "temperature",
146
- "maxTokens",
147
- "concurrency",
148
- "supportsJsonSchema",
149
- "extraParams",
150
- "contextLength",
151
- "enableThinking",
152
- ], `LLM engine ${key}`);
153
- if (typeof engine.endpoint !== "string" ||
154
- !engine.endpoint ||
155
- typeof engine.model !== "string" ||
156
- !engine.model ||
157
- !Number.isInteger(engine.concurrency) ||
158
- engine.concurrency < 1 ||
159
- engine.concurrency > WORKFLOW_MAX_CONCURRENCY)
160
- fail(`LLM engine ${key} is invalid`);
161
- if (engine.provider !== undefined)
162
- assertString(engine.provider, `LLM engine ${key} provider`);
163
- validateEndpoint(engine.endpoint, key);
164
- validateOptionalFiniteNumber(engine.temperature, `LLM engine ${key} temperature`);
165
- validateOptionalPositiveInteger(engine.maxTokens, `LLM engine ${key} maxTokens`);
166
- validateOptionalPositiveInteger(engine.contextLength, `LLM engine ${key} contextLength`);
167
- if (engine.supportsJsonSchema !== undefined && typeof engine.supportsJsonSchema !== "boolean")
168
- fail(`LLM engine ${key} supportsJsonSchema must be boolean`);
169
- if (engine.enableThinking !== undefined && typeof engine.enableThinking !== "boolean")
170
- fail(`LLM engine ${key} enableThinking must be boolean`);
171
- if (engine.extraParams !== undefined) {
172
- const issue = validateExtraParams(engine.extraParams)[0];
173
- if (issue)
174
- fail(formatExtraParamsIssue(`LLM engine ${key} extraParams`, issue));
175
- }
176
- validateExtraParamsValue(engine.extraParams, `LLM engine ${key} extraParams`, hooks);
177
- if (engine.credential !== undefined) {
178
- if (!isRecord(engine.credential) ||
179
- !Array.isArray(engine.credential.names) ||
180
- engine.credential.names.length === 0 ||
181
- engine.credential.names.length > 32 ||
182
- !engine.credential.names.every((n) => typeof n === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(n)) ||
183
- new Set(engine.credential.names).size !== engine.credential.names.length ||
184
- typeof engine.credential.required !== "boolean") {
185
- fail(`LLM engine ${key} has an invalid credential descriptor`);
186
- }
187
- assertKeys(engine.credential, ["names", "required"], `LLM engine ${key} credential`);
188
- }
189
- return;
190
- }
191
- assertKeys(engine, [
192
- "name",
193
- "kind",
194
- "runnerKind",
195
- "platform",
196
- "bin",
197
- "args",
198
- "workspace",
199
- "envPassthrough",
200
- "commandBuilder",
201
- "fallbackLlmEngine",
202
- ], `agent engine ${key}`);
203
- if ((engine.runnerKind !== "agent" && engine.runnerKind !== "sdk") ||
204
- typeof engine.platform !== "string" ||
205
- !engine.platform ||
206
- typeof engine.bin !== "string" ||
207
- !engine.bin ||
208
- !Array.isArray(engine.args) ||
209
- engine.args.length > MAX_LIST_ITEMS ||
210
- !engine.args.every((arg) => typeof arg === "string" && arg.length <= MAX_STRING_LENGTH) ||
211
- !(typeof engine.workspace === "string" || engine.workspace === null) ||
212
- !Array.isArray(engine.envPassthrough) ||
213
- engine.envPassthrough.length > MAX_LIST_ITEMS ||
214
- !engine.envPassthrough.every((name) => typeof name === "string" && WORKFLOW_ENV_VAR_NAME_PATTERN.test(name)) ||
215
- new Set(engine.envPassthrough).size !== engine.envPassthrough.length ||
216
- typeof engine.commandBuilder !== "string" ||
217
- engine.commandBuilder !== engine.platform ||
218
- (engine.workspace !== null && !path.isAbsolute(engine.workspace)) ||
219
- !HARNESS_BY_ID.get(engine.platform)?.capabilities.agentDispatch)
220
- fail(`agent engine ${key} is invalid`);
221
- if ((engine.platform === "opencode-sdk") !== (engine.runnerKind === "sdk"))
222
- fail(`agent engine ${key} has incompatible platform and runnerKind`);
223
- if (engine.runnerKind === "agent" && engine.fallbackLlmEngine !== null)
224
- fail(`agent engine ${key} cannot have an SDK fallback`);
225
- if (engine.fallbackLlmEngine !== null) {
226
- if (typeof engine.fallbackLlmEngine !== "string" || !engine.fallbackLlmEngine)
227
- fail(`agent engine ${key} has an invalid fallback`);
228
- references.add(engine.fallbackLlmEngine);
229
- }
230
- }
231
- function validateNode(node, stepId, references, nodeIds, hooks) {
76
+ function validateNode(node, stepId, nodeIds, unitExtraKeys) {
232
77
  if (!isRecord(node) ||
233
78
  (node.kind !== "unit" && node.kind !== "map") ||
234
79
  typeof node.id !== "string" ||
@@ -248,7 +93,7 @@ function validateNode(node, stepId, references, nodeIds, hooks) {
248
93
  if (node.id !== `${stepId}.map`)
249
94
  fail(`map ${node.id} does not match step ${stepId}`);
250
95
  validateSource(node.source, `map ${node.id} source`);
251
- validateNode(node.template, stepId, references, nodeIds, hooks);
96
+ validateNode(node.template, stepId, nodeIds, unitExtraKeys);
252
97
  if (!isRecord(node.template) || node.template.kind !== "unit")
253
98
  fail(`map ${node.id} template must be a unit`);
254
99
  if (node.template.id !== `${stepId}.unit`)
@@ -259,20 +104,17 @@ function validateNode(node, stepId, references, nodeIds, hooks) {
259
104
  "kind",
260
105
  "id",
261
106
  "instructions",
262
- "templating",
263
107
  "inputs",
264
- "invocation",
265
- "exec",
266
108
  "schema",
267
109
  "retry",
268
110
  "onError",
269
111
  "env",
270
112
  "isolation",
271
113
  "source",
114
+ ...unitExtraKeys,
272
115
  ], `unit ${node.id}`);
273
116
  if (typeof node.instructions !== "string" ||
274
117
  !node.instructions ||
275
- node.templating !== "verbatim" ||
276
118
  (node.onError !== "fail" && node.onError !== "continue") ||
277
119
  (node.isolation !== "none" && node.isolation !== "worktree"))
278
120
  fail(`unit ${node.id} is invalid`);
@@ -286,17 +128,6 @@ function validateNode(node, stepId, references, nodeIds, hooks) {
286
128
  validateStringArray(node.env, `unit ${node.id} env`, MAX_LIST_ITEMS, true);
287
129
  validateStringArray(node.inputs, `unit ${node.id} inputs`, WORKFLOW_MAX_INPUTS, true);
288
130
  validateSource(node.source, `unit ${node.id} source`);
289
- // Exactly one dispatch mechanism per unit. A node carrying both would let a
290
- // tampered plan smuggle a shell command past the engine-compatibility checks
291
- // (which key off the invocation); one carrying neither has nothing to run.
292
- if ((node.invocation === undefined) === (node.exec === undefined)) {
293
- fail(`unit ${node.id} must declare exactly one of invocation or exec`);
294
- }
295
- if (node.exec !== undefined) {
296
- validateExecSpec(node.exec, `unit ${node.id} exec`);
297
- return;
298
- }
299
- validateInvocation(node.invocation, references, hooks);
300
131
  }
301
132
  /**
302
133
  * Strictly decode a frozen {@link IrExecSpec}. This is the corruption gate for
@@ -307,7 +138,7 @@ function validateNode(node, stepId, references, nodeIds, hooks) {
307
138
  function validateExecSpec(value, label) {
308
139
  if (!isRecord(value))
309
140
  fail(`${label} must be an object`);
310
- assertKeys(value, ["command", "cwd", "passEnv", "inheritEnv", "timeoutMs"], label);
141
+ assertKeys(value, ["command", "cwd", "passEnv", "timeoutMs"], label);
311
142
  validateExecEnvScope(value, label);
312
143
  const command = value.command;
313
144
  if (!Array.isArray(command) ||
@@ -331,20 +162,22 @@ function validateExecSpec(value, label) {
331
162
  fail(`${label}.timeoutMs must be null or an integer from 1 through ${WORKFLOW_MAX_TIMEOUT_MS}`);
332
163
  }
333
164
  }
165
+ /** Decode one current frozen exec spec. */
166
+ export function decodeWorkflowExecSpec(value, label) {
167
+ validateExecSpec(value, label);
168
+ return value;
169
+ }
334
170
  /**
335
171
  * The child's ENVIRONMENT SCOPE half of a frozen exec spec. Split out so
336
172
  * {@link validateExecSpec} keeps its shape (and the src-fn-size ratchet stays
337
173
  * shrink-only).
338
174
  *
339
- * Both keys are canonical-form-only: `inheritEnv` may only be `true` and
340
- * `passEnv` may only be a non-empty deduplicated name list. A persisted `false`
341
- * or `[]` means exactly what absence means, and admitting a second spelling of
342
- * the default would give the same unit two different input hashes.
175
+ * `passEnv` is canonical-form-only and may only be a non-empty deduplicated
176
+ * name list. A persisted `[]` means exactly what absence means, and admitting a
177
+ * second spelling of the default would give the same unit two different input
178
+ * hashes.
343
179
  */
344
180
  function validateExecEnvScope(value, label) {
345
- if (value.inheritEnv !== undefined && value.inheritEnv !== true) {
346
- fail(`${label}.inheritEnv must be true when present (the allowlist default is the key's ABSENCE)`);
347
- }
348
181
  if (value.passEnv === undefined)
349
182
  return;
350
183
  const passEnv = value.passEnv;
@@ -357,7 +190,7 @@ function validateExecEnvScope(value, label) {
357
190
  `matching ${WORKFLOW_ENV_VAR_NAME_PATTERN.source}`);
358
191
  }
359
192
  }
360
- function validateGate(gate, stepId, references, nodeIds, hooks) {
193
+ function validateGate(gate, stepId, nodeIds, gateExtraKeys) {
361
194
  if (!isRecord(gate) ||
362
195
  gate.kind !== "gate" ||
363
196
  gate.id !== `${stepId}.gate` ||
@@ -371,26 +204,8 @@ function validateGate(gate, stepId, references, nodeIds, hooks) {
371
204
  fail(`gate for step ${stepId} is invalid`);
372
205
  if (nodeIds.has(gate.id))
373
206
  fail(`gate id ${gate.id} collides with a node`);
374
- assertKeys(gate, ["kind", "id", "stepId", "criteria", "maxLoops", "judge"], `gate ${stepId}`);
207
+ assertKeys(gate, ["kind", "id", "stepId", "criteria", "maxLoops", ...gateExtraKeys], `gate ${stepId}`);
375
208
  nodeIds.add(gate.id);
376
- if (gate.criteria.length === 0 && gate.judge !== null)
377
- fail(`gate ${gate.id} without criteria cannot have a judge`);
378
- if (gate.judge !== null)
379
- validateInvocation(gate.judge, references, hooks);
380
- }
381
- function validateInvocation(invocation, references, hooks) {
382
- if (!isRecord(invocation) ||
383
- typeof invocation.engine !== "string" ||
384
- !invocation.engine ||
385
- !((typeof invocation.model === "string" && invocation.model.length > 0) || invocation.model === null) ||
386
- !(invocation.timeoutMs === null ||
387
- (Number.isSafeInteger(invocation.timeoutMs) &&
388
- invocation.timeoutMs >= 1 &&
389
- invocation.timeoutMs <= WORKFLOW_MAX_TIMEOUT_MS)))
390
- fail("invocation is invalid");
391
- references.add(invocation.engine);
392
- assertKeys(invocation, ["engine", "model", "timeoutMs", "llm"], "invocation");
393
- validateLlmOverrides(invocation.llm, hooks);
394
209
  }
395
210
  function validateRoute(route, stepId) {
396
211
  if (!isRecord(route) ||
@@ -406,27 +221,6 @@ function validateRoute(route, stepId) {
406
221
  if (route.defaultStepId !== undefined && (typeof route.defaultStepId !== "string" || !route.defaultStepId))
407
222
  fail(`route for step ${stepId} has an invalid default target`);
408
223
  }
409
- function assertUnitEngineCompatibility(node, engines) {
410
- const unit = node.kind === "map" ? node.template : node;
411
- // An exec unit names no engine at all, so there is no engine pairing to
412
- // check. It is the ONE kind that may carry `env` + `isolation: worktree`
413
- // without an engine behind it: it spawns a real child process, which is
414
- // exactly the precondition those two features require. `validateNode` has
415
- // already proven it carries no `invocation`, so nothing here can be bypassed
416
- // by declaring both.
417
- if (unit.exec !== undefined)
418
- return;
419
- const engine = unit.invocation ? engines[unit.invocation.engine] : undefined;
420
- if (!engine)
421
- return;
422
- if (engine.kind === "llm" && unit.invocation?.model === null)
423
- fail(`LLM unit ${unit.id} has no exact model`);
424
- if (engine.kind === "agent" && unit.invocation?.llm !== undefined)
425
- fail(`agent unit ${unit.id} cannot carry LLM invocation settings`);
426
- if (engine.kind === "llm" && ((unit.env?.length ?? 0) > 0 || unit.isolation === "worktree")) {
427
- fail(`LLM unit ${unit.id} cannot use env injection or worktree isolation`);
428
- }
429
- }
430
224
  function assertKeys(value, allowed, label) {
431
225
  for (const key of Object.keys(value))
432
226
  if (!allowed.includes(key))
@@ -508,46 +302,12 @@ function validateRetry(retry, nodeId) {
508
302
  retry.on.some((reason) => !PROGRAM_RETRY_REASONS.includes(reason)))
509
303
  fail(`unit ${nodeId} retry.on is invalid`);
510
304
  }
511
- function validateLlmOverrides(value, hooks) {
512
- if (value === undefined)
513
- return;
514
- if (!isRecord(value))
515
- fail("invocation.llm must be an object");
516
- assertKeys(value, ["temperature", "maxTokens", "supportsJsonSchema", "extraParams", "contextLength", "enableThinking"], "invocation.llm");
517
- validateOptionalFiniteNumber(value.temperature, "invocation.llm.temperature");
518
- validateOptionalPositiveInteger(value.maxTokens, "invocation.llm.maxTokens");
519
- validateOptionalPositiveInteger(value.contextLength, "invocation.llm.contextLength");
520
- if (value.supportsJsonSchema !== undefined && typeof value.supportsJsonSchema !== "boolean")
521
- fail("invocation.llm.supportsJsonSchema must be boolean");
522
- if (value.enableThinking !== undefined && typeof value.enableThinking !== "boolean")
523
- fail("invocation.llm.enableThinking must be boolean");
524
- if (value.extraParams !== undefined) {
525
- const issue = validateExtraParams(value.extraParams)[0];
526
- if (issue)
527
- fail(formatExtraParamsIssue("invocation.llm.extraParams", issue));
528
- }
529
- validateExtraParamsValue(value.extraParams, "invocation.llm.extraParams", hooks);
530
- }
531
305
  function validateSchema(value, label) {
532
306
  if (!isRecord(value))
533
307
  fail(`${label} must be an object`);
534
308
  if (jsonBytes(value) > WORKFLOW_MAX_SCHEMA_BYTES)
535
309
  fail(`${label} exceeds the 256 KiB resource limit`);
536
310
  }
537
- function validateExtraParamsValue(value, label, hooks) {
538
- if (value === undefined)
539
- return;
540
- if (!isRecord(value))
541
- fail(`${label} must be an object`);
542
- if (jsonBytes(value) > WORKFLOW_MAX_EXTRA_PARAMS_BYTES)
543
- fail(`${label} exceeds the 64 KiB resource limit`);
544
- const issue = validateExtraParams(value)[0];
545
- if (issue)
546
- fail(formatExtraParamsIssue(label, issue));
547
- const policyError = hooks.validateExtraParams?.(value, label);
548
- if (policyError)
549
- fail(`${label}: ${policyError}`);
550
- }
551
311
  function validateStepExpressions(step, index, steps) {
552
312
  const validateReference = (text, label, paramsAllowed) => {
553
313
  const parsed = parseReference(text);
@@ -594,29 +354,10 @@ function validateStringArray(value, label, max, unique) {
594
354
  (unique && new Set(value).size !== value.length))
595
355
  fail(`${label} is invalid`);
596
356
  }
597
- function validateOptionalFiniteNumber(value, label) {
598
- if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value)))
599
- fail(`${label} must be finite`);
600
- }
601
357
  function validateOptionalPositiveInteger(value, label) {
602
358
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
603
359
  fail(`${label} must be a positive safe integer`);
604
360
  }
605
- function validateEndpoint(value, engine) {
606
- try {
607
- const url = new URL(value);
608
- if ((url.protocol !== "http:" && url.protocol !== "https:") ||
609
- url.username ||
610
- url.password ||
611
- url.search ||
612
- url.hash ||
613
- !url.pathname.endsWith("/chat/completions"))
614
- fail(`LLM engine ${engine} endpoint is not canonical`);
615
- }
616
- catch {
617
- fail(`LLM engine ${engine} endpoint is invalid`);
618
- }
619
- }
620
361
  function assertString(value, label) {
621
362
  if (typeof value !== "string" || value.length === 0 || value.length > MAX_STRING_LENGTH)
622
363
  fail(`${label} must be a non-empty bounded string`);