harness-dispatch 0.4.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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -0
  3. package/config.default.yaml +397 -0
  4. package/data/coding_benchmarks.json +431 -0
  5. package/dist/auth.d.ts +9 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +73 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/billing.d.ts +7 -0
  10. package/dist/billing.d.ts.map +1 -0
  11. package/dist/billing.js +201 -0
  12. package/dist/billing.js.map +1 -0
  13. package/dist/bin.d.ts +6 -0
  14. package/dist/bin.d.ts.map +1 -0
  15. package/dist/bin.js +488 -0
  16. package/dist/bin.js.map +1 -0
  17. package/dist/breaker-store.d.ts +31 -0
  18. package/dist/breaker-store.d.ts.map +1 -0
  19. package/dist/breaker-store.js +103 -0
  20. package/dist/breaker-store.js.map +1 -0
  21. package/dist/circuit-breaker.d.ts +47 -0
  22. package/dist/circuit-breaker.d.ts.map +1 -0
  23. package/dist/circuit-breaker.js +102 -0
  24. package/dist/circuit-breaker.js.map +1 -0
  25. package/dist/config.d.ts +48 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +1248 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/dashboard/live.d.ts +49 -0
  30. package/dist/dashboard/live.d.ts.map +1 -0
  31. package/dist/dashboard/live.js +149 -0
  32. package/dist/dashboard/live.js.map +1 -0
  33. package/dist/dispatch-log.d.ts +46 -0
  34. package/dist/dispatch-log.d.ts.map +1 -0
  35. package/dist/dispatch-log.js +89 -0
  36. package/dist/dispatch-log.js.map +1 -0
  37. package/dist/dispatchers/base.d.ts +55 -0
  38. package/dist/dispatchers/base.d.ts.map +1 -0
  39. package/dist/dispatchers/base.js +80 -0
  40. package/dist/dispatchers/base.js.map +1 -0
  41. package/dist/dispatchers/generic-cli.d.ts +30 -0
  42. package/dist/dispatchers/generic-cli.d.ts.map +1 -0
  43. package/dist/dispatchers/generic-cli.js +471 -0
  44. package/dist/dispatchers/generic-cli.js.map +1 -0
  45. package/dist/dispatchers/openai-compatible.d.ts +57 -0
  46. package/dist/dispatchers/openai-compatible.d.ts.map +1 -0
  47. package/dist/dispatchers/openai-compatible.js +662 -0
  48. package/dist/dispatchers/openai-compatible.js.map +1 -0
  49. package/dist/dispatchers/shared/kill-tree.d.ts +21 -0
  50. package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -0
  51. package/dist/dispatchers/shared/kill-tree.js +35 -0
  52. package/dist/dispatchers/shared/kill-tree.js.map +1 -0
  53. package/dist/dispatchers/shared/rate-limit-headers.d.ts +22 -0
  54. package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -0
  55. package/dist/dispatchers/shared/rate-limit-headers.js +124 -0
  56. package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -0
  57. package/dist/dispatchers/shared/stream-subprocess.d.ts +53 -0
  58. package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -0
  59. package/dist/dispatchers/shared/stream-subprocess.js +274 -0
  60. package/dist/dispatchers/shared/stream-subprocess.js.map +1 -0
  61. package/dist/dispatchers/shared/subprocess.d.ts +25 -0
  62. package/dist/dispatchers/shared/subprocess.d.ts.map +1 -0
  63. package/dist/dispatchers/shared/subprocess.js +110 -0
  64. package/dist/dispatchers/shared/subprocess.js.map +1 -0
  65. package/dist/dispatchers/shared/which-available.d.ts +2 -0
  66. package/dist/dispatchers/shared/which-available.d.ts.map +1 -0
  67. package/dist/dispatchers/shared/which-available.js +8 -0
  68. package/dist/dispatchers/shared/which-available.js.map +1 -0
  69. package/dist/dispatchers/shared/windows-cmd.d.ts +38 -0
  70. package/dist/dispatchers/shared/windows-cmd.d.ts.map +1 -0
  71. package/dist/dispatchers/shared/windows-cmd.js +91 -0
  72. package/dist/dispatchers/shared/windows-cmd.js.map +1 -0
  73. package/dist/http/server.d.ts +15 -0
  74. package/dist/http/server.d.ts.map +1 -0
  75. package/dist/http/server.js +495 -0
  76. package/dist/http/server.js.map +1 -0
  77. package/dist/index.d.ts +27 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +29 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/job-runner.d.ts +19 -0
  82. package/dist/job-runner.d.ts.map +1 -0
  83. package/dist/job-runner.js +42 -0
  84. package/dist/job-runner.js.map +1 -0
  85. package/dist/jobs.d.ts +116 -0
  86. package/dist/jobs.d.ts.map +1 -0
  87. package/dist/jobs.js +542 -0
  88. package/dist/jobs.js.map +1 -0
  89. package/dist/leaderboard.d.ts +62 -0
  90. package/dist/leaderboard.d.ts.map +1 -0
  91. package/dist/leaderboard.js +287 -0
  92. package/dist/leaderboard.js.map +1 -0
  93. package/dist/mcp/config-hot-reload.d.ts +64 -0
  94. package/dist/mcp/config-hot-reload.d.ts.map +1 -0
  95. package/dist/mcp/config-hot-reload.js +144 -0
  96. package/dist/mcp/config-hot-reload.js.map +1 -0
  97. package/dist/mcp/dispatcher-factory.d.ts +20 -0
  98. package/dist/mcp/dispatcher-factory.d.ts.map +1 -0
  99. package/dist/mcp/dispatcher-factory.js +30 -0
  100. package/dist/mcp/dispatcher-factory.js.map +1 -0
  101. package/dist/mcp/resources.d.ts +8 -0
  102. package/dist/mcp/resources.d.ts.map +1 -0
  103. package/dist/mcp/resources.js +45 -0
  104. package/dist/mcp/resources.js.map +1 -0
  105. package/dist/mcp/server.d.ts +36 -0
  106. package/dist/mcp/server.d.ts.map +1 -0
  107. package/dist/mcp/server.js +80 -0
  108. package/dist/mcp/server.js.map +1 -0
  109. package/dist/mcp/tools.d.ts +171 -0
  110. package/dist/mcp/tools.d.ts.map +1 -0
  111. package/dist/mcp/tools.js +658 -0
  112. package/dist/mcp/tools.js.map +1 -0
  113. package/dist/observability/index.d.ts +52 -0
  114. package/dist/observability/index.d.ts.map +1 -0
  115. package/dist/observability/index.js +111 -0
  116. package/dist/observability/index.js.map +1 -0
  117. package/dist/observability/spans.d.ts +47 -0
  118. package/dist/observability/spans.d.ts.map +1 -0
  119. package/dist/observability/spans.js +72 -0
  120. package/dist/observability/spans.js.map +1 -0
  121. package/dist/quota.d.ts +91 -0
  122. package/dist/quota.d.ts.map +1 -0
  123. package/dist/quota.js +331 -0
  124. package/dist/quota.js.map +1 -0
  125. package/dist/route-policy.d.ts +24 -0
  126. package/dist/route-policy.d.ts.map +1 -0
  127. package/dist/route-policy.js +92 -0
  128. package/dist/route-policy.js.map +1 -0
  129. package/dist/router.d.ts +162 -0
  130. package/dist/router.d.ts.map +1 -0
  131. package/dist/router.js +878 -0
  132. package/dist/router.js.map +1 -0
  133. package/dist/safety.d.ts +7 -0
  134. package/dist/safety.d.ts.map +1 -0
  135. package/dist/safety.js +33 -0
  136. package/dist/safety.js.map +1 -0
  137. package/dist/status.d.ts +100 -0
  138. package/dist/status.d.ts.map +1 -0
  139. package/dist/status.js +228 -0
  140. package/dist/status.js.map +1 -0
  141. package/dist/types.d.ts +402 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +9 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/version.d.ts +2 -0
  146. package/dist/version.d.ts.map +1 -0
  147. package/dist/version.js +13 -0
  148. package/dist/version.js.map +1 -0
  149. package/dist/working-dir.d.ts +15 -0
  150. package/dist/working-dir.d.ts.map +1 -0
  151. package/dist/working-dir.js +22 -0
  152. package/dist/working-dir.js.map +1 -0
  153. package/dist/workspaces.d.ts +19 -0
  154. package/dist/workspaces.d.ts.map +1 -0
  155. package/dist/workspaces.js +352 -0
  156. package/dist/workspaces.js.map +1 -0
  157. package/package.json +96 -0
package/dist/quota.js ADDED
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Quota management for harness-dispatch.
3
+ *
4
+ * Ported from `coding_agent.quota`. Two-layer approach:
5
+ * 1. Reactive — quota state is updated from every dispatch response
6
+ * (rate-limit headers on 429s, or usage headers on success).
7
+ * 2. Proactive — each dispatcher can optionally implement `checkQuota()`
8
+ * for a live snapshot. Results are cached with a TTL to avoid
9
+ * hammering provider APIs.
10
+ */
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import path from "node:path";
14
+ import { parseLimit, parseRemaining, } from "./dispatchers/shared/rate-limit-headers.js";
15
+ export const DEFAULT_QUOTA_TTL_MS = 300_000; // 5 minutes
16
+ export const PROACTIVE_CHECK_TIMEOUT_MS = 15_000;
17
+ /**
18
+ * Default quota state location — same HARNESS_DISPATCH_STATE_DIR-override,
19
+ * else ~/.harness-dispatch/<subdir> pattern as jobs.ts/dispatch-log.ts. A
20
+ * bare "quota_state.json" (the old default) resolves relative to
21
+ * process.cwd(): running the router from different directories splits
22
+ * state across stray files, and — worse — the test suite writes real
23
+ * counts into whatever cwd the tests happen to run from (this repo's own
24
+ * root, in dev) since nothing points it elsewhere by default.
25
+ */
26
+ function defaultStateFile() {
27
+ const dir = process.env.HARNESS_DISPATCH_STATE_DIR ?? path.join(homedir(), ".harness-dispatch");
28
+ return path.join(dir, "quota_state.json");
29
+ }
30
+ function monotonicSec() {
31
+ return performance.now() / 1000;
32
+ }
33
+ /** Mutable quota snapshot for one service, updated reactively. */
34
+ export class QuotaState {
35
+ service;
36
+ remaining = null;
37
+ limit = null;
38
+ used = null;
39
+ resetAt = null;
40
+ source = "unknown";
41
+ updatedAtSec = 0; // performance.now() seconds
42
+ constructor(service) {
43
+ this.service = service;
44
+ }
45
+ get score() {
46
+ if (this.remaining !== null && this.limit && this.limit > 0) {
47
+ return Math.max(0, Math.min(1, this.remaining / this.limit));
48
+ }
49
+ if (this.used !== null && this.limit && this.limit > 0) {
50
+ return Math.max(0, Math.min(1, (this.limit - this.used) / this.limit));
51
+ }
52
+ return 1.0;
53
+ }
54
+ updateFromQuotaInfo(info) {
55
+ this.remaining = info.remaining ?? null;
56
+ this.limit = info.limit ?? null;
57
+ this.used = info.used ?? null;
58
+ this.resetAt = info.resetAt ?? null;
59
+ this.source = info.source;
60
+ this.updatedAtSec = monotonicSec();
61
+ }
62
+ toJSON() {
63
+ return {
64
+ used: this.used,
65
+ limit: this.limit,
66
+ remaining: this.remaining,
67
+ resetAt: this.resetAt,
68
+ score: this.score,
69
+ source: this.source,
70
+ updatedAgeSec: Math.round((monotonicSec() - this.updatedAtSec) * 10) / 10,
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Manages quota state for all dispatchers.
76
+ */
77
+ export class QuotaCache {
78
+ dispatchers;
79
+ ttlMs;
80
+ stateFile;
81
+ states = {};
82
+ /** performance.now() seconds of last proactive check per service. */
83
+ lastChecked = {};
84
+ localCounts;
85
+ localSuccessCounts;
86
+ localFailureCounts;
87
+ persistCounter = 0;
88
+ constructor(dispatchers, opts = {}) {
89
+ this.dispatchers = dispatchers;
90
+ this.ttlMs = opts.ttlMs ?? DEFAULT_QUOTA_TTL_MS;
91
+ this.stateFile = opts.stateFile ?? defaultStateFile();
92
+ for (const name of Object.keys(dispatchers)) {
93
+ this.states[name] = new QuotaState(name);
94
+ }
95
+ const loaded = this.loadLocalCounts();
96
+ this.localCounts = loaded.calls;
97
+ this.localSuccessCounts = loaded.success;
98
+ this.localFailureCounts = loaded.failure;
99
+ }
100
+ // ------------------------------------------------------------------
101
+ // Public API — called by Router
102
+ // ------------------------------------------------------------------
103
+ async getQuotaScore(service) {
104
+ await this.maybeRefresh(service);
105
+ const state = this.states[service];
106
+ return state ? state.score : 1.0;
107
+ }
108
+ recordResult(service, result) {
109
+ this.localCounts[service] = (this.localCounts[service] ?? 0) + 1;
110
+ if (result.success) {
111
+ this.localSuccessCounts[service] = (this.localSuccessCounts[service] ?? 0) + 1;
112
+ }
113
+ else {
114
+ this.localFailureCounts[service] = (this.localFailureCounts[service] ?? 0) + 1;
115
+ }
116
+ // CLI commands often exit immediately after a route. Use the synchronous
117
+ // atomic path so local-count persistence cannot leave a temp file behind.
118
+ this.saveLocalCountsSync();
119
+ if (!result.rateLimitHeaders && !result.rateLimited) {
120
+ return;
121
+ }
122
+ let state = this.states[service];
123
+ if (!state) {
124
+ state = new QuotaState(service);
125
+ this.states[service] = state;
126
+ }
127
+ if (result.rateLimitHeaders) {
128
+ const remaining = parseRemaining(result.rateLimitHeaders);
129
+ const limit = parseLimit(result.rateLimitHeaders);
130
+ if (remaining !== null || limit !== null) {
131
+ state.remaining = remaining;
132
+ state.limit = limit;
133
+ state.source = "headers";
134
+ state.updatedAtSec = monotonicSec();
135
+ }
136
+ }
137
+ }
138
+ async getQuotaInfo(service) {
139
+ await this.maybeRefresh(service);
140
+ const state = this.states[service];
141
+ if (!state) {
142
+ return null;
143
+ }
144
+ const info = {
145
+ service,
146
+ source: state.source,
147
+ };
148
+ if (state.used !== null)
149
+ info.used = state.used;
150
+ if (state.limit !== null)
151
+ info.limit = state.limit;
152
+ if (state.remaining !== null)
153
+ info.remaining = state.remaining;
154
+ if (state.resetAt !== null)
155
+ info.resetAt = state.resetAt;
156
+ return info;
157
+ }
158
+ async fullStatus() {
159
+ const out = {};
160
+ for (const service of Object.keys(this.dispatchers)) {
161
+ await this.maybeRefresh(service);
162
+ const state = this.states[service] ?? new QuotaState(service);
163
+ out[service] = {
164
+ ...state.toJSON(),
165
+ localCallCount: this.localCounts[service] ?? 0,
166
+ localSuccessCount: this.localSuccessCounts[service] ?? 0,
167
+ localFailureCount: this.localFailureCounts[service] ?? 0,
168
+ };
169
+ }
170
+ return out;
171
+ }
172
+ // ------------------------------------------------------------------
173
+ // Proactive refresh
174
+ // ------------------------------------------------------------------
175
+ async maybeRefresh(service) {
176
+ const last = this.lastChecked[service];
177
+ // Node's performance.now() starts near 0 at process start, unlike Python's
178
+ // time.monotonic() (which references OS boot). Treat "never checked" as a
179
+ // force-refresh rather than comparing against 0 — otherwise the first call
180
+ // in a freshly-started process may short-circuit before TTL elapses.
181
+ if (last !== undefined && monotonicSec() - last < this.ttlMs / 1000) {
182
+ return;
183
+ }
184
+ const dispatcher = this.dispatchers[service];
185
+ if (!dispatcher) {
186
+ return;
187
+ }
188
+ this.lastChecked[service] = monotonicSec();
189
+ try {
190
+ const info = await withTimeout(dispatcher.checkQuota(), PROACTIVE_CHECK_TIMEOUT_MS);
191
+ if (info.source !== "unknown") {
192
+ let state = this.states[service];
193
+ if (!state) {
194
+ state = new QuotaState(service);
195
+ this.states[service] = state;
196
+ }
197
+ state.updateFromQuotaInfo(info);
198
+ }
199
+ }
200
+ catch {
201
+ // Proactive check failed — rely on reactive state.
202
+ }
203
+ }
204
+ // ------------------------------------------------------------------
205
+ // Local count persistence
206
+ // ------------------------------------------------------------------
207
+ loadLocalCounts() {
208
+ if (!existsSync(this.stateFile)) {
209
+ return { calls: {}, success: {}, failure: {} };
210
+ }
211
+ try {
212
+ const raw = readFileSync(this.stateFile, "utf-8");
213
+ const data = JSON.parse(raw);
214
+ const calls = {};
215
+ const success = {};
216
+ const failure = {};
217
+ for (const [k, v] of Object.entries(data)) {
218
+ if (!v)
219
+ continue;
220
+ if (typeof v.local_calls === "number")
221
+ calls[k] = v.local_calls;
222
+ if (typeof v.local_success === "number")
223
+ success[k] = v.local_success;
224
+ if (typeof v.local_failure === "number")
225
+ failure[k] = v.local_failure;
226
+ }
227
+ return { calls, success, failure };
228
+ }
229
+ catch {
230
+ return { calls: {}, success: {}, failure: {} };
231
+ }
232
+ }
233
+ /**
234
+ * Build the on-disk payload, merging new counts over any existing state.
235
+ *
236
+ * Known limitation: this read-modify-write is not atomic across processes.
237
+ * Two `harness-dispatch` CLI invocations racing on the same stateFile can
238
+ * both read the same baseline and each write back only their own count,
239
+ * silently losing the other's increment. Left as-is deliberately —
240
+ * localCallCount/localSuccessCount/localFailureCount are purely
241
+ * informational (surfaced in `status`/`doctor`/`usage` output and the live
242
+ * dashboard; see status.ts, dashboard/live.ts), never consulted by
243
+ * QuotaState.score or any routing/billing decision — so an occasional
244
+ * undercount here doesn't affect what the router actually does, only what
245
+ * it reports. Not worth cross-process file locking for a display counter.
246
+ */
247
+ buildStatePayload() {
248
+ let existing = {};
249
+ try {
250
+ if (existsSync(this.stateFile)) {
251
+ const raw = readFileSync(this.stateFile, "utf-8");
252
+ const parsed = JSON.parse(raw);
253
+ if (parsed && typeof parsed === "object") {
254
+ existing = parsed;
255
+ }
256
+ }
257
+ }
258
+ catch {
259
+ existing = {};
260
+ }
261
+ for (const [service, count] of Object.entries(this.localCounts)) {
262
+ const bucket = existing[service] ?? {};
263
+ bucket["local_calls"] = count;
264
+ existing[service] = bucket;
265
+ }
266
+ for (const [service, count] of Object.entries(this.localSuccessCounts)) {
267
+ const bucket = existing[service] ?? {};
268
+ bucket["local_success"] = count;
269
+ existing[service] = bucket;
270
+ }
271
+ for (const [service, count] of Object.entries(this.localFailureCounts)) {
272
+ const bucket = existing[service] ?? {};
273
+ bucket["local_failure"] = count;
274
+ existing[service] = bucket;
275
+ }
276
+ return JSON.stringify(existing, null, 2);
277
+ }
278
+ nextTempStateFile() {
279
+ this.persistCounter += 1;
280
+ return `${this.stateFile}.${process.pid}.${Date.now()}.${this.persistCounter}.tmp`;
281
+ }
282
+ writeStatePayloadAtomicSync(payload) {
283
+ const tmp = this.nextTempStateFile();
284
+ try {
285
+ mkdirSync(path.dirname(this.stateFile), { recursive: true });
286
+ writeFileSync(tmp, payload);
287
+ renameSync(tmp, this.stateFile);
288
+ }
289
+ catch (err) {
290
+ try {
291
+ unlinkSync(tmp);
292
+ }
293
+ catch {
294
+ // Ignore cleanup failures.
295
+ }
296
+ throw err;
297
+ }
298
+ }
299
+ /**
300
+ * Write local counts to disk via a temp-file-then-rename (atomic within
301
+ * this process — see buildStatePayload's doc comment for the known
302
+ * cross-process caveat). Synchronous so a CLI invocation that exits
303
+ * immediately after a route can't leave a temp file behind.
304
+ */
305
+ saveLocalCountsSync() {
306
+ try {
307
+ this.writeStatePayloadAtomicSync(this.buildStatePayload());
308
+ }
309
+ catch {
310
+ // Ignore.
311
+ }
312
+ }
313
+ }
314
+ // ---------------------------------------------------------------------------
315
+ // Helpers
316
+ // ---------------------------------------------------------------------------
317
+ function withTimeout(promise, timeoutMs) {
318
+ return new Promise((resolve, reject) => {
319
+ const timer = setTimeout(() => {
320
+ reject(new Error(`timeout after ${timeoutMs}ms`));
321
+ }, timeoutMs);
322
+ promise.then((value) => {
323
+ clearTimeout(timer);
324
+ resolve(value);
325
+ }, (err) => {
326
+ clearTimeout(timer);
327
+ reject(err instanceof Error ? err : new Error(String(err)));
328
+ });
329
+ });
330
+ }
331
+ //# sourceMappingURL=quota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Dispatcher } from "./dispatchers/base.js";
2
+ import type { RouteBilling, RoutePolicy, RouteSkip, SafetyProfile, ServiceConfig } from "./types.js";
3
+ export interface RoutePolicyResult {
4
+ blocked: boolean;
5
+ skipped?: RouteSkip;
6
+ }
7
+ export declare function evaluateRoutePolicy(route: string, svc: ServiceConfig, opts?: {
8
+ dispatcher?: Dispatcher;
9
+ circuitBroken?: boolean;
10
+ requestedSafetyProfile?: SafetyProfile;
11
+ routePolicy?: RoutePolicy;
12
+ }): RoutePolicyResult;
13
+ /**
14
+ * Scoring penalty applied to nudge route selection toward cheaper options
15
+ * when scores are otherwise close. Local routes (free, on this machine) pay
16
+ * nothing. Included-plan/free-quota-but-remote routes pay a small penalty
17
+ * (prefer local when close). Routes that can incur real per-use cost
18
+ * (metered API, unknown billing) must pay MORE than that, not less — they
19
+ * were previously falling through to the same 0 penalty as local routes,
20
+ * which meant the router could prefer spending real money over using a
21
+ * subscription you're already paying for or a free local model.
22
+ */
23
+ export declare function nonLocalIncludedRoutePenalty(billing: RouteBilling): number;
24
+ //# sourceMappingURL=route-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-policy.d.ts","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,aAAa,EACb,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,WAAW,CAAC,EAAE,WAAW,CAAC;CACtB,GACL,iBAAiB,CAqDnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAI1E"}
@@ -0,0 +1,92 @@
1
+ import { billingIsBlocked, billingIsUnknown, buildRouteBilling } from "./billing.js";
2
+ import { effectiveSafetyProfile, safetyProfileCompatible } from "./safety.js";
3
+ export function evaluateRoutePolicy(route, svc, opts = {}) {
4
+ if (!svc.enabled) {
5
+ return skip(route, "disabled", "route is disabled");
6
+ }
7
+ if (!opts.dispatcher) {
8
+ return skip(route, "no_dispatcher", "no dispatcher is registered for this route");
9
+ }
10
+ if (!opts.dispatcher.isAvailable()) {
11
+ return skip(route, "unavailable", "required command or endpoint is unavailable");
12
+ }
13
+ if (opts.circuitBroken) {
14
+ return skip(route, "circuit_broken", "route circuit breaker is open");
15
+ }
16
+ const billing = buildRouteBilling(svc);
17
+ const routePolicy = evaluateOperationalRoutePolicy(route, billing, opts.routePolicy);
18
+ if (routePolicy.blocked)
19
+ return routePolicy;
20
+ if (billingIsUnknown(billing) &&
21
+ !isIncludedOrLocalRoute(billing) &&
22
+ !billing.allowPaidUsage) {
23
+ return skip(route, "unknown_billing", "billing source is unknown and paid usage is not allowed — this is a config-level " +
24
+ `block, not an availability problem; the operator must add \`allow_paid_usage: true\` ` +
25
+ `to '${route}' in config.yaml to enable it`);
26
+ }
27
+ if (billingIsBlocked(billing)) {
28
+ return skip(route, "paid_blocked", "route can incur paid usage and paid usage is not allowed — this is a config-level " +
29
+ `block, not an availability problem; the operator must add \`allow_paid_usage: true\` ` +
30
+ `to '${route}' in config.yaml (or run \`harness-dispatch configure --allow-paid\`) to enable it`);
31
+ }
32
+ if (!safetyProfileCompatible(svc, opts.requestedSafetyProfile)) {
33
+ return skip(route, "safety_incompatible", `effective safety ${effectiveSafetyProfile(svc, opts.requestedSafetyProfile)} exceeds requested safety`);
34
+ }
35
+ return { blocked: false };
36
+ }
37
+ /**
38
+ * Scoring penalty applied to nudge route selection toward cheaper options
39
+ * when scores are otherwise close. Local routes (free, on this machine) pay
40
+ * nothing. Included-plan/free-quota-but-remote routes pay a small penalty
41
+ * (prefer local when close). Routes that can incur real per-use cost
42
+ * (metered API, unknown billing) must pay MORE than that, not less — they
43
+ * were previously falling through to the same 0 penalty as local routes,
44
+ * which meant the router could prefer spending real money over using a
45
+ * subscription you're already paying for or a free local model.
46
+ */
47
+ export function nonLocalIncludedRoutePenalty(billing) {
48
+ if (isLocalRoute(billing))
49
+ return 0;
50
+ if (isIncludedOrLocalRoute(billing))
51
+ return 0.2;
52
+ return 0.4;
53
+ }
54
+ function evaluateOperationalRoutePolicy(route, billing, routePolicy) {
55
+ if (routePolicy === "blocked") {
56
+ return skip(route, "route_policy", "excluded by the CALLER's own hints.routePolicy='blocked' on this request (dry-run) " +
57
+ "— not a config restriction or a router safety judgment about this route or its " +
58
+ "content; drop or change that hint to allow it");
59
+ }
60
+ if (routePolicy === "local_only" && !isLocalRoute(billing)) {
61
+ return skip(route, "route_policy", "excluded by the CALLER's own hints.routePolicy='local_only' on this request " +
62
+ "— not a config restriction or a router safety judgment about this route or its " +
63
+ "content; drop or change that hint to allow non-local routes");
64
+ }
65
+ if (routePolicy === "approval_required" && !isLocalRoute(billing)) {
66
+ return skip(route, "approval_required", "excluded by the CALLER's own hints.routePolicy='approval_required' on this request " +
67
+ "— not a config restriction or a router safety judgment about this route or its " +
68
+ "content; drop or change that hint to allow non-local routes");
69
+ }
70
+ return { blocked: false };
71
+ }
72
+ function isLocalRoute(billing) {
73
+ return (billing.kind === "local_compute" ||
74
+ billing.provider === "local" ||
75
+ billing.surface === "local_endpoint" ||
76
+ billing.authSource === "local_network");
77
+ }
78
+ function isIncludedOrLocalRoute(billing) {
79
+ return (isLocalRoute(billing) ||
80
+ billing.kind === "free_quota" ||
81
+ billing.kind === "included_plan_usage" ||
82
+ billing.kind === "included_plan_then_flexible_credits" ||
83
+ billing.kind === "included_credit_then_optional_overage" ||
84
+ billing.kind === "included_usage_then_on_demand");
85
+ }
86
+ function skip(route, code, message) {
87
+ return {
88
+ blocked: true,
89
+ skipped: { route, code, message },
90
+ };
91
+ }
92
+ //# sourceMappingURL=route-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-policy.js","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Load-balancing router for harness-dispatch.
3
+ *
4
+ * Routing strategy
5
+ * ----------------
6
+ * Services are grouped by tier (lower number = higher quality). Selection
7
+ * prefers the best-scoring candidate in the lowest eligible tier:
8
+ *
9
+ * Tier 1 (frontier) -> Tier 2 (strong) -> Tier 3 (fast/local)
10
+ *
11
+ * Services that are circuit-broken, policy-blocked, unavailable, or already
12
+ * tried this request are excluded from candidacy. On a failed dispatch
13
+ * (including rate limits) the router excludes that service and retries with
14
+ * the next-best candidate, up to `maxFallbacks` extra attempts per request
15
+ * (default 2, i.e. 3 attempts total — pass maxFallbacks: 0 to let the caller
16
+ * own retries). A request can therefore fail with untried routes remaining
17
+ * when the attempt cap is hit before candidates run out.
18
+ *
19
+ * Quality scoring
20
+ * ---------------
21
+ * Within a tier, services are ranked by a composite score:
22
+ *
23
+ * final_score = quality_score * cli_capability * capability[task_type]
24
+ * * quota_score * weight
25
+ *
26
+ * Adjustments applied during selection (reflected in the reported
27
+ * finalScore for picked/fallback routes, but not for forced/explicit ones):
28
+ * - Cost-based penalty under the "standard" route policy
29
+ * (nonLocalIncludedRoutePenalty): 0 for local routes, -0.2 for non-local
30
+ * included-plan/free-quota routes, -0.4 for routes that can incur real
31
+ * per-use cost (metered API, unknown billing) — cheapest/lowest-risk
32
+ * wins ties, in that order.
33
+ * - +0.5 when hints.model matches the service name or one of its models.
34
+ * - Under prefer_large_context, +0.3 for routes declaring >=2M
35
+ * max_input_tokens and +0.15 for >=1M — declared context size, not
36
+ * harness name.
37
+ * - +0.3 when task_type="local" and the service is an openai_compatible
38
+ * endpoint on localhost / 127.0.0.1.
39
+ *
40
+ * Tier auto-derivation
41
+ * --------------------
42
+ * If a service has `leaderboardModel` set in config, its tier is
43
+ * auto-derived from the Arena ELO score via LeaderboardCache.autoTier().
44
+ * Explicit `tier` in config is the fallback when ELO is unavailable.
45
+ *
46
+ * R3: adds `stream()` / `streamTo()` that emit `DispatcherEvent`s with an
47
+ * attached `RoutingDecision`. The buffered `route` / `routeTo` methods are
48
+ * reimplemented on top of the streaming primitives.
49
+ */
50
+ import type { DispatchResult, DispatcherEvent, RouterConfig, RoutingDecision, RouteHints, RouteSkip, SafetyProfile, ServiceConfig, TaskType } from "./types.js";
51
+ import { CircuitBreaker } from "./circuit-breaker.js";
52
+ import { BreakerStore } from "./breaker-store.js";
53
+ import { QuotaCache } from "./quota.js";
54
+ import { LeaderboardCache } from "./leaderboard.js";
55
+ import type { Dispatcher } from "./dispatchers/base.js";
56
+ import { drainDispatcherStream } from "./dispatchers/base.js";
57
+ /**
58
+ * Options for explicit-service dispatch (routeTo / streamTo).
59
+ *
60
+ * `model` is an instruction, not a routing hint: the caller already chose
61
+ * the service, so the value is passed to the harness as a model override
62
+ * verbatim (an invalid name fails loudly downstream instead of being
63
+ * silently dropped). `taskType` feeds capability scoring metadata and
64
+ * per-task model escalation (escalateOn/escalateModel).
65
+ */
66
+ export interface ExplicitDispatchOpts {
67
+ safetyProfile?: SafetyProfile;
68
+ workspacePolicy?: ServiceConfig["workspacePolicy"];
69
+ routePolicy?: import("./types.js").RoutePolicy;
70
+ model?: string;
71
+ taskType?: TaskType;
72
+ timeoutMs?: number;
73
+ /**
74
+ * Fallback timeout when neither `timeoutMs` (explicit per-call override)
75
+ * nor the service's own `timeoutMs` config is set — below both in
76
+ * precedence, so it never silently overrides a real value. Used by `job`
77
+ * to give background dispatches a generous ceiling without a caller
78
+ * having to know to ask for one; `code` (which blocks the MCP call) does
79
+ * not set this and keeps the dispatcher's own short default.
80
+ */
81
+ defaultTimeoutMs?: number;
82
+ }
83
+ /**
84
+ * Router streaming events wrap the dispatcher event with the active routing
85
+ * decision. The decision is emitted on the first event of each dispatch
86
+ * attempt (so consumers can show "routing to claude_code" before the first
87
+ * token arrives) and is also attached to every subsequent event in case the
88
+ * consumer missed the first.
89
+ */
90
+ export interface RouterStreamEvent {
91
+ event: DispatcherEvent;
92
+ decision: RoutingDecision | null;
93
+ }
94
+ export declare class Router {
95
+ #private;
96
+ private readonly config;
97
+ private readonly quota;
98
+ private readonly dispatchers;
99
+ private readonly leaderboard;
100
+ private readonly breakers;
101
+ private lastSkippedRoutes;
102
+ private readonly breakerStore;
103
+ constructor(config: RouterConfig, quota: QuotaCache, dispatchers: Record<string, Dispatcher>, leaderboard: LeaderboardCache, breakerStore?: BreakerStore);
104
+ private persistBreaker;
105
+ getBreaker(service: string): CircuitBreaker | undefined;
106
+ skippedRoutes(): RouteSkip[];
107
+ pickService(opts?: {
108
+ hints?: RouteHints;
109
+ prompt?: string;
110
+ files?: string[];
111
+ exclude?: Set<string>;
112
+ }): Promise<RoutingDecision | null>;
113
+ /**
114
+ * Stream events from the chosen dispatcher, with the same fallback logic
115
+ * as `route()`. When a dispatch fails (non-rate-limit), the router picks
116
+ * another service and yields that service's events — so the caller sees
117
+ * events from potentially multiple services during fallback.
118
+ *
119
+ * The last `completion` or `error` event always reflects the final
120
+ * outcome (success-with-fallback or all-attempts-failed).
121
+ */
122
+ stream(prompt: string, files: string[], workingDir: string, opts?: {
123
+ hints?: RouteHints;
124
+ maxFallbacks?: number;
125
+ defaultTimeoutMs?: number;
126
+ }): AsyncIterable<RouterStreamEvent>;
127
+ /**
128
+ * Stream from a specific service, bypassing tier selection. Same semantics
129
+ * as `routeTo()` but yields events in real time.
130
+ */
131
+ streamTo(service: string, prompt: string, files: string[], workingDir: string, opts?: ExplicitDispatchOpts): AsyncIterable<RouterStreamEvent>;
132
+ /**
133
+ * Route a task, with automatic fallback on transient failures.
134
+ *
135
+ * R3: reimplemented on top of `stream()`. The per-attempt result is
136
+ * captured from the `completion` event and drives the fallback loop.
137
+ *
138
+ * The old route() also had a quirk: when pickService returned null with
139
+ * no prior attempts, it yielded an error DispatchResult. On a later
140
+ * fallback round that returned null it returned the last attempt's
141
+ * result+decision. The streaming-based reimplementation below preserves
142
+ * the same externally observable behaviour for existing tests.
143
+ */
144
+ route(prompt: string, files: string[], workingDir: string, opts?: {
145
+ hints?: RouteHints;
146
+ maxFallbacks?: number;
147
+ }): Promise<{
148
+ result: DispatchResult;
149
+ decision: RoutingDecision | null;
150
+ }>;
151
+ /**
152
+ * Dispatch to a specific service, bypassing tier selection.
153
+ */
154
+ routeTo(service: string, prompt: string, files: string[], workingDir: string, opts?: ExplicitDispatchOpts): Promise<{
155
+ result: DispatchResult;
156
+ decision: RoutingDecision | null;
157
+ }>;
158
+ private handleResult;
159
+ circuitBreakerStatus(): Record<string, ReturnType<CircuitBreaker["status"]>>;
160
+ }
161
+ export { drainDispatcherStream };
162
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}