@shardflux/sdk 0.8.0 → 0.10.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 +258 -0
- package/README.md +338 -7
- package/dist/account.d.ts +493 -0
- package/dist/account.js +641 -0
- package/dist/cell.d.ts +203 -8
- package/dist/cell.js +457 -32
- package/dist/client.d.ts +120 -5
- package/dist/client.js +142 -6
- package/dist/errors.d.ts +90 -3
- package/dist/errors.js +93 -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 +10546 -5855
- package/dist/generated/cell-api.d.ts +501 -9
- 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 +28 -1
- package/dist/tools.js +172 -21
- package/dist/usage.d.ts +36 -6
- package/dist/usage.js +19 -4
- package/dist/version-check.d.ts +101 -0
- package/dist/version-check.js +191 -0
- package/dist/workspace.d.ts +100 -6
- package/dist/workspace.js +198 -12
- package/package.json +1 -1
package/dist/usage.d.ts
CHANGED
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
* Usage, allowances, estimates, grants and spend (Phase 9 application API).
|
|
3
3
|
*
|
|
4
4
|
* const s = await cloud.usage.summary(orgId);
|
|
5
|
-
* if (s.allowance_exhausted) ... // opens/resumes answer 402 allowance_exhausted
|
|
5
|
+
* if (s.allowance_exhausted) ... // opens/resumes answer 402 allowance_exhausted, details.reason = s.exhausted_reason
|
|
6
|
+
* s.spend_cap.state // opt-in overage (0.10.0): 'accruing', 'warning', 'reached', ...
|
|
6
7
|
*
|
|
7
8
|
* Every response carries `measurement.measured_through` (usage is complete up to it) and both raw
|
|
8
9
|
* (fractional) and billable (whole-unit) quantities. API keys read organization totals but only
|
|
9
10
|
* their own project's workspaces.
|
|
11
|
+
*
|
|
12
|
+
* Opt-in overage (0.10.0): an owner or billing member can turn on overage with a spend cap in the console. While it is
|
|
13
|
+
* on, a CPU-hours or RAM GiB-hours allowance past `included` is in cap state `overage` (starts are admitted and the
|
|
14
|
+
* usage past it is charged on the next invoice) until the charges reach the cap. `summary()`, `spend()` and
|
|
15
|
+
* `estimate()` report it as `spend_cap` (`SpendCap`: state, cap, charges, lines per allowance, projected date);
|
|
16
|
+
* `spendPolicy()` returns the settings. An API key only reads them: owners and billing members change them in the
|
|
17
|
+
* console or with a user session (`ShardfluxAccount.billing.setSpendPolicy`).
|
|
10
18
|
*/
|
|
11
|
-
import type { operations } from './generated/app-api.js';
|
|
19
|
+
import type { components, operations } from './generated/app-api.js';
|
|
12
20
|
import type { ClientContext } from './client.js';
|
|
13
21
|
type JsonOf<R> = R extends {
|
|
14
22
|
content: {
|
|
@@ -27,6 +35,13 @@ export type Grants = Ok<operations['getV1OrganizationsOrganizationIdGrants']>;
|
|
|
27
35
|
export type Spend = Ok<operations['getV1OrganizationsOrganizationIdSpend']>;
|
|
28
36
|
export type SpendPolicy = Ok<operations['getV1OrganizationsOrganizationIdSpendPolicy']>;
|
|
29
37
|
export type UsageMeter = UsageSummary['meters'][number]['meter'];
|
|
38
|
+
/**
|
|
39
|
+
* Opt-in overage this period (0.10.0), on `summary()`, `spend()` and `estimate()`: `state` (`unavailable`, `off`,
|
|
40
|
+
* `paused`, `within_allowance`, `accruing`, `warning`, `reached`), the configured and effective cap, `charges_minor`
|
|
41
|
+
* and `remaining_minor` (minor units of `currency`), `percent_of_cap`, `lines` (units past each allowance, billed units,
|
|
42
|
+
* rate and amount) and `projected_reached_at`.
|
|
43
|
+
*/
|
|
44
|
+
export type SpendCap = components['schemas']['SpendCap'];
|
|
30
45
|
export interface UsageSeriesParams {
|
|
31
46
|
/** RFC 3339; defaults to the current period's start. */
|
|
32
47
|
from?: string;
|
|
@@ -39,22 +54,37 @@ export interface UsageSeriesParams {
|
|
|
39
54
|
export declare class UsageApi {
|
|
40
55
|
#private;
|
|
41
56
|
constructor(ctx: () => ClientContext);
|
|
42
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Current-period usage per meter, allowances with enforcement and cap state (`overage` while opt-in overage covers
|
|
59
|
+
* usage past a CPU-hours or RAM GiB-hours allowance), measurement freshness, `exhausted_reason` (the 402
|
|
60
|
+
* allowance_exhausted reason while starts are refused: `allowance_used`, `overage_paused`, `spend_cap_reached`) and
|
|
61
|
+
* `spend_cap` (0.10.0).
|
|
62
|
+
*/
|
|
43
63
|
summary(organizationId: string): Promise<UsageSummary>;
|
|
44
64
|
/** Time series from the ledger (hour: <= 31 days, day: <= 400 days per request). */
|
|
45
65
|
series(organizationId: string, params?: UsageSeriesParams): Promise<UsageSeries>;
|
|
46
66
|
/** Usage of one workspace. */
|
|
47
67
|
workspace(workspaceId: string, params?: Omit<UsageSeriesParams, 'workspaceId' | 'projectId'>): Promise<UsageSeries>;
|
|
48
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Subscription fee, overage charges so far (`usage_charges_minor`, per meter in `usage_lines`), the estimated total,
|
|
70
|
+
* projected allowance use and `spend_cap` (0.10.0). Charges are 0 while overage is off or not on the plan.
|
|
71
|
+
*/
|
|
49
72
|
estimate(organizationId: string): Promise<UsageEstimate>;
|
|
50
73
|
/** Quotas, per-workspace ceilings/reservations/grants and the compute budget leases granted to the cell. */
|
|
51
74
|
grants(organizationId: string, params?: {
|
|
52
75
|
limit?: number;
|
|
53
76
|
cursor?: string;
|
|
54
77
|
}): Promise<Grants>;
|
|
55
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Spend policy, overage charges this period (`usage_charges_minor`), cap state (`overage` past an allowance under the
|
|
80
|
+
* cap), `exhausted_reason`, enforcement (leases, overshoot bound) and `spend_cap` (0.10.0).
|
|
81
|
+
*/
|
|
56
82
|
spend(organizationId: string): Promise<Spend>;
|
|
57
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Usage alert thresholds and the overage settings (0.10.0): `overage_available`, `overage_enabled`, `overage_state`
|
|
85
|
+
* (`unavailable`, `off`, `on`, `paused`), `spend_cap_minor` with its bounds, `rates` and `currency`. Read-only for API
|
|
86
|
+
* keys: owners and billing members change them in the console or with `ShardfluxAccount.billing.setSpendPolicy`.
|
|
87
|
+
*/
|
|
58
88
|
spendPolicy(organizationId: string): Promise<SpendPolicy>;
|
|
59
89
|
}
|
|
60
90
|
export {};
|
package/dist/usage.js
CHANGED
|
@@ -7,7 +7,12 @@ export class UsageApi {
|
|
|
7
7
|
const c = this.#ctx();
|
|
8
8
|
return c.http.json('GET', path, query ? { query } : {}, c.authorization);
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Current-period usage per meter, allowances with enforcement and cap state (`overage` while opt-in overage covers
|
|
12
|
+
* usage past a CPU-hours or RAM GiB-hours allowance), measurement freshness, `exhausted_reason` (the 402
|
|
13
|
+
* allowance_exhausted reason while starts are refused: `allowance_used`, `overage_paused`, `spend_cap_reached`) and
|
|
14
|
+
* `spend_cap` (0.10.0).
|
|
15
|
+
*/
|
|
11
16
|
summary(organizationId) {
|
|
12
17
|
return this.#get(`/v1/organizations/${encodeURIComponent(organizationId)}/usage/summary`);
|
|
13
18
|
}
|
|
@@ -26,7 +31,10 @@ export class UsageApi {
|
|
|
26
31
|
workspace(workspaceId, params = {}) {
|
|
27
32
|
return this.#get(`/v1/workspaces/${encodeURIComponent(workspaceId)}/usage`, { from: params.from, to: params.to, granularity: params.granularity, meter: params.meter });
|
|
28
33
|
}
|
|
29
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Subscription fee, overage charges so far (`usage_charges_minor`, per meter in `usage_lines`), the estimated total,
|
|
36
|
+
* projected allowance use and `spend_cap` (0.10.0). Charges are 0 while overage is off or not on the plan.
|
|
37
|
+
*/
|
|
30
38
|
estimate(organizationId) {
|
|
31
39
|
return this.#get(`/v1/organizations/${encodeURIComponent(organizationId)}/usage/estimate`);
|
|
32
40
|
}
|
|
@@ -34,11 +42,18 @@ export class UsageApi {
|
|
|
34
42
|
grants(organizationId, params = {}) {
|
|
35
43
|
return this.#get(`/v1/organizations/${encodeURIComponent(organizationId)}/grants`, { limit: params.limit, cursor: params.cursor });
|
|
36
44
|
}
|
|
37
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Spend policy, overage charges this period (`usage_charges_minor`), cap state (`overage` past an allowance under the
|
|
47
|
+
* cap), `exhausted_reason`, enforcement (leases, overshoot bound) and `spend_cap` (0.10.0).
|
|
48
|
+
*/
|
|
38
49
|
spend(organizationId) {
|
|
39
50
|
return this.#get(`/v1/organizations/${encodeURIComponent(organizationId)}/spend`);
|
|
40
51
|
}
|
|
41
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Usage alert thresholds and the overage settings (0.10.0): `overage_available`, `overage_enabled`, `overage_state`
|
|
54
|
+
* (`unavailable`, `off`, `on`, `paused`), `spend_cap_minor` with its bounds, `rates` and `currency`. Read-only for API
|
|
55
|
+
* keys: owners and billing members change them in the console or with `ShardfluxAccount.billing.setSpendPolicy`.
|
|
56
|
+
*/
|
|
42
57
|
spendPolicy(organizationId) {
|
|
43
58
|
return this.#get(`/v1/organizations/${encodeURIComponent(organizationId)}/spend-policy`);
|
|
44
59
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client version check (contracts §30.4; 0.9.0+). `GET /v1/client-versions` (unauthenticated) lists every published
|
|
3
|
+
* client with its `latest` and `minimum_supported` version.
|
|
4
|
+
*
|
|
5
|
+
* const s = await checkClientVersion(); // @shardflux/sdk at SDK_VERSION
|
|
6
|
+
* if (s.status !== 'current') console.error(s.message ?? s.status);
|
|
7
|
+
*
|
|
8
|
+
* Automatically, the first successful API response of a `Shardflux` or `ShardfluxAccount` client starts the same check
|
|
9
|
+
* in the background, once per process per package identity (one request, 3 s timeout, every error swallowed), and
|
|
10
|
+
* emits `process.emitWarning(message, { type: 'ShardfluxUpdateWarning', code: 'SHARDFLUX_UPDATE_AVAILABLE' })` when
|
|
11
|
+
* the package is outdated or unsupported. Opt out with the option `versionCheck: false`, or the environment
|
|
12
|
+
* `SHARDFLUX_NO_UPDATE_CHECK=1` (also `true`, `yes`, `on`) or `NO_UPDATE_NOTIFIER=1`. Tools built on the SDK pass their
|
|
13
|
+
* own identity (`versionCheck: { package, version }`) or `false`.
|
|
14
|
+
*/
|
|
15
|
+
import type { operations } from './generated/app-api.js';
|
|
16
|
+
type JsonOf<R> = R extends {
|
|
17
|
+
content: {
|
|
18
|
+
'application/json': infer T;
|
|
19
|
+
};
|
|
20
|
+
} ? T : never;
|
|
21
|
+
type Ok<Op> = Op extends {
|
|
22
|
+
responses: infer R;
|
|
23
|
+
} ? {
|
|
24
|
+
[K in keyof R]: K extends 200 | 201 | 202 ? JsonOf<R[K]> : never;
|
|
25
|
+
}[keyof R] : never;
|
|
26
|
+
/** The body of GET /v1/client-versions. */
|
|
27
|
+
export type ClientVersions = Ok<operations['getV1ClientVersions']>;
|
|
28
|
+
export type ClientVersionEntry = ClientVersions['clients'][number];
|
|
29
|
+
export type ClientEcosystem = ClientVersionEntry['ecosystem'];
|
|
30
|
+
export declare const DEFAULT_BASE_URL = "https://api.shardflux.dev";
|
|
31
|
+
export declare const SDK_PACKAGE = "@shardflux/sdk";
|
|
32
|
+
/** The check's request timeout (ms). */
|
|
33
|
+
export declare const VERSION_CHECK_TIMEOUT_MS = 3000;
|
|
34
|
+
/**
|
|
35
|
+
* `unsupported` (below `minimum_supported`), `outdated` (below `latest`), `current` (otherwise) or `unknown` (no entry
|
|
36
|
+
* for the package, `latest` null (not distributed yet), a version that does not parse, or the request failed).
|
|
37
|
+
*/
|
|
38
|
+
export type ClientVersionStatusKind = 'current' | 'outdated' | 'unsupported' | 'unknown';
|
|
39
|
+
export interface ClientVersionStatus {
|
|
40
|
+
status: ClientVersionStatusKind;
|
|
41
|
+
package: string;
|
|
42
|
+
ecosystem: ClientEcosystem;
|
|
43
|
+
/** The version this process runs. */
|
|
44
|
+
current: string;
|
|
45
|
+
latest: string | null;
|
|
46
|
+
minimumSupported: string | null;
|
|
47
|
+
upgradeCommand: string | null;
|
|
48
|
+
releaseNotesUrl: string | null;
|
|
49
|
+
/** The one-line notice, for `outdated` and `unsupported` only. */
|
|
50
|
+
message?: string;
|
|
51
|
+
}
|
|
52
|
+
/** A tool's own identity for the check (a CLI or server built on the SDK). */
|
|
53
|
+
export interface VersionCheckIdentity {
|
|
54
|
+
package: string;
|
|
55
|
+
version: string;
|
|
56
|
+
/** Default `npm`. */
|
|
57
|
+
ecosystem?: ClientEcosystem;
|
|
58
|
+
}
|
|
59
|
+
/** `true` (default): `@shardflux/sdk` at SDK_VERSION; `false`: no automatic check; or the tool's own identity. */
|
|
60
|
+
export type VersionCheckOption = boolean | VersionCheckIdentity;
|
|
61
|
+
export interface CheckClientVersionOptions {
|
|
62
|
+
/** Default https://api.shardflux.dev. */
|
|
63
|
+
baseUrl?: string;
|
|
64
|
+
/** Default: the SDK's default fetch. */
|
|
65
|
+
fetch?: typeof fetch;
|
|
66
|
+
/** Default `@shardflux/sdk`. */
|
|
67
|
+
package?: string;
|
|
68
|
+
/** Default SDK_VERSION (when `package` is `@shardflux/sdk`). */
|
|
69
|
+
version?: string;
|
|
70
|
+
/** Default `npm`. */
|
|
71
|
+
ecosystem?: ClientEcosystem;
|
|
72
|
+
/** Default 3000 ms. */
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
userAgent?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* -1, 0 or 1 as `a` is older than, the same as, or newer than `b`: `major.minor.patch` compare numerically, a
|
|
79
|
+
* pre-release (`0.8.0-rc.1`) sorts before its release, build metadata (`+local`) is ignored. `null` when either string
|
|
80
|
+
* is not such a version (the check then reports `unknown`).
|
|
81
|
+
*/
|
|
82
|
+
export declare function compareVersions(a: string, b: string): -1 | 0 | 1 | null;
|
|
83
|
+
/** The status of `package` at `version` from a GET /v1/client-versions body (no request). */
|
|
84
|
+
export declare function clientVersionStatus(body: unknown, identity: VersionCheckIdentity): ClientVersionStatus;
|
|
85
|
+
/**
|
|
86
|
+
* Asks the API whether this client is current (GET /v1/client-versions; one request, 3 s timeout by default). Never
|
|
87
|
+
* throws: a failed request, a non-2xx answer or a body that is not the documented JSON is `unknown`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function checkClientVersion(opts?: CheckClientVersionOptions): Promise<ClientVersionStatus>;
|
|
90
|
+
/** True when the environment turns the automatic check off (SHARDFLUX_NO_UPDATE_CHECK truthy, NO_UPDATE_NOTIFIER set). */
|
|
91
|
+
export declare function versionCheckDisabledByEnv(env?: Record<string, string | undefined> | undefined): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The HttpClient hook a client installs: after its first successful response, start the check for its identity
|
|
94
|
+
* unless it already ran in this process or is turned off. Returns undefined when the option turns the check off.
|
|
95
|
+
*/
|
|
96
|
+
export declare function versionCheckHook(option: VersionCheckOption | undefined, baseUrl: string, f: typeof fetch, userAgent: string): (() => void) | undefined;
|
|
97
|
+
/** Tests: resolves when every started background check has finished. */
|
|
98
|
+
export declare function settleVersionChecks(): Promise<void>;
|
|
99
|
+
/** Tests: forget which identities were checked in this process. */
|
|
100
|
+
export declare function resetVersionChecks(): void;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { SDK_VERSION, buildUrl, defaultFetch } from "./http.js";
|
|
2
|
+
export const DEFAULT_BASE_URL = 'https://api.shardflux.dev';
|
|
3
|
+
export const SDK_PACKAGE = '@shardflux/sdk';
|
|
4
|
+
/** The check's request timeout (ms). */
|
|
5
|
+
export const VERSION_CHECK_TIMEOUT_MS = 3_000;
|
|
6
|
+
// major.minor.patch, then a SemVer pre-release (-rc.1) or a PEP 440 one (rc1, .dev0), then +build metadata (ignored).
|
|
7
|
+
// The same grammar as the Python SDK's compare_versions.
|
|
8
|
+
const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)|\.?([A-Za-z][0-9A-Za-z]*(?:\.[0-9A-Za-z]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
9
|
+
function versionKey(version) {
|
|
10
|
+
if (typeof version !== 'string')
|
|
11
|
+
return null;
|
|
12
|
+
const m = VERSION.exec(version.trim());
|
|
13
|
+
if (!m)
|
|
14
|
+
return null;
|
|
15
|
+
const core = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
16
|
+
if (!core.every(Number.isSafeInteger))
|
|
17
|
+
return null;
|
|
18
|
+
const pre = m[4] ?? m[5];
|
|
19
|
+
if (pre === undefined)
|
|
20
|
+
return { core, pre: null };
|
|
21
|
+
// Numeric runs compare as numbers and sort before alphanumeric ones; "rc10" sorts after "rc2".
|
|
22
|
+
const tokens = [];
|
|
23
|
+
for (const ident of pre.split('.')) {
|
|
24
|
+
for (const part of ident.match(/\d+|\D+/g) ?? [])
|
|
25
|
+
tokens.push(/^\d+$/.test(part) ? [0, Number(part)] : [1, part]);
|
|
26
|
+
}
|
|
27
|
+
return { core, pre: tokens };
|
|
28
|
+
}
|
|
29
|
+
const sign = (n) => (n < 0 ? -1 : n > 0 ? 1 : 0);
|
|
30
|
+
function compareKeys(a, b) {
|
|
31
|
+
for (let i = 0; i < 3; i += 1)
|
|
32
|
+
if (a.core[i] !== b.core[i])
|
|
33
|
+
return sign(a.core[i] - b.core[i]);
|
|
34
|
+
if (a.pre === null || b.pre === null)
|
|
35
|
+
return a.pre === b.pre ? 0 : a.pre === null ? 1 : -1;
|
|
36
|
+
const n = Math.min(a.pre.length, b.pre.length);
|
|
37
|
+
for (let i = 0; i < n; i += 1) {
|
|
38
|
+
const [ka, va] = a.pre[i];
|
|
39
|
+
const [kb, vb] = b.pre[i];
|
|
40
|
+
if (ka !== kb)
|
|
41
|
+
return ka < kb ? -1 : 1;
|
|
42
|
+
if (va !== vb)
|
|
43
|
+
return va < vb ? -1 : 1;
|
|
44
|
+
}
|
|
45
|
+
return sign(a.pre.length - b.pre.length);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* -1, 0 or 1 as `a` is older than, the same as, or newer than `b`: `major.minor.patch` compare numerically, a
|
|
49
|
+
* pre-release (`0.8.0-rc.1`) sorts before its release, build metadata (`+local`) is ignored. `null` when either string
|
|
50
|
+
* is not such a version (the check then reports `unknown`).
|
|
51
|
+
*/
|
|
52
|
+
export function compareVersions(a, b) {
|
|
53
|
+
const ka = versionKey(a);
|
|
54
|
+
const kb = versionKey(b);
|
|
55
|
+
return ka && kb ? compareKeys(ka, kb) : null;
|
|
56
|
+
}
|
|
57
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : null);
|
|
58
|
+
/** The status of `package` at `version` from a GET /v1/client-versions body (no request). */
|
|
59
|
+
export function clientVersionStatus(body, identity) {
|
|
60
|
+
const ecosystem = identity.ecosystem ?? 'npm';
|
|
61
|
+
const base = {
|
|
62
|
+
status: 'unknown',
|
|
63
|
+
package: identity.package,
|
|
64
|
+
ecosystem,
|
|
65
|
+
current: identity.version,
|
|
66
|
+
latest: null,
|
|
67
|
+
minimumSupported: null,
|
|
68
|
+
upgradeCommand: null,
|
|
69
|
+
releaseNotesUrl: null,
|
|
70
|
+
};
|
|
71
|
+
const clients = typeof body === 'object' && body !== null ? body.clients : undefined;
|
|
72
|
+
const entry = Array.isArray(clients)
|
|
73
|
+
? clients.find((c) => typeof c === 'object' && c !== null && c.package === identity.package && c.ecosystem === ecosystem)
|
|
74
|
+
: undefined;
|
|
75
|
+
if (entry === undefined)
|
|
76
|
+
return base;
|
|
77
|
+
const latest = str(entry.latest);
|
|
78
|
+
const minimum = str(entry.minimum_supported);
|
|
79
|
+
const upgrade = str(entry.upgrade_command) ?? (ecosystem === 'npm' ? `npm install ${identity.package}@latest` : `pip install --upgrade ${identity.package}`);
|
|
80
|
+
const out = { ...base, latest, minimumSupported: minimum, upgradeCommand: upgrade, releaseNotesUrl: str(entry.release_notes_url) };
|
|
81
|
+
// latest null: not distributed yet, so stay silent (also about minimum_supported).
|
|
82
|
+
if (latest === null)
|
|
83
|
+
return out;
|
|
84
|
+
const vsLatest = compareVersions(identity.version, latest);
|
|
85
|
+
if (vsLatest === null)
|
|
86
|
+
return out;
|
|
87
|
+
const vsMinimum = minimum === null ? null : compareVersions(identity.version, minimum);
|
|
88
|
+
if (vsMinimum === -1) {
|
|
89
|
+
return { ...out, status: 'unsupported', message: `${identity.package} ${identity.version} is no longer supported by the Shardflux API (minimum ${minimum}). Update: ${upgrade}` };
|
|
90
|
+
}
|
|
91
|
+
if (vsLatest === -1)
|
|
92
|
+
return { ...out, status: 'outdated', message: `${identity.package} ${identity.version} is outdated: ${latest} is available. Update: ${upgrade}` };
|
|
93
|
+
return { ...out, status: 'current' };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Asks the API whether this client is current (GET /v1/client-versions; one request, 3 s timeout by default). Never
|
|
97
|
+
* throws: a failed request, a non-2xx answer or a body that is not the documented JSON is `unknown`.
|
|
98
|
+
*/
|
|
99
|
+
export async function checkClientVersion(opts = {}) {
|
|
100
|
+
const pkg = opts.package ?? SDK_PACKAGE;
|
|
101
|
+
const version = opts.version ?? (pkg === SDK_PACKAGE ? SDK_VERSION : undefined);
|
|
102
|
+
const identity = { package: pkg, version: version ?? '', ecosystem: opts.ecosystem ?? 'npm' };
|
|
103
|
+
// Another package without its version cannot be compared.
|
|
104
|
+
if (version === undefined)
|
|
105
|
+
return clientVersionStatus(null, identity);
|
|
106
|
+
try {
|
|
107
|
+
const f = opts.fetch ?? defaultFetch();
|
|
108
|
+
const timeout = AbortSignal.timeout(opts.timeoutMs ?? VERSION_CHECK_TIMEOUT_MS);
|
|
109
|
+
const signal = opts.signal ? AbortSignal.any([timeout, opts.signal]) : timeout;
|
|
110
|
+
const res = await f(buildUrl(opts.baseUrl ?? DEFAULT_BASE_URL, '/v1/client-versions'), {
|
|
111
|
+
method: 'GET',
|
|
112
|
+
headers: { accept: 'application/json', 'user-agent': opts.userAgent ?? `shardflux-sdk-ts/${SDK_VERSION}` },
|
|
113
|
+
signal,
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
await res.body?.cancel().catch(() => undefined);
|
|
117
|
+
return clientVersionStatus(null, identity);
|
|
118
|
+
}
|
|
119
|
+
return clientVersionStatus(JSON.parse(await res.text()), identity);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return clientVersionStatus(null, identity);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
126
|
+
/** True when the environment turns the automatic check off (SHARDFLUX_NO_UPDATE_CHECK truthy, NO_UPDATE_NOTIFIER set). */
|
|
127
|
+
export function versionCheckDisabledByEnv(env = globalThis.process?.env) {
|
|
128
|
+
if (!env)
|
|
129
|
+
return false;
|
|
130
|
+
const own = env.SHARDFLUX_NO_UPDATE_CHECK;
|
|
131
|
+
if (own !== undefined && TRUTHY.has(own.trim().toLowerCase()))
|
|
132
|
+
return true;
|
|
133
|
+
const npm = env.NO_UPDATE_NOTIFIER;
|
|
134
|
+
return npm !== undefined && npm !== '';
|
|
135
|
+
}
|
|
136
|
+
/** Package identities already checked (or being checked) in this process. */
|
|
137
|
+
const started = new Set();
|
|
138
|
+
const inFlight = new Set();
|
|
139
|
+
function emitUpdateWarning(message) {
|
|
140
|
+
const p = globalThis.process;
|
|
141
|
+
if (p && typeof p.emitWarning === 'function')
|
|
142
|
+
p.emitWarning(message, { type: 'ShardfluxUpdateWarning', code: 'SHARDFLUX_UPDATE_AVAILABLE' });
|
|
143
|
+
else
|
|
144
|
+
console.warn(message);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The HttpClient hook a client installs: after its first successful response, start the check for its identity
|
|
148
|
+
* unless it already ran in this process or is turned off. Returns undefined when the option turns the check off.
|
|
149
|
+
*/
|
|
150
|
+
export function versionCheckHook(option, baseUrl, f, userAgent) {
|
|
151
|
+
if (option === false)
|
|
152
|
+
return undefined;
|
|
153
|
+
const identity = option === undefined || option === true ? { package: SDK_PACKAGE, version: SDK_VERSION } : option;
|
|
154
|
+
let fired = false;
|
|
155
|
+
return () => {
|
|
156
|
+
if (fired)
|
|
157
|
+
return;
|
|
158
|
+
fired = true;
|
|
159
|
+
startVersionCheck(identity, baseUrl, f, userAgent);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function startVersionCheck(identity, baseUrl, f, userAgent) {
|
|
163
|
+
try {
|
|
164
|
+
if (versionCheckDisabledByEnv())
|
|
165
|
+
return;
|
|
166
|
+
const key = `${identity.ecosystem ?? 'npm'}\u0000${identity.package}\u0000${identity.version}`;
|
|
167
|
+
if (started.has(key))
|
|
168
|
+
return;
|
|
169
|
+
started.add(key);
|
|
170
|
+
const run = checkClientVersion({ baseUrl, fetch: f, userAgent, package: identity.package, version: identity.version, ecosystem: identity.ecosystem ?? 'npm' })
|
|
171
|
+
.then((s) => {
|
|
172
|
+
if ((s.status === 'outdated' || s.status === 'unsupported') && s.message)
|
|
173
|
+
emitUpdateWarning(s.message);
|
|
174
|
+
})
|
|
175
|
+
.catch(() => undefined);
|
|
176
|
+
inFlight.add(run);
|
|
177
|
+
void run.finally(() => inFlight.delete(run));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Never fails the caller.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Tests: resolves when every started background check has finished. */
|
|
184
|
+
export async function settleVersionChecks() {
|
|
185
|
+
while (inFlight.size > 0)
|
|
186
|
+
await Promise.all([...inFlight]);
|
|
187
|
+
}
|
|
188
|
+
/** Tests: forget which identities were checked in this process. */
|
|
189
|
+
export function resetVersionChecks() {
|
|
190
|
+
started.clear();
|
|
191
|
+
}
|
package/dist/workspace.d.ts
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
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, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
|
|
5
|
+
import type { ClientContext, 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
|
-
import type {
|
|
9
|
+
import type { WorkspaceMode } from './errors.js';
|
|
10
|
+
import type { FinishedOperation, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
|
|
10
11
|
import { Trace } from './progress.js';
|
|
11
12
|
import type { LifecycleTiming, ProgressListener } from './progress.js';
|
|
12
13
|
import { WorkspaceSecrets } from './secrets.js';
|
|
13
|
-
import type { CellClientOptions, WorkspaceChangesPage, WorkspaceChangesParams } from './cell.js';
|
|
14
|
+
import type { CellClientOptions, Residency, WorkspaceChangesPage, WorkspaceChangesParams } from './cell.js';
|
|
14
15
|
import type { SaveAsTemplateParams, SaveAsTemplateResponse, WorkspaceStartup } from './templates.js';
|
|
15
16
|
import { ToolTokenManager } from './tokens.js';
|
|
16
17
|
import type { ToolName, ToolToken } from './tokens.js';
|
|
@@ -20,6 +21,38 @@ export interface WakeOptions {
|
|
|
20
21
|
signal?: AbortSignal;
|
|
21
22
|
/** Progress of the wake: the resume request, observed states, conflicts retried, and `done` with the timing. */
|
|
22
23
|
onProgress?: ProgressListener;
|
|
24
|
+
/**
|
|
25
|
+
* The tool token the wake brings back (0.9.0): the held resume (contracts §22.6) returns one with the running
|
|
26
|
+
* workspace, for this agent label and tool set (defaults: those given to open()). It is kept for `cell()` clients of
|
|
27
|
+
* the same label and tools, whose next call then needs no token request. A `cell()` client's own wake passes its
|
|
28
|
+
* label and tools.
|
|
29
|
+
*/
|
|
30
|
+
agentLabel?: string;
|
|
31
|
+
tools?: ToolName[];
|
|
32
|
+
}
|
|
33
|
+
export interface HintOptions {
|
|
34
|
+
/** The tool token to use: the agent label and tool set of `cell()` (defaults: those given to open()). */
|
|
35
|
+
agentLabel?: string;
|
|
36
|
+
tools?: ToolName[];
|
|
37
|
+
/**
|
|
38
|
+
* When the workspace is not running: how to wake it in the background. Default `workspace.wake()`; `null` does not
|
|
39
|
+
* wake it; a function replaces the wake (the same hook as `CellClientOptions.wake`).
|
|
40
|
+
*/
|
|
41
|
+
wake?: CellClientOptions['wake'];
|
|
42
|
+
/** Bound on the background wake (default 120 000 ms). */
|
|
43
|
+
wakeTimeoutMs?: number;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
/** `workspace.hint()`: what the host found, or the background wake of a workspace that was not running. */
|
|
47
|
+
export interface HintResult {
|
|
48
|
+
/** The VM's residency when the hint arrived (contracts §25.1); null when the workspace was not running. */
|
|
49
|
+
residency: Residency | null;
|
|
50
|
+
/**
|
|
51
|
+
* The wake started in the background because the workspace was not running (409 `workspace_not_running`), else null.
|
|
52
|
+
* It resolves like `wake()`; nothing needs to await it (a failure is then left to the next tool call, which wakes
|
|
53
|
+
* the workspace itself), and concurrent hints share one wake.
|
|
54
|
+
*/
|
|
55
|
+
wake: Promise<boolean> | null;
|
|
23
56
|
}
|
|
24
57
|
export declare class Workspace {
|
|
25
58
|
#private;
|
|
@@ -67,8 +100,35 @@ export declare class Workspace {
|
|
|
67
100
|
* an older API. A failed startup leaves the workspace running for inspection; the next open runs the failed step again.
|
|
68
101
|
*/
|
|
69
102
|
get startup(): WorkspaceStartup | null;
|
|
103
|
+
/**
|
|
104
|
+
* The pending suspend-when-idle request as of the last view (0.10.0; `idle.suspend_request`): `{requested_at,
|
|
105
|
+
* after_seconds, not_before}`, or null when there is none, when the workspace is not running, or once a tool call or
|
|
106
|
+
* a resume after the request cancelled it. `refresh()` reads it again.
|
|
107
|
+
*/
|
|
108
|
+
get suspendRequest(): SuspendRequest | null;
|
|
70
109
|
/** The raw view (GET /v1/workspaces/{id}). */
|
|
71
110
|
get data(): WorkspaceView;
|
|
111
|
+
/**
|
|
112
|
+
* `processful` (one VM keeps processes, memory and files) or `file_first` (0.9.0; contracts §29: a versioned file
|
|
113
|
+
* tree, commands run as executions). Immutable. A view without the field (older API) is processful.
|
|
114
|
+
*/
|
|
115
|
+
get mode(): WorkspaceMode;
|
|
116
|
+
/**
|
|
117
|
+
* File-first workspaces: the newest tree revision this handle has seen (0 when created; each mutating files call or
|
|
118
|
+
* execution that changed something publishes the next). It follows every response of this handle's cell clients
|
|
119
|
+
* (`X-Tree-Revision`), execution results and refresh(); another writer's changes appear once a response reports
|
|
120
|
+
* them. Pass it as `ifTreeRevision` to make a write conditional. Null for processful workspaces.
|
|
121
|
+
*/
|
|
122
|
+
get treeRevision(): number | null;
|
|
123
|
+
/**
|
|
124
|
+
* Executions of this file-first workspace (contracts §29.8) through `cell()`'s default client: `run(argv, opts)` runs
|
|
125
|
+
* a command in a fresh VM on the latest tree and returns an ExecutionResult (output, exit code, `changed`,
|
|
126
|
+
* `treeRevision`); `get(id, { waitMs })` reads one. See CellClient.executions.
|
|
127
|
+
*
|
|
128
|
+
* const r = await workspace.executions.run(['bash', '-lc', 'pytest -q'], { timeoutMs: 600_000 });
|
|
129
|
+
* console.log(r.exitCode, r.stdoutText, r.changed, r.treeRevision);
|
|
130
|
+
*/
|
|
131
|
+
get executions(): CellClient['executions'];
|
|
72
132
|
/** Secret names bound to this workspace (injected into every exec/PTY start): `get()`, `set(names)`. */
|
|
73
133
|
get secrets(): WorkspaceSecrets;
|
|
74
134
|
/** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
|
|
@@ -91,12 +151,31 @@ export declare class Workspace {
|
|
|
91
151
|
*/
|
|
92
152
|
suspend(opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
93
153
|
suspend(opts?: LifecycleOptions): Promise<Operation>;
|
|
154
|
+
/**
|
|
155
|
+
* Suspends the workspace once it has been idle for `afterSeconds` (30..3600; 0.9.0): call it when your agent's turn
|
|
156
|
+
* ends, so the workspace stops using RAM soon after instead of waiting out its idle policy. A command still running,
|
|
157
|
+
* an attached stream or a keepalive postpones the suspend until `afterSeconds` after it ends; the next tool call (the
|
|
158
|
+
* next turn) or a resume cancels it. Resolves with the recorded `suspendRequest` (also `workspace.suspendRequest`), or
|
|
159
|
+
* with the suspend already in progress as `operation`. See WorkspacesApi.suspendWhenIdle for the errors. A file-first
|
|
160
|
+
* workspace (never suspended) is refused locally with NotSupportedForModeError.
|
|
161
|
+
*
|
|
162
|
+
* const { suspendRequest } = await workspace.suspendWhenIdle({ afterSeconds: 60 });
|
|
163
|
+
*/
|
|
164
|
+
suspendWhenIdle(opts: SuspendWhenIdleOptions): Promise<SuspendWhenIdleResult>;
|
|
165
|
+
/**
|
|
166
|
+
* Cancels a pending suspend-when-idle request (0.10.0; idempotent, in any state) and refreshes this handle's view.
|
|
167
|
+
* A suspend the request already started is not undone (it shows as `activeOperation`).
|
|
168
|
+
*/
|
|
169
|
+
cancelSuspendWhenIdle(): Promise<this>;
|
|
94
170
|
/**
|
|
95
171
|
* Resumes a suspended workspace. Resolves when the resume is REQUESTED; with `{ wait: true }`, once the workspace runs.
|
|
96
|
-
* Tool calls wake a suspended workspace by themselves, so this is rarely needed.
|
|
172
|
+
* Tool calls wake a suspended workspace by themselves, so this is rarely needed. With `wait` (0.9.0) it is one held
|
|
173
|
+
* request (contracts §22.6): the handle takes the running view and a tool token for `agentLabel`/`tools` (defaults:
|
|
174
|
+
* those given to open()), so `cell()` calls with that label and tools start at once. A workspace that is already
|
|
175
|
+
* running is ShardfluxApiError 409 `conflict` (`already_running`).
|
|
97
176
|
*/
|
|
98
|
-
resume(opts:
|
|
99
|
-
resume(opts?:
|
|
177
|
+
resume(opts: WaitedResumeOptions): Promise<FinishedOperation>;
|
|
178
|
+
resume(opts?: ResumeOptions): Promise<Operation>;
|
|
100
179
|
/** Takes a snapshot. Resolves when it is REQUESTED; with `{ wait: true }`, once it is taken. */
|
|
101
180
|
snapshot(opts: WaitedLifecycleOptions & {
|
|
102
181
|
label?: string;
|
|
@@ -151,6 +230,7 @@ export declare class Workspace {
|
|
|
151
230
|
/**
|
|
152
231
|
* The workspace's changes against its template (cell gateway GET /v1/workspaces/{id}/changes; needs the `files` tool).
|
|
153
232
|
* 409 workspace_not_running, or conflict with details.reason legacy_disk_layout / guest_feature_unavailable.
|
|
233
|
+
* File-first workspaces: NotSupportedForModeError (each execution result lists what it changed).
|
|
154
234
|
*/
|
|
155
235
|
changes(opts?: WorkspaceChangesParams & {
|
|
156
236
|
agentLabel?: string;
|
|
@@ -180,8 +260,22 @@ export declare class Workspace {
|
|
|
180
260
|
* operation_in_progress) at once. Cell calls use it automatically when they meet `workspace_not_running`. A resume no
|
|
181
261
|
* host admits within 15 minutes fails with `capacity_unavailable` (OperationFailedError, `retryable`: the workspace
|
|
182
262
|
* stays suspended with its state; try again later).
|
|
263
|
+
*
|
|
264
|
+
* Since 0.9.0 the resume is held by the server until the workspace runs (contracts §22.6): one request returns the
|
|
265
|
+
* running workspace and a tool token for `agentLabel`/`tools`, which this handle keeps, so a tool call that woke the
|
|
266
|
+
* workspace is retried at once (refused call, resume, call). Timing: one `request` phase with reason `held`. A server
|
|
267
|
+
* that does not hold the request answers at once; the wake then waits for the operation and reads the view.
|
|
183
268
|
*/
|
|
184
269
|
wake(opts?: WakeOptions): Promise<boolean>;
|
|
270
|
+
/**
|
|
271
|
+
* Announces an imminent tool call (cell `POST /wake-hint`, contracts §26.6; 0.9.0+) so a parked workspace is restored
|
|
272
|
+
* ahead of it: call it when the model starts emitting a tool call, before its arguments are complete. The agent tools
|
|
273
|
+
* of `workspaceTools()` send it when a call starts. Cheap and best effort: it returns what the host found. A workspace
|
|
274
|
+
* that is not running (suspended, or a token cannot be issued for it) is woken in the background (`wake`, not awaited
|
|
275
|
+
* here) so the call finds it running sooner. Other errors (network, auth) are thrown; callers that fire and forget
|
|
276
|
+
* should catch them.
|
|
277
|
+
*/
|
|
278
|
+
hint(opts?: HintOptions): Promise<HintResult>;
|
|
185
279
|
/** Tools granted by the most recent token (null before one was issued). */
|
|
186
280
|
get grantedTools(): ToolName[] | null;
|
|
187
281
|
}
|