@helmr/sdk 0.1.1-rc.6 → 0.1.1-rc.8

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/README.md CHANGED
@@ -13,9 +13,9 @@ Keep this layer stable and ergonomic. Runtime adapter internals, VM details, and
13
13
 
14
14
  Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload; use `secrets:` instead. Use payload for business context such as PR numbers, repo names, ticket ids, and other identifiers.
15
15
 
16
- Remote API/CLI run secret bindings are vault references, not host-local sources. Use
17
- `vault:SECRET_NAME` in `POST /api/runs` `secrets` and `helmr run --secret NAME=vault:SECRET_NAME`.
18
- Schemes such as `env:` and `file:` are local runner sources and are rejected for remote runs.
16
+ Task code declares required secret names and runtime placements. Store each value
17
+ with `helmr secret set NAME`; run creation does not accept secret values or
18
+ binding maps.
19
19
 
20
20
  ## TypeScript Runtime Client
21
21
 
@@ -37,7 +37,7 @@ const handle = await client.tasks.trigger<typeof impl>(
37
37
 
38
38
  const current = await client.runs.retrieve(handle)
39
39
  const pendingWaitpoint = current.pendingWaitpoint
40
- if (pendingWaitpoint !== null && pendingWaitpoint.kind === "manual") {
40
+ if (pendingWaitpoint !== null && pendingWaitpoint.kind === "human") {
41
41
  await client.waitpoints.respond(pendingWaitpoint, {
42
42
  value: { approved: true },
43
43
  })
@@ -69,10 +69,6 @@ await client.waitpoints.respond("waitpoint-456", {
69
69
 
70
70
  ### `POST /api/runs`
71
71
 
72
- `payload` is a JSON field for tasks that accept payload. Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload; use `secrets:` instead. Use payload for business context such as PR numbers, repo names, ticket ids, and other identifiers.
72
+ `payload` is a JSON field for tasks that accept payload. Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload; declare task secrets instead. Use payload for business context such as PR numbers, repo names, ticket ids, and other identifiers.
73
73
 
74
- `secrets` maps declared task secret names to vault URIs:
75
-
76
- ```json
77
- { "OPENAI_API_KEY": "vault:openai-api-key" }
78
- ```
74
+ Runs do not accept a `secrets` field. Secret names and placements are part of the deployed task definition.
package/dist/compile.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  FromSchema,
15
15
  ImageSpecSchema,
16
16
  ImageStepSchema,
17
+ NetworkPolicySchema,
17
18
  PlacementSchema as BundlePlacementSchema,
18
19
  PlatformSchema,
19
20
  QueueSpecSchema,
@@ -68,12 +69,8 @@ function validateSecretName(name, label = "secret name") {
68
69
  if (name.length > 128) {
69
70
  throw new Error(`${label} must be at most 128 characters`);
70
71
  }
71
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
72
- throw new Error(`${label} must match /^[A-Za-z_][A-Za-z0-9_]*$/`);
73
- }
74
- const upper = name.toUpperCase();
75
- if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
76
- throw new Error(`${label} is reserved`);
72
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
73
+ throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
77
74
  }
78
75
  }
79
76
  var taskBrand = Symbol.for("helmr.sdk.Task");
@@ -116,6 +113,7 @@ function compile(opts) {
116
113
  const subImages = compiler.compileSubImages(imageSpec);
117
114
  const workspace = compiler.compileWorkspace(task.sandbox);
118
115
  const resources = task.sandbox.resourceSpec;
116
+ const network = task.sandbox.networkSpec;
119
117
  const maxDurationSeconds = readOptionalMaxDurationSeconds(task.maxDuration, `task "${task.id}" maxDuration`);
120
118
  const sandboxSpec = create(SandboxSpecSchema, {
121
119
  id: task.sandbox.id,
@@ -126,7 +124,8 @@ function compile(opts) {
126
124
  ...resources.memory === undefined ? {} : { memory: resources.memory },
127
125
  ...resources.disk === undefined ? {} : { disk: resources.disk }
128
126
  })
129
- } : {}
127
+ } : {},
128
+ ...network ? { network: compileNetwork(network) } : {}
130
129
  });
131
130
  return create(BundleSchema, {
132
131
  image: imageSpec,
@@ -143,6 +142,7 @@ function compile(opts) {
143
142
  ...task.queue?.concurrencyLimit === undefined || task.queue.concurrencyLimit === null ? {} : { concurrencyLimit: task.queue.concurrencyLimit }
144
143
  }),
145
144
  ...task.ttl === undefined ? {} : { ttl: task.ttl },
145
+ retryPolicyJson: JSON.stringify(task.retry ?? false),
146
146
  schedules: compileTaskSchedules(task.schedule),
147
147
  secrets: Object.entries(readSecretDecls(task.secrets)).map(([name, placement]) => create(BundleSecretPlacementSchema, {
148
148
  name,
@@ -151,6 +151,13 @@ function compile(opts) {
151
151
  })
152
152
  });
153
153
  }
154
+ function compileNetwork(network) {
155
+ return create(NetworkPolicySchema, {
156
+ internet: network.internet,
157
+ allow: [...network.allow],
158
+ deny: [...network.deny]
159
+ });
160
+ }
154
161
  function compileTaskSchedules(schedule) {
155
162
  if (schedule === undefined) {
156
163
  return [];
@@ -159,8 +166,7 @@ function compileTaskSchedules(schedule) {
159
166
  create(TaskScheduleSpecSchema, {
160
167
  id: "",
161
168
  cron: schedule.cron,
162
- timezone: schedule.timezone ?? "UTC",
163
- secretBindings: { ...schedule.secretBindings }
169
+ timezone: schedule.timezone ?? "UTC"
164
170
  })
165
171
  ];
166
172
  }
@@ -426,14 +432,27 @@ function readSecretDecls(value) {
426
432
  if (value === undefined) {
427
433
  return {};
428
434
  }
429
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
430
- throw new Error("task secrets must be an object literal");
435
+ if (!Array.isArray(value)) {
436
+ throw new Error("task secrets must be an array");
431
437
  }
432
- const record = value;
433
- return Object.fromEntries(Object.entries(record).map(([name, placement]) => {
434
- validateSecretName(name, `task secrets.${name}`);
435
- return [name, readPlacement(placement, `task secrets.${name}`)];
436
- }));
438
+ const output = {};
439
+ value.forEach((item, index) => {
440
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
441
+ throw new Error(`task secrets.${index} must be a secret object`);
442
+ }
443
+ const record = item;
444
+ const name = record["name"];
445
+ if (typeof name !== "string") {
446
+ throw new Error(`task secrets.${index}.name must be a string`);
447
+ }
448
+ validateSecretName(name, `task secrets.${index}.name`);
449
+ if (Object.hasOwn(output, name)) {
450
+ throw new Error(`task secrets contains duplicate secret ${JSON.stringify(name)}`);
451
+ }
452
+ const { name: _name, ...placement } = record;
453
+ output[name] = readPlacement(placement, `task secrets.${index}`);
454
+ });
455
+ return output;
437
456
  }
438
457
  function readPlacement(value, label) {
439
458
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -445,6 +464,7 @@ function readPlacement(value, label) {
445
464
  if (Object.keys(record).length !== 1 || !isNonEmptyPlacementString(env)) {
446
465
  throw new Error(`${label} must be { env: string }`);
447
466
  }
467
+ validateEnvPlacementName(env, `${label}.env`);
448
468
  return { env };
449
469
  }
450
470
  if ("file" in record) {
@@ -508,6 +528,11 @@ function validatePlacementMode(mode, label) {
508
528
  throw new Error(`${label} must only contain permission bits`);
509
529
  }
510
530
  }
531
+ function validateEnvPlacementName(value, label) {
532
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
533
+ throw new Error(`${label} must match /^[A-Za-z_][A-Za-z0-9_]*$/`);
534
+ }
535
+ }
511
536
  function validatePlacementPath(path, label) {
512
537
  const normalized = path.trim().replaceAll("\\", "/");
513
538
  if (path !== path.trim()) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConcurrentWaitError, validateSecretName, type CacheBuilder, type CacheMount, type CacheMountBinding, type EmitContent, type EmitEvent, type ImageBuilder, type ImageRunOptions, type NoPayload, type Placement, type SandboxBuilder, type SecretDecls, type SecretMountBinding, type SourceCapabilities, type SourceDirRef, type SourceDirectoryOptions, type SourceFileRef, type TaskContext, type TaskWorkspace, type TaskOutput, type TaskPayload, type TaskTriggerPayload, type WaitCapabilities, type WaitForInput, type WaitJson, type WaitOptions, type WaitResolution, type WaitManualOptions, type WaitUntilInput } from "./internal.js";
1
+ import { ConcurrentWaitError, validateSecretName, type CacheBuilder, type CacheMount, type CacheMountBinding, type EmitContent, type EmitEvent, type ImageBuilder, type ImageRunOptions, type NoPayload, type Placement, type SandboxBuilder, type SandboxNetwork, type SecretDecls, type SecretMountBinding, type SourceCapabilities, type SourceDirRef, type SourceDirectoryOptions, type SourceFileRef, type TaskContext, type TaskWorkspace, type TaskOutput, type TaskPayload, type TaskTriggerPayload, type WaitCapabilities, type WaitForInput, type WaitJson, type WaitOptions, type WaitResolution, type WaitHumanOptions, type WaitUntilInput } from "./internal.js";
2
2
  import { PayloadSchemaValidationError } from "./schema/payload.js";
3
3
  import { idempotencyKeys } from "./idempotency.js";
4
4
  export type { IdempotencyKey, IdempotencyKeyCreateOptions, IdempotencyKeyInput, IdempotencyKeyScope } from "./idempotency.js";
@@ -12,9 +12,9 @@ import { task as scheduledTask, type ScheduleCron, type ScheduledTaskConfig, typ
12
12
  import { tasks } from "./trigger.js";
13
13
  export { AuthError, RunNotFoundError, TimeoutError, UnsupportedTransportError } from "./runtime/errors.js";
14
14
  export { ConcurrentWaitError, PayloadSchemaValidationError };
15
- export { type LogSnapshot, type ListRunEventsOptions, type ListRunsOptions, type PendingDelayWaitpoint, type PendingManualWaitpoint, type PendingWaitpoint, type RetrieveRunOptions, type RunEvent, type RunEventRecord, type RunEventRecordPage, type RunHandle, type RunSnapshot, type RunStatus, type RunSummary, type RunWaitOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef, } from "./runtime/run.js";
15
+ export { type LogSnapshot, type ListRunEventsOptions, type ListRunsOptions, type PendingDelayWaitpoint, type PendingHumanWaitpoint, type PendingWaitpoint, type RetrieveRunOptions, type RunEvent, type RunEventRecord, type RunEventRecordPage, type RunHandle, type RunSnapshot, type RunStatus, type RunSummary, type RunWaitOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef, } from "./runtime/run.js";
16
16
  export { defineConfig, idempotencyKeys, queue, sandbox, task, tasks };
17
- export type { HelmrConfig, HelmrConfigInput, Placement, SecretDecls, Task, TaskConfig, TaskQueueConfig, TaskOutput, TaskPayload, TaskTriggerPayload, WaitCapabilities, WaitForInput, WaitJson, WaitOptions, WaitResolution, WaitManualOptions, WaitUntilInput, PayloadSchema, StandardSchemaV1, NoPayload, };
17
+ export type { HelmrConfig, HelmrConfigInput, Placement, SandboxNetwork, SecretDecls, Task, TaskConfig, TaskQueueConfig, TaskOutput, TaskPayload, TaskTriggerPayload, WaitCapabilities, WaitForInput, WaitJson, WaitOptions, WaitResolution, WaitHumanOptions, WaitUntilInput, PayloadSchema, StandardSchemaV1, NoPayload, };
18
18
  export declare const image: (id: string) => ImageBuilder;
19
19
  export declare const cache: (id: string) => CacheBuilder;
20
20
  export declare const source: SourceCapabilities;
@@ -23,6 +23,8 @@ export { validateSecretName };
23
23
  export declare const runs: {
24
24
  retrieve: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./index.js").RetrieveRunOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
25
25
  wait: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./index.js").RunWaitOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
26
+ cancel: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./runtime/run.js").CancelRunOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
27
+ replay: <TPayload = unknown, TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./runtime/run.js").ReplayRunOptions<TPayload>) => Promise<import("./index.js").RunHandle<TOutput>>;
26
28
  list: (opts?: import("./index.js").ListRunsOptions) => Promise<import("./index.js").RunSummary[]>;
27
29
  logs: {
28
30
  retrieve: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: {
package/dist/index.js CHANGED
@@ -212,12 +212,8 @@ function validateSecretName(name, label = "secret name") {
212
212
  if (name.length > 128) {
213
213
  throw new Error(`${label} must be at most 128 characters`);
214
214
  }
215
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
216
- throw new Error(`${label} must match /^[A-Za-z_][A-Za-z0-9_]*$/`);
217
- }
218
- const upper = name.toUpperCase();
219
- if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
220
- throw new Error(`${label} is reserved`);
215
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
216
+ throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
221
217
  }
222
218
  }
223
219
  function validateImageBuildSecretRef(name) {
@@ -283,6 +279,7 @@ function markTaskInternal(config, schedule) {
283
279
  validateTaskQueue(config.id, config.queue);
284
280
  validateTaskSchedule(config.id, schedule);
285
281
  validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
282
+ validateRetryPolicy(config.retry, `task ${JSON.stringify(config.id)} retry`);
286
283
  assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
287
284
  if (schedule !== undefined) {
288
285
  Object.defineProperty(config, "schedule", {
@@ -294,6 +291,54 @@ function markTaskInternal(config, schedule) {
294
291
  Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
295
292
  return config;
296
293
  }
294
+ function validateRetryPolicy(value, label = "retry") {
295
+ if (value === undefined || value === false) {
296
+ return;
297
+ }
298
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
299
+ throw new Error(`${label} must be false or a retry policy object`);
300
+ }
301
+ const record = value;
302
+ for (const key of Object.keys(record)) {
303
+ if (key !== "maxAttempts" && key !== "backoff") {
304
+ throw new Error(`${label}.${key} is not supported`);
305
+ }
306
+ }
307
+ if (typeof record["maxAttempts"] !== "number" || !Number.isInteger(record["maxAttempts"]) || record["maxAttempts"] < 1 || record["maxAttempts"] > 10) {
308
+ throw new Error(`${label}.maxAttempts must be an integer between 1 and 10`);
309
+ }
310
+ const backoff = record["backoff"];
311
+ if (backoff !== undefined) {
312
+ if (backoff === null || typeof backoff !== "object" || Array.isArray(backoff)) {
313
+ throw new Error(`${label}.backoff must be an object`);
314
+ }
315
+ const backoffRecord = backoff;
316
+ for (const key of Object.keys(backoffRecord)) {
317
+ if (key !== "minMs" && key !== "maxMs" && key !== "factor" && key !== "jitter") {
318
+ throw new Error(`${label}.backoff.${key} is not supported`);
319
+ }
320
+ }
321
+ validateOptionalPositiveInteger(backoffRecord["minMs"], `${label}.backoff.minMs`);
322
+ validateOptionalPositiveInteger(backoffRecord["maxMs"], `${label}.backoff.maxMs`);
323
+ const factor = backoffRecord["factor"];
324
+ if (factor !== undefined && (typeof factor !== "number" || !Number.isFinite(factor) || factor <= 0)) {
325
+ throw new Error(`${label}.backoff.factor must be a positive number`);
326
+ }
327
+ const jitter = backoffRecord["jitter"];
328
+ if (jitter !== undefined && jitter !== "none" && jitter !== "full") {
329
+ throw new Error(`${label}.backoff.jitter must be "none" or "full"`);
330
+ }
331
+ }
332
+ }
333
+ function validateOptionalPositiveInteger(value, label) {
334
+ if (value === undefined) {
335
+ return;
336
+ }
337
+ if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
338
+ return;
339
+ }
340
+ throw new Error(`${label} must be a positive integer`);
341
+ }
297
342
  function validateTaskSchedule(taskId, value) {
298
343
  if (value === undefined) {
299
344
  return;
@@ -304,17 +349,6 @@ function validateTaskSchedule(taskId, value) {
304
349
  if (value.timezone !== undefined && value.timezone.trim() === "") {
305
350
  throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
306
351
  }
307
- if (value.secretBindings !== undefined) {
308
- if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
309
- throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
310
- }
311
- for (const [name, binding] of Object.entries(value.secretBindings)) {
312
- validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
313
- if (typeof binding !== "string" || binding.trim() === "") {
314
- throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
315
- }
316
- }
317
- }
318
352
  }
319
353
  function validateTaskQueue(taskId, value) {
320
354
  if (value === undefined) {
@@ -479,21 +513,23 @@ class SandboxBuilderImpl {
479
513
  imageBuilder;
480
514
  workspaceBinding;
481
515
  resourceSpec;
482
- constructor(id, imageBuilder, workspaceBinding, resourceSpec) {
516
+ networkSpec;
517
+ constructor(id, imageBuilder, workspaceBinding, resourceSpec, networkSpec) {
483
518
  Object.defineProperty(this, sandboxBuilderBrand, { value: true });
484
519
  this.id = id;
485
520
  this.imageBuilder = imageBuilder;
486
521
  this.workspaceBinding = workspaceBinding;
487
522
  this.resourceSpec = resourceSpec;
523
+ this.networkSpec = networkSpec;
488
524
  }
489
525
  image(img) {
490
526
  if (!isImageBuilder(img)) {
491
527
  throw new Error("sandbox.image() requires an ImageBuilder created by image()");
492
528
  }
493
- return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec);
529
+ return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec, this.networkSpec);
494
530
  }
495
531
  workspace(mountPath = "/workspace") {
496
- return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec);
532
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec, this.networkSpec);
497
533
  }
498
534
  resources(opts) {
499
535
  const resourceSpec = {
@@ -501,9 +537,38 @@ class SandboxBuilderImpl {
501
537
  ...opts.memory === undefined ? {} : { memory: opts.memory },
502
538
  ...opts.disk === undefined ? {} : { disk: opts.disk }
503
539
  };
504
- return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec);
540
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec, this.networkSpec);
541
+ }
542
+ network(opts) {
543
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, this.resourceSpec, normalizeSandboxNetwork(opts));
505
544
  }
506
545
  }
546
+ function normalizeSandboxNetwork(opts) {
547
+ const value = opts.internet;
548
+ if (value === undefined || value === true) {
549
+ return { internet: true, allow: [], deny: [] };
550
+ }
551
+ if (value === false) {
552
+ return { internet: false, allow: [], deny: [] };
553
+ }
554
+ return {
555
+ internet: true,
556
+ allow: normalizeNetworkEntries(value.allow, "network.internet.allow"),
557
+ deny: normalizeNetworkEntries(value.deny, "network.internet.deny")
558
+ };
559
+ }
560
+ function normalizeNetworkEntries(entries, label) {
561
+ if (entries === undefined) {
562
+ return [];
563
+ }
564
+ return entries.map((entry, index) => {
565
+ const normalized = entry.trim();
566
+ if (normalized === "") {
567
+ throw new Error(`${label}[${index}] must not be empty`);
568
+ }
569
+ return normalized;
570
+ });
571
+ }
507
572
 
508
573
  class SourceFileRefImpl {
509
574
  path;
@@ -609,6 +674,13 @@ class UnsupportedTransportError extends Error {
609
674
  }
610
675
  }
611
676
 
677
+ // sdk/typescript/src/version.ts
678
+ var HELMR_API_VERSION = "2026-06-06";
679
+ var HELMR_API_VERSION_HEADER = "Helmr-API-Version";
680
+ var HELMR_SDK_VERSION_HEADER = "Helmr-SDK-Version";
681
+ var SOURCE_PACKAGE_VERSION = "0.1.0";
682
+ var HELMR_SDK_VERSION = "0.1.1-rc.8".trim() !== "" ? "0.1.1-rc.8" : SOURCE_PACKAGE_VERSION;
683
+
612
684
  // sdk/typescript/src/runtime/run.ts
613
685
  function runHandle(id, taskId) {
614
686
  return { id, taskId };
@@ -623,6 +695,12 @@ function runSnapshot(snapshot) {
623
695
  createdAt: snapshot.createdAt ?? null,
624
696
  updatedAt: snapshot.updatedAt ?? null,
625
697
  pendingWaitpoint: snapshot.pendingWaitpoint ?? null,
698
+ ...snapshot.version === undefined && snapshot.deploymentVersion === undefined ? {} : { version: snapshot.version ?? snapshot.deploymentVersion ?? null },
699
+ ...snapshot.deploymentVersion === undefined && snapshot.version === undefined ? {} : { deploymentVersion: snapshot.deploymentVersion ?? snapshot.version ?? null },
700
+ ...snapshot.apiVersion === undefined ? {} : { apiVersion: snapshot.apiVersion },
701
+ ...snapshot.sdkVersion === undefined ? {} : { sdkVersion: snapshot.sdkVersion },
702
+ ...snapshot.cliVersion === undefined ? {} : { cliVersion: snapshot.cliVersion },
703
+ attemptNumber: snapshot.attemptNumber ?? null,
626
704
  ...runStateBooleans(status),
627
705
  ...snapshot.output === undefined ? {} : { output: snapshot.output }
628
706
  };
@@ -733,6 +811,7 @@ class HelmrClient {
733
811
  return await this.#triggerRun(task.id, payload, opts, readOptionalMaxDurationSeconds(task.maxDuration));
734
812
  }
735
813
  async#triggerRun(taskId, payload, opts, maxDurationSeconds) {
814
+ validateRetryPolicy(opts.retry, "retry");
736
815
  const runOptions = {
737
816
  ...opts.deploymentId === undefined ? {} : { deployment_id: opts.deploymentId },
738
817
  ...opts.version === undefined ? {} : { version: opts.version },
@@ -740,6 +819,9 @@ class HelmrClient {
740
819
  ...opts.concurrencyKey === undefined ? {} : { concurrency_key: opts.concurrencyKey },
741
820
  ...opts.priority === undefined ? {} : { priority: opts.priority },
742
821
  ...opts.ttl === undefined ? {} : { ttl: opts.ttl },
822
+ ...opts.retry === undefined ? {} : { retry: opts.retry },
823
+ ...opts.metadata === undefined ? {} : { metadata: opts.metadata },
824
+ ...opts.tags === undefined ? {} : { tags: opts.tags },
743
825
  ...maxDurationSeconds === undefined ? {} : { max_duration_seconds: maxDurationSeconds },
744
826
  ...runIdempotencyRequestFields(opts.idempotencyKey, opts.idempotencyKeyTTL)
745
827
  };
@@ -747,7 +829,6 @@ class HelmrClient {
747
829
  method: "POST",
748
830
  body: JSON.stringify({
749
831
  task_id: taskId,
750
- secrets: opts.secrets ?? {},
751
832
  ...payload === undefined ? {} : { payload },
752
833
  ...Object.keys(runOptions).length === 0 ? {} : { options: runOptions }
753
834
  }),
@@ -783,6 +864,24 @@ class HelmrClient {
783
864
  wait.cleanup();
784
865
  }
785
866
  },
867
+ cancel: async (idOrHandle, opts = {}) => {
868
+ const response = await this.#json(`/api/runs/${encodeURIComponent(runId(idOrHandle))}/cancel`, {
869
+ method: "POST",
870
+ body: JSON.stringify(cancelRunBody(opts)),
871
+ headers: { "content-type": "application/json" },
872
+ ...requestSignal(opts.signal)
873
+ });
874
+ return runResponseToSnapshot(response.run);
875
+ },
876
+ replay: async (idOrHandle, opts = {}) => {
877
+ const response = await this.#json(`/api/runs/${encodeURIComponent(runId(idOrHandle))}/replay`, {
878
+ method: "POST",
879
+ body: JSON.stringify(replayRunBody(opts)),
880
+ headers: { "content-type": "application/json" },
881
+ ...requestSignal(opts.signal)
882
+ });
883
+ return runHandle(response.run.id, response.run.task_id);
884
+ },
786
885
  list: async (opts = {}) => {
787
886
  const query = new URLSearchParams;
788
887
  if (opts.status !== undefined)
@@ -948,6 +1047,8 @@ class HelmrClient {
948
1047
  }
949
1048
  async#fetch(path, init = {}) {
950
1049
  const headers = new Headers(init.headers);
1050
+ headers.set(HELMR_API_VERSION_HEADER, HELMR_API_VERSION);
1051
+ headers.set(HELMR_SDK_VERSION_HEADER, HELMR_SDK_VERSION);
951
1052
  if (this.#apiKey !== undefined) {
952
1053
  headers.set("authorization", `Bearer ${this.#apiKey}`);
953
1054
  }
@@ -1001,6 +1102,12 @@ function runResponseToSnapshot(response) {
1001
1102
  return runSnapshot({
1002
1103
  id: response.id,
1003
1104
  taskId: response.task_id,
1105
+ ...response.version === undefined && response.deployment_version === undefined ? {} : { version: response.version ?? response.deployment_version ?? null },
1106
+ ...response.deployment_version === undefined && response.version === undefined ? {} : { deploymentVersion: response.deployment_version ?? response.version ?? null },
1107
+ ...response.api_version === undefined ? {} : { apiVersion: response.api_version },
1108
+ ...response.sdk_version === undefined ? {} : { sdkVersion: response.sdk_version },
1109
+ ...response.cli_version === undefined ? {} : { cliVersion: response.cli_version },
1110
+ attemptNumber: response.attempt_number ?? null,
1004
1111
  status: response.status,
1005
1112
  exitCode: response.exit_code ?? null,
1006
1113
  ...response.created_at === undefined ? {} : { createdAt: response.created_at },
@@ -1009,6 +1116,23 @@ function runResponseToSnapshot(response) {
1009
1116
  ..."output" in response ? { output: response.output } : {}
1010
1117
  });
1011
1118
  }
1119
+ function cancelRunBody(opts) {
1120
+ return {
1121
+ ...opts.reason === undefined ? {} : { reason: opts.reason },
1122
+ ...opts.force === undefined ? {} : { force: opts.force },
1123
+ ...opts.idempotencyKey === undefined ? {} : { idempotency_key: opts.idempotencyKey }
1124
+ };
1125
+ }
1126
+ function replayRunBody(opts) {
1127
+ return {
1128
+ ...opts.version === undefined ? {} : { version: opts.version },
1129
+ ...opts.payload === undefined ? {} : { payload: opts.payload },
1130
+ ...opts.reason === undefined ? {} : { reason: opts.reason },
1131
+ ...opts.idempotencyKey === undefined ? {} : { idempotency_key: opts.idempotencyKey },
1132
+ ...opts.metadata === undefined ? {} : { metadata: opts.metadata },
1133
+ ...opts.tags === undefined ? {} : { tags: opts.tags }
1134
+ };
1135
+ }
1012
1136
  function scheduleCreateBody(opts) {
1013
1137
  return {
1014
1138
  ..."deduplicationKey" in opts && opts.deduplicationKey !== undefined ? { deduplication_key: opts.deduplicationKey } : {},
@@ -1019,7 +1143,6 @@ function scheduleCreateBody(opts) {
1019
1143
  cron: opts.cron,
1020
1144
  ...opts.timezone === undefined ? {} : { timezone: opts.timezone },
1021
1145
  ...opts.active === undefined ? {} : { active: opts.active },
1022
- ...opts.secretBindings === undefined ? {} : { secret_bindings: opts.secretBindings },
1023
1146
  ...opts.options === undefined ? {} : { options: runOptionsBody(opts.options) }
1024
1147
  };
1025
1148
  }
@@ -1468,11 +1591,10 @@ function queue(config) {
1468
1591
 
1469
1592
  // sdk/typescript/src/schedules.ts
1470
1593
  function task2(config) {
1471
- const { cron, secretBindings, ...taskConfig } = config;
1594
+ const { cron, ...taskConfig } = config;
1472
1595
  const schedule = cron === undefined ? undefined : {
1473
1596
  cron: typeof cron === "string" ? cron : cron.pattern,
1474
- ...typeof cron === "string" || cron.timezone === undefined ? {} : { timezone: cron.timezone },
1475
- ...secretBindings === undefined ? {} : { secretBindings }
1597
+ ...typeof cron === "string" || cron.timezone === undefined ? {} : { timezone: cron.timezone }
1476
1598
  };
1477
1599
  const marked = markScheduledTask({ ...taskConfig, payload: scheduledTaskPayloadSchema }, schedule);
1478
1600
  Object.defineProperty(marked, "trigger", {
@@ -40,7 +40,7 @@ export interface WaitOptions {
40
40
  readonly policy?: string;
41
41
  readonly displayText?: string;
42
42
  }
43
- export interface WaitManualOptions<TSchema extends PayloadSchema<any, any> = PayloadSchema<any, any>> extends WaitOptions {
43
+ export interface WaitHumanOptions<TSchema extends PayloadSchema<any, any> = PayloadSchema<any, any>> extends WaitOptions {
44
44
  readonly schema?: TSchema;
45
45
  }
46
46
  export interface WaitResolution<TPayload = unknown> {
@@ -62,8 +62,8 @@ export type WaitUntilInput = string | Date | {
62
62
  export interface WaitCapabilities {
63
63
  for(input: WaitForInput, opts?: Omit<WaitOptions, "timeout" | "policy">): Promise<void>;
64
64
  until(input: WaitUntilInput, opts?: Omit<WaitOptions, "timeout" | "policy">): Promise<void>;
65
- manual<TSchema extends PayloadSchema<any, any>>(opts: WaitManualOptions<TSchema>): Promise<PayloadSchemaOutput<TSchema>>;
66
- manual<TPayload = unknown>(opts?: WaitManualOptions): Promise<TPayload>;
65
+ human<TSchema extends PayloadSchema<any, any>>(opts: WaitHumanOptions<TSchema>): Promise<PayloadSchemaOutput<TSchema>>;
66
+ human<TPayload = unknown>(opts?: WaitHumanOptions): Promise<TPayload>;
67
67
  }
68
68
  export declare class ConcurrentWaitError extends Error {
69
69
  constructor(message: string);
@@ -80,7 +80,20 @@ export type Placement = {
80
80
  readonly mode?: string;
81
81
  readonly owner?: string;
82
82
  };
83
- export type SecretDecls = Record<string, Placement>;
83
+ export type SecretDecl = ({
84
+ readonly env: string;
85
+ } | {
86
+ readonly file: string;
87
+ readonly mode?: string;
88
+ readonly owner?: string;
89
+ } | {
90
+ readonly dir: string;
91
+ readonly mode?: string;
92
+ readonly owner?: string;
93
+ }) & {
94
+ readonly name: string;
95
+ };
96
+ export type SecretDecls = readonly SecretDecl[];
84
97
  export declare function validateSecretName(name: string, label?: string): void;
85
98
  export interface TaskContext {
86
99
  readonly wait: WaitCapabilities;
@@ -93,6 +106,11 @@ export interface TaskContext {
93
106
  readonly signal: AbortSignal;
94
107
  readonly run: {
95
108
  readonly id: string;
109
+ readonly attemptId?: string;
110
+ readonly attemptNumber?: number;
111
+ readonly sessionId?: string;
112
+ readonly snapshotVersion?: number;
113
+ readonly replayedFromRunId?: string;
96
114
  };
97
115
  readonly task: {
98
116
  readonly id: string;
@@ -127,12 +145,13 @@ declare const noPayloadBrand: unique symbol;
127
145
  export interface NoPayload {
128
146
  readonly [noPayloadBrand]: "NoPayload";
129
147
  }
130
- export interface TaskConfigBase<TSecrets extends SecretDecls = Record<never, never>> {
148
+ export interface TaskConfigBase<TSecrets extends SecretDecls = readonly []> {
131
149
  readonly id: string;
132
150
  readonly sandbox: SandboxBuilder;
133
151
  readonly maxDuration?: number;
134
152
  readonly queue?: TaskQueueConfig;
135
153
  readonly ttl?: string;
154
+ readonly retry?: RetryPolicy;
136
155
  readonly secrets?: TSecrets;
137
156
  }
138
157
  export interface TaskQueueConfig {
@@ -142,7 +161,6 @@ export interface TaskQueueConfig {
142
161
  export interface InternalTaskScheduleConfig {
143
162
  readonly cron: string;
144
163
  readonly timezone?: string;
145
- readonly secretBindings?: Record<string, string>;
146
164
  }
147
165
  export type TaskRunOptions<TSecrets extends SecretDecls> = {
148
166
  readonly deploymentId?: string;
@@ -151,26 +169,32 @@ export type TaskRunOptions<TSecrets extends SecretDecls> = {
151
169
  readonly concurrencyKey?: string;
152
170
  readonly priority?: number;
153
171
  readonly ttl?: string;
172
+ readonly retry?: RetryPolicy;
173
+ readonly metadata?: Record<string, unknown>;
174
+ readonly tags?: readonly string[];
154
175
  readonly idempotencyKey?: IdempotencyKeyInput;
155
176
  readonly idempotencyKeyTTL?: string;
156
- } & ([keyof TSecrets] extends [never] ? {
157
- readonly secrets?: Record<never, never>;
158
- } : {
159
- readonly secrets: {
160
- readonly [K in keyof TSecrets]: string;
177
+ };
178
+ export type RetryPolicy = false | {
179
+ readonly maxAttempts: number;
180
+ readonly backoff?: {
181
+ readonly minMs?: number;
182
+ readonly maxMs?: number;
183
+ readonly factor?: number;
184
+ readonly jitter?: "none" | "full";
161
185
  };
162
- });
186
+ };
163
187
  export type TaskDirectTrigger<TPayloadInput, TOutput, TSecrets extends SecretDecls> = [TPayloadInput] extends [NoPayload] ? (opts: TaskRunOptions<TSecrets>) => Promise<RunHandle<Awaited<TOutput>>> : (payload: TPayloadInput, opts: TaskRunOptions<TSecrets>) => Promise<RunHandle<Awaited<TOutput>>>;
164
- export type TaskConfigWithPayload<TPayloadSchema extends PayloadSchema<any, any>, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = TaskConfigBase<TSecrets> & {
188
+ export type TaskConfigWithPayload<TPayloadSchema extends PayloadSchema<any, any>, TOutput = unknown, TSecrets extends SecretDecls = readonly []> = TaskConfigBase<TSecrets> & {
165
189
  readonly payload: TPayloadSchema;
166
190
  readonly run: (payload: PayloadSchemaOutput<TPayloadSchema>, ctx: TaskContext) => MaybePromise<TOutput>;
167
191
  };
168
- export type TaskConfigWithoutPayload<TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = TaskConfigBase<TSecrets> & {
192
+ export type TaskConfigWithoutPayload<TOutput = unknown, TSecrets extends SecretDecls = readonly []> = TaskConfigBase<TSecrets> & {
169
193
  readonly payload?: undefined;
170
194
  readonly run: (ctx: TaskContext) => MaybePromise<TOutput>;
171
195
  };
172
- export type TaskConfig<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>, TPayloadSchema extends PayloadSchema<any, any> | undefined = undefined> = TPayloadSchema extends PayloadSchema<any, any> ? TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets> : TaskConfigWithoutPayload<TOutput, TSecrets>;
173
- export type Task<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>, TPayloadInput = TPayload> = TaskConfigBase<TSecrets> & {
196
+ export type TaskConfig<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = readonly [], TPayloadSchema extends PayloadSchema<any, any> | undefined = undefined> = TPayloadSchema extends PayloadSchema<any, any> ? TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets> : TaskConfigWithoutPayload<TOutput, TSecrets>;
197
+ export type Task<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = readonly [], TPayloadInput = TPayload> = TaskConfigBase<TSecrets> & {
174
198
  readonly "~types"?: {
175
199
  readonly payloadInput: TPayloadInput;
176
200
  readonly payload: TPayload;
@@ -238,6 +262,7 @@ export interface SandboxBuilder {
238
262
  readonly memory?: string;
239
263
  readonly disk?: string;
240
264
  }): SandboxBuilder;
265
+ network(opts: SandboxNetwork): SandboxBuilder;
241
266
  }
242
267
  export type ImageBuildStep = {
243
268
  readonly kind: "from";
@@ -275,9 +300,21 @@ export interface SandboxResources {
275
300
  readonly memory?: string;
276
301
  readonly disk?: string;
277
302
  }
303
+ export type SandboxNetwork = {
304
+ readonly internet?: boolean | {
305
+ readonly allow?: readonly string[];
306
+ readonly deny?: readonly string[];
307
+ };
308
+ };
309
+ export interface SandboxNetworkSpec {
310
+ readonly internet: boolean;
311
+ readonly allow: readonly string[];
312
+ readonly deny: readonly string[];
313
+ }
278
314
  export type MarkedTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined> = TPayloadSchema extends PayloadSchema<any, any> ? Task<PayloadSchemaOutput<TPayloadSchema>, Awaited<TOutput>, TSecrets, PayloadSchemaInput<TPayloadSchema>> : Task<NoPayload, Awaited<TOutput>, TSecrets, NoPayload>;
279
315
  export declare function markTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined>(config: TaskConfig<TPayload, TOutput, TSecrets, TPayloadSchema>): MarkedTask<TPayload, TOutput, TSecrets, TPayloadSchema>;
280
316
  export declare function markScheduledTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined>(config: TaskConfig<TPayload, TOutput, TSecrets, TPayloadSchema>, schedule: InternalTaskScheduleConfig | undefined): MarkedTask<TPayload, TOutput, TSecrets, TPayloadSchema>;
317
+ export declare function validateRetryPolicy(value: unknown, label?: string): void;
281
318
  export declare function validateTaskSchedule(taskId: string, value: InternalTaskScheduleConfig | undefined): void;
282
319
  export declare function validateTaskQueue(taskId: string, value: TaskQueueConfig | undefined): void;
283
320
  export declare function defaultTaskQueueName(taskId: string): string;
@@ -313,7 +350,8 @@ export declare class SandboxBuilderImpl implements SandboxBuilder {
313
350
  readonly imageBuilder: ImageBuilderImpl | undefined;
314
351
  readonly workspaceBinding: SandboxWorkspace | undefined;
315
352
  readonly resourceSpec: SandboxResources | undefined;
316
- constructor(id: string, imageBuilder?: ImageBuilderImpl, workspaceBinding?: SandboxWorkspace, resourceSpec?: SandboxResources);
353
+ readonly networkSpec: SandboxNetworkSpec | undefined;
354
+ constructor(id: string, imageBuilder?: ImageBuilderImpl, workspaceBinding?: SandboxWorkspace, resourceSpec?: SandboxResources, networkSpec?: SandboxNetworkSpec);
317
355
  image(img: ImageBuilder): SandboxBuilder;
318
356
  workspace(mountPath?: string): SandboxBuilder;
319
357
  resources(opts: {
@@ -321,6 +359,7 @@ export declare class SandboxBuilderImpl implements SandboxBuilder {
321
359
  readonly memory?: string;
322
360
  readonly disk?: string;
323
361
  }): SandboxBuilder;
362
+ network(opts: SandboxNetwork): SandboxBuilder;
324
363
  }
325
364
  export declare class SourceFileRefImpl implements SourceFileRef {
326
365
  readonly path: string;
package/dist/internal.js CHANGED
@@ -212,12 +212,8 @@ function validateSecretName(name, label = "secret name") {
212
212
  if (name.length > 128) {
213
213
  throw new Error(`${label} must be at most 128 characters`);
214
214
  }
215
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
216
- throw new Error(`${label} must match /^[A-Za-z_][A-Za-z0-9_]*$/`);
217
- }
218
- const upper = name.toUpperCase();
219
- if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
220
- throw new Error(`${label} is reserved`);
215
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
216
+ throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
221
217
  }
222
218
  }
223
219
  function validateImageBuildSecretRef(name) {
@@ -283,6 +279,7 @@ function markTaskInternal(config, schedule) {
283
279
  validateTaskQueue(config.id, config.queue);
284
280
  validateTaskSchedule(config.id, schedule);
285
281
  validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
282
+ validateRetryPolicy(config.retry, `task ${JSON.stringify(config.id)} retry`);
286
283
  assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
287
284
  if (schedule !== undefined) {
288
285
  Object.defineProperty(config, "schedule", {
@@ -294,6 +291,54 @@ function markTaskInternal(config, schedule) {
294
291
  Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
295
292
  return config;
296
293
  }
294
+ function validateRetryPolicy(value, label = "retry") {
295
+ if (value === undefined || value === false) {
296
+ return;
297
+ }
298
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
299
+ throw new Error(`${label} must be false or a retry policy object`);
300
+ }
301
+ const record = value;
302
+ for (const key of Object.keys(record)) {
303
+ if (key !== "maxAttempts" && key !== "backoff") {
304
+ throw new Error(`${label}.${key} is not supported`);
305
+ }
306
+ }
307
+ if (typeof record["maxAttempts"] !== "number" || !Number.isInteger(record["maxAttempts"]) || record["maxAttempts"] < 1 || record["maxAttempts"] > 10) {
308
+ throw new Error(`${label}.maxAttempts must be an integer between 1 and 10`);
309
+ }
310
+ const backoff = record["backoff"];
311
+ if (backoff !== undefined) {
312
+ if (backoff === null || typeof backoff !== "object" || Array.isArray(backoff)) {
313
+ throw new Error(`${label}.backoff must be an object`);
314
+ }
315
+ const backoffRecord = backoff;
316
+ for (const key of Object.keys(backoffRecord)) {
317
+ if (key !== "minMs" && key !== "maxMs" && key !== "factor" && key !== "jitter") {
318
+ throw new Error(`${label}.backoff.${key} is not supported`);
319
+ }
320
+ }
321
+ validateOptionalPositiveInteger(backoffRecord["minMs"], `${label}.backoff.minMs`);
322
+ validateOptionalPositiveInteger(backoffRecord["maxMs"], `${label}.backoff.maxMs`);
323
+ const factor = backoffRecord["factor"];
324
+ if (factor !== undefined && (typeof factor !== "number" || !Number.isFinite(factor) || factor <= 0)) {
325
+ throw new Error(`${label}.backoff.factor must be a positive number`);
326
+ }
327
+ const jitter = backoffRecord["jitter"];
328
+ if (jitter !== undefined && jitter !== "none" && jitter !== "full") {
329
+ throw new Error(`${label}.backoff.jitter must be "none" or "full"`);
330
+ }
331
+ }
332
+ }
333
+ function validateOptionalPositiveInteger(value, label) {
334
+ if (value === undefined) {
335
+ return;
336
+ }
337
+ if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
338
+ return;
339
+ }
340
+ throw new Error(`${label} must be a positive integer`);
341
+ }
297
342
  function validateTaskSchedule(taskId, value) {
298
343
  if (value === undefined) {
299
344
  return;
@@ -304,17 +349,6 @@ function validateTaskSchedule(taskId, value) {
304
349
  if (value.timezone !== undefined && value.timezone.trim() === "") {
305
350
  throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
306
351
  }
307
- if (value.secretBindings !== undefined) {
308
- if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
309
- throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
310
- }
311
- for (const [name, binding] of Object.entries(value.secretBindings)) {
312
- validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
313
- if (typeof binding !== "string" || binding.trim() === "") {
314
- throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
315
- }
316
- }
317
- }
318
352
  }
319
353
  function validateTaskQueue(taskId, value) {
320
354
  if (value === undefined) {
@@ -495,21 +529,23 @@ class SandboxBuilderImpl {
495
529
  imageBuilder;
496
530
  workspaceBinding;
497
531
  resourceSpec;
498
- constructor(id, imageBuilder, workspaceBinding, resourceSpec) {
532
+ networkSpec;
533
+ constructor(id, imageBuilder, workspaceBinding, resourceSpec, networkSpec) {
499
534
  Object.defineProperty(this, sandboxBuilderBrand, { value: true });
500
535
  this.id = id;
501
536
  this.imageBuilder = imageBuilder;
502
537
  this.workspaceBinding = workspaceBinding;
503
538
  this.resourceSpec = resourceSpec;
539
+ this.networkSpec = networkSpec;
504
540
  }
505
541
  image(img) {
506
542
  if (!isImageBuilder(img)) {
507
543
  throw new Error("sandbox.image() requires an ImageBuilder created by image()");
508
544
  }
509
- return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec);
545
+ return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec, this.networkSpec);
510
546
  }
511
547
  workspace(mountPath = "/workspace") {
512
- return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec);
548
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec, this.networkSpec);
513
549
  }
514
550
  resources(opts) {
515
551
  const resourceSpec = {
@@ -517,8 +553,37 @@ class SandboxBuilderImpl {
517
553
  ...opts.memory === undefined ? {} : { memory: opts.memory },
518
554
  ...opts.disk === undefined ? {} : { disk: opts.disk }
519
555
  };
520
- return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec);
556
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec, this.networkSpec);
557
+ }
558
+ network(opts) {
559
+ return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, this.resourceSpec, normalizeSandboxNetwork(opts));
560
+ }
561
+ }
562
+ function normalizeSandboxNetwork(opts) {
563
+ const value = opts.internet;
564
+ if (value === undefined || value === true) {
565
+ return { internet: true, allow: [], deny: [] };
521
566
  }
567
+ if (value === false) {
568
+ return { internet: false, allow: [], deny: [] };
569
+ }
570
+ return {
571
+ internet: true,
572
+ allow: normalizeNetworkEntries(value.allow, "network.internet.allow"),
573
+ deny: normalizeNetworkEntries(value.deny, "network.internet.deny")
574
+ };
575
+ }
576
+ function normalizeNetworkEntries(entries, label) {
577
+ if (entries === undefined) {
578
+ return [];
579
+ }
580
+ return entries.map((entry, index) => {
581
+ const normalized = entry.trim();
582
+ if (normalized === "") {
583
+ throw new Error(`${label}[${index}] must not be empty`);
584
+ }
585
+ return normalized;
586
+ });
522
587
  }
523
588
 
524
589
  class SourceFileRefImpl {
@@ -557,6 +622,7 @@ export {
557
622
  validateTaskSchedule,
558
623
  validateTaskQueue,
559
624
  validateSecretName,
625
+ validateRetryPolicy,
560
626
  validateOptionalTTL,
561
627
  taskOriginFile,
562
628
  taskOriginBrand,
@@ -1,5 +1,5 @@
1
1
  import { type AnyTask, type NoPayload, type SecretDecls, type TaskOutput, type TaskRunOptions, type TaskSecrets, type TaskTriggerPayload } from "../internal.js";
2
- import { type LogSnapshot, type ListRunEventsOptions, type ListRunsOptions, type PendingManualWaitpoint, type PendingWaitpointResponse, type RetrieveRunOptions, type RunHandle, type RunEvent, type RunSnapshot, type RunSummary, type RunWaitOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef } from "./run.js";
2
+ import { type LogSnapshot, type CancelRunOptions, type ListRunEventsOptions, type ListRunsOptions, type PendingHumanWaitpoint, type PendingWaitpointResponse, type RetrieveRunOptions, type RunHandle, type RunEvent, type RunSnapshot, type RunSummary, type RunWaitOptions, type ReplayRunOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef } from "./run.js";
3
3
  export interface HelmrClientOptions {
4
4
  readonly url?: string;
5
5
  readonly apiKey?: string;
@@ -15,12 +15,12 @@ export declare const triggerTaskClientMethod: unique symbol;
15
15
  export interface WaitpointsApi {
16
16
  readonly create: (opts: WaitpointCreateOptions) => Promise<Waitpoint>;
17
17
  readonly respond: {
18
- (target: PendingManualWaitpoint | WaitpointRef, opts?: WaitpointRespondOptions): Promise<void>;
18
+ (target: PendingHumanWaitpoint | WaitpointRef, opts?: WaitpointRespondOptions): Promise<void>;
19
19
  (waitpointId: string, opts?: WaitpointRespondOptions): Promise<void>;
20
20
  };
21
21
  readonly tokens: {
22
22
  readonly create: {
23
- (target: PendingManualWaitpoint | WaitpointRef, opts?: WaitpointTokenCreateOptions): Promise<WaitpointResponseToken>;
23
+ (target: PendingHumanWaitpoint | WaitpointRef, opts?: WaitpointTokenCreateOptions): Promise<WaitpointResponseToken>;
24
24
  (waitpointId: string, opts?: WaitpointTokenCreateOptions): Promise<WaitpointResponseToken>;
25
25
  };
26
26
  readonly respond: {
@@ -47,7 +47,6 @@ export interface ScheduleCreateOptions {
47
47
  readonly cron: string;
48
48
  readonly timezone?: string;
49
49
  readonly active?: boolean;
50
- readonly secretBindings?: Record<string, string>;
51
50
  readonly options?: ScheduleRunOptions;
52
51
  }
53
52
  export type ScheduleUpdateOptions = Omit<ScheduleCreateOptions, "deduplicationKey"> & {
@@ -122,7 +121,7 @@ export interface Waitpoint {
122
121
  readonly id: string;
123
122
  readonly projectId: string;
124
123
  readonly environmentId: string;
125
- readonly kind: "manual" | "delay";
124
+ readonly kind: "human" | "delay";
126
125
  readonly status: "pending" | "completed" | "expired" | "cancelled";
127
126
  readonly request: unknown;
128
127
  readonly displayText: string;
@@ -139,6 +138,8 @@ export declare class HelmrClient {
139
138
  readonly runs: {
140
139
  retrieve: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: RetrieveRunOptions) => Promise<RunSnapshot<TOutput>>;
141
140
  wait: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: RunWaitOptions) => Promise<RunSnapshot<TOutput>>;
141
+ cancel: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: CancelRunOptions) => Promise<RunSnapshot<TOutput>>;
142
+ replay: <TPayload = unknown, TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: ReplayRunOptions<TPayload>) => Promise<RunHandle<TOutput>>;
142
143
  list: (opts?: ListRunsOptions) => Promise<RunSummary[]>;
143
144
  logs: {
144
145
  retrieve: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: {
@@ -157,6 +158,12 @@ export interface RunResponse {
157
158
  readonly id: string;
158
159
  readonly project_id?: string;
159
160
  readonly environment_id?: string;
161
+ readonly version?: string;
162
+ readonly deployment_version?: string;
163
+ readonly api_version?: string;
164
+ readonly sdk_version?: string;
165
+ readonly cli_version?: string;
166
+ readonly attempt_number?: number | null;
160
167
  readonly task_id: string;
161
168
  readonly status: string;
162
169
  readonly exit_code?: number | null;
@@ -20,6 +20,12 @@ export interface RunStateBooleans {
20
20
  export interface RunSnapshot<TOutput = unknown> extends RunStateBooleans {
21
21
  readonly id: string;
22
22
  readonly taskId: string;
23
+ readonly version?: string | null;
24
+ readonly deploymentVersion?: string | null;
25
+ readonly apiVersion?: string | null;
26
+ readonly sdkVersion?: string | null;
27
+ readonly cliVersion?: string | null;
28
+ readonly attemptNumber: number | null;
23
29
  readonly status: RunStatus;
24
30
  readonly exitCode: number | null;
25
31
  readonly createdAt: string | null;
@@ -28,7 +34,7 @@ export interface RunSnapshot<TOutput = unknown> extends RunStateBooleans {
28
34
  readonly output?: TOutput;
29
35
  }
30
36
  export type RunSummary<TOutput = unknown> = RunSnapshot<TOutput>;
31
- export type PendingWaitpoint = PendingManualWaitpoint | PendingDelayWaitpoint;
37
+ export type PendingWaitpoint = PendingHumanWaitpoint | PendingDelayWaitpoint;
32
38
  interface PendingWaitpointBase {
33
39
  readonly runId: string;
34
40
  readonly waitpointId: string;
@@ -37,8 +43,8 @@ interface PendingWaitpointBase {
37
43
  readonly request: unknown;
38
44
  readonly displayText: string;
39
45
  }
40
- export interface PendingManualWaitpoint extends PendingWaitpointBase {
41
- readonly kind: "manual";
46
+ export interface PendingHumanWaitpoint extends PendingWaitpointBase {
47
+ readonly kind: "human";
42
48
  }
43
49
  export interface PendingDelayWaitpoint extends PendingWaitpointBase {
44
50
  readonly kind: "delay";
@@ -57,6 +63,21 @@ export interface RunWaitOptions {
57
63
  readonly intervalMs?: number;
58
64
  readonly signal?: AbortSignal;
59
65
  }
66
+ export interface CancelRunOptions {
67
+ readonly reason?: string;
68
+ readonly force?: boolean;
69
+ readonly idempotencyKey?: string;
70
+ readonly signal?: AbortSignal;
71
+ }
72
+ export interface ReplayRunOptions<TPayload = unknown> {
73
+ readonly version?: "original" | "latest" | string;
74
+ readonly payload?: TPayload;
75
+ readonly reason?: string;
76
+ readonly idempotencyKey?: string;
77
+ readonly metadata?: Record<string, unknown>;
78
+ readonly tags?: readonly string[];
79
+ readonly signal?: AbortSignal;
80
+ }
60
81
  export interface RetrieveRunOptions {
61
82
  readonly signal?: AbortSignal;
62
83
  }
@@ -132,6 +153,8 @@ export type RunEvent = {
132
153
  export interface RunEventRecord {
133
154
  readonly id: string;
134
155
  readonly run_id?: string | null;
156
+ readonly session_id?: string | null;
157
+ readonly attempt_number?: number | null;
135
158
  readonly kind: string;
136
159
  readonly message: string;
137
160
  readonly at: string;
@@ -154,7 +177,7 @@ export interface LogSnapshot {
154
177
  readonly truncated: boolean;
155
178
  }
156
179
  export interface PendingWaitpointResponse {
157
- readonly kind: "manual" | "delay";
180
+ readonly kind: "human" | "delay";
158
181
  readonly waitpoint_id: string;
159
182
  readonly timeout?: number | null;
160
183
  readonly request?: unknown;
@@ -165,6 +188,12 @@ export declare function runHandle<TOutput = unknown>(id: string, taskId: string)
165
188
  export declare function runSnapshot<TOutput = unknown>(snapshot: {
166
189
  readonly id: string;
167
190
  readonly taskId: string;
191
+ readonly version?: string | null;
192
+ readonly deploymentVersion?: string | null;
193
+ readonly apiVersion?: string | null;
194
+ readonly sdkVersion?: string | null;
195
+ readonly cliVersion?: string | null;
196
+ readonly attemptNumber?: number | null;
168
197
  readonly status: string;
169
198
  readonly exitCode?: number | null;
170
199
  readonly createdAt?: string | null;
package/dist/sandbox.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { type SandboxBuilder } from "./internal.js";
1
+ import { type SandboxBuilder, type SandboxNetwork } from "./internal.js";
2
2
  export declare const sandbox: (id: string) => SandboxBuilder;
3
- export type { SandboxBuilder };
3
+ export type { SandboxBuilder, SandboxNetwork };
@@ -14,15 +14,13 @@ export type ScheduleCron = string | {
14
14
  };
15
15
  type DeclarativeScheduleFields = {
16
16
  readonly cron: ScheduleCron;
17
- readonly secretBindings?: Record<string, string>;
18
17
  } | {
19
18
  readonly cron?: undefined;
20
- readonly secretBindings?: undefined;
21
19
  };
22
- export type ScheduledTaskConfig<TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = Omit<TaskConfigBase<TSecrets>, "payload"> & DeclarativeScheduleFields & {
20
+ export type ScheduledTaskConfig<TOutput = unknown, TSecrets extends SecretDecls = readonly []> = Omit<TaskConfigBase<TSecrets>, "payload"> & DeclarativeScheduleFields & {
23
21
  readonly payload?: never;
24
22
  readonly run: (payload: ScheduledTaskPayload, ctx: TaskContext) => MaybePromise<TOutput>;
25
23
  };
26
- export declare function task<TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>>(config: ScheduledTaskConfig<TOutput, TSecrets>): MarkedTask<ScheduledTaskPayload, Awaited<TOutput>, TSecrets, typeof scheduledTaskPayloadSchema>;
24
+ export declare function task<TOutput = unknown, TSecrets extends SecretDecls = readonly []>(config: ScheduledTaskConfig<TOutput, TSecrets>): MarkedTask<ScheduledTaskPayload, Awaited<TOutput>, TSecrets, typeof scheduledTaskPayloadSchema>;
27
25
  declare const scheduledTaskPayloadSchema: PayloadSchema<unknown, ScheduledTaskPayload>;
28
26
  export {};
package/dist/task.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type NoPayload, type SecretDecls, type Task, type TaskConfig, type TaskQueueConfig, type TaskConfigWithPayload, type TaskConfigWithoutPayload, type TaskRunOptions, type TaskOutput, type TaskPayload, type TaskTriggerPayload } from "./internal.js";
2
2
  import type { PayloadSchema, PayloadSchemaInput, PayloadSchemaOutput } from "./schema/payload.js";
3
- export declare function task<TPayloadSchema extends PayloadSchema<any, any>, TOutput, TSecrets extends SecretDecls = Record<never, never>>(config: TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets>): Task<PayloadSchemaOutput<TPayloadSchema>, Awaited<TOutput>, TSecrets, PayloadSchemaInput<TPayloadSchema>>;
4
- export declare function task<TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>>(config: TaskConfigWithoutPayload<TOutput, TSecrets>): Task<NoPayload, Awaited<TOutput>, TSecrets, NoPayload>;
3
+ export declare function task<TPayloadSchema extends PayloadSchema<any, any>, TOutput, TSecrets extends SecretDecls = readonly []>(config: TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets>): Task<PayloadSchemaOutput<TPayloadSchema>, Awaited<TOutput>, TSecrets, PayloadSchemaInput<TPayloadSchema>>;
4
+ export declare function task<TOutput = unknown, TSecrets extends SecretDecls = readonly []>(config: TaskConfigWithoutPayload<TOutput, TSecrets>): Task<NoPayload, Awaited<TOutput>, TSecrets, NoPayload>;
5
5
  export declare function queue(config: TaskQueueConfig): TaskQueueConfig;
6
6
  export type { NoPayload, Task, TaskConfig, TaskOutput, TaskPayload, TaskQueueConfig, TaskRunOptions, TaskTriggerPayload };
@@ -0,0 +1,4 @@
1
+ export declare const HELMR_API_VERSION = "2026-06-06";
2
+ export declare const HELMR_API_VERSION_HEADER = "Helmr-API-Version";
3
+ export declare const HELMR_SDK_VERSION_HEADER = "Helmr-SDK-Version";
4
+ export declare const HELMR_SDK_VERSION: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helmr/sdk",
3
- "version": "0.1.1-rc.6",
3
+ "version": "0.1.1-rc.8",
4
4
  "description": "TypeScript SDK for authoring and triggering Helmr tasks.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -43,6 +43,6 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "@bufbuild/protobuf": "^2.11.0",
46
- "@helmr/proto": "0.1.1-rc.6"
46
+ "@helmr/proto": "0.1.1-rc.8"
47
47
  }
48
48
  }