mandala-computer-mcp 0.1.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/LICENSE +21 -0
- package/README.md +544 -0
- package/dist/api.d.ts +186 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +932 -0
- package/dist/api.js.map +1 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +292 -0
- package/dist/cli.js.map +1 -0
- package/dist/errors.d.ts +560 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +873 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +406 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +1679 -0
- package/dist/events.js.map +1 -0
- package/dist/format.d.ts +125 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +180 -0
- package/dist/format.js.map +1 -0
- package/dist/http.d.ts +46 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +792 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +394 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +677 -0
- package/dist/paths.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +97 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +78 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +166 -0
- package/dist/session.js.map +1 -0
- package/dist/stdio.d.ts +11 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +43 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools/agent.d.ts +16 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +147 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/computers.d.ts +3 -0
- package/dist/tools/computers.d.ts.map +1 -0
- package/dist/tools/computers.js +1037 -0
- package/dist/tools/computers.js.map +1 -0
- package/dist/tools/events.d.ts +3 -0
- package/dist/tools/events.d.ts.map +1 -0
- package/dist/tools/events.js +1077 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/guest.d.ts +3 -0
- package/dist/tools/guest.d.ts.map +1 -0
- package/dist/tools/guest.js +761 -0
- package/dist/tools/guest.js.map +1 -0
- package/dist/tools/input.d.ts +3 -0
- package/dist/tools/input.d.ts.map +1 -0
- package/dist/tools/input.js +240 -0
- package/dist/tools/input.js.map +1 -0
- package/dist/tools/snapshots.d.ts +3 -0
- package/dist/tools/snapshots.d.ts.map +1 -0
- package/dist/tools/snapshots.js +333 -0
- package/dist/tools/snapshots.js.map +1 -0
- package/dist/tools/templates.d.ts +3 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +492 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/types.d.ts +18 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/webhooks.d.ts +3 -0
- package/dist/tools/webhooks.d.ts.map +1 -0
- package/dist/tools/webhooks.js +260 -0
- package/dist/tools/webhooks.js.map +1 -0
- package/package.json +59 -0
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the platform's status codes mean, as types.
|
|
3
|
+
*
|
|
4
|
+
* The distinctions here are the ones an agent has to act on and cannot infer
|
|
5
|
+
* from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
|
|
6
|
+
* limit, which no amount of waiting fixes and which the user — not the model —
|
|
7
|
+
* has to resolve. A 409 is the one that is not uniform: most of them are a
|
|
8
|
+
* passing state and worth retrying, and some are a decision about the request
|
|
9
|
+
* that no retry turns into a yes — see {@link ConflictError} and
|
|
10
|
+
* {@link MoveRequiredError}.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
|
|
13
|
+
* deliberately so: two clients disagreeing about what a 402 is means the same
|
|
14
|
+
* failure reads differently depending on which one you reached for.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MandalaError extends Error {
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class APIError extends MandalaError {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly body?: unknown | undefined;
|
|
22
|
+
name: string;
|
|
23
|
+
/**
|
|
24
|
+
* The platform's own word for what KIND of refusal this is, where it sent
|
|
25
|
+
* one: `contention`, `starting`, `unavailable` or `unsupported` (OPL-3898).
|
|
26
|
+
* `undefined` for most errors, and always will be — the platform is explicit
|
|
27
|
+
* that an absent value means unclassified rather than "none of the four".
|
|
28
|
+
*
|
|
29
|
+
* Read on the base class rather than on the one 409 it was filed for, because
|
|
30
|
+
* the platform keys it on the ERROR and not on the route: the same sentinel is
|
|
31
|
+
* reached from several endpoints, and `unavailable` arrives as a 400 as well
|
|
32
|
+
* as a 409 — whoever loses the race to the running check hears the same fact
|
|
33
|
+
* the caller who arrived a moment earlier heard.
|
|
34
|
+
*/
|
|
35
|
+
readonly reason?: string;
|
|
36
|
+
constructor(message: string, status: number, body?: unknown | undefined);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* What to tell a model about a refusal the platform classified, or `undefined`.
|
|
40
|
+
*
|
|
41
|
+
* The word itself is for a program, and the client here is a language model that
|
|
42
|
+
* cannot switch on a JSON key it never sees: {@link failed} renders an error as
|
|
43
|
+
* one sentence. So the classification travels as the clause it means, and the
|
|
44
|
+
* loop OPL-3898 was filed about — a blanket retry against a computer that is
|
|
45
|
+
* simply stopped — is the one these sentences exist to stop.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately silent about anything else. A word this version does not know is
|
|
48
|
+
* not described at all, because inventing advice for it is the mistake the
|
|
49
|
+
* platform's "absent means unclassified" contract exists to prevent.
|
|
50
|
+
*/
|
|
51
|
+
export declare function reasonAdvice(reason: string | undefined): string | undefined;
|
|
52
|
+
/** 401 — the key is missing, malformed, or revoked. */
|
|
53
|
+
export declare class AuthenticationError extends APIError {
|
|
54
|
+
name: string;
|
|
55
|
+
}
|
|
56
|
+
/** 402 — the account's plan will not allow this. Not a retry. */
|
|
57
|
+
export declare class PlanLimitError extends APIError {
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
/** 403 — the key's role on the account is too low for this route. */
|
|
61
|
+
export declare class PermissionDeniedError extends APIError {
|
|
62
|
+
name: string;
|
|
63
|
+
}
|
|
64
|
+
/** 404 — no such computer, snapshot, or route. */
|
|
65
|
+
export declare class NotFoundError extends APIError {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 409 — the thing exists but is in the wrong state for this right now.
|
|
70
|
+
*
|
|
71
|
+
* The one status on this list that is usually temporary: a guest still booting,
|
|
72
|
+
* a guest agent busy with another call, a desktop session that has not come up
|
|
73
|
+
* yet. Worth retrying; the others on this list are not.
|
|
74
|
+
*
|
|
75
|
+
* USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
|
|
76
|
+
* 409 clears is a property of the BODY and not of the status: some describe a
|
|
77
|
+
* passing state, and some describe a decision about the request — the size does
|
|
78
|
+
* not fit, the computer is the wrong one for this, the saved session cannot
|
|
79
|
+
* travel — which no amount of retrying turns into a yes. This class said
|
|
80
|
+
* "worth retrying" flatly, {@link isTransient} agreed with it, and that
|
|
81
|
+
* predicate is exported, so a host application wrapping a resize in
|
|
82
|
+
* `if (isTransient(err)) retry()` looped on a refusal that was never going to
|
|
83
|
+
* move (OPL-3775).
|
|
84
|
+
*
|
|
85
|
+
* Only the refusal that could be acted on has been given a class of its own so
|
|
86
|
+
* far, because a type is worth adding where a caller can DO something different
|
|
87
|
+
* with it. The rest stay here and are told apart by {@link APIError.reason},
|
|
88
|
+
* which is the word the platform added for that purpose (OPL-3898) and the one
|
|
89
|
+
* its own reference now says to switch on — never the sentence, which is prose
|
|
90
|
+
* written for a person and rewritten whenever a better one is. Where no word was
|
|
91
|
+
* sent this class means what it always did, and that fallback is the contract
|
|
92
|
+
* rather than a gap: not every refusal here has an answer yet.
|
|
93
|
+
*/
|
|
94
|
+
export declare class ConflictError extends APIError {
|
|
95
|
+
name: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The 409 that is an OFFER rather than a refusal: a resize needs the computer
|
|
99
|
+
* moved to another host first.
|
|
100
|
+
*
|
|
101
|
+
* `PATCH /computers/{id}` growing `ram_mb` past what the computer's current host
|
|
102
|
+
* can run answers 409 with a `move` object on the body rather than only a
|
|
103
|
+
* sentence — `{"required":true,"possible":true}` means somewhere else in the
|
|
104
|
+
* region could run that size, and `POST /computers/{id}/move` is how a caller
|
|
105
|
+
* agrees to go there. `possible:false` means nowhere in the region can, and the
|
|
106
|
+
* size is the thing to change.
|
|
107
|
+
*
|
|
108
|
+
* Its own class for the reason {@link RangeNotSatisfiableError} has one: it is a
|
|
109
|
+
* refusal the caller can correct without knowing anything it did not just learn,
|
|
110
|
+
* and the correction is a different call rather than a smaller number. Reaching
|
|
111
|
+
* it through a bare `ConflictError` left the flag on `body` where nothing looked
|
|
112
|
+
* for it, and left the retry predicate saying yes.
|
|
113
|
+
*
|
|
114
|
+
* {@link movePossible} is the branch, and it is deliberately read off the body
|
|
115
|
+
* here rather than left to every caller: `move.required` is true in both cases
|
|
116
|
+
* and it is the second field that decides what to do.
|
|
117
|
+
*/
|
|
118
|
+
export declare class MoveRequiredError extends ConflictError {
|
|
119
|
+
/** Whether a host in this region could run the size that was asked for. */
|
|
120
|
+
readonly movePossible: boolean;
|
|
121
|
+
name: string;
|
|
122
|
+
constructor(message: string, status: number, body: unknown,
|
|
123
|
+
/** Whether a host in this region could run the size that was asked for. */
|
|
124
|
+
movePossible: boolean);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 416 — the `Range` named no byte the file has.
|
|
128
|
+
*
|
|
129
|
+
* Its own class because it is the one refusal on the download route that a
|
|
130
|
+
* caller can correct without knowing anything it did not just learn: the
|
|
131
|
+
* response carries `Content-Range: bytes *\/<size>`, so the file's real length
|
|
132
|
+
* arrives with the complaint about the offset. {@link size} is that number,
|
|
133
|
+
* kept off the message so a tool can put the offset it sent beside it.
|
|
134
|
+
*
|
|
135
|
+
* A model paging a file it has not measured is the caller that meets this, and
|
|
136
|
+
* an offset past the end is the mistake it will actually make.
|
|
137
|
+
*/
|
|
138
|
+
export declare class RangeNotSatisfiableError extends APIError {
|
|
139
|
+
/** The file's real length, off `Content-Range`, when the response sent one. */
|
|
140
|
+
readonly size?: number | undefined;
|
|
141
|
+
name: string;
|
|
142
|
+
constructor(message: string, status: number, body?: unknown,
|
|
143
|
+
/** The file's real length, off `Content-Range`, when the response sent one. */
|
|
144
|
+
size?: number | undefined);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 429 — the request is valid; the caller has spent a temporary rate budget.
|
|
148
|
+
*
|
|
149
|
+
* A class rather than a number in a list, and that is the OPL-3724 change in
|
|
150
|
+
* one line: {@link isTransient} used to reach 429 by matching `err.status`,
|
|
151
|
+
* which meant this client answered "is it worth retrying" by a mechanism the
|
|
152
|
+
* other two did not share. It is a moment rather than a property of the
|
|
153
|
+
* request, exactly like a 409, and it belongs in the same shape.
|
|
154
|
+
*
|
|
155
|
+
* {@link retryAfterMs} is set when the response carried a usable `Retry-After`.
|
|
156
|
+
* The poll loops honour it, because retrying at their own faster cadence is
|
|
157
|
+
* how a rate limit becomes a longer one.
|
|
158
|
+
*/
|
|
159
|
+
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
|
+
name: string;
|
|
163
|
+
constructor(message: string, status: number, body?: unknown,
|
|
164
|
+
/** From `Retry-After`, in milliseconds from now, when the response sent one. */
|
|
165
|
+
retryAfterMs?: number | undefined);
|
|
166
|
+
}
|
|
167
|
+
/** 503 — a hypervisor could not be reached, so an inventory would be short. */
|
|
168
|
+
export declare class UnavailableError extends APIError {
|
|
169
|
+
name: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The request was given up on before the platform answered.
|
|
173
|
+
*
|
|
174
|
+
* Not an API failure and not a connectivity failure, which is the whole reason
|
|
175
|
+
* it has a type: an aborted fetch surfaces from the client as a bare
|
|
176
|
+
* `TypeError: This operation was aborted`, and wrapping that as "could not
|
|
177
|
+
* reach <host>" told every reader the platform was down when in fact the caller
|
|
178
|
+
* had hung up. A cancellation is also the one failure here where the request
|
|
179
|
+
* may well have been received and acted on, so the message says so rather than
|
|
180
|
+
* claiming nothing happened.
|
|
181
|
+
*
|
|
182
|
+
* Deliberately not transient: retrying something nobody is waiting for is the
|
|
183
|
+
* behaviour `with(signal)` exists to stop.
|
|
184
|
+
*/
|
|
185
|
+
export declare class CancelledError extends MandalaError {
|
|
186
|
+
name: string;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The request never left. Nothing was dispatched, so anything may be replayed.
|
|
190
|
+
*
|
|
191
|
+
* NARROWER than it used to be, and the narrowing is the point. This class once
|
|
192
|
+
* wrapped every rejection the transport produced, which meant it also carried
|
|
193
|
+
* the failures that happen AFTER the request reached the platform — a socket
|
|
194
|
+
* reset while the response body was being read, a protocol error on the way
|
|
195
|
+
* back. Those wear the opposite outcome: the platform may well have acted, and
|
|
196
|
+
* the answer is what was lost. They now get {@link ConnectivityInterruptedError},
|
|
197
|
+
* which is a subclass, so `catch (e) { if (e instanceof ConnectivityError) }`
|
|
198
|
+
* still sees both.
|
|
199
|
+
*
|
|
200
|
+
* What is left here is what the name always claimed: DNS that did not resolve,
|
|
201
|
+
* a socket that was refused, a connect that timed out, a TLS handshake that
|
|
202
|
+
* failed. Not one byte of the request was written, so {@link isTransient} can
|
|
203
|
+
* say yes to it even for a caller replaying a create.
|
|
204
|
+
*
|
|
205
|
+
* `Api` raises this one only for a cause it can positively identify as
|
|
206
|
+
* connect-phase; see `neverDispatched` in `src/api.ts`. Everything it cannot
|
|
207
|
+
* identify is the subclass, because the cost of the two wrong answers is not
|
|
208
|
+
* symmetric — see there.
|
|
209
|
+
*/
|
|
210
|
+
export declare class ConnectivityError extends MandalaError {
|
|
211
|
+
name: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The request was dispatched and the answer was lost. Outcome unknown.
|
|
215
|
+
*
|
|
216
|
+
* A socket that resets while the response body is being read, an HTTP parser
|
|
217
|
+
* error on the way back, an undici body or headers timeout, a connection
|
|
218
|
+
* failure this client cannot place in either phase. The shared property is the
|
|
219
|
+
* one that matters: the platform may have received the request and acted on it,
|
|
220
|
+
* and nothing in the error says whether it did.
|
|
221
|
+
*
|
|
222
|
+
* So this is FATAL to {@link isTransient} and transparent to
|
|
223
|
+
* {@link isTransientForPoll}, and the split is the same one OPL-3724 made for
|
|
224
|
+
* 502 and 504. Its words apply here unchanged — "a status that is not safe for
|
|
225
|
+
* the riskiest caller of an exported predicate does not belong in it" — and
|
|
226
|
+
* this case had escaped them only because it wears a class whose name says the
|
|
227
|
+
* request never left. `computers.create()` reaches the platform, the platform
|
|
228
|
+
* builds the computer, the socket dies mid-response: an embedder asking
|
|
229
|
+
* {@link isTransient} used to be told yes, replayed the create, and paid for
|
|
230
|
+
* two computers.
|
|
231
|
+
*
|
|
232
|
+
* A SUBCLASS rather than a sibling, which is what keeps this from breaking
|
|
233
|
+
* anyone. `instanceof ConnectivityError` still matches, so existing catch
|
|
234
|
+
* blocks and {@link isTransientForPoll}'s floor need no change; only the one
|
|
235
|
+
* predicate that promises blind replay had to learn the difference. It is the
|
|
236
|
+
* same shape {@link MoveRequiredError} has under {@link ConflictError}, for the
|
|
237
|
+
* same reason: a case that is genuinely a kind of its parent and genuinely
|
|
238
|
+
* answers one question the other way.
|
|
239
|
+
*
|
|
240
|
+
* The poll predicate still rides it out, and that is not an oversight. The wait
|
|
241
|
+
* tools replay reads — a `GET /computers/:id`, an `exec 'exit 0'` probe — and a
|
|
242
|
+
* read whose outcome was lost can simply be read again. Only a caller who might
|
|
243
|
+
* be replaying a WRITE needs the distinction, which is exactly the caller
|
|
244
|
+
* {@link isTransient} is exported for.
|
|
245
|
+
*/
|
|
246
|
+
export declare class ConnectivityInterruptedError extends ConnectivityError {
|
|
247
|
+
name: string;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 504, 524 — a proxy in front of the platform gave up before the platform answered.
|
|
251
|
+
*
|
|
252
|
+
* Not a refusal. The request arrived, is very likely still running, and nothing
|
|
253
|
+
* was cancelled; what ended was one hop's willingness to hold a connection open
|
|
254
|
+
* with no response crossing it.
|
|
255
|
+
*
|
|
256
|
+
* One class, two retry answers, and {@link isTransientForPoll} keeps them apart
|
|
257
|
+
* by status rather than by type on purpose — the ONE place a status number
|
|
258
|
+
* still decides anything, because a type cannot separate two statuses that
|
|
259
|
+
* share it. A 504 is worth polling again and a 524 is not, because of where
|
|
260
|
+
* each is reachable from: the wait tools are the only thing here that retries,
|
|
261
|
+
* they poll with short requests, and a 504 on one of those is infrastructure
|
|
262
|
+
* noise that clears. A 524 is only ever reached by holding a request open past
|
|
263
|
+
* the ceiling below — so retrying it unchanged reproduces it exactly, at the
|
|
264
|
+
* same place, because the hop that gave up never saw how long the caller asked
|
|
265
|
+
* to wait.
|
|
266
|
+
*
|
|
267
|
+
* Neither is in {@link isTransient}. That predicate is what an embedder may
|
|
268
|
+
* wrap a create in, and a gateway timeout is the case where the platform has
|
|
269
|
+
* most likely acted already (OPL-3724).
|
|
270
|
+
*
|
|
271
|
+
* Against `app.mandala.computer` that hop is Cloudflare and the ceiling is about
|
|
272
|
+
* two minutes. Measured 2026-08-20: `sleep 130` died at 125.2s with
|
|
273
|
+
* `timeout_s: 300` and at 125.3s with `timeout_s: 3600`, while `sleep 110`
|
|
274
|
+
* returned normally at 110.6s. A foreground `exec` slower than that always ends
|
|
275
|
+
* here; `background: true` is the shape that does not, because it answers as
|
|
276
|
+
* soon as the command has started.
|
|
277
|
+
*
|
|
278
|
+
* The abandoned command keeps running, which is why the next call on the same
|
|
279
|
+
* computer often raises {@link ConflictError} — the guest agent is still busy
|
|
280
|
+
* with it. That is this failure continuing, not a second one.
|
|
281
|
+
*/
|
|
282
|
+
export declare class GatewayTimeoutError extends APIError {
|
|
283
|
+
name: string;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 521-523 — a proxy in front of the platform could not reach it.
|
|
287
|
+
*
|
|
288
|
+
* The range this once claimed — 520-523, 525, 526 — is the range before the two
|
|
289
|
+
* statuses that needed their own answers were split out of it: 520 to
|
|
290
|
+
* {@link OriginResponseError}, because the platform WAS reached, and 525-526 to
|
|
291
|
+
* {@link OriginTLSError}, because waiting does not fix a certificate.
|
|
292
|
+
*
|
|
293
|
+
* The rest of what an edge generates on its own, and the same bug as
|
|
294
|
+
* {@link GatewayTimeoutError} a few statuses along: with no class and no written
|
|
295
|
+
* message these fell through to the bare `HTTP 522`, which names no cause, no
|
|
296
|
+
* culprit and no way out — the exact reading that cost the debugging above.
|
|
297
|
+
*
|
|
298
|
+
* A different event from a gateway timeout, which is why it is a different type
|
|
299
|
+
* rather than more entries on that one. A 524 means the request arrived and is
|
|
300
|
+
* still being worked on; these mean it never arrived at all, so nothing was
|
|
301
|
+
* started and there is no command outliving anything. A caller branching on the
|
|
302
|
+
* class to decide whether its work survived gets opposite answers, correctly.
|
|
303
|
+
*
|
|
304
|
+
* Deliberately absent from mandala-computer-python's `_exceptions.py`, which
|
|
305
|
+
* this file otherwise mirrors. That mapping is of the platform's own statuses;
|
|
306
|
+
* these belong to whatever is deployed in front of it. The divergence is worth
|
|
307
|
+
* it here because this client's messages are read by a model, which cannot go
|
|
308
|
+
* and look up what a 523 is.
|
|
309
|
+
*/
|
|
310
|
+
export declare class OriginUnreachableError extends APIError {
|
|
311
|
+
name: string;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 502, 520 — a proxy had no usable answer from the platform.
|
|
315
|
+
*
|
|
316
|
+
* Sits between the other two and must not be filed with either, because the
|
|
317
|
+
* question a caller is really asking is whether their work happened, and these
|
|
318
|
+
* are the statuses whose honest answer is "unknown".
|
|
319
|
+
*
|
|
320
|
+
* One class, two messages, because the two do not know the same amount. A 520 is
|
|
321
|
+
* Cloudflare naming its origin's reply unreadable, so arrival is established. A
|
|
322
|
+
* 502 is any proxy saying it has nothing it can use, which covers both an
|
|
323
|
+
* invalid reply and no reply at all — indistinguishable from here, so it claims
|
|
324
|
+
* neither. See BAD_GATEWAY_MESSAGE.
|
|
325
|
+
*
|
|
326
|
+
* A 524 means the request arrived and is still being worked on. 521-523 mean it
|
|
327
|
+
* never arrived, so nothing was started. A 520 means it **did** arrive — the
|
|
328
|
+
* platform received it and then returned an empty, unknown or oversized
|
|
329
|
+
* response, so it may have been carried out in full, in part, or not at all, and
|
|
330
|
+
* the answer was lost rather than never produced.
|
|
331
|
+
*
|
|
332
|
+
* Which makes a blind retry the thing to be careful about, and a model the
|
|
333
|
+
* likeliest caller to attempt one. Re-sending a read costs nothing; re-sending a
|
|
334
|
+
* create can leave two computers where one was meant, both billable, on the
|
|
335
|
+
* strength of a failure that said the first never happened.
|
|
336
|
+
*
|
|
337
|
+
* It was filed with {@link OriginUnreachableError} at first, on the reading that
|
|
338
|
+
* the whole 52x range is the edge failing to reach the platform. It is not, and
|
|
339
|
+
* the message that came with it — "the request never arrived, so nothing was
|
|
340
|
+
* started" — was exactly the confident falsehood this work exists to remove,
|
|
341
|
+
* pointed the other way. Caught by a review of the Python SDK, which had
|
|
342
|
+
* inherited the same grouping from this file.
|
|
343
|
+
*/
|
|
344
|
+
export declare class OriginResponseError extends APIError {
|
|
345
|
+
name: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* 525, 526 — a proxy and the platform could not agree on TLS.
|
|
349
|
+
*
|
|
350
|
+
* Split from {@link OriginUnreachableError}, which it used to share, because the
|
|
351
|
+
* two need opposite answers to "should I try again". An unreachable origin is a
|
|
352
|
+
* passing outage; an expired or mismatched certificate fails identically on
|
|
353
|
+
* every retry, and is a deployment somebody has to go and fix.
|
|
354
|
+
*
|
|
355
|
+
* The line was already drawn by status number, so the retry behaviour here was
|
|
356
|
+
* unchanged when this class appeared — but a caller reading the TYPE was told
|
|
357
|
+
* the two were the same thing, while the list beside it said they were not.
|
|
358
|
+
*
|
|
359
|
+
* The type is now what draws it: {@link isTransientForPoll} names this class in
|
|
360
|
+
* its fatal set, and 521-523 fall through to the poll (OPL-3724). Which is also
|
|
361
|
+
* why splitting it mattered more than it looked — the mandala-computer-python
|
|
362
|
+
* SDK had the same pairing and named classes in its fatal set, so a wait helper
|
|
363
|
+
* there could not tell 526 from 522 and spent its whole timeout retrying a
|
|
364
|
+
* certificate.
|
|
365
|
+
*/
|
|
366
|
+
export declare class OriginTLSError extends APIError {
|
|
367
|
+
name: string;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* The sentence a response carried in its own words, or `undefined` for one that
|
|
371
|
+
* carried none — the same test {@link platformNamed} asks, with the answer kept.
|
|
372
|
+
*
|
|
373
|
+
* Exported for the one caller that has to print a message the substitutions
|
|
374
|
+
* above would otherwise have written for it. A tool whose route contradicts the
|
|
375
|
+
* generic wording for a status — `window_action` and a 504, where
|
|
376
|
+
* {@link GatewayTimeoutError}'s tail says the same call again is the move and on
|
|
377
|
+
* that route it is not — cannot append its own paragraph under prose that says
|
|
378
|
+
* the opposite. It needs to know whether the sentence in `message` came from a
|
|
379
|
+
* hop that knew this request, in which case it is worth repeating, or from this
|
|
380
|
+
* file, in which case it is worth replacing (OPL-3910).
|
|
381
|
+
*
|
|
382
|
+
* Reads the BODY rather than the message, deliberately. By the time an error
|
|
383
|
+
* exists the two may differ — that is what the substitutions are — so asking the
|
|
384
|
+
* message whether it is the platform's would be asking the answer to vouch for
|
|
385
|
+
* itself.
|
|
386
|
+
*/
|
|
387
|
+
export declare function platformSaid(body: unknown): string | undefined;
|
|
388
|
+
/** 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;
|
|
390
|
+
/**
|
|
391
|
+
* Whether an error is worth trying again without changing the request.
|
|
392
|
+
*
|
|
393
|
+
* The PUBLIC answer, exported from the package, and therefore a contract with
|
|
394
|
+
* embedders rather than a private note to this file. Its caller is a host
|
|
395
|
+
* application wrapping an arbitrary call in `if (isTransient(err)) retry()` —
|
|
396
|
+
* including one that creates something — so it names only failures that both
|
|
397
|
+
* clear on their own AND are safe to replay blind.
|
|
398
|
+
*
|
|
399
|
+
* Answered by TYPE, with no status numbers at all, which is the OPL-3724
|
|
400
|
+
* decision written down. Three clients had drifted into three mechanisms for
|
|
401
|
+
* one question: this file matched classes plus a list of numbers, the
|
|
402
|
+
* TypeScript SDK matched classes alone, and the Python SDK named the fatal
|
|
403
|
+
* exceptions and retried the rest. The status list is what let this one drift,
|
|
404
|
+
* because a number can be added to it without anyone having to say which of the
|
|
405
|
+
* three answers changed. It now reads identically in all three:
|
|
406
|
+
*
|
|
407
|
+
* - {@link ConflictError} — something is in flight that this cannot run
|
|
408
|
+
* alongside, minus the one that is a decision
|
|
409
|
+
* - {@link RateLimitError} — a cadence, and the response usually says how long
|
|
410
|
+
* - {@link UnavailableError} — a hypervisor briefly out of reach
|
|
411
|
+
* - {@link ConnectivityError} — the request never left
|
|
412
|
+
*
|
|
413
|
+
* That last line is now literally true, and it was not always. The class used
|
|
414
|
+
* to cover every transport rejection, a lost response body included, so this
|
|
415
|
+
* predicate told a caller replaying a create that the platform had not been
|
|
416
|
+
* reached when in fact it had been and the answer was what went missing.
|
|
417
|
+
* {@link ConnectivityInterruptedError} carries that case now and is excluded
|
|
418
|
+
* below — the same decision as the paragraph after this one, applied to the
|
|
419
|
+
* one class it had missed (OPL-3855).
|
|
420
|
+
*
|
|
421
|
+
* 502 and 504 USED to be here and are deliberately gone. The paragraph below
|
|
422
|
+
* had already conceded the point that removes them: both can arrive after the
|
|
423
|
+
* platform has acted, so replaying a `create_computer` through one can leave a
|
|
424
|
+
* second billable computer behind a failure that read as nothing having
|
|
425
|
+
* happened. A status that is not safe for the riskiest caller of an exported
|
|
426
|
+
* predicate does not belong in it. Nothing waits less as a result — the wait
|
|
427
|
+
* tools ask {@link isTransientForPoll}, which still rides both out.
|
|
428
|
+
*
|
|
429
|
+
* "Worth trying again" is not "the call definitely did not happen", and no
|
|
430
|
+
* predicate taking only an error can tell you the second. Even here, a 409 can
|
|
431
|
+
* be answered after a change landed. So retry reads freely, and check before
|
|
432
|
+
* repeating anything that creates something.
|
|
433
|
+
*
|
|
434
|
+
* Nor is a status enough on its own to answer it. 409 is the case: most of them
|
|
435
|
+
* are a passing state, and the move offer is a decision that no retry changes,
|
|
436
|
+
* which is why the check below leads with the type rather than the number. If a
|
|
437
|
+
* second such refusal earns a class, it belongs on that line too.
|
|
438
|
+
*
|
|
439
|
+
* One 409 could not be given a class and could not be seen from here at all: a
|
|
440
|
+
* clipboard read or write against a computer that is STOPPED does not clear on
|
|
441
|
+
* its own — a start is the fix, not another attempt — and nothing in the body
|
|
442
|
+
* told it apart from a conflict that is merely passing. The advice was to read
|
|
443
|
+
* the message, which is prose the platform is free to reword and exactly the
|
|
444
|
+
* matching OPL-3724 got three clients out of. The platform now says which kind
|
|
445
|
+
* it is, so {@link APIError.reason} is consulted BEFORE the types below, and an
|
|
446
|
+
* absent word — or one this version does not know — leaves the type answer
|
|
447
|
+
* standing unchanged (OPL-3898).
|
|
448
|
+
*
|
|
449
|
+
* One refusal is known to sit on the wrong side of that fallback, and it stays
|
|
450
|
+
* there deliberately (OPL-3909). A computer runs at most sixteen background
|
|
451
|
+
* commands, and the request for a seventeenth is refused 409 with no `reason` —
|
|
452
|
+
* correctly, since the slots may be held by servers and the platform will not
|
|
453
|
+
* advise a retry it cannot promise. The type answer therefore stands, and it
|
|
454
|
+
* says yes to something that may never clear. The alternative is to read the
|
|
455
|
+
* platform's sentence, which is the exported-contract version of the mistake
|
|
456
|
+
* OPL-3724 removed from three clients: a predicate that changes its answer when
|
|
457
|
+
* somebody rewords a message. So the next step is given where the sentence can
|
|
458
|
+
* be read safely — in `exec`, which knows it asked for a slot and which prints
|
|
459
|
+
* the platform's own words either way — and this predicate is left honest about
|
|
460
|
+
* what it can and cannot tell apart.
|
|
461
|
+
*/
|
|
462
|
+
export declare function isTransient(err: unknown): boolean;
|
|
463
|
+
/**
|
|
464
|
+
* The same words, a different question: worth POLLING again.
|
|
465
|
+
*
|
|
466
|
+
* Asked only by the wait tools, and deliberately not exported. They replay a
|
|
467
|
+
* `GET /computers/:id`, a `GET /moves`, a build read or an `exec 'exit 0'`
|
|
468
|
+
* probe — every one of them idempotent, every one of them under a deadline the
|
|
469
|
+
* caller set. That pair of properties is what makes this predicate generous,
|
|
470
|
+
* and it is a property of what those calls DO rather than of the error, which
|
|
471
|
+
* is exactly why it cannot be published: the same `true` handed to an embedder
|
|
472
|
+
* retrying a create means something else entirely.
|
|
473
|
+
*
|
|
474
|
+
* DENY-LIST, and the inversion is the second half of the OPL-3724 decision.
|
|
475
|
+
* Where {@link isTransient} names what may be retried, this names what may not
|
|
476
|
+
* and polls through everything else. The polarity follows from who pays for a
|
|
477
|
+
* wrong answer. Retrying something unretryable costs one poll interval and,
|
|
478
|
+
* at worst, the deadline the caller chose. NOT retrying something that would
|
|
479
|
+
* have cleared costs a wait that reports a machine as unreachable while it was
|
|
480
|
+
* coming up — and every status the edge invents next year lands in that second
|
|
481
|
+
* category under an allow-list, silently, until somebody notices and adds a
|
|
482
|
+
* number. This way round, an unmapped 5xx is ridden out, which is what a poll
|
|
483
|
+
* loop was for.
|
|
484
|
+
*
|
|
485
|
+
* The line is REQUEST versus MOMENT. A failure describing the request answers
|
|
486
|
+
* the same way forever and is fatal here; a failure describing the moment is
|
|
487
|
+
* what a poll exists to outlast.
|
|
488
|
+
*
|
|
489
|
+
* Fatal, therefore:
|
|
490
|
+
*
|
|
491
|
+
* - anything that is not a failed REQUEST. That is the floor, and a deny-list
|
|
492
|
+
* needs one: only {@link APIError} and {@link ConnectivityError} describe an
|
|
493
|
+
* exchange with the platform that did not work, and only those can be worth
|
|
494
|
+
* making again. A `TypeError` from a bug in this file is not the platform
|
|
495
|
+
* being slow, and riding one out spends the caller's deadline before
|
|
496
|
+
* reporting the wrong cause.
|
|
497
|
+
*
|
|
498
|
+
* A bare {@link MandalaError} is caught by the same floor, and that is the
|
|
499
|
+
* half worth spelling out. `Api` raises them for a response that arrived and
|
|
500
|
+
* made no sense — "expected JSON from GET /computers/:id, got: <html>" — and
|
|
501
|
+
* a poll loop raises them as verdicts about a poll that SUCCEEDED. Neither is
|
|
502
|
+
* a moment to outlast: one is a defect and the other is an answer. Polling
|
|
503
|
+
* through a verdict is an infinite loop with a deadline on it, which is
|
|
504
|
+
* exactly what the TypeScript SDK's suite caught when this predicate was
|
|
505
|
+
* ported there with `MandalaError` as its floor.
|
|
506
|
+
*
|
|
507
|
+
* {@link ConnectivityInterruptedError} passes that floor and is meant to, on
|
|
508
|
+
* the strength of what the callers here DO. It says the outcome of one
|
|
509
|
+
* request is unknown; every request this predicate guards is a read, and a
|
|
510
|
+
* read whose outcome is unknown can be read again. Fatal to
|
|
511
|
+
* {@link isTransient} and transparent here is the whole point of there being
|
|
512
|
+
* two predicates.
|
|
513
|
+
* - {@link CancelledError} — the caller hung up. Excluded by the floor above,
|
|
514
|
+
* since it is neither, and retrying something nobody is waiting for is what
|
|
515
|
+
* `with(signal)` exists to stop.
|
|
516
|
+
* - {@link MoveRequiredError} — a decision about the size that was asked for.
|
|
517
|
+
* - {@link OriginTLSError} (525, 526) — a certificate the edge and the platform
|
|
518
|
+
* cannot agree on fails identically on every retry, so waiting one out spends
|
|
519
|
+
* the whole deadline to report the wrong cause. Its own message says to go and
|
|
520
|
+
* fix the deployment; this is what makes that true.
|
|
521
|
+
* - 524 — reached only by holding a request open past the edge's ceiling, so an
|
|
522
|
+
* identical retry reproduces it at the same place. It shares
|
|
523
|
+
* {@link GatewayTimeoutError} with 504, which is retryable, and that is why
|
|
524
|
+
* this one status is still matched by NUMBER: the type cannot separate them.
|
|
525
|
+
* - anything below 500 that is not named. A 4xx describes the request — a bad
|
|
526
|
+
* body, a revoked key, a plan limit, a deleted id, an offset past the end of a
|
|
527
|
+
* file — and repeating it unchanged cannot change the answer. Three are named
|
|
528
|
+
* because they describe the moment instead: 409 (something in flight), 429 (a
|
|
529
|
+
* cadence), and 408, which RFC 9110 defines as a request the client may repeat
|
|
530
|
+
* unchanged and which the edge in front of this surface does emit.
|
|
531
|
+
*
|
|
532
|
+
* A 3xx goes with the 4xx, which is why the test is `>= 500` rather than "not
|
|
533
|
+
* a 4xx". `Api` treats every non-2xx as an error and does not follow
|
|
534
|
+
* redirects, so a MANDALA_BASE_URL missing its trailing path answers 301 — and
|
|
535
|
+
* under a 4xx-only rule that was polled until the deadline, ending in a
|
|
536
|
+
* give-up that named nothing about the redirect. The mandala-computer-python
|
|
537
|
+
* SDK found that one; this is the same rule, and it is why all three now say
|
|
538
|
+
* `>= 500`.
|
|
539
|
+
*
|
|
540
|
+
* {@link APIError.reason} is deliberately NOT consulted here, and that is the
|
|
541
|
+
* one place this predicate and {@link isTransient} part company (OPL-3898).
|
|
542
|
+
* `unavailable` means the computer is not running, which is a permanent answer
|
|
543
|
+
* to whoever asked — and a poll under a deadline is the one caller for whom it
|
|
544
|
+
* may not be, since a computer coming up passes through it. The same generosity
|
|
545
|
+
* as every unmapped 5xx below, for the same reason: this only ever replays a
|
|
546
|
+
* read, and the loops above return a refusal of their own the moment the status
|
|
547
|
+
* they are watching says stopped or suspended. mandala-computer-python's
|
|
548
|
+
* `_is_transient_for_poll` draws the line in the same place.
|
|
549
|
+
*
|
|
550
|
+
* Everything at 5xx polls through, 502 and 520-523 included: they mean the
|
|
551
|
+
* outcome is unknown, and a read whose outcome is unknown can simply be read
|
|
552
|
+
* again.
|
|
553
|
+
*
|
|
554
|
+
* 5xx has an upper bound as well as a lower one, and it is not decoration. The
|
|
555
|
+
* HTTP parser under `fetch` accepts any three digits, so a broken or hostile
|
|
556
|
+
* origin can answer 700 — which `>= 500` alone called a passing moment and
|
|
557
|
+
* polled until the caller's deadline (Codex adversarial review, OPL-3724).
|
|
558
|
+
*/
|
|
559
|
+
export declare function isTransientForPoll(err: unknown): boolean;
|
|
560
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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;IAjBhB,IAAI,SAAc;IAC3B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEvB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,YAAA;CAK1B;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAa3E;AAkBD,uDAAuD;AACvD,qBAAa,mBAAoB,SAAQ,QAAQ;IACtC,IAAI,SAAyB;CACvC;AAED,iEAAiE;AACjE,qBAAa,cAAe,SAAQ,QAAQ;IACjC,IAAI,SAAoB;CAClC;AAED,qEAAqE;AACrE,qBAAa,qBAAsB,SAAQ,QAAQ;IACxC,IAAI,SAA2B;CACzC;AAED,kDAAkD;AAClD,qBAAa,aAAc,SAAQ,QAAQ;IAChC,IAAI,SAAmB;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,aAAc,SAAQ,QAAQ;IAChC,IAAI,SAAmB;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,aAAa;IAMhD,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,OAAO;IANvB,IAAI,SAAuB;gBAElC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO;IACb,2EAA2E;IAClE,YAAY,EAAE,OAAO;CAIjC;AAoBD;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAyB,SAAQ,QAAQ;IAMlD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;IANf,IAAI,SAA8B;gBAEzC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO;IACd,+EAA+E;IACtE,IAAI,CAAC,EAAE,MAAM,YAAA;CAIzB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,QAAQ;IAMxC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM;IANvB,IAAI,SAAoB;gBAE/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO;IACd,gFAAgF;IACvE,YAAY,CAAC,EAAE,MAAM,YAAA;CAIjC;AAED,+EAA+E;AAC/E,qBAAa,gBAAiB,SAAQ,QAAQ;IACnC,IAAI,SAAsB;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAe,SAAQ,YAAY;IACrC,IAAI,SAAoB;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACxC,IAAI,SAAuB;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,4BAA6B,SAAQ,iBAAiB;IACxD,IAAI,SAAkC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,mBAAoB,SAAQ,QAAQ;IACtC,IAAI,SAAyB;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,sBAAuB,SAAQ,QAAQ;IACzC,IAAI,SAA4B;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,mBAAoB,SAAQ,QAAQ;IACtC,IAAI,SAAyB;CACvC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAe,SAAQ,QAAQ;IACjC,IAAI,SAAoB;CAClC;AA2KD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI9D;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAwCxF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA0BjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUxD"}
|