@helmr/sdk 0.1.1-rc.3 → 0.1.1-rc.5

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/index.js CHANGED
@@ -1,9 +1,92 @@
1
+ // sdk/typescript/src/schema/payload.ts
2
+ var payloadSchemaValidationErrorBrand = Symbol.for("helmr.sdk.PayloadSchemaValidationError");
3
+
4
+ class PayloadSchemaValidationError extends Error {
5
+ issues;
6
+ constructor(label, issues) {
7
+ super(formatPayloadSchemaValidationMessage(label, issues));
8
+ this.name = "PayloadSchemaValidationError";
9
+ this.issues = issues;
10
+ Object.defineProperty(this, payloadSchemaValidationErrorBrand, { value: true });
11
+ }
12
+ static [Symbol.hasInstance](value) {
13
+ return this === PayloadSchemaValidationError && typeof value === "object" && value !== null && payloadSchemaValidationErrorBrand in value;
14
+ }
15
+ }
16
+ function assertPayloadSchema(value, label = "payload") {
17
+ if (value === undefined) {
18
+ return;
19
+ }
20
+ assertStandardSchema(value, label);
21
+ }
22
+ function assertStandardSchema(value, label = "schema") {
23
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
24
+ throw new Error(`${label} must implement the Standard Schema v1 interface`);
25
+ }
26
+ const standard = value["~standard"];
27
+ if (standard === null || typeof standard !== "object") {
28
+ throw new Error(`${label} must implement the Standard Schema v1 interface`);
29
+ }
30
+ const record = standard;
31
+ if (record["version"] !== 1 || typeof record["validate"] !== "function") {
32
+ throw new Error(`${label} must implement the Standard Schema v1 interface`);
33
+ }
34
+ }
35
+ async function parsePayloadWithSchema(schema, payload, label) {
36
+ assertStandardSchema(schema, label);
37
+ const result = await schema["~standard"].validate(payload);
38
+ if ("issues" in result && result.issues !== undefined) {
39
+ throw new PayloadSchemaValidationError(label, result.issues);
40
+ }
41
+ return result.value;
42
+ }
43
+ function formatPayloadSchemaValidationMessage(label, issues) {
44
+ if (issues.length === 0) {
45
+ return `${label} failed validation`;
46
+ }
47
+ const formattedIssues = issues.slice(0, 5).map(formatPayloadSchemaIssue);
48
+ const suffix = issues.length > formattedIssues.length ? `; and ${issues.length - formattedIssues.length} more` : "";
49
+ return `${label} failed validation: ${formattedIssues.join("; ")}${suffix}`;
50
+ }
51
+ function formatPayloadSchemaIssue(issue) {
52
+ const path = formatPayloadSchemaIssuePath(issue.path);
53
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
54
+ }
55
+ function formatPayloadSchemaIssuePath(path) {
56
+ if (path === undefined || path.length === 0) {
57
+ return "";
58
+ }
59
+ let formatted = "payload";
60
+ for (const segment of path) {
61
+ const key = typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment;
62
+ if (typeof key === "string" && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
63
+ formatted += `.${key}`;
64
+ } else if (typeof key === "string" && isArrayIndexKey(key)) {
65
+ formatted += `[${key}]`;
66
+ } else if (typeof key === "number") {
67
+ formatted += `[${key}]`;
68
+ } else {
69
+ formatted += `[${JSON.stringify(String(key))}]`;
70
+ }
71
+ }
72
+ return formatted;
73
+ }
74
+ function isArrayIndexKey(value) {
75
+ if (!/^(0|[1-9]\d*)$/.test(value)) {
76
+ return false;
77
+ }
78
+ const parsed = Number(value);
79
+ return Number.isSafeInteger(parsed);
80
+ }
81
+
1
82
  // sdk/typescript/src/schema/task.ts
2
83
  var TASK_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$";
3
84
  var TASK_ID_MAX_LENGTH = 128;
4
85
  var DEFAULT_MAX_DURATION_SECONDS = 900;
5
86
  var MIN_MAX_DURATION_SECONDS = 5;
6
87
  var MAX_DURATION_SECONDS = 86400;
88
+ var QUEUE_NAME_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$";
89
+ var QUEUE_NAME_MAX_LENGTH = 256;
7
90
 
8
91
  class TaskIdError extends Error {
9
92
  name = "TaskIdError";
@@ -57,37 +140,61 @@ function readOptionalMaxDurationSeconds(value, label = "task maxDuration") {
57
140
  function validateOptionalMaxDurationSeconds(value, label = "task maxDuration") {
58
141
  readOptionalMaxDurationSeconds(value, label);
59
142
  }
60
- function isAsciiAlnum(code) {
61
- return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
62
- }
63
143
 
64
- // sdk/typescript/src/internal.ts
65
- var approvalTimeoutErrorBrand = Symbol.for("helmr.sdk.ApprovalTimeoutError");
66
- var messageTimeoutErrorBrand = Symbol.for("helmr.sdk.MessageTimeoutError");
67
- var concurrentWaitErrorBrand = Symbol.for("helmr.sdk.ConcurrentWaitError");
144
+ class TaskQueueNameError extends Error {
145
+ name = "TaskQueueNameError";
146
+ value;
147
+ constructor(value) {
148
+ super(`queue name must match ${QUEUE_NAME_PATTERN}: ${JSON.stringify(value)}`);
149
+ this.value = value;
150
+ }
151
+ }
68
152
 
69
- class ApprovalTimeoutError extends Error {
70
- constructor(message) {
71
- super(message);
72
- this.name = "ApprovalTimeoutError";
73
- Object.defineProperty(this, approvalTimeoutErrorBrand, { value: true });
153
+ class TaskQueueConcurrencyLimitError extends Error {
154
+ name = "TaskQueueConcurrencyLimitError";
155
+ value;
156
+ constructor(value) {
157
+ super("queue concurrencyLimit must be a positive integer");
158
+ this.value = value;
74
159
  }
75
- static [Symbol.hasInstance](value) {
76
- return this === ApprovalTimeoutError && typeof value === "object" && value !== null && approvalTimeoutErrorBrand in value;
160
+ }
161
+ function validateQueueName(value) {
162
+ if (!isValidQueueName(value)) {
163
+ throw new TaskQueueNameError(value);
77
164
  }
78
165
  }
79
-
80
- class MessageTimeoutError extends Error {
81
- constructor(message) {
82
- super(message);
83
- this.name = "MessageTimeoutError";
84
- Object.defineProperty(this, messageTimeoutErrorBrand, { value: true });
166
+ function isValidQueueName(value) {
167
+ if (value.length === 0 || value.length > QUEUE_NAME_MAX_LENGTH) {
168
+ return false;
85
169
  }
86
- static [Symbol.hasInstance](value) {
87
- return this === MessageTimeoutError && typeof value === "object" && value !== null && messageTimeoutErrorBrand in value;
170
+ const first = value.charCodeAt(0);
171
+ if (!isAsciiAlnum(first)) {
172
+ return false;
88
173
  }
174
+ for (let index = 1;index < value.length; index += 1) {
175
+ const code = value.charCodeAt(index);
176
+ if (!(isAsciiAlnum(code) || code === 46 || code === 95 || code === 45 || code === 47)) {
177
+ return false;
178
+ }
179
+ }
180
+ return true;
181
+ }
182
+ function validateOptionalQueueConcurrencyLimit(value) {
183
+ if (value === undefined || value === null) {
184
+ return;
185
+ }
186
+ if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
187
+ return;
188
+ }
189
+ throw new TaskQueueConcurrencyLimitError(value);
190
+ }
191
+ function isAsciiAlnum(code) {
192
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
89
193
  }
90
194
 
195
+ // sdk/typescript/src/internal.ts
196
+ var concurrentWaitErrorBrand = Symbol.for("helmr.sdk.ConcurrentWaitError");
197
+
91
198
  class ConcurrentWaitError extends Error {
92
199
  constructor(message) {
93
200
  super(message);
@@ -162,12 +269,136 @@ var sandboxBuilderBrand = Symbol.for("helmr.sdk.SandboxBuilder");
162
269
  var sourceFileRefBrand = Symbol.for("helmr.sdk.SourceFileRef");
163
270
  var sourceDirRefBrand = Symbol.for("helmr.sdk.SourceDirRef");
164
271
  function markTask(config) {
272
+ if ("schedule" in config) {
273
+ throw new Error(`task ${JSON.stringify(config.id)} must use schedules.task(...) for declarative schedules`);
274
+ }
275
+ return markTaskInternal(config, undefined);
276
+ }
277
+ function markScheduledTask(config, schedule) {
278
+ return markTaskInternal(config, schedule);
279
+ }
280
+ function markTaskInternal(config, schedule) {
165
281
  validateTaskId(config.id);
166
282
  validateOptionalMaxDurationSeconds(config.maxDuration);
283
+ validateTaskQueue(config.id, config.queue);
284
+ validateTaskSchedule(config.id, schedule);
285
+ validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
286
+ assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
287
+ if (schedule !== undefined) {
288
+ Object.defineProperty(config, "schedule", {
289
+ value: Object.freeze({ ...schedule }),
290
+ enumerable: true
291
+ });
292
+ }
167
293
  Object.defineProperty(config, taskBrand, { value: true });
168
294
  Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
169
295
  return config;
170
296
  }
297
+ function validateTaskSchedule(taskId, value) {
298
+ if (value === undefined) {
299
+ return;
300
+ }
301
+ if (value.cron.trim() === "") {
302
+ throw new Error(`task ${JSON.stringify(taskId)} schedule cron is required`);
303
+ }
304
+ if (value.timezone !== undefined && value.timezone.trim() === "") {
305
+ throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
306
+ }
307
+ if (value.workspace.kind !== "github") {
308
+ throw new Error(`task ${JSON.stringify(taskId)} schedule workspace must be workspace.github(...)`);
309
+ }
310
+ if (value.secretBindings !== undefined) {
311
+ if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
312
+ throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
313
+ }
314
+ for (const [name, binding] of Object.entries(value.secretBindings)) {
315
+ validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
316
+ if (typeof binding !== "string" || binding.trim() === "") {
317
+ throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
318
+ }
319
+ }
320
+ }
321
+ }
322
+ function validateTaskQueue(taskId, value) {
323
+ if (value === undefined) {
324
+ return;
325
+ }
326
+ if (value.name !== undefined) {
327
+ validateQueueName(value.name);
328
+ }
329
+ validateOptionalQueueConcurrencyLimit(value.concurrencyLimit);
330
+ if (value.name === undefined && value.concurrencyLimit === undefined) {
331
+ throw new Error(`task ${JSON.stringify(taskId)} queue must include name or concurrencyLimit`);
332
+ }
333
+ }
334
+ function validateOptionalTTL(value, label = "ttl") {
335
+ if (value === undefined) {
336
+ return;
337
+ }
338
+ if (typeof value === "string" && isPositiveDurationString(value)) {
339
+ return;
340
+ }
341
+ throw new Error(`${label} must be a positive duration string`);
342
+ }
343
+ function isPositiveDurationString(value) {
344
+ const raw = value.trim();
345
+ if (raw === "") {
346
+ return false;
347
+ }
348
+ if (/^[1-9][0-9]*d$/.test(raw)) {
349
+ return true;
350
+ }
351
+ const sign = raw.startsWith("+") || raw.startsWith("-") ? raw.slice(0, 1) : "";
352
+ if (sign === "-") {
353
+ return false;
354
+ }
355
+ const body = sign === "" ? raw : raw.slice(1);
356
+ const tokenPattern = /([0-9]+(?:\.[0-9]*)?|\.[0-9]+)(ns|us|µs|μs|ms|s|m|h)/gy;
357
+ let totalNanoseconds = 0;
358
+ let offset = 0;
359
+ for (;; ) {
360
+ tokenPattern.lastIndex = offset;
361
+ const match = tokenPattern.exec(body);
362
+ if (match === null) {
363
+ return offset === body.length && totalNanoseconds >= 1;
364
+ }
365
+ if (match.index !== offset) {
366
+ return false;
367
+ }
368
+ const amount = Number(match[1]);
369
+ if (!Number.isFinite(amount) || amount < 0) {
370
+ return false;
371
+ }
372
+ totalNanoseconds += amount * durationUnitNanoseconds(match[2] ?? "");
373
+ offset = tokenPattern.lastIndex;
374
+ }
375
+ }
376
+ function durationUnitNanoseconds(unit) {
377
+ switch (unit) {
378
+ case "ns":
379
+ return 1;
380
+ case "us":
381
+ case "µs":
382
+ case "μs":
383
+ return 1000;
384
+ case "ms":
385
+ return 1e6;
386
+ case "s":
387
+ return 1e9;
388
+ case "m":
389
+ return 60000000000;
390
+ case "h":
391
+ return 3600000000000;
392
+ default:
393
+ return 0;
394
+ }
395
+ }
396
+ async function parseTaskPayload(task, payload) {
397
+ if (task.payload === undefined) {
398
+ throw new Error(`task ${JSON.stringify(task.id)} does not accept payload`);
399
+ }
400
+ return await parsePayloadWithSchema(task.payload, payload, `task ${JSON.stringify(task.id)} payload`);
401
+ }
171
402
  function markConfig(config) {
172
403
  Object.defineProperty(config, configBrand, { value: true });
173
404
  return config;
@@ -192,7 +423,7 @@ function stackFrameFile(line) {
192
423
  return match[1].startsWith("file://") ? decodeURIComponent(new URL(match[1]).pathname) : match[1];
193
424
  }
194
425
  function isSdkInternalFrame(file) {
195
- return file.includes("/sdk/typescript/src/internal.ts") || file.includes("/sdk/typescript/src/task.ts") || file.includes("/sdk/typescript/src/index.ts") || file.includes("/runtime/typescript/src/");
426
+ return file.includes("/sdk/typescript/src/internal") || file.includes("/sdk/typescript/src/task") || file.includes("/sdk/typescript/src/index") || file.includes("/runtime/typescript/src/");
196
427
  }
197
428
 
198
429
  class ImageBuilderImpl {
@@ -300,6 +531,55 @@ function hasBrand(value, brand) {
300
531
  return value !== null && typeof value === "object" && value[brand] === true;
301
532
  }
302
533
 
534
+ // sdk/typescript/src/idempotency.ts
535
+ import { createHash } from "node:crypto";
536
+ var idempotencyKeys = {
537
+ create(key, options = {}) {
538
+ const scope = options.scope ?? "run";
539
+ return {
540
+ value: createIdempotencyKey(key, scope, options),
541
+ key,
542
+ scope
543
+ };
544
+ }
545
+ };
546
+ function runIdempotencyRequestFields(input, ttl) {
547
+ if (input === undefined) {
548
+ return {};
549
+ }
550
+ const key = isIdempotencyKey(input) ? input : idempotencyKeys.create(input);
551
+ return {
552
+ idempotency_key: key.value,
553
+ ...ttl === undefined ? {} : { idempotency_key_ttl: ttl },
554
+ idempotency_key_options: {
555
+ key: key.key,
556
+ scope: key.scope
557
+ }
558
+ };
559
+ }
560
+ function createIdempotencyKey(key, scope, options) {
561
+ const material = {
562
+ scope,
563
+ key: Array.isArray(key) ? [...key] : [key]
564
+ };
565
+ const runId = "runId" in options ? options.runId : undefined;
566
+ const attemptNumber = "attemptNumber" in options ? options.attemptNumber : undefined;
567
+ if (scope === "run" && runId !== undefined) {
568
+ material["runId"] = runId;
569
+ }
570
+ if (scope === "attempt") {
571
+ if (runId === undefined || attemptNumber === undefined) {
572
+ throw new Error("attempt-scoped idempotency keys require runId and attemptNumber");
573
+ }
574
+ material["runId"] = runId;
575
+ material["attemptNumber"] = attemptNumber;
576
+ }
577
+ return createHash("sha256").update(JSON.stringify(material)).digest("hex");
578
+ }
579
+ function isIdempotencyKey(value) {
580
+ return typeof value === "object" && value !== null && "value" in value && "key" in value && "scope" in value;
581
+ }
582
+
303
583
  // sdk/typescript/src/runtime/errors.ts
304
584
  class RunNotFoundError extends Error {
305
585
  runId;
@@ -352,27 +632,18 @@ function runSnapshot(snapshot) {
352
632
  function pendingWaitpointFromResponse(runId, wait) {
353
633
  if (wait === undefined || wait === null)
354
634
  return null;
355
- if (wait.kind === "approval") {
356
- return {
357
- kind: "approval",
358
- runId,
359
- waitpointId: wait.waitpoint_id,
360
- message: wait.message ?? "",
361
- timeout: wait.timeout ?? null,
362
- requestedAt: wait.requested_at
363
- };
364
- }
365
635
  return {
366
- kind: "message",
636
+ kind: wait.kind,
367
637
  runId,
368
638
  waitpointId: wait.waitpoint_id,
369
- prompt: wait.prompt ?? null,
370
639
  timeout: wait.timeout ?? null,
640
+ request: wait.request === undefined ? {} : wait.request,
641
+ displayText: wait.display_text ?? "",
371
642
  requestedAt: wait.requested_at
372
643
  };
373
644
  }
374
645
  function isTerminalRunStatus(status) {
375
- return status === "succeeded" || status === "failed" || status === "cancelled";
646
+ return status === "succeeded" || status === "failed" || status === "cancelled" || status === "expired";
376
647
  }
377
648
  function runId(value) {
378
649
  return typeof value === "string" ? value : value.id;
@@ -396,6 +667,7 @@ function runStatus(status) {
396
667
  case "succeeded":
397
668
  case "failed":
398
669
  case "cancelled":
670
+ case "expired":
399
671
  return status;
400
672
  default:
401
673
  throw new Error(`unsupported run status ${JSON.stringify(status)}`);
@@ -412,6 +684,9 @@ function runWorkspaceFromSpec(spec) {
412
684
  }
413
685
 
414
686
  // sdk/typescript/src/runtime/client.ts
687
+ var MAX_SSE_BUFFER_CHARS = 1024 * 1024;
688
+ var triggerTaskClientMethod = Symbol.for("helmr.sdk.client.triggerTask");
689
+
415
690
  class HelmrClient {
416
691
  #baseUrl;
417
692
  #apiKey;
@@ -443,24 +718,57 @@ class HelmrClient {
443
718
  }
444
719
  }
445
720
  tasks = {
446
- trigger: async (task, opts) => {
447
- const runWorkspace = runWorkspaceFromSpec(opts.workspace);
448
- const maxDurationSeconds = readOptionalMaxDurationSeconds(task.maxDuration);
449
- const response = await this.#fetch("/api/runs", {
450
- method: "POST",
451
- body: JSON.stringify({
452
- task_id: task.id,
453
- secrets: opts.secrets ?? {},
454
- payload: opts.payload,
455
- workspace: runWorkspace,
456
- max_duration_seconds: maxDurationSeconds
457
- }),
458
- headers: { "content-type": "application/json" }
459
- });
460
- const run = await response.json();
461
- return runHandle(run.id, run.task_id);
721
+ trigger: async (...args) => {
722
+ const taskId = args[0];
723
+ const hasPayload = args.length === 3;
724
+ const payload = hasPayload ? args[1] : undefined;
725
+ const opts = hasPayload ? args[2] : args[1];
726
+ if (hasPayload && payload === undefined) {
727
+ throw new Error(`task ${JSON.stringify(taskId)} requires payload`);
728
+ }
729
+ return await this.#triggerRun(taskId, payload, opts);
462
730
  }
463
731
  };
732
+ async[triggerTaskClientMethod](task, ...args) {
733
+ const hasPayload = task.payload !== undefined;
734
+ const payload = hasPayload ? args[0] : undefined;
735
+ const opts = hasPayload ? args[1] : args[0];
736
+ if (task.payload !== undefined) {
737
+ if (payload === undefined) {
738
+ throw new Error(`task ${JSON.stringify(task.id)} requires payload`);
739
+ }
740
+ await parseTaskPayload(task, payload);
741
+ } else if (args.length > 1) {
742
+ throw new Error(`task ${JSON.stringify(task.id)} does not accept payload`);
743
+ }
744
+ return await this.#triggerRun(task.id, payload, opts, readOptionalMaxDurationSeconds(task.maxDuration));
745
+ }
746
+ async#triggerRun(taskId, payload, opts, maxDurationSeconds) {
747
+ const runWorkspace = runWorkspaceFromSpec(opts.workspace);
748
+ const runOptions = {
749
+ ...opts.deploymentId === undefined ? {} : { deployment_id: opts.deploymentId },
750
+ ...opts.version === undefined ? {} : { version: opts.version },
751
+ ...opts.queue === undefined ? {} : { queue: { name: opts.queue } },
752
+ ...opts.concurrencyKey === undefined ? {} : { concurrency_key: opts.concurrencyKey },
753
+ ...opts.priority === undefined ? {} : { priority: opts.priority },
754
+ ...opts.ttl === undefined ? {} : { ttl: opts.ttl },
755
+ ...maxDurationSeconds === undefined ? {} : { max_duration_seconds: maxDurationSeconds },
756
+ ...runIdempotencyRequestFields(opts.idempotencyKey, opts.idempotencyKeyTTL)
757
+ };
758
+ const response = await this.#fetch("/api/runs", {
759
+ method: "POST",
760
+ body: JSON.stringify({
761
+ task_id: taskId,
762
+ secrets: opts.secrets ?? {},
763
+ ...payload === undefined ? {} : { payload },
764
+ workspace: runWorkspace,
765
+ ...Object.keys(runOptions).length === 0 ? {} : { options: runOptions }
766
+ }),
767
+ headers: { "content-type": "application/json" }
768
+ });
769
+ const run = await response.json();
770
+ return runHandle(run.id, run.task_id);
771
+ }
464
772
  runs = {
465
773
  retrieve: async (idOrHandle, opts = {}) => {
466
774
  const response = await this.#json(`/api/runs/${encodeURIComponent(runId(idOrHandle))}`, requestSignal(opts.signal));
@@ -471,16 +779,21 @@ class HelmrClient {
471
779
  const timeoutMs = opts.timeoutMs;
472
780
  const intervalMs = opts.intervalMs ?? 1000;
473
781
  const started = Date.now();
474
- for (;; ) {
475
- throwIfAborted(opts.signal);
476
- const run = await this.runs.retrieve(id, retrieveOptions(opts.signal));
477
- if (isTerminalRunStatus(run.status)) {
478
- return run;
782
+ const wait = waitSignal(opts.signal, timeoutMs, () => new TimeoutError(`run ${id} did not finish within ${timeoutMs}ms`));
783
+ try {
784
+ for (;; ) {
785
+ throwIfAborted(wait.signal);
786
+ const run = await this.runs.retrieve(id, retrieveOptions(wait.signal));
787
+ if (isTerminalRunStatus(run.status)) {
788
+ return run;
789
+ }
790
+ if (timeoutMs !== undefined && Date.now() - started > timeoutMs) {
791
+ throw new TimeoutError(`run ${id} did not finish within ${timeoutMs}ms`);
792
+ }
793
+ await delay(intervalMs, wait.signal);
479
794
  }
480
- if (timeoutMs !== undefined && Date.now() - started > timeoutMs) {
481
- throw new TimeoutError(`run ${id} did not finish within ${timeoutMs}ms`);
482
- }
483
- await delay(intervalMs, opts.signal);
795
+ } finally {
796
+ wait.cleanup();
484
797
  }
485
798
  },
486
799
  list: async (opts = {}) => {
@@ -512,27 +825,19 @@ class HelmrClient {
512
825
  }
513
826
  };
514
827
  waitpoints = {
515
- approve: async (target, waitpointIdOrOpts, opts = {}) => {
516
- const resolved = resolveWaitpointArgs(target, waitpointIdOrOpts, opts);
517
- await this.#fetch(`/api/runs/${encodeURIComponent(resolved.runId)}/waitpoints/${encodeURIComponent(resolved.waitpointId)}/approve`, {
518
- method: "POST",
519
- body: JSON.stringify(approvalBody(resolved.opts)),
520
- headers: { "content-type": "application/json" }
521
- });
522
- },
523
- deny: async (target, waitpointIdOrOpts, opts = {}) => {
524
- const resolved = resolveWaitpointArgs(target, waitpointIdOrOpts, opts);
525
- await this.#fetch(`/api/runs/${encodeURIComponent(resolved.runId)}/waitpoints/${encodeURIComponent(resolved.waitpointId)}/deny`, {
828
+ create: async (opts) => {
829
+ const response = await this.#json("/api/waitpoints", {
526
830
  method: "POST",
527
- body: JSON.stringify(approvalBody(resolved.opts)),
831
+ body: JSON.stringify(waitpointCreateBody(opts)),
528
832
  headers: { "content-type": "application/json" }
529
833
  });
834
+ return waitpointFromResponse(response);
530
835
  },
531
- reply: async (target, waitpointIdOrOpts, opts) => {
836
+ respond: async (target, waitpointIdOrOpts, opts = {}) => {
532
837
  const resolved = resolveWaitpointArgs(target, waitpointIdOrOpts, opts);
533
- await this.#fetch(`/api/runs/${encodeURIComponent(resolved.runId)}/waitpoints/${encodeURIComponent(resolved.waitpointId)}/message`, {
838
+ await this.#fetch(`/api/waitpoints/${encodeURIComponent(resolved.waitpointId)}/respond`, {
534
839
  method: "POST",
535
- body: JSON.stringify({ text: resolved.opts.text, attachments: [] }),
840
+ body: JSON.stringify(waitpointRespondBody(resolved.opts)),
536
841
  headers: { "content-type": "application/json" }
537
842
  });
538
843
  },
@@ -541,21 +846,77 @@ class HelmrClient {
541
846
  const resolved = resolveWaitpointArgs(target, waitpointIdOrOpts, opts);
542
847
  const response = await this.#json("/api/waitpoints/tokens", {
543
848
  method: "POST",
544
- body: JSON.stringify(waitpointTokenCreateBody(resolved.runId, resolved.waitpointId, resolved.opts)),
849
+ body: JSON.stringify(waitpointTokenCreateBody(resolved.waitpointId, resolved.opts)),
545
850
  headers: { "content-type": "application/json" }
546
851
  });
547
852
  return waitpointResponseTokenFromResponse(response);
548
853
  },
549
- complete: async (target, tokenOrOpts, maybeOpts) => {
550
- const resolved = typeof target === "string" ? resolveWaitpointTokenCompleteArgs(target, tokenOrOpts, maybeOpts) : { id: target.id, token: target.token, opts: tokenOrOpts };
551
- await this.#fetch(`/api/waitpoints/tokens/${encodeURIComponent(resolved.id)}/complete`, {
854
+ respond: async (target, tokenOrOpts, maybeOpts) => {
855
+ const resolved = typeof target === "string" ? resolveWaitpointTokenRespondArgs(target, tokenOrOpts, maybeOpts) : { id: target.id, token: target.token, opts: tokenOrOpts };
856
+ await this.#fetch(`/api/waitpoints/tokens/${encodeURIComponent(resolved.id)}/respond`, {
552
857
  method: "POST",
553
- body: JSON.stringify(waitpointTokenCompleteBody(resolved.token, resolved.opts)),
858
+ body: JSON.stringify(waitpointTokenRespondBody(resolved.token, resolved.opts)),
554
859
  headers: { "content-type": "application/json" }
555
860
  });
556
861
  }
557
862
  }
558
863
  };
864
+ schedules = {
865
+ create: async (opts) => {
866
+ const response = await this.#json("/api/schedules", {
867
+ method: "POST",
868
+ body: JSON.stringify(scheduleCreateBody(opts)),
869
+ headers: { "content-type": "application/json" }
870
+ });
871
+ return scheduleFromResponse(response);
872
+ },
873
+ list: async (opts = {}) => {
874
+ const query = scheduleScopeQuery(opts);
875
+ const suffix = query.size === 0 ? "" : `?${query}`;
876
+ const response = await this.#json(`/api/schedules${suffix}`, requestSignal(opts.signal));
877
+ return response.schedules.map(scheduleFromResponse);
878
+ },
879
+ update: async (id, opts) => {
880
+ const query = scheduleScopeQuery(opts);
881
+ const suffix = query.size === 0 ? "" : `?${query}`;
882
+ const response = await this.#json(`/api/schedules/${encodeURIComponent(id)}${suffix}`, {
883
+ method: "PUT",
884
+ body: JSON.stringify(scheduleCreateBody(opts)),
885
+ headers: { "content-type": "application/json" },
886
+ ...requestSignal(opts.signal)
887
+ });
888
+ return scheduleFromResponse(response);
889
+ },
890
+ retrieve: async (id, opts = {}) => {
891
+ const query = scheduleScopeQuery(opts);
892
+ const suffix = query.size === 0 ? "" : `?${query}`;
893
+ return scheduleFromResponse(await this.#json(`/api/schedules/${encodeURIComponent(id)}${suffix}`, requestSignal(opts.signal)));
894
+ },
895
+ activate: async (id, opts = {}) => {
896
+ const query = scheduleScopeQuery(opts);
897
+ const suffix = query.size === 0 ? "" : `?${query}`;
898
+ return scheduleFromResponse(await this.#json(`/api/schedules/${encodeURIComponent(id)}/activate${suffix}`, {
899
+ method: "POST",
900
+ ...requestSignal(opts.signal)
901
+ }));
902
+ },
903
+ deactivate: async (id, opts = {}) => {
904
+ const query = scheduleScopeQuery(opts);
905
+ const suffix = query.size === 0 ? "" : `?${query}`;
906
+ return scheduleFromResponse(await this.#json(`/api/schedules/${encodeURIComponent(id)}/deactivate${suffix}`, {
907
+ method: "POST",
908
+ ...requestSignal(opts.signal)
909
+ }));
910
+ },
911
+ delete: async (id, opts = {}) => {
912
+ const query = scheduleScopeQuery(opts);
913
+ const suffix = query.size === 0 ? "" : `?${query}`;
914
+ await this.#fetch(`/api/schedules/${encodeURIComponent(id)}${suffix}`, {
915
+ method: "DELETE",
916
+ ...requestSignal(opts.signal)
917
+ });
918
+ }
919
+ };
559
920
  async#retrieveLogs(id, signal) {
560
921
  const response = await this.#json(`/api/runs/${encodeURIComponent(id)}/logs`, requestSignal(signal));
561
922
  return {
@@ -657,43 +1018,122 @@ function runResponseToSnapshot(response) {
657
1018
  exitCode: response.exit_code ?? null,
658
1019
  ...response.created_at === undefined ? {} : { createdAt: response.created_at },
659
1020
  ...response.updated_at === undefined ? {} : { updatedAt: response.updated_at },
660
- pendingWaitpoint: pendingWaitpointFromResponse(response.id, response.pending_wait),
1021
+ pendingWaitpoint: pendingWaitpointFromResponse(response.id, response.pending_waitpoint),
661
1022
  ..."output" in response ? { output: response.output } : {}
662
1023
  });
663
1024
  }
664
- function approvalBody(opts) {
665
- return opts.reason === undefined ? {} : { reason: opts.reason };
1025
+ function scheduleCreateBody(opts) {
1026
+ return {
1027
+ ..."deduplicationKey" in opts && opts.deduplicationKey !== undefined ? { deduplication_key: opts.deduplicationKey } : {},
1028
+ ...opts.projectId === undefined ? {} : { project_id: opts.projectId },
1029
+ ...opts.environmentId === undefined ? {} : { environment_id: opts.environmentId },
1030
+ ...opts.externalId === undefined ? {} : { external_id: opts.externalId },
1031
+ task: opts.task,
1032
+ cron: opts.cron,
1033
+ ...opts.timezone === undefined ? {} : { timezone: opts.timezone },
1034
+ ...opts.active === undefined ? {} : { active: opts.active },
1035
+ workspace: runWorkspaceFromSpec(opts.workspace),
1036
+ ...opts.secretBindings === undefined ? {} : { secret_bindings: opts.secretBindings },
1037
+ ...opts.options === undefined ? {} : { options: runOptionsBody(opts.options) }
1038
+ };
666
1039
  }
667
- function waitpointTokenCreateBody(runId2, waitpointId, opts) {
1040
+ function runOptionsBody(opts) {
1041
+ if (opts === undefined)
1042
+ return {};
1043
+ return {
1044
+ ...opts.deploymentId === undefined ? {} : { deployment_id: opts.deploymentId },
1045
+ ...opts.version === undefined ? {} : { version: opts.version },
1046
+ ...opts.queue === undefined ? {} : { queue: { name: opts.queue } },
1047
+ ...opts.concurrencyKey === undefined ? {} : { concurrency_key: opts.concurrencyKey },
1048
+ ...opts.priority === undefined ? {} : { priority: opts.priority },
1049
+ ...opts.ttl === undefined ? {} : { ttl: opts.ttl },
1050
+ ...opts.maxDurationSeconds === undefined ? {} : { max_duration_seconds: opts.maxDurationSeconds }
1051
+ };
1052
+ }
1053
+ function scheduleScopeQuery(opts) {
1054
+ const query = new URLSearchParams;
1055
+ if (opts.projectId !== undefined)
1056
+ query.set("project_id", opts.projectId);
1057
+ if (opts.environmentId !== undefined)
1058
+ query.set("environment_id", opts.environmentId);
1059
+ return query;
1060
+ }
1061
+ function scheduleFromResponse(response) {
1062
+ return {
1063
+ id: response.id,
1064
+ type: response.type,
1065
+ projectId: response.project_id,
1066
+ environmentId: response.environment_id,
1067
+ task: response.task,
1068
+ ...response.deduplication_key === undefined || response.deduplication_key === "" ? {} : { deduplicationKey: response.deduplication_key },
1069
+ ...response.external_id === undefined || response.external_id === "" ? {} : { externalId: response.external_id },
1070
+ cron: response.cron,
1071
+ timezone: response.timezone,
1072
+ active: response.active,
1073
+ status: response.status,
1074
+ ...response.last_error === undefined || response.last_error === "" ? {} : { lastError: response.last_error },
1075
+ ..."workspace" in response ? { workspace: response.workspace } : {},
1076
+ ...response.next_scheduled_at === undefined ? {} : { nextScheduledAt: response.next_scheduled_at },
1077
+ ...response.last_scheduled_at === undefined ? {} : { lastScheduledAt: response.last_scheduled_at },
1078
+ createdAt: response.created_at,
1079
+ updatedAt: response.updated_at
1080
+ };
1081
+ }
1082
+ function waitpointTokenCreateBody(waitpointId, opts) {
668
1083
  return {
669
- run_id: runId2,
670
1084
  waitpoint_id: waitpointId,
671
- ...opts.actions === undefined ? {} : { actions: opts.actions },
672
1085
  ...opts.expiresInSeconds === undefined ? {} : { expires_in_seconds: opts.expiresInSeconds },
673
1086
  ...opts.expiresAt === undefined ? {} : { expires_at: opts.expiresAt },
674
1087
  ...opts.metadata === undefined ? {} : { metadata: opts.metadata }
675
1088
  };
676
1089
  }
677
- function waitpointTokenCompleteBody(token, opts) {
1090
+ function waitpointCreateBody(opts) {
678
1091
  return {
679
- token,
680
- action: opts.action,
681
- ..."reason" in opts && opts.reason === undefined ? {} : ("reason" in opts) ? { reason: opts.reason } : {},
682
- ..."text" in opts ? { text: opts.text } : {},
1092
+ ...opts.projectId === undefined ? {} : { project_id: opts.projectId },
1093
+ ...opts.environmentId === undefined ? {} : { environment_id: opts.environmentId },
1094
+ ...opts.request === undefined ? {} : { request: opts.request },
1095
+ ...opts.displayText === undefined ? {} : { display_text: opts.displayText },
1096
+ expires_at: opts.expiresAt,
1097
+ ...opts.idempotencyKey === undefined ? {} : { idempotency_key: opts.idempotencyKey },
1098
+ ...opts.idempotencyKeyExpiresAt === undefined ? {} : { idempotency_key_expires_at: opts.idempotencyKeyExpiresAt },
1099
+ ...opts.idempotencyKeyTTLSeconds === undefined ? {} : { idempotency_key_ttl_seconds: opts.idempotencyKeyTTLSeconds }
1100
+ };
1101
+ }
1102
+ function waitpointFromResponse(response) {
1103
+ return {
1104
+ id: response.id,
1105
+ projectId: response.project_id,
1106
+ environmentId: response.environment_id,
1107
+ kind: response.kind,
1108
+ status: response.status,
1109
+ request: response.request ?? {},
1110
+ displayText: response.display_text ?? "",
1111
+ expiresAt: response.expires_at ?? null,
1112
+ createdAt: response.created_at
1113
+ };
1114
+ }
1115
+ function waitpointRespondBody(opts) {
1116
+ return {
1117
+ ..."value" in opts ? { value: opts.value } : {},
683
1118
  ...opts.externalSubject === undefined ? {} : { external_subject: opts.externalSubject },
684
1119
  ...opts.metadata === undefined ? {} : { metadata: opts.metadata }
685
1120
  };
686
1121
  }
687
- function resolveWaitpointTokenCompleteArgs(id, token, opts) {
1122
+ function waitpointTokenRespondBody(token, opts) {
1123
+ return {
1124
+ token,
1125
+ ...waitpointRespondBody(opts)
1126
+ };
1127
+ }
1128
+ function resolveWaitpointTokenRespondArgs(id, token, opts) {
688
1129
  if (typeof token !== "string" || opts === undefined) {
689
- throw new Error("waitpoint token secret is required when completing by token id");
1130
+ throw new Error("waitpoint token secret is required when responding by token id");
690
1131
  }
691
1132
  return { id, token, opts };
692
1133
  }
693
1134
  function waitpointResponseTokenFromResponse(response) {
694
1135
  return {
695
1136
  id: response.id,
696
- runId: response.run_id,
697
1137
  waitpointId: response.waitpoint_id,
698
1138
  url: response.url,
699
1139
  token: response.token,
@@ -702,27 +1142,21 @@ function waitpointResponseTokenFromResponse(response) {
702
1142
  }
703
1143
  function resolveWaitpointArgs(target, waitpointIdOrOpts, opts) {
704
1144
  if (isWaitpointRef(target)) {
705
- const resolvedOpts = waitpointIdOrOpts !== undefined && typeof waitpointIdOrOpts !== "string" ? waitpointIdOrOpts : opts;
706
1145
  return {
707
- runId: target.runId,
708
1146
  waitpointId: target.waitpointId,
709
- opts: resolvedOpts ?? {}
1147
+ opts: waitpointIdOrOpts ?? opts ?? {}
710
1148
  };
711
1149
  }
712
- if (typeof waitpointIdOrOpts !== "string") {
713
- throw new Error("waitpoint id is required when resolving a waitpoint by run id");
714
- }
715
1150
  return {
716
- runId: target,
717
- waitpointId: waitpointIdOrOpts,
718
- opts: opts ?? {}
1151
+ waitpointId: target,
1152
+ opts: waitpointIdOrOpts ?? opts ?? {}
719
1153
  };
720
1154
  }
721
1155
  function isWaitpointRef(value) {
722
1156
  if (value === null || typeof value !== "object")
723
1157
  return false;
724
1158
  const record = value;
725
- return typeof record["runId"] === "string" && typeof record["waitpointId"] === "string";
1159
+ return typeof record["waitpointId"] === "string";
726
1160
  }
727
1161
  function retrieveOptions(signal) {
728
1162
  return signal === undefined ? {} : { signal };
@@ -730,6 +1164,28 @@ function retrieveOptions(signal) {
730
1164
  function requestSignal(signal) {
731
1165
  return signal === undefined ? {} : { signal };
732
1166
  }
1167
+ function waitSignal(signal, timeoutMs, timeoutError) {
1168
+ if (timeoutMs === undefined) {
1169
+ return { signal, cleanup: () => {} };
1170
+ }
1171
+ const controller = new AbortController;
1172
+ const abortFromParent = () => {
1173
+ controller.abort(signal?.reason);
1174
+ };
1175
+ if (signal?.aborted === true) {
1176
+ abortFromParent();
1177
+ } else {
1178
+ signal?.addEventListener("abort", abortFromParent, { once: true });
1179
+ }
1180
+ const timeout = setTimeout(() => controller.abort(timeoutError()), timeoutMs);
1181
+ return {
1182
+ signal: controller.signal,
1183
+ cleanup: () => {
1184
+ clearTimeout(timeout);
1185
+ signal?.removeEventListener("abort", abortFromParent);
1186
+ }
1187
+ };
1188
+ }
733
1189
  function throwIfAborted(signal) {
734
1190
  if (signal?.aborted !== true)
735
1191
  return;
@@ -763,29 +1219,52 @@ async function* parseSse(response) {
763
1219
  }
764
1220
  const decoder = new TextDecoder;
765
1221
  let buffer = "";
766
- for (;; ) {
767
- const { value, done } = await reader.read();
768
- if (done) {
769
- return;
770
- }
771
- buffer += decoder.decode(value, { stream: true });
772
- let boundary = findSseBoundary(buffer);
773
- while (boundary !== -1) {
774
- const delimiter = buffer.startsWith(`\r
1222
+ try {
1223
+ for (;; ) {
1224
+ const { value, done } = await reader.read();
1225
+ if (done) {
1226
+ buffer += decoder.decode();
1227
+ const finalEvent = parseSseFrame(buffer);
1228
+ if (finalEvent !== undefined) {
1229
+ yield finalEvent;
1230
+ }
1231
+ return;
1232
+ }
1233
+ buffer += decoder.decode(value, { stream: true });
1234
+ let boundary = findSseBoundary(buffer);
1235
+ while (boundary !== -1) {
1236
+ const delimiter = buffer.startsWith(`\r
775
1237
  \r
776
1238
  `, boundary) ? 4 : 2;
777
- const raw = buffer.slice(0, boundary);
778
- buffer = buffer.slice(boundary + delimiter);
779
- const data = raw.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
780
- `);
781
- if (data !== "") {
782
- const event = runEventRecordToRunEvent(JSON.parse(data));
1239
+ const raw = buffer.slice(0, boundary);
1240
+ buffer = buffer.slice(boundary + delimiter);
1241
+ const event = parseSseFrame(raw);
783
1242
  if (event !== undefined) {
784
1243
  yield event;
785
1244
  }
1245
+ boundary = findSseBoundary(buffer);
786
1246
  }
787
- boundary = findSseBoundary(buffer);
1247
+ if (buffer.length > MAX_SSE_BUFFER_CHARS) {
1248
+ throw new Error("SSE event exceeded the maximum buffer size");
1249
+ }
1250
+ }
1251
+ } finally {
1252
+ reader.releaseLock();
1253
+ }
1254
+ }
1255
+ function parseSseFrame(raw) {
1256
+ const data = raw.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
1257
+ `);
1258
+ if (data === "") {
1259
+ return;
1260
+ }
1261
+ try {
1262
+ return runEventRecordToRunEvent(JSON.parse(data));
1263
+ } catch (error) {
1264
+ if (error instanceof SyntaxError) {
1265
+ return;
788
1266
  }
1267
+ throw error;
789
1268
  }
790
1269
  }
791
1270
  function findSseBoundary(buffer) {
@@ -802,121 +1281,102 @@ function findSseBoundary(buffer) {
802
1281
  return Math.min(lf, crlf);
803
1282
  }
804
1283
  function runEventRecordToRunEvent(event) {
805
- const attributes = objectRecord(event.attributes);
806
- const runId2 = event.run_id ?? stringValue(attributes?.["run_id"]) ?? "";
807
- if (event.message === "log.stdout" || event.message === "log.stderr") {
808
- const stream = event.message === "log.stdout" ? "stdout" : "stderr";
1284
+ const record = objectRecord(event);
1285
+ const message = stringValue(record?.["message"]);
1286
+ const at = stringValue(record?.["at"]);
1287
+ if (record === undefined || message === undefined || at === undefined) {
1288
+ return;
1289
+ }
1290
+ const attributes = objectRecord(record["attributes"]);
1291
+ const runId2 = stringValue(record["run_id"]) ?? stringValue(attributes?.["run_id"]) ?? "";
1292
+ if (message === "log.stdout" || message === "log.stderr") {
1293
+ const stream = message === "log.stdout" ? "stdout" : "stderr";
809
1294
  return {
810
1295
  type: "log",
811
1296
  run_id: runId2,
812
1297
  stream,
813
1298
  bytes: numberValue(attributes?.["bytes"]) ?? 0,
814
1299
  observed_seq: numberValue(attributes?.["observed_seq"]) ?? 0,
815
- at: event.at
1300
+ at
816
1301
  };
817
1302
  }
818
- if (event.message === "waitpoint.requested" && stringValue(attributes?.["kind"]) === "approval") {
819
- const message = stringValue(attributes?.["display_text"]);
1303
+ if (message === "waitpoint.requested") {
820
1304
  const waitpointId = stringValue(attributes?.["waitpoint_id"]);
1305
+ const kind = stringValue(attributes?.["kind"]);
821
1306
  if (waitpointId === undefined)
822
1307
  return;
823
- return {
824
- type: "approval_request",
825
- run_id: runId2,
826
- waitpoint_id: waitpointId,
827
- message: message ?? "",
828
- ...optionalNumber("timeout", attributes?.["timeout"]),
829
- at: event.at
830
- };
831
- }
832
- if (event.message === "waitpoint.resolved" && stringValue(attributes?.["kind"]) === "approval") {
833
- const waitpointId = stringValue(attributes?.["waitpoint_id"]);
834
- const resolution = stringValue(attributes?.["resolution_kind"]);
835
- if (waitpointId === undefined)
836
- return;
837
- if (resolution !== "approved" && resolution !== "denied")
1308
+ if (kind === undefined)
838
1309
  return;
839
1310
  return {
840
- type: "approval_decided",
1311
+ type: "waitpoint_request",
841
1312
  run_id: runId2,
842
1313
  waitpoint_id: waitpointId,
843
- decision: resolution,
844
- ...optionalString("reason", attributes?.["reason"]),
845
- at: event.at
846
- };
847
- }
848
- if (event.message === "waitpoint.requested" && stringValue(attributes?.["kind"]) === "message") {
849
- const request = objectRecord(attributes?.["request"]);
850
- const message = stringValue(attributes?.["display_text"]) ?? stringValue(request?.["prompt"]);
851
- const waitpointId = stringValue(attributes?.["waitpoint_id"]);
852
- if (waitpointId === undefined)
853
- return;
854
- return {
855
- type: "message_request",
856
- run_id: runId2,
857
- waitpoint_id: waitpointId,
858
- ...optionalString("prompt", message),
1314
+ kind,
1315
+ displayText: stringValue(attributes?.["display_text"]) ?? "",
1316
+ request: attributes?.["request"] ?? {},
859
1317
  ...optionalNumber("timeout", attributes?.["timeout"]),
860
- at: event.at
1318
+ at
861
1319
  };
862
1320
  }
863
- if (event.message === "waitpoint.resolved" && stringValue(attributes?.["kind"]) === "message") {
864
- const result = objectRecord(attributes?.["result"]);
865
- const text = stringValue(result?.["text"]);
1321
+ if (message === "waitpoint.resolved") {
866
1322
  const waitpointId = stringValue(attributes?.["waitpoint_id"]);
1323
+ const kind = stringValue(attributes?.["kind"]);
1324
+ const resolution = stringValue(attributes?.["resolution_kind"]);
867
1325
  if (waitpointId === undefined)
868
1326
  return;
869
- if (stringValue(attributes?.["resolution_kind"]) !== "replied")
1327
+ if (kind === undefined || resolution === undefined)
870
1328
  return;
871
1329
  return {
872
- type: "message_received",
1330
+ type: "waitpoint_resolved",
873
1331
  run_id: runId2,
874
1332
  waitpoint_id: waitpointId,
875
- text: text ?? "",
876
- at: event.at
1333
+ kind,
1334
+ resolutionKind: resolution,
1335
+ value: attributes?.["result"],
1336
+ at
877
1337
  };
878
1338
  }
879
- if (event.message.startsWith("emit.")) {
1339
+ if (message.startsWith("emit.")) {
880
1340
  return {
881
1341
  type: "emit",
882
1342
  run_id: runId2,
883
- event_type: stringValue(attributes?.["type"]) ?? event.message.slice("emit.".length),
1343
+ event_type: stringValue(attributes?.["type"]) ?? message.slice("emit.".length),
884
1344
  content: attributes?.["content"],
885
- at: event.at
1345
+ at
886
1346
  };
887
1347
  }
888
- if (event.message === "run.completed") {
1348
+ if (message === "run.completed") {
889
1349
  return {
890
- type: "task_complete",
1350
+ type: "task_result",
891
1351
  run_id: runId2,
892
1352
  exit_code: numberValue(attributes?.["exit_code"]) ?? 0,
893
- at: event.at
1353
+ at
894
1354
  };
895
1355
  }
896
- if (event.message === "run.failed") {
1356
+ if (message === "run.failed") {
897
1357
  return {
898
1358
  type: "run_failed",
899
1359
  run_id: runId2,
900
1360
  failure_kind: stringValue(attributes?.["failure_kind"]) ?? "task_failed",
901
1361
  detail: attributes?.["detail"],
902
- at: event.at
1362
+ at
903
1363
  };
904
1364
  }
905
- if (event.message === "run.timeout") {
1365
+ if (message === "run.cancelled") {
906
1366
  return {
907
- type: "run_timeout",
1367
+ type: "run_cancelled",
908
1368
  run_id: runId2,
909
- elapsed_secs: numberValue(attributes?.["elapsed_active_secs"]) ?? 0,
910
- limit_secs: numberValue(attributes?.["limit_secs"]) ?? 0,
911
- at: event.at
1369
+ ...optionalString("reason", attributes?.["reason"]),
1370
+ at
912
1371
  };
913
1372
  }
914
- if (event.message === "run.cancelled") {
1373
+ if (message === "run.expired") {
915
1374
  return {
916
- type: "run_cancelled",
1375
+ type: "run_expired",
917
1376
  run_id: runId2,
918
- ...optionalString("reason", attributes?.["reason"]),
919
- at: event.at
1377
+ ...optionalString("ttl", attributes?.["ttl"]),
1378
+ ...optionalString("message", attributes?.["message"]),
1379
+ at
920
1380
  };
921
1381
  }
922
1382
  return;
@@ -951,20 +1411,21 @@ function defineConfig(config) {
951
1411
  if (config === null || typeof config !== "object") {
952
1412
  throw new Error("defineConfig() requires an object");
953
1413
  }
1414
+ if (!("project" in config)) {
1415
+ throw new Error("defineConfig({ project }) requires a non-empty string");
1416
+ }
1417
+ if (typeof config.project !== "string" || config.project.trim() === "") {
1418
+ throw new Error("defineConfig({ project }) must be a non-empty string");
1419
+ }
1420
+ if (config.project.includes("\x00")) {
1421
+ throw new Error("defineConfig({ project }) must not contain NUL");
1422
+ }
954
1423
  if (!("dirs" in config)) {
955
1424
  throw new Error("defineConfig({ dirs }) requires a non-empty dirs array");
956
1425
  }
957
1426
  if (!Array.isArray(config.dirs) || config.dirs.length === 0) {
958
1427
  throw new Error("defineConfig({ dirs }) requires a non-empty dirs array");
959
1428
  }
960
- if (config.project !== undefined) {
961
- if (typeof config.project !== "string" || config.project.trim() === "") {
962
- throw new Error("defineConfig({ project }) must be a non-empty string");
963
- }
964
- if (config.project.includes("\x00")) {
965
- throw new Error("defineConfig({ project }) must not contain NUL");
966
- }
967
- }
968
1429
  for (const dir of config.dirs) {
969
1430
  if (typeof dir !== "string" || dir.trim() === "") {
970
1431
  throw new Error("defineConfig({ dirs }) entries must be non-empty strings");
@@ -987,17 +1448,12 @@ function defineConfig(config) {
987
1448
  }
988
1449
  }
989
1450
  return markConfig({
990
- ...config.project === undefined ? {} : { project: config.project },
1451
+ project: config.project,
991
1452
  dirs: [...config.dirs],
992
1453
  ...config.ignorePatterns === undefined ? {} : { ignorePatterns: [...config.ignorePatterns] }
993
1454
  });
994
1455
  }
995
1456
 
996
- // sdk/typescript/src/task.ts
997
- function task(config) {
998
- return markTask(config);
999
- }
1000
-
1001
1457
  // sdk/typescript/src/trigger.ts
1002
1458
  var defaultClient;
1003
1459
  function getDefaultClient() {
@@ -1005,10 +1461,102 @@ function getDefaultClient() {
1005
1461
  return defaultClient;
1006
1462
  }
1007
1463
  var tasks = {
1008
- trigger(task2, opts) {
1009
- return getDefaultClient().tasks.trigger(task2, opts);
1464
+ trigger(...args) {
1465
+ return getDefaultClient().tasks.trigger(...args);
1466
+ }
1467
+ };
1468
+ function triggerTask(task, ...args) {
1469
+ return getDefaultClient()[triggerTaskClientMethod](task, ...args);
1470
+ }
1471
+
1472
+ // sdk/typescript/src/task.ts
1473
+ function task(config) {
1474
+ const marked = markTask(config);
1475
+ Object.defineProperty(marked, "trigger", {
1476
+ value: (...args) => triggerTask(marked, ...args)
1477
+ });
1478
+ return marked;
1479
+ }
1480
+ function queue(config) {
1481
+ return Object.freeze({ ...config });
1482
+ }
1483
+
1484
+ // sdk/typescript/src/schedules.ts
1485
+ function task2(config) {
1486
+ const { cron, workspace, secretBindings, ...taskConfig } = config;
1487
+ const taskId = config.id;
1488
+ if (cron !== undefined && workspace === undefined) {
1489
+ throw new Error(`task ${JSON.stringify(taskId)} schedule workspace is required`);
1490
+ }
1491
+ const schedule = cron === undefined ? undefined : {
1492
+ cron: typeof cron === "string" ? cron : cron.pattern,
1493
+ ...typeof cron === "string" || cron.timezone === undefined ? {} : { timezone: cron.timezone },
1494
+ workspace,
1495
+ ...secretBindings === undefined ? {} : { secretBindings }
1496
+ };
1497
+ const marked = markScheduledTask({ ...taskConfig, payload: scheduledTaskPayloadSchema }, schedule);
1498
+ Object.defineProperty(marked, "trigger", {
1499
+ value: (...args) => triggerTask(marked, ...args)
1500
+ });
1501
+ return marked;
1502
+ }
1503
+ var scheduledTaskPayloadSchema = {
1504
+ "~standard": {
1505
+ version: 1,
1506
+ vendor: "helmr",
1507
+ validate(value) {
1508
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1509
+ return { issues: [{ message: "expected scheduled task payload object" }] };
1510
+ }
1511
+ const input = value;
1512
+ const timestamp = parseDateField(input["timestamp"], "timestamp");
1513
+ const lastTimestamp = parseOptionalDateField(input["lastTimestamp"], "lastTimestamp");
1514
+ const timezone = input["timezone"];
1515
+ const scheduleId = input["scheduleId"];
1516
+ const externalId = input["externalId"];
1517
+ const upcoming = input["upcoming"];
1518
+ const issues = [
1519
+ ...timestamp.issues,
1520
+ ...lastTimestamp.issues,
1521
+ ...typeof timezone === "string" && timezone.trim() !== "" ? [] : [{ message: "expected string", path: ["timezone"] }],
1522
+ ...typeof scheduleId === "string" && scheduleId.trim() !== "" ? [] : [{ message: "expected string", path: ["scheduleId"] }],
1523
+ ...externalId === undefined || typeof externalId === "string" ? [] : [{ message: "expected string", path: ["externalId"] }],
1524
+ ...Array.isArray(upcoming) ? [] : [{ message: "expected array", path: ["upcoming"] }]
1525
+ ];
1526
+ const upcomingDates = Array.isArray(upcoming) ? upcoming.map((item, index) => parseDateField(item, `upcoming.${index}`)) : [];
1527
+ issues.push(...upcomingDates.flatMap((item) => item.issues));
1528
+ if (issues.length > 0) {
1529
+ return { issues };
1530
+ }
1531
+ return {
1532
+ value: {
1533
+ timestamp: timestamp.value,
1534
+ ...lastTimestamp.value === undefined ? {} : { lastTimestamp: lastTimestamp.value },
1535
+ timezone,
1536
+ scheduleId,
1537
+ ...externalId === undefined ? {} : { externalId },
1538
+ upcoming: upcomingDates.map((item) => item.value)
1539
+ }
1540
+ };
1541
+ }
1010
1542
  }
1011
1543
  };
1544
+ function parseDateField(value, path) {
1545
+ if (typeof value !== "string") {
1546
+ return { value: new Date(0), issues: [{ message: "expected ISO timestamp", path: path.split(".") }] };
1547
+ }
1548
+ const date = new Date(value);
1549
+ if (Number.isNaN(date.getTime())) {
1550
+ return { value: new Date(0), issues: [{ message: "expected ISO timestamp", path: path.split(".") }] };
1551
+ }
1552
+ return { value: date, issues: [] };
1553
+ }
1554
+ function parseOptionalDateField(value, path) {
1555
+ if (value === undefined || value === null) {
1556
+ return { issues: [] };
1557
+ }
1558
+ return parseDateField(value, path);
1559
+ }
1012
1560
  // sdk/typescript/src/index.ts
1013
1561
  var image = (id) => new ImageBuilderImpl(id);
1014
1562
  var cache = (id) => ({ id });
@@ -1051,6 +1599,16 @@ var waitpoints = new Proxy({}, {
1051
1599
  return Reflect.get(getDefaultClient().waitpoints, property, receiver);
1052
1600
  }
1053
1601
  });
1602
+ var schedules = Object.freeze({
1603
+ task: task2,
1604
+ create: (...args) => getDefaultClient().schedules.create(...args),
1605
+ update: (...args) => getDefaultClient().schedules.update(...args),
1606
+ list: (...args) => getDefaultClient().schedules.list(...args),
1607
+ retrieve: (...args) => getDefaultClient().schedules.retrieve(...args),
1608
+ activate: (...args) => getDefaultClient().schedules.activate(...args),
1609
+ deactivate: (...args) => getDefaultClient().schedules.deactivate(...args),
1610
+ delete: (...args) => getDefaultClient().schedules.delete(...args)
1611
+ });
1054
1612
  export {
1055
1613
  workspace,
1056
1614
  waitpoints,
@@ -1058,17 +1616,19 @@ export {
1058
1616
  tasks,
1059
1617
  task,
1060
1618
  source,
1619
+ schedules,
1061
1620
  sandbox,
1062
1621
  runs,
1622
+ queue,
1063
1623
  image,
1624
+ idempotencyKeys,
1064
1625
  defineConfig,
1065
1626
  cache,
1066
1627
  UnsupportedTransportError,
1067
1628
  TimeoutError,
1068
1629
  RunNotFoundError,
1069
- MessageTimeoutError,
1630
+ PayloadSchemaValidationError,
1070
1631
  HelmrClient,
1071
1632
  ConcurrentWaitError,
1072
- AuthError,
1073
- ApprovalTimeoutError
1633
+ AuthError
1074
1634
  };