@teepin/sdk 0.2.0 → 0.2.1
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 +173 -393
- package/dist/cjs/client.js +1 -1
- package/dist/esm/client.js +1 -1
- package/dist/types/client.d.ts +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,501 +1,281 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Teepin SDK for TypeScript and JavaScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Teepin Web Services runs AI models in confidential enclaves (Teepin Inference) and stores your files (Teepin
|
|
4
|
+
Storage). This is the official client for Node 20+ and browsers, with no dependencies.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Links: [Docs](https://www.teepin.com/docs) · [console.teepin.com](https://console.teepin.com) · [teepin.com](https://teepin.com)
|
|
7
|
+
|
|
8
|
+
## Install
|
|
6
9
|
|
|
7
10
|
```bash
|
|
8
11
|
npm install @teepin/sdk
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
## Authentication
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
Create an API key in [console.teepin.com](https://console.teepin.com): open a project, then Settings, then API keys.
|
|
17
|
+
A key belongs to one project and has the permissions you give it (for example `inference:invoke`,
|
|
18
|
+
`storage:read`, `storage:write`).
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
The client reads the key from `TEEPIN_API_KEY`, and the address from `TEEPIN_BASE_URL`, when you don't pass them.
|
|
21
|
+
A value you pass always wins. The address defaults to `https://api.teepin.com`.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
```bash
|
|
24
|
+
export TEEPIN_API_KEY=tpk_...
|
|
25
|
+
```
|
|
20
26
|
|
|
21
27
|
```typescript
|
|
22
28
|
import { TeepinClient } from '@teepin/sdk';
|
|
23
29
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
apiKey: 'your-api-key', // Optional
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// Deploy an instance
|
|
30
|
-
const instance = await client.instances.create({
|
|
31
|
-
name: 'pytorch-training',
|
|
32
|
-
image: 'pytorch/pytorch:latest',
|
|
33
|
-
gpuVram: '25GB',
|
|
34
|
-
cpuUnits: 4,
|
|
35
|
-
memory: '16GB',
|
|
36
|
-
env: {
|
|
37
|
-
EPOCHS: '100',
|
|
38
|
-
BATCH_SIZE: '32',
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
console.log(`Instance created: ${instance.id}`);
|
|
43
|
-
console.log(`Status: ${instance.status}`);
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### JavaScript (CommonJS)
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
const { TeepinClient } = require('@teepin/sdk');
|
|
50
|
-
|
|
51
|
-
const client = new TeepinClient({
|
|
52
|
-
baseUrl: 'http://localhost:8080',
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
async function deploy() {
|
|
56
|
-
const instance = await client.instances.create({
|
|
57
|
-
name: 'my-instance',
|
|
58
|
-
image: 'nginx:latest',
|
|
59
|
-
gpuVram: '25GB',
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
console.log('Instance created:', instance.id);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
deploy();
|
|
30
|
+
const teepin = new TeepinClient(); // reads TEEPIN_API_KEY and TEEPIN_BASE_URL
|
|
31
|
+
// or: new TeepinClient({ apiKey: 'tpk_...' })
|
|
66
32
|
```
|
|
67
33
|
|
|
68
|
-
|
|
34
|
+
Keep your API key on your server. To let a browser download or upload one file, sign a link on your server (see
|
|
35
|
+
[Signed links](#signed-links)).
|
|
69
36
|
|
|
70
|
-
|
|
71
|
-
- ✅ **Promise-based** - Modern async/await API
|
|
72
|
-
- ✅ **Error handling** - Rich error types with type guards
|
|
73
|
-
- ✅ **Well-documented** - Comprehensive JSDoc comments
|
|
74
|
-
- ✅ **Tree-shakeable** - ES modules support
|
|
75
|
-
- ✅ **No dependencies** - uses fetch and WebCrypto; Node 20+ and browsers
|
|
37
|
+
## Teepin Inference
|
|
76
38
|
|
|
77
|
-
|
|
39
|
+
An OpenAI-compatible chat API. The key needs `inference:invoke`, and may be limited to some models.
|
|
78
40
|
|
|
79
|
-
###
|
|
41
|
+
### Models
|
|
80
42
|
|
|
81
43
|
```typescript
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
console.log(`${instance.id}: ${instance.name} (${instance.status})`);
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Get Instance Details
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
import { isNotFoundError } from '@teepin/sdk';
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const instance = await client.instances.get('inst-a82e7f3');
|
|
96
|
-
console.log(`Instance: ${instance.name}`);
|
|
97
|
-
console.log(`Status: ${instance.status}`);
|
|
98
|
-
console.log(`GPU VRAM: ${instance.gpuVram}`);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (isNotFoundError(error)) {
|
|
101
|
-
console.log('Instance not found');
|
|
102
|
-
} else {
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
44
|
+
const models = await teepin.inference.listModels();
|
|
45
|
+
for (const m of models) {
|
|
46
|
+
console.log(m.id, m.confidential, m.pricing.input_per_million_tokens, m.pricing.output_per_million_tokens);
|
|
105
47
|
}
|
|
106
48
|
```
|
|
107
49
|
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
await client.instances.delete('inst-a82e7f3');
|
|
112
|
-
console.log('Instance deleted');
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Deploy with Port Mappings
|
|
50
|
+
### One answer
|
|
116
51
|
|
|
117
52
|
```typescript
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
memory: '8GB',
|
|
124
|
-
ports: [
|
|
125
|
-
{ container: 80, public: 8080, protocol: 'tcp' },
|
|
126
|
-
{ container: 443, public: 8443, protocol: 'tcp' },
|
|
127
|
-
],
|
|
53
|
+
const res = await teepin.inference.chat.completions.create({
|
|
54
|
+
model: 'teepin/glm-5.3',
|
|
55
|
+
messages: [{ role: 'user', content: 'What is TLS? One sentence.' }],
|
|
56
|
+
max_completion_tokens: 300,
|
|
57
|
+
chat_template_kwargs: { enable_thinking: false },
|
|
128
58
|
});
|
|
59
|
+
console.log(res.choices[0].message.content);
|
|
60
|
+
console.log(res.usage?.total_tokens, res.meta.requestId);
|
|
129
61
|
```
|
|
130
62
|
|
|
131
|
-
###
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
// Deploy 10 instances concurrently
|
|
135
|
-
const deployments = Array.from({ length: 10 }, (_, i) =>
|
|
136
|
-
client.instances.create({
|
|
137
|
-
name: `worker-${i}`,
|
|
138
|
-
image: 'pytorch/pytorch:latest',
|
|
139
|
-
gpuVram: '10GB',
|
|
140
|
-
})
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const instances = await Promise.all(deployments);
|
|
144
|
-
console.log(`Deployed ${instances.length} instances`);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### With Labels
|
|
63
|
+
### Streaming
|
|
148
64
|
|
|
149
65
|
```typescript
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
team: 'ml-research',
|
|
156
|
-
project: 'image-classification',
|
|
157
|
-
environment: 'production',
|
|
158
|
-
},
|
|
66
|
+
const stream = await teepin.inference.chat.completions.create({
|
|
67
|
+
model: 'teepin/glm-5.3',
|
|
68
|
+
messages: [{ role: 'user', content: 'Write a haiku about the sea.' }],
|
|
69
|
+
stream: true,
|
|
70
|
+
stream_options: { include_usage: true },
|
|
159
71
|
});
|
|
72
|
+
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta.content ?? '');
|
|
73
|
+
console.log('\n', stream.usage?.total_tokens);
|
|
160
74
|
```
|
|
161
75
|
|
|
162
|
-
|
|
76
|
+
Leaving the loop early closes the connection. A stream throws `StreamError` when the server reports a failure part
|
|
77
|
+
way through, and `StreamEndedError` when it closes before the end.
|
|
163
78
|
|
|
164
|
-
|
|
165
|
-
dependencies). Files on disk are in `@teepin/sdk/node`.
|
|
79
|
+
### Reasoning models
|
|
166
80
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await teepin.storage.createBucket('my-bucket');
|
|
81
|
+
Reasoning models (GLM-5.3, for example) think before they answer, and the thinking counts toward
|
|
82
|
+
`max_completion_tokens`. With a small limit the answer can come back empty (`content: null`,
|
|
83
|
+
`finish_reason: 'length'`). For a short answer, turn thinking off with
|
|
84
|
+
`chat_template_kwargs: { enable_thinking: false }`, or give it room. The thinking itself, when the model shows it,
|
|
85
|
+
is in `message.reasoning_content`.
|
|
173
86
|
|
|
174
|
-
|
|
175
|
-
// retried on its own and the stored ETag is checked against the bytes sent.
|
|
176
|
-
// resume continues an upload that was cut off.
|
|
177
|
-
await uploadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './a.mp4', {
|
|
178
|
-
resume: true,
|
|
179
|
-
onProgress: (done, total) => console.log(`${done} of ${total}`),
|
|
180
|
-
});
|
|
87
|
+
### Confidential models and attestation
|
|
181
88
|
|
|
182
|
-
|
|
183
|
-
|
|
89
|
+
Confidential models run in hardware enclaves that prove what code they run. Each answer says how far the enclave
|
|
90
|
+
was verified for that call:
|
|
184
91
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
92
|
+
```typescript
|
|
93
|
+
console.log(res.meta.attestation); // 'verified', 'verified-older-release' or 'not-verified'
|
|
94
|
+
const proof = await teepin.inference.getAttestation('teepin/glm-5.3'); // the live proof, to verify yourself
|
|
95
|
+
```
|
|
188
96
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const page = await teepin.storage.listObjects('my-bucket', { prefix: 'videos/', delimiter: '/' });
|
|
97
|
+
Anything less than `verified` is a warning to show, never an error. In the model list, a confidential model that
|
|
98
|
+
isn't fully verified right now carries `attestation: { level, warning }`.
|
|
192
99
|
|
|
193
|
-
|
|
194
|
-
await teepin.storage.putObject('my-bucket', 'lock', '1', { ifNoneMatch: true });
|
|
195
|
-
const changed = await teepin.storage.getObject('my-bucket', 'notes.txt', { ifNoneMatch: etag }); // null if not
|
|
100
|
+
### Errors, retries and rate limits
|
|
196
101
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
await teepin.storage.copyObject('backup', 'a.mp4', 'my-bucket', 'archive/a.mp4');
|
|
200
|
-
await teepin.storage.deleteObjects('my-bucket', keys); // 1000 to a request
|
|
102
|
+
Limits are per API key: by default 60 chat completions a minute and 10 at a time. A 429 is a `RateLimitError` with
|
|
103
|
+
`retryAfter` in seconds.
|
|
201
104
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
105
|
+
The server bills an answer once the model has written it, even if the caller has gone. So a chat call is retried
|
|
106
|
+
only when it certainly wasn't billed: on a 429, on Teepin's own `internal_error`, `upstream_error`,
|
|
107
|
+
`model_unavailable` or `billing_unavailable`, and on a connection that never reached the server. It is never
|
|
108
|
+
retried after a timeout or a dropped connection. Retries happen up to `maxRetries` times (default 2), waiting as
|
|
109
|
+
`Retry-After` says or with backoff. A stream is never retried once it has started.
|
|
206
110
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
and hand the page only the link; signed links work from any website:
|
|
111
|
+
A chat completion that isn't streamed may take up to 600 seconds. Change it per call with `{ timeout }`, or for
|
|
112
|
+
the client with `teepin.inference.chatTimeout`.
|
|
210
113
|
|
|
211
114
|
```typescript
|
|
212
|
-
|
|
213
|
-
await fetch(up.url, { method: up.method, headers: up.headers, body: file });
|
|
214
|
-
```
|
|
115
|
+
import { isInsufficientCreditError, isRateLimitError, isNotFoundError } from '@teepin/sdk';
|
|
215
116
|
|
|
216
|
-
|
|
217
|
-
|
|
117
|
+
try {
|
|
118
|
+
await teepin.inference.chat.completions.create({ model: 'teepin/glm-5.3', messages: [{ role: 'user', content: 'Hi' }] });
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (isInsufficientCreditError(err)) console.error('add credit in the console');
|
|
121
|
+
else if (isRateLimitError(err)) console.error(`try again in ${err.retryAfter ?? 'a few'} seconds`);
|
|
122
|
+
else if (isNotFoundError(err)) console.error('no such model, or this key may not call it');
|
|
123
|
+
else throw err;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
218
126
|
|
|
219
|
-
###
|
|
127
|
+
### With the OpenAI SDK
|
|
220
128
|
|
|
221
|
-
OpenAI-compatible
|
|
222
|
-
included. The key needs the `inference:invoke` permission. Requests, responses
|
|
223
|
-
and chunks use the API's own field names (`max_completion_tokens`,
|
|
224
|
-
`finish_reason`, ...); fields the SDK does not know yet pass through and stay
|
|
225
|
-
readable.
|
|
129
|
+
The API is OpenAI-compatible, so OpenAI's own SDK works too:
|
|
226
130
|
|
|
227
131
|
```typescript
|
|
228
|
-
import
|
|
132
|
+
import OpenAI from 'openai';
|
|
229
133
|
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// verified right now carries attestation: { level, warning }.
|
|
234
|
-
const models = await teepin.inference.listModels();
|
|
235
|
-
for (const m of models) console.log(m.id, m.confidential, m.attestation?.warning ?? '');
|
|
236
|
-
|
|
237
|
-
// A whole completion, with the response's metadata.
|
|
238
|
-
const res = await teepin.inference.chat.completions.create({
|
|
239
|
-
model: 'teepin/llama-3.3-70b',
|
|
134
|
+
const openai = new OpenAI({ baseURL: 'https://api.teepin.com/v1', apiKey: process.env.TEEPIN_API_KEY });
|
|
135
|
+
const res = await openai.chat.completions.create({
|
|
136
|
+
model: 'teepin/glm-5.3',
|
|
240
137
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
241
|
-
max_completion_tokens: 256,
|
|
242
138
|
});
|
|
243
|
-
console.log(res.choices[0].message.content, res.usage?.total_tokens);
|
|
244
|
-
console.log(res.meta.attestation, res.meta.requestId, res.meta.rateLimit); // 'verified' | 'verified-older-release' | 'not-verified' | undefined
|
|
245
|
-
|
|
246
|
-
// A stream: it resolves once the headers are in (stream.meta is ready then).
|
|
247
|
-
// Ask for include_usage to get the usage on the last chunk.
|
|
248
|
-
const stream = await teepin.inference.chat.completions.create(
|
|
249
|
-
{
|
|
250
|
-
model: 'teepin/llama-3.3-70b',
|
|
251
|
-
messages: [{ role: 'user', content: 'Write a haiku' }],
|
|
252
|
-
stream: true,
|
|
253
|
-
stream_options: { include_usage: true },
|
|
254
|
-
},
|
|
255
|
-
{ signal: AbortSignal.timeout(120_000) },
|
|
256
|
-
);
|
|
257
|
-
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta.content ?? '');
|
|
258
|
-
console.log(stream.usage);
|
|
259
|
-
|
|
260
|
-
// The live attestation proof of a confidential model, as the provider sends it.
|
|
261
|
-
const proof = await teepin.inference.getAttestation('teepin/confidential');
|
|
262
139
|
```
|
|
263
140
|
|
|
264
|
-
|
|
265
|
-
server reports a failure part way through, and `StreamEndedError` when it
|
|
266
|
-
closes without `[DONE]`. Leaving the `for await` loop early, or aborting the
|
|
267
|
-
signal, closes the connection. Not verified attestation is a warning in
|
|
268
|
-
`meta.attestation`, never an error.
|
|
269
|
-
|
|
270
|
-
The server bills a completion once it has the model's answer, even if the
|
|
271
|
-
caller has gone, so a chat call (streamed or not) is retried only when it
|
|
272
|
-
certainly was not billed: on 429, on Teepin's own 500 `internal_error`, 502
|
|
273
|
-
`upstream_error` and 503 `model_unavailable` or `billing_unavailable` (answers
|
|
274
|
-
with the API's error envelope), and on a connection that never reached the
|
|
275
|
-
server (refused, or DNS failed). It is never retried after a timeout, a
|
|
276
|
-
connection reset or cut after sending, a 504, a 5xx without Teepin's envelope
|
|
277
|
-
(such as a proxy's HTML 502), or a 400, 401, 402, 403, 404 or 413.
|
|
278
|
-
`listModels` and `getAttestation` are retried on 429, any 5xx and any
|
|
279
|
-
connection error. A stream is never retried once its headers are in.
|
|
280
|
-
|
|
281
|
-
Retries happen up to `maxRetries` times (default 2; set it on the client or per
|
|
282
|
-
call with `{ maxRetries }`), waiting as `Retry-After` says (at most 30 s) or
|
|
283
|
-
with jittered backoff from 0.5 s to 8 s.
|
|
284
|
-
|
|
285
|
-
A chat completion that is not streamed may take up to 600 s by default (a long
|
|
286
|
-
answer takes minutes), not the client's `timeout`; change it per call with
|
|
287
|
-
`{ timeout }` or for the client with `teepin.inference.chatTimeout`. A stream's
|
|
288
|
-
timeout covers only the wait for its headers. Other calls follow the client's
|
|
289
|
-
`timeout`.
|
|
290
|
-
|
|
291
|
-
## Error Handling
|
|
292
|
-
|
|
293
|
-
Errors from the API are `APIError` subclasses with the API's stable `code`:
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { NotFoundError, PaymentRequiredError, ChecksumMismatchError, UploadError, isRetryable } from '@teepin/sdk';
|
|
141
|
+
This SDK adds the attestation results, billing-safe retries and Teepin's typed errors.
|
|
297
142
|
|
|
298
|
-
|
|
299
|
-
await teepin.storage.statObject('my-bucket', 'nope');
|
|
300
|
-
} catch (err) {
|
|
301
|
-
if (err instanceof NotFoundError) {
|
|
302
|
-
// 404: err.code is bucket_not_found or object_not_found
|
|
303
|
-
} else if (err instanceof PaymentRequiredError && err.onHold) {
|
|
304
|
-
// no credit; held data is deleted after err.deleteAfter
|
|
305
|
-
} else if (isRetryable(err)) {
|
|
306
|
-
// the network or a busy server, after the client's own retries
|
|
307
|
-
} else {
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
Also: `UnauthorizedError` (401), `ForbiddenError` (403, e.g. no storage:write),
|
|
314
|
-
`ConflictError` (409), `PreconditionFailedError` (412), `TooLargeError` (413),
|
|
315
|
-
`RateLimitError` (429), `ConnectionError`, `ObjectChangedError`,
|
|
316
|
-
`ChecksumMismatchError`, `UploadError` (with `uploadId`, for resuming), and for
|
|
317
|
-
inference streams `StreamError` and `StreamEndedError`. Every API error carries
|
|
318
|
-
`status`, `code`, `type` (inference), `serverMessage`, `requestId` for support
|
|
319
|
-
and `retryAfter` (seconds, when the server sent Retry-After). Helpers:
|
|
320
|
-
`isNotFoundError`, `isRateLimitError`, `isInsufficientCreditError` (402),
|
|
321
|
-
`isPermissionError` (403) and `isRetryable`. Requests that fail for a passing
|
|
322
|
-
reason are retried with backoff, `maxRetries` times (default 4; 2 for
|
|
323
|
-
inference).
|
|
324
|
-
|
|
325
|
-
## Configuration
|
|
143
|
+
## Teepin Storage
|
|
326
144
|
|
|
327
|
-
|
|
145
|
+
Buckets and files, with large uploads in parallel parts. The key needs `storage:read` to read and `storage:write`
|
|
146
|
+
to write.
|
|
328
147
|
|
|
329
|
-
|
|
330
|
-
and `OPENAI_BASE_URL`. A value given in code always wins.
|
|
148
|
+
### Buckets
|
|
331
149
|
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
|
|
150
|
+
```typescript
|
|
151
|
+
await teepin.storage.createBucket('my-bucket');
|
|
152
|
+
const buckets = await teepin.storage.listBuckets();
|
|
153
|
+
await teepin.storage.deleteBucket('my-bucket'); // only when it is empty
|
|
335
154
|
```
|
|
336
155
|
|
|
337
|
-
###
|
|
156
|
+
### Put and get
|
|
338
157
|
|
|
339
158
|
```typescript
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
159
|
+
// Strings, bytes, Blobs, Files and streams.
|
|
160
|
+
await teepin.storage.upload('my-bucket', 'notes.txt', 'hello');
|
|
161
|
+
|
|
162
|
+
const { object, data } = await teepin.storage.download('my-bucket', 'notes.txt');
|
|
163
|
+
console.log(object.size, new TextDecoder().decode(data));
|
|
164
|
+
|
|
165
|
+
const info = await teepin.storage.statObject('my-bucket', 'notes.txt'); // size, etag, contentType
|
|
345
166
|
```
|
|
346
167
|
|
|
347
|
-
|
|
168
|
+
### Large files
|
|
348
169
|
|
|
349
|
-
|
|
170
|
+
Files on disk use `@teepin/sdk/node`. An upload of any size goes in one request up to 16 MiB, and in parallel
|
|
171
|
+
parts above that. Each part is retried on its own and checked against the bytes sent. `resume` continues an upload
|
|
172
|
+
that was cut off. A download fetches ranges in parallel and checks the SHA-256.
|
|
350
173
|
|
|
351
174
|
```typescript
|
|
352
|
-
|
|
353
|
-
constructor(config?: TeepinConfig);
|
|
175
|
+
import { uploadFile, downloadFile } from '@teepin/sdk/node';
|
|
354
176
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
177
|
+
await uploadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './a.mp4', {
|
|
178
|
+
resume: true,
|
|
179
|
+
onProgress: (done, total) => console.log(`${done} of ${total}`),
|
|
180
|
+
});
|
|
181
|
+
await downloadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './copy.mp4');
|
|
359
182
|
```
|
|
360
183
|
|
|
361
|
-
###
|
|
184
|
+
### List
|
|
362
185
|
|
|
363
186
|
```typescript
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
getAttestation(modelId: string, options?: InferenceRequestOptions): Promise<Record<string, unknown>>;
|
|
367
|
-
chat: {
|
|
368
|
-
completions: {
|
|
369
|
-
create(body: ChatCompletionCreateParams & { stream: true }, options?): Promise<ChatCompletionStream>;
|
|
370
|
-
create(body: ChatCompletionCreateParams, options?): Promise<ChatCompletion & { meta: ResponseMeta }>;
|
|
371
|
-
};
|
|
372
|
-
};
|
|
373
|
-
}
|
|
187
|
+
// Every object under a prefix.
|
|
188
|
+
for await (const obj of teepin.storage.objects('my-bucket', 'videos/')) console.log(obj.key, obj.size);
|
|
374
189
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
maxRetries?: number;
|
|
378
|
-
timeout?: number;
|
|
379
|
-
headers?: Record<string, string>;
|
|
380
|
-
}
|
|
190
|
+
// One page, with "folders".
|
|
191
|
+
const page = await teepin.storage.listObjects('my-bucket', { prefix: 'videos/', delimiter: '/' });
|
|
381
192
|
```
|
|
382
193
|
|
|
383
|
-
###
|
|
194
|
+
### Signed links
|
|
195
|
+
|
|
196
|
+
Links that work without your key, for a limited time:
|
|
384
197
|
|
|
385
198
|
```typescript
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* List all instances
|
|
394
|
-
*/
|
|
395
|
-
list(): Promise<Instance[]>;
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Get instance by ID
|
|
399
|
-
*/
|
|
400
|
-
get(instanceId: string): Promise<Instance>;
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Delete an instance
|
|
404
|
-
*/
|
|
405
|
-
delete(instanceId: string): Promise<void>;
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* List available instance types
|
|
409
|
-
*/
|
|
410
|
-
listTypes(): Promise<InstanceType[]>;
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Get instance metrics
|
|
414
|
-
*/
|
|
415
|
-
metrics(instanceId: string): Promise<Record<string, any>>;
|
|
416
|
-
}
|
|
199
|
+
const link = await teepin.storage.presignDownload('my-bucket', 'videos/a.mp4', { expiresIn: 3600 });
|
|
200
|
+
console.log(link.url);
|
|
201
|
+
|
|
202
|
+
const up = await teepin.storage.presignUpload('my-bucket', 'inbox/upload.bin', { maxBytes: 100 * 2 ** 20 });
|
|
203
|
+
// In the browser: one PUT to the link your server signed.
|
|
204
|
+
await fetch(up.url, { method: up.method, headers: up.headers, body: file });
|
|
417
205
|
```
|
|
418
206
|
|
|
419
|
-
###
|
|
207
|
+
### Rename, copy and delete
|
|
420
208
|
|
|
421
209
|
```typescript
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
cpuUnits?: number;
|
|
427
|
-
memory?: string;
|
|
428
|
-
env?: Record<string, string>;
|
|
429
|
-
ports?: PortMapping[];
|
|
430
|
-
labels?: Record<string, string>;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
interface Instance {
|
|
434
|
-
id: string;
|
|
435
|
-
name: string;
|
|
436
|
-
image: string;
|
|
437
|
-
status: string;
|
|
438
|
-
gpuVram?: string;
|
|
439
|
-
allocatedVram?: string;
|
|
440
|
-
cpuUnits: number;
|
|
441
|
-
memory: string;
|
|
442
|
-
endpoint?: string;
|
|
443
|
-
internalIp?: string;
|
|
444
|
-
createdAt: Date;
|
|
445
|
-
updatedAt: Date;
|
|
446
|
-
labels?: Record<string, string>;
|
|
447
|
-
}
|
|
210
|
+
await teepin.storage.renameObject('my-bucket', 'videos/a.mp4', 'archive/a.mp4'); // instant at any size
|
|
211
|
+
await teepin.storage.copyObject('backup', 'a.mp4', 'my-bucket', 'archive/a.mp4'); // to backup/a.mp4, from my-bucket
|
|
212
|
+
await teepin.storage.deleteObject('my-bucket', 'notes.txt');
|
|
213
|
+
await teepin.storage.deleteObjects('my-bucket', ['a.txt', 'b.txt']); // up to 1000 a request
|
|
448
214
|
```
|
|
449
215
|
|
|
450
|
-
|
|
216
|
+
### The S3 endpoint
|
|
451
217
|
|
|
452
|
-
|
|
218
|
+
Teepin Storage also speaks S3 at `https://s3.teepin.com`, so the AWS CLI, boto3, the AWS SDKs and rclone work
|
|
219
|
+
unchanged. Create an S3 access key in the console (a project's Settings, then S3 access keys). The region is
|
|
220
|
+
`teepin`.
|
|
453
221
|
|
|
454
222
|
```bash
|
|
455
|
-
|
|
223
|
+
aws configure set aws_access_key_id TPAK... --profile teepin
|
|
224
|
+
aws configure set aws_secret_access_key ... --profile teepin
|
|
225
|
+
aws configure set region teepin --profile teepin
|
|
226
|
+
aws --profile teepin --endpoint-url https://s3.teepin.com s3 ls
|
|
456
227
|
```
|
|
457
228
|
|
|
458
|
-
|
|
229
|
+
The buckets are the same ones this SDK and the console show.
|
|
459
230
|
|
|
460
|
-
|
|
461
|
-
npm run dev
|
|
462
|
-
```
|
|
231
|
+
## Errors
|
|
463
232
|
|
|
464
|
-
|
|
233
|
+
Errors from the API are `APIError` subclasses with the API's stable `code`, `status`, `serverMessage` and
|
|
234
|
+
`requestId` (for support):
|
|
465
235
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
236
|
+
- `UnauthorizedError` (401), `PaymentRequiredError` (402), `ForbiddenError` (403), `NotFoundError` (404),
|
|
237
|
+
`ConflictError` (409), `PreconditionFailedError` (412), `TooLargeError` (413), `RateLimitError` (429);
|
|
238
|
+
- `ConnectionError` when the server couldn't be reached;
|
|
239
|
+
- `StreamError` and `StreamEndedError` for inference streams;
|
|
240
|
+
- `ChecksumMismatchError`, `ObjectChangedError` and `UploadError` (with `uploadId`, for resuming) for storage.
|
|
469
241
|
|
|
470
|
-
|
|
242
|
+
Helpers: `isNotFoundError`, `isRateLimitError`, `isInsufficientCreditError`, `isPermissionError` and
|
|
243
|
+
`isRetryable`. Storage calls that fail for a passing reason are retried with backoff, up to `maxRetries` times
|
|
244
|
+
(default 4).
|
|
471
245
|
|
|
472
|
-
|
|
473
|
-
|
|
246
|
+
## Configuration
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const teepin = new TeepinClient({
|
|
250
|
+
apiKey: 'tpk_...', // default: TEEPIN_API_KEY
|
|
251
|
+
baseUrl: 'https://api.teepin.com', // default: TEEPIN_BASE_URL, else this
|
|
252
|
+
timeout: 60_000, // milliseconds for a call that isn't a transfer or a chat completion
|
|
253
|
+
maxRetries: 4, // 0 turns retries off
|
|
254
|
+
});
|
|
474
255
|
```
|
|
475
256
|
|
|
476
|
-
##
|
|
257
|
+
## Other APIs
|
|
477
258
|
|
|
478
|
-
|
|
259
|
+
The SDK also has clients for services that aren't offered yet. They answer 404 until they launch.
|
|
479
260
|
|
|
480
|
-
|
|
481
|
-
- `concurrent.ts` - Concurrent deployments
|
|
482
|
-
- `storage.ts` - Create a bucket, upload/download/delete objects, mint a signed link
|
|
483
|
-
- `inference.ts` - List models, chat, stream and check attestation
|
|
261
|
+
## Examples
|
|
484
262
|
|
|
485
|
-
|
|
263
|
+
`examples/inference.mts` and `examples/storage.mts` run with Node 22.6 or newer:
|
|
486
264
|
|
|
487
265
|
```bash
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
# Run example
|
|
492
|
-
ts-node examples/basic.ts
|
|
266
|
+
npm run build
|
|
267
|
+
TEEPIN_API_KEY=tpk_... node --experimental-strip-types examples/inference.mts
|
|
493
268
|
```
|
|
494
269
|
|
|
495
|
-
##
|
|
270
|
+
## Development
|
|
496
271
|
|
|
497
|
-
|
|
272
|
+
```bash
|
|
273
|
+
npm install
|
|
274
|
+
npm run build
|
|
275
|
+
npm test
|
|
276
|
+
npm run typecheck
|
|
277
|
+
```
|
|
498
278
|
|
|
499
279
|
## License
|
|
500
280
|
|
|
501
|
-
Apache License 2.0
|
|
281
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
package/dist/cjs/client.js
CHANGED
|
@@ -8,7 +8,7 @@ const inference_js_1 = require("./inference.js");
|
|
|
8
8
|
const instances_js_1 = require("./instances.js");
|
|
9
9
|
const storage_js_1 = require("./storage.js");
|
|
10
10
|
/** This SDK's version, sent in the User-Agent where the runtime allows it. */
|
|
11
|
-
exports.VERSION = '0.2.
|
|
11
|
+
exports.VERSION = '0.2.1';
|
|
12
12
|
/** The public API. */
|
|
13
13
|
exports.DEFAULT_BASE_URL = 'https://api.teepin.com';
|
|
14
14
|
/** An environment variable, where there is an environment (Node, Deno, Bun;
|
package/dist/esm/client.js
CHANGED
|
@@ -5,7 +5,7 @@ import { InferenceService } from './inference.js';
|
|
|
5
5
|
import { InstancesService } from './instances.js';
|
|
6
6
|
import { StorageService } from './storage.js';
|
|
7
7
|
/** This SDK's version, sent in the User-Agent where the runtime allows it. */
|
|
8
|
-
export const VERSION = '0.2.
|
|
8
|
+
export const VERSION = '0.2.1';
|
|
9
9
|
/** The public API. */
|
|
10
10
|
export const DEFAULT_BASE_URL = 'https://api.teepin.com';
|
|
11
11
|
/** An environment variable, where there is an environment (Node, Deno, Bun;
|
package/dist/types/client.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { InferenceService } from './inference.js';
|
|
|
2
2
|
import { InstancesService } from './instances.js';
|
|
3
3
|
import { StorageService } from './storage.js';
|
|
4
4
|
/** This SDK's version, sent in the User-Agent where the runtime allows it. */
|
|
5
|
-
export declare const VERSION = "0.2.
|
|
5
|
+
export declare const VERSION = "0.2.1";
|
|
6
6
|
/** The public API. */
|
|
7
7
|
export declare const DEFAULT_BASE_URL = "https://api.teepin.com";
|
|
8
8
|
/** Client configuration. Usually nothing: the key and address come from the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teepin/sdk",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Official TypeScript/JavaScript SDK for Teepin Web Services:
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Official TypeScript/JavaScript SDK for Teepin Web Services: Teepin Inference (AI models in confidential enclaves, OpenAI-compatible) and Teepin Storage, for Node 20+ and browsers",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"README.md",
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"storage",
|
|
19
19
|
"object-storage",
|
|
20
20
|
"s3",
|
|
21
|
-
"
|
|
21
|
+
"inference",
|
|
22
|
+
"llm",
|
|
23
|
+
"openai",
|
|
24
|
+
"confidential-computing",
|
|
22
25
|
"cloud",
|
|
23
26
|
"sdk",
|
|
24
27
|
"typescript"
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"bugs": {
|
|
33
36
|
"url": "https://github.com/FlashbackAi/teepin-node/issues"
|
|
34
37
|
},
|
|
35
|
-
"homepage": "https://teepin.com",
|
|
38
|
+
"homepage": "https://www.teepin.com/docs/sdks/node",
|
|
36
39
|
"dependencies": {},
|
|
37
40
|
"devDependencies": {
|
|
38
41
|
"@types/node": "^16.18.0",
|