@getmegabrain/cli 0.1.10 → 0.1.11
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/science/agent/model.js +64 -19
- package/package.json +1 -1
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* `ModelClient` so the transport is swappable; in production it's the Gateway
|
|
5
5
|
* (`/api/gateway/v1/messages`) authenticated with the signed-in account token.
|
|
6
6
|
*/
|
|
7
|
+
/** Transient HTTP statuses worth retrying: overload (429/529), gateway/upstream (500/502/503/504),
|
|
8
|
+
* and request timeout (408). A 503 "temporarily_unavailable" from the Gateway lands here. */
|
|
9
|
+
const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504, 529]);
|
|
7
10
|
export class GatewayModel {
|
|
8
11
|
url;
|
|
9
12
|
apiKey;
|
|
10
13
|
model;
|
|
11
14
|
maxTokens;
|
|
15
|
+
maxRetries;
|
|
16
|
+
retryBaseMs;
|
|
17
|
+
sleep;
|
|
12
18
|
constructor(options) {
|
|
13
19
|
this.url =
|
|
14
20
|
options.url ??
|
|
@@ -17,30 +23,69 @@ export class GatewayModel {
|
|
|
17
23
|
this.apiKey = options.apiKey;
|
|
18
24
|
this.model = options.model ?? process.env.MB_SCIENCE_MODEL ?? 'claude-opus-4-8';
|
|
19
25
|
this.maxTokens = options.maxTokens ?? 4096;
|
|
26
|
+
this.maxRetries = options.maxRetries ?? 4;
|
|
27
|
+
this.retryBaseMs = options.retryBaseMs ?? 800;
|
|
28
|
+
this.sleep = options.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
29
|
+
}
|
|
30
|
+
/** Backoff for a given attempt (0-indexed): exponential + jitter, honoring Retry-After. */
|
|
31
|
+
backoffMs(attempt, retryAfter) {
|
|
32
|
+
const headerSeconds = retryAfter ? Number(retryAfter) : NaN;
|
|
33
|
+
if (Number.isFinite(headerSeconds) && headerSeconds >= 0) {
|
|
34
|
+
return Math.min(headerSeconds * 1000, 30_000);
|
|
35
|
+
}
|
|
36
|
+
const base = this.retryBaseMs * 2 ** attempt;
|
|
37
|
+
return Math.min(base, 15_000) + Math.floor(Math.random() * 250);
|
|
20
38
|
}
|
|
21
39
|
async complete(request) {
|
|
22
40
|
if (!this.apiKey)
|
|
23
41
|
throw new Error('Sign in to MegaBrain to use the agent.');
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
body: JSON.stringify({
|
|
32
|
-
model: this.model,
|
|
33
|
-
max_tokens: this.maxTokens,
|
|
34
|
-
system: request.system,
|
|
35
|
-
messages: request.messages,
|
|
36
|
-
tools: request.tools,
|
|
37
|
-
}),
|
|
42
|
+
const body = JSON.stringify({
|
|
43
|
+
model: this.model,
|
|
44
|
+
max_tokens: this.maxTokens,
|
|
45
|
+
system: request.system,
|
|
46
|
+
messages: request.messages,
|
|
47
|
+
tools: request.tools,
|
|
38
48
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
let lastError = '';
|
|
50
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt += 1) {
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await fetch(this.url, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'content-type': 'application/json',
|
|
57
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
58
|
+
'anthropic-version': '2023-06-01',
|
|
59
|
+
},
|
|
60
|
+
body,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Network-level failure (DNS, connection reset) — treat as transient.
|
|
65
|
+
lastError = `network error: ${error instanceof Error ? error.message : String(error)}`;
|
|
66
|
+
if (attempt < this.maxRetries) {
|
|
67
|
+
await this.sleep(this.backoffMs(attempt));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Gateway request failed (${lastError}) after ${attempt + 1} attempts`);
|
|
71
|
+
}
|
|
72
|
+
if (response.ok) {
|
|
73
|
+
const data = (await response.json());
|
|
74
|
+
return { content: data.content ?? [], stopReason: data.stop_reason ?? 'end_turn' };
|
|
75
|
+
}
|
|
76
|
+
const detail = (await response.text()).slice(0, 500);
|
|
77
|
+
if (RETRYABLE_STATUSES.has(response.status)) {
|
|
78
|
+
lastError = `${response.status}: ${detail}`;
|
|
79
|
+
if (attempt < this.maxRetries) {
|
|
80
|
+
await this.sleep(this.backoffMs(attempt, response.headers.get('retry-after')));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Transient, but out of retries.
|
|
84
|
+
throw new Error(`Gateway request failed (${lastError}) after ${attempt + 1} attempts`);
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`Gateway request failed (${response.status}): ${detail}`);
|
|
42
87
|
}
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
// Unreachable: the loop always returns or throws. Present for exhaustiveness.
|
|
89
|
+
throw new Error(`Gateway request failed (${lastError})`);
|
|
45
90
|
}
|
|
46
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmegabrain/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "MegaBrain CLI — sign in once, then code from your terminal via OpenCode wired to the MegaBrain Gateway, or run `megabrain science` for the local research workbench.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|