@superbuilders/primer-tives 1.2.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +744 -533
  2. package/dist/client/choice-state.d.ts +2 -1
  3. package/dist/client/choice-state.d.ts.map +1 -1
  4. package/dist/client/create.d.ts +23 -7
  5. package/dist/client/create.d.ts.map +1 -1
  6. package/dist/client/extended-text-state.d.ts +2 -1
  7. package/dist/client/extended-text-state.d.ts.map +1 -1
  8. package/dist/client/feedback-state.d.ts +2 -2
  9. package/dist/client/feedback-state.d.ts.map +1 -1
  10. package/dist/client/index.js +226 -109
  11. package/dist/client/index.js.map +21 -17
  12. package/dist/client/match-state.d.ts +2 -1
  13. package/dist/client/match-state.d.ts.map +1 -1
  14. package/dist/client/observation-state.d.ts +2 -1
  15. package/dist/client/observation-state.d.ts.map +1 -1
  16. package/dist/client/order-state.d.ts +2 -1
  17. package/dist/client/order-state.d.ts.map +1 -1
  18. package/dist/client/pci-state.d.ts +2 -1
  19. package/dist/client/pci-state.d.ts.map +1 -1
  20. package/dist/client/session-context.d.ts +1 -1
  21. package/dist/client/session-context.d.ts.map +1 -1
  22. package/dist/client/session.d.ts +2 -2
  23. package/dist/client/session.d.ts.map +1 -1
  24. package/dist/client/text-entry-state.d.ts +2 -1
  25. package/dist/client/text-entry-state.d.ts.map +1 -1
  26. package/dist/client/transport.d.ts +13 -11
  27. package/dist/client/transport.d.ts.map +1 -1
  28. package/dist/client/types.d.ts +10 -1
  29. package/dist/client/types.d.ts.map +1 -1
  30. package/dist/contracts/index.d.ts +4 -3
  31. package/dist/contracts/index.d.ts.map +1 -1
  32. package/dist/contracts/index.js +42 -36
  33. package/dist/contracts/index.js.map +6 -5
  34. package/dist/contracts/pci-schemas.d.ts +24 -20
  35. package/dist/contracts/pci-schemas.d.ts.map +1 -1
  36. package/dist/contracts/pci.d.ts +26 -27
  37. package/dist/contracts/pci.d.ts.map +1 -1
  38. package/dist/contracts/types.d.ts +5 -9
  39. package/dist/contracts/types.d.ts.map +1 -1
  40. package/dist/contracts/validation.d.ts +34 -23
  41. package/dist/contracts/validation.d.ts.map +1 -1
  42. package/dist/errors.d.ts +3 -6
  43. package/dist/errors.d.ts.map +1 -1
  44. package/dist/errors.js +5 -11
  45. package/dist/errors.js.map +3 -3
  46. package/dist/server/create-server.d.ts +3 -33
  47. package/dist/server/create-server.d.ts.map +1 -1
  48. package/dist/server/exchange.d.ts +7 -14
  49. package/dist/server/exchange.d.ts.map +1 -1
  50. package/dist/server/index.d.ts +1 -3
  51. package/dist/server/index.d.ts.map +1 -1
  52. package/dist/server/index.js +32 -407
  53. package/dist/server/index.js.map +6 -9
  54. package/dist/subject-pcis.d.ts +13 -0
  55. package/dist/subject-pcis.d.ts.map +1 -0
  56. package/dist/version.d.ts +4 -0
  57. package/dist/version.d.ts.map +1 -0
  58. package/package.json +5 -1
  59. package/dist/server/hints.d.ts +0 -25
  60. package/dist/server/hints.d.ts.map +0 -1
  61. package/dist/server/students.d.ts +0 -12
  62. package/dist/server/students.d.ts.map +0 -1
@@ -3,6 +3,7 @@ import * as errors from "@superbuilders/errors";
3
3
  var ErrNetwork = errors.new("network");
4
4
  var ErrJsonParse = errors.new("json parse");
5
5
  var ErrUnsupportedPci = errors.new("unsupported pci");
6
+ var ErrMissingRequiredPci = errors.new("missing required pci");
6
7
  var ErrInvalidAccessToken = errors.new("invalid access token");
7
8
  var ErrMalformedAccessToken = errors.new("malformed access token");
8
9
  var ErrTokenExpired = errors.new("access token expired");
@@ -12,16 +13,12 @@ var ErrTimeout = errors.new("timeout");
12
13
  var ErrForbidden = errors.new("forbidden");
13
14
  var ErrNotFound = errors.new("not found");
14
15
  var ErrConflict = errors.new("conflict");
15
- var ErrExternalAuthorityRequired = errors.new("external authority required");
16
16
  var ErrRateLimited = errors.new("rate limited");
17
17
  var ErrServiceUnavailable = errors.new("service unavailable");
18
18
  var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
19
19
  var ErrInvalidSubmission = errors.new("invalid submission");
20
20
  var ErrInvalidSecretKey = errors.new("invalid secret key");
21
- var ErrStudentNotFound = errors.new("student not found");
22
- var ErrUnsupportedGrade = errors.new("unsupported grade");
23
- var ErrTimebackUnavailable = errors.new("timeback unavailable");
24
- var ErrNeedsHints = errors.new("student needs hints set before /advance");
21
+ var ErrSdkUpgradeRequired = errors.new("sdk upgrade required");
25
22
  // src/contracts/content.ts
26
23
  function inlinesToPlainText(nodes) {
27
24
  const parts = [];
@@ -46,31 +43,44 @@ function blocksToPlainText(blocks) {
46
43
  }).join(`
47
44
  `);
48
45
  }
46
+ // src/contracts/pci.ts
47
+ var PCI_IDS = ["urn:primer:pci:fraction-input"];
48
+ function isPciId(value) {
49
+ for (const id of PCI_IDS) {
50
+ if (id === value) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
49
56
  // src/contracts/validation.ts
50
57
  import { z as z2 } from "zod";
51
58
 
52
59
  // src/contracts/pci-schemas.ts
53
60
  import { z } from "zod";
54
- var DivisionRemainderPropsSchema = z.object({
55
- dividend: z.number(),
56
- divisor: z.number()
57
- });
58
- var DivisionRemainderSubmissionSchema = z.object({
59
- quotient: z.string(),
60
- remainder: z.string()
61
- });
62
- var FractionOperandSchema = z.object({
63
- numerator: z.number(),
64
- denominator: z.number()
65
- });
66
- var FractionAdditionPropsSchema = z.object({
67
- left: FractionOperandSchema,
68
- right: FractionOperandSchema
69
- });
70
- var FractionAdditionSubmissionSchema = z.object({
71
- numerator: z.string(),
72
- denominator: z.string()
61
+ var FractionInputPropsSchema = z.object({
62
+ form: z.enum(["whole", "proper", "improper", "mixed"]),
63
+ requireSimplified: z.boolean()
73
64
  });
65
+ var FractionInputSubmissionSchema = z.discriminatedUnion("form", [
66
+ z.object({ form: z.literal("whole"), whole: z.string() }),
67
+ z.object({
68
+ form: z.literal("proper"),
69
+ numerator: z.string(),
70
+ denominator: z.string()
71
+ }),
72
+ z.object({
73
+ form: z.literal("improper"),
74
+ numerator: z.string(),
75
+ denominator: z.string()
76
+ }),
77
+ z.object({
78
+ form: z.literal("mixed"),
79
+ whole: z.string(),
80
+ numerator: z.string(),
81
+ denominator: z.string()
82
+ })
83
+ ]);
74
84
 
75
85
  // src/contracts/validation.ts
76
86
  var MatchPairSchema = z2.object({
@@ -97,15 +107,10 @@ var MatchSubmissionSchema = z2.object({
97
107
  type: z2.literal("match"),
98
108
  pairs: z2.array(MatchPairSchema)
99
109
  });
100
- var DivisionRemainderPciSubmissionSchema = z2.object({
101
- type: z2.literal("portable-custom"),
102
- pciId: z2.literal("urn:primer:pci:division-remainder"),
103
- value: DivisionRemainderSubmissionSchema
104
- });
105
- var FractionAdditionPciSubmissionSchema = z2.object({
110
+ var FractionInputPciSubmissionSchema = z2.object({
106
111
  type: z2.literal("portable-custom"),
107
- pciId: z2.literal("urn:primer:pci:fraction-addition"),
108
- value: FractionAdditionSubmissionSchema
112
+ pciId: z2.literal("urn:primer:pci:fraction-input"),
113
+ value: FractionInputSubmissionSchema
109
114
  });
110
115
  var RendererSubmissionSchema = z2.union([
111
116
  ChoiceSubmissionSchema,
@@ -113,8 +118,7 @@ var RendererSubmissionSchema = z2.union([
113
118
  ExtendedTextSubmissionSchema,
114
119
  OrderSubmissionSchema,
115
120
  MatchSubmissionSchema,
116
- DivisionRemainderPciSubmissionSchema,
117
- FractionAdditionPciSubmissionSchema
121
+ FractionInputPciSubmissionSchema
118
122
  ]);
119
123
  function duplicates(values, keyOf) {
120
124
  const seen = new Set;
@@ -166,10 +170,8 @@ function validateUsageBounds(choices, counts, side) {
166
170
  }
167
171
  function pciSubmissionSchema(pciId) {
168
172
  switch (pciId) {
169
- case "urn:primer:pci:division-remainder":
170
- return DivisionRemainderSubmissionSchema;
171
- case "urn:primer:pci:fraction-addition":
172
- return FractionAdditionSubmissionSchema;
173
+ case "urn:primer:pci:fraction-input":
174
+ return FractionInputSubmissionSchema;
173
175
  }
174
176
  const exhaustiveCheck = pciId;
175
177
  return exhaustiveCheck;
@@ -331,23 +333,29 @@ function validateSubmissionForInteraction(interaction, submission) {
331
333
  function submissionValidationMessage(result) {
332
334
  return result.issues.join("; ");
333
335
  }
336
+ // src/subject.ts
337
+ var SUBJECTS = ["math", "vocabulary", "science"];
334
338
  // src/client/create.ts
335
339
  import * as errors10 from "@superbuilders/errors";
336
340
 
337
341
  // src/client/transport.ts
338
342
  import * as errors2 from "@superbuilders/errors";
343
+
344
+ // src/version.ts
345
+ var SDK_VERSION = "2.2.0";
346
+ var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
347
+
348
+ // src/client/transport.ts
339
349
  var ADVANCE_PATH = "/api/v0/advance";
340
- function isAdvanceErrorBody(value) {
341
- if (typeof value !== "object" || value === null) {
342
- return false;
343
- }
344
- if ("error" in value && value.error !== undefined && typeof value.error !== "string") {
345
- return false;
350
+ function readStringField(value, key) {
351
+ if (!(key in value)) {
352
+ return;
346
353
  }
347
- if ("detail" in value && value.detail !== undefined && typeof value.detail !== "string") {
348
- return false;
354
+ const v = Reflect.get(value, key);
355
+ if (typeof v !== "string") {
356
+ return;
349
357
  }
350
- return true;
358
+ return v;
351
359
  }
352
360
  function parseAdvanceErrorBody(body) {
353
361
  if (body.length === 0) {
@@ -359,13 +367,39 @@ function parseAdvanceErrorBody(body) {
359
367
  if (parsed.error) {
360
368
  return null;
361
369
  }
362
- if (!isAdvanceErrorBody(parsed.data)) {
370
+ const raw = parsed.data;
371
+ if (typeof raw !== "object" || raw === null) {
363
372
  return null;
364
373
  }
365
- return parsed.data;
374
+ const result = {};
375
+ const error = readStringField(raw, "error");
376
+ if (error !== undefined) {
377
+ result.error = error;
378
+ }
379
+ const detail = readStringField(raw, "detail");
380
+ if (detail !== undefined) {
381
+ result.detail = detail;
382
+ }
383
+ const minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
384
+ if (minimumSdkVersion !== undefined) {
385
+ result.minimumSdkVersion = minimumSdkVersion;
386
+ }
387
+ const receivedSdkVersion = readStringField(raw, "receivedSdkVersion");
388
+ if (receivedSdkVersion !== undefined) {
389
+ result.receivedSdkVersion = receivedSdkVersion;
390
+ }
391
+ const upgradeUrl = readStringField(raw, "upgradeUrl");
392
+ if (upgradeUrl !== undefined) {
393
+ result.upgradeUrl = upgradeUrl;
394
+ }
395
+ return result;
366
396
  }
367
397
  function httpSentinel(status, body) {
368
398
  if (status === 400) {
399
+ const parsed = parseAdvanceErrorBody(body);
400
+ if (parsed?.error === "sdk_upgrade_required") {
401
+ return ErrSdkUpgradeRequired;
402
+ }
369
403
  return ErrBadRequest;
370
404
  }
371
405
  if (status === 401) {
@@ -384,9 +418,6 @@ function httpSentinel(status, body) {
384
418
  if (status === 409) {
385
419
  return ErrConflict;
386
420
  }
387
- if (status === 412) {
388
- return ErrNeedsHints;
389
- }
390
421
  if (status === 429) {
391
422
  return ErrRateLimited;
392
423
  }
@@ -395,6 +426,29 @@ function httpSentinel(status, body) {
395
426
  }
396
427
  return ErrServerError;
397
428
  }
429
+ function buildSdkUpgradeRequiredError(sentinel, text, logger) {
430
+ const parsed = parseAdvanceErrorBody(text);
431
+ if (parsed === null) {
432
+ logger.error("sdk upgrade required", {
433
+ receivedSdkVersion: null,
434
+ minimumSdkVersion: "<unknown>",
435
+ upgradeUrl: NPM_PACKAGE_URL
436
+ });
437
+ const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
438
+ return errors2.wrap(sentinel, message2);
439
+ }
440
+ const minimumSdkVersion = parsed.minimumSdkVersion === undefined ? "<unknown>" : parsed.minimumSdkVersion;
441
+ const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
442
+ const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
443
+ const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
444
+ logger.error("sdk upgrade required", {
445
+ receivedSdkVersion,
446
+ minimumSdkVersion,
447
+ upgradeUrl
448
+ });
449
+ const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
450
+ return errors2.wrap(sentinel, message);
451
+ }
398
452
  function isAbortError(err) {
399
453
  if (err instanceof DOMException && err.name === "AbortError") {
400
454
  return true;
@@ -406,7 +460,7 @@ function isAbortError(err) {
406
460
  }
407
461
  function createTransport(tc) {
408
462
  const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
409
- const log = tc.log;
463
+ const logger = tc.logger;
410
464
  function transportSignal() {
411
465
  if (tc.abort) {
412
466
  return tc.abort.signal;
@@ -414,7 +468,7 @@ function createTransport(tc) {
414
468
  return;
415
469
  }
416
470
  async function transport(body) {
417
- log?.debug("transport request", {
471
+ logger.debug("transport request", {
418
472
  intentKind: body.intent.kind,
419
473
  subject: body.subject
420
474
  });
@@ -423,7 +477,8 @@ function createTransport(tc) {
423
477
  method: "POST",
424
478
  headers: {
425
479
  "Content-Type": "application/json",
426
- Authorization: `Bearer ${tc.accessToken}`
480
+ Authorization: `Bearer ${tc.accessToken}`,
481
+ "X-Primer-SDK-Version": SDK_VERSION
427
482
  },
428
483
  body: JSON.stringify(body),
429
484
  signal: transportSignal()
@@ -434,12 +489,12 @@ function createTransport(tc) {
434
489
  });
435
490
  if (!fetchResult.ok) {
436
491
  if (isAbortError(fetchResult.error)) {
437
- log?.error("transport timeout", {
492
+ logger.error("transport timeout", {
438
493
  intentKind: body.intent.kind
439
494
  });
440
495
  return { ok: false, error: errors2.wrap(ErrTimeout, fetchResult.error.message) };
441
496
  }
442
- log?.error("transport network error", {
497
+ logger.error("transport network error", {
443
498
  error: fetchResult.error
444
499
  });
445
500
  return { ok: false, error: errors2.wrap(ErrNetwork, fetchResult.error.message) };
@@ -449,8 +504,11 @@ function createTransport(tc) {
449
504
  const text = await res.text().catch(function fallback() {
450
505
  return "";
451
506
  });
452
- const sentinel = res.status === 422 ? ErrUnsupportedPci : httpSentinel(res.status, text);
453
- log?.error("transport http error", {
507
+ const sentinel = httpSentinel(res.status, text);
508
+ if (errors2.is(sentinel, ErrSdkUpgradeRequired)) {
509
+ return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
510
+ }
511
+ logger.error("transport http error", {
454
512
  status: res.status
455
513
  });
456
514
  return { ok: false, error: errors2.wrap(sentinel, text) };
@@ -461,12 +519,12 @@ function createTransport(tc) {
461
519
  return { ok: false, error: err };
462
520
  });
463
521
  if (!jsonResult.ok) {
464
- log?.error("transport json parse failed", {
522
+ logger.error("transport json parse failed", {
465
523
  error: jsonResult.error
466
524
  });
467
525
  return { ok: false, error: errors2.wrap(ErrJsonParse, jsonResult.error.message) };
468
526
  }
469
- log?.debug("transport success", {
527
+ logger.debug("transport success", {
470
528
  intentKind: body.intent.kind
471
529
  });
472
530
  return { ok: true, data: jsonResult.data };
@@ -484,7 +542,7 @@ function poisonToJSON() {
484
542
 
485
543
  // src/client/choice-state.ts
486
544
  import * as errors3 from "@superbuilders/errors";
487
- function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices) {
545
+ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minChoices) {
488
546
  let submitPending;
489
547
  let submitKey;
490
548
  let timeoutPending;
@@ -502,7 +560,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
502
560
  }
503
561
  const validation = validateSubmissionForInteraction(interaction, submission);
504
562
  if (!validation.ok) {
505
- ctx.log?.error("choice submit invalid", { selectedKeys, issues: validation.issues });
563
+ ctx.logger.error("choice submit invalid", { selectedKeys, issues: validation.issues });
506
564
  return Promise.resolve(ctx.errored(errors3.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
507
565
  }
508
566
  submitKey = key;
@@ -528,6 +586,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
528
586
  return {
529
587
  phase: "interaction",
530
588
  kind: "choice",
589
+ body,
531
590
  stimulus,
532
591
  interaction,
533
592
  options,
@@ -541,7 +600,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
541
600
 
542
601
  // src/client/extended-text-state.ts
543
602
  import * as errors4 from "@superbuilders/errors";
544
- function extendedTextState(ctx, stimulus, interaction) {
603
+ function extendedTextState(ctx, body, stimulus, interaction) {
545
604
  if (interaction.cardinality === "single") {
546
605
  let submitText = function(value) {
547
606
  const submission = { type: "extended-text", values: [value] };
@@ -557,7 +616,7 @@ function extendedTextState(ctx, stimulus, interaction) {
557
616
  }
558
617
  const validation = validateSubmissionForInteraction(interaction, submission);
559
618
  if (!validation.ok) {
560
- ctx.log?.error("extended-text submit invalid", { value, issues: validation.issues });
619
+ ctx.logger.error("extended-text submit invalid", { value, issues: validation.issues });
561
620
  return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
562
621
  }
563
622
  submitKey2 = key;
@@ -586,6 +645,7 @@ function extendedTextState(ctx, stimulus, interaction) {
586
645
  phase: "interaction",
587
646
  kind: "extended-text",
588
647
  cardinality: "single",
648
+ body,
589
649
  stimulus,
590
650
  interaction,
591
651
  submitText,
@@ -611,7 +671,7 @@ function extendedTextState(ctx, stimulus, interaction) {
611
671
  }
612
672
  const validation = validateSubmissionForInteraction(multi, submission);
613
673
  if (!validation.ok) {
614
- ctx.log?.error("extended-text submit invalid", { values, issues: validation.issues });
674
+ ctx.logger.error("extended-text submit invalid", { values, issues: validation.issues });
615
675
  return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
616
676
  }
617
677
  submitKey = key;
@@ -638,6 +698,7 @@ function extendedTextState(ctx, stimulus, interaction) {
638
698
  phase: "interaction",
639
699
  kind: "extended-text",
640
700
  cardinality: "multiple",
701
+ body,
641
702
  stimulus,
642
703
  interaction: multi,
643
704
  maxStrings: multi.maxStrings,
@@ -649,10 +710,11 @@ function extendedTextState(ctx, stimulus, interaction) {
649
710
  }
650
711
 
651
712
  // src/client/feedback-state.ts
652
- function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
713
+ function feedbackState(ctx, body, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
653
714
  let pending;
654
715
  return {
655
716
  phase: "feedback",
717
+ body,
656
718
  stimulus,
657
719
  interaction,
658
720
  submission,
@@ -672,7 +734,7 @@ function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedba
672
734
 
673
735
  // src/client/match-state.ts
674
736
  import * as errors5 from "@superbuilders/errors";
675
- function matchState(ctx, stimulus, interaction) {
737
+ function matchState(ctx, body, stimulus, interaction) {
676
738
  let submitPending;
677
739
  let submitKey;
678
740
  let timeoutPending;
@@ -690,7 +752,7 @@ function matchState(ctx, stimulus, interaction) {
690
752
  }
691
753
  const validation = validateSubmissionForInteraction(interaction, submission);
692
754
  if (!validation.ok) {
693
- ctx.log?.error("match submit invalid", { pairs, issues: validation.issues });
755
+ ctx.logger.error("match submit invalid", { pairs, issues: validation.issues });
694
756
  return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
695
757
  }
696
758
  submitKey = key;
@@ -716,6 +778,7 @@ function matchState(ctx, stimulus, interaction) {
716
778
  return {
717
779
  phase: "interaction",
718
780
  kind: "match",
781
+ body,
719
782
  stimulus,
720
783
  interaction,
721
784
  sourceChoices: interaction.sourceChoices,
@@ -729,10 +792,11 @@ function matchState(ctx, stimulus, interaction) {
729
792
  }
730
793
 
731
794
  // src/client/observation-state.ts
732
- function observationState(ctx, stimulus) {
795
+ function observationState(ctx, body, stimulus) {
733
796
  let pending;
734
797
  return {
735
798
  phase: "observation",
799
+ body,
736
800
  stimulus,
737
801
  advance: function advance() {
738
802
  if (pending) {
@@ -747,7 +811,7 @@ function observationState(ctx, stimulus) {
747
811
 
748
812
  // src/client/order-state.ts
749
813
  import * as errors6 from "@superbuilders/errors";
750
- function orderState(ctx, stimulus, interaction) {
814
+ function orderState(ctx, body, stimulus, interaction) {
751
815
  let submitPending;
752
816
  let submitKey;
753
817
  let timeoutPending;
@@ -765,7 +829,7 @@ function orderState(ctx, stimulus, interaction) {
765
829
  }
766
830
  const validation = validateSubmissionForInteraction(interaction, submission);
767
831
  if (!validation.ok) {
768
- ctx.log?.error("order submit invalid", { orderedKeys, issues: validation.issues });
832
+ ctx.logger.error("order submit invalid", { orderedKeys, issues: validation.issues });
769
833
  return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
770
834
  }
771
835
  submitKey = key;
@@ -791,6 +855,7 @@ function orderState(ctx, stimulus, interaction) {
791
855
  return {
792
856
  phase: "interaction",
793
857
  kind: "order",
858
+ body,
794
859
  stimulus,
795
860
  interaction,
796
861
  choices: interaction.choices,
@@ -804,7 +869,7 @@ function orderState(ctx, stimulus, interaction) {
804
869
 
805
870
  // src/client/pci-state.ts
806
871
  import * as errors7 from "@superbuilders/errors";
807
- function pciInteractionState(ctx, stimulus, interaction) {
872
+ function pciInteractionState(ctx, body, stimulus, interaction) {
808
873
  let submitPending;
809
874
  let submitKey;
810
875
  let timeoutPending;
@@ -821,7 +886,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
821
886
  }
822
887
  return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
823
888
  }
824
- ctx.log?.debug("pci submit", { pciId });
889
+ ctx.logger.debug("pci submit", { pciId });
825
890
  submitKey = key;
826
891
  submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
827
892
  submitPending = undefined;
@@ -837,7 +902,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
837
902
  if (timeoutPending) {
838
903
  return timeoutPending;
839
904
  }
840
- ctx.log?.debug("pci timeout", { pciId });
905
+ ctx.logger.debug("pci timeout", { pciId });
841
906
  timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
842
907
  timeoutPending = undefined;
843
908
  });
@@ -846,6 +911,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
846
911
  return {
847
912
  phase: "interaction",
848
913
  kind: "portable-custom",
914
+ body,
849
915
  stimulus,
850
916
  interaction,
851
917
  pciId,
@@ -858,7 +924,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
858
924
 
859
925
  // src/client/text-entry-state.ts
860
926
  import * as errors8 from "@superbuilders/errors";
861
- function textEntryState(ctx, stimulus, interaction) {
927
+ function textEntryState(ctx, body, stimulus, interaction) {
862
928
  let submitPending;
863
929
  let submitKey;
864
930
  let timeoutPending;
@@ -876,7 +942,7 @@ function textEntryState(ctx, stimulus, interaction) {
876
942
  }
877
943
  const validation = validateSubmissionForInteraction(interaction, submission);
878
944
  if (!validation.ok) {
879
- ctx.log?.error("text-entry submit invalid", { value, issues: validation.issues });
945
+ ctx.logger.error("text-entry submit invalid", { value, issues: validation.issues });
880
946
  return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
881
947
  }
882
948
  submitKey = key;
@@ -902,6 +968,7 @@ function textEntryState(ctx, stimulus, interaction) {
902
968
  return {
903
969
  phase: "interaction",
904
970
  kind: "text-entry",
971
+ body,
905
972
  stimulus,
906
973
  interaction,
907
974
  submitText,
@@ -917,7 +984,7 @@ var FATAL_SENTINELS = [
917
984
  ErrTokenExpired,
918
985
  ErrForbidden,
919
986
  ErrNotFound,
920
- ErrNeedsHints,
987
+ ErrSdkUpgradeRequired,
921
988
  ErrUnsupportedPci
922
989
  ];
923
990
  function isFatalError(err) {
@@ -932,13 +999,24 @@ function isRetriableError(err) {
932
999
  return !errors9.is(err, ErrInvalidSubmission);
933
1000
  }
934
1001
  function makeSession(sc) {
935
- const log = sc.log;
1002
+ const logger = sc.logger;
936
1003
  function resolve(result) {
937
1004
  switch (result.outcome) {
938
1005
  case "advanced":
939
- return fromAdvanced(result.stimulus, result.interaction);
940
- case "submitted":
941
- return feedbackState(ctx, result.stimulus, result.interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
1006
+ return fromAdvanced(result.frame.body, result.frame.stimulus, result.frame.interaction);
1007
+ case "submitted": {
1008
+ const interaction = result.frame.interaction;
1009
+ if (interaction === null) {
1010
+ logger.error("submitted result without interaction");
1011
+ return {
1012
+ phase: "fatal",
1013
+ error: errors9.wrap(ErrBadRequest, "submitted result missing interaction"),
1014
+ retriable: false,
1015
+ toJSON: poisonToJSON
1016
+ };
1017
+ }
1018
+ return feedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
1019
+ }
942
1020
  case "completed":
943
1021
  return { phase: "completed", toJSON: poisonToJSON };
944
1022
  }
@@ -952,13 +1030,13 @@ function makeSession(sc) {
952
1030
  retriable,
953
1031
  retry: function retry() {
954
1032
  if (!retriable) {
955
- log?.debug("retry ignored for non-retriable error", { failedPhase });
1033
+ logger.debug("retry ignored for non-retriable error", { failedPhase });
956
1034
  return Promise.resolve(state);
957
1035
  }
958
1036
  if (pending) {
959
1037
  return pending;
960
1038
  }
961
- log?.debug("retrying from errored state", { failedPhase });
1039
+ logger.debug("retrying from errored state", { failedPhase });
962
1040
  pending = execute(intent, failedPhase);
963
1041
  return pending;
964
1042
  },
@@ -968,14 +1046,13 @@ function makeSession(sc) {
968
1046
  }
969
1047
  async function execute(intent, phase) {
970
1048
  const body = {
971
- supportedPcis: sc.supportedPcis,
972
- intent,
973
- subject: sc.subject
1049
+ subject: sc.subject,
1050
+ intent
974
1051
  };
975
1052
  const result = await sc.transport(body);
976
1053
  if (!result.ok) {
977
1054
  if (isFatalError(result.error)) {
978
- log?.error("fatal transport error", { error: result.error, phase });
1055
+ logger.error("fatal transport error", { error: result.error, phase });
979
1056
  return { phase: "fatal", error: result.error, retriable: false, toJSON: poisonToJSON };
980
1057
  }
981
1058
  return errored(result.error, phase, intent);
@@ -990,13 +1067,13 @@ function makeSession(sc) {
990
1067
  }
991
1068
  return false;
992
1069
  }
993
- function fromAdvanced(stimulus, interaction) {
1070
+ function fromAdvanced(body, stimulus, interaction) {
994
1071
  if (interaction === null) {
995
- return observationState(ctx, stimulus);
1072
+ return observationState(ctx, body, stimulus);
996
1073
  }
997
1074
  if (interaction.type === "portable-custom") {
998
1075
  if (!isPciSupported(interaction.pciId)) {
999
- log?.error("unsupported pci in frame", { pciId: interaction.pciId });
1076
+ logger.error("unsupported pci in frame", { pciId: interaction.pciId });
1000
1077
  return {
1001
1078
  phase: "fatal",
1002
1079
  error: errors9.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
@@ -1005,28 +1082,59 @@ function makeSession(sc) {
1005
1082
  };
1006
1083
  }
1007
1084
  }
1008
- return pendingInteractionState(stimulus, interaction);
1085
+ return pendingInteractionState(body, stimulus, interaction);
1009
1086
  }
1010
- function pendingInteractionState(stimulus, interaction) {
1087
+ function pendingInteractionState(body, stimulus, interaction) {
1011
1088
  switch (interaction.type) {
1012
1089
  case "choice":
1013
- return choiceState(ctx, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
1090
+ return choiceState(ctx, body, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
1014
1091
  case "text-entry":
1015
- return textEntryState(ctx, stimulus, interaction);
1092
+ return textEntryState(ctx, body, stimulus, interaction);
1016
1093
  case "extended-text":
1017
- return extendedTextState(ctx, stimulus, interaction);
1094
+ return extendedTextState(ctx, body, stimulus, interaction);
1018
1095
  case "order":
1019
- return orderState(ctx, stimulus, interaction);
1096
+ return orderState(ctx, body, stimulus, interaction);
1020
1097
  case "match":
1021
- return matchState(ctx, stimulus, interaction);
1098
+ return matchState(ctx, body, stimulus, interaction);
1022
1099
  case "portable-custom":
1023
- return pciInteractionState(ctx, stimulus, interaction);
1100
+ return pciInteractionState(ctx, body, stimulus, interaction);
1024
1101
  }
1025
1102
  }
1026
- const ctx = { log, execute, errored };
1103
+ const ctx = { logger, execute, errored };
1027
1104
  return { execute };
1028
1105
  }
1029
1106
 
1107
+ // src/subject-pcis.ts
1108
+ var REQUIRED_PCIS_BY_SUBJECT = {
1109
+ math: ["urn:primer:pci:fraction-input"],
1110
+ vocabulary: [],
1111
+ science: []
1112
+ };
1113
+ function requiredPcisForSubject(subject) {
1114
+ if (subject === "all") {
1115
+ const all = [];
1116
+ for (const s of SUBJECTS) {
1117
+ for (const pci of REQUIRED_PCIS_BY_SUBJECT[s]) {
1118
+ if (!all.includes(pci)) {
1119
+ all.push(pci);
1120
+ }
1121
+ }
1122
+ }
1123
+ return all;
1124
+ }
1125
+ return REQUIRED_PCIS_BY_SUBJECT[subject];
1126
+ }
1127
+ function missingPcisForSubject(subject, provided) {
1128
+ const required = requiredPcisForSubject(subject);
1129
+ const missing = [];
1130
+ for (const pci of required) {
1131
+ if (provided === undefined || !provided.includes(pci)) {
1132
+ missing.push(pci);
1133
+ }
1134
+ }
1135
+ return missing;
1136
+ }
1137
+
1030
1138
  // src/client/create.ts
1031
1139
  var ACCESS_TOKEN_PREFIX = "eyJ";
1032
1140
  function isMalformedJws(token) {
@@ -1037,26 +1145,35 @@ function isMalformedJws(token) {
1037
1145
  return dotCount !== 2;
1038
1146
  }
1039
1147
  function create(config) {
1040
- const log = config.logger;
1148
+ const logger = config.logger;
1149
+ let supportedPcis = [];
1150
+ if (config.supportedPcis !== undefined) {
1151
+ supportedPcis = config.supportedPcis;
1152
+ }
1153
+ const missingPcis = missingPcisForSubject(config.subject, supportedPcis);
1154
+ if (missingPcis.length > 0) {
1155
+ logger.error("renderer missing required pcis", { subject: config.subject, missingPcis });
1156
+ throw errors10.wrap(ErrMissingRequiredPci, `subject '${config.subject}'`);
1157
+ }
1041
1158
  if (isMalformedJws(config.accessToken)) {
1042
- log?.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
1159
+ logger.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
1043
1160
  throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
1044
1161
  }
1045
1162
  const transport = createTransport({
1046
1163
  accessToken: config.accessToken,
1047
- supportedPcis: config.supportedPcis,
1164
+ subject: config.subject,
1048
1165
  origin: config.origin,
1049
1166
  fetch: config.fetch,
1050
1167
  abort: config.abort,
1051
- log
1168
+ logger
1052
1169
  });
1053
1170
  let startPromise;
1054
1171
  async function doStart() {
1055
- log?.debug("start", { subject: config.subject });
1172
+ logger.debug("start");
1056
1173
  const s = makeSession({
1057
- supportedPcis: config.supportedPcis,
1058
1174
  subject: config.subject,
1059
- log,
1175
+ supportedPcis,
1176
+ logger,
1060
1177
  transport
1061
1178
  });
1062
1179
  return s.execute({ kind: "observation" }, "observation");
@@ -1064,7 +1181,7 @@ function create(config) {
1064
1181
  return {
1065
1182
  start() {
1066
1183
  if (startPromise) {
1067
- log?.debug("start already called");
1184
+ logger.debug("start already called");
1068
1185
  return startPromise;
1069
1186
  }
1070
1187
  startPromise = doStart();
@@ -1076,4 +1193,4 @@ export {
1076
1193
  create
1077
1194
  };
1078
1195
 
1079
- //# debugId=EA813B14A6F0A81364756E2164756E21
1196
+ //# debugId=81BED1C3D30435C964756E2164756E21