mandala-computer-mcp 0.6.0 → 0.8.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 +268 -27
- package/dist/api.d.ts +26 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +23 -0
- package/dist/api.js.map +1 -1
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +57 -1
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +114 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +253 -3
- package/dist/errors.js.map +1 -1
- package/dist/format.d.ts +73 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +81 -3
- package/dist/format.js.map +1 -1
- package/dist/http.d.ts +32 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +440 -57
- package/dist/http.js.map +1 -1
- package/dist/paths.d.ts +75 -7
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +88 -12
- package/dist/paths.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -1
- package/dist/server.js.map +1 -1
- package/dist/tool-filters.d.ts +4 -4
- package/dist/tool-filters.d.ts.map +1 -1
- package/dist/tool-filters.js +13 -1
- package/dist/tool-filters.js.map +1 -1
- package/dist/tools/account.d.ts +14 -0
- package/dist/tools/account.d.ts.map +1 -1
- package/dist/tools/account.js +118 -0
- package/dist/tools/account.js.map +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +19 -25
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/chat.d.ts.map +1 -1
- package/dist/tools/chat.js +46 -4
- package/dist/tools/chat.js.map +1 -1
- package/dist/tools/computers.d.ts.map +1 -1
- package/dist/tools/computers.js +297 -51
- package/dist/tools/computers.js.map +1 -1
- package/dist/tools/guest.d.ts.map +1 -1
- package/dist/tools/guest.js +27 -4
- package/dist/tools/guest.js.map +1 -1
- package/dist/tools/input.d.ts +9 -0
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +247 -16
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/operations.d.ts +49 -0
- package/dist/tools/operations.d.ts.map +1 -0
- package/dist/tools/operations.js +255 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/secrets.d.ts +10 -1
- package/dist/tools/secrets.d.ts.map +1 -1
- package/dist/tools/secrets.js +63 -18
- package/dist/tools/secrets.js.map +1 -1
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +54 -18
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/ssh.d.ts.map +1 -1
- package/dist/tools/ssh.js +19 -2
- package/dist/tools/ssh.js.map +1 -1
- package/dist/tools/templates.js +2 -2
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/webhooks.js +1 -1
- package/dist/tools/webhooks.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,10 +145,12 @@ Parameter and response-mode support remains a separate contract.
|
|
|
145
145
|
|
|
146
146
|
**Lifecycle** — `create_computer`, `start_computer`, `stop_computer`,
|
|
147
147
|
`suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`,
|
|
148
|
-
`delete_computer`, `move_computer`, `list_moves`
|
|
148
|
+
`delete_computer`, `move_computer`, `list_moves`, `get_operation`,
|
|
149
|
+
`list_operations`, `wait_for_operation` — see [Lifecycle operations](#lifecycle-operations)
|
|
149
150
|
|
|
150
|
-
**Driving the desktop** — `screenshot`, `click`, `type_text`, `
|
|
151
|
-
`scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`,
|
|
151
|
+
**Driving the desktop** — `screenshot`, `click`, `type_text`, `paste_text`,
|
|
152
|
+
`press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`,
|
|
153
|
+
`wait`
|
|
152
154
|
|
|
153
155
|
**Inside the guest** — `exec`, `exec_poll`, `exec_kill`, `get_execution`,
|
|
154
156
|
`read_execution_output`, `open_url`,
|
|
@@ -185,6 +187,10 @@ which says nothing about the path; `isTransient` is false for both.
|
|
|
185
187
|
|
|
186
188
|
**Account quota** — `get_account`
|
|
187
189
|
|
|
190
|
+
**Who you are** — `whoami`, `list_api_keys`, `list_workspaces`,
|
|
191
|
+
`get_workspace`, `list_workspace_members`. Minting and revoking API keys are
|
|
192
|
+
deliberately not tools — see [Who you are, and API keys](#who-you-are-and-api-keys).
|
|
193
|
+
|
|
188
194
|
**Spending** — `get_usage`
|
|
189
195
|
|
|
190
196
|
**Being told somewhere else** — `list_webhooks`, `create_webhook`,
|
|
@@ -197,11 +203,12 @@ was issued to, not to the account, and are accepted by every computer with SSH
|
|
|
197
203
|
on, on every account where that person is an owner or member. A
|
|
198
204
|
workspace-scoped key can read keys but not add or remove them.
|
|
199
205
|
|
|
200
|
-
**Secrets** — `list_secrets`, `get_secret`, `create_secret`, `
|
|
201
|
-
`delete_secret` for the account's secret store, and
|
|
202
|
-
`set_computer_secrets` and `create_computer`'s `secrets`
|
|
203
|
-
computer receives. A value goes in through `create_secret
|
|
204
|
-
|
|
206
|
+
**Secrets** — `list_secrets`, `get_secret`, `create_secret`, `set_secret`,
|
|
207
|
+
`replace_secret`, `delete_secret` for the account's secret store, and
|
|
208
|
+
`get_computer_secrets`, `set_computer_secrets` and `create_computer`'s `secrets`
|
|
209
|
+
for which of them a computer receives. A value goes in through `create_secret`,
|
|
210
|
+
`set_secret` (create the name, or replace its value if the scope holds it —
|
|
211
|
+
names match ignoring ASCII case) or `replace_secret`, and never comes back out. No route answers one, and a store tool's result
|
|
205
212
|
holds only the decoded documented fields (success) or the status, the `reason`
|
|
206
213
|
word and a sentence of its own (refusal). It never includes the platform's
|
|
207
214
|
response text, and the `Api` keeps none for these routes. The bindings carry
|
|
@@ -211,13 +218,41 @@ binding list (`[]` removes every binding) and reaches the guest at the
|
|
|
211
218
|
computer's next start or restart; send the `version` a read answered to have it
|
|
212
219
|
refused with 409 if the list changed since. A replaced value also reaches a
|
|
213
220
|
running computer: a file binding's file is rewritten in place, and on an image
|
|
214
|
-
that supports it an env binding reaches new shells and `exec
|
|
215
|
-
`desktop: true
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
that supports it an env binding reaches new shells, and every `exec`, plain
|
|
222
|
+
or with `desktop: true`, runs with the bound variables as they are at that
|
|
223
|
+
moment (programs already running keep the old value until a restart).
|
|
224
|
+
`replace_secret`
|
|
218
225
|
and `delete_secret` need the current `revision_id`, and a stale one is a 409.
|
|
219
226
|
`delete_secret` also needs `confirm: true`: a computer still bound to a deleted
|
|
220
|
-
secret cannot start again until that binding is removed.
|
|
227
|
+
secret cannot start again until that binding is removed. A bound computer runs,
|
|
228
|
+
and its guest answers, a few seconds before its secrets land, so
|
|
229
|
+
`wait_for_computer(until="guest")` on one also waits until they have;
|
|
230
|
+
`get_computer` shows the gap as "its secrets are still on their way in".
|
|
231
|
+
|
|
232
|
+
**A proxy for the browsers** — `browser_proxy: {server, bypass}` on
|
|
233
|
+
`create_computer` and `update_computer` sends a Linux computer's browsers
|
|
234
|
+
(Chromium, Chrome, Firefox) through a proxy; nothing else on the computer uses
|
|
235
|
+
it. On `update_computer` it goes alone, replaces the setting whole, and `null`
|
|
236
|
+
removes it. Which proxies are accepted is the platform's rule, and its refusal
|
|
237
|
+
comes back as it is. A running computer has a change within seconds:
|
|
238
|
+
`wait_for_computer(until="guest")` waits until its browsers have it, and
|
|
239
|
+
`get_computer` shows the gap as "its browser proxy is still being applied".
|
|
240
|
+
|
|
241
|
+
**A proxy for all outbound traffic** — `egress_proxy: {server,
|
|
242
|
+
credentials_secret_id}` on `create_computer` and `update_computer` sends ALL of
|
|
243
|
+
a computer's outbound TCP (`exec`, terminals, package managers and browsers
|
|
244
|
+
alike) through a proxy, taken on its host so nothing inside the computer can
|
|
245
|
+
opt out. The server is `http://`, `https://` or `socks5://` with an explicit
|
|
246
|
+
port; there is no bypass list. `credentials_secret_id` names a secret holding
|
|
247
|
+
`user:password` that is not bound to the computer and never reaches it: the
|
|
248
|
+
computer's host signs in to the proxy with it. It fails closed (proxy down or
|
|
249
|
+
refusing, or credentials not on the host yet: the connection fails, nothing goes
|
|
250
|
+
direct), drops UDP to the internet and ICMP, and does not proxy DNS lookups.
|
|
251
|
+
On `update_computer` it goes alone — beside any other field it is refused
|
|
252
|
+
before a request is sent — replaces the setting whole, and `null` removes it.
|
|
253
|
+
`get_computer` names the proxy and, while the host waits for its credentials,
|
|
254
|
+
says "its egress proxy is waiting for credentials; connections are closed until
|
|
255
|
+
they arrive".
|
|
221
256
|
|
|
222
257
|
**Delegating** — `run_agent`, `run_agent_chat`, registered only when a model key is present:
|
|
223
258
|
`MANDALA_MODEL_KEY` on stdio, or the caller's own `X-Model-Key` header over HTTP.
|
|
@@ -242,10 +277,10 @@ with an error listing all valid tags.
|
|
|
242
277
|
|
|
243
278
|
| Tag | Tools |
|
|
244
279
|
| --- | --- |
|
|
245
|
-
| `account` | `get_account` |
|
|
280
|
+
| `account` | `get_account`, `whoami`, `list_api_keys`, `list_workspaces`, `get_workspace`, `list_workspace_members` |
|
|
246
281
|
| `computers` | `list_computers`, `get_computer`, `use_computer`, `wait_for_computer`, `get_desktop_url`, `list_sizes` |
|
|
247
|
-
| `lifecycle` | `create_computer`, `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`, `delete_computer`, `move_computer`, `list_moves` |
|
|
248
|
-
| `input` | `screenshot`, `click`, `type_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
|
|
282
|
+
| `lifecycle` | `create_computer`, `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`, `delete_computer`, `move_computer`, `list_moves`, `get_operation`, `list_operations`, `wait_for_operation` |
|
|
283
|
+
| `input` | `screenshot`, `click`, `type_text`, `paste_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
|
|
249
284
|
| `guest` | `exec`, `exec_poll`, `exec_kill`, `open_url`, `list_windows`, `window_action`, `read_clipboard`, `write_clipboard` |
|
|
250
285
|
| `files` | `list_directory`, `read_file`, `write_file`, `wait_for_file_change` |
|
|
251
286
|
| `executions` | `get_execution`, `read_execution_output` |
|
|
@@ -257,7 +292,7 @@ with an error listing all valid tags.
|
|
|
257
292
|
| `usage` | `get_usage` |
|
|
258
293
|
| `webhooks` | All webhook tools listed above |
|
|
259
294
|
| `ssh` | All SSH tools listed above |
|
|
260
|
-
| `secrets` | `list_secrets`, `get_secret`, `create_secret`, `replace_secret`, `delete_secret`, `get_computer_secrets`, `set_computer_secrets` |
|
|
295
|
+
| `secrets` | `list_secrets`, `get_secret`, `create_secret`, `set_secret`, `replace_secret`, `delete_secret`, `get_computer_secrets`, `set_computer_secrets` |
|
|
261
296
|
| `agent` | `run_agent`, `run_agent_chat` |
|
|
262
297
|
| `activities` | `list_activities`, `get_activity`, `get_activity_results` |
|
|
263
298
|
| `signals` | `read_signals` |
|
|
@@ -280,6 +315,116 @@ to both transports and the plugin forwards them. Embedders can pass
|
|
|
280
315
|
`readOnly: true` and `tags: ['input', 'guest']` in `ServerConfig`; the server
|
|
281
316
|
does not read the environment itself.
|
|
282
317
|
|
|
318
|
+
### Lifecycle operations
|
|
319
|
+
|
|
320
|
+
Every accepted create, clone, start, stop, suspend, restart, snapshot restore,
|
|
321
|
+
resize, move and delete records a lifecycle operation, and the tool that made the call
|
|
322
|
+
says its `operation_id` — in its sentence, as `(operation op_…)`, and in the
|
|
323
|
+
JSON. `move_computer` carries it onto the outcome it reports. It is absent where
|
|
324
|
+
the platform could not record one; the call happened either way.
|
|
325
|
+
|
|
326
|
+
`get_operation` reads one, and `list_operations` pages through them newest
|
|
327
|
+
first (`computer_id`, `limit`, `cursor`, `idempotency_key`; `computer_id` is not
|
|
328
|
+
defaulted to the selected computer, and `idempotency_key` finds the operation a
|
|
329
|
+
call sent with that key recorded, even when the call's answer was lost). `wait_for_operation` polls one until it is final: it
|
|
330
|
+
answers on `succeeded`, and a `failed` one is an error carrying the platform's
|
|
331
|
+
`error.code` (`start_failed`, `build_failed`, `computer_gone`, `move_failed`,
|
|
332
|
+
`resize_not_applied`, `lost`, and more may be added) and its sentence.
|
|
333
|
+
|
|
334
|
+
`succeeded` means the platform finished its step, not that the desktop has
|
|
335
|
+
booted: `wait_for_computer` is still the wait for a desktop that answers. Most
|
|
336
|
+
operations are already `succeeded` when their tool answers; a clone is
|
|
337
|
+
`running` until its disk is copied, and a move until it lands. All three are
|
|
338
|
+
reads, and all three stay registered when the lifecycle tools are withheld,
|
|
339
|
+
since start, stop, suspend and restart record operations too.
|
|
340
|
+
|
|
341
|
+
The eleven lifecycle tools (`create_computer`, `clone_computer`,
|
|
342
|
+
`start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`,
|
|
343
|
+
`update_computer`, `move_computer`, `delete_computer`, `restore_snapshot`,
|
|
344
|
+
`clone_snapshot`) take an optional `idempotency_key`. Leave it out and a fresh
|
|
345
|
+
one is sent for you; a failure whose outcome is unknown names the key it went
|
|
346
|
+
with. What to do next depends on who answered:
|
|
347
|
+
|
|
348
|
+
- **The answer was lost** (the connection dropped after the request went out,
|
|
349
|
+
or a proxy in front of the platform gave up), or the platform answers
|
|
350
|
+
`idempotency_in_progress`: call the same tool again with the same
|
|
351
|
+
`idempotency_key`. The platform answers with the first call's result, or says
|
|
352
|
+
it is still running; the step is not done twice.
|
|
353
|
+
- **The platform answered a `5xx` that names no `operation_id`**: nothing may
|
|
354
|
+
have been done. A refusal given before the call was sent anywhere — another
|
|
355
|
+
launch already in progress, no host to place it on, the template catalogue
|
|
356
|
+
incomplete — names none, and the platform releases its key. Call the same
|
|
357
|
+
tool again with the same `idempotency_key`: the platform carries it out if
|
|
358
|
+
the key was released, or answers `idempotency_outcome_unknown` if not, and
|
|
359
|
+
only then does the case below apply.
|
|
360
|
+
- **The platform answered a `5xx` that names an `operation_id`**, or
|
|
361
|
+
`idempotency_outcome_unknown`: the key is spent, and resending with it only
|
|
362
|
+
answers `idempotency_outcome_unknown`.
|
|
363
|
+
The platform then keeps the call's operation `pending` for up to an hour,
|
|
364
|
+
whatever happened, while its host may be carrying it out. What to read
|
|
365
|
+
depends on the tool:
|
|
366
|
+
- `start_computer`, `stop_computer`, `suspend_computer`,
|
|
367
|
+
`delete_computer`, `move_computer` and `update_computer`: read
|
|
368
|
+
`get_computer` (and `get_operation` with the `operation_id` the error
|
|
369
|
+
named: `succeeded` means the step happened, `running` means wait on it,
|
|
370
|
+
and `pending` alone is no reason to wait). If the step did not take
|
|
371
|
+
effect, send the call again with a new key, or none.
|
|
372
|
+
- `restart_computer`, which `get_computer` cannot show: a computer reads
|
|
373
|
+
`running` before and after a reset. Read the operation instead
|
|
374
|
+
(`get_operation` with the `operation_id` the error named, or
|
|
375
|
+
`list_operations` with the key): `succeeded` means the restart happened,
|
|
376
|
+
`running` means wait on it. Anything else leaves the restart possibly
|
|
377
|
+
done, so ask the user before sending it again with a new key, or none; a
|
|
378
|
+
second restart resets the guest again.
|
|
379
|
+
- `create_computer`, `clone_computer`, `clone_snapshot` and
|
|
380
|
+
`restore_snapshot`, where a read of the computer made straight away can
|
|
381
|
+
show no effect for a build or restore that then lands, and a second one
|
|
382
|
+
is a second computer or a second overwrite: read the operation first —
|
|
383
|
+
`get_operation` with the `operation_id` the error named, or
|
|
384
|
+
`list_operations` with the key — and while it is `pending` or `running`,
|
|
385
|
+
wait (`wait_for_operation`) and do not resend with any key. Only once it
|
|
386
|
+
is final as `failed` (`error.code` `lost`, for one) or no longer found, AND
|
|
387
|
+
`get_computer` (`list_computers` after a create or a clone) shows the step
|
|
388
|
+
did not happen, send the call again with a new key, or none. If no
|
|
389
|
+
operation is found at all, read the computer again after a few minutes
|
|
390
|
+
before resending.
|
|
391
|
+
- **`idempotency_key_reused`**: a different call already used that key, and
|
|
392
|
+
nothing was done. Send this one with a new key, or none.
|
|
393
|
+
|
|
394
|
+
Keys last 24 hours.
|
|
395
|
+
|
|
396
|
+
### Who you are, and API keys
|
|
397
|
+
|
|
398
|
+
`whoami` takes no arguments and reads `GET /api/v1/whoami`: the person this
|
|
399
|
+
server's API key was issued to, the account and role it acts with (the role as
|
|
400
|
+
it is now), the workspace it is confined to, and the key itself — including
|
|
401
|
+
`manage_keys`, whether it may manage API keys. It needs no permission and any
|
|
402
|
+
role, and a suspended account can call it; the answer says so when the account
|
|
403
|
+
is suspended. Behind the hosted server's OAuth sign-in, the key is the Connected
|
|
404
|
+
app's (`prefix` `oauth`).
|
|
405
|
+
|
|
406
|
+
`list_api_keys` lists the key holder's API keys on the account, newest first —
|
|
407
|
+
never a raw key. Each carries `minted_by_key_id`: the key that minted it over
|
|
408
|
+
the API, or `null` for one minted from the dashboard. Revoking a key does not
|
|
409
|
+
revoke the keys it minted. It needs this server's key to have the **Manage keys**
|
|
410
|
+
permission, which only a person can turn on, in the dashboard; without it the
|
|
411
|
+
tool answers the platform's 403, whose sentence says exactly that. A Connected
|
|
412
|
+
app's key never has the permission.
|
|
413
|
+
|
|
414
|
+
**Minting and revoking keys are not offered here, on purpose.** A mint answers
|
|
415
|
+
the new key in full, once, and a model's context — the conversation, the
|
|
416
|
+
client's logs, whatever the transcript is shared with — is the worst place for a
|
|
417
|
+
long-lived credential and one nobody can take it back from. A revoke is
|
|
418
|
+
irreversible, and on a model's reading of a list it can cut off the person's CI,
|
|
419
|
+
another agent, or this very session. People do both from the dashboard or with
|
|
420
|
+
the `mandala api-keys` CLI.
|
|
421
|
+
|
|
422
|
+
`list_workspaces`, `get_workspace` and `list_workspace_members` read the
|
|
423
|
+
account's workspaces, which partition its computers. A key confined to a
|
|
424
|
+
workspace lists only that one, and is refused (403) the member list, which is
|
|
425
|
+
the whole account's: workspaces do not divide people. All three are reads;
|
|
426
|
+
workspaces are created, renamed and deleted in the dashboard.
|
|
427
|
+
|
|
283
428
|
### Current account quota
|
|
284
429
|
|
|
285
430
|
`get_account` takes no arguments and reads `GET /api/v1/account` once with the
|
|
@@ -331,6 +476,12 @@ anything and keep the hint. When a stretch of work is over, `suspend_computer` (
|
|
|
331
476
|
suspend catches the ones a model forgets, but only after 30 minutes untouched.
|
|
332
477
|
`get_usage` is what says what any of it cost.
|
|
333
478
|
|
|
479
|
+
**A screenshot need not be the whole screen.** `screenshot` takes `region` (a
|
|
480
|
+
crop, in the screen pixels `click` takes, before any scaling), `scale` (0 to 1),
|
|
481
|
+
`format` (`png` or `jpeg`) and `quality` (JPEG, 1-100). A cropped or scaled
|
|
482
|
+
picture is in its own pixel space, so the tool says alongside it how to turn a
|
|
483
|
+
position in it back into screen coordinates.
|
|
484
|
+
|
|
334
485
|
**Ten clicks need not be ten screenshots.** Driving the desktop one tool at a
|
|
335
486
|
time — `screenshot`, `click`, `screenshot` — puts an image in the calling
|
|
336
487
|
model's context for every step. `run_agent` hands a task in plain language to
|
|
@@ -733,9 +884,15 @@ listing leaves it out — every ordinary caller is asking "what can I restore".
|
|
|
733
884
|
`list_snapshots(include_unfinished: true)` is the flag for when the question is
|
|
734
885
|
about storage instead.
|
|
735
886
|
|
|
736
|
-
**
|
|
737
|
-
|
|
738
|
-
|
|
887
|
+
**Read the `reason`, not the status.** Whether a refusal clears on its own is
|
|
888
|
+
the platform's `reason` word, and each tool's answer says what it means:
|
|
889
|
+
`contention` (something was in flight) and `starting` (the guest agent is still
|
|
890
|
+
inside its boot window) clear, so the same call works in a moment;
|
|
891
|
+
`unavailable`, `running`, `unsupported`, `exists` and `revoked` do not. A 409
|
|
892
|
+
does not mean "wait": `exec`, input and the clipboard on a stopped computer
|
|
893
|
+
answer 409 `unavailable`, and `start_computer` is what fixes that, while
|
|
894
|
+
`running` needs the computer stopped first. The platform's own error messages
|
|
895
|
+
come through unedited, because they are written to be acted on.
|
|
739
896
|
|
|
740
897
|
HTTP failures preserve their actual response status. An unsupported method on a
|
|
741
898
|
known path is `MethodNotAllowedError` (405), with the received `Allow` value when
|
|
@@ -973,7 +1130,8 @@ address.
|
|
|
973
1130
|
Settings → Connected apps.
|
|
974
1131
|
- **A bearer is checked with the platform before it gets anything.** An
|
|
975
1132
|
initialize makes no session until the platform accepts its token — a `2xx`
|
|
976
|
-
from `GET
|
|
1133
|
+
from `GET whoami`, which every valid credential gets, a suspended account's
|
|
1134
|
+
included — so invented tokens
|
|
977
1135
|
cannot fill the session pool. A `401` is the challenge above; any other
|
|
978
1136
|
answer is `503` and nothing is remembered. Refused initializes are budgeted
|
|
979
1137
|
per source address, 20 a minute. Past that, a token already accepted still
|
|
@@ -983,6 +1141,65 @@ address.
|
|
|
983
1141
|
carrying a request is checked again before it is dispatched, with an
|
|
984
1142
|
acceptance cached for 60 s under the token's digest, so an expired or
|
|
985
1143
|
revoked token is a clean `401` before any stream opens.
|
|
1144
|
+
- **One bearer holds at most 16 sessions**, and a suspended account's bearer
|
|
1145
|
+
one (enough to ask `whoami`), so no single token can fill the pool. At that
|
|
1146
|
+
limit, an initialize closes the bearer's least recently used idle session to
|
|
1147
|
+
make room, once its own session exists (a refused initialize closes
|
|
1148
|
+
nothing), and a client whose old session is gone initializes again; if none
|
|
1149
|
+
is idle, the answer is `429` with `Retry-After`. One race differs: if the
|
|
1150
|
+
idle session it planned to close is put to work before the new session is
|
|
1151
|
+
made, the new one is dropped instead and the initialize is answered `404`
|
|
1152
|
+
(`Session not found`, no `Retry-After`), which an SDK client reports as a
|
|
1153
|
+
failed connect. Other bearers' sessions are never touched. An account
|
|
1154
|
+
suspended after its bearer opened sessions keeps only one: the next request
|
|
1155
|
+
on any of them closes the bearer's other idle sessions, as does its next
|
|
1156
|
+
initialize, and one still serving a request goes on a later request once
|
|
1157
|
+
it is idle. A
|
|
1158
|
+
`429` names what the bearer holds, so one still over its limit is told to
|
|
1159
|
+
wait for its busy sessions rather than to close one. `runHttp` takes the 16
|
|
1160
|
+
as `maxSessionsPerBearer`, and the CLI reads it from
|
|
1161
|
+
`MANDALA_MCP_MAX_SESSIONS_PER_TOKEN` (a whole number from 1 to 256, the
|
|
1162
|
+
whole pool; anything else is refused at startup; a token is also held to
|
|
1163
|
+
its account's ceiling, below). A self-hosted `--http` server applies it
|
|
1164
|
+
too, but checks no bearer with the platform, so there any string is its own
|
|
1165
|
+
bucket: the per-bearer cap spreads one key's clients, and what bounds an
|
|
1166
|
+
untrusted caller is the process-wide cap (256), and the default loopback
|
|
1167
|
+
bind until you expose the port.
|
|
1168
|
+
- **One account holds at most 128 sessions, half the pool, across all its
|
|
1169
|
+
tokens.** Sessions are counted by the account id the platform's `whoami`
|
|
1170
|
+
names for each token, so an account with many keys or grants counts once;
|
|
1171
|
+
a token whose `whoami` names no account counts as an account of its own.
|
|
1172
|
+
Initializes in flight are counted too, so a burst across one account's keys
|
|
1173
|
+
cannot pass the ceiling together. At the ceiling, an initialize makes room
|
|
1174
|
+
by closing idle sessions of two kinds only: the account's sessions whose
|
|
1175
|
+
token the platform has already refused on a request made there, first,
|
|
1176
|
+
and then the requesting token's own least recently used,
|
|
1177
|
+
as at its own cap and even while it is under that cap. When that is not
|
|
1178
|
+
enough the answer is `429` with `Retry-After`, saying it is the account's
|
|
1179
|
+
limit and naming only that number. A live session of another token is
|
|
1180
|
+
never closed for the ceiling, nor any session of another account, and the
|
|
1181
|
+
race above applies here too (`404`). A refused session counts until it is
|
|
1182
|
+
closed, so revoking keys never lets an account past its ceiling; a session
|
|
1183
|
+
whose token was revoked but never sent a request again is not known to be
|
|
1184
|
+
refused, and counts until it idles out. So does the session a token
|
|
1185
|
+
leaves behind when the client refreshes it (see below): it is neither the
|
|
1186
|
+
new token's own nor refused, so an account that holds more than half its
|
|
1187
|
+
ceiling when its clients refresh can be answered `429` for up to the
|
|
1188
|
+
session idle timeout (30 minutes), longer than its `Retry-After` suggests,
|
|
1189
|
+
unless those clients close their old sessions. A revoked token's session
|
|
1190
|
+
idles out within about the acceptance window (60 s) plus that timeout even
|
|
1191
|
+
while its holder keeps sending notifications, holding the stream, or
|
|
1192
|
+
sending `HEAD /mcp` or a `DELETE /mcp` the server refuses: traffic that
|
|
1193
|
+
carries no request is never checked with the platform, so it keeps a
|
|
1194
|
+
session alive only while the token's acceptance is still cached. Suspension is held per
|
|
1195
|
+
token, as above, not per account. A full pool (256) is answered `503` with
|
|
1196
|
+
`Retry-After`, whoever holds it, and nothing is closed to make room in it.
|
|
1197
|
+
`runHttp` takes the ceiling as `maxSessionsPerAccount`, and the CLI reads
|
|
1198
|
+
it from `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` (a whole number from 1 to
|
|
1199
|
+
256; anything else is refused at startup). A self-hosted server verifies
|
|
1200
|
+
no account, so there each token is an account of its own, held to the
|
|
1201
|
+
ceiling as well as to its own cap; to let one token hold more than 128
|
|
1202
|
+
sessions, raise both.
|
|
986
1203
|
- **A token the platform refuses during a call comes back as that same
|
|
987
1204
|
`401`**, not as a tool error, so the client refreshes or authorizes again —
|
|
988
1205
|
the answer is held until its first byte for this. The one case that cannot
|
|
@@ -995,7 +1212,11 @@ address.
|
|
|
995
1212
|
`404 Unknown session`, the MCP spec's signal to initialize again. An access
|
|
996
1213
|
token says nothing this server can check about which grant it came from, so
|
|
997
1214
|
rebinding would let any valid credential that learned a session id take over
|
|
998
|
-
its bound computer, buffered events and retained results.
|
|
1215
|
+
its bound computer, buffered events and retained results. The old session
|
|
1216
|
+
is not closed by the refresh: it still counts against the token's account
|
|
1217
|
+
ceiling (above) until the client closes it with `DELETE /mcp` under the old
|
|
1218
|
+
token (only the token's digest is compared, so an expired one still works)
|
|
1219
|
+
or it idles out after 30 minutes.
|
|
999
1220
|
- `X-Mandala-MCP-Service` carries `MANDALA_MCP_SERVICE_SECRET` on every
|
|
1000
1221
|
platform request, only ever to `MANDALA_BASE_URL`, so a token cannot be
|
|
1001
1222
|
replayed at the API directly by the app it was issued to. A client's own
|
|
@@ -1017,8 +1238,10 @@ address.
|
|
|
1017
1238
|
| `MANDALA_ALLOWED_HOSTS`, `MANDALA_ALLOWED_ORIGINS` | Comma-separated. Which `Host` and `Origin` values this server answers to. On a loopback bind the host list defaults to the address it was given, so DNS-rebinding protection is on without configuration; set this when serving under a name. |
|
|
1018
1239
|
| `MANDALA_MCP_RESOURCE_METADATA_URL` | `--http` only. The OAuth protected-resource metadata URL this server is published under. Set, it answers OAuth clients as described under Hosted, with OAuth; unset, callers bring an API key. |
|
|
1019
1240
|
| `MANDALA_MCP_SERVICE_SECRET` | `--http` only. Sent as `X-Mandala-MCP-Service` on every platform request, to `MANDALA_BASE_URL` only. Never logged. |
|
|
1241
|
+
| `MANDALA_MCP_MAX_SESSIONS_PER_TOKEN` | `--http` only. How many live sessions one bearer token may hold. Default 16; a whole number from 1 to 256 (the server's whole session pool), and anything else is refused at startup. A suspended account's token is held to one whatever this says. A token is also held to its account's limit, below. |
|
|
1242
|
+
| `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` | `--http` only. How many live sessions one account may hold across all its tokens. Default 128, half the pool; a whole number from 1 to 256, and anything else is refused at startup. Hosted (the metadata URL set), the account is the one the platform's `whoami` names for the token; self-hosted, or when `whoami` names none, each token is an account of its own. Past it an initialize is refused `429`, after closing only the token's own idle sessions or the account's refused ones. Suspension is not counted here; it holds each token to one. |
|
|
1020
1243
|
|
|
1021
|
-
Every one of these but the model key, the two tool filters
|
|
1244
|
+
Every one of these but the model key, the two tool filters, the two OAuth settings and the two session limits has a flag as well, and a flag overrides
|
|
1022
1245
|
the environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
|
|
1023
1246
|
`--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, local `--profile`, plus `--help` and
|
|
1024
1247
|
`--version`. `--key` exists for a caller launching several servers under
|
|
@@ -1085,7 +1308,13 @@ default 20. This is computer control, not hosted general-purpose inference or a
|
|
|
1085
1308
|
chat UI; it adds no key storage or model billing service.
|
|
1086
1309
|
|
|
1087
1310
|
This tool deliberately sends `stream:false` and uses the JSON response so the
|
|
1088
|
-
result preserves the underlying `agent.stop`, step count and token usage
|
|
1311
|
+
result preserves the underlying `agent.stop`, step count and token usage: the
|
|
1312
|
+
streamed form of this route carries no usage and no `agent` block. The price is
|
|
1313
|
+
time. **On the hosted API a request that does not stream is cut at the edge after
|
|
1314
|
+
about 120 seconds** (a bodiless `524`), which stops the run where it was and loses
|
|
1315
|
+
its result, usage and steps; the default 20 steps can take longer than that. Use
|
|
1316
|
+
`run_agent`, which streams, for anything that may. The tool answers a `524` by
|
|
1317
|
+
saying so rather than as a generic gateway failure.
|
|
1089
1318
|
Only explicit `end_turn` consistent with the completion's finish reason can
|
|
1090
1319
|
report success. Limits, refusal, missing or conflicting terminal fields remain
|
|
1091
1320
|
errors with valid partial results. Nested failures preserve the agent computer,
|
|
@@ -1098,9 +1327,21 @@ Neither agent tool starts the computer or switches endpoints after a failure.
|
|
|
1098
1327
|
The caller supplies the model key through `MANDALA_MODEL_KEY` on stdio or their
|
|
1099
1328
|
own `X-Model-Key` header on HTTP. The account Authorization remains separate;
|
|
1100
1329
|
HTTP never falls back to the operator's model key. Waiting heartbeats count
|
|
1101
|
-
notifications, not completed actions.
|
|
1102
|
-
`
|
|
1103
|
-
`max_steps
|
|
1330
|
+
notifications, not completed actions. A `progressToken` plus
|
|
1331
|
+
`resetTimeoutOnProgress` keeps the MCP client waiting, but not the hosted edge
|
|
1332
|
+
above; `max_steps` is neither a time cap nor a spend cap.
|
|
1333
|
+
|
|
1334
|
+
On both agent tools a failure status is read for what it is on these routes
|
|
1335
|
+
rather than for what it means elsewhere. The platform's own re-check of the
|
|
1336
|
+
caller is a `401` or `403` carrying `reason: "revoked"`, and only that gets
|
|
1337
|
+
account advice. A `402`, `504` or `529` is the model provider's own status for
|
|
1338
|
+
the configured model key (its billing, timeout or overloaded error), so the
|
|
1339
|
+
advice is to check the model-provider account, not the Mandala plan; a `403`
|
|
1340
|
+
without `revoked` may be the provider's permission error for that key. The
|
|
1341
|
+
exception is a refusal `run_agent_chat` is given before any run started — a flat
|
|
1342
|
+
`{error: string}` body, from the role gate, a bad body, a suspension or the
|
|
1343
|
+
meter: nothing ran, and it keeps the usual role and plan advice every other tool
|
|
1344
|
+
gets.
|
|
1104
1345
|
|
|
1105
1346
|
## Development
|
|
1106
1347
|
|
package/dist/api.d.ts
CHANGED
|
@@ -4,6 +4,25 @@ export { isSecretStoreRoute } from './secret-errors.js';
|
|
|
4
4
|
export declare const DEFAULT_BASE_URL = "https://app.mandala.computer/api/v1";
|
|
5
5
|
/** Anthropic's own key, forwarded for the one route that runs a model. */
|
|
6
6
|
export declare const MODEL_KEY_HEADER = "X-Model-Key";
|
|
7
|
+
/**
|
|
8
|
+
* The header every lifecycle call carries so that sending it again cannot do
|
|
9
|
+
* it twice (platform OPL-5127). The platform records a call that carries one
|
|
10
|
+
* before carrying it out, and for 24 hours answers the same key with the same
|
|
11
|
+
* request from that record instead of doing the call again: `409` with `code:
|
|
12
|
+
* "idempotency_in_progress"` while it runs, the original answer once it has
|
|
13
|
+
* finished. A different request under the same key is a `422`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
16
|
+
/** The platform's rule for a key: 1 to 255 characters, each printable ASCII other than a space. */
|
|
17
|
+
export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* The key for ONE lifecycle tool call — the caller's, already checked by the
|
|
20
|
+
* tool's input schema, or a fresh random one — made once, before the request,
|
|
21
|
+
* so that whatever re-sends this call re-sends the same key.
|
|
22
|
+
*/
|
|
23
|
+
export declare const idempotencyKeyFor: (given?: string) => string;
|
|
24
|
+
/** The header that carries it. */
|
|
25
|
+
export declare const idempotencyHeaders: (key: string) => Record<string, string>;
|
|
7
26
|
/**
|
|
8
27
|
* Proof, to the platform, that a request comes through the hosted MCP service
|
|
9
28
|
* (OPL-4982). The platform takes an OAuth access token only alongside it, so a
|
|
@@ -19,7 +38,7 @@ export type RequestOptions = {
|
|
|
19
38
|
body?: unknown;
|
|
20
39
|
/** Raw bytes as the request body, for the file upload. Mutually exclusive with `body`. */
|
|
21
40
|
raw?: Uint8Array;
|
|
22
|
-
/** Extra headers for this call only
|
|
41
|
+
/** Extra headers for this call only: the model key, and a lifecycle call's Idempotency-Key. */
|
|
23
42
|
headers?: Record<string, string>;
|
|
24
43
|
signal?: AbortSignal;
|
|
25
44
|
};
|
|
@@ -65,6 +84,12 @@ export type Bytes = {
|
|
|
65
84
|
* differently" and "there is no offset that will work on this file".
|
|
66
85
|
*/
|
|
67
86
|
unrangeable: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* `X-GC-Frame`, when the platform labelled what it served: `suspended` is a
|
|
89
|
+
* screenshot answered with the saved frame of a suspended computer rather
|
|
90
|
+
* than a capture of its screen.
|
|
91
|
+
*/
|
|
92
|
+
frame?: string;
|
|
68
93
|
};
|
|
69
94
|
/**
|
|
70
95
|
* The longest foreground guest exec waits 600 seconds before it answers.
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAmE,MAAM,QAAQ,CAAC;AAsBhG,OAAO,EAAsB,KAAK,WAAW,EAA0B,MAAM,mBAAmB,CAAC;AAEjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,eAAO,MAAM,gBAAgB,wCAAwC,CAAC;AAEtE,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,QAAyB,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,MAA+B,CAAC;AAEnF,kCAAkC;AAClC,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAEpE,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,0BAA0B,CAAC;AA2BtD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0FAA0F;IAC1F,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD;;;;;;;OAOG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,mBAAmB,OAG9B,CAAC;AAWH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,QAAO,OAAO,UAAU,CAAC,KAG7B,CAAC;AAEvB,iDAAiD;AACjD,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD;;;;;;;;;;;GAWG;AACH;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,qBAAa,GAAG;;IACd,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAUvB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAyB,EAClC,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,UAAe;IAsD1B;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,GAAG;IAiW1C;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAe5F,4FAA4F;IACtF,cAAc,CAAC,CAAC,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,CAAC;IAexC,8FAA8F;IACxF,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,YAAY,CAAC;IA2DlB,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,OAAO,CAAC;IAuBnB;;;;;;OAMG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAazB;;;;;;;;;;;;;;OAcG;IACG,OAAO,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC;QAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAmB/D,kFAAkF;IAC5E,KAAK,CACT,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,EACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,GACpD,OAAO,CAAC,KAAK,CAAC;IAgGjB;;;;;;;;;;OAUG;IACH,IAAI,OAAO,IAAI,WAAW,CA6FzB;IAED;;;;;;OAMG;IACI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,cAAc,CAAC,QAAQ,CAAC;CAyG9F;AAyCD;;;;;;;;;GASG;AACH,wBAAiB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAQnF;AAkgBD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAmD3E;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC"}
|
package/dist/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { Agent, Headers as UndiciHeaders, fetch as undiciFetch } from 'undici';
|
|
2
3
|
import { CancelledError, ConnectivityError, ConnectivityInterruptedError, createOnlyRefusal, errorForStatus, isCreateOnlyUpload, MandalaError, platformSaid, RangeNotSatisfiableError, RedirectError, } from './errors.js';
|
|
3
4
|
import * as P from './paths.js';
|
|
@@ -7,6 +8,27 @@ export { isSecretStoreRoute } from './secret-errors.js';
|
|
|
7
8
|
export const DEFAULT_BASE_URL = 'https://app.mandala.computer/api/v1';
|
|
8
9
|
/** Anthropic's own key, forwarded for the one route that runs a model. */
|
|
9
10
|
export const MODEL_KEY_HEADER = 'X-Model-Key';
|
|
11
|
+
/**
|
|
12
|
+
* The header every lifecycle call carries so that sending it again cannot do
|
|
13
|
+
* it twice (platform OPL-5127). The platform records a call that carries one
|
|
14
|
+
* before carrying it out, and for 24 hours answers the same key with the same
|
|
15
|
+
* request from that record instead of doing the call again: `409` with `code:
|
|
16
|
+
* "idempotency_in_progress"` while it runs, the original answer once it has
|
|
17
|
+
* finished. A different request under the same key is a `422`.
|
|
18
|
+
*/
|
|
19
|
+
export const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key';
|
|
20
|
+
/** The platform's rule for a key: 1 to 255 characters, each printable ASCII other than a space. */
|
|
21
|
+
export const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7e]{1,255}$/;
|
|
22
|
+
/**
|
|
23
|
+
* The key for ONE lifecycle tool call — the caller's, already checked by the
|
|
24
|
+
* tool's input schema, or a fresh random one — made once, before the request,
|
|
25
|
+
* so that whatever re-sends this call re-sends the same key.
|
|
26
|
+
*/
|
|
27
|
+
export const idempotencyKeyFor = (given) => given ?? randomUUID();
|
|
28
|
+
/** The header that carries it. */
|
|
29
|
+
export const idempotencyHeaders = (key) => ({
|
|
30
|
+
[IDEMPOTENCY_KEY_HEADER]: key,
|
|
31
|
+
});
|
|
10
32
|
/**
|
|
11
33
|
* Proof, to the platform, that a request comes through the hosted MCP service
|
|
12
34
|
* (OPL-4982). The platform takes an OAuth access token only alongside it, so a
|
|
@@ -721,6 +743,7 @@ export class Api {
|
|
|
721
743
|
: bytes.length,
|
|
722
744
|
unrangeable: (resp.headers.get('accept-ranges') ?? '').trim().toLowerCase() === 'none',
|
|
723
745
|
window,
|
|
746
|
+
frame: resp.headers.get('x-gc-frame')?.trim().toLowerCase() || undefined,
|
|
724
747
|
};
|
|
725
748
|
}
|
|
726
749
|
// --- the account's secret store (OPL-4984) ------------------------------
|