@declaw/sdk 1.3.0 → 1.4.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/CHANGELOG.md +20 -0
- package/dist/index.cjs +79 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +76 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to the Declaw TypeScript / JavaScript SDK are documented in
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0]
|
|
9
|
+
|
|
10
|
+
_2026-08 train: idempotent sandbox creation._
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Sandbox.create` now sends an `Idempotency-Key`. A create that times out or is
|
|
15
|
+
retried no longer risks leaving a second running, billable sandbox the caller
|
|
16
|
+
has no handle for. The key is generated once per logical create and reused
|
|
17
|
+
across that call's retries.
|
|
18
|
+
- A `409` carrying `idempotency_in_progress` is retried automatically, honoring
|
|
19
|
+
`Retry-After`.
|
|
20
|
+
- `SandboxError.code` exposes the API's machine-readable error code, with the
|
|
21
|
+
`CODE_IDEMPOTENCY_IN_PROGRESS`, `CODE_IDEMPOTENCY_KEY_REUSED` and
|
|
22
|
+
`CODE_TEMPLATE_NOT_READY` constants exported. Branch on the code, never the
|
|
23
|
+
message.
|
|
24
|
+
|
|
25
|
+
_(Retry backoff already carried jitter in this SDK; unlike the Go and Python
|
|
26
|
+
clients it needed no change.)_
|
|
27
|
+
|
|
8
28
|
## [1.3.0]
|
|
9
29
|
|
|
10
30
|
_2026-07 train: credential vault client + injection domain scoping._
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,9 @@ __export(index_exports, {
|
|
|
34
34
|
ApiClient: () => ApiClient,
|
|
35
35
|
AuthenticationError: () => AuthenticationError,
|
|
36
36
|
BuildError: () => BuildError,
|
|
37
|
+
CODE_IDEMPOTENCY_IN_PROGRESS: () => CODE_IDEMPOTENCY_IN_PROGRESS,
|
|
38
|
+
CODE_IDEMPOTENCY_KEY_REUSED: () => CODE_IDEMPOTENCY_KEY_REUSED,
|
|
39
|
+
CODE_TEMPLATE_NOT_READY: () => CODE_TEMPLATE_NOT_READY,
|
|
37
40
|
CommandExitError: () => CommandExitError,
|
|
38
41
|
CommandHandle: () => CommandHandle,
|
|
39
42
|
Commands: () => Commands,
|
|
@@ -170,13 +173,51 @@ var ConnectionConfig = class {
|
|
|
170
173
|
}
|
|
171
174
|
};
|
|
172
175
|
|
|
176
|
+
// src/api/idempotency.ts
|
|
177
|
+
var CODE_IDEMPOTENCY_IN_PROGRESS = "idempotency_in_progress";
|
|
178
|
+
var CODE_IDEMPOTENCY_KEY_REUSED = "idempotency_key_reused";
|
|
179
|
+
var CODE_TEMPLATE_NOT_READY = "template_not_ready";
|
|
180
|
+
var MAX_RETRY_AFTER_MS = 6e4;
|
|
181
|
+
function newIdempotencyKey() {
|
|
182
|
+
const c = globalThis.crypto;
|
|
183
|
+
if (c?.randomUUID) {
|
|
184
|
+
return c.randomUUID();
|
|
185
|
+
}
|
|
186
|
+
if (c?.getRandomValues) {
|
|
187
|
+
const b = c.getRandomValues(new Uint8Array(16));
|
|
188
|
+
b[6] = b[6] & 15 | 64;
|
|
189
|
+
b[8] = b[8] & 63 | 128;
|
|
190
|
+
const hex = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
191
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
192
|
+
}
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
function retryAfterMs(response) {
|
|
196
|
+
const raw = response.headers.get("Retry-After");
|
|
197
|
+
if (raw === null) return void 0;
|
|
198
|
+
const secs = Number(raw);
|
|
199
|
+
if (!Number.isFinite(secs) || secs < 0) return void 0;
|
|
200
|
+
return Math.min(secs * 1e3, MAX_RETRY_AFTER_MS);
|
|
201
|
+
}
|
|
202
|
+
|
|
173
203
|
// src/errors.ts
|
|
174
204
|
var SandboxError = class extends Error {
|
|
175
205
|
sandboxId;
|
|
206
|
+
/**
|
|
207
|
+
* Machine-readable error code from the API's `code` field, when present.
|
|
208
|
+
*
|
|
209
|
+
* Branch on this, never on `message`. Messages are prose and change; codes are
|
|
210
|
+
* contract. It matters most where one status means several unrelated things:
|
|
211
|
+
* a 409 from `POST /sandboxes` is either `idempotency_in_progress` (the
|
|
212
|
+
* original create is still running — retry the identical request) or
|
|
213
|
+
* `template_not_ready` (rebuild the template; retrying cannot help).
|
|
214
|
+
*/
|
|
215
|
+
code;
|
|
176
216
|
constructor(message, opts) {
|
|
177
217
|
super(message);
|
|
178
218
|
this.name = "SandboxError";
|
|
179
219
|
this.sandboxId = opts?.sandboxId;
|
|
220
|
+
this.code = opts?.code;
|
|
180
221
|
}
|
|
181
222
|
};
|
|
182
223
|
var TimeoutError = class extends SandboxError {
|
|
@@ -434,6 +475,15 @@ var ApiClient = class {
|
|
|
434
475
|
await this.delay(attempt);
|
|
435
476
|
continue;
|
|
436
477
|
}
|
|
478
|
+
if (response.status === 409 && attempt < this.maxRetries - 1) {
|
|
479
|
+
const parsed = await this.readErrorBody(response);
|
|
480
|
+
if (parsed.code === CODE_IDEMPOTENCY_IN_PROGRESS) {
|
|
481
|
+
const after = retryAfterMs(response);
|
|
482
|
+
await (after !== void 0 ? new Promise((r) => setTimeout(r, after)) : this.delay(attempt));
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
throw this.errorFrom(response, parsed);
|
|
486
|
+
}
|
|
437
487
|
if (!response.ok) {
|
|
438
488
|
throw await this.buildError(response);
|
|
439
489
|
}
|
|
@@ -459,20 +509,36 @@ var ApiClient = class {
|
|
|
459
509
|
`Request failed after ${this.maxRetries} retries: ${lastError?.message ?? "unknown error"}`
|
|
460
510
|
);
|
|
461
511
|
}
|
|
462
|
-
|
|
463
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Read an error body ONCE.
|
|
514
|
+
*
|
|
515
|
+
* `Response` bodies are single-read streams, so the 409 path cannot inspect
|
|
516
|
+
* the code and then hand the response to a separate error builder — the
|
|
517
|
+
* second read yields nothing and the error loses its message. Everything that
|
|
518
|
+
* needs the body goes through here, and the parsed result is passed around
|
|
519
|
+
* instead of the response.
|
|
520
|
+
*/
|
|
521
|
+
async readErrorBody(response) {
|
|
464
522
|
try {
|
|
465
|
-
const body = await response.
|
|
523
|
+
const body = JSON.parse(await response.text());
|
|
466
524
|
const bodyMsg = body.message ?? body.error ?? response.statusText;
|
|
467
|
-
|
|
525
|
+
return {
|
|
526
|
+
message: `HTTP ${response.status}: ${bodyMsg}`,
|
|
527
|
+
code: typeof body.code === "string" ? body.code : void 0
|
|
528
|
+
};
|
|
468
529
|
} catch {
|
|
469
|
-
message
|
|
530
|
+
return { message: `HTTP ${response.status}: ${response.statusText}` };
|
|
470
531
|
}
|
|
532
|
+
}
|
|
533
|
+
errorFrom(response, parsed) {
|
|
471
534
|
const ErrorClass = STATUS_ERROR_MAP[response.status];
|
|
472
535
|
if (ErrorClass) {
|
|
473
|
-
return new ErrorClass(message);
|
|
536
|
+
return new ErrorClass(parsed.message, { code: parsed.code });
|
|
474
537
|
}
|
|
475
|
-
return new SandboxError(message);
|
|
538
|
+
return new SandboxError(parsed.message, { code: parsed.code });
|
|
539
|
+
}
|
|
540
|
+
async buildError(response) {
|
|
541
|
+
return this.errorFrom(response, await this.readErrorBody(response));
|
|
476
542
|
}
|
|
477
543
|
async parseResponseBody(response) {
|
|
478
544
|
const contentLength = response.headers.get("content-length");
|
|
@@ -2681,9 +2747,11 @@ var Sandbox = class _Sandbox {
|
|
|
2681
2747
|
if (opts?.volumes && opts.volumes.length > 0) {
|
|
2682
2748
|
body.volumes = opts.volumes.map(volumeAttachmentToJSON);
|
|
2683
2749
|
}
|
|
2750
|
+
const idempotencyKey = newIdempotencyKey();
|
|
2684
2751
|
const data = await client.post("/sandboxes", {
|
|
2685
2752
|
json: body,
|
|
2686
|
-
timeout: opts?.requestTimeout
|
|
2753
|
+
timeout: opts?.requestTimeout,
|
|
2754
|
+
...idempotencyKey ? { headers: { "Idempotency-Key": idempotencyKey } } : {}
|
|
2687
2755
|
});
|
|
2688
2756
|
const sandboxId = data.sandbox_id;
|
|
2689
2757
|
assertValidId(sandboxId, "sandbox ID (from server)");
|
|
@@ -3865,6 +3933,9 @@ var Governance = class {
|
|
|
3865
3933
|
ApiClient,
|
|
3866
3934
|
AuthenticationError,
|
|
3867
3935
|
BuildError,
|
|
3936
|
+
CODE_IDEMPOTENCY_IN_PROGRESS,
|
|
3937
|
+
CODE_IDEMPOTENCY_KEY_REUSED,
|
|
3938
|
+
CODE_TEMPLATE_NOT_READY,
|
|
3868
3939
|
CommandExitError,
|
|
3869
3940
|
CommandHandle,
|
|
3870
3941
|
Commands,
|