@runblackcat/sdk 0.1.0-alpha.77 → 0.1.0-alpha.79

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runblackcat/sdk",
3
- "version": "0.1.0-alpha.77",
3
+ "version": "0.1.0-alpha.79",
4
4
  "description": "TypeScript SDK for the Blackcat sandbox gateway",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/TreebeardHQ/blackcat",
@@ -328,6 +328,10 @@ export interface SandboxBatch {
328
328
  counts: Partial<Record<SandboxBatchMemberState, number>>;
329
329
  members: SandboxBatchMember[];
330
330
  }
331
+ export type SandboxBatchResponse = SandboxBatch | {
332
+ request_id: string;
333
+ state: "prevented";
334
+ };
331
335
  export interface SandboxLeaseInput {
332
336
  /** Initial lease duration, between 60 seconds and 24 hours. */
333
337
  ttl_secs: number;
@@ -351,6 +355,10 @@ export type CreateCapacityReservationInput = ({
351
355
  memory_mib: number;
352
356
  /** How long to hold the reservation, between 1 second and 4 hours. */
353
357
  ttl_secs: number;
358
+ /** Defaults to `consumable`; `reusable` returns each slot after confirmed destruction. */
359
+ mode?: "consumable" | "reusable";
360
+ /** Stable retry key, at most 107 ASCII letters, digits, `_` or `-`. */
361
+ idempotency_key?: string;
354
362
  };
355
363
  export interface CapacityReservation {
356
364
  id: string;
@@ -361,10 +369,13 @@ export interface CapacityReservation {
361
369
  /** The template resource this reservation follows, or null when pinned. */
362
370
  template_resource: string | null;
363
371
  count: number;
372
+ mode: "consumable" | "reusable";
364
373
  vcpus: number;
365
374
  memory_mib: number;
366
375
  /** Slots consumed by an actual sandbox create. */
367
376
  claimed: number;
377
+ /** Current slot occupancy for reusable reservations; zero otherwise. */
378
+ occupied: number;
368
379
  /** Slots bound to a specific node and not yet claimed. */
369
380
  bound: number;
370
381
  /** Bound slots whose node already has the template warm. */
@@ -379,6 +390,9 @@ export interface CapacityReservation {
379
390
  * retryable under a new request id rather than a protocol fault.
380
391
  */
381
392
  export type CreateSandboxResponse = {
393
+ request_id: string;
394
+ state: "prevented";
395
+ } | ({
382
396
  request_id: string;
383
397
  id: string;
384
398
  tier: "boot" | "warm_pool" | "restore";
@@ -402,7 +416,11 @@ export type CreateSandboxResponse = {
402
416
  state: "failed";
403
417
  node: null;
404
418
  error: string;
405
- });
419
+ } | {
420
+ state: "destroyed";
421
+ node: string | null;
422
+ error: string;
423
+ }));
406
424
  /**
407
425
  * Restore phase breakdown a node reported for a checkpoint launch (Proposal
408
426
  * F, docs/restore-performance.md). `phase_ms` mirrors the node's own
@@ -731,7 +749,7 @@ declare const MAX_PROCESS_STDIN: number;
731
749
  declare const MAX_EXEC_STDIN: number;
732
750
  export declare function epochMillis(value: unknown, field: string): number;
733
751
  export declare function parseCreateSandboxResponse(value: unknown, requestId: string): CreateSandboxResponse;
734
- export declare function parseSandboxBatch(value: unknown, requestId: string): SandboxBatch;
752
+ export declare function parseSandboxBatch(value: unknown, requestId: string): SandboxBatchResponse;
735
753
  export declare function assertSecureBlackcatGatewayUrl(baseUrl: string): void;
736
754
  declare class GatewayClient {
737
755
  readonly baseUrl: string;
@@ -811,6 +829,10 @@ declare class ReservationsResource {
811
829
  /** Release early. Unclaimed slots free immediately; bound nodes then fall
812
830
  * back to the normal empty-worker scale-in grace period. */
813
831
  release(id: string): Promise<CapacityReservation>;
832
+ renew(id: string, input: {
833
+ ttl_secs: number;
834
+ idempotency_key: string;
835
+ }): Promise<CapacityReservation>;
814
836
  }
815
837
  declare class SandboxesResource {
816
838
  private readonly client;
@@ -829,10 +851,15 @@ declare class SandboxesResource {
829
851
  * Repeat the exact request to recover a lost response: member IDs are
830
852
  * stable. Poll `batch` for counts by state, or a member with `acquisition`.
831
853
  */
832
- createBatch(input: CreateSandboxBatchInput): Promise<SandboxBatch>;
854
+ createBatch(input: CreateSandboxBatchInput): Promise<SandboxBatchResponse>;
833
855
  /** Counts by state and every member of a batch created with `createBatch`. */
834
- batch(requestId: string): Promise<SandboxBatch>;
856
+ batch(requestId: string): Promise<SandboxBatchResponse>;
835
857
  acquisition(requestId: string): Promise<CreateSandboxResponse>;
858
+ /**
859
+ * Fence an acquisition request ID before its create is recorded, or destroy
860
+ * the allocation when the create has already won.
861
+ */
862
+ cancelAcquisition(requestId: string): Promise<CreateSandboxResponse>;
836
863
  get(id: string): Promise<Sandbox>;
837
864
  /** Extend the soft expiry without moving the immutable hard deadline. */
838
865
  renew(id: string, ttl_secs: number): Promise<RenewSandboxResponse>;
package/sdk/dist/index.js CHANGED
@@ -91,7 +91,10 @@ function parseCreateSandboxResponse(value, requestId) {
91
91
  throw new BlackcatProtocolError("sandbox create receipt is malformed or belongs to another request");
92
92
  }
93
93
  const row = value;
94
- if (row.request_id !== requestId || typeof row.id !== "string" || row.id.length === 0 || !["boot", "warm_pool", "restore"].includes(String(row.tier)) || !["placed", "pending", "failed"].includes(String(row.state)) || row.state === "placed" && typeof row.node !== "string" || row.state === "pending" && row.node !== null && typeof row.node !== "string" || row.state === "failed" && (row.node !== null || typeof row.error !== "string") || !isSystemTime(row.expires_at) || !isSystemTime(row.renewable_until)) {
94
+ if (row.request_id === requestId && row.state === "prevented") {
95
+ return value;
96
+ }
97
+ if (row.request_id !== requestId || typeof row.id !== "string" || row.id.length === 0 || !["boot", "warm_pool", "restore"].includes(String(row.tier)) || !["placed", "pending", "failed", "destroyed"].includes(String(row.state)) || row.state === "placed" && typeof row.node !== "string" || row.state === "pending" && row.node !== null && typeof row.node !== "string" || row.state === "failed" && (row.node !== null || typeof row.error !== "string") || row.state === "destroyed" && (row.node !== null && typeof row.node !== "string" || typeof row.error !== "string") || !isSystemTime(row.expires_at) || !isSystemTime(row.renewable_until)) {
95
98
  throw new BlackcatProtocolError("sandbox create receipt is malformed or belongs to another request");
96
99
  }
97
100
  return value;
@@ -102,6 +105,9 @@ function parseSandboxBatch(value, requestId) {
102
105
  if (typeof value !== "object" || value === null || Array.isArray(value))
103
106
  throw malformed();
104
107
  const row = value;
108
+ if (row.request_id === requestId && row.state === "prevented") {
109
+ return value;
110
+ }
105
111
  if (row.request_id !== requestId || typeof row.count !== "number" || typeof row.counts !== "object" || row.counts === null || !Array.isArray(row.members) || row.members.length !== row.count || !row.members.every((member) => {
106
112
  const m = member;
107
113
  return typeof m === "object" && m !== null && typeof m.request_id === "string" && typeof m.id === "string" && BATCH_STATES.includes(String(m.state));
@@ -436,6 +442,9 @@ class ReservationsResource {
436
442
  release(id) {
437
443
  return this.client.json("DELETE", `/v1/capacity-reservations/${pathSegment(id)}`, undefined, "token");
438
444
  }
445
+ renew(id, input) {
446
+ return this.client.json("POST", `/v1/capacity-reservations/${pathSegment(id)}/renew`, input, "token");
447
+ }
439
448
  }
440
449
 
441
450
  class SandboxesResource {
@@ -458,6 +467,9 @@ class SandboxesResource {
458
467
  async acquisition(requestId) {
459
468
  return parseCreateSandboxResponse(await this.client.json("GET", `/v1/sandboxes/acquisitions/${pathSegment(requestId)}`, undefined, "token"), requestId);
460
469
  }
470
+ async cancelAcquisition(requestId) {
471
+ return parseCreateSandboxResponse(await this.client.json("DELETE", `/v1/sandboxes/acquisitions/${pathSegment(requestId)}`, undefined, "token"), requestId);
472
+ }
461
473
  get(id) {
462
474
  return this.client.json("GET", `/v1/sandboxes/${pathSegment(id)}`, undefined, "token");
463
475
  }