@declaw/sdk 1.2.3 → 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 +34 -0
- package/dist/index.cjs +419 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -7
- package/dist/index.d.ts +226 -7
- package/dist/index.js +411 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ 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
|
+
|
|
28
|
+
## [1.3.0]
|
|
29
|
+
|
|
30
|
+
_2026-07 train: credential vault client + injection domain scoping._
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Credential vault client — the `Vault` API for managing secrets **by name**:
|
|
35
|
+
`Vault.createSecret()`, `listSecrets()`, `rotateSecret()`, `deleteSecret()`,
|
|
36
|
+
`updateScopes()`, and `listPresets()`. Secret values are write-only (never
|
|
37
|
+
returned after create). Attach secrets to a sandbox with
|
|
38
|
+
`vaultRefs: { ENV_VAR: "secret-name" }`; the value is injected at the egress
|
|
39
|
+
proxy and never enters the sandbox (#386, #399, #408, #456).
|
|
40
|
+
- Opt-in domain scoping for full injection defense via the `domains` option.
|
|
41
|
+
|
|
8
42
|
## [1.2.3]
|
|
9
43
|
|
|
10
44
|
_Restore the conservative default connection ceiling._
|
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,
|
|
@@ -68,6 +71,7 @@ __export(index_exports, {
|
|
|
68
71
|
TemplateError: () => TemplateError,
|
|
69
72
|
TimeoutError: () => TimeoutError,
|
|
70
73
|
TransformDirection: () => TransformDirection,
|
|
74
|
+
Vault: () => Vault,
|
|
71
75
|
VolumeFiles: () => VolumeFiles,
|
|
72
76
|
VolumeLocks: () => VolumeLocks,
|
|
73
77
|
Volumes: () => Volumes,
|
|
@@ -119,6 +123,9 @@ __export(index_exports, {
|
|
|
119
123
|
parseSnapshotInfo: () => parseSnapshotInfo,
|
|
120
124
|
parseTemplateBuildStatus: () => parseTemplateBuildStatus,
|
|
121
125
|
parseToxicityConfig: () => parseToxicityConfig,
|
|
126
|
+
parseVaultPreset: () => parseVaultPreset,
|
|
127
|
+
parseVaultScope: () => parseVaultScope,
|
|
128
|
+
parseVaultSecret: () => parseVaultSecret,
|
|
122
129
|
parseVolumeInfo: () => parseVolumeInfo,
|
|
123
130
|
parseWriteInfo: () => parseWriteInfo,
|
|
124
131
|
requiresTlsInterception: () => requiresTlsInterception,
|
|
@@ -126,6 +133,7 @@ __export(index_exports, {
|
|
|
126
133
|
securityPolicyToJSON: () => securityPolicyToJSON,
|
|
127
134
|
toxicityConfigToJSON: () => toxicityConfigToJSON,
|
|
128
135
|
validateNetworkEntry: () => validateNetworkEntry,
|
|
136
|
+
vaultScopeToJSON: () => vaultScopeToJSON,
|
|
129
137
|
volumeAttachmentToJSON: () => volumeAttachmentToJSON
|
|
130
138
|
});
|
|
131
139
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -165,13 +173,51 @@ var ConnectionConfig = class {
|
|
|
165
173
|
}
|
|
166
174
|
};
|
|
167
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
|
+
|
|
168
203
|
// src/errors.ts
|
|
169
204
|
var SandboxError = class extends Error {
|
|
170
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;
|
|
171
216
|
constructor(message, opts) {
|
|
172
217
|
super(message);
|
|
173
218
|
this.name = "SandboxError";
|
|
174
219
|
this.sandboxId = opts?.sandboxId;
|
|
220
|
+
this.code = opts?.code;
|
|
175
221
|
}
|
|
176
222
|
};
|
|
177
223
|
var TimeoutError = class extends SandboxError {
|
|
@@ -429,6 +475,15 @@ var ApiClient = class {
|
|
|
429
475
|
await this.delay(attempt);
|
|
430
476
|
continue;
|
|
431
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
|
+
}
|
|
432
487
|
if (!response.ok) {
|
|
433
488
|
throw await this.buildError(response);
|
|
434
489
|
}
|
|
@@ -454,20 +509,36 @@ var ApiClient = class {
|
|
|
454
509
|
`Request failed after ${this.maxRetries} retries: ${lastError?.message ?? "unknown error"}`
|
|
455
510
|
);
|
|
456
511
|
}
|
|
457
|
-
|
|
458
|
-
|
|
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) {
|
|
459
522
|
try {
|
|
460
|
-
const body = await response.
|
|
523
|
+
const body = JSON.parse(await response.text());
|
|
461
524
|
const bodyMsg = body.message ?? body.error ?? response.statusText;
|
|
462
|
-
|
|
525
|
+
return {
|
|
526
|
+
message: `HTTP ${response.status}: ${bodyMsg}`,
|
|
527
|
+
code: typeof body.code === "string" ? body.code : void 0
|
|
528
|
+
};
|
|
463
529
|
} catch {
|
|
464
|
-
message
|
|
530
|
+
return { message: `HTTP ${response.status}: ${response.statusText}` };
|
|
465
531
|
}
|
|
532
|
+
}
|
|
533
|
+
errorFrom(response, parsed) {
|
|
466
534
|
const ErrorClass = STATUS_ERROR_MAP[response.status];
|
|
467
535
|
if (ErrorClass) {
|
|
468
|
-
return new ErrorClass(message);
|
|
536
|
+
return new ErrorClass(parsed.message, { code: parsed.code });
|
|
469
537
|
}
|
|
470
|
-
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));
|
|
471
542
|
}
|
|
472
543
|
async parseResponseBody(response) {
|
|
473
544
|
const contentLength = response.headers.get("content-length");
|
|
@@ -641,7 +712,7 @@ function createInjectionDefenseConfig(opts) {
|
|
|
641
712
|
enabled: opts?.enabled ?? false,
|
|
642
713
|
sensitivity: opts?.sensitivity ?? "medium" /* Medium */,
|
|
643
714
|
action: opts?.action ?? "log_only" /* LogOnly */,
|
|
644
|
-
threshold: opts?.threshold ?? 0.
|
|
715
|
+
threshold: opts?.threshold ?? 0.95,
|
|
645
716
|
domains: opts?.domains,
|
|
646
717
|
judge: opts?.judge,
|
|
647
718
|
injectionMode: opts?.injectionMode
|
|
@@ -668,7 +739,7 @@ function parseInjectionDefenseConfig(data) {
|
|
|
668
739
|
enabled: data.enabled ?? false,
|
|
669
740
|
sensitivity: data.sensitivity ?? "medium" /* Medium */,
|
|
670
741
|
action: data.action ?? "log_only" /* LogOnly */,
|
|
671
|
-
threshold: data.threshold ?? 0.
|
|
742
|
+
threshold: data.threshold ?? 0.95,
|
|
672
743
|
domains: data.domains,
|
|
673
744
|
judge: data.judge ? { enabled: data.judge.enabled ?? false, always: data.judge.always, policy: data.judge.policy } : void 0,
|
|
674
745
|
injectionMode: data.injection_mode ?? data.injectionMode
|
|
@@ -1046,14 +1117,14 @@ function fullInjectionDefensePolicy(opts) {
|
|
|
1046
1117
|
injectionDefense: createInjectionDefenseConfig({
|
|
1047
1118
|
enabled: true,
|
|
1048
1119
|
action: opts?.action ?? "block",
|
|
1049
|
-
threshold: opts?.threshold ?? 0.
|
|
1120
|
+
threshold: opts?.threshold ?? 0.95,
|
|
1050
1121
|
domains: opts?.domains,
|
|
1051
1122
|
injectionMode: opts?.mode ?? "balanced",
|
|
1052
1123
|
judge: { enabled: true, always: opts?.alwaysJudge ?? false, policy: opts?.agentPolicy ?? "" }
|
|
1053
1124
|
}),
|
|
1054
1125
|
customPolicy: createCustomPolicyConfig({
|
|
1055
1126
|
enabled: true,
|
|
1056
|
-
policyRef: "prompt-injection@
|
|
1127
|
+
policyRef: "prompt-injection@v3",
|
|
1057
1128
|
defaultDeny: false
|
|
1058
1129
|
})
|
|
1059
1130
|
});
|
|
@@ -1094,7 +1165,7 @@ function securityPolicyToJSON(policy) {
|
|
|
1094
1165
|
action: injDefConfig.action,
|
|
1095
1166
|
threshold: injDefConfig.threshold
|
|
1096
1167
|
};
|
|
1097
|
-
if (injDefConfig.domains !== void 0) {
|
|
1168
|
+
if (injDefConfig.domains !== void 0 && injDefConfig.domains.length > 0) {
|
|
1098
1169
|
injDef.domains = injDefConfig.domains;
|
|
1099
1170
|
}
|
|
1100
1171
|
if (injDefConfig.injectionMode !== void 0) {
|
|
@@ -2206,6 +2277,311 @@ function volumeAttachmentToJSON(att) {
|
|
|
2206
2277
|
return out;
|
|
2207
2278
|
}
|
|
2208
2279
|
|
|
2280
|
+
// src/vault/models.ts
|
|
2281
|
+
function parseVaultScope(data) {
|
|
2282
|
+
const scope = {
|
|
2283
|
+
domainRegex: String(data.domain_regex ?? "")
|
|
2284
|
+
};
|
|
2285
|
+
if (data.injection_type !== void 0 && data.injection_type !== null) {
|
|
2286
|
+
scope.injectionType = String(data.injection_type);
|
|
2287
|
+
}
|
|
2288
|
+
if (data.header_name !== void 0 && data.header_name !== null) {
|
|
2289
|
+
scope.headerName = String(data.header_name);
|
|
2290
|
+
}
|
|
2291
|
+
if (data.value_prefix !== void 0 && data.value_prefix !== null) {
|
|
2292
|
+
scope.valuePrefix = String(data.value_prefix);
|
|
2293
|
+
}
|
|
2294
|
+
if (data.basic_username !== void 0 && data.basic_username !== null) {
|
|
2295
|
+
scope.basicUsername = String(data.basic_username);
|
|
2296
|
+
}
|
|
2297
|
+
if (data.extra_headers !== void 0 && data.extra_headers !== null) {
|
|
2298
|
+
scope.extraHeaders = data.extra_headers;
|
|
2299
|
+
}
|
|
2300
|
+
if (data.query_params !== void 0 && data.query_params !== null) {
|
|
2301
|
+
scope.queryParams = data.query_params;
|
|
2302
|
+
}
|
|
2303
|
+
return scope;
|
|
2304
|
+
}
|
|
2305
|
+
function parseVaultSecret(data) {
|
|
2306
|
+
const secret = {
|
|
2307
|
+
secretId: String(data.secret_id ?? ""),
|
|
2308
|
+
name: String(data.name ?? ""),
|
|
2309
|
+
createdAt: String(data.created_at ?? ""),
|
|
2310
|
+
updatedAt: String(data.updated_at ?? "")
|
|
2311
|
+
};
|
|
2312
|
+
const rawScopes = data.scopes;
|
|
2313
|
+
if (rawScopes && rawScopes.length > 0) {
|
|
2314
|
+
secret.scopes = rawScopes.map(parseVaultScope);
|
|
2315
|
+
}
|
|
2316
|
+
if (data.rotated_at !== void 0 && data.rotated_at !== null) {
|
|
2317
|
+
secret.rotatedAt = String(data.rotated_at);
|
|
2318
|
+
}
|
|
2319
|
+
if (data.rotation_interval_days !== void 0 && data.rotation_interval_days !== null) {
|
|
2320
|
+
secret.rotationIntervalDays = Number(data.rotation_interval_days);
|
|
2321
|
+
}
|
|
2322
|
+
if (data.rotation_due !== void 0 && data.rotation_due !== null) {
|
|
2323
|
+
secret.rotationDue = Boolean(data.rotation_due);
|
|
2324
|
+
}
|
|
2325
|
+
return secret;
|
|
2326
|
+
}
|
|
2327
|
+
function parseVaultPreset(data) {
|
|
2328
|
+
const rawScopes = data.scopes ?? [];
|
|
2329
|
+
const preset = {
|
|
2330
|
+
key: String(data.key ?? ""),
|
|
2331
|
+
name: String(data.name ?? ""),
|
|
2332
|
+
category: String(data.category ?? ""),
|
|
2333
|
+
keyHint: String(data.key_hint ?? ""),
|
|
2334
|
+
scopes: rawScopes.map(parseVaultScope)
|
|
2335
|
+
};
|
|
2336
|
+
if (data.docs_url !== void 0 && data.docs_url !== null) {
|
|
2337
|
+
preset.docsUrl = String(data.docs_url);
|
|
2338
|
+
}
|
|
2339
|
+
return preset;
|
|
2340
|
+
}
|
|
2341
|
+
function vaultScopeToJSON(scope) {
|
|
2342
|
+
const out = {
|
|
2343
|
+
domain_regex: scope.domainRegex
|
|
2344
|
+
};
|
|
2345
|
+
if (scope.injectionType !== void 0) {
|
|
2346
|
+
out.injection_type = scope.injectionType;
|
|
2347
|
+
}
|
|
2348
|
+
if (scope.headerName !== void 0) {
|
|
2349
|
+
out.header_name = scope.headerName;
|
|
2350
|
+
}
|
|
2351
|
+
if (scope.valuePrefix !== void 0) {
|
|
2352
|
+
out.value_prefix = scope.valuePrefix;
|
|
2353
|
+
}
|
|
2354
|
+
if (scope.basicUsername !== void 0) {
|
|
2355
|
+
out.basic_username = scope.basicUsername;
|
|
2356
|
+
}
|
|
2357
|
+
if (scope.extraHeaders !== void 0) {
|
|
2358
|
+
out.extra_headers = scope.extraHeaders;
|
|
2359
|
+
}
|
|
2360
|
+
if (scope.queryParams !== void 0) {
|
|
2361
|
+
out.query_params = scope.queryParams;
|
|
2362
|
+
}
|
|
2363
|
+
return out;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// src/vault/vault.ts
|
|
2367
|
+
var DEFAULT_TEAM_NAME = "default";
|
|
2368
|
+
var DEFAULT_ENV_NAME = "prod";
|
|
2369
|
+
var defaultTeamCache = /* @__PURE__ */ new Map();
|
|
2370
|
+
function cacheKey(config) {
|
|
2371
|
+
return `${config.apiUrl}\0${config.apiKey}`;
|
|
2372
|
+
}
|
|
2373
|
+
async function resolveDefaultTeamId(config, create, timeout) {
|
|
2374
|
+
const key = cacheKey(config);
|
|
2375
|
+
const cached = defaultTeamCache.get(key);
|
|
2376
|
+
if (cached !== void 0) return cached;
|
|
2377
|
+
const client = getSharedClient(config);
|
|
2378
|
+
const resp = await client.get("/teams", { timeout });
|
|
2379
|
+
const rows = resp.teams ?? [];
|
|
2380
|
+
let best = null;
|
|
2381
|
+
for (const t of rows) {
|
|
2382
|
+
if (t.name === DEFAULT_TEAM_NAME) {
|
|
2383
|
+
if (best === null || t.created_at < best.created_at) {
|
|
2384
|
+
best = t;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (best !== null) {
|
|
2389
|
+
defaultTeamCache.set(key, best.team_id);
|
|
2390
|
+
return best.team_id;
|
|
2391
|
+
}
|
|
2392
|
+
if (!create) return null;
|
|
2393
|
+
const created = await client.post("/teams", {
|
|
2394
|
+
json: { name: DEFAULT_TEAM_NAME },
|
|
2395
|
+
timeout
|
|
2396
|
+
});
|
|
2397
|
+
defaultTeamCache.set(key, created.team_id);
|
|
2398
|
+
return created.team_id;
|
|
2399
|
+
}
|
|
2400
|
+
async function ensureDefaultEnv(config, teamId, timeout) {
|
|
2401
|
+
const client = getSharedClient(config);
|
|
2402
|
+
const resp = await client.get(
|
|
2403
|
+
`/teams/${encodeURIComponent(teamId)}/environments`,
|
|
2404
|
+
{ timeout }
|
|
2405
|
+
);
|
|
2406
|
+
const rows = resp.environments ?? [];
|
|
2407
|
+
if (rows.some((e) => e.name === DEFAULT_ENV_NAME)) return;
|
|
2408
|
+
try {
|
|
2409
|
+
await client.post(`/teams/${encodeURIComponent(teamId)}/environments`, {
|
|
2410
|
+
json: { name: DEFAULT_ENV_NAME },
|
|
2411
|
+
timeout
|
|
2412
|
+
});
|
|
2413
|
+
} catch {
|
|
2414
|
+
const resp2 = await client.get(
|
|
2415
|
+
`/teams/${encodeURIComponent(teamId)}/environments`,
|
|
2416
|
+
{ timeout }
|
|
2417
|
+
);
|
|
2418
|
+
const rows2 = resp2.environments ?? [];
|
|
2419
|
+
if (rows2.some((e) => e.name === DEFAULT_ENV_NAME)) return;
|
|
2420
|
+
throw new Error(`Failed to ensure default environment "${DEFAULT_ENV_NAME}" on team ${teamId}`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
async function expandVaultRefs(config, refs, timeout) {
|
|
2424
|
+
if (Object.keys(refs).length === 0) return refs;
|
|
2425
|
+
const needsTeam = Object.values(refs).some((v) => !v.startsWith("vault://"));
|
|
2426
|
+
if (!needsTeam) return refs;
|
|
2427
|
+
const teamId = await resolveDefaultTeamId(config, false, timeout);
|
|
2428
|
+
if (teamId === null) {
|
|
2429
|
+
throw new Error("vault_refs given but no vault secrets exist for this account");
|
|
2430
|
+
}
|
|
2431
|
+
const out = {};
|
|
2432
|
+
for (const [envVar, ref] of Object.entries(refs)) {
|
|
2433
|
+
out[envVar] = ref.startsWith("vault://") ? ref : `vault://${teamId}/${DEFAULT_ENV_NAME}/${ref}`;
|
|
2434
|
+
}
|
|
2435
|
+
return out;
|
|
2436
|
+
}
|
|
2437
|
+
function buildConfig(opts) {
|
|
2438
|
+
return new ConnectionConfig({
|
|
2439
|
+
apiKey: opts?.apiKey,
|
|
2440
|
+
domain: opts?.domain,
|
|
2441
|
+
apiUrl: opts?.apiUrl,
|
|
2442
|
+
requestTimeout: opts?.requestTimeout
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
var Vault = class _Vault {
|
|
2446
|
+
// -------------------------------------------------------------------------
|
|
2447
|
+
// Secrets
|
|
2448
|
+
// -------------------------------------------------------------------------
|
|
2449
|
+
/**
|
|
2450
|
+
* Store a secret's value (server-side, in OpenBao) plus its injection
|
|
2451
|
+
* scopes, under the auto-provisioned default team + "prod" environment.
|
|
2452
|
+
* Returns metadata only — the value is never echoed.
|
|
2453
|
+
*
|
|
2454
|
+
* POST /teams/{teamId}/vault/secrets
|
|
2455
|
+
*/
|
|
2456
|
+
static async createSecret(input, opts) {
|
|
2457
|
+
const config = buildConfig(opts);
|
|
2458
|
+
const teamId = await resolveDefaultTeamId(config, true, opts?.requestTimeout);
|
|
2459
|
+
if (teamId === null) throw new Error("Failed to resolve or create default team");
|
|
2460
|
+
await ensureDefaultEnv(config, teamId, opts?.requestTimeout);
|
|
2461
|
+
const body = {
|
|
2462
|
+
environment: DEFAULT_ENV_NAME,
|
|
2463
|
+
value: input.value
|
|
2464
|
+
};
|
|
2465
|
+
if (input.name) body.name = input.name;
|
|
2466
|
+
if (input.provider) body.provider = input.provider;
|
|
2467
|
+
if (input.scopes && input.scopes.length > 0) {
|
|
2468
|
+
body.scopes = input.scopes.map(vaultScopeToJSON);
|
|
2469
|
+
}
|
|
2470
|
+
if (input.rotationIntervalDays && input.rotationIntervalDays > 0) {
|
|
2471
|
+
body.rotation_interval_days = input.rotationIntervalDays;
|
|
2472
|
+
}
|
|
2473
|
+
const client = getSharedClient(config);
|
|
2474
|
+
const resp = await client.post(
|
|
2475
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets`,
|
|
2476
|
+
{ json: body, timeout: opts?.requestTimeout }
|
|
2477
|
+
);
|
|
2478
|
+
return parseVaultSecret(resp);
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* List secret metadata for the default team. Returns an empty array if no
|
|
2482
|
+
* default team has been provisioned yet.
|
|
2483
|
+
*
|
|
2484
|
+
* GET /teams/{teamId}/vault/secrets -> {secrets}
|
|
2485
|
+
*/
|
|
2486
|
+
static async listSecrets(opts) {
|
|
2487
|
+
const config = buildConfig(opts);
|
|
2488
|
+
const teamId = await resolveDefaultTeamId(config, false, opts?.requestTimeout);
|
|
2489
|
+
if (teamId === null) return [];
|
|
2490
|
+
const client = getSharedClient(config);
|
|
2491
|
+
const resp = await client.get(
|
|
2492
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets`,
|
|
2493
|
+
{ timeout: opts?.requestTimeout }
|
|
2494
|
+
);
|
|
2495
|
+
const rows = resp.secrets ?? [];
|
|
2496
|
+
return rows.map(parseVaultSecret);
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Replace a secret's value by name (server-side); scopes are unchanged.
|
|
2500
|
+
*
|
|
2501
|
+
* POST /teams/{teamId}/vault/secrets/{secretId}/rotate {value}
|
|
2502
|
+
*/
|
|
2503
|
+
static async rotateSecret(name, value, opts) {
|
|
2504
|
+
const config = buildConfig(opts);
|
|
2505
|
+
const teamId = await resolveDefaultTeamId(config, false, opts?.requestTimeout);
|
|
2506
|
+
if (teamId === null) throw new Error(`vault secret "${name}" not found`);
|
|
2507
|
+
const secretId = await _Vault._resolveSecretId(config, teamId, name, opts?.requestTimeout);
|
|
2508
|
+
const client = getSharedClient(config);
|
|
2509
|
+
await client.post(
|
|
2510
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets/${encodeURIComponent(secretId)}/rotate`,
|
|
2511
|
+
{ json: { value }, timeout: opts?.requestTimeout }
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Delete a secret by name — metadata and stored value.
|
|
2516
|
+
*
|
|
2517
|
+
* DELETE /teams/{teamId}/vault/secrets/{secretId}
|
|
2518
|
+
*/
|
|
2519
|
+
static async deleteSecret(name, opts) {
|
|
2520
|
+
const config = buildConfig(opts);
|
|
2521
|
+
const teamId = await resolveDefaultTeamId(config, false, opts?.requestTimeout);
|
|
2522
|
+
if (teamId === null) throw new Error(`vault secret "${name}" not found`);
|
|
2523
|
+
const secretId = await _Vault._resolveSecretId(config, teamId, name, opts?.requestTimeout);
|
|
2524
|
+
const client = getSharedClient(config);
|
|
2525
|
+
await client.delete(
|
|
2526
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets/${encodeURIComponent(secretId)}`,
|
|
2527
|
+
{ timeout: opts?.requestTimeout }
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Replace a secret's injection scopes by name; the value is unchanged. Use
|
|
2532
|
+
* this to change a secret's destination(s) or injection format in place
|
|
2533
|
+
* instead of delete + recreate. At least one scope is required.
|
|
2534
|
+
*
|
|
2535
|
+
* POST /teams/{teamId}/vault/secrets/{secretId}/scopes
|
|
2536
|
+
*/
|
|
2537
|
+
static async updateScopes(name, scopes, opts) {
|
|
2538
|
+
if (!scopes || scopes.length === 0) {
|
|
2539
|
+
throw new Error("at least one scope is required");
|
|
2540
|
+
}
|
|
2541
|
+
const config = buildConfig(opts);
|
|
2542
|
+
const teamId = await resolveDefaultTeamId(config, false, opts?.requestTimeout);
|
|
2543
|
+
if (teamId === null) throw new Error(`vault secret "${name}" not found`);
|
|
2544
|
+
const secretId = await _Vault._resolveSecretId(config, teamId, name, opts?.requestTimeout);
|
|
2545
|
+
const client = getSharedClient(config);
|
|
2546
|
+
await client.post(
|
|
2547
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets/${encodeURIComponent(secretId)}/scopes`,
|
|
2548
|
+
{ json: { scopes: scopes.map(vaultScopeToJSON) }, timeout: opts?.requestTimeout }
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
// -------------------------------------------------------------------------
|
|
2552
|
+
// Presets
|
|
2553
|
+
// -------------------------------------------------------------------------
|
|
2554
|
+
/**
|
|
2555
|
+
* List built-in provider preset catalog (templates only, no secret material).
|
|
2556
|
+
*
|
|
2557
|
+
* GET /vault/presets -> {presets}
|
|
2558
|
+
*/
|
|
2559
|
+
static async listPresets(opts) {
|
|
2560
|
+
const client = getSharedClient(buildConfig(opts));
|
|
2561
|
+
const resp = await client.get("/vault/presets", {
|
|
2562
|
+
timeout: opts?.requestTimeout
|
|
2563
|
+
});
|
|
2564
|
+
const rows = resp.presets ?? [];
|
|
2565
|
+
return rows.map(parseVaultPreset);
|
|
2566
|
+
}
|
|
2567
|
+
// -------------------------------------------------------------------------
|
|
2568
|
+
// Internal helpers
|
|
2569
|
+
// -------------------------------------------------------------------------
|
|
2570
|
+
/** Maps a secret name to its id within the given team. */
|
|
2571
|
+
static async _resolveSecretId(config, teamId, name, timeout) {
|
|
2572
|
+
const client = getSharedClient(config);
|
|
2573
|
+
const resp = await client.get(
|
|
2574
|
+
`/teams/${encodeURIComponent(teamId)}/vault/secrets`,
|
|
2575
|
+
{ timeout }
|
|
2576
|
+
);
|
|
2577
|
+
const rows = resp.secrets ?? [];
|
|
2578
|
+
for (const s of rows) {
|
|
2579
|
+
if (s.name === name) return String(s.secret_id ?? "");
|
|
2580
|
+
}
|
|
2581
|
+
throw new Error(`vault secret "${name}" not found`);
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
|
|
2209
2585
|
// src/sandbox/sandbox.ts
|
|
2210
2586
|
var DEFAULT_TEMPLATE = "base";
|
|
2211
2587
|
var DEFAULT_TIMEOUT = 300;
|
|
@@ -2351,6 +2727,9 @@ var Sandbox = class _Sandbox {
|
|
|
2351
2727
|
if (opts?.envs) {
|
|
2352
2728
|
body.envs = opts.envs;
|
|
2353
2729
|
}
|
|
2730
|
+
if (opts?.vaultRefs) {
|
|
2731
|
+
body.vault_refs = await expandVaultRefs(config, opts.vaultRefs, opts.requestTimeout);
|
|
2732
|
+
}
|
|
2354
2733
|
if (opts?.network) {
|
|
2355
2734
|
body.network = networkOptsToJSON(opts.network);
|
|
2356
2735
|
} else if (opts?.allowInternetAccess === false) {
|
|
@@ -2368,9 +2747,11 @@ var Sandbox = class _Sandbox {
|
|
|
2368
2747
|
if (opts?.volumes && opts.volumes.length > 0) {
|
|
2369
2748
|
body.volumes = opts.volumes.map(volumeAttachmentToJSON);
|
|
2370
2749
|
}
|
|
2750
|
+
const idempotencyKey = newIdempotencyKey();
|
|
2371
2751
|
const data = await client.post("/sandboxes", {
|
|
2372
2752
|
json: body,
|
|
2373
|
-
timeout: opts?.requestTimeout
|
|
2753
|
+
timeout: opts?.requestTimeout,
|
|
2754
|
+
...idempotencyKey ? { headers: { "Idempotency-Key": idempotencyKey } } : {}
|
|
2374
2755
|
});
|
|
2375
2756
|
const sandboxId = data.sandbox_id;
|
|
2376
2757
|
assertValidId(sandboxId, "sandbox ID (from server)");
|
|
@@ -3254,7 +3635,16 @@ function assertValidVolumeId3(id) {
|
|
|
3254
3635
|
}
|
|
3255
3636
|
}
|
|
3256
3637
|
var Volumes = class {
|
|
3257
|
-
/**
|
|
3638
|
+
/**
|
|
3639
|
+
* Create a volume named `name`, optionally populated with `data`.
|
|
3640
|
+
*
|
|
3641
|
+
* `POST /volumes` is the canonical create endpoint. With `data` (a gzip tar.gz)
|
|
3642
|
+
* the body is ingested into a new file-granular volume (or a legacy tarball
|
|
3643
|
+
* blob if no file-granular backend is configured). Creating an empty volume
|
|
3644
|
+
* (no `data`, or an empty buffer) requires a file-granular backend.
|
|
3645
|
+
* `empty(name)` / `ingest(name, data)` remain available for explicit,
|
|
3646
|
+
* backend-specific control.
|
|
3647
|
+
*/
|
|
3258
3648
|
static async create(name, data, opts) {
|
|
3259
3649
|
if (!name) {
|
|
3260
3650
|
throw new InvalidArgumentError("volume name is required");
|
|
@@ -3266,6 +3656,13 @@ var Volumes = class {
|
|
|
3266
3656
|
requestTimeout: opts?.requestTimeout
|
|
3267
3657
|
});
|
|
3268
3658
|
const client = getSharedClient(config);
|
|
3659
|
+
if (data === void 0 || data.byteLength === 0) {
|
|
3660
|
+
const resp2 = await client.post("/volumes", {
|
|
3661
|
+
params: { name },
|
|
3662
|
+
timeout: opts?.requestTimeout
|
|
3663
|
+
});
|
|
3664
|
+
return parseVolumeInfo(resp2);
|
|
3665
|
+
}
|
|
3269
3666
|
const body = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
3270
3667
|
const resp = await client.post("/volumes", {
|
|
3271
3668
|
params: { name },
|
|
@@ -3536,6 +3933,9 @@ var Governance = class {
|
|
|
3536
3933
|
ApiClient,
|
|
3537
3934
|
AuthenticationError,
|
|
3538
3935
|
BuildError,
|
|
3936
|
+
CODE_IDEMPOTENCY_IN_PROGRESS,
|
|
3937
|
+
CODE_IDEMPOTENCY_KEY_REUSED,
|
|
3938
|
+
CODE_TEMPLATE_NOT_READY,
|
|
3539
3939
|
CommandExitError,
|
|
3540
3940
|
CommandHandle,
|
|
3541
3941
|
Commands,
|
|
@@ -3570,6 +3970,7 @@ var Governance = class {
|
|
|
3570
3970
|
TemplateError,
|
|
3571
3971
|
TimeoutError,
|
|
3572
3972
|
TransformDirection,
|
|
3973
|
+
Vault,
|
|
3573
3974
|
VolumeFiles,
|
|
3574
3975
|
VolumeLocks,
|
|
3575
3976
|
Volumes,
|
|
@@ -3621,6 +4022,9 @@ var Governance = class {
|
|
|
3621
4022
|
parseSnapshotInfo,
|
|
3622
4023
|
parseTemplateBuildStatus,
|
|
3623
4024
|
parseToxicityConfig,
|
|
4025
|
+
parseVaultPreset,
|
|
4026
|
+
parseVaultScope,
|
|
4027
|
+
parseVaultSecret,
|
|
3624
4028
|
parseVolumeInfo,
|
|
3625
4029
|
parseWriteInfo,
|
|
3626
4030
|
requiresTlsInterception,
|
|
@@ -3628,6 +4032,7 @@ var Governance = class {
|
|
|
3628
4032
|
securityPolicyToJSON,
|
|
3629
4033
|
toxicityConfigToJSON,
|
|
3630
4034
|
validateNetworkEntry,
|
|
4035
|
+
vaultScopeToJSON,
|
|
3631
4036
|
volumeAttachmentToJSON
|
|
3632
4037
|
});
|
|
3633
4038
|
//# sourceMappingURL=index.cjs.map
|