mandala-computer-mcp 0.11.0 → 0.13.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 +159 -39
- package/dist/api.d.ts +9 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +15 -4
- package/dist/api.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/credits.d.ts +187 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +119 -0
- package/dist/credits.js.map +1 -0
- package/dist/errors.d.ts +92 -26
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +206 -35
- package/dist/errors.js.map +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +30 -8
- package/dist/format.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +82 -8
- package/dist/http.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/paths.d.ts +42 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +46 -0
- package/dist/paths.js.map +1 -1
- package/dist/poll.d.ts +1 -1
- package/dist/poll.js +1 -1
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +129 -5
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +20 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +5 -0
- package/dist/session.js.map +1 -1
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +11 -1
- package/dist/stdio.js.map +1 -1
- package/dist/tool-filters.d.ts +3 -2
- package/dist/tool-filters.d.ts.map +1 -1
- package/dist/tool-filters.js +3 -1
- package/dist/tool-filters.js.map +1 -1
- package/dist/tools/account.d.ts.map +1 -1
- package/dist/tools/account.js +10 -3
- package/dist/tools/account.js.map +1 -1
- package/dist/tools/activities.d.ts.map +1 -1
- package/dist/tools/activities.js +4 -0
- package/dist/tools/activities.js.map +1 -1
- package/dist/tools/agent.d.ts +12 -4
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +292 -132
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/chat.d.ts +11 -2
- package/dist/tools/chat.d.ts.map +1 -1
- package/dist/tools/chat.js +19 -9
- package/dist/tools/chat.js.map +1 -1
- package/dist/tools/computers.d.ts.map +1 -1
- package/dist/tools/computers.js +92 -11
- package/dist/tools/computers.js.map +1 -1
- package/dist/tools/credits.d.ts +9 -0
- package/dist/tools/credits.d.ts.map +1 -0
- package/dist/tools/credits.js +53 -0
- package/dist/tools/credits.js.map +1 -0
- package/dist/tools/guest.d.ts.map +1 -1
- package/dist/tools/guest.js +6 -7
- package/dist/tools/guest.js.map +1 -1
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +51 -18
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/operations.js +2 -2
- package/dist/tools/operations.js.map +1 -1
- package/dist/tools/publicurls.d.ts +3 -0
- package/dist/tools/publicurls.d.ts.map +1 -0
- package/dist/tools/publicurls.js +287 -0
- package/dist/tools/publicurls.js.map +1 -0
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +1 -0
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/ssh.d.ts.map +1 -1
- package/dist/tools/ssh.js +4 -2
- package/dist/tools/ssh.js.map +1 -1
- package/dist/tools/webhooks.d.ts.map +1 -1
- package/dist/tools/webhooks.js +32 -23
- package/dist/tools/webhooks.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/errors.d.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* The distinctions here are the ones an agent has to act on and cannot infer
|
|
5
5
|
* from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
|
|
6
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:
|
|
8
|
-
* passing state and worth retrying,
|
|
9
|
-
* that no retry turns into a
|
|
10
|
-
* {@link MoveRequiredError}.
|
|
7
|
+
* has to resolve. A 409 is the one that is not uniform: some of them are a
|
|
8
|
+
* passing state and worth retrying, which the platform says with a `reason`,
|
|
9
|
+
* and the rest are a decision about the request that no retry turns into a
|
|
10
|
+
* yes — see {@link ConflictError} and {@link MoveRequiredError}.
|
|
11
11
|
*
|
|
12
12
|
* Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
|
|
13
13
|
* deliberately so: two clients disagreeing about what a 402 is means the same
|
|
@@ -50,6 +50,13 @@ export declare class APIError extends MandalaError {
|
|
|
50
50
|
* the caller who arrived a moment earlier heard.
|
|
51
51
|
*/
|
|
52
52
|
readonly reason?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The platform's machine-readable `code` beside `error`, where it sent one:
|
|
55
|
+
* `LABEL_TAKEN`, `WORKSPACE_MOVING`, `idempotency_in_progress` and so on.
|
|
56
|
+
* Top-level only, and only a nonblank string; `undefined` otherwise. Like
|
|
57
|
+
* `reason`, it is the word to switch on, never the sentence.
|
|
58
|
+
*/
|
|
59
|
+
readonly code?: string;
|
|
53
60
|
/** Response header first, then the top-level body ID; never an idempotency key. */
|
|
54
61
|
readonly requestId?: string;
|
|
55
62
|
/** The received Allow header, when supplied. */
|
|
@@ -108,6 +115,32 @@ export declare function reasonKind(reason: string | undefined): ReasonKind | und
|
|
|
108
115
|
* platform's "absent means unclassified" contract exists to prevent.
|
|
109
116
|
*/
|
|
110
117
|
export declare function reasonAdvice(reason: string | undefined): string | undefined;
|
|
118
|
+
/** Whether a 409 carries a code the platform documents as a passing state. */
|
|
119
|
+
export declare function conflictCodeClears(err: APIError): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* What to tell a model about a refusal the platform named with a `code`, or
|
|
122
|
+
* `undefined` for a code with nothing to add. Ahead of {@link statusAdvice} in
|
|
123
|
+
* the formatter, because these codes are more specific than the status:
|
|
124
|
+
* `PUBLIC_URLS_BLOCKED` is a 403 that is neither a role nor a suspended
|
|
125
|
+
* account, and `PUBLIC_URLS_DISABLED` is a 404 that is not a missing computer.
|
|
126
|
+
*/
|
|
127
|
+
export declare function codeAdvice(err: APIError): string | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* A 409 the platform did not classify: no `reason` this version knows, no code
|
|
130
|
+
* from {@link codeAdvice} or {@link CODE_CLEARS}, and none of the 409s that
|
|
131
|
+
* carry their own class and say what they are. Not a passing state
|
|
132
|
+
* (OPL-5975): the platform says absent means no classification.
|
|
133
|
+
*/
|
|
134
|
+
export declare function unclassifiedConflict(err: unknown): boolean;
|
|
135
|
+
/** What a model is told about an {@link unclassifiedConflict}. */
|
|
136
|
+
export declare const UNCLASSIFIED_CONFLICT_ADVICE = "the platform did not say whether this clears, so treat it as one that does not: do not send it again unchanged. The message usually says what to do instead";
|
|
137
|
+
/**
|
|
138
|
+
* Whether this is the keyed call's "still running" refusal as the platform
|
|
139
|
+
* documents it: a 409 with code `idempotency_in_progress` that the retry
|
|
140
|
+
* predicate calls transient. A permanent word beside the code, or another
|
|
141
|
+
* status, is not that refusal, and earns no "send it again with this key".
|
|
142
|
+
*/
|
|
143
|
+
export declare function idempotencyInProgress(err: APIError): boolean;
|
|
111
144
|
/** The platform's `code` on a keyed call's own refusals (OPL-5127), or `undefined`. */
|
|
112
145
|
export declare function idempotencyCode(err: APIError): string | undefined;
|
|
113
146
|
/**
|
|
@@ -240,8 +273,38 @@ export declare function statusAdvice(status: number | undefined, reason?: string
|
|
|
240
273
|
* `undefined` for a status with nothing to add, so the caller keeps its own
|
|
241
274
|
* wording. A 401 without `revoked` is left to {@link authenticationAdvice},
|
|
242
275
|
* which already says it cannot tell the account key from the model key.
|
|
276
|
+
*
|
|
277
|
+
* The one 402 that is Mandala's own is a run on the account's AI credits that
|
|
278
|
+
* could not reserve its first model call (OPL-5866), and the platform says so
|
|
279
|
+
* by naming a top-up page. `topUp` is that page, resolved; given, the 402 is
|
|
280
|
+
* worded as credits run out rather than as the model provider's billing error,
|
|
281
|
+
* because the fix is on the Mandala billing page and not on a provider account.
|
|
282
|
+
*/
|
|
283
|
+
export declare function agentRunAdvice(status: number | undefined, reason: string | undefined, tool: string, topUp?: string): string | undefined;
|
|
284
|
+
/**
|
|
285
|
+
* A run on the account's AI credits refused because they cannot cover it.
|
|
286
|
+
*
|
|
287
|
+
* Not the model provider, not the plan and not the computer, and waiting does
|
|
288
|
+
* not change it: the account's balance is what it is until a person tops it up
|
|
289
|
+
* or a model key is configured. Said so a model reports it rather than retrying.
|
|
290
|
+
*/
|
|
291
|
+
export declare function creditsExhaustedAdvice(topUp: string, tool: string): string;
|
|
292
|
+
/**
|
|
293
|
+
* The platform's own fixed words for its AI service failing a run on credits,
|
|
294
|
+
* over the model provider's status. The provider's text never leaves the
|
|
295
|
+
* platform on such a run, so this sentence is how the failure is recognised.
|
|
296
|
+
*/
|
|
297
|
+
export declare const FUNDED_UNAVAILABLE = "Mandala AI is unavailable";
|
|
298
|
+
/**
|
|
299
|
+
* A run on AI credits that the platform's AI service could not serve.
|
|
300
|
+
*
|
|
301
|
+
* Its status is the model provider's, answering Mandala's own key, so neither
|
|
302
|
+
* of the usual readings fits: a 401 or 403 is not the caller's credential or
|
|
303
|
+
* role, a 404 not the computer, a 413 not the request, a 402 not the plan, and
|
|
304
|
+
* there is no model key of the caller's to check. Steps already taken stand and
|
|
305
|
+
* were charged, so running the same prompt again would repeat them.
|
|
243
306
|
*/
|
|
244
|
-
export declare function
|
|
307
|
+
export declare function fundedUnavailableAdvice(tool: string): string;
|
|
245
308
|
/** 401 — the key is missing, malformed, or revoked. */
|
|
246
309
|
export declare class AuthenticationError extends APIError {
|
|
247
310
|
name: string;
|
|
@@ -265,9 +328,10 @@ export declare class MethodNotAllowedError extends APIError {
|
|
|
265
328
|
/**
|
|
266
329
|
* 409 — the thing exists but is in the wrong state for this right now.
|
|
267
330
|
*
|
|
268
|
-
* The one status on this list that is
|
|
269
|
-
* a guest agent busy with another call, a desktop session that has not
|
|
270
|
-
* yet.
|
|
331
|
+
* The one status on this list that is SOMETIMES temporary: a guest still
|
|
332
|
+
* booting, a guest agent busy with another call, a desktop session that has not
|
|
333
|
+
* come up yet. Those are worth retrying, and the platform says so with a
|
|
334
|
+
* `reason`; one without a word is not (OPL-5975, see {@link isTransient}).
|
|
271
335
|
*
|
|
272
336
|
* USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
|
|
273
337
|
* 409 clears is a property of the BODY and not of the status: some describe a
|
|
@@ -284,9 +348,11 @@ export declare class MethodNotAllowedError extends APIError {
|
|
|
284
348
|
* with it. The rest stay here and are told apart by {@link APIError.reason},
|
|
285
349
|
* which is the word the platform added for that purpose (OPL-3898) and the one
|
|
286
350
|
* its own reference now says to switch on — never the sentence, which is prose
|
|
287
|
-
* written for a person and rewritten whenever a better one is
|
|
288
|
-
*
|
|
289
|
-
*
|
|
351
|
+
* written for a person and rewritten whenever a better one is — or by
|
|
352
|
+
* {@link APIError.code} where the platform documents a code as passing. Where
|
|
353
|
+
* neither says so, the refusal is unclassified and {@link isTransient} says no
|
|
354
|
+
* (OPL-5975): not every refusal here has an answer yet, and an unanswered one
|
|
355
|
+
* is not a promise that waiting helps.
|
|
290
356
|
*/
|
|
291
357
|
export declare class ConflictError extends APIError {
|
|
292
358
|
name: string;
|
|
@@ -719,8 +785,10 @@ export declare function errorForStatus(status: number, message: string, body?: u
|
|
|
719
785
|
* because a number can be added to it without anyone having to say which of the
|
|
720
786
|
* three answers changed. It now reads identically in all three:
|
|
721
787
|
*
|
|
722
|
-
* - {@link ConflictError} —
|
|
723
|
-
*
|
|
788
|
+
* - {@link ConflictError} — only where the platform says it is passing: a
|
|
789
|
+
* `reason` of `contention` or `starting`, or a code it documents as
|
|
790
|
+
* passing (`idempotency_in_progress`, `WORKSPACE_MOVING`). A 409 with
|
|
791
|
+
* neither is unclassified and not transient (OPL-5975)
|
|
724
792
|
* - {@link RateLimitError} — a cadence, and the response usually says how long
|
|
725
793
|
* - {@link UnavailableError} — something briefly out of reach, for a GET or
|
|
726
794
|
* HEAD only. Any change answered 503 may have happened, so it is not
|
|
@@ -748,7 +816,7 @@ export declare function errorForStatus(status: number, message: string, body?: u
|
|
|
748
816
|
* be answered after a change landed. So retry reads freely, and check before
|
|
749
817
|
* repeating anything that creates something.
|
|
750
818
|
*
|
|
751
|
-
* Nor is a status enough on its own to answer it. 409 is the case:
|
|
819
|
+
* Nor is a status enough on its own to answer it. 409 is the case: some of them
|
|
752
820
|
* are a passing state, and the move offer is a decision that no retry changes,
|
|
753
821
|
* which is why the check below leads with the type rather than the number. If a
|
|
754
822
|
* second such refusal earns a class, it belongs on that line too.
|
|
@@ -763,18 +831,16 @@ export declare function errorForStatus(status: number, message: string, body?: u
|
|
|
763
831
|
* absent word — or one this version does not know — leaves the type answer
|
|
764
832
|
* standing unchanged (OPL-3898).
|
|
765
833
|
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
* the platform's own words either way — and this predicate is left honest about
|
|
777
|
-
* what it can and cannot tell apart.
|
|
834
|
+
* And the fallback for a 409 with no word is NO, not yes (OPL-5975). It used
|
|
835
|
+
* to be yes — "a 409 is usually passing" — and that said yes to every refusal
|
|
836
|
+
* the platform had not classified: a label that is taken, a computer at its
|
|
837
|
+
* public URL limit, the webhook cap, an SSH key already registered, the
|
|
838
|
+
* seventeenth background command (OPL-3909, whose slots may be held by servers
|
|
839
|
+
* and never free). The platform's reference is explicit that an absent
|
|
840
|
+
* `reason` is no classification and that treating 409 as uniformly transient
|
|
841
|
+
* loops forever. So a 409 is transient here only when its word clears or its
|
|
842
|
+
* code is one the platform documents as passing ({@link conflictCodeClears}),
|
|
843
|
+
* and the same classification holds in both SDKs.
|
|
778
844
|
*/
|
|
779
845
|
export declare function isTransient(err: unknown): boolean;
|
|
780
846
|
/**
|
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,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,qBAAa,QAAS,SAAQ,YAAY;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* The distinctions here are the ones an agent has to act on and cannot infer
|
|
5
5
|
* from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
|
|
6
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:
|
|
8
|
-
* passing state and worth retrying,
|
|
9
|
-
* that no retry turns into a
|
|
10
|
-
* {@link MoveRequiredError}.
|
|
7
|
+
* has to resolve. A 409 is the one that is not uniform: some of them are a
|
|
8
|
+
* passing state and worth retrying, which the platform says with a `reason`,
|
|
9
|
+
* and the rest are a decision about the request that no retry turns into a
|
|
10
|
+
* yes — see {@link ConflictError} and {@link MoveRequiredError}.
|
|
11
11
|
*
|
|
12
12
|
* Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
|
|
13
13
|
* deliberately so: two clients disagreeing about what a 402 is means the same
|
|
@@ -38,6 +38,13 @@ export class APIError extends MandalaError {
|
|
|
38
38
|
* the caller who arrived a moment earlier heard.
|
|
39
39
|
*/
|
|
40
40
|
reason;
|
|
41
|
+
/**
|
|
42
|
+
* The platform's machine-readable `code` beside `error`, where it sent one:
|
|
43
|
+
* `LABEL_TAKEN`, `WORKSPACE_MOVING`, `idempotency_in_progress` and so on.
|
|
44
|
+
* Top-level only, and only a nonblank string; `undefined` otherwise. Like
|
|
45
|
+
* `reason`, it is the word to switch on, never the sentence.
|
|
46
|
+
*/
|
|
47
|
+
code;
|
|
41
48
|
/** Response header first, then the top-level body ID; never an idempotency key. */
|
|
42
49
|
requestId;
|
|
43
50
|
/** The received Allow header, when supplied. */
|
|
@@ -54,6 +61,7 @@ export class APIError extends MandalaError {
|
|
|
54
61
|
this.body = body;
|
|
55
62
|
this.retryAfterMs = retryAfterMs;
|
|
56
63
|
this.reason = refusalReason(body);
|
|
64
|
+
this.code = record(body) ? nonblank(body.code) : undefined;
|
|
57
65
|
this.requestId =
|
|
58
66
|
nonblank(metadata?.requestId) ?? (record(body) ? nonblank(body.request_id) : undefined);
|
|
59
67
|
this.allow = nonblank(metadata?.allow);
|
|
@@ -117,6 +125,8 @@ const REASON_PERMANENT = new Set([
|
|
|
117
125
|
'running',
|
|
118
126
|
'name_taken',
|
|
119
127
|
'stale_revision',
|
|
128
|
+
// An evicted screenshot cannot be recovered by replaying its capture name.
|
|
129
|
+
'stale_capture',
|
|
120
130
|
]);
|
|
121
131
|
/**
|
|
122
132
|
* How a refusal the platform classified behaves, or `undefined` for a word this
|
|
@@ -188,7 +198,11 @@ export function reasonAdvice(reason) {
|
|
|
188
198
|
// reason code, not a computer — so it names the call that CAN.
|
|
189
199
|
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';
|
|
190
200
|
case 'unsupported':
|
|
191
|
-
|
|
201
|
+
// Subject-neutral (OPL-5975). It said "this computer cannot do it at
|
|
202
|
+
// all … the same forever", and the platform sends this word for refusals
|
|
203
|
+
// that are about no computer and that a step clears: a workspace that
|
|
204
|
+
// still holds computers or secrets, a workspace change still unsettled.
|
|
205
|
+
return 'waiting will not change this answer: either it cannot be done at all, or it needs another step first, which the message names. Do not send it again unchanged; take that step, if there is one, and then send it';
|
|
192
206
|
case 'exists':
|
|
193
207
|
// Only THIS attempt is known to have written nothing: a create-only
|
|
194
208
|
// write whose earlier attempt lost its answer may have written the file
|
|
@@ -214,6 +228,108 @@ export function reasonAdvice(reason) {
|
|
|
214
228
|
return undefined;
|
|
215
229
|
}
|
|
216
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* The `code`s that mark a 409 as a passing state when its `reason` does not
|
|
233
|
+
* say otherwise (OPL-5975), taken from the platform's own words for each:
|
|
234
|
+
*
|
|
235
|
+
* - `idempotency_in_progress`: the call with this key is still being carried
|
|
236
|
+
* out; send it again shortly for its answer. The platform sends it with
|
|
237
|
+
* `reason: "contention"` as well, which already says so; the code is named
|
|
238
|
+
* so that it is still read as passing if the word is ever missing.
|
|
239
|
+
* - `WORKSPACE_MOVING`: the computer's workspace change is unsettled; try
|
|
240
|
+
* again once the move has finished. Sent with `reason: "contention"` while
|
|
241
|
+
* the move is settling, and `reason: "unsupported"` when it is stuck and
|
|
242
|
+
* needs checking — the word, read first, decides those two.
|
|
243
|
+
*
|
|
244
|
+
* Every other 409 without a reason the platform documents as passing is NOT
|
|
245
|
+
* a passing state: the platform says an absent `reason` is no
|
|
246
|
+
* classification, and a client that treats 409 as uniformly transient "loops
|
|
247
|
+
* forever on half of them". That includes the ones that carry a code and no
|
|
248
|
+
* word — `LABEL_TAKEN`, `PUBLIC_URL_LIMIT` — and the ones that carry neither:
|
|
249
|
+
* the webhook cap, a test of a disabled webhook, an SSH key already
|
|
250
|
+
* registered, the SSH key limit, the sixteen background-command slots.
|
|
251
|
+
*/
|
|
252
|
+
const CODE_CLEARS = new Set(['idempotency_in_progress', 'WORKSPACE_MOVING']);
|
|
253
|
+
/** Whether a 409 carries a code the platform documents as a passing state. */
|
|
254
|
+
export function conflictCodeClears(err) {
|
|
255
|
+
return err.code !== undefined && CODE_CLEARS.has(err.code);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* What to tell a model about a refusal the platform named with a `code`, or
|
|
259
|
+
* `undefined` for a code with nothing to add. Ahead of {@link statusAdvice} in
|
|
260
|
+
* the formatter, because these codes are more specific than the status:
|
|
261
|
+
* `PUBLIC_URLS_BLOCKED` is a 403 that is neither a role nor a suspended
|
|
262
|
+
* account, and `PUBLIC_URLS_DISABLED` is a 404 that is not a missing computer.
|
|
263
|
+
*/
|
|
264
|
+
export function codeAdvice(err) {
|
|
265
|
+
// Each code on the one status the platform sends it with. The same code on
|
|
266
|
+
// another status is not the refusal this advice describes, and the status's
|
|
267
|
+
// own advice — a 401 to 405 is never a retry — is left to speak.
|
|
268
|
+
if (CODE_STATUS[err.code ?? ''] !== err.status)
|
|
269
|
+
return undefined;
|
|
270
|
+
// The word first, as in isTransient: a reason this version knows, beside any
|
|
271
|
+
// code but WORKSPACE_MOVING (whose advice reads the word itself), is the
|
|
272
|
+
// more specific answer, and its own advice is given instead.
|
|
273
|
+
if (err.code !== 'WORKSPACE_MOVING' && reasonKind(err.reason) !== undefined)
|
|
274
|
+
return undefined;
|
|
275
|
+
switch (err.code) {
|
|
276
|
+
case 'LABEL_TAKEN':
|
|
277
|
+
return 'that label is taken, and this does not clear by waiting. A label is never given to a second account, so one another account has ever held, or one the platform has blocked, reads as taken, as does one a URL of this account holds now (list_public_urls shows those). Choose another label, or leave label out to have one picked';
|
|
278
|
+
case 'PUBLIC_URL_LIMIT':
|
|
279
|
+
return 'this computer already has as many public URLs as its plan allows, and this does not clear by waiting. Delete one it no longer needs (list_public_urls), point an existing one at this port with update_public_url, or say the plan is the limit';
|
|
280
|
+
case 'PUBLIC_URLS_BLOCKED':
|
|
281
|
+
return 'the platform has turned public URLs off for this computer or this account. It is not a role, a credential or anything wrong with the computer, and neither retrying nor waiting changes it: say so to the user, who can contact support if they think it is a mistake';
|
|
282
|
+
case 'PUBLIC_URLS_DISABLED':
|
|
283
|
+
return 'public URLs are not enabled here at all. This is not a missing computer or URL, and waiting does not change it: say that public URLs are unavailable';
|
|
284
|
+
case 'WORKSPACE_MOVING':
|
|
285
|
+
// The word decides, as it does in isTransient: `unsupported` is the
|
|
286
|
+
// stuck change, which a step clears. Otherwise "send it again" is said
|
|
287
|
+
// only where isTransient agrees — never over a permanent word, a nested
|
|
288
|
+
// word (which grants no retry), or anything else it calls final.
|
|
289
|
+
if (err.reason === 'unsupported')
|
|
290
|
+
return 'this computer\u2019s workspace change is not settled, and waiting alone will not settle it. Repeating the same move_computer_to_workspace call reconciles it, or check the workspace change in the dashboard; then send this again';
|
|
291
|
+
if (!isTransient(err))
|
|
292
|
+
return undefined;
|
|
293
|
+
return 'this computer is being moved to another workspace; this clears once the move has finished, so wait a moment and send it again';
|
|
294
|
+
default:
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/** The status each code in {@link codeAdvice} arrives with. */
|
|
299
|
+
const CODE_STATUS = {
|
|
300
|
+
LABEL_TAKEN: 409,
|
|
301
|
+
PUBLIC_URL_LIMIT: 409,
|
|
302
|
+
PUBLIC_URLS_BLOCKED: 403,
|
|
303
|
+
PUBLIC_URLS_DISABLED: 404,
|
|
304
|
+
WORKSPACE_MOVING: 409,
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* A 409 the platform did not classify: no `reason` this version knows, no code
|
|
308
|
+
* from {@link codeAdvice} or {@link CODE_CLEARS}, and none of the 409s that
|
|
309
|
+
* carry their own class and say what they are. Not a passing state
|
|
310
|
+
* (OPL-5975): the platform says absent means no classification.
|
|
311
|
+
*/
|
|
312
|
+
export function unclassifiedConflict(err) {
|
|
313
|
+
return (err instanceof ConflictError &&
|
|
314
|
+
!(err instanceof MoveRequiredError) &&
|
|
315
|
+
!(err instanceof FileExistsError) &&
|
|
316
|
+
!(err instanceof CreateOnlyConflictError) &&
|
|
317
|
+
reasonKind(err.reason) === undefined &&
|
|
318
|
+
!conflictCodeClears(err) &&
|
|
319
|
+
codeAdvice(err) === undefined &&
|
|
320
|
+
idempotencyCode(err) === undefined);
|
|
321
|
+
}
|
|
322
|
+
/** What a model is told about an {@link unclassifiedConflict}. */
|
|
323
|
+
export const UNCLASSIFIED_CONFLICT_ADVICE = 'the platform did not say whether this clears, so treat it as one that does not: do not send it again unchanged. The message usually says what to do instead';
|
|
324
|
+
/**
|
|
325
|
+
* Whether this is the keyed call's "still running" refusal as the platform
|
|
326
|
+
* documents it: a 409 with code `idempotency_in_progress` that the retry
|
|
327
|
+
* predicate calls transient. A permanent word beside the code, or another
|
|
328
|
+
* status, is not that refusal, and earns no "send it again with this key".
|
|
329
|
+
*/
|
|
330
|
+
export function idempotencyInProgress(err) {
|
|
331
|
+
return (err.status === 409 && idempotencyCode(err) === 'idempotency_in_progress' && isTransient(err));
|
|
332
|
+
}
|
|
217
333
|
/** The platform's `code` on a keyed call's own refusals (OPL-5127), or `undefined`. */
|
|
218
334
|
export function idempotencyCode(err) {
|
|
219
335
|
const code = record(err.body) ? err.body.code : undefined;
|
|
@@ -239,6 +355,11 @@ export function idempotencyAdvice(err, tool) {
|
|
|
239
355
|
: undefined;
|
|
240
356
|
switch (idempotencyCode(err)) {
|
|
241
357
|
case 'idempotency_in_progress':
|
|
358
|
+
// Only the passing refusal the platform documents: a word beside the
|
|
359
|
+
// code that does not clear, or another status, is answered by its own
|
|
360
|
+
// advice instead (Codex, round 4 on #213).
|
|
361
|
+
if (!idempotencyInProgress(err))
|
|
362
|
+
return undefined;
|
|
242
363
|
return `the same call with this idempotency_key is still running; wait and call again with the same idempotency_key for its answer${operation ? `, or read get_operation ${operation}` : ''}`;
|
|
243
364
|
case 'idempotency_outcome_unknown':
|
|
244
365
|
return `the platform did not hear how the call with this idempotency_key ended; do not send it again with this key, since the same key answers the same thing. ${resendAfterSpentKey(tool, operation, undefined, tool ? `call ${tool} again` : undefined)}`;
|
|
@@ -380,8 +501,7 @@ export function resendOnlyOnceFinal(operation, key, resend = 'send the call agai
|
|
|
380
501
|
export function keyedOutcomeUnknown(err) {
|
|
381
502
|
return (err instanceof ConnectivityInterruptedError ||
|
|
382
503
|
(err instanceof APIError &&
|
|
383
|
-
(
|
|
384
|
-
(err.status >= 500 && !platformAnsweredFailure(err)))));
|
|
504
|
+
(idempotencyInProgress(err) || (err.status >= 500 && !platformAnsweredFailure(err)))));
|
|
385
505
|
}
|
|
386
506
|
/**
|
|
387
507
|
* Whether a `5xx` came from the platform itself rather than from a hop in
|
|
@@ -465,8 +585,16 @@ export function statusAdvice(status, reason) {
|
|
|
465
585
|
* `undefined` for a status with nothing to add, so the caller keeps its own
|
|
466
586
|
* wording. A 401 without `revoked` is left to {@link authenticationAdvice},
|
|
467
587
|
* which already says it cannot tell the account key from the model key.
|
|
588
|
+
*
|
|
589
|
+
* The one 402 that is Mandala's own is a run on the account's AI credits that
|
|
590
|
+
* could not reserve its first model call (OPL-5866), and the platform says so
|
|
591
|
+
* by naming a top-up page. `topUp` is that page, resolved; given, the 402 is
|
|
592
|
+
* worded as credits run out rather than as the model provider's billing error,
|
|
593
|
+
* because the fix is on the Mandala billing page and not on a provider account.
|
|
468
594
|
*/
|
|
469
|
-
export function agentRunAdvice(status, reason, tool) {
|
|
595
|
+
export function agentRunAdvice(status, reason, tool, topUp) {
|
|
596
|
+
if (status === 402 && topUp)
|
|
597
|
+
return creditsExhaustedAdvice(topUp, tool);
|
|
470
598
|
const noReplay = `Do not call ${tool} again with the same task: completed steps were already billed, so read what they did first`;
|
|
471
599
|
if (status === 403 && reason === 'revoked') {
|
|
472
600
|
return `this Mandala account no longer permits the run — a role that changed or an account suspended, not anything wrong with the computer. Nothing you can send changes that, and a new credential does not either: say what was refused and stop. ${noReplay}`;
|
|
@@ -485,6 +613,34 @@ export function agentRunAdvice(status, reason, tool) {
|
|
|
485
613
|
return undefined;
|
|
486
614
|
return `this is the model provider's own status for the configured model key (its ${provider}), not the Mandala plan and not the computer — check the model-provider account${status === 402 ? '' : ' and its status page, and try later'}. ${noReplay}`;
|
|
487
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* A run on the account's AI credits refused because they cannot cover it.
|
|
618
|
+
*
|
|
619
|
+
* Not the model provider, not the plan and not the computer, and waiting does
|
|
620
|
+
* not change it: the account's balance is what it is until a person tops it up
|
|
621
|
+
* or a model key is configured. Said so a model reports it rather than retrying.
|
|
622
|
+
*/
|
|
623
|
+
export function creditsExhaustedAdvice(topUp, tool) {
|
|
624
|
+
return `this Mandala account's AI credits cannot cover the run — not the model provider, the plan or the computer. Waiting does not change it: a person can top up at ${topUp}, or configure their own model key. Say so and do not call ${tool} again until then`;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* The platform's own fixed words for its AI service failing a run on credits,
|
|
628
|
+
* over the model provider's status. The provider's text never leaves the
|
|
629
|
+
* platform on such a run, so this sentence is how the failure is recognised.
|
|
630
|
+
*/
|
|
631
|
+
export const FUNDED_UNAVAILABLE = 'Mandala AI is unavailable';
|
|
632
|
+
/**
|
|
633
|
+
* A run on AI credits that the platform's AI service could not serve.
|
|
634
|
+
*
|
|
635
|
+
* Its status is the model provider's, answering Mandala's own key, so neither
|
|
636
|
+
* of the usual readings fits: a 401 or 403 is not the caller's credential or
|
|
637
|
+
* role, a 404 not the computer, a 413 not the request, a 402 not the plan, and
|
|
638
|
+
* there is no model key of the caller's to check. Steps already taken stand and
|
|
639
|
+
* were charged, so running the same prompt again would repeat them.
|
|
640
|
+
*/
|
|
641
|
+
export function fundedUnavailableAdvice(tool) {
|
|
642
|
+
return `the platform's AI service could not serve this run on credits — its model provider answered this status, which is not the computer, your credential or the plan, and there is no key of yours to check. Waiting can clear it. If no step ran, ${tool} can be tried again later; if steps ran, they stand and were charged, so read what they did and send only what is left`;
|
|
643
|
+
}
|
|
488
644
|
/**
|
|
489
645
|
* The platform's one-word classification off a refusal body, or `undefined`.
|
|
490
646
|
*
|
|
@@ -527,9 +683,10 @@ export class MethodNotAllowedError extends APIError {
|
|
|
527
683
|
/**
|
|
528
684
|
* 409 — the thing exists but is in the wrong state for this right now.
|
|
529
685
|
*
|
|
530
|
-
* The one status on this list that is
|
|
531
|
-
* a guest agent busy with another call, a desktop session that has not
|
|
532
|
-
* yet.
|
|
686
|
+
* The one status on this list that is SOMETIMES temporary: a guest still
|
|
687
|
+
* booting, a guest agent busy with another call, a desktop session that has not
|
|
688
|
+
* come up yet. Those are worth retrying, and the platform says so with a
|
|
689
|
+
* `reason`; one without a word is not (OPL-5975, see {@link isTransient}).
|
|
533
690
|
*
|
|
534
691
|
* USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
|
|
535
692
|
* 409 clears is a property of the BODY and not of the status: some describe a
|
|
@@ -546,9 +703,11 @@ export class MethodNotAllowedError extends APIError {
|
|
|
546
703
|
* with it. The rest stay here and are told apart by {@link APIError.reason},
|
|
547
704
|
* which is the word the platform added for that purpose (OPL-3898) and the one
|
|
548
705
|
* its own reference now says to switch on — never the sentence, which is prose
|
|
549
|
-
* written for a person and rewritten whenever a better one is
|
|
550
|
-
*
|
|
551
|
-
*
|
|
706
|
+
* written for a person and rewritten whenever a better one is — or by
|
|
707
|
+
* {@link APIError.code} where the platform documents a code as passing. Where
|
|
708
|
+
* neither says so, the refusal is unclassified and {@link isTransient} says no
|
|
709
|
+
* (OPL-5975): not every refusal here has an answer yet, and an unanswered one
|
|
710
|
+
* is not a promise that waiting helps.
|
|
552
711
|
*/
|
|
553
712
|
export class ConflictError extends APIError {
|
|
554
713
|
name = 'ConflictError';
|
|
@@ -1242,8 +1401,10 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
|
|
|
1242
1401
|
* because a number can be added to it without anyone having to say which of the
|
|
1243
1402
|
* three answers changed. It now reads identically in all three:
|
|
1244
1403
|
*
|
|
1245
|
-
* - {@link ConflictError} —
|
|
1246
|
-
*
|
|
1404
|
+
* - {@link ConflictError} — only where the platform says it is passing: a
|
|
1405
|
+
* `reason` of `contention` or `starting`, or a code it documents as
|
|
1406
|
+
* passing (`idempotency_in_progress`, `WORKSPACE_MOVING`). A 409 with
|
|
1407
|
+
* neither is unclassified and not transient (OPL-5975)
|
|
1247
1408
|
* - {@link RateLimitError} — a cadence, and the response usually says how long
|
|
1248
1409
|
* - {@link UnavailableError} — something briefly out of reach, for a GET or
|
|
1249
1410
|
* HEAD only. Any change answered 503 may have happened, so it is not
|
|
@@ -1271,7 +1432,7 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
|
|
|
1271
1432
|
* be answered after a change landed. So retry reads freely, and check before
|
|
1272
1433
|
* repeating anything that creates something.
|
|
1273
1434
|
*
|
|
1274
|
-
* Nor is a status enough on its own to answer it. 409 is the case:
|
|
1435
|
+
* Nor is a status enough on its own to answer it. 409 is the case: some of them
|
|
1275
1436
|
* are a passing state, and the move offer is a decision that no retry changes,
|
|
1276
1437
|
* which is why the check below leads with the type rather than the number. If a
|
|
1277
1438
|
* second such refusal earns a class, it belongs on that line too.
|
|
@@ -1286,18 +1447,16 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
|
|
|
1286
1447
|
* absent word — or one this version does not know — leaves the type answer
|
|
1287
1448
|
* standing unchanged (OPL-3898).
|
|
1288
1449
|
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
* the platform's own words either way — and this predicate is left honest about
|
|
1300
|
-
* what it can and cannot tell apart.
|
|
1450
|
+
* And the fallback for a 409 with no word is NO, not yes (OPL-5975). It used
|
|
1451
|
+
* to be yes — "a 409 is usually passing" — and that said yes to every refusal
|
|
1452
|
+
* the platform had not classified: a label that is taken, a computer at its
|
|
1453
|
+
* public URL limit, the webhook cap, an SSH key already registered, the
|
|
1454
|
+
* seventeenth background command (OPL-3909, whose slots may be held by servers
|
|
1455
|
+
* and never free). The platform's reference is explicit that an absent
|
|
1456
|
+
* `reason` is no classification and that treating 409 as uniformly transient
|
|
1457
|
+
* loops forever. So a 409 is transient here only when its word clears or its
|
|
1458
|
+
* code is one the platform documents as passing ({@link conflictCodeClears}),
|
|
1459
|
+
* and the same classification holds in both SDKs.
|
|
1301
1460
|
*/
|
|
1302
1461
|
export function isTransient(err) {
|
|
1303
1462
|
if (err instanceof APIError && [401, 402, 403, 404, 405].includes(err.status))
|
|
@@ -1350,15 +1509,27 @@ export function isTransient(err) {
|
|
|
1350
1509
|
// Through the exported classifier rather than the sets directly, so what an
|
|
1351
1510
|
// embedder is told and what this server does are one answer and not two.
|
|
1352
1511
|
const kind = reasonKind(err.reason);
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1512
|
+
if (kind === 'clears') {
|
|
1513
|
+
// A nested run reason can arrive after billed work and grants no replay
|
|
1514
|
+
// permission, whatever code sits beside it.
|
|
1515
|
+
if (!(record(err.body) && typeof err.body.reason === 'string'))
|
|
1516
|
+
return false;
|
|
1517
|
+
// The word describes a refusal: a 400 or a 409. On any other status the
|
|
1518
|
+
// status's own answer stands, as in both SDKs.
|
|
1519
|
+
if (err.status === 400 || err.status === 409)
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1356
1522
|
if (kind === 'permanent')
|
|
1357
1523
|
return false;
|
|
1358
1524
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1525
|
+
// A 409 is a passing state only where the platform says so (OPL-5975): a
|
|
1526
|
+
// word that clears, above, or a code it documents as passing. One with
|
|
1527
|
+
// neither is unclassified, and the platform's own reference says a client
|
|
1528
|
+
// treating 409 as uniformly transient loops forever on half of them — a
|
|
1529
|
+
// label that is taken, a computer at its URL limit, a webhook cap.
|
|
1530
|
+
if (err instanceof ConflictError)
|
|
1531
|
+
return conflictCodeClears(err);
|
|
1532
|
+
return err instanceof RateLimitError || err instanceof ConnectivityError;
|
|
1362
1533
|
}
|
|
1363
1534
|
/**
|
|
1364
1535
|
* The same words, a different question: worth POLLING again.
|