@shardflux/sdk 0.7.0 → 0.9.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 +220 -1
- package/README.md +247 -10
- package/dist/account.d.ts +469 -0
- package/dist/account.js +620 -0
- package/dist/cell.d.ts +197 -8
- package/dist/cell.js +449 -31
- package/dist/client.d.ts +76 -5
- package/dist/client.js +114 -6
- package/dist/errors.d.ts +62 -3
- package/dist/errors.js +65 -1
- package/dist/executions.d.ts +120 -0
- package/dist/executions.js +99 -0
- package/dist/feedback.d.ts +67 -0
- package/dist/feedback.js +39 -0
- package/dist/generated/app-api.d.ts +12323 -8072
- package/dist/generated/cell-api.d.ts +463 -8
- package/dist/http.d.ts +7 -1
- package/dist/http.js +26 -7
- package/dist/index.d.ts +18 -7
- package/dist/index.js +5 -1
- package/dist/lifecycle.d.ts +27 -2
- package/dist/lifecycle.js +5 -0
- package/dist/progress.js +4 -2
- package/dist/templates.js +2 -2
- package/dist/tools.d.ts +72 -22
- package/dist/tools.js +156 -23
- package/dist/version-check.d.ts +101 -0
- package/dist/version-check.js +191 -0
- package/dist/workspace.d.ts +77 -5
- package/dist/workspace.js +164 -12
- package/package.json +2 -1
package/dist/client.d.ts
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { components, operations } from './generated/app-api.js';
|
|
11
11
|
import { ShardfluxApiError } from './errors.js';
|
|
12
|
+
import type { WorkspaceMode } from './errors.js';
|
|
12
13
|
import { HttpClient } from './http.js';
|
|
13
|
-
import type {
|
|
14
|
+
import type { RequestOptions } from './http.js';
|
|
15
|
+
import type { ToolName, ToolToken } from './tokens.js';
|
|
14
16
|
import { Workspace } from './workspace.js';
|
|
15
17
|
import { AuditApi } from './audit.js';
|
|
16
18
|
import { EgressPolicyApi } from './egress.js';
|
|
@@ -19,9 +21,11 @@ import { TemplatesApi } from './templates.js';
|
|
|
19
21
|
import type { SaveAsTemplateParams, SaveAsTemplateResponse } from './templates.js';
|
|
20
22
|
import { UsageApi } from './usage.js';
|
|
21
23
|
import { VolumesApi } from './volumes.js';
|
|
22
|
-
import type {
|
|
24
|
+
import type { VersionCheckOption } from './version-check.js';
|
|
25
|
+
import type { FinishedOperation, InternalLifecycleOptions, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
|
|
23
26
|
import type { ProgressListener } from './progress.js';
|
|
24
27
|
import { CaptureRegistry } from './capture.js';
|
|
28
|
+
import type { FeedbackReceipt, SendFeedbackParams } from './feedback.js';
|
|
25
29
|
export type WorkspaceView = components['schemas']['Workspace'];
|
|
26
30
|
export type Operation = components['schemas']['Operation'];
|
|
27
31
|
/** persistent (kept until deleted) or session (discarded when the session ends: close(), idle timeout; contracts §19.11). */
|
|
@@ -52,6 +56,32 @@ type Ok<Op> = Op extends {
|
|
|
52
56
|
[K in keyof R]: K extends 200 | 201 | 202 ? JsonOf<R[K]> : never;
|
|
53
57
|
}[keyof R] : never;
|
|
54
58
|
export type OpenResponse = Ok<operations['postV1WorkspacesOpen']>;
|
|
59
|
+
/** POST /v1/workspaces/{id}/resume: the held 200 (contracts §22.6) or the lifecycle 202. */
|
|
60
|
+
export type ResumeResponse = Ok<operations['postV1WorkspacesWorkspaceIdResume']>;
|
|
61
|
+
/** What a resume request answered (0.9.0; `WorkspacesApi.requestResume`). */
|
|
62
|
+
export interface ResumeAnswer {
|
|
63
|
+
/**
|
|
64
|
+
* True for a held resume that ended with the workspace running (200 with `Preference-Applied`): `workspace` and
|
|
65
|
+
* `toolToken` are current. False for any other answer: wait for `operation` (an older server answers 202 at once).
|
|
66
|
+
*/
|
|
67
|
+
ready: boolean;
|
|
68
|
+
/** The resume, or the open/resume it joined; null when a held resume found the workspace already running. */
|
|
69
|
+
operation: Operation | null;
|
|
70
|
+
workspace: WorkspaceView;
|
|
71
|
+
/** The tool token of a ready answer (null when the API could not issue one: the first tool call fetches it). */
|
|
72
|
+
toolToken: ToolToken | null;
|
|
73
|
+
requestId: string | null;
|
|
74
|
+
}
|
|
75
|
+
export interface ResumeRequestOptions {
|
|
76
|
+
/** Seconds the server may hold the request (`Prefer: wait`, at most 20); below 1 the request is not held. */
|
|
77
|
+
waitS: number;
|
|
78
|
+
/** The tool token a held resume returns: agent label and tools (default all the key's tools, label `default`). */
|
|
79
|
+
agentLabel?: string | undefined;
|
|
80
|
+
tools?: readonly ToolName[] | undefined;
|
|
81
|
+
idempotencyKey?: string | undefined;
|
|
82
|
+
signal?: AbortSignal | undefined;
|
|
83
|
+
onRetry?: RequestOptions['onRetry'] | undefined;
|
|
84
|
+
}
|
|
55
85
|
export type AgentSession = Ok<operations['getV1WorkspacesWorkspaceIdAgentSessions']>['data'][number];
|
|
56
86
|
export type Entitlements = Ok<operations['getV1OrganizationsOrganizationIdEntitlements']>;
|
|
57
87
|
export type Me = Ok<operations['getV1Me']>;
|
|
@@ -83,6 +113,13 @@ export interface ShardfluxOptions {
|
|
|
83
113
|
* retries and busy waits of tool calls): for logs or telemetry. Per-call `onProgress` listeners get their own events too.
|
|
84
114
|
*/
|
|
85
115
|
onProgress?: ProgressListener;
|
|
116
|
+
/**
|
|
117
|
+
* The automatic version check (0.9.0; contracts §30.4): after the first successful API response of the process, a
|
|
118
|
+
* background GET /v1/client-versions (3 s timeout, errors swallowed) emits a `ShardfluxUpdateWarning` when this
|
|
119
|
+
* package is outdated or unsupported. Default true (`@shardflux/sdk` at SDK_VERSION); tools built on the SDK pass
|
|
120
|
+
* their own `{ package, version }` or `false`. `SHARDFLUX_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1` turn it off.
|
|
121
|
+
*/
|
|
122
|
+
versionCheck?: VersionCheckOption;
|
|
86
123
|
}
|
|
87
124
|
export interface Caps {
|
|
88
125
|
cpu_millis?: number;
|
|
@@ -144,6 +181,16 @@ export interface OpenParams {
|
|
|
144
181
|
* with another value is ShardfluxApiError 409 (details.reason `lifetime_mismatch`).
|
|
145
182
|
*/
|
|
146
183
|
lifetime?: WorkspaceLifetime;
|
|
184
|
+
/**
|
|
185
|
+
* `file_first` (0.9.0; contracts §29): the workspace is a versioned file tree with no VM between executions. It is
|
|
186
|
+
* ready at once (no operation: the open answers with a tool token), never suspended, and runs commands as executions
|
|
187
|
+
* (`workspace.executions.run()`): a fresh VM on the latest tree whose changed files become the next tree revision;
|
|
188
|
+
* nothing else survives an execution. Needs a layered template version (409 `layout_unsupported` otherwise) and is
|
|
189
|
+
* persistent (`lifetime: 'session'` with it is 422 `not_supported_for_mode`). Omitted: `processful` for a new key, the
|
|
190
|
+
* stored mode for an existing one. Immutable: reopening a key with another mode is 409 `mode_mismatch`; 422
|
|
191
|
+
* `mode_not_available` while the deployment does not offer file-first workspaces.
|
|
192
|
+
*/
|
|
193
|
+
mode?: WorkspaceMode;
|
|
147
194
|
/** `false`: return immediately (possibly not ready). Default: wait until ready. */
|
|
148
195
|
wait?: false | WaitOptions;
|
|
149
196
|
/** Defaults to a fresh key per open() call so transport retries replay instead of duplicating. */
|
|
@@ -252,9 +299,24 @@ export declare class WorkspacesApi {
|
|
|
252
299
|
*/
|
|
253
300
|
suspend(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
254
301
|
suspend(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
255
|
-
/**
|
|
256
|
-
|
|
257
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Resumes a suspended workspace. Resolves when the resume is requested; with `wait`, once the workspace runs. With
|
|
304
|
+
* `wait` (0.9.0) the request is held by the server until the workspace runs (contracts §22.6: one request, timing phase
|
|
305
|
+
* `request` with reason `held`); a server that does not hold it answers at once and the operation is polled.
|
|
306
|
+
* `serverWait: false` polls only. `agentLabel`/`tools` choose the tool token the held answer carries (attribution
|
|
307
|
+
* only here; `workspace.resume()` keeps it). A workspace that is already running is ShardfluxApiError 409 `conflict`
|
|
308
|
+
* (details.reason `already_running`), with or without `wait`.
|
|
309
|
+
*/
|
|
310
|
+
resume(workspaceId: string, opts: WaitedResumeOptions): Promise<FinishedOperation>;
|
|
311
|
+
resume(workspaceId: string, opts?: ResumeOptions): Promise<Operation>;
|
|
312
|
+
/**
|
|
313
|
+
* One `POST /v1/workspaces/{id}/resume` (0.9.0), held for up to `waitS` seconds when that is at least 1 (`Prefer:
|
|
314
|
+
* wait`, contracts §22.6), asking for the tool token of `agentLabel`/`tools`. `ready` only for a 200 the server says it
|
|
315
|
+
* held (`Preference-Applied`); every other answer is the operation to wait for, so a server without the held resume
|
|
316
|
+
* works unchanged. Refusals (409 operation_in_progress, already_running without the hold, ...) throw as usual.
|
|
317
|
+
* `Workspace.wake()` and `resume({ wait })` use it; most callers want those.
|
|
318
|
+
*/
|
|
319
|
+
requestResume(workspaceId: string, o: ResumeRequestOptions): Promise<ResumeAnswer>;
|
|
258
320
|
/** Takes a snapshot. Resolves when it is requested; with `wait`, once it is taken. */
|
|
259
321
|
snapshot(workspaceId: string, opts: WaitedLifecycleOptions & {
|
|
260
322
|
label?: string;
|
|
@@ -354,6 +416,15 @@ export declare class Shardflux {
|
|
|
354
416
|
/** The authenticated principal (the API key, its organization and project). */
|
|
355
417
|
me(): Promise<Me>;
|
|
356
418
|
entitlements(organizationId: string): Promise<Entitlements>;
|
|
419
|
+
/**
|
|
420
|
+
* Send feedback straight to the Shardflux founder (0.9.0+; POST /v1/feedback), who reads every message. Use it while
|
|
421
|
+
* you work, the moment something fails unexpectedly, an error or doc is confusing, something is missing or slow, or a
|
|
422
|
+
* workaround was needed; short and specific beats polished, and `context.requestId` / `errorCode` let the founder find
|
|
423
|
+
* the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
424
|
+
* (`duplicate`: the same message from this key within 24 hours; no second email). 429 `rate_limited` (with
|
|
425
|
+
* `retryAfterSeconds`) and 422 `validation_failed` are ShardfluxApiErrors; the call is never retried.
|
|
426
|
+
*/
|
|
427
|
+
sendFeedback(params: SendFeedbackParams): Promise<FeedbackReceipt>;
|
|
357
428
|
/** Raw access to any /v1 endpoint with the SDK's authentication and error handling. */
|
|
358
429
|
request<T>(method: string, path: string, init?: Parameters<HttpClient['json']>[2]): Promise<T>;
|
|
359
430
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError } from "./errors.js";
|
|
1
|
+
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
2
2
|
import { HttpClient, SDK_VERSION, SERVER_WAIT_MAX_S, defaultFetch, defaultSleep, pollWithWait, randomId } from "./http.js";
|
|
3
3
|
import { Workspace } from "./workspace.js";
|
|
4
4
|
import { AuditApi } from "./audit.js";
|
|
@@ -7,9 +7,11 @@ import { SecretsApi } from "./secrets.js";
|
|
|
7
7
|
import { TemplatesApi, saveAsTemplateBody } from "./templates.js";
|
|
8
8
|
import { UsageApi } from "./usage.js";
|
|
9
9
|
import { VolumesApi } from "./volumes.js";
|
|
10
|
-
import {
|
|
10
|
+
import { versionCheckHook } from "./version-check.js";
|
|
11
|
+
import { AFTER_WAIT, HELD_RESUME, TRACE, runLifecycle, waitOptionsOf } from "./lifecycle.js";
|
|
11
12
|
import { Trace, combineListeners, traced } from "./progress.js";
|
|
12
13
|
import { CaptureRegistry } from "./capture.js";
|
|
14
|
+
import { sendFeedback } from "./feedback.js";
|
|
13
15
|
/**
|
|
14
16
|
* The workspace a key names (contracts §19.11): the live row (deleted_at null) when there is one, since at most one live
|
|
15
17
|
* workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
|
|
@@ -29,6 +31,21 @@ export function pickByKey(rows, key) {
|
|
|
29
31
|
return tomb;
|
|
30
32
|
}
|
|
31
33
|
const TERMINAL = new Set(['succeeded', 'failed', 'canceled']);
|
|
34
|
+
/**
|
|
35
|
+
* `resume({ wait })` of a workspace that is already running: a held resume answers 200 without an operation (contracts
|
|
36
|
+
* §22.6); the SDK reports it as the API does without the hold, 409 `conflict` `already_running`.
|
|
37
|
+
*/
|
|
38
|
+
function alreadyRunning(answer) {
|
|
39
|
+
return new ShardfluxApiError(409, {
|
|
40
|
+
error: {
|
|
41
|
+
code: 'conflict',
|
|
42
|
+
message: 'Only a suspended workspace can be resumed; this one is already running.',
|
|
43
|
+
request_id: answer.requestId ?? '',
|
|
44
|
+
retryable: false,
|
|
45
|
+
details: { reason: 'already_running', observed_state: answer.workspace.observed_state, desired_state: answer.workspace.desired_state },
|
|
46
|
+
},
|
|
47
|
+
}, 'api');
|
|
48
|
+
}
|
|
32
49
|
/** Races the injected sleep against the signal (the injected sleep itself may not be abortable). */
|
|
33
50
|
function abortableSleep(sleep, ms, signal) {
|
|
34
51
|
if (signal.aborted)
|
|
@@ -81,6 +98,8 @@ export class WorkspacesApi {
|
|
|
81
98
|
body.inputs = params.inputs;
|
|
82
99
|
if (params.lifetime !== undefined)
|
|
83
100
|
body.lifetime = params.lifetime;
|
|
101
|
+
if (params.mode !== undefined)
|
|
102
|
+
body.mode = params.mode;
|
|
84
103
|
const waitOpts = params.wait === false ? null : (params.wait ?? {});
|
|
85
104
|
const trace = new Trace('open', combineListeners(this.#ctx().onProgress, params.onProgress, waitOpts?.onProgress));
|
|
86
105
|
return traced(trace, async () => {
|
|
@@ -188,7 +207,7 @@ export class WorkspacesApi {
|
|
|
188
207
|
return traced(trace, run);
|
|
189
208
|
}
|
|
190
209
|
async #poll(operationId, opts, trace) {
|
|
191
|
-
const { sleep, http
|
|
210
|
+
const { sleep, http } = this.#ctx();
|
|
192
211
|
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
193
212
|
const maxInterval = opts.maxPollIntervalMs ?? 5_000;
|
|
194
213
|
let interval = opts.pollIntervalMs ?? 250;
|
|
@@ -200,7 +219,8 @@ export class WorkspacesApi {
|
|
|
200
219
|
throw aborted();
|
|
201
220
|
const waitS = opts.serverWait === false ? 0 : (timeoutMs - (Date.now() - started)) / 1000;
|
|
202
221
|
const t0 = Date.now();
|
|
203
|
-
|
|
222
|
+
// Read per poll: a user session (ShardfluxAccount) may rotate its token while the wait runs.
|
|
223
|
+
const { body, applied } = await pollWithWait(http, `/v1/operations/${encodeURIComponent(operationId)}`, this.#ctx().authorization, waitS, opts.signal, trace.onRetry);
|
|
204
224
|
const operation = body.operation;
|
|
205
225
|
trace.observe(operation);
|
|
206
226
|
if (operation.state === 'succeeded')
|
|
@@ -317,7 +337,83 @@ export class WorkspacesApi {
|
|
|
317
337
|
return this.#op('suspend', workspaceId, undefined, opts);
|
|
318
338
|
}
|
|
319
339
|
resume(workspaceId, opts = {}) {
|
|
320
|
-
|
|
340
|
+
const waitOpts = waitOptionsOf(opts);
|
|
341
|
+
if (!waitOpts || waitOpts.serverWait === false)
|
|
342
|
+
return this.#op('resume', workspaceId, undefined, opts);
|
|
343
|
+
const trace = new Trace('resume', combineListeners(this.#ctx().onProgress, opts.onProgress, waitOpts.onProgress), { workspaceId });
|
|
344
|
+
return traced(trace, async () => {
|
|
345
|
+
const timeoutMs = waitOpts.timeoutMs ?? 300_000;
|
|
346
|
+
const started = Date.now();
|
|
347
|
+
// A handle's target takes the answer; by id, `agentLabel`/`tools` only choose the token (attribution).
|
|
348
|
+
const target = opts[HELD_RESUME] ?? { agentLabel: opts.agentLabel, tools: opts.tools, adopt: () => undefined };
|
|
349
|
+
const waitS = Math.min(SERVER_WAIT_MAX_S, Math.floor(timeoutMs / 1000));
|
|
350
|
+
trace.phase('request', waitS >= 1 ? 'held' : null);
|
|
351
|
+
const answer = await this.requestResume(workspaceId, {
|
|
352
|
+
waitS,
|
|
353
|
+
agentLabel: target.agentLabel,
|
|
354
|
+
tools: target.tools,
|
|
355
|
+
idempotencyKey: opts.idempotencyKey,
|
|
356
|
+
signal: waitOpts.signal,
|
|
357
|
+
onRetry: trace.onRetry,
|
|
358
|
+
});
|
|
359
|
+
if (answer.operation)
|
|
360
|
+
trace.observe(answer.operation);
|
|
361
|
+
if (answer.ready) {
|
|
362
|
+
// Held (200): the running workspace and its token, no wait, view or token request.
|
|
363
|
+
target.adopt(answer.workspace, answer.toolToken);
|
|
364
|
+
if (!answer.operation)
|
|
365
|
+
throw alreadyRunning(answer);
|
|
366
|
+
return answer.operation;
|
|
367
|
+
}
|
|
368
|
+
const operation = answer.operation;
|
|
369
|
+
if (operation.state === 'failed' || operation.state === 'canceled')
|
|
370
|
+
throw new OperationFailedError(operation);
|
|
371
|
+
let final = operation;
|
|
372
|
+
if (operation.state !== 'succeeded') {
|
|
373
|
+
const left = waitS >= 1 ? { timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)) } : {};
|
|
374
|
+
final = await this.waitForOperation(operation.id, { ...waitOpts, ...left, [TRACE]: trace });
|
|
375
|
+
}
|
|
376
|
+
await opts[AFTER_WAIT]?.(trace, final);
|
|
377
|
+
return final;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* One `POST /v1/workspaces/{id}/resume` (0.9.0), held for up to `waitS` seconds when that is at least 1 (`Prefer:
|
|
382
|
+
* wait`, contracts §22.6), asking for the tool token of `agentLabel`/`tools`. `ready` only for a 200 the server says it
|
|
383
|
+
* held (`Preference-Applied`); every other answer is the operation to wait for, so a server without the held resume
|
|
384
|
+
* works unchanged. Refusals (409 operation_in_progress, already_running without the hold, ...) throw as usual.
|
|
385
|
+
* `Workspace.wake()` and `resume({ wait })` use it; most callers want those.
|
|
386
|
+
*/
|
|
387
|
+
async requestResume(workspaceId, o) {
|
|
388
|
+
const s = Math.min(SERVER_WAIT_MAX_S, Math.floor(o.waitS));
|
|
389
|
+
const held = s >= 1;
|
|
390
|
+
const body = {};
|
|
391
|
+
if (held && o.agentLabel !== undefined)
|
|
392
|
+
body.agent_label = o.agentLabel;
|
|
393
|
+
if (held && o.tools !== undefined)
|
|
394
|
+
body.tools = [...o.tools];
|
|
395
|
+
const init = {
|
|
396
|
+
idempotencyKey: o.idempotencyKey ?? randomId('op-'),
|
|
397
|
+
...(Object.keys(body).length > 0 ? { json: body } : {}),
|
|
398
|
+
...(o.signal ? { signal: o.signal } : {}),
|
|
399
|
+
...(o.onRetry ? { onRetry: o.onRetry } : {}),
|
|
400
|
+
};
|
|
401
|
+
if (held) {
|
|
402
|
+
init.headers = { prefer: `wait=${s}` };
|
|
403
|
+
// The held response may take the whole wait: the request timeout must outlast it.
|
|
404
|
+
init.timeoutMs = Math.max(this.#http.opts.timeoutMs, s * 1000 + 10_000);
|
|
405
|
+
}
|
|
406
|
+
const res = await this.#http.jsonWithHeaders('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/resume`, init, this.#auth);
|
|
407
|
+
const b = res.body;
|
|
408
|
+
if (!b || typeof b !== 'object' || !b.workspace)
|
|
409
|
+
throw new ShardfluxProtocolError('resume: response has no workspace', res.status);
|
|
410
|
+
const pa = res.headers.get('preference-applied');
|
|
411
|
+
const ready = held && res.status === 200 && pa !== null && /\bwait\s*=/i.test(pa);
|
|
412
|
+
if (!ready && !b.operation)
|
|
413
|
+
throw new ShardfluxProtocolError('resume: response has no operation', res.status);
|
|
414
|
+
const toolToken = ready ? (b.tool_token ?? null) : null;
|
|
415
|
+
this.#noteToken(toolToken);
|
|
416
|
+
return { ready, operation: b.operation ?? null, workspace: b.workspace, toolToken, requestId: res.headers.get('x-request-id') };
|
|
321
417
|
}
|
|
322
418
|
snapshot(workspaceId, opts = {}) {
|
|
323
419
|
return this.#op('snapshot', workspaceId, opts.label === undefined ? {} : { label: opts.label }, opts);
|
|
@@ -439,8 +535,9 @@ export class Shardflux {
|
|
|
439
535
|
this.egress = new EgressPolicyApi(() => this.#ctx);
|
|
440
536
|
this.audit = new AuditApi(() => this.#ctx);
|
|
441
537
|
this.volumes = new VolumesApi(() => this.#ctx);
|
|
538
|
+
const baseUrl = opts.baseUrl ?? 'https://api.shardflux.dev';
|
|
442
539
|
this.#ctx = {
|
|
443
|
-
http: new HttpClient({ baseUrl
|
|
540
|
+
http: new HttpClient({ baseUrl, fetch: f, userAgent, timeoutMs: opts.timeoutMs ?? 30_000, maxRetries: opts.maxRetries ?? 2, source: 'api', sleep, onSuccess: versionCheckHook(opts.versionCheck, baseUrl, f, userAgent) }),
|
|
444
541
|
authorization: `Bearer ${opts.apiKey}`,
|
|
445
542
|
fetch: f,
|
|
446
543
|
userAgent,
|
|
@@ -457,6 +554,17 @@ export class Shardflux {
|
|
|
457
554
|
entitlements(organizationId) {
|
|
458
555
|
return this.#ctx.http.json('GET', `/v1/organizations/${encodeURIComponent(organizationId)}/entitlements`, {}, this.#ctx.authorization);
|
|
459
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Send feedback straight to the Shardflux founder (0.9.0+; POST /v1/feedback), who reads every message. Use it while
|
|
559
|
+
* you work, the moment something fails unexpectedly, an error or doc is confusing, something is missing or slow, or a
|
|
560
|
+
* workaround was needed; short and specific beats polished, and `context.requestId` / `errorCode` let the founder find
|
|
561
|
+
* the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
562
|
+
* (`duplicate`: the same message from this key within 24 hours; no second email). 429 `rate_limited` (with
|
|
563
|
+
* `retryAfterSeconds`) and 422 `validation_failed` are ShardfluxApiErrors; the call is never retried.
|
|
564
|
+
*/
|
|
565
|
+
sendFeedback(params) {
|
|
566
|
+
return sendFeedback(this.#ctx, params);
|
|
567
|
+
}
|
|
460
568
|
/** Raw access to any /v1 endpoint with the SDK's authentication and error handling. */
|
|
461
569
|
request(method, path, init = {}) {
|
|
462
570
|
return this.#ctx.http.json(method, path, init, this.#ctx.authorization);
|
package/dist/errors.d.ts
CHANGED
|
@@ -19,9 +19,27 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
|
|
|
19
19
|
* services_unsupported, input_required, input_unknown, input_invalid, egress_widening, reserved_session_id,
|
|
20
20
|
* env_collision, reserved_template_slug; 409 conflict package_index_unavailable; 404 package_not_found; the operation
|
|
21
21
|
* error `startup_failed` (details.reason startup_failed, service_not_ready, secrets_unavailable, secret_not_available,
|
|
22
|
-
* guest_feature_unavailable).
|
|
22
|
+
* guest_feature_unavailable). File tools and parking (contracts §25-§28, 0.9.0) added: 409 conflict revision_mismatch
|
|
23
|
+
* (details.current_revision); 422 validation_failed edit_not_found, edit_ambiguous (details.index), edit_not_text,
|
|
24
|
+
* patch_invalid; 503 service_unavailable host_capacity and wake_failed (both retryable, with Retry-After); 409
|
|
25
|
+
* workspace_busy workspace_fenced (a lifecycle operation holds the workspace; waited out like any workspace_busy).
|
|
26
|
+
* Reads of a sleeping workspace (§26.4/§26.5): 409 workspace_not_running offline_unavailable and offline_budget
|
|
27
|
+
* (retryable; the disk could not be read offline: the workspace is woken and the call retried like any
|
|
28
|
+
* workspace_not_running), 503 offline_changed (retryable; the retry is served by the guest).
|
|
29
|
+
* Hosts without the features (contracts §26.7, 0.9.0): 409 conflict host_feature_unavailable (not retryable;
|
|
30
|
+
* details.feature `file_search` or `file_patch`): the workspace runs on a host agent that predates the call, until it
|
|
31
|
+
* runs on an upgraded host (minutes to hours); read and write the file, or run a search command, instead.
|
|
32
|
+
* File-first workspaces (contracts §29.7, §29.8; 0.9.0) added: 409 conflict not_supported_for_mode (details.mode,
|
|
33
|
+
* details.operation; NotSupportedForModeError), mode_mismatch (reopening a key with another mode), layout_unsupported (a
|
|
34
|
+
* legacy template), tree_revision_mismatch (details.current_tree_revision; TreeRevisionMismatchError),
|
|
35
|
+
* execution_id_reused, operation_id_reused (an Idempotency-Key reused for another request); 422 validation_failed
|
|
36
|
+
* mode_not_available (the deployment does not offer file-first workspaces), outside_tree_root (also 404 on reads); 409
|
|
37
|
+
* workspace_busy execution_in_progress (details.execution_id; waited out like any workspace_busy); 503
|
|
38
|
+
* service_unavailable no_execution_host (retryable, Retry-After). An execution that ended `failed` or `lost` names its
|
|
39
|
+
* cause in `error.details.reason`: lease_expired, host_unreachable, host_restarted, tree_moved, blob_missing,
|
|
40
|
+
* blob_corrupt, exec_failed_to_start, among others.
|
|
23
41
|
*/
|
|
24
|
-
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' | 'update_policy_not_available' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found';
|
|
42
|
+
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' | 'update_policy_not_available' | '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';
|
|
25
43
|
/** A known reason, or any other string the server sends (reasons are open-ended). */
|
|
26
44
|
export type ErrorReason = KnownErrorReason | (string & {});
|
|
27
45
|
export interface ErrorBodyLike {
|
|
@@ -49,8 +67,49 @@ export declare class ShardfluxApiError extends Error {
|
|
|
49
67
|
readonly reason: ErrorReason | undefined;
|
|
50
68
|
/** Where the time went when a traced call (open, a waited lifecycle call, a wake) failed with this error. */
|
|
51
69
|
timing: LifecycleTiming | undefined;
|
|
52
|
-
|
|
70
|
+
/**
|
|
71
|
+
* `X-Tree-Revision` of the refusal (file-first workspaces, contracts §29.8): the tree revision the refused call saw.
|
|
72
|
+
* Undefined when the response carried none (processful workspaces, the application API).
|
|
73
|
+
*/
|
|
74
|
+
readonly treeRevision: number | undefined;
|
|
75
|
+
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number);
|
|
76
|
+
}
|
|
77
|
+
/** processful (the default: one VM keeps processes, memory and files) or file_first (contracts §29). */
|
|
78
|
+
export type WorkspaceMode = AppComponents['schemas']['WorkspaceMode'];
|
|
79
|
+
/**
|
|
80
|
+
* The call does not exist for the workspace's mode (contracts §29.7, §29.8): 409 `conflict` with details.reason
|
|
81
|
+
* `not_supported_for_mode`, `details.mode` (the workspace's mode) and `details.operation`. File-first workspaces have no
|
|
82
|
+
* VM between executions, so exec sessions, PTY, processes, version control, browser, changes, suspend, resume,
|
|
83
|
+
* snapshot, fork, reset, save-as-template, volumes and idle policies are refused; processful workspaces have no
|
|
84
|
+
* executions and no tree revisions. When the handle knows the workspace's mode the SDK raises it before any request
|
|
85
|
+
* (`local` true); otherwise it is the server's refusal. Either way `err.reason === 'not_supported_for_mode'`.
|
|
86
|
+
*/
|
|
87
|
+
export declare class NotSupportedForModeError extends ShardfluxApiError {
|
|
88
|
+
/** The workspace's mode (null when the server did not say). */
|
|
89
|
+
readonly mode: WorkspaceMode | null;
|
|
90
|
+
/** The refused call, e.g. `pty.open`, `suspend`, `executions.run` (null when the server did not say). */
|
|
91
|
+
readonly operation: string | null;
|
|
92
|
+
/** True when the SDK refused the call itself, without a request. */
|
|
93
|
+
readonly local: boolean;
|
|
94
|
+
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number, local?: boolean);
|
|
95
|
+
/** The SDK's own refusal of `operation` for a workspace whose mode it knows (no request was made). */
|
|
96
|
+
static local(mode: WorkspaceMode, operation: string, source: 'api' | 'cell', message: string): NotSupportedForModeError;
|
|
53
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A file-first call with `ifTreeRevision` (`If-Match`) found the tree at another revision (contracts §29.8): 409
|
|
100
|
+
* `conflict`, details.reason `tree_revision_mismatch`. Nothing changed. `currentTreeRevision` is the revision the tree
|
|
101
|
+
* is at: read what changed, then retry against it.
|
|
102
|
+
*/
|
|
103
|
+
export declare class TreeRevisionMismatchError extends ShardfluxApiError {
|
|
104
|
+
readonly currentTreeRevision: number | null;
|
|
105
|
+
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the error for an error body, typed by its reason: NotSupportedForModeError for `not_supported_for_mode`,
|
|
109
|
+
* TreeRevisionMismatchError for `tree_revision_mismatch`, else ShardfluxApiError. Every SDK request builds its errors
|
|
110
|
+
* here, so `instanceof` works whichever call was refused.
|
|
111
|
+
*/
|
|
112
|
+
export declare function apiError(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number): ShardfluxApiError;
|
|
54
113
|
/** The response was not the documented shape (e.g. a proxy error page). */
|
|
55
114
|
export declare class ShardfluxProtocolError extends Error {
|
|
56
115
|
readonly status: number;
|
package/dist/errors.js
CHANGED
|
@@ -19,7 +19,12 @@ export class ShardfluxApiError extends Error {
|
|
|
19
19
|
reason;
|
|
20
20
|
/** Where the time went when a traced call (open, a waited lifecycle call, a wake) failed with this error. */
|
|
21
21
|
timing = undefined;
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* `X-Tree-Revision` of the refusal (file-first workspaces, contracts §29.8): the tree revision the refused call saw.
|
|
24
|
+
* Undefined when the response carried none (processful workspaces, the application API).
|
|
25
|
+
*/
|
|
26
|
+
treeRevision;
|
|
27
|
+
constructor(status, body, source, retryAfterSeconds, treeRevision) {
|
|
23
28
|
super(body.error.message);
|
|
24
29
|
this.name = 'ShardfluxApiError';
|
|
25
30
|
this.status = status;
|
|
@@ -30,10 +35,69 @@ export class ShardfluxApiError extends Error {
|
|
|
30
35
|
this.operationId = body.error.operation_id;
|
|
31
36
|
this.source = source;
|
|
32
37
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
38
|
+
this.treeRevision = treeRevision;
|
|
33
39
|
const reason = body.error.details?.reason;
|
|
34
40
|
this.reason = typeof reason === 'string' ? reason : undefined;
|
|
35
41
|
}
|
|
36
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The call does not exist for the workspace's mode (contracts §29.7, §29.8): 409 `conflict` with details.reason
|
|
45
|
+
* `not_supported_for_mode`, `details.mode` (the workspace's mode) and `details.operation`. File-first workspaces have no
|
|
46
|
+
* VM between executions, so exec sessions, PTY, processes, version control, browser, changes, suspend, resume,
|
|
47
|
+
* snapshot, fork, reset, save-as-template, volumes and idle policies are refused; processful workspaces have no
|
|
48
|
+
* executions and no tree revisions. When the handle knows the workspace's mode the SDK raises it before any request
|
|
49
|
+
* (`local` true); otherwise it is the server's refusal. Either way `err.reason === 'not_supported_for_mode'`.
|
|
50
|
+
*/
|
|
51
|
+
export class NotSupportedForModeError extends ShardfluxApiError {
|
|
52
|
+
/** The workspace's mode (null when the server did not say). */
|
|
53
|
+
mode;
|
|
54
|
+
/** The refused call, e.g. `pty.open`, `suspend`, `executions.run` (null when the server did not say). */
|
|
55
|
+
operation;
|
|
56
|
+
/** True when the SDK refused the call itself, without a request. */
|
|
57
|
+
local;
|
|
58
|
+
constructor(status, body, source, retryAfterSeconds, treeRevision, local = false) {
|
|
59
|
+
super(status, body, source, retryAfterSeconds, treeRevision);
|
|
60
|
+
this.name = 'NotSupportedForModeError';
|
|
61
|
+
const mode = body.error.details?.mode;
|
|
62
|
+
const operation = body.error.details?.operation;
|
|
63
|
+
this.mode = mode === 'processful' || mode === 'file_first' ? mode : null;
|
|
64
|
+
this.operation = typeof operation === 'string' && operation !== '' ? operation : null;
|
|
65
|
+
this.local = local;
|
|
66
|
+
}
|
|
67
|
+
/** The SDK's own refusal of `operation` for a workspace whose mode it knows (no request was made). */
|
|
68
|
+
static local(mode, operation, source, message) {
|
|
69
|
+
return new NotSupportedForModeError(409, { error: { code: 'conflict', message, request_id: '', retryable: false, details: { reason: 'not_supported_for_mode', mode, operation } } }, source, undefined, undefined, true);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A file-first call with `ifTreeRevision` (`If-Match`) found the tree at another revision (contracts §29.8): 409
|
|
74
|
+
* `conflict`, details.reason `tree_revision_mismatch`. Nothing changed. `currentTreeRevision` is the revision the tree
|
|
75
|
+
* is at: read what changed, then retry against it.
|
|
76
|
+
*/
|
|
77
|
+
export class TreeRevisionMismatchError extends ShardfluxApiError {
|
|
78
|
+
currentTreeRevision;
|
|
79
|
+
constructor(status, body, source, retryAfterSeconds, treeRevision) {
|
|
80
|
+
super(status, body, source, retryAfterSeconds, treeRevision);
|
|
81
|
+
this.name = 'TreeRevisionMismatchError';
|
|
82
|
+
const cur = body.error.details?.current_tree_revision;
|
|
83
|
+
this.currentTreeRevision = typeof cur === 'number' && Number.isSafeInteger(cur) ? cur : (treeRevision ?? null);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Builds the error for an error body, typed by its reason: NotSupportedForModeError for `not_supported_for_mode`,
|
|
88
|
+
* TreeRevisionMismatchError for `tree_revision_mismatch`, else ShardfluxApiError. Every SDK request builds its errors
|
|
89
|
+
* here, so `instanceof` works whichever call was refused.
|
|
90
|
+
*/
|
|
91
|
+
export function apiError(status, body, source, retryAfterSeconds, treeRevision) {
|
|
92
|
+
switch (body.error.details?.reason) {
|
|
93
|
+
case 'not_supported_for_mode':
|
|
94
|
+
return new NotSupportedForModeError(status, body, source, retryAfterSeconds, treeRevision);
|
|
95
|
+
case 'tree_revision_mismatch':
|
|
96
|
+
return new TreeRevisionMismatchError(status, body, source, retryAfterSeconds, treeRevision);
|
|
97
|
+
default:
|
|
98
|
+
return new ShardfluxApiError(status, body, source, retryAfterSeconds, treeRevision);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
37
101
|
/** The response was not the documented shape (e.g. a proxy error page). */
|
|
38
102
|
export class ShardfluxProtocolError extends Error {
|
|
39
103
|
status;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executions of file-first workspaces (contracts §29.1, §29.8): each `POST /exec` runs one command in a fresh VM on the
|
|
3
|
+
* workspace's latest tree revision and answers when it ended; the files it changed under /home/user become the next
|
|
4
|
+
* revision. The execution id is the idempotency key: a request retried with the same id returns the recorded result
|
|
5
|
+
* (or waits for the running execution) and never runs the command a second time.
|
|
6
|
+
*/
|
|
7
|
+
import type { components } from './generated/cell-api.js';
|
|
8
|
+
type S = components['schemas'];
|
|
9
|
+
/** The cell's `ExecutionResult` as sent on the wire (stdout/stderr base64). */
|
|
10
|
+
export type ExecutionResultBody = S['ExecutionResult'];
|
|
11
|
+
/** One entry the execution added, modified or deleted under /home/user (`type` is the new entry's, or the removed one's). */
|
|
12
|
+
export type ExecutionChange = S['ExecutionChange'];
|
|
13
|
+
/** queued / running (in progress), succeeded (the command ran to its end, any exit code), failed or lost (nothing published). */
|
|
14
|
+
export type ExecutionState = S['ExecutionState'];
|
|
15
|
+
/** The `error` of a failed or lost execution: `details.reason` names the cause (e.g. `lease_expired`, `exec_failed_to_start`). */
|
|
16
|
+
export interface ExecutionError {
|
|
17
|
+
code: string;
|
|
18
|
+
message: string;
|
|
19
|
+
/** A new execution id may succeed (the SDK never retries with a new id itself). */
|
|
20
|
+
retryable: boolean;
|
|
21
|
+
details?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/** `^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$` (cell-api.yaml ExecutionIdValue). */
|
|
24
|
+
export declare const EXECUTION_ID: RegExp;
|
|
25
|
+
/** A fresh execution id: `ex-` plus a random UUID (39 characters, valid per EXECUTION_ID). */
|
|
26
|
+
export declare function newExecutionId(): string;
|
|
27
|
+
export interface ExecutionRunOptions {
|
|
28
|
+
/**
|
|
29
|
+
* The execution's idempotency key (8-128 characters of A-Z a-z 0-9 . _ : -, starting with a letter or digit). Default:
|
|
30
|
+
* a fresh `ex-<uuid>`. Pass your own to make the call safe to repeat across processes: the same id with the same request
|
|
31
|
+
* returns the recorded result (`replayed`), with another request 409 `execution_id_reused`.
|
|
32
|
+
*/
|
|
33
|
+
executionId?: string;
|
|
34
|
+
/** Working directory (absolute, under /home/user). */
|
|
35
|
+
cwd?: string;
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
user?: string;
|
|
38
|
+
/** Written to stdin, which is then closed (at most 1 MiB). */
|
|
39
|
+
stdin?: string | Uint8Array;
|
|
40
|
+
/** Kill the command after this long (`timedOut`); 0 or omitted: no limit. */
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
killGraceMs?: number;
|
|
43
|
+
/** Names of customer secrets injected as environment variables of this command only (as for `exec.run`). */
|
|
44
|
+
secretRefs?: string[];
|
|
45
|
+
/** stdout and stderr are each captured up to this many bytes (1-16 MiB; cell default 1 MiB), `stdoutTruncated` beyond. */
|
|
46
|
+
outputLimitBytes?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Retries, with the same execution id, of network failures and retryable 429/5xx answers (e.g. 503
|
|
49
|
+
* `no_execution_host`: no host has room; `Retry-After` is honoured up to 30 s). Default 5. Refusals such as
|
|
50
|
+
* `execution_id_reused` or a validation error are not retried; `workspace_busy` (another execution holds the
|
|
51
|
+
* workspace) is waited out within the client's `transitionTimeoutMs` like any tool call.
|
|
52
|
+
*/
|
|
53
|
+
maxRetries?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Longest single wait for the answer (default 300 000 ms). When it elapses the request is sent again with the same id,
|
|
56
|
+
* which joins the running execution; these re-attachments are not failures and never run the command again.
|
|
57
|
+
*/
|
|
58
|
+
attemptTimeoutMs?: number;
|
|
59
|
+
/** Stops waiting (the execution continues server side; `executions.get(id)` returns its result later). */
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
export interface ExecutionGetOptions {
|
|
63
|
+
/**
|
|
64
|
+
* While the execution is queued or running (202), poll until it ended or this long passed (backoff 250 ms doubling to
|
|
65
|
+
* 2 s), then return the last view. Default 0: return at once (a pending result has `pending` true).
|
|
66
|
+
*/
|
|
67
|
+
waitMs?: number;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}
|
|
70
|
+
/** The outcome of an execution: decoded output, exit status, the revision it produced and what it changed. */
|
|
71
|
+
export declare class ExecutionResult {
|
|
72
|
+
readonly executionId: string;
|
|
73
|
+
readonly state: ExecutionState;
|
|
74
|
+
/** The tree revision the command ran on. */
|
|
75
|
+
readonly baseRevision: number;
|
|
76
|
+
/**
|
|
77
|
+
* The tree revision after the execution: `baseRevision + 1` when it changed files, else `baseRevision`; null unless
|
|
78
|
+
* `state` is `succeeded` (a failed or lost execution publishes nothing).
|
|
79
|
+
*/
|
|
80
|
+
readonly treeRevision: number | null;
|
|
81
|
+
/** The command's exit status (-1 when a signal ended it); null unless succeeded. */
|
|
82
|
+
readonly exitCode: number | null;
|
|
83
|
+
readonly termSignal: number | null;
|
|
84
|
+
/** The command ran past `timeoutMs` and was killed. */
|
|
85
|
+
readonly timedOut: boolean;
|
|
86
|
+
/** Standard output, at most `outputLimitBytes`. */
|
|
87
|
+
readonly stdout: Uint8Array;
|
|
88
|
+
readonly stderr: Uint8Array;
|
|
89
|
+
readonly stdoutTruncated: boolean;
|
|
90
|
+
readonly stderrTruncated: boolean;
|
|
91
|
+
/** What the execution changed under /home/user, sorted by path (at most 10 000; see `changedTruncated`). */
|
|
92
|
+
readonly changed: ExecutionChange[];
|
|
93
|
+
readonly changedTruncated: boolean;
|
|
94
|
+
/** Milliseconds and counts: queue_ms, run_ms, publish_ms, total_ms and the host's (boot_ms, exec_ms, vm_seconds, ...). */
|
|
95
|
+
readonly timings: Record<string, number>;
|
|
96
|
+
/** failed / lost: why (`details.reason`); null otherwise. */
|
|
97
|
+
readonly error: ExecutionError | null;
|
|
98
|
+
readonly createdAt: string;
|
|
99
|
+
readonly finishedAt: string | null;
|
|
100
|
+
/**
|
|
101
|
+
* The call that returned this result did not start the execution: `executions.run()` answered 200 (the execution
|
|
102
|
+
* already existed: its recorded result, or the running one waited for), or it came from `executions.get()`. False
|
|
103
|
+
* only for the run() call whose request started it (201).
|
|
104
|
+
*/
|
|
105
|
+
readonly replayed: boolean;
|
|
106
|
+
/** The body as the cell sent it. */
|
|
107
|
+
readonly raw: ExecutionResultBody;
|
|
108
|
+
constructor(body: ExecutionResultBody, replayed: boolean);
|
|
109
|
+
/** Still queued or running (only `executions.get()` returns such a result). */
|
|
110
|
+
get pending(): boolean;
|
|
111
|
+
/** The command ran to its end with exit code 0. */
|
|
112
|
+
get ok(): boolean;
|
|
113
|
+
/** A stream as text (UTF-8; invalid sequences become U+FFFD). */
|
|
114
|
+
text(stream?: 'stdout' | 'stderr'): string;
|
|
115
|
+
get stdoutText(): string;
|
|
116
|
+
get stderrText(): string;
|
|
117
|
+
/** `error.details.reason` of a failed or lost execution (null otherwise). */
|
|
118
|
+
get errorReason(): string | null;
|
|
119
|
+
}
|
|
120
|
+
export {};
|