openpond-sdk 0.0.13 → 0.0.15

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/training.js CHANGED
@@ -3,6 +3,105 @@ import { z as z2 } from "zod";
3
3
 
4
4
  // src/model-projects.ts
5
5
  import { z } from "zod";
6
+
7
+ // src/protocol.ts
8
+ var OPENPOND_TRAINING_PROTOCOL_MAJOR = 2;
9
+ var OPENPOND_TRAINING_MEDIA_TYPE = "application/vnd.openpond.training+json;version=2";
10
+ var TRAINING_JOB_SUBMISSION_MAX_BYTES = 1048576;
11
+ var TRAINING_INPUT_ARTIFACT_MAX_BYTES = 67108864;
12
+ var TRAINING_API_RESPONSE_MAX_BYTES = 8388608;
13
+ var OpenPondProtocolError = class extends Error {
14
+ code;
15
+ constructor(code, message) {
16
+ super(message);
17
+ this.name = "OpenPondProtocolError";
18
+ this.code = code;
19
+ }
20
+ };
21
+ function canonicalJson(value) {
22
+ return JSON.stringify(normalizeCanonicalJson(value));
23
+ }
24
+ function canonicalJsonByteLength(value) {
25
+ return new TextEncoder().encode(canonicalJson(value)).byteLength;
26
+ }
27
+ async function canonicalSha256(value) {
28
+ const bytes = new TextEncoder().encode(canonicalJson(value));
29
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
30
+ return Array.from(
31
+ new Uint8Array(digest),
32
+ (byte) => byte.toString(16).padStart(2, "0")
33
+ ).join("");
34
+ }
35
+ function assertCanonicalPayloadSize(value, maximumBytes, label) {
36
+ const actualBytes = canonicalJsonByteLength(value);
37
+ if (actualBytes > maximumBytes) {
38
+ throw new OpenPondProtocolError(
39
+ "payload_too_large",
40
+ `${label} is ${actualBytes} bytes; the maximum is ${maximumBytes} bytes.`
41
+ );
42
+ }
43
+ }
44
+ function parseBoundedJson(text, maximumBytes, label) {
45
+ const actualBytes = new TextEncoder().encode(text).byteLength;
46
+ if (actualBytes > maximumBytes) {
47
+ throw new OpenPondProtocolError(
48
+ "response_too_large",
49
+ `${label} is ${actualBytes} bytes; the maximum is ${maximumBytes} bytes.`
50
+ );
51
+ }
52
+ try {
53
+ return JSON.parse(text);
54
+ } catch {
55
+ throw new OpenPondProtocolError(
56
+ "invalid_json",
57
+ `${label} was not valid JSON.`
58
+ );
59
+ }
60
+ }
61
+ function normalizeCanonicalJson(value) {
62
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
63
+ return value;
64
+ }
65
+ if (typeof value === "number") {
66
+ if (!Number.isFinite(value)) {
67
+ throw new OpenPondProtocolError(
68
+ "non_json_value",
69
+ "Canonical JSON cannot contain a non-finite number."
70
+ );
71
+ }
72
+ return Object.is(value, -0) ? 0 : value;
73
+ }
74
+ if (Array.isArray(value)) {
75
+ return value.map((entry) => normalizeCanonicalJson(entry));
76
+ }
77
+ if (typeof value === "object") {
78
+ const prototype = Object.getPrototypeOf(value);
79
+ if (prototype !== Object.prototype && prototype !== null) {
80
+ throw new OpenPondProtocolError(
81
+ "non_json_value",
82
+ "Canonical JSON accepts only plain objects."
83
+ );
84
+ }
85
+ const result = {};
86
+ for (const key of Object.keys(value).sort()) {
87
+ const entry = value[key];
88
+ if (entry === void 0 || typeof entry === "function" || typeof entry === "symbol") {
89
+ throw new OpenPondProtocolError(
90
+ "non_json_value",
91
+ `Canonical JSON cannot contain ${typeof entry} at ${key}.`
92
+ );
93
+ }
94
+ result[key] = normalizeCanonicalJson(entry);
95
+ }
96
+ return result;
97
+ }
98
+ throw new OpenPondProtocolError(
99
+ "non_json_value",
100
+ `Canonical JSON cannot contain ${typeof value}.`
101
+ );
102
+ }
103
+
104
+ // src/model-projects.ts
6
105
  var IdSchema = z.string().trim().min(1).max(500);
7
106
  var HashSchema = z.string().regex(/^[a-f0-9]{64}$/);
8
107
  var TimestampSchema = z.string().datetime({ offset: true });
@@ -144,11 +243,56 @@ var HostedModelProjectDetailSchema = z.object({
144
243
  jobCount: z.number().int().nonnegative(),
145
244
  latestJobIds: z.array(IdSchema).max(100)
146
245
  }).strict();
246
+ var ModelProjectApiErrorSchema = z.object({
247
+ schemaVersion: z.literal("openpond.modelProjectApiError.v2"),
248
+ code: z.string().trim().min(1).max(200),
249
+ message: z.string().trim().min(1).max(5e3),
250
+ retryable: z.boolean().default(false),
251
+ requestId: z.string().trim().min(1).max(500).nullable().default(null),
252
+ details: z.record(z.string(), z.unknown()).default({})
253
+ }).strict();
147
254
 
148
255
  // src/training.ts
149
256
  var IdSchema2 = z2.string().trim().min(1).max(500);
150
257
  var HashSchema2 = z2.string().regex(/^[a-f0-9]{64}$/);
151
258
  var TimestampSchema2 = z2.string().datetime({ offset: true });
259
+ var TrainingInputArtifactUploadSchema = z2.object({
260
+ schemaVersion: z2.literal("openpond.trainingInputArtifactUpload.v2"),
261
+ kind: z2.enum(["portable_training_bundle", "reward_model_dataset"]),
262
+ idempotencyKey: z2.string().trim().min(1).max(500),
263
+ sourceManifest: ModelProjectImmutableRefSchema,
264
+ payload: z2.unknown(),
265
+ contentHash: HashSchema2
266
+ }).strict();
267
+ var TrainingInputArtifactSchema = z2.object({
268
+ schemaVersion: z2.literal("openpond.trainingInputArtifact.v2"),
269
+ kind: z2.enum(["portable_training_bundle", "reward_model_dataset"]),
270
+ sourceManifest: ModelProjectImmutableRefSchema,
271
+ artifactRef: z2.string().trim().min(1).max(2e3),
272
+ contentHash: HashSchema2,
273
+ sizeBytes: z2.number().int().positive(),
274
+ createdAt: TimestampSchema2
275
+ }).strict();
276
+ async function trainingInputArtifactUploadHash(upload) {
277
+ const { contentHash: _contentHash, ...content } = upload;
278
+ return canonicalSha256(content);
279
+ }
280
+ async function parseAndVerifyTrainingInputArtifactUpload(value) {
281
+ assertCanonicalPayloadSize(
282
+ value,
283
+ TRAINING_INPUT_ARTIFACT_MAX_BYTES,
284
+ "Training input artifact"
285
+ );
286
+ const parsed = TrainingInputArtifactUploadSchema.parse(value);
287
+ const expectedHash = await trainingInputArtifactUploadHash(parsed);
288
+ if (parsed.contentHash !== expectedHash) {
289
+ throw new OpenPondProtocolError(
290
+ "content_hash_mismatch",
291
+ `Training input artifact contentHash ${parsed.contentHash} does not match ${expectedHash}.`
292
+ );
293
+ }
294
+ return parsed;
295
+ }
152
296
  var TrainingJobKindSchema = z2.enum([
153
297
  "reward_model_train",
154
298
  "policy_optimize"
@@ -255,6 +399,26 @@ var TrainingJobSubmissionSchema = z2.object({
255
399
  });
256
400
  }
257
401
  });
402
+ async function trainingJobSubmissionHash(submission) {
403
+ const { contentHash: _contentHash, ...content } = submission;
404
+ return canonicalSha256(content);
405
+ }
406
+ async function parseAndVerifyTrainingJobSubmission(value) {
407
+ assertCanonicalPayloadSize(
408
+ value,
409
+ TRAINING_JOB_SUBMISSION_MAX_BYTES,
410
+ "Training Job submission"
411
+ );
412
+ const parsed = TrainingJobSubmissionSchema.parse(value);
413
+ const expectedHash = await trainingJobSubmissionHash(parsed);
414
+ if (parsed.contentHash !== expectedHash) {
415
+ throw new OpenPondProtocolError(
416
+ "content_hash_mismatch",
417
+ `Training Job contentHash ${parsed.contentHash} does not match ${expectedHash}.`
418
+ );
419
+ }
420
+ return parsed;
421
+ }
258
422
  var TrainingJobSchema = z2.object({
259
423
  schemaVersion: z2.literal("openpond.trainingJob.v2"),
260
424
  id: IdSchema2,
@@ -274,6 +438,11 @@ var TrainingJobSchema = z2.object({
274
438
  updatedAt: TimestampSchema2,
275
439
  completedAt: TimestampSchema2.nullable()
276
440
  }).strict();
441
+ var TrainingJobPageSchema = z2.object({
442
+ schemaVersion: z2.literal("openpond.trainingJobPage.v2"),
443
+ jobs: z2.array(TrainingJobSchema).max(1e3),
444
+ nextCursor: z2.string().trim().min(1).max(2e3).nullable()
445
+ }).strict();
277
446
  var TrainingJobEventSchema = z2.object({
278
447
  schemaVersion: z2.literal("openpond.trainingJobEvent.v2"),
279
448
  id: IdSchema2,
@@ -293,6 +462,7 @@ var TrainingJobLogSchema = z2.object({
293
462
  message: z2.string().max(2e4),
294
463
  createdAt: TimestampSchema2
295
464
  }).strict();
465
+ var TrainingJobControlRequestSchema = z2.object({ expectedVersion: z2.number().int().nonnegative() }).strict();
296
466
  var TrainingJobOutputSchema = z2.object({
297
467
  schemaVersion: z2.literal("openpond.trainingJobOutput.v2"),
298
468
  id: IdSchema2,
@@ -330,6 +500,69 @@ var TrainingExecutionReceiptSchema = z2.object({
330
500
  issuedAt: TimestampSchema2,
331
501
  signature: z2.string().trim().min(1).max(1e4).nullable()
332
502
  }).strict();
503
+ async function trainingExecutionReceiptHash(receipt) {
504
+ return canonicalSha256(TrainingExecutionReceiptSchema.parse(receipt));
505
+ }
506
+ async function parseAndVerifyTrainingExecutionReceipt(value, expected) {
507
+ const receipt = TrainingExecutionReceiptSchema.parse(value);
508
+ const expectedId = IdSchema2.parse(expected.id);
509
+ const expectedHash = HashSchema2.parse(expected.contentHash);
510
+ const actualHash = await trainingExecutionReceiptHash(receipt);
511
+ if (receipt.id !== expectedId || actualHash !== expectedHash) {
512
+ throw new OpenPondProtocolError(
513
+ "execution_receipt_mismatch",
514
+ "The execution receipt identity or canonical content hash did not match."
515
+ );
516
+ }
517
+ if (expected.teamId !== void 0 && receipt.teamId !== IdSchema2.parse(expected.teamId)) {
518
+ throw new OpenPondProtocolError(
519
+ "execution_receipt_team_mismatch",
520
+ "The execution receipt belongs to a different team."
521
+ );
522
+ }
523
+ if (expected.jobId !== void 0 && receipt.jobId !== IdSchema2.parse(expected.jobId)) {
524
+ throw new OpenPondProtocolError(
525
+ "execution_receipt_job_mismatch",
526
+ "The execution receipt belongs to a different Training Job."
527
+ );
528
+ }
529
+ if ((expected.requireCleanup ?? true) && !receipt.cleanupComplete) {
530
+ throw new OpenPondProtocolError(
531
+ "execution_cleanup_incomplete",
532
+ "The execution receipt does not attest complete terminal cleanup."
533
+ );
534
+ }
535
+ return receipt;
536
+ }
537
+ var TrainingJobOutputsSchema = z2.object({
538
+ schemaVersion: z2.literal("openpond.trainingJobOutputs.v2"),
539
+ outputs: z2.array(TrainingJobOutputSchema).max(1e4),
540
+ receipt: TrainingExecutionReceiptSchema.nullable()
541
+ }).strict();
542
+ var TrainingApiErrorSchema = z2.object({
543
+ schemaVersion: z2.literal("openpond.trainingApiError.v2"),
544
+ code: z2.string().trim().min(1).max(200),
545
+ message: z2.string().trim().min(1).max(5e3),
546
+ retryable: z2.boolean().default(false),
547
+ requestId: z2.string().trim().min(1).max(500).nullable().default(null),
548
+ details: z2.record(z2.string(), z2.unknown()).default({})
549
+ }).strict();
550
+ var OpenPondTrainingApiError = class extends Error {
551
+ status;
552
+ code;
553
+ retryable;
554
+ requestId;
555
+ details;
556
+ constructor(status, error) {
557
+ super(error.message);
558
+ this.name = "OpenPondTrainingApiError";
559
+ this.status = status;
560
+ this.code = error.code;
561
+ this.retryable = error.retryable;
562
+ this.requestId = error.requestId;
563
+ this.details = error.details;
564
+ }
565
+ };
333
566
  var TrainingCapabilitiesSchema = z2.object({
334
567
  schemaVersion: z2.literal("openpond.trainingCapabilities.v2"),
335
568
  capabilityHash: HashSchema2,
@@ -366,14 +599,18 @@ function createTrainingClient(input) {
366
599
  const response = await fetchImpl(`${baseUrl}${pathname}`, {
367
600
  ...init,
368
601
  headers: {
369
- accept: "application/json",
370
- ...init?.body ? { "content-type": "application/json" } : {},
602
+ accept: OPENPOND_TRAINING_MEDIA_TYPE,
603
+ ...init?.body ? { "content-type": OPENPOND_TRAINING_MEDIA_TYPE } : {},
371
604
  ...headersRecord(configuredHeaders),
372
605
  ...headersRecord(init?.headers)
373
606
  }
374
607
  });
375
- const body = await response.json();
376
- if (!response.ok) throw new Error(errorMessage(body, response.status));
608
+ const body = parseBoundedJson(
609
+ await response.text(),
610
+ TRAINING_API_RESPONSE_MAX_BYTES,
611
+ "Training API response"
612
+ );
613
+ if (!response.ok) throw trainingApiError(body, response.status);
377
614
  return body;
378
615
  }
379
616
  return {
@@ -382,8 +619,20 @@ function createTrainingClient(input) {
382
619
  unwrapObject(await request("/v1/training/capabilities"), "capabilities")
383
620
  );
384
621
  },
622
+ async stageArtifact(upload) {
623
+ const parsed = await parseAndVerifyTrainingInputArtifactUpload(upload);
624
+ return TrainingInputArtifactSchema.parse(
625
+ unwrapObject(
626
+ await request("/v1/training/artifacts", {
627
+ method: "POST",
628
+ body: JSON.stringify(parsed)
629
+ }),
630
+ "artifact"
631
+ )
632
+ );
633
+ },
385
634
  async createJob(submission) {
386
- const parsed = TrainingJobSubmissionSchema.parse(submission);
635
+ const parsed = await parseAndVerifyTrainingJobSubmission(submission);
387
636
  return TrainingJobSchema.parse(
388
637
  unwrapObject(
389
638
  await request("/v1/training/jobs", {
@@ -394,9 +643,24 @@ function createTrainingClient(input) {
394
643
  )
395
644
  );
396
645
  },
397
- async listJobs(modelProjectId) {
398
- const query = modelProjectId ? `?modelProjectId=${encodeURIComponent(IdSchema2.parse(modelProjectId))}` : "";
399
- return z2.array(TrainingJobSchema).parse(unwrapObject(await request(`/v1/training/jobs${query}`), "jobs"));
646
+ async listJobs(options = {}) {
647
+ const parameters = new URLSearchParams();
648
+ if (options.modelProjectId) {
649
+ parameters.set("modelProjectId", IdSchema2.parse(options.modelProjectId));
650
+ }
651
+ if (options.cursor) {
652
+ parameters.set("cursor", z2.string().trim().min(1).max(2e3).parse(options.cursor));
653
+ }
654
+ if (options.limit !== void 0) {
655
+ parameters.set(
656
+ "limit",
657
+ String(z2.number().int().min(1).max(1e3).parse(options.limit))
658
+ );
659
+ }
660
+ const query = parameters.size > 0 ? `?${parameters.toString()}` : "";
661
+ return TrainingJobPageSchema.parse(
662
+ await request(`/v1/training/jobs${query}`)
663
+ );
400
664
  },
401
665
  async getJob(jobId) {
402
666
  return TrainingJobSchema.parse(
@@ -406,12 +670,25 @@ function createTrainingClient(input) {
406
670
  )
407
671
  );
408
672
  },
409
- async cancelJob(jobId) {
673
+ async cancelJob(jobId, expectedVersion) {
674
+ const control = TrainingJobControlRequestSchema.parse({ expectedVersion });
410
675
  return TrainingJobSchema.parse(
411
676
  unwrapObject(
412
677
  await request(
413
678
  `/v1/training/jobs/${encodeURIComponent(IdSchema2.parse(jobId))}/cancel`,
414
- { method: "POST" }
679
+ { method: "POST", body: JSON.stringify(control) }
680
+ ),
681
+ "job"
682
+ )
683
+ );
684
+ },
685
+ async stopAfterGroup(jobId, expectedVersion) {
686
+ const control = TrainingJobControlRequestSchema.parse({ expectedVersion });
687
+ return TrainingJobSchema.parse(
688
+ unwrapObject(
689
+ await request(
690
+ `/v1/training/jobs/${encodeURIComponent(IdSchema2.parse(jobId))}/stop-after-group`,
691
+ { method: "POST", body: JSON.stringify(control) }
415
692
  ),
416
693
  "job"
417
694
  )
@@ -428,12 +705,19 @@ function createTrainingClient(input) {
428
705
  );
429
706
  },
430
707
  async outputs(jobId) {
431
- return z2.array(TrainingJobOutputSchema).parse(
708
+ return TrainingJobOutputsSchema.parse(
709
+ await request(
710
+ `/v1/training/jobs/${encodeURIComponent(IdSchema2.parse(jobId))}/outputs`
711
+ )
712
+ );
713
+ },
714
+ async logs(jobId) {
715
+ return z2.array(TrainingJobLogSchema).max(1e5).parse(
432
716
  unwrapObject(
433
717
  await request(
434
- `/v1/training/jobs/${encodeURIComponent(IdSchema2.parse(jobId))}/outputs`
718
+ `/v1/training/jobs/${encodeURIComponent(IdSchema2.parse(jobId))}/logs`
435
719
  ),
436
- "outputs"
720
+ "logs"
437
721
  )
438
722
  );
439
723
  }
@@ -443,28 +727,56 @@ function unwrapObject(value, key) {
443
727
  if (!value || typeof value !== "object" || Array.isArray(value)) return value;
444
728
  return key in value ? value[key] : value;
445
729
  }
446
- function errorMessage(value, status) {
447
- if (value && typeof value === "object" && "error" in value) {
448
- return String(value.error);
730
+ function trainingApiError(value, status) {
731
+ const parsed = TrainingApiErrorSchema.safeParse(value);
732
+ if (parsed.success) {
733
+ return new OpenPondTrainingApiError(status, parsed.data);
449
734
  }
450
- return `Training request failed with HTTP ${status}.`;
735
+ return new OpenPondProtocolError(
736
+ "invalid_error_response",
737
+ `Training request failed with HTTP ${status} and an invalid error envelope.`
738
+ );
451
739
  }
452
740
  export {
741
+ OPENPOND_TRAINING_MEDIA_TYPE,
742
+ OPENPOND_TRAINING_PROTOCOL_MAJOR,
743
+ OpenPondProtocolError,
744
+ OpenPondTrainingApiError,
453
745
  PolicyOptimizationRequestSchema,
454
746
  RewardModelTrainingRequestSchema,
747
+ TRAINING_API_RESPONSE_MAX_BYTES,
748
+ TRAINING_INPUT_ARTIFACT_MAX_BYTES,
749
+ TRAINING_JOB_SUBMISSION_MAX_BYTES,
750
+ TrainingApiErrorSchema,
455
751
  TrainingCapabilitiesSchema,
456
752
  TrainingCapabilityRequirementSchema,
457
753
  TrainingExecutionReceiptSchema,
754
+ TrainingInputArtifactSchema,
755
+ TrainingInputArtifactUploadSchema,
458
756
  TrainingJobApprovalSchema,
757
+ TrainingJobControlRequestSchema,
459
758
  TrainingJobEventSchema,
460
759
  TrainingJobKindSchema,
461
760
  TrainingJobLogSchema,
462
761
  TrainingJobOutputSchema,
762
+ TrainingJobOutputsSchema,
763
+ TrainingJobPageSchema,
463
764
  TrainingJobRequestSchema,
464
765
  TrainingJobSchema,
465
766
  TrainingJobSourceSchema,
466
767
  TrainingJobStateSchema,
467
768
  TrainingJobSubmissionSchema,
468
- createTrainingClient
769
+ assertCanonicalPayloadSize,
770
+ canonicalJson,
771
+ canonicalJsonByteLength,
772
+ canonicalSha256,
773
+ createTrainingClient,
774
+ parseAndVerifyTrainingExecutionReceipt,
775
+ parseAndVerifyTrainingInputArtifactUpload,
776
+ parseAndVerifyTrainingJobSubmission,
777
+ parseBoundedJson,
778
+ trainingExecutionReceiptHash,
779
+ trainingInputArtifactUploadHash,
780
+ trainingJobSubmissionHash
469
781
  };
470
782
  //# sourceMappingURL=training.js.map