runbios-mcp 0.2.1-dev.62

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.
Files changed (90) hide show
  1. package/README.md +363 -0
  2. package/dist/api-client.d.ts +147 -0
  3. package/dist/api-client.d.ts.map +1 -0
  4. package/dist/api-client.js +806 -0
  5. package/dist/api-client.js.map +1 -0
  6. package/dist/auth.d.ts +3 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +11 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/config.d.ts +30 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +16 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/deployment-contract.d.ts +78 -0
  15. package/dist/deployment-contract.d.ts.map +1 -0
  16. package/dist/deployment-contract.js +155 -0
  17. package/dist/deployment-contract.js.map +1 -0
  18. package/dist/gpu-priorities.d.ts +25 -0
  19. package/dist/gpu-priorities.d.ts.map +1 -0
  20. package/dist/gpu-priorities.js +61 -0
  21. package/dist/gpu-priorities.js.map +1 -0
  22. package/dist/http/app.d.ts +14 -0
  23. package/dist/http/app.d.ts.map +1 -0
  24. package/dist/http/app.js +140 -0
  25. package/dist/http/app.js.map +1 -0
  26. package/dist/http/audit.d.ts +23 -0
  27. package/dist/http/audit.d.ts.map +1 -0
  28. package/dist/http/audit.js +41 -0
  29. package/dist/http/audit.js.map +1 -0
  30. package/dist/http/config.d.ts +23 -0
  31. package/dist/http/config.d.ts.map +1 -0
  32. package/dist/http/config.js +70 -0
  33. package/dist/http/config.js.map +1 -0
  34. package/dist/http/consent.d.ts +18 -0
  35. package/dist/http/consent.d.ts.map +1 -0
  36. package/dist/http/consent.js +148 -0
  37. package/dist/http/consent.js.map +1 -0
  38. package/dist/http/internal-auth.d.ts +22 -0
  39. package/dist/http/internal-auth.d.ts.map +1 -0
  40. package/dist/http/internal-auth.js +73 -0
  41. package/dist/http/internal-auth.js.map +1 -0
  42. package/dist/http/main.d.ts +3 -0
  43. package/dist/http/main.d.ts.map +1 -0
  44. package/dist/http/main.js +65 -0
  45. package/dist/http/main.js.map +1 -0
  46. package/dist/http/mcp-handler.d.ts +12 -0
  47. package/dist/http/mcp-handler.d.ts.map +1 -0
  48. package/dist/http/mcp-handler.js +103 -0
  49. package/dist/http/mcp-handler.js.map +1 -0
  50. package/dist/http/metadata.d.ts +6 -0
  51. package/dist/http/metadata.d.ts.map +1 -0
  52. package/dist/http/metadata.js +32 -0
  53. package/dist/http/metadata.js.map +1 -0
  54. package/dist/http/oauth.d.ts +56 -0
  55. package/dist/http/oauth.d.ts.map +1 -0
  56. package/dist/http/oauth.js +486 -0
  57. package/dist/http/oauth.js.map +1 -0
  58. package/dist/http/serviceHostGuard.d.ts +31 -0
  59. package/dist/http/serviceHostGuard.d.ts.map +1 -0
  60. package/dist/http/serviceHostGuard.js +68 -0
  61. package/dist/http/serviceHostGuard.js.map +1 -0
  62. package/dist/http/store.d.ts +150 -0
  63. package/dist/http/store.d.ts.map +1 -0
  64. package/dist/http/store.js +366 -0
  65. package/dist/http/store.js.map +1 -0
  66. package/dist/index.d.ts +3 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +79 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/inference-contract.d.ts +29 -0
  71. package/dist/inference-contract.d.ts.map +1 -0
  72. package/dist/inference-contract.js +112 -0
  73. package/dist/inference-contract.js.map +1 -0
  74. package/dist/redaction.d.ts +74 -0
  75. package/dist/redaction.d.ts.map +1 -0
  76. package/dist/redaction.js +316 -0
  77. package/dist/redaction.js.map +1 -0
  78. package/dist/server.d.ts +63 -0
  79. package/dist/server.d.ts.map +1 -0
  80. package/dist/server.js +2282 -0
  81. package/dist/server.js.map +1 -0
  82. package/dist/training-contract.d.ts +107 -0
  83. package/dist/training-contract.d.ts.map +1 -0
  84. package/dist/training-contract.js +141 -0
  85. package/dist/training-contract.js.map +1 -0
  86. package/dist/version.d.ts +2 -0
  87. package/dist/version.d.ts.map +1 -0
  88. package/dist/version.js +2 -0
  89. package/dist/version.js.map +1 -0
  90. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # Run BiOS MCP server
2
+
3
+ The MCP server exposes the authenticated Run BiOS training and model-serving
4
+ control planes to MCP-compatible automation clients. This build registers **42
5
+ tools**; `get_platform_guide` lists all of them and a test fails if that
6
+ inventory drifts from what is actually registered, so the count in the guide is
7
+ always the count on the wire (`tools/list`).
8
+
9
+ PRE-LAUNCH: while the coming-soon gates are on (the default — see
10
+ `src/config.ts`), the three creation tools (`create_training_job`,
11
+ `upload_dataset`, `import_huggingface_dataset`) are not registered at all, so
12
+ the wire count is **39** and the guide lists only the live tools. Read and
13
+ lifecycle tools are unaffected. Set `RUNBIOS_TRAINING_COMING_SOON=false` /
14
+ `RUNBIOS_DATASETS_COMING_SOON=false` at launch and the registrations return.
15
+
16
+ Set these environment variables in the MCP client configuration:
17
+
18
+ ```json
19
+ {
20
+ "command": "npx",
21
+ "args": ["-y", "runbios-mcp"],
22
+ "env": {
23
+ "RUNBIOS_API_KEY": "bios-...",
24
+ "RUNBIOS_BASE_URL": "https://api.runbios.ai",
25
+ "RUNBIOS_WORKSPACE_ID": "workspace-id",
26
+ "RUNBIOS_INFERENCE_KEY": "sk-bios-deployment-key"
27
+ }
28
+ }
29
+ ```
30
+
31
+ `RUNBIOS_*` is the canonical environment prefix. API keys start with `bios-`;
32
+ legacy `usf-` keys are still accepted.
33
+ The legacy `BIOS_*` variable names are still accepted as fallbacks.
34
+
35
+ `https://api.runbios.ai` is the canonical production hostname. Use
36
+ `https://api-dev.runbios.ai` explicitly during dev.
37
+
38
+ For interactive JWT authentication, omit `RUNBIOS_API_KEY` and set
39
+ `RUNBIOS_ACCESS_TOKEN`. The server never guesses credential type from a prefix:
40
+ API keys use `X-API-Key`, while access tokens use `Authorization: Bearer`.
41
+
42
+ `chat_with_inference` reads the deployment key only from
43
+ `RUNBIOS_INFERENCE_KEY`; a model cannot place it in a tool argument or expose it
44
+ in a transcript. Set `RUNBIOS_INFERENCE_BASE_URL=https://api-dev.runbios.ai`
45
+ explicitly for dev. The tool validates function schemas and matching tool
46
+ response messages, propagates MCP cancellation to the HTTP request, makes one
47
+ non-streaming OpenAI-compatible request, and never retries a dispatched
48
+ inference POST automatically. `RUNBIOS_INFERENCE_TIMEOUT_MS` defaults to
49
+ `900000` (15 minutes). Its idempotency header is propagated, but replay/dedup is
50
+ not claimed without an explicit acknowledgement from the endpoint.
51
+
52
+ ## What the platform derives (never a tool input)
53
+
54
+ An MCP client acts on the schema it is given, so the schema must not offer a
55
+ choice the control plane will reject. These serving settings are SERVER-DERIVED
56
+ and immutable, and are therefore absent from `create_inference` /
57
+ `preflight_inference`:
58
+
59
+ - `serving_mode` (`full` / `adapter` / `merged`) — from the verified
60
+ checkpoint's lineage; a catalog base model is served whole.
61
+ - `model_task` — from the resolved model: `chat` for an instruct or
62
+ vision-language model, `completion` for a base model with no chat template,
63
+ `embedding`/`rerank` for those architectures. Every model in the current
64
+ catalog resolves to `chat`, so there is nothing to select.
65
+ - `supports_images` — from the model's own config, never a caller claim.
66
+
67
+ They are reported back on the preflight/create/status responses, which is where
68
+ a caller reads them. Copying `canonical_request` from preflight into create
69
+ still works: the echoed derived fields are dropped rather than rejected.
70
+
71
+ `context_length` is the one window a caller may narrow. The policy the server
72
+ enforces is: default `min(native max, 262144)`, falling back to `32768` when the
73
+ native window is unknown (a model with a smaller native window keeps its own),
74
+ and the hard ceiling is always the model's OWN native max. No static JSON Schema
75
+ can express a per-model ceiling, so the tool advertises a `1024`-`1048576` sanity
76
+ range and its description names the real authority —
77
+ `model.native_max_context` from `preflight_inference`. An out-of-range value is
78
+ rejected with the whole policy in the message, not a bare bounds error.
79
+
80
+ `max_seq_length` (training) follows the same rule: the platform default is
81
+ `2048`, the hard ceiling is the base model's OWN window, and the authoritative
82
+ field bounds live in `get_training_capabilities` (`max_length`), so the tool
83
+ advertises the contract's `128` floor and the same `1048576` sanity ceiling
84
+ instead of the meaningless `10000000` it used to. `chat_with_inference`'s
85
+ `max_tokens` is bounded the same way — the real ceiling is the served window
86
+ minus the prompt.
87
+
88
+ ## Spend consent is bounded by what the platform can charge
89
+
90
+ `max_price_hour_cents` is the one field that authorizes money: the maximum
91
+ accepted TOTAL hourly price for the complete GPU count. The control plane has no
92
+ ceiling of its own — it only refuses a cap BELOW the primary placement's
93
+ verified price — so `z.number().int()` alone advertised `9007199254740991`, i.e.
94
+ told an agent that consenting to nine quadrillion cents an hour is a legal
95
+ request. All five money-bearing tools (`preflight_inference`, `create_inference`,
96
+ `update_inference_policy`, `preflight_training_job`, `create_training_job`) now
97
+ cap it at **100000 cents ($1,000/hour)**: the dearest configuration the GPU
98
+ catalog can book is 8 x B300 at 8280 cents/hour, and the beta serving cap
99
+ (models up to ~130B total parameters) keeps a deployment inside that ladder, so
100
+ the bound has an order of magnitude of headroom and still reads as a limit
101
+ rather than a blank cheque. It is a sanity bound on consent, never a price
102
+ quote: `get_gpu_pricing` and the preflight response carry the real numbers, and
103
+ the rejection message says all of this.
104
+
105
+ ## Error text an agent can act on
106
+
107
+ - A capacity rejection stays the structured compact JSON below.
108
+ - A model id outside the Run BiOS catalog answers `MODEL_NOT_HOSTED` and names every
109
+ route to a valid id (`list_models`, `search_models`, ids already returned by
110
+ `list_inferences`/`list_training_jobs`, the console Models page) plus what to
111
+ do when none of them answer — recovery advice never dead-ends on one tool.
112
+ - A model-resolution failure answers `MODEL_NOT_RESOLVED` /
113
+ `MODEL_REVISION_UNRESOLVED` in the platform's own terms. The upstream mirror's
114
+ transport error is never forwarded: an agent cannot act on a vendor's 404, and
115
+ the vendor is not the answer to "why can this not be sized".
116
+ - A workspace-scoped read with no workspace context answers
117
+ `WORKSPACE_CONTEXT_REQUIRED` and says the fix is configuration
118
+ (`RUNBIOS_WORKSPACE_ID` or a workspace-bound API key), not a retry.
119
+ - An unknown id answers a structured `<THING>_NOT_FOUND`
120
+ (`INFERENCE_NOT_FOUND`, `TRAINING_JOB_NOT_FOUND`, `CHECKPOINT_NOT_FOUND`,
121
+ `DATASET_NOT_FOUND`, `INTEGRATION_NOT_FOUND`, `BOOKING_NOT_FOUND`, or
122
+ `RESOURCE_NOT_FOUND` for a path with no id) carrying the id, the resource, the
123
+ upstream code, and the read that hands out valid ids — never the bare sentence
124
+ ("Deployment not found") an agent would have to string-match. A 404 on a static
125
+ endpoint under the same prefix (`/api/inference/gpu-options`) is never reported
126
+ as an id that does not exist.
127
+ - FastAPI-shaped bodies (`{"detail": ...}`) are read as well as
128
+ `{"error":{"message"}}`, and a response with no message at all reports the
129
+ status, the path, and what to do — never a bare `BIOS API error 400`.
130
+ - No tool result is ever a bare `null`.
131
+ - `get_inference_status` adds a derived `status_summary` (phase, `count x SKU`
132
+ GPU, `endpoint_usable`, queue/GPU state) built only from fields in the same
133
+ response, so a field that is null before the pod reports is not read as "no
134
+ GPU" or "no phase".
135
+ - `list_supported_architectures` labels its rows as manifest provenance. Which
136
+ build published a row is registry provenance, never the image the platform is
137
+ running, and the control plane no longer publishes it at all: the public
138
+ projection drops `image_tag`, `bios_version` and `manifest_sha256` at the
139
+ boundary. Internal build references are additionally removed **by content**
140
+ from every string of every scope, not by field name: dropping the `image_tag`
141
+ key alone left the identical `<registry>/<engine>@sha256:<digest>` in the
142
+ free-text `notes` of 194 training-scope rows, and a rule that matches only a
143
+ key is defeated the moment the value moves. `manifest_provenance` reports how
144
+ many references were redacted and how many rows came from a published manifest
145
+ — counted from `source`, the one provenance field that survives the
146
+ projection. It publishes no build-version or digest counts, because after the
147
+ projection those could only ever report 0, and a standing 0 reads as "the
148
+ registry records no build" rather than "this response cannot see one".
149
+
150
+ ## Safe inference workflow
151
+
152
+ 1. `get_inference_gpu_options` returns inference-fit configurations joined to
153
+ authoritative deployment prices and stock. Prefer the MODEL-ADDRESSED form
154
+ (`model=<catalog_model_id>` — an id from `list_models`/`search_models`,
155
+ optionally `revision`): the server resolves the model facts (vision-aware)
156
+ and answers with computed
157
+ `min_gpus`, `valid_counts`, and `bookable_counts` — the exact minimums the
158
+ create gate enforces, so client-supplied facts can never understate a
159
+ minimum. `unknown` is not treated as available; never offer a count below
160
+ `min_gpus` or outside `bookable_counts`.
161
+ 2. `preflight_inference` validates the exact source, model, GPU, queue, price
162
+ cap, and wallet-hold terms without creating a deployment or mutating funds.
163
+ 3. `create_inference` uses the same request contract, self-validates the
164
+ selection against the model-addressed minimums before submitting, and
165
+ follows BOOK-BEFORE-REVEAL: a non-queued create answers `202` with a
166
+ booking handle while the GPU is secured on real capacity (30-40 s typical); the
167
+ tool polls the handle and returns the full payload — including the ONE-TIME
168
+ `inference_key` — only after the GPU is confirmed. A definitive miss
169
+ returns the standard `CAPACITY_UNAVAILABLE` stock-miss error with fresh
170
+ alternatives and NO deployment exists (nothing charged). A selection that
171
+ can never run at all is refused up front with a permanent code instead
172
+ (see below). Queueing is opt-in, a maximum
173
+ total hourly price is explicit, and a GPU alternative is never selected
174
+ without approval. `gpu_priorities` backups (1-5 ranked choices) may be
175
+ supplied on ANY create — they form the after-start replacement ladder;
176
+ the queue itself stays opt-in.
177
+ 4. `get_inference_booking` polls a booking handle from a long-running
178
+ `create_inference` (pending → revealed payload → or the standard 409).
179
+ 5. `get_inference_status` reports durable queue state, provisioning/loading,
180
+ endpoint readiness, price cap, notification summary, and wallet
181
+ authorization. An endpoint is live only when the returned status is
182
+ `running`. `queued_capacity` means waiting for stock at zero charge —
183
+ after a deployment has ever been booked, GPU loss never ends in a
184
+ capacity failure; it waits and auto-resumes.
185
+ 6. `get_inference_notifications` reports durable lifecycle-email delivery,
186
+ bounded retries, and operator-visible dead letters.
187
+ 7. `update_inference_policy`, `stop_inference`, `resume_inference`,
188
+ `restart_inference`, and `delete_inference` manage the lifecycle.
189
+
190
+ ## Standard GPU rejection contract
191
+
192
+ Training and inference speak ONE GPU rejection shape, with a `code` that says
193
+ whether waiting can ever help. The MCP transport serializes every code in the
194
+ family as compact JSON in the tool-error text, even when zero alternatives
195
+ exist, so an agent can act on it directly.
196
+
197
+ | `reason` | `code` | HTTP | `queue_offered` |
198
+ | --- | --- | --- | --- |
199
+ | `insufficient_stock` | `CAPACITY_UNAVAILABLE` | 409 | `true` |
200
+ | `model_too_large` | `GPU_TYPE_TOO_SMALL` | 400 | `false` |
201
+ | `below_model_minimum` | `GPU_COUNT_BELOW_MINIMUM` | 400 | `false` |
202
+ | `invalid_gpu_count` | `GPU_COUNT_INVALID` | 400 | `false` |
203
+ | `gpu_unsupported` | `GPU_TYPE_UNSUPPORTED` | 400 | `false` |
204
+
205
+ `CAPACITY_UNAVAILABLE` is the ONLY transient one: the GPU is not free right
206
+ now, so retrying later or joining the queue can succeed (training also echoes
207
+ the deprecated `SELECTED_GPU_UNAVAILABLE` as `legacy_code` for one release).
208
+ The four `400` codes are permanent facts about the request itself, so the queue
209
+ is never offered for them and resending the same `gpu_type`/`gpu_count` can
210
+ never succeed. Branch on `retryable_as_submitted` (or `queue_offered`) rather
211
+ than on the message text.
212
+
213
+ ```json
214
+ {
215
+ "code": "GPU_TYPE_TOO_SMALL",
216
+ "status": 400,
217
+ "reason": "model_too_large",
218
+ "message": "This model does not fit on L40_48GB, at any GPU count.",
219
+ "recoverable": true,
220
+ "retryable_as_submitted": false,
221
+ "instruction": "This GPU cannot run the request as submitted, and free capacity will never change that. Retry the same call with gpu_type and gpu_count set to one of available_gpus, never below that entry's min_gpus. Do not set allow_capacity_queue, and do not send the same gpu_type and gpu_count again: neither waiting nor the queue can make this request fit.",
222
+ "available_gpus": [
223
+ { "gpu_type": "H100_80GB", "gpu_count": 2, "min_gpus": 2, "valid_counts": [2, 4, 8],
224
+ "tier": "secure", "price_hour_cents": 598, "available_count": 6 }
225
+ ],
226
+ "minimum_requirement": {
227
+ "selected_gpu_min": 2, "selected_valid_counts": [2, 4, 8],
228
+ "per_type": [{ "gpu_type": "H100_80GB", "min_gpus": 2, "valid_counts": [2, 4, 8] }]
229
+ },
230
+ "selected": { "gpu_type": "L40_48GB", "gpu_count": 2, "availability_status": "available" },
231
+ "queue_offered": false,
232
+ "queue_eligible": false,
233
+ "gpu_priorities_entry": 1,
234
+ "checked_at": "2026-07-19T00:00:00Z"
235
+ }
236
+ ```
237
+
238
+ `available_alternatives` is the deprecated alias of `available_gpus`, and
239
+ `gpu_priorities_entry` is the 1-based rank of the refused `gpu_priorities`
240
+ entry when the rejection belongs to one. A stock miss carries the same shape
241
+ with `code: "CAPACITY_UNAVAILABLE"`, `queue_offered: true`,
242
+ `retryable_as_submitted: true`, and an `instruction` that does offer
243
+ `allow_capacity_queue=true`.
244
+
245
+ A `503 ADMISSION_UNAVAILABLE` is a transient market outage, never a capacity
246
+ verdict. Retry shortly, and never conclude out-of-stock from it. See
247
+ `get_platform_guide` topic `capacity_errors` for the recovery playbook.
248
+
249
+ Models are selectable only from the platform's own catalog: `list_models`
250
+ and `search_models` read the hosted registry, and `create_training_job`,
251
+ `preflight_training_job`, `create_inference`, and `preflight_inference`
252
+ validate the model id against it, answering `MODEL_NOT_HOSTED` — never a
253
+ live Hugging Face lookup. Catalog models are pre-mirrored, so raw access
254
+ tokens are intentionally excluded from MCP tool inputs and no integration is
255
+ needed for models; Hugging Face integrations remain for dataset imports via
256
+ `import_huggingface_dataset`.
257
+
258
+ `create_training_job` and `resume_training_job` accept an optional
259
+ `idempotency_key`. Reuse the same value after a timeout so the API replays the
260
+ original acknowledgement instead of creating another job, wallet hold, queue
261
+ entry, or GPU request. The MCP server generates a fresh key when it is omitted.
262
+
263
+ ## Training lifecycle (book-before-reveal)
264
+
265
+ Like inference, `create_training_job` books a GPU before it reveals a job.
266
+ The call blocks while the ranked ladder is booked (~40s typical); a job id and
267
+ the "training started" email exist only once a real pod is secured. Read
268
+ `status` on the response and from `get_training_status`:
269
+
270
+ - `booked` — a GPU is secured (booked == secured); the job then provisions,
271
+ downloads, and runs on its own. Money is captured only after this point.
272
+ - `securing` — still booking at the deadline; the async tail continues.
273
+ Poll `get_training_status` until `booked`/`running` (or a terminal state).
274
+ Nothing is charged while securing.
275
+ - `queued` — returned only with explicit queue consent; waits for stock at
276
+ zero charge and books via the same path.
277
+ - A booking-time stock miss returns the standard `CAPACITY_UNAVAILABLE`
278
+ (409) with fresh `available_gpus` and **no job exists** (nothing charged);
279
+ a selection that can never run returns one of the permanent `400` codes
280
+ above instead, and no queue is offered for it. A transient
281
+ `503 ADMISSION_UNAVAILABLE` is a retry, never out-of-stock, and the platform
282
+ never auto-substitutes a GPU.
283
+
284
+ If a started job loses its pod, it rests at `interrupted` — a self-heal state
285
+ distinct from `failed` in which billing is already stopped. Call
286
+ `resume_training_job` to continue from the last checkpoint; resume re-books on
287
+ secured capacity before reporting resumed, so the same guarantees apply.
288
+
289
+ Training and deployment share one authoritative GPU-capacity broker. Server
290
+ priority is evaluated first. Within an equal-priority class, the broker rotates
291
+ across users and preserves FIFO order within each user's own requests. This
292
+ anti-starvation policy prevents one user's large backlog from monopolizing GPU
293
+ capacity; consequently, a newer request from another user can run before an
294
+ older request in a busy user's backlog.
295
+
296
+ For each eligible request, the broker selects the lowest-ranked approved GPU
297
+ choice that is live, has enough cards, and remains within the accepted total
298
+ hourly price cap. Thus an older request's third choice can receive capacity
299
+ before a newer request's first choice. Unknown/stale provider inventory issues
300
+ no grant. Every vendor mutation requires the broker's short-lived fenced grant,
301
+ and atomic reservations prevent training and deployment replicas from booking
302
+ the same cards.
303
+
304
+ If capacity disappears before the vendor mutation, a queued request releases
305
+ the grant and returns to its original global position. Expired worker leases
306
+ are fenced and recovered with a new grant identity. Ambiguous provider results
307
+ retain the same immutable provisioning request and grant fence until
308
+ reconciliation proves success or definitive failure, so MCP clients should
309
+ retry with the same idempotency key rather than submit a replacement request.
310
+
311
+ ## What never leaves the platform
312
+
313
+ An MCP client is a program the customer runs, so every tool result is a
314
+ customer-facing response. Two facts never appear in one:
315
+
316
+ - **Container build identity** — any image name, version, tag, digest or path.
317
+ `list_supported_architectures` used to ship the engine image reference that
318
+ published each registry row (`image_tag`, `bios_version`, `manifest_sha256`,
319
+ and the same tag again inside the free-text `notes`), and
320
+ `get_training_capabilities` shipped `image_compatibility`, the engine image
321
+ VERSION range the contract was written against. Both are removed at the tool
322
+ boundary (`src/redaction.ts`), by CONTENT as well as by key — a key-only rule
323
+ is defeated the moment the same value moves one field over, which is exactly
324
+ what `notes` did. Architecture support is unchanged and still authoritative:
325
+ the deployment and training gates read those same rows, and the response says
326
+ so. Engine text that reaches an agent as an error (upstream bodies, serving
327
+ exceptions, training log lines) goes through the same content rule.
328
+ - **Which supplier backs a placement** — the control plane already answers the
329
+ neutral `bios-cloud` / `global` placement labels, and the tools echo those
330
+ unchanged. Tool text describes what was bought ("the GPU is secured on real
331
+ capacity"), never who sold it.
332
+
333
+ ## Hosted (remote) connector — implemented, not planned
334
+
335
+ Besides the stdio entry (`dist/index.js`), this package ships the hosted
336
+ Streamable HTTP connector used by clients that only accept remote MCP servers
337
+ (`dist/http/main.js`, ECS service `bios-mcp`, default port 7152). It is built and
338
+ deployed, not a roadmap item:
339
+
340
+ - `POST {PUBLIC_BASE_URL}/mcp` — stateless Streamable HTTP endpoint. `GET` and
341
+ `DELETE` answer `405`; an unauthenticated call answers `401` with the
342
+ RFC 9728 `WWW-Authenticate: Bearer resource_metadata=...` challenge.
343
+ - OAuth 2.1 authorization server with dynamic client registration and PKCE,
344
+ advertised at `/.well-known/oauth-protected-resource`,
345
+ `/.well-known/oauth-authorization-server`, and
346
+ `/.well-known/openid-configuration` (each also under `/mcp`).
347
+ - Consent is granted in the console (`CONSOLE_BASE_URL`); every request
348
+ re-validates the bearer token and re-checks scopes and workspace membership
349
+ through the internal api-gateway.
350
+ - `chat_with_inference` is intentionally unavailable on the hosted connection:
351
+ a deployment's serving key is shown exactly once at create time and is never
352
+ retrievable, so the tool fails with that explanation instead of a
353
+ local-environment hint that does not apply.
354
+
355
+ Any documentation still describing the hosted connector as "planned" is stale.
356
+
357
+ ## Verification
358
+
359
+ ```bash
360
+ npm test
361
+ npm run typecheck
362
+ npm run build
363
+ ```
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The ONE transient GPU rejection code: the GPU is not in stock right now.
3
+ * Stock changes, so retrying later or joining the capacity queue is a real
4
+ * answer here (and ONLY here).
5
+ */
6
+ export declare const CAPACITY_UNAVAILABLE_CODE = "CAPACITY_UNAVAILABLE";
7
+ /**
8
+ * Deprecated training-side alias of CAPACITY_UNAVAILABLE, still echoed for one
9
+ * release. It means the same transient thing.
10
+ */
11
+ export declare const LEGACY_CAPACITY_UNAVAILABLE_CODE = "SELECTED_GPU_UNAVAILABLE";
12
+ /**
13
+ * Permanent GPU rejection codes. Each one is a fixed fact about the request
14
+ * itself: the model does not fit on that GPU, the GPU count is below what the
15
+ * model needs, the count cannot be used for this model, or that GPU cannot run
16
+ * the model at all. Free stock never changes any of them, so waiting and the
17
+ * capacity queue can never turn one of these into a booking. Callers MUST NOT
18
+ * offer the queue for these.
19
+ */
20
+ export declare const PERMANENT_GPU_CODES: readonly ["GPU_TYPE_TOO_SMALL", "GPU_COUNT_BELOW_MINIMUM", "GPU_COUNT_INVALID", "GPU_TYPE_UNSUPPORTED"];
21
+ /** Resolve the machine code for a rejection reason. */
22
+ export declare function gpuRejectionCodeFor(reason?: string): string;
23
+ /** True when this code can never be fixed by waiting for stock. */
24
+ export declare function isPermanentGpuCode(code: unknown): boolean;
25
+ /**
26
+ * HTTP status for a rejection reason: 409 for a stock miss (a retry can resolve
27
+ * it), 400 for a permanent rejection the request itself has to change.
28
+ */
29
+ export declare function gpuRejectionStatusFor(reason?: string): number;
30
+ /** Every code whose body carries the standard GPU rejection fields. */
31
+ export declare function isGpuRejectionCode(code: unknown): boolean;
32
+ /**
33
+ * The recovery instruction handed to the calling agent. It branches on whether
34
+ * the rejection is permanent, because "wait for stock" is actively harmful
35
+ * advice on a request that can never book: the agent would park a deployment
36
+ * that no amount of capacity can satisfy.
37
+ */
38
+ export declare function gpuRejectionInstruction(opts: {
39
+ permanent: boolean;
40
+ hasAlternatives: boolean;
41
+ /** How to name the retry, e.g. "create_inference". Defaults to "the same call". */
42
+ call?: string;
43
+ }): string;
44
+ export interface ApiOpts {
45
+ method?: string;
46
+ body?: unknown;
47
+ params?: Record<string, string | undefined>;
48
+ formData?: FormData;
49
+ headers?: Record<string, string>;
50
+ }
51
+ export interface BiosClientOptions {
52
+ /** Control-plane base URL; no trailing slash. */
53
+ baseUrl: string;
54
+ /** Control-plane auth headers: {"X-API-Key": k} or {"Authorization": "Bearer ..."}. */
55
+ authHeaders: Record<string, string>;
56
+ orgId?: string;
57
+ workspaceId?: string;
58
+ /** Base URL for inference calls; defaults to baseUrl. */
59
+ inferenceBaseUrl?: string;
60
+ /** Authorization header value for inference calls (may be empty). */
61
+ inferenceAuthHeader?: string;
62
+ /**
63
+ * Fallback Authorization header for /v1/chat/completions when no dedicated
64
+ * deployment key (inferenceAuthHeader) is set. A workspace platform key with
65
+ * serverless scope authenticates model-ID-routed serverless catalog calls;
66
+ * the unified /v1 gateway routes by the request's `model`. Dedicated
67
+ * deployment keys still take precedence when present.
68
+ */
69
+ serverlessAuthHeader?: string;
70
+ /** Inference request timeout in milliseconds; defaults to 900000. */
71
+ inferenceTimeoutMs?: number;
72
+ /** User-Agent header; defaults to "bios-mcp/<VERSION>". */
73
+ userAgent?: string;
74
+ }
75
+ /**
76
+ * Format a failed control-plane response as error text an agent can act on.
77
+ *
78
+ * Four shapes come out of here:
79
+ * 1. Capacity-class rejections stay the structured compact JSON of the
80
+ * standard capacity contract (book-first §3) — unchanged.
81
+ * 2. A missing-workspace 400 becomes a structured, self-recoverable error
82
+ * naming the exact fix, because "Workspace context required" alone does
83
+ * not tell an agent where workspace context comes from.
84
+ * 3. A 404 becomes a structured `<THING>_NOT_FOUND` naming the id, the
85
+ * resource, and the read that hands out valid ids — the same shape as
86
+ * MODEL_NOT_HOSTED, so an unknown id never has to be string-matched out
87
+ * of a bare sentence.
88
+ * 4. Everything else keeps the API's own prose (so a real message is never
89
+ * paraphrased), and a response with NO message gets the status, the path,
90
+ * and what to do about it instead of a bare error code.
91
+ *
92
+ * CUSTOMER BOUNDARY: whichever shape comes out, it is redacted before it is
93
+ * returned. Every one of the four carries text the platform did not author —
94
+ * an upstream message, a proxy body, an engine exception — so build identity
95
+ * (image name/tag/digest/version) can ride any of them out to an MCP client.
96
+ * The redaction is applied HERE rather than at the single throw site so the
97
+ * exported helper cannot be reused later without it.
98
+ */
99
+ export declare function formatApiError(status: number, text: string, path: string): string;
100
+ export declare class BiosClient {
101
+ private readonly baseUrl;
102
+ private readonly authHeaders;
103
+ private readonly orgId;
104
+ private readonly workspaceId;
105
+ private readonly inferenceBaseUrl;
106
+ private readonly inferenceAuthHeader;
107
+ private readonly serverlessAuthHeader;
108
+ private readonly inferenceTimeoutMs;
109
+ private readonly userAgent;
110
+ constructor(opts: BiosClientOptions);
111
+ /**
112
+ * Tri-state probe against the public model registry (model-service):
113
+ * "hosted" — the exact author/name id is a verified catalog model;
114
+ * "not_hosted" — the registry definitively answered 404; "unknown" — the
115
+ * registry could not answer (network/5xx). Callers must treat "unknown" as
116
+ * advisory only and let the server-side gate enforce: never fail closed.
117
+ */
118
+ modelRegistryStatus(modelId: string): Promise<"hosted" | "not_hosted" | "unknown">;
119
+ api<T = unknown>(path: string, opts?: ApiOpts): Promise<T>;
120
+ /**
121
+ * Select the Authorization header for /v1/chat/completions. A dedicated
122
+ * deployment key (inferenceAuthHeader) wins when present; otherwise a
123
+ * serverless-scoped platform key (serverlessAuthHeader) authenticates
124
+ * model-ID-routed serverless catalog calls on the unified endpoint.
125
+ */
126
+ private resolveInferenceAuth;
127
+ inferenceApi(body: Record<string, unknown>, idempotencyKey?: string, requestId?: string, callerSignal?: AbortSignal): Promise<unknown>;
128
+ /**
129
+ * POST a streaming /v1/chat/completions request, consume the SSE, and
130
+ * aggregate the deltas into ONE OpenAI-shaped completion. Content and
131
+ * reasoning_content are concatenated in arrival order (so reasoning surfaces
132
+ * as produced), tool-call fragments are reassembled by index, and the final
133
+ * usage row (requested via stream_options.include_usage) is attached. The
134
+ * MCP transport returns a single tool result, so streaming is used for
135
+ * on-the-wire liveness/real-TTFT parity while the caller receives the
136
+ * assembled message.
137
+ */
138
+ streamInferenceApi(body: Record<string, unknown>, idempotencyKey?: string, requestId?: string, callerSignal?: AbortSignal): Promise<unknown>;
139
+ }
140
+ /**
141
+ * Parse the OpenAI chat-completion SSE from `stream` and fold its deltas into a
142
+ * single non-streaming-shaped response. Content and reasoning_content are
143
+ * concatenated in arrival order; tool-call fragments are merged by index;
144
+ * finish_reason, role, id, model, and the final usage row are captured.
145
+ */
146
+ export declare function aggregateChatStream(stream: ReadableStream<Uint8Array>): Promise<Record<string, unknown>>;
147
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}