@brainbase-labs/cli 0.58.0 → 0.59.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2822 -677
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36091,7 +36091,7 @@ function padStart(s, n) {
|
|
|
36091
36091
|
// package.json
|
|
36092
36092
|
var package_default = {
|
|
36093
36093
|
name: "@brainbase-labs/cli",
|
|
36094
|
-
version: "0.
|
|
36094
|
+
version: "0.59.0-rc.1",
|
|
36095
36095
|
description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
|
|
36096
36096
|
type: "module",
|
|
36097
36097
|
bin: {
|
|
@@ -40975,6 +40975,245 @@ import fs7 from "node:fs";
|
|
|
40975
40975
|
import path8 from "node:path";
|
|
40976
40976
|
import fs6 from "node:fs";
|
|
40977
40977
|
import { randomUUID } from "node:crypto";
|
|
40978
|
+
|
|
40979
|
+
// src/core/http-retry.ts
|
|
40980
|
+
var HTTP_RETRY_DEADLINE_MS = 30000;
|
|
40981
|
+
var HTTP_RETRY_MAX_ATTEMPTS = 3;
|
|
40982
|
+
var DEFAULT_RETRY_BUDGET_MS = 300000;
|
|
40983
|
+
var IDLE_WATCHDOG_MS = 45000;
|
|
40984
|
+
var REFRESH_RETRY_WINDOW_MS = 8000;
|
|
40985
|
+
var FULL_JITTER_BASE_MS = 100;
|
|
40986
|
+
var FULL_JITTER_FACTOR = 2;
|
|
40987
|
+
var FULL_JITTER_MAX_MS = 30000;
|
|
40988
|
+
var STREAM_RETRY_MAX_DELAY_MS = 30000;
|
|
40989
|
+
function httpRetryEnabled(env = process.env) {
|
|
40990
|
+
return env.BRAINBASE_HTTP_RETRY?.trim() !== "0";
|
|
40991
|
+
}
|
|
40992
|
+
function parseRetryAfterMs(header, nowMs = Date.now()) {
|
|
40993
|
+
const raw = header?.trim();
|
|
40994
|
+
if (!raw)
|
|
40995
|
+
return null;
|
|
40996
|
+
if (/^\d+$/.test(raw)) {
|
|
40997
|
+
const delayMs = Number(raw) * 1000;
|
|
40998
|
+
return Number.isFinite(delayMs) ? delayMs : null;
|
|
40999
|
+
}
|
|
41000
|
+
const weekday = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
|
|
41001
|
+
const month = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
|
|
41002
|
+
const time = "\\d{2}:\\d{2}:\\d{2}";
|
|
41003
|
+
const httpDateForms = [
|
|
41004
|
+
`${weekday}, \\d{2} ${month} \\d{4} ${time} GMT`,
|
|
41005
|
+
`(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \\d{2}-${month}-\\d{2} ${time} GMT`,
|
|
41006
|
+
`${weekday} ${month} (?: \\d|\\d{2}) ${time} \\d{4}`
|
|
41007
|
+
];
|
|
41008
|
+
if (!httpDateForms.some((form) => new RegExp(`^${form}$`).test(raw)))
|
|
41009
|
+
return null;
|
|
41010
|
+
const date = Date.parse(raw.endsWith("GMT") ? raw : `${raw} GMT`);
|
|
41011
|
+
if (Number.isNaN(date))
|
|
41012
|
+
return null;
|
|
41013
|
+
return Math.max(0, date - nowMs);
|
|
41014
|
+
}
|
|
41015
|
+
function fullJitterDelayMs(attempt, opts = {}) {
|
|
41016
|
+
const { baseMs = FULL_JITTER_BASE_MS, factor = FULL_JITTER_FACTOR, maxMs = FULL_JITTER_MAX_MS } = opts;
|
|
41017
|
+
const draw = opts.rng ?? Math.random;
|
|
41018
|
+
const exponent = Math.max(0, Math.floor(attempt));
|
|
41019
|
+
const cap = Math.min(maxMs, baseMs * factor ** exponent);
|
|
41020
|
+
return Math.floor(draw() * cap);
|
|
41021
|
+
}
|
|
41022
|
+
function createRetryBudget(totalMs, startedAtMs = Date.now()) {
|
|
41023
|
+
const deadlineMs = startedAtMs + totalMs;
|
|
41024
|
+
return {
|
|
41025
|
+
totalMs,
|
|
41026
|
+
remainingMs(nowMs = Date.now()) {
|
|
41027
|
+
return Math.max(0, deadlineMs - nowMs);
|
|
41028
|
+
},
|
|
41029
|
+
fits(delayMs, nowMs = Date.now()) {
|
|
41030
|
+
return delayMs < this.remainingMs(nowMs);
|
|
41031
|
+
}
|
|
41032
|
+
};
|
|
41033
|
+
}
|
|
41034
|
+
function retryDelayMs(attempt, budget, retryAfterHeader, opts = {}, nowMs = Date.now()) {
|
|
41035
|
+
const parsed = parseRetryAfterMs(retryAfterHeader, nowMs);
|
|
41036
|
+
const delayMs = parsed ?? fullJitterDelayMs(attempt, opts);
|
|
41037
|
+
return budget.fits(delayMs, nowMs) ? delayMs : null;
|
|
41038
|
+
}
|
|
41039
|
+
var UNSENT_CODES = new Set([
|
|
41040
|
+
"ConnectionRefused",
|
|
41041
|
+
"ECONNREFUSED",
|
|
41042
|
+
"ENOTFOUND",
|
|
41043
|
+
"EAI_AGAIN",
|
|
41044
|
+
"FailedToOpenSocket",
|
|
41045
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
41046
|
+
"UND_ERR_POOL_TIMEOUT",
|
|
41047
|
+
"UND_ERR_QUEUE_TIMEOUT",
|
|
41048
|
+
"ConnectError",
|
|
41049
|
+
"CERT_HAS_EXPIRED",
|
|
41050
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
41051
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
41052
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
41053
|
+
]);
|
|
41054
|
+
var AMBIGUOUS_CODES = new Set([
|
|
41055
|
+
"ECONNRESET",
|
|
41056
|
+
"EPIPE",
|
|
41057
|
+
"UND_ERR_SOCKET",
|
|
41058
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
41059
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
41060
|
+
"SocketError"
|
|
41061
|
+
]);
|
|
41062
|
+
function networkFailureCode(error) {
|
|
41063
|
+
const seen = new Set;
|
|
41064
|
+
while (error && typeof error === "object" && !seen.has(error)) {
|
|
41065
|
+
seen.add(error);
|
|
41066
|
+
const record = error;
|
|
41067
|
+
if (typeof record.code === "string")
|
|
41068
|
+
return record.code;
|
|
41069
|
+
error = record.cause;
|
|
41070
|
+
}
|
|
41071
|
+
return;
|
|
41072
|
+
}
|
|
41073
|
+
function isHttpTimeout(error) {
|
|
41074
|
+
const seen = new Set;
|
|
41075
|
+
while (error && typeof error === "object" && !seen.has(error)) {
|
|
41076
|
+
seen.add(error);
|
|
41077
|
+
const record = error;
|
|
41078
|
+
if (record.name === "TimeoutError" || record.name === "TimeoutDOMException" || record.code === "request_timeout" || record.code === "UND_ERR_BODY_TIMEOUT" || record.code === "UND_ERR_HEADERS_TIMEOUT")
|
|
41079
|
+
return true;
|
|
41080
|
+
error = record.cause;
|
|
41081
|
+
}
|
|
41082
|
+
return false;
|
|
41083
|
+
}
|
|
41084
|
+
function classifyHttpFailure(error, keyed = false) {
|
|
41085
|
+
if (!error || typeof error !== "object")
|
|
41086
|
+
return "permanent";
|
|
41087
|
+
const record = error;
|
|
41088
|
+
const code = record.body?.code ?? record.body?.detail?.code ?? record.body?.error?.code ?? record.code;
|
|
41089
|
+
if (code === "invalid_grant" || code === "idempotency_key_reused" || code === "idempotency_resource_deleted")
|
|
41090
|
+
return "permanent";
|
|
41091
|
+
const networkCode = networkFailureCode(record.cause) ?? networkFailureCode(error);
|
|
41092
|
+
if (networkCode === "ERR_INVALID_URL" || record.cause?.message === "bad port")
|
|
41093
|
+
return "permanent";
|
|
41094
|
+
if (record.definitelyUnsent === true)
|
|
41095
|
+
return "unsent";
|
|
41096
|
+
if (["ConnectError", "ConnectTimeoutError", "PoolTimeoutError"].includes(record.name ?? ""))
|
|
41097
|
+
return "unsent";
|
|
41098
|
+
if (record.definitelyUnsent === false)
|
|
41099
|
+
return "ambiguous";
|
|
41100
|
+
if (UNSENT_CODES.has(networkCode ?? ""))
|
|
41101
|
+
return "unsent";
|
|
41102
|
+
if (AMBIGUOUS_CODES.has(networkCode ?? "") || isHttpTimeout(error))
|
|
41103
|
+
return "ambiguous";
|
|
41104
|
+
if (record.status === 409)
|
|
41105
|
+
return keyed && code === "idempotency_request_in_progress" ? "retryable_status" : "permanent";
|
|
41106
|
+
if ([408, 425, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524].includes(record.status ?? 0))
|
|
41107
|
+
return "retryable_status";
|
|
41108
|
+
if (/^(PGRST00[0-3]|08\w*|53300|57P0\w)$/.test(code ?? ""))
|
|
41109
|
+
return "retryable_status";
|
|
41110
|
+
if (record.name === "TypeError" && /fetch failed|terminated|socket|disconnected/i.test(record.message ?? ""))
|
|
41111
|
+
return "ambiguous";
|
|
41112
|
+
return "permanent";
|
|
41113
|
+
}
|
|
41114
|
+
function retrySleep(ms, signal) {
|
|
41115
|
+
return new Promise((resolve, reject) => {
|
|
41116
|
+
if (signal?.aborted) {
|
|
41117
|
+
reject(signal.reason);
|
|
41118
|
+
return;
|
|
41119
|
+
}
|
|
41120
|
+
const abort = () => {
|
|
41121
|
+
clearTimeout(timer);
|
|
41122
|
+
reject(signal?.reason);
|
|
41123
|
+
};
|
|
41124
|
+
const timer = setTimeout(() => {
|
|
41125
|
+
signal?.removeEventListener("abort", abort);
|
|
41126
|
+
resolve();
|
|
41127
|
+
}, ms);
|
|
41128
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
41129
|
+
});
|
|
41130
|
+
}
|
|
41131
|
+
function logRetryEvent(event) {
|
|
41132
|
+
const logger = {
|
|
41133
|
+
warn: console.warn,
|
|
41134
|
+
error: console.error,
|
|
41135
|
+
info: (record) => {
|
|
41136
|
+
process.stderr.write(`${record}
|
|
41137
|
+
`);
|
|
41138
|
+
}
|
|
41139
|
+
};
|
|
41140
|
+
const log = event.event === "retry_attempt" ? logger.warn : event.event === "retry_exhausted" ? logger.error : logger.info;
|
|
41141
|
+
log(JSON.stringify(event));
|
|
41142
|
+
}
|
|
41143
|
+
async function retryHttpRequest(operation, options) {
|
|
41144
|
+
if (!httpRetryEnabled())
|
|
41145
|
+
return operation(options.signal, 1);
|
|
41146
|
+
const now = options.now ?? Date.now;
|
|
41147
|
+
const started = now();
|
|
41148
|
+
const budget = createRetryBudget(options.budgetMs ?? HTTP_RETRY_DEADLINE_MS, started);
|
|
41149
|
+
const emit = options.onRetryEvent ?? logRetryEvent;
|
|
41150
|
+
const sleep = options.sleep ?? retrySleep;
|
|
41151
|
+
const maxAttempts = options.maxAttempts ?? HTTP_RETRY_MAX_ATTEMPTS;
|
|
41152
|
+
let lastError;
|
|
41153
|
+
for (let attempt = 1;; attempt++) {
|
|
41154
|
+
if (options.signal?.aborted)
|
|
41155
|
+
throw options.signal.reason;
|
|
41156
|
+
const remaining = budget.remainingMs(now());
|
|
41157
|
+
if (remaining <= 0)
|
|
41158
|
+
throw lastError ?? new DOMException("HTTP retry budget exhausted", "TimeoutError");
|
|
41159
|
+
const controller = new AbortController;
|
|
41160
|
+
const abort = () => controller.abort(options.signal?.reason);
|
|
41161
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
41162
|
+
const timeout = setTimeout(() => controller.abort(new DOMException("HTTP request deadline exceeded", "TimeoutError")), Math.max(1, Math.floor(Math.min(remaining, options.timeoutMs ?? remaining))));
|
|
41163
|
+
let rejectAbort;
|
|
41164
|
+
try {
|
|
41165
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
41166
|
+
rejectAbort = () => reject(controller.signal.reason);
|
|
41167
|
+
controller.signal.addEventListener("abort", rejectAbort, { once: true });
|
|
41168
|
+
});
|
|
41169
|
+
const result = await Promise.race([operation(controller.signal, attempt), aborted]);
|
|
41170
|
+
if (budget.remainingMs(now()) <= 0)
|
|
41171
|
+
throw new DOMException("HTTP request deadline exceeded", "TimeoutError");
|
|
41172
|
+
return result;
|
|
41173
|
+
} catch (error) {
|
|
41174
|
+
lastError = error;
|
|
41175
|
+
} finally {
|
|
41176
|
+
clearTimeout(timeout);
|
|
41177
|
+
options.signal?.removeEventListener("abort", abort);
|
|
41178
|
+
if (rejectAbort)
|
|
41179
|
+
controller.signal.removeEventListener("abort", rejectAbort);
|
|
41180
|
+
}
|
|
41181
|
+
if (options.signal?.aborted)
|
|
41182
|
+
throw options.signal.reason;
|
|
41183
|
+
const failureClass = classifyHttpFailure(lastError, options.keyed);
|
|
41184
|
+
const attemptsUsed = Math.max(attempt, options.attemptsUsed?.() ?? attempt);
|
|
41185
|
+
const record = lastError;
|
|
41186
|
+
const event = {
|
|
41187
|
+
layer: options.layer ?? "http",
|
|
41188
|
+
op: options.op ?? "request",
|
|
41189
|
+
attempt: attemptsUsed,
|
|
41190
|
+
class: failureClass,
|
|
41191
|
+
status: record?.status ?? null,
|
|
41192
|
+
elapsed_ms: Math.max(0, Math.floor(now() - started)),
|
|
41193
|
+
budget_ms: budget.totalMs,
|
|
41194
|
+
error: record?.name ?? "Error"
|
|
41195
|
+
};
|
|
41196
|
+
const retryable = failureClass === "unsent" || (failureClass === "ambiguous" || failureClass === "retryable_status") && (options.idempotent || options.keyed);
|
|
41197
|
+
if (!retryable || failureClass === "ambiguous" && options.retryTimeouts === false && isHttpTimeout(lastError)) {
|
|
41198
|
+
if (failureClass !== "permanent")
|
|
41199
|
+
emit({ ...event, event: "retry_deferred" });
|
|
41200
|
+
throw lastError;
|
|
41201
|
+
}
|
|
41202
|
+
const delay = retryDelayMs(attemptsUsed - 1, budget, record?.retryAfter, { rng: options.rng }, now());
|
|
41203
|
+
if (attemptsUsed >= maxAttempts || delay === null) {
|
|
41204
|
+
emit({ ...event, event: "retry_exhausted" });
|
|
41205
|
+
throw lastError;
|
|
41206
|
+
}
|
|
41207
|
+
emit({ ...event, event: "retry_attempt", delay_ms: delay });
|
|
41208
|
+
await sleep(delay, options.signal);
|
|
41209
|
+
if (budget.remainingMs(now()) <= 0) {
|
|
41210
|
+
emit({ ...event, event: "retry_exhausted", elapsed_ms: Math.max(0, Math.floor(now() - started)) });
|
|
41211
|
+
throw lastError;
|
|
41212
|
+
}
|
|
41213
|
+
}
|
|
41214
|
+
}
|
|
41215
|
+
|
|
41216
|
+
// src/core/auth.ts
|
|
40978
41217
|
var AUTH_FILE = path8.join(BRAINBASE_HOME, "auth.json");
|
|
40979
41218
|
var AUTH_LOCK_FILE = `${AUTH_FILE}.lock`;
|
|
40980
41219
|
var AUTH_LOCK_TIMEOUT_MS = 5000;
|
|
@@ -41038,15 +41277,15 @@ function acquireLock(lockFile, timeoutMs) {
|
|
|
41038
41277
|
}
|
|
41039
41278
|
}
|
|
41040
41279
|
}
|
|
41041
|
-
function acquireAuthLock() {
|
|
41042
|
-
const release = acquireLock(AUTH_LOCK_FILE,
|
|
41280
|
+
function acquireAuthLock(timeoutMs = AUTH_LOCK_TIMEOUT_MS) {
|
|
41281
|
+
const release = acquireLock(AUTH_LOCK_FILE, timeoutMs);
|
|
41043
41282
|
if (!release) {
|
|
41044
41283
|
throw new Error(`Timed out waiting to update CLI authentication.${lockHolderState(AUTH_LOCK_FILE)} Retry, or remove ${AUTH_LOCK_FILE}`);
|
|
41045
41284
|
}
|
|
41046
41285
|
return release;
|
|
41047
41286
|
}
|
|
41048
|
-
function withAuthLock(operation) {
|
|
41049
|
-
const release = acquireAuthLock();
|
|
41287
|
+
function withAuthLock(operation, timeoutMs = AUTH_LOCK_TIMEOUT_MS) {
|
|
41288
|
+
const release = acquireAuthLock(timeoutMs);
|
|
41050
41289
|
try {
|
|
41051
41290
|
return operation();
|
|
41052
41291
|
} finally {
|
|
@@ -41183,6 +41422,163 @@ function markRefreshRejected(session, code) {
|
|
|
41183
41422
|
});
|
|
41184
41423
|
});
|
|
41185
41424
|
}
|
|
41425
|
+
function isUnsentAuthRequest(cause) {
|
|
41426
|
+
const seen = new Set;
|
|
41427
|
+
while (cause && typeof cause === "object" && !seen.has(cause)) {
|
|
41428
|
+
seen.add(cause);
|
|
41429
|
+
const value = cause;
|
|
41430
|
+
if (value.code && [
|
|
41431
|
+
"ConnectionRefused",
|
|
41432
|
+
"ECONNREFUSED",
|
|
41433
|
+
"ENOTFOUND",
|
|
41434
|
+
"EAI_AGAIN",
|
|
41435
|
+
"FailedToOpenSocket",
|
|
41436
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
41437
|
+
].includes(value.code))
|
|
41438
|
+
return true;
|
|
41439
|
+
cause = value.cause;
|
|
41440
|
+
}
|
|
41441
|
+
return false;
|
|
41442
|
+
}
|
|
41443
|
+
async function exchangeRefresh(session, timeoutMs) {
|
|
41444
|
+
let res;
|
|
41445
|
+
try {
|
|
41446
|
+
res = await fetch(`${session.supabase_url.replace(/\/+$/, "")}/auth/v1/token?grant_type=refresh_token`, {
|
|
41447
|
+
method: "POST",
|
|
41448
|
+
signal: AbortSignal.timeout(Math.max(1, Math.floor(timeoutMs))),
|
|
41449
|
+
headers: {
|
|
41450
|
+
"Content-Type": "application/json",
|
|
41451
|
+
apikey: session.supabase_anon_key,
|
|
41452
|
+
Authorization: `Bearer ${session.supabase_anon_key}`
|
|
41453
|
+
},
|
|
41454
|
+
body: JSON.stringify({ refresh_token: session.refresh_token })
|
|
41455
|
+
});
|
|
41456
|
+
} catch (cause) {
|
|
41457
|
+
return {
|
|
41458
|
+
kind: "transient",
|
|
41459
|
+
failure: {
|
|
41460
|
+
kind: "transient",
|
|
41461
|
+
class: isUnsentAuthRequest(cause) ? "unsent" : "ambiguous",
|
|
41462
|
+
status: null,
|
|
41463
|
+
detail: "couldn't reach the auth server",
|
|
41464
|
+
cause
|
|
41465
|
+
}
|
|
41466
|
+
};
|
|
41467
|
+
}
|
|
41468
|
+
if (!res.ok) {
|
|
41469
|
+
const code = await rejectionCode(res);
|
|
41470
|
+
if (code)
|
|
41471
|
+
return { kind: "failure", failure: { kind: "rejected", detail: code } };
|
|
41472
|
+
if ([408, 425, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524].includes(res.status)) {
|
|
41473
|
+
await res.body?.cancel().catch(() => {});
|
|
41474
|
+
return {
|
|
41475
|
+
kind: "transient",
|
|
41476
|
+
failure: {
|
|
41477
|
+
kind: "transient",
|
|
41478
|
+
class: "retryable_status",
|
|
41479
|
+
status: res.status,
|
|
41480
|
+
retryAfter: res.headers.get("Retry-After"),
|
|
41481
|
+
detail: "couldn't reach the auth server"
|
|
41482
|
+
}
|
|
41483
|
+
};
|
|
41484
|
+
}
|
|
41485
|
+
return {
|
|
41486
|
+
kind: "failure",
|
|
41487
|
+
failure: {
|
|
41488
|
+
kind: "unavailable",
|
|
41489
|
+
detail: `the auth server answered HTTP ${res.status}`
|
|
41490
|
+
}
|
|
41491
|
+
};
|
|
41492
|
+
}
|
|
41493
|
+
try {
|
|
41494
|
+
const data = await res.json();
|
|
41495
|
+
if (!data || typeof data.access_token !== "string" || !data.access_token) {
|
|
41496
|
+
throw new Error("Missing access token");
|
|
41497
|
+
}
|
|
41498
|
+
return { kind: "success", data };
|
|
41499
|
+
} catch (cause) {
|
|
41500
|
+
return {
|
|
41501
|
+
kind: "transient",
|
|
41502
|
+
failure: {
|
|
41503
|
+
kind: "transient",
|
|
41504
|
+
class: "ambiguous",
|
|
41505
|
+
status: res.status,
|
|
41506
|
+
detail: "couldn't reach the auth server",
|
|
41507
|
+
cause
|
|
41508
|
+
}
|
|
41509
|
+
};
|
|
41510
|
+
}
|
|
41511
|
+
}
|
|
41512
|
+
function saveRefreshResult(session, data) {
|
|
41513
|
+
const now = Math.floor(Date.now() / 1000);
|
|
41514
|
+
const updated = {
|
|
41515
|
+
...session,
|
|
41516
|
+
access_token: data.access_token,
|
|
41517
|
+
refresh_token: data.refresh_token ?? session.refresh_token,
|
|
41518
|
+
expires_at: data.expires_at ?? (data.expires_in ? now + data.expires_in : undefined),
|
|
41519
|
+
user_id: data.user?.id ?? session.user_id,
|
|
41520
|
+
email: data.user?.email ?? session.email,
|
|
41521
|
+
authedAt: new Date().toISOString()
|
|
41522
|
+
};
|
|
41523
|
+
if (!isSameAuthIdentity(updated, session))
|
|
41524
|
+
throw new AuthSessionChangedError;
|
|
41525
|
+
return withAuthLock(() => {
|
|
41526
|
+
const adopted = adoptRefreshedLineage(session);
|
|
41527
|
+
if (adopted !== undefined)
|
|
41528
|
+
return adopted;
|
|
41529
|
+
writeAuthUnlocked(updated);
|
|
41530
|
+
lastRefreshFailure = null;
|
|
41531
|
+
return updated;
|
|
41532
|
+
});
|
|
41533
|
+
}
|
|
41534
|
+
async function refreshWithRetry(session) {
|
|
41535
|
+
const started = Date.now();
|
|
41536
|
+
const budget = createRetryBudget(REFRESH_RETRY_WINDOW_MS, started);
|
|
41537
|
+
for (let attempt = 1;attempt <= HTTP_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
41538
|
+
const adopted = adoptRefreshedLineage(session);
|
|
41539
|
+
if (adopted !== undefined)
|
|
41540
|
+
return adopted;
|
|
41541
|
+
const current = readAuth();
|
|
41542
|
+
if (current.refresh_rejected)
|
|
41543
|
+
return null;
|
|
41544
|
+
if (!budget.remainingMs())
|
|
41545
|
+
break;
|
|
41546
|
+
const result = await exchangeRefresh(session, Math.min(REFRESH_REQUEST_TIMEOUT_MS, budget.remainingMs()));
|
|
41547
|
+
if (result.kind === "success") {
|
|
41548
|
+
return saveRefreshResult(session, result.data);
|
|
41549
|
+
}
|
|
41550
|
+
const latest = adoptRefreshedLineage(session);
|
|
41551
|
+
if (latest !== undefined)
|
|
41552
|
+
return latest;
|
|
41553
|
+
noteRefreshFailure(session, result.failure);
|
|
41554
|
+
if (result.kind === "failure") {
|
|
41555
|
+
if (result.failure.kind === "rejected") {
|
|
41556
|
+
markRefreshRejected(session, result.failure.detail);
|
|
41557
|
+
}
|
|
41558
|
+
return null;
|
|
41559
|
+
}
|
|
41560
|
+
const delay = attempt < HTTP_RETRY_MAX_ATTEMPTS ? retryDelayMs(attempt - 1, budget, result.failure.retryAfter) : null;
|
|
41561
|
+
const event = {
|
|
41562
|
+
event: delay === null ? "retry_exhausted" : "retry_attempt",
|
|
41563
|
+
layer: "supabase",
|
|
41564
|
+
op: "refresh_session",
|
|
41565
|
+
attempt,
|
|
41566
|
+
class: result.failure.class,
|
|
41567
|
+
status: result.failure.status,
|
|
41568
|
+
elapsed_ms: Date.now() - started,
|
|
41569
|
+
budget_ms: REFRESH_RETRY_WINDOW_MS,
|
|
41570
|
+
error: result.failure.cause instanceof Error ? result.failure.cause.name : "HttpError",
|
|
41571
|
+
...delay === null ? {} : { delay_ms: delay }
|
|
41572
|
+
};
|
|
41573
|
+
if (delay === null) {
|
|
41574
|
+
console.error(JSON.stringify(event));
|
|
41575
|
+
return null;
|
|
41576
|
+
}
|
|
41577
|
+
console.warn(JSON.stringify(event));
|
|
41578
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
41579
|
+
}
|
|
41580
|
+
return null;
|
|
41581
|
+
}
|
|
41186
41582
|
var refreshInFlight = null;
|
|
41187
41583
|
function validatedRefreshResult(expectedSession, result) {
|
|
41188
41584
|
const current = readAuth();
|
|
@@ -41234,6 +41630,8 @@ async function refreshSession(session) {
|
|
|
41234
41630
|
});
|
|
41235
41631
|
return null;
|
|
41236
41632
|
}
|
|
41633
|
+
if (httpRetryEnabled())
|
|
41634
|
+
return await refreshWithRetry(session);
|
|
41237
41635
|
const url = `${session.supabase_url.replace(/\/+$/, "")}/auth/v1/token?grant_type=refresh_token`;
|
|
41238
41636
|
let res;
|
|
41239
41637
|
try {
|
|
@@ -41284,28 +41682,7 @@ async function refreshSession(session) {
|
|
|
41284
41682
|
});
|
|
41285
41683
|
return null;
|
|
41286
41684
|
}
|
|
41287
|
-
|
|
41288
|
-
const expires_at = data.expires_at ?? (data.expires_in ? now + data.expires_in : undefined);
|
|
41289
|
-
const updated = {
|
|
41290
|
-
...session,
|
|
41291
|
-
access_token: data.access_token,
|
|
41292
|
-
refresh_token: data.refresh_token ?? session.refresh_token,
|
|
41293
|
-
expires_at,
|
|
41294
|
-
user_id: data.user?.id ?? session.user_id,
|
|
41295
|
-
email: data.user?.email ?? session.email,
|
|
41296
|
-
authedAt: new Date().toISOString()
|
|
41297
|
-
};
|
|
41298
|
-
if (!isSameAuthIdentity(updated, session)) {
|
|
41299
|
-
throw new AuthSessionChangedError;
|
|
41300
|
-
}
|
|
41301
|
-
return withAuthLock(() => {
|
|
41302
|
-
const adopted2 = adoptRefreshedLineage(session);
|
|
41303
|
-
if (adopted2 !== undefined)
|
|
41304
|
-
return adopted2;
|
|
41305
|
-
writeAuthUnlocked(updated);
|
|
41306
|
-
lastRefreshFailure = null;
|
|
41307
|
-
return updated;
|
|
41308
|
-
});
|
|
41685
|
+
return saveRefreshResult(session, data);
|
|
41309
41686
|
} finally {
|
|
41310
41687
|
releaseRefreshLock();
|
|
41311
41688
|
}
|
|
@@ -41356,7 +41733,9 @@ async function refreshSessionAfterUnauthorized(rejectedSession) {
|
|
|
41356
41733
|
if (latest.access_token !== rejectedSession.access_token && isAuthValid(latest)) {
|
|
41357
41734
|
return latest;
|
|
41358
41735
|
}
|
|
41359
|
-
|
|
41736
|
+
if (!httpRetryEnabled() || refreshFailureFor(rejectedSession)?.kind !== "transient") {
|
|
41737
|
+
expireRejectedAccessToken(rejectedSession);
|
|
41738
|
+
}
|
|
41360
41739
|
return null;
|
|
41361
41740
|
}
|
|
41362
41741
|
function expireRejectedAccessToken(session) {
|
|
@@ -55061,10 +55440,14 @@ function announceStoredPatFallback(session, stored) {
|
|
|
55061
55440
|
if (!session || announcedFallback)
|
|
55062
55441
|
return;
|
|
55063
55442
|
announcedFallback = true;
|
|
55064
|
-
|
|
55443
|
+
const nextStep = refreshFailureFor(session)?.kind === "transient" ? "Retry when the auth server is reachable to use your login session again." : "Run `brainbase login` to switch back.";
|
|
55444
|
+
console.error(`Login session ${sessionProblem(session)}; using the PAT saved in ${TOKEN_FILE} ` + `(${stored.prefix}…). ${nextStep}`);
|
|
55065
55445
|
}
|
|
55066
55446
|
function sessionProblem(session) {
|
|
55067
55447
|
const failure = refreshFailureFor(session);
|
|
55448
|
+
if (failure?.kind === "transient") {
|
|
55449
|
+
return "couldn't reach the auth server to renew it";
|
|
55450
|
+
}
|
|
55068
55451
|
if (failure?.kind === "rejected") {
|
|
55069
55452
|
return `expired and could not be renewed (the auth server rejected its refresh token: ${failure.detail})`;
|
|
55070
55453
|
}
|
|
@@ -55082,6 +55465,9 @@ function noUsableCredentialMessage() {
|
|
|
55082
55465
|
return "Not logged in. Run `brainbase login` to connect this device.";
|
|
55083
55466
|
}
|
|
55084
55467
|
const failure = refreshFailureFor(session);
|
|
55468
|
+
if (failure?.kind === "transient") {
|
|
55469
|
+
return "We couldn't reach the auth server to renew your login session. Check your connection and retry.";
|
|
55470
|
+
}
|
|
55085
55471
|
const retry = failure?.kind === "unavailable" ? " Check your connection and retry, or run `brainbase login`." : " Run `brainbase login` to sign in again.";
|
|
55086
55472
|
const stored = readToken();
|
|
55087
55473
|
const mismatch = stored && !storedPatFor(session) ? ` The PAT saved in ${TOKEN_FILE} belongs to a different account, so it was not used.` : "";
|
|
@@ -55093,6 +55479,9 @@ function withRejectedSessionHint(message, source) {
|
|
|
55093
55479
|
const trimmed = message.trimEnd();
|
|
55094
55480
|
const separator = /[.!?:]$/.test(trimmed) ? " " : ". ";
|
|
55095
55481
|
const session = readAuth();
|
|
55482
|
+
if (session && refreshFailureFor(session)?.kind === "transient") {
|
|
55483
|
+
return `${trimmed}${separator}We couldn't reach the auth server to renew your login session. Check your connection and retry.`;
|
|
55484
|
+
}
|
|
55096
55485
|
const renewable = session ? isSessionRefreshable(session) : false;
|
|
55097
55486
|
if (session && !renewable && storedPatFor(session)) {
|
|
55098
55487
|
return `${trimmed}${separator}The login session could not be renewed; ` + `retry to use the PAT saved in ${TOKEN_FILE}, or run \`brainbase login\`.`;
|
|
@@ -55139,7 +55528,7 @@ var DEFAULT_PROXY_BASE = "https://api.v1.brainbaselabs.com";
|
|
|
55139
55528
|
var MAS_IN_PROGRESS_MAX_WAIT_MS = 15000;
|
|
55140
55529
|
var MAS_REQUEST_TIMEOUT_MS = 30000;
|
|
55141
55530
|
var MAS_MIN_RETRY_DELAY_MS = 100;
|
|
55142
|
-
var
|
|
55531
|
+
var AUTH_REFRESH_RESERVE_MS = 1e4 + REFRESH_RETRY_WINDOW_MS + 5000;
|
|
55143
55532
|
var DEFINITELY_UNSENT_NETWORK_CODES = new Set([
|
|
55144
55533
|
"ConnectionRefused",
|
|
55145
55534
|
"ECONNREFUSED",
|
|
@@ -55156,8 +55545,8 @@ class ApiError extends Error {
|
|
|
55156
55545
|
status;
|
|
55157
55546
|
body;
|
|
55158
55547
|
retryAfter;
|
|
55159
|
-
constructor(message, status, body) {
|
|
55160
|
-
super(message);
|
|
55548
|
+
constructor(message, status, body, cause) {
|
|
55549
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
55161
55550
|
this.status = status;
|
|
55162
55551
|
this.body = body;
|
|
55163
55552
|
this.name = "ApiError";
|
|
@@ -55166,13 +55555,13 @@ class ApiError extends Error {
|
|
|
55166
55555
|
|
|
55167
55556
|
class NetworkApiError extends ApiError {
|
|
55168
55557
|
definitelyUnsent;
|
|
55169
|
-
constructor(message, definitelyUnsent) {
|
|
55170
|
-
super(message);
|
|
55558
|
+
constructor(message, definitelyUnsent, cause) {
|
|
55559
|
+
super(message, undefined, undefined, httpRetryEnabled() ? cause : undefined);
|
|
55171
55560
|
this.definitelyUnsent = definitelyUnsent;
|
|
55172
55561
|
}
|
|
55173
55562
|
}
|
|
55174
55563
|
function isLostResponseError(error) {
|
|
55175
|
-
return error instanceof NetworkApiError && !error.definitelyUnsent;
|
|
55564
|
+
return error instanceof ApiError && (error.outcomeUnknown === true || error instanceof NetworkApiError && !error.definitelyUnsent);
|
|
55176
55565
|
}
|
|
55177
55566
|
function isUnsentRequestError(error) {
|
|
55178
55567
|
return error instanceof NetworkApiError && error.definitelyUnsent;
|
|
@@ -55275,6 +55664,8 @@ function networkErrorCode(error) {
|
|
|
55275
55664
|
return typeof causeCode === "string" ? causeCode : undefined;
|
|
55276
55665
|
}
|
|
55277
55666
|
function isDefinitelyUnsentNetworkError(error) {
|
|
55667
|
+
if (httpRetryEnabled())
|
|
55668
|
+
return classifyHttpFailure(error) === "unsent";
|
|
55278
55669
|
const code = networkErrorCode(error);
|
|
55279
55670
|
if (DEFINITELY_UNSENT_NETWORK_CODES.has(code ?? ""))
|
|
55280
55671
|
return true;
|
|
@@ -55296,7 +55687,7 @@ async function sendRequest(url, init, bearer) {
|
|
|
55296
55687
|
try {
|
|
55297
55688
|
return await fetch(url, { ...init, headers });
|
|
55298
55689
|
} catch (err) {
|
|
55299
|
-
throw new NetworkApiError(`Network error: ${err.message}`, isDefinitelyUnsentNetworkError(err));
|
|
55690
|
+
throw new NetworkApiError(`Network error: ${err.message}`, isDefinitelyUnsentNetworkError(err), err);
|
|
55300
55691
|
}
|
|
55301
55692
|
}
|
|
55302
55693
|
async function sendWithAuthRetry(session, send) {
|
|
@@ -55311,7 +55702,106 @@ async function sendWithAuthRetry(session, send) {
|
|
|
55311
55702
|
res.body.cancel().catch(() => {});
|
|
55312
55703
|
return await send(refreshed);
|
|
55313
55704
|
}
|
|
55314
|
-
|
|
55705
|
+
function controlPlaneApiErrorMessage(body, status) {
|
|
55706
|
+
const detail = body && typeof body === "object" ? body.detail : undefined;
|
|
55707
|
+
return detail && ["self_edge_disabled", "runtime_auto_disabled"].includes(detail.code ?? "") && typeof detail.message === "string" ? `${detail.code}: ${detail.message}` : apiErrorMessage(body, status);
|
|
55708
|
+
}
|
|
55709
|
+
async function resilientApiRequest(pathname, init, resource, onHeaders, options = {}) {
|
|
55710
|
+
const credential = await resolveCredential();
|
|
55711
|
+
let currentSession = credential.session;
|
|
55712
|
+
let refreshAvailable = credential.source === "session" && !new Headers(init.headers).has("Authorization");
|
|
55713
|
+
const method2 = (init.method ?? "GET").toUpperCase();
|
|
55714
|
+
const keyed = !!new Headers(init.headers).get("Idempotency-Key")?.trim();
|
|
55715
|
+
const singleAttempt = options.singleAttempt === true || resource && method2 === "DELETE" && !keyed || options.timeoutMs !== undefined;
|
|
55716
|
+
let sends = 0;
|
|
55717
|
+
let lastResponse;
|
|
55718
|
+
let ambiguousWrite = false;
|
|
55719
|
+
const read = method2 === "GET" || method2 === "HEAD";
|
|
55720
|
+
const budget = createRetryBudget(options.timeoutMs ?? HTTP_RETRY_DEADLINE_MS);
|
|
55721
|
+
try {
|
|
55722
|
+
return await retryHttpRequest(async (signal) => {
|
|
55723
|
+
const send = async (refreshed) => {
|
|
55724
|
+
if (refreshed) {
|
|
55725
|
+
currentSession = refreshed;
|
|
55726
|
+
refreshAvailable = false;
|
|
55727
|
+
}
|
|
55728
|
+
if (signal?.aborted)
|
|
55729
|
+
throw signal.reason;
|
|
55730
|
+
if (sends >= HTTP_RETRY_MAX_ATTEMPTS)
|
|
55731
|
+
throw new ApiError("HTTP request attempt budget exhausted", lastResponse?.status);
|
|
55732
|
+
sends++;
|
|
55733
|
+
try {
|
|
55734
|
+
lastResponse = await sendRequest(`${resource ? masApiBase(currentSession) : apiBase(currentSession)}${pathname}`, { ...init, signal }, currentSession?.access_token ?? credential.bearer);
|
|
55735
|
+
} catch (error) {
|
|
55736
|
+
if (!read && isLostResponseError(error))
|
|
55737
|
+
ambiguousWrite = true;
|
|
55738
|
+
throw error;
|
|
55739
|
+
}
|
|
55740
|
+
return lastResponse;
|
|
55741
|
+
};
|
|
55742
|
+
const first = await send();
|
|
55743
|
+
const res = first.status === 401 && refreshAvailable && !signal?.aborted && sends < HTTP_RETRY_MAX_ATTEMPTS && budget.fits(AUTH_REFRESH_RESERVE_MS) ? await sendWithAuthRetry(currentSession, (refreshed) => refreshed ? send(refreshed) : Promise.resolve(first)) : first;
|
|
55744
|
+
let text2;
|
|
55745
|
+
try {
|
|
55746
|
+
text2 = await res.text();
|
|
55747
|
+
} catch (error) {
|
|
55748
|
+
if (resource && read && !res.ok && !signal?.aborted) {
|
|
55749
|
+
const message = `MAS returned HTTP ${res.status}, but its response body was interrupted`;
|
|
55750
|
+
const failure = new ApiError(res.status === 401 ? withRejectedSessionHint(message, credential.source) : message, res.status);
|
|
55751
|
+
failure.retryAfter = res.headers.get("Retry-After") ?? undefined;
|
|
55752
|
+
throw failure;
|
|
55753
|
+
}
|
|
55754
|
+
if (!read)
|
|
55755
|
+
ambiguousWrite = true;
|
|
55756
|
+
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false, error);
|
|
55757
|
+
}
|
|
55758
|
+
let body = text2;
|
|
55759
|
+
try {
|
|
55760
|
+
body = text2 ? JSON.parse(text2) : null;
|
|
55761
|
+
} catch {}
|
|
55762
|
+
if (!res.ok) {
|
|
55763
|
+
const message = resource ? masApiErrorMessage(body, res.status) : controlPlaneApiErrorMessage(body, res.status);
|
|
55764
|
+
const failure = new ApiError(res.status === 401 ? withRejectedSessionHint(message, credential.source) : resource && res.status === 404 && isUnroutedPath(body) ? `This control plane does not serve ${pathname} yet. Update the server, or use the web app.` : message, res.status, body);
|
|
55765
|
+
failure.retryAfter = res.headers.get("Retry-After") ?? undefined;
|
|
55766
|
+
if (!read && res.status !== 429 && res.status !== 425 && classifyHttpFailure(failure, keyed) === "retryable_status")
|
|
55767
|
+
ambiguousWrite = true;
|
|
55768
|
+
throw failure;
|
|
55769
|
+
}
|
|
55770
|
+
onHeaders?.(res.headers);
|
|
55771
|
+
return body;
|
|
55772
|
+
}, {
|
|
55773
|
+
idempotent: read || options.idempotent === true,
|
|
55774
|
+
keyed,
|
|
55775
|
+
signal: init.signal ?? undefined,
|
|
55776
|
+
budgetMs: options.timeoutMs ?? HTTP_RETRY_DEADLINE_MS,
|
|
55777
|
+
maxAttempts: singleAttempt ? 1 : HTTP_RETRY_MAX_ATTEMPTS,
|
|
55778
|
+
attemptsUsed: () => sends,
|
|
55779
|
+
retryTimeouts: false,
|
|
55780
|
+
onRetryEvent: options.singleAttempt ? () => {} : undefined,
|
|
55781
|
+
op: options.op ?? (resource ? `${method2} /v2` : `${method2} /v2/cli`)
|
|
55782
|
+
});
|
|
55783
|
+
} catch (error) {
|
|
55784
|
+
if (init.signal?.aborted)
|
|
55785
|
+
throw init.signal.reason;
|
|
55786
|
+
if (ambiguousWrite && isUnsentRequestError(error)) {
|
|
55787
|
+
throw new NetworkApiError(error.message, false, error);
|
|
55788
|
+
}
|
|
55789
|
+
if (ambiguousWrite && error instanceof ApiError) {
|
|
55790
|
+
const body = error.body;
|
|
55791
|
+
const code = body?.code ?? body?.detail?.code ?? body?.error?.code;
|
|
55792
|
+
const settled = error.status === 409 && (code === "idempotency_key_reused" || code === "idempotency_resource_deleted");
|
|
55793
|
+
if (!settled)
|
|
55794
|
+
error.outcomeUnknown = true;
|
|
55795
|
+
}
|
|
55796
|
+
if (isHttpTimeout(error) && !(error instanceof NetworkApiError)) {
|
|
55797
|
+
throw new NetworkApiError(`Network error: ${error.message}`, false, error);
|
|
55798
|
+
}
|
|
55799
|
+
throw error;
|
|
55800
|
+
}
|
|
55801
|
+
}
|
|
55802
|
+
async function request(pathname, init = {}, options = { idempotent: false }) {
|
|
55803
|
+
if (httpRetryEnabled())
|
|
55804
|
+
return resilientApiRequest(pathname, init, false, undefined, options);
|
|
55315
55805
|
const credential = await resolveCredential();
|
|
55316
55806
|
let currentSession = credential.session;
|
|
55317
55807
|
let refreshSessionAvailable = credential.source === "session" && !new Headers(init.headers).has("Authorization");
|
|
@@ -55332,7 +55822,7 @@ async function request(pathname, init = {}) {
|
|
|
55332
55822
|
try {
|
|
55333
55823
|
text2 = await res.text();
|
|
55334
55824
|
} catch (error) {
|
|
55335
|
-
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false);
|
|
55825
|
+
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false, error);
|
|
55336
55826
|
}
|
|
55337
55827
|
break;
|
|
55338
55828
|
} catch (error) {
|
|
@@ -55347,8 +55837,7 @@ async function request(pathname, init = {}) {
|
|
|
55347
55837
|
body = text2 ? JSON.parse(text2) : null;
|
|
55348
55838
|
} catch {}
|
|
55349
55839
|
if (!res.ok) {
|
|
55350
|
-
const
|
|
55351
|
-
const message = detail && ["self_edge_disabled", "runtime_auto_disabled"].includes(detail.code ?? "") && typeof detail.message === "string" ? `${detail.code}: ${detail.message}` : apiErrorMessage(body, res.status);
|
|
55840
|
+
const message = controlPlaneApiErrorMessage(body, res.status);
|
|
55352
55841
|
throw new ApiError(res.status === 401 ? withRejectedSessionHint(message, credential.source) : message, res.status, body);
|
|
55353
55842
|
}
|
|
55354
55843
|
return body;
|
|
@@ -55384,7 +55873,7 @@ function masRetryDelayMs(retryAfterHeader, fallbackMs) {
|
|
|
55384
55873
|
if (!Number.isFinite(retryAfterSeconds) || retryAfterSeconds < 0) {
|
|
55385
55874
|
return fallbackMs;
|
|
55386
55875
|
}
|
|
55387
|
-
return Math.min(
|
|
55876
|
+
return Math.min(2000, Math.max(MAS_MIN_RETRY_DELAY_MS, retryAfterSeconds * 1000));
|
|
55388
55877
|
}
|
|
55389
55878
|
function parseMasTaskCreateResponse(body) {
|
|
55390
55879
|
if (!body || typeof body !== "object") {
|
|
@@ -55402,6 +55891,21 @@ function parseMasTaskCreateResponse(body) {
|
|
|
55402
55891
|
};
|
|
55403
55892
|
}
|
|
55404
55893
|
async function masRequest(pathname, init) {
|
|
55894
|
+
if (httpRetryEnabled()) {
|
|
55895
|
+
try {
|
|
55896
|
+
return await resilientApiRequest(pathname, init, true);
|
|
55897
|
+
} catch (error) {
|
|
55898
|
+
const failure = error;
|
|
55899
|
+
const detail = failure.body?.detail;
|
|
55900
|
+
if (detail?.code === "idempotency_request_in_progress") {
|
|
55901
|
+
throw new ApiError(detail.task_id ? `Task ${detail.task_id} is still being processed. Check its status before starting another task.` : failure.message, failure.status, failure.body, error);
|
|
55902
|
+
}
|
|
55903
|
+
if (isLostResponseError(error) || failure.status !== undefined && failure.status >= 500) {
|
|
55904
|
+
throw new ApiError(`${failure.message} Task creation may still be processing; check your tasks before running this command again.`, failure.status, failure.body, error);
|
|
55905
|
+
}
|
|
55906
|
+
throw error;
|
|
55907
|
+
}
|
|
55908
|
+
}
|
|
55405
55909
|
const credential = await resolveCredential();
|
|
55406
55910
|
let currentSession = credential.session;
|
|
55407
55911
|
let ambiguousNetworkRetriesRemaining = 1;
|
|
@@ -55506,7 +56010,9 @@ function masNamedRecord(body, what, nameField) {
|
|
|
55506
56010
|
}
|
|
55507
56011
|
return { id: record.id, name };
|
|
55508
56012
|
}
|
|
55509
|
-
async function masReadRequest(pathname, callerSignal) {
|
|
56013
|
+
async function masReadRequest(pathname, callerSignal, options = {}) {
|
|
56014
|
+
if (httpRetryEnabled())
|
|
56015
|
+
return resilientApiRequest(pathname, { signal: callerSignal }, true, undefined, options);
|
|
55510
56016
|
const credential = await resolveCredential();
|
|
55511
56017
|
let currentSession = credential.session;
|
|
55512
56018
|
let refreshSessionAvailable = credential.source === "session";
|
|
@@ -55529,7 +56035,7 @@ async function masReadRequest(pathname, callerSignal) {
|
|
|
55529
56035
|
try {
|
|
55530
56036
|
text2 = await res.text();
|
|
55531
56037
|
} catch (error) {
|
|
55532
|
-
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false);
|
|
56038
|
+
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false, error);
|
|
55533
56039
|
}
|
|
55534
56040
|
break;
|
|
55535
56041
|
} catch (error) {
|
|
@@ -55545,7 +56051,9 @@ async function masReadRequest(pathname, callerSignal) {
|
|
|
55545
56051
|
} catch {}
|
|
55546
56052
|
if (!res.ok) {
|
|
55547
56053
|
const message = masApiErrorMessage(body, res.status);
|
|
55548
|
-
|
|
56054
|
+
const error = new ApiError(res.status === 401 ? withRejectedSessionHint(message, credential.source) : res.status === 404 && isUnroutedPath(body) ? `This control plane does not serve ${pathname} yet. Update the server, or use the web app.` : message, res.status, body);
|
|
56055
|
+
error.retryAfter = res.headers.get("retry-after") ?? undefined;
|
|
56056
|
+
throw error;
|
|
55549
56057
|
}
|
|
55550
56058
|
return body;
|
|
55551
56059
|
}
|
|
@@ -55564,6 +56072,8 @@ function withRequestDeadline(callerSignal, timeoutMs) {
|
|
|
55564
56072
|
return combined.signal;
|
|
55565
56073
|
}
|
|
55566
56074
|
async function masResourceRequest(pathname, init = {}, onHeaders, options = {}) {
|
|
56075
|
+
if (httpRetryEnabled())
|
|
56076
|
+
return resilientApiRequest(pathname, init, true, onHeaders, options);
|
|
55567
56077
|
const credential = await resolveCredential();
|
|
55568
56078
|
let currentSession = credential.session;
|
|
55569
56079
|
const res = await sendWithAuthRetry(credential.source === "session" ? currentSession : null, async (refreshed) => {
|
|
@@ -55578,7 +56088,7 @@ async function masResourceRequest(pathname, init = {}, onHeaders, options = {})
|
|
|
55578
56088
|
try {
|
|
55579
56089
|
text2 = await res.text();
|
|
55580
56090
|
} catch (error) {
|
|
55581
|
-
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false);
|
|
56091
|
+
throw new NetworkApiError(`Network error while reading response: ${error.message}`, false, error);
|
|
55582
56092
|
}
|
|
55583
56093
|
let body = text2;
|
|
55584
56094
|
try {
|
|
@@ -55603,8 +56113,12 @@ async function masStreamTarget(pathname) {
|
|
|
55603
56113
|
const base2 = masApiBase(credential.session);
|
|
55604
56114
|
return {
|
|
55605
56115
|
url: `${base2}${pathname}`,
|
|
55606
|
-
resolveBearer: async () => {
|
|
55607
|
-
|
|
56116
|
+
resolveBearer: async (rejectedBearer) => {
|
|
56117
|
+
let fresh = await resolveCredential();
|
|
56118
|
+
if (rejectedBearer && fresh.source === "session" && fresh.session?.access_token === rejectedBearer) {
|
|
56119
|
+
await refreshSessionAfterUnauthorized(fresh.session);
|
|
56120
|
+
fresh = await resolveCredential();
|
|
56121
|
+
}
|
|
55608
56122
|
const freshBase = masApiBase(fresh.session);
|
|
55609
56123
|
if (freshBase !== base2) {
|
|
55610
56124
|
throw new StreamHostMovedError(`The control plane moved from ${base2} to ${freshBase} while this stream was open, so the current credential was not sent. Re-run the command to follow against the new host.`);
|
|
@@ -55701,7 +56215,7 @@ var masApi = {
|
|
|
55701
56215
|
return masItems(await masReadRequest(`/tasks${qs}`), "task list");
|
|
55702
56216
|
},
|
|
55703
56217
|
async getTask(taskId, options = {}) {
|
|
55704
|
-
const body = await masReadRequest(`/tasks/${encodeURIComponent(taskId)}`, options.signal);
|
|
56218
|
+
const body = await masReadRequest(`/tasks/${encodeURIComponent(taskId)}`, options.signal, { singleAttempt: options.singleAttempt });
|
|
55705
56219
|
return parseMasTask(body);
|
|
55706
56220
|
},
|
|
55707
56221
|
async listTaskEvents(taskId, options = {}) {
|
|
@@ -55714,7 +56228,7 @@ var masApi = {
|
|
|
55714
56228
|
}
|
|
55715
56229
|
if (options.desc !== undefined)
|
|
55716
56230
|
params.set("desc", String(options.desc));
|
|
55717
|
-
const body = await masReadRequest(`/tasks/${encodeURIComponent(taskId)}/events?${params.toString()}
|
|
56231
|
+
const body = await masReadRequest(`/tasks/${encodeURIComponent(taskId)}/events?${params.toString()}`, options.signal);
|
|
55718
56232
|
return masItems(body, "task events");
|
|
55719
56233
|
},
|
|
55720
56234
|
async listMachines(options = {}) {
|
|
@@ -55899,26 +56413,29 @@ var api = {
|
|
|
55899
56413
|
body: JSON.stringify(input)
|
|
55900
56414
|
});
|
|
55901
56415
|
},
|
|
55902
|
-
getAgent(agentId) {
|
|
55903
|
-
return request(`/agents/${encodeURIComponent(agentId)}
|
|
56416
|
+
getAgent(agentId, options) {
|
|
56417
|
+
return request(`/agents/${encodeURIComponent(agentId)}`, options);
|
|
55904
56418
|
},
|
|
55905
56419
|
getAgentRevision(agentId) {
|
|
55906
56420
|
return request(`/agents/${encodeURIComponent(agentId)}/revision`);
|
|
55907
56421
|
},
|
|
55908
|
-
getAgentManifest(agentId) {
|
|
55909
|
-
return request(`/agents/${encodeURIComponent(agentId)}/manifest
|
|
56422
|
+
getAgentManifest(agentId, options) {
|
|
56423
|
+
return request(`/agents/${encodeURIComponent(agentId)}/manifest`, options);
|
|
55910
56424
|
},
|
|
55911
|
-
updateAgent(agentId, input) {
|
|
56425
|
+
updateAgent(agentId, input, options) {
|
|
55912
56426
|
if (usesLegacyControlPlane() && (input.machine_kind !== undefined || input.default_model !== undefined || input.runtime_mode !== undefined)) {
|
|
55913
56427
|
return Promise.reject(legacyAgentConfigError());
|
|
55914
56428
|
}
|
|
56429
|
+
const { idempotent = false, ...transport } = options ?? {};
|
|
55915
56430
|
return request(`/agents/${encodeURIComponent(agentId)}`, {
|
|
56431
|
+
...transport,
|
|
55916
56432
|
method: "PUT",
|
|
55917
56433
|
body: JSON.stringify(input)
|
|
55918
|
-
});
|
|
56434
|
+
}, { idempotent, op: idempotent ? "update_agent" : undefined });
|
|
55919
56435
|
},
|
|
55920
|
-
pushAgentManifest(agentId, input) {
|
|
56436
|
+
pushAgentManifest(agentId, input, options) {
|
|
55921
56437
|
return request(`/agents/${encodeURIComponent(agentId)}/manifest`, {
|
|
56438
|
+
...options,
|
|
55922
56439
|
method: "PUT",
|
|
55923
56440
|
body: JSON.stringify(input)
|
|
55924
56441
|
});
|
|
@@ -55946,11 +56463,13 @@ var api = {
|
|
|
55946
56463
|
}
|
|
55947
56464
|
return { secrets };
|
|
55948
56465
|
},
|
|
55949
|
-
putAgentSecrets(agentId, secrets) {
|
|
56466
|
+
putAgentSecrets(agentId, secrets, options) {
|
|
56467
|
+
const { idempotent = false, ...transport } = options ?? {};
|
|
55950
56468
|
return request(`/agents/${encodeURIComponent(agentId)}/secrets`, {
|
|
56469
|
+
...transport,
|
|
55951
56470
|
method: "PUT",
|
|
55952
56471
|
body: JSON.stringify({ secrets })
|
|
55953
|
-
});
|
|
56472
|
+
}, { idempotent, op: idempotent ? "put_agent_secrets" : undefined });
|
|
55954
56473
|
},
|
|
55955
56474
|
createCliKey(agentId, input) {
|
|
55956
56475
|
return request(`/agents/${encodeURIComponent(agentId)}/keys`, {
|
|
@@ -56002,8 +56521,8 @@ var api = {
|
|
|
56002
56521
|
body: JSON.stringify(input)
|
|
56003
56522
|
});
|
|
56004
56523
|
},
|
|
56005
|
-
getOrchestration(orchId) {
|
|
56006
|
-
return request(`/orchestrations/${encodeURIComponent(orchId)}
|
|
56524
|
+
getOrchestration(orchId, options) {
|
|
56525
|
+
return request(`/orchestrations/${encodeURIComponent(orchId)}`, options);
|
|
56007
56526
|
},
|
|
56008
56527
|
fireOrchestrationTrigger(orchId, triggerId, payload, idempotencyKey) {
|
|
56009
56528
|
return masResourceRequest(`/orchestrations/${encodeURIComponent(orchId)}/triggers/${encodeURIComponent(triggerId)}/fire`, {
|
|
@@ -56015,7 +56534,7 @@ var api = {
|
|
|
56015
56534
|
getOrchestrationManifest(orchId) {
|
|
56016
56535
|
return request(`/orchestrations/${encodeURIComponent(orchId)}/manifest`);
|
|
56017
56536
|
},
|
|
56018
|
-
async updateOrchestration(orchId, input) {
|
|
56537
|
+
async updateOrchestration(orchId, input, options) {
|
|
56019
56538
|
let payload = input;
|
|
56020
56539
|
if (usesLegacyControlPlane() && input.triggers !== undefined) {
|
|
56021
56540
|
if (input.triggers.length > 0)
|
|
@@ -56028,6 +56547,7 @@ var api = {
|
|
|
56028
56547
|
payload = legacyPayload;
|
|
56029
56548
|
}
|
|
56030
56549
|
return request(`/orchestrations/${encodeURIComponent(orchId)}`, {
|
|
56550
|
+
...options,
|
|
56031
56551
|
method: "PUT",
|
|
56032
56552
|
body: JSON.stringify(payload)
|
|
56033
56553
|
});
|
|
@@ -56046,6 +56566,7 @@ function proxyBaseUrl(session) {
|
|
|
56046
56566
|
}
|
|
56047
56567
|
|
|
56048
56568
|
// src/core/registry-client.ts
|
|
56569
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
56049
56570
|
var DEFAULT_BASE = "https://api.v1.brainbaselabs.com";
|
|
56050
56571
|
function baseUrl(session) {
|
|
56051
56572
|
const env3 = process.env.BRAINBASE_REGISTRY_URL || process.env.BRAINBASE_API_URL;
|
|
@@ -56120,27 +56641,256 @@ function mergeHeaders(auth, overrides) {
|
|
|
56120
56641
|
new Headers(overrides).forEach((value, key2) => headers.set(key2, value));
|
|
56121
56642
|
return headers;
|
|
56122
56643
|
}
|
|
56123
|
-
|
|
56644
|
+
var RETRYABLE_GET_STATUSES = new Set([
|
|
56645
|
+
408,
|
|
56646
|
+
425,
|
|
56647
|
+
429,
|
|
56648
|
+
500,
|
|
56649
|
+
502,
|
|
56650
|
+
503,
|
|
56651
|
+
504,
|
|
56652
|
+
520,
|
|
56653
|
+
521,
|
|
56654
|
+
522,
|
|
56655
|
+
523,
|
|
56656
|
+
524
|
|
56657
|
+
]);
|
|
56658
|
+
var AUTH_REFRESH_RESERVE_MS2 = 1e4 + REFRESH_RETRY_WINDOW_MS + 5000;
|
|
56659
|
+
var DEFINITELY_UNSENT_NETWORK_CODES2 = new Set([
|
|
56660
|
+
"ConnectionRefused",
|
|
56661
|
+
"ECONNREFUSED",
|
|
56662
|
+
"ENOTFOUND",
|
|
56663
|
+
"EAI_AGAIN",
|
|
56664
|
+
"ERR_INVALID_URL",
|
|
56665
|
+
"FailedToOpenSocket",
|
|
56666
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
56667
|
+
]);
|
|
56668
|
+
function networkErrorCode2(error) {
|
|
56669
|
+
if (!error || typeof error !== "object")
|
|
56670
|
+
return;
|
|
56671
|
+
const code = error.code;
|
|
56672
|
+
if (typeof code === "string")
|
|
56673
|
+
return code;
|
|
56674
|
+
const cause = error.cause;
|
|
56675
|
+
if (!cause || typeof cause !== "object")
|
|
56676
|
+
return;
|
|
56677
|
+
const causeCode = cause.code;
|
|
56678
|
+
return typeof causeCode === "string" ? causeCode : undefined;
|
|
56679
|
+
}
|
|
56680
|
+
function isDefinitelyUnsentNetworkError2(error) {
|
|
56681
|
+
return DEFINITELY_UNSENT_NETWORK_CODES2.has(networkErrorCode2(error) ?? "");
|
|
56682
|
+
}
|
|
56683
|
+
|
|
56684
|
+
class RegistryLocalBodyError extends Error {
|
|
56685
|
+
original;
|
|
56686
|
+
constructor(original) {
|
|
56687
|
+
super(original instanceof Error ? original.message : String(original));
|
|
56688
|
+
this.original = original;
|
|
56689
|
+
this.name = "RegistryLocalBodyError";
|
|
56690
|
+
}
|
|
56691
|
+
}
|
|
56692
|
+
function toNetworkApiError(err) {
|
|
56693
|
+
const apiError = new ApiError(`Network error: ${err.message}`);
|
|
56694
|
+
apiError.cause = err;
|
|
56695
|
+
return apiError;
|
|
56696
|
+
}
|
|
56697
|
+
function attemptSignal(external2, timeoutMs) {
|
|
56698
|
+
const controller = new AbortController;
|
|
56699
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
56700
|
+
const onAbort = () => controller.abort(external2?.reason);
|
|
56701
|
+
if (external2?.aborted)
|
|
56702
|
+
onAbort();
|
|
56703
|
+
else
|
|
56704
|
+
external2?.addEventListener("abort", onAbort, { once: true });
|
|
56705
|
+
return {
|
|
56706
|
+
signal: controller.signal,
|
|
56707
|
+
cleanup: () => {
|
|
56708
|
+
clearTimeout(timer);
|
|
56709
|
+
external2?.removeEventListener("abort", onAbort);
|
|
56710
|
+
}
|
|
56711
|
+
};
|
|
56712
|
+
}
|
|
56713
|
+
function responseWithCleanup(res, cleanup) {
|
|
56714
|
+
const reader = res.body.getReader();
|
|
56715
|
+
reader.closed.then(cleanup, cleanup);
|
|
56716
|
+
const body = new ReadableStream({
|
|
56717
|
+
async pull(controller) {
|
|
56718
|
+
try {
|
|
56719
|
+
const { value, done } = await reader.read();
|
|
56720
|
+
if (done)
|
|
56721
|
+
controller.close();
|
|
56722
|
+
else
|
|
56723
|
+
controller.enqueue(value);
|
|
56724
|
+
} catch (error) {
|
|
56725
|
+
controller.error(error);
|
|
56726
|
+
}
|
|
56727
|
+
},
|
|
56728
|
+
async cancel(reason) {
|
|
56729
|
+
try {
|
|
56730
|
+
await reader.cancel(reason);
|
|
56731
|
+
} finally {
|
|
56732
|
+
cleanup();
|
|
56733
|
+
}
|
|
56734
|
+
}
|
|
56735
|
+
});
|
|
56736
|
+
const wrapped = new Response(body, {
|
|
56737
|
+
status: res.status,
|
|
56738
|
+
statusText: res.statusText,
|
|
56739
|
+
headers: res.headers
|
|
56740
|
+
});
|
|
56741
|
+
Object.defineProperties(wrapped, {
|
|
56742
|
+
url: { value: res.url },
|
|
56743
|
+
redirected: { value: res.redirected },
|
|
56744
|
+
type: { value: res.type }
|
|
56745
|
+
});
|
|
56746
|
+
return wrapped;
|
|
56747
|
+
}
|
|
56748
|
+
async function consumeBody(res, opts) {
|
|
56749
|
+
if (!opts.readBody || !res.body)
|
|
56750
|
+
return;
|
|
56751
|
+
try {
|
|
56752
|
+
await opts.readBody(res);
|
|
56753
|
+
} catch (err) {
|
|
56754
|
+
if (res.ok)
|
|
56755
|
+
throw err;
|
|
56756
|
+
const refusal = new ApiError(`HTTP ${res.status}`, res.status);
|
|
56757
|
+
refusal.cause = err;
|
|
56758
|
+
throw refusal;
|
|
56759
|
+
}
|
|
56760
|
+
}
|
|
56761
|
+
function logRetry(state, failureClass, status, error, delayMs) {
|
|
56762
|
+
const record = {
|
|
56763
|
+
event: delayMs === null ? "retry_exhausted" : "retry_attempt",
|
|
56764
|
+
layer: "http",
|
|
56765
|
+
op: "registry_get",
|
|
56766
|
+
attempt: state.attempts,
|
|
56767
|
+
class: failureClass,
|
|
56768
|
+
status,
|
|
56769
|
+
elapsed_ms: state.budget.totalMs - state.budget.remainingMs(),
|
|
56770
|
+
budget_ms: state.budget.totalMs,
|
|
56771
|
+
error,
|
|
56772
|
+
...delayMs === null ? {} : { delay_ms: delayMs }
|
|
56773
|
+
};
|
|
56774
|
+
if (delayMs === null)
|
|
56775
|
+
console.error(JSON.stringify(record));
|
|
56776
|
+
else
|
|
56777
|
+
console.warn(JSON.stringify(record));
|
|
56778
|
+
}
|
|
56779
|
+
async function retryingGetRequest(url, init, opts, state) {
|
|
56780
|
+
const { budget, signal } = state;
|
|
56781
|
+
let lastNetworkError = null;
|
|
56782
|
+
while (state.attempts < HTTP_RETRY_MAX_ATTEMPTS && budget.remainingMs() > 0) {
|
|
56783
|
+
state.attempts += 1;
|
|
56784
|
+
try {
|
|
56785
|
+
const res = await fetch(url, { ...init, signal });
|
|
56786
|
+
if (RETRYABLE_GET_STATUSES.has(res.status)) {
|
|
56787
|
+
const delayMs = state.attempts < HTTP_RETRY_MAX_ATTEMPTS ? retryDelayMs(state.attempts - 1, budget, res.headers.get("Retry-After")) : null;
|
|
56788
|
+
logRetry(state, "retryable_status", res.status, "ApiError", delayMs);
|
|
56789
|
+
if (delayMs !== null) {
|
|
56790
|
+
if (res.body)
|
|
56791
|
+
res.body.cancel().catch(() => {});
|
|
56792
|
+
await sleep(delayMs, undefined, { signal });
|
|
56793
|
+
continue;
|
|
56794
|
+
}
|
|
56795
|
+
}
|
|
56796
|
+
if (res.status !== 401)
|
|
56797
|
+
await consumeBody(res, opts);
|
|
56798
|
+
return res;
|
|
56799
|
+
} catch (err) {
|
|
56800
|
+
if (err instanceof RegistryLocalBodyError)
|
|
56801
|
+
throw err;
|
|
56802
|
+
if (err instanceof ApiError && err.status !== undefined)
|
|
56803
|
+
throw err;
|
|
56804
|
+
if (init.signal?.aborted)
|
|
56805
|
+
throw toNetworkApiError(err);
|
|
56806
|
+
lastNetworkError = toNetworkApiError(err);
|
|
56807
|
+
const delayMs = state.attempts < HTTP_RETRY_MAX_ATTEMPTS && !signal.aborted ? retryDelayMs(state.attempts - 1, budget, null) : null;
|
|
56808
|
+
logRetry(state, isDefinitelyUnsentNetworkError2(err) ? "unsent" : "ambiguous", null, err instanceof Error ? err.name : "Error", delayMs);
|
|
56809
|
+
if (delayMs === null)
|
|
56810
|
+
break;
|
|
56811
|
+
try {
|
|
56812
|
+
await sleep(delayMs, undefined, { signal });
|
|
56813
|
+
} catch (abort) {
|
|
56814
|
+
throw toNetworkApiError(abort);
|
|
56815
|
+
}
|
|
56816
|
+
}
|
|
56817
|
+
}
|
|
56818
|
+
throw lastNetworkError ?? new ApiError("Network error: request timed out");
|
|
56819
|
+
}
|
|
56820
|
+
async function sendRequest2(pathname, init, session, opts = {}, state) {
|
|
56124
56821
|
const url = `${baseUrl(session)}${pathname}`;
|
|
56822
|
+
if (!state) {
|
|
56823
|
+
try {
|
|
56824
|
+
return await fetch(url, init);
|
|
56825
|
+
} catch (err) {
|
|
56826
|
+
throw new ApiError(`Network error: ${err.message}`);
|
|
56827
|
+
}
|
|
56828
|
+
}
|
|
56829
|
+
state.signal.throwIfAborted();
|
|
56830
|
+
if ((init.method ?? "GET").toUpperCase() === "GET") {
|
|
56831
|
+
return await retryingGetRequest(url, init, opts, state);
|
|
56832
|
+
}
|
|
56125
56833
|
try {
|
|
56126
|
-
|
|
56834
|
+
const res = await fetch(url, { ...init, signal: state.signal });
|
|
56835
|
+
if (res.status !== 401)
|
|
56836
|
+
await consumeBody(res, opts);
|
|
56837
|
+
return res;
|
|
56127
56838
|
} catch (err) {
|
|
56128
|
-
|
|
56839
|
+
if (err instanceof ApiError)
|
|
56840
|
+
throw err;
|
|
56841
|
+
throw toNetworkApiError(err);
|
|
56129
56842
|
}
|
|
56130
56843
|
}
|
|
56131
|
-
async function sendAuthenticatedRequest(pathname, init, auth) {
|
|
56844
|
+
async function sendAuthenticatedRequest(pathname, init, auth, opts = {}, state) {
|
|
56132
56845
|
return await sendRequest2(pathname, {
|
|
56133
56846
|
...init,
|
|
56134
56847
|
headers: mergeHeaders(auth.headers, init.headers)
|
|
56135
|
-
}, auth.session);
|
|
56848
|
+
}, auth.session, opts, state);
|
|
56849
|
+
}
|
|
56850
|
+
async function registryFetch(pathname, init = {}, opts = {}) {
|
|
56851
|
+
if (httpRetryEnabled()) {
|
|
56852
|
+
const budget = createRetryBudget(HTTP_RETRY_DEADLINE_MS);
|
|
56853
|
+
const { signal, cleanup } = attemptSignal(init.signal, budget.remainingMs());
|
|
56854
|
+
const state = { budget, attempts: 0, signal };
|
|
56855
|
+
let bodyOwnsCleanup = false;
|
|
56856
|
+
try {
|
|
56857
|
+
const res = await fetchWithAuth(pathname, init, opts, state);
|
|
56858
|
+
if (!res.bodyUsed)
|
|
56859
|
+
await consumeBody(res, opts);
|
|
56860
|
+
if (!opts.readBody && res.body && !res.bodyUsed) {
|
|
56861
|
+
const wrapped = responseWithCleanup(res, cleanup);
|
|
56862
|
+
bodyOwnsCleanup = true;
|
|
56863
|
+
return wrapped;
|
|
56864
|
+
}
|
|
56865
|
+
return res;
|
|
56866
|
+
} catch (err) {
|
|
56867
|
+
if (err instanceof RegistryLocalBodyError)
|
|
56868
|
+
throw err.original;
|
|
56869
|
+
if (err instanceof ApiError)
|
|
56870
|
+
throw err;
|
|
56871
|
+
throw toNetworkApiError(err);
|
|
56872
|
+
} finally {
|
|
56873
|
+
if (!bodyOwnsCleanup)
|
|
56874
|
+
cleanup();
|
|
56875
|
+
}
|
|
56876
|
+
}
|
|
56877
|
+
return await fetchWithAuth(pathname, init);
|
|
56136
56878
|
}
|
|
56137
|
-
async function
|
|
56879
|
+
async function fetchWithAuth(pathname, init, opts = {}, state) {
|
|
56880
|
+
state?.signal.throwIfAborted();
|
|
56138
56881
|
if (new Headers(init.headers).has("Authorization")) {
|
|
56139
|
-
return await sendRequest2(pathname, init, readAuth());
|
|
56882
|
+
return await sendRequest2(pathname, init, readAuth(), opts, state);
|
|
56140
56883
|
}
|
|
56141
56884
|
const auth = await resolveAuth();
|
|
56142
56885
|
const retrySession = auth.headers["X-Auth-Source"] === "jwt" ? auth.session : null;
|
|
56143
|
-
|
|
56886
|
+
const send = (refreshed) => sendAuthenticatedRequest(pathname, init, refreshed ? jwtAuth(refreshed) : auth, opts, state);
|
|
56887
|
+
if (!state)
|
|
56888
|
+
return await sendWithAuthRetry(retrySession, send);
|
|
56889
|
+
const res = await send();
|
|
56890
|
+
if (res.status !== 401 || !retrySession || state.signal.aborted || state.attempts >= HTTP_RETRY_MAX_ATTEMPTS || !state.budget.fits(AUTH_REFRESH_RESERVE_MS2)) {
|
|
56891
|
+
return res;
|
|
56892
|
+
}
|
|
56893
|
+
return await sendWithAuthRetry(retrySession, (refreshed) => refreshed ? send(refreshed) : Promise.resolve(res));
|
|
56144
56894
|
}
|
|
56145
56895
|
async function jsonRequest(pathname, init = {}) {
|
|
56146
56896
|
const headers = new Headers(init.headers);
|
|
@@ -56149,8 +56899,15 @@ async function jsonRequest(pathname, init = {}) {
|
|
|
56149
56899
|
if (init.body && !headers.has("Content-Type") && typeof init.body === "string") {
|
|
56150
56900
|
headers.set("Content-Type", "application/json");
|
|
56151
56901
|
}
|
|
56152
|
-
|
|
56153
|
-
|
|
56902
|
+
let bodyText = null;
|
|
56903
|
+
let bodyFrom = null;
|
|
56904
|
+
const res = await registryFetch(pathname, { ...init, headers }, {
|
|
56905
|
+
readBody: async (response) => {
|
|
56906
|
+
bodyText = await response.text();
|
|
56907
|
+
bodyFrom = response;
|
|
56908
|
+
}
|
|
56909
|
+
});
|
|
56910
|
+
const text2 = bodyFrom === res && bodyText !== null ? bodyText : await res.text();
|
|
56154
56911
|
let body = text2;
|
|
56155
56912
|
try {
|
|
56156
56913
|
body = text2 ? JSON.parse(text2) : null;
|
|
@@ -56208,32 +56965,58 @@ var registryApi = {
|
|
|
56208
56965
|
},
|
|
56209
56966
|
async getFile(creator, slug, version, relPath) {
|
|
56210
56967
|
const pathname = `/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/files/${relPath}`;
|
|
56211
|
-
|
|
56968
|
+
let buffer = null;
|
|
56969
|
+
let bufferFrom = null;
|
|
56970
|
+
const res = await registryFetch(pathname, {}, {
|
|
56971
|
+
readBody: async (response) => {
|
|
56972
|
+
if (!response.ok)
|
|
56973
|
+
return;
|
|
56974
|
+
buffer = Buffer.from(await response.arrayBuffer());
|
|
56975
|
+
bufferFrom = response;
|
|
56976
|
+
}
|
|
56977
|
+
});
|
|
56212
56978
|
if (!res.ok) {
|
|
56213
56979
|
throw new ApiError(`HTTP ${res.status} reading file ${relPath}`, res.status);
|
|
56214
56980
|
}
|
|
56981
|
+
if (bufferFrom === res && buffer !== null)
|
|
56982
|
+
return buffer;
|
|
56215
56983
|
const ab = await res.arrayBuffer();
|
|
56216
56984
|
return Buffer.from(ab);
|
|
56217
56985
|
},
|
|
56218
56986
|
async downloadTarball(creator, slug, version, destPath) {
|
|
56219
56987
|
const pathname = `/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/tarball`;
|
|
56220
|
-
|
|
56221
|
-
|
|
56222
|
-
|
|
56223
|
-
|
|
56224
|
-
|
|
56225
|
-
|
|
56226
|
-
|
|
56227
|
-
|
|
56228
|
-
|
|
56229
|
-
|
|
56230
|
-
|
|
56231
|
-
|
|
56232
|
-
|
|
56988
|
+
if (!httpRetryEnabled()) {
|
|
56989
|
+
const res2 = await registryFetch(pathname);
|
|
56990
|
+
if (!res2.ok || !res2.body) {
|
|
56991
|
+
throw new ApiError(`HTTP ${res2.status} downloading tarball`, res2.status);
|
|
56992
|
+
}
|
|
56993
|
+
const out = fs54.createWriteStream(destPath);
|
|
56994
|
+
const reader = res2.body.getReader();
|
|
56995
|
+
try {
|
|
56996
|
+
while (true) {
|
|
56997
|
+
const { value, done } = await reader.read();
|
|
56998
|
+
if (done)
|
|
56999
|
+
break;
|
|
57000
|
+
if (value) {
|
|
57001
|
+
await new Promise((resolve, reject2) => out.write(value, (err) => err ? reject2(err) : resolve()));
|
|
57002
|
+
}
|
|
56233
57003
|
}
|
|
57004
|
+
} finally {
|
|
57005
|
+
out.end();
|
|
56234
57006
|
}
|
|
56235
|
-
|
|
56236
|
-
|
|
57007
|
+
return;
|
|
57008
|
+
}
|
|
57009
|
+
let downloadedFrom = null;
|
|
57010
|
+
const res = await registryFetch(pathname, {}, {
|
|
57011
|
+
readBody: async (response) => {
|
|
57012
|
+
if (!response.ok || !response.body)
|
|
57013
|
+
return;
|
|
57014
|
+
await streamResponseToFile(response.body, destPath);
|
|
57015
|
+
downloadedFrom = response;
|
|
57016
|
+
}
|
|
57017
|
+
});
|
|
57018
|
+
if (!res.ok || !res.body || downloadedFrom !== res) {
|
|
57019
|
+
throw new ApiError(`HTTP ${res.status} downloading tarball`, res.status);
|
|
56237
57020
|
}
|
|
56238
57021
|
},
|
|
56239
57022
|
createPackage(input) {
|
|
@@ -56251,20 +57034,24 @@ var registryApi = {
|
|
|
56251
57034
|
form.set("manifest", new Blob([input.manifestJson], { type: "application/json" }), "manifest.json");
|
|
56252
57035
|
const bytes = fs54.readFileSync(input.bundlePath);
|
|
56253
57036
|
form.set("bundle", new Blob([bytes], { type: "application/gzip" }), "bundle.tgz");
|
|
56254
|
-
const
|
|
57037
|
+
const init = {
|
|
56255
57038
|
method: "POST",
|
|
56256
57039
|
headers: { Accept: "application/json" },
|
|
56257
57040
|
body: form
|
|
56258
|
-
}
|
|
56259
|
-
|
|
56260
|
-
|
|
56261
|
-
|
|
56262
|
-
body = text2
|
|
56263
|
-
|
|
56264
|
-
|
|
56265
|
-
|
|
57041
|
+
};
|
|
57042
|
+
if (!httpRetryEnabled()) {
|
|
57043
|
+
const res = await registryFetch(pathname, init);
|
|
57044
|
+
const text2 = await res.text();
|
|
57045
|
+
let body = text2;
|
|
57046
|
+
try {
|
|
57047
|
+
body = text2 ? JSON.parse(text2) : null;
|
|
57048
|
+
} catch {}
|
|
57049
|
+
if (!res.ok) {
|
|
57050
|
+
throw new ApiError(`Publish failed: HTTP ${res.status}`, res.status, body);
|
|
57051
|
+
}
|
|
57052
|
+
return body;
|
|
56266
57053
|
}
|
|
56267
|
-
return
|
|
57054
|
+
return await publishVersionVerified(creator, slug, input, pathname, init);
|
|
56268
57055
|
},
|
|
56269
57056
|
yankVersion(creator, slug, version, reason) {
|
|
56270
57057
|
return jsonRequest(`/v1/registry/templates/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/yank`, {
|
|
@@ -56297,6 +57084,97 @@ var registryApi = {
|
|
|
56297
57084
|
return jsonRequest(`/v1/registry/cli-tokens/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
56298
57085
|
}
|
|
56299
57086
|
};
|
|
57087
|
+
async function readBackPublishedVersion(creator, slug, version, bundleSha256) {
|
|
57088
|
+
try {
|
|
57089
|
+
const readBack = await registryApi.getVersion(creator, slug, version);
|
|
57090
|
+
if (readBack.version === version && readBack.bundle_sha256 === bundleSha256) {
|
|
57091
|
+
return readBack;
|
|
57092
|
+
}
|
|
57093
|
+
} catch {}
|
|
57094
|
+
return null;
|
|
57095
|
+
}
|
|
57096
|
+
async function publishVersionVerified(creator, slug, input, pathname, init) {
|
|
57097
|
+
let res;
|
|
57098
|
+
let text2 = "";
|
|
57099
|
+
try {
|
|
57100
|
+
res = await registryFetch(pathname, init, {
|
|
57101
|
+
readBody: async (response) => {
|
|
57102
|
+
text2 = await response.text();
|
|
57103
|
+
}
|
|
57104
|
+
});
|
|
57105
|
+
} catch (err) {
|
|
57106
|
+
if (!(err instanceof ApiError) || err.status !== undefined || isDefinitelyUnsentNetworkError2(err.cause)) {
|
|
57107
|
+
throw err;
|
|
57108
|
+
}
|
|
57109
|
+
const adopted = await readBackPublishedVersion(creator, slug, input.version, input.bundleSha256);
|
|
57110
|
+
if (adopted)
|
|
57111
|
+
return adopted;
|
|
57112
|
+
const failure = new ApiError(`Publish status undetermined: ${err.message}; could not confirm ${input.version} with the expected bundle checksum. Check the registry before publishing again.`);
|
|
57113
|
+
failure.cause = err;
|
|
57114
|
+
throw failure;
|
|
57115
|
+
}
|
|
57116
|
+
let body = text2;
|
|
57117
|
+
try {
|
|
57118
|
+
body = text2 ? JSON.parse(text2) : null;
|
|
57119
|
+
} catch {}
|
|
57120
|
+
if (!res.ok) {
|
|
57121
|
+
throw new ApiError(`Publish failed: HTTP ${res.status}`, res.status, body);
|
|
57122
|
+
}
|
|
57123
|
+
let readBack;
|
|
57124
|
+
try {
|
|
57125
|
+
readBack = await registryApi.getVersion(creator, slug, input.version);
|
|
57126
|
+
} catch (err) {
|
|
57127
|
+
const failure = new ApiError(`Publish status undetermined: version ${input.version} was submitted but reading it back failed (${err.message}). Check the registry before publishing again.`);
|
|
57128
|
+
failure.cause = err;
|
|
57129
|
+
throw failure;
|
|
57130
|
+
}
|
|
57131
|
+
if (readBack.version !== input.version || readBack.bundle_sha256 !== input.bundleSha256) {
|
|
57132
|
+
throw new ApiError(`Publish verification failed: reading ${input.version} back returned version ${readBack.version} with bundle_sha256 ${readBack.bundle_sha256}, expected ${input.version} with ${input.bundleSha256}.`);
|
|
57133
|
+
}
|
|
57134
|
+
return readBack;
|
|
57135
|
+
}
|
|
57136
|
+
async function streamResponseToFile(body, destPath) {
|
|
57137
|
+
let out;
|
|
57138
|
+
try {
|
|
57139
|
+
out = fs54.createWriteStream(destPath);
|
|
57140
|
+
} catch (err) {
|
|
57141
|
+
throw new RegistryLocalBodyError(err);
|
|
57142
|
+
}
|
|
57143
|
+
const closed = new Promise((resolve) => out.once("close", () => resolve()));
|
|
57144
|
+
let writeError = null;
|
|
57145
|
+
out.on("error", (err) => {
|
|
57146
|
+
writeError ??= err;
|
|
57147
|
+
});
|
|
57148
|
+
const reader = body.getReader();
|
|
57149
|
+
let failure = null;
|
|
57150
|
+
try {
|
|
57151
|
+
while (true) {
|
|
57152
|
+
const { value, done } = await reader.read();
|
|
57153
|
+
if (done)
|
|
57154
|
+
break;
|
|
57155
|
+
if (value) {
|
|
57156
|
+
try {
|
|
57157
|
+
await new Promise((resolve, reject2) => out.write(value, (err) => err ? reject2(err) : resolve()));
|
|
57158
|
+
} catch (err) {
|
|
57159
|
+
throw new RegistryLocalBodyError(err);
|
|
57160
|
+
}
|
|
57161
|
+
}
|
|
57162
|
+
}
|
|
57163
|
+
} catch (err) {
|
|
57164
|
+
failure = err;
|
|
57165
|
+
reader.cancel().catch(() => {});
|
|
57166
|
+
}
|
|
57167
|
+
if (failure === null) {
|
|
57168
|
+
out.end();
|
|
57169
|
+
} else {
|
|
57170
|
+
out.destroy();
|
|
57171
|
+
}
|
|
57172
|
+
await closed;
|
|
57173
|
+
if (failure !== null)
|
|
57174
|
+
throw failure;
|
|
57175
|
+
if (writeError !== null)
|
|
57176
|
+
throw new RegistryLocalBodyError(writeError);
|
|
57177
|
+
}
|
|
56300
57178
|
|
|
56301
57179
|
// src/core/tarball.ts
|
|
56302
57180
|
import fs57 from "node:fs";
|
|
@@ -63055,6 +63933,12 @@ var DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
|
63055
63933
|
var MIN_POLL_INTERVAL_MS = 500;
|
|
63056
63934
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
63057
63935
|
var MAX_CONSECUTIVE_FAILURES = 20;
|
|
63936
|
+
var retryDiagnostics = {
|
|
63937
|
+
info(message) {
|
|
63938
|
+
process.stderr.write(`${message}
|
|
63939
|
+
`);
|
|
63940
|
+
}
|
|
63941
|
+
};
|
|
63058
63942
|
|
|
63059
63943
|
class LoginTimeoutError extends Error {
|
|
63060
63944
|
constructor(timeoutMs, lastError) {
|
|
@@ -63062,7 +63946,7 @@ class LoginTimeoutError extends Error {
|
|
|
63062
63946
|
this.name = "LoginTimeoutError";
|
|
63063
63947
|
}
|
|
63064
63948
|
}
|
|
63065
|
-
function
|
|
63949
|
+
function sleep2(ms2) {
|
|
63066
63950
|
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
63067
63951
|
}
|
|
63068
63952
|
var MAX_POLL_INTERVAL_MS = 60000;
|
|
@@ -63112,6 +63996,49 @@ async function readJson2(res) {
|
|
|
63112
63996
|
return null;
|
|
63113
63997
|
}
|
|
63114
63998
|
}
|
|
63999
|
+
function interruptedLogin() {
|
|
64000
|
+
return new Error("The auth server sent an unreadable or interrupted login response. Run `brainbase login` again.");
|
|
64001
|
+
}
|
|
64002
|
+
async function startWithRetry(url, fetchImpl) {
|
|
64003
|
+
const started = Date.now();
|
|
64004
|
+
const budget = createRetryBudget(HTTP_RETRY_DEADLINE_MS, started);
|
|
64005
|
+
for (let attempt2 = 1;; attempt2++) {
|
|
64006
|
+
try {
|
|
64007
|
+
return await fetchImpl(url, {
|
|
64008
|
+
method: "POST",
|
|
64009
|
+
headers: JSON_HEADERS,
|
|
64010
|
+
body: "{}",
|
|
64011
|
+
signal: AbortSignal.timeout(Math.max(1, Math.floor(Math.min(REQUEST_TIMEOUT_MS, budget.remainingMs()))))
|
|
64012
|
+
});
|
|
64013
|
+
} catch (cause) {
|
|
64014
|
+
const unsent = isUnsentAuthRequest(cause);
|
|
64015
|
+
const delay2 = unsent && attempt2 < HTTP_RETRY_MAX_ATTEMPTS ? retryDelayMs(attempt2 - 1, budget) : null;
|
|
64016
|
+
const event = {
|
|
64017
|
+
event: delay2 === null ? unsent ? "retry_exhausted" : "retry_deferred" : "retry_attempt",
|
|
64018
|
+
layer: "http",
|
|
64019
|
+
op: "device_authorization_start",
|
|
64020
|
+
attempt: attempt2,
|
|
64021
|
+
class: unsent ? "unsent" : "ambiguous",
|
|
64022
|
+
status: null,
|
|
64023
|
+
elapsed_ms: Date.now() - started,
|
|
64024
|
+
budget_ms: HTTP_RETRY_DEADLINE_MS,
|
|
64025
|
+
error: cause instanceof Error ? cause.name : "Error",
|
|
64026
|
+
...delay2 === null ? {} : { delay_ms: delay2 }
|
|
64027
|
+
};
|
|
64028
|
+
if (delay2 === null) {
|
|
64029
|
+
if (unsent)
|
|
64030
|
+
console.error(JSON.stringify(event));
|
|
64031
|
+
else
|
|
64032
|
+
retryDiagnostics.info(JSON.stringify(event));
|
|
64033
|
+
throw new Error(`Could not reach ${url}: ${cause.message}`, {
|
|
64034
|
+
cause
|
|
64035
|
+
});
|
|
64036
|
+
}
|
|
64037
|
+
console.warn(JSON.stringify(event));
|
|
64038
|
+
await sleep2(delay2);
|
|
64039
|
+
}
|
|
64040
|
+
}
|
|
64041
|
+
}
|
|
63115
64042
|
var JSON_HEADERS = {
|
|
63116
64043
|
"Content-Type": "application/json",
|
|
63117
64044
|
Accept: "application/json"
|
|
@@ -63121,13 +64048,15 @@ async function startDeviceAuthorization(options) {
|
|
|
63121
64048
|
const url = `${webBase}/api/cli/auth/device`;
|
|
63122
64049
|
let res;
|
|
63123
64050
|
try {
|
|
63124
|
-
res = await fetchImpl(url, {
|
|
64051
|
+
res = httpRetryEnabled() ? await startWithRetry(url, fetchImpl) : await fetchImpl(url, {
|
|
63125
64052
|
method: "POST",
|
|
63126
64053
|
headers: JSON_HEADERS,
|
|
63127
64054
|
body: "{}",
|
|
63128
64055
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
63129
64056
|
});
|
|
63130
64057
|
} catch (err) {
|
|
64058
|
+
if (httpRetryEnabled())
|
|
64059
|
+
throw err;
|
|
63131
64060
|
throw new Error(`Could not reach ${url}: ${err.message}`);
|
|
63132
64061
|
}
|
|
63133
64062
|
if (res.status === 404)
|
|
@@ -63137,6 +64066,8 @@ async function startDeviceAuthorization(options) {
|
|
|
63137
64066
|
}
|
|
63138
64067
|
const body = await readJson2(res);
|
|
63139
64068
|
if (!body?.device_code || !body.user_code) {
|
|
64069
|
+
if (httpRetryEnabled())
|
|
64070
|
+
throw interruptedLogin();
|
|
63140
64071
|
throw unsupportedDeployment(webBase);
|
|
63141
64072
|
}
|
|
63142
64073
|
return {
|
|
@@ -63148,12 +64079,14 @@ async function startDeviceAuthorization(options) {
|
|
|
63148
64079
|
};
|
|
63149
64080
|
}
|
|
63150
64081
|
async function awaitDeviceSession(options) {
|
|
64082
|
+
if (httpRetryEnabled())
|
|
64083
|
+
return await pollWithBackoff(options);
|
|
63151
64084
|
const {
|
|
63152
64085
|
webBase,
|
|
63153
64086
|
authorization,
|
|
63154
64087
|
timeoutMs = Math.min(authorization.expiresInMs, DEFAULT_LOGIN_TIMEOUT_MS),
|
|
63155
64088
|
now: now2 = Date.now,
|
|
63156
|
-
wait =
|
|
64089
|
+
wait = sleep2,
|
|
63157
64090
|
fetchImpl = fetch
|
|
63158
64091
|
} = options;
|
|
63159
64092
|
const url = `${webBase}/api/cli/auth/device/token`;
|
|
@@ -63221,6 +64154,108 @@ async function awaitDeviceSession(options) {
|
|
|
63221
64154
|
}
|
|
63222
64155
|
throw new LoginTimeoutError(timeoutMs, lastError);
|
|
63223
64156
|
}
|
|
64157
|
+
async function pollWithBackoff(options) {
|
|
64158
|
+
const {
|
|
64159
|
+
webBase,
|
|
64160
|
+
authorization,
|
|
64161
|
+
now: now2 = Date.now,
|
|
64162
|
+
wait = sleep2,
|
|
64163
|
+
fetchImpl = fetch
|
|
64164
|
+
} = options;
|
|
64165
|
+
const timeoutMs = Math.min(options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS, authorization.expiresInMs);
|
|
64166
|
+
const started = now2();
|
|
64167
|
+
const budget = createRetryBudget(timeoutMs, started);
|
|
64168
|
+
const url = `${webBase}/api/cli/auth/device/token`;
|
|
64169
|
+
let intervalMs = authorization.intervalMs;
|
|
64170
|
+
let failures = 0;
|
|
64171
|
+
let attempt2 = 0;
|
|
64172
|
+
let lastError;
|
|
64173
|
+
async function backoff(status, retryAfter, slowDown = false) {
|
|
64174
|
+
if (slowDown)
|
|
64175
|
+
intervalMs = Math.min(intervalMs + 5000, MAX_POLL_INTERVAL_MS);
|
|
64176
|
+
const delay2 = Math.max(intervalMs, parseRetryAfterMs(retryAfter, now2()) ?? 0) + fullJitterDelayMs(Math.max(0, failures - 1));
|
|
64177
|
+
const exhausted = !budget.fits(delay2, now2());
|
|
64178
|
+
const event = {
|
|
64179
|
+
event: exhausted ? "retry_exhausted" : "retry_attempt",
|
|
64180
|
+
layer: "http",
|
|
64181
|
+
op: "device_authorization_poll",
|
|
64182
|
+
attempt: attempt2,
|
|
64183
|
+
class: status === null ? "unsent" : "retryable_status",
|
|
64184
|
+
status,
|
|
64185
|
+
elapsed_ms: now2() - started,
|
|
64186
|
+
budget_ms: timeoutMs,
|
|
64187
|
+
error: status === null ? "NetworkError" : "HttpError",
|
|
64188
|
+
...exhausted ? {} : { delay_ms: delay2 }
|
|
64189
|
+
};
|
|
64190
|
+
if (exhausted) {
|
|
64191
|
+
console.error(JSON.stringify(event));
|
|
64192
|
+
throw new LoginTimeoutError(timeoutMs, lastError);
|
|
64193
|
+
}
|
|
64194
|
+
console.warn(JSON.stringify(event));
|
|
64195
|
+
await wait(delay2);
|
|
64196
|
+
}
|
|
64197
|
+
while (budget.remainingMs(now2()) > 0) {
|
|
64198
|
+
attempt2++;
|
|
64199
|
+
let res;
|
|
64200
|
+
try {
|
|
64201
|
+
res = await fetchImpl(url, {
|
|
64202
|
+
method: "POST",
|
|
64203
|
+
headers: JSON_HEADERS,
|
|
64204
|
+
body: JSON.stringify({ device_code: authorization.deviceCode }),
|
|
64205
|
+
signal: AbortSignal.timeout(Math.max(1, Math.floor(Math.min(REQUEST_TIMEOUT_MS, budget.remainingMs(now2())))))
|
|
64206
|
+
});
|
|
64207
|
+
} catch (cause) {
|
|
64208
|
+
if (!isUnsentAuthRequest(cause))
|
|
64209
|
+
throw interruptedLogin();
|
|
64210
|
+
lastError = cause.message;
|
|
64211
|
+
if (++failures >= MAX_CONSECUTIVE_FAILURES)
|
|
64212
|
+
throw new Error(`Could not reach ${url}: ${lastError}`, { cause });
|
|
64213
|
+
await backoff(null);
|
|
64214
|
+
continue;
|
|
64215
|
+
}
|
|
64216
|
+
if (res.status === 404)
|
|
64217
|
+
throw unsupportedDeployment(webBase);
|
|
64218
|
+
if (res.status === 410)
|
|
64219
|
+
throw new Error("This login expired. Run `brainbase login` again.");
|
|
64220
|
+
if (res.status === 409)
|
|
64221
|
+
throw new Error("This login was already completed on another device. Run `brainbase login` again.");
|
|
64222
|
+
if (res.status === 429) {
|
|
64223
|
+
failures = 0;
|
|
64224
|
+
lastError = "the server asked us to slow down";
|
|
64225
|
+
await res.body?.cancel().catch(() => {});
|
|
64226
|
+
await backoff(res.status, res.headers.get("Retry-After"), true);
|
|
64227
|
+
continue;
|
|
64228
|
+
}
|
|
64229
|
+
if (!res.ok && res.status >= 500) {
|
|
64230
|
+
lastError = `poll returned ${res.status}`;
|
|
64231
|
+
await res.body?.cancel().catch(() => {});
|
|
64232
|
+
if (++failures >= MAX_CONSECUTIVE_FAILURES)
|
|
64233
|
+
throw new Error(`Could not reach ${url}: ${lastError}`);
|
|
64234
|
+
await backoff(res.status, res.headers.get("Retry-After"));
|
|
64235
|
+
continue;
|
|
64236
|
+
}
|
|
64237
|
+
const body = await readJson2(res);
|
|
64238
|
+
if (body?.status === "slow_down" || body?.error === "slow_down") {
|
|
64239
|
+
failures = 0;
|
|
64240
|
+
lastError = "the server asked us to slow down";
|
|
64241
|
+
await backoff(res.status, res.headers.get("Retry-After"), true);
|
|
64242
|
+
continue;
|
|
64243
|
+
}
|
|
64244
|
+
if (!res.ok) {
|
|
64245
|
+
throw new Error(typeof body?.error === "string" ? body.error : `The server rejected this login (${res.status}). Run \`brainbase login\` again.`);
|
|
64246
|
+
}
|
|
64247
|
+
if (!body)
|
|
64248
|
+
throw interruptedLogin();
|
|
64249
|
+
failures = 0;
|
|
64250
|
+
if (body.status === "ready") {
|
|
64251
|
+
if (!body.session)
|
|
64252
|
+
throw new Error("The server said the login was ready but sent no session. Run `brainbase login` again.");
|
|
64253
|
+
return body.session;
|
|
64254
|
+
}
|
|
64255
|
+
await wait(Math.min(intervalMs, budget.remainingMs(now2())));
|
|
64256
|
+
}
|
|
64257
|
+
throw new LoginTimeoutError(timeoutMs, lastError);
|
|
64258
|
+
}
|
|
63224
64259
|
|
|
63225
64260
|
// src/core/open-browser.ts
|
|
63226
64261
|
import { spawn } from "node:child_process";
|
|
@@ -67522,6 +68557,7 @@ function handleApiError2(err) {
|
|
|
67522
68557
|
}
|
|
67523
68558
|
|
|
67524
68559
|
// src/cli/agent-push.ts
|
|
68560
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
67525
68561
|
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
67526
68562
|
|
|
67527
68563
|
// src/core/collection-reconcile.ts
|
|
@@ -68072,8 +69108,8 @@ class LostResponseError extends ApiError {
|
|
|
68072
69108
|
idempotencyKey;
|
|
68073
69109
|
action;
|
|
68074
69110
|
retryCommand;
|
|
68075
|
-
constructor(idempotencyKey, action, retryCommand) {
|
|
68076
|
-
super(`The connection dropped before Brainbase answered, so ${action} may or may not have happened. ` + (retryCommand ? `Run \`${retryCommand}\` to finish it without doing it twice.` : `Re-run the same command with --idempotency-key ${idempotencyKey} to finish it without doing it twice.`));
|
|
69111
|
+
constructor(idempotencyKey, action, retryCommand, cause) {
|
|
69112
|
+
super(`The connection dropped before Brainbase answered, so ${action} may or may not have happened. ` + (retryCommand ? `Run \`${retryCommand}\` to finish it without doing it twice.` : `Re-run the same command with --idempotency-key ${idempotencyKey} to finish it without doing it twice.`), undefined, undefined, cause);
|
|
68077
69113
|
this.idempotencyKey = idempotencyKey;
|
|
68078
69114
|
this.action = action;
|
|
68079
69115
|
this.retryCommand = retryCommand;
|
|
@@ -68159,7 +69195,7 @@ function fieldMessages(fields) {
|
|
|
68159
69195
|
return fastApiValidationMessage({ detail: fields });
|
|
68160
69196
|
}
|
|
68161
69197
|
function toInterfaceError(error, options = {}) {
|
|
68162
|
-
if (error instanceof InterfaceApiError || !(error instanceof ApiError))
|
|
69198
|
+
if (isLostResponseError(error) || error instanceof InterfaceApiError || !(error instanceof ApiError))
|
|
68163
69199
|
return error;
|
|
68164
69200
|
const status = error.status;
|
|
68165
69201
|
const body = error.body;
|
|
@@ -68239,6 +69275,8 @@ async function keyedWrite(action, pathname, init, key2) {
|
|
|
68239
69275
|
} catch (error) {
|
|
68240
69276
|
if (!isLostResponseError(error) || init.signal?.aborted)
|
|
68241
69277
|
throw error;
|
|
69278
|
+
if (httpRetryEnabled())
|
|
69279
|
+
throw new LostResponseError(key2, action, undefined, error);
|
|
68242
69280
|
}
|
|
68243
69281
|
try {
|
|
68244
69282
|
return await guard(send());
|
|
@@ -69276,7 +70314,7 @@ async function runAgentPush(cwd2, args) {
|
|
|
69276
70314
|
if (config.browser.localChanged) {
|
|
69277
70315
|
update2.browser_enabled = manifest.capabilities?.browser;
|
|
69278
70316
|
}
|
|
69279
|
-
cloudAgent = await api.updateAgent(agentId, update2);
|
|
70317
|
+
cloudAgent = await api.updateAgent(agentId, update2, httpRetryEnabled() ? { idempotent: true } : undefined);
|
|
69280
70318
|
if (update2.runtime_mode !== undefined && cloudAgent.runtime_mode !== update2.runtime_mode) {
|
|
69281
70319
|
throw new Error("This control plane did not apply runtime_mode. Upgrade it before retrying; the sync baseline is unchanged.");
|
|
69282
70320
|
}
|
|
@@ -69311,16 +70349,27 @@ async function runAgentPush(cwd2, args) {
|
|
|
69311
70349
|
const pushSpinner = spinner();
|
|
69312
70350
|
pushSpinner.start("Pushing…");
|
|
69313
70351
|
let updatedCloud;
|
|
70352
|
+
const pushStartedAt = Date.now();
|
|
69314
70353
|
try {
|
|
69315
70354
|
const reconcileEvals = evalDecision.kind !== "skip";
|
|
69316
70355
|
const reconcilePlaybooks = playbookDecision.kind !== "skip";
|
|
69317
70356
|
const components = outgoing.filter((c2) => (reconcileEvals || c2.type !== "eval") && (reconcilePlaybooks || c2.type !== "playbook"));
|
|
69318
|
-
|
|
69319
|
-
|
|
69320
|
-
|
|
69321
|
-
|
|
69322
|
-
|
|
69323
|
-
|
|
70357
|
+
try {
|
|
70358
|
+
updatedCloud = await api.pushAgentManifest(agentId, {
|
|
70359
|
+
components,
|
|
70360
|
+
base_revision: cloud.revision,
|
|
70361
|
+
reconcile_playbooks: reconcilePlaybooks,
|
|
70362
|
+
reconcile_evals: reconcileEvals
|
|
70363
|
+
}, httpRetryEnabled() ? { keepalive: false } : undefined);
|
|
70364
|
+
} catch (err) {
|
|
70365
|
+
if (!httpRetryEnabled() || !isLostResponseError(err))
|
|
70366
|
+
throw err;
|
|
70367
|
+
pushSpinner.stop("Connection lost after the manifest was sent — reading the cloud manifest back…");
|
|
70368
|
+
updatedCloud = await reconcilePushedManifest(agentId, components, {
|
|
70369
|
+
reconcileEvals,
|
|
70370
|
+
reconcilePlaybooks
|
|
70371
|
+
}, pushStartedAt);
|
|
70372
|
+
}
|
|
69324
70373
|
pushSpinner.stop(`Pushed. New revision ${updatedCloud.revision}.`);
|
|
69325
70374
|
} catch (err) {
|
|
69326
70375
|
pushSpinner.stop("Failed.");
|
|
@@ -69464,7 +70513,7 @@ async function pushSecrets(cwd2, agentId, plan) {
|
|
|
69464
70513
|
const sp = spinner();
|
|
69465
70514
|
sp.start("Pushing secrets…");
|
|
69466
70515
|
try {
|
|
69467
|
-
await api.putAgentSecrets(agentId, plan.secrets);
|
|
70516
|
+
await api.putAgentSecrets(agentId, plan.secrets, httpRetryEnabled() ? { idempotent: true } : undefined);
|
|
69468
70517
|
sp.stop(`Secrets pushed (${secretDiffSummary(plan.diff)}).`);
|
|
69469
70518
|
} catch (err) {
|
|
69470
70519
|
sp.stop("Failed.");
|
|
@@ -69504,6 +70553,126 @@ function describeArchiveSet(evals, playbooks) {
|
|
|
69504
70553
|
}
|
|
69505
70554
|
return parts.join(" and ");
|
|
69506
70555
|
}
|
|
70556
|
+
function logRetryEvent2(event) {
|
|
70557
|
+
const line = JSON.stringify(event);
|
|
70558
|
+
if (event.event !== "retry_deferred")
|
|
70559
|
+
console.error(line);
|
|
70560
|
+
else
|
|
70561
|
+
console.log(line);
|
|
70562
|
+
}
|
|
70563
|
+
function pushRecoverySignal(startedAt) {
|
|
70564
|
+
const remaining = HTTP_RETRY_DEADLINE_MS - (Date.now() - startedAt);
|
|
70565
|
+
if (remaining <= 0)
|
|
70566
|
+
throw new Error("Push confirmation deadline exceeded");
|
|
70567
|
+
return AbortSignal.timeout(remaining);
|
|
70568
|
+
}
|
|
70569
|
+
async function reconcilePushedManifest(agentId, sent, claimed, startedAt) {
|
|
70570
|
+
logRetryEvent2({
|
|
70571
|
+
event: "retry_deferred",
|
|
70572
|
+
layer: "http",
|
|
70573
|
+
op: "push_agent_manifest",
|
|
70574
|
+
attempt: 1,
|
|
70575
|
+
class: "ambiguous",
|
|
70576
|
+
status: null,
|
|
70577
|
+
elapsed_ms: Date.now() - startedAt,
|
|
70578
|
+
budget_ms: HTTP_RETRY_DEADLINE_MS,
|
|
70579
|
+
error: "NetworkApiError"
|
|
70580
|
+
});
|
|
70581
|
+
let reread;
|
|
70582
|
+
let divergence;
|
|
70583
|
+
try {
|
|
70584
|
+
const signal = pushRecoverySignal(startedAt);
|
|
70585
|
+
reread = await api.getAgentManifest(agentId, { signal });
|
|
70586
|
+
divergence = await manifestDivergence(reread.components, sent, claimed, signal);
|
|
70587
|
+
} catch (err) {
|
|
70588
|
+
throw new ApiError(`The manifest push may have succeeded, but the CLI could not read the agent back to confirm it (${err.message}). Run brainbase agent pull before pushing again.`);
|
|
70589
|
+
}
|
|
70590
|
+
if (divergence) {
|
|
70591
|
+
throw new ApiError(`Cannot confirm the push: the cloud manifest at revision ${reread.revision} does not match what was sent (${divergence}).`, 409);
|
|
70592
|
+
}
|
|
70593
|
+
return reread;
|
|
70594
|
+
}
|
|
70595
|
+
async function manifestDivergence(cloud, sent, claimed, signal) {
|
|
70596
|
+
const cloudByType = new Map;
|
|
70597
|
+
for (const c2 of cloud) {
|
|
70598
|
+
if (c2.type === "skill" && c2.meta?.source && !sent.some((s3) => s3.type === c2.type && s3.slug === c2.slug))
|
|
70599
|
+
continue;
|
|
70600
|
+
const slugs = cloudByType.get(c2.type) ?? new Set;
|
|
70601
|
+
slugs.add(c2.slug);
|
|
70602
|
+
cloudByType.set(c2.type, slugs);
|
|
70603
|
+
}
|
|
70604
|
+
const sentByType = new Map;
|
|
70605
|
+
for (const c2 of sent) {
|
|
70606
|
+
const slugs = sentByType.get(c2.type) ?? new Set;
|
|
70607
|
+
slugs.add(c2.slug);
|
|
70608
|
+
sentByType.set(c2.type, slugs);
|
|
70609
|
+
}
|
|
70610
|
+
const claimedTypes = new Set(["instruction", "skill", "mcp"]);
|
|
70611
|
+
if (claimed.reconcileEvals)
|
|
70612
|
+
claimedTypes.add("eval");
|
|
70613
|
+
if (claimed.reconcilePlaybooks)
|
|
70614
|
+
claimedTypes.add("playbook");
|
|
70615
|
+
const diffs = [];
|
|
70616
|
+
for (const type of claimedTypes) {
|
|
70617
|
+
const sentSlugs = sentByType.get(type) ?? new Set;
|
|
70618
|
+
const cloudSlugs = cloudByType.get(type) ?? new Set;
|
|
70619
|
+
const missing = [...sentSlugs].filter((s3) => !cloudSlugs.has(s3));
|
|
70620
|
+
const unexpected = [...cloudSlugs].filter((s3) => !sentSlugs.has(s3));
|
|
70621
|
+
if (missing.length === 0 && unexpected.length === 0)
|
|
70622
|
+
continue;
|
|
70623
|
+
const parts = [];
|
|
70624
|
+
if (missing.length)
|
|
70625
|
+
parts.push(`missing ${missing.join(", ")}`);
|
|
70626
|
+
if (unexpected.length)
|
|
70627
|
+
parts.push(`unexpected ${unexpected.join(", ")}`);
|
|
70628
|
+
diffs.push(`${type}: ${parts.join(", ")}`);
|
|
70629
|
+
}
|
|
70630
|
+
for (const component of sent) {
|
|
70631
|
+
const other = cloud.find((c2) => c2.type === component.type && c2.slug === component.slug);
|
|
70632
|
+
if (other && !await componentContentsMatch(component, other, signal)) {
|
|
70633
|
+
diffs.push(`${component.type}: ${component.slug} contents differ`);
|
|
70634
|
+
}
|
|
70635
|
+
}
|
|
70636
|
+
return diffs.length ? diffs.join("; ") : null;
|
|
70637
|
+
}
|
|
70638
|
+
async function componentContentsMatch(sent, cloud, signal) {
|
|
70639
|
+
switch (sent.type) {
|
|
70640
|
+
case "instruction":
|
|
70641
|
+
return sent.files[0]?.content.trim() === cloud.files[0]?.content.trim();
|
|
70642
|
+
case "playbook":
|
|
70643
|
+
return sent.files[0]?.content === cloud.files[0]?.content;
|
|
70644
|
+
case "skill":
|
|
70645
|
+
return sent.meta?.version === undefined || sent.meta.version === cloud.meta?.version;
|
|
70646
|
+
case "mcp": {
|
|
70647
|
+
const config = (component) => {
|
|
70648
|
+
const m3 = component.meta?.mcp;
|
|
70649
|
+
return m3 && {
|
|
70650
|
+
url: m3.url ?? null,
|
|
70651
|
+
command: m3.command ?? null,
|
|
70652
|
+
args: m3.args ?? [],
|
|
70653
|
+
env: m3.env ?? {},
|
|
70654
|
+
headers: m3.headers ?? {},
|
|
70655
|
+
is_enabled: m3.is_enabled ?? true
|
|
70656
|
+
};
|
|
70657
|
+
};
|
|
70658
|
+
return isDeepStrictEqual3(config(sent), config(cloud));
|
|
70659
|
+
}
|
|
70660
|
+
case "eval": {
|
|
70661
|
+
const expected = { ...sent.meta?.eval };
|
|
70662
|
+
const actual = cloud.meta?.eval;
|
|
70663
|
+
if (expected.judge_type === "agent" && typeof expected.judge_agent === "string" && typeof actual?.judge_agent === "string" && expected.judge_agent !== actual.judge_agent) {
|
|
70664
|
+
const judge = await api.getAgent(actual.judge_agent, { signal });
|
|
70665
|
+
const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
70666
|
+
if (expected.judge_agent === judge.slug || expected.judge_agent === slug(judge.name)) {
|
|
70667
|
+
expected.judge_agent = actual.judge_agent;
|
|
70668
|
+
}
|
|
70669
|
+
}
|
|
70670
|
+
return isDeepStrictEqual3(expected, actual);
|
|
70671
|
+
}
|
|
70672
|
+
default:
|
|
70673
|
+
return false;
|
|
70674
|
+
}
|
|
70675
|
+
}
|
|
69507
70676
|
function secretDiffSummary(diff2) {
|
|
69508
70677
|
return `${diff2.localOnly.length} added, ${diff2.changed.length} updated, ${diff2.cloudOnly.length} removed`;
|
|
69509
70678
|
}
|
|
@@ -82571,6 +83740,32 @@ var import_picocolors46 = __toESM(require_picocolors(), 1);
|
|
|
82571
83740
|
|
|
82572
83741
|
// src/cli/interface-common.ts
|
|
82573
83742
|
var import_picocolors45 = __toESM(require_picocolors(), 1);
|
|
83743
|
+
|
|
83744
|
+
// src/ui/error-causes.ts
|
|
83745
|
+
var explicitDebug = false;
|
|
83746
|
+
function enableDebugOutput() {
|
|
83747
|
+
explicitDebug = true;
|
|
83748
|
+
}
|
|
83749
|
+
function debugOutputEnabled() {
|
|
83750
|
+
return httpRetryEnabled() && (explicitDebug || !!process.env.BRAINBASE_DEBUG);
|
|
83751
|
+
}
|
|
83752
|
+
function formatErrorCauseChain(error2) {
|
|
83753
|
+
const lines = [];
|
|
83754
|
+
const seen = new Set;
|
|
83755
|
+
while (error2 !== undefined && error2 !== null && !seen.has(error2)) {
|
|
83756
|
+
seen.add(error2);
|
|
83757
|
+
const record5 = error2;
|
|
83758
|
+
const prefix = lines.length ? "Caused by: " : "";
|
|
83759
|
+
lines.push(...sanitizeLines(prefix + (record5.stack ?? `${record5.name ?? "Error"}: ${record5.message ?? String(error2)}`)).map(redactSensitiveText));
|
|
83760
|
+
if (record5.code !== undefined)
|
|
83761
|
+
lines.push(...sanitizeLines(`code: ${String(record5.code)}`).map(redactSensitiveText));
|
|
83762
|
+
error2 = record5.cause;
|
|
83763
|
+
}
|
|
83764
|
+
return lines.join(`
|
|
83765
|
+
`);
|
|
83766
|
+
}
|
|
83767
|
+
|
|
83768
|
+
// src/cli/interface-common.ts
|
|
82574
83769
|
var JSON_FLAG = { key: "json", names: ["--json"], kind: "boolean" };
|
|
82575
83770
|
var YES_FLAG = { key: "yes", names: ["--yes", "-y"], kind: "boolean" };
|
|
82576
83771
|
var KEY_FLAG = {
|
|
@@ -82679,6 +83874,8 @@ async function runReportingErrors(json4, work) {
|
|
|
82679
83874
|
if (!(typeof json4 === "function" ? json4() : json4))
|
|
82680
83875
|
throw error2;
|
|
82681
83876
|
printJson({ error: errorShape(error2) });
|
|
83877
|
+
if (debugOutputEnabled())
|
|
83878
|
+
console.error(formatErrorCauseChain(error2));
|
|
82682
83879
|
process.exitCode = 1;
|
|
82683
83880
|
}
|
|
82684
83881
|
}
|
|
@@ -83194,6 +84391,584 @@ async function unpin(ctx) {
|
|
|
83194
84391
|
}
|
|
83195
84392
|
var writeCommands = { label, restore, pin, unpin };
|
|
83196
84393
|
|
|
84394
|
+
// src/core/idempotency.ts
|
|
84395
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
84396
|
+
function runPlanIdempotencyKey(explicitKey) {
|
|
84397
|
+
return explicitKey ?? (httpRetryEnabled() ? randomUUID4() : undefined);
|
|
84398
|
+
}
|
|
84399
|
+
function runPlanRetryHint(key2, uncertain = true) {
|
|
84400
|
+
const uncertainty = uncertain ? "Plan creation may have reached Brainbase. " : "";
|
|
84401
|
+
if (!key2)
|
|
84402
|
+
return `${uncertainty}Check existing runs before creating another plan.`;
|
|
84403
|
+
if (redactSensitiveText(key2) !== key2 || !/^[A-Za-z0-9._:-]+$/.test(key2)) {
|
|
84404
|
+
return `${uncertainty}For a retry of the same request, use the original --idempotency-key value, not a new key.`;
|
|
84405
|
+
}
|
|
84406
|
+
return uncertain ? `${uncertainty}Re-run the same command with --idempotency-key=${key2} to recover it without creating another plan.` : `For a retry of the same request, reuse --idempotency-key=${key2}.`;
|
|
84407
|
+
}
|
|
84408
|
+
|
|
84409
|
+
// src/core/benchmark-api.ts
|
|
84410
|
+
var DEFAULT_BENCHMARK_REQUEST_TIMEOUT_MS = 30000;
|
|
84411
|
+
var BENCHMARK_UPLOAD_PROCESSING_ALLOWANCE_MS = 30000;
|
|
84412
|
+
var BENCHMARK_UPLOAD_MIN_BYTES_PER_SECOND = 256 * 1024;
|
|
84413
|
+
|
|
84414
|
+
class BenchmarkClientUsageError extends Error {
|
|
84415
|
+
code = "invalid_arguments";
|
|
84416
|
+
retryable = false;
|
|
84417
|
+
constructor(message) {
|
|
84418
|
+
super(message);
|
|
84419
|
+
this.name = "BenchmarkClientUsageError";
|
|
84420
|
+
}
|
|
84421
|
+
}
|
|
84422
|
+
|
|
84423
|
+
class BenchmarkApiError extends ApiError {
|
|
84424
|
+
code;
|
|
84425
|
+
requestId;
|
|
84426
|
+
retryable;
|
|
84427
|
+
constructor(message, status, body, code, requestId, retryable) {
|
|
84428
|
+
super(message, status, body);
|
|
84429
|
+
this.code = code;
|
|
84430
|
+
this.requestId = requestId;
|
|
84431
|
+
this.name = "BenchmarkApiError";
|
|
84432
|
+
this.retryable = retryable ?? (status === undefined || status === 408 || status === 425 || status === 429 || status !== undefined && status >= 500);
|
|
84433
|
+
}
|
|
84434
|
+
}
|
|
84435
|
+
function safeCode(body) {
|
|
84436
|
+
if (!body || typeof body !== "object")
|
|
84437
|
+
return;
|
|
84438
|
+
const object4 = body;
|
|
84439
|
+
const nested = object4.error && typeof object4.error === "object" ? object4.error.code : object4.detail && typeof object4.detail === "object" ? object4.detail.code : undefined;
|
|
84440
|
+
const code = object4.code ?? nested;
|
|
84441
|
+
return typeof code === "string" && /^[a-z][a-z0-9_]{0,99}$/.test(code) ? code : undefined;
|
|
84442
|
+
}
|
|
84443
|
+
function queryString(query) {
|
|
84444
|
+
if (!query)
|
|
84445
|
+
return "";
|
|
84446
|
+
const params = new URLSearchParams;
|
|
84447
|
+
for (const key2 of Object.keys(query).sort()) {
|
|
84448
|
+
const value3 = query[key2];
|
|
84449
|
+
if (value3 === undefined)
|
|
84450
|
+
continue;
|
|
84451
|
+
for (const item of Array.isArray(value3) ? value3 : [value3]) {
|
|
84452
|
+
params.append(key2, String(item));
|
|
84453
|
+
}
|
|
84454
|
+
}
|
|
84455
|
+
const encoded = params.toString();
|
|
84456
|
+
return encoded ? `?${encoded}` : "";
|
|
84457
|
+
}
|
|
84458
|
+
function filenameFromHeaders(headers) {
|
|
84459
|
+
const disposition = headers.get("Content-Disposition");
|
|
84460
|
+
if (!disposition)
|
|
84461
|
+
return null;
|
|
84462
|
+
const utf8 = disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1];
|
|
84463
|
+
if (utf8) {
|
|
84464
|
+
try {
|
|
84465
|
+
return decodeURIComponent(utf8);
|
|
84466
|
+
} catch {
|
|
84467
|
+
return utf8;
|
|
84468
|
+
}
|
|
84469
|
+
}
|
|
84470
|
+
return disposition.match(/filename="?([^";]+)"?/i)?.[1] ?? null;
|
|
84471
|
+
}
|
|
84472
|
+
function uploadBlob(upload) {
|
|
84473
|
+
if (upload.data instanceof Blob)
|
|
84474
|
+
return upload.data;
|
|
84475
|
+
return new Blob([upload.data], {
|
|
84476
|
+
type: upload.contentType ?? "application/octet-stream"
|
|
84477
|
+
});
|
|
84478
|
+
}
|
|
84479
|
+
function requestAbortContext(callerSignal, timeoutMs3) {
|
|
84480
|
+
const controller = new AbortController;
|
|
84481
|
+
let source;
|
|
84482
|
+
const abortFromCaller = () => {
|
|
84483
|
+
if (source !== undefined)
|
|
84484
|
+
return;
|
|
84485
|
+
source = "caller";
|
|
84486
|
+
controller.abort(callerSignal?.reason);
|
|
84487
|
+
};
|
|
84488
|
+
if (callerSignal?.aborted) {
|
|
84489
|
+
abortFromCaller();
|
|
84490
|
+
} else {
|
|
84491
|
+
callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
84492
|
+
}
|
|
84493
|
+
const timer = setTimeout(() => {
|
|
84494
|
+
if (source !== undefined)
|
|
84495
|
+
return;
|
|
84496
|
+
source = "timeout";
|
|
84497
|
+
controller.abort(new DOMException(`Benchmark request timed out after ${timeoutMs3} ms`, "TimeoutError"));
|
|
84498
|
+
}, timeoutMs3);
|
|
84499
|
+
timer.unref?.();
|
|
84500
|
+
return {
|
|
84501
|
+
signal: controller.signal,
|
|
84502
|
+
source: () => source,
|
|
84503
|
+
dispose: () => {
|
|
84504
|
+
clearTimeout(timer);
|
|
84505
|
+
callerSignal?.removeEventListener("abort", abortFromCaller);
|
|
84506
|
+
}
|
|
84507
|
+
};
|
|
84508
|
+
}
|
|
84509
|
+
function callerAbortError(signal) {
|
|
84510
|
+
return signal?.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
84511
|
+
}
|
|
84512
|
+
function idempotencyHeaders(key2) {
|
|
84513
|
+
const headers = new Headers;
|
|
84514
|
+
if (key2 === undefined)
|
|
84515
|
+
return headers;
|
|
84516
|
+
if (key2.length < 8 || key2.length > 200 || /[^\x21-\x7e]/.test(key2)) {
|
|
84517
|
+
throw new BenchmarkClientUsageError("Idempotency keys must be 8-200 visible ASCII characters");
|
|
84518
|
+
}
|
|
84519
|
+
headers.set("Idempotency-Key", key2);
|
|
84520
|
+
return headers;
|
|
84521
|
+
}
|
|
84522
|
+
|
|
84523
|
+
class BenchmarkApiClient {
|
|
84524
|
+
agentId;
|
|
84525
|
+
fixedBaseUrl;
|
|
84526
|
+
fetchImpl;
|
|
84527
|
+
credentialResolver;
|
|
84528
|
+
authRetry;
|
|
84529
|
+
requestTimeoutMs;
|
|
84530
|
+
uploadTimeoutMs;
|
|
84531
|
+
constructor(options) {
|
|
84532
|
+
this.agentId = options.agentId;
|
|
84533
|
+
this.fixedBaseUrl = options.baseUrl?.replace(/\/+$/, "");
|
|
84534
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
84535
|
+
this.credentialResolver = options.resolveCredential ?? (async () => await resolveCredential());
|
|
84536
|
+
this.authRetry = options.authRetry ?? sendWithAuthRetry;
|
|
84537
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_BENCHMARK_REQUEST_TIMEOUT_MS;
|
|
84538
|
+
if (!Number.isSafeInteger(this.requestTimeoutMs) || this.requestTimeoutMs < 1) {
|
|
84539
|
+
throw new BenchmarkClientUsageError("Benchmark request timeout must be a positive safe integer");
|
|
84540
|
+
}
|
|
84541
|
+
this.uploadTimeoutMs = options.uploadTimeoutMs;
|
|
84542
|
+
if (this.uploadTimeoutMs !== undefined && (!Number.isSafeInteger(this.uploadTimeoutMs) || this.uploadTimeoutMs < 1)) {
|
|
84543
|
+
throw new BenchmarkClientUsageError("Benchmark upload timeout must be a positive safe integer");
|
|
84544
|
+
}
|
|
84545
|
+
}
|
|
84546
|
+
root(session) {
|
|
84547
|
+
const base2 = this.fixedBaseUrl ?? masControlPlaneBaseUrl(session);
|
|
84548
|
+
return `${base2}/v2/agents/${encodeURIComponent(this.agentId)}/benchmarks`;
|
|
84549
|
+
}
|
|
84550
|
+
async request(pathname, options = {}) {
|
|
84551
|
+
const credential = await this.credentialResolver();
|
|
84552
|
+
let session = credential.session;
|
|
84553
|
+
const headers = new Headers(options.headers);
|
|
84554
|
+
const idempotency = idempotencyHeaders(options.idempotencyKey);
|
|
84555
|
+
idempotency.forEach((value3, key2) => headers.set(key2, value3));
|
|
84556
|
+
if (!headers.has("Accept")) {
|
|
84557
|
+
headers.set("Accept", options.mode === "binary" ? "application/octet-stream, application/gzip, application/zip, application/yaml, application/json" : "application/json");
|
|
84558
|
+
}
|
|
84559
|
+
let body = options.body;
|
|
84560
|
+
if (body !== undefined && !(body instanceof FormData) && !(body instanceof Blob) && !(body instanceof ArrayBuffer) && !(body instanceof Uint8Array) && typeof body !== "string") {
|
|
84561
|
+
body = stableJsonStringify(body);
|
|
84562
|
+
if (!headers.has("Content-Type")) {
|
|
84563
|
+
headers.set("Content-Type", "application/json");
|
|
84564
|
+
}
|
|
84565
|
+
}
|
|
84566
|
+
const method2 = (options.method ?? "GET").toUpperCase();
|
|
84567
|
+
const timeoutMs3 = options.timeoutMs ?? this.requestTimeoutMs;
|
|
84568
|
+
const abort = requestAbortContext(options.signal, timeoutMs3);
|
|
84569
|
+
const send = async (refreshed) => {
|
|
84570
|
+
if (refreshed)
|
|
84571
|
+
session = refreshed;
|
|
84572
|
+
if (options.sendBudget && options.sendBudget.remaining <= 0) {
|
|
84573
|
+
throw new BenchmarkApiError("HTTP request attempt budget exhausted", undefined, null, "retry_exhausted", undefined, false);
|
|
84574
|
+
}
|
|
84575
|
+
if (options.sendBudget) {
|
|
84576
|
+
options.sendBudget.remaining--;
|
|
84577
|
+
options.sendBudget.lastStatus = undefined;
|
|
84578
|
+
}
|
|
84579
|
+
const requestHeaders = new Headers(headers);
|
|
84580
|
+
requestHeaders.set("Authorization", `Bearer ${session?.access_token ?? credential.bearer}`);
|
|
84581
|
+
const response2 = await this.fetchImpl(`${this.root(session)}${pathname}${queryString(options.query)}`, { method: method2, headers: requestHeaders, body, signal: abort.signal });
|
|
84582
|
+
if (options.sendBudget)
|
|
84583
|
+
options.sendBudget.lastStatus = response2.status;
|
|
84584
|
+
return response2;
|
|
84585
|
+
};
|
|
84586
|
+
let response;
|
|
84587
|
+
try {
|
|
84588
|
+
try {
|
|
84589
|
+
response = await this.authRetry(credential.source === "session" ? session : null, send);
|
|
84590
|
+
} catch (error2) {
|
|
84591
|
+
if (abort.source() === "caller") {
|
|
84592
|
+
throw callerAbortError(options.signal);
|
|
84593
|
+
}
|
|
84594
|
+
if (abort.source() === "timeout") {
|
|
84595
|
+
throw new BenchmarkApiError(`Benchmark request timed out after ${timeoutMs3} ms`, 408, null, "request_timeout");
|
|
84596
|
+
}
|
|
84597
|
+
const failure = new BenchmarkApiError(redactSensitiveText(`Network error: ${error2 instanceof Error ? error2.message : String(error2)}`), undefined, null, "network_error");
|
|
84598
|
+
if (httpRetryEnabled())
|
|
84599
|
+
failure.cause = error2;
|
|
84600
|
+
throw failure;
|
|
84601
|
+
}
|
|
84602
|
+
if (!response.ok) {
|
|
84603
|
+
const text4 = await response.text();
|
|
84604
|
+
let responseBody = text4;
|
|
84605
|
+
try {
|
|
84606
|
+
responseBody = text4 ? JSON.parse(text4) : null;
|
|
84607
|
+
} catch {}
|
|
84608
|
+
const failure = new BenchmarkApiError(redactSensitiveText(masApiErrorMessage(responseBody, response.status)), response.status, responseBody, safeCode(responseBody), response.headers.get("x-request-id") ?? response.headers.get("x-correlation-id") ?? undefined);
|
|
84609
|
+
if (httpRetryEnabled())
|
|
84610
|
+
failure.retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
84611
|
+
throw failure;
|
|
84612
|
+
}
|
|
84613
|
+
if (options.mode === "binary") {
|
|
84614
|
+
return {
|
|
84615
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
84616
|
+
contentType: response.headers.get("Content-Type"),
|
|
84617
|
+
filename: filenameFromHeaders(response.headers)
|
|
84618
|
+
};
|
|
84619
|
+
}
|
|
84620
|
+
if (response.status === 204)
|
|
84621
|
+
return;
|
|
84622
|
+
const text3 = await response.text();
|
|
84623
|
+
if (!text3)
|
|
84624
|
+
return null;
|
|
84625
|
+
try {
|
|
84626
|
+
return JSON.parse(text3);
|
|
84627
|
+
} catch {
|
|
84628
|
+
throw new BenchmarkApiError("Benchmark API returned an invalid JSON response", response.status, null, "invalid_response", response.headers.get("x-request-id") ?? response.headers.get("x-correlation-id") ?? undefined, false);
|
|
84629
|
+
}
|
|
84630
|
+
} catch (error2) {
|
|
84631
|
+
if (abort.source() === "caller") {
|
|
84632
|
+
throw callerAbortError(options.signal);
|
|
84633
|
+
}
|
|
84634
|
+
if (abort.source() === "timeout") {
|
|
84635
|
+
throw new BenchmarkApiError(`Benchmark request timed out after ${timeoutMs3} ms`, 408, null, "request_timeout");
|
|
84636
|
+
}
|
|
84637
|
+
if (httpRetryEnabled() && !(error2 instanceof ApiError)) {
|
|
84638
|
+
const status = response && !response.ok ? response.status : undefined;
|
|
84639
|
+
const failure = new BenchmarkApiError(status === undefined ? redactSensitiveText(`Network error while reading response: ${error2 instanceof Error ? error2.message : String(error2)}`) : `Benchmark API returned HTTP ${status}, but its response body was interrupted`, status, null, status === undefined ? "network_error" : undefined, response?.headers.get("x-request-id") ?? response?.headers.get("x-correlation-id") ?? undefined);
|
|
84640
|
+
if (status !== undefined)
|
|
84641
|
+
failure.retryAfter = response?.headers.get("Retry-After") ?? undefined;
|
|
84642
|
+
else
|
|
84643
|
+
failure.cause = error2;
|
|
84644
|
+
throw failure;
|
|
84645
|
+
}
|
|
84646
|
+
throw error2;
|
|
84647
|
+
} finally {
|
|
84648
|
+
abort.dispose();
|
|
84649
|
+
}
|
|
84650
|
+
}
|
|
84651
|
+
validateManifest(manifest) {
|
|
84652
|
+
return this.request("/validate", {
|
|
84653
|
+
method: "POST",
|
|
84654
|
+
body: { manifest }
|
|
84655
|
+
});
|
|
84656
|
+
}
|
|
84657
|
+
createBenchmark(manifest, options) {
|
|
84658
|
+
return this.request("", {
|
|
84659
|
+
method: "POST",
|
|
84660
|
+
body: { manifest },
|
|
84661
|
+
...options
|
|
84662
|
+
});
|
|
84663
|
+
}
|
|
84664
|
+
listBenchmarks(query = {}) {
|
|
84665
|
+
return this.request("", {
|
|
84666
|
+
query: {
|
|
84667
|
+
limit: query.limit,
|
|
84668
|
+
cursor: query.cursor,
|
|
84669
|
+
archived: query.archived
|
|
84670
|
+
}
|
|
84671
|
+
});
|
|
84672
|
+
}
|
|
84673
|
+
getBenchmark(benchmarkId) {
|
|
84674
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}`);
|
|
84675
|
+
}
|
|
84676
|
+
updateBenchmark(benchmarkId, input) {
|
|
84677
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}`, {
|
|
84678
|
+
method: "PATCH",
|
|
84679
|
+
body: input
|
|
84680
|
+
});
|
|
84681
|
+
}
|
|
84682
|
+
archiveBenchmark(benchmarkId, expectedVersion) {
|
|
84683
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/archive`, {
|
|
84684
|
+
method: "POST",
|
|
84685
|
+
body: { expected_version: expectedVersion }
|
|
84686
|
+
});
|
|
84687
|
+
}
|
|
84688
|
+
restoreBenchmark(benchmarkId, expectedVersion) {
|
|
84689
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/restore`, {
|
|
84690
|
+
method: "POST",
|
|
84691
|
+
body: { expected_version: expectedVersion }
|
|
84692
|
+
});
|
|
84693
|
+
}
|
|
84694
|
+
async purgeBenchmark(benchmarkId, input) {
|
|
84695
|
+
await this.request(`/${encodeURIComponent(benchmarkId)}/purge`, {
|
|
84696
|
+
method: "POST",
|
|
84697
|
+
body: input
|
|
84698
|
+
});
|
|
84699
|
+
}
|
|
84700
|
+
createRevision(benchmarkId, expectedVersion, options) {
|
|
84701
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/revisions`, {
|
|
84702
|
+
method: "POST",
|
|
84703
|
+
body: { expected_version: expectedVersion },
|
|
84704
|
+
...options
|
|
84705
|
+
});
|
|
84706
|
+
}
|
|
84707
|
+
listRevisions(benchmarkId, query = {}) {
|
|
84708
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/revisions`, {
|
|
84709
|
+
query: {
|
|
84710
|
+
limit: query.limit,
|
|
84711
|
+
before_revision_number: query.beforeRevisionNumber
|
|
84712
|
+
}
|
|
84713
|
+
});
|
|
84714
|
+
}
|
|
84715
|
+
exportManifest(benchmarkId, format, revisionId) {
|
|
84716
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/export`, {
|
|
84717
|
+
mode: "binary",
|
|
84718
|
+
query: { format, revision_id: revisionId }
|
|
84719
|
+
});
|
|
84720
|
+
}
|
|
84721
|
+
exportBundle(benchmarkId, revisionId) {
|
|
84722
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/export`, {
|
|
84723
|
+
mode: "binary",
|
|
84724
|
+
query: { format: "bundle", revision_id: revisionId }
|
|
84725
|
+
});
|
|
84726
|
+
}
|
|
84727
|
+
signBenchmarkObject(benchmarkId, objectId, query = {}) {
|
|
84728
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/objects/${encodeURIComponent(objectId)}/signed-url`, {
|
|
84729
|
+
query: {
|
|
84730
|
+
expires_in_seconds: query.expiresInSeconds,
|
|
84731
|
+
download_name: query.downloadName
|
|
84732
|
+
}
|
|
84733
|
+
});
|
|
84734
|
+
}
|
|
84735
|
+
upload(pathname, upload, fields, options) {
|
|
84736
|
+
const form = new FormData;
|
|
84737
|
+
const blob = uploadBlob(upload);
|
|
84738
|
+
form.append("file", blob, upload.filename);
|
|
84739
|
+
for (const [key2, value3] of Object.entries(fields)) {
|
|
84740
|
+
if (value3 !== undefined)
|
|
84741
|
+
form.append(key2, value3);
|
|
84742
|
+
}
|
|
84743
|
+
const scaledTimeoutMs = BENCHMARK_UPLOAD_PROCESSING_ALLOWANCE_MS + Math.ceil(blob.size / BENCHMARK_UPLOAD_MIN_BYTES_PER_SECOND * 1000);
|
|
84744
|
+
return this.request(pathname, {
|
|
84745
|
+
method: "POST",
|
|
84746
|
+
body: form,
|
|
84747
|
+
timeoutMs: this.uploadTimeoutMs ?? Math.max(this.requestTimeoutMs, scaledTimeoutMs),
|
|
84748
|
+
...options
|
|
84749
|
+
});
|
|
84750
|
+
}
|
|
84751
|
+
importManifest(upload, options) {
|
|
84752
|
+
return this.upload("/imports/manifest", upload, {}, options);
|
|
84753
|
+
}
|
|
84754
|
+
importCsv(upload, fields, options) {
|
|
84755
|
+
return this.upload("/imports/csv", upload, {
|
|
84756
|
+
mapping_json: stableJsonStringify(fields.mapping),
|
|
84757
|
+
metadata_json: stableJsonStringify(fields.metadata),
|
|
84758
|
+
environment_json: fields.environment === undefined ? undefined : stableJsonStringify(fields.environment),
|
|
84759
|
+
evaluators_json: fields.evaluators === undefined ? undefined : stableJsonStringify(fields.evaluators),
|
|
84760
|
+
primary_evaluator_key: fields.primaryEvaluatorKey
|
|
84761
|
+
}, options);
|
|
84762
|
+
}
|
|
84763
|
+
importBundle(upload, options) {
|
|
84764
|
+
return this.upload("/imports/bundle", upload, {}, options);
|
|
84765
|
+
}
|
|
84766
|
+
previewTaskImport(input) {
|
|
84767
|
+
return this.request("/imports/task/preview", {
|
|
84768
|
+
method: "POST",
|
|
84769
|
+
body: input
|
|
84770
|
+
});
|
|
84771
|
+
}
|
|
84772
|
+
materializeTaskImport(input) {
|
|
84773
|
+
return this.request("/imports/task/materialize", {
|
|
84774
|
+
method: "POST",
|
|
84775
|
+
body: input
|
|
84776
|
+
});
|
|
84777
|
+
}
|
|
84778
|
+
previewHuggingFaceImport(input) {
|
|
84779
|
+
return this.request("/imports/hugging-face/preview", {
|
|
84780
|
+
method: "POST",
|
|
84781
|
+
body: input
|
|
84782
|
+
});
|
|
84783
|
+
}
|
|
84784
|
+
materializeHuggingFaceImport(input, options) {
|
|
84785
|
+
return this.request("/imports/hugging-face/materialize", {
|
|
84786
|
+
method: "POST",
|
|
84787
|
+
body: input,
|
|
84788
|
+
...options
|
|
84789
|
+
});
|
|
84790
|
+
}
|
|
84791
|
+
createRunPlan(benchmarkId, input, options) {
|
|
84792
|
+
const sendBudget = httpRetryEnabled() ? { remaining: HTTP_RETRY_MAX_ATTEMPTS } : undefined;
|
|
84793
|
+
const send = (signal, fixedOptions2 = options) => this.request(`/${encodeURIComponent(benchmarkId)}/runs/plan`, {
|
|
84794
|
+
method: "POST",
|
|
84795
|
+
body: input,
|
|
84796
|
+
...fixedOptions2,
|
|
84797
|
+
signal,
|
|
84798
|
+
sendBudget
|
|
84799
|
+
});
|
|
84800
|
+
if (!httpRetryEnabled())
|
|
84801
|
+
return send();
|
|
84802
|
+
const fixedOptions = { ...options };
|
|
84803
|
+
const key2 = fixedOptions.idempotencyKey?.trim();
|
|
84804
|
+
fixedOptions.idempotencyKey = key2;
|
|
84805
|
+
let uncertain = false;
|
|
84806
|
+
const mayHaveApplied = (error2) => {
|
|
84807
|
+
const status = sendBudget?.lastStatus;
|
|
84808
|
+
if (status === 401)
|
|
84809
|
+
return false;
|
|
84810
|
+
if (status !== undefined && status >= 200 && status < 300)
|
|
84811
|
+
return true;
|
|
84812
|
+
const kind = classifyHttpFailure(error2, !!key2);
|
|
84813
|
+
return kind === "ambiguous" || kind === "retryable_status" && status !== 429 && status !== 425;
|
|
84814
|
+
};
|
|
84815
|
+
const request2 = async (signal) => {
|
|
84816
|
+
try {
|
|
84817
|
+
return await send(signal, fixedOptions);
|
|
84818
|
+
} catch (error2) {
|
|
84819
|
+
if ((sendBudget?.remaining ?? HTTP_RETRY_MAX_ATTEMPTS) < HTTP_RETRY_MAX_ATTEMPTS && mayHaveApplied(error2))
|
|
84820
|
+
uncertain = true;
|
|
84821
|
+
throw error2;
|
|
84822
|
+
}
|
|
84823
|
+
};
|
|
84824
|
+
return retryHttpRequest(request2, {
|
|
84825
|
+
idempotent: false,
|
|
84826
|
+
keyed: !!key2,
|
|
84827
|
+
budgetMs: this.requestTimeoutMs,
|
|
84828
|
+
attemptsUsed: () => HTTP_RETRY_MAX_ATTEMPTS - (sendBudget?.remaining ?? 0),
|
|
84829
|
+
retryTimeouts: false,
|
|
84830
|
+
op: "benchmark_run_plan"
|
|
84831
|
+
}).catch((error2) => {
|
|
84832
|
+
const original = error2 instanceof BenchmarkApiError ? error2 : undefined;
|
|
84833
|
+
if (original && key2)
|
|
84834
|
+
original.idempotencyKey = key2;
|
|
84835
|
+
const code = original ? safeCode(original.body) : undefined;
|
|
84836
|
+
const settled = original?.status === 409 && (code === "idempotency_key_reused" || code === "idempotency_resource_deleted");
|
|
84837
|
+
const dispatched = (sendBudget?.remaining ?? HTTP_RETRY_MAX_ATTEMPTS) < HTTP_RETRY_MAX_ATTEMPTS;
|
|
84838
|
+
const unknown3 = uncertain || dispatched && mayHaveApplied(error2);
|
|
84839
|
+
if (settled || !dispatched || !uncertain && sendBudget?.lastStatus === 401 || !unknown3 && !key2)
|
|
84840
|
+
throw error2;
|
|
84841
|
+
const failure = new BenchmarkApiError(`${runPlanRetryHint(key2, unknown3)} ${redactSensitiveText(error2 instanceof Error ? error2.message : String(error2))}`, original?.status, original?.body ?? null, original?.code ?? (original ? undefined : isHttpTimeout(error2) ? "request_timeout" : "network_error"), original?.requestId, original?.retryable);
|
|
84842
|
+
failure.cause = error2;
|
|
84843
|
+
if (key2)
|
|
84844
|
+
failure.idempotencyKey = key2;
|
|
84845
|
+
throw failure;
|
|
84846
|
+
});
|
|
84847
|
+
}
|
|
84848
|
+
getRunPlan(planId) {
|
|
84849
|
+
return this.request(`/run-plans/${encodeURIComponent(planId)}`);
|
|
84850
|
+
}
|
|
84851
|
+
confirmRunPlan(planId) {
|
|
84852
|
+
return this.request(`/run-plans/${encodeURIComponent(planId)}/confirm`, {
|
|
84853
|
+
method: "POST"
|
|
84854
|
+
});
|
|
84855
|
+
}
|
|
84856
|
+
confirmRun(runId) {
|
|
84857
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/confirm`, {
|
|
84858
|
+
method: "POST"
|
|
84859
|
+
});
|
|
84860
|
+
}
|
|
84861
|
+
listRuns(query = {}) {
|
|
84862
|
+
return this.request("/runs", {
|
|
84863
|
+
query: {
|
|
84864
|
+
benchmark_id: query.benchmarkId,
|
|
84865
|
+
status: query.status,
|
|
84866
|
+
limit: query.limit,
|
|
84867
|
+
cursor: query.cursor
|
|
84868
|
+
}
|
|
84869
|
+
});
|
|
84870
|
+
}
|
|
84871
|
+
getRun(runId, options = {}) {
|
|
84872
|
+
return this.request(`/runs/${encodeURIComponent(runId)}`, options);
|
|
84873
|
+
}
|
|
84874
|
+
updateRunAnnotation(runId, input) {
|
|
84875
|
+
return this.request(`/runs/${encodeURIComponent(runId)}`, {
|
|
84876
|
+
method: "PATCH",
|
|
84877
|
+
body: input
|
|
84878
|
+
});
|
|
84879
|
+
}
|
|
84880
|
+
cancelRun(runId) {
|
|
84881
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
84882
|
+
method: "POST"
|
|
84883
|
+
});
|
|
84884
|
+
}
|
|
84885
|
+
resumeRun(runId) {
|
|
84886
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/resume`, {
|
|
84887
|
+
method: "POST"
|
|
84888
|
+
});
|
|
84889
|
+
}
|
|
84890
|
+
listDiagnoses(runId, query = {}) {
|
|
84891
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/diagnoses`, {
|
|
84892
|
+
query: {
|
|
84893
|
+
limit: query.limit,
|
|
84894
|
+
cursor: query.cursor
|
|
84895
|
+
}
|
|
84896
|
+
});
|
|
84897
|
+
}
|
|
84898
|
+
getRunResults(runId, baselineTargetId) {
|
|
84899
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/results`, {
|
|
84900
|
+
query: { baseline_target_id: baselineTargetId }
|
|
84901
|
+
});
|
|
84902
|
+
}
|
|
84903
|
+
exportRunResults(runId, query = {}) {
|
|
84904
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/results/export`, {
|
|
84905
|
+
mode: "binary",
|
|
84906
|
+
query: {
|
|
84907
|
+
include_billing_receipts: query.includeBillingReceipts || undefined
|
|
84908
|
+
}
|
|
84909
|
+
});
|
|
84910
|
+
}
|
|
84911
|
+
listTrials(runId, query = {}) {
|
|
84912
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/trials`, {
|
|
84913
|
+
query: {
|
|
84914
|
+
status: query.status,
|
|
84915
|
+
limit: query.limit,
|
|
84916
|
+
cursor: query.cursor
|
|
84917
|
+
}
|
|
84918
|
+
});
|
|
84919
|
+
}
|
|
84920
|
+
listAttempts(runId, query = {}) {
|
|
84921
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/attempts`, {
|
|
84922
|
+
query: {
|
|
84923
|
+
trial_id: query.trialId,
|
|
84924
|
+
status: query.status,
|
|
84925
|
+
limit: query.limit,
|
|
84926
|
+
cursor: query.cursor
|
|
84927
|
+
}
|
|
84928
|
+
});
|
|
84929
|
+
}
|
|
84930
|
+
getAttempt(runId, attemptId, query = {}) {
|
|
84931
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/attempts/${encodeURIComponent(attemptId)}`, {
|
|
84932
|
+
query: {
|
|
84933
|
+
evaluator_limit: query.evaluatorLimit,
|
|
84934
|
+
evaluator_cursor: query.evaluatorCursor,
|
|
84935
|
+
artifact_limit: query.artifactLimit,
|
|
84936
|
+
artifact_cursor: query.artifactCursor
|
|
84937
|
+
}
|
|
84938
|
+
});
|
|
84939
|
+
}
|
|
84940
|
+
signAttemptArtifact(runId, attemptId, artifactId, query = {}) {
|
|
84941
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/attempts/${encodeURIComponent(attemptId)}/artifacts/${encodeURIComponent(artifactId)}/signed-url`, {
|
|
84942
|
+
query: {
|
|
84943
|
+
expires_in_seconds: query.expiresInSeconds,
|
|
84944
|
+
download_name: query.downloadName
|
|
84945
|
+
}
|
|
84946
|
+
});
|
|
84947
|
+
}
|
|
84948
|
+
getSummaries(benchmarkIds, historyLimit) {
|
|
84949
|
+
return this.request("/analytics/summaries", {
|
|
84950
|
+
query: {
|
|
84951
|
+
benchmark_id: benchmarkIds,
|
|
84952
|
+
history_limit: historyLimit
|
|
84953
|
+
}
|
|
84954
|
+
});
|
|
84955
|
+
}
|
|
84956
|
+
getAnalytics(benchmarkId, historyLimit) {
|
|
84957
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/analytics`, {
|
|
84958
|
+
query: { history_limit: historyLimit }
|
|
84959
|
+
});
|
|
84960
|
+
}
|
|
84961
|
+
setBaseline(benchmarkId, targetKey, baselineTargetId) {
|
|
84962
|
+
return this.request(`/${encodeURIComponent(benchmarkId)}/baselines/${encodeURIComponent(targetKey)}`, {
|
|
84963
|
+
method: "PUT",
|
|
84964
|
+
body: { baseline_target_id: baselineTargetId }
|
|
84965
|
+
});
|
|
84966
|
+
}
|
|
84967
|
+
async clearBaseline(benchmarkId, targetKey) {
|
|
84968
|
+
await this.request(`/${encodeURIComponent(benchmarkId)}/baselines/${encodeURIComponent(targetKey)}`, { method: "DELETE" });
|
|
84969
|
+
}
|
|
84970
|
+
}
|
|
84971
|
+
|
|
83197
84972
|
// src/cli/task-status.ts
|
|
83198
84973
|
var TERMINAL_TASK_STATUSES = new Set([
|
|
83199
84974
|
"success",
|
|
@@ -83208,6 +84983,65 @@ function exitCodeForTaskStatus(status) {
|
|
|
83208
84983
|
return status === SUCCESS_STATUS ? 0 : 1;
|
|
83209
84984
|
}
|
|
83210
84985
|
var SIGINT_EXIT_CODE = 130;
|
|
84986
|
+
var EX_TEMPFAIL_EXIT_CODE = 75;
|
|
84987
|
+
function isRetryablePollError(error2) {
|
|
84988
|
+
if (!(error2 instanceof ApiError))
|
|
84989
|
+
return false;
|
|
84990
|
+
if (error2 instanceof BenchmarkApiError && error2.code === "request_timeout")
|
|
84991
|
+
return false;
|
|
84992
|
+
const status = error2.status;
|
|
84993
|
+
return status === undefined && (isUnsentRequestError(error2) || isLostResponseError(error2) || error2 instanceof BenchmarkApiError && error2.code === "network_error") || status === 408 || status === 425 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status !== undefined && status >= 520 && status <= 524;
|
|
84994
|
+
}
|
|
84995
|
+
function retryAfterHeaderOf(error2) {
|
|
84996
|
+
if (!(error2 instanceof ApiError))
|
|
84997
|
+
return null;
|
|
84998
|
+
const retryAfter = error2.retryAfter;
|
|
84999
|
+
return typeof retryAfter === "string" && retryAfter.trim() ? retryAfter : null;
|
|
85000
|
+
}
|
|
85001
|
+
function logWaitRetryEvent(record5) {
|
|
85002
|
+
const line = JSON.stringify(record5);
|
|
85003
|
+
if (record5.event === "retry_exhausted")
|
|
85004
|
+
console.error(line);
|
|
85005
|
+
else
|
|
85006
|
+
console.warn(line);
|
|
85007
|
+
}
|
|
85008
|
+
function pollFailureClass(error2) {
|
|
85009
|
+
if (isUnsentRequestError(error2))
|
|
85010
|
+
return "unsent";
|
|
85011
|
+
if (error2 instanceof ApiError && error2.status !== undefined) {
|
|
85012
|
+
return "retryable_status";
|
|
85013
|
+
}
|
|
85014
|
+
return "ambiguous";
|
|
85015
|
+
}
|
|
85016
|
+
function pollRetryTelemetry(event, op, error2, attempt2, startedAtMs, budgetMs, delayMs, nowMs = Date.now()) {
|
|
85017
|
+
return {
|
|
85018
|
+
event,
|
|
85019
|
+
layer: "http",
|
|
85020
|
+
op,
|
|
85021
|
+
attempt: attempt2,
|
|
85022
|
+
class: pollFailureClass(error2),
|
|
85023
|
+
status: error2 instanceof ApiError && error2.status !== undefined ? error2.status : null,
|
|
85024
|
+
elapsed_ms: nowMs - startedAtMs,
|
|
85025
|
+
budget_ms: budgetMs,
|
|
85026
|
+
error: error2 instanceof Error ? error2.name : String(error2),
|
|
85027
|
+
...delayMs !== undefined ? { delay_ms: delayMs } : {}
|
|
85028
|
+
};
|
|
85029
|
+
}
|
|
85030
|
+
function waitUnreachableNotice(what, resumeHint, budgetMs) {
|
|
85031
|
+
return `${what} is unreachable; the ${Math.round(budgetMs / 1000)}s downtime retry budget is exhausted. ` + `It may still be running; ${resumeHint}.`;
|
|
85032
|
+
}
|
|
85033
|
+
function exitCodeForWaitOutcome(outcome) {
|
|
85034
|
+
switch (outcome.kind) {
|
|
85035
|
+
case "terminal":
|
|
85036
|
+
return exitCodeForTaskStatus(outcome.task.status);
|
|
85037
|
+
case "interrupted":
|
|
85038
|
+
return SIGINT_EXIT_CODE;
|
|
85039
|
+
case "unreachable":
|
|
85040
|
+
return EX_TEMPFAIL_EXIT_CODE;
|
|
85041
|
+
case "timeout":
|
|
85042
|
+
return 1;
|
|
85043
|
+
}
|
|
85044
|
+
}
|
|
83211
85045
|
async function waitForTask(taskId, options) {
|
|
83212
85046
|
const stop = new AbortController;
|
|
83213
85047
|
const onExternalAbort = () => stop.abort();
|
|
@@ -83217,23 +85051,92 @@ async function waitForTask(taskId, options) {
|
|
|
83217
85051
|
const deadlineTimer = options.timeoutMs === undefined ? null : setTimeout(() => stop.abort(), options.timeoutMs);
|
|
83218
85052
|
let latest = null;
|
|
83219
85053
|
let lastStatus = null;
|
|
83220
|
-
const
|
|
85054
|
+
const retry = httpRetryEnabled();
|
|
85055
|
+
const downtimeBudgetMs = options.downtimeBudgetMs ?? DEFAULT_RETRY_BUDGET_MS;
|
|
85056
|
+
let downtime;
|
|
85057
|
+
let downtimeTimer;
|
|
85058
|
+
let downtimeExpired = false;
|
|
85059
|
+
let lastPollError;
|
|
85060
|
+
let failedPolls = 0;
|
|
85061
|
+
const now2 = options.now ?? Date.now;
|
|
85062
|
+
const pause2 = options.sleep ?? sleep3;
|
|
85063
|
+
let outageStartedAt = now2();
|
|
85064
|
+
const unreachable = () => {
|
|
85065
|
+
logWaitRetryEvent(pollRetryTelemetry("retry_exhausted", "task_status_wait", lastPollError, failedPolls, outageStartedAt, downtimeBudgetMs, undefined, now2()));
|
|
85066
|
+
return { kind: "unreachable", task: latest, budgetMs: downtimeBudgetMs };
|
|
85067
|
+
};
|
|
85068
|
+
const givenUp = () => {
|
|
85069
|
+
if (options.signal?.aborted)
|
|
85070
|
+
return { kind: "interrupted", task: latest };
|
|
85071
|
+
if (downtimeExpired || downtime && downtime.remainingMs(now2()) === 0)
|
|
85072
|
+
return unreachable();
|
|
85073
|
+
return { kind: "timeout", task: latest };
|
|
85074
|
+
};
|
|
83221
85075
|
try {
|
|
83222
85076
|
while (true) {
|
|
83223
85077
|
if (stop.signal.aborted)
|
|
83224
85078
|
return givenUp();
|
|
83225
|
-
|
|
83226
|
-
|
|
83227
|
-
|
|
85079
|
+
if (downtime && downtime.remainingMs(now2()) === 0)
|
|
85080
|
+
return unreachable();
|
|
85081
|
+
const pollStartedAt = now2();
|
|
85082
|
+
const pollStop = retry ? new AbortController : stop;
|
|
85083
|
+
const onStop = () => pollStop.abort(stop.signal.reason);
|
|
85084
|
+
const requestTimer = retry ? setTimeout(() => {
|
|
85085
|
+
pollStop.abort(new DOMException(`Task status request timed out after ${HTTP_RETRY_DEADLINE_MS} ms`, "TimeoutError"));
|
|
85086
|
+
}, HTTP_RETRY_DEADLINE_MS) : undefined;
|
|
85087
|
+
if (retry)
|
|
85088
|
+
stop.signal.addEventListener("abort", onStop, { once: true });
|
|
85089
|
+
let polled;
|
|
85090
|
+
try {
|
|
85091
|
+
polled = await raceAbort(masApi.getTask(taskId, { signal: pollStop.signal, singleAttempt: retry }), pollStop.signal);
|
|
85092
|
+
} finally {
|
|
85093
|
+
if (requestTimer !== undefined)
|
|
85094
|
+
clearTimeout(requestTimer);
|
|
85095
|
+
if (retry)
|
|
85096
|
+
stop.signal.removeEventListener("abort", onStop);
|
|
85097
|
+
}
|
|
85098
|
+
if (polled.kind === "aborted") {
|
|
85099
|
+
if (stop.signal.aborted)
|
|
85100
|
+
return givenUp();
|
|
85101
|
+
throw pollStop.signal.reason;
|
|
85102
|
+
}
|
|
83228
85103
|
if (polled.kind === "failed") {
|
|
83229
85104
|
if (stop.signal.aborted)
|
|
83230
85105
|
return givenUp();
|
|
83231
|
-
if (!
|
|
85106
|
+
if (!retry) {
|
|
85107
|
+
if (!options.retryRead?.(polled.error))
|
|
85108
|
+
throw polled.error;
|
|
85109
|
+
if (await pause2(options.intervalMs, stop.signal))
|
|
85110
|
+
return givenUp();
|
|
85111
|
+
continue;
|
|
85112
|
+
}
|
|
85113
|
+
const retryable = options.retryRead ? options.retryRead(polled.error) : isRetryablePollError(polled.error);
|
|
85114
|
+
if (!retryable)
|
|
83232
85115
|
throw polled.error;
|
|
83233
|
-
|
|
85116
|
+
lastPollError = polled.error;
|
|
85117
|
+
if (!downtime) {
|
|
85118
|
+
outageStartedAt = pollStartedAt;
|
|
85119
|
+
downtime = createRetryBudget(downtimeBudgetMs, outageStartedAt);
|
|
85120
|
+
downtimeTimer = setTimeout(() => {
|
|
85121
|
+
downtimeExpired = true;
|
|
85122
|
+
stop.abort();
|
|
85123
|
+
}, downtime.remainingMs(now2()));
|
|
85124
|
+
}
|
|
85125
|
+
failedPolls += 1;
|
|
85126
|
+
const delayMs = retryDelayMs(failedPolls - 1, downtime, retryAfterHeaderOf(polled.error), { maxMs: Math.min(STREAM_RETRY_MAX_DELAY_MS, Math.max(0, downtime.remainingMs(now2()) - 1)), rng: options.jitter }, now2());
|
|
85127
|
+
if (delayMs === null) {
|
|
85128
|
+
return unreachable();
|
|
85129
|
+
}
|
|
85130
|
+
logWaitRetryEvent(pollRetryTelemetry("retry_attempt", "task_status_wait", polled.error, failedPolls, outageStartedAt, downtimeBudgetMs, delayMs, now2()));
|
|
85131
|
+
if (await pause2(delayMs, stop.signal))
|
|
83234
85132
|
return givenUp();
|
|
83235
85133
|
continue;
|
|
83236
85134
|
}
|
|
85135
|
+
downtime = undefined;
|
|
85136
|
+
if (downtimeTimer !== undefined)
|
|
85137
|
+
clearTimeout(downtimeTimer);
|
|
85138
|
+
downtimeTimer = undefined;
|
|
85139
|
+
failedPolls = 0;
|
|
83237
85140
|
latest = polled.value;
|
|
83238
85141
|
if (latest.status !== lastStatus) {
|
|
83239
85142
|
lastStatus = latest.status;
|
|
@@ -83242,13 +85145,15 @@ async function waitForTask(taskId, options) {
|
|
|
83242
85145
|
if (isTerminalTaskStatus(latest.status)) {
|
|
83243
85146
|
return { kind: "terminal", task: latest };
|
|
83244
85147
|
}
|
|
83245
|
-
const cutShort = await
|
|
85148
|
+
const cutShort = await pause2(options.intervalMs, stop.signal);
|
|
83246
85149
|
if (cutShort)
|
|
83247
85150
|
return givenUp();
|
|
83248
85151
|
}
|
|
83249
85152
|
} finally {
|
|
83250
85153
|
if (deadlineTimer !== null)
|
|
83251
85154
|
clearTimeout(deadlineTimer);
|
|
85155
|
+
if (downtimeTimer !== undefined)
|
|
85156
|
+
clearTimeout(downtimeTimer);
|
|
83252
85157
|
options.signal?.removeEventListener("abort", onExternalAbort);
|
|
83253
85158
|
}
|
|
83254
85159
|
}
|
|
@@ -83267,7 +85172,7 @@ function raceAbort(work, signal) {
|
|
|
83267
85172
|
});
|
|
83268
85173
|
});
|
|
83269
85174
|
}
|
|
83270
|
-
function
|
|
85175
|
+
function sleep3(ms2, signal) {
|
|
83271
85176
|
if (signal.aborted)
|
|
83272
85177
|
return Promise.resolve(true);
|
|
83273
85178
|
return new Promise((resolve) => {
|
|
@@ -83353,7 +85258,7 @@ async function pollCapture(agentId, limitMs, deps, say, interrupted) {
|
|
|
83353
85258
|
say(import_picocolors49.default.dim(" Waiting for the latest edit to be saved as a version..."));
|
|
83354
85259
|
announced = true;
|
|
83355
85260
|
}
|
|
83356
|
-
if (await
|
|
85261
|
+
if (await sleep3(Math.min(deps.pollIntervalMs ?? 2000, remaining), interrupted))
|
|
83357
85262
|
return false;
|
|
83358
85263
|
}
|
|
83359
85264
|
}
|
|
@@ -84044,7 +85949,10 @@ var DECISION_MODELS = [
|
|
|
84044
85949
|
"pplx-decider-v1.2",
|
|
84045
85950
|
"d1-ultra",
|
|
84046
85951
|
"jev-1.14",
|
|
84047
|
-
"classify-1"
|
|
85952
|
+
"classify-1",
|
|
85953
|
+
"microsoft-decision-1",
|
|
85954
|
+
"clef-omni",
|
|
85955
|
+
"d1"
|
|
84048
85956
|
];
|
|
84049
85957
|
var DEFAULT_DECISION_MODEL = "clef-flash";
|
|
84050
85958
|
var DECISION_LIMITS = {
|
|
@@ -85650,7 +87558,7 @@ async function runOrchestrationPush(cwd2, args) {
|
|
|
85650
87558
|
const sp = spinner();
|
|
85651
87559
|
sp.start("Updating orchestration graph…");
|
|
85652
87560
|
try {
|
|
85653
|
-
const
|
|
87561
|
+
const updateInput = {
|
|
85654
87562
|
...metaField("name"),
|
|
85655
87563
|
...metaField("description"),
|
|
85656
87564
|
...metaField("icon"),
|
|
@@ -85662,7 +87570,17 @@ async function runOrchestrationPush(cwd2, args) {
|
|
|
85662
87570
|
...sendWebhooks ? { replace_webhook_triggers: true } : {},
|
|
85663
87571
|
...sendDecisions ? { decisions: decisionsPayload } : {},
|
|
85664
87572
|
...lock?.revision != null ? { base_revision: lock.revision } : {}
|
|
85665
|
-
}
|
|
87573
|
+
};
|
|
87574
|
+
let updated;
|
|
87575
|
+
const pushStartedAt = Date.now();
|
|
87576
|
+
try {
|
|
87577
|
+
updated = await api.updateOrchestration(link2.orchestration_id, updateInput, httpRetryEnabled() ? { keepalive: false } : undefined);
|
|
87578
|
+
} catch (err) {
|
|
87579
|
+
if (!httpRetryEnabled() || !isLostResponseError(err))
|
|
87580
|
+
throw err;
|
|
87581
|
+
sp.stop("Connection lost after the graph was sent — reading the cloud graph back…");
|
|
87582
|
+
updated = await reconcileOrchestrationUpdate(link2.orchestration_id, updateInput, sendDecisions ? localDecisions : null, slugForAgent, pushStartedAt);
|
|
87583
|
+
}
|
|
85666
87584
|
sp.stop(`Graph updated. New cloud revision ${updated.revision}.`);
|
|
85667
87585
|
const settledDecisions = sendDecisions ? cloudDecisions(updated, slugForAgent) : decisionPlan?.kind === "reconcile" ? cloudDecisionList : null;
|
|
85668
87586
|
writeOrchSyncState(cwd2, {
|
|
@@ -85698,6 +87616,192 @@ async function runOrchestrationPush(cwd2, args) {
|
|
|
85698
87616
|
process.exitCode = 1;
|
|
85699
87617
|
}
|
|
85700
87618
|
}
|
|
87619
|
+
async function reconcileOrchestrationUpdate(orchestrationId, sent, sentDecisions, slugForAgent, startedAt) {
|
|
87620
|
+
logRetryEvent2({
|
|
87621
|
+
event: "retry_deferred",
|
|
87622
|
+
layer: "http",
|
|
87623
|
+
op: "update_orchestration",
|
|
87624
|
+
attempt: 1,
|
|
87625
|
+
class: "ambiguous",
|
|
87626
|
+
status: null,
|
|
87627
|
+
elapsed_ms: Date.now() - startedAt,
|
|
87628
|
+
budget_ms: HTTP_RETRY_DEADLINE_MS,
|
|
87629
|
+
error: "NetworkApiError"
|
|
87630
|
+
});
|
|
87631
|
+
let reread;
|
|
87632
|
+
try {
|
|
87633
|
+
reread = await api.getOrchestration(orchestrationId, {
|
|
87634
|
+
signal: pushRecoverySignal(startedAt)
|
|
87635
|
+
});
|
|
87636
|
+
} catch (err) {
|
|
87637
|
+
throw new ApiError(`The graph update may have succeeded, but the CLI could not read the orchestration back to confirm it (${err.message}). Run brainbase orchestration pull before pushing again.`);
|
|
87638
|
+
}
|
|
87639
|
+
const divergence = [
|
|
87640
|
+
...graphDivergence(reread, sent),
|
|
87641
|
+
...sentDecisions ? decisionsDivergence(sentDecisions, reread, slugForAgent) : []
|
|
87642
|
+
];
|
|
87643
|
+
if (divergence.length) {
|
|
87644
|
+
throw new ApiError(`Cannot confirm the push: the cloud graph at revision ${reread.revision} does not match what was sent (${divergence.join("; ")}).`, 409);
|
|
87645
|
+
}
|
|
87646
|
+
return reread;
|
|
87647
|
+
}
|
|
87648
|
+
function graphDivergence(cloud, sent) {
|
|
87649
|
+
const diffs = [];
|
|
87650
|
+
if (sent.members) {
|
|
87651
|
+
const sentMembers = new Set(sent.members);
|
|
87652
|
+
const cloudMembers = new Set(cloud.members.map((m3) => m3.agent_id));
|
|
87653
|
+
diffs.push(...setDivergence("members", sentMembers, cloudMembers));
|
|
87654
|
+
}
|
|
87655
|
+
if (sent.edges) {
|
|
87656
|
+
const sentEdges = sent.edges.map((e2) => JSON.stringify(canonical({
|
|
87657
|
+
from: e2.from_agent_id,
|
|
87658
|
+
to: e2.to_agent_id,
|
|
87659
|
+
description: e2.description ?? "",
|
|
87660
|
+
payload_schema: e2.payload_schema ?? {},
|
|
87661
|
+
settings: e2.settings ?? {}
|
|
87662
|
+
}))).sort();
|
|
87663
|
+
const cloudEdges = cloud.edges.map((e2) => JSON.stringify(canonical({
|
|
87664
|
+
from: e2.from_agent_id,
|
|
87665
|
+
to: e2.to_agent_id,
|
|
87666
|
+
description: e2.description ?? "",
|
|
87667
|
+
payload_schema: e2.payload_schema ?? {},
|
|
87668
|
+
settings: e2.settings ?? {}
|
|
87669
|
+
}))).sort();
|
|
87670
|
+
if (JSON.stringify(sentEdges) !== JSON.stringify(cloudEdges)) {
|
|
87671
|
+
diffs.push(`edges: sent ${sent.edges.length}, cloud has ${cloud.edges.length} or contents differ`);
|
|
87672
|
+
}
|
|
87673
|
+
}
|
|
87674
|
+
if (sent.triggers) {
|
|
87675
|
+
const kindOf = (t) => t.trigger_type ?? t.type ?? "schedule";
|
|
87676
|
+
const sentSchedules = sent.triggers.filter((t) => kindOf(t) === "schedule");
|
|
87677
|
+
const sentWebhooks = sent.triggers.filter((t) => kindOf(t) === "webhook");
|
|
87678
|
+
const cloudSchedules = (cloud.triggers ?? []).filter((t) => t.trigger_type === "schedule");
|
|
87679
|
+
const cloudWebhooks = (cloud.triggers ?? []).filter((t) => t.trigger_type === "webhook");
|
|
87680
|
+
const scheduleDiffs = triggerDivergence("schedule triggers", sentSchedules.map((t) => ({
|
|
87681
|
+
node_id: t.node_id,
|
|
87682
|
+
is_active: t.is_active ?? false,
|
|
87683
|
+
cron: cronOf(t.config) ?? t.cron_expression,
|
|
87684
|
+
props: t.config?.configured_props ?? {},
|
|
87685
|
+
targets: (t.edges ?? []).map(triggerEdgeProjection)
|
|
87686
|
+
})), cloudSchedules.map((t) => ({
|
|
87687
|
+
node_id: t.node_id,
|
|
87688
|
+
is_active: t.is_active ?? false,
|
|
87689
|
+
cron: cronOf(t.config),
|
|
87690
|
+
props: t.config?.configured_props ?? {},
|
|
87691
|
+
targets: t.edges.map(triggerEdgeProjection)
|
|
87692
|
+
})));
|
|
87693
|
+
diffs.push(...scheduleDiffs);
|
|
87694
|
+
if (sent.replace_webhook_triggers) {
|
|
87695
|
+
diffs.push(...triggerDivergence("webhook triggers", sentWebhooks.map((t) => ({
|
|
87696
|
+
node_id: t.node_id,
|
|
87697
|
+
is_active: t.is_active ?? false,
|
|
87698
|
+
public: typeof t.public === "boolean" ? t.public : webhookPublicFromConfig(t.config),
|
|
87699
|
+
targets: (t.edges ?? []).map(triggerEdgeProjection)
|
|
87700
|
+
})), cloudWebhooks.map((t) => ({
|
|
87701
|
+
node_id: t.node_id,
|
|
87702
|
+
is_active: t.is_active ?? false,
|
|
87703
|
+
public: typeof t.public === "boolean" ? t.public : webhookPublicFromConfig(t.config),
|
|
87704
|
+
targets: t.edges.map(triggerEdgeProjection)
|
|
87705
|
+
}))));
|
|
87706
|
+
}
|
|
87707
|
+
}
|
|
87708
|
+
for (const field2 of [
|
|
87709
|
+
"name",
|
|
87710
|
+
"description",
|
|
87711
|
+
"icon",
|
|
87712
|
+
"icon_color",
|
|
87713
|
+
"credit_limit"
|
|
87714
|
+
]) {
|
|
87715
|
+
let expected = sent[field2];
|
|
87716
|
+
if (field2 === "name" && typeof expected === "string")
|
|
87717
|
+
expected = expected.trim();
|
|
87718
|
+
if ((field2 === "icon" || field2 === "icon_color") && expected === "")
|
|
87719
|
+
expected = null;
|
|
87720
|
+
if (expected !== undefined && cloud[field2] !== expected) {
|
|
87721
|
+
diffs.push(`${field2}: cloud has "${String(cloud[field2])}", sent "${String(expected)}"`);
|
|
87722
|
+
}
|
|
87723
|
+
}
|
|
87724
|
+
return diffs;
|
|
87725
|
+
}
|
|
87726
|
+
function triggerDivergence(label2, sent, cloud) {
|
|
87727
|
+
const diffs = setDivergence(label2, new Set(sent.map((t) => canonicalUuid2(t.node_id))), new Set(cloud.map((t) => canonicalUuid2(t.node_id))));
|
|
87728
|
+
const cloudById = new Map(cloud.map((t) => [canonicalUuid2(t.node_id), t]));
|
|
87729
|
+
for (const t of sent) {
|
|
87730
|
+
const other = cloudById.get(canonicalUuid2(t.node_id));
|
|
87731
|
+
if (!other)
|
|
87732
|
+
continue;
|
|
87733
|
+
if (t.cron !== undefined && t.cron !== other.cron) {
|
|
87734
|
+
diffs.push(`${label2}: ${t.node_id} cron "${other.cron ?? ""}", sent "${t.cron}"`);
|
|
87735
|
+
}
|
|
87736
|
+
if (JSON.stringify(canonical(t.props)) !== JSON.stringify(canonical(other.props))) {
|
|
87737
|
+
diffs.push(`${label2}: ${t.node_id} configured properties differ`);
|
|
87738
|
+
}
|
|
87739
|
+
if (t.is_active !== other.is_active) {
|
|
87740
|
+
diffs.push(`${label2}: ${t.node_id} is_active ${other.is_active}, sent ${t.is_active}`);
|
|
87741
|
+
}
|
|
87742
|
+
if (t.public !== undefined && (t.public ?? null) !== (other.public ?? null)) {
|
|
87743
|
+
diffs.push(`${label2}: ${t.node_id} public ${other.public ?? null}, sent ${t.public ?? null}`);
|
|
87744
|
+
}
|
|
87745
|
+
if (JSON.stringify([...t.targets].sort()) !== JSON.stringify([...other.targets].sort())) {
|
|
87746
|
+
diffs.push(`${label2}: ${t.node_id} targets differ`);
|
|
87747
|
+
}
|
|
87748
|
+
}
|
|
87749
|
+
return diffs;
|
|
87750
|
+
}
|
|
87751
|
+
function decisionsDivergence(sent, cloud, slugForAgent) {
|
|
87752
|
+
const cloudDecisionsList = cloudDecisions(cloud, slugForAgent);
|
|
87753
|
+
if (!cloudDecisionsList) {
|
|
87754
|
+
return [
|
|
87755
|
+
`decisions: the re-read shows none, but this push sent ${sent.length}`
|
|
87756
|
+
];
|
|
87757
|
+
}
|
|
87758
|
+
const diffs = setDivergence("decisions", new Set(sent.map((d3) => d3.id)), new Set(cloudDecisionsList.map((d3) => d3.id)));
|
|
87759
|
+
if ([...deadDecisionLinks(cloud).values()].some((count) => count > 0)) {
|
|
87760
|
+
diffs.push("decisions: cloud still has dangling inputs or routes");
|
|
87761
|
+
}
|
|
87762
|
+
const cloudById = new Map(cloudDecisionsList.map((d3) => [d3.id, d3]));
|
|
87763
|
+
for (const d3 of sent) {
|
|
87764
|
+
const other = cloudById.get(d3.id);
|
|
87765
|
+
if (!other)
|
|
87766
|
+
continue;
|
|
87767
|
+
if (decisionHash(d3) !== decisionHash(other)) {
|
|
87768
|
+
diffs.push(`decisions: ${d3.name} (${d3.id}) contents differ`);
|
|
87769
|
+
}
|
|
87770
|
+
}
|
|
87771
|
+
return diffs;
|
|
87772
|
+
}
|
|
87773
|
+
function triggerEdgeProjection(edge) {
|
|
87774
|
+
return JSON.stringify(canonical({
|
|
87775
|
+
to_agent_id: edge.to_agent_id,
|
|
87776
|
+
description: edge.description ?? "",
|
|
87777
|
+
payload_schema: edge.payload_schema ?? {}
|
|
87778
|
+
}));
|
|
87779
|
+
}
|
|
87780
|
+
function cronOf(config2) {
|
|
87781
|
+
const cron = config2?.cron_expression;
|
|
87782
|
+
return typeof cron === "string" ? cron : undefined;
|
|
87783
|
+
}
|
|
87784
|
+
function canonical(value3) {
|
|
87785
|
+
if (Array.isArray(value3))
|
|
87786
|
+
return value3.map(canonical);
|
|
87787
|
+
if (value3 && typeof value3 === "object") {
|
|
87788
|
+
return Object.fromEntries(Object.entries(value3).sort(([a3], [b4]) => a3 < b4 ? -1 : a3 > b4 ? 1 : 0).map(([key2, v3]) => [key2, canonical(v3)]));
|
|
87789
|
+
}
|
|
87790
|
+
return value3;
|
|
87791
|
+
}
|
|
87792
|
+
function setDivergence(label2, sent, cloud) {
|
|
87793
|
+
const missing = [...sent].filter((v3) => !cloud.has(v3));
|
|
87794
|
+
const unexpected = [...cloud].filter((v3) => !sent.has(v3));
|
|
87795
|
+
if (missing.length === 0 && unexpected.length === 0)
|
|
87796
|
+
return [];
|
|
87797
|
+
const parts = [];
|
|
87798
|
+
if (missing.length)
|
|
87799
|
+
parts.push(`missing ${missing.join(", ")}`);
|
|
87800
|
+
if (unexpected.length) {
|
|
87801
|
+
parts.push(`unexpected ${unexpected.join(", ")}`);
|
|
87802
|
+
}
|
|
87803
|
+
return [`${label2}: ${parts.join(", ")}`];
|
|
87804
|
+
}
|
|
85701
87805
|
function handleApiError6(err) {
|
|
85702
87806
|
if (err instanceof ApiError) {
|
|
85703
87807
|
if (err.status === 401) {
|
|
@@ -88680,7 +90784,7 @@ var import_picocolors74 = __toESM(require_picocolors(), 1);
|
|
|
88680
90784
|
|
|
88681
90785
|
// src/cli/task-create.ts
|
|
88682
90786
|
var import_picocolors70 = __toESM(require_picocolors(), 1);
|
|
88683
|
-
import { randomUUID as
|
|
90787
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
88684
90788
|
|
|
88685
90789
|
// src/cli/task-update.ts
|
|
88686
90790
|
var import_picocolors69 = __toESM(require_picocolors(), 1);
|
|
@@ -88891,7 +90995,7 @@ function isPublishScopeError(error2) {
|
|
|
88891
90995
|
return /PAT lacks required scope:\s*publish/i.test(`${message3} ${body}`);
|
|
88892
90996
|
}
|
|
88893
90997
|
async function runTaskCreate(cwd2, options, dependencies = {}) {
|
|
88894
|
-
const idempotencyKey = (dependencies.randomUUID ??
|
|
90998
|
+
const idempotencyKey = (dependencies.randomUUID ?? randomUUID5)();
|
|
88895
90999
|
const message3 = requiredValue(options.message, "--message");
|
|
88896
91000
|
const interfaceId = optionalValue(options.interfaceId, "--interface");
|
|
88897
91001
|
const agentId = await resolveAgentId2(cwd2, options.agentId, interfaceId, dependencies.readManifest ?? readManifest);
|
|
@@ -88963,8 +91067,11 @@ async function waitForCreatedTask(created, options, dependencies, version2) {
|
|
|
88963
91067
|
onStatus: options.json ? undefined : (task) => console.log(`Status: ${task.status}`)
|
|
88964
91068
|
});
|
|
88965
91069
|
const status = outcome.task?.status ?? created.status;
|
|
88966
|
-
const code = outcome
|
|
91070
|
+
const code = exitCodeForWaitOutcome(outcome);
|
|
88967
91071
|
setExitCode(code);
|
|
91072
|
+
if (outcome.kind === "unreachable") {
|
|
91073
|
+
console.error(waitUnreachableNotice(`Task ${created.id}'s status`, `check with \`brainbase task get ${created.id}\``, outcome.budgetMs));
|
|
91074
|
+
}
|
|
88968
91075
|
if (options.json) {
|
|
88969
91076
|
console.log(JSON.stringify({
|
|
88970
91077
|
task_id: created.id,
|
|
@@ -88977,6 +91084,9 @@ async function waitForCreatedTask(created, options, dependencies, version2) {
|
|
|
88977
91084
|
}));
|
|
88978
91085
|
return;
|
|
88979
91086
|
}
|
|
91087
|
+
if (outcome.kind === "unreachable") {
|
|
91088
|
+
return;
|
|
91089
|
+
}
|
|
88980
91090
|
if (outcome.kind === "timeout") {
|
|
88981
91091
|
console.log(import_picocolors70.default.yellow(`Timed out after ${options.timeoutSeconds}s with the task still ${status}.`));
|
|
88982
91092
|
return;
|
|
@@ -89206,7 +91316,46 @@ function failureSummary(task) {
|
|
|
89206
91316
|
var import_picocolors73 = __toESM(require_picocolors(), 1);
|
|
89207
91317
|
|
|
89208
91318
|
// src/core/task-events.ts
|
|
89209
|
-
var
|
|
91319
|
+
var LEGACY_RETRY_BUDGET_MS = 60000;
|
|
91320
|
+
var DEFAULT_RETRY_BUDGET_MS2 = httpRetryEnabled() ? DEFAULT_RETRY_BUDGET_MS : LEGACY_RETRY_BUDGET_MS;
|
|
91321
|
+
var RECONNECT_FIRST_DELAY_MS = 5000;
|
|
91322
|
+
var RECONNECT_MAX_DELAY_MS = 30000;
|
|
91323
|
+
|
|
91324
|
+
class FollowBudgetExhaustedError extends ApiError {
|
|
91325
|
+
lastEventId;
|
|
91326
|
+
budgetMs;
|
|
91327
|
+
constructor(message3, lastEventId, budgetMs) {
|
|
91328
|
+
super(message3);
|
|
91329
|
+
this.lastEventId = lastEventId;
|
|
91330
|
+
this.budgetMs = budgetMs;
|
|
91331
|
+
this.name = "FollowBudgetExhaustedError";
|
|
91332
|
+
}
|
|
91333
|
+
}
|
|
91334
|
+
var RETRYABLE_HTTP_STATUSES = new Set([
|
|
91335
|
+
408,
|
|
91336
|
+
425,
|
|
91337
|
+
429,
|
|
91338
|
+
500,
|
|
91339
|
+
502,
|
|
91340
|
+
503,
|
|
91341
|
+
504,
|
|
91342
|
+
520,
|
|
91343
|
+
521,
|
|
91344
|
+
522,
|
|
91345
|
+
523,
|
|
91346
|
+
524
|
|
91347
|
+
]);
|
|
91348
|
+
var EXHAUSTED = Symbol("exhausted");
|
|
91349
|
+
function isRetryableHttpStatus(status) {
|
|
91350
|
+
return status !== undefined && RETRYABLE_HTTP_STATUSES.has(status);
|
|
91351
|
+
}
|
|
91352
|
+
function logRetryEvent3(record5) {
|
|
91353
|
+
const line2 = JSON.stringify(record5);
|
|
91354
|
+
if (record5.event === "retry_exhausted")
|
|
91355
|
+
console.error(line2);
|
|
91356
|
+
else
|
|
91357
|
+
console.warn(line2);
|
|
91358
|
+
}
|
|
89210
91359
|
function followTaskEvents(options) {
|
|
89211
91360
|
const {
|
|
89212
91361
|
url: url2,
|
|
@@ -89215,22 +91364,124 @@ function followTaskEvents(options) {
|
|
|
89215
91364
|
onOpen,
|
|
89216
91365
|
onReconnect,
|
|
89217
91366
|
signal,
|
|
89218
|
-
|
|
91367
|
+
reconnectFirstDelayMs = RECONNECT_FIRST_DELAY_MS,
|
|
91368
|
+
reconnectMaxDelayMs = RECONNECT_MAX_DELAY_MS,
|
|
91369
|
+
now: now2 = Date.now
|
|
89219
91370
|
} = options;
|
|
91371
|
+
const retry = httpRetryEnabled();
|
|
91372
|
+
const retryBudgetMs = options.retryBudgetMs ?? (retry ? DEFAULT_RETRY_BUDGET_MS : LEGACY_RETRY_BUDGET_MS);
|
|
91373
|
+
const idleWatchdogMs = options.idleWatchdogMs ?? IDLE_WATCHDOG_MS;
|
|
89220
91374
|
return new Promise((resolve, reject2) => {
|
|
89221
91375
|
if (signal?.aborted) {
|
|
89222
91376
|
resolve();
|
|
89223
91377
|
return;
|
|
89224
91378
|
}
|
|
89225
91379
|
let source;
|
|
91380
|
+
let sourceStop;
|
|
89226
91381
|
let settled = false;
|
|
89227
91382
|
let closeRequested = false;
|
|
89228
91383
|
let downTimer;
|
|
91384
|
+
let downSince;
|
|
91385
|
+
let watchdogTimer;
|
|
91386
|
+
let reopenTimer;
|
|
89229
91387
|
let lastReason = "connection lost";
|
|
91388
|
+
let lastEventId = null;
|
|
91389
|
+
let lastByteAt = now2();
|
|
91390
|
+
let rejectedBearer;
|
|
91391
|
+
let attemptBearer;
|
|
91392
|
+
let retryAfterMs;
|
|
91393
|
+
let reconnectAttempt = 0;
|
|
91394
|
+
let unauthorizedRetries = 0;
|
|
91395
|
+
const startedAt = now2();
|
|
91396
|
+
let lastFailureClass = "ambiguous";
|
|
91397
|
+
let lastFailureStatus = null;
|
|
89230
91398
|
const clearBudget = () => {
|
|
89231
91399
|
if (downTimer !== undefined)
|
|
89232
91400
|
clearTimeout(downTimer);
|
|
89233
91401
|
downTimer = undefined;
|
|
91402
|
+
downSince = undefined;
|
|
91403
|
+
};
|
|
91404
|
+
const armBudget = (since = now2()) => {
|
|
91405
|
+
if (downSince !== undefined) {
|
|
91406
|
+
if (now2() - downSince >= retryBudgetMs) {
|
|
91407
|
+
emitExhausted();
|
|
91408
|
+
finish(exhaustedError());
|
|
91409
|
+
return false;
|
|
91410
|
+
}
|
|
91411
|
+
return true;
|
|
91412
|
+
}
|
|
91413
|
+
downSince = since;
|
|
91414
|
+
const remaining = Math.max(0, retryBudgetMs - (now2() - since));
|
|
91415
|
+
downTimer = setTimeout(() => {
|
|
91416
|
+
downTimer = undefined;
|
|
91417
|
+
emitExhausted();
|
|
91418
|
+
finish(exhaustedError());
|
|
91419
|
+
}, remaining);
|
|
91420
|
+
return true;
|
|
91421
|
+
};
|
|
91422
|
+
const emitExhausted = (status = lastFailureStatus) => {
|
|
91423
|
+
if (!retry)
|
|
91424
|
+
return;
|
|
91425
|
+
logRetryEvent3({
|
|
91426
|
+
event: "retry_exhausted",
|
|
91427
|
+
layer: "http",
|
|
91428
|
+
op: "task_events_follow",
|
|
91429
|
+
attempt: Math.max(1, reconnectAttempt),
|
|
91430
|
+
class: lastFailureClass,
|
|
91431
|
+
status,
|
|
91432
|
+
elapsed_ms: now2() - startedAt,
|
|
91433
|
+
budget_ms: retryBudgetMs,
|
|
91434
|
+
error: "FollowBudgetExhaustedError"
|
|
91435
|
+
});
|
|
91436
|
+
};
|
|
91437
|
+
const exhaustedError = () => {
|
|
91438
|
+
const message3 = `The log stream has been unreachable for ${Math.round(retryBudgetMs / 1000)}s (${lastReason}). ` + `Giving up — the task may still be running; re-run to reattach.` + (retry ? ` Last event id: ${lastEventId ?? "(none)"}.` : "");
|
|
91439
|
+
if (!retry)
|
|
91440
|
+
return new ApiError(message3);
|
|
91441
|
+
return new FollowBudgetExhaustedError(message3, lastEventId, retryBudgetMs);
|
|
91442
|
+
};
|
|
91443
|
+
const clearWatchdog = () => {
|
|
91444
|
+
if (watchdogTimer !== undefined)
|
|
91445
|
+
clearTimeout(watchdogTimer);
|
|
91446
|
+
watchdogTimer = undefined;
|
|
91447
|
+
};
|
|
91448
|
+
const armWatchdog = () => {
|
|
91449
|
+
if (!retry)
|
|
91450
|
+
return;
|
|
91451
|
+
clearWatchdog();
|
|
91452
|
+
watchdogTimer = setTimeout(() => {
|
|
91453
|
+
watchdogTimer = undefined;
|
|
91454
|
+
if (settled || closeRequested || source === undefined)
|
|
91455
|
+
return;
|
|
91456
|
+
reconnectAttempt += 1;
|
|
91457
|
+
lastReason = `no data for ${Math.round(idleWatchdogMs / 1000)}s (idle watchdog)`;
|
|
91458
|
+
onReconnect?.(lastReason);
|
|
91459
|
+
const delay2 = jitterDelayMs();
|
|
91460
|
+
emitRetry("ambiguous", null, "IdleWatchdog", delay2);
|
|
91461
|
+
if (!armBudget(lastByteAt))
|
|
91462
|
+
return;
|
|
91463
|
+
closeSource();
|
|
91464
|
+
scheduleReopen(delay2);
|
|
91465
|
+
}, idleWatchdogMs);
|
|
91466
|
+
};
|
|
91467
|
+
const noteByte = () => {
|
|
91468
|
+
lastByteAt = now2();
|
|
91469
|
+
armWatchdog();
|
|
91470
|
+
};
|
|
91471
|
+
const clearReopen = () => {
|
|
91472
|
+
if (reopenTimer !== undefined)
|
|
91473
|
+
clearTimeout(reopenTimer);
|
|
91474
|
+
reopenTimer = undefined;
|
|
91475
|
+
};
|
|
91476
|
+
const closeSource = () => {
|
|
91477
|
+
clearWatchdog();
|
|
91478
|
+
const current = source;
|
|
91479
|
+
source = undefined;
|
|
91480
|
+
sourceStop?.abort();
|
|
91481
|
+
sourceStop = undefined;
|
|
91482
|
+
current?.close();
|
|
91483
|
+
if (retry && current)
|
|
91484
|
+
queueMicrotask(() => current.close());
|
|
89234
91485
|
};
|
|
89235
91486
|
const finish = (error2) => {
|
|
89236
91487
|
if (settled)
|
|
@@ -89238,60 +91489,198 @@ function followTaskEvents(options) {
|
|
|
89238
91489
|
settled = true;
|
|
89239
91490
|
closeRequested = true;
|
|
89240
91491
|
clearBudget();
|
|
89241
|
-
|
|
91492
|
+
clearWatchdog();
|
|
91493
|
+
clearReopen();
|
|
91494
|
+
closeSource();
|
|
89242
91495
|
if (error2)
|
|
89243
91496
|
reject2(error2);
|
|
89244
91497
|
else
|
|
89245
91498
|
resolve();
|
|
89246
91499
|
};
|
|
89247
|
-
|
|
89248
|
-
|
|
89249
|
-
|
|
89250
|
-
|
|
89251
|
-
|
|
89252
|
-
|
|
89253
|
-
|
|
89254
|
-
|
|
91500
|
+
const emitRetry = (failureClass, status, errorName, delayMs) => {
|
|
91501
|
+
if (!retry)
|
|
91502
|
+
return;
|
|
91503
|
+
lastFailureClass = failureClass;
|
|
91504
|
+
lastFailureStatus = status;
|
|
91505
|
+
logRetryEvent3({
|
|
91506
|
+
event: "retry_attempt",
|
|
91507
|
+
layer: "http",
|
|
91508
|
+
op: "task_events_follow",
|
|
91509
|
+
attempt: reconnectAttempt,
|
|
91510
|
+
class: failureClass,
|
|
91511
|
+
status,
|
|
91512
|
+
elapsed_ms: now2() - startedAt,
|
|
91513
|
+
budget_ms: retryBudgetMs,
|
|
91514
|
+
error: errorName,
|
|
91515
|
+
delay_ms: delayMs
|
|
91516
|
+
});
|
|
91517
|
+
};
|
|
91518
|
+
const jitterDelayMs = () => {
|
|
91519
|
+
const cap = Math.min(reconnectMaxDelayMs, reconnectFirstDelayMs * 2 ** Math.max(0, reconnectAttempt - 1), downSince === undefined ? retryBudgetMs : Math.max(0, retryBudgetMs - (now2() - downSince)));
|
|
91520
|
+
return Math.floor(Math.random() * cap);
|
|
91521
|
+
};
|
|
91522
|
+
const reconnectDelayMs = () => {
|
|
91523
|
+
if (retryAfterMs === undefined)
|
|
91524
|
+
return jitterDelayMs();
|
|
91525
|
+
const remaining = retryBudgetMs - (now2() - (downSince ?? now2()));
|
|
91526
|
+
if (retryAfterMs > remaining)
|
|
91527
|
+
return EXHAUSTED;
|
|
91528
|
+
return retryAfterMs;
|
|
91529
|
+
};
|
|
91530
|
+
const scheduleReopen = (delayMs) => {
|
|
91531
|
+
if (settled || closeRequested)
|
|
91532
|
+
return;
|
|
91533
|
+
if (!armBudget())
|
|
91534
|
+
return;
|
|
91535
|
+
reopenTimer = setTimeout(() => {
|
|
91536
|
+
reopenTimer = undefined;
|
|
91537
|
+
if (!settled && !closeRequested)
|
|
91538
|
+
openSource();
|
|
91539
|
+
}, Math.max(0, delayMs));
|
|
91540
|
+
};
|
|
91541
|
+
const openSource = () => {
|
|
91542
|
+
if (settled || closeRequested)
|
|
91543
|
+
return;
|
|
91544
|
+
const stop = retry ? new AbortController : undefined;
|
|
91545
|
+
sourceStop = stop;
|
|
91546
|
+
const es2 = new EventSource(url2, {
|
|
91547
|
+
fetch: async (input, init) => {
|
|
91548
|
+
retryAfterMs = undefined;
|
|
91549
|
+
let bearer;
|
|
91550
|
+
try {
|
|
91551
|
+
bearer = await resolveBearer(retry ? rejectedBearer : undefined);
|
|
91552
|
+
rejectedBearer = undefined;
|
|
91553
|
+
attemptBearer = bearer;
|
|
91554
|
+
} catch (error2) {
|
|
91555
|
+
if (error2 instanceof StreamHostMovedError) {
|
|
91556
|
+
finish(error2);
|
|
91557
|
+
throw error2;
|
|
91558
|
+
}
|
|
89255
91559
|
throw error2;
|
|
89256
91560
|
}
|
|
89257
|
-
|
|
91561
|
+
const headers = new Headers(init?.headers);
|
|
91562
|
+
headers.set("Authorization", `Bearer ${bearer}`);
|
|
91563
|
+
if (retry && lastEventId && !headers.has("Last-Event-ID")) {
|
|
91564
|
+
headers.set("Last-Event-ID", lastEventId);
|
|
91565
|
+
}
|
|
91566
|
+
const response = await fetch(input, { ...init, headers, signal: stop?.signal ?? init?.signal });
|
|
91567
|
+
if (!retry || response.status !== 200) {
|
|
91568
|
+
if (response.status !== 200) {
|
|
91569
|
+
retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after")) ?? undefined;
|
|
91570
|
+
if (retry)
|
|
91571
|
+
await response.body?.cancel().catch(() => {});
|
|
91572
|
+
}
|
|
91573
|
+
return response;
|
|
91574
|
+
}
|
|
91575
|
+
if (!response.body)
|
|
91576
|
+
return response;
|
|
91577
|
+
const observed = response.body.pipeThrough(new TransformStream({
|
|
91578
|
+
transform(chunk2, controller) {
|
|
91579
|
+
if (source === es2)
|
|
91580
|
+
noteByte();
|
|
91581
|
+
controller.enqueue(chunk2);
|
|
91582
|
+
}
|
|
91583
|
+
}));
|
|
91584
|
+
return new Response(observed, {
|
|
91585
|
+
status: response.status,
|
|
91586
|
+
statusText: response.statusText,
|
|
91587
|
+
headers: response.headers
|
|
91588
|
+
});
|
|
89258
91589
|
}
|
|
89259
|
-
|
|
89260
|
-
|
|
89261
|
-
|
|
89262
|
-
|
|
89263
|
-
|
|
89264
|
-
|
|
89265
|
-
|
|
89266
|
-
|
|
89267
|
-
|
|
89268
|
-
|
|
89269
|
-
|
|
89270
|
-
|
|
89271
|
-
|
|
89272
|
-
|
|
89273
|
-
|
|
89274
|
-
|
|
89275
|
-
|
|
91590
|
+
});
|
|
91591
|
+
source = es2;
|
|
91592
|
+
if (retry) {
|
|
91593
|
+
lastByteAt = now2();
|
|
91594
|
+
armWatchdog();
|
|
91595
|
+
}
|
|
91596
|
+
if (closeRequested)
|
|
91597
|
+
es2.close();
|
|
91598
|
+
const dispatch = es2.dispatchEvent.bind(es2);
|
|
91599
|
+
es2.dispatchEvent = (event) => {
|
|
91600
|
+
if (source !== es2)
|
|
91601
|
+
return dispatch(event);
|
|
91602
|
+
if (event instanceof MessageEvent) {
|
|
91603
|
+
if (retry) {
|
|
91604
|
+
clearBudget();
|
|
91605
|
+
reconnectAttempt = 0;
|
|
91606
|
+
}
|
|
91607
|
+
if (event.lastEventId)
|
|
91608
|
+
lastEventId = event.lastEventId;
|
|
91609
|
+
onFrame({
|
|
91610
|
+
id: event.lastEventId,
|
|
91611
|
+
type: event.type,
|
|
91612
|
+
data: parseFrameData(event.data)
|
|
91613
|
+
});
|
|
91614
|
+
}
|
|
91615
|
+
return dispatch(event);
|
|
91616
|
+
};
|
|
91617
|
+
es2.addEventListener("open", () => {
|
|
91618
|
+
if (source !== es2)
|
|
91619
|
+
return;
|
|
91620
|
+
unauthorizedRetries = 0;
|
|
91621
|
+
if (retry) {
|
|
91622
|
+
lastByteAt = now2();
|
|
91623
|
+
armWatchdog();
|
|
91624
|
+
} else {
|
|
91625
|
+
clearBudget();
|
|
91626
|
+
}
|
|
91627
|
+
onOpen?.();
|
|
91628
|
+
});
|
|
91629
|
+
es2.addEventListener("error", (event) => {
|
|
91630
|
+
if (source !== es2)
|
|
91631
|
+
return;
|
|
91632
|
+
const { message: message3, code } = event;
|
|
91633
|
+
const status = typeof code === "number" ? code : undefined;
|
|
91634
|
+
if (es2.readyState === es2.CLOSED) {
|
|
91635
|
+
clearWatchdog();
|
|
91636
|
+
if (retry && status === 401 && unauthorizedRetries < 1) {
|
|
91637
|
+
unauthorizedRetries += 1;
|
|
91638
|
+
rejectedBearer = attemptBearer;
|
|
91639
|
+
lastReason = message3?.trim() || "unauthorized";
|
|
91640
|
+
onReconnect?.(lastReason);
|
|
91641
|
+
closeSource();
|
|
91642
|
+
scheduleReopen(0);
|
|
91643
|
+
return;
|
|
91644
|
+
}
|
|
91645
|
+
if (retry && isRetryableHttpStatus(status)) {
|
|
91646
|
+
reconnectAttempt += 1;
|
|
91647
|
+
lastFailureClass = "retryable_status";
|
|
91648
|
+
lastFailureStatus = status ?? null;
|
|
91649
|
+
lastReason = message3?.trim() || `HTTP ${status}`;
|
|
91650
|
+
onReconnect?.(lastReason);
|
|
91651
|
+
if (!armBudget())
|
|
91652
|
+
return;
|
|
91653
|
+
const delay2 = reconnectDelayMs();
|
|
91654
|
+
if (delay2 === EXHAUSTED) {
|
|
91655
|
+
emitExhausted(status ?? null);
|
|
91656
|
+
finish(exhaustedError());
|
|
91657
|
+
return;
|
|
91658
|
+
}
|
|
91659
|
+
emitRetry("retryable_status", status ?? null, "ErrorEvent", delay2);
|
|
91660
|
+
closeSource();
|
|
91661
|
+
scheduleReopen(delay2);
|
|
91662
|
+
return;
|
|
91663
|
+
}
|
|
91664
|
+
finish(streamError(message3, code));
|
|
91665
|
+
return;
|
|
91666
|
+
}
|
|
91667
|
+
clearWatchdog();
|
|
91668
|
+
lastReason = message3?.trim() || "connection lost";
|
|
91669
|
+
onReconnect?.(lastReason);
|
|
91670
|
+
lastFailureClass = "ambiguous";
|
|
91671
|
+
lastFailureStatus = null;
|
|
91672
|
+
if (!armBudget())
|
|
91673
|
+
return;
|
|
91674
|
+
if (retry) {
|
|
91675
|
+
reconnectAttempt += 1;
|
|
91676
|
+
const delay2 = jitterDelayMs();
|
|
91677
|
+
emitRetry("ambiguous", null, "ErrorEvent", delay2);
|
|
91678
|
+
closeSource();
|
|
91679
|
+
scheduleReopen(delay2);
|
|
91680
|
+
}
|
|
91681
|
+
});
|
|
89276
91682
|
};
|
|
89277
|
-
|
|
89278
|
-
clearBudget();
|
|
89279
|
-
onOpen?.();
|
|
89280
|
-
});
|
|
89281
|
-
source.addEventListener("error", (event) => {
|
|
89282
|
-
const { message: message3, code } = event;
|
|
89283
|
-
if (source.readyState === source.CLOSED) {
|
|
89284
|
-
finish(streamError(message3, code));
|
|
89285
|
-
return;
|
|
89286
|
-
}
|
|
89287
|
-
lastReason = message3?.trim() || "connection lost";
|
|
89288
|
-
onReconnect?.(lastReason);
|
|
89289
|
-
if (downTimer === undefined) {
|
|
89290
|
-
downTimer = setTimeout(() => {
|
|
89291
|
-
finish(new ApiError(`The log stream has been unreachable for ${Math.round(retryBudgetMs / 1000)}s (${lastReason}). Giving up — the task may still be running; re-run to reattach.`));
|
|
89292
|
-
}, retryBudgetMs);
|
|
89293
|
-
}
|
|
89294
|
-
});
|
|
91683
|
+
openSource();
|
|
89295
91684
|
signal?.addEventListener("abort", () => finish(), { once: true });
|
|
89296
91685
|
});
|
|
89297
91686
|
}
|
|
@@ -89465,12 +91854,55 @@ function isPermanentRefusal(error2) {
|
|
|
89465
91854
|
// src/cli/task-logs.ts
|
|
89466
91855
|
var MAX_PAGE = 1000;
|
|
89467
91856
|
var PAGE_SIZE2 = 500;
|
|
91857
|
+
var STREAM_EXHAUSTED_EXIT_CODE = 75;
|
|
91858
|
+
var PAGE_RETRY_BASE_DELAY_MS = 250;
|
|
91859
|
+
var PAGE_RETRY_MAX_DELAY_MS = 30000;
|
|
91860
|
+
|
|
91861
|
+
class TranscriptPageBudgetExhaustedError extends ApiError {
|
|
91862
|
+
collected;
|
|
91863
|
+
budgetMs;
|
|
91864
|
+
constructor(message3, collected, budgetMs) {
|
|
91865
|
+
super(message3);
|
|
91866
|
+
this.collected = collected;
|
|
91867
|
+
this.budgetMs = budgetMs;
|
|
91868
|
+
this.name = "TranscriptPageBudgetExhaustedError";
|
|
91869
|
+
}
|
|
91870
|
+
}
|
|
91871
|
+
|
|
91872
|
+
class TranscriptPageTimeoutError extends ApiError {
|
|
91873
|
+
collected;
|
|
91874
|
+
constructor(collected) {
|
|
91875
|
+
super("The task transcript request timed out. Collected events are shown; re-run to read the rest.");
|
|
91876
|
+
this.collected = collected;
|
|
91877
|
+
this.name = "TranscriptPageTimeoutError";
|
|
91878
|
+
}
|
|
91879
|
+
}
|
|
89468
91880
|
async function runTaskLogs(taskId, options = {}, dependencies = {}) {
|
|
89469
91881
|
if (options.follow) {
|
|
89470
91882
|
await followTranscript(taskId, options, dependencies);
|
|
89471
91883
|
return;
|
|
89472
91884
|
}
|
|
89473
|
-
|
|
91885
|
+
let events;
|
|
91886
|
+
try {
|
|
91887
|
+
events = await readTranscript(taskId, options.limit, {
|
|
91888
|
+
budgetMs: dependencies.pageBudgetMs,
|
|
91889
|
+
requestTimeoutMs: dependencies.pageRequestTimeoutMs,
|
|
91890
|
+
now: dependencies.now
|
|
91891
|
+
});
|
|
91892
|
+
} catch (error2) {
|
|
91893
|
+
if (error2 instanceof TranscriptPageBudgetExhaustedError || error2 instanceof TranscriptPageTimeoutError) {
|
|
91894
|
+
const setExitCode = dependencies.setExitCode ?? ((code) => process.exitCode = code);
|
|
91895
|
+
events = error2.collected;
|
|
91896
|
+
renderTranscript(events, options);
|
|
91897
|
+
console.error(import_picocolors73.default.dim(error2.message));
|
|
91898
|
+
setExitCode(error2 instanceof TranscriptPageBudgetExhaustedError ? STREAM_EXHAUSTED_EXIT_CODE : 1);
|
|
91899
|
+
return;
|
|
91900
|
+
}
|
|
91901
|
+
throw error2;
|
|
91902
|
+
}
|
|
91903
|
+
renderTranscript(events, options);
|
|
91904
|
+
}
|
|
91905
|
+
function renderTranscript(events, options) {
|
|
89474
91906
|
if (options.json) {
|
|
89475
91907
|
console.log(JSON.stringify({ items: events }, null, 2));
|
|
89476
91908
|
return;
|
|
@@ -89518,6 +91950,9 @@ async function followTranscript(taskId, options, dependencies) {
|
|
|
89518
91950
|
resolveBearer,
|
|
89519
91951
|
signal: controller.signal,
|
|
89520
91952
|
retryBudgetMs: dependencies.retryBudgetMs,
|
|
91953
|
+
idleWatchdogMs: dependencies.idleWatchdogMs,
|
|
91954
|
+
reconnectFirstDelayMs: dependencies.reconnectFirstDelayMs,
|
|
91955
|
+
now: dependencies.now,
|
|
89521
91956
|
onFrame: (frame) => {
|
|
89522
91957
|
settle.noteFrame();
|
|
89523
91958
|
const event = frame.data;
|
|
@@ -89546,6 +91981,13 @@ async function followTranscript(taskId, options, dependencies) {
|
|
|
89546
91981
|
});
|
|
89547
91982
|
if (interruptCode !== undefined)
|
|
89548
91983
|
setExitCode(interruptCode);
|
|
91984
|
+
} catch (error2) {
|
|
91985
|
+
if (error2 instanceof FollowBudgetExhaustedError) {
|
|
91986
|
+
console.error(import_picocolors73.default.red(error2.message));
|
|
91987
|
+
setExitCode(STREAM_EXHAUSTED_EXIT_CODE);
|
|
91988
|
+
return;
|
|
91989
|
+
}
|
|
91990
|
+
throw error2;
|
|
89549
91991
|
} finally {
|
|
89550
91992
|
settle.stop();
|
|
89551
91993
|
process.off("SIGINT", onSigint);
|
|
@@ -89576,15 +92018,79 @@ function isSupersededChunk(event, rolledUp) {
|
|
|
89576
92018
|
function laneOf(event) {
|
|
89577
92019
|
return `${event.subagent_id ?? ""}\x00${event.turn_id ?? ""}`;
|
|
89578
92020
|
}
|
|
89579
|
-
async function readTranscript(taskId, limit) {
|
|
92021
|
+
async function readTranscript(taskId, limit, options = {}) {
|
|
92022
|
+
const retry = httpRetryEnabled();
|
|
92023
|
+
const budgetMs = options.budgetMs ?? DEFAULT_RETRY_BUDGET_MS;
|
|
92024
|
+
const now2 = options.now ?? Date.now;
|
|
92025
|
+
let startedAt = now2();
|
|
89580
92026
|
const collected = [];
|
|
89581
92027
|
let after2;
|
|
92028
|
+
let attempt2 = 0;
|
|
92029
|
+
let deadline = startedAt + budgetMs;
|
|
89582
92030
|
while (true) {
|
|
89583
92031
|
const remaining = limit === undefined ? PAGE_SIZE2 : limit - collected.length;
|
|
89584
92032
|
if (remaining <= 0)
|
|
89585
92033
|
break;
|
|
89586
92034
|
const pageSize = Math.min(MAX_PAGE, remaining === PAGE_SIZE2 ? PAGE_SIZE2 : remaining);
|
|
89587
|
-
|
|
92035
|
+
let page;
|
|
92036
|
+
if (retry && now2() >= deadline) {
|
|
92037
|
+
throw pageBudgetExhausted(collected, budgetMs, now2() - startedAt, attempt2, "ambiguous");
|
|
92038
|
+
}
|
|
92039
|
+
attempt2 += 1;
|
|
92040
|
+
const stop = new AbortController;
|
|
92041
|
+
const request2 = masApi.listTaskEvents(taskId, {
|
|
92042
|
+
limit: pageSize,
|
|
92043
|
+
after: after2,
|
|
92044
|
+
...retry ? { signal: stop.signal } : {}
|
|
92045
|
+
});
|
|
92046
|
+
request2.catch(() => {});
|
|
92047
|
+
let pageTimer;
|
|
92048
|
+
try {
|
|
92049
|
+
page = !retry ? await request2 : await Promise.race([
|
|
92050
|
+
request2,
|
|
92051
|
+
new Promise((_4, reject2) => {
|
|
92052
|
+
const left = deadline - now2();
|
|
92053
|
+
if (left <= 0) {
|
|
92054
|
+
const error2 = new PageDeadlineError;
|
|
92055
|
+
reject2(error2);
|
|
92056
|
+
stop.abort(error2);
|
|
92057
|
+
return;
|
|
92058
|
+
}
|
|
92059
|
+
pageTimer = setTimeout(() => {
|
|
92060
|
+
const error2 = left <= (options.requestTimeoutMs ?? HTTP_RETRY_DEADLINE_MS) ? new PageDeadlineError : new TranscriptPageTimeoutError(collected);
|
|
92061
|
+
reject2(error2);
|
|
92062
|
+
stop.abort(error2);
|
|
92063
|
+
}, Math.min(left, options.requestTimeoutMs ?? HTTP_RETRY_DEADLINE_MS));
|
|
92064
|
+
})
|
|
92065
|
+
]);
|
|
92066
|
+
} catch (error2) {
|
|
92067
|
+
if (pageTimer !== undefined)
|
|
92068
|
+
clearTimeout(pageTimer);
|
|
92069
|
+
if (error2 instanceof PageDeadlineError) {
|
|
92070
|
+
throw pageBudgetExhausted(collected, budgetMs, now2() - startedAt, attempt2, "ambiguous");
|
|
92071
|
+
}
|
|
92072
|
+
const failureClass = classifyPageFailure(error2);
|
|
92073
|
+
if (!retry || failureClass === undefined)
|
|
92074
|
+
throw error2;
|
|
92075
|
+
if (now2() >= deadline) {
|
|
92076
|
+
throw pageBudgetExhausted(collected, budgetMs, now2() - startedAt, attempt2, failureClass, error2 instanceof ApiError ? error2.status ?? null : null);
|
|
92077
|
+
}
|
|
92078
|
+
const cap = Math.min(PAGE_RETRY_MAX_DELAY_MS, PAGE_RETRY_BASE_DELAY_MS * 2 ** (attempt2 - 1), Math.max(0, deadline - now2()));
|
|
92079
|
+
const delay2 = Math.floor(Math.random() * cap);
|
|
92080
|
+
const retryAfter = error2 instanceof ApiError ? parseRetryAfterMs(error2.retryAfter) : undefined;
|
|
92081
|
+
if (retryAfter != null && retryAfter > deadline - now2()) {
|
|
92082
|
+
throw pageBudgetExhausted(collected, budgetMs, now2() - startedAt, attempt2, failureClass, error2 instanceof ApiError ? error2.status : undefined);
|
|
92083
|
+
}
|
|
92084
|
+
const wait = retryAfter ?? delay2;
|
|
92085
|
+
logPageRetry(attempt2, failureClass, error2, budgetMs, now2() - startedAt, wait);
|
|
92086
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
92087
|
+
continue;
|
|
92088
|
+
}
|
|
92089
|
+
if (pageTimer !== undefined)
|
|
92090
|
+
clearTimeout(pageTimer);
|
|
92091
|
+
attempt2 = 0;
|
|
92092
|
+
startedAt = now2();
|
|
92093
|
+
deadline = startedAt + budgetMs;
|
|
89588
92094
|
if (page.length < pageSize) {
|
|
89589
92095
|
collected.push(...page);
|
|
89590
92096
|
break;
|
|
@@ -89599,6 +92105,51 @@ async function readTranscript(taskId, limit) {
|
|
|
89599
92105
|
}
|
|
89600
92106
|
return collected;
|
|
89601
92107
|
}
|
|
92108
|
+
function classifyPageFailure(error2) {
|
|
92109
|
+
if (isUnsentRequestError(error2))
|
|
92110
|
+
return "unsent";
|
|
92111
|
+
if (isLostResponseError(error2))
|
|
92112
|
+
return "ambiguous";
|
|
92113
|
+
if (error2 instanceof ApiError && isRetryableHttpStatus(error2.status)) {
|
|
92114
|
+
return "retryable_status";
|
|
92115
|
+
}
|
|
92116
|
+
return;
|
|
92117
|
+
}
|
|
92118
|
+
|
|
92119
|
+
class PageDeadlineError extends Error {
|
|
92120
|
+
constructor() {
|
|
92121
|
+
super("page budget elapsed");
|
|
92122
|
+
this.name = "PageDeadlineError";
|
|
92123
|
+
}
|
|
92124
|
+
}
|
|
92125
|
+
function pageBudgetExhausted(collected, budgetMs, elapsedMs, attempt2, failureClass, status = null) {
|
|
92126
|
+
logRetryEvent3({
|
|
92127
|
+
event: "retry_exhausted",
|
|
92128
|
+
layer: "http",
|
|
92129
|
+
op: "task_logs_page",
|
|
92130
|
+
attempt: attempt2,
|
|
92131
|
+
class: failureClass,
|
|
92132
|
+
status,
|
|
92133
|
+
elapsed_ms: elapsedMs,
|
|
92134
|
+
budget_ms: budgetMs,
|
|
92135
|
+
error: "TranscriptPageBudgetExhaustedError"
|
|
92136
|
+
});
|
|
92137
|
+
return new TranscriptPageBudgetExhaustedError(`The task transcript has been unreadable for ${Math.round(budgetMs / 1000)}s. ` + `${collected.length} event${collected.length === 1 ? "" : "s"} collected before the failure ` + "are shown; the task may still be running — re-run to read the rest.", collected, budgetMs);
|
|
92138
|
+
}
|
|
92139
|
+
function logPageRetry(attempt2, failureClass, error2, budgetMs, elapsedMs, delayCapMs) {
|
|
92140
|
+
logRetryEvent3({
|
|
92141
|
+
event: "retry_attempt",
|
|
92142
|
+
layer: "http",
|
|
92143
|
+
op: "task_logs_page",
|
|
92144
|
+
attempt: attempt2,
|
|
92145
|
+
class: failureClass,
|
|
92146
|
+
status: error2 instanceof ApiError ? error2.status ?? null : null,
|
|
92147
|
+
elapsed_ms: elapsedMs,
|
|
92148
|
+
budget_ms: budgetMs,
|
|
92149
|
+
error: error2 instanceof Error ? error2.name : String(error2),
|
|
92150
|
+
delay_ms: delayCapMs
|
|
92151
|
+
});
|
|
92152
|
+
}
|
|
89602
92153
|
function eventTimestamp(event) {
|
|
89603
92154
|
return event.ended_at || event.ts;
|
|
89604
92155
|
}
|
|
@@ -89687,7 +92238,7 @@ function oneLine2(value3) {
|
|
|
89687
92238
|
}
|
|
89688
92239
|
|
|
89689
92240
|
// src/cli/task-input.ts
|
|
89690
|
-
import { randomUUID as
|
|
92241
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
89691
92242
|
function errorCode(error2) {
|
|
89692
92243
|
if (!(error2 instanceof ApiError) || !error2.body || typeof error2.body !== "object")
|
|
89693
92244
|
return;
|
|
@@ -89751,7 +92302,7 @@ function onInterrupt(handler) {
|
|
|
89751
92302
|
async function runTaskSend(taskId, message3, options = {}) {
|
|
89752
92303
|
if (!message3.trim())
|
|
89753
92304
|
throw new Error("The follow-up message must not be blank.");
|
|
89754
|
-
const inputId = options.inputId?.trim().toLowerCase() ??
|
|
92305
|
+
const inputId = options.inputId?.trim().toLowerCase() ?? randomUUID6();
|
|
89755
92306
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inputId)) {
|
|
89756
92307
|
throw new Error("--input-id must be a UUID.");
|
|
89757
92308
|
}
|
|
@@ -90689,6 +93240,11 @@ async function build(ctx) {
|
|
|
90689
93240
|
signal: stop.signal,
|
|
90690
93241
|
retryRead: isTransientReadError
|
|
90691
93242
|
});
|
|
93243
|
+
if (settled.kind === "unreachable") {
|
|
93244
|
+
console.error(waitUnreachableNotice(`Conversation ${taskId}'s status`, `check with \`brainbase task get ${taskId}\``, settled.budgetMs));
|
|
93245
|
+
finish({ version_id: null, build_id: null, status: "task_unsettled", task_status: settled.task?.status ?? null }, EX_TEMPFAIL_EXIT_CODE);
|
|
93246
|
+
return;
|
|
93247
|
+
}
|
|
90692
93248
|
if (settled.kind !== "terminal") {
|
|
90693
93249
|
const interrupted = settled.kind === "interrupted";
|
|
90694
93250
|
const status2 = settled.task?.status ?? null;
|
|
@@ -95254,494 +97810,6 @@ var import_picocolors85 = __toESM(require_picocolors(), 1);
|
|
|
95254
97810
|
import fs93 from "node:fs";
|
|
95255
97811
|
import path104 from "node:path";
|
|
95256
97812
|
|
|
95257
|
-
// src/core/benchmark-api.ts
|
|
95258
|
-
var DEFAULT_BENCHMARK_REQUEST_TIMEOUT_MS = 30000;
|
|
95259
|
-
var BENCHMARK_UPLOAD_PROCESSING_ALLOWANCE_MS = 30000;
|
|
95260
|
-
var BENCHMARK_UPLOAD_MIN_BYTES_PER_SECOND = 256 * 1024;
|
|
95261
|
-
|
|
95262
|
-
class BenchmarkClientUsageError extends Error {
|
|
95263
|
-
code = "invalid_arguments";
|
|
95264
|
-
retryable = false;
|
|
95265
|
-
constructor(message3) {
|
|
95266
|
-
super(message3);
|
|
95267
|
-
this.name = "BenchmarkClientUsageError";
|
|
95268
|
-
}
|
|
95269
|
-
}
|
|
95270
|
-
|
|
95271
|
-
class BenchmarkApiError extends ApiError {
|
|
95272
|
-
code;
|
|
95273
|
-
requestId;
|
|
95274
|
-
retryable;
|
|
95275
|
-
constructor(message3, status, body, code, requestId, retryable) {
|
|
95276
|
-
super(message3, status, body);
|
|
95277
|
-
this.code = code;
|
|
95278
|
-
this.requestId = requestId;
|
|
95279
|
-
this.name = "BenchmarkApiError";
|
|
95280
|
-
this.retryable = retryable ?? (status === undefined || status === 408 || status === 425 || status === 429 || status !== undefined && status >= 500);
|
|
95281
|
-
}
|
|
95282
|
-
}
|
|
95283
|
-
function safeCode(body) {
|
|
95284
|
-
if (!body || typeof body !== "object")
|
|
95285
|
-
return;
|
|
95286
|
-
const object4 = body;
|
|
95287
|
-
const nested = object4.error && typeof object4.error === "object" ? object4.error.code : object4.detail && typeof object4.detail === "object" ? object4.detail.code : undefined;
|
|
95288
|
-
const code = object4.code ?? nested;
|
|
95289
|
-
return typeof code === "string" && /^[a-z][a-z0-9_]{0,99}$/.test(code) ? code : undefined;
|
|
95290
|
-
}
|
|
95291
|
-
function queryString(query) {
|
|
95292
|
-
if (!query)
|
|
95293
|
-
return "";
|
|
95294
|
-
const params = new URLSearchParams;
|
|
95295
|
-
for (const key2 of Object.keys(query).sort()) {
|
|
95296
|
-
const value3 = query[key2];
|
|
95297
|
-
if (value3 === undefined)
|
|
95298
|
-
continue;
|
|
95299
|
-
for (const item of Array.isArray(value3) ? value3 : [value3]) {
|
|
95300
|
-
params.append(key2, String(item));
|
|
95301
|
-
}
|
|
95302
|
-
}
|
|
95303
|
-
const encoded = params.toString();
|
|
95304
|
-
return encoded ? `?${encoded}` : "";
|
|
95305
|
-
}
|
|
95306
|
-
function filenameFromHeaders(headers) {
|
|
95307
|
-
const disposition = headers.get("Content-Disposition");
|
|
95308
|
-
if (!disposition)
|
|
95309
|
-
return null;
|
|
95310
|
-
const utf8 = disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1];
|
|
95311
|
-
if (utf8) {
|
|
95312
|
-
try {
|
|
95313
|
-
return decodeURIComponent(utf8);
|
|
95314
|
-
} catch {
|
|
95315
|
-
return utf8;
|
|
95316
|
-
}
|
|
95317
|
-
}
|
|
95318
|
-
return disposition.match(/filename="?([^";]+)"?/i)?.[1] ?? null;
|
|
95319
|
-
}
|
|
95320
|
-
function uploadBlob(upload) {
|
|
95321
|
-
if (upload.data instanceof Blob)
|
|
95322
|
-
return upload.data;
|
|
95323
|
-
return new Blob([upload.data], {
|
|
95324
|
-
type: upload.contentType ?? "application/octet-stream"
|
|
95325
|
-
});
|
|
95326
|
-
}
|
|
95327
|
-
function requestAbortContext(callerSignal, timeoutMs3) {
|
|
95328
|
-
const controller = new AbortController;
|
|
95329
|
-
let source;
|
|
95330
|
-
const abortFromCaller = () => {
|
|
95331
|
-
if (source !== undefined)
|
|
95332
|
-
return;
|
|
95333
|
-
source = "caller";
|
|
95334
|
-
controller.abort(callerSignal?.reason);
|
|
95335
|
-
};
|
|
95336
|
-
if (callerSignal?.aborted) {
|
|
95337
|
-
abortFromCaller();
|
|
95338
|
-
} else {
|
|
95339
|
-
callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
95340
|
-
}
|
|
95341
|
-
const timer = setTimeout(() => {
|
|
95342
|
-
if (source !== undefined)
|
|
95343
|
-
return;
|
|
95344
|
-
source = "timeout";
|
|
95345
|
-
controller.abort(new DOMException(`Benchmark request timed out after ${timeoutMs3} ms`, "TimeoutError"));
|
|
95346
|
-
}, timeoutMs3);
|
|
95347
|
-
timer.unref?.();
|
|
95348
|
-
return {
|
|
95349
|
-
signal: controller.signal,
|
|
95350
|
-
source: () => source,
|
|
95351
|
-
dispose: () => {
|
|
95352
|
-
clearTimeout(timer);
|
|
95353
|
-
callerSignal?.removeEventListener("abort", abortFromCaller);
|
|
95354
|
-
}
|
|
95355
|
-
};
|
|
95356
|
-
}
|
|
95357
|
-
function callerAbortError(signal) {
|
|
95358
|
-
return signal?.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
95359
|
-
}
|
|
95360
|
-
function idempotencyHeaders(key2) {
|
|
95361
|
-
const headers = new Headers;
|
|
95362
|
-
if (key2 === undefined)
|
|
95363
|
-
return headers;
|
|
95364
|
-
if (key2.length < 8 || key2.length > 200 || /[^\x21-\x7e]/.test(key2)) {
|
|
95365
|
-
throw new BenchmarkClientUsageError("Idempotency keys must be 8-200 visible ASCII characters");
|
|
95366
|
-
}
|
|
95367
|
-
headers.set("Idempotency-Key", key2);
|
|
95368
|
-
return headers;
|
|
95369
|
-
}
|
|
95370
|
-
|
|
95371
|
-
class BenchmarkApiClient {
|
|
95372
|
-
agentId;
|
|
95373
|
-
fixedBaseUrl;
|
|
95374
|
-
fetchImpl;
|
|
95375
|
-
credentialResolver;
|
|
95376
|
-
authRetry;
|
|
95377
|
-
requestTimeoutMs;
|
|
95378
|
-
uploadTimeoutMs;
|
|
95379
|
-
constructor(options) {
|
|
95380
|
-
this.agentId = options.agentId;
|
|
95381
|
-
this.fixedBaseUrl = options.baseUrl?.replace(/\/+$/, "");
|
|
95382
|
-
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
95383
|
-
this.credentialResolver = options.resolveCredential ?? (async () => await resolveCredential());
|
|
95384
|
-
this.authRetry = options.authRetry ?? sendWithAuthRetry;
|
|
95385
|
-
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_BENCHMARK_REQUEST_TIMEOUT_MS;
|
|
95386
|
-
if (!Number.isSafeInteger(this.requestTimeoutMs) || this.requestTimeoutMs < 1) {
|
|
95387
|
-
throw new BenchmarkClientUsageError("Benchmark request timeout must be a positive safe integer");
|
|
95388
|
-
}
|
|
95389
|
-
this.uploadTimeoutMs = options.uploadTimeoutMs;
|
|
95390
|
-
if (this.uploadTimeoutMs !== undefined && (!Number.isSafeInteger(this.uploadTimeoutMs) || this.uploadTimeoutMs < 1)) {
|
|
95391
|
-
throw new BenchmarkClientUsageError("Benchmark upload timeout must be a positive safe integer");
|
|
95392
|
-
}
|
|
95393
|
-
}
|
|
95394
|
-
root(session) {
|
|
95395
|
-
const base2 = this.fixedBaseUrl ?? masControlPlaneBaseUrl(session);
|
|
95396
|
-
return `${base2}/v2/agents/${encodeURIComponent(this.agentId)}/benchmarks`;
|
|
95397
|
-
}
|
|
95398
|
-
async request(pathname, options = {}) {
|
|
95399
|
-
const credential = await this.credentialResolver();
|
|
95400
|
-
let session = credential.session;
|
|
95401
|
-
const headers = new Headers(options.headers);
|
|
95402
|
-
const idempotency = idempotencyHeaders(options.idempotencyKey);
|
|
95403
|
-
idempotency.forEach((value3, key2) => headers.set(key2, value3));
|
|
95404
|
-
if (!headers.has("Accept")) {
|
|
95405
|
-
headers.set("Accept", options.mode === "binary" ? "application/octet-stream, application/gzip, application/zip, application/yaml, application/json" : "application/json");
|
|
95406
|
-
}
|
|
95407
|
-
let body = options.body;
|
|
95408
|
-
if (body !== undefined && !(body instanceof FormData) && !(body instanceof Blob) && !(body instanceof ArrayBuffer) && !(body instanceof Uint8Array) && typeof body !== "string") {
|
|
95409
|
-
body = stableJsonStringify(body);
|
|
95410
|
-
if (!headers.has("Content-Type")) {
|
|
95411
|
-
headers.set("Content-Type", "application/json");
|
|
95412
|
-
}
|
|
95413
|
-
}
|
|
95414
|
-
const method2 = (options.method ?? "GET").toUpperCase();
|
|
95415
|
-
const timeoutMs3 = options.timeoutMs ?? this.requestTimeoutMs;
|
|
95416
|
-
const abort = requestAbortContext(options.signal, timeoutMs3);
|
|
95417
|
-
const send = async (refreshed) => {
|
|
95418
|
-
if (refreshed)
|
|
95419
|
-
session = refreshed;
|
|
95420
|
-
const requestHeaders = new Headers(headers);
|
|
95421
|
-
requestHeaders.set("Authorization", `Bearer ${session?.access_token ?? credential.bearer}`);
|
|
95422
|
-
return await this.fetchImpl(`${this.root(session)}${pathname}${queryString(options.query)}`, { method: method2, headers: requestHeaders, body, signal: abort.signal });
|
|
95423
|
-
};
|
|
95424
|
-
try {
|
|
95425
|
-
let response;
|
|
95426
|
-
try {
|
|
95427
|
-
response = await this.authRetry(credential.source === "session" ? session : null, send);
|
|
95428
|
-
} catch (error2) {
|
|
95429
|
-
if (abort.source() === "caller") {
|
|
95430
|
-
throw callerAbortError(options.signal);
|
|
95431
|
-
}
|
|
95432
|
-
if (abort.source() === "timeout") {
|
|
95433
|
-
throw new BenchmarkApiError(`Benchmark request timed out after ${timeoutMs3} ms`, 408, null, "request_timeout");
|
|
95434
|
-
}
|
|
95435
|
-
throw new BenchmarkApiError(redactSensitiveText(`Network error: ${error2 instanceof Error ? error2.message : String(error2)}`), undefined, null, "network_error");
|
|
95436
|
-
}
|
|
95437
|
-
if (!response.ok) {
|
|
95438
|
-
const text5 = await response.text();
|
|
95439
|
-
let responseBody = text5;
|
|
95440
|
-
try {
|
|
95441
|
-
responseBody = text5 ? JSON.parse(text5) : null;
|
|
95442
|
-
} catch {}
|
|
95443
|
-
throw new BenchmarkApiError(redactSensitiveText(masApiErrorMessage(responseBody, response.status)), response.status, responseBody, safeCode(responseBody), response.headers.get("x-request-id") ?? response.headers.get("x-correlation-id") ?? undefined);
|
|
95444
|
-
}
|
|
95445
|
-
if (options.mode === "binary") {
|
|
95446
|
-
return {
|
|
95447
|
-
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
95448
|
-
contentType: response.headers.get("Content-Type"),
|
|
95449
|
-
filename: filenameFromHeaders(response.headers)
|
|
95450
|
-
};
|
|
95451
|
-
}
|
|
95452
|
-
if (response.status === 204)
|
|
95453
|
-
return;
|
|
95454
|
-
const text4 = await response.text();
|
|
95455
|
-
if (!text4)
|
|
95456
|
-
return null;
|
|
95457
|
-
try {
|
|
95458
|
-
return JSON.parse(text4);
|
|
95459
|
-
} catch {
|
|
95460
|
-
throw new BenchmarkApiError("Benchmark API returned an invalid JSON response", response.status, null, "invalid_response", response.headers.get("x-request-id") ?? response.headers.get("x-correlation-id") ?? undefined, false);
|
|
95461
|
-
}
|
|
95462
|
-
} catch (error2) {
|
|
95463
|
-
if (abort.source() === "caller") {
|
|
95464
|
-
throw callerAbortError(options.signal);
|
|
95465
|
-
}
|
|
95466
|
-
if (abort.source() === "timeout") {
|
|
95467
|
-
throw new BenchmarkApiError(`Benchmark request timed out after ${timeoutMs3} ms`, 408, null, "request_timeout");
|
|
95468
|
-
}
|
|
95469
|
-
throw error2;
|
|
95470
|
-
} finally {
|
|
95471
|
-
abort.dispose();
|
|
95472
|
-
}
|
|
95473
|
-
}
|
|
95474
|
-
validateManifest(manifest) {
|
|
95475
|
-
return this.request("/validate", {
|
|
95476
|
-
method: "POST",
|
|
95477
|
-
body: { manifest }
|
|
95478
|
-
});
|
|
95479
|
-
}
|
|
95480
|
-
createBenchmark(manifest, options) {
|
|
95481
|
-
return this.request("", {
|
|
95482
|
-
method: "POST",
|
|
95483
|
-
body: { manifest },
|
|
95484
|
-
...options
|
|
95485
|
-
});
|
|
95486
|
-
}
|
|
95487
|
-
listBenchmarks(query = {}) {
|
|
95488
|
-
return this.request("", {
|
|
95489
|
-
query: {
|
|
95490
|
-
limit: query.limit,
|
|
95491
|
-
cursor: query.cursor,
|
|
95492
|
-
archived: query.archived
|
|
95493
|
-
}
|
|
95494
|
-
});
|
|
95495
|
-
}
|
|
95496
|
-
getBenchmark(benchmarkId) {
|
|
95497
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}`);
|
|
95498
|
-
}
|
|
95499
|
-
updateBenchmark(benchmarkId, input) {
|
|
95500
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}`, {
|
|
95501
|
-
method: "PATCH",
|
|
95502
|
-
body: input
|
|
95503
|
-
});
|
|
95504
|
-
}
|
|
95505
|
-
archiveBenchmark(benchmarkId, expectedVersion) {
|
|
95506
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/archive`, {
|
|
95507
|
-
method: "POST",
|
|
95508
|
-
body: { expected_version: expectedVersion }
|
|
95509
|
-
});
|
|
95510
|
-
}
|
|
95511
|
-
restoreBenchmark(benchmarkId, expectedVersion) {
|
|
95512
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/restore`, {
|
|
95513
|
-
method: "POST",
|
|
95514
|
-
body: { expected_version: expectedVersion }
|
|
95515
|
-
});
|
|
95516
|
-
}
|
|
95517
|
-
async purgeBenchmark(benchmarkId, input) {
|
|
95518
|
-
await this.request(`/${encodeURIComponent(benchmarkId)}/purge`, {
|
|
95519
|
-
method: "POST",
|
|
95520
|
-
body: input
|
|
95521
|
-
});
|
|
95522
|
-
}
|
|
95523
|
-
createRevision(benchmarkId, expectedVersion, options) {
|
|
95524
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/revisions`, {
|
|
95525
|
-
method: "POST",
|
|
95526
|
-
body: { expected_version: expectedVersion },
|
|
95527
|
-
...options
|
|
95528
|
-
});
|
|
95529
|
-
}
|
|
95530
|
-
listRevisions(benchmarkId, query = {}) {
|
|
95531
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/revisions`, {
|
|
95532
|
-
query: {
|
|
95533
|
-
limit: query.limit,
|
|
95534
|
-
before_revision_number: query.beforeRevisionNumber
|
|
95535
|
-
}
|
|
95536
|
-
});
|
|
95537
|
-
}
|
|
95538
|
-
exportManifest(benchmarkId, format3, revisionId) {
|
|
95539
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/export`, {
|
|
95540
|
-
mode: "binary",
|
|
95541
|
-
query: { format: format3, revision_id: revisionId }
|
|
95542
|
-
});
|
|
95543
|
-
}
|
|
95544
|
-
exportBundle(benchmarkId, revisionId) {
|
|
95545
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/export`, {
|
|
95546
|
-
mode: "binary",
|
|
95547
|
-
query: { format: "bundle", revision_id: revisionId }
|
|
95548
|
-
});
|
|
95549
|
-
}
|
|
95550
|
-
signBenchmarkObject(benchmarkId, objectId, query = {}) {
|
|
95551
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/objects/${encodeURIComponent(objectId)}/signed-url`, {
|
|
95552
|
-
query: {
|
|
95553
|
-
expires_in_seconds: query.expiresInSeconds,
|
|
95554
|
-
download_name: query.downloadName
|
|
95555
|
-
}
|
|
95556
|
-
});
|
|
95557
|
-
}
|
|
95558
|
-
upload(pathname, upload, fields, options) {
|
|
95559
|
-
const form = new FormData;
|
|
95560
|
-
const blob = uploadBlob(upload);
|
|
95561
|
-
form.append("file", blob, upload.filename);
|
|
95562
|
-
for (const [key2, value3] of Object.entries(fields)) {
|
|
95563
|
-
if (value3 !== undefined)
|
|
95564
|
-
form.append(key2, value3);
|
|
95565
|
-
}
|
|
95566
|
-
const scaledTimeoutMs = BENCHMARK_UPLOAD_PROCESSING_ALLOWANCE_MS + Math.ceil(blob.size / BENCHMARK_UPLOAD_MIN_BYTES_PER_SECOND * 1000);
|
|
95567
|
-
return this.request(pathname, {
|
|
95568
|
-
method: "POST",
|
|
95569
|
-
body: form,
|
|
95570
|
-
timeoutMs: this.uploadTimeoutMs ?? Math.max(this.requestTimeoutMs, scaledTimeoutMs),
|
|
95571
|
-
...options
|
|
95572
|
-
});
|
|
95573
|
-
}
|
|
95574
|
-
importManifest(upload, options) {
|
|
95575
|
-
return this.upload("/imports/manifest", upload, {}, options);
|
|
95576
|
-
}
|
|
95577
|
-
importCsv(upload, fields, options) {
|
|
95578
|
-
return this.upload("/imports/csv", upload, {
|
|
95579
|
-
mapping_json: stableJsonStringify(fields.mapping),
|
|
95580
|
-
metadata_json: stableJsonStringify(fields.metadata),
|
|
95581
|
-
environment_json: fields.environment === undefined ? undefined : stableJsonStringify(fields.environment),
|
|
95582
|
-
evaluators_json: fields.evaluators === undefined ? undefined : stableJsonStringify(fields.evaluators),
|
|
95583
|
-
primary_evaluator_key: fields.primaryEvaluatorKey
|
|
95584
|
-
}, options);
|
|
95585
|
-
}
|
|
95586
|
-
importBundle(upload, options) {
|
|
95587
|
-
return this.upload("/imports/bundle", upload, {}, options);
|
|
95588
|
-
}
|
|
95589
|
-
previewTaskImport(input) {
|
|
95590
|
-
return this.request("/imports/task/preview", {
|
|
95591
|
-
method: "POST",
|
|
95592
|
-
body: input
|
|
95593
|
-
});
|
|
95594
|
-
}
|
|
95595
|
-
materializeTaskImport(input) {
|
|
95596
|
-
return this.request("/imports/task/materialize", {
|
|
95597
|
-
method: "POST",
|
|
95598
|
-
body: input
|
|
95599
|
-
});
|
|
95600
|
-
}
|
|
95601
|
-
previewHuggingFaceImport(input) {
|
|
95602
|
-
return this.request("/imports/hugging-face/preview", {
|
|
95603
|
-
method: "POST",
|
|
95604
|
-
body: input
|
|
95605
|
-
});
|
|
95606
|
-
}
|
|
95607
|
-
materializeHuggingFaceImport(input, options) {
|
|
95608
|
-
return this.request("/imports/hugging-face/materialize", {
|
|
95609
|
-
method: "POST",
|
|
95610
|
-
body: input,
|
|
95611
|
-
...options
|
|
95612
|
-
});
|
|
95613
|
-
}
|
|
95614
|
-
createRunPlan(benchmarkId, input, options) {
|
|
95615
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/runs/plan`, {
|
|
95616
|
-
method: "POST",
|
|
95617
|
-
body: input,
|
|
95618
|
-
...options
|
|
95619
|
-
});
|
|
95620
|
-
}
|
|
95621
|
-
getRunPlan(planId) {
|
|
95622
|
-
return this.request(`/run-plans/${encodeURIComponent(planId)}`);
|
|
95623
|
-
}
|
|
95624
|
-
confirmRunPlan(planId) {
|
|
95625
|
-
return this.request(`/run-plans/${encodeURIComponent(planId)}/confirm`, {
|
|
95626
|
-
method: "POST"
|
|
95627
|
-
});
|
|
95628
|
-
}
|
|
95629
|
-
confirmRun(runId) {
|
|
95630
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/confirm`, {
|
|
95631
|
-
method: "POST"
|
|
95632
|
-
});
|
|
95633
|
-
}
|
|
95634
|
-
listRuns(query = {}) {
|
|
95635
|
-
return this.request("/runs", {
|
|
95636
|
-
query: {
|
|
95637
|
-
benchmark_id: query.benchmarkId,
|
|
95638
|
-
status: query.status,
|
|
95639
|
-
limit: query.limit,
|
|
95640
|
-
cursor: query.cursor
|
|
95641
|
-
}
|
|
95642
|
-
});
|
|
95643
|
-
}
|
|
95644
|
-
getRun(runId, options = {}) {
|
|
95645
|
-
return this.request(`/runs/${encodeURIComponent(runId)}`, options);
|
|
95646
|
-
}
|
|
95647
|
-
updateRunAnnotation(runId, input) {
|
|
95648
|
-
return this.request(`/runs/${encodeURIComponent(runId)}`, {
|
|
95649
|
-
method: "PATCH",
|
|
95650
|
-
body: input
|
|
95651
|
-
});
|
|
95652
|
-
}
|
|
95653
|
-
cancelRun(runId) {
|
|
95654
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
95655
|
-
method: "POST"
|
|
95656
|
-
});
|
|
95657
|
-
}
|
|
95658
|
-
resumeRun(runId) {
|
|
95659
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/resume`, {
|
|
95660
|
-
method: "POST"
|
|
95661
|
-
});
|
|
95662
|
-
}
|
|
95663
|
-
listDiagnoses(runId, query = {}) {
|
|
95664
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/diagnoses`, {
|
|
95665
|
-
query: {
|
|
95666
|
-
limit: query.limit,
|
|
95667
|
-
cursor: query.cursor
|
|
95668
|
-
}
|
|
95669
|
-
});
|
|
95670
|
-
}
|
|
95671
|
-
getRunResults(runId, baselineTargetId) {
|
|
95672
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/results`, {
|
|
95673
|
-
query: { baseline_target_id: baselineTargetId }
|
|
95674
|
-
});
|
|
95675
|
-
}
|
|
95676
|
-
exportRunResults(runId, query = {}) {
|
|
95677
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/results/export`, {
|
|
95678
|
-
mode: "binary",
|
|
95679
|
-
query: {
|
|
95680
|
-
include_billing_receipts: query.includeBillingReceipts || undefined
|
|
95681
|
-
}
|
|
95682
|
-
});
|
|
95683
|
-
}
|
|
95684
|
-
listTrials(runId, query = {}) {
|
|
95685
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/trials`, {
|
|
95686
|
-
query: {
|
|
95687
|
-
status: query.status,
|
|
95688
|
-
limit: query.limit,
|
|
95689
|
-
cursor: query.cursor
|
|
95690
|
-
}
|
|
95691
|
-
});
|
|
95692
|
-
}
|
|
95693
|
-
listAttempts(runId, query = {}) {
|
|
95694
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/attempts`, {
|
|
95695
|
-
query: {
|
|
95696
|
-
trial_id: query.trialId,
|
|
95697
|
-
status: query.status,
|
|
95698
|
-
limit: query.limit,
|
|
95699
|
-
cursor: query.cursor
|
|
95700
|
-
}
|
|
95701
|
-
});
|
|
95702
|
-
}
|
|
95703
|
-
getAttempt(runId, attemptId, query = {}) {
|
|
95704
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/attempts/${encodeURIComponent(attemptId)}`, {
|
|
95705
|
-
query: {
|
|
95706
|
-
evaluator_limit: query.evaluatorLimit,
|
|
95707
|
-
evaluator_cursor: query.evaluatorCursor,
|
|
95708
|
-
artifact_limit: query.artifactLimit,
|
|
95709
|
-
artifact_cursor: query.artifactCursor
|
|
95710
|
-
}
|
|
95711
|
-
});
|
|
95712
|
-
}
|
|
95713
|
-
signAttemptArtifact(runId, attemptId, artifactId, query = {}) {
|
|
95714
|
-
return this.request(`/runs/${encodeURIComponent(runId)}/attempts/${encodeURIComponent(attemptId)}/artifacts/${encodeURIComponent(artifactId)}/signed-url`, {
|
|
95715
|
-
query: {
|
|
95716
|
-
expires_in_seconds: query.expiresInSeconds,
|
|
95717
|
-
download_name: query.downloadName
|
|
95718
|
-
}
|
|
95719
|
-
});
|
|
95720
|
-
}
|
|
95721
|
-
getSummaries(benchmarkIds, historyLimit) {
|
|
95722
|
-
return this.request("/analytics/summaries", {
|
|
95723
|
-
query: {
|
|
95724
|
-
benchmark_id: benchmarkIds,
|
|
95725
|
-
history_limit: historyLimit
|
|
95726
|
-
}
|
|
95727
|
-
});
|
|
95728
|
-
}
|
|
95729
|
-
getAnalytics(benchmarkId, historyLimit) {
|
|
95730
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/analytics`, {
|
|
95731
|
-
query: { history_limit: historyLimit }
|
|
95732
|
-
});
|
|
95733
|
-
}
|
|
95734
|
-
setBaseline(benchmarkId, targetKey, baselineTargetId) {
|
|
95735
|
-
return this.request(`/${encodeURIComponent(benchmarkId)}/baselines/${encodeURIComponent(targetKey)}`, {
|
|
95736
|
-
method: "PUT",
|
|
95737
|
-
body: { baseline_target_id: baselineTargetId }
|
|
95738
|
-
});
|
|
95739
|
-
}
|
|
95740
|
-
async clearBaseline(benchmarkId, targetKey) {
|
|
95741
|
-
await this.request(`/${encodeURIComponent(benchmarkId)}/baselines/${encodeURIComponent(targetKey)}`, { method: "DELETE" });
|
|
95742
|
-
}
|
|
95743
|
-
}
|
|
95744
|
-
|
|
95745
97813
|
// src/core/benchmark-authoring.ts
|
|
95746
97814
|
var import_yaml9 = __toESM(require_dist(), 1);
|
|
95747
97815
|
import crypto8 from "node:crypto";
|
|
@@ -98075,6 +100143,9 @@ class BenchmarkControlOutput {
|
|
|
98075
100143
|
this.result({ items: [...page.items] }, all ? undefined : page.next_cursor);
|
|
98076
100144
|
}
|
|
98077
100145
|
error(error2) {
|
|
100146
|
+
if (debugOutputEnabled())
|
|
100147
|
+
this.writer.stderr(`${formatErrorCauseChain(error2)}
|
|
100148
|
+
`);
|
|
98078
100149
|
if (this.mode.json || this.mode.jsonl) {
|
|
98079
100150
|
const encoded = this.mode.jsonl ? formatJsonLine : formatJson;
|
|
98080
100151
|
this.writer.stderr(encoded(automationErrorEnvelope(this.command, error2)));
|
|
@@ -98708,7 +100779,7 @@ async function runPlan(cwd2, argv, deps) {
|
|
|
98708
100779
|
const input = runPlanInput(options, requiredOption(options.parsed, "revision"));
|
|
98709
100780
|
const { client, output } = context(cwd2, "benchmark run plan", options, deps);
|
|
98710
100781
|
output.result(json4(await client.createRunPlan(benchmarkId, input, {
|
|
98711
|
-
idempotencyKey: idempotencyKey(options)
|
|
100782
|
+
idempotencyKey: runPlanIdempotencyKey(idempotencyKey(options))
|
|
98712
100783
|
})));
|
|
98713
100784
|
}
|
|
98714
100785
|
async function runStart(cwd2, argv, deps) {
|
|
@@ -98739,14 +100810,73 @@ async function watchRun(client, output, runId, interval, agentId, deps) {
|
|
|
98739
100810
|
deps.addSigintListener(onInterrupt2);
|
|
98740
100811
|
let previous = "";
|
|
98741
100812
|
let latest = {};
|
|
100813
|
+
const retry = httpRetryEnabled();
|
|
100814
|
+
const downtimeBudgetMs = deps.watchDowntimeBudgetMs ?? DEFAULT_RETRY_BUDGET_MS;
|
|
100815
|
+
let downtime;
|
|
100816
|
+
let downtimeTimer;
|
|
100817
|
+
let lastPollError;
|
|
100818
|
+
let downtimeExpired = false;
|
|
100819
|
+
let failedPolls = 0;
|
|
100820
|
+
const now2 = deps.now ?? Date.now;
|
|
100821
|
+
let outageStartedAt = now2();
|
|
100822
|
+
const telemetry = (event, error2, delayMs) => pollRetryTelemetry(event, "benchmark_run_watch", error2, failedPolls, outageStartedAt, downtimeBudgetMs, delayMs, now2());
|
|
100823
|
+
const unreachable = () => {
|
|
100824
|
+
logWaitRetryEvent(telemetry("retry_exhausted", lastPollError));
|
|
100825
|
+
deps.writer.stderr(`${waitUnreachableNotice(`The benchmark run's status (run ${runId})`, `check with \`brainbase benchmark run show ${runId}${agentId ? ` --agent ${agentId}` : ""}\``, downtimeBudgetMs)}
|
|
100826
|
+
`);
|
|
100827
|
+
deps.setExitCode(EX_TEMPFAIL_EXIT_CODE);
|
|
100828
|
+
return { run: latest, interrupted: false, unreachable: true };
|
|
100829
|
+
};
|
|
98742
100830
|
try {
|
|
98743
100831
|
while (!interrupted) {
|
|
100832
|
+
if (downtime && downtime.remainingMs(now2()) === 0)
|
|
100833
|
+
return unreachable();
|
|
100834
|
+
const pollStartedAt = now2();
|
|
98744
100835
|
try {
|
|
98745
|
-
|
|
100836
|
+
if (retry) {
|
|
100837
|
+
const polled = await raceAbort(client.getRun(runId, { signal: controller.signal }), controller.signal);
|
|
100838
|
+
if (polled.kind === "aborted") {
|
|
100839
|
+
if (downtimeExpired)
|
|
100840
|
+
return unreachable();
|
|
100841
|
+
break;
|
|
100842
|
+
}
|
|
100843
|
+
if (polled.kind === "failed")
|
|
100844
|
+
throw polled.error;
|
|
100845
|
+
latest = polled.value;
|
|
100846
|
+
} else {
|
|
100847
|
+
latest = await client.getRun(runId, { signal: controller.signal });
|
|
100848
|
+
}
|
|
100849
|
+
if (downtimeExpired)
|
|
100850
|
+
return unreachable();
|
|
100851
|
+
downtime = undefined;
|
|
100852
|
+
if (downtimeTimer !== undefined)
|
|
100853
|
+
clearTimeout(downtimeTimer);
|
|
100854
|
+
downtimeTimer = undefined;
|
|
100855
|
+
failedPolls = 0;
|
|
98746
100856
|
} catch (error2) {
|
|
98747
100857
|
if (interrupted)
|
|
98748
100858
|
break;
|
|
98749
|
-
|
|
100859
|
+
if (downtimeExpired)
|
|
100860
|
+
return unreachable();
|
|
100861
|
+
if (!retry || !isRetryablePollError(error2))
|
|
100862
|
+
throw error2;
|
|
100863
|
+
lastPollError = error2;
|
|
100864
|
+
if (!downtime) {
|
|
100865
|
+
outageStartedAt = pollStartedAt;
|
|
100866
|
+
downtime = createRetryBudget(downtimeBudgetMs, outageStartedAt);
|
|
100867
|
+
downtimeTimer = setTimeout(() => {
|
|
100868
|
+
downtimeExpired = true;
|
|
100869
|
+
controller.abort();
|
|
100870
|
+
}, downtime.remainingMs(now2()));
|
|
100871
|
+
}
|
|
100872
|
+
failedPolls += 1;
|
|
100873
|
+
const delayMs = retryDelayMs(failedPolls - 1, downtime, retryAfterHeaderOf(error2), { maxMs: Math.min(STREAM_RETRY_MAX_DELAY_MS, Math.max(0, downtime.remainingMs(now2()) - 1)) }, now2());
|
|
100874
|
+
if (delayMs === null) {
|
|
100875
|
+
return unreachable();
|
|
100876
|
+
}
|
|
100877
|
+
logWaitRetryEvent(telemetry("retry_attempt", error2, delayMs));
|
|
100878
|
+
await deps.sleep(delayMs, controller.signal);
|
|
100879
|
+
continue;
|
|
98750
100880
|
}
|
|
98751
100881
|
const digest2 = output.digest(latest);
|
|
98752
100882
|
if (digest2 !== previous) {
|
|
@@ -98769,6 +100899,8 @@ async function watchRun(client, output, runId, interval, agentId, deps) {
|
|
|
98769
100899
|
await deps.sleep(interval, controller.signal);
|
|
98770
100900
|
}
|
|
98771
100901
|
} finally {
|
|
100902
|
+
if (downtimeTimer !== undefined)
|
|
100903
|
+
clearTimeout(downtimeTimer);
|
|
98772
100904
|
deps.removeSigintListener(onInterrupt2);
|
|
98773
100905
|
}
|
|
98774
100906
|
output.event("watch.interrupted", { run_id: runId });
|
|
@@ -98786,7 +100918,8 @@ async function runWatch(cwd2, argv, deps) {
|
|
|
98786
100918
|
if (options.json) {
|
|
98787
100919
|
output.result({
|
|
98788
100920
|
run: watched.run,
|
|
98789
|
-
interrupted: watched.interrupted
|
|
100921
|
+
interrupted: watched.interrupted,
|
|
100922
|
+
...watched.unreachable ? { unreachable: true } : {}
|
|
98790
100923
|
});
|
|
98791
100924
|
}
|
|
98792
100925
|
}
|
|
@@ -98875,7 +101008,8 @@ async function runOneShot(cwd2, argv, deps) {
|
|
|
98875
101008
|
output.result({
|
|
98876
101009
|
plan,
|
|
98877
101010
|
run: watched.run,
|
|
98878
|
-
interrupted: watched.interrupted
|
|
101011
|
+
interrupted: watched.interrupted,
|
|
101012
|
+
...watched.unreachable ? { unreachable: true } : {}
|
|
98879
101013
|
});
|
|
98880
101014
|
}
|
|
98881
101015
|
}
|
|
@@ -99732,7 +101866,7 @@ async function replaceAgentKey(credential) {
|
|
|
99732
101866
|
writeJsonAtomic(decideKeysFile(), cache3);
|
|
99733
101867
|
return mintAgentKey(credential.base, credential.agentId);
|
|
99734
101868
|
}
|
|
99735
|
-
function
|
|
101869
|
+
function retryDelayMs2(attempt2, retryAfter) {
|
|
99736
101870
|
const seconds = retryAfter === null ? Number.NaN : Number(retryAfter);
|
|
99737
101871
|
const base2 = Number.isFinite(seconds) ? seconds * 1000 : 1000 * 2 ** attempt2;
|
|
99738
101872
|
return Math.min(Math.max(base2, 0), MAX_RETRY_DELAY_MS) + Math.random() * 500;
|
|
@@ -99747,7 +101881,7 @@ function errorMessage3(status, text4) {
|
|
|
99747
101881
|
return `HTTP ${status}`;
|
|
99748
101882
|
}
|
|
99749
101883
|
async function postDecide(credential, body, options = {}) {
|
|
99750
|
-
const
|
|
101884
|
+
const sleep4 = options.sleep ?? ((ms2) => new Promise((resolve) => setTimeout(resolve, ms2)));
|
|
99751
101885
|
const payload = JSON.stringify(body);
|
|
99752
101886
|
let failure = new DecideHttpError("The decide request failed", null);
|
|
99753
101887
|
for (let attempt2 = 0;attempt2 < ATTEMPTS; attempt2 += 1) {
|
|
@@ -99780,7 +101914,7 @@ async function postDecide(credential, body, options = {}) {
|
|
|
99780
101914
|
failure = new DecideHttpError(`Network error: ${error2.message}`, null);
|
|
99781
101915
|
}
|
|
99782
101916
|
if (attempt2 + 1 < ATTEMPTS)
|
|
99783
|
-
await
|
|
101917
|
+
await sleep4(retryDelayMs2(attempt2, retryAfter));
|
|
99784
101918
|
}
|
|
99785
101919
|
throw failure;
|
|
99786
101920
|
}
|
|
@@ -100176,7 +102310,8 @@ Questions:
|
|
|
100176
102310
|
--model <slug> jev, kev-4b, clef, clef-flash, gpt-6-luna, reflex,
|
|
100177
102311
|
joey-1, d1.1, mammoth-1, elephant-1, drex-max,
|
|
100178
102312
|
pplx-decider-flash-v1.2, pplx-decider-v1.2,
|
|
100179
|
-
d1-ultra, jev-1.14
|
|
102313
|
+
d1-ultra, jev-1.14, classify-1,
|
|
102314
|
+
microsoft-decision-1, clef-omni or d1
|
|
100180
102315
|
(default: the server's default)
|
|
100181
102316
|
--min-confidence <0-1> mark answers below this confidence as uncertain
|
|
100182
102317
|
|
|
@@ -100742,6 +102877,8 @@ function help() {
|
|
|
100742
102877
|
out.push("");
|
|
100743
102878
|
out.push(` ${import_picocolors86.default.dim("BRAINBASE_HOME")} override the local config dir (default ~/.brainbase)`);
|
|
100744
102879
|
out.push(` ${import_picocolors86.default.dim("BRAINBASE_DEBUG")} print full stack traces on error (any value; unset to disable)`);
|
|
102880
|
+
if (httpRetryEnabled())
|
|
102881
|
+
out.push(` ${import_picocolors86.default.dim("--debug")} print stack traces and the full error cause chain`);
|
|
100745
102882
|
out.push(` ${import_picocolors86.default.dim("BRAINBASE_WEB_URL")} override the web app URL used by login, console links and integrations`);
|
|
100746
102883
|
out.push(` ${import_picocolors86.default.dim("BRAINBASE_CONTROL_PLANE_URL")} override the MAS host (/v2/cli; task and interface commands use /v2/tasks and /v2/interfaces)`);
|
|
100747
102884
|
out.push(` ${import_picocolors86.default.dim("BRAINBASE_API_URL")} legacy KLS host override (uses /api/cli; proxy/registry fallback)`);
|
|
@@ -100931,6 +103068,13 @@ async function requireAuth(cmd) {
|
|
|
100931
103068
|
}
|
|
100932
103069
|
async function main() {
|
|
100933
103070
|
const argv = process14.argv.slice(2);
|
|
103071
|
+
const separator = argv.indexOf("--");
|
|
103072
|
+
const debugIndex = argv.findIndex((arg, index) => arg === "--debug" && !isValueOfPriorFlag2(argv, index) && (separator < 0 || index < separator));
|
|
103073
|
+
const debug = httpRetryEnabled() && argv[0] !== "run" && debugIndex >= 0;
|
|
103074
|
+
if (debug) {
|
|
103075
|
+
argv.splice(debugIndex, 1);
|
|
103076
|
+
enableDebugOutput();
|
|
103077
|
+
}
|
|
100934
103078
|
const cmd = argv.shift();
|
|
100935
103079
|
const rawCwd = process14.cwd();
|
|
100936
103080
|
const cwd2 = (() => {
|
|
@@ -101220,8 +103364,9 @@ ${message3}`));
|
|
|
101220
103364
|
if (err instanceof ApiError && err.status === 401 && !err.message.includes("brainbase login")) {
|
|
101221
103365
|
console.error(` Run ${import_picocolors86.default.cyan("brainbase login")} to connect this device.`);
|
|
101222
103366
|
}
|
|
101223
|
-
if (process14.env.BRAINBASE_DEBUG)
|
|
101224
|
-
console.error(err.stack);
|
|
103367
|
+
if (debug || process14.env.BRAINBASE_DEBUG) {
|
|
103368
|
+
console.error(debug || httpRetryEnabled() ? formatErrorCauseChain(err) : err.stack);
|
|
103369
|
+
}
|
|
101225
103370
|
process14.exit(1);
|
|
101226
103371
|
}
|
|
101227
103372
|
}
|