@jettson/sdk 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,464 @@
1
+ // src/errors.ts
2
+ var JettsonError = class extends Error {
3
+ code;
4
+ status;
5
+ details;
6
+ constructor(opts) {
7
+ super(opts.message);
8
+ this.name = "JettsonError";
9
+ this.code = opts.code;
10
+ this.status = opts.status;
11
+ this.details = opts.details;
12
+ }
13
+ };
14
+ var JettsonAuthError = class extends JettsonError {
15
+ constructor(opts) {
16
+ super({ ...opts, status: 401 });
17
+ this.name = "JettsonAuthError";
18
+ }
19
+ };
20
+ var JettsonRateLimitError = class extends JettsonError {
21
+ retryAfterSeconds;
22
+ constructor(opts) {
23
+ super({ ...opts, status: 429 });
24
+ this.name = "JettsonRateLimitError";
25
+ this.retryAfterSeconds = opts.retryAfterSeconds;
26
+ }
27
+ };
28
+ var JettsonQuotaExceededError = class extends JettsonError {
29
+ used;
30
+ limit;
31
+ plan;
32
+ constructor(opts) {
33
+ super({ ...opts, status: 402 });
34
+ this.name = "JettsonQuotaExceededError";
35
+ this.used = opts.used;
36
+ this.limit = opts.limit;
37
+ this.plan = opts.plan;
38
+ }
39
+ };
40
+ var JettsonValidationError = class extends JettsonError {
41
+ constructor(opts) {
42
+ super({ ...opts, status: 400 });
43
+ this.name = "JettsonValidationError";
44
+ }
45
+ };
46
+ var JettsonNotFoundError = class extends JettsonError {
47
+ constructor(opts) {
48
+ super({ ...opts, status: 404 });
49
+ this.name = "JettsonNotFoundError";
50
+ }
51
+ };
52
+ var JettsonServerError = class extends JettsonError {
53
+ constructor(opts) {
54
+ super(opts);
55
+ this.name = "JettsonServerError";
56
+ }
57
+ };
58
+ var JettsonNetworkError = class extends JettsonError {
59
+ constructor(message) {
60
+ super({ message, code: "network_error", status: 0 });
61
+ this.name = "JettsonNetworkError";
62
+ }
63
+ };
64
+ var AgentTimeoutError = class extends JettsonError {
65
+ agentId;
66
+ elapsedMs;
67
+ constructor(agentId, elapsedMs) {
68
+ super({
69
+ message: `Agent ${agentId} did not reach a terminal state within ${elapsedMs}ms.`,
70
+ code: "agent_timeout",
71
+ status: 0
72
+ });
73
+ this.name = "AgentTimeoutError";
74
+ this.agentId = agentId;
75
+ this.elapsedMs = elapsedMs;
76
+ }
77
+ };
78
+ function errorFromResponse(input) {
79
+ const status = input.status;
80
+ const code = input.body?.error?.code ?? "unknown_error";
81
+ const message = input.body?.error?.message ?? `Request failed with status ${status}`;
82
+ const details = input.body?.error?.details;
83
+ if (status === 402) {
84
+ return new JettsonQuotaExceededError({
85
+ message,
86
+ code,
87
+ used: numField(details, "used") ?? 0,
88
+ limit: numField(details, "limit") ?? numField(details, "limitHours") ?? 0,
89
+ plan: strField(details, "plan") ?? "unknown",
90
+ details
91
+ });
92
+ }
93
+ if (status === 401) return new JettsonAuthError({ message, code, details });
94
+ if (status === 404) return new JettsonNotFoundError({ message, code, details });
95
+ if (status === 400 || status === 422) {
96
+ return new JettsonValidationError({ message, code, details });
97
+ }
98
+ if (status === 429) {
99
+ const retryAfter = parseRetryAfter(input.retryAfterHeader, details);
100
+ return new JettsonRateLimitError({
101
+ message,
102
+ code,
103
+ retryAfterSeconds: retryAfter,
104
+ details
105
+ });
106
+ }
107
+ if (status >= 500) {
108
+ return new JettsonServerError({ message, code, status, details });
109
+ }
110
+ return new JettsonError({ message, code, status, details });
111
+ }
112
+ function parseRetryAfter(header, details) {
113
+ if (header) {
114
+ const n = Number.parseInt(header, 10);
115
+ if (Number.isFinite(n) && n > 0) return n;
116
+ }
117
+ const detail = numField(details, "retryAfterSeconds");
118
+ return detail ?? 1;
119
+ }
120
+ function numField(obj, key) {
121
+ const v = obj?.[key];
122
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
123
+ }
124
+ function strField(obj, key) {
125
+ const v = obj?.[key];
126
+ return typeof v === "string" ? v : null;
127
+ }
128
+
129
+ // src/agents.ts
130
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
131
+ "completed",
132
+ "error",
133
+ "stopped"
134
+ ]);
135
+ var AgentsResource = class {
136
+ constructor(http) {
137
+ this.http = http;
138
+ }
139
+ http;
140
+ /**
141
+ * Spawn a new agent. Returns immediately with `status: "spawning"`.
142
+ * Use `wait()` to block until the agent finishes.
143
+ */
144
+ async spawn(input) {
145
+ const body = { task: input.task };
146
+ if (input.name !== void 0) body.name = input.name;
147
+ if (input.region !== void 0) body.region = input.region;
148
+ if (input.metadata !== void 0) body.metadata = input.metadata;
149
+ return this.http.request({
150
+ method: "POST",
151
+ path: "/agents",
152
+ body,
153
+ idempotencyKey: input.idempotencyKey
154
+ });
155
+ }
156
+ /** Fetch one agent by ID. */
157
+ async get(agentId) {
158
+ return this.http.request({
159
+ method: "GET",
160
+ path: `/agents/${encodeURIComponent(agentId)}`
161
+ });
162
+ }
163
+ /** Paginated list of the caller's agents, newest first. */
164
+ async list(input = {}) {
165
+ return this.http.request({
166
+ method: "GET",
167
+ path: "/agents",
168
+ query: { limit: input.limit, cursor: input.cursor }
169
+ });
170
+ }
171
+ /**
172
+ * Cancel a running agent. Idempotent — terminal agents return 200
173
+ * unchanged.
174
+ */
175
+ async cancel(agentId) {
176
+ await this.http.request({
177
+ method: "DELETE",
178
+ path: `/agents/${encodeURIComponent(agentId)}`
179
+ });
180
+ }
181
+ /**
182
+ * Poll `get(agentId)` until the agent reaches a terminal status, with
183
+ * gentle exponential backoff. Throws `AgentTimeoutError` if the
184
+ * deadline lapses.
185
+ */
186
+ async wait(agentId, opts = {}) {
187
+ const timeoutMs = opts.timeoutMs ?? 5 * 60 * 1e3;
188
+ const initialInterval = opts.pollIntervalMs ?? 1e3;
189
+ const maxInterval = opts.maxPollIntervalMs ?? 5e3;
190
+ const deadline = Date.now() + timeoutMs;
191
+ let interval = initialInterval;
192
+ while (true) {
193
+ const agent = await this.get(agentId);
194
+ if (TERMINAL_STATUSES.has(agent.status)) return agent;
195
+ if (Date.now() >= deadline) {
196
+ throw new AgentTimeoutError(agentId, timeoutMs);
197
+ }
198
+ const remaining = deadline - Date.now();
199
+ const sleepMs = Math.max(0, Math.min(interval, remaining));
200
+ await sleep(sleepMs);
201
+ interval = Math.min(Math.round(interval * 1.5), maxInterval);
202
+ }
203
+ }
204
+ };
205
+ function sleep(ms) {
206
+ return new Promise((resolve) => setTimeout(resolve, ms));
207
+ }
208
+
209
+ // src/http.ts
210
+ var SDK_VERSION = "0.1.0";
211
+ var HttpClient = class {
212
+ apiKey;
213
+ baseUrl;
214
+ maxRetries;
215
+ initialBackoffMs;
216
+ constructor(opts) {
217
+ if (!opts.apiKey) {
218
+ throw new JettsonError({
219
+ message: "Missing `apiKey` \u2014 pass one when constructing `new Jettson()`.",
220
+ code: "missing_api_key",
221
+ status: 0
222
+ });
223
+ }
224
+ this.apiKey = opts.apiKey;
225
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
226
+ this.maxRetries = opts.maxRetries ?? 3;
227
+ this.initialBackoffMs = opts.initialBackoffMs ?? 1e3;
228
+ }
229
+ async request(req) {
230
+ const url = this.buildUrl(req.path, req.query);
231
+ const headers = {
232
+ Authorization: `Bearer ${this.apiKey}`,
233
+ "User-Agent": `jettson-node/${SDK_VERSION}`,
234
+ Accept: "application/json"
235
+ };
236
+ if (req.body !== void 0) {
237
+ headers["Content-Type"] = "application/json";
238
+ }
239
+ if (req.idempotencyKey) {
240
+ headers["X-Idempotency-Key"] = req.idempotencyKey;
241
+ }
242
+ let lastError = null;
243
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
244
+ let res;
245
+ try {
246
+ res = await fetch(url, {
247
+ method: req.method,
248
+ headers,
249
+ body: req.body !== void 0 ? JSON.stringify(req.body) : void 0
250
+ });
251
+ } catch (err) {
252
+ lastError = new JettsonNetworkError(
253
+ err instanceof Error ? err.message : "Network request failed"
254
+ );
255
+ if (attempt === 0 && this.maxRetries > 0) {
256
+ await sleep2(this.initialBackoffMs);
257
+ continue;
258
+ }
259
+ throw lastError;
260
+ }
261
+ if (res.ok) {
262
+ if (res.status === 204) return void 0;
263
+ const text = await res.text();
264
+ if (text.length === 0) return void 0;
265
+ try {
266
+ return JSON.parse(text);
267
+ } catch {
268
+ throw new JettsonError({
269
+ message: "Server returned malformed JSON.",
270
+ code: "invalid_response",
271
+ status: res.status
272
+ });
273
+ }
274
+ }
275
+ const body = await safeJson(res);
276
+ const error = errorFromResponse({
277
+ status: res.status,
278
+ body,
279
+ retryAfterHeader: res.headers.get("retry-after")
280
+ });
281
+ lastError = error;
282
+ const retryable = res.status === 429 && attempt < this.maxRetries || res.status >= 500 && attempt < this.maxRetries;
283
+ if (!retryable) throw error;
284
+ const delayMs = res.status === 429 ? Math.max(1, "retryAfterSeconds" in error ? error.retryAfterSeconds : 1) * 1e3 : this.initialBackoffMs * 2 ** attempt;
285
+ await sleep2(delayMs);
286
+ }
287
+ throw lastError ?? new JettsonError({
288
+ message: "Request failed after retries.",
289
+ code: "max_retries_exceeded",
290
+ status: 0
291
+ });
292
+ }
293
+ buildUrl(path, query) {
294
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
295
+ if (!query) return url;
296
+ const search = new URLSearchParams();
297
+ for (const [k, v] of Object.entries(query)) {
298
+ if (v === void 0 || v === null) continue;
299
+ search.append(k, String(v));
300
+ }
301
+ const qs = search.toString();
302
+ return qs.length === 0 ? url : `${url}?${qs}`;
303
+ }
304
+ };
305
+ function sleep2(ms) {
306
+ return new Promise((resolve) => setTimeout(resolve, ms));
307
+ }
308
+ async function safeJson(res) {
309
+ try {
310
+ const text = await res.text();
311
+ if (text.length === 0) return null;
312
+ return JSON.parse(text);
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+
318
+ // src/memory.ts
319
+ var MemoryResource = class {
320
+ constructor(http) {
321
+ this.http = http;
322
+ }
323
+ http;
324
+ async put(input) {
325
+ const body = {
326
+ key: input.key,
327
+ value: input.value
328
+ };
329
+ if (input.namespace !== void 0) body.namespace = input.namespace;
330
+ if (input.tags !== void 0) body.tags = input.tags;
331
+ if (input.importance !== void 0) body.importance = input.importance;
332
+ if (input.expiresInDays !== void 0) {
333
+ body.expires_in_days = input.expiresInDays;
334
+ }
335
+ return this.http.request({
336
+ method: "POST",
337
+ path: "/memory",
338
+ body
339
+ });
340
+ }
341
+ /** Returns `null` (not an error) when no memory matches. */
342
+ async get(key, opts = {}) {
343
+ try {
344
+ return await this.http.request({
345
+ method: "GET",
346
+ path: "/memory",
347
+ query: { key, namespace: opts.namespace }
348
+ });
349
+ } catch (err) {
350
+ if (err?.status === 404) return null;
351
+ throw err;
352
+ }
353
+ }
354
+ async delete(key, opts = {}) {
355
+ await this.http.request({
356
+ method: "DELETE",
357
+ path: "/memory",
358
+ query: {
359
+ key,
360
+ namespace: opts.namespace,
361
+ hard: opts.hardDelete ? "true" : void 0
362
+ }
363
+ });
364
+ }
365
+ async search(input) {
366
+ const body = { query: input.query };
367
+ if (input.namespace !== void 0) body.namespace = input.namespace;
368
+ if (input.tags !== void 0) body.tags = input.tags;
369
+ if (input.limit !== void 0) body.limit = input.limit;
370
+ if (input.mode !== void 0) body.mode = input.mode;
371
+ if (input.minScore !== void 0) body.min_score = input.minScore;
372
+ const res = await this.http.request({
373
+ method: "POST",
374
+ path: "/memory/search",
375
+ body
376
+ });
377
+ return res.results;
378
+ }
379
+ async list(input = {}) {
380
+ const res = await this.http.request({
381
+ method: "GET",
382
+ path: "/memory/list",
383
+ query: {
384
+ namespace: input.namespace,
385
+ tags: input.tags?.join(","),
386
+ limit: input.limit
387
+ }
388
+ });
389
+ return res.memories;
390
+ }
391
+ async dedupe(input = {}) {
392
+ const body = {};
393
+ if (input.namespace !== void 0) body.namespace = input.namespace;
394
+ if (input.similarityThreshold !== void 0) {
395
+ body.threshold = input.similarityThreshold;
396
+ }
397
+ if (input.dryRun !== void 0) body.dry_run = input.dryRun;
398
+ return this.http.request({
399
+ method: "POST",
400
+ path: "/memory/dedupe",
401
+ body
402
+ });
403
+ }
404
+ async consolidate(input = {}) {
405
+ const body = {};
406
+ if (input.namespace !== void 0) body.namespace = input.namespace;
407
+ if (input.minClusterSize !== void 0) {
408
+ body.min_cluster_size = input.minClusterSize;
409
+ }
410
+ if (input.threshold !== void 0) body.threshold = input.threshold;
411
+ return this.http.request({
412
+ method: "POST",
413
+ path: "/memory/consolidate",
414
+ body
415
+ });
416
+ }
417
+ async namespaces() {
418
+ const res = await this.http.request({
419
+ method: "GET",
420
+ path: "/memory/namespaces"
421
+ });
422
+ return res.namespaces;
423
+ }
424
+ async export() {
425
+ return this.http.request({
426
+ method: "GET",
427
+ path: "/memory/export"
428
+ });
429
+ }
430
+ async import(memories) {
431
+ return this.http.request({
432
+ method: "POST",
433
+ path: "/memory/import",
434
+ body: { memories }
435
+ });
436
+ }
437
+ };
438
+
439
+ // src/index.ts
440
+ var Jettson = class {
441
+ agents;
442
+ memory;
443
+ constructor(options) {
444
+ const http = new HttpClient({
445
+ apiKey: options.apiKey,
446
+ baseUrl: options.baseUrl ?? "https://jettson.dev/api/v1",
447
+ maxRetries: options.maxRetries
448
+ });
449
+ this.agents = new AgentsResource(http);
450
+ this.memory = new MemoryResource(http);
451
+ }
452
+ };
453
+ export {
454
+ AgentTimeoutError,
455
+ Jettson,
456
+ JettsonAuthError,
457
+ JettsonError,
458
+ JettsonNetworkError,
459
+ JettsonNotFoundError,
460
+ JettsonQuotaExceededError,
461
+ JettsonRateLimitError,
462
+ JettsonServerError,
463
+ JettsonValidationError
464
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@jettson/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official Jettson SDK for Node.js — build AI agents in 3 lines of code.",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --target node18",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "lint": "eslint src tests",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "keywords": [
32
+ "jettson",
33
+ "ai",
34
+ "agents",
35
+ "automation",
36
+ "sdk"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/jettsondev/jettson-sdk-node.git"
44
+ },
45
+ "homepage": "https://jettson.dev/docs/sdks/node",
46
+ "bugs": {
47
+ "url": "https://github.com/jettsondev/jettson-sdk-node/issues"
48
+ },
49
+ "license": "MIT",
50
+ "author": "Jettson",
51
+ "devDependencies": {
52
+ "@types/node": "^22.0.0",
53
+ "eslint": "^9.0.0",
54
+ "tsup": "^8.0.0",
55
+ "typescript": "^5.5.0",
56
+ "vitest": "^2.0.0"
57
+ }
58
+ }