@shardflux/sdk 0.13.1 → 0.14.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.
@@ -953,6 +953,12 @@ export interface components {
953
953
  burst_vcpus?: number;
954
954
  /** @description The burst VM's memory in MiB (burst always only; default the host's, 8192), at most the plan's workspace memory ceiling. */
955
955
  burst_memory_mib?: number;
956
+ /**
957
+ * @description Contracts §41.1: whether an elastic workspace's memory grows before the command starts. `heavy` grows it to the exec-start size first; `light` starts at once (pressure grows cover a spike); `auto` (default) grows only for a heavy command family (package installers, test runners, compilers, type checkers, bundlers). Changes only elastic workspaces in memory layout v2; any other value is 422 validation_failed (details.field resource_hint).
958
+ * @default auto
959
+ * @enum {string}
960
+ */
961
+ resource_hint?: "auto" | "light" | "heavy";
956
962
  };
957
963
  /** @description Names of customer secrets (contracts §17) to inject as environment variables NAME=value of this process only. The cell resolves them at session start through the API with the caller's tool token (permission-checked and audited by the API); values are never logged, persisted or returned by the cell. Any name the caller may not use (unknown, not permitted for this workspace/project/tool) refuses the whole start with 403 forbidden (details.reason secret_not_available, details.names); nothing is started. A name that is also a key of `env` is refused (422 validation_failed). Requires a workspace tool token (browser stream tickets cannot resolve secrets: 403, details.reason secret_refs_require_tool_token). Resolution failures: 401 (token revoked/expired), 409 stale_epoch, 429 rate_limited, 503 dependency_unavailable. A retried start with the same session_id re-resolves the names; if a value changed since the first start the host refuses the different request (409 conflict). */
958
964
  SecretRefs: string[];
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.13.1";
2
+ export declare const SDK_VERSION = "0.14.0";
3
3
  export interface RequestOptions {
4
4
  query?: Record<string, string | number | boolean | undefined | null>;
5
5
  json?: unknown;
package/dist/http.js CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { ShardfluxApiError, ShardfluxProtocolError, apiError, isErrorBody, isWorkingQuotaRefusal } from "./errors.js";
13
13
  import { describeFailure } from "./progress.js";
14
- export const SDK_VERSION = '0.13.1';
14
+ export const SDK_VERSION = '0.14.0';
15
15
  export const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
16
16
  /**
17
17
  * How long an idle pooled connection stays reusable (ms): 5 minutes, instead of undici's 4 s default, so the request
package/dist/index.d.ts CHANGED
@@ -15,9 +15,9 @@
15
15
  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
- export type { AgentSession, AllocationMode, 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, WaitOptions, WorkspaceInputs, WorkspaceLifetime, WorkspaceMemory, WorkspaceOrigin, WorkspacePurpose, WorkspaceView, } from './client.js';
18
+ export type { AgentSession, AllocationMode, BillingCatalog, BillingSubscription, Caps, CheckoutSession, DiskLayout, Entitlements, FindByKeyOptions, ForkTarget, Invoice, InvoicePage, LifetimeFilter, ListParams, Me, OpenParams, OpenResponse, Operation, Page, PortalSession, PurposeFilter, ResetWorkspaceBody, ResizeAppliesAt, ResizeCpu, ResizeDeferReason, ResizeDisk, ResizeLimitReason, ResizeMemory, ResizeParams, ResizeResult, ResizeResponse, ResumeAnswer, ResumeRequestOptions, ResumeResponse, ShardfluxOptions, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResponse, SuspendWhenIdleResult, WaitOptions, WorkspaceInputs, WorkspaceLifetime, WorkspaceMemory, WorkspaceOrigin, WorkspacePurpose, WorkspaceView, } from './client.js';
19
19
  export type { FinishedOperation, ForkOptions, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedForkOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
20
- export { durabilityOf, formatTiming, hostLostOf, isDurable, lostSuspendOf } from './progress.js';
20
+ export { COLD_BOOT_REASONS, durabilityOf, formatTiming, hostLostOf, isDurable, lostSuspendOf } from './progress.js';
21
21
  export type { ColdBootReason, Durability, HostLost, 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';
@@ -29,6 +29,8 @@ export type { PackedFile, YamlParser } from './template-file.js';
29
29
  export { tarEnd, tarHeader, tarPadding } from './tar.js';
30
30
  export type { TarEntry, TarEntryType } from './tar.js';
31
31
  export type { BuildFromFileEvent, BuildFromFileOptions, BuildFromFileResult, BuildFromRecipeOptions, BuilderAvailability, CreateDraftBody, CreateDraftParams, CreateTemplateBuildParams, CreateTestInstanceBody, CreateVersionTestInstanceBody, CreateVersionTestInstanceParams, DraftOpened, LocalUpload, DraftState, OpenTestInstanceParams, OrgTemplateStorage, PublishDraftBody, PublishDraftParams, PutUploadOptions, SaveAsTemplateBody, SaveAsTemplateParams, SaveAsTemplateResponse, TemplateBuild, TemplateBuildLogUrl, TemplateBuildRecipeV2, TemplateBuildRegistrationState, TemplateBuildState, TemplateCategory, TemplateDefaults, TemplateDefaultsInput, TemplateDetail, TemplateDiffChange, TemplateDiffEntry, TemplateDiffPage, TemplateDiffParams, TemplateDraft, TemplateDraftSummary, TemplateEgressDefault, TemplateFileEntry, TemplateFilePage, TemplateFilesParams, TemplateFilesSummary, TemplateInput, TemplateLanguage, TemplateLanguages, TemplateOwner, TemplatePackage, TemplatePackageEcosystem, TemplatePackagePage, TemplateRecipe, TemplateRecipeV2, TemplateRecipeV2File, TemplateService, TemplateSettings, TemplateSettingsInput, TemplateSource, TemplateStartCommand, TemplateStorage, TemplateStorageWarning, TemplateSummary, TemplateUpload, TemplateUploadRequest, TemplateUploadResponse, TemplateUploadResult, TemplateVersion, TemplateVersionImmutable, TemplateVersionRecipe, TemplateVersionState, UploadData, WaitForBuildOptions, WorkspaceStartup, } from './templates.js';
32
+ export { WorkspacePorts } from './ports.js';
33
+ export type { ExposePortResult, ExposedPort, PortCallbackResponse, PortCallbackUrl, PortLink, PortLinkOptions, PortLinkResponse, PortToken, PortTokenOptions, PortTokenResponse, PortView } from './ports.js';
32
34
  export { SecretsApi, WorkspaceSecrets } from './secrets.js';
33
35
  export type { BoundSecretStatus, CreateOrganizationSecretParams, CreateSecretParams, Secret, SecretAccessEvent, SecretPermissions, SecretScope, SecretVersion, UpdateSecretParams, WorkspaceSecretBindings, } from './secrets.js';
34
36
  export { EgressPolicyApi } from './egress.js';
@@ -42,7 +44,7 @@ export type { HintOptions, HintResult, WakeOptions } from './workspace.js';
42
44
  export { CellClient, DEFAULT_TRANSITION_TIMEOUT_MS, cellPath, ndjson } from './cell.js';
43
45
  export { EXECUTION_ID, ExecutionResult, newExecutionId } from './executions.js';
44
46
  export type { ExecutionChange, ExecutionError, ExecutionGetOptions, ExecutionResultBody, ExecutionRunOptions, ExecutionState } from './executions.js';
45
- export type { TreeRevisionOptions, BrowserContent, BrowserContentRequest, BrowserScreenshotRequest, CellClientOptions, ExecSession, ExecStartRequest, MemoryGrow, BurstSummary, BurstError, FileEdit, FileInfo, FileList, FilePatchEdit, FilePatchParams, FilePatchRequest, FilePatchResult, FileReadResult, FileRevision, FileSearchMatch, FileSearchOptions, FileSearchRequest, FileSearchResponse, FileSearchResult, FileWriteResult, GitResult, GitStatus, OutputEvent, ProcessList, PtyOpenRequest, PtySession, Residency, RunOptions, RunResult, ServedFrom, Signal, WakeHintResult, WorkspaceChange, WorkspaceChangeKind, WorkspaceChangesPage, WorkspaceChangesParams, WorkspaceChangesSummary, } from './cell.js';
47
+ export type { TreeRevisionOptions, BrowserContent, BrowserContentRequest, BrowserScreenshotRequest, CellClientOptions, ExecSession, ExecStartRequest, MemoryGrow, BurstSummary, BurstError, FileEdit, FileInfo, FileList, FilePatchEdit, FilePatchParams, FilePatchRequest, FilePatchResult, FileReadResult, FileRevision, FileSearchMatch, FileSearchOptions, FileSearchRequest, FileSearchResponse, FileSearchResult, FileWriteResult, GitResult, GitStatus, OutputEvent, ProcessList, PtyOpenRequest, PtySession, Residency, ResourceHint, RunOptions, RunResult, ServedFrom, Signal, WakeHintResult, WorkspaceChange, WorkspaceChangeKind, WorkspaceChangesPage, WorkspaceChangesParams, WorkspaceChangesSummary, } from './cell.js';
46
48
  export { ToolTokenManager } from './tokens.js';
47
49
  export type { ToolName, ToolToken, ToolTokenOptions } from './tokens.js';
48
50
  export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from './tools.js';
@@ -54,7 +56,9 @@ export { DurabilityLostError, ExecStartError, NotSupportedForModeError, Operatio
54
56
  export type { AppErrorCode, CellErrorCode, ErrorCode, ErrorReason, KnownErrorReason, WorkspaceMode } from './errors.js';
55
57
  export { SDK_VERSION } from './http.js';
56
58
  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
- 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';
59
+ export { codexIdentityProof } from './codex-proof.js';
60
+ export type { CodexIdentityProof, CodexProofOptions, CodexProofUnavailable } from './codex-proof.js';
61
+ 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, SignupResult, SignupAccess, EmailCodeResult, ResendVerificationResult, SessionTokenUpdate, ShardfluxAccountOptions, SpendPolicyUpdate, StepUpResult, TotpConfirmResult, TotpDisableResult, TotpEnrollment, VerifyEmailResult, WaitForCheckoutOptions, } from './account.js';
58
62
  export { checkClientVersion, clientVersionStatus, compareVersions, versionCheckDisabledByEnv } from './version-check.js';
59
63
  export type { CheckClientVersionOptions, ClientEcosystem, ClientVersionEntry, ClientVersionStatus, ClientVersionStatusKind, ClientVersions, VersionCheckIdentity, VersionCheckOption, } from './version-check.js';
60
64
  export { isWorkspaceGone } from './errors.js';
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from "./client.js";
2
- export { durabilityOf, formatTiming, hostLostOf, isDurable, lostSuspendOf } from "./progress.js";
2
+ export { COLD_BOOT_REASONS, durabilityOf, formatTiming, hostLostOf, 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";
6
6
  export { TemplateFileError, packDirectory, parseTemplateText, readTemplateFile } from "./template-file.js";
7
7
  export { tarEnd, tarHeader, tarPadding } from "./tar.js";
8
+ export { WorkspacePorts } from "./ports.js";
8
9
  export { SecretsApi, WorkspaceSecrets } from "./secrets.js";
9
10
  export { EgressPolicyApi } from "./egress.js";
10
11
  export { VolumesApi } from "./volumes.js";
@@ -18,6 +19,7 @@ export { CaptureError, ToolCallCapture, captureTool } from "./capture.js";
18
19
  export { DurabilityLostError, ExecStartError, NotSupportedForModeError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TreeRevisionMismatchError } from "./errors.js";
19
20
  export { SDK_VERSION } from "./http.js";
20
21
  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";
22
+ export { codexIdentityProof } from "./codex-proof.js";
21
23
  export { checkClientVersion, clientVersionStatus, compareVersions, versionCheckDisabledByEnv } from "./version-check.js";
22
24
  export { isWorkspaceGone } from "./errors.js";
23
25
  export { defaultFetch } from "./http.js";
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Inbound ports (0.14.0; contracts §39.3): serve a TCP port of a processful workspace at its own HTTPS URL,
3
+ * `https://<port>-<handle>.<ingress domain>`. Every port is private: a request carries a port token
4
+ * (`Authorization: Bearer sfp_…`), a signed link's browser session, or arrives on the port's callback URL. A request
5
+ * to a suspended or parked workspace wakes it and is served once it runs.
6
+ *
7
+ * const { url } = await workspace.ports.expose(3000);
8
+ * const { token } = await workspace.ports.token(3000);
9
+ * await fetch(url, { headers: { authorization: `Bearer ${token}` } });
10
+ *
11
+ * Types are written by hand (checked against the generated contract in type-checks.ts).
12
+ */
13
+ import type { components, operations } from './generated/app-api.js';
14
+ import type { ClientContext } from './client.js';
15
+ type Created<Op> = Op extends {
16
+ responses: {
17
+ 201: {
18
+ content: {
19
+ 'application/json': infer T;
20
+ };
21
+ };
22
+ };
23
+ } ? T : never;
24
+ /** An exposed port as the API sends it (`Port`). */
25
+ export type PortView = components['schemas']['Port'];
26
+ /** POST …/ports/{port}/tokens (201). */
27
+ export type PortTokenResponse = Created<operations['postV1WorkspacesWorkspaceIdPortsPortTokens']>;
28
+ /** POST …/ports/{port}/links (201). */
29
+ export type PortLinkResponse = Created<operations['postV1WorkspacesWorkspaceIdPortsPortLinks']>;
30
+ /** POST …/ports/{port}/callback (201). */
31
+ export type PortCallbackResponse = Created<operations['postV1WorkspacesWorkspaceIdPortsPortCallback']>;
32
+ /** An exposed port of a workspace. */
33
+ export interface ExposedPort {
34
+ /** TCP port inside the workspace (1-65535). */
35
+ port: number;
36
+ /** `https://<port>-<handle>.<ingress domain>`: requests need a port token, a link's browser session or the callback URL. */
37
+ url: string;
38
+ /** When the port was exposed (RFC 3339). Exposing it again after a close starts a new exposure. */
39
+ createdAt: string;
40
+ /** Set when the port has a callback URL (its secret is shown only by `createCallbackUrl()`). */
41
+ callback: {
42
+ createdAt: string;
43
+ } | null;
44
+ }
45
+ /** `expose()`: the port, and whether this call exposed it (201) or it already was (200, unchanged). */
46
+ export interface ExposePortResult extends ExposedPort {
47
+ created: boolean;
48
+ }
49
+ /** A port token: send it as `Authorization: Bearer <token>` (or `X-Shardflux-Token`) to `url`. */
50
+ export interface PortToken {
51
+ /** `sfp_…`. Revoked when the port is closed, the workspace is deleted or the API key that minted it is revoked. */
52
+ token: string;
53
+ /** RFC 3339. */
54
+ expiresAt: string;
55
+ /** The port's URL. */
56
+ url: string;
57
+ }
58
+ /** A signed link: opening it in a browser starts a session for the port (a cookie until `expiresAt`) and lands on its path. */
59
+ export interface PortLink {
60
+ url: string;
61
+ /** RFC 3339: the link and the browser session it starts end then. */
62
+ expiresAt: string;
63
+ }
64
+ /**
65
+ * The port's callback URL: register `url` plus your own path with GitHub, Slack, Stripe or an OAuth provider. A request
66
+ * to `<url><rest>` reaches `/<rest>` (query kept) without a token header and wakes the workspace like any request.
67
+ */
68
+ export interface PortCallbackUrl {
69
+ /** `https://<port>-<handle>.<domain>/__shardflux/callback/sfcb_…/`. Shown once: the API keeps only its hash. */
70
+ url: string;
71
+ /** RFC 3339. */
72
+ createdAt: string;
73
+ }
74
+ export interface PortTokenOptions {
75
+ /** Lifetime in seconds: 60-86400 (default 3600). */
76
+ ttlSeconds?: number;
77
+ }
78
+ export interface PortLinkOptions {
79
+ /** Lifetime of the link and of the browser session it starts, in seconds: 60-604800 (default 86400). */
80
+ ttlSeconds?: number;
81
+ /** Where the link lands: a path starting with one `/`, printable ASCII, query allowed (default `/`). */
82
+ path?: string;
83
+ }
84
+ export declare function exposedPortOf(view: PortView): ExposedPort;
85
+ /**
86
+ * The exposed ports of one workspace (`workspace.ports`, `cloud.workspaces.ports(id)`). Errors are ShardfluxApiError:
87
+ * 403 `forbidden` reason `inbound_ports_not_available` (inbound ports are not enabled for the organization), 404
88
+ * `not_found` reason `port_not_exposed` (tokens, links and callback URLs of a port that is not exposed), 409 `conflict`
89
+ * reason `port_limit` (at most 10 exposed ports, `details.limit`) or `workspace_deleted`, NotSupportedForModeError for a
90
+ * file-first workspace, 422 `validation_failed` for a port outside 1-65535 or an option out of range.
91
+ */
92
+ export declare class WorkspacePorts {
93
+ #private;
94
+ constructor(ctx: ClientContext, workspaceId: string);
95
+ /**
96
+ * Exposes TCP `port` at its own HTTPS URL (idempotent: a port already exposed is returned unchanged, `created`
97
+ * false, and its tokens, links and callback URL stay valid). The server inside the workspace must listen on
98
+ * 0.0.0.0 (all interfaces), not only on 127.0.0.1.
99
+ */
100
+ expose(port: number): Promise<ExposePortResult>;
101
+ /** The exposed ports, ordered by port. */
102
+ list(): Promise<ExposedPort[]>;
103
+ /**
104
+ * Closes the port (idempotent: also when it is not exposed). Its tokens, links and callback URL stop working at once,
105
+ * and stay revoked if the port is exposed again.
106
+ */
107
+ close(port: number): Promise<void>;
108
+ /** Mints a port token for an exposed port (`use` bearer): `Authorization: Bearer <token>` on requests to `url`. */
109
+ token(port: number, opts?: PortTokenOptions): Promise<PortToken>;
110
+ /** A signed link that opens the port in a browser. Share it like a password: anyone with it can open the port until it expires. */
111
+ link(port: number, opts?: PortLinkOptions): Promise<PortLink>;
112
+ /** Creates the port's callback URL, or replaces it (the previous one stops working). Its secret is shown only here. */
113
+ createCallbackUrl(port: number): Promise<PortCallbackUrl>;
114
+ /** Revokes the port's callback URL (idempotent while the port is exposed). */
115
+ revokeCallbackUrl(port: number): Promise<void>;
116
+ }
117
+ export {};
package/dist/ports.js ADDED
@@ -0,0 +1,62 @@
1
+ export function exposedPortOf(view) {
2
+ return { port: view.port, url: view.url, createdAt: view.created_at, callback: view.callback ? { createdAt: view.callback.created_at } : null };
3
+ }
4
+ /**
5
+ * The exposed ports of one workspace (`workspace.ports`, `cloud.workspaces.ports(id)`). Errors are ShardfluxApiError:
6
+ * 403 `forbidden` reason `inbound_ports_not_available` (inbound ports are not enabled for the organization), 404
7
+ * `not_found` reason `port_not_exposed` (tokens, links and callback URLs of a port that is not exposed), 409 `conflict`
8
+ * reason `port_limit` (at most 10 exposed ports, `details.limit`) or `workspace_deleted`, NotSupportedForModeError for a
9
+ * file-first workspace, 422 `validation_failed` for a port outside 1-65535 or an option out of range.
10
+ */
11
+ export class WorkspacePorts {
12
+ #ctx;
13
+ #workspaceId;
14
+ constructor(ctx, workspaceId) {
15
+ this.#ctx = ctx;
16
+ this.#workspaceId = workspaceId;
17
+ }
18
+ #path(port, rest = '') {
19
+ const base = `/v1/workspaces/${encodeURIComponent(this.#workspaceId)}/ports`;
20
+ return port === undefined ? base : `${base}/${encodeURIComponent(String(port))}${rest}`;
21
+ }
22
+ /**
23
+ * Exposes TCP `port` at its own HTTPS URL (idempotent: a port already exposed is returned unchanged, `created`
24
+ * false, and its tokens, links and callback URL stay valid). The server inside the workspace must listen on
25
+ * 0.0.0.0 (all interfaces), not only on 127.0.0.1.
26
+ */
27
+ async expose(port) {
28
+ const r = await this.#ctx.http.jsonWithStatus('PUT', this.#path(port), { idempotent: true }, this.#ctx.authorization);
29
+ return { ...exposedPortOf(r.body), created: r.status === 201 };
30
+ }
31
+ /** The exposed ports, ordered by port. */
32
+ async list() {
33
+ const body = await this.#ctx.http.json('GET', this.#path(), {}, this.#ctx.authorization);
34
+ return body.ports.map(exposedPortOf);
35
+ }
36
+ /**
37
+ * Closes the port (idempotent: also when it is not exposed). Its tokens, links and callback URL stop working at once,
38
+ * and stay revoked if the port is exposed again.
39
+ */
40
+ async close(port) {
41
+ await this.#ctx.http.json('DELETE', this.#path(port), { idempotent: true }, this.#ctx.authorization);
42
+ }
43
+ /** Mints a port token for an exposed port (`use` bearer): `Authorization: Bearer <token>` on requests to `url`. */
44
+ async token(port, opts = {}) {
45
+ const body = await this.#ctx.http.json('POST', this.#path(port, '/tokens'), { json: opts.ttlSeconds !== undefined ? { ttl_seconds: opts.ttlSeconds } : {} }, this.#ctx.authorization);
46
+ return { token: body.token, expiresAt: body.expires_at, url: body.url };
47
+ }
48
+ /** A signed link that opens the port in a browser. Share it like a password: anyone with it can open the port until it expires. */
49
+ async link(port, opts = {}) {
50
+ const body = await this.#ctx.http.json('POST', this.#path(port, '/links'), { json: { ...(opts.ttlSeconds !== undefined ? { ttl_seconds: opts.ttlSeconds } : {}), ...(opts.path !== undefined ? { path: opts.path } : {}) } }, this.#ctx.authorization);
51
+ return { url: body.url, expiresAt: body.expires_at };
52
+ }
53
+ /** Creates the port's callback URL, or replaces it (the previous one stops working). Its secret is shown only here. */
54
+ async createCallbackUrl(port) {
55
+ const body = await this.#ctx.http.json('POST', this.#path(port, '/callback'), {}, this.#ctx.authorization);
56
+ return { url: body.url, createdAt: body.created_at };
57
+ }
58
+ /** Revokes the port's callback URL (idempotent while the port is exposed). */
59
+ async revokeCallbackUrl(port) {
60
+ await this.#ctx.http.json('DELETE', this.#path(port, '/callback'), { idempotent: true }, this.#ctx.authorization);
61
+ }
62
+ }
@@ -18,7 +18,7 @@
18
18
  import type { components } from './generated/app-api.js';
19
19
  type Operation = components['schemas']['Operation'];
20
20
  /** The SDK call a trace follows. `wait` is a direct waitForOperation(); `token` a tool token fetched for tool calls. */
21
- export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork' | 'delete' | 'close' | 'reset' | 'wake' | 'wait' | 'token';
21
+ export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork' | 'delete' | 'close' | 'reset' | 'resize' | 'wake' | 'wait' | 'token';
22
22
  /**
23
23
  * - `request`: an API request that starts or joins the operation. A held open (`Prefer: wait`) spends the server's
24
24
  * hold here (reason `held`), so the states inside it show only in the server timing.
@@ -89,8 +89,9 @@ export interface ServerTiming {
89
89
  memoryRestored?: boolean | null;
90
90
  /**
91
91
  * `result.cold_boot_reason` (0.11.0+), with `resumePath` `cold_boot`: why the memory could not be restored:
92
- * `runtime_changed` (the platform's VM runtime changed after the suspend) or `host_lost` (0.13.1+: the machine the
93
- * workspace ran on failed; it booted from its disk, files kept, see `hostLost`). Null otherwise.
92
+ * `runtime_changed` (the platform's VM runtime changed after the suspend), `host_lost` (0.13.1+: the machine the
93
+ * workspace ran on failed; it booted from its disk, files kept, see `hostLost`) or `runtime_retired` (0.14.0+: the
94
+ * runtime the workspace was suspended on was retired after an announced window). Null otherwise.
94
95
  */
95
96
  coldBootReason?: ColdBootReason | null;
96
97
  /**
@@ -152,11 +153,14 @@ export interface LostSuspend {
152
153
  stateAsOf: string | null;
153
154
  }
154
155
  /**
155
- * `result.cold_boot_reason` (0.11.0+): `runtime_changed` (the platform's VM runtime changed after the suspend) or
156
- * `host_lost` (0.13.1+: the machine the workspace ran on failed). Any other string is a reason this version does not
157
- * know.
156
+ * `result.cold_boot_reason` (0.11.0+): `runtime_changed` (the platform's VM runtime changed after the suspend),
157
+ * `host_lost` (0.13.1+: the machine the workspace ran on failed) or `runtime_retired` (0.14.0+: the runtime the
158
+ * workspace was suspended on was retired after an announced window). Any other string is a reason this version does
159
+ * not know.
158
160
  */
159
- export type ColdBootReason = 'runtime_changed' | 'host_lost' | (string & {});
161
+ export type ColdBootReason = (typeof COLD_BOOT_REASONS)[number] | (string & {});
162
+ /** Every {@link ColdBootReason} this version knows (0.14.0+). */
163
+ export declare const COLD_BOOT_REASONS: readonly ["runtime_changed", "host_lost", "runtime_retired"];
160
164
  /**
161
165
  * `result.host_lost` (0.13.1+), camelCased: the machine the workspace ran on failed. The workspace was moved to
162
166
  * `suspended` at that moment, and its next use (a resume, a tool call's wake, an open) restored it. See the lifecycle
package/dist/progress.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** Every {@link ColdBootReason} this version knows (0.14.0+). */
2
+ export const COLD_BOOT_REASONS = ['runtime_changed', 'host_lost', 'runtime_retired'];
1
3
  const DURABILITY_STATES = new Set(['pending', 'durable', 'lost']);
2
4
  /**
3
5
  * The durable copy of a suspend or fork operation (0.12.0+): `result.durability` camelCased, or null when the result has
package/dist/tools.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface WorkspaceTool<A extends Record<string, unknown> = Record<string
33
33
  };
34
34
  /** Which workspace tool permission the call needs (exec, files, pty, process, git, browser). */
35
35
  permission: ToolName;
36
+ /** Opt-in input-start hook (0.14.0+): call when the model starts this tool's input. Returns immediately. */
37
+ onInputStart?: () => void;
36
38
  /** `toolCallId` (0.7.0+): the model's call id, recorded by tool-call capture (executeToolCall passes it). */
37
39
  execute(args: A, options?: {
38
40
  signal?: AbortSignal;
@@ -74,6 +76,12 @@ export interface WorkspaceToolsOptions {
74
76
  * suspended workspace (or restore a hibernated one) that the read does not need.
75
77
  */
76
78
  hint?: boolean;
79
+ /**
80
+ * Add `onInputStart` hooks (0.14.0+) to tools that need the VM. Invoke the matching hook when the model starts
81
+ * streaming that tool's input, ahead of execute. Default false: no hook, timer or request is installed.
82
+ * Offline file reads and file-first workspaces have no hook.
83
+ */
84
+ prewake?: boolean;
77
85
  /**
78
86
  * The mode to build the tools for (0.9.0+; default `workspace.mode`). Given, the workspace is not touched until a tool
79
87
  * runs, so definitions can be built without one (e.g. to publish them before any workspace exists).
package/dist/tools.js CHANGED
@@ -87,6 +87,13 @@ const ALL = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
87
87
  const FILE_FIRST_TOOLS = ['exec', 'files'];
88
88
  /** Changed paths returned to the model per execution (the rest is flagged `changed_truncated`). */
89
89
  const MAX_CHANGED_LISTED = 200;
90
+ /** The processful exec's memory hint (0.14.0; RunOptions.resourceHint). Sent only when the model sets it. */
91
+ const RESOURCE_HINT_SCHEMA = {
92
+ type: 'string',
93
+ enum: ['auto', 'light', 'heavy'],
94
+ description: 'heavy: give this command more memory before it starts (a build, a test suite, a package install, a training run); light: start it at once. Default auto: decided from the command.',
95
+ };
96
+ const RESOURCE_HINTS = new Set(['auto', 'light', 'heavy']);
90
97
  /** Tools served from a sleeping workspace's disk without waking it: the runner sends no hint. */
91
98
  const DISK_READS = new Set(['read_file', 'list_files', 'search_files']);
92
99
  const obj = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
@@ -267,6 +274,7 @@ export function workspaceTools(workspace, opts = {}) {
267
274
  type: 'boolean',
268
275
  description: 'Start the command and return its session_id at once instead of waiting for it to exit (default false). Use it for anything that may run longer than a few minutes, such as a build, a test suite, a training run or a server. Keep using the other tools meanwhile, read it with exec_read and stop it with exec_cancel. Set timeout_ms to the longest it may run: the workspace stays awake until the command ends or that time passes (1 hour when unset).',
269
276
  },
277
+ resource_hint: RESOURCE_HINT_SCHEMA,
270
278
  ...(opts.burst
271
279
  ? {
272
280
  burst: {
@@ -330,6 +338,7 @@ export function workspaceTools(workspace, opts = {}) {
330
338
  const burst = opts.burst && (a.burst === 'always' || a.burst === 'never') ? a.burst : undefined;
331
339
  const burstVcpus = opts.burst && typeof a.burst_vcpus === 'number' ? a.burst_vcpus : undefined;
332
340
  const burstMemoryMib = opts.burst && typeof a.burst_memory_mib === 'number' ? a.burst_memory_mib : undefined;
341
+ const resourceHint = RESOURCE_HINTS.has(a.resource_hint) ? a.resource_hint : undefined;
333
342
  const cwd = typeof a.cwd === 'string' ? a.cwd : opts.defaultCwd;
334
343
  if (a.background === true) {
335
344
  // A session of its own: the start answers at once, and exec_read / exec_cancel find it by session_id.
@@ -339,6 +348,7 @@ export function workspaceTools(workspace, opts = {}) {
339
348
  ...(cwd !== undefined ? { cwd } : {}),
340
349
  ...(typeof a.timeout_ms === 'number' ? { timeout_ms: a.timeout_ms } : {}),
341
350
  ...(typeof a.stdin === 'string' ? { stdin: Buffer.from(a.stdin, 'utf8').toString('base64') } : {}),
351
+ ...(resourceHint !== undefined ? { resource_hint: resourceHint } : {}),
342
352
  ...(burst !== undefined ? { burst } : {}),
343
353
  ...(burstVcpus !== undefined ? { burst_vcpus: burstVcpus } : {}),
344
354
  ...(burstMemoryMib !== undefined ? { burst_memory_mib: burstMemoryMib } : {}),
@@ -353,6 +363,7 @@ export function workspaceTools(workspace, opts = {}) {
353
363
  ...(cwd !== undefined ? { cwd } : {}),
354
364
  timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
355
365
  ...(typeof a.stdin === 'string' ? { stdin: a.stdin } : {}),
366
+ ...(resourceHint !== undefined ? { resourceHint } : {}),
356
367
  ...(burst !== undefined ? { burst } : {}),
357
368
  ...(burstVcpus !== undefined ? { burstVcpus } : {}),
358
369
  ...(burstMemoryMib !== undefined ? { burstMemoryMib } : {}),
@@ -698,6 +709,15 @@ export function workspaceTools(workspace, opts = {}) {
698
709
  description: d.description,
699
710
  parameters: d.parameters,
700
711
  permission: d.permission,
712
+ ...(opts.prewake === true && !fileFirst && !DISK_READS.has(d.name) ? {
713
+ onInputStart: () => {
714
+ workspace.hint({
715
+ ...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}),
716
+ wake: opts.wake ?? null,
717
+ ...(opts.transitionTimeoutMs !== undefined ? { wakeTimeoutMs: opts.transitionTimeoutMs } : {}),
718
+ }).catch(() => undefined);
719
+ },
720
+ } : {}),
701
721
  execute: async (args, options = {}) => {
702
722
  const issues = validateArgs(d.parameters, args);
703
723
  if (issues.length === 0 && d.refuse)
@@ -2,7 +2,7 @@
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 { AllocationMode, ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceMemory, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
5
+ import type { AllocationMode, ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, ResizeParams, ResizeResult, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceMemory, 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';
@@ -10,6 +10,7 @@ import type { WorkspaceMode } from './errors.js';
10
10
  import type { FinishedOperation, ForkOptions, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedForkOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
11
11
  import { Trace } from './progress.js';
12
12
  import type { LifecycleTiming, ProgressListener } from './progress.js';
13
+ import { WorkspacePorts } from './ports.js';
13
14
  import { WorkspaceSecrets } from './secrets.js';
14
15
  import type { CellClientOptions, Residency, WorkspaceChangesPage, WorkspaceChangesParams } from './cell.js';
15
16
  import type { SaveAsTemplateParams, SaveAsTemplateResponse, WorkspaceStartup } from './templates.js';
@@ -150,6 +151,15 @@ export declare class Workspace {
150
151
  get executions(): CellClient['executions'];
151
152
  /** Secret names bound to this workspace (injected into every exec/PTY start): `get()`, `set(names)`. */
152
153
  get secrets(): WorkspaceSecrets;
154
+ /**
155
+ * Inbound ports (0.14.0): serve a TCP port of the workspace at its own private HTTPS URL. A request wakes a parked or
156
+ * suspended workspace and is served once it runs.
157
+ *
158
+ * const { url } = await workspace.ports.expose(3000); // the server listens on 0.0.0.0:3000
159
+ * const { token } = await workspace.ports.token(3000); // Authorization: Bearer <token>
160
+ * const link = await workspace.ports.link(3000); // open link.url in a browser
161
+ */
162
+ get ports(): WorkspacePorts;
153
163
  /** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
154
164
  inputs(): Promise<Record<string, string>>;
155
165
  get labels(): Record<string, string>;
@@ -200,6 +210,21 @@ export declare class Workspace {
200
210
  * A suspend the request already started is not undone (it shows as `activeOperation`).
201
211
  */
202
212
  cancelSuspendWhenIdle(): Promise<this>;
213
+ /**
214
+ * Resizes this workspace (0.14.0): memory, the held floor, the allocation mode, CPU and disk, whether it is running or
215
+ * suspended, fixed or elastic, without a restart or a fork. Memory changes live on a running workspace (a shrink gives
216
+ * back what the guest frees: `memory.converged`); a suspended one gets its new size when it resumes, before its first
217
+ * call. CPU changes live up to the vCPUs the workspace booted with, beyond that at its next start. Disks grow online.
218
+ * The new caps are stored, so every later start uses them. Resolves once the resize has finished, with per resource
219
+ * when it applies (`now`, `resume` or `next_start` with the `reason`); the handle's view is refreshed. See
220
+ * WorkspacesApi.resize for the errors. A file-first workspace (no VM to resize) is refused locally with
221
+ * NotSupportedForModeError.
222
+ *
223
+ * const r = await workspace.resize({ memoryMib: 6144, cpuMillis: 4000 });
224
+ * r.memory?.applies_at; // 'now'
225
+ * r.cpu?.applies_at; // 'next_start' (reason 'boot_vcpus': more vCPUs than it booted with)
226
+ */
227
+ resize(params: ResizeParams): Promise<ResizeResult>;
203
228
  /**
204
229
  * Resumes a suspended workspace. Resolves when the resume is REQUESTED; with `{ wait: true }`, once the workspace runs.
205
230
  * Tool calls wake a suspended workspace by themselves, so this is rarely needed. With `wait` (0.9.0) it is one held
package/dist/workspace.js CHANGED
@@ -4,6 +4,7 @@ import { NotSupportedForModeError, OperationFailedError, ShardfluxApiError } fro
4
4
  import { SERVER_WAIT_MAX_S, defaultSleep, randomId } from "./http.js";
5
5
  import { AFTER_WAIT, HELD_RESUME, TRACE } from "./lifecycle.js";
6
6
  import { Trace, combineListeners, traced } from "./progress.js";
7
+ import { WorkspacePorts } from "./ports.js";
7
8
  import { WorkspaceSecrets } from "./secrets.js";
8
9
  import { ToolTokenManager } from "./tokens.js";
9
10
  const notRunning = (e) => e instanceof ShardfluxApiError && (e.code === 'workspace_not_running' || (e.code === 'conflict' && e.reason === 'workspace_not_running'));
@@ -200,6 +201,17 @@ export class Workspace {
200
201
  get secrets() {
201
202
  return new WorkspaceSecrets(this.#ctx, this.id);
202
203
  }
204
+ /**
205
+ * Inbound ports (0.14.0): serve a TCP port of the workspace at its own private HTTPS URL. A request wakes a parked or
206
+ * suspended workspace and is served once it runs.
207
+ *
208
+ * const { url } = await workspace.ports.expose(3000); // the server listens on 0.0.0.0:3000
209
+ * const { token } = await workspace.ports.token(3000); // Authorization: Bearer <token>
210
+ * const link = await workspace.ports.link(3000); // open link.url in a browser
211
+ */
212
+ get ports() {
213
+ return new WorkspacePorts(this.#ctx, this.id);
214
+ }
203
215
  /** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
204
216
  inputs() {
205
217
  return this.#ctx.workspaces.inputs(this.id);
@@ -274,6 +286,29 @@ export class Workspace {
274
286
  this.#view = (await this.#ctx.workspaces.cancelSuspendWhenIdle(this.id)).data;
275
287
  return this;
276
288
  }
289
+ /**
290
+ * Resizes this workspace (0.14.0): memory, the held floor, the allocation mode, CPU and disk, whether it is running or
291
+ * suspended, fixed or elastic, without a restart or a fork. Memory changes live on a running workspace (a shrink gives
292
+ * back what the guest frees: `memory.converged`); a suspended one gets its new size when it resumes, before its first
293
+ * call. CPU changes live up to the vCPUs the workspace booted with, beyond that at its next start. Disks grow online.
294
+ * The new caps are stored, so every later start uses them. Resolves once the resize has finished, with per resource
295
+ * when it applies (`now`, `resume` or `next_start` with the `reason`); the handle's view is refreshed. See
296
+ * WorkspacesApi.resize for the errors. A file-first workspace (no VM to resize) is refused locally with
297
+ * NotSupportedForModeError.
298
+ *
299
+ * const r = await workspace.resize({ memoryMib: 6144, cpuMillis: 4000 });
300
+ * r.memory?.applies_at; // 'now'
301
+ * r.cpu?.applies_at; // 'next_start' (reason 'boot_vcpus': more vCPUs than it booted with)
302
+ */
303
+ async resize(params) {
304
+ const refusal = this.#needsVm('resize');
305
+ if (refusal)
306
+ throw refusal;
307
+ const out = await this.#ctx.workspaces.resize(this.id, this.#tracked(params));
308
+ // The resize has happened: a failed view read leaves the old view (the next refresh() reads it again).
309
+ await this.refresh().catch(() => undefined);
310
+ return out;
311
+ }
277
312
  resume(opts = {}) {
278
313
  const refusal = this.#needsVm('resume');
279
314
  if (refusal)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/sdk",
3
- "version": "0.13.1",
3
+ "version": "0.14.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",