@caupulican/pi-adaptative 0.80.86 → 0.80.89
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 +178 -0
- package/dist/core/agent-session.d.ts +412 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2053 -41
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/approval-gate.d.ts +4 -0
- package/dist/core/autonomy/approval-gate.d.ts.map +1 -0
- package/dist/core/autonomy/approval-gate.js +27 -0
- package/dist/core/autonomy/approval-gate.js.map +1 -0
- package/dist/core/autonomy/bounded-completion.d.ts +27 -0
- package/dist/core/autonomy/bounded-completion.d.ts.map +1 -0
- package/dist/core/autonomy/bounded-completion.js +44 -0
- package/dist/core/autonomy/bounded-completion.js.map +1 -0
- package/dist/core/autonomy/contracts.d.ts +129 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -0
- package/dist/core/autonomy/contracts.js +2 -0
- package/dist/core/autonomy/contracts.js.map +1 -0
- package/dist/core/autonomy/gates.d.ts +15 -0
- package/dist/core/autonomy/gates.d.ts.map +1 -0
- package/dist/core/autonomy/gates.js +205 -0
- package/dist/core/autonomy/gates.js.map +1 -0
- package/dist/core/autonomy/lane-tracker.d.ts +48 -0
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -0
- package/dist/core/autonomy/lane-tracker.js +125 -0
- package/dist/core/autonomy/lane-tracker.js.map +1 -0
- package/dist/core/autonomy/path-scope.d.ts +9 -0
- package/dist/core/autonomy/path-scope.d.ts.map +1 -0
- package/dist/core/autonomy/path-scope.js +122 -0
- package/dist/core/autonomy/path-scope.js.map +1 -0
- package/dist/core/autonomy/risk-assessment.d.ts +3 -0
- package/dist/core/autonomy/risk-assessment.d.ts.map +1 -0
- package/dist/core/autonomy/risk-assessment.js +122 -0
- package/dist/core/autonomy/risk-assessment.js.map +1 -0
- package/dist/core/autonomy/session-lane-record.d.ts +10 -0
- package/dist/core/autonomy/session-lane-record.d.ts.map +1 -0
- package/dist/core/autonomy/session-lane-record.js +36 -0
- package/dist/core/autonomy/session-lane-record.js.map +1 -0
- package/dist/core/autonomy/status.d.ts +40 -0
- package/dist/core/autonomy/status.d.ts.map +1 -0
- package/dist/core/autonomy/status.js +107 -0
- package/dist/core/autonomy/status.js.map +1 -0
- package/dist/core/autonomy/subagent-prompt.d.ts +21 -0
- package/dist/core/autonomy/subagent-prompt.d.ts.map +1 -0
- package/dist/core/autonomy/subagent-prompt.js +28 -0
- package/dist/core/autonomy/subagent-prompt.js.map +1 -0
- package/dist/core/autonomy/telemetry-events.d.ts +18 -0
- package/dist/core/autonomy/telemetry-events.d.ts.map +1 -0
- package/dist/core/autonomy/telemetry-events.js +60 -0
- package/dist/core/autonomy/telemetry-events.js.map +1 -0
- package/dist/core/context/artifact-retrieval.d.ts +49 -0
- package/dist/core/context/artifact-retrieval.d.ts.map +1 -0
- package/dist/core/context/artifact-retrieval.js +49 -0
- package/dist/core/context/artifact-retrieval.js.map +1 -0
- package/dist/core/context/brain-curator.d.ts +88 -0
- package/dist/core/context/brain-curator.d.ts.map +1 -0
- package/dist/core/context/brain-curator.js +192 -0
- package/dist/core/context/brain-curator.js.map +1 -0
- package/dist/core/context/context-artifacts.d.ts +94 -0
- package/dist/core/context/context-artifacts.d.ts.map +1 -0
- package/dist/core/context/context-artifacts.js +307 -0
- package/dist/core/context/context-artifacts.js.map +1 -0
- package/dist/core/context/context-audit.d.ts +66 -0
- package/dist/core/context/context-audit.d.ts.map +1 -0
- package/dist/core/context/context-audit.js +173 -0
- package/dist/core/context/context-audit.js.map +1 -0
- package/dist/core/context/context-composition.d.ts +122 -0
- package/dist/core/context/context-composition.d.ts.map +1 -0
- package/dist/core/context/context-composition.js +163 -0
- package/dist/core/context/context-composition.js.map +1 -0
- package/dist/core/context/context-item.d.ts +117 -0
- package/dist/core/context/context-item.d.ts.map +1 -0
- package/dist/core/context/context-item.js +36 -0
- package/dist/core/context/context-item.js.map +1 -0
- package/dist/core/context/context-prompt-enforcement.d.ts +86 -0
- package/dist/core/context/context-prompt-enforcement.d.ts.map +1 -0
- package/dist/core/context/context-prompt-enforcement.js +168 -0
- package/dist/core/context/context-prompt-enforcement.js.map +1 -0
- package/dist/core/context/context-prompt-policy.d.ts +90 -0
- package/dist/core/context/context-prompt-policy.d.ts.map +1 -0
- package/dist/core/context/context-prompt-policy.js +73 -0
- package/dist/core/context/context-prompt-policy.js.map +1 -0
- package/dist/core/context/context-retention.d.ts +36 -0
- package/dist/core/context/context-retention.d.ts.map +1 -0
- package/dist/core/context/context-retention.js +108 -0
- package/dist/core/context/context-retention.js.map +1 -0
- package/dist/core/context/context-store.d.ts +37 -0
- package/dist/core/context/context-store.d.ts.map +1 -0
- package/dist/core/context/context-store.js +45 -0
- package/dist/core/context/context-store.js.map +1 -0
- package/dist/core/context/memory-diagnostics.d.ts +50 -0
- package/dist/core/context/memory-diagnostics.d.ts.map +1 -0
- package/dist/core/context/memory-diagnostics.js +43 -0
- package/dist/core/context/memory-diagnostics.js.map +1 -0
- package/dist/core/context/memory-index-store.d.ts +28 -0
- package/dist/core/context/memory-index-store.d.ts.map +1 -0
- package/dist/core/context/memory-index-store.js +38 -0
- package/dist/core/context/memory-index-store.js.map +1 -0
- package/dist/core/context/memory-prompt-block.d.ts +34 -0
- package/dist/core/context/memory-prompt-block.d.ts.map +1 -0
- package/dist/core/context/memory-prompt-block.js +58 -0
- package/dist/core/context/memory-prompt-block.js.map +1 -0
- package/dist/core/context/memory-provider-contract.d.ts +114 -0
- package/dist/core/context/memory-provider-contract.d.ts.map +1 -0
- package/dist/core/context/memory-provider-contract.js +121 -0
- package/dist/core/context/memory-provider-contract.js.map +1 -0
- package/dist/core/context/memory-retrieval.d.ts +27 -0
- package/dist/core/context/memory-retrieval.d.ts.map +1 -0
- package/dist/core/context/memory-retrieval.js +91 -0
- package/dist/core/context/memory-retrieval.js.map +1 -0
- package/dist/core/context/okf-memory-provider.d.ts +26 -0
- package/dist/core/context/okf-memory-provider.d.ts.map +1 -0
- package/dist/core/context/okf-memory-provider.js +154 -0
- package/dist/core/context/okf-memory-provider.js.map +1 -0
- package/dist/core/context/okf-memory.d.ts +42 -0
- package/dist/core/context/okf-memory.d.ts.map +1 -0
- package/dist/core/context/okf-memory.js +175 -0
- package/dist/core/context/okf-memory.js.map +1 -0
- package/dist/core/context/policy-engine.d.ts +66 -0
- package/dist/core/context/policy-engine.d.ts.map +1 -0
- package/dist/core/context/policy-engine.js +171 -0
- package/dist/core/context/policy-engine.js.map +1 -0
- package/dist/core/context/policy-types.d.ts +102 -0
- package/dist/core/context/policy-types.d.ts.map +1 -0
- package/dist/core/context/policy-types.js +7 -0
- package/dist/core/context/policy-types.js.map +1 -0
- package/dist/core/context/sqlite-runtime-index.d.ts +19 -0
- package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -0
- package/dist/core/context/sqlite-runtime-index.js +344 -0
- package/dist/core/context/sqlite-runtime-index.js.map +1 -0
- package/dist/core/context/storage-authority.d.ts +20 -0
- package/dist/core/context/storage-authority.d.ts.map +1 -0
- package/dist/core/context/storage-authority.js +51 -0
- package/dist/core/context/storage-authority.js.map +1 -0
- package/dist/core/context/tool-output-packer.d.ts +75 -0
- package/dist/core/context/tool-output-packer.d.ts.map +1 -0
- package/dist/core/context/tool-output-packer.js +77 -0
- package/dist/core/context/tool-output-packer.js.map +1 -0
- package/dist/core/context-gc.d.ts +13 -0
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +6 -0
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/cost/session-usage.d.ts +20 -0
- package/dist/core/cost/session-usage.d.ts.map +1 -0
- package/dist/core/cost/session-usage.js +164 -0
- package/dist/core/cost/session-usage.js.map +1 -0
- package/dist/core/delegation/session-worker-result.d.ts +10 -0
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -0
- package/dist/core/delegation/session-worker-result.js +36 -0
- package/dist/core/delegation/session-worker-result.js.map +1 -0
- package/dist/core/delegation/worker-result.d.ts +9 -0
- package/dist/core/delegation/worker-result.d.ts.map +1 -0
- package/dist/core/delegation/worker-result.js +152 -0
- package/dist/core/delegation/worker-result.js.map +1 -0
- package/dist/core/delegation/worker-runner.d.ts +58 -0
- package/dist/core/delegation/worker-runner.d.ts.map +1 -0
- package/dist/core/delegation/worker-runner.js +188 -0
- package/dist/core/delegation/worker-runner.js.map +1 -0
- package/dist/core/extensions/builtin.d.ts +5 -1
- package/dist/core/extensions/builtin.d.ts.map +1 -1
- package/dist/core/extensions/builtin.js +23 -1
- package/dist/core/extensions/builtin.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +5 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +13 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-continuation-controller.d.ts +22 -0
- package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-controller.js +88 -0
- package/dist/core/goals/goal-continuation-controller.js.map +1 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts +10 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-defaults.js +10 -0
- package/dist/core/goals/goal-continuation-defaults.js.map +1 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts +18 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-prompt.js +141 -0
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts +19 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.js +23 -0
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -0
- package/dist/core/goals/goal-state.d.ts +87 -0
- package/dist/core/goals/goal-state.d.ts.map +1 -0
- package/dist/core/goals/goal-state.js +259 -0
- package/dist/core/goals/goal-state.js.map +1 -0
- package/dist/core/goals/goal-tool-core.d.ts +66 -0
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -0
- package/dist/core/goals/goal-tool-core.js +146 -0
- package/dist/core/goals/goal-tool-core.js.map +1 -0
- package/dist/core/goals/session-goal-state.d.ts +10 -0
- package/dist/core/goals/session-goal-state.d.ts.map +1 -0
- package/dist/core/goals/session-goal-state.js +35 -0
- package/dist/core/goals/session-goal-state.js.map +1 -0
- package/dist/core/learning/learning-audit.d.ts +45 -0
- package/dist/core/learning/learning-audit.d.ts.map +1 -0
- package/dist/core/learning/learning-audit.js +139 -0
- package/dist/core/learning/learning-audit.js.map +1 -0
- package/dist/core/learning/learning-gate.d.ts +29 -0
- package/dist/core/learning/learning-gate.d.ts.map +1 -0
- package/dist/core/learning/learning-gate.js +150 -0
- package/dist/core/learning/learning-gate.js.map +1 -0
- package/dist/core/learning/session-learning-decision.d.ts +10 -0
- package/dist/core/learning/session-learning-decision.d.ts.map +1 -0
- package/dist/core/learning/session-learning-decision.js +36 -0
- package/dist/core/learning/session-learning-decision.js.map +1 -0
- package/dist/core/model-capability.d.ts +41 -0
- package/dist/core/model-capability.d.ts.map +1 -0
- package/dist/core/model-capability.js +101 -0
- package/dist/core/model-capability.js.map +1 -0
- package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
- package/dist/core/model-router/config-diagnostics.js +1 -0
- package/dist/core/model-router/config-diagnostics.js.map +1 -1
- package/dist/core/model-router/intent-classifier.d.ts +2 -0
- package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
- package/dist/core/model-router/intent-classifier.js +154 -9
- package/dist/core/model-router/intent-classifier.js.map +1 -1
- package/dist/core/model-router/route-judge.d.ts +54 -0
- package/dist/core/model-router/route-judge.d.ts.map +1 -0
- package/dist/core/model-router/route-judge.js +128 -0
- package/dist/core/model-router/route-judge.js.map +1 -0
- package/dist/core/model-router/status.d.ts +4 -1
- package/dist/core/model-router/status.d.ts.map +1 -1
- package/dist/core/model-router/status.js +30 -6
- package/dist/core/model-router/status.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts +4 -6
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +1 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts +40 -0
- package/dist/core/models/fitness-store.d.ts.map +1 -0
- package/dist/core/models/fitness-store.js +61 -0
- package/dist/core/models/fitness-store.js.map +1 -0
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +1 -1
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +2 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +12 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/research/automata-provider.d.ts +5 -0
- package/dist/core/research/automata-provider.d.ts.map +1 -0
- package/dist/core/research/automata-provider.js +15 -0
- package/dist/core/research/automata-provider.js.map +1 -0
- package/dist/core/research/evidence-bundle.d.ts +10 -0
- package/dist/core/research/evidence-bundle.d.ts.map +1 -0
- package/dist/core/research/evidence-bundle.js +116 -0
- package/dist/core/research/evidence-bundle.js.map +1 -0
- package/dist/core/research/model-fitness.d.ts +82 -0
- package/dist/core/research/model-fitness.d.ts.map +1 -0
- package/dist/core/research/model-fitness.js +308 -0
- package/dist/core/research/model-fitness.js.map +1 -0
- package/dist/core/research/research-gate.d.ts +11 -0
- package/dist/core/research/research-gate.d.ts.map +1 -0
- package/dist/core/research/research-gate.js +82 -0
- package/dist/core/research/research-gate.js.map +1 -0
- package/dist/core/research/research-runner.d.ts +59 -0
- package/dist/core/research/research-runner.d.ts.map +1 -0
- package/dist/core/research/research-runner.js +155 -0
- package/dist/core/research/research-runner.js.map +1 -0
- package/dist/core/research/session-evidence-bundle.d.ts +11 -0
- package/dist/core/research/session-evidence-bundle.d.ts.map +1 -0
- package/dist/core/research/session-evidence-bundle.js +55 -0
- package/dist/core/research/session-evidence-bundle.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +160 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +304 -9
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +4 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +18 -6
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +10 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/toolkit/script-registry.d.ts +34 -0
- package/dist/core/toolkit/script-registry.d.ts.map +1 -0
- package/dist/core/toolkit/script-registry.js +71 -0
- package/dist/core/toolkit/script-registry.js.map +1 -0
- package/dist/core/toolkit/script-runner.d.ts +28 -0
- package/dist/core/toolkit/script-runner.d.ts.map +1 -0
- package/dist/core/toolkit/script-runner.js +48 -0
- package/dist/core/toolkit/script-runner.js.map +1 -0
- package/dist/core/tools/artifact-retrieve.d.ts +23 -0
- package/dist/core/tools/artifact-retrieve.d.ts.map +1 -0
- package/dist/core/tools/artifact-retrieve.js +110 -0
- package/dist/core/tools/artifact-retrieve.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +32 -0
- package/dist/core/tools/delegate.d.ts.map +1 -0
- package/dist/core/tools/delegate.js +60 -0
- package/dist/core/tools/delegate.js.map +1 -0
- package/dist/core/tools/fff-search-backend.d.ts +103 -0
- package/dist/core/tools/fff-search-backend.d.ts.map +1 -0
- package/dist/core/tools/fff-search-backend.js +151 -0
- package/dist/core/tools/fff-search-backend.js.map +1 -0
- package/dist/core/tools/find.d.ts +21 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +183 -10
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts +35 -0
- package/dist/core/tools/goal.d.ts.map +1 -0
- package/dist/core/tools/goal.js +122 -0
- package/dist/core/tools/goal.js.map +1 -0
- package/dist/core/tools/grep.d.ts +21 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +272 -27
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +4 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/model-fitness.d.ts +30 -0
- package/dist/core/tools/model-fitness.d.ts.map +1 -0
- package/dist/core/tools/model-fitness.js +38 -0
- package/dist/core/tools/model-fitness.js.map +1 -0
- package/dist/core/tools/run-toolkit-script.d.ts +24 -0
- package/dist/core/tools/run-toolkit-script.d.ts.map +1 -0
- package/dist/core/tools/run-toolkit-script.js +103 -0
- package/dist/core/tools/run-toolkit-script.js.map +1 -0
- package/dist/core/tools/search-router.d.ts +75 -0
- package/dist/core/tools/search-router.d.ts.map +1 -0
- package/dist/core/tools/search-router.js +85 -0
- package/dist/core/tools/search-router.js.map +1 -0
- package/dist/modes/interactive/components/fitness-role-selector.d.ts +13 -0
- package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/fitness-role-selector.js +65 -0
- package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +18 -16
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +16 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +555 -11
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +308 -39
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +2 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +154 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +368 -12
- package/package.json +5 -4
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { runBoundedCompletion } from "../autonomy/bounded-completion.js";
|
|
2
|
+
/**
|
|
3
|
+
* Routing-only judge: a bounded, tool-less completion (default: the medium model) that decides the
|
|
4
|
+
* final cheap/medium/expensive tier for a user prompt, refining the regex classifier's baseline.
|
|
5
|
+
* Core rule: planning is never cheap unless the judge explicitly deems the task trivial. The judge
|
|
6
|
+
* proposes; the existing pipeline (model resolution, auth, escalation, gates) still decides.
|
|
7
|
+
* Failure is honest: unparseable/timeout/unavailable falls back to the baseline with a visible
|
|
8
|
+
* reasonCode, never silently.
|
|
9
|
+
*/
|
|
10
|
+
/** Static across calls — callers pass cacheRetention "short" so only the variable tail is billed. */
|
|
11
|
+
export const ROUTE_JUDGE_SYSTEM_PROMPT = [
|
|
12
|
+
"You are a routing judge for a coding agent. You only route; you never answer the task.",
|
|
13
|
+
"Pick which model tier should handle the user prompt:",
|
|
14
|
+
'- "cheap": trivial, mechanical, read-only lookups only.',
|
|
15
|
+
'- "medium": normal implementation, scoped edits, tests, and NON-trivial planning/design.',
|
|
16
|
+
'- "expensive": architecture, ambiguity, security/auth, destructive or release operations, high-impact changes.',
|
|
17
|
+
"Planning, design, and strategy prompts are NEVER cheap unless the task is genuinely trivial.",
|
|
18
|
+
"Respond with STRICT JSON only - no prose:",
|
|
19
|
+
'{"tier":"cheap"|"medium"|"expensive","risk":"read-only"|"scoped-write"|"high-impact"|"approval-required","trivial":true|false,"reason":"<short reason>"}',
|
|
20
|
+
].join("\n");
|
|
21
|
+
export const ROUTE_JUDGE_MAX_OUTPUT_TOKENS = 128;
|
|
22
|
+
export const ROUTE_JUDGE_MAX_WALL_CLOCK_MS = 10_000;
|
|
23
|
+
export function buildRouteJudgeUserPrompt(args) {
|
|
24
|
+
return [
|
|
25
|
+
`Baseline (regex) verdict: tier=${args.baseline.tier}, risk=${args.baseline.risk}, reason=${args.baseline.reasonCode}.`,
|
|
26
|
+
"User prompt:",
|
|
27
|
+
args.prompt.slice(0, 4000),
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
30
|
+
const JUDGE_TIERS = ["cheap", "medium", "expensive"];
|
|
31
|
+
const JUDGE_RISKS = ["read-only", "scoped-write", "high-impact", "approval-required"];
|
|
32
|
+
export function parseRouteJudgeVerdict(text) {
|
|
33
|
+
const trimmed = text.trim();
|
|
34
|
+
const candidates = [trimmed];
|
|
35
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(trimmed);
|
|
36
|
+
if (fenced?.[1])
|
|
37
|
+
candidates.push(fenced[1].trim());
|
|
38
|
+
const start = trimmed.indexOf("{");
|
|
39
|
+
const end = trimmed.lastIndexOf("}");
|
|
40
|
+
if (start >= 0 && end > start)
|
|
41
|
+
candidates.push(trimmed.slice(start, end + 1));
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(candidate);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
51
|
+
continue;
|
|
52
|
+
const record = parsed;
|
|
53
|
+
// The judge may never select the learning tier or anything outside the three foreground tiers.
|
|
54
|
+
if (typeof record.tier !== "string" || !JUDGE_TIERS.includes(record.tier))
|
|
55
|
+
continue;
|
|
56
|
+
const risk = typeof record.risk === "string" && JUDGE_RISKS.includes(record.risk)
|
|
57
|
+
? record.risk
|
|
58
|
+
: undefined;
|
|
59
|
+
if (!risk)
|
|
60
|
+
continue;
|
|
61
|
+
return {
|
|
62
|
+
tier: record.tier,
|
|
63
|
+
risk,
|
|
64
|
+
trivial: record.trivial === true,
|
|
65
|
+
reason: typeof record.reason === "string" ? record.reason.slice(0, 200) : "",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
/** Merge a judge verdict into the baseline decision (pure; never returns learning). */
|
|
71
|
+
export function applyRouteJudgeVerdict(baseline, verdict) {
|
|
72
|
+
// Enforce the core rule in code, not just in the judge prompt: downgrading a non-cheap
|
|
73
|
+
// baseline to cheap requires an EXPLICIT trivial verdict. An untrusted judge saying
|
|
74
|
+
// {tier:"cheap", trivial:false} for an elevated prompt keeps the baseline tier.
|
|
75
|
+
const tier = verdict.tier === "cheap" && baseline.tier !== "cheap" && !verdict.trivial ? baseline.tier : verdict.tier;
|
|
76
|
+
return {
|
|
77
|
+
...baseline,
|
|
78
|
+
tier,
|
|
79
|
+
risk: verdict.risk,
|
|
80
|
+
confidence: Math.max(baseline.confidence, 0.75),
|
|
81
|
+
reasonCode: `judge_${tier}${verdict.trivial ? "_trivial" : ""}`,
|
|
82
|
+
reasons: [...baseline.reasons, `Route judge: ${verdict.reason || "no reason given"}`],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run the judge over a baseline decision. The completion executor is injected (production:
|
|
87
|
+
* AgentSession.runIsolatedCompletion on the judge model). Never throws; every failure keeps the
|
|
88
|
+
* baseline with a visible fallbackReason.
|
|
89
|
+
*/
|
|
90
|
+
export async function runRouteJudge(args) {
|
|
91
|
+
const bounded = await runBoundedCompletion({
|
|
92
|
+
maxWallClockMs: args.maxWallClockMs ?? ROUTE_JUDGE_MAX_WALL_CLOCK_MS,
|
|
93
|
+
signal: args.signal,
|
|
94
|
+
execute: (signal) => args.complete({
|
|
95
|
+
systemPrompt: ROUTE_JUDGE_SYSTEM_PROMPT,
|
|
96
|
+
userPrompt: buildRouteJudgeUserPrompt({ prompt: args.prompt, baseline: args.baseline }),
|
|
97
|
+
signal,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const costUsd = bounded.completion?.costUsd ?? 0;
|
|
101
|
+
if (bounded.failure || !bounded.completion) {
|
|
102
|
+
return {
|
|
103
|
+
decision: {
|
|
104
|
+
...args.baseline,
|
|
105
|
+
reasons: [...args.baseline.reasons, "Route judge unavailable; baseline kept"],
|
|
106
|
+
},
|
|
107
|
+
fallbackReason: bounded.failure ? `judge_${bounded.failure.reasonCode}` : "judge_unavailable_fallback",
|
|
108
|
+
costUsd,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (bounded.completion.stopReason === "error" || bounded.completion.stopReason === "aborted") {
|
|
112
|
+
return {
|
|
113
|
+
decision: { ...args.baseline, reasons: [...args.baseline.reasons, "Route judge errored; baseline kept"] },
|
|
114
|
+
fallbackReason: "judge_model_error",
|
|
115
|
+
costUsd,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const verdict = parseRouteJudgeVerdict(bounded.completion.text);
|
|
119
|
+
if (!verdict) {
|
|
120
|
+
return {
|
|
121
|
+
decision: { ...args.baseline, reasons: [...args.baseline.reasons, "Route judge unparseable; baseline kept"] },
|
|
122
|
+
fallbackReason: "judge_unparseable_fallback",
|
|
123
|
+
costUsd,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { decision: applyRouteJudgeVerdict(args.baseline, verdict), verdict, costUsd };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=route-judge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-judge.js","sourceRoot":"","sources":["../../../src/core/model-router/route-judge.ts"],"names":[],"mappings":"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","sourcesContent":["import { runBoundedCompletion } from \"../autonomy/bounded-completion.ts\";\nimport type { RouteDecision } from \"../autonomy/contracts.ts\";\n\n/**\n * Routing-only judge: a bounded, tool-less completion (default: the medium model) that decides the\n * final cheap/medium/expensive tier for a user prompt, refining the regex classifier's baseline.\n * Core rule: planning is never cheap unless the judge explicitly deems the task trivial. The judge\n * proposes; the existing pipeline (model resolution, auth, escalation, gates) still decides.\n * Failure is honest: unparseable/timeout/unavailable falls back to the baseline with a visible\n * reasonCode, never silently.\n */\n\n/** Static across calls — callers pass cacheRetention \"short\" so only the variable tail is billed. */\nexport const ROUTE_JUDGE_SYSTEM_PROMPT = [\n\t\"You are a routing judge for a coding agent. You only route; you never answer the task.\",\n\t\"Pick which model tier should handle the user prompt:\",\n\t'- \"cheap\": trivial, mechanical, read-only lookups only.',\n\t'- \"medium\": normal implementation, scoped edits, tests, and NON-trivial planning/design.',\n\t'- \"expensive\": architecture, ambiguity, security/auth, destructive or release operations, high-impact changes.',\n\t\"Planning, design, and strategy prompts are NEVER cheap unless the task is genuinely trivial.\",\n\t\"Respond with STRICT JSON only - no prose:\",\n\t'{\"tier\":\"cheap\"|\"medium\"|\"expensive\",\"risk\":\"read-only\"|\"scoped-write\"|\"high-impact\"|\"approval-required\",\"trivial\":true|false,\"reason\":\"<short reason>\"}',\n].join(\"\\n\");\n\nexport const ROUTE_JUDGE_MAX_OUTPUT_TOKENS = 128;\nexport const ROUTE_JUDGE_MAX_WALL_CLOCK_MS = 10_000;\n\nexport interface RouteJudgeVerdict {\n\ttier: \"cheap\" | \"medium\" | \"expensive\";\n\trisk: RouteDecision[\"risk\"];\n\ttrivial: boolean;\n\treason: string;\n}\n\nexport function buildRouteJudgeUserPrompt(args: { prompt: string; baseline: RouteDecision }): string {\n\treturn [\n\t\t`Baseline (regex) verdict: tier=${args.baseline.tier}, risk=${args.baseline.risk}, reason=${args.baseline.reasonCode}.`,\n\t\t\"User prompt:\",\n\t\targs.prompt.slice(0, 4000),\n\t].join(\"\\n\");\n}\n\nconst JUDGE_TIERS: readonly string[] = [\"cheap\", \"medium\", \"expensive\"];\nconst JUDGE_RISKS: readonly string[] = [\"read-only\", \"scoped-write\", \"high-impact\", \"approval-required\"];\n\nexport function parseRouteJudgeVerdict(text: string): RouteJudgeVerdict | undefined {\n\tconst trimmed = text.trim();\n\tconst candidates: string[] = [trimmed];\n\tconst fenced = /```(?:json)?\\s*([\\s\\S]*?)```/.exec(trimmed);\n\tif (fenced?.[1]) candidates.push(fenced[1].trim());\n\tconst start = trimmed.indexOf(\"{\");\n\tconst end = trimmed.lastIndexOf(\"}\");\n\tif (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));\n\n\tfor (const candidate of candidates) {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(candidate);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n\t\tconst record = parsed as Record<string, unknown>;\n\t\t// The judge may never select the learning tier or anything outside the three foreground tiers.\n\t\tif (typeof record.tier !== \"string\" || !JUDGE_TIERS.includes(record.tier)) continue;\n\t\tconst risk =\n\t\t\ttypeof record.risk === \"string\" && JUDGE_RISKS.includes(record.risk)\n\t\t\t\t? (record.risk as RouteDecision[\"risk\"])\n\t\t\t\t: undefined;\n\t\tif (!risk) continue;\n\t\treturn {\n\t\t\ttier: record.tier as RouteJudgeVerdict[\"tier\"],\n\t\t\trisk,\n\t\t\ttrivial: record.trivial === true,\n\t\t\treason: typeof record.reason === \"string\" ? record.reason.slice(0, 200) : \"\",\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Merge a judge verdict into the baseline decision (pure; never returns learning). */\nexport function applyRouteJudgeVerdict(baseline: RouteDecision, verdict: RouteJudgeVerdict): RouteDecision {\n\t// Enforce the core rule in code, not just in the judge prompt: downgrading a non-cheap\n\t// baseline to cheap requires an EXPLICIT trivial verdict. An untrusted judge saying\n\t// {tier:\"cheap\", trivial:false} for an elevated prompt keeps the baseline tier.\n\tconst tier =\n\t\tverdict.tier === \"cheap\" && baseline.tier !== \"cheap\" && !verdict.trivial ? baseline.tier : verdict.tier;\n\treturn {\n\t\t...baseline,\n\t\ttier,\n\t\trisk: verdict.risk,\n\t\tconfidence: Math.max(baseline.confidence, 0.75),\n\t\treasonCode: `judge_${tier}${verdict.trivial ? \"_trivial\" : \"\"}`,\n\t\treasons: [...baseline.reasons, `Route judge: ${verdict.reason || \"no reason given\"}`],\n\t};\n}\n\nexport interface RouteJudgeRunResult {\n\tdecision: RouteDecision;\n\tverdict?: RouteJudgeVerdict;\n\t/** Set when the judge could not decide and the baseline was kept. */\n\tfallbackReason?: string;\n\tcostUsd: number;\n}\n\n/**\n * Run the judge over a baseline decision. The completion executor is injected (production:\n * AgentSession.runIsolatedCompletion on the judge model). Never throws; every failure keeps the\n * baseline with a visible fallbackReason.\n */\nexport async function runRouteJudge(args: {\n\tprompt: string;\n\tbaseline: RouteDecision;\n\tcomplete: (input: { systemPrompt: string; userPrompt: string; signal?: AbortSignal }) => Promise<{\n\t\ttext: string;\n\t\tcostUsd: number;\n\t\tstopReason: string;\n\t}>;\n\tsignal?: AbortSignal;\n\tmaxWallClockMs?: number;\n}): Promise<RouteJudgeRunResult> {\n\tconst bounded = await runBoundedCompletion({\n\t\tmaxWallClockMs: args.maxWallClockMs ?? ROUTE_JUDGE_MAX_WALL_CLOCK_MS,\n\t\tsignal: args.signal,\n\t\texecute: (signal) =>\n\t\t\targs.complete({\n\t\t\t\tsystemPrompt: ROUTE_JUDGE_SYSTEM_PROMPT,\n\t\t\t\tuserPrompt: buildRouteJudgeUserPrompt({ prompt: args.prompt, baseline: args.baseline }),\n\t\t\t\tsignal,\n\t\t\t}),\n\t});\n\tconst costUsd = bounded.completion?.costUsd ?? 0;\n\n\tif (bounded.failure || !bounded.completion) {\n\t\treturn {\n\t\t\tdecision: {\n\t\t\t\t...args.baseline,\n\t\t\t\treasons: [...args.baseline.reasons, \"Route judge unavailable; baseline kept\"],\n\t\t\t},\n\t\t\tfallbackReason: bounded.failure ? `judge_${bounded.failure.reasonCode}` : \"judge_unavailable_fallback\",\n\t\t\tcostUsd,\n\t\t};\n\t}\n\tif (bounded.completion.stopReason === \"error\" || bounded.completion.stopReason === \"aborted\") {\n\t\treturn {\n\t\t\tdecision: { ...args.baseline, reasons: [...args.baseline.reasons, \"Route judge errored; baseline kept\"] },\n\t\t\tfallbackReason: \"judge_model_error\",\n\t\t\tcostUsd,\n\t\t};\n\t}\n\n\tconst verdict = parseRouteJudgeVerdict(bounded.completion.text);\n\tif (!verdict) {\n\t\treturn {\n\t\t\tdecision: { ...args.baseline, reasons: [...args.baseline.reasons, \"Route judge unparseable; baseline kept\"] },\n\t\t\tfallbackReason: \"judge_unparseable_fallback\",\n\t\t\tcostUsd,\n\t\t};\n\t}\n\n\treturn { decision: applyRouteJudgeVerdict(args.baseline, verdict), verdict, costUsd };\n}\n"]}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import type { RouteDecision } from "../autonomy/contracts.ts";
|
|
1
2
|
import type { SessionEntry } from "../session-manager.ts";
|
|
2
3
|
import type { ModelRouterIntent } from "./intent-classifier.ts";
|
|
3
4
|
export declare const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
4
5
|
export type ModelRouterStatusSettings = {
|
|
5
6
|
enabled: boolean;
|
|
6
7
|
cheapModel?: string;
|
|
8
|
+
mediumModel?: string;
|
|
7
9
|
expensiveModel?: string;
|
|
8
10
|
learningModel?: string;
|
|
9
11
|
};
|
|
10
12
|
export type ModelRouterDecisionStatus = {
|
|
11
|
-
|
|
13
|
+
route: RouteDecision;
|
|
12
14
|
routedModel: string;
|
|
13
15
|
outcome: "routed" | "escalated" | "failed";
|
|
14
16
|
retryModel?: string;
|
|
17
|
+
intent?: ModelRouterIntent;
|
|
15
18
|
};
|
|
16
19
|
export declare function getRecentModelRouterDecisions(entries: SessionEntry[], limit?: number): ModelRouterDecisionStatus[];
|
|
17
20
|
export declare function formatModelRouterStatus(settings: ModelRouterStatusSettings, lastDecision?: ModelRouterDecisionStatus, formatLabel?: (label: string) => string, recentDecisions?: ModelRouterDecisionStatus[], lastSkipReason?: string, latestIntent?: ModelRouterIntent): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,eAAO,MAAM,iCAAiC,0BAA0B,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAa,MAAM,0BAA0B,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,eAAO,MAAM,iCAAiC,0BAA0B,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAiDF,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,yBAAyB,EAAE,CAQ7G;AAED,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,yBAAyB,EACnC,YAAY,CAAC,EAAE,yBAAyB,EACxC,WAAW,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAyB,EACzD,eAAe,GAAE,yBAAyB,EAAO,EACjD,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,iBAAiB,GAC9B,MAAM,CA+BR","sourcesContent":["import type { ModelTier, RouteDecision, RouteRisk } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry } from \"../session-manager.ts\";\nimport type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nexport const MODEL_ROUTER_DECISION_CUSTOM_TYPE = \"model_router_decision\";\n\nexport type ModelRouterStatusSettings = {\n\tenabled: boolean;\n\tcheapModel?: string;\n\tmediumModel?: string;\n\texpensiveModel?: string;\n\tlearningModel?: string;\n};\n\nexport type ModelRouterDecisionStatus = {\n\troute: RouteDecision;\n\troutedModel: string;\n\toutcome: \"routed\" | \"escalated\" | \"failed\";\n\tretryModel?: string;\n\tintent?: ModelRouterIntent;\n};\n\nfunction isRouteDecision(value: unknown): value is RouteDecision {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst route = value as Partial<RouteDecision>;\n\tconst validTiers = new Set<ModelTier>([\"cheap\", \"medium\", \"expensive\", \"learning\"]);\n\tconst validRisks = new Set<RouteRisk>([\"read-only\", \"scoped-write\", \"high-impact\", \"approval-required\"]);\n\n\treturn (\n\t\ttypeof route.confidence === \"number\" &&\n\t\ttypeof route.reasonCode === \"string\" &&\n\t\tArray.isArray(route.reasons) &&\n\t\troute.reasons.every((r) => typeof r === \"string\") &&\n\t\tvalidTiers.has(route.tier as ModelTier) &&\n\t\tvalidRisks.has(route.risk as RouteRisk) &&\n\t\t(route.model === undefined || typeof route.model === \"string\") &&\n\t\t(route.fallbackFrom === undefined ||\n\t\t\troute.fallbackFrom === \"cheap\" ||\n\t\t\troute.fallbackFrom === \"medium\" ||\n\t\t\troute.fallbackFrom === \"expensive\" ||\n\t\t\troute.fallbackFrom === \"learning\") &&\n\t\t(route.createdAt === undefined || typeof route.createdAt === \"string\")\n\t);\n}\n\nfunction isModelRouterDecisionStatus(data: unknown): data is ModelRouterDecisionStatus {\n\tif (!data || typeof data !== \"object\") return false;\n\tconst record = data as Partial<ModelRouterDecisionStatus>;\n\treturn (\n\t\tisRouteDecision(record.route) &&\n\t\trecord.route.tier !== \"learning\" && // Validate user prompt decisions never need learning tier in runtime\n\t\ttypeof record.routedModel === \"string\" &&\n\t\t(record.outcome === \"routed\" || record.outcome === \"escalated\" || record.outcome === \"failed\") &&\n\t\t(record.retryModel === undefined || typeof record.retryModel === \"string\") &&\n\t\t(record.intent === undefined || record.intent === \"research\" || record.intent === \"modify\")\n\t);\n}\n\nfunction formatDecision(decision: ModelRouterDecisionStatus): string {\n\tconst { tier, risk, reasonCode } = decision.route;\n\tlet outcomeText: string = decision.outcome;\n\tif (decision.outcome === \"escalated\" && decision.retryModel) {\n\t\toutcomeText = `escalated -> ${decision.retryModel}`;\n\t} else if (decision.outcome === \"failed\") {\n\t\toutcomeText = \"failed\";\n\t}\n\treturn `${tier}/${risk} -> ${decision.routedModel} (${reasonCode}, ${outcomeText})`;\n}\n\nexport function getRecentModelRouterDecisions(entries: SessionEntry[], limit = 3): ModelRouterDecisionStatus[] {\n\tconst decisions: ModelRouterDecisionStatus[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== MODEL_ROUTER_DECISION_CUSTOM_TYPE) continue;\n\t\tif (!isModelRouterDecisionStatus(entry.data)) continue;\n\t\tdecisions.push(entry.data);\n\t}\n\treturn decisions.slice(-limit);\n}\n\nexport function formatModelRouterStatus(\n\tsettings: ModelRouterStatusSettings,\n\tlastDecision?: ModelRouterDecisionStatus,\n\tformatLabel: (label: string) => string = (label) => label,\n\trecentDecisions: ModelRouterDecisionStatus[] = [],\n\tlastSkipReason?: string,\n\tlatestIntent?: ModelRouterIntent,\n): string {\n\tconst effectiveLastDecision = lastSkipReason ? undefined : lastDecision;\n\tconst lines = [\n\t\t`${formatLabel(\"Status:\")} ${settings.enabled ? \"enabled\" : \"disabled\"}`,\n\t\t`${formatLabel(\"Cheap model:\")} ${settings.cheapModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Medium model:\")} ${settings.mediumModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Expensive model:\")} ${settings.expensiveModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Learning model:\")} ${settings.learningModel ?? \"active\"}`,\n\t];\n\tif (!settings.enabled) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} inactive (disabled)`);\n\t} else if (lastSkipReason) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} skipped (${lastSkipReason})`);\n\t} else if (effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} active`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Routing:\")} waiting for prompt`);\n\t}\n\tlines.push(`${formatLabel(\"Latest intent:\")} ${latestIntent ?? \"none\"}`);\n\tif (!effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} none`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} ${formatDecision(effectiveLastDecision)}`);\n\t}\n\tif (recentDecisions.length > 0) {\n\t\tlines.push(formatLabel(\"Recent decisions:\"));\n\t\tfor (const decision of recentDecisions) {\n\t\t\tlines.push(`- ${formatDecision(decision)}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
export const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
2
|
+
function isRouteDecision(value) {
|
|
3
|
+
if (!value || typeof value !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
const route = value;
|
|
6
|
+
const validTiers = new Set(["cheap", "medium", "expensive", "learning"]);
|
|
7
|
+
const validRisks = new Set(["read-only", "scoped-write", "high-impact", "approval-required"]);
|
|
8
|
+
return (typeof route.confidence === "number" &&
|
|
9
|
+
typeof route.reasonCode === "string" &&
|
|
10
|
+
Array.isArray(route.reasons) &&
|
|
11
|
+
route.reasons.every((r) => typeof r === "string") &&
|
|
12
|
+
validTiers.has(route.tier) &&
|
|
13
|
+
validRisks.has(route.risk) &&
|
|
14
|
+
(route.model === undefined || typeof route.model === "string") &&
|
|
15
|
+
(route.fallbackFrom === undefined ||
|
|
16
|
+
route.fallbackFrom === "cheap" ||
|
|
17
|
+
route.fallbackFrom === "medium" ||
|
|
18
|
+
route.fallbackFrom === "expensive" ||
|
|
19
|
+
route.fallbackFrom === "learning") &&
|
|
20
|
+
(route.createdAt === undefined || typeof route.createdAt === "string"));
|
|
21
|
+
}
|
|
2
22
|
function isModelRouterDecisionStatus(data) {
|
|
3
23
|
if (!data || typeof data !== "object")
|
|
4
24
|
return false;
|
|
5
25
|
const record = data;
|
|
6
|
-
return ((record.
|
|
26
|
+
return (isRouteDecision(record.route) &&
|
|
27
|
+
record.route.tier !== "learning" && // Validate user prompt decisions never need learning tier in runtime
|
|
7
28
|
typeof record.routedModel === "string" &&
|
|
8
29
|
(record.outcome === "routed" || record.outcome === "escalated" || record.outcome === "failed") &&
|
|
9
|
-
(record.retryModel === undefined || typeof record.retryModel === "string")
|
|
30
|
+
(record.retryModel === undefined || typeof record.retryModel === "string") &&
|
|
31
|
+
(record.intent === undefined || record.intent === "research" || record.intent === "modify"));
|
|
10
32
|
}
|
|
11
33
|
function formatDecision(decision) {
|
|
12
|
-
|
|
34
|
+
const { tier, risk, reasonCode } = decision.route;
|
|
35
|
+
let outcomeText = decision.outcome;
|
|
13
36
|
if (decision.outcome === "escalated" && decision.retryModel) {
|
|
14
|
-
|
|
37
|
+
outcomeText = `escalated -> ${decision.retryModel}`;
|
|
15
38
|
}
|
|
16
39
|
else if (decision.outcome === "failed") {
|
|
17
|
-
|
|
40
|
+
outcomeText = "failed";
|
|
18
41
|
}
|
|
19
|
-
return
|
|
42
|
+
return `${tier}/${risk} -> ${decision.routedModel} (${reasonCode}, ${outcomeText})`;
|
|
20
43
|
}
|
|
21
44
|
export function getRecentModelRouterDecisions(entries, limit = 3) {
|
|
22
45
|
const decisions = [];
|
|
@@ -34,6 +57,7 @@ export function formatModelRouterStatus(settings, lastDecision, formatLabel = (l
|
|
|
34
57
|
const lines = [
|
|
35
58
|
`${formatLabel("Status:")} ${settings.enabled ? "enabled" : "disabled"}`,
|
|
36
59
|
`${formatLabel("Cheap model:")} ${settings.cheapModel ?? "unset"}`,
|
|
60
|
+
`${formatLabel("Medium model:")} ${settings.mediumModel ?? "unset"}`,
|
|
37
61
|
`${formatLabel("Expensive model:")} ${settings.expensiveModel ?? "unset"}`,
|
|
38
62
|
`${formatLabel("Learning model:")} ${settings.learningModel ?? "active"}`,
|
|
39
63
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ModelTier, RouteDecision, RouteRisk } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry } from \"../session-manager.ts\";\nimport type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nexport const MODEL_ROUTER_DECISION_CUSTOM_TYPE = \"model_router_decision\";\n\nexport type ModelRouterStatusSettings = {\n\tenabled: boolean;\n\tcheapModel?: string;\n\tmediumModel?: string;\n\texpensiveModel?: string;\n\tlearningModel?: string;\n};\n\nexport type ModelRouterDecisionStatus = {\n\troute: RouteDecision;\n\troutedModel: string;\n\toutcome: \"routed\" | \"escalated\" | \"failed\";\n\tretryModel?: string;\n\tintent?: ModelRouterIntent;\n};\n\nfunction isRouteDecision(value: unknown): value is RouteDecision {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst route = value as Partial<RouteDecision>;\n\tconst validTiers = new Set<ModelTier>([\"cheap\", \"medium\", \"expensive\", \"learning\"]);\n\tconst validRisks = new Set<RouteRisk>([\"read-only\", \"scoped-write\", \"high-impact\", \"approval-required\"]);\n\n\treturn (\n\t\ttypeof route.confidence === \"number\" &&\n\t\ttypeof route.reasonCode === \"string\" &&\n\t\tArray.isArray(route.reasons) &&\n\t\troute.reasons.every((r) => typeof r === \"string\") &&\n\t\tvalidTiers.has(route.tier as ModelTier) &&\n\t\tvalidRisks.has(route.risk as RouteRisk) &&\n\t\t(route.model === undefined || typeof route.model === \"string\") &&\n\t\t(route.fallbackFrom === undefined ||\n\t\t\troute.fallbackFrom === \"cheap\" ||\n\t\t\troute.fallbackFrom === \"medium\" ||\n\t\t\troute.fallbackFrom === \"expensive\" ||\n\t\t\troute.fallbackFrom === \"learning\") &&\n\t\t(route.createdAt === undefined || typeof route.createdAt === \"string\")\n\t);\n}\n\nfunction isModelRouterDecisionStatus(data: unknown): data is ModelRouterDecisionStatus {\n\tif (!data || typeof data !== \"object\") return false;\n\tconst record = data as Partial<ModelRouterDecisionStatus>;\n\treturn (\n\t\tisRouteDecision(record.route) &&\n\t\trecord.route.tier !== \"learning\" && // Validate user prompt decisions never need learning tier in runtime\n\t\ttypeof record.routedModel === \"string\" &&\n\t\t(record.outcome === \"routed\" || record.outcome === \"escalated\" || record.outcome === \"failed\") &&\n\t\t(record.retryModel === undefined || typeof record.retryModel === \"string\") &&\n\t\t(record.intent === undefined || record.intent === \"research\" || record.intent === \"modify\")\n\t);\n}\n\nfunction formatDecision(decision: ModelRouterDecisionStatus): string {\n\tconst { tier, risk, reasonCode } = decision.route;\n\tlet outcomeText: string = decision.outcome;\n\tif (decision.outcome === \"escalated\" && decision.retryModel) {\n\t\toutcomeText = `escalated -> ${decision.retryModel}`;\n\t} else if (decision.outcome === \"failed\") {\n\t\toutcomeText = \"failed\";\n\t}\n\treturn `${tier}/${risk} -> ${decision.routedModel} (${reasonCode}, ${outcomeText})`;\n}\n\nexport function getRecentModelRouterDecisions(entries: SessionEntry[], limit = 3): ModelRouterDecisionStatus[] {\n\tconst decisions: ModelRouterDecisionStatus[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== MODEL_ROUTER_DECISION_CUSTOM_TYPE) continue;\n\t\tif (!isModelRouterDecisionStatus(entry.data)) continue;\n\t\tdecisions.push(entry.data);\n\t}\n\treturn decisions.slice(-limit);\n}\n\nexport function formatModelRouterStatus(\n\tsettings: ModelRouterStatusSettings,\n\tlastDecision?: ModelRouterDecisionStatus,\n\tformatLabel: (label: string) => string = (label) => label,\n\trecentDecisions: ModelRouterDecisionStatus[] = [],\n\tlastSkipReason?: string,\n\tlatestIntent?: ModelRouterIntent,\n): string {\n\tconst effectiveLastDecision = lastSkipReason ? undefined : lastDecision;\n\tconst lines = [\n\t\t`${formatLabel(\"Status:\")} ${settings.enabled ? \"enabled\" : \"disabled\"}`,\n\t\t`${formatLabel(\"Cheap model:\")} ${settings.cheapModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Medium model:\")} ${settings.mediumModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Expensive model:\")} ${settings.expensiveModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Learning model:\")} ${settings.learningModel ?? \"active\"}`,\n\t];\n\tif (!settings.enabled) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} inactive (disabled)`);\n\t} else if (lastSkipReason) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} skipped (${lastSkipReason})`);\n\t} else if (effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} active`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Routing:\")} waiting for prompt`);\n\t}\n\tlines.push(`${formatLabel(\"Latest intent:\")} ${latestIntent ?? \"none\"}`);\n\tif (!effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} none`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} ${formatDecision(effectiveLastDecision)}`);\n\t}\n\tif (recentDecisions.length > 0) {\n\t\tlines.push(formatLabel(\"Recent decisions:\"));\n\t\tfor (const decision of recentDecisions) {\n\t\t\tlines.push(`- ${formatDecision(decision)}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { ModelTier } from "../autonomy/contracts.ts";
|
|
2
|
+
export declare function shouldEscalateModelRouterTool(options: {
|
|
3
|
+
tier: ModelTier;
|
|
4
4
|
toolName: string;
|
|
5
5
|
args?: unknown;
|
|
6
|
-
};
|
|
7
|
-
export declare function shouldEscalateModelRouterTool(options: ToolEscalationOptions): boolean;
|
|
8
|
-
export {};
|
|
6
|
+
}): boolean;
|
|
9
7
|
//# sourceMappingURL=tool-escalation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-escalation.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"tool-escalation.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA2F1D,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAUrH","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command)) return false;\n\tconst segments = command.split(/\\s*&&\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every(isReadOnlyShellSegment);\n}\n\nexport function shouldEscalateModelRouterTool(options: { tier: ModelTier; toolName: string; args?: unknown }): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
|
|
@@ -81,7 +81,7 @@ function isReadOnlyShellCommand(command) {
|
|
|
81
81
|
return segments.length > 0 && segments.every(isReadOnlyShellSegment);
|
|
82
82
|
}
|
|
83
83
|
export function shouldEscalateModelRouterTool(options) {
|
|
84
|
-
if (options.
|
|
84
|
+
if (options.tier !== "cheap")
|
|
85
85
|
return false;
|
|
86
86
|
const toolName = options.toolName.trim().toLowerCase();
|
|
87
87
|
if (!toolName)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-escalation.js","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACpC,MAAM;IACN,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,WAAW;IACX,UAAU;IACV,aAAa;IACb,YAAY;IACZ,kBAAkB;IAClB,mBAAmB;CACnB,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7F,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,IAAI;IACJ,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,IAAI;IACJ,OAAO;IACP,MAAM;CACN,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3G,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAChG,MAAM,uBAAuB,GAC5B,qUAAqU,CAAC;AACvU,MAAM,qBAAqB,GAC1B,+IAA+I,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-escalation.js","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACpC,MAAM;IACN,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,WAAW;IACX,UAAU;IACV,aAAa;IACb,YAAY;IACZ,kBAAkB;IAClB,mBAAmB;CACnB,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7F,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,IAAI;IACJ,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,IAAI;IACJ,OAAO;IACP,MAAM;CACN,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3G,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAChG,MAAM,uBAAuB,GAC5B,qUAAqU,CAAC;AACvU,MAAM,qBAAqB,GAC1B,+IAA+I,CAAC;AAEjJ,SAAS,eAAe,CAAC,IAAa,EAAsB;IAC3D,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC;IACpE,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAChE;AAED,SAAS,WAAW,CAAC,OAAe,EAAsB;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;AAAA,CAC9C;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,KAAa,EAAsB;IACvE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;AAAA,CACzD;AAED,SAAS,sBAAsB,CAAC,OAAe,EAAW;IACzD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1D,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,sBAAsB,CAAC,OAAe,EAAW;IACzD,IAAI,CAAC,OAAO,IAAI,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAAA,CACrE;AAED,MAAM,UAAU,6BAA6B,CAAC,OAA8D,EAAW;IACtH,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IACD,OAAO,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAAA,CAC7E","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command)) return false;\n\tconst segments = command.split(/\\s*&&\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every(isReadOnlyShellSegment);\n}\n\nexport function shouldEscalateModelRouterTool(options: { tier: ModelTier; toolName: string; args?: unknown }): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ModelFitnessReport } from "../research/model-fitness.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Durable, HOST-KEYED storage for model fitness reports. Fitness is a property of a model ON a
|
|
4
|
+
* host (tok/s and latency-driven failures do not travel between machines), so reports are keyed
|
|
5
|
+
* by a hardware fingerprint: the same model can be "the heavy lifter" on one machine and
|
|
6
|
+
* "waiting for better hardware" on another, and pi remembers both without confusing them —
|
|
7
|
+
* including when settings/dotfiles are synced across machines.
|
|
8
|
+
*/
|
|
9
|
+
export interface HostFingerprint {
|
|
10
|
+
/** Stable, human-readable id derived from the specs below. */
|
|
11
|
+
id: string;
|
|
12
|
+
cpu: string;
|
|
13
|
+
cores: number;
|
|
14
|
+
totalMemGb: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function currentHostFingerprint(): HostFingerprint;
|
|
17
|
+
export interface StoredFitnessReport {
|
|
18
|
+
model: string;
|
|
19
|
+
report: ModelFitnessReport;
|
|
20
|
+
at: string;
|
|
21
|
+
host: HostFingerprint;
|
|
22
|
+
}
|
|
23
|
+
export declare class FitnessStore {
|
|
24
|
+
private readonly filePath;
|
|
25
|
+
private readonly fingerprint;
|
|
26
|
+
constructor(filePath: string, options?: {
|
|
27
|
+
fingerprint?: () => HostFingerprint;
|
|
28
|
+
});
|
|
29
|
+
static forAgentDir(agentDir: string, options?: {
|
|
30
|
+
fingerprint?: () => HostFingerprint;
|
|
31
|
+
}): FitnessStore;
|
|
32
|
+
private load;
|
|
33
|
+
/** Persist the latest report for a model on the CURRENT host. Best-effort, returns the entry. */
|
|
34
|
+
save(model: string, report: ModelFitnessReport, at?: string): StoredFitnessReport;
|
|
35
|
+
/** Reports for the current host (default) or an explicit host id. */
|
|
36
|
+
getForHost(hostId?: string): StoredFitnessReport[];
|
|
37
|
+
/** Every stored report across all hosts (for cross-machine comparisons). */
|
|
38
|
+
getAll(): StoredFitnessReport[];
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=fitness-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fitness-store.d.ts","sourceRoot":"","sources":["../../../src/core/models/fitness-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC/B,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,sBAAsB,IAAI,eAAe,CAWxD;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;CACtB;AAQD,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;IAEpD,YAAY,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,eAAe,CAAA;KAAE,EAG9E;IAED,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,eAAe,CAAA;KAAE,GAAG,YAAY,CAEpG;IAED,OAAO,CAAC,IAAI;IAaZ,iGAAiG;IACjG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAQhF;IAED,qEAAqE;IACrE,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAGjD;IAED,4EAA4E;IAC5E,MAAM,IAAI,mBAAmB,EAAE,CAG9B;CACD","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { cpus, totalmem } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { ModelFitnessReport } from \"../research/model-fitness.ts\";\n\n/**\n * Durable, HOST-KEYED storage for model fitness reports. Fitness is a property of a model ON a\n * host (tok/s and latency-driven failures do not travel between machines), so reports are keyed\n * by a hardware fingerprint: the same model can be \"the heavy lifter\" on one machine and\n * \"waiting for better hardware\" on another, and pi remembers both without confusing them —\n * including when settings/dotfiles are synced across machines.\n */\n\nexport interface HostFingerprint {\n\t/** Stable, human-readable id derived from the specs below. */\n\tid: string;\n\tcpu: string;\n\tcores: number;\n\ttotalMemGb: number;\n}\n\nexport function currentHostFingerprint(): HostFingerprint {\n\tconst cpuList = cpus();\n\tconst cpu = (cpuList[0]?.model ?? \"unknown-cpu\").trim();\n\tconst cores = cpuList.length;\n\tconst totalMemGb = Math.round(totalmem() / 1024 ** 3);\n\tconst id = `${cpu\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 48)}-${cores}c-${totalMemGb}g`;\n\treturn { id, cpu, cores, totalMemGb };\n}\n\nexport interface StoredFitnessReport {\n\tmodel: string;\n\treport: ModelFitnessReport;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface FitnessStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored report. */\n\thosts: Record<string, Record<string, StoredFitnessReport>>;\n}\n\nexport class FitnessStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): FitnessStore {\n\t\treturn new FitnessStore(join(agentDir, \"state\", \"model-fitness.json\"), options);\n\t}\n\n\tprivate load(): FitnessStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: 1, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as FitnessStoreFile;\n\t\t\tif (parsed && parsed.version === 1 && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: 1, hosts: {} };\n\t}\n\n\t/** Persist the latest report for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, report: ModelFitnessReport, at?: string): StoredFitnessReport {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredFitnessReport = { model, report, at: at ?? new Date().toISOString(), host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t\treturn entry;\n\t}\n\n\t/** Reports for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredFitnessReport[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {});\n\t}\n\n\t/** Every stored report across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredFitnessReport[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) => Object.values(models));\n\t}\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { cpus, totalmem } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function currentHostFingerprint() {
|
|
5
|
+
const cpuList = cpus();
|
|
6
|
+
const cpu = (cpuList[0]?.model ?? "unknown-cpu").trim();
|
|
7
|
+
const cores = cpuList.length;
|
|
8
|
+
const totalMemGb = Math.round(totalmem() / 1024 ** 3);
|
|
9
|
+
const id = `${cpu
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
12
|
+
.replace(/^-+|-+$/g, "")
|
|
13
|
+
.slice(0, 48)}-${cores}c-${totalMemGb}g`;
|
|
14
|
+
return { id, cpu, cores, totalMemGb };
|
|
15
|
+
}
|
|
16
|
+
export class FitnessStore {
|
|
17
|
+
filePath;
|
|
18
|
+
fingerprint;
|
|
19
|
+
constructor(filePath, options) {
|
|
20
|
+
this.filePath = filePath;
|
|
21
|
+
this.fingerprint = options?.fingerprint ?? currentHostFingerprint;
|
|
22
|
+
}
|
|
23
|
+
static forAgentDir(agentDir, options) {
|
|
24
|
+
return new FitnessStore(join(agentDir, "state", "model-fitness.json"), options);
|
|
25
|
+
}
|
|
26
|
+
load() {
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(this.filePath))
|
|
29
|
+
return { version: 1, hosts: {} };
|
|
30
|
+
const parsed = JSON.parse(readFileSync(this.filePath, "utf-8"));
|
|
31
|
+
if (parsed && parsed.version === 1 && parsed.hosts && typeof parsed.hosts === "object") {
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.
|
|
37
|
+
}
|
|
38
|
+
return { version: 1, hosts: {} };
|
|
39
|
+
}
|
|
40
|
+
/** Persist the latest report for a model on the CURRENT host. Best-effort, returns the entry. */
|
|
41
|
+
save(model, report, at) {
|
|
42
|
+
const host = this.fingerprint();
|
|
43
|
+
const entry = { model, report, at: at ?? new Date().toISOString(), host };
|
|
44
|
+
const file = this.load();
|
|
45
|
+
file.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };
|
|
46
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
47
|
+
writeFileSync(this.filePath, `${JSON.stringify(file, null, "\t")}\n`, "utf-8");
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
/** Reports for the current host (default) or an explicit host id. */
|
|
51
|
+
getForHost(hostId) {
|
|
52
|
+
const file = this.load();
|
|
53
|
+
return Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {});
|
|
54
|
+
}
|
|
55
|
+
/** Every stored report across all hosts (for cross-machine comparisons). */
|
|
56
|
+
getAll() {
|
|
57
|
+
const file = this.load();
|
|
58
|
+
return Object.values(file.hosts).flatMap((models) => Object.values(models));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=fitness-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fitness-store.js","sourceRoot":"","sources":["../../../src/core/models/fitness-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { cpus, totalmem } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { ModelFitnessReport } from \"../research/model-fitness.ts\";\n\n/**\n * Durable, HOST-KEYED storage for model fitness reports. Fitness is a property of a model ON a\n * host (tok/s and latency-driven failures do not travel between machines), so reports are keyed\n * by a hardware fingerprint: the same model can be \"the heavy lifter\" on one machine and\n * \"waiting for better hardware\" on another, and pi remembers both without confusing them —\n * including when settings/dotfiles are synced across machines.\n */\n\nexport interface HostFingerprint {\n\t/** Stable, human-readable id derived from the specs below. */\n\tid: string;\n\tcpu: string;\n\tcores: number;\n\ttotalMemGb: number;\n}\n\nexport function currentHostFingerprint(): HostFingerprint {\n\tconst cpuList = cpus();\n\tconst cpu = (cpuList[0]?.model ?? \"unknown-cpu\").trim();\n\tconst cores = cpuList.length;\n\tconst totalMemGb = Math.round(totalmem() / 1024 ** 3);\n\tconst id = `${cpu\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 48)}-${cores}c-${totalMemGb}g`;\n\treturn { id, cpu, cores, totalMemGb };\n}\n\nexport interface StoredFitnessReport {\n\tmodel: string;\n\treport: ModelFitnessReport;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface FitnessStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored report. */\n\thosts: Record<string, Record<string, StoredFitnessReport>>;\n}\n\nexport class FitnessStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): FitnessStore {\n\t\treturn new FitnessStore(join(agentDir, \"state\", \"model-fitness.json\"), options);\n\t}\n\n\tprivate load(): FitnessStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: 1, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as FitnessStoreFile;\n\t\t\tif (parsed && parsed.version === 1 && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: 1, hosts: {} };\n\t}\n\n\t/** Persist the latest report for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, report: ModelFitnessReport, at?: string): StoredFitnessReport {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredFitnessReport = { model, report, at: at ?? new Date().toISOString(), host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t\treturn entry;\n\t}\n\n\t/** Reports for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredFitnessReport[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {});\n\t}\n\n\t/** Every stored report across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredFitnessReport[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) => Object.values(models));\n\t}\n}\n"]}
|