@shardflux/sdk 0.5.0 → 0.6.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.
- package/CHANGELOG.md +73 -0
- package/README.md +228 -24
- package/dist/cell.d.ts +67 -2
- package/dist/cell.js +98 -8
- package/dist/client.d.ts +148 -22
- package/dist/client.js +237 -29
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +10 -0
- package/dist/generated/app-api.d.ts +10223 -5857
- package/dist/generated/cell-api.d.ts +140 -8
- package/dist/http.d.ts +30 -1
- package/dist/http.js +57 -3
- package/dist/index.d.ts +13 -9
- package/dist/index.js +5 -4
- package/dist/lifecycle.d.ts +48 -0
- package/dist/lifecycle.js +33 -0
- package/dist/progress.d.ts +166 -0
- package/dist/progress.js +240 -0
- package/dist/secrets.d.ts +83 -6
- package/dist/secrets.js +53 -1
- package/dist/templates.d.ts +279 -7
- package/dist/templates.js +216 -4
- package/dist/tokens.d.ts +8 -3
- package/dist/tokens.js +25 -13
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +5 -1
- package/dist/workspace.d.ts +112 -20
- package/dist/workspace.js +176 -10
- package/package.json +2 -1
package/dist/templates.d.ts
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template registry
|
|
3
|
-
*
|
|
2
|
+
* Template registry, custom template builds, the version file tree and diff, and template dev mode (drafts and test
|
|
3
|
+
* instances) over the application API (/v1). Registry and build types are written by hand and checked against the
|
|
4
|
+
* generated contract in type-checks.ts; the Templates v2 types (contracts §19) alias the generated schemas.
|
|
4
5
|
*
|
|
5
6
|
* Publishing/archiving an organization template version is a browser-only
|
|
6
7
|
* owner/admin action (it changes what `open` resolves for every project), so
|
|
7
8
|
* it is not exposed here.
|
|
8
9
|
*/
|
|
9
|
-
import type {
|
|
10
|
+
import type { components } from './generated/app-api.js';
|
|
11
|
+
import type { ClientContext, Operation, Page, WaitOptions } from './client.js';
|
|
12
|
+
import type { ToolName } from './tokens.js';
|
|
13
|
+
import { Workspace } from './workspace.js';
|
|
14
|
+
type S = components['schemas'];
|
|
15
|
+
/** Manifest v2 `defaults` of a version (contracts §19.7). */
|
|
16
|
+
export type TemplateDefaults = S['TemplateDefaults'];
|
|
17
|
+
/** How a version was produced: a recipe build, a saved workspace (or draft publish), or git (reserved). */
|
|
18
|
+
export type TemplateSource = S['TemplateSource'];
|
|
19
|
+
/** The version's file list state (the tree and diff routes read it). */
|
|
20
|
+
export type TemplateFilesSummary = S['TemplateFilesSummary'];
|
|
21
|
+
/** Storage of a template version (contracts §19.13). */
|
|
22
|
+
export type TemplateStorage = S['TemplateStorage'];
|
|
23
|
+
export type TemplateStorageWarning = S['TemplateStorageWarning'];
|
|
24
|
+
/** Template storage of an organization (counts toward retained_state_gib). */
|
|
25
|
+
export type OrgTemplateStorage = S['OrgTemplateStorage'];
|
|
26
|
+
/** One inode path of a template version (contracts §19.10). */
|
|
27
|
+
export type TemplateFileEntry = S['TemplateFileEntry'];
|
|
28
|
+
/** One directory level of a version's tree (keyset-paginated). */
|
|
29
|
+
export type TemplateFilePage = S['TemplateFilePage'];
|
|
30
|
+
/** One path of a version diff: added | removed | changed | type_changed | metadata. */
|
|
31
|
+
export type TemplateDiffEntry = S['TemplateDiffEntry'];
|
|
32
|
+
export type TemplateDiffChange = TemplateDiffEntry['change'];
|
|
33
|
+
/** One page of a version diff; the first page (no cursor) carries `summary`. */
|
|
34
|
+
export type TemplateDiffPage = S['TemplateDiffPage'];
|
|
35
|
+
/** The live draft of an organization template (contracts §19.9). */
|
|
36
|
+
export type TemplateDraft = S['TemplateDraft'];
|
|
37
|
+
/** A disk-only capture of the draft (a test instance or a publish starts from one). */
|
|
38
|
+
export type DraftState = S['DraftState'];
|
|
39
|
+
export type CreateDraftBody = S['CreateDraftBody'];
|
|
40
|
+
export type CreateTestInstanceBody = S['CreateTestInstanceBody'];
|
|
41
|
+
export type PublishDraftBody = S['PublishDraftBody'];
|
|
42
|
+
export type SaveAsTemplateBody = S['SaveAsTemplateBody'];
|
|
43
|
+
/** Defaults of a version produced by save-as-template or a draft publish (omitted fields: persistent, platform idle timeout, no limits). */
|
|
44
|
+
export type TemplateDefaultsInput = NonNullable<SaveAsTemplateBody['defaults']>;
|
|
10
45
|
export type TemplateOwner = 'platform' | 'organization';
|
|
11
46
|
export type TemplateVersionState = 'unpublished' | 'published' | 'archived';
|
|
12
47
|
export type CeilingSource = 'template' | 'user' | 'plan';
|
|
@@ -76,6 +111,42 @@ export interface TemplateVersion {
|
|
|
76
111
|
}>;
|
|
77
112
|
build_id: string | null;
|
|
78
113
|
publishable: boolean;
|
|
114
|
+
/** `shardflux.template.v1` or `shardflux.template.v2` (Templates v2). */
|
|
115
|
+
manifest_schema: string | null;
|
|
116
|
+
/** Layouts a new workspace of this version may use (a manifest without the field: legacy only). */
|
|
117
|
+
disk_layouts: Array<'legacy' | 'layered'>;
|
|
118
|
+
source: TemplateSource | null;
|
|
119
|
+
/** The platform base (or parent) the version was built on. */
|
|
120
|
+
base: {
|
|
121
|
+
name: string;
|
|
122
|
+
version: string;
|
|
123
|
+
manifest_sha256: string | null;
|
|
124
|
+
rootfs_sha256: string | null;
|
|
125
|
+
} | null;
|
|
126
|
+
defaults: TemplateDefaults;
|
|
127
|
+
/** The file list the tree and diff read (`loaded` when files() and diff() can answer). */
|
|
128
|
+
files: TemplateFilesSummary;
|
|
129
|
+
rootfs_bytes: number | null;
|
|
130
|
+
/** The org layers of this version's chain, bottom to top (empty for images). */
|
|
131
|
+
chain: Array<{
|
|
132
|
+
layer_id: string;
|
|
133
|
+
bytes: number;
|
|
134
|
+
introduced_in_version: number | null;
|
|
135
|
+
}>;
|
|
136
|
+
storage: TemplateStorage;
|
|
137
|
+
}
|
|
138
|
+
/** Summary of an organization template's live draft on the template view (null when none). */
|
|
139
|
+
export interface TemplateDraftSummary {
|
|
140
|
+
workspace_id: string;
|
|
141
|
+
workspace_key: string;
|
|
142
|
+
observed_state: string;
|
|
143
|
+
base_version: {
|
|
144
|
+
id: string;
|
|
145
|
+
template_id: string;
|
|
146
|
+
slug: string;
|
|
147
|
+
version: number;
|
|
148
|
+
};
|
|
149
|
+
created_at: string;
|
|
79
150
|
}
|
|
80
151
|
export interface TemplateSummary {
|
|
81
152
|
id: string;
|
|
@@ -86,7 +157,16 @@ export interface TemplateSummary {
|
|
|
86
157
|
created_at: string;
|
|
87
158
|
archived_at: string | null;
|
|
88
159
|
shadowed_by_organization_template: boolean;
|
|
160
|
+
/** Mutable template-level defaults (contracts §20.5, §20.7): the size of a start without caps.memory_mib, and the idle policy of a workspace that sets none. */
|
|
161
|
+
defaults: {
|
|
162
|
+
memory_mib: number | null;
|
|
163
|
+
idle_policy: string | null;
|
|
164
|
+
};
|
|
89
165
|
open_version: TemplateVersion | null;
|
|
166
|
+
/** The live draft (organization templates in dev mode), or null. */
|
|
167
|
+
draft: TemplateDraftSummary | null;
|
|
168
|
+
/** Reserved (T2): always `pinned` in T1. */
|
|
169
|
+
update_policy: 'pinned' | 'auto';
|
|
90
170
|
}
|
|
91
171
|
export interface TemplateDetail extends TemplateSummary {
|
|
92
172
|
plan: {
|
|
@@ -179,14 +259,15 @@ export interface TemplateBuild {
|
|
|
179
259
|
mode: 'none' | 'egress_allowlist';
|
|
180
260
|
allow_hosts: string[];
|
|
181
261
|
};
|
|
262
|
+
/** recipe_schema/recipe_sha256 are null for workspace-source builds (save-as-template, draft publish). */
|
|
182
263
|
provenance: {
|
|
183
|
-
recipe_schema: string;
|
|
184
|
-
recipe_sha256: string;
|
|
264
|
+
recipe_schema: string | null;
|
|
265
|
+
recipe_sha256: string | null;
|
|
185
266
|
base_artifact_sha256: string;
|
|
186
267
|
builder_id: string | null;
|
|
187
268
|
attempt: number;
|
|
188
269
|
};
|
|
189
|
-
/**
|
|
270
|
+
/** Recipe builds only, on create and get of one build. */
|
|
190
271
|
recipe?: {
|
|
191
272
|
dockerfile: string;
|
|
192
273
|
};
|
|
@@ -220,6 +301,20 @@ export interface TemplateBuild {
|
|
|
220
301
|
};
|
|
221
302
|
publishable: boolean;
|
|
222
303
|
builder_availability: BuilderAvailability;
|
|
304
|
+
/** `recipe` (Dockerfile dialect) or `workspace` (save-as-template, draft publish; contracts §19.8). */
|
|
305
|
+
source_kind: 'recipe' | 'workspace';
|
|
306
|
+
source_workspace_id: string | null;
|
|
307
|
+
/** The captured checkpoint (null until the capture operation succeeded). */
|
|
308
|
+
source_checkpoint_id: string | null;
|
|
309
|
+
/** sf-scrub.v1 report of a workspace build: {policy, removed_count, removed_bytes, removed_paths, emptied, reimposed}. */
|
|
310
|
+
scrub_result: Record<string, unknown> | null;
|
|
311
|
+
/** The produced file list: {key, size, sha256, content_sha256, entries, total_file_bytes}. */
|
|
312
|
+
files: Record<string, unknown> | null;
|
|
313
|
+
produced_layer_id: string | null;
|
|
314
|
+
/** The base chain already had 4 org layers and was squashed into the produced one. */
|
|
315
|
+
squashed: boolean | null;
|
|
316
|
+
/** Organization bytes of the produced chain (limit: the plan's template_org_bytes_max). */
|
|
317
|
+
org_bytes: number | null;
|
|
223
318
|
}
|
|
224
319
|
export interface TemplateRecipe {
|
|
225
320
|
/** `<template slug>@<version>`, referenced in the Dockerfile as `FROM shardflux-base`. */
|
|
@@ -258,8 +353,11 @@ export interface TemplateBuildLogUrl {
|
|
|
258
353
|
}
|
|
259
354
|
export interface WaitForBuildOptions {
|
|
260
355
|
timeoutMs?: number;
|
|
356
|
+
/** Backoff used when the server does not hold the poll (default 1 s doubling to 10 s). */
|
|
261
357
|
pollIntervalMs?: number;
|
|
262
358
|
maxPollIntervalMs?: number;
|
|
359
|
+
/** Ask the server to hold each poll until the build changes (`Prefer: wait`, contracts §3; default true). */
|
|
360
|
+
serverWait?: boolean;
|
|
263
361
|
signal?: AbortSignal;
|
|
264
362
|
}
|
|
265
363
|
/** Finished from the caller's point of view: failed/canceled/legacy succeeded, or published and its registration settled. */
|
|
@@ -269,6 +367,157 @@ export declare class TemplateBuildTimeoutError extends Error {
|
|
|
269
367
|
readonly build: TemplateBuild;
|
|
270
368
|
constructor(build: TemplateBuild, waitedMs: number);
|
|
271
369
|
}
|
|
370
|
+
/** operation: the capture (layer_snapshot) of a running workspace, null otherwise. build: poll it until registration.state is registered. */
|
|
371
|
+
export interface SaveAsTemplateResponse {
|
|
372
|
+
operation: Operation | null;
|
|
373
|
+
build: TemplateBuild;
|
|
374
|
+
}
|
|
375
|
+
/** Save-as-template (contracts §19.8): everything in the workspace, minus the sf-scrub.v1 list, becomes one new org layer. */
|
|
376
|
+
export interface SaveAsTemplateParams {
|
|
377
|
+
/** Organization template to save into (created when absent; platform slugs are refused with platform_template_slug). */
|
|
378
|
+
templateSlug: string;
|
|
379
|
+
/** Template name when this save creates the template. */
|
|
380
|
+
displayName?: string;
|
|
381
|
+
/** The version description (default: the source version's). */
|
|
382
|
+
description?: string;
|
|
383
|
+
/** Defaults of the new version (default: the source version's, else persistent). */
|
|
384
|
+
defaults?: TemplateDefaultsInput;
|
|
385
|
+
/** A committed checkpoint of this workspace to save instead of its current state. */
|
|
386
|
+
checkpointId?: string;
|
|
387
|
+
/** Publish the produced version once registered (default true). */
|
|
388
|
+
autoPublish?: boolean;
|
|
389
|
+
/** Up to 200 absolute paths the credential scan may report without failing the build. */
|
|
390
|
+
acknowledgedScanFindings?: string[];
|
|
391
|
+
/** Defaults to a random key so retries never start a second save. */
|
|
392
|
+
idempotencyKey?: string;
|
|
393
|
+
}
|
|
394
|
+
/** The JSON body of save-as-template (and, without template_slug/display_name/checkpoint_id, of a draft publish). */
|
|
395
|
+
export declare function saveAsTemplateBody(params: SaveAsTemplateParams): SaveAsTemplateBody;
|
|
396
|
+
export interface TemplateFilesParams {
|
|
397
|
+
/** Directory to list (absolute, normalized; default `/`). */
|
|
398
|
+
path?: string;
|
|
399
|
+
/** 1..1000 (default 200). */
|
|
400
|
+
limit?: number;
|
|
401
|
+
cursor?: string;
|
|
402
|
+
/** Pick the platform template even when an organization template shadows its slug. */
|
|
403
|
+
owner?: TemplateOwner;
|
|
404
|
+
/** Use the organization form of the route (default: the API key's organization). */
|
|
405
|
+
organizationId?: string;
|
|
406
|
+
}
|
|
407
|
+
export interface TemplateDiffParams {
|
|
408
|
+
/** A version number of this template, or `base` (the `to` version's build base, possibly a platform version). */
|
|
409
|
+
from: number | 'base';
|
|
410
|
+
to: number;
|
|
411
|
+
/** Only paths starting with this (absolute) prefix. */
|
|
412
|
+
pathPrefix?: string;
|
|
413
|
+
change?: TemplateDiffChange;
|
|
414
|
+
/** 1..1000. */
|
|
415
|
+
limit?: number;
|
|
416
|
+
cursor?: string;
|
|
417
|
+
owner?: TemplateOwner;
|
|
418
|
+
organizationId?: string;
|
|
419
|
+
}
|
|
420
|
+
export interface CreateDraftParams {
|
|
421
|
+
/** `<slug>@<version>`: a published, layered-capable version (default: the template's latest published version; required when it has none). */
|
|
422
|
+
base?: string;
|
|
423
|
+
caps?: {
|
|
424
|
+
cpu_millis?: number;
|
|
425
|
+
memory_mib?: number;
|
|
426
|
+
disk_gib?: number;
|
|
427
|
+
};
|
|
428
|
+
/** Browser/session principals only; API keys use their own project. */
|
|
429
|
+
projectId?: string;
|
|
430
|
+
/** Attribution label for tool tokens obtained through the returned workspace object. */
|
|
431
|
+
agentLabel?: string;
|
|
432
|
+
tools?: ToolName[];
|
|
433
|
+
/** `false`: return at once (possibly not ready). Default: wait until the draft is ready. */
|
|
434
|
+
wait?: false | WaitOptions;
|
|
435
|
+
idempotencyKey?: string;
|
|
436
|
+
}
|
|
437
|
+
export interface OpenTestInstanceParams {
|
|
438
|
+
/** A draft state (states()). Omitted: a fresh capture of the running draft (its current checkpoint when suspended). */
|
|
439
|
+
stateId?: string;
|
|
440
|
+
/** Workspace key (default sf:test:<slug>:<8 hex>); keys starting with `sf:` are reserved. */
|
|
441
|
+
key?: string;
|
|
442
|
+
caps?: {
|
|
443
|
+
cpu_millis?: number;
|
|
444
|
+
memory_mib?: number;
|
|
445
|
+
disk_gib?: number;
|
|
446
|
+
};
|
|
447
|
+
agentLabel?: string;
|
|
448
|
+
tools?: ToolName[];
|
|
449
|
+
/** `false`: return at once. Default: wait until the test instance is ready. */
|
|
450
|
+
wait?: false | WaitOptions;
|
|
451
|
+
idempotencyKey?: string;
|
|
452
|
+
}
|
|
453
|
+
export interface PublishDraftParams {
|
|
454
|
+
/** A draft state to publish (default: the draft's current state; a fresh capture when running). */
|
|
455
|
+
stateId?: string;
|
|
456
|
+
description?: string;
|
|
457
|
+
defaults?: TemplateDefaultsInput;
|
|
458
|
+
autoPublish?: boolean;
|
|
459
|
+
acknowledgedScanFindings?: string[];
|
|
460
|
+
idempotencyKey?: string;
|
|
461
|
+
}
|
|
462
|
+
/** The draft's workspace (a Workspace handle), the draft view and the open operation (null when it was already ready). */
|
|
463
|
+
export interface DraftOpened {
|
|
464
|
+
workspace: Workspace;
|
|
465
|
+
draft: TemplateDraft;
|
|
466
|
+
operation: Operation | null;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
|
|
470
|
+
* workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
|
|
471
|
+
* state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
|
|
472
|
+
* tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
|
|
473
|
+
*/
|
|
474
|
+
export declare class TemplateDraftApi {
|
|
475
|
+
#private;
|
|
476
|
+
readonly slug: string;
|
|
477
|
+
constructor(ctx: () => ClientContext, slug: string, organizationId?: string);
|
|
478
|
+
/**
|
|
479
|
+
* Opens the template's draft on `base` (202; waits until ready unless `wait: false`). 409 template_not_layered (the
|
|
480
|
+
* base is not layered-capable, or layered opens are off), 409 draft_exists (details.workspace_id).
|
|
481
|
+
*/
|
|
482
|
+
create(params?: CreateDraftParams): Promise<DraftOpened>;
|
|
483
|
+
/** The draft (404 not_found with details.reason draft_not_found when the template has none). */
|
|
484
|
+
get(): Promise<TemplateDraft>;
|
|
485
|
+
/** Like get() but null when the template has no draft. */
|
|
486
|
+
find(): Promise<TemplateDraft | null>;
|
|
487
|
+
/** Deletes the draft (operation `delete`, input.reason draft_discarded) and ends its live test instances. */
|
|
488
|
+
discard(opts?: {
|
|
489
|
+
idempotencyKey?: string;
|
|
490
|
+
}): Promise<Operation>;
|
|
491
|
+
/** Captures a draft state (`layer_snapshot`; its result carries checkpoint_id). A suspended draft is 409 workspace_not_running. */
|
|
492
|
+
captureState(opts?: {
|
|
493
|
+
label?: string;
|
|
494
|
+
idempotencyKey?: string;
|
|
495
|
+
}): Promise<Operation>;
|
|
496
|
+
/** The draft's states, newest first. */
|
|
497
|
+
states(params?: {
|
|
498
|
+
limit?: number;
|
|
499
|
+
cursor?: string;
|
|
500
|
+
}): Promise<Page<DraftState>>;
|
|
501
|
+
statesAll(params?: {
|
|
502
|
+
limit?: number;
|
|
503
|
+
}): AsyncGenerator<DraftState>;
|
|
504
|
+
/**
|
|
505
|
+
* Opens a test instance: a session workspace on the draft base whose layer is a copy of the state (its writes never
|
|
506
|
+
* reach the draft). It ends on close(), idle or a draft discard. Waits until ready unless `wait: false`.
|
|
507
|
+
*/
|
|
508
|
+
openTestInstance(params?: OpenTestInstanceParams): Promise<Workspace>;
|
|
509
|
+
/** The draft's live test instances (`includeEnded` adds ended ones: tombstones with ended_reason). */
|
|
510
|
+
testInstances(params?: {
|
|
511
|
+
limit?: number;
|
|
512
|
+
cursor?: string;
|
|
513
|
+
includeEnded?: boolean;
|
|
514
|
+
}): Promise<Page<Workspace>>;
|
|
515
|
+
/**
|
|
516
|
+
* Publishes the draft as the template's next version (save-as-template from the draft). 409 draft_stale (details
|
|
517
|
+
* latest_version, draft_base_version) or build_in_progress. Poll the returned build (templates.builds.waitForBuild).
|
|
518
|
+
*/
|
|
519
|
+
publish(params?: PublishDraftParams): Promise<SaveAsTemplateResponse>;
|
|
520
|
+
}
|
|
272
521
|
export declare class TemplateBuildsApi {
|
|
273
522
|
#private;
|
|
274
523
|
constructor(ctx: () => ClientContext);
|
|
@@ -290,7 +539,8 @@ export declare class TemplateBuildsApi {
|
|
|
290
539
|
cancel(organizationId: string, buildId: string): Promise<TemplateBuild>;
|
|
291
540
|
builderAvailability(organizationId: string): Promise<BuilderAvailability>;
|
|
292
541
|
/**
|
|
293
|
-
*
|
|
542
|
+
* Waits until the build is settled (each poll asks the server to hold it until the build changes, `Prefer: wait`,
|
|
543
|
+
* falling back to backoff polling on servers that do not wait) and returns it: failed | canceled, or published with its registration
|
|
294
544
|
* `registered` (the version exists; `template_version`) or `failed`. Legacy builds end `succeeded`.
|
|
295
545
|
* Throws TemplateBuildTimeoutError at the deadline (default 30 min); the build continues.
|
|
296
546
|
*/
|
|
@@ -325,4 +575,26 @@ export declare class TemplatesApi {
|
|
|
325
575
|
includeArchived?: boolean;
|
|
326
576
|
owner?: TemplateOwner;
|
|
327
577
|
}): Promise<TemplateDetail | null>;
|
|
578
|
+
/**
|
|
579
|
+
* One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
|
|
580
|
+
* details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
|
|
581
|
+
* version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
|
|
582
|
+
*/
|
|
583
|
+
files(slug: string, version: number, params?: TemplateFilesParams): Promise<TemplateFilePage>;
|
|
584
|
+
/** Every entry of one directory, following next_cursor. */
|
|
585
|
+
filesAll(slug: string, version: number, params?: Omit<TemplateFilesParams, 'cursor'>): AsyncGenerator<TemplateFileEntry>;
|
|
586
|
+
/** One entry of a version's file tree (404 path_not_found). */
|
|
587
|
+
fileEntry(slug: string, version: number, path: string, params?: {
|
|
588
|
+
owner?: TemplateOwner;
|
|
589
|
+
organizationId?: string;
|
|
590
|
+
}): Promise<TemplateFileEntry>;
|
|
591
|
+
/** One page of the diff between two versions (keyset-paginated by path; the first page carries `summary`). */
|
|
592
|
+
diff(slug: string, params: TemplateDiffParams): Promise<TemplateDiffPage>;
|
|
593
|
+
/** Every diff entry, following next_cursor. */
|
|
594
|
+
diffAll(slug: string, params: Omit<TemplateDiffParams, 'cursor'>): AsyncGenerator<TemplateDiffEntry>;
|
|
595
|
+
/** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
|
|
596
|
+
draft(slug: string, params?: {
|
|
597
|
+
organizationId?: string;
|
|
598
|
+
}): TemplateDraftApi;
|
|
328
599
|
}
|
|
600
|
+
export {};
|
package/dist/templates.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ShardfluxApiError } from "./errors.js";
|
|
2
|
-
import { randomId } from "./http.js";
|
|
2
|
+
import { pollWithWait, randomId } from "./http.js";
|
|
3
|
+
import { Workspace } from "./workspace.js";
|
|
3
4
|
const TERMINAL = new Set(['succeeded', 'failed', 'canceled']);
|
|
4
5
|
/** Finished from the caller's point of view: failed/canceled/legacy succeeded, or published and its registration settled. */
|
|
5
6
|
export function buildSettled(build) {
|
|
@@ -17,6 +18,164 @@ export class TemplateBuildTimeoutError extends Error {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
const enc = encodeURIComponent;
|
|
21
|
+
/** Where a template route lives: the SDK form `/v1/templates/{slug}` or the organization form (both take API keys). */
|
|
22
|
+
function templatePath(slug, organizationId) {
|
|
23
|
+
return organizationId === undefined ? `/v1/templates/${enc(slug)}` : `/v1/organizations/${enc(organizationId)}/templates/${enc(slug)}`;
|
|
24
|
+
}
|
|
25
|
+
/** The JSON body of save-as-template (and, without template_slug/display_name/checkpoint_id, of a draft publish). */
|
|
26
|
+
export function saveAsTemplateBody(params) {
|
|
27
|
+
const body = { template_slug: params.templateSlug };
|
|
28
|
+
if (params.displayName !== undefined)
|
|
29
|
+
body.display_name = params.displayName;
|
|
30
|
+
if (params.description !== undefined)
|
|
31
|
+
body.description = params.description;
|
|
32
|
+
if (params.defaults !== undefined)
|
|
33
|
+
body.defaults = params.defaults;
|
|
34
|
+
if (params.checkpointId !== undefined)
|
|
35
|
+
body.checkpoint_id = params.checkpointId;
|
|
36
|
+
if (params.autoPublish !== undefined)
|
|
37
|
+
body.auto_publish = params.autoPublish;
|
|
38
|
+
if (params.acknowledgedScanFindings !== undefined)
|
|
39
|
+
body.acknowledged_scan_findings = params.acknowledgedScanFindings;
|
|
40
|
+
return body;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
|
|
44
|
+
* workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
|
|
45
|
+
* state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
|
|
46
|
+
* tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
|
|
47
|
+
*/
|
|
48
|
+
export class TemplateDraftApi {
|
|
49
|
+
slug;
|
|
50
|
+
#ctx;
|
|
51
|
+
#org;
|
|
52
|
+
constructor(ctx, slug, organizationId) {
|
|
53
|
+
this.#ctx = ctx;
|
|
54
|
+
this.slug = slug;
|
|
55
|
+
this.#org = organizationId;
|
|
56
|
+
}
|
|
57
|
+
#path(suffix = '') {
|
|
58
|
+
return `${templatePath(this.slug, this.#org)}/draft${suffix}`;
|
|
59
|
+
}
|
|
60
|
+
async #opened(res, p) {
|
|
61
|
+
const ctx = this.#ctx();
|
|
62
|
+
const wrap = { agentLabel: p.agentLabel, tools: p.tools };
|
|
63
|
+
if (res.status === 200 || p.wait === false || res.body.operation === null) {
|
|
64
|
+
return { workspace: new Workspace(ctx, res.body.workspace, { ...wrap, token: res.body.tool_token }), operation: res.body.operation };
|
|
65
|
+
}
|
|
66
|
+
const operation = await ctx.workspaces.waitForOperation(res.body.operation.id, p.wait ?? {});
|
|
67
|
+
const view = await ctx.http.json('GET', `/v1/workspaces/${enc(res.body.workspace.id)}`, {}, ctx.authorization);
|
|
68
|
+
return { workspace: new Workspace(ctx, view, wrap), operation };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Opens the template's draft on `base` (202; waits until ready unless `wait: false`). 409 template_not_layered (the
|
|
72
|
+
* base is not layered-capable, or layered opens are off), 409 draft_exists (details.workspace_id).
|
|
73
|
+
*/
|
|
74
|
+
async create(params = {}) {
|
|
75
|
+
const body = {};
|
|
76
|
+
if (params.base !== undefined)
|
|
77
|
+
body.base = params.base;
|
|
78
|
+
if (params.caps !== undefined)
|
|
79
|
+
body.caps = params.caps;
|
|
80
|
+
if (params.projectId !== undefined)
|
|
81
|
+
body.project_id = params.projectId;
|
|
82
|
+
if (params.agentLabel !== undefined)
|
|
83
|
+
body.agent_label = params.agentLabel;
|
|
84
|
+
if (params.tools !== undefined)
|
|
85
|
+
body.tools = params.tools;
|
|
86
|
+
const ctx = this.#ctx();
|
|
87
|
+
const res = await ctx.http.jsonWithStatus('POST', this.#path(), { json: body, idempotencyKey: params.idempotencyKey ?? randomId('draft-') }, ctx.authorization);
|
|
88
|
+
const { workspace, operation } = await this.#opened(res, params);
|
|
89
|
+
return { workspace, draft: { ...res.body.draft, workspace: workspace.data }, operation };
|
|
90
|
+
}
|
|
91
|
+
/** The draft (404 not_found with details.reason draft_not_found when the template has none). */
|
|
92
|
+
get() {
|
|
93
|
+
const ctx = this.#ctx();
|
|
94
|
+
return ctx.http.json('GET', this.#path(), {}, ctx.authorization);
|
|
95
|
+
}
|
|
96
|
+
/** Like get() but null when the template has no draft. */
|
|
97
|
+
async find() {
|
|
98
|
+
try {
|
|
99
|
+
return await this.get();
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof ShardfluxApiError && err.status === 404 && err.details?.reason === 'draft_not_found')
|
|
103
|
+
return null;
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Deletes the draft (operation `delete`, input.reason draft_discarded) and ends its live test instances. */
|
|
108
|
+
async discard(opts = {}) {
|
|
109
|
+
const ctx = this.#ctx();
|
|
110
|
+
const out = await ctx.http.json('DELETE', this.#path(), { idempotencyKey: opts.idempotencyKey ?? randomId('op-') }, ctx.authorization);
|
|
111
|
+
return out.operation;
|
|
112
|
+
}
|
|
113
|
+
/** Captures a draft state (`layer_snapshot`; its result carries checkpoint_id). A suspended draft is 409 workspace_not_running. */
|
|
114
|
+
async captureState(opts = {}) {
|
|
115
|
+
const ctx = this.#ctx();
|
|
116
|
+
const out = await ctx.http.json('POST', this.#path('/states'), { json: opts.label === undefined ? {} : { label: opts.label }, idempotencyKey: opts.idempotencyKey ?? randomId('op-') }, ctx.authorization);
|
|
117
|
+
return out.operation;
|
|
118
|
+
}
|
|
119
|
+
/** The draft's states, newest first. */
|
|
120
|
+
async states(params = {}) {
|
|
121
|
+
const ctx = this.#ctx();
|
|
122
|
+
const page = await ctx.http.json('GET', this.#path('/states'), { query: { limit: params.limit, cursor: params.cursor } }, ctx.authorization);
|
|
123
|
+
return { data: page.data, nextCursor: page.next_cursor };
|
|
124
|
+
}
|
|
125
|
+
async *statesAll(params = {}) {
|
|
126
|
+
let cursor;
|
|
127
|
+
do {
|
|
128
|
+
const page = await this.states({ ...params, ...(cursor === undefined ? {} : { cursor }) });
|
|
129
|
+
yield* page.data;
|
|
130
|
+
cursor = page.nextCursor ?? undefined;
|
|
131
|
+
} while (cursor !== undefined);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Opens a test instance: a session workspace on the draft base whose layer is a copy of the state (its writes never
|
|
135
|
+
* reach the draft). It ends on close(), idle or a draft discard. Waits until ready unless `wait: false`.
|
|
136
|
+
*/
|
|
137
|
+
async openTestInstance(params = {}) {
|
|
138
|
+
const body = {};
|
|
139
|
+
if (params.stateId !== undefined)
|
|
140
|
+
body.state_id = params.stateId;
|
|
141
|
+
if (params.key !== undefined)
|
|
142
|
+
body.key = params.key;
|
|
143
|
+
if (params.caps !== undefined)
|
|
144
|
+
body.caps = params.caps;
|
|
145
|
+
if (params.agentLabel !== undefined)
|
|
146
|
+
body.agent_label = params.agentLabel;
|
|
147
|
+
if (params.tools !== undefined)
|
|
148
|
+
body.tools = params.tools;
|
|
149
|
+
const ctx = this.#ctx();
|
|
150
|
+
const res = await ctx.http.jsonWithStatus('POST', this.#path('/test-instances'), { json: body, idempotencyKey: params.idempotencyKey ?? randomId('test-') }, ctx.authorization);
|
|
151
|
+
return (await this.#opened(res, params)).workspace;
|
|
152
|
+
}
|
|
153
|
+
/** The draft's live test instances (`includeEnded` adds ended ones: tombstones with ended_reason). */
|
|
154
|
+
async testInstances(params = {}) {
|
|
155
|
+
const ctx = this.#ctx();
|
|
156
|
+
const page = await ctx.http.json('GET', this.#path('/test-instances'), { query: { limit: params.limit, cursor: params.cursor, include_ended: params.includeEnded } }, ctx.authorization);
|
|
157
|
+
return { data: page.data.map((v) => new Workspace(ctx, v)), nextCursor: page.next_cursor };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Publishes the draft as the template's next version (save-as-template from the draft). 409 draft_stale (details
|
|
161
|
+
* latest_version, draft_base_version) or build_in_progress. Poll the returned build (templates.builds.waitForBuild).
|
|
162
|
+
*/
|
|
163
|
+
publish(params = {}) {
|
|
164
|
+
const body = {};
|
|
165
|
+
if (params.stateId !== undefined)
|
|
166
|
+
body.state_id = params.stateId;
|
|
167
|
+
if (params.description !== undefined)
|
|
168
|
+
body.description = params.description;
|
|
169
|
+
if (params.defaults !== undefined)
|
|
170
|
+
body.defaults = params.defaults;
|
|
171
|
+
if (params.autoPublish !== undefined)
|
|
172
|
+
body.auto_publish = params.autoPublish;
|
|
173
|
+
if (params.acknowledgedScanFindings !== undefined)
|
|
174
|
+
body.acknowledged_scan_findings = params.acknowledgedScanFindings;
|
|
175
|
+
const ctx = this.#ctx();
|
|
176
|
+
return ctx.http.json('POST', this.#path('/publish'), { json: body, idempotencyKey: params.idempotencyKey ?? randomId('publish-') }, ctx.authorization);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
20
179
|
export class TemplateBuildsApi {
|
|
21
180
|
#ctx;
|
|
22
181
|
constructor(ctx) {
|
|
@@ -53,23 +212,35 @@ export class TemplateBuildsApi {
|
|
|
53
212
|
return this.#ctx().http.json('GET', `/v1/organizations/${enc(organizationId)}/template-builder-availability`, {}, this.#ctx().authorization);
|
|
54
213
|
}
|
|
55
214
|
/**
|
|
56
|
-
*
|
|
215
|
+
* Waits until the build is settled (each poll asks the server to hold it until the build changes, `Prefer: wait`,
|
|
216
|
+
* falling back to backoff polling on servers that do not wait) and returns it: failed | canceled, or published with its registration
|
|
57
217
|
* `registered` (the version exists; `template_version`) or `failed`. Legacy builds end `succeeded`.
|
|
58
218
|
* Throws TemplateBuildTimeoutError at the deadline (default 30 min); the build continues.
|
|
59
219
|
*/
|
|
60
220
|
async waitForBuild(organizationId, buildId, opts = {}) {
|
|
61
|
-
const { sleep } = this.#ctx();
|
|
221
|
+
const { sleep, http, authorization } = this.#ctx();
|
|
62
222
|
const timeoutMs = opts.timeoutMs ?? 1_800_000;
|
|
63
223
|
const maxInterval = opts.maxPollIntervalMs ?? 10_000;
|
|
64
224
|
let interval = opts.pollIntervalMs ?? 1_000;
|
|
65
225
|
const started = Date.now();
|
|
226
|
+
let lastKey = null;
|
|
66
227
|
for (;;) {
|
|
67
|
-
|
|
228
|
+
if (opts.signal?.aborted)
|
|
229
|
+
throw opts.signal.reason instanceof Error ? opts.signal.reason : new Error('aborted');
|
|
230
|
+
const waitS = opts.serverWait === false ? 0 : (timeoutMs - (Date.now() - started)) / 1000;
|
|
231
|
+
const t0 = Date.now();
|
|
232
|
+
const { body: build, applied } = await pollWithWait(http, `/v1/organizations/${enc(organizationId)}/template-builds/${enc(buildId)}`, authorization, waitS, opts.signal);
|
|
68
233
|
if (buildSettled(build))
|
|
69
234
|
return build;
|
|
70
235
|
const waited = Date.now() - started;
|
|
71
236
|
if (waited >= timeoutMs)
|
|
72
237
|
throw new TemplateBuildTimeoutError(build, waited);
|
|
238
|
+
// Held by the server (or a change): poll again at once; a quick unchanged answer falls through to the backoff.
|
|
239
|
+
const key = `${build.state}/${build.registration.state}`;
|
|
240
|
+
const changed = key !== lastKey;
|
|
241
|
+
lastKey = key;
|
|
242
|
+
if (applied && (changed || Date.now() - t0 >= 1_000))
|
|
243
|
+
continue;
|
|
73
244
|
await sleep(Math.max(10, Math.min(interval, timeoutMs - waited)));
|
|
74
245
|
if (opts.signal?.aborted)
|
|
75
246
|
throw opts.signal.reason instanceof Error ? opts.signal.reason : new Error('aborted');
|
|
@@ -114,4 +285,45 @@ export class TemplatesApi {
|
|
|
114
285
|
throw err;
|
|
115
286
|
}
|
|
116
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
|
|
290
|
+
* details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
|
|
291
|
+
* version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
|
|
292
|
+
*/
|
|
293
|
+
async files(slug, version, params = {}) {
|
|
294
|
+
const ctx = this.#ctx();
|
|
295
|
+
return ctx.http.json('GET', `${templatePath(slug, params.organizationId)}/versions/${enc(String(version))}/files`, { query: { path: params.path, limit: params.limit, cursor: params.cursor, owner: params.owner } }, ctx.authorization);
|
|
296
|
+
}
|
|
297
|
+
/** Every entry of one directory, following next_cursor. */
|
|
298
|
+
async *filesAll(slug, version, params = {}) {
|
|
299
|
+
let cursor;
|
|
300
|
+
do {
|
|
301
|
+
const page = await this.files(slug, version, { limit: 1000, ...params, ...(cursor === undefined ? {} : { cursor }) });
|
|
302
|
+
yield* page.data;
|
|
303
|
+
cursor = page.next_cursor ?? undefined;
|
|
304
|
+
} while (cursor !== undefined);
|
|
305
|
+
}
|
|
306
|
+
/** One entry of a version's file tree (404 path_not_found). */
|
|
307
|
+
fileEntry(slug, version, path, params = {}) {
|
|
308
|
+
const ctx = this.#ctx();
|
|
309
|
+
return ctx.http.json('GET', `${templatePath(slug, params.organizationId)}/versions/${enc(String(version))}/files/entry`, { query: { path, owner: params.owner } }, ctx.authorization);
|
|
310
|
+
}
|
|
311
|
+
/** One page of the diff between two versions (keyset-paginated by path; the first page carries `summary`). */
|
|
312
|
+
diff(slug, params) {
|
|
313
|
+
const ctx = this.#ctx();
|
|
314
|
+
return ctx.http.json('GET', `${templatePath(slug, params.organizationId)}/diff`, { query: { from: String(params.from), to: params.to, path_prefix: params.pathPrefix, change: params.change, limit: params.limit, cursor: params.cursor, owner: params.owner } }, ctx.authorization);
|
|
315
|
+
}
|
|
316
|
+
/** Every diff entry, following next_cursor. */
|
|
317
|
+
async *diffAll(slug, params) {
|
|
318
|
+
let cursor;
|
|
319
|
+
do {
|
|
320
|
+
const page = await this.diff(slug, { limit: 1000, ...params, ...(cursor === undefined ? {} : { cursor }) });
|
|
321
|
+
yield* page.data;
|
|
322
|
+
cursor = page.next_cursor ?? undefined;
|
|
323
|
+
} while (cursor !== undefined);
|
|
324
|
+
}
|
|
325
|
+
/** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
|
|
326
|
+
draft(slug, params = {}) {
|
|
327
|
+
return new TemplateDraftApi(this.#ctx, slug, params.organizationId);
|
|
328
|
+
}
|
|
117
329
|
}
|
package/dist/tokens.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { components } from './generated/app-api.js';
|
|
10
10
|
import type { HttpClient } from './http.js';
|
|
11
|
+
import type { ProgressListener } from './progress.js';
|
|
11
12
|
export type ToolToken = components['schemas']['ToolToken'];
|
|
12
13
|
export type ToolName = ToolToken['tools'][number];
|
|
13
14
|
export interface ToolTokenOptions {
|
|
@@ -28,8 +29,12 @@ export declare class ToolTokenManager {
|
|
|
28
29
|
/** Accepts a token obtained elsewhere (e.g. the one returned by a 200 open). */
|
|
29
30
|
seed(token: ToolToken): void;
|
|
30
31
|
get current(): ToolToken | undefined;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The current token, or a new one when there is none or it expires within the skew. A fetch is a traced `token` call:
|
|
34
|
+
* `onProgress` sees it (phase `request` with the reason `initial`, `expiring` or `invalidated`) and its timing.
|
|
35
|
+
*/
|
|
36
|
+
get(onProgress?: ProgressListener): Promise<ToolToken>;
|
|
37
|
+
/** Forces a new token (single-flight: concurrent callers share one request, and the first caller's trace). */
|
|
38
|
+
refresh(onProgress?: ProgressListener, reason?: string): Promise<ToolToken>;
|
|
34
39
|
invalidate(): void;
|
|
35
40
|
}
|