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.
- package/LICENSE +21 -0
- package/README.md +582 -0
- package/config.default.yaml +397 -0
- package/data/coding_benchmarks.json +431 -0
- package/dist/auth.d.ts +9 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +73 -0
- package/dist/auth.js.map +1 -0
- package/dist/billing.d.ts +7 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +201 -0
- package/dist/billing.js.map +1 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +488 -0
- package/dist/bin.js.map +1 -0
- package/dist/breaker-store.d.ts +31 -0
- package/dist/breaker-store.d.ts.map +1 -0
- package/dist/breaker-store.js +103 -0
- package/dist/breaker-store.js.map +1 -0
- package/dist/circuit-breaker.d.ts +47 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +102 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1248 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/live.d.ts +49 -0
- package/dist/dashboard/live.d.ts.map +1 -0
- package/dist/dashboard/live.js +149 -0
- package/dist/dashboard/live.js.map +1 -0
- package/dist/dispatch-log.d.ts +46 -0
- package/dist/dispatch-log.d.ts.map +1 -0
- package/dist/dispatch-log.js +89 -0
- package/dist/dispatch-log.js.map +1 -0
- package/dist/dispatchers/base.d.ts +55 -0
- package/dist/dispatchers/base.d.ts.map +1 -0
- package/dist/dispatchers/base.js +80 -0
- package/dist/dispatchers/base.js.map +1 -0
- package/dist/dispatchers/generic-cli.d.ts +30 -0
- package/dist/dispatchers/generic-cli.d.ts.map +1 -0
- package/dist/dispatchers/generic-cli.js +471 -0
- package/dist/dispatchers/generic-cli.js.map +1 -0
- package/dist/dispatchers/openai-compatible.d.ts +57 -0
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -0
- package/dist/dispatchers/openai-compatible.js +662 -0
- package/dist/dispatchers/openai-compatible.js.map +1 -0
- package/dist/dispatchers/shared/kill-tree.d.ts +21 -0
- package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -0
- package/dist/dispatchers/shared/kill-tree.js +35 -0
- package/dist/dispatchers/shared/kill-tree.js.map +1 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts +22 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -0
- package/dist/dispatchers/shared/rate-limit-headers.js +124 -0
- package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts +53 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -0
- package/dist/dispatchers/shared/stream-subprocess.js +274 -0
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -0
- package/dist/dispatchers/shared/subprocess.d.ts +25 -0
- package/dist/dispatchers/shared/subprocess.d.ts.map +1 -0
- package/dist/dispatchers/shared/subprocess.js +110 -0
- package/dist/dispatchers/shared/subprocess.js.map +1 -0
- package/dist/dispatchers/shared/which-available.d.ts +2 -0
- package/dist/dispatchers/shared/which-available.d.ts.map +1 -0
- package/dist/dispatchers/shared/which-available.js +8 -0
- package/dist/dispatchers/shared/which-available.js.map +1 -0
- package/dist/dispatchers/shared/windows-cmd.d.ts +38 -0
- package/dist/dispatchers/shared/windows-cmd.d.ts.map +1 -0
- package/dist/dispatchers/shared/windows-cmd.js +91 -0
- package/dist/dispatchers/shared/windows-cmd.js.map +1 -0
- package/dist/http/server.d.ts +15 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +495 -0
- package/dist/http/server.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/job-runner.d.ts +19 -0
- package/dist/job-runner.d.ts.map +1 -0
- package/dist/job-runner.js +42 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/jobs.d.ts +116 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +542 -0
- package/dist/jobs.js.map +1 -0
- package/dist/leaderboard.d.ts +62 -0
- package/dist/leaderboard.d.ts.map +1 -0
- package/dist/leaderboard.js +287 -0
- package/dist/leaderboard.js.map +1 -0
- package/dist/mcp/config-hot-reload.d.ts +64 -0
- package/dist/mcp/config-hot-reload.d.ts.map +1 -0
- package/dist/mcp/config-hot-reload.js +144 -0
- package/dist/mcp/config-hot-reload.js.map +1 -0
- package/dist/mcp/dispatcher-factory.d.ts +20 -0
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -0
- package/dist/mcp/dispatcher-factory.js +30 -0
- package/dist/mcp/dispatcher-factory.js.map +1 -0
- package/dist/mcp/resources.d.ts +8 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +80 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +171 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +658 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/observability/index.d.ts +52 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +111 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/spans.d.ts +47 -0
- package/dist/observability/spans.d.ts.map +1 -0
- package/dist/observability/spans.js +72 -0
- package/dist/observability/spans.js.map +1 -0
- package/dist/quota.d.ts +91 -0
- package/dist/quota.d.ts.map +1 -0
- package/dist/quota.js +331 -0
- package/dist/quota.js.map +1 -0
- package/dist/route-policy.d.ts +24 -0
- package/dist/route-policy.d.ts.map +1 -0
- package/dist/route-policy.js +92 -0
- package/dist/route-policy.js.map +1 -0
- package/dist/router.d.ts +162 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +878 -0
- package/dist/router.js.map +1 -0
- package/dist/safety.d.ts +7 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +33 -0
- package/dist/safety.js.map +1 -0
- package/dist/status.d.ts +100 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +228 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +402 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/dist/working-dir.d.ts +15 -0
- package/dist/working-dir.d.ts.map +1 -0
- package/dist/working-dir.js +22 -0
- package/dist/working-dir.js.map +1 -0
- package/dist/workspaces.d.ts +19 -0
- package/dist/workspaces.d.ts.map +1 -0
- package/dist/workspaces.js +352 -0
- package/dist/workspaces.js.map +1 -0
- 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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"}
|
package/dist/router.d.ts
ADDED
|
@@ -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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"}
|