unfazed 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/oidc.js ADDED
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Workload identity: OIDC tokens the platform issues to an app.
3
+ *
4
+ * An app running on unfazed (a service or a cron job) asks the control plane
5
+ * for a short-lived JWT that names it, for one audience, and presents it to a
6
+ * service that trusts the issuer `https://unfazed.dev/oidc` instead of a
7
+ * static API key. The only credential is the app's own `UNFZ_STATE_TOKEN`,
8
+ * which the platform puts into each of its VMs; no `UNFZ_API_TOKEN` is
9
+ * involved.
10
+ *
11
+ * ```ts
12
+ * import { oidcToken } from "unfazed";
13
+ * const jwt = await oidcToken("https://api.example.com");
14
+ * ```
15
+ *
16
+ * Wire contract: `POST <platform>/oidc/token` with
17
+ * `Authorization: Bearer $UNFZ_STATE_TOKEN` and `{ audience, ttl_seconds? }`;
18
+ * 200 is `{ token, expires_at, token_type: "Bearer" }`, an error
19
+ * `{ error, error_description }`.
20
+ */
21
+ import { DEFAULT_ENDPOINT, VERSION, configuredEndpoint, platformBaseUrl, processEnv, untilAborted, } from "./client.js";
22
+ import { UnfazedConfigError, UnfazedOIDCError, UnfazedTimeoutError } from "./errors.js";
23
+ /** Environment variable holding the app's state token, set in every unfazed service and cron VM. */
24
+ export const STATE_TOKEN_ENV_VAR = "UNFZ_STATE_TOKEN";
25
+ /** Path of the token endpoint under the platform origin. */
26
+ export const OIDC_TOKEN_PATH = "/oidc/token";
27
+ /** A cached token is used until this share of its lifetime has passed. */
28
+ const REFRESH_AT = 0.8;
29
+ /** Retries after the first attempt, for 503s, 429s and network failures. */
30
+ const MAX_RETRIES = 3;
31
+ /** First retry delay; doubles per retry (200, 400, 800 ms). */
32
+ const BACKOFF_MS = 200;
33
+ /** The longest `Retry-After` waited out; a longer one fails at once, carrying it. */
34
+ const MAX_RETRY_AFTER_MS = 10_000;
35
+ const MIN_TTL_SECONDS = 60;
36
+ const MAX_TTL_SECONDS = 3600;
37
+ /** One attempt's timeout. */
38
+ const DEFAULT_TIMEOUT_MS = 10_000;
39
+ /** 1 to 256 printable ASCII characters, none of them whitespace. */
40
+ const AUDIENCE = /^[\x21-\x7e]{1,256}$/;
41
+ /**
42
+ * One cache per process, not per copy of this module (a framework can bundle
43
+ * the package into several server layers) nor per `Unfazed` instance (a
44
+ * request handler that builds one per request must not ask for a token per
45
+ * request). Keys carry the token URL and the state token, so instances
46
+ * configured differently never share a token.
47
+ */
48
+ const STORE_KEY = Symbol.for("unfazed//oidc/v1");
49
+ function sharedStore() {
50
+ const g = globalThis;
51
+ return (g[STORE_KEY] ??= newStore());
52
+ }
53
+ function newStore() {
54
+ return { cache: new Map(), inflight: new Map() };
55
+ }
56
+ /**
57
+ * Workload identity tokens for the app this code runs in: `unfazed.oidc` on an
58
+ * {@link import("./unfazed.js").Unfazed} instance, or the standalone `oidc`.
59
+ *
60
+ * Tokens are cached per (audience, ttl) and reused until 80% of their
61
+ * lifetime has passed; concurrent calls for the same pair share one request.
62
+ * 503 (and a gateway's 502/504), 429 and network failures are retried up to 3
63
+ * times (200, 400, 800 ms; a 429's or 503's `Retry-After` up to 10 s is waited
64
+ * out instead); no other 4xx is retried. When a refresh fails for one of
65
+ * those passing reasons, the token it was replacing is returned while it is
66
+ * still valid.
67
+ */
68
+ export class UnfazedOIDC {
69
+ #options;
70
+ #store;
71
+ constructor(options = {}) {
72
+ this.#options = { ...options };
73
+ this.#store = options.fetch || options.now ? newStore() : undefined;
74
+ }
75
+ /**
76
+ * A JWT for `audience`, from the cache while it is fresh.
77
+ *
78
+ * ```ts
79
+ * const jwt = await unfazed.oidc.token("https://api.example.com", { ttlSeconds: 300 });
80
+ * ```
81
+ *
82
+ * Throws {@link UnfazedOIDCError}: `not_in_app` without `UNFZ_STATE_TOKEN`,
83
+ * `invalid_audience`, `invalid_request` (`ttlSeconds`), or the platform's
84
+ * code.
85
+ */
86
+ async token(audience, options = {}) {
87
+ return (await this.tokenResponse(audience, options)).token;
88
+ }
89
+ /** {@link token}, with the token's expiry: `{ token, expiresAt }`. */
90
+ async tokenResponse(audience, options = {}) {
91
+ checkAudience(audience);
92
+ const ttl = options.ttlSeconds;
93
+ checkTtl(ttl);
94
+ const { signal } = options;
95
+ if (signal?.aborted)
96
+ throw aborted();
97
+ const stateToken = this.#stateToken();
98
+ const url = this.#tokenUrl();
99
+ const key = `${url}\n${stateToken}\n${audience}\n${ttl ?? ""}`;
100
+ const store = this.#store ?? sharedStore();
101
+ const cached = store.cache.get(key);
102
+ if (cached && this.#now() < cached.refreshAt)
103
+ return view(cached);
104
+ let pending = store.inflight.get(key);
105
+ if (!pending) {
106
+ pending = this.#issue(url, stateToken, audience, ttl)
107
+ .then((entry) => {
108
+ store.cache.set(key, entry);
109
+ return entry;
110
+ }, (error) => {
111
+ // The control plane restarting must not take down an app whose
112
+ // token is still good: fall back to the token being replaced.
113
+ const previous = store.cache.get(key);
114
+ if (previous && error instanceof UnfazedOIDCError && error.retryable && this.#now() < previous.validUntil) {
115
+ return previous;
116
+ }
117
+ throw error;
118
+ })
119
+ .finally(() => store.inflight.delete(key));
120
+ // Every caller may stop waiting (its signal); the request then ends
121
+ // with nobody listening, which must not be an unhandled rejection.
122
+ pending.catch(() => { });
123
+ store.inflight.set(key, pending);
124
+ }
125
+ return view(await untilAborted(pending, signal).catch(rethrowAbort));
126
+ }
127
+ /**
128
+ * A `fetch` that sends every request with `Authorization: Bearer <token>`,
129
+ * a fresh token for `audience` each time (from the cache while it lasts).
130
+ * Every other header, the body and the signal pass through; an
131
+ * `Authorization` already there is replaced.
132
+ *
133
+ * ```ts
134
+ * const openai = new OpenAI({
135
+ * baseURL: process.env.PRIVATEINFERENCE_BASE_URL,
136
+ * apiKey: "unused",
137
+ * fetch: unfazed.oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),
138
+ * });
139
+ * ```
140
+ *
141
+ * An invalid `audience` (an unset variable among them) throws here, at
142
+ * once; a missing `UNFZ_STATE_TOKEN` on the first request.
143
+ */
144
+ fetchWithToken(audience, options = {}) {
145
+ checkAudience(audience);
146
+ const aud = audience;
147
+ const ttl = options.ttlSeconds;
148
+ checkTtl(ttl);
149
+ return async (input, init) => {
150
+ const request = typeof Request === "function" && input instanceof Request ? input : undefined;
151
+ const signal = init?.signal ?? request?.signal ?? undefined;
152
+ const token = await this.token(aud, {
153
+ ...(ttl === undefined ? {} : { ttlSeconds: ttl }),
154
+ ...(signal ? { signal } : {}),
155
+ });
156
+ // init.headers replace a Request's own, as fetch() itself does.
157
+ const headers = new Headers(init?.headers ?? request?.headers);
158
+ headers.set("Authorization", `Bearer ${token}`);
159
+ const send = options.fetch ?? globalFetch();
160
+ return send(input, { ...init, headers });
161
+ };
162
+ }
163
+ #now() {
164
+ return (this.#options.now ?? Date.now)();
165
+ }
166
+ #stateToken() {
167
+ const env = this.#options.env ?? processEnv();
168
+ const token = (this.#options.stateToken ?? env[STATE_TOKEN_ENV_VAR])?.trim();
169
+ if (!token) {
170
+ throw new UnfazedOIDCError("not_in_app", `unfazed: not running inside an unfazed app: ${STATE_TOKEN_ENV_VAR} is not set. ` +
171
+ `OIDC tokens are issued to an app's services and cron jobs, for the ${STATE_TOKEN_ENV_VAR} ` +
172
+ "the platform gives each of them (no UNFZ_API_TOKEN needed).");
173
+ }
174
+ return token;
175
+ }
176
+ #tokenUrl() {
177
+ const { endpoint, baseUrl } = this.#options;
178
+ const configured = configuredEndpoint({ ...(endpoint ? { endpoint } : {}), ...(baseUrl ? { baseUrl } : {}) }, this.#options.env ?? processEnv());
179
+ return platformBaseUrl(configured ?? DEFAULT_ENDPOINT) + OIDC_TOKEN_PATH;
180
+ }
181
+ async #issue(url, stateToken, audience, ttl) {
182
+ const transport = this.#options.fetch ?? globalFetch();
183
+ const sleep = this.#options.sleep ?? defaultSleep;
184
+ const userAgent = this.#options.userAgent ?? `unfazed-sdk/${VERSION}`;
185
+ const headers = {
186
+ "Content-Type": "application/json",
187
+ Accept: "application/json",
188
+ Authorization: `Bearer ${stateToken}`,
189
+ // The edge refuses some generic agents; always name ourselves.
190
+ ...(userAgent ? { "User-Agent": userAgent } : {}),
191
+ };
192
+ const body = JSON.stringify(ttl === undefined ? { audience } : { audience, ttl_seconds: ttl });
193
+ const what = `unfazed: no OIDC token for audience ${JSON.stringify(audience)}`;
194
+ for (let retries = 0;; retries++) {
195
+ const startedAt = this.#now();
196
+ let answer;
197
+ try {
198
+ answer = await this.#send(transport, url, headers, body);
199
+ }
200
+ catch (cause) {
201
+ if (retries < MAX_RETRIES) {
202
+ await sleep(BACKOFF_MS * 2 ** retries);
203
+ continue;
204
+ }
205
+ throw new UnfazedOIDCError("unreachable", `${what}: ${url} did not answer in ${retries + 1} attempts (${describe(cause)})`, { retryable: true, cause });
206
+ }
207
+ const { status, text, retryAfter } = answer;
208
+ if (status >= 200 && status < 300)
209
+ return this.#entry(text, startedAt, what);
210
+ const error = refusal(status, text, retryAfterMs(retryAfter, this.#now()), what);
211
+ if (!error.retryable || retries >= MAX_RETRIES)
212
+ throw error;
213
+ const waitMs = error.retryAfterMs ?? BACKOFF_MS * 2 ** retries;
214
+ if (waitMs > MAX_RETRY_AFTER_MS)
215
+ throw error;
216
+ await sleep(waitMs);
217
+ }
218
+ }
219
+ async #send(transport, url, headers, body) {
220
+ const timeoutMs = this.#options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
221
+ const controller = timeoutMs > 0 && typeof AbortController === "function" ? new AbortController() : undefined;
222
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
223
+ try {
224
+ const signal = controller?.signal;
225
+ const response = await untilAborted(transport(url, { method: "POST", headers, body, ...(signal ? { signal } : {}) }), signal);
226
+ const text = await untilAborted(response.text(), signal);
227
+ return { status: response.status, text, retryAfter: response.headers?.get("retry-after") ?? null };
228
+ }
229
+ finally {
230
+ if (timer !== undefined)
231
+ clearTimeout(timer);
232
+ }
233
+ }
234
+ #entry(text, startedAt, what) {
235
+ const body = parseJson(text);
236
+ const token = body?.token;
237
+ const expiresAtMs = typeof body?.expires_at === "string" ? Date.parse(body.expires_at) : NaN;
238
+ if (typeof token !== "string" || token === "" || !Number.isFinite(expiresAtMs)) {
239
+ throw new UnfazedOIDCError("invalid_response", `${what}: the platform answered 200 without a token and its expires_at: ${truncate(text)}`);
240
+ }
241
+ // The JWT's own iat/exp are both the platform's clock, so its lifetime
242
+ // holds even when this VM's clock is off (a guest resumed from a
243
+ // snapshot); elapsed time is then measured on this clock alone.
244
+ const lifetimeMs = jwtLifetimeMs(token) ?? expiresAtMs - startedAt;
245
+ const life = Math.max(0, lifetimeMs);
246
+ return {
247
+ token,
248
+ expiresAtMs,
249
+ refreshAt: startedAt + REFRESH_AT * life,
250
+ validUntil: startedAt + life,
251
+ };
252
+ }
253
+ }
254
+ /**
255
+ * The process's own {@link UnfazedOIDC}: `UNFZ_STATE_TOKEN` and `UNFZ_API_URL`
256
+ * read on every call, the global `fetch`, one cache.
257
+ *
258
+ * ```ts
259
+ * import { oidc } from "unfazed";
260
+ * const fetchWithToken = oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE);
261
+ * ```
262
+ */
263
+ export const oidc = /* @__PURE__ */ new UnfazedOIDC();
264
+ /**
265
+ * A JWT for `audience`, issued to the app this code runs in:
266
+ * {@link UnfazedOIDC.token} on the process's {@link oidc}. Needs only
267
+ * `UNFZ_STATE_TOKEN`.
268
+ *
269
+ * ```ts
270
+ * import { oidcToken } from "unfazed";
271
+ * const res = await fetch(url, { headers: { Authorization: `Bearer ${await oidcToken(url)}` } });
272
+ * ```
273
+ */
274
+ export function oidcToken(audience, options) {
275
+ return oidc.token(audience, options);
276
+ }
277
+ function view(entry) {
278
+ return { token: entry.token, expiresAt: new Date(entry.expiresAtMs) };
279
+ }
280
+ function checkAudience(audience) {
281
+ if (typeof audience === "string" && AUDIENCE.test(audience))
282
+ return;
283
+ const got = typeof audience === "string" ? truncate(JSON.stringify(audience), 80) : String(audience);
284
+ throw new UnfazedOIDCError("invalid_audience", `unfazed: invalid OIDC audience ${got}: it must be 1 to 256 printable ASCII characters, without whitespace`);
285
+ }
286
+ function checkTtl(ttl) {
287
+ if (ttl === undefined)
288
+ return;
289
+ if (typeof ttl === "number" && Number.isInteger(ttl) && ttl >= MIN_TTL_SECONDS && ttl <= MAX_TTL_SECONDS)
290
+ return;
291
+ throw new UnfazedOIDCError("invalid_request", `unfazed: ttlSeconds must be a whole number from ${MIN_TTL_SECONDS} to ${MAX_TTL_SECONDS} (got ${String(ttl)})`);
292
+ }
293
+ /** The error for a non-2xx answer; `retryable` says whether the loop asks again. */
294
+ function refusal(status, text, retryAfter, what) {
295
+ const body = parseJson(text);
296
+ const code = typeof body?.error === "string" && body.error ? body.error : codeForStatus(status);
297
+ const description = typeof body?.error_description === "string" && body.error_description ? body.error_description : undefined;
298
+ const detail = description ?? (body === undefined && text ? truncate(text) : undefined);
299
+ // Never a 4xx but 429: the request itself is what is wrong.
300
+ const retryable = status === 429 ||
301
+ status === 502 ||
302
+ status === 503 ||
303
+ status === 504 ||
304
+ (status >= 500 && code === "temporarily_unavailable");
305
+ const hint = code === "invalid_token" ? ` (the platform refused ${STATE_TOKEN_ENV_VAR})` : "";
306
+ const later = retryAfter === undefined ? "" : `; retry after ${Math.ceil(retryAfter / 1000)} s`;
307
+ return new UnfazedOIDCError(code, `${what}: ${code} (HTTP ${status})${hint}${detail ? `: ${detail}` : ""}${later}`, {
308
+ status,
309
+ ...(description ? { description } : {}),
310
+ retryable,
311
+ ...(retryAfter === undefined ? {} : { retryAfterMs: retryAfter }),
312
+ });
313
+ }
314
+ function codeForStatus(status) {
315
+ switch (status) {
316
+ case 400:
317
+ return "invalid_request";
318
+ case 401:
319
+ return "invalid_token";
320
+ case 429:
321
+ return "rate_limited";
322
+ case 503:
323
+ return "temporarily_unavailable";
324
+ default:
325
+ return "http_error";
326
+ }
327
+ }
328
+ /** `Retry-After` in ms: delay-seconds or an HTTP-date; undefined when absent or unreadable. */
329
+ function retryAfterMs(value, now) {
330
+ if (value === null)
331
+ return undefined;
332
+ const trimmed = value.trim();
333
+ if (/^\d+$/.test(trimmed))
334
+ return Number(trimmed) * 1000;
335
+ const at = Date.parse(trimmed);
336
+ return Number.isFinite(at) ? Math.max(0, at - now) : undefined;
337
+ }
338
+ /** `exp - iat` of a JWT, in ms, when both are there. */
339
+ function jwtLifetimeMs(token) {
340
+ const payload = token.split(".")[1];
341
+ if (!payload || typeof atob !== "function")
342
+ return undefined;
343
+ try {
344
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
345
+ const claims = JSON.parse(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "=")));
346
+ const { iat, exp } = claims;
347
+ if (typeof iat === "number" && typeof exp === "number" && Number.isFinite(iat) && exp > iat) {
348
+ return (exp - iat) * 1000;
349
+ }
350
+ }
351
+ catch {
352
+ // Not a JWT we can read: the platform's expires_at decides.
353
+ }
354
+ return undefined;
355
+ }
356
+ function globalFetch() {
357
+ const fetch = globalThis.fetch;
358
+ if (typeof fetch !== "function") {
359
+ throw new UnfazedConfigError("unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch }.");
360
+ }
361
+ return fetch.bind(globalThis);
362
+ }
363
+ function defaultSleep(ms) {
364
+ return new Promise((resolve) => setTimeout(resolve, ms));
365
+ }
366
+ function aborted() {
367
+ return new UnfazedTimeoutError("unfazed: OIDC token request aborted");
368
+ }
369
+ function rethrowAbort(error) {
370
+ if (typeof error === "object" && error !== null && error.name === "AbortError") {
371
+ throw aborted();
372
+ }
373
+ throw error;
374
+ }
375
+ function describe(cause) {
376
+ if (cause instanceof Error)
377
+ return cause.name === "AbortError" ? "timed out" : cause.message;
378
+ return String(cause);
379
+ }
380
+ function parseJson(text) {
381
+ try {
382
+ return JSON.parse(text);
383
+ }
384
+ catch {
385
+ return undefined;
386
+ }
387
+ }
388
+ function truncate(s, max = 300) {
389
+ return s.length <= max ? s : `${s.slice(0, max)}…`;
390
+ }
391
+ //# sourceMappingURL=oidc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Workload identity: OIDC tokens the platform issues to an app.\n *\n * An app running on unfazed (a service or a cron job) asks the control plane\n * for a short-lived JWT that names it, for one audience, and presents it to a\n * service that trusts the issuer `https://unfazed.dev/oidc` instead of a\n * static API key. The only credential is the app's own `UNFZ_STATE_TOKEN`,\n * which the platform puts into each of its VMs; no `UNFZ_API_TOKEN` is\n * involved.\n *\n * ```ts\n * import { oidcToken } from \"unfazed\";\n * const jwt = await oidcToken(\"https://api.example.com\");\n * ```\n *\n * Wire contract: `POST <platform>/oidc/token` with\n * `Authorization: Bearer $UNFZ_STATE_TOKEN` and `{ audience, ttl_seconds? }`;\n * 200 is `{ token, expires_at, token_type: \"Bearer\" }`, an error\n * `{ error, error_description }`.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n VERSION,\n configuredEndpoint,\n platformBaseUrl,\n processEnv,\n untilAborted,\n} from \"./client.js\";\nimport { UnfazedConfigError, UnfazedOIDCError, UnfazedTimeoutError, type OIDCErrorCode } from \"./errors.js\";\n\n/** Environment variable holding the app's state token, set in every unfazed service and cron VM. */\nexport const STATE_TOKEN_ENV_VAR = \"UNFZ_STATE_TOKEN\";\n\n/** Path of the token endpoint under the platform origin. */\nexport const OIDC_TOKEN_PATH = \"/oidc/token\";\n\n/** A cached token is used until this share of its lifetime has passed. */\nconst REFRESH_AT = 0.8;\n/** Retries after the first attempt, for 503s, 429s and network failures. */\nconst MAX_RETRIES = 3;\n/** First retry delay; doubles per retry (200, 400, 800 ms). */\nconst BACKOFF_MS = 200;\n/** The longest `Retry-After` waited out; a longer one fails at once, carrying it. */\nconst MAX_RETRY_AFTER_MS = 10_000;\nconst MIN_TTL_SECONDS = 60;\nconst MAX_TTL_SECONDS = 3600;\n/** One attempt's timeout. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n/** 1 to 256 printable ASCII characters, none of them whitespace. */\nconst AUDIENCE = /^[\\x21-\\x7e]{1,256}$/;\n\n/**\n * The `fetch` the token request goes through: the global `fetch`, or anything\n * shaped like it (the client's `FetchLike` fits). `headers` on the response\n * is read for `Retry-After` when present.\n */\nexport type OIDCTransport = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n status: number;\n headers?: { get(name: string): string | null };\n text(): Promise<string>;\n}>;\n\n/** A `fetch` as the OpenAI SDK (and most HTTP clients) take one. */\nexport type TokenFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport interface OIDCOptions {\n /** The app's state token. Default: `UNFZ_STATE_TOKEN`, read on every call. */\n stateToken?: string;\n /**\n * Full GraphQL endpoint; tokens come from its origin. Wins over `baseUrl`.\n * Falls back to `UNFZ_API_URL`, then `https://unfazed.dev` (as the client).\n */\n endpoint?: string;\n /** Platform origin; `/oidc/token` is appended. */\n baseUrl?: string;\n /** Where `UNFZ_STATE_TOKEN` and `UNFZ_API_URL` are read from. Default: `process.env`. */\n env?: Record<string, string | undefined>;\n /**\n * The transport of the token request. Default: the global `fetch`. An\n * instance with its own `fetch` (or `now`) keeps its own cache; every other\n * one shares the process's.\n */\n fetch?: OIDCTransport;\n /** Overrides the default `unfazed-sdk/<version>` User-Agent. */\n userAgent?: string;\n /** Timeout of one attempt, in ms. Default 10_000; 0 disables it. */\n timeoutMs?: number;\n /** Injected wall clock, ms (tests). */\n now?: () => number;\n /** Injected sleep between retries (tests). */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface OIDCTokenOptions {\n /** Token lifetime, a whole number of seconds from 60 to 3600. Default: the platform's. */\n ttlSeconds?: number;\n /**\n * Stops waiting, with {@link UnfazedTimeoutError}. A request other callers\n * wait on too keeps going (and fills the cache).\n */\n signal?: AbortSignal;\n}\n\n/** A token with its expiry, from {@link UnfazedOIDC.tokenResponse}. */\nexport interface OIDCToken {\n /** The JWT. */\n token: string;\n /** When it expires (the platform's `expires_at`). */\n expiresAt: Date;\n}\n\nexport interface FetchWithTokenOptions {\n /** Token lifetime, a whole number of seconds from 60 to 3600. Default: the platform's. */\n ttlSeconds?: number;\n /** The `fetch` that sends the request. Default: the global `fetch`, at call time. */\n fetch?: TokenFetch;\n}\n\ninterface Entry {\n token: string;\n expiresAtMs: number;\n /** When a call stops using it and asks for a new one (80% of its lifetime). */\n refreshAt: number;\n /** When it expires, on this process's clock. */\n validUntil: number;\n}\n\ninterface Store {\n cache: Map<string, Entry>;\n inflight: Map<string, Promise<Entry>>;\n}\n\n/**\n * One cache per process, not per copy of this module (a framework can bundle\n * the package into several server layers) nor per `Unfazed` instance (a\n * request handler that builds one per request must not ask for a token per\n * request). Keys carry the token URL and the state token, so instances\n * configured differently never share a token.\n */\nconst STORE_KEY = Symbol.for(\"unfazed//oidc/v1\");\n\nfunction sharedStore(): Store {\n const g = globalThis as typeof globalThis & Record<symbol, Store | undefined>;\n return (g[STORE_KEY] ??= newStore());\n}\n\nfunction newStore(): Store {\n return { cache: new Map(), inflight: new Map() };\n}\n\n/**\n * Workload identity tokens for the app this code runs in: `unfazed.oidc` on an\n * {@link import(\"./unfazed.js\").Unfazed} instance, or the standalone `oidc`.\n *\n * Tokens are cached per (audience, ttl) and reused until 80% of their\n * lifetime has passed; concurrent calls for the same pair share one request.\n * 503 (and a gateway's 502/504), 429 and network failures are retried up to 3\n * times (200, 400, 800 ms; a 429's or 503's `Retry-After` up to 10 s is waited\n * out instead); no other 4xx is retried. When a refresh fails for one of\n * those passing reasons, the token it was replacing is returned while it is\n * still valid.\n */\nexport class UnfazedOIDC {\n readonly #options: OIDCOptions;\n readonly #store: Store | undefined;\n\n constructor(options: OIDCOptions = {}) {\n this.#options = { ...options };\n this.#store = options.fetch || options.now ? newStore() : undefined;\n }\n\n /**\n * A JWT for `audience`, from the cache while it is fresh.\n *\n * ```ts\n * const jwt = await unfazed.oidc.token(\"https://api.example.com\", { ttlSeconds: 300 });\n * ```\n *\n * Throws {@link UnfazedOIDCError}: `not_in_app` without `UNFZ_STATE_TOKEN`,\n * `invalid_audience`, `invalid_request` (`ttlSeconds`), or the platform's\n * code.\n */\n async token(audience: string | undefined, options: OIDCTokenOptions = {}): Promise<string> {\n return (await this.tokenResponse(audience, options)).token;\n }\n\n /** {@link token}, with the token's expiry: `{ token, expiresAt }`. */\n async tokenResponse(audience: string | undefined, options: OIDCTokenOptions = {}): Promise<OIDCToken> {\n checkAudience(audience);\n const ttl = options.ttlSeconds;\n checkTtl(ttl);\n const { signal } = options;\n if (signal?.aborted) throw aborted();\n\n const stateToken = this.#stateToken();\n const url = this.#tokenUrl();\n const key = `${url}\\n${stateToken}\\n${audience}\\n${ttl ?? \"\"}`;\n const store = this.#store ?? sharedStore();\n\n const cached = store.cache.get(key);\n if (cached && this.#now() < cached.refreshAt) return view(cached);\n\n let pending = store.inflight.get(key);\n if (!pending) {\n pending = this.#issue(url, stateToken, audience, ttl)\n .then(\n (entry) => {\n store.cache.set(key, entry);\n return entry;\n },\n (error: unknown) => {\n // The control plane restarting must not take down an app whose\n // token is still good: fall back to the token being replaced.\n const previous = store.cache.get(key);\n if (previous && error instanceof UnfazedOIDCError && error.retryable && this.#now() < previous.validUntil) {\n return previous;\n }\n throw error;\n },\n )\n .finally(() => store.inflight.delete(key));\n // Every caller may stop waiting (its signal); the request then ends\n // with nobody listening, which must not be an unhandled rejection.\n pending.catch(() => {});\n store.inflight.set(key, pending);\n }\n return view(await untilAborted(pending, signal).catch(rethrowAbort));\n }\n\n /**\n * A `fetch` that sends every request with `Authorization: Bearer <token>`,\n * a fresh token for `audience` each time (from the cache while it lasts).\n * Every other header, the body and the signal pass through; an\n * `Authorization` already there is replaced.\n *\n * ```ts\n * const openai = new OpenAI({\n * baseURL: process.env.PRIVATEINFERENCE_BASE_URL,\n * apiKey: \"unused\",\n * fetch: unfazed.oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),\n * });\n * ```\n *\n * An invalid `audience` (an unset variable among them) throws here, at\n * once; a missing `UNFZ_STATE_TOKEN` on the first request.\n */\n fetchWithToken(audience: string | undefined, options: FetchWithTokenOptions = {}): TokenFetch {\n checkAudience(audience);\n const aud: string = audience;\n const ttl = options.ttlSeconds;\n checkTtl(ttl);\n return async (input, init) => {\n const request = typeof Request === \"function\" && input instanceof Request ? input : undefined;\n const signal = init?.signal ?? request?.signal ?? undefined;\n const token = await this.token(aud, {\n ...(ttl === undefined ? {} : { ttlSeconds: ttl }),\n ...(signal ? { signal } : {}),\n });\n // init.headers replace a Request's own, as fetch() itself does.\n const headers = new Headers(init?.headers ?? request?.headers);\n headers.set(\"Authorization\", `Bearer ${token}`);\n const send = options.fetch ?? (globalFetch() as TokenFetch);\n return send(input, { ...init, headers });\n };\n }\n\n #now(): number {\n return (this.#options.now ?? Date.now)();\n }\n\n #stateToken(): string {\n const env = this.#options.env ?? processEnv();\n const token = (this.#options.stateToken ?? env[STATE_TOKEN_ENV_VAR])?.trim();\n if (!token) {\n throw new UnfazedOIDCError(\n \"not_in_app\",\n `unfazed: not running inside an unfazed app: ${STATE_TOKEN_ENV_VAR} is not set. ` +\n `OIDC tokens are issued to an app's services and cron jobs, for the ${STATE_TOKEN_ENV_VAR} ` +\n \"the platform gives each of them (no UNFZ_API_TOKEN needed).\",\n );\n }\n return token;\n }\n\n #tokenUrl(): string {\n const { endpoint, baseUrl } = this.#options;\n const configured = configuredEndpoint(\n { ...(endpoint ? { endpoint } : {}), ...(baseUrl ? { baseUrl } : {}) },\n this.#options.env ?? processEnv(),\n );\n return platformBaseUrl(configured ?? DEFAULT_ENDPOINT) + OIDC_TOKEN_PATH;\n }\n\n async #issue(url: string, stateToken: string, audience: string, ttl: number | undefined): Promise<Entry> {\n const transport = this.#options.fetch ?? (globalFetch() as OIDCTransport);\n const sleep = this.#options.sleep ?? defaultSleep;\n const userAgent = this.#options.userAgent ?? `unfazed-sdk/${VERSION}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${stateToken}`,\n // The edge refuses some generic agents; always name ourselves.\n ...(userAgent ? { \"User-Agent\": userAgent } : {}),\n };\n const body = JSON.stringify(ttl === undefined ? { audience } : { audience, ttl_seconds: ttl });\n const what = `unfazed: no OIDC token for audience ${JSON.stringify(audience)}`;\n\n for (let retries = 0; ; retries++) {\n const startedAt = this.#now();\n let answer: { status: number; text: string; retryAfter: string | null };\n try {\n answer = await this.#send(transport, url, headers, body);\n } catch (cause) {\n if (retries < MAX_RETRIES) {\n await sleep(BACKOFF_MS * 2 ** retries);\n continue;\n }\n throw new UnfazedOIDCError(\n \"unreachable\",\n `${what}: ${url} did not answer in ${retries + 1} attempts (${describe(cause)})`,\n { retryable: true, cause },\n );\n }\n const { status, text, retryAfter } = answer;\n if (status >= 200 && status < 300) return this.#entry(text, startedAt, what);\n\n const error = refusal(status, text, retryAfterMs(retryAfter, this.#now()), what);\n if (!error.retryable || retries >= MAX_RETRIES) throw error;\n const waitMs = error.retryAfterMs ?? BACKOFF_MS * 2 ** retries;\n if (waitMs > MAX_RETRY_AFTER_MS) throw error;\n await sleep(waitMs);\n }\n }\n\n async #send(\n transport: OIDCTransport,\n url: string,\n headers: Record<string, string>,\n body: string,\n ): Promise<{ status: number; text: string; retryAfter: string | null }> {\n const timeoutMs = this.#options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const controller = timeoutMs > 0 && typeof AbortController === \"function\" ? new AbortController() : undefined;\n const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n try {\n const signal = controller?.signal;\n const response = await untilAborted(\n transport(url, { method: \"POST\", headers, body, ...(signal ? { signal } : {}) }),\n signal,\n );\n const text = await untilAborted(response.text(), signal);\n return { status: response.status, text, retryAfter: response.headers?.get(\"retry-after\") ?? null };\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n #entry(text: string, startedAt: number, what: string): Entry {\n const body = parseJson(text) as { token?: unknown; expires_at?: unknown } | undefined;\n const token = body?.token;\n const expiresAtMs = typeof body?.expires_at === \"string\" ? Date.parse(body.expires_at) : NaN;\n if (typeof token !== \"string\" || token === \"\" || !Number.isFinite(expiresAtMs)) {\n throw new UnfazedOIDCError(\n \"invalid_response\",\n `${what}: the platform answered 200 without a token and its expires_at: ${truncate(text)}`,\n );\n }\n // The JWT's own iat/exp are both the platform's clock, so its lifetime\n // holds even when this VM's clock is off (a guest resumed from a\n // snapshot); elapsed time is then measured on this clock alone.\n const lifetimeMs = jwtLifetimeMs(token) ?? expiresAtMs - startedAt;\n const life = Math.max(0, lifetimeMs);\n return {\n token,\n expiresAtMs,\n refreshAt: startedAt + REFRESH_AT * life,\n validUntil: startedAt + life,\n };\n }\n}\n\n/**\n * The process's own {@link UnfazedOIDC}: `UNFZ_STATE_TOKEN` and `UNFZ_API_URL`\n * read on every call, the global `fetch`, one cache.\n *\n * ```ts\n * import { oidc } from \"unfazed\";\n * const fetchWithToken = oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE);\n * ```\n */\nexport const oidc: UnfazedOIDC = /* @__PURE__ */ new UnfazedOIDC();\n\n/**\n * A JWT for `audience`, issued to the app this code runs in:\n * {@link UnfazedOIDC.token} on the process's {@link oidc}. Needs only\n * `UNFZ_STATE_TOKEN`.\n *\n * ```ts\n * import { oidcToken } from \"unfazed\";\n * const res = await fetch(url, { headers: { Authorization: `Bearer ${await oidcToken(url)}` } });\n * ```\n */\nexport function oidcToken(audience: string | undefined, options?: OIDCTokenOptions): Promise<string> {\n return oidc.token(audience, options);\n}\n\nfunction view(entry: Entry): OIDCToken {\n return { token: entry.token, expiresAt: new Date(entry.expiresAtMs) };\n}\n\nfunction checkAudience(audience: unknown): asserts audience is string {\n if (typeof audience === \"string\" && AUDIENCE.test(audience)) return;\n const got = typeof audience === \"string\" ? truncate(JSON.stringify(audience), 80) : String(audience);\n throw new UnfazedOIDCError(\n \"invalid_audience\",\n `unfazed: invalid OIDC audience ${got}: it must be 1 to 256 printable ASCII characters, without whitespace`,\n );\n}\n\nfunction checkTtl(ttl: unknown): void {\n if (ttl === undefined) return;\n if (typeof ttl === \"number\" && Number.isInteger(ttl) && ttl >= MIN_TTL_SECONDS && ttl <= MAX_TTL_SECONDS) return;\n throw new UnfazedOIDCError(\n \"invalid_request\",\n `unfazed: ttlSeconds must be a whole number from ${MIN_TTL_SECONDS} to ${MAX_TTL_SECONDS} (got ${String(ttl)})`,\n );\n}\n\n/** The error for a non-2xx answer; `retryable` says whether the loop asks again. */\nfunction refusal(status: number, text: string, retryAfter: number | undefined, what: string): UnfazedOIDCError {\n const body = parseJson(text) as { error?: unknown; error_description?: unknown } | undefined;\n const code = typeof body?.error === \"string\" && body.error ? body.error : codeForStatus(status);\n const description =\n typeof body?.error_description === \"string\" && body.error_description ? body.error_description : undefined;\n const detail = description ?? (body === undefined && text ? truncate(text) : undefined);\n // Never a 4xx but 429: the request itself is what is wrong.\n const retryable =\n status === 429 ||\n status === 502 ||\n status === 503 ||\n status === 504 ||\n (status >= 500 && code === \"temporarily_unavailable\");\n const hint = code === \"invalid_token\" ? ` (the platform refused ${STATE_TOKEN_ENV_VAR})` : \"\";\n const later = retryAfter === undefined ? \"\" : `; retry after ${Math.ceil(retryAfter / 1000)} s`;\n return new UnfazedOIDCError(code, `${what}: ${code} (HTTP ${status})${hint}${detail ? `: ${detail}` : \"\"}${later}`, {\n status,\n ...(description ? { description } : {}),\n retryable,\n ...(retryAfter === undefined ? {} : { retryAfterMs: retryAfter }),\n });\n}\n\nfunction codeForStatus(status: number): OIDCErrorCode {\n switch (status) {\n case 400:\n return \"invalid_request\";\n case 401:\n return \"invalid_token\";\n case 429:\n return \"rate_limited\";\n case 503:\n return \"temporarily_unavailable\";\n default:\n return \"http_error\";\n }\n}\n\n/** `Retry-After` in ms: delay-seconds or an HTTP-date; undefined when absent or unreadable. */\nfunction retryAfterMs(value: string | null, now: number): number | undefined {\n if (value === null) return undefined;\n const trimmed = value.trim();\n if (/^\\d+$/.test(trimmed)) return Number(trimmed) * 1000;\n const at = Date.parse(trimmed);\n return Number.isFinite(at) ? Math.max(0, at - now) : undefined;\n}\n\n/** `exp - iat` of a JWT, in ms, when both are there. */\nfunction jwtLifetimeMs(token: string): number | undefined {\n const payload = token.split(\".\")[1];\n if (!payload || typeof atob !== \"function\") return undefined;\n try {\n const base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const claims = JSON.parse(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\"))) as {\n iat?: unknown;\n exp?: unknown;\n };\n const { iat, exp } = claims;\n if (typeof iat === \"number\" && typeof exp === \"number\" && Number.isFinite(iat) && exp > iat) {\n return (exp - iat) * 1000;\n }\n } catch {\n // Not a JWT we can read: the platform's expires_at decides.\n }\n return undefined;\n}\n\nfunction globalFetch(): (input: string, init: RequestInit) => Promise<Response> {\n const fetch = (globalThis as { fetch?: unknown }).fetch;\n if (typeof fetch !== \"function\") {\n throw new UnfazedConfigError(\"unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch }.\");\n }\n return fetch.bind(globalThis) as (input: string, init: RequestInit) => Promise<Response>;\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction aborted(): UnfazedTimeoutError {\n return new UnfazedTimeoutError(\"unfazed: OIDC token request aborted\");\n}\n\nfunction rethrowAbort(error: unknown): never {\n if (typeof error === \"object\" && error !== null && (error as { name?: unknown }).name === \"AbortError\") {\n throw aborted();\n }\n throw error;\n}\n\nfunction describe(cause: unknown): string {\n if (cause instanceof Error) return cause.name === \"AbortError\" ? \"timed out\" : cause.message;\n return String(cause);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n}\n\nfunction truncate(s: string, max = 300): string {\n return s.length <= max ? s : `${s.slice(0, max)}…`;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  // GENERATED by scripts/embed-reference.ts from REFERENCE.md. Do not edit:
2
2
  // change REFERENCE.md and run `bun run embed-reference`.
3
3
  /** The SDK's reference: every method with its signature (REFERENCE.md). */
4
- export const REFERENCE = "# unfazed SDK reference\n\nEvery method of `Unfazed`, with its signature, in one page. In a script:\n`import { Unfazed, help } from \"unfazed\"; const unfazed = new Unfazed();`\n(run_script injects the credential; elsewhere `UNFZ_API_TOKEN` or\n`npx unfazed login`). `help()` returns this page; `help(\"writeFiles\")` one\nentry. Methods are async and throw an `UnfazedError` subclass on failure.\nEvery method takes an optional last `options` with `signal` (and, for single\nrequests, `timeoutMs`). The names follow the MCP tools: `listCommits` is\n`list_commits`, `waitForEvents` is `wait_for_events`.\n\n```ts\nimport { Unfazed } from \"unfazed\";\n\nexport default async () => {\n const unfazed = new Unfazed();\n const ws = await unfazed.ensureWorkspace(\"hello\");\n const wrote = await unfazed.writeFiles(ws.id, {\n \"server.ts\": `export default { fetch: () => new Response(\"hi\") }`,\n \"package.json\": JSON.stringify({ name: \"hello\" }),\n }, { commitMessage: \"First version\" }); // ONE request, ONE commit\n const app = await unfazed.deployService({ workspaceId: ws.id, alias: \"hello\", wait: true });\n return { url: app.url, commit: wrote.commit?.sha, timings: app.timings };\n};\n```\n\n## Saved, committed, deployed\n\nA file write has three states. **Saved**: the call returned; the app's\nfiles have it and the next deploy uses it. **Committed**: on an app with\nplatform git, `commit.committed && !commit.waiting`; it is in `listCommits`.\n**Deployed**: a deploy of those files went READY (`deployService({ wait:\ntrue })`). A write never deploys by itself, except when `commit.deploys` is\ntrue (the branch head is imported like a push).\n\n`commit.waiting: true` means saved, with the commit queued for the git\nserver: it lands in order, `sha` is null until then, nothing to retry.\n`waitForCommit` (or `writeFiles(…, { waitForCommit: true })`) resolves once\nit landed.\n\n## Workspaces\n\n### `createWorkspace(name: string): Promise<Workspace>`\nA new workspace: files, env, deployments. Names are unique per owner,\ncase-insensitively; prefer `ensureWorkspace` in re-runnable scripts.\n\n### `ensureWorkspace(name: string): Promise<Workspace>`\nThe workspace named `name`, created only when missing. Safe to re-run.\n\n### `listWorkspaces(): Promise<Workspace[]>`\n\n### `liveApps(workspaceId: string): Promise<LiveApp[]>`\nThe apps the workspace serves now: `{ alias, deploymentID, domains }`.\n\n### `deleteWorkspace(workspaceId: string, options?: { withDeployments?: true, confirmAliases: string[], ignoreNotFound?: boolean }): Promise<boolean>`\nDeletes it; with `withDeployments`, every app it serves, which\n`confirmAliases` must name exactly (`liveApps`). Kept 30 days for support.\n\n### `deleteApp(app: { workspaceId, alias }, options?: { ignoreNotFound?: boolean }): Promise<DeleteAppResult>`\nDeletes one app (every deployment of the alias, its App State, env and\nbindings); the workspace and its other apps stay.\n\n### `whoami(): Promise<Whoami>`\nWho the token is, its scopes, usage and limits.\n\n## Files\n\n### `writeFiles(workspaceId: string, files: Record<string, string | { text } | { base64 } | { bytes }>, options?: { commitMessage?: string, waitForCommit?: boolean | WaitOptions, allowPartial?: boolean }): Promise<WriteFilesResult>`\nWrites every file in ONE request, which is ONE commit on an app with\nplatform git (MCP `write_workspace_files`). Result: `{ files: [{ path, ok,\nerror, file }], written, failed, totalBytes, commit, commits, landed? }`;\n`commit` is `{ sha, branch, message, committed, waiting, waitingCount,\ndeploys, conflicts, writeSHA, paused }` or null without platform git. Not\ntransactional: when a file fails it throws `UnfazedWriteError` (`.result`,\n`.failed`) unless `allowPartial`. Up to 100 files and 32 MiB per request; a\nbigger map is several requests, each a commit.\n\n```ts\nconst out = await unfazed.writeFiles(ws.id, { \"a.ts\": a, \"logo.png\": { bytes } }, { commitMessage: \"Logo\" });\nif (out.commit?.conflicts.length) console.log(\"a push changed\", out.commit.conflicts, \"mine:\", out.commit.writeSHA);\n```\n\n### `writeFile(workspaceId: string, path: string, text: string, options?: { commitMessage?, waitForCommit? }): Promise<WorkspaceFile & { commit }>`\nOne text file: `writeFiles` with one entry.\n\n### `writeBinaryFile(workspaceId: string, path: string, content: Uint8Array | ArrayBuffer | { base64: string }, options?): Promise<WorkspaceFile & { commit }>`\n\n### `waitForCommit(workspaceId: string, waitOptions?: WaitOptions): Promise<{ branch, head, waitedMs }>`\nResolves once no write of the app waits for its commit; `head` then has\nevery earlier write. Immediate (`head: null`) without platform git. Default\ntimeout 2 minutes (`UnfazedTimeoutError`; the writes stay saved).\n\n### `readFile(workspaceId: string, path: string, input?: { offset?, limit? }): Promise<{ text, totalBytes, truncated, nextOffset, file } | null>`\n\n### `listFiles(workspaceId: string, input?: { limit?, offset?, order? }): Promise<WorkspaceFilePage>`\n\n### `deleteFile(workspaceId: string, path: string): Promise<boolean>`\n`false` when there was nothing to delete.\n\n## Deploys\n\n### `deployService(options: { workspaceId, alias?, wait?, waitOptions?, timings?, runtime?, serviceMode?, healthPath?, cronSchedule?, cronPath? }): Promise<DeployResult>`\nBuilds and deploys the workspace as an HTTP service (`server.ts` default\nexport, or `unfazed.json`). Returns BUILDING at once; with `wait: true`,\nresolves READY or throws `UnfazedDeployError` (`reason` FAILED with the\nbuild error, or TIMEOUT; default 10 minutes). Every call is a new release;\nthe same `alias` redeploys that app.\n\nWith `wait: true` the result has `timings`:\n`{ client: { createMs, waitMs, totalMs, waitRequests, waitMode },\nplatform: { totalMs, buildMs, promoteMs, phases, buildMode,\nartifactTransport, buildMemoryMiB } }`, both in milliseconds. `platform` is\nwhat the platform measured (created to READY); `client` is what you waited.\nThe wait is server-side, so the two differ by about one round trip.\n\n```ts\nconst app = await unfazed.deployService({ workspaceId: ws.id, alias: \"api\", wait: true });\napp.timings?.platform?.phases; // { vm_install: 900, vm_build: 2600, admission: 40, … }\n```\n\n### `deployStatic(options: { workspaceId, alias?, wait?, spaMode? }): Promise<DeployResult>`\n\n### `deployBox(options: { workspaceId, alias, wait?, resources?: { memMiB?, vcpus?, diskGiB? } }): Promise<DeployResult>`\nA persistent Linux VM over SSH (`sshCommand`). Always pass `alias`.\n\n### `deploy(input: CreateDeploymentInput, options?: { wait?, waitOptions?, timings? }): Promise<DeployResult>`\nAny `createDeployment` input.\n\n### `waitForDeployment(target: string | { workspaceId, gitCommit, alias?, after? }, waitOptions?: WaitOptions): Promise<Deployment>`\nWaits server-side (MCP `wait_for_deployment`: one request per 45 s at most,\nanswered when the status changes) until READY; throws `UnfazedDeployError`\non FAILED or timeout. A new app's landing on its server is waited for too.\n`waitOptions: { timeoutMs?, signal?, mode?: \"server\" | \"poll\" }`. The\nplatform holds at most 16 waits of one account at once; past that a wait\nby id polls instead.\n\nInstead of an id, name the deploy a git import makes of a commit (a\nrestore, a push or a sync deploys before any id exists): `{ workspaceId,\ngitCommit, alias?, after? }`. The wait covers the import too, and a failed\nimport throws `UnfazedDeployError` with its error. `alias` is required when\nseveral apps (`apps[]`) deployed the commit; `after` (a restore's\n`waitAfter`) counts only deployments begun after it.\n\n### `getDeployment(deploymentId: string): Promise<Deployment | null>`\n\n### `getDeploymentByAlias(alias: string): Promise<Deployment | null>`\n\n### `listDeployments(input?: { workspaceID?, alias?, kind?, limit?, offset? }): Promise<DeploymentPage>`\nNewest first.\n\n### `listReleases(alias: string): Promise<Deployment[]>`\nThe alias's history; `aliasHeld` marks the live one.\n\n### `rollback(options: { alias, to?, expectedCurrentId?, retries? }): Promise<Deployment>`\nPoints the alias back at a prior READY release (default: the one before\nthe live one). A bare `rollback({ alias })` toggles; pin `to` in anything\nre-runnable.\n\n### `deleteDeployment(deploymentId: string, options?: { ignoreNotFound? }): Promise<boolean>`\n\n## Runtime, logs, events, timings\n\n### `getStatus(deploymentId: string): Promise<ServiceStatus>`\n`{ running, health, inFlight, restartCount, memSizeMiB, lastError, … }`.\n\n### `getLogs(deploymentId: string, input?: { tailBytes? }): Promise<{ stdout, stderr, truncated, … }>`\nFilter in the script: `logs.stderr.split(\"\\n\").filter((l) => l.includes(\"ERROR\"))`.\n\n### `getEvents(deploymentId: string, limit?: number): Promise<DeploymentEvent[]>`\nThe latest `limit` (default 20) lifecycle events, oldest first: `{ type,\ntypeEnum, severity, message, vmID, metadata, timings, createdAt }`. `type`\nis the event's own name, as MCP reports it: `\"deployment.build_finished\"`,\n`\"deployment.phases\"`, `\"service.vm_started\"`. `typeEnum` is the GraphQL\nenum (`UNKNOWN` for names it lacks); match on `type`. A timing event's\n`timings` is what it measured, parsed by the platform; null otherwise.\n\n### `getDeploymentTimings(deploymentId: string): Promise<PlatformTimings | null>`\nA deploy's measured phases (ms), as the platform reads them from all its\ntiming events: `{ totalMs, buildMs, promoteMs, phases, buildMode,\nartifactTransport, buildMemoryMiB }`. `parseDeploymentTimings(events)` does\nthe same for events you already read, preferring each event's `timings`;\ndurations are whole, non-negative milliseconds (anything else is dropped).\n\n### `restartService(deploymentId: string)` / `stopService(deploymentId: string)`\n\n## Env and secrets\n\nSnapshotted into a release at deploy time: redeploy the alias to apply.\n\n### `setEnv(workspaceId, name, value)` / `setSecret(workspaceId, name, value)`: `Promise<EnvVar>`\nWorkspace defaults. A name is plain or secret, never both.\n\n### `setEnvMany(workspaceId: string, vars: Record<string, string>, options?: { secret?: boolean }): Promise<EnvVar[]>`\n\n### `deleteEnv(workspaceId, name): Promise<boolean>` / `listEnv(workspaceId): Promise<EnvVar[]>`\n\n### `setAppEnv(workspaceId, alias, name, value)` / `setAppSecret(workspaceId, alias, name, value)`: `Promise<EnvVar>`\nOne app's override.\n\n### `removeAppEnv(workspaceId, alias, name): Promise<boolean>`\nBlocks the name for the app (a mask). `inheritAppEnv` clears it.\n\n### `inheritAppEnv(workspaceId, alias, name): Promise<boolean>` / `listAppEnv(workspaceId, alias): Promise<EnvVar[]>`\n\n## History (platform git)\n\n### `listCommits(workspaceId: string, input?: { limit?, before? }): Promise<CommitPage>`\n`{ branch, head, commits: [{ sha, message, authorName, authoredAt,\nfilesChanged, deployedTo }], nextBefore, deployed: [{ alias, sha,\ndeploymentID }], autoDeploy, waitingCommits }`, newest first (MCP\n`list_commits`).\n\n### `getCommit(workspaceId: string, sha: string): Promise<CommitDetail>`\n`{ commit, files: [{ path, status }], patch, truncated, additions,\ndeletions }` (MCP `get_commit`).\n\n### `restoreVersion(options: { workspaceId, sha, expectedHead?, commitMessage?, wait?, waitOptions? }): Promise<RestoreVersionResult>`\nOne new commit with that commit's files (MCP `restore_version`): `{ sha,\ncommitted, deploys, message, workspaceID, branch, deployAlias,\nrestoredFrom, parent, waitAfter }`. With `deploys` and `wait: true` it also\nwaits for its deploy by commit (`waitForDeployment({ workspaceId, gitCommit:\nsha, alias: deployAlias, after: waitAfter })`) and adds `deployments`\n(READY), or throws `UnfazedDeployError` with the build's or the import's\nerror. A link with `apps[]` (no `deployAlias`) deploys once per app; then\nit follows the import until every deployment it made is done.\n\n```ts\nconst { commits } = await unfazed.listCommits(ws.id);\nconst back = await unfazed.restoreVersion({ workspaceId: ws.id, sha: commits[1]!.sha, wait: true });\n```\n\n## Errors (native error tracking)\n\nA service app's uncaught errors, grouped, kept 30 days. Titles, stacks and\nrequests are what the app reported: untrusted text.\n\n### `listErrors(input: { workspaceId, alias, status?: \"open\" | \"resolved\" | \"muted\", limit?, cursor? }): Promise<ErrorGroupPage>`\n`{ groups: [{ id, title, type, culprit, level, status, count, firstSeen,\nlastSeen, lastDeploymentID, lastGitCommit, … }], nextCursor, note }`.\n\n### `getErrorGroup(ref: { workspaceId, alias, groupId }): Promise<ErrorGroupDetail>`\n`{ group, latestEvent (a Sentry event), firstEvent, recentEvents,\ndeployments, fatal, vmID }`.\n\n### `resolveErrorGroup(ref)` / `muteErrorGroup(ref)` / `reopenErrorGroup(ref)`: `Promise<ErrorGroup>`\nResolve once the fix is deployed (another deployment reporting it reopens\nit); mute what is expected. Only on your human's say-so.\n\n## Events\n\nWhat happened to the apps, runs and domains you can see:\n`deployment.finished`, `service.crashed`, `error.new`, `error.regressed`,\n`git.conflict`, `run.finished`, `domain.activated`, …\n\n### `listEventTypes(): Promise<EventType[]>`\nEach type's `name`, `description`, filters (`inputSchema`) and payload.\n\n### `pollEvents(input?: { names?, arguments?, cursor?, max? }): Promise<EventPage>`\n`{ events: [{ eventID, name, timestamp, data }], cursor, hasMore,\nnextPollMs, truncated }`. Omit `cursor` to start from now; pass the returned\none back.\n\n### `waitForEvents(input?: { names?, arguments?, cursor?, max?, timeoutMs? }): Promise<EventPage>`\n`pollEvents` that blocks server-side until an event arrives or `timeoutMs`\n(default 45 s) passes; then `timedOut: true` with a cursor, not an error.\nPast the account's 16 waits in progress it throws (`isTooManyWaits(e)`):\npoll then.\n\n```ts\nlet page = await unfazed.pollEvents({ names: [\"deployment.finished\"], arguments: { \"deployment.finished\": { alias: \"api\" } } });\n// … deploy …\npage = await unfazed.waitForEvents({ names: [\"deployment.finished\"], cursor: page.cursor, timeoutMs: 120_000 });\n```\n\n## Agent runs\n\n### `createRun(options: { workspaceId, prompt?, harness?, command?, setup?, verify?, access?, alias?, maxSeconds?, wait? }): Promise<Run>`\nA one-shot agent run in a fresh VM. `wait: true` polls to SUCCEEDED,\nFAILED or CANCELLED (all returned, never thrown).\n\n### `waitForRun(runId: string, waitOptions?: WaitOptions): Promise<Run>`\n\n### `getRun(runId)` / `listRuns(filter?: { workspaceId?, status?, limit?, offset? })` / `cancelRun(runId)`\n\n### `followUpRun(runId: string, options: { prompt, mode?, wait? }): Promise<Run>`\n\n### `answerRun(runId: string, answer: string): Promise<Run>`\nAnswers a BLOCKED run's question.\n\n### `runEvents(runId)` / `runConsole(runId, { tailBytes? })` / `runTranscript(runId, { tailBytes? })`\n\n### `listRunHarnesses(): Promise<RunHarness[]>`\n\n## Anything else\n\n`unfazed.client` is the typed GraphQL transport over the same credential:\n`await unfazed.client.query(document, variables)`. `operations` exports\nevery document the SDK uses.\n";
4
+ export const REFERENCE = "# unfazed SDK reference\n\nEvery method of `Unfazed`, with its signature, in one page. In a script:\n`import { Unfazed, help } from \"unfazed\"; const unfazed = new Unfazed();`\n(run_script injects the credential; elsewhere `UNFZ_API_TOKEN` or\n`npx unfazed login`; `oidc` needs neither). `help()` returns this page;\n`help(\"writeFiles\")` one entry. Methods are async and throw an\n`UnfazedError` subclass on failure.\nEvery method takes an optional last `options` with `signal` (and, for single\nrequests, `timeoutMs`). The names follow the MCP tools: `listCommits` is\n`list_commits`, `waitForEvents` is `wait_for_events`.\n\n```ts\nimport { Unfazed } from \"unfazed\";\n\nexport default async () => {\n const unfazed = new Unfazed();\n const ws = await unfazed.ensureWorkspace(\"hello\");\n const wrote = await unfazed.writeFiles(ws.id, {\n \"server.ts\": `export default { fetch: () => new Response(\"hi\") }`,\n \"package.json\": JSON.stringify({ name: \"hello\" }),\n }, { commitMessage: \"First version\" }); // ONE request, ONE commit\n const app = await unfazed.deployService({ workspaceId: ws.id, alias: \"hello\", wait: true });\n return { url: app.url, commit: wrote.commit?.sha, timings: app.timings };\n};\n```\n\n## Saved, committed, deployed\n\nA file write has three states. **Saved**: the call returned; the app's\nfiles have it and the next deploy uses it. **Committed**: on an app with\nplatform git, `commit.committed && !commit.waiting`; it is in `listCommits`.\n**Deployed**: a deploy of those files went READY (`deployService({ wait:\ntrue })`). A write never deploys by itself, except when `commit.deploys` is\ntrue (the branch head is imported like a push).\n\n`commit.waiting: true` means saved, with the commit queued for the git\nserver: it lands in order, `sha` is null until then, nothing to retry.\n`waitForCommit` (or `writeFiles(…, { waitForCommit: true })`) resolves once\nit landed.\n\n## Workspaces\n\n### `createWorkspace(name: string): Promise<Workspace>`\nA new workspace: files, env, deployments. Names are unique per owner,\ncase-insensitively; prefer `ensureWorkspace` in re-runnable scripts.\n\n### `ensureWorkspace(name: string): Promise<Workspace>`\nThe workspace named `name`, created only when missing. Safe to re-run.\n\n### `listWorkspaces(): Promise<Workspace[]>`\n\n### `liveApps(workspaceId: string): Promise<LiveApp[]>`\nThe apps the workspace serves now: `{ alias, deploymentID, domains }`.\n\n### `deleteWorkspace(workspaceId: string, options?: { withDeployments?: true, confirmAliases: string[], ignoreNotFound?: boolean }): Promise<boolean>`\nDeletes it; with `withDeployments`, every app it serves, which\n`confirmAliases` must name exactly (`liveApps`). Kept 30 days for support.\n\n### `deleteApp(app: { workspaceId, alias }, options?: { ignoreNotFound?: boolean }): Promise<DeleteAppResult>`\nDeletes one app (every deployment of the alias, its App State, env and\nbindings); the workspace and its other apps stay.\n\n### `whoami(): Promise<Whoami>`\nWho the token is, its scopes, usage and limits.\n\n## Files\n\n### `writeFiles(workspaceId: string, files: Record<string, string | { text } | { base64 } | { bytes }>, options?: { commitMessage?: string, waitForCommit?: boolean | WaitOptions, allowPartial?: boolean }): Promise<WriteFilesResult>`\nWrites every file in ONE request, which is ONE commit on an app with\nplatform git (MCP `write_workspace_files`). Result: `{ files: [{ path, ok,\nerror, file }], written, failed, totalBytes, commit, commits, landed? }`;\n`commit` is `{ sha, branch, message, committed, waiting, waitingCount,\ndeploys, conflicts, writeSHA, paused }` or null without platform git. Not\ntransactional: when a file fails it throws `UnfazedWriteError` (`.result`,\n`.failed`) unless `allowPartial`. Up to 100 files and 32 MiB per request; a\nbigger map is several requests, each a commit.\n\n```ts\nconst out = await unfazed.writeFiles(ws.id, { \"a.ts\": a, \"logo.png\": { bytes } }, { commitMessage: \"Logo\" });\nif (out.commit?.conflicts.length) console.log(\"a push changed\", out.commit.conflicts, \"mine:\", out.commit.writeSHA);\n```\n\n### `writeFile(workspaceId: string, path: string, text: string, options?: { commitMessage?, waitForCommit? }): Promise<WorkspaceFile & { commit }>`\nOne text file: `writeFiles` with one entry.\n\n### `writeBinaryFile(workspaceId: string, path: string, content: Uint8Array | ArrayBuffer | { base64: string }, options?): Promise<WorkspaceFile & { commit }>`\n\n### `waitForCommit(workspaceId: string, waitOptions?: WaitOptions): Promise<{ branch, head, waitedMs }>`\nResolves once no write of the app waits for its commit; `head` then has\nevery earlier write. Immediate (`head: null`) without platform git. Default\ntimeout 2 minutes (`UnfazedTimeoutError`; the writes stay saved).\n\n### `readFile(workspaceId: string, path: string, input?: { offset?, limit? }): Promise<{ text, totalBytes, truncated, nextOffset, file } | null>`\n\n### `listFiles(workspaceId: string, input?: { limit?, offset?, order? }): Promise<WorkspaceFilePage>`\n\n### `deleteFile(workspaceId: string, path: string): Promise<boolean>`\n`false` when there was nothing to delete.\n\n## Deploys\n\n### `deployService(options: { workspaceId, alias?, wait?, waitOptions?, timings?, runtime?, serviceMode?, healthPath?, cronSchedule?, cronPath? }): Promise<DeployResult>`\nBuilds and deploys the workspace as an HTTP service (`server.ts` default\nexport, or `unfazed.json`). Returns BUILDING at once; with `wait: true`,\nresolves READY or throws `UnfazedDeployError` (`reason` FAILED with the\nbuild error, or TIMEOUT; default 10 minutes). Every call is a new release;\nthe same `alias` redeploys that app.\n\nWith `wait: true` the result has `timings`:\n`{ client: { createMs, waitMs, totalMs, waitRequests, waitMode },\nplatform: { totalMs, buildMs, promoteMs, phases, buildMode,\nartifactTransport, buildMemoryMiB } }`, both in milliseconds. `platform` is\nwhat the platform measured (created to READY); `client` is what you waited.\nThe wait is server-side, so the two differ by about one round trip.\n\n```ts\nconst app = await unfazed.deployService({ workspaceId: ws.id, alias: \"api\", wait: true });\napp.timings?.platform?.phases; // { vm_install: 900, vm_build: 2600, admission: 40, … }\n```\n\n### `deployStatic(options: { workspaceId, alias?, wait?, spaMode? }): Promise<DeployResult>`\n\n### `deployBox(options: { workspaceId, alias, wait?, resources?: { memMiB?, vcpus?, diskGiB? } }): Promise<DeployResult>`\nA persistent Linux VM over SSH (`sshCommand`). Always pass `alias`.\n\n### `deploy(input: CreateDeploymentInput, options?: { wait?, waitOptions?, timings? }): Promise<DeployResult>`\nAny `createDeployment` input.\n\n### `waitForDeployment(target: string | { workspaceId, gitCommit, alias?, after? }, waitOptions?: WaitOptions): Promise<Deployment>`\nWaits server-side (MCP `wait_for_deployment`: one request per 45 s at most,\nanswered when the status changes) until READY; throws `UnfazedDeployError`\non FAILED or timeout. A new app's landing on its server is waited for too.\n`waitOptions: { timeoutMs?, signal?, mode?: \"server\" | \"poll\" }`. The\nplatform holds at most 16 waits of one account at once; past that a wait\nby id polls instead.\n\nInstead of an id, name the deploy a git import makes of a commit (a\nrestore, a push or a sync deploys before any id exists): `{ workspaceId,\ngitCommit, alias?, after? }`. The wait covers the import too, and a failed\nimport throws `UnfazedDeployError` with its error. `alias` is required when\nseveral apps (`apps[]`) deployed the commit; `after` (a restore's\n`waitAfter`) counts only deployments begun after it.\n\n### `getDeployment(deploymentId: string): Promise<Deployment | null>`\n\n### `getDeploymentByAlias(alias: string): Promise<Deployment | null>`\n\n### `listDeployments(input?: { workspaceID?, alias?, kind?, limit?, offset? }): Promise<DeploymentPage>`\nNewest first.\n\n### `listReleases(alias: string): Promise<Deployment[]>`\nThe alias's history; `aliasHeld` marks the live one.\n\n### `rollback(options: { alias, to?, expectedCurrentId?, retries? }): Promise<Deployment>`\nPoints the alias back at a prior READY release (default: the one before\nthe live one). A bare `rollback({ alias })` toggles; pin `to` in anything\nre-runnable.\n\n### `deleteDeployment(deploymentId: string, options?: { ignoreNotFound? }): Promise<boolean>`\n\n## Runtime, logs, events, timings\n\n### `getStatus(deploymentId: string): Promise<ServiceStatus>`\n`{ running, health, inFlight, restartCount, memSizeMiB, lastError, … }`.\n\n### `getLogs(deploymentId: string, input?: { tailBytes? }): Promise<{ stdout, stderr, truncated, … }>`\nFilter in the script: `logs.stderr.split(\"\\n\").filter((l) => l.includes(\"ERROR\"))`.\n\n### `getEvents(deploymentId: string, limit?: number): Promise<DeploymentEvent[]>`\nThe latest `limit` (default 20) lifecycle events, oldest first: `{ type,\ntypeEnum, severity, message, vmID, metadata, timings, createdAt }`. `type`\nis the event's own name, as MCP reports it: `\"deployment.build_finished\"`,\n`\"deployment.phases\"`, `\"service.vm_started\"`. `typeEnum` is the GraphQL\nenum (`UNKNOWN` for names it lacks); match on `type`. A timing event's\n`timings` is what it measured, parsed by the platform; null otherwise.\n\n### `getDeploymentTimings(deploymentId: string): Promise<PlatformTimings | null>`\nA deploy's measured phases (ms), as the platform reads them from all its\ntiming events: `{ totalMs, buildMs, promoteMs, phases, buildMode,\nartifactTransport, buildMemoryMiB }`. `parseDeploymentTimings(events)` does\nthe same for events you already read, preferring each event's `timings`;\ndurations are whole, non-negative milliseconds (anything else is dropped).\n\n### `restartService(deploymentId: string)` / `stopService(deploymentId: string)`\n\n## Env and secrets\n\nSnapshotted into a release at deploy time: redeploy the alias to apply.\n\n### `setEnv(workspaceId, name, value)` / `setSecret(workspaceId, name, value)`: `Promise<EnvVar>`\nWorkspace defaults. A name is plain or secret, never both.\n\n### `setEnvMany(workspaceId: string, vars: Record<string, string>, options?: { secret?: boolean }): Promise<EnvVar[]>`\n\n### `deleteEnv(workspaceId, name): Promise<boolean>` / `listEnv(workspaceId): Promise<EnvVar[]>`\n\n### `setAppEnv(workspaceId, alias, name, value)` / `setAppSecret(workspaceId, alias, name, value)`: `Promise<EnvVar>`\nOne app's override.\n\n### `removeAppEnv(workspaceId, alias, name): Promise<boolean>`\nBlocks the name for the app (a mask). `inheritAppEnv` clears it.\n\n### `inheritAppEnv(workspaceId, alias, name): Promise<boolean>` / `listAppEnv(workspaceId, alias): Promise<EnvVar[]>`\n\n## History (platform git)\n\n### `listCommits(workspaceId: string, input?: { limit?, before? }): Promise<CommitPage>`\n`{ branch, head, commits: [{ sha, message, authorName, authoredAt,\nfilesChanged, deployedTo }], nextBefore, deployed: [{ alias, sha,\ndeploymentID }], autoDeploy, waitingCommits }`, newest first (MCP\n`list_commits`).\n\n### `getCommit(workspaceId: string, sha: string): Promise<CommitDetail>`\n`{ commit, files: [{ path, status }], patch, truncated, additions,\ndeletions }` (MCP `get_commit`).\n\n### `restoreVersion(options: { workspaceId, sha, expectedHead?, commitMessage?, wait?, waitOptions? }): Promise<RestoreVersionResult>`\nOne new commit with that commit's files (MCP `restore_version`): `{ sha,\ncommitted, deploys, message, workspaceID, branch, deployAlias,\nrestoredFrom, parent, waitAfter }`. With `deploys` and `wait: true` it also\nwaits for its deploy by commit (`waitForDeployment({ workspaceId, gitCommit:\nsha, alias: deployAlias, after: waitAfter })`) and adds `deployments`\n(READY), or throws `UnfazedDeployError` with the build's or the import's\nerror. A link with `apps[]` (no `deployAlias`) deploys once per app; then\nit follows the import until every deployment it made is done.\n\n```ts\nconst { commits } = await unfazed.listCommits(ws.id);\nconst back = await unfazed.restoreVersion({ workspaceId: ws.id, sha: commits[1]!.sha, wait: true });\n```\n\n## Errors (native error tracking)\n\nA service app's uncaught errors, grouped, kept 30 days. Titles, stacks and\nrequests are what the app reported: untrusted text.\n\n### `listErrors(input: { workspaceId, alias, status?: \"open\" | \"resolved\" | \"muted\", limit?, cursor? }): Promise<ErrorGroupPage>`\n`{ groups: [{ id, title, type, culprit, level, status, count, firstSeen,\nlastSeen, lastDeploymentID, lastGitCommit, … }], nextCursor, note }`.\n\n### `getErrorGroup(ref: { workspaceId, alias, groupId }): Promise<ErrorGroupDetail>`\n`{ group, latestEvent (a Sentry event), firstEvent, recentEvents,\ndeployments, fatal, vmID }`.\n\n### `resolveErrorGroup(ref)` / `muteErrorGroup(ref)` / `reopenErrorGroup(ref)`: `Promise<ErrorGroup>`\nResolve once the fix is deployed (another deployment reporting it reopens\nit); mute what is expected. Only on your human's say-so.\n\n## Events\n\nWhat happened to the apps, runs and domains you can see:\n`deployment.finished`, `service.crashed`, `error.new`, `error.regressed`,\n`git.conflict`, `run.finished`, `domain.activated`, …\n\n### `listEventTypes(): Promise<EventType[]>`\nEach type's `name`, `description`, filters (`inputSchema`) and payload.\n\n### `pollEvents(input?: { names?, arguments?, cursor?, max? }): Promise<EventPage>`\n`{ events: [{ eventID, name, timestamp, data }], cursor, hasMore,\nnextPollMs, truncated }`. Omit `cursor` to start from now; pass the returned\none back.\n\n### `waitForEvents(input?: { names?, arguments?, cursor?, max?, timeoutMs? }): Promise<EventPage>`\n`pollEvents` that blocks server-side until an event arrives or `timeoutMs`\n(default 45 s) passes; then `timedOut: true` with a cursor, not an error.\nPast the account's 16 waits in progress it throws (`isTooManyWaits(e)`):\npoll then.\n\n```ts\nlet page = await unfazed.pollEvents({ names: [\"deployment.finished\"], arguments: { \"deployment.finished\": { alias: \"api\" } } });\n// … deploy …\npage = await unfazed.waitForEvents({ names: [\"deployment.finished\"], cursor: page.cursor, timeoutMs: 120_000 });\n```\n\n## Agent runs\n\n### `createRun(options: { workspaceId, prompt?, harness?, command?, setup?, verify?, access?, alias?, maxSeconds?, wait? }): Promise<Run>`\nA one-shot agent run in a fresh VM. `wait: true` polls to SUCCEEDED,\nFAILED or CANCELLED (all returned, never thrown).\n\n### `waitForRun(runId: string, waitOptions?: WaitOptions): Promise<Run>`\n\n### `getRun(runId)` / `listRuns(filter?: { workspaceId?, status?, limit?, offset? })` / `cancelRun(runId)`\n\n### `followUpRun(runId: string, options: { prompt, mode?, wait? }): Promise<Run>`\n\n### `answerRun(runId: string, answer: string): Promise<Run>`\nAnswers a BLOCKED run's question.\n\n### `runEvents(runId)` / `runConsole(runId, { tailBytes? })` / `runTranscript(runId, { tailBytes? })`\n\n### `listRunHarnesses(): Promise<RunHarness[]>`\n\n## Workload identity\n\nOIDC tokens the platform issues to the app the code runs in (a service or a\ncron job): short-lived JWTs for one audience, from the issuer\n`https://unfazed.dev/oidc`, which a service that trusts it takes instead of\nan API key. Claims include `sub`, `aud`, `owner_id`, `project_id`, `app_id`,\n`alias`, `environment` and `deployment_id`. The only credential is the app's\n`UNFZ_STATE_TOKEN`, set in each of its VMs: no API token, so a bare\n`new Unfazed()` works in an app for this. Without an instance:\n`import { oidc, oidcToken } from \"unfazed\"` (`oidcToken(audience)` is\n`oidc.token(audience)`).\n\n### `oidc.token(audience: string, options?: { ttlSeconds?, signal? }): Promise<string>`\nA JWT for `audience` (1 to 256 printable ASCII characters, no whitespace);\n`ttlSeconds` from 60 to 3600, else the platform's default. Cached per\n(audience, ttl) until 80% of its lifetime; concurrent calls share one\nrequest. 503s, network failures and 429s (after their `Retry-After`) are\nretried up to 3 times; if a refresh still fails that way, the previous token\ncomes back while it is valid. Throws `UnfazedOIDCError` (`code`, `status`,\n`description`, `retryable`, `retryAfterMs`): `not_in_app` (no\n`UNFZ_STATE_TOKEN`), `invalid_audience`, `invalid_request`, `invalid_token`,\n`rate_limited`, `oidc_disabled`, `temporarily_unavailable`, `unreachable`.\n\n```ts\nconst jwt = await unfazed.oidc.token(\"https://api.example.com\", { ttlSeconds: 300 });\nawait fetch(\"https://api.example.com/v1/things\", { headers: { Authorization: `Bearer ${jwt}` } });\n```\n\n### `oidc.tokenResponse(audience: string, options?: { ttlSeconds?, signal? }): Promise<{ token, expiresAt: Date }>`\n`oidc.token` with the token's expiry.\n\n### `oidc.fetchWithToken(audience: string, options?: { ttlSeconds?, fetch? }): fetch`\nA `fetch` that sends each request with `Authorization: Bearer <token>`\n(replacing one already there), everything else as given: for a client that\ntakes a `fetch`. An invalid audience (an unset variable) throws at once.\n\n```ts\nconst openai = new OpenAI({\n baseURL: process.env.PRIVATEINFERENCE_BASE_URL,\n apiKey: \"unused\",\n fetch: unfazed.oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),\n});\n```\n\n## Anything else\n\n`unfazed.client` is the typed GraphQL transport over the same credential:\n`await unfazed.client.query(document, variables)`. `operations` exports\nevery document the SDK uses.\n";
5
5
  //# sourceMappingURL=reference.generated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reference.generated.js","sourceRoot":"","sources":["../src/reference.generated.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yDAAyD;AAEzD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,SAAS,GAAW,+3dAA+3d,CAAC","sourcesContent":["// GENERATED by scripts/embed-reference.ts from REFERENCE.md. Do not edit:\n// change REFERENCE.md and run `bun run embed-reference`.\n\n/** The SDK's reference: every method with its signature (REFERENCE.md). */\nexport const REFERENCE: string = \"# unfazed SDK reference\\n\\nEvery method of `Unfazed`, with its signature, in one page. In a script:\\n`import { Unfazed, help } from \\\"unfazed\\\"; const unfazed = new Unfazed();`\\n(run_script injects the credential; elsewhere `UNFZ_API_TOKEN` or\\n`npx unfazed login`). `help()` returns this page; `help(\\\"writeFiles\\\")` one\\nentry. Methods are async and throw an `UnfazedError` subclass on failure.\\nEvery method takes an optional last `options` with `signal` (and, for single\\nrequests, `timeoutMs`). The names follow the MCP tools: `listCommits` is\\n`list_commits`, `waitForEvents` is `wait_for_events`.\\n\\n```ts\\nimport { Unfazed } from \\\"unfazed\\\";\\n\\nexport default async () => {\\n const unfazed = new Unfazed();\\n const ws = await unfazed.ensureWorkspace(\\\"hello\\\");\\n const wrote = await unfazed.writeFiles(ws.id, {\\n \\\"server.ts\\\": `export default { fetch: () => new Response(\\\"hi\\\") }`,\\n \\\"package.json\\\": JSON.stringify({ name: \\\"hello\\\" }),\\n }, { commitMessage: \\\"First version\\\" }); // ONE request, ONE commit\\n const app = await unfazed.deployService({ workspaceId: ws.id, alias: \\\"hello\\\", wait: true });\\n return { url: app.url, commit: wrote.commit?.sha, timings: app.timings };\\n};\\n```\\n\\n## Saved, committed, deployed\\n\\nA file write has three states. **Saved**: the call returned; the app's\\nfiles have it and the next deploy uses it. **Committed**: on an app with\\nplatform git, `commit.committed && !commit.waiting`; it is in `listCommits`.\\n**Deployed**: a deploy of those files went READY (`deployService({ wait:\\ntrue })`). A write never deploys by itself, except when `commit.deploys` is\\ntrue (the branch head is imported like a push).\\n\\n`commit.waiting: true` means saved, with the commit queued for the git\\nserver: it lands in order, `sha` is null until then, nothing to retry.\\n`waitForCommit` (or `writeFiles(…, { waitForCommit: true })`) resolves once\\nit landed.\\n\\n## Workspaces\\n\\n### `createWorkspace(name: string): Promise<Workspace>`\\nA new workspace: files, env, deployments. Names are unique per owner,\\ncase-insensitively; prefer `ensureWorkspace` in re-runnable scripts.\\n\\n### `ensureWorkspace(name: string): Promise<Workspace>`\\nThe workspace named `name`, created only when missing. Safe to re-run.\\n\\n### `listWorkspaces(): Promise<Workspace[]>`\\n\\n### `liveApps(workspaceId: string): Promise<LiveApp[]>`\\nThe apps the workspace serves now: `{ alias, deploymentID, domains }`.\\n\\n### `deleteWorkspace(workspaceId: string, options?: { withDeployments?: true, confirmAliases: string[], ignoreNotFound?: boolean }): Promise<boolean>`\\nDeletes it; with `withDeployments`, every app it serves, which\\n`confirmAliases` must name exactly (`liveApps`). Kept 30 days for support.\\n\\n### `deleteApp(app: { workspaceId, alias }, options?: { ignoreNotFound?: boolean }): Promise<DeleteAppResult>`\\nDeletes one app (every deployment of the alias, its App State, env and\\nbindings); the workspace and its other apps stay.\\n\\n### `whoami(): Promise<Whoami>`\\nWho the token is, its scopes, usage and limits.\\n\\n## Files\\n\\n### `writeFiles(workspaceId: string, files: Record<string, string | { text } | { base64 } | { bytes }>, options?: { commitMessage?: string, waitForCommit?: boolean | WaitOptions, allowPartial?: boolean }): Promise<WriteFilesResult>`\\nWrites every file in ONE request, which is ONE commit on an app with\\nplatform git (MCP `write_workspace_files`). Result: `{ files: [{ path, ok,\\nerror, file }], written, failed, totalBytes, commit, commits, landed? }`;\\n`commit` is `{ sha, branch, message, committed, waiting, waitingCount,\\ndeploys, conflicts, writeSHA, paused }` or null without platform git. Not\\ntransactional: when a file fails it throws `UnfazedWriteError` (`.result`,\\n`.failed`) unless `allowPartial`. Up to 100 files and 32 MiB per request; a\\nbigger map is several requests, each a commit.\\n\\n```ts\\nconst out = await unfazed.writeFiles(ws.id, { \\\"a.ts\\\": a, \\\"logo.png\\\": { bytes } }, { commitMessage: \\\"Logo\\\" });\\nif (out.commit?.conflicts.length) console.log(\\\"a push changed\\\", out.commit.conflicts, \\\"mine:\\\", out.commit.writeSHA);\\n```\\n\\n### `writeFile(workspaceId: string, path: string, text: string, options?: { commitMessage?, waitForCommit? }): Promise<WorkspaceFile & { commit }>`\\nOne text file: `writeFiles` with one entry.\\n\\n### `writeBinaryFile(workspaceId: string, path: string, content: Uint8Array | ArrayBuffer | { base64: string }, options?): Promise<WorkspaceFile & { commit }>`\\n\\n### `waitForCommit(workspaceId: string, waitOptions?: WaitOptions): Promise<{ branch, head, waitedMs }>`\\nResolves once no write of the app waits for its commit; `head` then has\\nevery earlier write. Immediate (`head: null`) without platform git. Default\\ntimeout 2 minutes (`UnfazedTimeoutError`; the writes stay saved).\\n\\n### `readFile(workspaceId: string, path: string, input?: { offset?, limit? }): Promise<{ text, totalBytes, truncated, nextOffset, file } | null>`\\n\\n### `listFiles(workspaceId: string, input?: { limit?, offset?, order? }): Promise<WorkspaceFilePage>`\\n\\n### `deleteFile(workspaceId: string, path: string): Promise<boolean>`\\n`false` when there was nothing to delete.\\n\\n## Deploys\\n\\n### `deployService(options: { workspaceId, alias?, wait?, waitOptions?, timings?, runtime?, serviceMode?, healthPath?, cronSchedule?, cronPath? }): Promise<DeployResult>`\\nBuilds and deploys the workspace as an HTTP service (`server.ts` default\\nexport, or `unfazed.json`). Returns BUILDING at once; with `wait: true`,\\nresolves READY or throws `UnfazedDeployError` (`reason` FAILED with the\\nbuild error, or TIMEOUT; default 10 minutes). Every call is a new release;\\nthe same `alias` redeploys that app.\\n\\nWith `wait: true` the result has `timings`:\\n`{ client: { createMs, waitMs, totalMs, waitRequests, waitMode },\\nplatform: { totalMs, buildMs, promoteMs, phases, buildMode,\\nartifactTransport, buildMemoryMiB } }`, both in milliseconds. `platform` is\\nwhat the platform measured (created to READY); `client` is what you waited.\\nThe wait is server-side, so the two differ by about one round trip.\\n\\n```ts\\nconst app = await unfazed.deployService({ workspaceId: ws.id, alias: \\\"api\\\", wait: true });\\napp.timings?.platform?.phases; // { vm_install: 900, vm_build: 2600, admission: 40, … }\\n```\\n\\n### `deployStatic(options: { workspaceId, alias?, wait?, spaMode? }): Promise<DeployResult>`\\n\\n### `deployBox(options: { workspaceId, alias, wait?, resources?: { memMiB?, vcpus?, diskGiB? } }): Promise<DeployResult>`\\nA persistent Linux VM over SSH (`sshCommand`). Always pass `alias`.\\n\\n### `deploy(input: CreateDeploymentInput, options?: { wait?, waitOptions?, timings? }): Promise<DeployResult>`\\nAny `createDeployment` input.\\n\\n### `waitForDeployment(target: string | { workspaceId, gitCommit, alias?, after? }, waitOptions?: WaitOptions): Promise<Deployment>`\\nWaits server-side (MCP `wait_for_deployment`: one request per 45 s at most,\\nanswered when the status changes) until READY; throws `UnfazedDeployError`\\non FAILED or timeout. A new app's landing on its server is waited for too.\\n`waitOptions: { timeoutMs?, signal?, mode?: \\\"server\\\" | \\\"poll\\\" }`. The\\nplatform holds at most 16 waits of one account at once; past that a wait\\nby id polls instead.\\n\\nInstead of an id, name the deploy a git import makes of a commit (a\\nrestore, a push or a sync deploys before any id exists): `{ workspaceId,\\ngitCommit, alias?, after? }`. The wait covers the import too, and a failed\\nimport throws `UnfazedDeployError` with its error. `alias` is required when\\nseveral apps (`apps[]`) deployed the commit; `after` (a restore's\\n`waitAfter`) counts only deployments begun after it.\\n\\n### `getDeployment(deploymentId: string): Promise<Deployment | null>`\\n\\n### `getDeploymentByAlias(alias: string): Promise<Deployment | null>`\\n\\n### `listDeployments(input?: { workspaceID?, alias?, kind?, limit?, offset? }): Promise<DeploymentPage>`\\nNewest first.\\n\\n### `listReleases(alias: string): Promise<Deployment[]>`\\nThe alias's history; `aliasHeld` marks the live one.\\n\\n### `rollback(options: { alias, to?, expectedCurrentId?, retries? }): Promise<Deployment>`\\nPoints the alias back at a prior READY release (default: the one before\\nthe live one). A bare `rollback({ alias })` toggles; pin `to` in anything\\nre-runnable.\\n\\n### `deleteDeployment(deploymentId: string, options?: { ignoreNotFound? }): Promise<boolean>`\\n\\n## Runtime, logs, events, timings\\n\\n### `getStatus(deploymentId: string): Promise<ServiceStatus>`\\n`{ running, health, inFlight, restartCount, memSizeMiB, lastError, … }`.\\n\\n### `getLogs(deploymentId: string, input?: { tailBytes? }): Promise<{ stdout, stderr, truncated, … }>`\\nFilter in the script: `logs.stderr.split(\\\"\\\\n\\\").filter((l) => l.includes(\\\"ERROR\\\"))`.\\n\\n### `getEvents(deploymentId: string, limit?: number): Promise<DeploymentEvent[]>`\\nThe latest `limit` (default 20) lifecycle events, oldest first: `{ type,\\ntypeEnum, severity, message, vmID, metadata, timings, createdAt }`. `type`\\nis the event's own name, as MCP reports it: `\\\"deployment.build_finished\\\"`,\\n`\\\"deployment.phases\\\"`, `\\\"service.vm_started\\\"`. `typeEnum` is the GraphQL\\nenum (`UNKNOWN` for names it lacks); match on `type`. A timing event's\\n`timings` is what it measured, parsed by the platform; null otherwise.\\n\\n### `getDeploymentTimings(deploymentId: string): Promise<PlatformTimings | null>`\\nA deploy's measured phases (ms), as the platform reads them from all its\\ntiming events: `{ totalMs, buildMs, promoteMs, phases, buildMode,\\nartifactTransport, buildMemoryMiB }`. `parseDeploymentTimings(events)` does\\nthe same for events you already read, preferring each event's `timings`;\\ndurations are whole, non-negative milliseconds (anything else is dropped).\\n\\n### `restartService(deploymentId: string)` / `stopService(deploymentId: string)`\\n\\n## Env and secrets\\n\\nSnapshotted into a release at deploy time: redeploy the alias to apply.\\n\\n### `setEnv(workspaceId, name, value)` / `setSecret(workspaceId, name, value)`: `Promise<EnvVar>`\\nWorkspace defaults. A name is plain or secret, never both.\\n\\n### `setEnvMany(workspaceId: string, vars: Record<string, string>, options?: { secret?: boolean }): Promise<EnvVar[]>`\\n\\n### `deleteEnv(workspaceId, name): Promise<boolean>` / `listEnv(workspaceId): Promise<EnvVar[]>`\\n\\n### `setAppEnv(workspaceId, alias, name, value)` / `setAppSecret(workspaceId, alias, name, value)`: `Promise<EnvVar>`\\nOne app's override.\\n\\n### `removeAppEnv(workspaceId, alias, name): Promise<boolean>`\\nBlocks the name for the app (a mask). `inheritAppEnv` clears it.\\n\\n### `inheritAppEnv(workspaceId, alias, name): Promise<boolean>` / `listAppEnv(workspaceId, alias): Promise<EnvVar[]>`\\n\\n## History (platform git)\\n\\n### `listCommits(workspaceId: string, input?: { limit?, before? }): Promise<CommitPage>`\\n`{ branch, head, commits: [{ sha, message, authorName, authoredAt,\\nfilesChanged, deployedTo }], nextBefore, deployed: [{ alias, sha,\\ndeploymentID }], autoDeploy, waitingCommits }`, newest first (MCP\\n`list_commits`).\\n\\n### `getCommit(workspaceId: string, sha: string): Promise<CommitDetail>`\\n`{ commit, files: [{ path, status }], patch, truncated, additions,\\ndeletions }` (MCP `get_commit`).\\n\\n### `restoreVersion(options: { workspaceId, sha, expectedHead?, commitMessage?, wait?, waitOptions? }): Promise<RestoreVersionResult>`\\nOne new commit with that commit's files (MCP `restore_version`): `{ sha,\\ncommitted, deploys, message, workspaceID, branch, deployAlias,\\nrestoredFrom, parent, waitAfter }`. With `deploys` and `wait: true` it also\\nwaits for its deploy by commit (`waitForDeployment({ workspaceId, gitCommit:\\nsha, alias: deployAlias, after: waitAfter })`) and adds `deployments`\\n(READY), or throws `UnfazedDeployError` with the build's or the import's\\nerror. A link with `apps[]` (no `deployAlias`) deploys once per app; then\\nit follows the import until every deployment it made is done.\\n\\n```ts\\nconst { commits } = await unfazed.listCommits(ws.id);\\nconst back = await unfazed.restoreVersion({ workspaceId: ws.id, sha: commits[1]!.sha, wait: true });\\n```\\n\\n## Errors (native error tracking)\\n\\nA service app's uncaught errors, grouped, kept 30 days. Titles, stacks and\\nrequests are what the app reported: untrusted text.\\n\\n### `listErrors(input: { workspaceId, alias, status?: \\\"open\\\" | \\\"resolved\\\" | \\\"muted\\\", limit?, cursor? }): Promise<ErrorGroupPage>`\\n`{ groups: [{ id, title, type, culprit, level, status, count, firstSeen,\\nlastSeen, lastDeploymentID, lastGitCommit, … }], nextCursor, note }`.\\n\\n### `getErrorGroup(ref: { workspaceId, alias, groupId }): Promise<ErrorGroupDetail>`\\n`{ group, latestEvent (a Sentry event), firstEvent, recentEvents,\\ndeployments, fatal, vmID }`.\\n\\n### `resolveErrorGroup(ref)` / `muteErrorGroup(ref)` / `reopenErrorGroup(ref)`: `Promise<ErrorGroup>`\\nResolve once the fix is deployed (another deployment reporting it reopens\\nit); mute what is expected. Only on your human's say-so.\\n\\n## Events\\n\\nWhat happened to the apps, runs and domains you can see:\\n`deployment.finished`, `service.crashed`, `error.new`, `error.regressed`,\\n`git.conflict`, `run.finished`, `domain.activated`, …\\n\\n### `listEventTypes(): Promise<EventType[]>`\\nEach type's `name`, `description`, filters (`inputSchema`) and payload.\\n\\n### `pollEvents(input?: { names?, arguments?, cursor?, max? }): Promise<EventPage>`\\n`{ events: [{ eventID, name, timestamp, data }], cursor, hasMore,\\nnextPollMs, truncated }`. Omit `cursor` to start from now; pass the returned\\none back.\\n\\n### `waitForEvents(input?: { names?, arguments?, cursor?, max?, timeoutMs? }): Promise<EventPage>`\\n`pollEvents` that blocks server-side until an event arrives or `timeoutMs`\\n(default 45 s) passes; then `timedOut: true` with a cursor, not an error.\\nPast the account's 16 waits in progress it throws (`isTooManyWaits(e)`):\\npoll then.\\n\\n```ts\\nlet page = await unfazed.pollEvents({ names: [\\\"deployment.finished\\\"], arguments: { \\\"deployment.finished\\\": { alias: \\\"api\\\" } } });\\n// … deploy …\\npage = await unfazed.waitForEvents({ names: [\\\"deployment.finished\\\"], cursor: page.cursor, timeoutMs: 120_000 });\\n```\\n\\n## Agent runs\\n\\n### `createRun(options: { workspaceId, prompt?, harness?, command?, setup?, verify?, access?, alias?, maxSeconds?, wait? }): Promise<Run>`\\nA one-shot agent run in a fresh VM. `wait: true` polls to SUCCEEDED,\\nFAILED or CANCELLED (all returned, never thrown).\\n\\n### `waitForRun(runId: string, waitOptions?: WaitOptions): Promise<Run>`\\n\\n### `getRun(runId)` / `listRuns(filter?: { workspaceId?, status?, limit?, offset? })` / `cancelRun(runId)`\\n\\n### `followUpRun(runId: string, options: { prompt, mode?, wait? }): Promise<Run>`\\n\\n### `answerRun(runId: string, answer: string): Promise<Run>`\\nAnswers a BLOCKED run's question.\\n\\n### `runEvents(runId)` / `runConsole(runId, { tailBytes? })` / `runTranscript(runId, { tailBytes? })`\\n\\n### `listRunHarnesses(): Promise<RunHarness[]>`\\n\\n## Anything else\\n\\n`unfazed.client` is the typed GraphQL transport over the same credential:\\n`await unfazed.client.query(document, variables)`. `operations` exports\\nevery document the SDK uses.\\n\";\n"]}
1
+ {"version":3,"file":"reference.generated.js","sourceRoot":"","sources":["../src/reference.generated.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yDAAyD;AAEzD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,SAAS,GAAW,ymiBAAymiB,CAAC","sourcesContent":["// GENERATED by scripts/embed-reference.ts from REFERENCE.md. Do not edit:\n// change REFERENCE.md and run `bun run embed-reference`.\n\n/** The SDK's reference: every method with its signature (REFERENCE.md). */\nexport const REFERENCE: string = \"# unfazed SDK reference\\n\\nEvery method of `Unfazed`, with its signature, in one page. In a script:\\n`import { Unfazed, help } from \\\"unfazed\\\"; const unfazed = new Unfazed();`\\n(run_script injects the credential; elsewhere `UNFZ_API_TOKEN` or\\n`npx unfazed login`; `oidc` needs neither). `help()` returns this page;\\n`help(\\\"writeFiles\\\")` one entry. Methods are async and throw an\\n`UnfazedError` subclass on failure.\\nEvery method takes an optional last `options` with `signal` (and, for single\\nrequests, `timeoutMs`). The names follow the MCP tools: `listCommits` is\\n`list_commits`, `waitForEvents` is `wait_for_events`.\\n\\n```ts\\nimport { Unfazed } from \\\"unfazed\\\";\\n\\nexport default async () => {\\n const unfazed = new Unfazed();\\n const ws = await unfazed.ensureWorkspace(\\\"hello\\\");\\n const wrote = await unfazed.writeFiles(ws.id, {\\n \\\"server.ts\\\": `export default { fetch: () => new Response(\\\"hi\\\") }`,\\n \\\"package.json\\\": JSON.stringify({ name: \\\"hello\\\" }),\\n }, { commitMessage: \\\"First version\\\" }); // ONE request, ONE commit\\n const app = await unfazed.deployService({ workspaceId: ws.id, alias: \\\"hello\\\", wait: true });\\n return { url: app.url, commit: wrote.commit?.sha, timings: app.timings };\\n};\\n```\\n\\n## Saved, committed, deployed\\n\\nA file write has three states. **Saved**: the call returned; the app's\\nfiles have it and the next deploy uses it. **Committed**: on an app with\\nplatform git, `commit.committed && !commit.waiting`; it is in `listCommits`.\\n**Deployed**: a deploy of those files went READY (`deployService({ wait:\\ntrue })`). A write never deploys by itself, except when `commit.deploys` is\\ntrue (the branch head is imported like a push).\\n\\n`commit.waiting: true` means saved, with the commit queued for the git\\nserver: it lands in order, `sha` is null until then, nothing to retry.\\n`waitForCommit` (or `writeFiles(…, { waitForCommit: true })`) resolves once\\nit landed.\\n\\n## Workspaces\\n\\n### `createWorkspace(name: string): Promise<Workspace>`\\nA new workspace: files, env, deployments. Names are unique per owner,\\ncase-insensitively; prefer `ensureWorkspace` in re-runnable scripts.\\n\\n### `ensureWorkspace(name: string): Promise<Workspace>`\\nThe workspace named `name`, created only when missing. Safe to re-run.\\n\\n### `listWorkspaces(): Promise<Workspace[]>`\\n\\n### `liveApps(workspaceId: string): Promise<LiveApp[]>`\\nThe apps the workspace serves now: `{ alias, deploymentID, domains }`.\\n\\n### `deleteWorkspace(workspaceId: string, options?: { withDeployments?: true, confirmAliases: string[], ignoreNotFound?: boolean }): Promise<boolean>`\\nDeletes it; with `withDeployments`, every app it serves, which\\n`confirmAliases` must name exactly (`liveApps`). Kept 30 days for support.\\n\\n### `deleteApp(app: { workspaceId, alias }, options?: { ignoreNotFound?: boolean }): Promise<DeleteAppResult>`\\nDeletes one app (every deployment of the alias, its App State, env and\\nbindings); the workspace and its other apps stay.\\n\\n### `whoami(): Promise<Whoami>`\\nWho the token is, its scopes, usage and limits.\\n\\n## Files\\n\\n### `writeFiles(workspaceId: string, files: Record<string, string | { text } | { base64 } | { bytes }>, options?: { commitMessage?: string, waitForCommit?: boolean | WaitOptions, allowPartial?: boolean }): Promise<WriteFilesResult>`\\nWrites every file in ONE request, which is ONE commit on an app with\\nplatform git (MCP `write_workspace_files`). Result: `{ files: [{ path, ok,\\nerror, file }], written, failed, totalBytes, commit, commits, landed? }`;\\n`commit` is `{ sha, branch, message, committed, waiting, waitingCount,\\ndeploys, conflicts, writeSHA, paused }` or null without platform git. Not\\ntransactional: when a file fails it throws `UnfazedWriteError` (`.result`,\\n`.failed`) unless `allowPartial`. Up to 100 files and 32 MiB per request; a\\nbigger map is several requests, each a commit.\\n\\n```ts\\nconst out = await unfazed.writeFiles(ws.id, { \\\"a.ts\\\": a, \\\"logo.png\\\": { bytes } }, { commitMessage: \\\"Logo\\\" });\\nif (out.commit?.conflicts.length) console.log(\\\"a push changed\\\", out.commit.conflicts, \\\"mine:\\\", out.commit.writeSHA);\\n```\\n\\n### `writeFile(workspaceId: string, path: string, text: string, options?: { commitMessage?, waitForCommit? }): Promise<WorkspaceFile & { commit }>`\\nOne text file: `writeFiles` with one entry.\\n\\n### `writeBinaryFile(workspaceId: string, path: string, content: Uint8Array | ArrayBuffer | { base64: string }, options?): Promise<WorkspaceFile & { commit }>`\\n\\n### `waitForCommit(workspaceId: string, waitOptions?: WaitOptions): Promise<{ branch, head, waitedMs }>`\\nResolves once no write of the app waits for its commit; `head` then has\\nevery earlier write. Immediate (`head: null`) without platform git. Default\\ntimeout 2 minutes (`UnfazedTimeoutError`; the writes stay saved).\\n\\n### `readFile(workspaceId: string, path: string, input?: { offset?, limit? }): Promise<{ text, totalBytes, truncated, nextOffset, file } | null>`\\n\\n### `listFiles(workspaceId: string, input?: { limit?, offset?, order? }): Promise<WorkspaceFilePage>`\\n\\n### `deleteFile(workspaceId: string, path: string): Promise<boolean>`\\n`false` when there was nothing to delete.\\n\\n## Deploys\\n\\n### `deployService(options: { workspaceId, alias?, wait?, waitOptions?, timings?, runtime?, serviceMode?, healthPath?, cronSchedule?, cronPath? }): Promise<DeployResult>`\\nBuilds and deploys the workspace as an HTTP service (`server.ts` default\\nexport, or `unfazed.json`). Returns BUILDING at once; with `wait: true`,\\nresolves READY or throws `UnfazedDeployError` (`reason` FAILED with the\\nbuild error, or TIMEOUT; default 10 minutes). Every call is a new release;\\nthe same `alias` redeploys that app.\\n\\nWith `wait: true` the result has `timings`:\\n`{ client: { createMs, waitMs, totalMs, waitRequests, waitMode },\\nplatform: { totalMs, buildMs, promoteMs, phases, buildMode,\\nartifactTransport, buildMemoryMiB } }`, both in milliseconds. `platform` is\\nwhat the platform measured (created to READY); `client` is what you waited.\\nThe wait is server-side, so the two differ by about one round trip.\\n\\n```ts\\nconst app = await unfazed.deployService({ workspaceId: ws.id, alias: \\\"api\\\", wait: true });\\napp.timings?.platform?.phases; // { vm_install: 900, vm_build: 2600, admission: 40, … }\\n```\\n\\n### `deployStatic(options: { workspaceId, alias?, wait?, spaMode? }): Promise<DeployResult>`\\n\\n### `deployBox(options: { workspaceId, alias, wait?, resources?: { memMiB?, vcpus?, diskGiB? } }): Promise<DeployResult>`\\nA persistent Linux VM over SSH (`sshCommand`). Always pass `alias`.\\n\\n### `deploy(input: CreateDeploymentInput, options?: { wait?, waitOptions?, timings? }): Promise<DeployResult>`\\nAny `createDeployment` input.\\n\\n### `waitForDeployment(target: string | { workspaceId, gitCommit, alias?, after? }, waitOptions?: WaitOptions): Promise<Deployment>`\\nWaits server-side (MCP `wait_for_deployment`: one request per 45 s at most,\\nanswered when the status changes) until READY; throws `UnfazedDeployError`\\non FAILED or timeout. A new app's landing on its server is waited for too.\\n`waitOptions: { timeoutMs?, signal?, mode?: \\\"server\\\" | \\\"poll\\\" }`. The\\nplatform holds at most 16 waits of one account at once; past that a wait\\nby id polls instead.\\n\\nInstead of an id, name the deploy a git import makes of a commit (a\\nrestore, a push or a sync deploys before any id exists): `{ workspaceId,\\ngitCommit, alias?, after? }`. The wait covers the import too, and a failed\\nimport throws `UnfazedDeployError` with its error. `alias` is required when\\nseveral apps (`apps[]`) deployed the commit; `after` (a restore's\\n`waitAfter`) counts only deployments begun after it.\\n\\n### `getDeployment(deploymentId: string): Promise<Deployment | null>`\\n\\n### `getDeploymentByAlias(alias: string): Promise<Deployment | null>`\\n\\n### `listDeployments(input?: { workspaceID?, alias?, kind?, limit?, offset? }): Promise<DeploymentPage>`\\nNewest first.\\n\\n### `listReleases(alias: string): Promise<Deployment[]>`\\nThe alias's history; `aliasHeld` marks the live one.\\n\\n### `rollback(options: { alias, to?, expectedCurrentId?, retries? }): Promise<Deployment>`\\nPoints the alias back at a prior READY release (default: the one before\\nthe live one). A bare `rollback({ alias })` toggles; pin `to` in anything\\nre-runnable.\\n\\n### `deleteDeployment(deploymentId: string, options?: { ignoreNotFound? }): Promise<boolean>`\\n\\n## Runtime, logs, events, timings\\n\\n### `getStatus(deploymentId: string): Promise<ServiceStatus>`\\n`{ running, health, inFlight, restartCount, memSizeMiB, lastError, … }`.\\n\\n### `getLogs(deploymentId: string, input?: { tailBytes? }): Promise<{ stdout, stderr, truncated, … }>`\\nFilter in the script: `logs.stderr.split(\\\"\\\\n\\\").filter((l) => l.includes(\\\"ERROR\\\"))`.\\n\\n### `getEvents(deploymentId: string, limit?: number): Promise<DeploymentEvent[]>`\\nThe latest `limit` (default 20) lifecycle events, oldest first: `{ type,\\ntypeEnum, severity, message, vmID, metadata, timings, createdAt }`. `type`\\nis the event's own name, as MCP reports it: `\\\"deployment.build_finished\\\"`,\\n`\\\"deployment.phases\\\"`, `\\\"service.vm_started\\\"`. `typeEnum` is the GraphQL\\nenum (`UNKNOWN` for names it lacks); match on `type`. A timing event's\\n`timings` is what it measured, parsed by the platform; null otherwise.\\n\\n### `getDeploymentTimings(deploymentId: string): Promise<PlatformTimings | null>`\\nA deploy's measured phases (ms), as the platform reads them from all its\\ntiming events: `{ totalMs, buildMs, promoteMs, phases, buildMode,\\nartifactTransport, buildMemoryMiB }`. `parseDeploymentTimings(events)` does\\nthe same for events you already read, preferring each event's `timings`;\\ndurations are whole, non-negative milliseconds (anything else is dropped).\\n\\n### `restartService(deploymentId: string)` / `stopService(deploymentId: string)`\\n\\n## Env and secrets\\n\\nSnapshotted into a release at deploy time: redeploy the alias to apply.\\n\\n### `setEnv(workspaceId, name, value)` / `setSecret(workspaceId, name, value)`: `Promise<EnvVar>`\\nWorkspace defaults. A name is plain or secret, never both.\\n\\n### `setEnvMany(workspaceId: string, vars: Record<string, string>, options?: { secret?: boolean }): Promise<EnvVar[]>`\\n\\n### `deleteEnv(workspaceId, name): Promise<boolean>` / `listEnv(workspaceId): Promise<EnvVar[]>`\\n\\n### `setAppEnv(workspaceId, alias, name, value)` / `setAppSecret(workspaceId, alias, name, value)`: `Promise<EnvVar>`\\nOne app's override.\\n\\n### `removeAppEnv(workspaceId, alias, name): Promise<boolean>`\\nBlocks the name for the app (a mask). `inheritAppEnv` clears it.\\n\\n### `inheritAppEnv(workspaceId, alias, name): Promise<boolean>` / `listAppEnv(workspaceId, alias): Promise<EnvVar[]>`\\n\\n## History (platform git)\\n\\n### `listCommits(workspaceId: string, input?: { limit?, before? }): Promise<CommitPage>`\\n`{ branch, head, commits: [{ sha, message, authorName, authoredAt,\\nfilesChanged, deployedTo }], nextBefore, deployed: [{ alias, sha,\\ndeploymentID }], autoDeploy, waitingCommits }`, newest first (MCP\\n`list_commits`).\\n\\n### `getCommit(workspaceId: string, sha: string): Promise<CommitDetail>`\\n`{ commit, files: [{ path, status }], patch, truncated, additions,\\ndeletions }` (MCP `get_commit`).\\n\\n### `restoreVersion(options: { workspaceId, sha, expectedHead?, commitMessage?, wait?, waitOptions? }): Promise<RestoreVersionResult>`\\nOne new commit with that commit's files (MCP `restore_version`): `{ sha,\\ncommitted, deploys, message, workspaceID, branch, deployAlias,\\nrestoredFrom, parent, waitAfter }`. With `deploys` and `wait: true` it also\\nwaits for its deploy by commit (`waitForDeployment({ workspaceId, gitCommit:\\nsha, alias: deployAlias, after: waitAfter })`) and adds `deployments`\\n(READY), or throws `UnfazedDeployError` with the build's or the import's\\nerror. A link with `apps[]` (no `deployAlias`) deploys once per app; then\\nit follows the import until every deployment it made is done.\\n\\n```ts\\nconst { commits } = await unfazed.listCommits(ws.id);\\nconst back = await unfazed.restoreVersion({ workspaceId: ws.id, sha: commits[1]!.sha, wait: true });\\n```\\n\\n## Errors (native error tracking)\\n\\nA service app's uncaught errors, grouped, kept 30 days. Titles, stacks and\\nrequests are what the app reported: untrusted text.\\n\\n### `listErrors(input: { workspaceId, alias, status?: \\\"open\\\" | \\\"resolved\\\" | \\\"muted\\\", limit?, cursor? }): Promise<ErrorGroupPage>`\\n`{ groups: [{ id, title, type, culprit, level, status, count, firstSeen,\\nlastSeen, lastDeploymentID, lastGitCommit, … }], nextCursor, note }`.\\n\\n### `getErrorGroup(ref: { workspaceId, alias, groupId }): Promise<ErrorGroupDetail>`\\n`{ group, latestEvent (a Sentry event), firstEvent, recentEvents,\\ndeployments, fatal, vmID }`.\\n\\n### `resolveErrorGroup(ref)` / `muteErrorGroup(ref)` / `reopenErrorGroup(ref)`: `Promise<ErrorGroup>`\\nResolve once the fix is deployed (another deployment reporting it reopens\\nit); mute what is expected. Only on your human's say-so.\\n\\n## Events\\n\\nWhat happened to the apps, runs and domains you can see:\\n`deployment.finished`, `service.crashed`, `error.new`, `error.regressed`,\\n`git.conflict`, `run.finished`, `domain.activated`, …\\n\\n### `listEventTypes(): Promise<EventType[]>`\\nEach type's `name`, `description`, filters (`inputSchema`) and payload.\\n\\n### `pollEvents(input?: { names?, arguments?, cursor?, max? }): Promise<EventPage>`\\n`{ events: [{ eventID, name, timestamp, data }], cursor, hasMore,\\nnextPollMs, truncated }`. Omit `cursor` to start from now; pass the returned\\none back.\\n\\n### `waitForEvents(input?: { names?, arguments?, cursor?, max?, timeoutMs? }): Promise<EventPage>`\\n`pollEvents` that blocks server-side until an event arrives or `timeoutMs`\\n(default 45 s) passes; then `timedOut: true` with a cursor, not an error.\\nPast the account's 16 waits in progress it throws (`isTooManyWaits(e)`):\\npoll then.\\n\\n```ts\\nlet page = await unfazed.pollEvents({ names: [\\\"deployment.finished\\\"], arguments: { \\\"deployment.finished\\\": { alias: \\\"api\\\" } } });\\n// … deploy …\\npage = await unfazed.waitForEvents({ names: [\\\"deployment.finished\\\"], cursor: page.cursor, timeoutMs: 120_000 });\\n```\\n\\n## Agent runs\\n\\n### `createRun(options: { workspaceId, prompt?, harness?, command?, setup?, verify?, access?, alias?, maxSeconds?, wait? }): Promise<Run>`\\nA one-shot agent run in a fresh VM. `wait: true` polls to SUCCEEDED,\\nFAILED or CANCELLED (all returned, never thrown).\\n\\n### `waitForRun(runId: string, waitOptions?: WaitOptions): Promise<Run>`\\n\\n### `getRun(runId)` / `listRuns(filter?: { workspaceId?, status?, limit?, offset? })` / `cancelRun(runId)`\\n\\n### `followUpRun(runId: string, options: { prompt, mode?, wait? }): Promise<Run>`\\n\\n### `answerRun(runId: string, answer: string): Promise<Run>`\\nAnswers a BLOCKED run's question.\\n\\n### `runEvents(runId)` / `runConsole(runId, { tailBytes? })` / `runTranscript(runId, { tailBytes? })`\\n\\n### `listRunHarnesses(): Promise<RunHarness[]>`\\n\\n## Workload identity\\n\\nOIDC tokens the platform issues to the app the code runs in (a service or a\\ncron job): short-lived JWTs for one audience, from the issuer\\n`https://unfazed.dev/oidc`, which a service that trusts it takes instead of\\nan API key. Claims include `sub`, `aud`, `owner_id`, `project_id`, `app_id`,\\n`alias`, `environment` and `deployment_id`. The only credential is the app's\\n`UNFZ_STATE_TOKEN`, set in each of its VMs: no API token, so a bare\\n`new Unfazed()` works in an app for this. Without an instance:\\n`import { oidc, oidcToken } from \\\"unfazed\\\"` (`oidcToken(audience)` is\\n`oidc.token(audience)`).\\n\\n### `oidc.token(audience: string, options?: { ttlSeconds?, signal? }): Promise<string>`\\nA JWT for `audience` (1 to 256 printable ASCII characters, no whitespace);\\n`ttlSeconds` from 60 to 3600, else the platform's default. Cached per\\n(audience, ttl) until 80% of its lifetime; concurrent calls share one\\nrequest. 503s, network failures and 429s (after their `Retry-After`) are\\nretried up to 3 times; if a refresh still fails that way, the previous token\\ncomes back while it is valid. Throws `UnfazedOIDCError` (`code`, `status`,\\n`description`, `retryable`, `retryAfterMs`): `not_in_app` (no\\n`UNFZ_STATE_TOKEN`), `invalid_audience`, `invalid_request`, `invalid_token`,\\n`rate_limited`, `oidc_disabled`, `temporarily_unavailable`, `unreachable`.\\n\\n```ts\\nconst jwt = await unfazed.oidc.token(\\\"https://api.example.com\\\", { ttlSeconds: 300 });\\nawait fetch(\\\"https://api.example.com/v1/things\\\", { headers: { Authorization: `Bearer ${jwt}` } });\\n```\\n\\n### `oidc.tokenResponse(audience: string, options?: { ttlSeconds?, signal? }): Promise<{ token, expiresAt: Date }>`\\n`oidc.token` with the token's expiry.\\n\\n### `oidc.fetchWithToken(audience: string, options?: { ttlSeconds?, fetch? }): fetch`\\nA `fetch` that sends each request with `Authorization: Bearer <token>`\\n(replacing one already there), everything else as given: for a client that\\ntakes a `fetch`. An invalid audience (an unset variable) throws at once.\\n\\n```ts\\nconst openai = new OpenAI({\\n baseURL: process.env.PRIVATEINFERENCE_BASE_URL,\\n apiKey: \\\"unused\\\",\\n fetch: unfazed.oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),\\n});\\n```\\n\\n## Anything else\\n\\n`unfazed.client` is the typed GraphQL transport over the same credential:\\n`await unfazed.client.query(document, variables)`. `operations` exports\\nevery document the SDK uses.\\n\";\n"]}