@teepin/sdk 0.2.0 → 0.3.0-rc.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 +181 -396
- package/dist/cjs/client.js +42 -4
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/direct/bytes.js +50 -0
- package/dist/cjs/direct/bytes.js.map +1 -0
- package/dist/cjs/direct/common.js +167 -0
- package/dist/cjs/direct/common.js.map +1 -0
- package/dist/cjs/direct/downloads-plan.js +127 -0
- package/dist/cjs/direct/downloads-plan.js.map +1 -0
- package/dist/cjs/direct/engine.js +405 -0
- package/dist/cjs/direct/engine.js.map +1 -0
- package/dist/cjs/direct/formats.js +44 -0
- package/dist/cjs/direct/formats.js.map +1 -0
- package/dist/cjs/direct/hpke.js +275 -0
- package/dist/cjs/direct/hpke.js.map +1 -0
- package/dist/cjs/direct/journal.js +80 -0
- package/dist/cjs/direct/journal.js.map +1 -0
- package/dist/cjs/direct/large.js +850 -0
- package/dist/cjs/direct/large.js.map +1 -0
- package/dist/cjs/direct/reads.js +1314 -0
- package/dist/cjs/direct/reads.js.map +1 -0
- package/dist/cjs/direct/sha256.js +122 -0
- package/dist/cjs/direct/sha256.js.map +1 -0
- package/dist/cjs/direct/tws1.js +480 -0
- package/dist/cjs/direct/tws1.js.map +1 -0
- package/dist/cjs/direct/uploads.js +1706 -0
- package/dist/cjs/direct/uploads.js.map +1 -0
- package/dist/cjs/errors.js +70 -13
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/links.js +195 -0
- package/dist/cjs/links.js.map +1 -0
- package/dist/cjs/node/downloads.js +835 -0
- package/dist/cjs/node/downloads.js.map +1 -0
- package/dist/cjs/node/journal.js +841 -0
- package/dist/cjs/node/journal.js.map +1 -0
- package/dist/cjs/node/uploads.js +219 -0
- package/dist/cjs/node/uploads.js.map +1 -0
- package/dist/cjs/node.js +26 -39
- package/dist/cjs/node.js.map +1 -1
- package/dist/cjs/storage.js +214 -184
- package/dist/cjs/storage.js.map +1 -1
- package/dist/cjs/transfer.js +18 -351
- package/dist/cjs/transfer.js.map +1 -1
- package/dist/esm/client.js +42 -4
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/direct/bytes.js +42 -0
- package/dist/esm/direct/bytes.js.map +1 -0
- package/dist/esm/direct/common.js +155 -0
- package/dist/esm/direct/common.js.map +1 -0
- package/dist/esm/direct/downloads-plan.js +117 -0
- package/dist/esm/direct/downloads-plan.js.map +1 -0
- package/dist/esm/direct/engine.js +385 -0
- package/dist/esm/direct/engine.js.map +1 -0
- package/dist/esm/direct/formats.js +40 -0
- package/dist/esm/direct/formats.js.map +1 -0
- package/dist/esm/direct/hpke.js +262 -0
- package/dist/esm/direct/hpke.js.map +1 -0
- package/dist/esm/direct/journal.js +75 -0
- package/dist/esm/direct/journal.js.map +1 -0
- package/dist/esm/direct/large.js +816 -0
- package/dist/esm/direct/large.js.map +1 -0
- package/dist/esm/direct/reads.js +1275 -0
- package/dist/esm/direct/reads.js.map +1 -0
- package/dist/esm/direct/sha256.js +118 -0
- package/dist/esm/direct/sha256.js.map +1 -0
- package/dist/esm/direct/tws1.js +460 -0
- package/dist/esm/direct/tws1.js.map +1 -0
- package/dist/esm/direct/uploads.js +1660 -0
- package/dist/esm/direct/uploads.js.map +1 -0
- package/dist/esm/errors.js +64 -11
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/links.js +189 -0
- package/dist/esm/links.js.map +1 -0
- package/dist/esm/node/downloads.js +829 -0
- package/dist/esm/node/downloads.js.map +1 -0
- package/dist/esm/node/journal.js +824 -0
- package/dist/esm/node/journal.js.map +1 -0
- package/dist/esm/node/uploads.js +187 -0
- package/dist/esm/node/uploads.js.map +1 -0
- package/dist/esm/node.js +17 -14
- package/dist/esm/node.js.map +1 -1
- package/dist/esm/storage.js +214 -184
- package/dist/esm/storage.js.map +1 -1
- package/dist/esm/transfer.js +19 -349
- package/dist/esm/transfer.js.map +1 -1
- package/dist/types/client.d.ts +45 -4
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/direct/bytes.d.ts +11 -0
- package/dist/types/direct/bytes.d.ts.map +1 -0
- package/dist/types/direct/common.d.ts +47 -0
- package/dist/types/direct/common.d.ts.map +1 -0
- package/dist/types/direct/downloads-plan.d.ts +75 -0
- package/dist/types/direct/downloads-plan.d.ts.map +1 -0
- package/dist/types/direct/engine.d.ts +191 -0
- package/dist/types/direct/engine.d.ts.map +1 -0
- package/dist/types/direct/formats.d.ts +9 -0
- package/dist/types/direct/formats.d.ts.map +1 -0
- package/dist/types/direct/hpke.d.ts +55 -0
- package/dist/types/direct/hpke.d.ts.map +1 -0
- package/dist/types/direct/journal.d.ts +55 -0
- package/dist/types/direct/journal.d.ts.map +1 -0
- package/dist/types/direct/large.d.ts +170 -0
- package/dist/types/direct/large.d.ts.map +1 -0
- package/dist/types/direct/reads.d.ts +306 -0
- package/dist/types/direct/reads.d.ts.map +1 -0
- package/dist/types/direct/sha256.d.ts +16 -0
- package/dist/types/direct/sha256.d.ts.map +1 -0
- package/dist/types/direct/tws1.d.ts +157 -0
- package/dist/types/direct/tws1.d.ts.map +1 -0
- package/dist/types/direct/uploads.d.ts +461 -0
- package/dist/types/direct/uploads.d.ts.map +1 -0
- package/dist/types/errors.d.ts +47 -8
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/links.d.ts +86 -0
- package/dist/types/links.d.ts.map +1 -0
- package/dist/types/node/downloads.d.ts +73 -0
- package/dist/types/node/downloads.d.ts.map +1 -0
- package/dist/types/node/journal.d.ts +162 -0
- package/dist/types/node/journal.d.ts.map +1 -0
- package/dist/types/node/uploads.d.ts +86 -0
- package/dist/types/node/uploads.d.ts.map +1 -0
- package/dist/types/node.d.ts +4 -2
- package/dist/types/node.d.ts.map +1 -1
- package/dist/types/storage.d.ts +107 -64
- package/dist/types/storage.d.ts.map +1 -1
- package/dist/types/transfer.d.ts +14 -23
- package/dist/types/transfer.d.ts.map +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,501 +1,286 @@
|
|
|
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}`);
|
|
30
|
+
const teepin = new TeepinClient(); // reads TEEPIN_API_KEY and TEEPIN_BASE_URL
|
|
31
|
+
// or: new TeepinClient({ apiKey: 'tpk_...' })
|
|
44
32
|
```
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
const { TeepinClient } = require('@teepin/sdk');
|
|
50
|
-
|
|
51
|
-
const client = new TeepinClient({
|
|
52
|
-
baseUrl: 'http://localhost:8080',
|
|
53
|
-
});
|
|
34
|
+
Keep your API key on your server.
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
const instance = await client.instances.create({
|
|
57
|
-
name: 'my-instance',
|
|
58
|
-
image: 'nginx:latest',
|
|
59
|
-
gpuVram: '25GB',
|
|
60
|
-
});
|
|
36
|
+
## Teepin Inference
|
|
61
37
|
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
deploy();
|
|
66
|
-
```
|
|
38
|
+
An OpenAI-compatible chat API. The key needs `inference:invoke`, and may be limited to some models.
|
|
67
39
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- ✅ **Type-safe** - Full TypeScript support with complete type definitions
|
|
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
|
|
76
|
-
|
|
77
|
-
## Usage Examples
|
|
78
|
-
|
|
79
|
-
### List Instances
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
const instances = await client.instances.list();
|
|
83
|
-
|
|
84
|
-
for (const instance of instances) {
|
|
85
|
-
console.log(`${instance.id}: ${instance.name} (${instance.status})`);
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Get Instance Details
|
|
40
|
+
### Models
|
|
90
41
|
|
|
91
42
|
```typescript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
}
|
|
43
|
+
const models = await teepin.inference.listModels();
|
|
44
|
+
for (const m of models) {
|
|
45
|
+
console.log(m.id, m.confidential, m.pricing.input_per_million_tokens, m.pricing.output_per_million_tokens);
|
|
105
46
|
}
|
|
106
47
|
```
|
|
107
48
|
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
await client.instances.delete('inst-a82e7f3');
|
|
112
|
-
console.log('Instance deleted');
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Deploy with Port Mappings
|
|
49
|
+
### One answer
|
|
116
50
|
|
|
117
51
|
```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
|
-
],
|
|
52
|
+
const res = await teepin.inference.chat.completions.create({
|
|
53
|
+
model: 'teepin/glm-5.3',
|
|
54
|
+
messages: [{ role: 'user', content: 'What is TLS? One sentence.' }],
|
|
55
|
+
max_completion_tokens: 300,
|
|
56
|
+
chat_template_kwargs: { enable_thinking: false },
|
|
128
57
|
});
|
|
58
|
+
console.log(res.choices[0].message.content);
|
|
59
|
+
console.log(res.usage?.total_tokens, res.meta.requestId);
|
|
129
60
|
```
|
|
130
61
|
|
|
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
|
|
62
|
+
### Streaming
|
|
148
63
|
|
|
149
64
|
```typescript
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
team: 'ml-research',
|
|
156
|
-
project: 'image-classification',
|
|
157
|
-
environment: 'production',
|
|
158
|
-
},
|
|
65
|
+
const stream = await teepin.inference.chat.completions.create({
|
|
66
|
+
model: 'teepin/glm-5.3',
|
|
67
|
+
messages: [{ role: 'user', content: 'Write a haiku about the sea.' }],
|
|
68
|
+
stream: true,
|
|
69
|
+
stream_options: { include_usage: true },
|
|
159
70
|
});
|
|
71
|
+
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta.content ?? '');
|
|
72
|
+
console.log('\n', stream.usage?.total_tokens);
|
|
160
73
|
```
|
|
161
74
|
|
|
162
|
-
|
|
75
|
+
Leaving the loop early closes the connection. A stream throws `StreamError` when the server reports a failure part
|
|
76
|
+
way through, and `StreamEndedError` when it closes before the end.
|
|
163
77
|
|
|
164
|
-
|
|
165
|
-
dependencies). Files on disk are in `@teepin/sdk/node`.
|
|
78
|
+
### Reasoning models
|
|
166
79
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
80
|
+
Reasoning models (GLM-5.3, for example) think before they answer, and the thinking counts toward
|
|
81
|
+
`max_completion_tokens`. With a small limit the answer can come back empty (`content: null`,
|
|
82
|
+
`finish_reason: 'length'`). For a short answer, turn thinking off with
|
|
83
|
+
`chat_template_kwargs: { enable_thinking: false }`, or give it room. The thinking itself, when the model shows it,
|
|
84
|
+
is in `message.reasoning_content`.
|
|
170
85
|
|
|
171
|
-
|
|
172
|
-
await teepin.storage.createBucket('my-bucket');
|
|
86
|
+
### Confidential models and attestation
|
|
173
87
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
});
|
|
88
|
+
Confidential models run in hardware enclaves that prove what code they run. Each answer says how far the enclave
|
|
89
|
+
was verified for that call:
|
|
181
90
|
|
|
182
|
-
|
|
183
|
-
|
|
91
|
+
```typescript
|
|
92
|
+
console.log(res.meta.attestation); // 'verified', 'verified-older-release' or 'not-verified'
|
|
93
|
+
const proof = await teepin.inference.getAttestation('teepin/glm-5.3'); // the live proof, to verify yourself
|
|
94
|
+
```
|
|
184
95
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const { data } = await teepin.storage.download('my-bucket', 'notes.txt'); // into memory
|
|
96
|
+
Anything less than `verified` is a warning to show, never an error. In the model list, a confidential model that
|
|
97
|
+
isn't fully verified right now carries `attestation: { level, warning }`.
|
|
188
98
|
|
|
189
|
-
|
|
190
|
-
for await (const obj of teepin.storage.objects('my-bucket', 'videos/')) console.log(obj.key, obj.size);
|
|
191
|
-
const page = await teepin.storage.listObjects('my-bucket', { prefix: 'videos/', delimiter: '/' });
|
|
192
|
-
|
|
193
|
-
// Write only if nothing is there; read only if it changed.
|
|
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
|
|
99
|
+
### Errors, retries and rate limits
|
|
196
100
|
|
|
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
|
|
101
|
+
Limits are per API key: by default 60 chat completions a minute and 10 at a time. A 429 is a `RateLimitError` with
|
|
102
|
+
`retryAfter` in seconds.
|
|
201
103
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
104
|
+
The server bills an answer once the model has written it, even if the caller has gone. So a chat call is retried
|
|
105
|
+
only when it certainly wasn't billed: on a 429, on Teepin's own `internal_error`, `upstream_error`,
|
|
106
|
+
`model_unavailable` or `billing_unavailable`, and on a connection that never reached the server. It is never
|
|
107
|
+
retried after a timeout or a dropped connection. Retries happen up to `maxRetries` times (default 2), waiting as
|
|
108
|
+
`Retry-After` says or with backoff. A stream is never retried once it has started.
|
|
206
109
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
and hand the page only the link; signed links work from any website:
|
|
110
|
+
A chat completion that isn't streamed may take up to 600 seconds. Change it per call with `{ timeout }`, or for
|
|
111
|
+
the client with `teepin.inference.chatTimeout`.
|
|
210
112
|
|
|
211
113
|
```typescript
|
|
212
|
-
|
|
213
|
-
await fetch(up.url, { method: up.method, headers: up.headers, body: file });
|
|
214
|
-
```
|
|
114
|
+
import { isInsufficientCreditError, isRateLimitError, isNotFoundError } from '@teepin/sdk';
|
|
215
115
|
|
|
216
|
-
|
|
217
|
-
|
|
116
|
+
try {
|
|
117
|
+
await teepin.inference.chat.completions.create({ model: 'teepin/glm-5.3', messages: [{ role: 'user', content: 'Hi' }] });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (isInsufficientCreditError(err)) console.error('add credit in the console');
|
|
120
|
+
else if (isRateLimitError(err)) console.error(`try again in ${err.retryAfter ?? 'a few'} seconds`);
|
|
121
|
+
else if (isNotFoundError(err)) console.error('no such model, or this key may not call it');
|
|
122
|
+
else throw err;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
218
125
|
|
|
219
|
-
###
|
|
126
|
+
### With the OpenAI SDK
|
|
220
127
|
|
|
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.
|
|
128
|
+
The API is OpenAI-compatible, so OpenAI's own SDK works too:
|
|
226
129
|
|
|
227
130
|
```typescript
|
|
228
|
-
import
|
|
229
|
-
|
|
230
|
-
const teepin = new TeepinClient(); // reads TEEPIN_API_KEY (and TEEPIN_BASE_URL)
|
|
131
|
+
import OpenAI from 'openai';
|
|
231
132
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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',
|
|
133
|
+
const openai = new OpenAI({ baseURL: 'https://api.teepin.com/v1', apiKey: process.env.TEEPIN_API_KEY });
|
|
134
|
+
const res = await openai.chat.completions.create({
|
|
135
|
+
model: 'teepin/glm-5.3',
|
|
240
136
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
241
|
-
max_completion_tokens: 256,
|
|
242
137
|
});
|
|
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
138
|
```
|
|
263
139
|
|
|
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`:
|
|
140
|
+
This SDK adds the attestation results, billing-safe retries and Teepin's typed errors.
|
|
294
141
|
|
|
295
|
-
|
|
296
|
-
import { NotFoundError, PaymentRequiredError, ChecksumMismatchError, UploadError, isRetryable } from '@teepin/sdk';
|
|
142
|
+
## Teepin Storage
|
|
297
143
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
```
|
|
144
|
+
Buckets and files. Files are encrypted on this device and sent straight to the storage network, and every byte read
|
|
145
|
+
is verified on this device before it is used: no file byte passes through Teepin's API. The key needs `storage:read`
|
|
146
|
+
to read and `storage:write` to write.
|
|
312
147
|
|
|
313
|
-
|
|
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
|
|
148
|
+
### Buckets
|
|
326
149
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
```bash
|
|
333
|
-
export TEEPIN_API_KEY=your-api-key
|
|
334
|
-
export TEEPIN_BASE_URL=https://api.teepin.com # optional; the default
|
|
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`. Files up to 16 MiB are packed together, and larger ones go in parallel
|
|
171
|
+
parts, each retried on its own. A large file's upload is journaled on disk, so `resumeUploads` finishes it after a
|
|
172
|
+
crash or a lost connection; `uploadFiles` uploads many files as one such run. A download fetches ranges in parallel
|
|
173
|
+
and checks the SHA-256.
|
|
350
174
|
|
|
351
175
|
```typescript
|
|
352
|
-
|
|
353
|
-
constructor(config?: TeepinConfig);
|
|
176
|
+
import { uploadFile, downloadFile, resumeUploads } from '@teepin/sdk/node';
|
|
354
177
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
178
|
+
await uploadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './a.mp4', {
|
|
179
|
+
onProgress: (done, total) => console.log(`${done} of ${total}`),
|
|
180
|
+
});
|
|
181
|
+
await downloadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './copy.mp4');
|
|
182
|
+
|
|
183
|
+
await resumeUploads(teepin.storage); // finishes what an earlier process left unfinished
|
|
359
184
|
```
|
|
360
185
|
|
|
361
|
-
|
|
186
|
+
On a slow connection, uploads use smaller requests. The client measures how fast its uploads to storage go (a request
|
|
187
|
+
that gets no answer in time counts too, so a slow connection is noticed from the first requests), and when a request
|
|
188
|
+
would take more than about 4 minutes, packs shrink below 9 MiB, an object too large for one such request (over 5 MiB)
|
|
189
|
+
goes in parts instead of a pack, and new uploads use smaller parts (down to 5 MiB, the smallest allowed). A file that
|
|
190
|
+
still can't be sent, because even 5 MiB parts would be too slow or it would need more than 3,000 parts, throws
|
|
191
|
+
`TooLargeError` with `reason` `"connection_speed"` before anything is sent.
|
|
192
|
+
|
|
193
|
+
### List
|
|
362
194
|
|
|
363
195
|
```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
|
-
}
|
|
196
|
+
// Every object under a prefix.
|
|
197
|
+
for await (const obj of teepin.storage.objects('my-bucket', 'videos/')) console.log(obj.key, obj.size);
|
|
374
198
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
maxRetries?: number;
|
|
378
|
-
timeout?: number;
|
|
379
|
-
headers?: Record<string, string>;
|
|
380
|
-
}
|
|
199
|
+
// One page, with "folders".
|
|
200
|
+
const page = await teepin.storage.listObjects('my-bucket', { prefix: 'videos/', delimiter: '/' });
|
|
381
201
|
```
|
|
382
202
|
|
|
383
|
-
###
|
|
203
|
+
### Rename, copy and delete
|
|
384
204
|
|
|
385
205
|
```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
|
-
}
|
|
206
|
+
await teepin.storage.renameObject('my-bucket', 'videos/a.mp4', 'archive/a.mp4'); // instant at any size
|
|
207
|
+
await teepin.storage.copyObject('backup', 'a.mp4', 'my-bucket', 'archive/a.mp4'); // read and uploaded again, to backup/a.mp4
|
|
208
|
+
await teepin.storage.deleteObject('my-bucket', 'notes.txt');
|
|
209
|
+
await teepin.storage.deleteObject('my-bucket', 'notes.txt', { ifMatch: etag }); // only if it still has statObject's etag, else PreconditionFailedError
|
|
210
|
+
await teepin.storage.deleteObjects('my-bucket', ['a.txt', 'b.txt']); // up to 1000 a request
|
|
417
211
|
```
|
|
418
212
|
|
|
419
|
-
###
|
|
213
|
+
### Share links
|
|
420
214
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
cpuUnits?: number;
|
|
427
|
-
memory?: string;
|
|
428
|
-
env?: Record<string, string>;
|
|
429
|
-
ports?: PortMapping[];
|
|
430
|
-
labels?: Record<string, string>;
|
|
431
|
-
}
|
|
215
|
+
A share link is a page on the console that anyone holding it can open with no sign-in, to preview or save one
|
|
216
|
+
object, for up to 24 hours (15 minutes by default). It needs `storage:read`. Its token is sealed, so the link names
|
|
217
|
+
no bucket or key, and rides in the URL's fragment (`https://console.teepin.com/l#…`). `openLink` opens one in code,
|
|
218
|
+
with no API key: the bytes come straight from the storage network and are verified on this device. A download that
|
|
219
|
+
began while the link was valid may finish after it expires, for up to 24 hours more.
|
|
432
220
|
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
## Development
|
|
221
|
+
```typescript
|
|
222
|
+
import { openLink } from '@teepin/sdk';
|
|
451
223
|
|
|
452
|
-
|
|
224
|
+
const link = await teepin.storage.createLink('my-bucket', 'reports/q3.pdf', { expiresIn: 3600 });
|
|
225
|
+
console.log(link.url, link.expiresAt);
|
|
453
226
|
|
|
454
|
-
|
|
455
|
-
|
|
227
|
+
const shared = await openLink(link.url); // InvalidLinkError once it has expired
|
|
228
|
+
console.log(shared.name, shared.size, shared.contentType);
|
|
229
|
+
const body = shared.stream(); // or shared.read(offset, length), or shared.downloadTo(sink)
|
|
230
|
+
shared.close();
|
|
456
231
|
```
|
|
457
232
|
|
|
458
|
-
|
|
233
|
+
## Errors
|
|
459
234
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
```
|
|
235
|
+
Errors from the API are `APIError` subclasses with the API's stable `code`, `status`, `serverMessage` and
|
|
236
|
+
`requestId` (for support):
|
|
463
237
|
|
|
464
|
-
|
|
238
|
+
- `UnauthorizedError` (401), `PaymentRequiredError` (402), `ForbiddenError` (403), `NotFoundError` (404),
|
|
239
|
+
`ConflictError` (409), `PreconditionFailedError` (412), `TooLargeError` (413; `reason` is `"connection_speed"`
|
|
240
|
+
when the connection measured too slow to send a file), `RateLimitError` (429);
|
|
241
|
+
- `ConnectionError` when the server couldn't be reached;
|
|
242
|
+
- `StreamError` and `StreamEndedError` for inference streams;
|
|
243
|
+
- `DataIntegrityError` (with `ChecksumMismatchError` under it), `ObjectChangedError`, `KeyErasedError`,
|
|
244
|
+
`StorageUnavailableError` and `FileChangedError` for storage, and `InvalidLinkError` (a `ForbiddenError`) for a
|
|
245
|
+
share link that is damaged or has expired.
|
|
465
246
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
247
|
+
Helpers: `isNotFoundError`, `isRateLimitError`, `isInsufficientCreditError`, `isPermissionError` and
|
|
248
|
+
`isRetryable`. Storage calls that fail for a passing reason are retried with backoff, up to `maxRetries` times
|
|
249
|
+
(default 4).
|
|
469
250
|
|
|
470
|
-
|
|
251
|
+
## Configuration
|
|
471
252
|
|
|
472
|
-
```
|
|
473
|
-
|
|
253
|
+
```typescript
|
|
254
|
+
const teepin = new TeepinClient({
|
|
255
|
+
apiKey: 'tpk_...', // default: TEEPIN_API_KEY
|
|
256
|
+
baseUrl: 'https://api.teepin.com', // default: TEEPIN_BASE_URL, else this
|
|
257
|
+
timeout: 60_000, // milliseconds for a call that isn't a transfer or a chat completion
|
|
258
|
+
maxRetries: 4, // 0 turns retries off
|
|
259
|
+
});
|
|
474
260
|
```
|
|
475
261
|
|
|
476
|
-
##
|
|
262
|
+
## Other APIs
|
|
477
263
|
|
|
478
|
-
|
|
264
|
+
The SDK also has clients for services that aren't offered yet. They answer 404 until they launch.
|
|
479
265
|
|
|
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
|
|
266
|
+
## Examples
|
|
484
267
|
|
|
485
|
-
|
|
268
|
+
`examples/inference.mts` and `examples/storage.mts` run with Node 22.6 or newer:
|
|
486
269
|
|
|
487
270
|
```bash
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
# Run example
|
|
492
|
-
ts-node examples/basic.ts
|
|
271
|
+
npm run build
|
|
272
|
+
TEEPIN_API_KEY=tpk_... node --experimental-strip-types examples/inference.mts
|
|
493
273
|
```
|
|
494
274
|
|
|
495
|
-
##
|
|
275
|
+
## Development
|
|
496
276
|
|
|
497
|
-
|
|
277
|
+
```bash
|
|
278
|
+
npm install
|
|
279
|
+
npm run build
|
|
280
|
+
npm test
|
|
281
|
+
npm run typecheck
|
|
282
|
+
```
|
|
498
283
|
|
|
499
284
|
## License
|
|
500
285
|
|
|
501
|
-
Apache License 2.0
|
|
286
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|