agents-relay 1.0.129 → 1.0.130
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/usage.js +68 -3
- package/dist/worker-routing.js +13 -2
- package/package.json +1 -1
package/dist/usage.js
CHANGED
|
@@ -4,6 +4,67 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
+
export class BrowserWorkspaceUsageReader {
|
|
8
|
+
command;
|
|
9
|
+
timeoutMs;
|
|
10
|
+
now;
|
|
11
|
+
staleAfterMs;
|
|
12
|
+
constructor(command = 'browser-harness', timeoutMs = 10_000, now = () => new Date(), staleAfterMs = 15 * 60_000) {
|
|
13
|
+
this.command = command;
|
|
14
|
+
this.timeoutMs = timeoutMs;
|
|
15
|
+
this.now = now;
|
|
16
|
+
this.staleAfterMs = staleAfterMs;
|
|
17
|
+
}
|
|
18
|
+
async read() {
|
|
19
|
+
const observedAt = this.now().toISOString();
|
|
20
|
+
return await new Promise(resolve => {
|
|
21
|
+
const child = spawn(this.command, [], { stdio: ['pipe', 'pipe', 'ignore'] });
|
|
22
|
+
let output = '';
|
|
23
|
+
let settled = false;
|
|
24
|
+
const finish = (capacity) => {
|
|
25
|
+
if (settled)
|
|
26
|
+
return;
|
|
27
|
+
settled = true;
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
child.kill();
|
|
30
|
+
resolve(capacity);
|
|
31
|
+
};
|
|
32
|
+
const unavailable = (reason) => ({ name: 'Harness', poolSize: null, idle: null, leased: null, available: null, observedAt, reason });
|
|
33
|
+
const timer = setTimeout(() => finish(unavailable('Browser Workspace capacity probe timed out.')), this.timeoutMs);
|
|
34
|
+
child.stdout.setEncoding('utf8');
|
|
35
|
+
child.stdout.on('data', chunk => { output += chunk; });
|
|
36
|
+
child.once('error', () => finish(unavailable('Browser Workspace capacity probe could not be started.')));
|
|
37
|
+
child.once('close', code => {
|
|
38
|
+
if (settled)
|
|
39
|
+
return;
|
|
40
|
+
const lines = output.trim().split('\n').reverse();
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(line);
|
|
44
|
+
const name = typeof raw.name === 'string' ? raw.name : 'Harness';
|
|
45
|
+
const rawObservedAt = typeof raw.observedAt === 'string' && !Number.isNaN(Date.parse(raw.observedAt)) ? raw.observedAt : observedAt;
|
|
46
|
+
if (Date.parse(observedAt) - Date.parse(rawObservedAt) > this.staleAfterMs) {
|
|
47
|
+
finish(unavailable('Browser Workspace capacity probe is stale.'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const numberOrNull = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
51
|
+
const idle = numberOrNull(raw.idle);
|
|
52
|
+
const leased = numberOrNull(raw.leased);
|
|
53
|
+
const poolSize = numberOrNull(raw.poolSize);
|
|
54
|
+
const available = typeof raw.available === 'boolean' ? raw.available : idle === null ? null : idle > 0;
|
|
55
|
+
if (idle !== null || leased !== null || poolSize !== null || available !== null) {
|
|
56
|
+
finish({ name, poolSize, idle, leased, available, observedAt: rawObservedAt, reason: null });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch { /* browser-harness may print startup diagnostics before JSON */ }
|
|
61
|
+
}
|
|
62
|
+
finish(unavailable(code === 0 ? 'Browser Workspace capacity probe returned no usable capacity.' : `Browser Workspace capacity probe exited with code ${code ?? 'unknown'}.`));
|
|
63
|
+
});
|
|
64
|
+
child.stdin.end('import json\nprint(json.dumps(workspace_capacity()))\n');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
7
68
|
function finiteNumber(value) {
|
|
8
69
|
if (typeof value === 'number' && Number.isFinite(value))
|
|
9
70
|
return value;
|
|
@@ -320,18 +381,21 @@ export class UsageRegistry {
|
|
|
320
381
|
zaiReader;
|
|
321
382
|
deepSeekReader;
|
|
322
383
|
openRouterReader;
|
|
323
|
-
|
|
384
|
+
browserWorkspaceReader;
|
|
385
|
+
constructor(openaiReader, zaiReader = new ZaiUsageReader(), deepSeekReader = null, openRouterReader = null, browserWorkspaceReader = null) {
|
|
324
386
|
this.openaiReader = openaiReader;
|
|
325
387
|
this.zaiReader = zaiReader;
|
|
326
388
|
this.deepSeekReader = deepSeekReader;
|
|
327
389
|
this.openRouterReader = openRouterReader;
|
|
390
|
+
this.browserWorkspaceReader = browserWorkspaceReader;
|
|
328
391
|
}
|
|
329
392
|
async snapshot() {
|
|
330
|
-
const [openai, zai, deepseek, openrouter] = await Promise.all([
|
|
393
|
+
const [openai, zai, deepseek, openrouter, browserWorkspace] = await Promise.all([
|
|
331
394
|
this.openaiReader.read(),
|
|
332
395
|
this.zaiReader.read(),
|
|
333
396
|
this.deepSeekReader?.read() ?? Promise.resolve(null),
|
|
334
397
|
this.openRouterReader?.read() ?? Promise.resolve(null),
|
|
398
|
+
this.browserWorkspaceReader?.read() ?? Promise.resolve(null),
|
|
335
399
|
]);
|
|
336
400
|
const credits = [];
|
|
337
401
|
if (deepseek)
|
|
@@ -345,6 +409,7 @@ export class UsageRegistry {
|
|
|
345
409
|
{ id: 'zai', label: 'Z.ai', usage: zai },
|
|
346
410
|
],
|
|
347
411
|
credits,
|
|
412
|
+
...(browserWorkspace ? { browserWorkspace } : {}),
|
|
348
413
|
};
|
|
349
414
|
}
|
|
350
415
|
}
|
|
@@ -370,5 +435,5 @@ export class UnavailableUsageReader {
|
|
|
370
435
|
}
|
|
371
436
|
}
|
|
372
437
|
export function codexAndZaiUsageRegistry(codexHome, _legacyWindowDays) {
|
|
373
|
-
return new UsageRegistry(new CodexApiUsageReader(), new ZaiUsageReader(), process.env.DEEPSEEK_API_KEY ? new DeepSeekCreditReader() : null, process.env.OPENROUTER_API_KEY ? new OpenRouterCreditReader() : null);
|
|
438
|
+
return new UsageRegistry(new CodexApiUsageReader(), new ZaiUsageReader(), process.env.DEEPSEEK_API_KEY ? new DeepSeekCreditReader() : null, process.env.OPENROUTER_API_KEY ? new OpenRouterCreditReader() : null, new BrowserWorkspaceUsageReader());
|
|
374
439
|
}
|
package/dist/worker-routing.js
CHANGED
|
@@ -68,6 +68,7 @@ export function workerCandidates(adapters, registered, snapshot) {
|
|
|
68
68
|
resourceResetAt: resource.resetAt,
|
|
69
69
|
resourceResetInMinutes: resource.resetInMinutes,
|
|
70
70
|
resourceWindows: resource.windows,
|
|
71
|
+
browserWorkspaceCapacity: adapter.name === 'chatgpt' ? snapshot?.browserWorkspace : undefined,
|
|
71
72
|
};
|
|
72
73
|
});
|
|
73
74
|
});
|
|
@@ -82,6 +83,8 @@ export function satisfiesWorker(candidate, requirements) {
|
|
|
82
83
|
return false;
|
|
83
84
|
if (requirements.runtime && candidate.runtime !== requirements.runtime)
|
|
84
85
|
return false;
|
|
86
|
+
if (candidate.adapter === 'chatgpt' && candidate.browserWorkspaceCapacity?.idle === 0)
|
|
87
|
+
return false;
|
|
85
88
|
return true;
|
|
86
89
|
}
|
|
87
90
|
export function eligibleWorkerCandidates(candidates, requirements) {
|
|
@@ -104,6 +107,7 @@ function selection(selected, selectedBy, decisionEvidence) {
|
|
|
104
107
|
resourceResetAt: selected.resourceResetAt,
|
|
105
108
|
resourceResetInMinutes: selected.resourceResetInMinutes,
|
|
106
109
|
resourceWindows: selected.resourceWindows,
|
|
110
|
+
browserWorkspaceCapacity: selected.browserWorkspaceCapacity,
|
|
107
111
|
decisionEvidence,
|
|
108
112
|
};
|
|
109
113
|
}
|
|
@@ -111,15 +115,21 @@ function selection(selected, selectedBy, decisionEvidence) {
|
|
|
111
115
|
export function selectWorker(candidates, requirements) {
|
|
112
116
|
const eligible = eligibleWorkerCandidates(candidates, requirements);
|
|
113
117
|
if (eligible.length === 0)
|
|
114
|
-
throw new Error(
|
|
118
|
+
throw new Error(noEligibleWorkerReason(candidates));
|
|
115
119
|
const selected = deterministicWorker(eligible);
|
|
116
120
|
return { candidate: selected, selection: selection(selected, 'worker-router:fallback') };
|
|
117
121
|
}
|
|
122
|
+
function noEligibleWorkerReason(candidates) {
|
|
123
|
+
if (candidates.some(candidate => candidate.adapter === 'chatgpt' && candidate.browserWorkspaceCapacity?.idle === 0)) {
|
|
124
|
+
return 'No eligible worker: ChatGPT Browser Workspace has no idle capacity.';
|
|
125
|
+
}
|
|
126
|
+
return 'No eligible worker satisfies the authoritative worker constraints';
|
|
127
|
+
}
|
|
118
128
|
/** Gather factual worker/task/resource context, then let decide() choose among legal workers. */
|
|
119
129
|
export async function routeWorker(candidates, requirements, options = {}) {
|
|
120
130
|
const eligible = eligibleWorkerCandidates(candidates, requirements);
|
|
121
131
|
if (eligible.length === 0)
|
|
122
|
-
throw new Error(
|
|
132
|
+
throw new Error(noEligibleWorkerReason(candidates));
|
|
123
133
|
const preferredChatgptConcurrency = options.preferredChatgptConcurrency ?? DEFAULT_CHATGPT_PREFERRED_CONCURRENCY;
|
|
124
134
|
const chatgptRunning = options.chatgptRunning ?? 0;
|
|
125
135
|
const deterministic = deterministicWorker(eligible);
|
|
@@ -152,6 +162,7 @@ export async function routeWorker(candidates, requirements, options = {}) {
|
|
|
152
162
|
resourceResetAt: item.resourceResetAt ?? null,
|
|
153
163
|
resourceResetInMinutes: item.resourceResetInMinutes ?? null,
|
|
154
164
|
resourceWindows: item.resourceWindows ?? null,
|
|
165
|
+
browserWorkspaceCapacity: item.browserWorkspaceCapacity ?? null,
|
|
155
166
|
})),
|
|
156
167
|
}));
|
|
157
168
|
const choice = await chooseWithDecision(options.decisionEngine, options.decisionMode ?? 'off', { facts }, {
|