@harness-engineering/orchestrator 0.11.1 → 0.12.0
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/dist/index.d.mts +132 -3
- package/dist/index.d.ts +132 -3
- package/dist/index.js +709 -207
- package/dist/index.mjs +702 -200
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -5,7 +5,8 @@ import { EnrichedSpec, ComplexityScore, SimulationResult, IntelligencePipeline,
|
|
|
5
5
|
import { GraphStore } from '@harness-engineering/graph';
|
|
6
6
|
import { execFile } from 'node:child_process';
|
|
7
7
|
import { EventEmitter } from 'node:events';
|
|
8
|
-
import { PoolStateProvider, SchedulerTimerHandle } from '@harness-engineering/local-models';
|
|
8
|
+
import { PoolStateProvider, SchedulerTimerHandle, DiscoverCandidatesOptions, DiscoverCandidatesResult } from '@harness-engineering/local-models';
|
|
9
|
+
export { discoverCandidates } from '@harness-engineering/local-models';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -1698,6 +1699,13 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1698
1699
|
* so this map is the single source of truth post-migration.
|
|
1699
1700
|
*/
|
|
1700
1701
|
private localResolvers;
|
|
1702
|
+
/**
|
|
1703
|
+
* Consumption Phase 1 (T2): bus listener that debounce-refreshes every local
|
|
1704
|
+
* resolver when a `local-models:pool` mutation fires, so a just-installed or
|
|
1705
|
+
* swapped model becomes usable within the refresh window instead of waiting up
|
|
1706
|
+
* to `probeIntervalMs` for the next poll. Held for removal in {@link stop}.
|
|
1707
|
+
*/
|
|
1708
|
+
private poolRefreshListener;
|
|
1701
1709
|
/** Phase 4 (D5): pool-state port shared by all local/pi resolvers. Null when LMLM disabled. */
|
|
1702
1710
|
private poolStateProvider;
|
|
1703
1711
|
private poolStateStore;
|
|
@@ -1735,6 +1743,10 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1735
1743
|
* see the Phase 2 candidate-parser gap noted on `startRefreshScheduler`.
|
|
1736
1744
|
*/
|
|
1737
1745
|
private modelRecommender;
|
|
1746
|
+
/** Live HF candidate discovery (injectable for tests so startup makes no network calls). */
|
|
1747
|
+
private readonly discoverCandidatesFn;
|
|
1748
|
+
/** Snapshot of the last candidate seeding, surfaced to the refresh route. */
|
|
1749
|
+
private candidateSourceState;
|
|
1738
1750
|
/** Test seam: injected timer/clock for the scheduler so no real 24h timer runs. */
|
|
1739
1751
|
private readonly schedulerTimerOverride;
|
|
1740
1752
|
/**
|
|
@@ -1813,6 +1825,8 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1813
1825
|
now?: () => number;
|
|
1814
1826
|
random?: () => number;
|
|
1815
1827
|
};
|
|
1828
|
+
/** Live-candidate-discovery seam: tests inject a fake so startup makes no HF calls. */
|
|
1829
|
+
discoverCandidates?: (opts: DiscoverCandidatesOptions) => Promise<DiscoverCandidatesResult>;
|
|
1816
1830
|
});
|
|
1817
1831
|
/**
|
|
1818
1832
|
* Phase 5: construct the OTLP/HTTP trace exporter and wire telemetry fanout.
|
|
@@ -1993,6 +2007,14 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1993
2007
|
* `store.load()`.
|
|
1994
2008
|
*/
|
|
1995
2009
|
private initModelPool;
|
|
2010
|
+
/**
|
|
2011
|
+
* Resume installs interrupted by a restart. A proposal left `installing` had
|
|
2012
|
+
* its background `ollama pull` cut short when the orchestrator went down; the
|
|
2013
|
+
* pull is idempotent (ollama resumes from cached blobs), so we re-drive it.
|
|
2014
|
+
* Fire-and-forget with its own error isolation — a resumed multi-GB download
|
|
2015
|
+
* must not block startup, and a re-drive failure only logs.
|
|
2016
|
+
*/
|
|
2017
|
+
private redriveInterruptedInstalls;
|
|
1996
2018
|
/**
|
|
1997
2019
|
* LMLM Phase 7 wiring: apply the operator's configured pool bounds (disk
|
|
1998
2020
|
* budget + org/family allowlist) from `localModels.pool` to the live pool.
|
|
@@ -2015,6 +2037,17 @@ declare class Orchestrator extends EventEmitter {
|
|
|
2015
2037
|
* breadth is the only piece deferred to the Phase 2 recommender.
|
|
2016
2038
|
*/
|
|
2017
2039
|
private startRefreshScheduler;
|
|
2040
|
+
/** (Re)build the recommender over `candidates` and record the seeding source. */
|
|
2041
|
+
private seedRecommender;
|
|
2042
|
+
/**
|
|
2043
|
+
* Refresh ranking candidates live from HuggingFace, merge the curated
|
|
2044
|
+
* `ollamaName`/`family` tags from the frozen snapshot (so results stay
|
|
2045
|
+
* installable — decision A), and re-seed the recommender. Fail-closed: on any
|
|
2046
|
+
* error or an empty installable result, the current candidates stand. Runs a
|
|
2047
|
+
* `forceRefresh` tick so recommendations + proposals reflect the fresh set.
|
|
2048
|
+
* Used by both the startup background refresh and the operator "Refresh" button.
|
|
2049
|
+
*/
|
|
2050
|
+
private refreshCandidatesLive;
|
|
2018
2051
|
/** Resolve the hardware profile for a refresh tick (operator override wins). */
|
|
2019
2052
|
private detectLmlmHardware;
|
|
2020
2053
|
/** Map the on-disk model-proposal queue to F7 dedup pairs (open→pending, rejected→rejected). */
|
|
@@ -2087,6 +2120,16 @@ declare function launchTUI(orchestrator: Orchestrator): {
|
|
|
2087
2120
|
* `LocalModelResolver` (so multi-resolver array-fallback works without
|
|
2088
2121
|
* leaking resolver lifetimes into the factory).
|
|
2089
2122
|
*/
|
|
2123
|
+
/**
|
|
2124
|
+
* Runtime-feedback callbacks a `local`/`pi` backend fires as turns complete.
|
|
2125
|
+
* See {@link OrchestratorBackendFactoryOptions.getModelUsageHooksFor}.
|
|
2126
|
+
*/
|
|
2127
|
+
interface LocalModelUsageHooks {
|
|
2128
|
+
/** Fired with the resolved model after a successful turn (LRU + breaker clear). */
|
|
2129
|
+
onModelUsed?: (model: string) => void;
|
|
2130
|
+
/** Fired with the resolved model after a failed turn (breaker increment). */
|
|
2131
|
+
onModelFailed?: (model: string) => void;
|
|
2132
|
+
}
|
|
2090
2133
|
interface OrchestratorBackendFactoryOptions {
|
|
2091
2134
|
backends: Record<string, BackendDef>;
|
|
2092
2135
|
routing: RoutingConfig;
|
|
@@ -2105,7 +2148,17 @@ interface OrchestratorBackendFactoryOptions {
|
|
|
2105
2148
|
* existence and lifecycle while still letting it produce backends that
|
|
2106
2149
|
* route through the resolver Map.
|
|
2107
2150
|
*/
|
|
2108
|
-
getResolverModelFor?: (backendName: string) => (() => string | null) | undefined;
|
|
2151
|
+
getResolverModelFor?: (backendName: string, useCase: RoutingUseCase) => (() => string | null) | undefined;
|
|
2152
|
+
/**
|
|
2153
|
+
* Consumption Phase 3 (T11): per-`local`/`pi` backend hook returning the
|
|
2154
|
+
* runtime-feedback callbacks bound to that backend's resolver + pool. The
|
|
2155
|
+
* factory forwards them to the constructed `LocalBackend` so a completed turn
|
|
2156
|
+
* stamps `lastUsedAt` (LRU) + clears the circuit breaker, and a failed turn
|
|
2157
|
+
* feeds the breaker. Returning `undefined` means "no feedback wiring" (the
|
|
2158
|
+
* backend runs exactly as before). Kept parallel to `getResolverModelFor` so
|
|
2159
|
+
* the factory stays ignorant of resolver/pool lifecycles.
|
|
2160
|
+
*/
|
|
2161
|
+
getModelUsageHooksFor?: (backendName: string) => LocalModelUsageHooks | undefined;
|
|
2109
2162
|
/**
|
|
2110
2163
|
* Phase 5: prompt-cache recorder forwarded to Anthropic-capable backends.
|
|
2111
2164
|
* Other backends accept-but-ignore. Shared across dispatches so the
|
|
@@ -2451,6 +2504,26 @@ interface LocalModelResolverOptions {
|
|
|
2451
2504
|
poolState?: PoolStateProvider;
|
|
2452
2505
|
/** Probe cadence in ms; default 30_000, minimum 1_000. */
|
|
2453
2506
|
probeIntervalMs?: number;
|
|
2507
|
+
/** Debounce window for event-driven {@link LocalModelResolver.refresh}; default 250ms. */
|
|
2508
|
+
refreshDebounceMs?: number;
|
|
2509
|
+
/**
|
|
2510
|
+
* Consumption Phase 5 (T19/T20): called with the newly-resolved model name
|
|
2511
|
+
* whenever the resolver's composite selection changes (and is non-null), so
|
|
2512
|
+
* the orchestrator can warm it into VRAM before the next dispatch. Naturally
|
|
2513
|
+
* debounced — it fires only on an actual selection change, not every probe.
|
|
2514
|
+
* Best-effort: a throwing hook is swallowed. Injected in tests; wired to
|
|
2515
|
+
* {@link defaultWarmModel} in production.
|
|
2516
|
+
*/
|
|
2517
|
+
warmModel?: (ollamaName: string) => void;
|
|
2518
|
+
/**
|
|
2519
|
+
* Consumption Phase 3 (T10): consecutive inference failures before a model is
|
|
2520
|
+
* deprioritized by the circuit breaker. Default 3.
|
|
2521
|
+
*/
|
|
2522
|
+
breakerThreshold?: number;
|
|
2523
|
+
/** Cooldown (ms) after which a tripped model is eligible again. Default 60_000. */
|
|
2524
|
+
breakerCooldownMs?: number;
|
|
2525
|
+
/** Injectable clock (ms since epoch) for the circuit-breaker cooldown; default `Date.now`. Test seam. */
|
|
2526
|
+
now?: () => number;
|
|
2454
2527
|
/**
|
|
2455
2528
|
* Per-request timeout for the default fetch implementation, in ms.
|
|
2456
2529
|
* Default: 5_000. Ignored when a custom `fetchModels` is provided
|
|
@@ -2482,8 +2555,21 @@ declare class LocalModelResolver {
|
|
|
2482
2555
|
private readonly poolState?;
|
|
2483
2556
|
private readonly probeIntervalMs;
|
|
2484
2557
|
private readonly fetchModels;
|
|
2558
|
+
private readonly warmModel?;
|
|
2485
2559
|
private readonly logger;
|
|
2486
2560
|
private timer;
|
|
2561
|
+
/** Debounce handle for event-driven {@link refresh}; coalesces a burst into one probe. */
|
|
2562
|
+
private refreshTimer;
|
|
2563
|
+
/** Test seam for the refresh debounce delay (0 in tests → next-tick probe). */
|
|
2564
|
+
private readonly refreshDebounceMs;
|
|
2565
|
+
/** Consumption Phase 3 (T10): circuit-breaker config + per-model failure/trip state. */
|
|
2566
|
+
private readonly breakerThreshold;
|
|
2567
|
+
private readonly breakerCooldownMs;
|
|
2568
|
+
private readonly now;
|
|
2569
|
+
/** Consecutive inference failures per model since its last success. */
|
|
2570
|
+
private consecutiveFailures;
|
|
2571
|
+
/** Epoch-ms at which a tripped model becomes eligible again (cooldown expiry). */
|
|
2572
|
+
private trippedUntil;
|
|
2487
2573
|
private listeners;
|
|
2488
2574
|
/**
|
|
2489
2575
|
* Tracks an in-flight probe so concurrent invocations (interval tick while a
|
|
@@ -2502,7 +2588,15 @@ declare class LocalModelResolver {
|
|
|
2502
2588
|
private warnings;
|
|
2503
2589
|
private available;
|
|
2504
2590
|
constructor(opts: LocalModelResolverOptions);
|
|
2505
|
-
|
|
2591
|
+
/**
|
|
2592
|
+
* The model to dispatch to. With no `useCase`, returns the cached composite
|
|
2593
|
+
* resolution from the last probe (byte-identical to the pre-Phase-4 resolver).
|
|
2594
|
+
* With a `useCase`, orders the (pool-derived) candidates by that use-case's
|
|
2595
|
+
* task profile and picks the best loaded, breaker-healthy one — so a
|
|
2596
|
+
* coding-tagged dispatch prefers the coding specialist. A `general`-mapped
|
|
2597
|
+
* use-case returns the cached composite resolution unchanged.
|
|
2598
|
+
*/
|
|
2599
|
+
resolveModel(useCase?: RoutingUseCase): string | null;
|
|
2506
2600
|
getStatus(): LocalModelStatus;
|
|
2507
2601
|
/**
|
|
2508
2602
|
* Effective candidate list. With a poolState port present the list derives
|
|
@@ -2513,6 +2607,41 @@ declare class LocalModelResolver {
|
|
|
2513
2607
|
onStatusChange(handler: (status: LocalModelStatus) => void): () => void;
|
|
2514
2608
|
probe(): Promise<LocalModelStatus>;
|
|
2515
2609
|
private runProbe;
|
|
2610
|
+
/** Best-effort warm-hook invocation — a throwing warm never breaks a probe. */
|
|
2611
|
+
private warm;
|
|
2612
|
+
/**
|
|
2613
|
+
* Consumption Phase 3 (T10): pick the resolved model from the candidates that
|
|
2614
|
+
* are actually loaded, preferring ones whose circuit breaker is not tripped.
|
|
2615
|
+
* Candidate order (score-desc from the pool) is preserved within each tier, so
|
|
2616
|
+
* a tripped top pick sinks below a healthy lower pick but still resolves as a
|
|
2617
|
+
* last resort when every loaded candidate is tripped (better a flaky model than
|
|
2618
|
+
* none). Returns null when no candidate is loaded.
|
|
2619
|
+
*/
|
|
2620
|
+
private selectMatch;
|
|
2621
|
+
/**
|
|
2622
|
+
* Whether `model`'s circuit breaker is currently tripped. Lazily clears the
|
|
2623
|
+
* trip once its cooldown has elapsed so the model becomes eligible again on the
|
|
2624
|
+
* next resolution without needing a separate timer.
|
|
2625
|
+
*/
|
|
2626
|
+
private isTripped;
|
|
2627
|
+
/**
|
|
2628
|
+
* Record a successful inference on `model`: clears its failure count and any
|
|
2629
|
+
* active trip so a recovered model is immediately re-preferred.
|
|
2630
|
+
*/
|
|
2631
|
+
recordSuccess(model: string): void;
|
|
2632
|
+
/**
|
|
2633
|
+
* Record a failed inference on `model`. On the Nth consecutive failure the
|
|
2634
|
+
* breaker trips (deprioritizing the model for `breakerCooldownMs`) and a
|
|
2635
|
+
* re-probe is scheduled so the resolver rolls to a healthy alternative.
|
|
2636
|
+
*/
|
|
2637
|
+
recordFailure(model: string): void;
|
|
2638
|
+
/**
|
|
2639
|
+
* Event-driven refresh: schedule a debounced re-probe. Called when a
|
|
2640
|
+
* `local-models:pool` mutation fires so a just-installed/swapped model becomes
|
|
2641
|
+
* usable within a debounce window instead of waiting up to `probeIntervalMs`.
|
|
2642
|
+
* A burst of frames coalesces into one probe (the timer is only armed once).
|
|
2643
|
+
*/
|
|
2644
|
+
refresh(): void;
|
|
2516
2645
|
start(): Promise<void>;
|
|
2517
2646
|
stop(): void;
|
|
2518
2647
|
private snapshotForDiff;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { EnrichedSpec, ComplexityScore, SimulationResult, IntelligencePipeline,
|
|
|
5
5
|
import { GraphStore } from '@harness-engineering/graph';
|
|
6
6
|
import { execFile } from 'node:child_process';
|
|
7
7
|
import { EventEmitter } from 'node:events';
|
|
8
|
-
import { PoolStateProvider, SchedulerTimerHandle } from '@harness-engineering/local-models';
|
|
8
|
+
import { PoolStateProvider, SchedulerTimerHandle, DiscoverCandidatesOptions, DiscoverCandidatesResult } from '@harness-engineering/local-models';
|
|
9
|
+
export { discoverCandidates } from '@harness-engineering/local-models';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -1698,6 +1699,13 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1698
1699
|
* so this map is the single source of truth post-migration.
|
|
1699
1700
|
*/
|
|
1700
1701
|
private localResolvers;
|
|
1702
|
+
/**
|
|
1703
|
+
* Consumption Phase 1 (T2): bus listener that debounce-refreshes every local
|
|
1704
|
+
* resolver when a `local-models:pool` mutation fires, so a just-installed or
|
|
1705
|
+
* swapped model becomes usable within the refresh window instead of waiting up
|
|
1706
|
+
* to `probeIntervalMs` for the next poll. Held for removal in {@link stop}.
|
|
1707
|
+
*/
|
|
1708
|
+
private poolRefreshListener;
|
|
1701
1709
|
/** Phase 4 (D5): pool-state port shared by all local/pi resolvers. Null when LMLM disabled. */
|
|
1702
1710
|
private poolStateProvider;
|
|
1703
1711
|
private poolStateStore;
|
|
@@ -1735,6 +1743,10 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1735
1743
|
* see the Phase 2 candidate-parser gap noted on `startRefreshScheduler`.
|
|
1736
1744
|
*/
|
|
1737
1745
|
private modelRecommender;
|
|
1746
|
+
/** Live HF candidate discovery (injectable for tests so startup makes no network calls). */
|
|
1747
|
+
private readonly discoverCandidatesFn;
|
|
1748
|
+
/** Snapshot of the last candidate seeding, surfaced to the refresh route. */
|
|
1749
|
+
private candidateSourceState;
|
|
1738
1750
|
/** Test seam: injected timer/clock for the scheduler so no real 24h timer runs. */
|
|
1739
1751
|
private readonly schedulerTimerOverride;
|
|
1740
1752
|
/**
|
|
@@ -1813,6 +1825,8 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1813
1825
|
now?: () => number;
|
|
1814
1826
|
random?: () => number;
|
|
1815
1827
|
};
|
|
1828
|
+
/** Live-candidate-discovery seam: tests inject a fake so startup makes no HF calls. */
|
|
1829
|
+
discoverCandidates?: (opts: DiscoverCandidatesOptions) => Promise<DiscoverCandidatesResult>;
|
|
1816
1830
|
});
|
|
1817
1831
|
/**
|
|
1818
1832
|
* Phase 5: construct the OTLP/HTTP trace exporter and wire telemetry fanout.
|
|
@@ -1993,6 +2007,14 @@ declare class Orchestrator extends EventEmitter {
|
|
|
1993
2007
|
* `store.load()`.
|
|
1994
2008
|
*/
|
|
1995
2009
|
private initModelPool;
|
|
2010
|
+
/**
|
|
2011
|
+
* Resume installs interrupted by a restart. A proposal left `installing` had
|
|
2012
|
+
* its background `ollama pull` cut short when the orchestrator went down; the
|
|
2013
|
+
* pull is idempotent (ollama resumes from cached blobs), so we re-drive it.
|
|
2014
|
+
* Fire-and-forget with its own error isolation — a resumed multi-GB download
|
|
2015
|
+
* must not block startup, and a re-drive failure only logs.
|
|
2016
|
+
*/
|
|
2017
|
+
private redriveInterruptedInstalls;
|
|
1996
2018
|
/**
|
|
1997
2019
|
* LMLM Phase 7 wiring: apply the operator's configured pool bounds (disk
|
|
1998
2020
|
* budget + org/family allowlist) from `localModels.pool` to the live pool.
|
|
@@ -2015,6 +2037,17 @@ declare class Orchestrator extends EventEmitter {
|
|
|
2015
2037
|
* breadth is the only piece deferred to the Phase 2 recommender.
|
|
2016
2038
|
*/
|
|
2017
2039
|
private startRefreshScheduler;
|
|
2040
|
+
/** (Re)build the recommender over `candidates` and record the seeding source. */
|
|
2041
|
+
private seedRecommender;
|
|
2042
|
+
/**
|
|
2043
|
+
* Refresh ranking candidates live from HuggingFace, merge the curated
|
|
2044
|
+
* `ollamaName`/`family` tags from the frozen snapshot (so results stay
|
|
2045
|
+
* installable — decision A), and re-seed the recommender. Fail-closed: on any
|
|
2046
|
+
* error or an empty installable result, the current candidates stand. Runs a
|
|
2047
|
+
* `forceRefresh` tick so recommendations + proposals reflect the fresh set.
|
|
2048
|
+
* Used by both the startup background refresh and the operator "Refresh" button.
|
|
2049
|
+
*/
|
|
2050
|
+
private refreshCandidatesLive;
|
|
2018
2051
|
/** Resolve the hardware profile for a refresh tick (operator override wins). */
|
|
2019
2052
|
private detectLmlmHardware;
|
|
2020
2053
|
/** Map the on-disk model-proposal queue to F7 dedup pairs (open→pending, rejected→rejected). */
|
|
@@ -2087,6 +2120,16 @@ declare function launchTUI(orchestrator: Orchestrator): {
|
|
|
2087
2120
|
* `LocalModelResolver` (so multi-resolver array-fallback works without
|
|
2088
2121
|
* leaking resolver lifetimes into the factory).
|
|
2089
2122
|
*/
|
|
2123
|
+
/**
|
|
2124
|
+
* Runtime-feedback callbacks a `local`/`pi` backend fires as turns complete.
|
|
2125
|
+
* See {@link OrchestratorBackendFactoryOptions.getModelUsageHooksFor}.
|
|
2126
|
+
*/
|
|
2127
|
+
interface LocalModelUsageHooks {
|
|
2128
|
+
/** Fired with the resolved model after a successful turn (LRU + breaker clear). */
|
|
2129
|
+
onModelUsed?: (model: string) => void;
|
|
2130
|
+
/** Fired with the resolved model after a failed turn (breaker increment). */
|
|
2131
|
+
onModelFailed?: (model: string) => void;
|
|
2132
|
+
}
|
|
2090
2133
|
interface OrchestratorBackendFactoryOptions {
|
|
2091
2134
|
backends: Record<string, BackendDef>;
|
|
2092
2135
|
routing: RoutingConfig;
|
|
@@ -2105,7 +2148,17 @@ interface OrchestratorBackendFactoryOptions {
|
|
|
2105
2148
|
* existence and lifecycle while still letting it produce backends that
|
|
2106
2149
|
* route through the resolver Map.
|
|
2107
2150
|
*/
|
|
2108
|
-
getResolverModelFor?: (backendName: string) => (() => string | null) | undefined;
|
|
2151
|
+
getResolverModelFor?: (backendName: string, useCase: RoutingUseCase) => (() => string | null) | undefined;
|
|
2152
|
+
/**
|
|
2153
|
+
* Consumption Phase 3 (T11): per-`local`/`pi` backend hook returning the
|
|
2154
|
+
* runtime-feedback callbacks bound to that backend's resolver + pool. The
|
|
2155
|
+
* factory forwards them to the constructed `LocalBackend` so a completed turn
|
|
2156
|
+
* stamps `lastUsedAt` (LRU) + clears the circuit breaker, and a failed turn
|
|
2157
|
+
* feeds the breaker. Returning `undefined` means "no feedback wiring" (the
|
|
2158
|
+
* backend runs exactly as before). Kept parallel to `getResolverModelFor` so
|
|
2159
|
+
* the factory stays ignorant of resolver/pool lifecycles.
|
|
2160
|
+
*/
|
|
2161
|
+
getModelUsageHooksFor?: (backendName: string) => LocalModelUsageHooks | undefined;
|
|
2109
2162
|
/**
|
|
2110
2163
|
* Phase 5: prompt-cache recorder forwarded to Anthropic-capable backends.
|
|
2111
2164
|
* Other backends accept-but-ignore. Shared across dispatches so the
|
|
@@ -2451,6 +2504,26 @@ interface LocalModelResolverOptions {
|
|
|
2451
2504
|
poolState?: PoolStateProvider;
|
|
2452
2505
|
/** Probe cadence in ms; default 30_000, minimum 1_000. */
|
|
2453
2506
|
probeIntervalMs?: number;
|
|
2507
|
+
/** Debounce window for event-driven {@link LocalModelResolver.refresh}; default 250ms. */
|
|
2508
|
+
refreshDebounceMs?: number;
|
|
2509
|
+
/**
|
|
2510
|
+
* Consumption Phase 5 (T19/T20): called with the newly-resolved model name
|
|
2511
|
+
* whenever the resolver's composite selection changes (and is non-null), so
|
|
2512
|
+
* the orchestrator can warm it into VRAM before the next dispatch. Naturally
|
|
2513
|
+
* debounced — it fires only on an actual selection change, not every probe.
|
|
2514
|
+
* Best-effort: a throwing hook is swallowed. Injected in tests; wired to
|
|
2515
|
+
* {@link defaultWarmModel} in production.
|
|
2516
|
+
*/
|
|
2517
|
+
warmModel?: (ollamaName: string) => void;
|
|
2518
|
+
/**
|
|
2519
|
+
* Consumption Phase 3 (T10): consecutive inference failures before a model is
|
|
2520
|
+
* deprioritized by the circuit breaker. Default 3.
|
|
2521
|
+
*/
|
|
2522
|
+
breakerThreshold?: number;
|
|
2523
|
+
/** Cooldown (ms) after which a tripped model is eligible again. Default 60_000. */
|
|
2524
|
+
breakerCooldownMs?: number;
|
|
2525
|
+
/** Injectable clock (ms since epoch) for the circuit-breaker cooldown; default `Date.now`. Test seam. */
|
|
2526
|
+
now?: () => number;
|
|
2454
2527
|
/**
|
|
2455
2528
|
* Per-request timeout for the default fetch implementation, in ms.
|
|
2456
2529
|
* Default: 5_000. Ignored when a custom `fetchModels` is provided
|
|
@@ -2482,8 +2555,21 @@ declare class LocalModelResolver {
|
|
|
2482
2555
|
private readonly poolState?;
|
|
2483
2556
|
private readonly probeIntervalMs;
|
|
2484
2557
|
private readonly fetchModels;
|
|
2558
|
+
private readonly warmModel?;
|
|
2485
2559
|
private readonly logger;
|
|
2486
2560
|
private timer;
|
|
2561
|
+
/** Debounce handle for event-driven {@link refresh}; coalesces a burst into one probe. */
|
|
2562
|
+
private refreshTimer;
|
|
2563
|
+
/** Test seam for the refresh debounce delay (0 in tests → next-tick probe). */
|
|
2564
|
+
private readonly refreshDebounceMs;
|
|
2565
|
+
/** Consumption Phase 3 (T10): circuit-breaker config + per-model failure/trip state. */
|
|
2566
|
+
private readonly breakerThreshold;
|
|
2567
|
+
private readonly breakerCooldownMs;
|
|
2568
|
+
private readonly now;
|
|
2569
|
+
/** Consecutive inference failures per model since its last success. */
|
|
2570
|
+
private consecutiveFailures;
|
|
2571
|
+
/** Epoch-ms at which a tripped model becomes eligible again (cooldown expiry). */
|
|
2572
|
+
private trippedUntil;
|
|
2487
2573
|
private listeners;
|
|
2488
2574
|
/**
|
|
2489
2575
|
* Tracks an in-flight probe so concurrent invocations (interval tick while a
|
|
@@ -2502,7 +2588,15 @@ declare class LocalModelResolver {
|
|
|
2502
2588
|
private warnings;
|
|
2503
2589
|
private available;
|
|
2504
2590
|
constructor(opts: LocalModelResolverOptions);
|
|
2505
|
-
|
|
2591
|
+
/**
|
|
2592
|
+
* The model to dispatch to. With no `useCase`, returns the cached composite
|
|
2593
|
+
* resolution from the last probe (byte-identical to the pre-Phase-4 resolver).
|
|
2594
|
+
* With a `useCase`, orders the (pool-derived) candidates by that use-case's
|
|
2595
|
+
* task profile and picks the best loaded, breaker-healthy one — so a
|
|
2596
|
+
* coding-tagged dispatch prefers the coding specialist. A `general`-mapped
|
|
2597
|
+
* use-case returns the cached composite resolution unchanged.
|
|
2598
|
+
*/
|
|
2599
|
+
resolveModel(useCase?: RoutingUseCase): string | null;
|
|
2506
2600
|
getStatus(): LocalModelStatus;
|
|
2507
2601
|
/**
|
|
2508
2602
|
* Effective candidate list. With a poolState port present the list derives
|
|
@@ -2513,6 +2607,41 @@ declare class LocalModelResolver {
|
|
|
2513
2607
|
onStatusChange(handler: (status: LocalModelStatus) => void): () => void;
|
|
2514
2608
|
probe(): Promise<LocalModelStatus>;
|
|
2515
2609
|
private runProbe;
|
|
2610
|
+
/** Best-effort warm-hook invocation — a throwing warm never breaks a probe. */
|
|
2611
|
+
private warm;
|
|
2612
|
+
/**
|
|
2613
|
+
* Consumption Phase 3 (T10): pick the resolved model from the candidates that
|
|
2614
|
+
* are actually loaded, preferring ones whose circuit breaker is not tripped.
|
|
2615
|
+
* Candidate order (score-desc from the pool) is preserved within each tier, so
|
|
2616
|
+
* a tripped top pick sinks below a healthy lower pick but still resolves as a
|
|
2617
|
+
* last resort when every loaded candidate is tripped (better a flaky model than
|
|
2618
|
+
* none). Returns null when no candidate is loaded.
|
|
2619
|
+
*/
|
|
2620
|
+
private selectMatch;
|
|
2621
|
+
/**
|
|
2622
|
+
* Whether `model`'s circuit breaker is currently tripped. Lazily clears the
|
|
2623
|
+
* trip once its cooldown has elapsed so the model becomes eligible again on the
|
|
2624
|
+
* next resolution without needing a separate timer.
|
|
2625
|
+
*/
|
|
2626
|
+
private isTripped;
|
|
2627
|
+
/**
|
|
2628
|
+
* Record a successful inference on `model`: clears its failure count and any
|
|
2629
|
+
* active trip so a recovered model is immediately re-preferred.
|
|
2630
|
+
*/
|
|
2631
|
+
recordSuccess(model: string): void;
|
|
2632
|
+
/**
|
|
2633
|
+
* Record a failed inference on `model`. On the Nth consecutive failure the
|
|
2634
|
+
* breaker trips (deprioritizing the model for `breakerCooldownMs`) and a
|
|
2635
|
+
* re-probe is scheduled so the resolver rolls to a healthy alternative.
|
|
2636
|
+
*/
|
|
2637
|
+
recordFailure(model: string): void;
|
|
2638
|
+
/**
|
|
2639
|
+
* Event-driven refresh: schedule a debounced re-probe. Called when a
|
|
2640
|
+
* `local-models:pool` mutation fires so a just-installed/swapped model becomes
|
|
2641
|
+
* usable within a debounce window instead of waiting up to `probeIntervalMs`.
|
|
2642
|
+
* A burst of frames coalesces into one probe (the timer is only armed once).
|
|
2643
|
+
*/
|
|
2644
|
+
refresh(): void;
|
|
2516
2645
|
start(): Promise<void>;
|
|
2517
2646
|
stop(): void;
|
|
2518
2647
|
private snapshotForDiff;
|