@shardflux/sdk 0.11.1 → 0.12.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 +32 -0
- package/README.md +66 -4
- package/dist/account.d.ts +1 -1
- package/dist/account.js +1 -1
- package/dist/cell.d.ts +15 -0
- package/dist/cell.js +37 -8
- package/dist/client.d.ts +26 -5
- package/dist/client.js +72 -4
- package/dist/errors.d.ts +33 -4
- package/dist/errors.js +50 -5
- package/dist/feedback.js +1 -1
- package/dist/generated/app-api.d.ts +227 -9
- package/dist/generated/cell-api.d.ts +66 -0
- package/dist/http.d.ts +4 -4
- package/dist/http.js +28 -14
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/lifecycle.d.ts +17 -0
- package/dist/lifecycle.js +8 -1
- package/dist/progress.d.ts +62 -1
- package/dist/progress.js +61 -0
- package/dist/workspace.d.ts +22 -8
- package/dist/workspace.js +25 -3
- package/package.json +4 -1
package/dist/http.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RetryRecord } from './progress.js';
|
|
2
|
-
export declare const SDK_VERSION = "0.
|
|
2
|
+
export declare const SDK_VERSION = "0.12.0";
|
|
3
3
|
export interface RequestOptions {
|
|
4
4
|
query?: Record<string, string | number | boolean | undefined | null>;
|
|
5
5
|
json?: unknown;
|
|
@@ -29,9 +29,9 @@ export interface HttpOptions {
|
|
|
29
29
|
}
|
|
30
30
|
export declare const defaultSleep: (ms: number) => Promise<void>;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Reuses TLS connections. Node 26's bundled undici 8.9 can stall reused connections: use pinned undici with a
|
|
33
|
+
* private HTTP/1.1-only dispatcher. Other runtimes retain native fetch. SHARDFLUX_HTTP_KEEPALIVE=0 forces close
|
|
34
|
+
* for diagnosis; =1 retains the historical opt-in to native pooling. An explicitly supplied fetch is untouched.
|
|
35
35
|
*/
|
|
36
36
|
export declare function defaultFetch(env?: Record<string, string | undefined> | undefined, undiciVersion?: string | undefined): typeof fetch;
|
|
37
37
|
export declare function buildUrl(base: string, path: string, query?: RequestOptions['query']): string;
|
package/dist/http.js
CHANGED
|
@@ -8,23 +8,37 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { ShardfluxApiError, ShardfluxProtocolError, apiError, isErrorBody } from "./errors.js";
|
|
10
10
|
import { describeFailure } from "./progress.js";
|
|
11
|
-
export const SDK_VERSION = '0.
|
|
11
|
+
export const SDK_VERSION = '0.12.0';
|
|
12
12
|
export const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
/** One private HTTP/1.1 pool on Node 26+, shared by SDK clients. No global dispatcher changes. */
|
|
14
|
+
let nodeFetch;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* Reuses TLS connections. Node 26's bundled undici 8.9 can stall reused connections: use pinned undici with a
|
|
17
|
+
* private HTTP/1.1-only dispatcher. Other runtimes retain native fetch. SHARDFLUX_HTTP_KEEPALIVE=0 forces close
|
|
18
|
+
* for diagnosis; =1 retains the historical opt-in to native pooling. An explicitly supplied fetch is untouched.
|
|
17
19
|
*/
|
|
18
20
|
export function defaultFetch(env = globalThis.process?.env, undiciVersion = globalThis.process?.versions?.undici) {
|
|
19
21
|
// Why a fresh connection on Node 26 (its bundled HTTP client, measured): docs/progress/startup-latency.md.
|
|
20
22
|
const base = (input, init) => fetch(input, init);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
if (env?.SHARDFLUX_HTTP_KEEPALIVE === '0')
|
|
24
|
+
return (input, init) => {
|
|
25
|
+
const headers = new Headers(init?.headers);
|
|
26
|
+
headers.set('connection', 'close');
|
|
27
|
+
return base(input, { ...init, headers });
|
|
28
|
+
};
|
|
29
|
+
if (Number((undiciVersion ?? '').split('.')[0]) < 8 || !undiciVersion || env?.SHARDFLUX_HTTP_KEEPALIVE === '1')
|
|
23
30
|
return base;
|
|
24
|
-
return (input, init) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
return async (input, init) => {
|
|
32
|
+
nodeFetch ??= import('undici').then(({ Agent, fetch: pooledFetch }) => {
|
|
33
|
+
const dispatcher = new Agent({ allowH2: false, pipelining: 1 });
|
|
34
|
+
return async (input, init) => {
|
|
35
|
+
const response = await pooledFetch(input, { ...init, dispatcher });
|
|
36
|
+
// Web-standard runtime shape; undici and DOM iterator declarations differ.
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- Required by consumers compiling with lib.dom instead of only Node types.
|
|
38
|
+
return response;
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
return (await nodeFetch)(input, init);
|
|
28
42
|
};
|
|
29
43
|
}
|
|
30
44
|
export function buildUrl(base, path, query) {
|
|
@@ -62,7 +76,7 @@ export async function errorFrom(res, source) {
|
|
|
62
76
|
}
|
|
63
77
|
if (isErrorBody(parsed))
|
|
64
78
|
return apiError(res.status, parsed, source, retryAfterSeconds(res), treeRevisionOf(res.headers) ?? undefined);
|
|
65
|
-
return new ShardfluxProtocolError(`HTTP ${res.status} from ${source === 'api' ? 'the Shardflux API' : 'the cell gateway'} without an error body`, res.status);
|
|
79
|
+
return new ShardfluxProtocolError(`HTTP ${res.status} from ${source === 'api' ? 'the Shardflux API' : 'the cell gateway'} without an error body`, res.status, source);
|
|
66
80
|
}
|
|
67
81
|
export class HttpClient {
|
|
68
82
|
opts;
|
|
@@ -142,7 +156,7 @@ export class HttpClient {
|
|
|
142
156
|
return JSON.parse(text);
|
|
143
157
|
}
|
|
144
158
|
catch {
|
|
145
|
-
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status);
|
|
159
|
+
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status, this.opts.source);
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
/** Like json() but also returns the status and the response headers (bounded waits read Preference-Applied). */
|
|
@@ -153,7 +167,7 @@ export class HttpClient {
|
|
|
153
167
|
return { status: res.status, headers: res.headers, body: (text.length === 0 ? undefined : JSON.parse(text)) };
|
|
154
168
|
}
|
|
155
169
|
catch {
|
|
156
|
-
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status);
|
|
170
|
+
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status, this.opts.source);
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
/** Like json() but also returns the HTTP status (open() distinguishes 200 from 202). */
|
|
@@ -164,7 +178,7 @@ export class HttpClient {
|
|
|
164
178
|
return { status: res.status, body: (text.length === 0 ? undefined : JSON.parse(text)) };
|
|
165
179
|
}
|
|
166
180
|
catch {
|
|
167
|
-
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status);
|
|
181
|
+
throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status, this.opts.source);
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,9 +16,9 @@ export type { components, operations, paths } from './generated/app-api.js';
|
|
|
16
16
|
export type { components as CellComponents, paths as CellPaths } from './generated/cell-api.js';
|
|
17
17
|
export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from './client.js';
|
|
18
18
|
export type { AgentSession, BillingCatalog, BillingSubscription, Caps, CheckoutSession, DiskLayout, Entitlements, FindByKeyOptions, ForkTarget, Invoice, InvoicePage, LifetimeFilter, ListParams, Me, OpenParams, OpenResponse, Operation, Page, PortalSession, PurposeFilter, ResetWorkspaceBody, ResumeAnswer, ResumeRequestOptions, ResumeResponse, ShardfluxOptions, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResponse, SuspendWhenIdleResult, UpdatePolicy, WaitOptions, WorkspaceInputs, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView, } from './client.js';
|
|
19
|
-
export type { FinishedOperation, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
|
|
20
|
-
export { formatTiming } from './progress.js';
|
|
21
|
-
export type { LifecycleAction, LifecyclePhase, LifecycleTiming, ProgressEvent, ProgressListener, RetryRecord, ServerTiming, TimingOutcome, TimingPhase, } from './progress.js';
|
|
19
|
+
export type { FinishedOperation, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
|
|
20
|
+
export { durabilityOf, formatTiming, isDurable, lostSuspendOf } from './progress.js';
|
|
21
|
+
export type { Durability, LostSuspend, LifecycleAction, LifecyclePhase, LifecycleTiming, ProgressEvent, ProgressListener, RetryRecord, ServerTiming, TimingOutcome, TimingPhase, } from './progress.js';
|
|
22
22
|
export { FEEDBACK_CATEGORIES, FEEDBACK_MESSAGE_MAX_LENGTH } from './feedback.js';
|
|
23
23
|
export type { AccountFeedbackParams, FeedbackCategory, FeedbackContext, FeedbackReceipt, SendFeedbackParams } from './feedback.js';
|
|
24
24
|
export { UsageApi } from './usage.js';
|
|
@@ -50,10 +50,15 @@ export type { AnthropicToolDefinition, JsonSchema, OpenAIChatToolDefinition, Ope
|
|
|
50
50
|
export { CaptureError, ToolCallCapture, captureTool } from './capture.js';
|
|
51
51
|
export type { CallLike, CallRef, CaptureCall, CaptureErrorKind, CaptureEvent, CaptureFlushResult, CapturePart, CaptureSelector, CaptureSource, CaptureStats, CaptureStatus, DropReason, ToolCallCaptureOptions, WrapOptions, } from './capture.js';
|
|
52
52
|
export type { AiSdkAdapter, AiSdkToolEndEvent, AnthropicAdapter, ClaudeAdapter, ClaudeCaptureHooks, ClaudeHookCallback, ClaudeHookMatcher, LangChainAdapter, LangChainToolHandler, MastraAdapter, MastraAfterToolCallContext, McpAdapter, OpenAIAgentsAdapter, } from './capture-adapters.js';
|
|
53
|
-
export { ExecStartError, NotSupportedForModeError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TreeRevisionMismatchError } from './errors.js';
|
|
53
|
+
export { DurabilityLostError, ExecStartError, NotSupportedForModeError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TreeRevisionMismatchError } from './errors.js';
|
|
54
54
|
export type { AppErrorCode, CellErrorCode, ErrorCode, ErrorReason, KnownErrorReason, WorkspaceMode } from './errors.js';
|
|
55
55
|
export { SDK_VERSION } from './http.js';
|
|
56
56
|
export { API_KEY_TOOL_PERMISSIONS, AccountApiKeysApi, AccountAuditApi, AccountAuthApi, AccountBillingApi, AccountExportsApi, AccountInvitationsApi, AccountMembersApi, AccountOrganizationsApi, AccountProjectsApi, AccountTemplatesApi, AccountTotpApi, AccountUserApi, CheckoutTimeoutError, OrganizationExportsApi, SESSION_TOKEN_PATTERN, ShardfluxAccount, isSessionToken, parseEmailToken, } from './account.js';
|
|
57
57
|
export type { AccountClientOptions, AccountDeletion, AccountDeletionCanceled, AccountDeletionScheduled, ApiKey, ApiKeyPage, ApiKeyToolPermission, AuditExportParams, AuthSessionInfo, AuthSessionPage, AuthSessionState, AuthUser, BillingInvoicePage, BillingPortalSession, CheckoutStatus, CreateApiKeyParams, CreatedApiKey, DataExport, EmailChangeConfirmResult, EmailChangeResult, Invitation, InvitationPage, LoginResult, Member, MemberPage, MemberRole, MfaChallengeResult, Organization, OrganizationDeletion, OrganizationDeletionResult, OrganizationPage, OrganizationRole, OrganizationWorkspacePage, OrganizationWorkspacesParams, PageParams, PasswordChangeResult, PasswordResetConfirmResult, PasswordResetRequestResult, Project, ProjectPage, RecoveryCodes, RegisterResult, ResendVerificationResult, SessionTokenUpdate, ShardfluxAccountOptions, SpendPolicyUpdate, StepUpResult, TotpConfirmResult, TotpDisableResult, TotpEnrollment, VerifyEmailResult, WaitForCheckoutOptions, } from './account.js';
|
|
58
58
|
export { checkClientVersion, clientVersionStatus, compareVersions, versionCheckDisabledByEnv } from './version-check.js';
|
|
59
59
|
export type { CheckClientVersionOptions, ClientEcosystem, ClientVersionEntry, ClientVersionStatus, ClientVersionStatusKind, ClientVersions, VersionCheckIdentity, VersionCheckOption, } from './version-check.js';
|
|
60
|
+
export { isWorkspaceGone } from './errors.js';
|
|
61
|
+
export { defaultFetch } from './http.js';
|
|
62
|
+
export type { IdlePolicy } from './client.js';
|
|
63
|
+
export type { IdleStatus, KeepaliveResult } from './cell.js';
|
|
64
|
+
export type { ExecInputResult } from './cell.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from "./client.js";
|
|
2
|
-
export { formatTiming } from "./progress.js";
|
|
2
|
+
export { durabilityOf, formatTiming, isDurable, lostSuspendOf } from "./progress.js";
|
|
3
3
|
export { FEEDBACK_CATEGORIES, FEEDBACK_MESSAGE_MAX_LENGTH } from "./feedback.js";
|
|
4
4
|
export { UsageApi } from "./usage.js";
|
|
5
5
|
export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatePackagesApi, TemplateUploadError, TemplateUploadsApi, TemplateVersionTestInstancesApi, TemplateVersionsApi, TemplatesApi, buildSettled, saveAsTemplateBody, } from "./templates.js";
|
|
@@ -15,7 +15,9 @@ export { EXECUTION_ID, ExecutionResult, newExecutionId } from "./executions.js";
|
|
|
15
15
|
export { ToolTokenManager } from "./tokens.js";
|
|
16
16
|
export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from "./tools.js";
|
|
17
17
|
export { CaptureError, ToolCallCapture, captureTool } from "./capture.js";
|
|
18
|
-
export { ExecStartError, NotSupportedForModeError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TreeRevisionMismatchError } from "./errors.js";
|
|
18
|
+
export { DurabilityLostError, ExecStartError, NotSupportedForModeError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TreeRevisionMismatchError } from "./errors.js";
|
|
19
19
|
export { SDK_VERSION } from "./http.js";
|
|
20
20
|
export { API_KEY_TOOL_PERMISSIONS, AccountApiKeysApi, AccountAuditApi, AccountAuthApi, AccountBillingApi, AccountExportsApi, AccountInvitationsApi, AccountMembersApi, AccountOrganizationsApi, AccountProjectsApi, AccountTemplatesApi, AccountTotpApi, AccountUserApi, CheckoutTimeoutError, OrganizationExportsApi, SESSION_TOKEN_PATTERN, ShardfluxAccount, isSessionToken, parseEmailToken, } from "./account.js";
|
|
21
21
|
export { checkClientVersion, clientVersionStatus, compareVersions, versionCheckDisabledByEnv } from "./version-check.js";
|
|
22
|
+
export { isWorkspaceGone } from "./errors.js";
|
|
23
|
+
export { defaultFetch } from "./http.js";
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -32,6 +32,22 @@ export interface LifecycleOptions {
|
|
|
32
32
|
export type WaitedLifecycleOptions = LifecycleOptions & {
|
|
33
33
|
wait: true | WaitOptions;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* `suspend()` options (0.12.0+). A waited suspend resolves as soon as the workspace is sealed on its host, typically in a
|
|
37
|
+
* few hundred ms; the finished operation's `result.durable` turns true when the copy lands in durable storage,
|
|
38
|
+
* typically within a second. `durable: true` resolves only then: it waits for the suspend (so it implies `wait`), then
|
|
39
|
+
* for `result.durable`, within the same `timeoutMs` and `signal`. It throws DurabilityLostError if the copy cannot be
|
|
40
|
+
* made, and OperationTimeoutError (`durable: true`) when the time runs out first; the copy continues server side.
|
|
41
|
+
*/
|
|
42
|
+
export interface SuspendOptions extends LifecycleOptions {
|
|
43
|
+
durable?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Suspend options that wait: `wait` or `durable`. */
|
|
46
|
+
export type WaitedSuspendOptions = SuspendOptions & ({
|
|
47
|
+
wait: true | WaitOptions;
|
|
48
|
+
} | {
|
|
49
|
+
durable: true;
|
|
50
|
+
});
|
|
35
51
|
/**
|
|
36
52
|
* `resume()` options (0.9.0). With `wait`, the server holds the resume until the workspace runs and returns a tool token
|
|
37
53
|
* with it: `agentLabel` and `tools` choose that token (defaults: a workspace handle's own, those given
|
|
@@ -63,6 +79,7 @@ export type InternalWaitOptions = WaitOptions & {
|
|
|
63
79
|
[TRACE]?: Trace;
|
|
64
80
|
};
|
|
65
81
|
export type InternalLifecycleOptions = ResumeOptions & {
|
|
82
|
+
durable?: boolean;
|
|
66
83
|
[AFTER_WAIT]?: (trace: Trace, operation: Operation) => Promise<void>;
|
|
67
84
|
[HELD_RESUME]?: HeldResumeTarget;
|
|
68
85
|
};
|
package/dist/lifecycle.js
CHANGED
|
@@ -19,7 +19,9 @@ export function waitOptionsOf(opts) {
|
|
|
19
19
|
* the request, every observed state, and `AFTER_WAIT`; it ends with `done` either way.
|
|
20
20
|
*/
|
|
21
21
|
export async function runLifecycle(ctx, kind, workspaceId, start, opts, capture = {}) {
|
|
22
|
-
|
|
22
|
+
// `durable` (suspend) waits for the durable copy, so it implies `wait`.
|
|
23
|
+
const waitOpts = waitOptionsOf(opts) ?? (opts.durable ? {} : null);
|
|
24
|
+
const started = Date.now();
|
|
23
25
|
const trace = new Trace(kind, combineListeners(ctx.onProgress, opts.onProgress, waitOpts?.onProgress), { workspaceId });
|
|
24
26
|
return traced(trace, async () => {
|
|
25
27
|
// Tool-call capture writes recorded before this call land first (snapshot, fork, suspend and close include them).
|
|
@@ -36,6 +38,11 @@ export async function runLifecycle(ctx, kind, workspaceId, start, opts, capture
|
|
|
36
38
|
throw new OperationFailedError(operation);
|
|
37
39
|
if (operation.state !== 'succeeded')
|
|
38
40
|
final = await ctx.workspaces.waitForOperation(operation.id, { ...waitOpts, [TRACE]: trace });
|
|
41
|
+
if (opts.durable) {
|
|
42
|
+
// One budget for the whole call: what the suspend itself left.
|
|
43
|
+
const timeoutMs = Math.max(1, (waitOpts.timeoutMs ?? 300_000) - (Date.now() - started));
|
|
44
|
+
final = await ctx.workspaces.waitForDurable(final, { ...waitOpts, timeoutMs, [TRACE]: trace });
|
|
45
|
+
}
|
|
39
46
|
await opts[AFTER_WAIT]?.(trace, final);
|
|
40
47
|
return final;
|
|
41
48
|
});
|
package/dist/progress.d.ts
CHANGED
|
@@ -28,8 +28,10 @@ export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork
|
|
|
28
28
|
* - `busy`: a tool call waiting out `workspace_busy` (only in `tool` events).
|
|
29
29
|
* - `capture_flush` (0.7.0+): a lifecycle call waiting for tool-call capture writes recorded before it (only when some
|
|
30
30
|
* were pending).
|
|
31
|
+
* - `durable` (0.12.0+): `suspend({ durable: true })` or `waitForDurable()` waiting for the durable copy after the
|
|
32
|
+
* operation succeeded (reason `overdue` once the result has `durability.overdue_at`).
|
|
31
33
|
*/
|
|
32
|
-
export type LifecyclePhase = 'request' | 'queued' | 'capacity_pending' | 'running' | 'view' | 'token' | 'busy' | 'capture_flush';
|
|
34
|
+
export type LifecyclePhase = 'request' | 'queued' | 'capacity_pending' | 'running' | 'view' | 'token' | 'busy' | 'capture_flush' | 'durable';
|
|
33
35
|
export interface TimingPhase {
|
|
34
36
|
phase: LifecyclePhase;
|
|
35
37
|
/** The server's state_reason, or why the SDK entered the phase (`held`, `initial`, `expiring`, `invalidated`). */
|
|
@@ -89,11 +91,70 @@ export interface ServerTiming {
|
|
|
89
91
|
* `runtime_changed` (the platform's VM runtime changed after the suspend). Null otherwise.
|
|
90
92
|
*/
|
|
91
93
|
coldBootReason?: string | null;
|
|
94
|
+
/**
|
|
95
|
+
* `result.durable` (0.12.0+), suspend and fork: `true` when the capture is in durable storage, `false` while its
|
|
96
|
+
* durable copy is being written (see `durability`). `null` when the result does not say (other kinds, an older API).
|
|
97
|
+
*/
|
|
98
|
+
durable?: boolean | null;
|
|
99
|
+
/** `result.suspend_path` (0.12.0+): `local_commit` when the suspend returned as soon as it was sealed on its host. */
|
|
100
|
+
suspendPath?: string | null;
|
|
101
|
+
/** `result.durability` (0.12.0+): the durable copy's state and timestamps. Null when the result has none. */
|
|
102
|
+
durability?: Durability | null;
|
|
103
|
+
/**
|
|
104
|
+
* `result.lost_suspend` (0.12.0+), resume: the workspace's latest suspend could not be kept and this resume restored the
|
|
105
|
+
* checkpoint before it (`restoredCheckpointId`, state as of `stateAsOf`). Null otherwise.
|
|
106
|
+
*/
|
|
107
|
+
lostSuspend?: LostSuspend | null;
|
|
92
108
|
/** `result.boot_to_ready_ms`: VM start until the guest agent answered. */
|
|
93
109
|
bootToReadyMs: number | null;
|
|
94
110
|
/** `result.host_timings_ms`: the host's own steps (restore: load, after_restore, ready, …). */
|
|
95
111
|
hostTimingsMs: Record<string, number> | null;
|
|
96
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* `result.durability` of a suspend or fork (0.12.0+), camelCased. A suspend returns as soon as the workspace is sealed on
|
|
115
|
+
* its host (`pending`); `durable` follows when the copy lands in durable storage (`durableAt`). Timestamps are RFC 3339.
|
|
116
|
+
*/
|
|
117
|
+
export interface Durability {
|
|
118
|
+
/** `pending` (the durable copy is being written), `durable` (it committed) or `lost` (see the lifecycle reference). */
|
|
119
|
+
state: 'pending' | 'durable' | 'lost';
|
|
120
|
+
checkpointId: string | null;
|
|
121
|
+
generationId: string | null;
|
|
122
|
+
/** When the suspend (or fork) completed on its host. */
|
|
123
|
+
localCommitAt: string | null;
|
|
124
|
+
/** When the durable copy is expected by. */
|
|
125
|
+
durableBy: string | null;
|
|
126
|
+
/** When the durable copy committed (state `durable`). */
|
|
127
|
+
durableAt: string | null;
|
|
128
|
+
/** Local commit to durable commit, in milliseconds (state `durable`). */
|
|
129
|
+
localCommitToDurableMs: number | null;
|
|
130
|
+
/** Set once `durableBy` passed while the copy was still being written; it keeps going. */
|
|
131
|
+
overdueAt: string | null;
|
|
132
|
+
/** State `lost`: why (e.g. `host_lost`). */
|
|
133
|
+
reason: string | null;
|
|
134
|
+
}
|
|
135
|
+
/** `result.lost_suspend` of a resume (0.12.0+), camelCased. See the lifecycle reference. */
|
|
136
|
+
export interface LostSuspend {
|
|
137
|
+
checkpointId: string;
|
|
138
|
+
generationId: string | null;
|
|
139
|
+
reason: string | null;
|
|
140
|
+
suspendedAt: string | null;
|
|
141
|
+
/** The earlier checkpoint this resume restored. */
|
|
142
|
+
restoredCheckpointId: string | null;
|
|
143
|
+
/** The restored state is as of this time (RFC 3339). */
|
|
144
|
+
stateAsOf: string | null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The durable copy of a suspend or fork operation (0.12.0+): `result.durability` camelCased, or null when the result has
|
|
148
|
+
* none (the suspend was stored durably before it completed, or another kind).
|
|
149
|
+
*/
|
|
150
|
+
export declare function durabilityOf(op: Pick<Operation, 'result'>): Durability | null;
|
|
151
|
+
/** A resume operation's `result.lost_suspend` camelCased (0.12.0+), or null. */
|
|
152
|
+
export declare function lostSuspendOf(op: Pick<Operation, 'result'>): LostSuspend | null;
|
|
153
|
+
/**
|
|
154
|
+
* Whether a suspend or fork operation's capture is in durable storage (0.12.0+): `result.durable`, else `true` for a
|
|
155
|
+
* succeeded suspend or fork whose result predates the field, else null.
|
|
156
|
+
*/
|
|
157
|
+
export declare function isDurable(op: Pick<Operation, 'result' | 'kind' | 'state'>): boolean | null;
|
|
97
158
|
export type TimingOutcome = 'succeeded' | 'failed' | 'timed_out' | 'error';
|
|
98
159
|
export interface LifecycleTiming {
|
|
99
160
|
action: LifecycleAction;
|
package/dist/progress.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
const DURABILITY_STATES = new Set(['pending', 'durable', 'lost']);
|
|
2
|
+
/**
|
|
3
|
+
* The durable copy of a suspend or fork operation (0.12.0+): `result.durability` camelCased, or null when the result has
|
|
4
|
+
* none (the suspend was stored durably before it completed, or another kind).
|
|
5
|
+
*/
|
|
6
|
+
export function durabilityOf(op) {
|
|
7
|
+
const d = op.result?.durability;
|
|
8
|
+
if (typeof d !== 'object' || d === null)
|
|
9
|
+
return null;
|
|
10
|
+
const r = d;
|
|
11
|
+
if (typeof r.state !== 'string' || !DURABILITY_STATES.has(r.state))
|
|
12
|
+
return null;
|
|
13
|
+
return {
|
|
14
|
+
state: r.state,
|
|
15
|
+
checkpointId: str(r.checkpoint_id),
|
|
16
|
+
generationId: str(r.generation_id),
|
|
17
|
+
localCommitAt: str(r.local_commit_at),
|
|
18
|
+
durableBy: str(r.durable_by),
|
|
19
|
+
durableAt: str(r.durable_at),
|
|
20
|
+
localCommitToDurableMs: num(r.local_commit_to_durable_ms),
|
|
21
|
+
overdueAt: str(r.overdue_at),
|
|
22
|
+
reason: str(r.reason),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** A resume operation's `result.lost_suspend` camelCased (0.12.0+), or null. */
|
|
26
|
+
export function lostSuspendOf(op) {
|
|
27
|
+
const l = op.result?.lost_suspend;
|
|
28
|
+
if (typeof l !== 'object' || l === null)
|
|
29
|
+
return null;
|
|
30
|
+
const r = l;
|
|
31
|
+
const checkpointId = str(r.checkpoint_id);
|
|
32
|
+
if (!checkpointId)
|
|
33
|
+
return null;
|
|
34
|
+
return {
|
|
35
|
+
checkpointId,
|
|
36
|
+
generationId: str(r.generation_id),
|
|
37
|
+
reason: str(r.reason),
|
|
38
|
+
suspendedAt: str(r.suspended_at),
|
|
39
|
+
restoredCheckpointId: str(r.restored_checkpoint_id),
|
|
40
|
+
stateAsOf: str(r.state_as_of),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether a suspend or fork operation's capture is in durable storage (0.12.0+): `result.durable`, else `true` for a
|
|
45
|
+
* succeeded suspend or fork whose result predates the field, else null.
|
|
46
|
+
*/
|
|
47
|
+
export function isDurable(op) {
|
|
48
|
+
const v = op.result?.durable;
|
|
49
|
+
if (typeof v === 'boolean')
|
|
50
|
+
return v;
|
|
51
|
+
return op.state === 'succeeded' && (op.kind === 'suspend' || op.kind === 'fork') ? true : null;
|
|
52
|
+
}
|
|
1
53
|
/** Calls every listener; a listener that throws never breaks the SDK call. */
|
|
2
54
|
export function emitTo(listeners, event) {
|
|
3
55
|
for (const l of listeners) {
|
|
@@ -66,6 +118,10 @@ export function serverTiming(op) {
|
|
|
66
118
|
// Unknown (null) unless the result says so: a result without the field is never read as "not restored".
|
|
67
119
|
memoryRestored: typeof r.memory_restored === 'boolean' ? r.memory_restored : null,
|
|
68
120
|
coldBootReason: str(r.cold_boot_reason),
|
|
121
|
+
durable: typeof r.durable === 'boolean' ? r.durable : null,
|
|
122
|
+
suspendPath: str(r.suspend_path),
|
|
123
|
+
durability: durabilityOf(op),
|
|
124
|
+
lostSuspend: lostSuspendOf(op),
|
|
69
125
|
bootToReadyMs: num(r.boot_to_ready_ms),
|
|
70
126
|
hostTimingsMs,
|
|
71
127
|
};
|
|
@@ -76,6 +132,8 @@ function outcomeOf(err) {
|
|
|
76
132
|
return 'failed';
|
|
77
133
|
if (name === 'OperationTimeoutError')
|
|
78
134
|
return 'timed_out';
|
|
135
|
+
if (name === 'DurabilityLostError')
|
|
136
|
+
return 'failed';
|
|
79
137
|
return 'error';
|
|
80
138
|
}
|
|
81
139
|
/** Why a request failed, in one short phrase (for retry records). */
|
|
@@ -249,6 +307,9 @@ export function formatTiming(t) {
|
|
|
249
307
|
const how = [
|
|
250
308
|
s.startPath ? `start ${s.startPath}${s.warmFallback ? ` (warm fallback: ${s.warmFallback})` : ''}` : null,
|
|
251
309
|
s.resumePath ? `resume from ${s.resumePath}${s.memoryRestored === false ? `: processes restarted${s.coldBootReason ? ` (${s.coldBootReason})` : ''}` : ''}` : null,
|
|
310
|
+
s.lostSuspend ? `restored ${s.lostSuspend.restoredCheckpointId ?? 'no checkpoint'} (latest suspend ${s.lostSuspend.checkpointId} ${s.lostSuspend.reason ?? 'lost'})` : null,
|
|
311
|
+
s.suspendPath === 'local_commit' ? 'sealed on host' : null,
|
|
312
|
+
s.durability?.state === 'durable' ? `durable${s.durability.localCommitToDurableMs !== null ? ` ${fmt(s.durability.localCommitToDurableMs)} later` : ''}` : s.durability ? `durable copy ${s.durability.state}` : null,
|
|
252
313
|
s.bootToReadyMs !== null ? `boot to ready ${fmt(s.bootToReadyMs)}` : null,
|
|
253
314
|
s.hostTimingsMs ? `host ${Object.entries(s.hostTimingsMs).map(([k, v]) => `${k} ${fmt(v)}`).join(', ')}` : null,
|
|
254
315
|
].filter(Boolean);
|
package/dist/workspace.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* A workspace handle: the latest view from the application API plus managed
|
|
3
3
|
* tool tokens and cell clients (one per agent label / tool set).
|
|
4
4
|
*/
|
|
5
|
-
import type { ClientContext, DiskLayout, ForkTarget, Operation, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
|
|
5
|
+
import type { ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
|
|
6
6
|
import { CAPTURE_BARRIER, CellClient } from './cell.js';
|
|
7
7
|
import { ToolCallCapture } from './capture.js';
|
|
8
8
|
import type { ToolCallCaptureOptions } from './capture.js';
|
|
9
9
|
import type { WorkspaceMode } from './errors.js';
|
|
10
|
-
import type { FinishedOperation, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
|
|
10
|
+
import type { FinishedOperation, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
|
|
11
11
|
import { Trace } from './progress.js';
|
|
12
12
|
import type { LifecycleTiming, ProgressListener } from './progress.js';
|
|
13
13
|
import { WorkspaceSecrets } from './secrets.js';
|
|
@@ -135,11 +135,20 @@ export declare class Workspace {
|
|
|
135
135
|
get secrets(): WorkspaceSecrets;
|
|
136
136
|
/** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
|
|
137
137
|
inputs(): Promise<Record<string, string>>;
|
|
138
|
+
get labels(): Record<string, string>;
|
|
139
|
+
setLabels(labels: Record<string, string>): Promise<this>;
|
|
140
|
+
setIdlePolicy(policy: IdlePolicy | null): Promise<this>;
|
|
141
|
+
idle(signal?: AbortSignal): ReturnType<CellClient['idle']>;
|
|
142
|
+
keepalive(seconds: number, signal?: AbortSignal): ReturnType<CellClient['keepalive']>;
|
|
138
143
|
refresh(): Promise<this>;
|
|
139
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Waits for the active operation (if any) and refreshes. A suspend-when-idle that found the workspace active is
|
|
146
|
+
* canceled `workspace_active` (0.12.0+: `OperationFailedError.workspaceActive`); the workspace keeps running, so this
|
|
147
|
+
* resolves.
|
|
148
|
+
*/
|
|
140
149
|
waitUntilReady(opts?: WaitOptions): Promise<this>;
|
|
141
150
|
/**
|
|
142
|
-
* Deletes the workspace (tool access ends at once;
|
|
151
|
+
* Deletes the workspace (tool access ends at once; the key can be reused after deletion finishes). Resolves when the delete is REQUESTED;
|
|
143
152
|
* with `{ wait: true }`, once it has FINISHED.
|
|
144
153
|
*/
|
|
145
154
|
delete(opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
@@ -147,12 +156,15 @@ export declare class Workspace {
|
|
|
147
156
|
/**
|
|
148
157
|
* Suspends the workspace: memory and processes are checkpointed, compute stops. Resolves when the suspend is
|
|
149
158
|
* REQUESTED (the operation is usually still `queued`, and the workspace still running); pass `{ wait: true }` to
|
|
150
|
-
* resolve once it has FINISHED, with `workspace.state` then `suspended`.
|
|
159
|
+
* resolve once it has FINISHED, with `workspace.state` then `suspended`. That is as soon as the workspace is sealed on
|
|
160
|
+
* its host, typically in a few hundred ms. `result.durable` (also `lastTiming.server.durable`, 0.12.0+) turns true
|
|
161
|
+
* when the copy lands in durable storage, typically within a second; `{ durable: true }` resolves only then.
|
|
151
162
|
*
|
|
152
163
|
* await workspace.suspend({ wait: true });
|
|
164
|
+
* await workspace.suspend({ durable: true }); // 0.12.0+: also wait for the durable copy
|
|
153
165
|
*/
|
|
154
|
-
suspend(opts:
|
|
155
|
-
suspend(opts?:
|
|
166
|
+
suspend(opts: WaitedSuspendOptions): Promise<FinishedOperation>;
|
|
167
|
+
suspend(opts?: SuspendOptions): Promise<Operation>;
|
|
156
168
|
/**
|
|
157
169
|
* Suspends the workspace once it has been idle for `afterSeconds` (0..3600, 0 = as soon as it is idle; 0.9.0): call it when your agent's turn
|
|
158
170
|
* ends, so the workspace stops using RAM soon after instead of waiting out its idle policy. A command still running,
|
|
@@ -176,7 +188,9 @@ export declare class Workspace {
|
|
|
176
188
|
* those given to open()), so `cell()` calls with that label and tools start at once. A workspace that is already
|
|
177
189
|
* running is ShardfluxApiError 409 `conflict` (`already_running`). The finished operation's `result.memory_restored`
|
|
178
190
|
* (also `lastTiming.server.memoryRestored`, 0.11.0+) is false when the resume booted the saved disk instead
|
|
179
|
-
* (`resume_path` `cold_boot`): files kept, processes restarted.
|
|
191
|
+
* (`resume_path` `cold_boot`): files kept, processes restarted. `result.lost_suspend` (also
|
|
192
|
+
* `lastTiming.server.lostSuspend`, `lostSuspendOf(op)`, 0.12.0+) names a suspend this resume could not restore and the
|
|
193
|
+
* checkpoint it restored instead (see the lifecycle reference).
|
|
180
194
|
*/
|
|
181
195
|
resume(opts: WaitedResumeOptions): Promise<FinishedOperation>;
|
|
182
196
|
resume(opts?: ResumeOptions): Promise<Operation>;
|
package/dist/workspace.js
CHANGED
|
@@ -181,17 +181,39 @@ export class Workspace {
|
|
|
181
181
|
inputs() {
|
|
182
182
|
return this.#ctx.workspaces.inputs(this.id);
|
|
183
183
|
}
|
|
184
|
+
get labels() { return { ...this.#view.labels }; }
|
|
185
|
+
async setLabels(labels) {
|
|
186
|
+
this.#view = (await this.#ctx.workspaces.setLabels(this.id, labels)).data;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
async setIdlePolicy(policy) {
|
|
190
|
+
this.#view = (await this.#ctx.workspaces.setIdlePolicy(this.id, policy)).data;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
idle(signal) { return this.cell().idle(signal); }
|
|
194
|
+
keepalive(seconds, signal) { return this.cell().keepalive(seconds, signal); }
|
|
184
195
|
async refresh() {
|
|
185
196
|
this.#view = await this.#ctx.http.json('GET', `/v1/workspaces/${encodeURIComponent(this.id)}`, {}, this.#ctx.authorization);
|
|
186
197
|
if (this.#view.mode === 'file_first' && typeof this.#view.tree_revision === 'number')
|
|
187
198
|
this.#noteTreeRevision(this.#view.tree_revision);
|
|
188
199
|
return this;
|
|
189
200
|
}
|
|
190
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Waits for the active operation (if any) and refreshes. A suspend-when-idle that found the workspace active is
|
|
203
|
+
* canceled `workspace_active` (0.12.0+: `OperationFailedError.workspaceActive`); the workspace keeps running, so this
|
|
204
|
+
* resolves.
|
|
205
|
+
*/
|
|
191
206
|
async waitUntilReady(opts = {}) {
|
|
192
207
|
const op = this.#view.active_operation;
|
|
193
|
-
if (op)
|
|
194
|
-
|
|
208
|
+
if (op) {
|
|
209
|
+
try {
|
|
210
|
+
await this.#ctx.workspaces.waitForOperation(op.id, opts);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
if (!(e instanceof OperationFailedError) || !e.workspaceActive)
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
195
217
|
return this.refresh();
|
|
196
218
|
}
|
|
197
219
|
delete(opts = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shardflux TypeScript SDK: open persistent agent workspaces by key and give your agent workspace tools (exec, files, processes, PTY, git, browser).",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"yaml": "2.9.1",
|
|
72
72
|
"zod": "4.6.5"
|
|
73
73
|
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"undici": "8.10.2"
|
|
76
|
+
},
|
|
74
77
|
"scripts": {
|
|
75
78
|
"build": "node scripts/build.mjs",
|
|
76
79
|
"generate": "openapi-typescript ../contracts/openapi/app-api.json -o src/generated/app-api.ts && openapi-typescript ../contracts/openapi/cell-api.yaml --default-non-nullable false -o src/generated/cell-api.ts",
|