agents-relay 1.0.102 → 1.0.103
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/dashboard-html.js +2 -1
- package/dist/model-routing.js +3 -3
- package/dist/worker-routing.js +36 -2
- package/package.json +1 -1
package/dist/dashboard-html.js
CHANGED
|
@@ -260,7 +260,8 @@ async function refresh(force=false){jobsRefresh.disabled=true;jobsRefresh.textCo
|
|
|
260
260
|
function renderEventScope(){const task=eventTaskId&&selected()?.tasks?.find(candidate=>candidate.id===eventTaskId);eventsAllTasks.disabled=!task;eventsAllTasks.setAttribute('aria-pressed',String(!task));eventsScope.textContent=task?'Task '+task.id+' · newest first · 60 max':'Job-wide · newest first · 60 max';}
|
|
261
261
|
function eventLabel(type){return ({'task.process.launched':'Process launched','task.started':'Worker started','task.process.exited':'Sync process exited','task.process.async_exited':'Async process exited','task.completed':'Worker completed','task.failed':'Worker failed','task.blocked':'Worker blocked','review.started':'Review started','review.approved':'Review approved','review.changes_required':'Review changes required','review.blocked':'Review blocked'})[type]||type||'Event';}
|
|
262
262
|
function addEvent(event,level){if(!event||selectedJob&&event.job_id&&event.job_id!==selectedJob||eventTaskId&&event.task_id!==eventTaskId)return;if(event.event_id&&displayedEventIds.has(event.event_id))return;if(event.event_id)displayedEventIds.add(event.event_id);const item=element('li','event-item');const meta=element('div','event-meta');meta.append(element('span','event-kind',text(eventLabel(event.type))),element('span','event-type',text(event.type||'event')),element('span','',text(relativeTime(event.timestamp))),element('span','',text('task: '+value(event.task_id))));item.append(meta);const message=element('p','event-message',text(event.message||'Event received'));if(level==='error'||event.level==='error')message.classList.add('error-text');item.append(message);eventFeed.querySelector('.empty')?.remove();eventFeed.prepend(item);while(eventFeed.children.length>MAX_EVENTS)eventFeed.lastElementChild?.remove();}
|
|
263
|
-
function
|
|
263
|
+
function resetText(windowData){if(!Number.isFinite(windowData.resetsAt))return '';const at=new Date(windowData.resetsAt*1000);const minutes=Math.max(0,Math.ceil((at.getTime()-Date.now())/60000));const relative=minutes<60?minutes+'m':minutes<1440?Math.floor(minutes/60)+'h '+minutes%60+'m':Math.floor(minutes/1440)+'d '+Math.floor((minutes%1440)/60)+'h';return 'Resets '+at.toLocaleString()+' · in '+relative;}
|
|
264
|
+
function usageBar(label,windowData){const percent=Math.max(0,Math.min(100,100-windowData.usedPercent));const row=element('div','usage-row');const heading=element('div','usage-label');heading.append(element('span','',text(label)),element('span','',text(Math.round(percent)+'%')));const track=element('div','usage-track');track.setAttribute('role','progressbar');track.setAttribute('aria-label',label+' available');track.setAttribute('aria-valuemin','0');track.setAttribute('aria-valuemax','100');track.setAttribute('aria-valuenow',String(percent));const fill=element('div','usage-fill');fill.style.width=percent+'%';track.append(fill);row.append(heading,track);const reset=resetText(windowData);if(reset)row.append(element('div','subtle',text(reset)));return row;}
|
|
264
265
|
function usageProvider(provider){const card=element('section','usage-provider');card.append(element('h3','',text(provider.label)));const usage=provider.usage||{};if(!usage.available||!usage.fiveHour||!usage.weekly){card.append(element('p','empty',text(usage.reason||'Account usage telemetry is unavailable.')));return card;}const bars=element('div','usage-bars');bars.append(usageBar('5-hour remaining',usage.fiveHour),usageBar('Weekly remaining',usage.weekly));card.append(bars);return card;}
|
|
265
266
|
function formatCredit(value,currency){if(value===null||value===undefined||!Number.isFinite(Number(value)))return '—';const number=Number(value);if(currency==='USD')return '$'+number.toFixed(2);if(currency==='CNY')return '¥'+number.toFixed(2);return number.toFixed(2)+' '+currency;}
|
|
266
267
|
function creditProvider(provider){const row=element('section','usage-provider');row.append(element('h3','',text(provider.label)));const credits=provider.credits||{};if(!credits.available||!Array.isArray(credits.balances)||!credits.balances.length){row.append(element('p','empty',text(credits.reason||'Credit balance is unavailable.')));return row;}const balance=credits.balances[0];row.append(element('div','detail-value',text(formatCredit(balance.remaining,balance.currency))));return row;}
|
package/dist/model-routing.js
CHANGED
|
@@ -38,7 +38,7 @@ async function readConfig(path) {
|
|
|
38
38
|
}
|
|
39
39
|
function withResource(candidate, usage) {
|
|
40
40
|
const resource = usageForProvider(usage, candidate.provider);
|
|
41
|
-
return { ...candidate, resourceHealthy: resource.healthy, resourceUsedPercent: resource.usedPercent };
|
|
41
|
+
return { ...candidate, resourceHealthy: resource.healthy, resourceUsedPercent: resource.usedPercent, resourceRemainingPercent: resource.remainingPercent, resourceResetAt: resource.resetAt, resourceResetInMinutes: resource.resetInMinutes, resourceWindows: resource.windows };
|
|
42
42
|
}
|
|
43
43
|
export async function discoverModelCandidates(worker, registered, usage, codexHome = process.env.CODEX_HOME ?? join(homedir(), '.codex')) {
|
|
44
44
|
const registration = registered.find(item => item.id === worker.workerId);
|
|
@@ -105,9 +105,9 @@ export async function routeModel(worker, candidates, options = {}) {
|
|
|
105
105
|
facts: JSON.parse(JSON.stringify({
|
|
106
106
|
task: options.taskFacts ?? {},
|
|
107
107
|
worker: { id: worker.workerId, adapter: worker.adapter, capabilities: [...worker.capabilities] },
|
|
108
|
-
candidates: eligible.map(item => ({ id: item.id, provider: item.provider, model: item.model, profile: item.profile ?? null, reasoning: item.reasoning ?? null, resourceHealthy: item.resourceHealthy ?? null, resourceUsedPercent: item.resourceUsedPercent ?? null })),
|
|
108
|
+
candidates: eligible.map(item => ({ id: item.id, provider: item.provider, model: item.model, profile: item.profile ?? null, reasoning: item.reasoning ?? null, resourceHealthy: item.resourceHealthy ?? null, resourceUsedPercent: item.resourceUsedPercent ?? null, resourceRemainingPercent: item.resourceRemainingPercent ?? null, resourceResetAt: item.resourceResetAt ?? null, resourceResetInMinutes: item.resourceResetInMinutes ?? null, resourceWindows: item.resourceWindows ?? null })),
|
|
109
109
|
})),
|
|
110
|
-
}, { type: 'choice', question: 'Which eligible model profile is best suited to execute this task?', choices: eligible.map(item => item.id) }, () => deterministic.id);
|
|
110
|
+
}, { type: 'choice', question: 'Which eligible model profile is best suited to execute this task? Prefer spending otherwise-expiring paid quota on stronger reasoning when reset is near and substantial capacity remains, but consider every quota window so a tighter longer-horizon limit is not needlessly depleted; never violate task fit or continuity.', choices: eligible.map(item => item.id) }, () => deterministic.id);
|
|
111
111
|
const selected = eligible.find(item => item.id === choice.choice) ?? deterministic;
|
|
112
112
|
return {
|
|
113
113
|
candidate: selected,
|
package/dist/worker-routing.js
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { chooseWithDecision } from './decision.js';
|
|
2
|
-
export function usageForProvider(snapshot, provider) {
|
|
2
|
+
export function usageForProvider(snapshot, provider, now = Date.now()) {
|
|
3
3
|
const usage = snapshot?.providers.find(item => item.id === provider)?.usage;
|
|
4
4
|
if (usage?.available) {
|
|
5
5
|
const usedPercent = Math.max(usage.fiveHour?.usedPercent ?? 0, usage.weekly?.usedPercent ?? 0);
|
|
6
|
-
|
|
6
|
+
const resourceWindow = (window) => {
|
|
7
|
+
if (!window)
|
|
8
|
+
return undefined;
|
|
9
|
+
const resetAt = window.resetsAt;
|
|
10
|
+
return {
|
|
11
|
+
remainingPercent: 100 - window.usedPercent,
|
|
12
|
+
resetAt,
|
|
13
|
+
resetInMinutes: resetAt === null ? null : Math.max(0, Math.ceil((resetAt * 1000 - now) / 60_000)),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const windows = { fiveHour: resourceWindow(usage.fiveHour), weekly: resourceWindow(usage.weekly) };
|
|
17
|
+
const resetWindow = [usage.fiveHour, usage.weekly]
|
|
18
|
+
.filter((window) => window?.resetsAt !== null && window?.resetsAt !== undefined)
|
|
19
|
+
.filter(window => window.resetsAt * 1000 > now)
|
|
20
|
+
.sort((left, right) => left.resetsAt - right.resetsAt)[0];
|
|
21
|
+
return {
|
|
22
|
+
healthy: usedPercent < 100,
|
|
23
|
+
usedPercent,
|
|
24
|
+
remainingPercent: resetWindow ? 100 - resetWindow.usedPercent : undefined,
|
|
25
|
+
resetAt: resetWindow?.resetsAt ?? undefined,
|
|
26
|
+
resetInMinutes: resetWindow?.resetsAt ? Math.max(0, Math.ceil((resetWindow.resetsAt * 1000 - now) / 60_000)) : undefined,
|
|
27
|
+
windows,
|
|
28
|
+
};
|
|
7
29
|
}
|
|
8
30
|
const credits = snapshot?.credits.find(item => item.id === provider)?.credits;
|
|
9
31
|
if (credits?.available) {
|
|
@@ -34,6 +56,10 @@ export function workerCandidates(adapters, registered, snapshot) {
|
|
|
34
56
|
resourceHealthy: resource.healthy,
|
|
35
57
|
resourceProvider: provider,
|
|
36
58
|
resourceUsedPercent: resource.usedPercent,
|
|
59
|
+
resourceRemainingPercent: resource.remainingPercent,
|
|
60
|
+
resourceResetAt: resource.resetAt,
|
|
61
|
+
resourceResetInMinutes: resource.resetInMinutes,
|
|
62
|
+
resourceWindows: resource.windows,
|
|
37
63
|
};
|
|
38
64
|
});
|
|
39
65
|
});
|
|
@@ -76,6 +102,10 @@ function selection(selected, selectedBy, decisionEvidence) {
|
|
|
76
102
|
resourceHealthy: selected.resourceHealthy,
|
|
77
103
|
resourceProvider: selected.resourceProvider,
|
|
78
104
|
resourceUsedPercent: selected.resourceUsedPercent,
|
|
105
|
+
resourceRemainingPercent: selected.resourceRemainingPercent,
|
|
106
|
+
resourceResetAt: selected.resourceResetAt,
|
|
107
|
+
resourceResetInMinutes: selected.resourceResetInMinutes,
|
|
108
|
+
resourceWindows: selected.resourceWindows,
|
|
79
109
|
decisionEvidence,
|
|
80
110
|
};
|
|
81
111
|
}
|
|
@@ -108,6 +138,10 @@ export async function routeWorker(candidates, requirements, options = {}) {
|
|
|
108
138
|
resourceHealthy: item.resourceHealthy ?? null,
|
|
109
139
|
resourceProvider: item.resourceProvider ?? null,
|
|
110
140
|
resourceUsedPercent: item.resourceUsedPercent ?? null,
|
|
141
|
+
resourceRemainingPercent: item.resourceRemainingPercent ?? null,
|
|
142
|
+
resourceResetAt: item.resourceResetAt ?? null,
|
|
143
|
+
resourceResetInMinutes: item.resourceResetInMinutes ?? null,
|
|
144
|
+
resourceWindows: item.resourceWindows ?? null,
|
|
111
145
|
})),
|
|
112
146
|
}));
|
|
113
147
|
const choice = await chooseWithDecision(options.decisionEngine, options.decisionMode ?? 'off', { facts }, { type: 'choice', question: 'Which eligible worker is best suited to execute this task?', choices: eligible.map(item => item.workerId) }, () => deterministic.workerId);
|