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.
- package/CHANGELOG.md +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { cloneExecutionJsonObject } from "../../execution/json.js";
|
|
5
|
+
import { withEngineFallback } from "./engine-fallback.js";
|
|
6
|
+
import { planExecutionCascade, } from "./execution-cascade.js";
|
|
7
|
+
import { executionEngineDefinitionsFromConfig } from "./execution-definitions.js";
|
|
8
|
+
import { loadModelMap } from "./model-map.js";
|
|
9
|
+
function own(value, key) {
|
|
10
|
+
return Object.hasOwn(value, key);
|
|
11
|
+
}
|
|
12
|
+
/** Pure common cascade composition once caller-specific sources are rendered. */
|
|
13
|
+
export function planPreparedExecution(options) {
|
|
14
|
+
return planExecutionCascade({
|
|
15
|
+
command: options.command,
|
|
16
|
+
...(own(options, "conversation") ? { conversation: options.conversation } : {}),
|
|
17
|
+
...(own(options, "persona") ? { persona: options.persona } : {}),
|
|
18
|
+
layers: {
|
|
19
|
+
installation: {
|
|
20
|
+
id: "installation-defaults",
|
|
21
|
+
values: options.installationEngine === undefined ? {} : { engine: options.installationEngine },
|
|
22
|
+
},
|
|
23
|
+
...(options.agentLayer ? { agent: options.agentLayer } : {}),
|
|
24
|
+
command: options.commandLayer,
|
|
25
|
+
...(options.invocationDefaults
|
|
26
|
+
? { invocationDefaults: { id: "invocation-defaults", values: options.invocationDefaults } }
|
|
27
|
+
: {}),
|
|
28
|
+
...(options.current ? { current: { id: "current-invocation", values: options.current } } : {}),
|
|
29
|
+
},
|
|
30
|
+
engines: options.engines,
|
|
31
|
+
modelMap: options.modelMap,
|
|
32
|
+
invocationKind: options.invocationKind,
|
|
33
|
+
...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The one planner adapter for already-rendered command/persona content.
|
|
38
|
+
* Direct, task, improve, proposal, and workflow callers contribute named
|
|
39
|
+
* layers here; none may rebuild engine/model precedence around the cascade.
|
|
40
|
+
*/
|
|
41
|
+
export function prepareResolvedExecution(options) {
|
|
42
|
+
const inputConfig = cloneExecutionJsonObject(options.config, "execution preparation input config");
|
|
43
|
+
const fallback = withEngineFallback(inputConfig);
|
|
44
|
+
const config = cloneExecutionJsonObject(fallback.config, "execution preparation resolved config");
|
|
45
|
+
const defaults = own(config, "defaults") ? config.defaults : undefined;
|
|
46
|
+
const installationEngine = defaults && own(defaults, "engine") ? defaults.engine : undefined;
|
|
47
|
+
const plan = planPreparedExecution({
|
|
48
|
+
command: options.command,
|
|
49
|
+
...(own(options, "conversation") ? { conversation: options.conversation } : {}),
|
|
50
|
+
...(own(options, "persona") ? { persona: options.persona } : {}),
|
|
51
|
+
...(installationEngine === undefined ? {} : { installationEngine }),
|
|
52
|
+
...(options.agentLayer ? { agentLayer: options.agentLayer } : {}),
|
|
53
|
+
commandLayer: options.commandLayer,
|
|
54
|
+
...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}),
|
|
55
|
+
...(options.current ? { current: options.current } : {}),
|
|
56
|
+
engines: executionEngineDefinitionsFromConfig(config),
|
|
57
|
+
modelMap: options.modelMap ?? loadModelMap().map,
|
|
58
|
+
invocationKind: options.invocationKind,
|
|
59
|
+
...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
|
|
60
|
+
});
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
plan,
|
|
63
|
+
request: plan.request,
|
|
64
|
+
config,
|
|
65
|
+
...(fallback.fallbackEngineName ? { fallbackEngineName: fallback.fallbackEngineName } : {}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
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
|
-
export { getCommandBuilder } from "./builders.js";
|
|
5
4
|
export { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
|
|
6
5
|
export { _setAgentDetectForTests, defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
|
|
7
|
-
export { listBuiltinModelAliases, resolveModel } from "./model-aliases.js";
|
|
8
6
|
export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
|
|
9
7
|
export { buildProposePrompt, buildReflectPrompt, buildSchemaRepairPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "./prompts.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { createInlineResolvedCommand, } from "../../execution/resolved-request.js";
|
|
5
|
+
import { executionEngineDefinitionFromRunner } from "./execution-definitions.js";
|
|
6
|
+
import { planPreparedExecution, prepareResolvedExecution } from "./execution-preparation.js";
|
|
7
|
+
import { MODEL_MAP_VERSION } from "./model-map.js";
|
|
8
|
+
const EMPTY_EXACT_MODEL_MAP = Object.freeze({
|
|
9
|
+
version: MODEL_MAP_VERSION,
|
|
10
|
+
aliases: Object.freeze(Object.create(null)),
|
|
11
|
+
});
|
|
12
|
+
function own(value, key) {
|
|
13
|
+
return Object.hasOwn(value, key);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve anonymous command content through the same far-to-near cascade used
|
|
17
|
+
* by stored commands. Tasks, workflows, and internal model work adapt their
|
|
18
|
+
* already-rendered content here rather than rebuilding engine/model precedence.
|
|
19
|
+
*/
|
|
20
|
+
export function prepareInlineExecution(options) {
|
|
21
|
+
const command = createInlineResolvedCommand({
|
|
22
|
+
template: options.content,
|
|
23
|
+
...(own(options, "argumentInput") ? { argumentInput: options.argumentInput } : {}),
|
|
24
|
+
content: options.content,
|
|
25
|
+
});
|
|
26
|
+
return prepareResolvedExecution({
|
|
27
|
+
command,
|
|
28
|
+
config: options.config,
|
|
29
|
+
invocationKind: options.invocationKind,
|
|
30
|
+
...(own(options, "conversation") ? { conversation: options.conversation } : {}),
|
|
31
|
+
...(own(options, "persona") ? { persona: options.persona } : {}),
|
|
32
|
+
commandLayer: { id: "inline-command", values: {} },
|
|
33
|
+
...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}),
|
|
34
|
+
...(options.current ? { current: options.current } : {}),
|
|
35
|
+
...(options.modelMap ? { modelMap: options.modelMap } : {}),
|
|
36
|
+
...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Prepare anonymous work from an already-resolved runner without reading live
|
|
41
|
+
* config or interpreting aliases. Used by resume-safe workflow/judge and by
|
|
42
|
+
* improve/proposal calls that already froze runner material.
|
|
43
|
+
*/
|
|
44
|
+
export function prepareInlineExecutionWithRunner(options) {
|
|
45
|
+
const command = createInlineResolvedCommand({
|
|
46
|
+
template: options.content,
|
|
47
|
+
...(own(options, "argumentInput") ? { argumentInput: options.argumentInput } : {}),
|
|
48
|
+
content: options.content,
|
|
49
|
+
});
|
|
50
|
+
const material = executionEngineDefinitionFromRunner(options.runner, own(options, "sdkFallbackModelFromRequest")
|
|
51
|
+
? { sdkFallbackModelFromRequest: options.sdkFallbackModelFromRequest === true }
|
|
52
|
+
: {});
|
|
53
|
+
const engines = Object.create(null);
|
|
54
|
+
Object.defineProperty(engines, material.engineName, {
|
|
55
|
+
value: material.definition,
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: false,
|
|
58
|
+
writable: false,
|
|
59
|
+
});
|
|
60
|
+
const plan = planPreparedExecution({
|
|
61
|
+
command,
|
|
62
|
+
...(own(options, "conversation") ? { conversation: options.conversation } : {}),
|
|
63
|
+
...(own(options, "persona") ? { persona: options.persona } : {}),
|
|
64
|
+
installationEngine: material.engineName,
|
|
65
|
+
commandLayer: { id: "inline-command", values: {} },
|
|
66
|
+
...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}),
|
|
67
|
+
...(options.current ? { current: options.current } : {}),
|
|
68
|
+
engines: Object.freeze(engines),
|
|
69
|
+
modelMap: options.modelMap ?? EMPTY_EXACT_MODEL_MAP,
|
|
70
|
+
invocationKind: options.invocationKind,
|
|
71
|
+
...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
|
|
72
|
+
});
|
|
73
|
+
return Object.freeze({ plan, request: plan.request, runner: material.runner });
|
|
74
|
+
}
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { MAX_CONFIG_FILE_BYTES, readTextFileDescriptorWithLimit, writeFileAtomic } from "../../core/common.js";
|
|
9
|
+
import { ENGINE_NAME_PATTERN_SOURCE } from "../../core/config/engine-semantics.js";
|
|
10
|
+
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
11
|
+
import { getConfigDir } from "../../core/paths.js";
|
|
12
|
+
import { cloneExecutionJsonObject } from "../../execution/json.js";
|
|
13
|
+
/**
|
|
14
|
+
* Installed and operator-owned model intent aliases (#802 / WP2).
|
|
15
|
+
*
|
|
16
|
+
* The common cascade consumes this loader/expansion API for current runtime
|
|
17
|
+
* execution callers. A recognized alias expands once into exact model and
|
|
18
|
+
* inference defaults at its selecting layer; engine lowerers consume that
|
|
19
|
+
* resolved request and never reinterpret the alias. The independent API also
|
|
20
|
+
* remains usable by WP6/WP7 source and freeze adapters without hard-coding
|
|
21
|
+
* provider mappings.
|
|
22
|
+
*/
|
|
23
|
+
export const MODEL_MAP_VERSION = 1;
|
|
24
|
+
export const MODEL_MAP_MAX_BYTES = MAX_CONFIG_FILE_BYTES;
|
|
25
|
+
export const MODEL_MAP_MAX_DEPTH = 64;
|
|
26
|
+
export const MODEL_MAP_MAX_NODES = 100_000;
|
|
27
|
+
let standaloneInstalledModelMapText;
|
|
28
|
+
const ENGINE_KEY_PATTERN = new RegExp(ENGINE_NAME_PATTERN_SOURCE);
|
|
29
|
+
const ALIAS_KEY_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
30
|
+
const RESERVED_MAP_KEYS = new Set(["__proto__", "constructor", "prototype", "tostring"]);
|
|
31
|
+
function assertSafeMapKey(key, source, jsonPath) {
|
|
32
|
+
if (RESERVED_MAP_KEYS.has(key.toLowerCase())) {
|
|
33
|
+
invalid(source, jsonPath, "reserved prototype-like key is not allowed");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function invalid(source, jsonPath, detail) {
|
|
37
|
+
throw new ConfigError(`Invalid ${source} at ${jsonPath}: ${detail}`, "INVALID_CONFIG_FILE", `Fix ${source}, or remove the optional user models.json to use AKM's installed defaults.`);
|
|
38
|
+
}
|
|
39
|
+
function requireRecord(value, source, jsonPath) {
|
|
40
|
+
if (value === null ||
|
|
41
|
+
typeof value !== "object" ||
|
|
42
|
+
Array.isArray(value) ||
|
|
43
|
+
Object.getPrototypeOf(value) !== Object.prototype) {
|
|
44
|
+
invalid(source, jsonPath, "expected a JSON object");
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function assertOnlyKeys(value, allowed, source, jsonPath) {
|
|
49
|
+
const allowedSet = new Set(allowed);
|
|
50
|
+
for (const key of Object.keys(value)) {
|
|
51
|
+
if (!allowedSet.has(key))
|
|
52
|
+
invalid(source, `${jsonPath}.${key}`, "unknown field");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function requireNonemptyString(value, source, jsonPath) {
|
|
56
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
|
|
57
|
+
invalid(source, jsonPath, "expected a nonempty string without surrounding whitespace");
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function freezeRecord(entries) {
|
|
62
|
+
const record = Object.create(null);
|
|
63
|
+
for (const [key, value] of entries)
|
|
64
|
+
record[key] = value;
|
|
65
|
+
return Object.freeze(record);
|
|
66
|
+
}
|
|
67
|
+
function ownValue(record, key) {
|
|
68
|
+
return record !== undefined && Object.hasOwn(record, key) ? record[key] : undefined;
|
|
69
|
+
}
|
|
70
|
+
function parseProfileLayer(value, source, jsonPath) {
|
|
71
|
+
if (typeof value === "string")
|
|
72
|
+
return requireNonemptyString(value, source, jsonPath);
|
|
73
|
+
const record = requireRecord(value, source, jsonPath);
|
|
74
|
+
assertOnlyKeys(record, ["model", "inference"], source, jsonPath);
|
|
75
|
+
if (!Object.hasOwn(record, "model") && !Object.hasOwn(record, "inference")) {
|
|
76
|
+
invalid(source, jsonPath, "structured profile must contain model and/or inference");
|
|
77
|
+
}
|
|
78
|
+
const out = {};
|
|
79
|
+
if (Object.hasOwn(record, "model"))
|
|
80
|
+
out.model = requireNonemptyString(record.model, source, `${jsonPath}.model`);
|
|
81
|
+
if (Object.hasOwn(record, "inference")) {
|
|
82
|
+
if (record.inference === null)
|
|
83
|
+
out.inference = null;
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
out.inference = cloneExecutionJsonObject(record.inference, `${source} ${jsonPath}.inference`);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
invalid(source, `${jsonPath}.inference`, error instanceof Error ? error.message : String(error));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze(out);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Bound nesting before JSON.parse or any recursive durable-JSON clone runs.
|
|
97
|
+
* Brackets inside strings are ignored, including escaped quote sequences.
|
|
98
|
+
*/
|
|
99
|
+
function assertJsonDepthBudget(text, source) {
|
|
100
|
+
let depth = 0;
|
|
101
|
+
let inString = false;
|
|
102
|
+
let escaped = false;
|
|
103
|
+
for (const character of text) {
|
|
104
|
+
if (inString) {
|
|
105
|
+
if (escaped)
|
|
106
|
+
escaped = false;
|
|
107
|
+
else if (character === "\\")
|
|
108
|
+
escaped = true;
|
|
109
|
+
else if (character === '"')
|
|
110
|
+
inString = false;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (character === '"') {
|
|
114
|
+
inString = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (character === "{" || character === "[") {
|
|
118
|
+
depth += 1;
|
|
119
|
+
if (depth > MODEL_MAP_MAX_DEPTH) {
|
|
120
|
+
invalid(source, "$", `JSON nesting exceeds the ${MODEL_MAP_MAX_DEPTH}-level limit`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (character === "}" || character === "]") {
|
|
124
|
+
depth -= 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Iterative node budget, deliberately completed before recursive inference cloning. */
|
|
129
|
+
function assertJsonNodeBudget(value, source) {
|
|
130
|
+
const pending = [value];
|
|
131
|
+
let count = 0;
|
|
132
|
+
while (pending.length > 0) {
|
|
133
|
+
const current = pending.pop();
|
|
134
|
+
count += 1;
|
|
135
|
+
if (count > MODEL_MAP_MAX_NODES) {
|
|
136
|
+
invalid(source, "$", `JSON value exceeds the ${MODEL_MAP_MAX_NODES}-node limit`);
|
|
137
|
+
}
|
|
138
|
+
if (current !== null && typeof current === "object") {
|
|
139
|
+
const children = Array.isArray(current) ? current : Object.values(current);
|
|
140
|
+
for (const child of children)
|
|
141
|
+
pending.push(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Parse one installed or user layer. Partial structured profiles are valid at this stage. */
|
|
146
|
+
export function parseModelMapLayer(text, source) {
|
|
147
|
+
if (Buffer.byteLength(text, "utf8") > MODEL_MAP_MAX_BYTES) {
|
|
148
|
+
invalid(source, "$", `document exceeds the ${MODEL_MAP_MAX_BYTES}-byte limit`);
|
|
149
|
+
}
|
|
150
|
+
assertJsonDepthBudget(text, source);
|
|
151
|
+
let parsed;
|
|
152
|
+
try {
|
|
153
|
+
parsed = JSON.parse(text);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
invalid(source, "$", "invalid JSON syntax");
|
|
157
|
+
}
|
|
158
|
+
assertJsonNodeBudget(parsed, source);
|
|
159
|
+
const root = requireRecord(parsed, source, "$");
|
|
160
|
+
assertOnlyKeys(root, ["version", "aliases"], source, "$");
|
|
161
|
+
if (root.version !== MODEL_MAP_VERSION) {
|
|
162
|
+
invalid(source, "$.version", `expected version ${MODEL_MAP_VERSION}`);
|
|
163
|
+
}
|
|
164
|
+
if (!Object.hasOwn(root, "aliases"))
|
|
165
|
+
invalid(source, "$.aliases", "required field is missing");
|
|
166
|
+
const rawAliases = requireRecord(root.aliases, source, "$.aliases");
|
|
167
|
+
const aliases = [];
|
|
168
|
+
const aliasesSeen = new Map();
|
|
169
|
+
for (const [rawAlias, rawEngines] of Object.entries(rawAliases)) {
|
|
170
|
+
const alias = rawAlias.toLowerCase();
|
|
171
|
+
assertSafeMapKey(alias, source, `$.aliases.${rawAlias}`);
|
|
172
|
+
if (!ALIAS_KEY_PATTERN.test(alias))
|
|
173
|
+
invalid(source, `$.aliases.${rawAlias}`, "alias must be lowercase kebab-case");
|
|
174
|
+
const previous = aliasesSeen.get(alias);
|
|
175
|
+
if (previous !== undefined) {
|
|
176
|
+
invalid(source, `$.aliases.${rawAlias}`, `alias collides case-insensitively with ${previous}`);
|
|
177
|
+
}
|
|
178
|
+
aliasesSeen.set(alias, rawAlias);
|
|
179
|
+
const engineRecord = requireRecord(rawEngines, source, `$.aliases.${rawAlias}`);
|
|
180
|
+
if (Object.keys(engineRecord).length === 0) {
|
|
181
|
+
invalid(source, `$.aliases.${rawAlias}`, "alias must contain at least one engine mapping");
|
|
182
|
+
}
|
|
183
|
+
const engines = [];
|
|
184
|
+
const enginesSeen = new Map();
|
|
185
|
+
for (const [rawEngine, rawProfile] of Object.entries(engineRecord)) {
|
|
186
|
+
const engine = rawEngine.toLowerCase();
|
|
187
|
+
assertSafeMapKey(engine, source, `$.aliases.${rawAlias}.${rawEngine}`);
|
|
188
|
+
if (!ENGINE_KEY_PATTERN.test(engine)) {
|
|
189
|
+
invalid(source, `$.aliases.${rawAlias}.${rawEngine}`, "engine key must be lowercase kebab-case");
|
|
190
|
+
}
|
|
191
|
+
const previousEngine = enginesSeen.get(engine);
|
|
192
|
+
if (previousEngine !== undefined) {
|
|
193
|
+
invalid(source, `$.aliases.${rawAlias}.${rawEngine}`, `engine key collides case-insensitively with ${previousEngine}`);
|
|
194
|
+
}
|
|
195
|
+
enginesSeen.set(engine, rawEngine);
|
|
196
|
+
engines.push([engine, parseProfileLayer(rawProfile, source, `$.aliases.${rawAlias}.${rawEngine}`)]);
|
|
197
|
+
}
|
|
198
|
+
aliases.push([alias, freezeRecord(engines)]);
|
|
199
|
+
}
|
|
200
|
+
return Object.freeze({ version: MODEL_MAP_VERSION, aliases: freezeRecord(aliases) });
|
|
201
|
+
}
|
|
202
|
+
function isJsonObject(value) {
|
|
203
|
+
return value !== null && value !== undefined && typeof value === "object" && !Array.isArray(value);
|
|
204
|
+
}
|
|
205
|
+
function mergeJsonValue(base, overlay) {
|
|
206
|
+
if (!isJsonObject(base) || !isJsonObject(overlay))
|
|
207
|
+
return overlay;
|
|
208
|
+
const entries = new Map(Object.entries(base));
|
|
209
|
+
for (const [key, value] of Object.entries(overlay))
|
|
210
|
+
entries.set(key, mergeJsonValue(entries.get(key), value));
|
|
211
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
212
|
+
}
|
|
213
|
+
function normalizeLayerEntry(entry) {
|
|
214
|
+
return typeof entry === "string" ? Object.freeze({ model: entry }) : entry;
|
|
215
|
+
}
|
|
216
|
+
function mergeProfiles(base, overlay) {
|
|
217
|
+
const next = normalizeLayerEntry(overlay);
|
|
218
|
+
const out = {};
|
|
219
|
+
if (base?.model !== undefined)
|
|
220
|
+
out.model = base.model;
|
|
221
|
+
if (base && Object.hasOwn(base, "inference"))
|
|
222
|
+
out.inference = base.inference;
|
|
223
|
+
if (next.model !== undefined)
|
|
224
|
+
out.model = next.model;
|
|
225
|
+
if (Object.hasOwn(next, "inference")) {
|
|
226
|
+
out.inference =
|
|
227
|
+
next.inference !== null && next.inference !== undefined && isJsonObject(base?.inference)
|
|
228
|
+
? mergeJsonValue(base.inference, next.inference)
|
|
229
|
+
: next.inference;
|
|
230
|
+
}
|
|
231
|
+
return Object.freeze(out);
|
|
232
|
+
}
|
|
233
|
+
/** Overlay user fields over installed fields, then enforce usable merged profiles. */
|
|
234
|
+
export function mergeModelMapLayers(installed, user) {
|
|
235
|
+
const aliases = new Map();
|
|
236
|
+
const apply = (layer) => {
|
|
237
|
+
for (const [alias, engines] of Object.entries(layer.aliases)) {
|
|
238
|
+
const mergedEngines = aliases.get(alias) ?? new Map();
|
|
239
|
+
for (const [engine, profile] of Object.entries(engines)) {
|
|
240
|
+
mergedEngines.set(engine, mergeProfiles(mergedEngines.get(engine), profile));
|
|
241
|
+
}
|
|
242
|
+
aliases.set(alias, mergedEngines);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
apply(installed);
|
|
246
|
+
if (user)
|
|
247
|
+
apply(user);
|
|
248
|
+
const resolvedAliases = [];
|
|
249
|
+
for (const [alias, engines] of aliases) {
|
|
250
|
+
const resolvedEngines = [];
|
|
251
|
+
for (const [engine, profile] of engines) {
|
|
252
|
+
if (profile.model === undefined) {
|
|
253
|
+
invalid("merged models.json", `$.aliases.${alias}.${engine}.model`, "a usable model is required after overlay");
|
|
254
|
+
}
|
|
255
|
+
resolvedEngines.push([engine, Object.freeze({ ...profile, model: profile.model })]);
|
|
256
|
+
}
|
|
257
|
+
resolvedAliases.push([alias, freezeRecord(resolvedEngines)]);
|
|
258
|
+
}
|
|
259
|
+
return Object.freeze({ version: MODEL_MAP_VERSION, aliases: freezeRecord(resolvedAliases) });
|
|
260
|
+
}
|
|
261
|
+
function selectionFromProfile(input, profile) {
|
|
262
|
+
if (!profile.model)
|
|
263
|
+
throw new ConfigError(`Model alias ${JSON.stringify(input)} does not resolve to a usable model.`);
|
|
264
|
+
return Object.freeze({
|
|
265
|
+
input,
|
|
266
|
+
interpretation: "alias",
|
|
267
|
+
model: profile.model,
|
|
268
|
+
...(Object.hasOwn(profile, "inference") ? { inference: profile.inference } : {}),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/** Resolve one alias through the merged installed/user registry. */
|
|
272
|
+
export function resolveModelMapAlias(input, engine, map) {
|
|
273
|
+
const alias = input.toLowerCase();
|
|
274
|
+
const selectedEngine = engine.toLowerCase();
|
|
275
|
+
const tier = ownValue(map.aliases, alias);
|
|
276
|
+
const profile = ownValue(tier, selectedEngine);
|
|
277
|
+
if (profile !== undefined)
|
|
278
|
+
return selectionFromProfile(input, profile);
|
|
279
|
+
if (tier !== undefined) {
|
|
280
|
+
throw new ConfigError(`Known alias ${JSON.stringify(input)} has no model mapping for selected engine ${JSON.stringify(engine)}.`, "INVALID_CONFIG_FILE", `Add $.aliases.${alias}.${engine} to models.json.`);
|
|
281
|
+
}
|
|
282
|
+
return Object.freeze({ input, interpretation: "exact", model: input });
|
|
283
|
+
}
|
|
284
|
+
export function userModelMapPath(env = process.env) {
|
|
285
|
+
return path.join(getConfigDir(env), "models.json");
|
|
286
|
+
}
|
|
287
|
+
function modelMapFileError(label, filePath, action) {
|
|
288
|
+
return new ConfigError(`${label} could not be ${action}: ${filePath}.`, "INVALID_CONFIG_FILE", `Ensure ${filePath} is a readable regular file owned by the expected user.`);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Read through a nonblocking, no-follow descriptor and enforce the config-size
|
|
292
|
+
* ceiling before parsing. `optional` recognizes only a true lstat ENOENT as
|
|
293
|
+
* absence; dangling links and every non-regular type are configuration errors.
|
|
294
|
+
*/
|
|
295
|
+
function readModelMapFile(filePath, label, optional) {
|
|
296
|
+
let linkStat;
|
|
297
|
+
try {
|
|
298
|
+
linkStat = fs.lstatSync(filePath);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (optional && error?.code === "ENOENT")
|
|
302
|
+
return undefined;
|
|
303
|
+
throw modelMapFileError(label, filePath, optional ? "inspected" : "found");
|
|
304
|
+
}
|
|
305
|
+
if (!linkStat.isFile()) {
|
|
306
|
+
throw new ConfigError(`Unable to read ${label.toLowerCase()} because it is not a readable regular file: ${filePath}.`, "INVALID_CONFIG_FILE", "Move the symlink or non-regular target aside, or replace it with a readable regular models.json file.");
|
|
307
|
+
}
|
|
308
|
+
const noFollow = process.platform !== "win32" && typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
309
|
+
const nonblock = process.platform !== "win32" && typeof fs.constants.O_NONBLOCK === "number" ? fs.constants.O_NONBLOCK : 0;
|
|
310
|
+
let fd;
|
|
311
|
+
try {
|
|
312
|
+
fd = fs.openSync(filePath, fs.constants.O_RDONLY | noFollow | nonblock);
|
|
313
|
+
const openedStat = fs.fstatSync(fd);
|
|
314
|
+
if (!sameFileIdentity(linkStat, openedStat)) {
|
|
315
|
+
throw new ConfigError(`${label} changed while it was being opened: ${filePath}.`, "INVALID_CONFIG_FILE", "Retry after ensuring no other process is replacing models.json.");
|
|
316
|
+
}
|
|
317
|
+
const text = readTextFileDescriptorWithLimit(fd, MODEL_MAP_MAX_BYTES, label, filePath);
|
|
318
|
+
fs.closeSync(fd);
|
|
319
|
+
fd = undefined;
|
|
320
|
+
return text;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error instanceof ConfigError)
|
|
324
|
+
throw error;
|
|
325
|
+
throw modelMapFileError(label, filePath, "read");
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
if (fd !== undefined) {
|
|
329
|
+
try {
|
|
330
|
+
fs.closeSync(fd);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
// The actionable read error above remains authoritative.
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** Resolve the one authoritative external-asset location for this runtime. */
|
|
339
|
+
export function installedModelMapPaths(moduleUrl = import.meta.url) {
|
|
340
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
341
|
+
const moduleDirectory = path.dirname(modulePath);
|
|
342
|
+
const unbundledModule = path.basename(moduleDirectory) === "agent" && path.basename(path.dirname(moduleDirectory)) === "integrations";
|
|
343
|
+
const assetPath = path.resolve(moduleDirectory, unbundledModule ? "../../assets/models.json" : "assets/models.json");
|
|
344
|
+
return Object.freeze([assetPath]);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Standalone bootstrap only. `scripts/akm-standalone.ts` derives these bytes
|
|
348
|
+
* from the authoritative JSON import that Bun embeds into the executable.
|
|
349
|
+
*/
|
|
350
|
+
export function registerStandaloneModelMapFallback(text) {
|
|
351
|
+
standaloneInstalledModelMapText = text;
|
|
352
|
+
}
|
|
353
|
+
/** Load installed bytes lazily so health can report a missing/bad package asset. */
|
|
354
|
+
export function readInstalledModelMapText(options = {}) {
|
|
355
|
+
if (options.installedText !== undefined)
|
|
356
|
+
return options.installedText;
|
|
357
|
+
// Registration occurs only in the compiled standalone bootstrap. Once set,
|
|
358
|
+
// the embedded authority must win over any mutable file beside the binary.
|
|
359
|
+
if (standaloneInstalledModelMapText !== undefined)
|
|
360
|
+
return standaloneInstalledModelMapText;
|
|
361
|
+
const candidates = installedModelMapPaths();
|
|
362
|
+
for (const candidate of candidates) {
|
|
363
|
+
const external = readModelMapFile(candidate, "Installed models.json", true);
|
|
364
|
+
if (external !== undefined)
|
|
365
|
+
return external;
|
|
366
|
+
}
|
|
367
|
+
throw new ConfigError(`Installed models.json is missing from this AKM installation (checked: ${candidates.join(", ")}).`, "INVALID_CONFIG_FILE", "Reinstall AKM so its dist/assets/models.json package asset is restored.");
|
|
368
|
+
}
|
|
369
|
+
/** Load the installed authority plus the optional operator overlay. */
|
|
370
|
+
export function loadModelMap(options = {}) {
|
|
371
|
+
const installed = parseModelMapLayer(readInstalledModelMapText(options), "installed models.json");
|
|
372
|
+
const userPath = userModelMapPath(options.env);
|
|
373
|
+
const userText = readModelMapFile(userPath, "User models.json", true);
|
|
374
|
+
let user;
|
|
375
|
+
if (userText !== undefined)
|
|
376
|
+
user = parseModelMapLayer(userText, `user models.json (${userPath})`);
|
|
377
|
+
return Object.freeze({ map: mergeModelMapLayers(installed, user), userPath, userStatus: user ? "loaded" : "absent" });
|
|
378
|
+
}
|
|
379
|
+
function targetExistsError(target, detail) {
|
|
380
|
+
return new UsageError(`${detail}: ${target}`, "RESOURCE_ALREADY_EXISTS", "Move the existing target aside, then retry. Use --overwrite only for a stable regular file you intend to replace.");
|
|
381
|
+
}
|
|
382
|
+
function copyIoError(target, action, hint) {
|
|
383
|
+
return new ConfigError(`Unable to ${action} user models.json at ${target}.`, "INVALID_CONFIG_FILE", hint ?? "Check the configuration directory ownership and permissions, then retry.");
|
|
384
|
+
}
|
|
385
|
+
function inspectCopyTarget(target) {
|
|
386
|
+
try {
|
|
387
|
+
return fs.lstatSync(target);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error?.code === "ENOENT")
|
|
391
|
+
return undefined;
|
|
392
|
+
throw copyIoError(target, "inspect");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function sameFileIdentity(left, right) {
|
|
396
|
+
if (!left.isFile() || !right.isFile())
|
|
397
|
+
return false;
|
|
398
|
+
if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
|
|
399
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
400
|
+
}
|
|
401
|
+
return left.mode === right.mode && left.size === right.size && left.mtimeMs === right.mtimeMs;
|
|
402
|
+
}
|
|
403
|
+
function syncCopyDirectory(directory) {
|
|
404
|
+
if (process.platform === "win32")
|
|
405
|
+
return;
|
|
406
|
+
let fd;
|
|
407
|
+
try {
|
|
408
|
+
fd = fs.openSync(directory, fs.constants.O_RDONLY);
|
|
409
|
+
fs.fsyncSync(fd);
|
|
410
|
+
fs.closeSync(fd);
|
|
411
|
+
fd = undefined;
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
const code = error?.code;
|
|
415
|
+
if (code !== "EINVAL" && code !== "ENOTSUP")
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
if (fd !== undefined) {
|
|
420
|
+
try {
|
|
421
|
+
fs.closeSync(fd);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// A preceding sync/open failure is more useful than a cleanup failure.
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function stageDefaultModelMap(target, text) {
|
|
430
|
+
const stage = `${target}.copy.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
431
|
+
try {
|
|
432
|
+
writeFileAtomic(stage, text, 0o600);
|
|
433
|
+
return stage;
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
throw copyIoError(target, "write a staged copy of");
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** Explicitly copy validated installed bytes into the normal config directory. */
|
|
440
|
+
export function copyDefaultModelMap(options = {}) {
|
|
441
|
+
const text = readInstalledModelMapText(options);
|
|
442
|
+
mergeModelMapLayers(parseModelMapLayer(text, "installed models.json"));
|
|
443
|
+
const target = userModelMapPath(options.env);
|
|
444
|
+
try {
|
|
445
|
+
fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
throw copyIoError(target, "create the configuration directory for");
|
|
449
|
+
}
|
|
450
|
+
const existing = inspectCopyTarget(target);
|
|
451
|
+
if (existing && !existing.isFile()) {
|
|
452
|
+
throw targetExistsError(target, "Refusing to replace a non-regular models.json target");
|
|
453
|
+
}
|
|
454
|
+
if (existing && options.overwrite !== true) {
|
|
455
|
+
throw targetExistsError(target, "User models.json already exists");
|
|
456
|
+
}
|
|
457
|
+
const stage = stageDefaultModelMap(target, text);
|
|
458
|
+
let stagePresent = true;
|
|
459
|
+
try {
|
|
460
|
+
if (existing === undefined) {
|
|
461
|
+
try {
|
|
462
|
+
// Hard-link publication is an atomic no-replace operation: EEXIST
|
|
463
|
+
// means a racing creator won, and their bytes remain untouched.
|
|
464
|
+
fs.linkSync(stage, target);
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
if (error?.code === "EEXIST") {
|
|
468
|
+
throw targetExistsError(target, "User models.json was created while defaults were being prepared");
|
|
469
|
+
}
|
|
470
|
+
throw copyIoError(target, "publish");
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
fs.unlinkSync(stage);
|
|
474
|
+
stagePresent = false;
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// The target was already published atomically. Cleanup is best-effort
|
|
478
|
+
// from this point forward; `finally` retries without turning a
|
|
479
|
+
// successful copy into a false failure.
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
const current = inspectCopyTarget(target);
|
|
484
|
+
if (!current || !sameFileIdentity(existing, current)) {
|
|
485
|
+
throw targetExistsError(target, "User models.json changed while defaults were being prepared");
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
// rename replaces the directory entry itself; it never follows a
|
|
489
|
+
// symlink inserted after the final identity check.
|
|
490
|
+
fs.renameSync(stage, target);
|
|
491
|
+
stagePresent = false;
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
throw copyIoError(target, "replace");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
if (stagePresent) {
|
|
500
|
+
try {
|
|
501
|
+
fs.unlinkSync(stage);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
// Best-effort cleanup; the target was never published from this path.
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
syncCopyDirectory(path.dirname(target));
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
throw copyIoError(target, "durably publish", `The target may already exist at ${target}; inspect it before retrying because directory durability could not be confirmed.`);
|
|
513
|
+
}
|
|
514
|
+
return { path: target, copied: true, overwritten: existing !== undefined };
|
|
515
|
+
}
|