mandala-computer-mcp 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -16
- package/dist/api.d.ts +19 -6
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +261 -57
- package/dist/api.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +113 -8
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +74 -9
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +115 -25
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +36 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +223 -33
- package/dist/events.js.map +1 -1
- package/dist/format.d.ts +48 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +31 -1
- package/dist/format.js.map +1 -1
- package/dist/http-body.d.ts +17 -0
- package/dist/http-body.d.ts.map +1 -0
- package/dist/http-body.js +48 -0
- package/dist/http-body.js.map +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +177 -51
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +28 -18
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +85 -22
- package/dist/paths.js.map +1 -1
- package/dist/poll.d.ts +103 -0
- package/dist/poll.d.ts.map +1 -0
- package/dist/poll.js +129 -0
- package/dist/poll.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +14 -2
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/computers.d.ts.map +1 -1
- package/dist/tools/computers.js +346 -62
- package/dist/tools/computers.js.map +1 -1
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +295 -49
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/guest.d.ts.map +1 -1
- package/dist/tools/guest.js +228 -32
- package/dist/tools/guest.js.map +1 -1
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +29 -3
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +478 -20
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +52 -13
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/webhooks.d.ts.map +1 -1
- package/dist/tools/webhooks.js +116 -17
- package/dist/tools/webhooks.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -20,8 +20,10 @@ Environment
|
|
|
20
20
|
MANDALA_MODEL_KEY an Anthropic key; enables the run_agent tool. stdio only
|
|
21
21
|
— over HTTP each caller sends their own X-Model-Key, and
|
|
22
22
|
this is ignored rather than spent on their runs
|
|
23
|
-
MANDALA_NO_LIFECYCLE
|
|
24
|
-
clone_snapshot, delete_computer and
|
|
23
|
+
MANDALA_NO_LIFECYCLE 1, true, yes or on to withhold create_computer,
|
|
24
|
+
clone_computer, clone_snapshot, delete_computer and
|
|
25
|
+
delete_snapshot. Any other value is refused rather than
|
|
26
|
+
read as off, since a typo here would leave them enabled
|
|
25
27
|
PORT, HOST for --http (default 3000, 127.0.0.1)
|
|
26
28
|
MANDALA_ALLOWED_HOSTS, MANDALA_ALLOWED_ORIGINS
|
|
27
29
|
comma-separated; which Host and Origin values to answer
|
|
@@ -39,6 +41,18 @@ Flags override the environment.`;
|
|
|
39
41
|
* present, and anything following it is the next argument, not its value.
|
|
40
42
|
*/
|
|
41
43
|
const BOOLEAN = new Set(['help', 'h', 'version', 'v', 'http', 'no-lifecycle']);
|
|
44
|
+
/**
|
|
45
|
+
* The yes-or-no flags whose VALUE is checked against a vocabulary.
|
|
46
|
+
*
|
|
47
|
+
* Not all of them, because the argument for refusing an unrecognised spelling
|
|
48
|
+
* does not reach the other four. `--http` and `--no-lifecycle` decide what the
|
|
49
|
+
* server exposes, so reading a misspelled no as a yes arms something — a
|
|
50
|
+
* listener nobody asked for, or the withholding of tools somebody wanted.
|
|
51
|
+
* `--help` and `--version` print and exit: nothing is armed either way, and
|
|
52
|
+
* refusing `--help=` would turn the one flag people reach for when they are
|
|
53
|
+
* already confused into another error. They keep the older, looser reading.
|
|
54
|
+
*/
|
|
55
|
+
const CHECKED = new Set(['http', 'no-lifecycle']);
|
|
42
56
|
const KNOWN = new Set([
|
|
43
57
|
...BOOLEAN,
|
|
44
58
|
'port',
|
|
@@ -51,6 +65,61 @@ const KNOWN = new Set([
|
|
|
51
65
|
]);
|
|
52
66
|
/** What `--http=…` may say to mean no. */
|
|
53
67
|
const FALSEY = new Set(['false', '0', 'no', 'off']);
|
|
68
|
+
/** And its mirror, for the environment variables that are a yes-or-no. */
|
|
69
|
+
const TRUTHY = new Set(['true', '1', 'yes', 'on']);
|
|
70
|
+
/**
|
|
71
|
+
* The value of a yes-or-no FLAG, refusing a spelling it does not know.
|
|
72
|
+
*
|
|
73
|
+
* The same vocabulary and the same refusal as {@link envFlag} below, because
|
|
74
|
+
* `--no-lifecycle=…` and `MANDALA_NO_LIFECYCLE=…` are two spellings of one
|
|
75
|
+
* control and it would be a poor joke for them to disagree about what `yes`
|
|
76
|
+
* means. Only the wording of the message differs, since one names a flag and
|
|
77
|
+
* the other a variable.
|
|
78
|
+
*/
|
|
79
|
+
function boolFlag(name, inline) {
|
|
80
|
+
const v = inline.trim().toLowerCase();
|
|
81
|
+
if (TRUTHY.has(v))
|
|
82
|
+
return true;
|
|
83
|
+
if (FALSEY.has(v))
|
|
84
|
+
return false;
|
|
85
|
+
throw new Error(`--${name}=${inline} is not a yes or a no. Use one of ${[...TRUTHY].join(', ')} to turn it ` +
|
|
86
|
+
`on — as does --${name} with no value at all — or one of ${[...FALSEY].join(', ')} to ` +
|
|
87
|
+
'turn it off.');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A yes-or-no environment variable, refusing a spelling it does not know.
|
|
91
|
+
*
|
|
92
|
+
* REFUSED rather than read as no, which is the decision worth recording.
|
|
93
|
+
* `MANDALA_NO_LIFECYCLE` withholds the tools that create and delete computers,
|
|
94
|
+
* so it is a safety control, and the two wrong answers do not cost the same: an
|
|
95
|
+
* unrecognised value read as "no" leaves those tools registered on a server
|
|
96
|
+
* whose operator believes they are gone, and says nothing. A typo is far more
|
|
97
|
+
* likely than a deliberate `MANDALA_NO_LIFECYCLE=ture`, and the loud version
|
|
98
|
+
* costs one clear line at startup.
|
|
99
|
+
*
|
|
100
|
+
* Empty is unset, as everywhere else here — the ordinary shape of an unquoted
|
|
101
|
+
* assignment in a compose file, and `env` has already turned it into undefined.
|
|
102
|
+
*/
|
|
103
|
+
function envFlag(name, raw) {
|
|
104
|
+
if (raw === undefined)
|
|
105
|
+
return false;
|
|
106
|
+
const v = raw.trim().toLowerCase();
|
|
107
|
+
// Here as well as in `env`, rather than only there. `env` folds an empty
|
|
108
|
+
// variable to undefined before this sees it, but that is the caller's
|
|
109
|
+
// behaviour and this reads as a general helper — `lifecycleEnabled` takes its
|
|
110
|
+
// value as a parameter, so a caller passing `process.env.X` straight in would
|
|
111
|
+
// otherwise refuse to start over `X=`, which is what an unquoted assignment
|
|
112
|
+
// in a compose file writes and what plugin.json's `${MANDALA_NO_LIFECYCLE:-}`
|
|
113
|
+
// expands to when it is unset.
|
|
114
|
+
if (!v)
|
|
115
|
+
return false;
|
|
116
|
+
if (TRUTHY.has(v))
|
|
117
|
+
return true;
|
|
118
|
+
if (FALSEY.has(v))
|
|
119
|
+
return false;
|
|
120
|
+
throw new Error(`${name}=${raw} is not a yes or a no. Use one of ${[...TRUTHY].join(', ')} to turn it on, ` +
|
|
121
|
+
`or one of ${[...FALSEY].join(', ')} (or leave it unset) to turn it off.`);
|
|
122
|
+
}
|
|
54
123
|
/**
|
|
55
124
|
* argv into flags.
|
|
56
125
|
*
|
|
@@ -89,11 +158,43 @@ export function parse(argv) {
|
|
|
89
158
|
// `--http=false` has to mean false. It is the one spelling that carries an
|
|
90
159
|
// explicit answer, and reading it as the truthy string "false" would turn
|
|
91
160
|
// the clearest way to say no into a yes.
|
|
92
|
-
|
|
93
|
-
|
|
161
|
+
//
|
|
162
|
+
// Matched against BOTH vocabularies rather than "anything that is not a no
|
|
163
|
+
// is a yes", which is what this was and which made a misspelling of a NO
|
|
164
|
+
// into a YES. `--http=ture` started a network listener the operator was
|
|
165
|
+
// trying not to start, and `--no-lifecycle=fasle` withheld the tools that
|
|
166
|
+
// make and destroy computers from someone who meant to keep them. The
|
|
167
|
+
// second fails safe and the first does not, so the loose reading had to go
|
|
168
|
+
// (OPL-4515).
|
|
169
|
+
//
|
|
170
|
+
// Refused rather than defaulted, which is the same call `envFlag` makes one
|
|
171
|
+
// screen up for the environment half of the same controls, and the message
|
|
172
|
+
// is deliberately its twin. An empty `--http=` is refused too: it is
|
|
173
|
+
// neither a yes nor a no, and `--port=` is already refused for being
|
|
174
|
+
// neither a number nor absent. Refusing is also the only answer to `--http=`
|
|
175
|
+
// that neither arms nor disarms — reading it as "off" would let a launcher
|
|
176
|
+
// template whose variable failed to expand quietly turn a safety control
|
|
177
|
+
// off, which is the failure this whole change is about.
|
|
178
|
+
//
|
|
179
|
+
// Only the flags in CHECKED: `--help` and `--version` arm nothing, so the
|
|
180
|
+
// argument does not reach them and they keep the looser reading.
|
|
181
|
+
if (BOOLEAN.has(name)) {
|
|
182
|
+
flags[name] =
|
|
183
|
+
inline === undefined
|
|
184
|
+
? true
|
|
185
|
+
: CHECKED.has(name)
|
|
186
|
+
? boolFlag(name, inline)
|
|
187
|
+
: !FALSEY.has(inline.trim().toLowerCase());
|
|
188
|
+
}
|
|
94
189
|
else if (inline !== undefined)
|
|
95
190
|
flags[name] = inline;
|
|
96
|
-
|
|
191
|
+
// PRESENT, not truthy. `--key ""` is a value token like any other, and a
|
|
192
|
+
// truthiness test skipped it: the flag became the boolean `true` and the
|
|
193
|
+
// empty token came round again as a stray argument, so the two spellings of
|
|
194
|
+
// one flag disagreed — `--key=` is the empty value `str()` documents, while
|
|
195
|
+
// `--key ""` died with `unexpected argument .`, naming nothing the user
|
|
196
|
+
// could act on.
|
|
197
|
+
else if (argv[i + 1] !== undefined && !argv[i + 1].startsWith('--'))
|
|
97
198
|
flags[name] = argv[++i];
|
|
98
199
|
else
|
|
99
200
|
flags[name] = true;
|
|
@@ -115,9 +216,13 @@ export const wantsVersion = (flags) => Boolean(flags.version || flags.v);
|
|
|
115
216
|
export function lifecycleEnabled(flags, configured = env('MANDALA_NO_LIFECYCLE')) {
|
|
116
217
|
// Presence is what establishes precedence. `--no-lifecycle=false` carries a
|
|
117
218
|
// false value deliberately and must not fall through to a true environment.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
219
|
+
// Validated before precedence is applied, and that ordering is the point. A
|
|
220
|
+
// misspelling is a mistake to report whether or not a flag happens to sit in
|
|
221
|
+
// front of it: an operator who set MANDALA_NO_LIFECYCLE=ture meaning to
|
|
222
|
+
// withhold the tools, under a launcher that also passes --no-lifecycle=false,
|
|
223
|
+
// would otherwise get exactly the silent arming this refusal exists to stop.
|
|
224
|
+
const fromEnv = envFlag('MANDALA_NO_LIFECYCLE', configured);
|
|
225
|
+
const disabled = Object.hasOwn(flags, 'no-lifecycle') ? Boolean(flags['no-lifecycle']) : fromEnv;
|
|
121
226
|
return !disabled;
|
|
122
227
|
}
|
|
123
228
|
/**
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,KAAK,GAAG;;;;;;;;iCAQmB,gBAAgB
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export declare class MandalaError extends Error {
|
|
|
19
19
|
export declare class APIError extends MandalaError {
|
|
20
20
|
readonly status: number;
|
|
21
21
|
readonly body?: unknown | undefined;
|
|
22
|
+
/** Parsed Retry-After delay in milliseconds; absent when missing or invalid. */
|
|
23
|
+
readonly retryAfterMs?: number | undefined;
|
|
22
24
|
name: string;
|
|
23
25
|
/**
|
|
24
26
|
* The platform's own word for what KIND of refusal this is, where it sent
|
|
@@ -33,8 +35,42 @@ export declare class APIError extends MandalaError {
|
|
|
33
35
|
* the caller who arrived a moment earlier heard.
|
|
34
36
|
*/
|
|
35
37
|
readonly reason?: string;
|
|
36
|
-
constructor(message: string, status: number, body?: unknown | undefined
|
|
38
|
+
constructor(message: string, status: number, body?: unknown | undefined,
|
|
39
|
+
/** Parsed Retry-After delay in milliseconds; absent when missing or invalid. */
|
|
40
|
+
retryAfterMs?: number | undefined);
|
|
37
41
|
}
|
|
42
|
+
/** Whether waiting can change a classified refusal's answer. */
|
|
43
|
+
export type ReasonKind = 'clears' | 'permanent';
|
|
44
|
+
/**
|
|
45
|
+
* How a refusal the platform classified behaves, or `undefined` for a word this
|
|
46
|
+
* version has no opinion about.
|
|
47
|
+
*
|
|
48
|
+
* The EXPORTED half of the two the sets above feed, and the split is
|
|
49
|
+
* deliberate. {@link reasonAdvice} beside it is this server's prose for a
|
|
50
|
+
* language model, tuned to how a model reads an MCP tool result and rewritten
|
|
51
|
+
* whenever one reads it wrong; publishing those sentences would make their
|
|
52
|
+
* wording something this package versions. What an embedder needs from here is
|
|
53
|
+
* narrower and does not move: whether waiting can change the answer.
|
|
54
|
+
*
|
|
55
|
+
* The raw word stays theirs. `APIError.reason` is public, the platform's set is
|
|
56
|
+
* documented and deliberately open, and switching on it is the supported path —
|
|
57
|
+
* this only offers the classification of the words this version knows, so an
|
|
58
|
+
* embedder need not duplicate a table that lives here. A word it has never
|
|
59
|
+
* heard of is `undefined` rather than a guess, which is the same contract
|
|
60
|
+
* `reasonAdvice` keeps and the reason "absent means unclassified" exists.
|
|
61
|
+
*
|
|
62
|
+
* NOT A RETRY PREDICATE. It classifies the WORD, and an error is more than its
|
|
63
|
+
* word: {@link isTransient} answers `MoveRequiredError` and
|
|
64
|
+
* {@link ConnectivityInterruptedError} before it ever looks at `reason`, because
|
|
65
|
+
* both are subclasses of branches that would otherwise say yes (OPL-3775,
|
|
66
|
+
* OPL-3855). A move-required 409 whose body also carries `reason: 'contention'`
|
|
67
|
+
* classifies here as `'clears'` while `isTransient` correctly says no — it is a
|
|
68
|
+
* decision about the size that was asked for, and it answers the same forever.
|
|
69
|
+
* So `if (reasonKind(err.reason) !== 'permanent') retry()` is the loop this
|
|
70
|
+
* sentence exists to prevent; ask {@link isTransient} whether to retry, and ask
|
|
71
|
+
* this what the platform's word meant.
|
|
72
|
+
*/
|
|
73
|
+
export declare function reasonKind(reason: string | undefined): ReasonKind | undefined;
|
|
38
74
|
/**
|
|
39
75
|
* What to tell a model about a refusal the platform classified, or `undefined`.
|
|
40
76
|
*
|
|
@@ -121,7 +157,7 @@ export declare class MoveRequiredError extends ConflictError {
|
|
|
121
157
|
name: string;
|
|
122
158
|
constructor(message: string, status: number, body: unknown,
|
|
123
159
|
/** Whether a host in this region could run the size that was asked for. */
|
|
124
|
-
movePossible: boolean);
|
|
160
|
+
movePossible: boolean, retryAfterMs?: number);
|
|
125
161
|
}
|
|
126
162
|
/**
|
|
127
163
|
* 416 — the `Range` named no byte the file has.
|
|
@@ -141,7 +177,7 @@ export declare class RangeNotSatisfiableError extends APIError {
|
|
|
141
177
|
name: string;
|
|
142
178
|
constructor(message: string, status: number, body?: unknown,
|
|
143
179
|
/** The file's real length, off `Content-Range`, when the response sent one. */
|
|
144
|
-
size?: number | undefined);
|
|
180
|
+
size?: number | undefined, retryAfterMs?: number);
|
|
145
181
|
}
|
|
146
182
|
/**
|
|
147
183
|
* 429 — the request is valid; the caller has spent a temporary rate budget.
|
|
@@ -157,12 +193,10 @@ export declare class RangeNotSatisfiableError extends APIError {
|
|
|
157
193
|
* how a rate limit becomes a longer one.
|
|
158
194
|
*/
|
|
159
195
|
export declare class RateLimitError extends APIError {
|
|
160
|
-
/** From `Retry-After`, in milliseconds from now, when the response sent one. */
|
|
161
|
-
readonly retryAfterMs?: number | undefined;
|
|
162
196
|
name: string;
|
|
163
197
|
constructor(message: string, status: number, body?: unknown,
|
|
164
198
|
/** From `Retry-After`, in milliseconds from now, when the response sent one. */
|
|
165
|
-
retryAfterMs?: number
|
|
199
|
+
retryAfterMs?: number);
|
|
166
200
|
}
|
|
167
201
|
/** 503 — a hypervisor could not be reached, so an inventory would be short. */
|
|
168
202
|
export declare class UnavailableError extends APIError {
|
|
@@ -310,6 +344,27 @@ export declare class GatewayTimeoutError extends APIError {
|
|
|
310
344
|
export declare class OriginUnreachableError extends APIError {
|
|
311
345
|
name: string;
|
|
312
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* A 3xx, which this client answers rather than follows.
|
|
349
|
+
*
|
|
350
|
+
* Following one is the tempting default and it is wrong twice over. The bearer
|
|
351
|
+
* is the smaller half: `fetch` strips `Authorization` across origins but keeps
|
|
352
|
+
* it on a same-origin hop, so a redirect inside the configured origin carries
|
|
353
|
+
* the key to a path the operator did not name. The larger half is that a
|
|
354
|
+
* redirect is a CONFIGURATION fact — a `MANDALA_BASE_URL` missing its trailing
|
|
355
|
+
* path, an http URL for an https deployment, a tenant that has moved — and
|
|
356
|
+
* following it silently means the operator never learns the value they set is
|
|
357
|
+
* not the value in use, while every request pays an extra round trip forever.
|
|
358
|
+
*
|
|
359
|
+
* So it is surfaced, and it names the `Location`: the whole point is that the
|
|
360
|
+
* next thing the operator does is put that in `MANDALA_BASE_URL`. It is an
|
|
361
|
+
* `APIError` with the real status so {@link isTransientForPoll}'s `>= 500` rule
|
|
362
|
+
* files it with the 4xx, where it belongs — repeating the request unchanged
|
|
363
|
+
* cannot change the answer.
|
|
364
|
+
*/
|
|
365
|
+
export declare class RedirectError extends APIError {
|
|
366
|
+
name: string;
|
|
367
|
+
}
|
|
313
368
|
/**
|
|
314
369
|
* 502, 520 — a proxy had no usable answer from the platform.
|
|
315
370
|
*
|
|
@@ -386,7 +441,7 @@ export declare class OriginTLSError extends APIError {
|
|
|
386
441
|
*/
|
|
387
442
|
export declare function platformSaid(body: unknown): string | undefined;
|
|
388
443
|
/** Build the error for a status, with the platform's own message when it sent one. */
|
|
389
|
-
export declare function errorForStatus(status: number, message: string, body?: unknown): APIError;
|
|
444
|
+
export declare function errorForStatus(status: number, message: string, body?: unknown, retryAfterMs?: number): APIError;
|
|
390
445
|
/**
|
|
391
446
|
* Whether an error is worth trying again without changing the request.
|
|
392
447
|
*
|
|
@@ -537,6 +592,13 @@ export declare function isTransient(err: unknown): boolean;
|
|
|
537
592
|
* SDK found that one; this is the same rule, and it is why all three now say
|
|
538
593
|
* `>= 500`.
|
|
539
594
|
*
|
|
595
|
+
* "Does not follow redirects" is a claim about `#fetch`'s `redirect: 'manual'`
|
|
596
|
+
* and is load-bearing HERE, in a way it was not while the default `'follow'`
|
|
597
|
+
* silently made it untrue: under `'follow'` a 3xx never reaches this predicate
|
|
598
|
+
* at all, so the paragraph above described a case that could not arise and the
|
|
599
|
+
* `>= 500` bound rested on nothing. {@link RedirectError} is what a 3xx
|
|
600
|
+
* becomes now. Change one and this paragraph is wrong again.
|
|
601
|
+
*
|
|
540
602
|
* {@link APIError.reason} is deliberately NOT consulted here, and that is the
|
|
541
603
|
* one place this predicate and {@link isTransient} part company (OPL-3898).
|
|
542
604
|
* `unavailable` means the computer is not running, which is a permanent answer
|
|
@@ -544,8 +606,11 @@ export declare function isTransient(err: unknown): boolean;
|
|
|
544
606
|
* may not be, since a computer coming up passes through it. The same generosity
|
|
545
607
|
* as every unmapped 5xx below, for the same reason: this only ever replays a
|
|
546
608
|
* read, and the loops above return a refusal of their own the moment the status
|
|
547
|
-
* they are watching says stopped or suspended
|
|
548
|
-
*
|
|
609
|
+
* they are watching says stopped or suspended AND the platform says it is
|
|
610
|
+
* holding nothing for the machine — the qualification OPL-4631 added, because
|
|
611
|
+
* a start that has been admitted reads as stopped or suspended until its guest
|
|
612
|
+
* process exists. mandala-computer-python's `_is_transient_for_poll` draws the
|
|
613
|
+
* line in the same place.
|
|
549
614
|
*
|
|
550
615
|
* Everything at 5xx polls through, 502 and 520-523 included: they mean the
|
|
551
616
|
* outcome is unknown, and a read whose outcome is unknown can simply be read
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,SAAQ,KAAK;IAC5B,IAAI,SAAkB;CAChC;AAED,qBAAa,QAAS,SAAQ,YAAY;IAiBtC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
CHANGED
|
@@ -19,6 +19,7 @@ export class MandalaError extends Error {
|
|
|
19
19
|
export class APIError extends MandalaError {
|
|
20
20
|
status;
|
|
21
21
|
body;
|
|
22
|
+
retryAfterMs;
|
|
22
23
|
name = 'APIError';
|
|
23
24
|
/**
|
|
24
25
|
* The platform's own word for what KIND of refusal this is, where it sent
|
|
@@ -33,10 +34,13 @@ export class APIError extends MandalaError {
|
|
|
33
34
|
* the caller who arrived a moment earlier heard.
|
|
34
35
|
*/
|
|
35
36
|
reason;
|
|
36
|
-
constructor(message, status, body
|
|
37
|
+
constructor(message, status, body,
|
|
38
|
+
/** Parsed Retry-After delay in milliseconds; absent when missing or invalid. */
|
|
39
|
+
retryAfterMs) {
|
|
37
40
|
super(message);
|
|
38
41
|
this.status = status;
|
|
39
42
|
this.body = body;
|
|
43
|
+
this.retryAfterMs = retryAfterMs;
|
|
40
44
|
this.reason = refusalReason(body);
|
|
41
45
|
}
|
|
42
46
|
}
|
|
@@ -54,6 +58,44 @@ export class APIError extends MandalaError {
|
|
|
54
58
|
*/
|
|
55
59
|
const REASON_CLEARS = new Set(['contention', 'starting']);
|
|
56
60
|
const REASON_PERMANENT = new Set(['unavailable', 'unsupported']);
|
|
61
|
+
/**
|
|
62
|
+
* How a refusal the platform classified behaves, or `undefined` for a word this
|
|
63
|
+
* version has no opinion about.
|
|
64
|
+
*
|
|
65
|
+
* The EXPORTED half of the two the sets above feed, and the split is
|
|
66
|
+
* deliberate. {@link reasonAdvice} beside it is this server's prose for a
|
|
67
|
+
* language model, tuned to how a model reads an MCP tool result and rewritten
|
|
68
|
+
* whenever one reads it wrong; publishing those sentences would make their
|
|
69
|
+
* wording something this package versions. What an embedder needs from here is
|
|
70
|
+
* narrower and does not move: whether waiting can change the answer.
|
|
71
|
+
*
|
|
72
|
+
* The raw word stays theirs. `APIError.reason` is public, the platform's set is
|
|
73
|
+
* documented and deliberately open, and switching on it is the supported path —
|
|
74
|
+
* this only offers the classification of the words this version knows, so an
|
|
75
|
+
* embedder need not duplicate a table that lives here. A word it has never
|
|
76
|
+
* heard of is `undefined` rather than a guess, which is the same contract
|
|
77
|
+
* `reasonAdvice` keeps and the reason "absent means unclassified" exists.
|
|
78
|
+
*
|
|
79
|
+
* NOT A RETRY PREDICATE. It classifies the WORD, and an error is more than its
|
|
80
|
+
* word: {@link isTransient} answers `MoveRequiredError` and
|
|
81
|
+
* {@link ConnectivityInterruptedError} before it ever looks at `reason`, because
|
|
82
|
+
* both are subclasses of branches that would otherwise say yes (OPL-3775,
|
|
83
|
+
* OPL-3855). A move-required 409 whose body also carries `reason: 'contention'`
|
|
84
|
+
* classifies here as `'clears'` while `isTransient` correctly says no — it is a
|
|
85
|
+
* decision about the size that was asked for, and it answers the same forever.
|
|
86
|
+
* So `if (reasonKind(err.reason) !== 'permanent') retry()` is the loop this
|
|
87
|
+
* sentence exists to prevent; ask {@link isTransient} whether to retry, and ask
|
|
88
|
+
* this what the platform's word meant.
|
|
89
|
+
*/
|
|
90
|
+
export function reasonKind(reason) {
|
|
91
|
+
if (reason === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (REASON_CLEARS.has(reason))
|
|
94
|
+
return 'clears';
|
|
95
|
+
if (REASON_PERMANENT.has(reason))
|
|
96
|
+
return 'permanent';
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
57
99
|
/**
|
|
58
100
|
* What to tell a model about a refusal the platform classified, or `undefined`.
|
|
59
101
|
*
|
|
@@ -74,7 +116,14 @@ export function reasonAdvice(reason) {
|
|
|
74
116
|
case 'starting':
|
|
75
117
|
return 'the guest agent is still inside its boot window, so this is worth sending again in a moment';
|
|
76
118
|
case 'unavailable':
|
|
77
|
-
|
|
119
|
+
// Softened from "does NOT clear by waiting", which was false of exactly
|
|
120
|
+
// the case OPL-4631 is about and is the sentence a model actually sees:
|
|
121
|
+
// the platform raises this whenever no guest process is up, so a start
|
|
122
|
+
// that has been admitted but has not launched yet lands here too, and
|
|
123
|
+
// telling that caller to start_computer is telling them to start it
|
|
124
|
+
// twice. This refusal cannot see the reservation — it is a
|
|
125
|
+
// reason code, not a computer — so it names the call that CAN.
|
|
126
|
+
return 'the computer is not running; if nothing is starting it this will not clear on its own and start_computer is the fix, and if a start is already under way wait_for_computer says so without starting a second one';
|
|
78
127
|
case 'unsupported':
|
|
79
128
|
return 'this computer cannot do it at all, so do not retry it — the answer is the same forever';
|
|
80
129
|
default:
|
|
@@ -168,8 +217,8 @@ export class MoveRequiredError extends ConflictError {
|
|
|
168
217
|
name = 'MoveRequiredError';
|
|
169
218
|
constructor(message, status, body,
|
|
170
219
|
/** Whether a host in this region could run the size that was asked for. */
|
|
171
|
-
movePossible) {
|
|
172
|
-
super(message, status, body);
|
|
220
|
+
movePossible, retryAfterMs) {
|
|
221
|
+
super(message, status, body, retryAfterMs);
|
|
173
222
|
this.movePossible = movePossible;
|
|
174
223
|
}
|
|
175
224
|
}
|
|
@@ -210,8 +259,8 @@ export class RangeNotSatisfiableError extends APIError {
|
|
|
210
259
|
name = 'RangeNotSatisfiableError';
|
|
211
260
|
constructor(message, status, body,
|
|
212
261
|
/** The file's real length, off `Content-Range`, when the response sent one. */
|
|
213
|
-
size) {
|
|
214
|
-
super(message, status, body);
|
|
262
|
+
size, retryAfterMs) {
|
|
263
|
+
super(message, status, body, retryAfterMs);
|
|
215
264
|
this.size = size;
|
|
216
265
|
}
|
|
217
266
|
}
|
|
@@ -229,13 +278,12 @@ export class RangeNotSatisfiableError extends APIError {
|
|
|
229
278
|
* how a rate limit becomes a longer one.
|
|
230
279
|
*/
|
|
231
280
|
export class RateLimitError extends APIError {
|
|
232
|
-
retryAfterMs;
|
|
233
281
|
name = 'RateLimitError';
|
|
282
|
+
// biome-ignore lint/complexity/noUselessConstructor: Preserve the documented public constructor.
|
|
234
283
|
constructor(message, status, body,
|
|
235
284
|
/** From `Retry-After`, in milliseconds from now, when the response sent one. */
|
|
236
285
|
retryAfterMs) {
|
|
237
|
-
super(message, status, body);
|
|
238
|
-
this.retryAfterMs = retryAfterMs;
|
|
286
|
+
super(message, status, body, retryAfterMs);
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
289
|
/** 503 — a hypervisor could not be reached, so an inventory would be short. */
|
|
@@ -384,6 +432,27 @@ export class GatewayTimeoutError extends APIError {
|
|
|
384
432
|
export class OriginUnreachableError extends APIError {
|
|
385
433
|
name = 'OriginUnreachableError';
|
|
386
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* A 3xx, which this client answers rather than follows.
|
|
437
|
+
*
|
|
438
|
+
* Following one is the tempting default and it is wrong twice over. The bearer
|
|
439
|
+
* is the smaller half: `fetch` strips `Authorization` across origins but keeps
|
|
440
|
+
* it on a same-origin hop, so a redirect inside the configured origin carries
|
|
441
|
+
* the key to a path the operator did not name. The larger half is that a
|
|
442
|
+
* redirect is a CONFIGURATION fact — a `MANDALA_BASE_URL` missing its trailing
|
|
443
|
+
* path, an http URL for an https deployment, a tenant that has moved — and
|
|
444
|
+
* following it silently means the operator never learns the value they set is
|
|
445
|
+
* not the value in use, while every request pays an extra round trip forever.
|
|
446
|
+
*
|
|
447
|
+
* So it is surfaced, and it names the `Location`: the whole point is that the
|
|
448
|
+
* next thing the operator does is put that in `MANDALA_BASE_URL`. It is an
|
|
449
|
+
* `APIError` with the real status so {@link isTransientForPoll}'s `>= 500` rule
|
|
450
|
+
* files it with the 4xx, where it belongs — repeating the request unchanged
|
|
451
|
+
* cannot change the answer.
|
|
452
|
+
*/
|
|
453
|
+
export class RedirectError extends APIError {
|
|
454
|
+
name = 'RedirectError';
|
|
455
|
+
}
|
|
387
456
|
/**
|
|
388
457
|
* 502, 520 — a proxy had no usable answer from the platform.
|
|
389
458
|
*
|
|
@@ -452,11 +521,8 @@ const BY_STATUS = {
|
|
|
452
521
|
// arriving from anywhere else is still the right class with the platform's
|
|
453
522
|
// own message on it.
|
|
454
523
|
416: RangeNotSatisfiableError,
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
// reason: `Retry-After` is on the headers and the number is worth keeping.
|
|
458
|
-
// The entry is here so a 429 arriving from anywhere else is still the right
|
|
459
|
-
// class, which is what {@link isTransient} now asks about.
|
|
524
|
+
// Retry-After is parsed by Api and passed through errorForStatus.
|
|
525
|
+
// A 429 built without headers still has the same public error class.
|
|
460
526
|
429: RateLimitError,
|
|
461
527
|
// The other status a proxy writes on its own, and it was the one gap left in
|
|
462
528
|
// this range: with no entry it fell through to a bare APIError, so a model
|
|
@@ -615,7 +681,7 @@ export function platformSaid(body) {
|
|
|
615
681
|
return typeof err === 'string' && err.length > 0 ? err : undefined;
|
|
616
682
|
}
|
|
617
683
|
/** Build the error for a status, with the platform's own message when it sent one. */
|
|
618
|
-
export function errorForStatus(status, message, body) {
|
|
684
|
+
export function errorForStatus(status, message, body, retryAfterMs) {
|
|
619
685
|
const Cls = BY_STATUS[status] ?? APIError;
|
|
620
686
|
// The 409 that is an offer, told apart by its body. Before the substitutions
|
|
621
687
|
// below because it never wants one: the platform's sentence here is the whole
|
|
@@ -624,7 +690,10 @@ export function errorForStatus(status, message, body) {
|
|
|
624
690
|
if (Cls === ConflictError) {
|
|
625
691
|
const offer = moveOffer(body);
|
|
626
692
|
if (offer)
|
|
627
|
-
return new MoveRequiredError(message, status, body, offer.possible);
|
|
693
|
+
return new MoveRequiredError(message, status, body, offer.possible, retryAfterMs);
|
|
694
|
+
}
|
|
695
|
+
if (Cls === RangeNotSatisfiableError) {
|
|
696
|
+
return new RangeNotSatisfiableError(message, status, body, undefined, retryAfterMs);
|
|
628
697
|
}
|
|
629
698
|
// Substituted for an empty body, which says nothing, and for a proxy's HTML
|
|
630
699
|
// page, which says 500 characters of nothing. NOT for a structured message:
|
|
@@ -641,21 +710,21 @@ export function errorForStatus(status, message, body) {
|
|
|
641
710
|
// that deployment than the generic outage prose here does, and discarding it
|
|
642
711
|
// was the very thing the 504 and 520 guards exist to prevent.
|
|
643
712
|
if (Cls === GatewayTimeoutError && !platformNamed(body)) {
|
|
644
|
-
return new GatewayTimeoutError(gatewayTimeoutMessage(status), status, body);
|
|
713
|
+
return new GatewayTimeoutError(gatewayTimeoutMessage(status), status, body, retryAfterMs);
|
|
645
714
|
}
|
|
646
715
|
if (Cls === OriginResponseError && !platformNamed(body)) {
|
|
647
716
|
// 502 and 520 share a class and not a message: one knows the request
|
|
648
717
|
// arrived, the other cannot tell. See BAD_GATEWAY_MESSAGE.
|
|
649
718
|
const said = status === 502 ? BAD_GATEWAY_MESSAGE : ORIGIN_RESPONSE_MESSAGE;
|
|
650
|
-
return new OriginResponseError(said, status, body);
|
|
719
|
+
return new OriginResponseError(said, status, body, retryAfterMs);
|
|
651
720
|
}
|
|
652
721
|
if (Cls === OriginTLSError && !platformNamed(body)) {
|
|
653
|
-
return new OriginTLSError(ORIGIN_TLS_MESSAGE, status, body);
|
|
722
|
+
return new OriginTLSError(ORIGIN_TLS_MESSAGE, status, body, retryAfterMs);
|
|
654
723
|
}
|
|
655
724
|
if (Cls === OriginUnreachableError && !platformNamed(body)) {
|
|
656
|
-
return new OriginUnreachableError(ORIGIN_UNREACHABLE_MESSAGE, status, body);
|
|
725
|
+
return new OriginUnreachableError(ORIGIN_UNREACHABLE_MESSAGE, status, body, retryAfterMs);
|
|
657
726
|
}
|
|
658
|
-
return new Cls(message, status, body);
|
|
727
|
+
return new Cls(message, status, body, retryAfterMs);
|
|
659
728
|
}
|
|
660
729
|
/**
|
|
661
730
|
* Whether an error is worth trying again without changing the request.
|
|
@@ -730,6 +799,14 @@ export function errorForStatus(status, message, body) {
|
|
|
730
799
|
* what it can and cannot tell apart.
|
|
731
800
|
*/
|
|
732
801
|
export function isTransient(err) {
|
|
802
|
+
// Preparation requires the original template and a token, and may have failed.
|
|
803
|
+
// A generic retry of the unchanged create is not the continuation protocol.
|
|
804
|
+
if (err instanceof APIError &&
|
|
805
|
+
err.body !== null &&
|
|
806
|
+
typeof err.body === 'object' &&
|
|
807
|
+
err.body.code === 'template_image_preparing') {
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
733
810
|
// A move offer is a 409 and is NOT transient — it is a decision about the
|
|
734
811
|
// size that was asked for, and the same request answers the same way forever.
|
|
735
812
|
// First, because it is a subclass of the very branch below that would say yes
|
|
@@ -748,9 +825,12 @@ export function isTransient(err) {
|
|
|
748
825
|
// an arbitrary exception may happen to have a `reason` property, and that is
|
|
749
826
|
// neither this protocol nor retry advice.
|
|
750
827
|
if (err instanceof APIError && err.reason !== undefined) {
|
|
751
|
-
|
|
828
|
+
// Through the exported classifier rather than the sets directly, so what an
|
|
829
|
+
// embedder is told and what this server does are one answer and not two.
|
|
830
|
+
const kind = reasonKind(err.reason);
|
|
831
|
+
if (kind === 'clears')
|
|
752
832
|
return true;
|
|
753
|
-
if (
|
|
833
|
+
if (kind === 'permanent')
|
|
754
834
|
return false;
|
|
755
835
|
}
|
|
756
836
|
return (err instanceof ConflictError ||
|
|
@@ -835,6 +915,13 @@ export function isTransient(err) {
|
|
|
835
915
|
* SDK found that one; this is the same rule, and it is why all three now say
|
|
836
916
|
* `>= 500`.
|
|
837
917
|
*
|
|
918
|
+
* "Does not follow redirects" is a claim about `#fetch`'s `redirect: 'manual'`
|
|
919
|
+
* and is load-bearing HERE, in a way it was not while the default `'follow'`
|
|
920
|
+
* silently made it untrue: under `'follow'` a 3xx never reaches this predicate
|
|
921
|
+
* at all, so the paragraph above described a case that could not arise and the
|
|
922
|
+
* `>= 500` bound rested on nothing. {@link RedirectError} is what a 3xx
|
|
923
|
+
* becomes now. Change one and this paragraph is wrong again.
|
|
924
|
+
*
|
|
838
925
|
* {@link APIError.reason} is deliberately NOT consulted here, and that is the
|
|
839
926
|
* one place this predicate and {@link isTransient} part company (OPL-3898).
|
|
840
927
|
* `unavailable` means the computer is not running, which is a permanent answer
|
|
@@ -842,8 +929,11 @@ export function isTransient(err) {
|
|
|
842
929
|
* may not be, since a computer coming up passes through it. The same generosity
|
|
843
930
|
* as every unmapped 5xx below, for the same reason: this only ever replays a
|
|
844
931
|
* read, and the loops above return a refusal of their own the moment the status
|
|
845
|
-
* they are watching says stopped or suspended
|
|
846
|
-
*
|
|
932
|
+
* they are watching says stopped or suspended AND the platform says it is
|
|
933
|
+
* holding nothing for the machine — the qualification OPL-4631 added, because
|
|
934
|
+
* a start that has been admitted reads as stopped or suspended until its guest
|
|
935
|
+
* process exists. mandala-computer-python's `_is_transient_for_poll` draws the
|
|
936
|
+
* line in the same place.
|
|
847
937
|
*
|
|
848
938
|
* Everything at 5xx polls through, 502 and 520-523 included: they mean the
|
|
849
939
|
* outcome is unknown, and a read whose outcome is unknown can simply be read
|