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 +460 -0
- package/dist/client.d.ts +202 -0
- package/dist/client.js +408 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +93 -0
- package/dist/resources/datasets.d.ts +180 -0
- package/dist/resources/datasets.js +358 -0
- package/dist/resources/gpu-priorities.d.ts +23 -0
- package/dist/resources/gpu-priorities.js +63 -0
- package/dist/resources/gpu.d.ts +60 -0
- package/dist/resources/gpu.js +101 -0
- package/dist/resources/inference.d.ts +224 -0
- package/dist/resources/inference.js +794 -0
- package/dist/resources/models.d.ts +113 -0
- package/dist/resources/models.js +171 -0
- package/dist/resources/training.d.ts +166 -0
- package/dist/resources/training.js +419 -0
- package/dist/resources/wallet.d.ts +53 -0
- package/dist/resources/wallet.js +61 -0
- package/dist/types.d.ts +1916 -0
- package/dist/types.js +4 -0
- package/package.json +52 -0
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
|
package/dist/client.d.ts
ADDED
|
@@ -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
|
+
}
|