runbios-sdk 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.
package/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # runbios-sdk
2
+
3
+ Official TypeScript/Node.js SDK for the [Run BiOS](https://runbios.ai) fine-tuning platform API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install runbios-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { RunBiOS } from 'runbios-sdk';
15
+
16
+ const client = new RunBiOS({
17
+ apiKey: 'bios-...',
18
+ });
19
+
20
+ // Search the hosted catalog. Rows come straight from the model registry, so
21
+ // they are snake_case, and the HANDLE you pass everywhere else is repo_id
22
+ // (`id` is the registry UUID).
23
+ const models = await client.models.search({ query: 'llama', type: 'llm' });
24
+ console.log(`Found ${models.total} models`);
25
+ for (const m of models.models) {
26
+ console.log(`${m.repo_id} -- ${m.params_total_b}B params, ${m.maxContext} ctx`);
27
+ }
28
+
29
+ // Create a training job
30
+ const job = await client.training.create({
31
+ idempotencyKey: 'training-create-20260711-0001',
32
+ model: 'meta-llama/Llama-3.1-8B-Instruct',
33
+ datasetId: 'ds_abc123',
34
+ method: 'sft',
35
+ adapter: 'lora',
36
+ epochs: 3,
37
+ learningRate: 2e-4,
38
+ loraRank: 16,
39
+ gpuType: 'A100_80GB',
40
+ });
41
+ console.log(`Job ${job.id} created`);
42
+ ```
43
+
44
+ > **Note:** `BiOS` remains exported as a deprecated alias of `RunBiOS`, so
45
+ > existing code keeps working unchanged.
46
+
47
+ ## Authentication
48
+
49
+ ### API Key (recommended)
50
+
51
+ API keys start with `bios-` (legacy `usf-` keys stay valid).
52
+
53
+ ```typescript
54
+ const client = new RunBiOS({
55
+ apiKey: 'bios-...',
56
+ });
57
+ ```
58
+
59
+ ### Environment Variables
60
+
61
+ When `apiKey`, `baseUrl`, or `inferenceKey` is omitted from the config, the SDK
62
+ reads them from the environment:
63
+
64
+ ```bash
65
+ export RUNBIOS_API_KEY=bios-...
66
+ export RUNBIOS_BASE_URL=https://api.runbios.ai # optional; this is the default
67
+ export RUNBIOS_INFERENCE_KEY=sk-bios-... # optional; defaults to RUNBIOS_API_KEY
68
+ ```
69
+
70
+ The legacy `BIOS_API_KEY` / `BIOS_BASE_URL` / `BIOS_INFERENCE_KEY` names keep
71
+ working as fallbacks when the `RUNBIOS_*` variable is not set.
72
+
73
+ ```typescript
74
+ const client = new RunBiOS({}); // uses RUNBIOS_API_KEY / RUNBIOS_BASE_URL / RUNBIOS_INFERENCE_KEY
75
+ ```
76
+
77
+ ### JWT Access Token
78
+
79
+ ```typescript
80
+ const client = new RunBiOS({
81
+ accessToken: 'eyJhbG...',
82
+ orgId: 'org_abc123',
83
+ });
84
+ ```
85
+
86
+ ## Resources
87
+
88
+ ### Inference
89
+
90
+ Pass a per-deployment `sk-bios-...` key for a dedicated deployment. For
91
+ serverless catalog models you need nothing extra: `inferenceKey` falls back to
92
+ `RUNBIOS_INFERENCE_KEY` (legacy `BIOS_INFERENCE_KEY`) and then to the
93
+ control-plane `apiKey`, so a platform key
94
+ carrying the serverless scope calls `/v1` directly without being supplied twice.
95
+ The async iterator parses SSE across arbitrary chunk boundaries and aborts the
96
+ upstream request when iteration is stopped. Inference POSTs are not retried implicitly;
97
+ reuse an explicit `idempotencyKey` only when retrying the same request. The SDK
98
+ propagates the header but does not promise server-side replay/deduplication
99
+ without an explicit replay acknowledgement from the endpoint.
100
+
101
+ **Parameter naming on `/v1` calls.** Only the parameters this SDK names are
102
+ camelCase: `model`, `messages`, `tools`, `toolChoice`, `reasoningEffort`,
103
+ `inferenceKey`, `idempotencyKey`, `requestId`, `signal`. Every other key is
104
+ forwarded to the OpenAI-compatible endpoint **verbatim**, so it must use
105
+ OpenAI's own snake_case spelling — `max_tokens`, `top_p`, `stop`,
106
+ `frequency_penalty`, `presence_penalty`. The endpoint rejects an unknown key
107
+ rather than ignoring it, so `maxTokens` is a `400 Unknown parameter:
108
+ 'maxTokens'`, not a silently dropped limit.
109
+
110
+ ```typescript
111
+ const client = new RunBiOS({
112
+ apiKey: 'bios-control-plane-key',
113
+ inferenceKey: 'sk-bios-deployment-key', // omit to reuse apiKey
114
+ // inferenceBaseUrl: 'https://api-dev.runbios.ai', // explicit in dev
115
+ });
116
+
117
+ const controller = new AbortController();
118
+ for await (const chunk of client.inference.streamChatCompletions({
119
+ messages: [{ role: 'user', content: 'Look up record 42' }],
120
+ tools: [{
121
+ type: 'function',
122
+ function: {
123
+ name: 'lookup',
124
+ parameters: { type: 'object', properties: { id: { type: 'integer' } } },
125
+ },
126
+ }],
127
+ idempotencyKey: 'chat-42-attempt-1',
128
+ signal: controller.signal,
129
+ })) {
130
+ console.log(chunk);
131
+ }
132
+ // controller.abort() cancels an unfinished generation.
133
+ ```
134
+
135
+ #### Serverless catalog models
136
+
137
+ Call any catalog model by id on the unified `/v1` endpoint with a workspace
138
+ platform key that carries the serverless scope — no per-deployment key. The
139
+ gateway routes by `model`; dedicated deployments and serverless models share the
140
+ same endpoint. `reasoningEffort` is forwarded to the endpoint, and streaming
141
+ surfaces `content` and `reasoning_content` deltas incrementally.
142
+
143
+ A platform key with the serverless scope is enough — `inferenceKey` falls back
144
+ to `RUNBIOS_INFERENCE_KEY` (legacy `BIOS_INFERENCE_KEY`) and then to the
145
+ control-plane `apiKey`, so the same key
146
+ never has to be supplied twice.
147
+
148
+ ```typescript
149
+ const client = new RunBiOS({
150
+ apiKey: 'bios-platform-key-with-serverless-scope',
151
+ });
152
+
153
+ for await (const chunk of client.inference.streamChatCompletions({
154
+ model: 'meta-llama/Llama-3.1-8B-Instruct', // serverless catalog id
155
+ messages: [{ role: 'user', content: 'Explain tensor parallelism briefly.' }],
156
+ reasoningEffort: 'low',
157
+ })) {
158
+ const delta = (chunk.choices as any)?.[0]?.delta ?? {};
159
+ if (delta.reasoning_content) process.stdout.write(delta.reasoning_content);
160
+ if (delta.content) process.stdout.write(delta.content);
161
+ }
162
+
163
+ // Non-streaming; the final chunk carries `usage` when the model reports it.
164
+ const completion = await client.inference.chatCompletions({
165
+ model: 'meta-llama/Llama-3.1-8B-Instruct',
166
+ messages: [{ role: 'user', content: 'One sentence on GPUs.' }],
167
+ });
168
+ // The response is an open OpenAI-shaped record; narrow it to read the answer.
169
+ const choice = (completion.choices as Array<Record<string, any>>)?.[0];
170
+ const answer = choice?.message?.content as string | null | undefined;
171
+ if (answer) {
172
+ console.log(answer);
173
+ } else {
174
+ // A 200 is NOT proof the model answered. Reasoning models can spend the whole
175
+ // budget inside reasoning_content and return content: null with
176
+ // finish_reason "length" — an empty answer that looks like success. Always
177
+ // read choices[0].message.content, and raise max_tokens (or lower
178
+ // reasoningEffort) when finish_reason is "length". max_tokens is an OpenAI
179
+ // passthrough parameter, so it keeps OpenAI's snake_case spelling -- the
180
+ // camelCase form is rejected with a 400, not silently ignored.
181
+ console.log('no answer:', choice?.finish_reason,
182
+ 'reasoning tokens only:', Boolean(choice?.message?.reasoning_content));
183
+ }
184
+ ```
185
+
186
+ Streaming billing is charged server-side on completed usage; the SDK only needs
187
+ to request `usage` where the endpoint exposes it (no client change).
188
+
189
+ ### Models
190
+
191
+ The catalog lists only models hosted on Run BiOS (the platform's own verified
192
+ registry, mirrored in Run BiOS storage) — every result can be trained and
193
+ deployed; it is never a live Hugging Face search.
194
+
195
+ Search results are the registry's own rows: snake_case fields, `repo_id` as the
196
+ model handle (`id` is the registry UUID), plus `maxContext` — the native context
197
+ window that caps a deployment's `contextLength` — and `weightBytes`, the on-disk
198
+ weight size. `query` becomes the registry's `q` filter, the only search
199
+ parameter it reads.
200
+
201
+ ```typescript
202
+ // Search models
203
+ const results = await client.models.search({ query: 'llama', limit: 10 });
204
+ for (const m of results.models) {
205
+ console.log(m.repo_id, m.params_total_b, m.surface_type, m.maxContext, m.weightBytes);
206
+ }
207
+
208
+ // One model, by its author/name handle
209
+ const detail = await client.models.get('meta-llama/Llama-3.1-8B-Instruct');
210
+ console.log(detail.model.architecture, detail.model.maxContext);
211
+
212
+ // The context ceiling on its own — undefined when the registry does not record it
213
+ console.log(await client.models.nativeMaxContext('meta-llama/Llama-3.1-8B-Instruct'));
214
+
215
+ // Get model config
216
+ const config = await client.models.getConfig('meta-llama/Llama-3.1-8B-Instruct');
217
+
218
+ // Check adapter compatibility
219
+ const compat = await client.models.getAdapterCompatibility({
220
+ modelType: 'llama',
221
+ trainingMethod: 'sft',
222
+ });
223
+ ```
224
+
225
+ ### Datasets
226
+
227
+ ```typescript
228
+ // List datasets
229
+ const datasets = await client.datasets.list();
230
+
231
+ // Upload a dataset
232
+ const uploaded = await client.datasets.upload({
233
+ filePath: './data.jsonl',
234
+ name: 'My Dataset',
235
+ });
236
+
237
+ // Preview rows
238
+ const preview = await client.datasets.preview('ds_abc123', { pageSize: 5 });
239
+
240
+ // Import from HuggingFace
241
+ const imported = await client.datasets.importFromHuggingFace({
242
+ repoId: 'databricks/dolly-15k',
243
+ integrationId: 'int_abc123',
244
+ name: 'Dolly 15k',
245
+ });
246
+
247
+ // Validate before upload
248
+ const validation = await client.datasets.validate('./data.jsonl');
249
+ ```
250
+
251
+ ### Training
252
+
253
+ ```typescript
254
+ const request = {
255
+ idempotencyKey: 'training-create-20260711-0001',
256
+ model: 'meta-llama/Llama-3.1-8B-Instruct',
257
+ datasetIds: ['ds_abc123', 'ds_def456'],
258
+ method: 'sft' as const,
259
+ adapter: 'lora' as const,
260
+ queueIfUnavailable: true,
261
+ queueDeadline: '2026-07-18T00:00:00Z',
262
+ maxPriceHourCents: 500,
263
+ gpuPriorities: [
264
+ { gpuType: 'H100_80GB', gpuCount: 1 },
265
+ { gpuType: 'A100_80GB', gpuCount: 1 },
266
+ { gpuType: 'L40S_48GB', gpuCount: 1 },
267
+ ],
268
+ };
269
+
270
+ // Side-effect-free validation, canonical sizing, live stock and alternatives
271
+ const check = await client.training.preflight(request);
272
+ console.log(check.request_hash, check.recommended, check.queue_eligible);
273
+
274
+ // Create the paid job only after reviewing preflight
275
+ const job = await client.training.create(request);
276
+
277
+ // List jobs
278
+ const jobs = await client.training.list({ status: 'running' });
279
+ const page = await client.training.listPage({ limit: 50, offset: 0 });
280
+
281
+ // Get metrics
282
+ const metrics = await client.training.getMetrics('job_abc123');
283
+ for (const point of metrics.metrics) console.log(point.step, point.loss);
284
+
285
+ // Get checkpoints
286
+ const checkpoints = await client.training.getCheckpoints('job_abc123');
287
+
288
+ // Structured logs
289
+ const logs = await client.training.getLogs('job_abc123');
290
+ for (const entry of logs.logs) console.log(entry.level, entry.message);
291
+
292
+ // Stop / Resume
293
+ await client.training.stop('job_abc123');
294
+ await client.training.resume('job_abc123', 'training-resume-20260711-0001');
295
+ ```
296
+
297
+ ### Wallet
298
+
299
+ `balance_cents` is the deposited balance; `available_balance_cents` is what can
300
+ actually be spent right now (balance minus `active_holds_cents` and
301
+ `accruing_cents`). Spend decisions read the second one. Auto top-up is flat
302
+ (`auto_topup_enabled` / `auto_topup_threshold` / `auto_topup_amount`), and the
303
+ transaction list is a wrapped page.
304
+
305
+ ```typescript
306
+ // Get balance
307
+ const balance = await client.wallet.getBalance();
308
+ console.log(`Balance: $${(balance.balance_cents / 100).toFixed(2)}`);
309
+ console.log(`Spendable: $${(balance.available_balance_cents / 100).toFixed(2)}`);
310
+
311
+ // Transaction history -- the rows are WRAPPED, so read .transactions
312
+ const page = await client.wallet.getTransactions({ limit: 20 });
313
+ console.log(`${page.total} transactions`);
314
+ for (const t of page.transactions) {
315
+ console.log(`${t.type}/${t.category}: $${(t.amount_cents / 100).toFixed(2)} -- ${t.description}`);
316
+ }
317
+ ```
318
+
319
+ ### Inference deployments
320
+
321
+ `allowCapacityQueue: true` is explicit consent to wait for stock, so it requires
322
+ 3 to 5 ranked `gpuPriorities` — the SDK rejects the combination locally before
323
+ any request. Leave the queue off to book exactly one placement.
324
+
325
+ `servingMode`, `modelTask`, and `supportsImages` are all **server-derived and
326
+ immutable**: the platform reads them off the resolved model and rejects a
327
+ conflicting assertion, so omit them and read the result back from the
328
+ deployment. Which `modelTask` values are accepted depends on the model, so the
329
+ SDK does not judge one locally — it forwards whatever you pass and the server
330
+ rules on it. `preflight()` echoes the derived task at
331
+ `canonical_request.model_task` if you want to see it before creating.
332
+
333
+ `contextLength` is optional and follows one policy: omit it and the server
334
+ pre-fills `min(nativeMax, 262144)`; the window is adjustable only when the
335
+ model's native max exceeds the 32,768 floor, and it can never exceed the model's
336
+ own native max. It is also a **sizing input** — a bigger window means a bigger
337
+ KV cache, which can raise the minimum GPU count. Read the ceiling with
338
+ `client.models.nativeMaxContext(modelId)` first, and read it back from
339
+ `native_max_context` on the deployment detail. Pass a whole number: the local
340
+ capacity pre-check only runs on a window it can prove fits, so anything else (a
341
+ numeric string out of a JSON config included) is forwarded for the server to
342
+ answer.
343
+
344
+ ```typescript
345
+ const nativeMax = await client.models.nativeMaxContext('meta-llama/Llama-3.1-8B-Instruct');
346
+ const request = {
347
+ name: 'llama-api',
348
+ sourceType: 'hf_model' as const,
349
+ hfModelId: 'meta-llama/Llama-3.1-8B-Instruct',
350
+ gpuType: 'H100_80GB',
351
+ gpuCount: 1,
352
+ // Queueing needs 3-5 ranked placements; the first is the one booked now.
353
+ allowCapacityQueue: true,
354
+ gpuPriorities: [
355
+ { gpuType: 'H100_80GB', gpuCount: 1 },
356
+ { gpuType: 'A100_80GB', gpuCount: 1 },
357
+ { gpuType: 'L40S_48GB', gpuCount: 2 },
358
+ ],
359
+ // Never ask for more context than the model has.
360
+ ...(nativeMax ? { contextLength: Math.min(65536, nativeMax) } : {}),
361
+ maxPriceHourCents: 500,
362
+ };
363
+
364
+ // No wallet mutation and no GPU allocation.
365
+ const check = await client.inference.preflight(request);
366
+ console.log(check.selected_gpu, check.alternatives, check.billing);
367
+
368
+ // Creates only after your automation accepts the live price/queue terms.
369
+ const deployment = await client.inference.create(request, 'deploy-create-20260711-0001');
370
+ console.log(deployment.inference_key); // returned once; store securely
371
+
372
+ const status = await client.inference.status(deployment.id);
373
+ console.log(status.status, status.status_reason, status.queue_expires_at, status.wallet_authorization_status);
374
+ // The detail resolves the per-deployment serving settings a list row omits.
375
+ console.log(status.context_length, status.native_max_context, status.model_task);
376
+ // status remains "failed" for existing filters when status_reason is "queue_expired".
377
+ const notificationHistory = await client.inference.notifications(deployment.id);
378
+ console.log(notificationHistory.map(({ event_type, state, attempt_count }) => ({ event_type, state, attempt_count })));
379
+
380
+ // Bounded newest-first listing. Reuse next_cursor with unchanged filters.
381
+ // LIST rows are a lean grid projection: the model handle is `model_ref` (there
382
+ // is no `model` key) and the serving settings above are absent -- call
383
+ // client.inference.get(id) for those.
384
+ const page = await client.inference.listPage({ limit: 100, status: 'running', search: 'llama' });
385
+ if (page.has_more && page.next_cursor) {
386
+ const older = await client.inference.listPage({ limit: 100, status: 'running', search: 'llama', cursor: page.next_cursor });
387
+ console.log(older.deployments);
388
+ }
389
+ // Or traverse lazily without one unbounded response.
390
+ for await (const item of client.inference.iterate({ status: 'running' })) {
391
+ console.log(item.name, item.model_ref, item.requests_total);
392
+ }
393
+
394
+ await client.inference.stop(deployment.id);
395
+ await client.inference.delete(deployment.id);
396
+ ```
397
+
398
+ ### GPU
399
+
400
+ ```typescript
401
+ // Get pricing
402
+ const pricing = await client.gpu.getPricing();
403
+
404
+ // Authoritative model-aware choices, live stock, total pricing, and alternatives
405
+ const options = await client.gpu.getOptions({
406
+ modelId: 'meta-llama/Llama-3.1-8B',
407
+ trainType: 'qlora',
408
+ method: 'sft',
409
+ });
410
+
411
+ // Get recommendation for a model
412
+ const rec = await client.gpu.getRecommended('meta-llama/Llama-3.1-8B');
413
+ ```
414
+
415
+ ### Key Introspection
416
+
417
+ ```typescript
418
+ const info = await client.introspect();
419
+ console.log(`Org: ${info.org.name}`);
420
+ console.log(`Scopes: ${info.scopes.join(', ')}`);
421
+ ```
422
+
423
+ ## Error Handling
424
+
425
+ ```typescript
426
+ import { RunBiOS, ApiError } from 'runbios-sdk';
427
+
428
+ try {
429
+ await client.training.get('bad_id');
430
+ } catch (err) {
431
+ if (err instanceof ApiError) {
432
+ console.log(`Status: ${err.status}`);
433
+ console.log(`Message: ${err.message}`);
434
+ console.log(`Code: ${err.code}`);
435
+ }
436
+ }
437
+ ```
438
+
439
+ ## Configuration
440
+
441
+ | Option | Default | Description |
442
+ |--------|---------|-------------|
443
+ | `apiKey` | `RUNBIOS_API_KEY` env var (legacy `BIOS_API_KEY`) | API key (`bios-...`; legacy `usf-...` keys stay valid) |
444
+ | `accessToken` | — | JWT access token |
445
+ | `orgId` | — | Organization ID (auto-resolved with API keys) |
446
+ | `workspaceId` | — | Workspace ID (auto-resolved with API keys) |
447
+ | `baseUrl` | `RUNBIOS_BASE_URL` env var (legacy `BIOS_BASE_URL`), then `https://api.runbios.ai` | Canonical production hostname (release-gated; this documentation does not assert current availability). During prelaunch/dev, pass `https://api-dev.runbios.ai` explicitly. |
448
+ | `timeout` | `30000` | Request timeout in ms |
449
+ | `inferenceKey` | `RUNBIOS_INFERENCE_KEY` env var (legacy `BIOS_INFERENCE_KEY`), then `apiKey` | Key used by `client.inference` for `/v1` calls. A per-deployment `sk-bios-...` key, or the platform `apiKey` itself when it carries the serverless scope — you never pass the same key twice |
450
+ | `inferenceBaseUrl` | `baseUrl` | Explicit dev or production inference hostname |
451
+ | `inferenceTimeout` | `900000` | End-to-end inference/stream timeout in ms |
452
+
453
+ ## Requirements
454
+
455
+ - Node.js >= 18.0.0
456
+ - ESM modules
457
+
458
+ ## License
459
+
460
+ MIT
@@ -0,0 +1,202 @@
1
+ import type { BiOSConfig, ApiErrorBody, AvailableGpuAlternative, CapacityMinimumRequirement } from './types.js';
2
+ /**
3
+ * Typed error thrown by every SDK method when the API returns a non-2xx status.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * try {
8
+ * await client.training.get('bad_id');
9
+ * } catch (err) {
10
+ * if (err instanceof ApiError && err.status === 404) {
11
+ * console.log('Job not found');
12
+ * }
13
+ * }
14
+ * ```
15
+ *
16
+ * Availability rejections are self-recoverable: when the selected GPU is no
17
+ * longer bookable at submit time the API answers 409 with a machine code and
18
+ * the currently bookable alternatives, and the SDK surfaces them typed:
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * try {
23
+ * await client.training.create({ ...req, gpu_type: 'A100_80GB' });
24
+ * } catch (err) {
25
+ * if (err instanceof ApiError && err.code === 'SELECTED_GPU_UNAVAILABLE') {
26
+ * const next = err.availableGpus?.[0];
27
+ * if (next) await client.training.create({ ...req, gpu_type: next.gpu_type });
28
+ * }
29
+ * }
30
+ * ```
31
+ */
32
+ export declare class ApiError extends Error {
33
+ /** HTTP status code (e.g. 401, 404, 500). */
34
+ readonly status: number;
35
+ /** Machine-readable error code from the API, if provided. */
36
+ readonly code: string | undefined;
37
+ /** Server-assigned request ID for support / debugging. */
38
+ readonly requestId: string | undefined;
39
+ /**
40
+ * The full parsed error response body. Availability rejections carry
41
+ * structured recovery data here (`available_gpus`, `checked_at`).
42
+ */
43
+ readonly body: ApiErrorBody;
44
+ /**
45
+ * Bookable-now GPU alternatives on availability rejections
46
+ * (SELECTED_GPU_UNAVAILABLE / CAPACITY_UNAVAILABLE); undefined otherwise.
47
+ * Every entry fit the requested model when `checkedAt` was stamped —
48
+ * resubmit with one of these and nothing else changed.
49
+ */
50
+ readonly availableGpus: AvailableGpuAlternative[] | undefined;
51
+ /** Availability snapshot time behind an availability rejection. */
52
+ readonly checkedAt: string | undefined;
53
+ constructor(status: number, body: ApiErrorBody);
54
+ }
55
+ /**
56
+ * The one transient GPU-rejection code: the GPU is not in stock right now, so
57
+ * waiting or joining the capacity queue can still succeed.
58
+ */
59
+ export declare const CAPACITY_UNAVAILABLE_CODE = "CAPACITY_UNAVAILABLE";
60
+ /**
61
+ * The permanent GPU-rejection codes (HTTP 400). Each is a fixed fact about the
62
+ * request: the model does not fit on that GPU type, the count is below the
63
+ * model's minimum, the count cannot split the model, or the serving engine
64
+ * cannot run that card at all. Stock is irrelevant to all four, so the capacity
65
+ * queue is never offered for them and retrying the same request never helps.
66
+ */
67
+ export declare const PERMANENT_GPU_CODES: readonly ["GPU_TYPE_TOO_SMALL", "GPU_COUNT_BELOW_MINIMUM", "GPU_COUNT_INVALID", "GPU_TYPE_UNSUPPORTED"];
68
+ /**
69
+ * Typed error for the standard GPU-rejection body, shared by training and
70
+ * inference. Covers BOTH the 409 stock miss and the 400 permanent rejections,
71
+ * so one `catch` reaches every case that carries recovery data. Carries the
72
+ * rejection class (`reason`), the explicit server-computed `minimumRequirement`
73
+ * (min_gpus / valid_counts, never pick below them), and the canonical bookable
74
+ * alternatives via {@link ApiError.availableGpus}. Instanceof-compatible with
75
+ * ApiError so existing handlers keep working.
76
+ *
77
+ * Branch on {@link GpuRejectionError.queueOffered} (or `.permanent`), never on
78
+ * the status or the code, to decide whether offering "wait for capacity" makes
79
+ * sense. `CapacityUnavailableError` is a kept alias of this class.
80
+ */
81
+ export declare class GpuRejectionError extends ApiError {
82
+ /**
83
+ * insufficient_stock | below_model_minimum | invalid_gpu_count |
84
+ * model_too_large | gpu_unsupported.
85
+ */
86
+ readonly reason: string | undefined;
87
+ /** The explicit minimum block for the model (selected type + per-type table). */
88
+ readonly minimumRequirement: CapacityMinimumRequirement | undefined;
89
+ /** The selection the rejection was about. */
90
+ readonly selected: ApiErrorBody['selected'];
91
+ /**
92
+ * True only when waiting for capacity is a real option. False for every
93
+ * permanent rejection: do not offer the queue, and do not retry unchanged.
94
+ */
95
+ readonly queueOffered: boolean;
96
+ /** Whether the capacity queue may be joined instead. */
97
+ readonly queueEligible: boolean;
98
+ /**
99
+ * 1-based rank of the gpu_priorities entry the rejection is about, when it
100
+ * came from a submitted ladder.
101
+ */
102
+ readonly gpuPrioritiesEntry: number | undefined;
103
+ constructor(status: number, body: ApiErrorBody);
104
+ /**
105
+ * True when the request can never succeed as submitted, whatever happens to
106
+ * stock. Only a different GPU type or count helps.
107
+ */
108
+ get permanent(): boolean;
109
+ }
110
+ /**
111
+ * Historical name for {@link GpuRejectionError}. It is the SAME class, so
112
+ * `err instanceof CapacityUnavailableError` still catches every rejection,
113
+ * including the permanent ones that now answer 400 with their own codes.
114
+ */
115
+ export declare const CapacityUnavailableError: typeof GpuRejectionError;
116
+ export type CapacityUnavailableError = GpuRejectionError;
117
+ /**
118
+ * Machine codes the services answer with while a surface is pre-launch
119
+ * (HTTP 403): TRAINING_COMING_SOON from POST /api/training/jobs,
120
+ * DATASETS_COMING_SOON from the dataset creation endpoints (upload,
121
+ * uploads/initiate, register-hf, integration import).
122
+ */
123
+ export declare const COMING_SOON_CODES: readonly ["TRAINING_COMING_SOON", "DATASETS_COMING_SOON"];
124
+ /**
125
+ * Raised when a pre-launch surface rejects a creation call: fine-tuning and
126
+ * datasets launch soon, so training.create and dataset upload/import/register
127
+ * answer 403 with one of {@link COMING_SOON_CODES}. Read and lifecycle methods
128
+ * (list, get, status, stop, resume, delete, preview) are unaffected.
129
+ *
130
+ * The service is the authority: the SDK never blocks client-side, so when the
131
+ * gate lifts at launch every SDK version works again without an upgrade. This
132
+ * verdict is a deliberate product state, not an outage — retrying in a loop
133
+ * cannot succeed while the gate is on.
134
+ */
135
+ export declare class ComingSoonError extends ApiError {
136
+ constructor(status: number, body: ApiErrorBody);
137
+ }
138
+ /** Whether a machine code is one of the permanent GPU rejections. */
139
+ export declare function isPermanentGpuCode(code: string | undefined): boolean;
140
+ /**
141
+ * The machine code that goes with a rejection `reason`, mirroring the service's
142
+ * mapping so a client-synthesized rejection is indistinguishable from the
143
+ * server's. An unknown reason falls back to the stock-miss code.
144
+ */
145
+ export declare function gpuRejectionCodeForReason(reason: string | undefined): string;
146
+ /**
147
+ * Map a non-2xx body to the most specific typed error. The service is the
148
+ * authority: any GPU-rejection code becomes {@link GpuRejectionError} so
149
+ * callers read `.availableGpus`/`.minimumRequirement`/`.queueOffered` without
150
+ * string matching. @internal
151
+ */
152
+ export declare function buildApiError(status: number, body: ApiErrorBody): ApiError;
153
+ /**
154
+ * Default API key when the config omits one. Falls back to the
155
+ * `RUNBIOS_API_KEY` environment variable (legacy: `BIOS_API_KEY`). @internal
156
+ */
157
+ export declare function envApiKey(): string | undefined;
158
+ /**
159
+ * Inference key when the config omits one. Falls back to the
160
+ * `RUNBIOS_INFERENCE_KEY` environment variable (legacy: `BIOS_INFERENCE_KEY`),
161
+ * then to the API key at the call site: a platform key carrying the serverless
162
+ * scope calls `/v1` directly. @internal
163
+ */
164
+ export declare function envInferenceKey(): string | undefined;
165
+ /**
166
+ * Default base URL when the config omits one. Falls back to the
167
+ * `RUNBIOS_BASE_URL` environment variable (legacy: `BIOS_BASE_URL`). @internal
168
+ */
169
+ export declare function envBaseUrl(): string | undefined;
170
+ export declare class HttpClient {
171
+ private readonly baseUrl;
172
+ private readonly apiKey;
173
+ private readonly accessToken;
174
+ private readonly orgId;
175
+ private readonly workspaceIdValue;
176
+ private readonly timeout;
177
+ constructor(config: BiOSConfig);
178
+ /** Workspace configured on the client, used by multipart/control-plane helpers. */
179
+ get workspaceId(): string | undefined;
180
+ private buildHeaders;
181
+ /**
182
+ * Send a JSON request and parse the response.
183
+ * Throws {@link ApiError} on non-2xx responses.
184
+ */
185
+ request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
186
+ /** Send a GET request. */
187
+ fetchGet<T>(path: string, extraHeaders?: Record<string, string>): Promise<T>;
188
+ /** Send a POST request with a JSON body. */
189
+ fetchPost<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
190
+ /** Send a PATCH request with a JSON body. */
191
+ fetchPatch<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
192
+ /** Send a PUT request with a JSON body. */
193
+ fetchPut<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
194
+ /** Send a DELETE request. */
195
+ fetchDelete<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
196
+ /**
197
+ * Upload a file via multipart/form-data.
198
+ * The caller is responsible for constructing the FormData.
199
+ * Throws {@link ApiError} on non-2xx responses.
200
+ */
201
+ fetchUpload<T>(path: string, formData: FormData): Promise<T>;
202
+ }