@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.
Files changed (134) hide show
  1. package/README.md +181 -396
  2. package/dist/cjs/client.js +42 -4
  3. package/dist/cjs/client.js.map +1 -1
  4. package/dist/cjs/direct/bytes.js +50 -0
  5. package/dist/cjs/direct/bytes.js.map +1 -0
  6. package/dist/cjs/direct/common.js +167 -0
  7. package/dist/cjs/direct/common.js.map +1 -0
  8. package/dist/cjs/direct/downloads-plan.js +127 -0
  9. package/dist/cjs/direct/downloads-plan.js.map +1 -0
  10. package/dist/cjs/direct/engine.js +405 -0
  11. package/dist/cjs/direct/engine.js.map +1 -0
  12. package/dist/cjs/direct/formats.js +44 -0
  13. package/dist/cjs/direct/formats.js.map +1 -0
  14. package/dist/cjs/direct/hpke.js +275 -0
  15. package/dist/cjs/direct/hpke.js.map +1 -0
  16. package/dist/cjs/direct/journal.js +80 -0
  17. package/dist/cjs/direct/journal.js.map +1 -0
  18. package/dist/cjs/direct/large.js +850 -0
  19. package/dist/cjs/direct/large.js.map +1 -0
  20. package/dist/cjs/direct/reads.js +1314 -0
  21. package/dist/cjs/direct/reads.js.map +1 -0
  22. package/dist/cjs/direct/sha256.js +122 -0
  23. package/dist/cjs/direct/sha256.js.map +1 -0
  24. package/dist/cjs/direct/tws1.js +480 -0
  25. package/dist/cjs/direct/tws1.js.map +1 -0
  26. package/dist/cjs/direct/uploads.js +1706 -0
  27. package/dist/cjs/direct/uploads.js.map +1 -0
  28. package/dist/cjs/errors.js +70 -13
  29. package/dist/cjs/errors.js.map +1 -1
  30. package/dist/cjs/index.js +5 -1
  31. package/dist/cjs/index.js.map +1 -1
  32. package/dist/cjs/links.js +195 -0
  33. package/dist/cjs/links.js.map +1 -0
  34. package/dist/cjs/node/downloads.js +835 -0
  35. package/dist/cjs/node/downloads.js.map +1 -0
  36. package/dist/cjs/node/journal.js +841 -0
  37. package/dist/cjs/node/journal.js.map +1 -0
  38. package/dist/cjs/node/uploads.js +219 -0
  39. package/dist/cjs/node/uploads.js.map +1 -0
  40. package/dist/cjs/node.js +26 -39
  41. package/dist/cjs/node.js.map +1 -1
  42. package/dist/cjs/storage.js +214 -184
  43. package/dist/cjs/storage.js.map +1 -1
  44. package/dist/cjs/transfer.js +18 -351
  45. package/dist/cjs/transfer.js.map +1 -1
  46. package/dist/esm/client.js +42 -4
  47. package/dist/esm/client.js.map +1 -1
  48. package/dist/esm/direct/bytes.js +42 -0
  49. package/dist/esm/direct/bytes.js.map +1 -0
  50. package/dist/esm/direct/common.js +155 -0
  51. package/dist/esm/direct/common.js.map +1 -0
  52. package/dist/esm/direct/downloads-plan.js +117 -0
  53. package/dist/esm/direct/downloads-plan.js.map +1 -0
  54. package/dist/esm/direct/engine.js +385 -0
  55. package/dist/esm/direct/engine.js.map +1 -0
  56. package/dist/esm/direct/formats.js +40 -0
  57. package/dist/esm/direct/formats.js.map +1 -0
  58. package/dist/esm/direct/hpke.js +262 -0
  59. package/dist/esm/direct/hpke.js.map +1 -0
  60. package/dist/esm/direct/journal.js +75 -0
  61. package/dist/esm/direct/journal.js.map +1 -0
  62. package/dist/esm/direct/large.js +816 -0
  63. package/dist/esm/direct/large.js.map +1 -0
  64. package/dist/esm/direct/reads.js +1275 -0
  65. package/dist/esm/direct/reads.js.map +1 -0
  66. package/dist/esm/direct/sha256.js +118 -0
  67. package/dist/esm/direct/sha256.js.map +1 -0
  68. package/dist/esm/direct/tws1.js +460 -0
  69. package/dist/esm/direct/tws1.js.map +1 -0
  70. package/dist/esm/direct/uploads.js +1660 -0
  71. package/dist/esm/direct/uploads.js.map +1 -0
  72. package/dist/esm/errors.js +64 -11
  73. package/dist/esm/errors.js.map +1 -1
  74. package/dist/esm/index.js +1 -0
  75. package/dist/esm/index.js.map +1 -1
  76. package/dist/esm/links.js +189 -0
  77. package/dist/esm/links.js.map +1 -0
  78. package/dist/esm/node/downloads.js +829 -0
  79. package/dist/esm/node/downloads.js.map +1 -0
  80. package/dist/esm/node/journal.js +824 -0
  81. package/dist/esm/node/journal.js.map +1 -0
  82. package/dist/esm/node/uploads.js +187 -0
  83. package/dist/esm/node/uploads.js.map +1 -0
  84. package/dist/esm/node.js +17 -14
  85. package/dist/esm/node.js.map +1 -1
  86. package/dist/esm/storage.js +214 -184
  87. package/dist/esm/storage.js.map +1 -1
  88. package/dist/esm/transfer.js +19 -349
  89. package/dist/esm/transfer.js.map +1 -1
  90. package/dist/types/client.d.ts +45 -4
  91. package/dist/types/client.d.ts.map +1 -1
  92. package/dist/types/direct/bytes.d.ts +11 -0
  93. package/dist/types/direct/bytes.d.ts.map +1 -0
  94. package/dist/types/direct/common.d.ts +47 -0
  95. package/dist/types/direct/common.d.ts.map +1 -0
  96. package/dist/types/direct/downloads-plan.d.ts +75 -0
  97. package/dist/types/direct/downloads-plan.d.ts.map +1 -0
  98. package/dist/types/direct/engine.d.ts +191 -0
  99. package/dist/types/direct/engine.d.ts.map +1 -0
  100. package/dist/types/direct/formats.d.ts +9 -0
  101. package/dist/types/direct/formats.d.ts.map +1 -0
  102. package/dist/types/direct/hpke.d.ts +55 -0
  103. package/dist/types/direct/hpke.d.ts.map +1 -0
  104. package/dist/types/direct/journal.d.ts +55 -0
  105. package/dist/types/direct/journal.d.ts.map +1 -0
  106. package/dist/types/direct/large.d.ts +170 -0
  107. package/dist/types/direct/large.d.ts.map +1 -0
  108. package/dist/types/direct/reads.d.ts +306 -0
  109. package/dist/types/direct/reads.d.ts.map +1 -0
  110. package/dist/types/direct/sha256.d.ts +16 -0
  111. package/dist/types/direct/sha256.d.ts.map +1 -0
  112. package/dist/types/direct/tws1.d.ts +157 -0
  113. package/dist/types/direct/tws1.d.ts.map +1 -0
  114. package/dist/types/direct/uploads.d.ts +461 -0
  115. package/dist/types/direct/uploads.d.ts.map +1 -0
  116. package/dist/types/errors.d.ts +47 -8
  117. package/dist/types/errors.d.ts.map +1 -1
  118. package/dist/types/index.d.ts +1 -0
  119. package/dist/types/index.d.ts.map +1 -1
  120. package/dist/types/links.d.ts +86 -0
  121. package/dist/types/links.d.ts.map +1 -0
  122. package/dist/types/node/downloads.d.ts +73 -0
  123. package/dist/types/node/downloads.d.ts.map +1 -0
  124. package/dist/types/node/journal.d.ts +162 -0
  125. package/dist/types/node/journal.d.ts.map +1 -0
  126. package/dist/types/node/uploads.d.ts +86 -0
  127. package/dist/types/node/uploads.d.ts.map +1 -0
  128. package/dist/types/node.d.ts +4 -2
  129. package/dist/types/node.d.ts.map +1 -1
  130. package/dist/types/storage.d.ts +107 -64
  131. package/dist/types/storage.d.ts.map +1 -1
  132. package/dist/types/transfer.d.ts +14 -23
  133. package/dist/types/transfer.d.ts.map +1 -1
  134. package/package.json +8 -5
package/README.md CHANGED
@@ -1,501 +1,286 @@
1
- # TEEPIN TypeScript SDK
1
+ # Teepin SDK for TypeScript and JavaScript
2
2
 
3
- Official TypeScript/JavaScript client library for the TEEPIN API.
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
- ## Installation
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
- Or with Yarn:
14
+ ## Authentication
12
15
 
13
- ```bash
14
- yarn add @teepin/sdk
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
- ## Quick Start
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
- ### TypeScript
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 client = new TeepinClient({
25
- baseUrl: 'http://localhost:8080',
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
- ### JavaScript (CommonJS)
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
- async function deploy() {
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
- console.log('Instance created:', instance.id);
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
- ## Features
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
- 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
- }
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
- ### Delete Instance
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 instance = await client.instances.create({
119
- name: 'web-app',
120
- image: 'nginx:latest',
121
- gpuVram: '10GB',
122
- cpuUnits: 2,
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
- ### Concurrent Deployments
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 instance = await client.instances.create({
151
- name: 'training-job',
152
- image: 'pytorch/pytorch:latest',
153
- gpuVram: '40GB',
154
- labels: {
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
- ### Teepin Storage
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
- Works in Node 20+ and browsers (it uses `fetch` and WebCrypto, with no
165
- dependencies). Files on disk are in `@teepin/sdk/node`.
78
+ ### Reasoning models
166
79
 
167
- ```typescript
168
- import { TeepinClient } from '@teepin/sdk';
169
- import { uploadFile, downloadFile } from '@teepin/sdk/node';
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
- const teepin = new TeepinClient(); // reads TEEPIN_API_KEY (and TEEPIN_BASE_URL)
172
- await teepin.storage.createBucket('my-bucket');
86
+ ### Confidential models and attestation
173
87
 
174
- // Any size: one request up to 16 MiB, parallel parts above that. Each part is
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
- });
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
- // Strings, bytes, Blobs and Files (browsers), and streams of unknown size.
183
- await teepin.storage.upload('my-bucket', 'notes.txt', 'hello');
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
- // Parallel ranges into a temporary file, renamed into place, checked against the SHA-256.
186
- await downloadFile(teepin.storage, 'my-bucket', 'videos/a.mp4', './copy.mp4');
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
- // Every object under a prefix, or one page like folders.
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
- // Rename is instant at any size; copy writes the bytes again.
198
- await teepin.storage.renameObject('my-bucket', 'videos/a.mp4', 'archive/a.mp4');
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
- // Links that work without your key.
203
- const link = await teepin.storage.presignDownload('my-bucket', 'archive/a.mp4', { expiresIn: 3600 });
204
- const up = await teepin.storage.presignUpload('inbox', 'upload.bin', { maxBytes: 100 * 2 ** 20 });
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
- **Keep your API key on your server.** Every call but a signed link sends it.
208
- To let a browser download or upload one object, sign a link on your server
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
- // In the page: one PUT to the link your server signed.
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
- The calls map one to one onto the API reference at
217
- `https://api.teepin.com/v1/openapi.json`.
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
- ### Teepin Inference
126
+ ### With the OpenAI SDK
220
127
 
221
- OpenAI-compatible chat completions on Teepin's models, confidential ones
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 { TeepinClient } from '@teepin/sdk';
229
-
230
- const teepin = new TeepinClient(); // reads TEEPIN_API_KEY (and TEEPIN_BASE_URL)
131
+ import OpenAI from 'openai';
231
132
 
232
- // The models this key may call. A confidential model that is not fully
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',
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
- A stream throws `StreamError` (an `APIError`, code `stream_error`) when the
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
- ```typescript
296
- import { NotFoundError, PaymentRequiredError, ChecksumMismatchError, UploadError, isRetryable } from '@teepin/sdk';
142
+ ## Teepin Storage
297
143
 
298
- try {
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
- ```
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
- 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
148
+ ### Buckets
326
149
 
327
- ### Environment Variables
328
-
329
- `new TeepinClient()` reads these when `apiKey` or `baseUrl` isn't given, as OpenAI's SDKs read `OPENAI_API_KEY`
330
- and `OPENAI_BASE_URL`. A value given in code always wins.
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
- ### Custom Client Configuration
156
+ ### Put and get
338
157
 
339
158
  ```typescript
340
- const client = new TeepinClient({
341
- baseUrl: 'https://api.teepin.com', // the default
342
- apiKey: process.env.TEEPIN_API_KEY,
343
- timeout: 60000, // 60 seconds
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
- ## API Reference
168
+ ### Large files
348
169
 
349
- ### TeepinClient
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
- class TeepinClient {
353
- constructor(config?: TeepinConfig);
176
+ import { uploadFile, downloadFile, resumeUploads } from '@teepin/sdk/node';
354
177
 
355
- readonly instances: InstancesService;
356
- readonly storage: StorageService;
357
- readonly inference: InferenceService;
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
- ### InferenceService
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
- interface InferenceService {
365
- listModels(options?: InferenceRequestOptions): Promise<Model[]>;
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
- interface InferenceRequestOptions {
376
- signal?: AbortSignal;
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
- ### InstancesService
203
+ ### Rename, copy and delete
384
204
 
385
205
  ```typescript
386
- interface InstancesService {
387
- /**
388
- * Create a new instance
389
- */
390
- create(request: CreateInstanceRequest): Promise<Instance>;
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
- ### Types
213
+ ### Share links
420
214
 
421
- ```typescript
422
- interface CreateInstanceRequest {
423
- name: string;
424
- image: string;
425
- gpuVram?: string;
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
- 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
- }
448
- ```
449
-
450
- ## Development
221
+ ```typescript
222
+ import { openLink } from '@teepin/sdk';
451
223
 
452
- ### Build
224
+ const link = await teepin.storage.createLink('my-bucket', 'reports/q3.pdf', { expiresIn: 3600 });
225
+ console.log(link.url, link.expiresAt);
453
226
 
454
- ```bash
455
- npm run build
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
- ### Watch Mode
233
+ ## Errors
459
234
 
460
- ```bash
461
- npm run dev
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
- ### Lint
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
- ```bash
467
- npm run lint
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
- ### Format
251
+ ## Configuration
471
252
 
472
- ```bash
473
- npm run format
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
- ## Examples
262
+ ## Other APIs
477
263
 
478
- See the `examples/` directory for more examples:
264
+ The SDK also has clients for services that aren't offered yet. They answer 404 until they launch.
479
265
 
480
- - `basic.ts` - Basic usage
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
- Run examples:
268
+ `examples/inference.mts` and `examples/storage.mts` run with Node 22.6 or newer:
486
269
 
487
270
  ```bash
488
- # Install ts-node
489
- npm install -g ts-node
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
- ## Contributing
275
+ ## Development
496
276
 
497
- See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
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).