@coool/opencode-adaptive 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +99 -2
- package/dist/adapters/codex-capacity/index.d.ts +4 -0
- package/dist/adapters/codex-capacity/index.js +5 -0
- package/dist/adapters/codex-capacity/index.js.map +1 -0
- package/dist/adapters/codex-capacity/parse.d.ts +28 -0
- package/dist/adapters/codex-capacity/parse.js +146 -0
- package/dist/adapters/codex-capacity/parse.js.map +1 -0
- package/dist/adapters/codex-capacity/reading.d.ts +9 -0
- package/dist/adapters/codex-capacity/reading.js +45 -0
- package/dist/adapters/codex-capacity/reading.js.map +1 -0
- package/dist/adapters/codex-capacity/sensor.d.ts +23 -0
- package/dist/adapters/codex-capacity/sensor.js +30 -0
- package/dist/adapters/codex-capacity/sensor.js.map +1 -0
- package/dist/adapters/codex-capacity/types.d.ts +31 -0
- package/dist/adapters/codex-capacity/types.js +18 -0
- package/dist/adapters/codex-capacity/types.js.map +1 -0
- package/dist/adapters/opencode/escalation-tool.d.ts +51 -0
- package/dist/adapters/opencode/escalation-tool.js +61 -0
- package/dist/adapters/opencode/escalation-tool.js.map +1 -0
- package/dist/adapters/opencode/events.d.ts +23 -0
- package/dist/adapters/opencode/events.js +68 -0
- package/dist/adapters/opencode/events.js.map +1 -0
- package/dist/adapters/opencode/hooks.d.ts +34 -0
- package/dist/adapters/opencode/hooks.js +29 -0
- package/dist/adapters/opencode/hooks.js.map +1 -0
- package/dist/adapters/opencode/index.d.ts +16 -0
- package/dist/adapters/opencode/index.js +9 -0
- package/dist/adapters/opencode/index.js.map +1 -0
- package/dist/adapters/opencode/inventory.d.ts +30 -0
- package/dist/adapters/opencode/inventory.js +53 -0
- package/dist/adapters/opencode/inventory.js.map +1 -0
- package/dist/adapters/opencode/report.d.ts +91 -0
- package/dist/adapters/opencode/report.js +145 -0
- package/dist/adapters/opencode/report.js.map +1 -0
- package/dist/adapters/opencode/session-ops.d.ts +39 -0
- package/dist/adapters/opencode/session-ops.js +32 -0
- package/dist/adapters/opencode/session-ops.js.map +1 -0
- package/dist/adapters/opencode/storage.d.ts +15 -0
- package/dist/adapters/opencode/storage.js +8 -0
- package/dist/adapters/opencode/storage.js.map +1 -0
- package/dist/adapters/opencode/tier-tool.d.ts +37 -0
- package/dist/adapters/opencode/tier-tool.js +72 -0
- package/dist/adapters/opencode/tier-tool.js.map +1 -0
- package/dist/adapters/opencode/types.d.ts +101 -0
- package/dist/adapters/opencode/types.js +2 -0
- package/dist/adapters/opencode/types.js.map +1 -0
- package/dist/core/compatibility/gate.d.ts +7 -0
- package/dist/core/compatibility/gate.js +51 -0
- package/dist/core/compatibility/gate.js.map +1 -0
- package/dist/core/compatibility/index.d.ts +2 -0
- package/dist/core/compatibility/index.js +3 -0
- package/dist/core/compatibility/index.js.map +1 -0
- package/dist/core/compatibility/types.d.ts +53 -0
- package/dist/core/compatibility/types.js +27 -0
- package/dist/core/compatibility/types.js.map +1 -0
- package/dist/core/scheduler/admit.d.ts +6 -0
- package/dist/core/scheduler/admit.js +154 -0
- package/dist/core/scheduler/admit.js.map +1 -0
- package/dist/core/scheduler/escalate.d.ts +6 -0
- package/dist/core/scheduler/escalate.js +45 -0
- package/dist/core/scheduler/escalate.js.map +1 -0
- package/dist/core/scheduler/index.d.ts +4 -0
- package/dist/core/scheduler/index.js +5 -0
- package/dist/core/scheduler/index.js.map +1 -0
- package/dist/core/scheduler/mode.d.ts +8 -0
- package/dist/core/scheduler/mode.js +10 -0
- package/dist/core/scheduler/mode.js.map +1 -0
- package/dist/core/scheduler/types.d.ts +136 -0
- package/dist/core/scheduler/types.js +75 -0
- package/dist/core/scheduler/types.js.map +1 -0
- package/dist/core/tier-policy/index.d.ts +3 -0
- package/dist/core/tier-policy/index.js +4 -0
- package/dist/core/tier-policy/index.js.map +1 -0
- package/dist/core/tier-policy/resolve.d.ts +18 -0
- package/dist/core/tier-policy/resolve.js +52 -0
- package/dist/core/tier-policy/resolve.js.map +1 -0
- package/dist/core/tier-policy/tier-list.d.ts +10 -0
- package/dist/core/tier-policy/tier-list.js +23 -0
- package/dist/core/tier-policy/tier-list.js.map +1 -0
- package/dist/core/tier-policy/types.d.ts +86 -0
- package/dist/core/tier-policy/types.js +49 -0
- package/dist/core/tier-policy/types.js.map +1 -0
- package/dist/core/work-unit/index.d.ts +3 -0
- package/dist/core/work-unit/index.js +4 -0
- package/dist/core/work-unit/index.js.map +1 -0
- package/dist/core/work-unit/interruption.d.ts +7 -0
- package/dist/core/work-unit/interruption.js +12 -0
- package/dist/core/work-unit/interruption.js.map +1 -0
- package/dist/core/work-unit/tracker.d.ts +53 -0
- package/dist/core/work-unit/tracker.js +156 -0
- package/dist/core/work-unit/tracker.js.map +1 -0
- package/dist/core/work-unit/types.d.ts +151 -0
- package/dist/core/work-unit/types.js +23 -0
- package/dist/core/work-unit/types.js.map +1 -0
- package/dist/execution/continuation/execute.d.ts +18 -0
- package/dist/execution/continuation/execute.js +31 -0
- package/dist/execution/continuation/execute.js.map +1 -0
- package/dist/execution/continuation/index.d.ts +4 -0
- package/dist/execution/continuation/index.js +4 -0
- package/dist/execution/continuation/index.js.map +1 -0
- package/dist/execution/continuation/plan.d.ts +12 -0
- package/dist/execution/continuation/plan.js +18 -0
- package/dist/execution/continuation/plan.js.map +1 -0
- package/dist/execution/continuation/prompts.d.ts +7 -0
- package/dist/execution/continuation/prompts.js +8 -0
- package/dist/execution/continuation/prompts.js.map +1 -0
- package/dist/execution/continuation/types.d.ts +68 -0
- package/dist/execution/continuation/types.js +15 -0
- package/dist/execution/continuation/types.js.map +1 -0
- package/dist/execution/escalation/index.d.ts +1 -0
- package/dist/execution/escalation/index.js +2 -0
- package/dist/execution/escalation/index.js.map +1 -0
- package/dist/execution/escalation/types.d.ts +30 -0
- package/dist/execution/escalation/types.js +49 -0
- package/dist/execution/escalation/types.js.map +1 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +5 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/parking/index.d.ts +2 -0
- package/dist/execution/parking/index.js +2 -0
- package/dist/execution/parking/index.js.map +1 -0
- package/dist/execution/parking/plan.d.ts +39 -0
- package/dist/execution/parking/plan.js +34 -0
- package/dist/execution/parking/plan.js.map +1 -0
- package/dist/execution/retry/decide.d.ts +29 -0
- package/dist/execution/retry/decide.js +75 -0
- package/dist/execution/retry/decide.js.map +1 -0
- package/dist/execution/retry/index.d.ts +2 -0
- package/dist/execution/retry/index.js +3 -0
- package/dist/execution/retry/index.js.map +1 -0
- package/dist/execution/retry/types.d.ts +64 -0
- package/dist/execution/retry/types.js +9 -0
- package/dist/execution/retry/types.js.map +1 -0
- package/dist/gui-experimental.cjs +1 -0
- package/dist/gui.d.ts +16 -0
- package/dist/gui.js +73 -0
- package/dist/gui.js.map +1 -0
- package/dist/health/capacity/index.d.ts +3 -0
- package/dist/health/capacity/index.js +4 -0
- package/dist/health/capacity/index.js.map +1 -0
- package/dist/health/capacity/merge.d.ts +57 -0
- package/dist/health/capacity/merge.js +154 -0
- package/dist/health/capacity/merge.js.map +1 -0
- package/dist/health/capacity/registry.d.ts +40 -0
- package/dist/health/capacity/registry.js +106 -0
- package/dist/health/capacity/registry.js.map +1 -0
- package/dist/health/capacity/types.d.ts +73 -0
- package/dist/health/capacity/types.js +6 -0
- package/dist/health/capacity/types.js.map +1 -0
- package/dist/health/circuit-breaker/breaker.d.ts +91 -0
- package/dist/health/circuit-breaker/breaker.js +150 -0
- package/dist/health/circuit-breaker/breaker.js.map +1 -0
- package/dist/health/circuit-breaker/index.d.ts +2 -0
- package/dist/health/circuit-breaker/index.js +3 -0
- package/dist/health/circuit-breaker/index.js.map +1 -0
- package/dist/health/circuit-breaker/types.d.ts +52 -0
- package/dist/health/circuit-breaker/types.js +13 -0
- package/dist/health/circuit-breaker/types.js.map +1 -0
- package/dist/health/classifier/classify.d.ts +8 -0
- package/dist/health/classifier/classify.js +245 -0
- package/dist/health/classifier/classify.js.map +1 -0
- package/dist/health/classifier/index.d.ts +2 -0
- package/dist/health/classifier/index.js +3 -0
- package/dist/health/classifier/index.js.map +1 -0
- package/dist/health/classifier/types.d.ts +71 -0
- package/dist/health/classifier/types.js +27 -0
- package/dist/health/classifier/types.js.map +1 -0
- package/dist/health/index.d.ts +4 -0
- package/dist/health/index.js +5 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/resource-health/index.d.ts +4 -0
- package/dist/health/resource-health/index.js +5 -0
- package/dist/health/resource-health/index.js.map +1 -0
- package/dist/health/resource-health/key.d.ts +18 -0
- package/dist/health/resource-health/key.js +54 -0
- package/dist/health/resource-health/key.js.map +1 -0
- package/dist/health/resource-health/registry.d.ts +46 -0
- package/dist/health/resource-health/registry.js +276 -0
- package/dist/health/resource-health/registry.js.map +1 -0
- package/dist/health/resource-health/transitions.d.ts +9 -0
- package/dist/health/resource-health/transitions.js +157 -0
- package/dist/health/resource-health/transitions.js.map +1 -0
- package/dist/health/resource-health/types.d.ts +122 -0
- package/dist/health/resource-health/types.js +44 -0
- package/dist/health/resource-health/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-id.d.ts +2 -0
- package/dist/plugin-id.js +3 -0
- package/dist/plugin-id.js.map +1 -0
- package/dist/rpc/definition.d.ts +123 -0
- package/dist/rpc/definition.js +130 -0
- package/dist/rpc/definition.js.map +1 -0
- package/dist/rpc/handlers.d.ts +53 -0
- package/dist/rpc/handlers.js +125 -0
- package/dist/rpc/handlers.js.map +1 -0
- package/dist/rpc/status.d.ts +42 -0
- package/dist/rpc/status.js +45 -0
- package/dist/rpc/status.js.map +1 -0
- package/dist/rpc.d.ts +10 -0
- package/dist/rpc.js +8 -0
- package/dist/rpc.js.map +1 -0
- package/dist/state/capacity/index.d.ts +3 -0
- package/dist/state/capacity/index.js +4 -0
- package/dist/state/capacity/index.js.map +1 -0
- package/dist/state/capacity/parse.d.ts +2 -0
- package/dist/state/capacity/parse.js +122 -0
- package/dist/state/capacity/parse.js.map +1 -0
- package/dist/state/capacity/store.d.ts +18 -0
- package/dist/state/capacity/store.js +78 -0
- package/dist/state/capacity/store.js.map +1 -0
- package/dist/state/capacity/types.d.ts +37 -0
- package/dist/state/capacity/types.js +11 -0
- package/dist/state/capacity/types.js.map +1 -0
- package/dist/state/health/index.d.ts +4 -0
- package/dist/state/health/index.js +4 -0
- package/dist/state/health/index.js.map +1 -0
- package/dist/state/health/parse.d.ts +2 -0
- package/dist/state/health/parse.js +137 -0
- package/dist/state/health/parse.js.map +1 -0
- package/dist/state/health/store.d.ts +17 -0
- package/dist/state/health/store.js +69 -0
- package/dist/state/health/store.js.map +1 -0
- package/dist/state/health/types.d.ts +23 -0
- package/dist/state/health/types.js +5 -0
- package/dist/state/health/types.js.map +1 -0
- package/dist/state/parked-work/index.d.ts +4 -0
- package/dist/state/parked-work/index.js +4 -0
- package/dist/state/parked-work/index.js.map +1 -0
- package/dist/state/parked-work/parse.d.ts +6 -0
- package/dist/state/parked-work/parse.js +83 -0
- package/dist/state/parked-work/parse.js.map +1 -0
- package/dist/state/parked-work/store.d.ts +17 -0
- package/dist/state/parked-work/store.js +98 -0
- package/dist/state/parked-work/store.js.map +1 -0
- package/dist/state/parked-work/types.d.ts +41 -0
- package/dist/state/parked-work/types.js +7 -0
- package/dist/state/parked-work/types.js.map +1 -0
- package/dist/state/receipts/bound.d.ts +14 -0
- package/dist/state/receipts/bound.js +77 -0
- package/dist/state/receipts/bound.js.map +1 -0
- package/dist/state/receipts/index.d.ts +6 -0
- package/dist/state/receipts/index.js +5 -0
- package/dist/state/receipts/index.js.map +1 -0
- package/dist/state/receipts/parse.d.ts +6 -0
- package/dist/state/receipts/parse.js +226 -0
- package/dist/state/receipts/parse.js.map +1 -0
- package/dist/state/receipts/store.d.ts +34 -0
- package/dist/state/receipts/store.js +148 -0
- package/dist/state/receipts/store.js.map +1 -0
- package/dist/state/receipts/types.d.ts +61 -0
- package/dist/state/receipts/types.js +12 -0
- package/dist/state/receipts/types.js.map +1 -0
- package/dist/state/session-modes/index.d.ts +4 -0
- package/dist/state/session-modes/index.js +4 -0
- package/dist/state/session-modes/index.js.map +1 -0
- package/dist/state/session-modes/parse.d.ts +8 -0
- package/dist/state/session-modes/parse.js +62 -0
- package/dist/state/session-modes/parse.js.map +1 -0
- package/dist/state/session-modes/store.d.ts +20 -0
- package/dist/state/session-modes/store.js +87 -0
- package/dist/state/session-modes/store.js.map +1 -0
- package/dist/state/session-modes/types.d.ts +41 -0
- package/dist/state/session-modes/types.js +7 -0
- package/dist/state/session-modes/types.js.map +1 -0
- package/dist/state/settings/index.d.ts +4 -0
- package/dist/state/settings/index.js +4 -0
- package/dist/state/settings/index.js.map +1 -0
- package/dist/state/settings/parse.d.ts +33 -0
- package/dist/state/settings/parse.js +261 -0
- package/dist/state/settings/parse.js.map +1 -0
- package/dist/state/settings/store.d.ts +21 -0
- package/dist/state/settings/store.js +65 -0
- package/dist/state/settings/store.js.map +1 -0
- package/dist/state/settings/types.d.ts +62 -0
- package/dist/state/settings/types.js +12 -0
- package/dist/state/settings/types.js.map +1 -0
- package/dist/state/tier-requests/index.d.ts +4 -0
- package/dist/state/tier-requests/index.js +4 -0
- package/dist/state/tier-requests/index.js.map +1 -0
- package/dist/state/tier-requests/parse.d.ts +3 -0
- package/dist/state/tier-requests/parse.js +53 -0
- package/dist/state/tier-requests/parse.js.map +1 -0
- package/dist/state/tier-requests/store.d.ts +27 -0
- package/dist/state/tier-requests/store.js +96 -0
- package/dist/state/tier-requests/store.js.map +1 -0
- package/dist/state/tier-requests/types.d.ts +30 -0
- package/dist/state/tier-requests/types.js +7 -0
- package/dist/state/tier-requests/types.js.map +1 -0
- package/dist/supervisor/index.d.ts +4 -0
- package/dist/supervisor/index.js +4 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/supervisor/log.d.ts +10 -0
- package/dist/supervisor/log.js +14 -0
- package/dist/supervisor/log.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +86 -0
- package/dist/supervisor/supervisor.js +1629 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/supervisor/types.d.ts +49 -0
- package/dist/supervisor/types.js +3 -0
- package/dist/supervisor/types.js.map +1 -0
- package/dist/supervisor/view.d.ts +3 -0
- package/dist/supervisor/view.js +4 -0
- package/dist/supervisor/view.js.map +1 -0
- package/dist/tui.d.ts +15 -0
- package/dist/tui.js +256 -0
- package/dist/tui.js.map +1 -0
- package/dist/ui/gui/api.d.ts +17 -0
- package/dist/ui/gui/api.js +21 -0
- package/dist/ui/gui/api.js.map +1 -0
- package/dist/ui/gui/view.d.ts +26 -0
- package/dist/ui/gui/view.js +49 -0
- package/dist/ui/gui/view.js.map +1 -0
- package/dist/ui/tui/controller.d.ts +97 -0
- package/dist/ui/tui/controller.js +415 -0
- package/dist/ui/tui/controller.js.map +1 -0
- package/dist/ui/tui/menu.d.ts +55 -0
- package/dist/ui/tui/menu.js +176 -0
- package/dist/ui/tui/menu.js.map +1 -0
- package/dist/ui/tui/options.d.ts +19 -0
- package/dist/ui/tui/options.js +17 -0
- package/dist/ui/tui/options.js.map +1 -0
- package/dist/ui/tui/receipts.d.ts +21 -0
- package/dist/ui/tui/receipts.js +112 -0
- package/dist/ui/tui/receipts.js.map +1 -0
- package/dist/ui/tui/status.d.ts +22 -0
- package/dist/ui/tui/status.js +92 -0
- package/dist/ui/tui/status.js.map +1 -0
- package/dist/ui/tui/tiers.d.ts +51 -0
- package/dist/ui/tui/tiers.js +108 -0
- package/dist/ui/tui/tiers.js.map +1 -0
- package/package.json +85 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admission routing (ADP-013/016/022): pick the model for a fresh work unit,
|
|
3
|
+
* or decide to defer (OpenCode's choice stands) or park (wait for recovery).
|
|
4
|
+
*
|
|
5
|
+
* Selection, after the compatibility gate (ADP-017) filters candidates: the
|
|
6
|
+
* first candidate in user order that is ready or due for its half-open probe
|
|
7
|
+
* wins; draining only when none is. Rationale: a due probe at a preferred
|
|
8
|
+
* position is how that resource returns (ADP-018/019) — passing it over for a
|
|
9
|
+
* later ready sibling would keep it out forever; a draining resource is being
|
|
10
|
+
* retired, so depending on it is the last resort. Breaker single-flight is
|
|
11
|
+
* enforced by the resource-health registry, not here.
|
|
12
|
+
*/
|
|
13
|
+
import { checkCompatibility } from "../compatibility/gate.js";
|
|
14
|
+
import { sameModelRef } from "../work-unit/types.js";
|
|
15
|
+
import { MAX_EVIDENCE_SKIPPED, schedulerHealthKey } from "./types.js";
|
|
16
|
+
function refOfCandidate(candidate) {
|
|
17
|
+
return {
|
|
18
|
+
providerID: candidate.providerID,
|
|
19
|
+
id: candidate.modelID,
|
|
20
|
+
variant: candidate.variant,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function lookup(inventory, ref) {
|
|
24
|
+
return inventory.find((candidate) => sameModelRef(refOfCandidate(candidate), ref));
|
|
25
|
+
}
|
|
26
|
+
function classify(assessment, signals) {
|
|
27
|
+
if (signals?.exhausted === true) {
|
|
28
|
+
return { kind: "skip", why: "capacity-exhausted" };
|
|
29
|
+
}
|
|
30
|
+
if (assessment === undefined) {
|
|
31
|
+
return signals?.draining === true ? { kind: "draining" } : { kind: "ready" };
|
|
32
|
+
}
|
|
33
|
+
if (assessment.kind === "probe") {
|
|
34
|
+
return { kind: "probe", probeId: assessment.ticket.id };
|
|
35
|
+
}
|
|
36
|
+
if (assessment.kind === "available") {
|
|
37
|
+
return signals?.draining === true ? { kind: "draining" } : { kind: "ready" };
|
|
38
|
+
}
|
|
39
|
+
return assessment.reason === "draining"
|
|
40
|
+
? { kind: "draining" }
|
|
41
|
+
: { kind: "skip", why: assessment.reason };
|
|
42
|
+
}
|
|
43
|
+
function apply(model, tier, reason, skipped, probeId) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "apply",
|
|
46
|
+
model,
|
|
47
|
+
...(tier === undefined ? {} : { tier }),
|
|
48
|
+
reason,
|
|
49
|
+
evidence: {
|
|
50
|
+
skipped: skipped.slice(0, MAX_EVIDENCE_SKIPPED),
|
|
51
|
+
...(probeId === undefined ? {} : { probeId }),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function admitPin(input, pin) {
|
|
56
|
+
const entry = lookup(input.inventory, pin);
|
|
57
|
+
if (entry === undefined) {
|
|
58
|
+
return { kind: "defer", reason: "pin-unverifiable" };
|
|
59
|
+
}
|
|
60
|
+
const verdict = checkCompatibility(entry, input.demand);
|
|
61
|
+
if (!verdict.compatible) {
|
|
62
|
+
return { kind: "defer", reason: "pin-incompatible", detail: verdict.reasons.join(",") };
|
|
63
|
+
}
|
|
64
|
+
const key = schedulerHealthKey(pin);
|
|
65
|
+
const bucket = classify(input.health[key], input.capacity[key]);
|
|
66
|
+
if (bucket.kind === "skip") {
|
|
67
|
+
return { kind: "park", reason: "pin-blocked", detail: bucket.why };
|
|
68
|
+
}
|
|
69
|
+
// Manual intent wins (GOAL): a pinned model is applied even while draining;
|
|
70
|
+
// a probe admission is recorded for observability but never overrides intent.
|
|
71
|
+
return apply(pin, undefined, "pin", [], bucket.kind === "probe" ? bucket.probeId : undefined);
|
|
72
|
+
}
|
|
73
|
+
function admitTier(input, tier) {
|
|
74
|
+
if (input.tierCandidates.length === 0) {
|
|
75
|
+
// The tier was reported configured but has an empty list — a configuration
|
|
76
|
+
// discrepancy; park rather than invent resources (ADP-005).
|
|
77
|
+
return { kind: "park", reason: "no-candidates" };
|
|
78
|
+
}
|
|
79
|
+
// Candidates passed over before the winner (or all of them, when none won).
|
|
80
|
+
const seen = [];
|
|
81
|
+
let gateSkipped = 0;
|
|
82
|
+
const skipWhys = [];
|
|
83
|
+
let chosen;
|
|
84
|
+
for (const ref of input.tierCandidates) {
|
|
85
|
+
const entry = lookup(input.inventory, ref);
|
|
86
|
+
if (entry === undefined) {
|
|
87
|
+
seen.push({ ref, bucket: { kind: "skip", why: "not-in-inventory" } });
|
|
88
|
+
skipWhys.push("not-in-inventory");
|
|
89
|
+
gateSkipped += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const verdict = checkCompatibility(entry, input.demand);
|
|
93
|
+
if (!verdict.compatible) {
|
|
94
|
+
// The gate reports incompatible only when at least one reason exists; the
|
|
95
|
+
// non-null assertion is the only way to express that invariant locally.
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
97
|
+
const why = verdict.reasons[0];
|
|
98
|
+
seen.push({ ref, bucket: { kind: "skip", why } });
|
|
99
|
+
skipWhys.push(why);
|
|
100
|
+
gateSkipped += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const key = schedulerHealthKey(ref);
|
|
104
|
+
const bucket = classify(input.health[key], input.capacity[key]);
|
|
105
|
+
if (bucket.kind === "ready" || bucket.kind === "probe") {
|
|
106
|
+
// The first ready or probe candidate wins; later candidates are
|
|
107
|
+
// irrelevant, so the scan stops and the evidence stays an honest prefix
|
|
108
|
+
// of the user order.
|
|
109
|
+
chosen = { ref, bucket };
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
seen.push({ ref, bucket });
|
|
113
|
+
if (bucket.kind === "skip") {
|
|
114
|
+
skipWhys.push(bucket.why);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const first = input.tierCandidates[0];
|
|
118
|
+
const evidenceBefore = (until) => seen.slice(0, until).map((e) => ({
|
|
119
|
+
ref: e.ref,
|
|
120
|
+
why: e.bucket.kind === "skip" ? e.bucket.why : "draining",
|
|
121
|
+
}));
|
|
122
|
+
if (chosen?.bucket.kind === "probe") {
|
|
123
|
+
return apply(chosen.ref, tier, "probe", evidenceBefore(seen.length), chosen.bucket.probeId);
|
|
124
|
+
}
|
|
125
|
+
if (chosen !== undefined) {
|
|
126
|
+
return apply(chosen.ref, tier, first !== undefined && sameModelRef(chosen.ref, first) ? "preferred" : "fallback", evidenceBefore(seen.length), undefined);
|
|
127
|
+
}
|
|
128
|
+
const drainIndex = seen.findIndex((e) => e.bucket.kind === "draining");
|
|
129
|
+
const drainEntry = drainIndex >= 0 ? seen[drainIndex] : undefined;
|
|
130
|
+
if (drainEntry !== undefined) {
|
|
131
|
+
return apply(drainEntry.ref, tier, "draining", evidenceBefore(drainIndex), undefined);
|
|
132
|
+
}
|
|
133
|
+
const summary = skipWhys.slice(0, 3).join(",");
|
|
134
|
+
return {
|
|
135
|
+
kind: "park",
|
|
136
|
+
reason: gateSkipped === skipWhys.length ? "no-compatible-resource" : "all-blocked",
|
|
137
|
+
detail: summary,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Decide the model for a fresh work unit. Total and deterministic: the same
|
|
142
|
+
* input always yields the same decision, and the function has no effects.
|
|
143
|
+
*/
|
|
144
|
+
export function admit(input) {
|
|
145
|
+
const routing = input.routing;
|
|
146
|
+
if (routing.kind === "defer") {
|
|
147
|
+
return { kind: "defer", reason: "routing-defer", detail: routing.reason };
|
|
148
|
+
}
|
|
149
|
+
if (routing.kind === "pin") {
|
|
150
|
+
return admitPin(input, routing.model);
|
|
151
|
+
}
|
|
152
|
+
return admitTier(input, routing.tier);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=admit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admit.js","sourceRoot":"","sources":["../../../src/core/scheduler/admit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admission routing (ADP-013/016/022): pick the model for a fresh work unit,\n * or decide to defer (OpenCode's choice stands) or park (wait for recovery).\n *\n * Selection, after the compatibility gate (ADP-017) filters candidates: the\n * first candidate in user order that is ready or due for its half-open probe\n * wins; draining only when none is. Rationale: a due probe at a preferred\n * position is how that resource returns (ADP-018/019) — passing it over for a\n * later ready sibling would keep it out forever; a draining resource is being\n * retired, so depending on it is the last resort. Breaker single-flight is\n * enforced by the resource-health registry, not here.\n */\nimport { checkCompatibility } from \"../compatibility/gate.js\"\nimport type { CandidateModel } from \"../compatibility/types.js\"\nimport type { RoutingDecision, TierName } from \"../tier-policy/types.js\"\nimport { sameModelRef, type ModelRef } from \"../work-unit/types.js\"\nimport type { Assessment } from \"../../health/resource-health/types.js\"\nimport type { CapacitySignals } from \"../../health/capacity/types.js\"\nimport type {\n AdmissionDecision,\n ApplyDecision,\n ApplyReason,\n SchedulerInput,\n SkippedCandidate,\n SkipReason,\n} from \"./types.js\"\nimport { MAX_EVIDENCE_SKIPPED, schedulerHealthKey } from \"./types.js\"\n\n/** The health/capacity view of one candidate, resolved to an admission bucket. */\ntype Bucket =\n | { readonly kind: \"ready\" }\n | { readonly kind: \"probe\"; readonly probeId: string }\n | { readonly kind: \"draining\" }\n | { readonly kind: \"skip\"; readonly why: SkipReason }\n\nfunction refOfCandidate(candidate: CandidateModel): ModelRef {\n return {\n providerID: candidate.providerID,\n id: candidate.modelID,\n variant: candidate.variant,\n }\n}\n\nfunction lookup(inventory: readonly CandidateModel[], ref: ModelRef): CandidateModel | undefined {\n return inventory.find((candidate) => sameModelRef(refOfCandidate(candidate), ref))\n}\n\nfunction classify(\n assessment: Assessment | undefined,\n signals: CapacitySignals | undefined,\n): Bucket {\n if (signals?.exhausted === true) {\n return { kind: \"skip\", why: \"capacity-exhausted\" }\n }\n if (assessment === undefined) {\n return signals?.draining === true ? { kind: \"draining\" } : { kind: \"ready\" }\n }\n if (assessment.kind === \"probe\") {\n return { kind: \"probe\", probeId: assessment.ticket.id }\n }\n if (assessment.kind === \"available\") {\n return signals?.draining === true ? { kind: \"draining\" } : { kind: \"ready\" }\n }\n return assessment.reason === \"draining\"\n ? { kind: \"draining\" }\n : { kind: \"skip\", why: assessment.reason }\n}\n\nfunction apply(\n model: ModelRef,\n tier: TierName | undefined,\n reason: ApplyReason,\n skipped: readonly SkippedCandidate[],\n probeId: string | undefined,\n): ApplyDecision {\n return {\n kind: \"apply\",\n model,\n ...(tier === undefined ? {} : { tier }),\n reason,\n evidence: {\n skipped: skipped.slice(0, MAX_EVIDENCE_SKIPPED),\n ...(probeId === undefined ? {} : { probeId }),\n },\n }\n}\n\nfunction admitPin(input: SchedulerInput, pin: ModelRef): AdmissionDecision {\n const entry = lookup(input.inventory, pin)\n if (entry === undefined) {\n return { kind: \"defer\", reason: \"pin-unverifiable\" }\n }\n const verdict = checkCompatibility(entry, input.demand)\n if (!verdict.compatible) {\n return { kind: \"defer\", reason: \"pin-incompatible\", detail: verdict.reasons.join(\",\") }\n }\n const key = schedulerHealthKey(pin)\n const bucket = classify(input.health[key], input.capacity[key])\n if (bucket.kind === \"skip\") {\n return { kind: \"park\", reason: \"pin-blocked\", detail: bucket.why }\n }\n // Manual intent wins (GOAL): a pinned model is applied even while draining;\n // a probe admission is recorded for observability but never overrides intent.\n return apply(pin, undefined, \"pin\", [], bucket.kind === \"probe\" ? bucket.probeId : undefined)\n}\n\nfunction admitTier(input: SchedulerInput, tier: TierName): AdmissionDecision {\n if (input.tierCandidates.length === 0) {\n // The tier was reported configured but has an empty list — a configuration\n // discrepancy; park rather than invent resources (ADP-005).\n return { kind: \"park\", reason: \"no-candidates\" }\n }\n // Candidates passed over before the winner (or all of them, when none won).\n const seen: { ref: ModelRef; bucket: Extract<Bucket, { kind: \"draining\" | \"skip\" }> }[] = []\n let gateSkipped = 0\n const skipWhys: string[] = []\n let chosen: { ref: ModelRef; bucket: Extract<Bucket, { kind: \"ready\" | \"probe\" }> } | undefined\n for (const ref of input.tierCandidates) {\n const entry = lookup(input.inventory, ref)\n if (entry === undefined) {\n seen.push({ ref, bucket: { kind: \"skip\", why: \"not-in-inventory\" } })\n skipWhys.push(\"not-in-inventory\")\n gateSkipped += 1\n continue\n }\n const verdict = checkCompatibility(entry, input.demand)\n if (!verdict.compatible) {\n // The gate reports incompatible only when at least one reason exists; the\n // non-null assertion is the only way to express that invariant locally.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const why = verdict.reasons[0]!\n seen.push({ ref, bucket: { kind: \"skip\", why } })\n skipWhys.push(why)\n gateSkipped += 1\n continue\n }\n const key = schedulerHealthKey(ref)\n const bucket = classify(input.health[key], input.capacity[key])\n if (bucket.kind === \"ready\" || bucket.kind === \"probe\") {\n // The first ready or probe candidate wins; later candidates are\n // irrelevant, so the scan stops and the evidence stays an honest prefix\n // of the user order.\n chosen = { ref, bucket }\n break\n }\n seen.push({ ref, bucket })\n if (bucket.kind === \"skip\") {\n skipWhys.push(bucket.why)\n }\n }\n const first = input.tierCandidates[0]\n const evidenceBefore = (until: number): SkippedCandidate[] =>\n seen.slice(0, until).map((e): SkippedCandidate => ({\n ref: e.ref,\n why: e.bucket.kind === \"skip\" ? e.bucket.why : \"draining\",\n }))\n if (chosen?.bucket.kind === \"probe\") {\n return apply(chosen.ref, tier, \"probe\", evidenceBefore(seen.length), chosen.bucket.probeId)\n }\n if (chosen !== undefined) {\n return apply(\n chosen.ref,\n tier,\n first !== undefined && sameModelRef(chosen.ref, first) ? \"preferred\" : \"fallback\",\n evidenceBefore(seen.length),\n undefined,\n )\n }\n const drainIndex = seen.findIndex((e) => e.bucket.kind === \"draining\")\n const drainEntry = drainIndex >= 0 ? seen[drainIndex] : undefined\n if (drainEntry !== undefined) {\n return apply(drainEntry.ref, tier, \"draining\", evidenceBefore(drainIndex), undefined)\n }\n const summary = skipWhys.slice(0, 3).join(\",\")\n return {\n kind: \"park\",\n reason: gateSkipped === skipWhys.length ? \"no-compatible-resource\" : \"all-blocked\",\n detail: summary,\n }\n}\n\n/**\n * Decide the model for a fresh work unit. Total and deterministic: the same\n * input always yields the same decision, and the function has no effects.\n */\nexport function admit(input: SchedulerInput): AdmissionDecision {\n const routing: RoutingDecision = input.routing\n if (routing.kind === \"defer\") {\n return { kind: \"defer\", reason: \"routing-defer\", detail: routing.reason }\n }\n if (routing.kind === \"pin\") {\n return admitPin(input, routing.model)\n }\n return admitTier(input, routing.tier)\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DeferDecision, EscalateDecision, EscalationInput } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Plan the next escalation step, or defer with the reason escalation cannot
|
|
4
|
+
* proceed. Total and deterministic.
|
|
5
|
+
*/
|
|
6
|
+
export declare function planEscalation(input: EscalationInput): EscalateDecision | DeferDecision;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability escalation planner (ADP-015).
|
|
3
|
+
*
|
|
4
|
+
* Escalation is justified only by non-empty structured evidence, follows the
|
|
5
|
+
* ladder next tier, and is bounded by the caller's finite budget. The
|
|
6
|
+
* same-model stronger-variant rung is not implemented: it would need an
|
|
7
|
+
* ordered variant ladder the host does not expose, and strength is never
|
|
8
|
+
* guessed from variant names. Every output moves strictly up the ladder or
|
|
9
|
+
* refuses. Termination rests on the budget, which the caller must carry
|
|
10
|
+
* across every unit of one task (each escalation continues on a new unit).
|
|
11
|
+
*/
|
|
12
|
+
import { nextTier } from "../tier-policy/types.js";
|
|
13
|
+
import { MAX_ESCALATION_EVIDENCE } from "./types.js";
|
|
14
|
+
function escalate(input, target, step, tier) {
|
|
15
|
+
return {
|
|
16
|
+
kind: "escalate",
|
|
17
|
+
target,
|
|
18
|
+
step,
|
|
19
|
+
tier,
|
|
20
|
+
evidence: input.evidence.slice(-MAX_ESCALATION_EVIDENCE),
|
|
21
|
+
budgetRemaining: input.budget.max - input.budget.used - 1,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Plan the next escalation step, or defer with the reason escalation cannot
|
|
26
|
+
* proceed. Total and deterministic.
|
|
27
|
+
*/
|
|
28
|
+
export function planEscalation(input) {
|
|
29
|
+
if (input.evidence.length === 0) {
|
|
30
|
+
return { kind: "defer", reason: "escalation-not-justified" };
|
|
31
|
+
}
|
|
32
|
+
if (input.budget.max <= 0 || input.budget.used >= input.budget.max) {
|
|
33
|
+
return { kind: "defer", reason: "escalation-budget-exhausted" };
|
|
34
|
+
}
|
|
35
|
+
const next = nextTier(input.currentTier);
|
|
36
|
+
if (next === undefined) {
|
|
37
|
+
return { kind: "defer", reason: "escalation-ceiling" };
|
|
38
|
+
}
|
|
39
|
+
const target = input.nextTierResources[0];
|
|
40
|
+
if (target === undefined) {
|
|
41
|
+
return { kind: "defer", reason: "escalation-no-candidate" };
|
|
42
|
+
}
|
|
43
|
+
return escalate(input, target, "tier", next);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=escalate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalate.js","sourceRoot":"","sources":["../../../src/core/scheduler/escalate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAGlD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAEpD,SAAS,QAAQ,CACf,KAAsB,EACtB,MAAgB,EAChB,IAA8B,EAC9B,IAA8B;IAE9B,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,uBAAuB,CAAC;QACxD,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;KAC1D,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAA;IAC9D,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAA;IACjE,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IACxC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAA;IACxD,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAA;IAC7D,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;AAC9C,CAAC","sourcesContent":["/**\n * Capability escalation planner (ADP-015).\n *\n * Escalation is justified only by non-empty structured evidence, follows the\n * ladder next tier, and is bounded by the caller's finite budget. The\n * same-model stronger-variant rung is not implemented: it would need an\n * ordered variant ladder the host does not expose, and strength is never\n * guessed from variant names. Every output moves strictly up the ladder or\n * refuses. Termination rests on the budget, which the caller must carry\n * across every unit of one task (each escalation continues on a new unit).\n */\nimport { nextTier } from \"../tier-policy/types.js\"\nimport type { ModelRef } from \"../work-unit/types.js\"\nimport type { DeferDecision, EscalateDecision, EscalationInput } from \"./types.js\"\nimport { MAX_ESCALATION_EVIDENCE } from \"./types.js\"\n\nfunction escalate(\n input: EscalationInput,\n target: ModelRef,\n step: EscalateDecision[\"step\"],\n tier: EscalateDecision[\"tier\"],\n): EscalateDecision {\n return {\n kind: \"escalate\",\n target,\n step,\n tier,\n evidence: input.evidence.slice(-MAX_ESCALATION_EVIDENCE),\n budgetRemaining: input.budget.max - input.budget.used - 1,\n }\n}\n\n/**\n * Plan the next escalation step, or defer with the reason escalation cannot\n * proceed. Total and deterministic.\n */\nexport function planEscalation(input: EscalationInput): EscalateDecision | DeferDecision {\n if (input.evidence.length === 0) {\n return { kind: \"defer\", reason: \"escalation-not-justified\" }\n }\n if (input.budget.max <= 0 || input.budget.used >= input.budget.max) {\n return { kind: \"defer\", reason: \"escalation-budget-exhausted\" }\n }\n const next = nextTier(input.currentTier)\n if (next === undefined) {\n return { kind: \"defer\", reason: \"escalation-ceiling\" }\n }\n const target = input.nextTierResources[0]\n if (target === undefined) {\n return { kind: \"defer\", reason: \"escalation-no-candidate\" }\n }\n return escalate(input, target, \"tier\", next)\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/scheduler/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA","sourcesContent":["export * from \"./types.js\"\nexport * from \"./admit.js\"\nexport * from \"./escalate.js\"\nexport * from \"./mode.js\"\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operating-mode wrapper (GOAL operating modes). OBSERVE and CONTROL run the
|
|
3
|
+
* identical core; OBSERVE wraps the result without applying it. OFF never
|
|
4
|
+
* reaches the scheduler, but the total wrapper maps it to an explicit defer
|
|
5
|
+
* so the union stays closed.
|
|
6
|
+
*/
|
|
7
|
+
import type { Decision, OperatingMode } from "./types.js";
|
|
8
|
+
export declare function forMode(decision: Decision, mode: OperatingMode): Decision;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function forMode(decision, mode) {
|
|
2
|
+
if (mode === "observe") {
|
|
3
|
+
return { kind: "observe-only", would: decision };
|
|
4
|
+
}
|
|
5
|
+
if (mode === "active") {
|
|
6
|
+
return decision;
|
|
7
|
+
}
|
|
8
|
+
return { kind: "defer", reason: "adaptive-off" };
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.js","sourceRoot":"","sources":["../../../src/core/scheduler/mode.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,OAAO,CAAC,QAAkB,EAAE,IAAmB;IAC7D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;IAClD,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;AAClD,CAAC","sourcesContent":["/**\n * Operating-mode wrapper (GOAL operating modes). OBSERVE and CONTROL run the\n * identical core; OBSERVE wraps the result without applying it. OFF never\n * reaches the scheduler, but the total wrapper maps it to an explicit defer\n * so the union stays closed.\n */\nimport type { Decision, OperatingMode } from \"./types.js\"\n\nexport function forMode(decision: Decision, mode: OperatingMode): Decision {\n if (mode === \"observe\") {\n return { kind: \"observe-only\", would: decision }\n }\n if (mode === \"active\") {\n return decision\n }\n return { kind: \"defer\", reason: \"adaptive-off\" }\n}\n"]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler types (ADP-013/015/016/022): the pure decision core's vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The core is a total function over its inputs — routing outcome, demand,
|
|
5
|
+
* inventory, per-candidate health and capacity views — and never performs
|
|
6
|
+
* effects. Effects (switching, receipts, parking) belong to the execution
|
|
7
|
+
* layer. OBSERVE and CONTROL call the same core; mode handling is a pure
|
|
8
|
+
* wrapper (`forMode`).
|
|
9
|
+
*/
|
|
10
|
+
import type { ModelRef } from "../work-unit/types.js";
|
|
11
|
+
import type { RoutingDecision, TierName } from "../tier-policy/types.js";
|
|
12
|
+
import type { CandidateModel, Demand } from "../compatibility/types.js";
|
|
13
|
+
import type { Assessment } from "../../health/resource-health/types.js";
|
|
14
|
+
import type { CapacitySignals } from "../../health/capacity/types.js";
|
|
15
|
+
/** Operating modes (GOAL manual control). The plugin defaults to `observe`. */
|
|
16
|
+
export declare const OPERATING_MODES: readonly ["off", "observe", "active"];
|
|
17
|
+
export type OperatingMode = (typeof OPERATING_MODES)[number];
|
|
18
|
+
/** Why an `apply` decision chose its model (receipt provenance). */
|
|
19
|
+
export declare const APPLY_REASONS: readonly ["pin", "preferred", "fallback", "probe", "draining"];
|
|
20
|
+
export type ApplyReason = (typeof APPLY_REASONS)[number];
|
|
21
|
+
/**
|
|
22
|
+
* Why the core did not apply anything.
|
|
23
|
+
* - `routing-defer` — the tier ladder deferred; OpenCode's choice stands.
|
|
24
|
+
* - `pin-unverifiable` — the pinned model is not in the host inventory, so
|
|
25
|
+
* availability cannot be verified (inventory gate); deferring keeps the
|
|
26
|
+
* user's own model choice, which for a pin is typically the pinned model.
|
|
27
|
+
* - `pin-incompatible` — the pinned model cannot execute the work.
|
|
28
|
+
* - `escalation-*` — escalation evidence was absent, the budget was spent,
|
|
29
|
+
* the tier ladder is at its ceiling, or the next tier has no resources
|
|
30
|
+
* (no cross-tier borrowing).
|
|
31
|
+
* - `adaptive-off` — the plugin is off; only reachable through `forMode`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SCHEDULER_DEFER_REASONS: readonly ["routing-defer", "pin-unverifiable", "pin-incompatible", "escalation-not-justified", "escalation-budget-exhausted", "escalation-ceiling", "escalation-no-candidate", "adaptive-off"];
|
|
34
|
+
export type SchedulerDeferReason = (typeof SCHEDULER_DEFER_REASONS)[number];
|
|
35
|
+
/**
|
|
36
|
+
* Why work parks instead of applying: the required tier has no usable
|
|
37
|
+
* resource right now. Parking waits for recovery — it never borrows across
|
|
38
|
+
* tiers (ADP-022).
|
|
39
|
+
*/
|
|
40
|
+
export declare const PARK_REASONS: readonly ["pin-blocked", "no-candidates", "no-compatible-resource", "all-blocked"];
|
|
41
|
+
export type ParkReason = (typeof PARK_REASONS)[number];
|
|
42
|
+
/** Why a tier candidate was not usable. Superset of gate and health reasons. */
|
|
43
|
+
export declare const SKIP_REASONS: readonly ["not-in-inventory", "capacity-exhausted", "provider-unavailable", "model-unavailable", "variant-unavailable", "tool-use-unsupported", "input-modality-unsupported", "output-modality-unsupported", "context-capacity-insufficient", "context-capacity-unknown", "unavailable", "quota-exhausted", "rate-limited", "breaker-open", "probe-in-flight", "draining"];
|
|
44
|
+
export type SkipReason = (typeof SKIP_REASONS)[number];
|
|
45
|
+
/** A tier candidate that could not take the work, with the bounded reason. */
|
|
46
|
+
export interface SkippedCandidate {
|
|
47
|
+
readonly ref: ModelRef;
|
|
48
|
+
readonly why: SkipReason;
|
|
49
|
+
}
|
|
50
|
+
/** Bounded evidence attached to an `apply` decision. */
|
|
51
|
+
export interface ApplyEvidence {
|
|
52
|
+
/** Candidates considered before the applied one, in order (bounded). */
|
|
53
|
+
readonly skipped: readonly SkippedCandidate[];
|
|
54
|
+
/** Breaker probe id when the applied unit is the half-open probe. */
|
|
55
|
+
readonly probeId?: string | undefined;
|
|
56
|
+
}
|
|
57
|
+
export interface ApplyDecision {
|
|
58
|
+
readonly kind: "apply";
|
|
59
|
+
readonly model: ModelRef;
|
|
60
|
+
/** Required tier; `undefined` for pin routing (a pin is not a tier). */
|
|
61
|
+
readonly tier?: TierName | undefined;
|
|
62
|
+
readonly reason: ApplyReason;
|
|
63
|
+
readonly evidence: ApplyEvidence;
|
|
64
|
+
}
|
|
65
|
+
export interface DeferDecision {
|
|
66
|
+
readonly kind: "defer";
|
|
67
|
+
readonly reason: SchedulerDeferReason;
|
|
68
|
+
readonly detail?: string | undefined;
|
|
69
|
+
}
|
|
70
|
+
export interface ParkDecision {
|
|
71
|
+
readonly kind: "park";
|
|
72
|
+
readonly reason: ParkReason;
|
|
73
|
+
readonly detail?: string | undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Structured capability evidence for escalation (ADP-015). Never prose mining. */
|
|
76
|
+
export declare const ESCALATION_EVIDENCE_KINDS: readonly ["agent-request", "reviewer-rejection", "verification-failure", "repeated-failure", "explicit-blocker"];
|
|
77
|
+
export type EscalationEvidenceKind = (typeof ESCALATION_EVIDENCE_KINDS)[number];
|
|
78
|
+
export interface EscalationEvidence {
|
|
79
|
+
readonly kind: EscalationEvidenceKind;
|
|
80
|
+
readonly detail?: string | undefined;
|
|
81
|
+
}
|
|
82
|
+
export declare const ESCALATION_STEPS: readonly ["variant", "tier"];
|
|
83
|
+
export type EscalationStep = (typeof ESCALATION_STEPS)[number];
|
|
84
|
+
/** Finite per-unit escalation budget (ADP-015). `used >= max` ends escalation. */
|
|
85
|
+
export interface EscalationBudget {
|
|
86
|
+
readonly used: number;
|
|
87
|
+
readonly max: number;
|
|
88
|
+
}
|
|
89
|
+
/** Bound on evidence carried by an `escalate` decision (latest wins). */
|
|
90
|
+
export declare const MAX_ESCALATION_EVIDENCE = 4;
|
|
91
|
+
export interface EscalationInput {
|
|
92
|
+
readonly currentModel: ModelRef;
|
|
93
|
+
readonly currentTier: TierName;
|
|
94
|
+
/** Configured resources of the next tier, user order, already gated. */
|
|
95
|
+
readonly nextTierResources: readonly ModelRef[];
|
|
96
|
+
/** Non-empty structured evidence justifies escalation. */
|
|
97
|
+
readonly evidence: readonly EscalationEvidence[];
|
|
98
|
+
readonly budget: EscalationBudget;
|
|
99
|
+
}
|
|
100
|
+
export interface EscalateDecision {
|
|
101
|
+
readonly kind: "escalate";
|
|
102
|
+
readonly target: ModelRef;
|
|
103
|
+
readonly step: EscalationStep;
|
|
104
|
+
/** Tier of the escalated-to resource (the next tier). */
|
|
105
|
+
readonly tier: TierName;
|
|
106
|
+
readonly evidence: readonly EscalationEvidence[];
|
|
107
|
+
readonly budgetRemaining: number;
|
|
108
|
+
}
|
|
109
|
+
/** OBSERVE mode's wrapper: the identical decision, recorded but not applied. */
|
|
110
|
+
export interface ObserveOnlyDecision {
|
|
111
|
+
readonly kind: "observe-only";
|
|
112
|
+
readonly would: Decision;
|
|
113
|
+
}
|
|
114
|
+
export type Decision = ApplyDecision | DeferDecision | ParkDecision | EscalateDecision | ObserveOnlyDecision;
|
|
115
|
+
/** What admission can return — escalation never originates from admission. */
|
|
116
|
+
export type AdmissionDecision = ApplyDecision | DeferDecision | ParkDecision;
|
|
117
|
+
/**
|
|
118
|
+
* Everything admission looks at. `health` and `capacity` are keyed by
|
|
119
|
+
* `schedulerHealthKey(ref)`; an absent health entry means "no observed
|
|
120
|
+
* failure" (the registry's default), an absent capacity entry means "no
|
|
121
|
+
* estimate" — unknown is never treated as exhausted.
|
|
122
|
+
*/
|
|
123
|
+
export interface SchedulerInput {
|
|
124
|
+
readonly routing: RoutingDecision;
|
|
125
|
+
readonly demand: Demand;
|
|
126
|
+
/** Host inventory (adapter-normalized). Candidates absent here are unverifiable. */
|
|
127
|
+
readonly inventory: readonly CandidateModel[];
|
|
128
|
+
/** Configured resources of the required tier, in user order (ADP-005). */
|
|
129
|
+
readonly tierCandidates: readonly ModelRef[];
|
|
130
|
+
readonly health: Readonly<Record<string, Assessment>>;
|
|
131
|
+
readonly capacity: Readonly<Record<string, CapacitySignals>>;
|
|
132
|
+
}
|
|
133
|
+
/** Bound on the `skipped` evidence list of an `apply` decision. */
|
|
134
|
+
export declare const MAX_EVIDENCE_SKIPPED = 8;
|
|
135
|
+
/** Canonical health/capacity map key for a candidate model reference. */
|
|
136
|
+
export declare function schedulerHealthKey(ref: ModelRef): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { INCOMPATIBILITY_REASONS } from "../compatibility/types.js";
|
|
2
|
+
import { BLOCKED_REASONS } from "../../health/resource-health/types.js";
|
|
3
|
+
import { resourceKeyID } from "../../health/resource-health/key.js";
|
|
4
|
+
/** Operating modes (GOAL manual control). The plugin defaults to `observe`. */
|
|
5
|
+
export const OPERATING_MODES = ["off", "observe", "active"];
|
|
6
|
+
/** Why an `apply` decision chose its model (receipt provenance). */
|
|
7
|
+
export const APPLY_REASONS = [
|
|
8
|
+
"pin",
|
|
9
|
+
"preferred",
|
|
10
|
+
"fallback",
|
|
11
|
+
"probe",
|
|
12
|
+
"draining",
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Why the core did not apply anything.
|
|
16
|
+
* - `routing-defer` — the tier ladder deferred; OpenCode's choice stands.
|
|
17
|
+
* - `pin-unverifiable` — the pinned model is not in the host inventory, so
|
|
18
|
+
* availability cannot be verified (inventory gate); deferring keeps the
|
|
19
|
+
* user's own model choice, which for a pin is typically the pinned model.
|
|
20
|
+
* - `pin-incompatible` — the pinned model cannot execute the work.
|
|
21
|
+
* - `escalation-*` — escalation evidence was absent, the budget was spent,
|
|
22
|
+
* the tier ladder is at its ceiling, or the next tier has no resources
|
|
23
|
+
* (no cross-tier borrowing).
|
|
24
|
+
* - `adaptive-off` — the plugin is off; only reachable through `forMode`.
|
|
25
|
+
*/
|
|
26
|
+
export const SCHEDULER_DEFER_REASONS = [
|
|
27
|
+
"routing-defer",
|
|
28
|
+
"pin-unverifiable",
|
|
29
|
+
"pin-incompatible",
|
|
30
|
+
"escalation-not-justified",
|
|
31
|
+
"escalation-budget-exhausted",
|
|
32
|
+
"escalation-ceiling",
|
|
33
|
+
"escalation-no-candidate",
|
|
34
|
+
"adaptive-off",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Why work parks instead of applying: the required tier has no usable
|
|
38
|
+
* resource right now. Parking waits for recovery — it never borrows across
|
|
39
|
+
* tiers (ADP-022).
|
|
40
|
+
*/
|
|
41
|
+
export const PARK_REASONS = [
|
|
42
|
+
"pin-blocked",
|
|
43
|
+
"no-candidates",
|
|
44
|
+
"no-compatible-resource",
|
|
45
|
+
"all-blocked",
|
|
46
|
+
];
|
|
47
|
+
/** Why a tier candidate was not usable. Superset of gate and health reasons. */
|
|
48
|
+
export const SKIP_REASONS = [
|
|
49
|
+
"not-in-inventory",
|
|
50
|
+
"capacity-exhausted",
|
|
51
|
+
...INCOMPATIBILITY_REASONS,
|
|
52
|
+
...BLOCKED_REASONS,
|
|
53
|
+
];
|
|
54
|
+
/** Structured capability evidence for escalation (ADP-015). Never prose mining. */
|
|
55
|
+
export const ESCALATION_EVIDENCE_KINDS = [
|
|
56
|
+
"agent-request",
|
|
57
|
+
"reviewer-rejection",
|
|
58
|
+
"verification-failure",
|
|
59
|
+
"repeated-failure",
|
|
60
|
+
"explicit-blocker",
|
|
61
|
+
];
|
|
62
|
+
// Persisted receipt vocabulary. The planner produces only "tier" today; the
|
|
63
|
+
// historical "variant" step stays parseable so old receipts are not dropped.
|
|
64
|
+
export const ESCALATION_STEPS = ["variant", "tier"];
|
|
65
|
+
/** Bound on evidence carried by an `escalate` decision (latest wins). */
|
|
66
|
+
export const MAX_ESCALATION_EVIDENCE = 4;
|
|
67
|
+
/** Bound on the `skipped` evidence list of an `apply` decision. */
|
|
68
|
+
export const MAX_EVIDENCE_SKIPPED = 8;
|
|
69
|
+
/** Canonical health/capacity map key for a candidate model reference. */
|
|
70
|
+
export function schedulerHealthKey(ref) {
|
|
71
|
+
return resourceKeyID(ref.variant === undefined
|
|
72
|
+
? { providerID: ref.providerID, modelID: ref.id }
|
|
73
|
+
: { providerID: ref.providerID, modelID: ref.id, variantID: ref.variant });
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/scheduler/types.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAA;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAEnE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAsC,CAAA;AAIhG,oEAAoE;AACpE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK;IACL,WAAW;IACX,UAAU;IACV,OAAO;IACP,UAAU;CAC0B,CAAA;AAItC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,eAAe;IACf,kBAAkB;IAClB,kBAAkB;IAClB,0BAA0B;IAC1B,6BAA6B;IAC7B,oBAAoB;IACpB,yBAAyB;IACzB,cAAc;CACsB,CAAA;AAItC;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,aAAa;IACb,eAAe;IACf,wBAAwB;IACxB,aAAa;CACuB,CAAA;AAItC,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,oBAAoB;IACpB,GAAG,uBAAuB;IAC1B,GAAG,eAAe;CACkB,CAAA;AAuCtC,mFAAmF;AACnF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,eAAe;IACf,oBAAoB;IACpB,sBAAsB;IACtB,kBAAkB;IAClB,kBAAkB;CACkB,CAAA;AAStC,4EAA4E;AAC5E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,CAAsC,CAAA;AAUxF,yEAAyE;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAA;AAmDxC,mEAAmE;AACnE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAA;AAErC,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,GAAa;IAC9C,OAAO,aAAa,CAClB,GAAG,CAAC,OAAO,KAAK,SAAS;QACvB,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE;QACjD,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,CAC5E,CAAA;AACH,CAAC","sourcesContent":["/**\n * Scheduler types (ADP-013/015/016/022): the pure decision core's vocabulary.\n *\n * The core is a total function over its inputs — routing outcome, demand,\n * inventory, per-candidate health and capacity views — and never performs\n * effects. Effects (switching, receipts, parking) belong to the execution\n * layer. OBSERVE and CONTROL call the same core; mode handling is a pure\n * wrapper (`forMode`).\n */\nimport type { ModelRef } from \"../work-unit/types.js\"\nimport type { RoutingDecision, TierName } from \"../tier-policy/types.js\"\nimport type { CandidateModel, Demand } from \"../compatibility/types.js\"\nimport type { Assessment } from \"../../health/resource-health/types.js\"\nimport type { CapacitySignals } from \"../../health/capacity/types.js\"\nimport { INCOMPATIBILITY_REASONS } from \"../compatibility/types.js\"\nimport { BLOCKED_REASONS } from \"../../health/resource-health/types.js\"\nimport { resourceKeyID } from \"../../health/resource-health/key.js\"\n\n/** Operating modes (GOAL manual control). The plugin defaults to `observe`. */\nexport const OPERATING_MODES = [\"off\", \"observe\", \"active\"] as const satisfies readonly string[]\n\nexport type OperatingMode = (typeof OPERATING_MODES)[number]\n\n/** Why an `apply` decision chose its model (receipt provenance). */\nexport const APPLY_REASONS = [\n \"pin\",\n \"preferred\",\n \"fallback\",\n \"probe\",\n \"draining\",\n] as const satisfies readonly string[]\n\nexport type ApplyReason = (typeof APPLY_REASONS)[number]\n\n/**\n * Why the core did not apply anything.\n * - `routing-defer` — the tier ladder deferred; OpenCode's choice stands.\n * - `pin-unverifiable` — the pinned model is not in the host inventory, so\n * availability cannot be verified (inventory gate); deferring keeps the\n * user's own model choice, which for a pin is typically the pinned model.\n * - `pin-incompatible` — the pinned model cannot execute the work.\n * - `escalation-*` — escalation evidence was absent, the budget was spent,\n * the tier ladder is at its ceiling, or the next tier has no resources\n * (no cross-tier borrowing).\n * - `adaptive-off` — the plugin is off; only reachable through `forMode`.\n */\nexport const SCHEDULER_DEFER_REASONS = [\n \"routing-defer\",\n \"pin-unverifiable\",\n \"pin-incompatible\",\n \"escalation-not-justified\",\n \"escalation-budget-exhausted\",\n \"escalation-ceiling\",\n \"escalation-no-candidate\",\n \"adaptive-off\",\n] as const satisfies readonly string[]\n\nexport type SchedulerDeferReason = (typeof SCHEDULER_DEFER_REASONS)[number]\n\n/**\n * Why work parks instead of applying: the required tier has no usable\n * resource right now. Parking waits for recovery — it never borrows across\n * tiers (ADP-022).\n */\nexport const PARK_REASONS = [\n \"pin-blocked\",\n \"no-candidates\",\n \"no-compatible-resource\",\n \"all-blocked\",\n] as const satisfies readonly string[]\n\nexport type ParkReason = (typeof PARK_REASONS)[number]\n\n/** Why a tier candidate was not usable. Superset of gate and health reasons. */\nexport const SKIP_REASONS = [\n \"not-in-inventory\",\n \"capacity-exhausted\",\n ...INCOMPATIBILITY_REASONS,\n ...BLOCKED_REASONS,\n] as const satisfies readonly string[]\n\nexport type SkipReason = (typeof SKIP_REASONS)[number]\n\n/** A tier candidate that could not take the work, with the bounded reason. */\nexport interface SkippedCandidate {\n readonly ref: ModelRef\n readonly why: SkipReason\n}\n\n/** Bounded evidence attached to an `apply` decision. */\nexport interface ApplyEvidence {\n /** Candidates considered before the applied one, in order (bounded). */\n readonly skipped: readonly SkippedCandidate[]\n /** Breaker probe id when the applied unit is the half-open probe. */\n readonly probeId?: string | undefined\n}\n\nexport interface ApplyDecision {\n readonly kind: \"apply\"\n readonly model: ModelRef\n /** Required tier; `undefined` for pin routing (a pin is not a tier). */\n readonly tier?: TierName | undefined\n readonly reason: ApplyReason\n readonly evidence: ApplyEvidence\n}\n\nexport interface DeferDecision {\n readonly kind: \"defer\"\n readonly reason: SchedulerDeferReason\n readonly detail?: string | undefined\n}\n\nexport interface ParkDecision {\n readonly kind: \"park\"\n readonly reason: ParkReason\n readonly detail?: string | undefined\n}\n\n/** Structured capability evidence for escalation (ADP-015). Never prose mining. */\nexport const ESCALATION_EVIDENCE_KINDS = [\n \"agent-request\",\n \"reviewer-rejection\",\n \"verification-failure\",\n \"repeated-failure\",\n \"explicit-blocker\",\n] as const satisfies readonly string[]\n\nexport type EscalationEvidenceKind = (typeof ESCALATION_EVIDENCE_KINDS)[number]\n\nexport interface EscalationEvidence {\n readonly kind: EscalationEvidenceKind\n readonly detail?: string | undefined\n}\n\n// Persisted receipt vocabulary. The planner produces only \"tier\" today; the\n// historical \"variant\" step stays parseable so old receipts are not dropped.\nexport const ESCALATION_STEPS = [\"variant\", \"tier\"] as const satisfies readonly string[]\n\nexport type EscalationStep = (typeof ESCALATION_STEPS)[number]\n\n/** Finite per-unit escalation budget (ADP-015). `used >= max` ends escalation. */\nexport interface EscalationBudget {\n readonly used: number\n readonly max: number\n}\n\n/** Bound on evidence carried by an `escalate` decision (latest wins). */\nexport const MAX_ESCALATION_EVIDENCE = 4\n\nexport interface EscalationInput {\n readonly currentModel: ModelRef\n readonly currentTier: TierName\n /** Configured resources of the next tier, user order, already gated. */\n readonly nextTierResources: readonly ModelRef[]\n /** Non-empty structured evidence justifies escalation. */\n readonly evidence: readonly EscalationEvidence[]\n readonly budget: EscalationBudget\n}\n\nexport interface EscalateDecision {\n readonly kind: \"escalate\"\n readonly target: ModelRef\n readonly step: EscalationStep\n /** Tier of the escalated-to resource (the next tier). */\n readonly tier: TierName\n readonly evidence: readonly EscalationEvidence[]\n readonly budgetRemaining: number\n}\n\n/** OBSERVE mode's wrapper: the identical decision, recorded but not applied. */\nexport interface ObserveOnlyDecision {\n readonly kind: \"observe-only\"\n readonly would: Decision\n}\n\nexport type Decision =\n ApplyDecision | DeferDecision | ParkDecision | EscalateDecision | ObserveOnlyDecision\n\n/** What admission can return — escalation never originates from admission. */\nexport type AdmissionDecision = ApplyDecision | DeferDecision | ParkDecision\n\n/**\n * Everything admission looks at. `health` and `capacity` are keyed by\n * `schedulerHealthKey(ref)`; an absent health entry means \"no observed\n * failure\" (the registry's default), an absent capacity entry means \"no\n * estimate\" — unknown is never treated as exhausted.\n */\nexport interface SchedulerInput {\n readonly routing: RoutingDecision\n readonly demand: Demand\n /** Host inventory (adapter-normalized). Candidates absent here are unverifiable. */\n readonly inventory: readonly CandidateModel[]\n /** Configured resources of the required tier, in user order (ADP-005). */\n readonly tierCandidates: readonly ModelRef[]\n readonly health: Readonly<Record<string, Assessment>>\n readonly capacity: Readonly<Record<string, CapacitySignals>>\n}\n\n/** Bound on the `skipped` evidence list of an `apply` decision. */\nexport const MAX_EVIDENCE_SKIPPED = 8\n\n/** Canonical health/capacity map key for a candidate model reference. */\nexport function schedulerHealthKey(ref: ModelRef): string {\n return resourceKeyID(\n ref.variant === undefined\n ? { providerID: ref.providerID, modelID: ref.id }\n : { providerID: ref.providerID, modelID: ref.id, variantID: ref.variant },\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/tier-policy/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA","sourcesContent":["export * from \"./types.js\"\nexport * from \"./resolve.js\"\nexport * from \"./tier-list.js\"\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing priority ladder (ADP-002):
|
|
3
|
+
*
|
|
4
|
+
* explicit user/model pin
|
|
5
|
+
* → explicit tier request
|
|
6
|
+
* → supported agent role hint
|
|
7
|
+
* → deterministic local request evidence (auxiliary kinds)
|
|
8
|
+
* → default `standard`
|
|
9
|
+
*
|
|
10
|
+
* Ambiguity within a level resolves to `standard`. An explicit signal whose
|
|
11
|
+
* tier has no configured resources is NOT silently downgraded to another
|
|
12
|
+
* tier (that would borrow across tiers — GOAL tenet 4): the router defers
|
|
13
|
+
* instead (ADP-003), keeping OpenCode's model choice. No LLM call and no
|
|
14
|
+
* keyword classifier participates (ADP-004) — every input is a structured
|
|
15
|
+
* field.
|
|
16
|
+
*/
|
|
17
|
+
import { type RoutingDecision, type RoutingInput } from "./types.js";
|
|
18
|
+
export declare function resolveRouting(input: RoutingInput): RoutingDecision;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing priority ladder (ADP-002):
|
|
3
|
+
*
|
|
4
|
+
* explicit user/model pin
|
|
5
|
+
* → explicit tier request
|
|
6
|
+
* → supported agent role hint
|
|
7
|
+
* → deterministic local request evidence (auxiliary kinds)
|
|
8
|
+
* → default `standard`
|
|
9
|
+
*
|
|
10
|
+
* Ambiguity within a level resolves to `standard`. An explicit signal whose
|
|
11
|
+
* tier has no configured resources is NOT silently downgraded to another
|
|
12
|
+
* tier (that would borrow across tiers — GOAL tenet 4): the router defers
|
|
13
|
+
* instead (ADP-003), keeping OpenCode's model choice. No LLM call and no
|
|
14
|
+
* keyword classifier participates (ADP-004) — every input is a structured
|
|
15
|
+
* field.
|
|
16
|
+
*/
|
|
17
|
+
import { AUXILIARY_TIER, DEFAULT_TIER, isAuxiliary, } from "./types.js";
|
|
18
|
+
export function resolveRouting(input) {
|
|
19
|
+
if (input.pin !== undefined) {
|
|
20
|
+
return { kind: "pin", model: input.pin };
|
|
21
|
+
}
|
|
22
|
+
if (input.tierRequest !== undefined) {
|
|
23
|
+
return byConfiguration(input.tierRequest.tier, "tier-request", input.configuredTiers);
|
|
24
|
+
}
|
|
25
|
+
// Role level: an agent-name hint from user policy and deterministic
|
|
26
|
+
// auxiliary-request evidence. Conflict inside the level is ambiguity
|
|
27
|
+
// (ADP-002) and resolves to `standard`; agreement keeps the tier.
|
|
28
|
+
const agentTier = input.agent !== undefined &&
|
|
29
|
+
input.agent.length > 0 &&
|
|
30
|
+
input.agentTiers !== undefined &&
|
|
31
|
+
Object.hasOwn(input.agentTiers, input.agent)
|
|
32
|
+
? input.agentTiers[input.agent]
|
|
33
|
+
: undefined;
|
|
34
|
+
const auxiliaryTier = input.requestKind !== undefined && isAuxiliary(input.requestKind) ? AUXILIARY_TIER : undefined;
|
|
35
|
+
if (agentTier !== undefined && auxiliaryTier !== undefined && agentTier !== auxiliaryTier) {
|
|
36
|
+
return byConfiguration(DEFAULT_TIER, "ambiguity", input.configuredTiers);
|
|
37
|
+
}
|
|
38
|
+
if (agentTier !== undefined) {
|
|
39
|
+
return byConfiguration(agentTier, "agent-role", input.configuredTiers);
|
|
40
|
+
}
|
|
41
|
+
if (auxiliaryTier !== undefined) {
|
|
42
|
+
return byConfiguration(auxiliaryTier, "auxiliary-request", input.configuredTiers);
|
|
43
|
+
}
|
|
44
|
+
return byConfiguration(DEFAULT_TIER, "default", input.configuredTiers);
|
|
45
|
+
}
|
|
46
|
+
function byConfiguration(tier, source, configuredTiers) {
|
|
47
|
+
if (!configuredTiers.includes(tier)) {
|
|
48
|
+
return { kind: "defer", tier, reason: "tier-not-configured" };
|
|
49
|
+
}
|
|
50
|
+
return { kind: "tier", tier, source };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/tier-policy/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,GAKZ,MAAM,YAAY,CAAA;AAEnB,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,CAAA;IAC1C,CAAC;IAED,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACvF,CAAC;IAED,oEAAoE;IACpE,qEAAqE;IACrE,kEAAkE;IAClE,MAAM,SAAS,GACb,KAAK,CAAC,KAAK,KAAK,SAAS;QACzB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACtB,KAAK,CAAC,UAAU,KAAK,SAAS;QAC9B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;QAC1C,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;QAC/B,CAAC,CAAC,SAAS,CAAA;IACf,MAAM,aAAa,GACjB,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAA;IAEhG,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS,IAAI,SAAS,KAAK,aAAa,EAAE,CAAC;QAC1F,OAAO,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IAC1E,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,eAAe,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACxE,CAAC;IACD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,eAAe,CAAC,aAAa,EAAE,mBAAmB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACnF,CAAC;IAED,OAAO,eAAe,CAAC,YAAY,EAAE,SAAS,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;AACxE,CAAC;AAED,SAAS,eAAe,CACtB,IAAc,EACd,MAAqB,EACrB,eAAoC;IAEpC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAA;IAC/D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACvC,CAAC","sourcesContent":["/**\n * Routing priority ladder (ADP-002):\n *\n * explicit user/model pin\n * → explicit tier request\n * → supported agent role hint\n * → deterministic local request evidence (auxiliary kinds)\n * → default `standard`\n *\n * Ambiguity within a level resolves to `standard`. An explicit signal whose\n * tier has no configured resources is NOT silently downgraded to another\n * tier (that would borrow across tiers — GOAL tenet 4): the router defers\n * instead (ADP-003), keeping OpenCode's model choice. No LLM call and no\n * keyword classifier participates (ADP-004) — every input is a structured\n * field.\n */\nimport {\n AUXILIARY_TIER,\n DEFAULT_TIER,\n isAuxiliary,\n type RoutingDecision,\n type RoutingInput,\n type RoutingSource,\n type TierName,\n} from \"./types.js\"\n\nexport function resolveRouting(input: RoutingInput): RoutingDecision {\n if (input.pin !== undefined) {\n return { kind: \"pin\", model: input.pin }\n }\n\n if (input.tierRequest !== undefined) {\n return byConfiguration(input.tierRequest.tier, \"tier-request\", input.configuredTiers)\n }\n\n // Role level: an agent-name hint from user policy and deterministic\n // auxiliary-request evidence. Conflict inside the level is ambiguity\n // (ADP-002) and resolves to `standard`; agreement keeps the tier.\n const agentTier =\n input.agent !== undefined &&\n input.agent.length > 0 &&\n input.agentTiers !== undefined &&\n Object.hasOwn(input.agentTiers, input.agent)\n ? input.agentTiers[input.agent]\n : undefined\n const auxiliaryTier =\n input.requestKind !== undefined && isAuxiliary(input.requestKind) ? AUXILIARY_TIER : undefined\n\n if (agentTier !== undefined && auxiliaryTier !== undefined && agentTier !== auxiliaryTier) {\n return byConfiguration(DEFAULT_TIER, \"ambiguity\", input.configuredTiers)\n }\n if (agentTier !== undefined) {\n return byConfiguration(agentTier, \"agent-role\", input.configuredTiers)\n }\n if (auxiliaryTier !== undefined) {\n return byConfiguration(auxiliaryTier, \"auxiliary-request\", input.configuredTiers)\n }\n\n return byConfiguration(DEFAULT_TIER, \"default\", input.configuredTiers)\n}\n\nfunction byConfiguration(\n tier: TierName,\n source: RoutingSource,\n configuredTiers: readonly TierName[],\n): RoutingDecision {\n if (!configuredTiers.includes(tier)) {\n return { kind: \"defer\", tier, reason: \"tier-not-configured\" }\n }\n return { kind: \"tier\", tier, source }\n}\n"]}
|