@shardflux/sdk 0.11.1 → 0.13.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 +142 -0
- package/README.md +158 -6
- package/dist/account.d.ts +1 -1
- package/dist/account.js +1 -1
- package/dist/cell.d.ts +59 -0
- package/dist/cell.js +108 -18
- package/dist/client.d.ts +55 -10
- package/dist/client.js +105 -9
- package/dist/errors.d.ts +54 -6
- package/dist/errors.js +50 -5
- package/dist/feedback.js +1 -1
- package/dist/generated/app-api.d.ts +553 -54
- package/dist/generated/cell-api.d.ts +233 -9
- package/dist/http.d.ts +9 -4
- package/dist/http.js +39 -16
- package/dist/index.d.ts +12 -7
- package/dist/index.js +4 -2
- package/dist/lifecycle.d.ts +24 -1
- package/dist/lifecycle.js +10 -3
- package/dist/progress.d.ts +62 -1
- package/dist/progress.js +61 -0
- package/dist/templates.d.ts +25 -3
- package/dist/tools.d.ts +7 -0
- package/dist/tools.js +275 -7
- package/dist/workspace.d.ts +39 -10
- package/dist/workspace.js +46 -3
- package/package.json +4 -1
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { components as AppComponents } from './generated/app-api.js';
|
|
2
2
|
import type { components as CellComponents } from './generated/cell-api.js';
|
|
3
|
-
import type { LifecycleTiming } from './progress.js';
|
|
3
|
+
import type { Durability, LifecycleTiming } from './progress.js';
|
|
4
4
|
export type AppErrorBody = AppComponents['schemas']['ErrorBody'];
|
|
5
5
|
export type AppErrorCode = AppErrorBody['error']['code'];
|
|
6
6
|
export type CellErrorCode = CellComponents['schemas']['ErrorCode'];
|
|
@@ -11,7 +11,7 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
|
|
|
11
11
|
* added: 409 conflict legacy_disk_layout, not_session, session_lifetime, lifetime_mismatch,
|
|
12
12
|
* not_resettable, template_not_layered, draft_exists, draft_stale, build_in_progress, file_list_unavailable,
|
|
13
13
|
* file_list_indexing (retryable), guest_feature_unavailable; 422 validation_failed confirm_destructive_required,
|
|
14
|
-
* reserved_key_prefix, invalid_defaults,
|
|
14
|
+
* reserved_key_prefix, invalid_defaults, invalid_path, too_many_acknowledged_findings;
|
|
15
15
|
* 403 forbidden template_dev_mode_role; 404 not_found draft_not_found, version_not_found, path_not_found.
|
|
16
16
|
* The template editor (0.7.0) added: 422 validation_failed invalid_recipe, base_not_layered,
|
|
17
17
|
* language_unavailable, language_conflict, invalid_package, too_many_files, platform_owned_path, upload_required,
|
|
@@ -51,8 +51,27 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
|
|
|
51
51
|
* overage_unavailable, spend_cap_required, spend_cap_below_minimum (details.min_minor), spend_cap_above_plan_price
|
|
52
52
|
* (details.max_minor) and spend_cap_below_charges (details.charges_minor); with `ifMatch`, 409 conflict
|
|
53
53
|
* version_mismatch (details.current_version), as egress puts with `ifMatch` answer too.
|
|
54
|
+
* Elastic memory (0.13.0): 422 validation_failed allocation_mode_not_available (details.field
|
|
55
|
+
* `caps.allocation_mode`; the organization does not have elastic memory, nothing is created or changed),
|
|
56
|
+
* not_supported_for_mode (elastic with file_first), requires_elastic and exceeds_memory_mib (details.field
|
|
57
|
+
* `caps.memory_mib_held`).
|
|
58
|
+
* Burst execution (0.13.0): the codes `burst_unavailable` (409; details.reason
|
|
59
|
+
* not_available, layout_unsupported, shared_volumes, host_capacity, fence_not_drained, workspace_fenced, apply_pending,
|
|
60
|
+
* park_failed, workspace_resumed, interrupted; details.replayed on a journaled failure answered again) and
|
|
61
|
+
* `burst_apply_failed` (409; details.reason disk_full, apply_failed, reverted, revert_failed with
|
|
62
|
+
* details.applied_entries and details.pending_entries: retry with the same session id to finish an apply_failed one);
|
|
63
|
+
* 422 validation_failed burst_mode_not_supported (`auto`), burst_not_supported (with stdin; also 409 conflict for
|
|
64
|
+
* signal/cancel of a burst session) and burst_size_exceeds_plan (details.field, details.limit); a burst session nobody
|
|
65
|
+
* followed any more ends with burst.error service_unavailable burst_lost.
|
|
66
|
+
* Immutable paths (0.13.0): 422 validation_failed immutable_path_removed (a recipe drops a path of the template's open
|
|
67
|
+
* version: details.removed, details.open_version; a version keeps every immutable path), immutable_paths_unsupported_base
|
|
68
|
+
* (the base's guest agent lacks the feature, or a Dockerfile build of a template with immutable paths: details.base
|
|
69
|
+
* {name, version}, details.required_feature, details.paths) and invalid_path at details.field `recipe.immutable[<i>]`;
|
|
70
|
+
* 409 conflict read_only_path (a files write under an immutable path, from the cell gateway). The reason
|
|
71
|
+
* `update_policy_not_available` is gone with the update policy. A build that fails on them carries `failure.code`
|
|
72
|
+
* immutable_path_missing (details.path) or immutable_image_too_large.
|
|
54
73
|
*/
|
|
55
|
-
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | '
|
|
74
|
+
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found' | 'revision_mismatch' | 'edit_not_found' | 'edit_ambiguous' | 'edit_not_text' | 'patch_invalid' | 'host_capacity' | 'wake_failed' | 'workspace_fenced' | 'offline_unavailable' | 'offline_budget' | 'offline_changed' | 'host_feature_unavailable' | 'not_supported_for_mode' | 'mode_mismatch' | 'mode_not_available' | 'layout_unsupported' | 'tree_revision_mismatch' | 'outside_tree_root' | 'execution_in_progress' | 'execution_id_reused' | 'operation_id_reused' | 'no_execution_host' | 'lease_expired' | 'host_unreachable' | 'host_restarted' | 'tree_moved' | 'blob_missing' | 'blob_corrupt' | 'exec_failed_to_start' | 'invalid_cwd' | 'allowance_used' | 'overage_paused' | 'spend_cap_reached' | 'overage_unavailable' | 'spend_cap_required' | 'spend_cap_below_minimum' | 'spend_cap_above_plan_price' | 'spend_cap_below_charges' | 'version_mismatch' | 'allocation_mode_not_available' | 'requires_elastic' | 'exceeds_memory_mib' | 'burst_mode_not_supported' | 'burst_not_supported' | 'burst_size_exceeds_plan' | 'not_available' | 'shared_volumes' | 'fence_not_drained' | 'apply_pending' | 'park_failed' | 'workspace_resumed' | 'interrupted' | 'burst_lost' | 'disk_full' | 'apply_failed' | 'reverted' | 'revert_failed' | 'immutable_path_removed' | 'immutable_paths_unsupported_base' | 'read_only_path';
|
|
56
75
|
/** A known reason, or any other string the server sends (reasons are open-ended). */
|
|
57
76
|
export type ErrorReason = KnownErrorReason | (string & {});
|
|
58
77
|
export interface ErrorBodyLike {
|
|
@@ -141,8 +160,12 @@ export declare function apiError(status: number, body: ErrorBodyLike, source: 'a
|
|
|
141
160
|
/** The response was not the documented shape (e.g. a proxy error page). */
|
|
142
161
|
export declare class ShardfluxProtocolError extends Error {
|
|
143
162
|
readonly status: number;
|
|
144
|
-
|
|
163
|
+
/** The responding surface; unknown only for errors constructed by older callers. Never proof of deletion. */
|
|
164
|
+
readonly source: 'api' | 'cell' | 'unknown';
|
|
165
|
+
constructor(message: string, status: number, source?: 'api' | 'cell' | 'unknown');
|
|
145
166
|
}
|
|
167
|
+
/** True only for an explicit API tombstone refusal. A 404, even a structured one, may be routing or scope. */
|
|
168
|
+
export declare function isWorkspaceGone(error: unknown): boolean;
|
|
146
169
|
type Operation = AppComponents['schemas']['Operation'];
|
|
147
170
|
/**
|
|
148
171
|
* Waiting for an operation ran out of time. The operation keeps running server side:
|
|
@@ -163,13 +186,24 @@ export declare class OperationTimeoutError extends Error {
|
|
|
163
186
|
readonly waitedMs: number;
|
|
164
187
|
/** Where the time went: client phases, retries and the operation's own server timing so far. */
|
|
165
188
|
timing: LifecycleTiming | undefined;
|
|
166
|
-
|
|
189
|
+
/**
|
|
190
|
+
* `durable` (0.12.0+): the wait was for the durable copy of a succeeded suspend or fork (`suspend({ durable: true })`,
|
|
191
|
+
* `waitForDurable()`); `lastState` is then `succeeded` and the copy continues server side.
|
|
192
|
+
*/
|
|
193
|
+
readonly durable: boolean;
|
|
194
|
+
constructor(op: Operation, waitedMs: number, durable?: boolean);
|
|
167
195
|
}
|
|
168
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* The operation reached `failed` or `canceled`. A suspend-when-idle that found the workspace active (a tool call after
|
|
198
|
+
* the request, an attached stream) is `canceled` with `errorCode` `workspace_active` (`workspaceActive` true, 0.12.0+):
|
|
199
|
+
* nothing changed and the workspace keeps running. `waitUntilReady()` and `wake()` treat it as running, not as a failure.
|
|
200
|
+
*/
|
|
169
201
|
export declare class OperationFailedError extends Error {
|
|
170
202
|
readonly operation: Operation;
|
|
171
203
|
readonly operationId: string;
|
|
172
204
|
readonly errorCode: string | null;
|
|
205
|
+
/** 0.12.0+: a suspend-when-idle canceled because the workspace was active; it keeps running, nothing changed. */
|
|
206
|
+
readonly workspaceActive: boolean;
|
|
173
207
|
/**
|
|
174
208
|
* `operation.error.retryable` (false when absent): the same request may succeed later. `capacity_unavailable` is
|
|
175
209
|
* retryable: the start passed its deadline and nothing was started; send it again. The SDK never retries a failed
|
|
@@ -180,4 +214,18 @@ export declare class OperationFailedError extends Error {
|
|
|
180
214
|
timing: LifecycleTiming | undefined;
|
|
181
215
|
constructor(op: Operation);
|
|
182
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* `suspend({ durable: true })` or `waitForDurable()` (0.12.0+): the suspend (or fork) succeeded, but its durable copy
|
|
219
|
+
* could not be made (`durability.state` `lost`). `durability` has the reason; see the lifecycle reference for what the
|
|
220
|
+
* next resume restores.
|
|
221
|
+
*/
|
|
222
|
+
export declare class DurabilityLostError extends Error {
|
|
223
|
+
readonly operation: Operation;
|
|
224
|
+
readonly operationId: string;
|
|
225
|
+
readonly workspaceId: string | null;
|
|
226
|
+
readonly durability: Durability;
|
|
227
|
+
/** Where the time went. */
|
|
228
|
+
timing: LifecycleTiming | undefined;
|
|
229
|
+
constructor(op: Operation);
|
|
230
|
+
}
|
|
183
231
|
export {};
|
package/dist/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { capacityDeadlineOf } from "./progress.js";
|
|
1
|
+
import { capacityDeadlineOf, durabilityOf } from "./progress.js";
|
|
2
2
|
export function isErrorBody(v) {
|
|
3
3
|
if (typeof v !== 'object' || v === null || !('error' in v))
|
|
4
4
|
return false;
|
|
@@ -129,12 +129,20 @@ export function apiError(status, body, source, retryAfterSeconds, treeRevision)
|
|
|
129
129
|
/** The response was not the documented shape (e.g. a proxy error page). */
|
|
130
130
|
export class ShardfluxProtocolError extends Error {
|
|
131
131
|
status;
|
|
132
|
-
|
|
132
|
+
/** The responding surface; unknown only for errors constructed by older callers. Never proof of deletion. */
|
|
133
|
+
source;
|
|
134
|
+
constructor(message, status, source = 'unknown') {
|
|
133
135
|
super(message);
|
|
134
136
|
this.name = 'ShardfluxProtocolError';
|
|
135
137
|
this.status = status;
|
|
138
|
+
this.source = source;
|
|
136
139
|
}
|
|
137
140
|
}
|
|
141
|
+
/** True only for an explicit API tombstone refusal. A 404, even a structured one, may be routing or scope. */
|
|
142
|
+
export function isWorkspaceGone(error) {
|
|
143
|
+
return error instanceof ShardfluxApiError && error.source === 'api' &&
|
|
144
|
+
error.code === 'conflict' && error.reason === 'workspace_deleted';
|
|
145
|
+
}
|
|
138
146
|
/**
|
|
139
147
|
* Waiting for an operation ran out of time. The operation keeps running server side:
|
|
140
148
|
* resume with `cloud.workspaces.waitForOperation(err.operationId)` or call open() again
|
|
@@ -154,10 +162,18 @@ export class OperationTimeoutError extends Error {
|
|
|
154
162
|
waitedMs;
|
|
155
163
|
/** Where the time went: client phases, retries and the operation's own server timing so far. */
|
|
156
164
|
timing = undefined;
|
|
157
|
-
|
|
165
|
+
/**
|
|
166
|
+
* `durable` (0.12.0+): the wait was for the durable copy of a succeeded suspend or fork (`suspend({ durable: true })`,
|
|
167
|
+
* `waitForDurable()`); `lastState` is then `succeeded` and the copy continues server side.
|
|
168
|
+
*/
|
|
169
|
+
durable;
|
|
170
|
+
constructor(op, waitedMs, durable = false) {
|
|
158
171
|
const deadlineAt = capacityDeadlineOf(op);
|
|
159
172
|
const after = deadlineAt ? `it stays queued server side until ${deadlineAt} and fails with capacity_unavailable if it has not started by then.` : 'it continues server side.';
|
|
160
|
-
super(
|
|
173
|
+
super(durable
|
|
174
|
+
? `Operation ${op.id} (${op.kind}) succeeded; its durable copy was still ${durabilityOf(op)?.state ?? 'pending'} after ${Math.round(waitedMs)} ms and continues server side.`
|
|
175
|
+
: `Operation ${op.id} (${op.kind}) is still ${op.state}${op.state_reason ? ` (${op.state_reason})` : ''} after ${Math.round(waitedMs)} ms; ${after}`);
|
|
176
|
+
this.durable = durable;
|
|
161
177
|
this.name = 'OperationTimeoutError';
|
|
162
178
|
this.operationId = op.id;
|
|
163
179
|
this.workspaceId = op.workspace_id;
|
|
@@ -167,11 +183,17 @@ export class OperationTimeoutError extends Error {
|
|
|
167
183
|
this.waitedMs = waitedMs;
|
|
168
184
|
}
|
|
169
185
|
}
|
|
170
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* The operation reached `failed` or `canceled`. A suspend-when-idle that found the workspace active (a tool call after
|
|
188
|
+
* the request, an attached stream) is `canceled` with `errorCode` `workspace_active` (`workspaceActive` true, 0.12.0+):
|
|
189
|
+
* nothing changed and the workspace keeps running. `waitUntilReady()` and `wake()` treat it as running, not as a failure.
|
|
190
|
+
*/
|
|
171
191
|
export class OperationFailedError extends Error {
|
|
172
192
|
operation;
|
|
173
193
|
operationId;
|
|
174
194
|
errorCode;
|
|
195
|
+
/** 0.12.0+: a suspend-when-idle canceled because the workspace was active; it keeps running, nothing changed. */
|
|
196
|
+
workspaceActive;
|
|
175
197
|
/**
|
|
176
198
|
* `operation.error.retryable` (false when absent): the same request may succeed later. `capacity_unavailable` is
|
|
177
199
|
* retryable: the start passed its deadline and nothing was started; send it again. The SDK never retries a failed
|
|
@@ -187,6 +209,29 @@ export class OperationFailedError extends Error {
|
|
|
187
209
|
this.operation = op;
|
|
188
210
|
this.operationId = op.id;
|
|
189
211
|
this.errorCode = code;
|
|
212
|
+
this.workspaceActive = code === 'workspace_active' && op.kind === 'suspend';
|
|
190
213
|
this.retryable = op.error?.retryable === true;
|
|
191
214
|
}
|
|
192
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* `suspend({ durable: true })` or `waitForDurable()` (0.12.0+): the suspend (or fork) succeeded, but its durable copy
|
|
218
|
+
* could not be made (`durability.state` `lost`). `durability` has the reason; see the lifecycle reference for what the
|
|
219
|
+
* next resume restores.
|
|
220
|
+
*/
|
|
221
|
+
export class DurabilityLostError extends Error {
|
|
222
|
+
operation;
|
|
223
|
+
operationId;
|
|
224
|
+
workspaceId;
|
|
225
|
+
durability;
|
|
226
|
+
/** Where the time went. */
|
|
227
|
+
timing = undefined;
|
|
228
|
+
constructor(op) {
|
|
229
|
+
const durability = durabilityOf(op) ?? { state: 'lost', checkpointId: null, generationId: null, localCommitAt: null, durableBy: null, durableAt: null, localCommitToDurableMs: null, overdueAt: null, reason: null };
|
|
230
|
+
super(`Operation ${op.id} (${op.kind}): the durable copy of checkpoint ${durability.checkpointId ?? '?'} was not made${durability.reason ? ` (${durability.reason})` : ''}`);
|
|
231
|
+
this.name = 'DurabilityLostError';
|
|
232
|
+
this.operation = op;
|
|
233
|
+
this.operationId = op.id;
|
|
234
|
+
this.workspaceId = op.workspace_id;
|
|
235
|
+
this.durability = durability;
|
|
236
|
+
}
|
|
237
|
+
}
|
package/dist/feedback.js
CHANGED
|
@@ -33,7 +33,7 @@ function feedbackBody(params) {
|
|
|
33
33
|
export async function sendFeedback(ctx, params) {
|
|
34
34
|
const { status, body } = await ctx.http.jsonWithStatus('POST', '/v1/feedback', { json: feedbackBody(params) }, ctx.authorization);
|
|
35
35
|
if (typeof body?.id !== 'string' || typeof body.received_at !== 'string' || typeof body.duplicate !== 'boolean') {
|
|
36
|
-
throw new ShardfluxProtocolError('POST /v1/feedback: the response is not {id, received_at, duplicate}', status);
|
|
36
|
+
throw new ShardfluxProtocolError('POST /v1/feedback: the response is not {id, received_at, duplicate}', status, 'api');
|
|
37
37
|
}
|
|
38
38
|
return { id: body.id, receivedAt: body.received_at, duplicate: body.duplicate };
|
|
39
39
|
}
|