@intellectif/lk-core 0.13.1 → 0.14.1

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 (57) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +1 -1
  3. package/dist/{activity-DfAmJ1sl.d.cts → activity-BVkg9VDo.d.cts} +153 -4
  4. package/dist/{activity-DfAmJ1sl.d.ts → activity-BVkg9VDo.d.ts} +153 -4
  5. package/dist/{chunk-DQKJUMUW.cjs → chunk-2NIALQEH.cjs} +24 -19
  6. package/dist/chunk-2NIALQEH.cjs.map +1 -0
  7. package/dist/{chunk-XKKKWKRH.js → chunk-6RND47QE.js} +694 -126
  8. package/dist/chunk-6RND47QE.js.map +1 -0
  9. package/dist/{chunk-FT7SAC3X.cjs → chunk-E7LPIZXI.cjs} +13 -10
  10. package/dist/chunk-E7LPIZXI.cjs.map +1 -0
  11. package/dist/chunk-J5OIRIOW.cjs +1034 -0
  12. package/dist/chunk-J5OIRIOW.cjs.map +1 -0
  13. package/dist/{chunk-PYP3HCRQ.js → chunk-KTDOZR7Y.js} +7 -4
  14. package/dist/chunk-KTDOZR7Y.js.map +1 -0
  15. package/dist/{chunk-Y36S3X3L.js → chunk-NBADQ3JU.js} +7 -2
  16. package/dist/chunk-NBADQ3JU.js.map +1 -0
  17. package/dist/chunk-RTIR6R2U.js +1034 -0
  18. package/dist/chunk-RTIR6R2U.js.map +1 -0
  19. package/dist/{chunk-XONKWL6D.cjs → chunk-VCXIPLEW.cjs} +585 -17
  20. package/dist/chunk-VCXIPLEW.cjs.map +1 -0
  21. package/dist/{index-BefQ8FAS.d.cts → index-BE1bM2Wi.d.cts} +238 -16
  22. package/dist/{index-BiIvdASA.d.ts → index-v2_-Fyn3.d.ts} +238 -16
  23. package/dist/index.cjs +110 -80
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +45 -17
  26. package/dist/index.d.ts +45 -17
  27. package/dist/index.js +35 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/schemas.cjs +11 -3
  30. package/dist/schemas.cjs.map +1 -1
  31. package/dist/schemas.d.cts +2 -2
  32. package/dist/schemas.d.ts +2 -2
  33. package/dist/schemas.js +10 -2
  34. package/dist/scoring.cjs +23 -3
  35. package/dist/scoring.cjs.map +1 -1
  36. package/dist/scoring.d.cts +471 -16
  37. package/dist/scoring.d.ts +471 -16
  38. package/dist/scoring.js +24 -4
  39. package/dist/xapi.cjs +3 -3
  40. package/dist/xapi.d.cts +1 -1
  41. package/dist/xapi.d.ts +1 -1
  42. package/dist/xapi.js +2 -2
  43. package/package.json +1 -1
  44. package/vectors/README.md +63 -3
  45. package/vectors/replay.d.mts +7 -2
  46. package/vectors/replay.mjs +83 -1
  47. package/vectors/scoring.json +10875 -2
  48. package/dist/chunk-DQKJUMUW.cjs.map +0 -1
  49. package/dist/chunk-FGN4LLSP.js +0 -387
  50. package/dist/chunk-FGN4LLSP.js.map +0 -1
  51. package/dist/chunk-FT7SAC3X.cjs.map +0 -1
  52. package/dist/chunk-PYP3HCRQ.js.map +0 -1
  53. package/dist/chunk-UHQVIC3P.cjs +0 -387
  54. package/dist/chunk-UHQVIC3P.cjs.map +0 -1
  55. package/dist/chunk-XKKKWKRH.js.map +0 -1
  56. package/dist/chunk-XONKWL6D.cjs.map +0 -1
  57. package/dist/chunk-Y36S3X3L.js.map +0 -1
@@ -20,7 +20,7 @@ var UnknownActivityTypeError = class extends Error {
20
20
  var RedactedScoringError = class extends Error {
21
21
  constructor(activityType) {
22
22
  super(
23
- `Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side, or use evaluate() which returns { status: "unscorable" }.`
23
+ `Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side. evaluate() answers this input without throwing, but it never grades it.`
24
24
  );
25
25
  this.activityType = activityType;
26
26
  this.name = "RedactedScoringError";
@@ -220,6 +220,10 @@ var SPACE_BESIDE_UNSPACED_RE = new RegExp(
220
220
  );
221
221
  var WORD_RE = new RegExp(`^[\\p{L}\\p{M}\\p{N}${JOINERS}]$`, "u");
222
222
  var UNSPACED_CHARACTER_RE = new RegExp(`^${UNSPACED_CHARACTER}$`, "u");
223
+ var UNSPACED_LETTER_RE = new RegExp(`(?=\\p{L})${UNSPACED_CHARACTER}`, "u");
224
+ function containsUnspacedScript(text) {
225
+ return UNSPACED_LETTER_RE.test(text);
226
+ }
223
227
  var EXTENDING_RE = new RegExp(`^[\\p{M}${JOINERS}${TAGS}]$`, "u");
224
228
  var WORD = 1;
225
229
  var UNSPACED = 2;
@@ -1341,6 +1345,168 @@ var MultipleChoiceDataSchema = z5.looseObject({
1341
1345
  path: ["options"]
1342
1346
  });
1343
1347
 
1348
+ // src/scoring/speech/limits.ts
1349
+ var READ_ALOUD_MAX_REFERENCE_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH;
1350
+ var READ_ALOUD_MAX_SECONDS = 300;
1351
+ var READ_ALOUD_MAX_TAKES = 20;
1352
+ var READ_ALOUD_MAX_DIMENSION_WEIGHT = 1e3;
1353
+ var SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
1354
+ var SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
1355
+
1356
+ // src/schemas/read-aloud.ts
1357
+ import { z as z6 } from "zod/v4";
1358
+
1359
+ // src/scoring/speech/locale.ts
1360
+ var CANONICAL_LOCALE_RE = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?-(?:[A-Z]{2}|[0-9]{3})$/;
1361
+
1362
+ // src/schemas/read-aloud.ts
1363
+ var READ_ALOUD_DIMENSIONS = ["accuracy", "fluency", "completeness", "prosody"];
1364
+ var READ_ALOUD_MAX_DIMENSIONS = READ_ALOUD_DIMENSIONS.length;
1365
+ var ReadAloudSlowMediaSchema = z6.strictObject({
1366
+ type: z6.literal("audio"),
1367
+ url: MediaUrlSchema,
1368
+ alt: z6.string().min(1).optional()
1369
+ });
1370
+ var ReferenceTextSchema = z6.string().min(1).refine((text) => text.trim() !== "", {
1371
+ error: "The text to read aloud must contain something to read."
1372
+ }).meta({ maxLength: READ_ALOUD_MAX_REFERENCE_LENGTH });
1373
+ var RecordingBoundsSchema = z6.looseObject({
1374
+ maxSeconds: z6.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
1375
+ minSeconds: z6.number().min(0).optional(),
1376
+ maxTakes: z6.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
1377
+ });
1378
+ var ReadAloudDimensionWeightSchema = z6.looseObject({
1379
+ name: z6.enum(READ_ALOUD_DIMENSIONS),
1380
+ weight: z6.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
1381
+ });
1382
+ var ReadAloudScoringSchema = z6.looseObject({
1383
+ dimensions: z6.array(ReadAloudDimensionWeightSchema).min(1).max(READ_ALOUD_MAX_DIMENSIONS)
1384
+ });
1385
+ function checkEvenAfterIssues2(check) {
1386
+ const guard = new z6.core.$ZodCheck({ check: "custom", when: () => true });
1387
+ guard._zod.check = check;
1388
+ return guard;
1389
+ }
1390
+ var ReadAloudDataShape = z6.looseObject({
1391
+ schemaVersion: z6.literal("1.0"),
1392
+ type: z6.literal("read-aloud"),
1393
+ id: z6.string().min(1),
1394
+ title: z6.string().min(1),
1395
+ instructions: z6.string().optional(),
1396
+ referenceText: ReferenceTextSchema,
1397
+ locale: z6.string().regex(CANONICAL_LOCALE_RE, {
1398
+ error: `The locale must be a BCP 47 tag in canonical form, with a region: "en-US", "es-419", "zh-Hant-TW". Pronunciation is assessed against one locale's speech, and "en" does not say whose.`
1399
+ }),
1400
+ media: MediaSchema.optional(),
1401
+ slowMedia: ReadAloudSlowMediaSchema.optional(),
1402
+ recording: RecordingBoundsSchema,
1403
+ scoring: ReadAloudScoringSchema,
1404
+ passThreshold: z6.number().min(0).max(1).optional(),
1405
+ feedback: FeedbackSchema.optional(),
1406
+ learningObjectives: z6.array(z6.string()).optional(),
1407
+ difficultyLevel: z6.literal([1, 2, 3, 4, 5]).optional()
1408
+ });
1409
+ var ReadAloudDataSchema = ReadAloudDataShape.check(
1410
+ checkEvenAfterIssues2((ctx) => {
1411
+ if (ctx.issues.some((issue2) => (issue2.path?.length ?? 0) === 0)) {
1412
+ return;
1413
+ }
1414
+ const data = ctx.value;
1415
+ const refused = new Set(ctx.issues.map((issue2) => String(issue2.path?.[0])));
1416
+ const report = (path, input, message) => {
1417
+ ctx.issues.push({ code: "custom", input, message, path: [...path] });
1418
+ };
1419
+ if (!refused.has("referenceText")) {
1420
+ const text = data.referenceText;
1421
+ const measured = dictationNormalizer(void 0).measure(text);
1422
+ if (measured.tooLong) {
1423
+ report(
1424
+ ["referenceText"],
1425
+ text,
1426
+ `The text to read aloud is at most ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before and after normalisation.`
1427
+ );
1428
+ }
1429
+ if (measured.normalized === "") {
1430
+ report(
1431
+ ["referenceText"],
1432
+ text,
1433
+ "The text to read aloud must contain a word: after ignoring punctuation and spacing nothing is left, so no word would be marked and every reading would align as one insertion after another."
1434
+ );
1435
+ }
1436
+ if (containsUnspacedScript(text)) {
1437
+ report(
1438
+ ["referenceText"],
1439
+ text,
1440
+ "A read-aloud item needs a script written with spaces between its words. The text is compared word by word, and a run of Han, kana, Thai, Lao, Khmer or Myanmar letters is one word however many it holds."
1441
+ );
1442
+ }
1443
+ }
1444
+ const mediaParses = !refused.has("media");
1445
+ const slowParses = !refused.has("slowMedia");
1446
+ if (mediaParses && data.media !== void 0 && data.media.type !== "audio") {
1447
+ report(
1448
+ ["media", "type"],
1449
+ data.media.type,
1450
+ "A read-aloud model is read aloud: its recording must be audio."
1451
+ );
1452
+ }
1453
+ if (mediaParses && slowParses && data.slowMedia !== void 0) {
1454
+ if (data.media === void 0) {
1455
+ report(
1456
+ ["media"],
1457
+ data.media,
1458
+ "A slow model recording accompanies a recording: add `media` first."
1459
+ );
1460
+ }
1461
+ if (data.media?.playback?.maxPlays !== void 0) {
1462
+ report(
1463
+ ["slowMedia"],
1464
+ data.slowMedia,
1465
+ "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays`."
1466
+ );
1467
+ }
1468
+ if (typeof data.slowMedia.url === "string" && typeof data.media?.url === "string" && data.slowMedia.url === data.media.url) {
1469
+ report(
1470
+ ["slowMedia", "url"],
1471
+ data.slowMedia.url,
1472
+ "The slow model recording must be a different file from the recording."
1473
+ );
1474
+ }
1475
+ }
1476
+ if (!refused.has("scoring")) {
1477
+ const dimensions = data.scoring.dimensions;
1478
+ const seen = /* @__PURE__ */ new Set();
1479
+ for (const [index, dimension] of dimensions.entries()) {
1480
+ if (seen.has(dimension.name)) {
1481
+ report(
1482
+ ["scoring", "dimensions", index, "name"],
1483
+ dimension.name,
1484
+ `"${dimension.name}" is already weighed: a dimension counted twice hides which weight the grade used.`
1485
+ );
1486
+ }
1487
+ seen.add(dimension.name);
1488
+ }
1489
+ if (dimensions.every((dimension) => dimension.weight === 0)) {
1490
+ report(
1491
+ ["scoring", "dimensions"],
1492
+ dimensions,
1493
+ "At least one dimension must carry a weight above 0: with every weight at 0 there is no weighted total, and every take would be unscorable."
1494
+ );
1495
+ }
1496
+ }
1497
+ if (!refused.has("recording")) {
1498
+ const { minSeconds, maxSeconds } = data.recording;
1499
+ if (minSeconds !== void 0 && minSeconds >= maxSeconds) {
1500
+ report(
1501
+ ["recording", "minSeconds"],
1502
+ minSeconds,
1503
+ "The shortest take must be shorter than the longest."
1504
+ );
1505
+ }
1506
+ }
1507
+ })
1508
+ );
1509
+
1344
1510
  // src/count-words.ts
1345
1511
  function countWords(text) {
1346
1512
  if (typeof text !== "string") {
@@ -1354,21 +1520,21 @@ function countWords(text) {
1354
1520
  }
1355
1521
 
1356
1522
  // src/schemas/gap-select.ts
1357
- import { z as z6 } from "zod/v4";
1358
- var GapSelectChoiceSchema = z6.looseObject({
1359
- id: z6.string().min(1),
1360
- text: z6.string().min(1)
1523
+ import { z as z7 } from "zod/v4";
1524
+ var GapSelectChoiceSchema = z7.looseObject({
1525
+ id: z7.string().min(1),
1526
+ text: z7.string().min(1)
1361
1527
  });
1362
- var GapSelectBankSchema = z6.looseObject({
1363
- id: z6.string().min(1),
1364
- choices: z6.array(GapSelectChoiceSchema).min(2)
1528
+ var GapSelectBankSchema = z7.looseObject({
1529
+ id: z7.string().min(1),
1530
+ choices: z7.array(GapSelectChoiceSchema).min(2)
1365
1531
  });
1366
- var GapSelectGapSchema = z6.looseObject({
1367
- id: z6.string().min(1),
1368
- choices: z6.array(GapSelectChoiceSchema).min(2).optional(),
1369
- bankId: z6.string().min(1).optional(),
1370
- correctChoiceId: z6.string().min(1),
1371
- feedback: z6.string().optional()
1532
+ var GapSelectGapSchema = z7.looseObject({
1533
+ id: z7.string().min(1),
1534
+ choices: z7.array(GapSelectChoiceSchema).min(2).optional(),
1535
+ bankId: z7.string().min(1).optional(),
1536
+ correctChoiceId: z7.string().min(1),
1537
+ feedback: z7.string().optional()
1372
1538
  });
1373
1539
  function resolveChoices(gap, banks) {
1374
1540
  if (gap.choices !== void 0) {
@@ -1387,24 +1553,24 @@ function placeholderCounts(passage) {
1387
1553
  }
1388
1554
  return counts;
1389
1555
  }
1390
- var GapSelectDataSchema = z6.looseObject({
1391
- schemaVersion: z6.literal("1.0"),
1392
- type: z6.literal("gap-select"),
1393
- id: z6.string().min(1),
1394
- title: z6.string().min(1),
1395
- passage: z6.string().min(1),
1396
- passageHtml: z6.string().optional(),
1397
- gaps: z6.array(GapSelectGapSchema).min(1),
1398
- banks: z6.array(GapSelectBankSchema).optional(),
1399
- scoringStrategy: z6.enum(["all-or-nothing", "partial"]),
1400
- presentation: z6.literal("dropdown").optional(),
1401
- shuffleChoices: z6.boolean().optional(),
1556
+ var GapSelectDataSchema = z7.looseObject({
1557
+ schemaVersion: z7.literal("1.0"),
1558
+ type: z7.literal("gap-select"),
1559
+ id: z7.string().min(1),
1560
+ title: z7.string().min(1),
1561
+ passage: z7.string().min(1),
1562
+ passageHtml: z7.string().optional(),
1563
+ gaps: z7.array(GapSelectGapSchema).min(1),
1564
+ banks: z7.array(GapSelectBankSchema).optional(),
1565
+ scoringStrategy: z7.enum(["all-or-nothing", "partial"]),
1566
+ presentation: z7.literal("dropdown").optional(),
1567
+ shuffleChoices: z7.boolean().optional(),
1402
1568
  media: MediaSchema.optional(),
1403
1569
  feedback: FeedbackSchema.optional(),
1404
- passThreshold: z6.number().min(0).max(1).optional(),
1405
- locale: z6.string().optional(),
1406
- learningObjectives: z6.array(z6.string()).optional(),
1407
- difficultyLevel: z6.literal([1, 2, 3, 4, 5]).optional()
1570
+ passThreshold: z7.number().min(0).max(1).optional(),
1571
+ locale: z7.string().optional(),
1572
+ learningObjectives: z7.array(z7.string()).optional(),
1573
+ difficultyLevel: z7.literal([1, 2, 3, 4, 5]).optional()
1408
1574
  }).refine(
1409
1575
  (data) => {
1410
1576
  const ids = (data.banks ?? []).map((bank) => bank.id);
@@ -1458,137 +1624,137 @@ var GapSelectDataSchema = z6.looseObject({
1458
1624
  );
1459
1625
 
1460
1626
  // src/schemas/written-response.ts
1461
- import { z as z7 } from "zod/v4";
1462
- var WrittenResponseRubricCriterionSchema = z7.looseObject({
1463
- name: z7.string().min(1),
1464
- description: z7.string().optional(),
1465
- weight: z7.number().min(0)
1627
+ import { z as z8 } from "zod/v4";
1628
+ var WrittenResponseRubricCriterionSchema = z8.looseObject({
1629
+ name: z8.string().min(1),
1630
+ description: z8.string().optional(),
1631
+ weight: z8.number().min(0)
1466
1632
  });
1467
- var WrittenResponseRubricSchema = z7.looseObject({
1468
- label: z7.string().optional(),
1469
- criteria: z7.array(WrittenResponseRubricCriterionSchema).min(1)
1633
+ var WrittenResponseRubricSchema = z8.looseObject({
1634
+ label: z8.string().optional(),
1635
+ criteria: z8.array(WrittenResponseRubricCriterionSchema).min(1)
1470
1636
  });
1471
- var RedactedWrittenResponseRubricCriterionSchema = z7.strictObject({
1472
- name: z7.string().min(1),
1473
- description: z7.string().optional(),
1474
- weight: z7.number().min(0)
1637
+ var RedactedWrittenResponseRubricCriterionSchema = z8.strictObject({
1638
+ name: z8.string().min(1),
1639
+ description: z8.string().optional(),
1640
+ weight: z8.number().min(0)
1475
1641
  });
1476
- var RedactedWrittenResponseRubricSchema = z7.strictObject({
1477
- label: z7.string().optional(),
1478
- criteria: z7.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
1642
+ var RedactedWrittenResponseRubricSchema = z8.strictObject({
1643
+ label: z8.string().optional(),
1644
+ criteria: z8.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
1479
1645
  });
1480
- var WrittenResponseDataSchema = z7.looseObject({
1481
- schemaVersion: z7.literal("1.0"),
1482
- type: z7.literal("written-response"),
1483
- id: z7.string().min(1),
1484
- title: z7.string().min(1),
1485
- prompt: z7.string(),
1486
- promptHtml: z7.string().optional(),
1487
- minWords: z7.number().int().min(0),
1488
- maxWords: z7.number().int().min(1),
1646
+ var WrittenResponseDataSchema = z8.looseObject({
1647
+ schemaVersion: z8.literal("1.0"),
1648
+ type: z8.literal("written-response"),
1649
+ id: z8.string().min(1),
1650
+ title: z8.string().min(1),
1651
+ prompt: z8.string(),
1652
+ promptHtml: z8.string().optional(),
1653
+ minWords: z8.number().int().min(0),
1654
+ maxWords: z8.number().int().min(1),
1489
1655
  rubric: WrittenResponseRubricSchema.optional(),
1490
- languageTarget: z7.string().optional(),
1656
+ languageTarget: z8.string().optional(),
1491
1657
  media: MediaSchema.optional(),
1492
1658
  feedback: FeedbackSchema.optional(),
1493
- passThreshold: z7.number().min(0).max(1).optional(),
1494
- locale: z7.string().optional(),
1495
- learningObjectives: z7.array(z7.string()).optional(),
1496
- difficultyLevel: z7.literal([1, 2, 3, 4, 5]).optional()
1659
+ passThreshold: z8.number().min(0).max(1).optional(),
1660
+ locale: z8.string().optional(),
1661
+ learningObjectives: z8.array(z8.string()).optional(),
1662
+ difficultyLevel: z8.literal([1, 2, 3, 4, 5]).optional()
1497
1663
  }).refine((data) => data.maxWords >= data.minWords, {
1498
1664
  error: "maxWords must be greater than or equal to minWords.",
1499
1665
  path: ["maxWords"]
1500
1666
  });
1501
1667
 
1502
1668
  // src/schemas/redacted.ts
1503
- import { z as z8 } from "zod/v4";
1669
+ import { z as z9 } from "zod/v4";
1504
1670
  var redactedBase = {
1505
1671
  /** Marker distinguishing a redacted projection from full activity data. */
1506
- redacted: z8.literal(true),
1672
+ redacted: z9.literal(true),
1507
1673
  /** Slot identity, carried through redaction so the client and the plan agree. */
1508
- slotKey: z8.string().min(1).optional(),
1509
- schemaVersion: z8.literal("1.0"),
1510
- id: z8.string().min(1),
1511
- title: z8.string().min(1),
1674
+ slotKey: z9.string().min(1).optional(),
1675
+ schemaVersion: z9.literal("1.0"),
1676
+ id: z9.string().min(1),
1677
+ title: z9.string().min(1),
1512
1678
  media: RedactedMediaSchema.optional(),
1513
- passThreshold: z8.number().min(0).max(1).optional(),
1514
- locale: z8.string().optional(),
1515
- learningObjectives: z8.array(z8.string()).optional(),
1516
- difficultyLevel: z8.literal([1, 2, 3, 4, 5]).optional()
1679
+ passThreshold: z9.number().min(0).max(1).optional(),
1680
+ locale: z9.string().optional(),
1681
+ learningObjectives: z9.array(z9.string()).optional(),
1682
+ difficultyLevel: z9.literal([1, 2, 3, 4, 5]).optional()
1517
1683
  };
1518
- var RedactedMultipleChoiceOptionMediaSchema = z8.strictObject({
1519
- type: z8.enum(["image", "audio"]),
1520
- url: z8.string().min(1),
1521
- alt: z8.string().min(1).optional(),
1522
- captionsUrl: z8.string().min(1).optional()
1684
+ var RedactedMultipleChoiceOptionMediaSchema = z9.strictObject({
1685
+ type: z9.enum(["image", "audio"]),
1686
+ url: z9.string().min(1),
1687
+ alt: z9.string().min(1).optional(),
1688
+ captionsUrl: z9.string().min(1).optional()
1523
1689
  });
1524
- var RedactedMultipleChoiceOptionSchema = z8.strictObject({
1525
- id: z8.string().min(1),
1526
- text: z8.string().min(1),
1690
+ var RedactedMultipleChoiceOptionSchema = z9.strictObject({
1691
+ id: z9.string().min(1),
1692
+ text: z9.string().min(1),
1527
1693
  media: RedactedMultipleChoiceOptionMediaSchema.optional()
1528
1694
  });
1529
- var RedactedMultipleChoiceDataSchema = z8.strictObject({
1695
+ var RedactedMultipleChoiceDataSchema = z9.strictObject({
1530
1696
  ...redactedBase,
1531
- type: z8.literal("multiple-choice"),
1532
- question: z8.string().min(1),
1533
- questionHtml: z8.string().optional(),
1534
- mode: z8.enum(["single", "multi"]),
1535
- options: z8.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
1536
- shuffle: z8.boolean().optional()
1697
+ type: z9.literal("multiple-choice"),
1698
+ question: z9.string().min(1),
1699
+ questionHtml: z9.string().optional(),
1700
+ mode: z9.enum(["single", "multi"]),
1701
+ options: z9.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
1702
+ shuffle: z9.boolean().optional()
1537
1703
  });
1538
- var RedactedBlankConfigSchema = z8.strictObject({
1539
- id: z8.string().min(1),
1540
- hint: z8.string().optional()
1704
+ var RedactedBlankConfigSchema = z9.strictObject({
1705
+ id: z9.string().min(1),
1706
+ hint: z9.string().optional()
1541
1707
  });
1542
- var RedactedFillInTheBlanksDataSchema = z8.strictObject({
1708
+ var RedactedFillInTheBlanksDataSchema = z9.strictObject({
1543
1709
  ...redactedBase,
1544
- type: z8.literal("fill-in-the-blanks"),
1545
- passage: z8.string().min(1),
1546
- passageHtml: z8.string().optional(),
1547
- blanks: z8.array(RedactedBlankConfigSchema).min(1)
1710
+ type: z9.literal("fill-in-the-blanks"),
1711
+ passage: z9.string().min(1),
1712
+ passageHtml: z9.string().optional(),
1713
+ blanks: z9.array(RedactedBlankConfigSchema).min(1)
1548
1714
  });
1549
- var RedactedGapSelectChoiceSchema = z8.strictObject({
1550
- id: z8.string().min(1),
1551
- text: z8.string().min(1)
1715
+ var RedactedGapSelectChoiceSchema = z9.strictObject({
1716
+ id: z9.string().min(1),
1717
+ text: z9.string().min(1)
1552
1718
  });
1553
- var RedactedGapSelectBankSchema = z8.strictObject({
1554
- id: z8.string().min(1),
1555
- choices: z8.array(RedactedGapSelectChoiceSchema).min(2)
1719
+ var RedactedGapSelectBankSchema = z9.strictObject({
1720
+ id: z9.string().min(1),
1721
+ choices: z9.array(RedactedGapSelectChoiceSchema).min(2)
1556
1722
  });
1557
- var RedactedGapSelectGapSchema = z8.strictObject({
1558
- id: z8.string().min(1),
1559
- choices: z8.array(RedactedGapSelectChoiceSchema).min(2).optional(),
1560
- bankId: z8.string().min(1).optional()
1723
+ var RedactedGapSelectGapSchema = z9.strictObject({
1724
+ id: z9.string().min(1),
1725
+ choices: z9.array(RedactedGapSelectChoiceSchema).min(2).optional(),
1726
+ bankId: z9.string().min(1).optional()
1561
1727
  });
1562
- var RedactedGapSelectDataSchema = z8.strictObject({
1728
+ var RedactedGapSelectDataSchema = z9.strictObject({
1563
1729
  ...redactedBase,
1564
- type: z8.literal("gap-select"),
1565
- passage: z8.string().min(1),
1566
- passageHtml: z8.string().optional(),
1567
- gaps: z8.array(RedactedGapSelectGapSchema).min(1),
1568
- banks: z8.array(RedactedGapSelectBankSchema).optional(),
1569
- presentation: z8.literal("dropdown").optional(),
1570
- shuffleChoices: z8.boolean().optional()
1730
+ type: z9.literal("gap-select"),
1731
+ passage: z9.string().min(1),
1732
+ passageHtml: z9.string().optional(),
1733
+ gaps: z9.array(RedactedGapSelectGapSchema).min(1),
1734
+ banks: z9.array(RedactedGapSelectBankSchema).optional(),
1735
+ presentation: z9.literal("dropdown").optional(),
1736
+ shuffleChoices: z9.boolean().optional()
1571
1737
  });
1572
- var RedactedWrittenResponseDataSchema = z8.strictObject({
1738
+ var RedactedWrittenResponseDataSchema = z9.strictObject({
1573
1739
  ...redactedBase,
1574
- type: z8.literal("written-response"),
1575
- prompt: z8.string(),
1576
- promptHtml: z8.string().optional(),
1577
- minWords: z8.number().int().min(0),
1578
- maxWords: z8.number().int().min(1),
1740
+ type: z9.literal("written-response"),
1741
+ prompt: z9.string(),
1742
+ promptHtml: z9.string().optional(),
1743
+ minWords: z9.number().int().min(0),
1744
+ maxWords: z9.number().int().min(1),
1579
1745
  rubric: RedactedWrittenResponseRubricSchema.optional(),
1580
- languageTarget: z8.string().optional()
1746
+ languageTarget: z9.string().optional()
1581
1747
  });
1582
- var RedactedDictationSlowMediaSchema = z8.strictObject({
1583
- type: z8.literal("audio"),
1748
+ var RedactedDictationSlowMediaSchema = z9.strictObject({
1749
+ type: z9.literal("audio"),
1584
1750
  url: MediaUrlSchema,
1585
- alt: z8.string().min(1).optional()
1751
+ alt: z9.string().min(1).optional()
1586
1752
  });
1587
- var RedactedDictationDataSchema = z8.strictObject({
1753
+ var RedactedDictationDataSchema = z9.strictObject({
1588
1754
  ...redactedBase,
1589
- type: z8.literal("dictation"),
1755
+ type: z9.literal("dictation"),
1590
1756
  slowMedia: RedactedDictationSlowMediaSchema.optional(),
1591
- hints: z8.strictObject({ mode: z8.literal("progressive-words") }).optional()
1757
+ hints: z9.strictObject({ mode: z9.literal("progressive-words") }).optional()
1592
1758
  }).check((ctx) => {
1593
1759
  const data = ctx.value;
1594
1760
  if (data.media?.captionsUrl !== void 0) {
@@ -1616,6 +1782,61 @@ var RedactedDictationDataSchema = z8.strictObject({
1616
1782
  });
1617
1783
  }
1618
1784
  });
1785
+ var RedactedReadAloudDataSchema = z9.strictObject({
1786
+ ...redactedBase,
1787
+ type: z9.literal("read-aloud"),
1788
+ locale: z9.string().regex(CANONICAL_LOCALE_RE),
1789
+ instructions: z9.string().optional(),
1790
+ referenceText: z9.string().min(1),
1791
+ slowMedia: ReadAloudSlowMediaSchema.optional(),
1792
+ recording: z9.strictObject({
1793
+ maxSeconds: z9.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
1794
+ minSeconds: z9.number().min(0).optional(),
1795
+ maxTakes: z9.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
1796
+ }),
1797
+ scoring: z9.strictObject({
1798
+ dimensions: z9.array(
1799
+ z9.strictObject({
1800
+ name: z9.enum(READ_ALOUD_DIMENSIONS),
1801
+ weight: z9.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
1802
+ })
1803
+ ).min(1).max(READ_ALOUD_DIMENSIONS.length)
1804
+ })
1805
+ }).check((ctx) => {
1806
+ const data = ctx.value;
1807
+ if (data.media !== void 0 && data.media.type !== "audio") {
1808
+ ctx.issues.push({
1809
+ code: "custom",
1810
+ input: data.media.type,
1811
+ message: "A read-aloud model recording must be audio, so this payload is not renderable.",
1812
+ path: ["media", "type"]
1813
+ });
1814
+ }
1815
+ if (data.slowMedia !== void 0 && data.media === void 0) {
1816
+ ctx.issues.push({
1817
+ code: "custom",
1818
+ input: data.slowMedia,
1819
+ message: "A slow model recording accompanies a recording: this payload has no `media`.",
1820
+ path: ["media"]
1821
+ });
1822
+ }
1823
+ if (data.slowMedia !== void 0 && data.media?.playback?.maxPlays !== void 0) {
1824
+ ctx.issues.push({
1825
+ code: "custom",
1826
+ input: data.slowMedia,
1827
+ message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
1828
+ path: ["slowMedia"]
1829
+ });
1830
+ }
1831
+ if (data.slowMedia !== void 0 && data.slowMedia.url === data.media?.url) {
1832
+ ctx.issues.push({
1833
+ code: "custom",
1834
+ input: data.slowMedia.url,
1835
+ message: "The slow model recording must be a different file from the recording.",
1836
+ path: ["slowMedia", "url"]
1837
+ });
1838
+ }
1839
+ });
1619
1840
 
1620
1841
  // src/scoring/dictation/edit-distance.ts
1621
1842
  function codePoints(text) {
@@ -2037,7 +2258,27 @@ var DRAFT_ISSUE_SEVERITY = {
2037
2258
  dc_hints_mode_invalid: "invalid",
2038
2259
  dc_equivalence_from_required: "incomplete",
2039
2260
  dc_equivalence_to_required: "incomplete",
2040
- dc_tolerance_invalid: "invalid"
2261
+ dc_tolerance_invalid: "invalid",
2262
+ // read-aloud
2263
+ ra_reference_text_required: "incomplete",
2264
+ ra_reference_text_too_long: "invalid",
2265
+ ra_reference_text_unreadable: "invalid",
2266
+ ra_reference_text_unspaced_script: "invalid",
2267
+ ra_locale_required: "incomplete",
2268
+ ra_locale_invalid: "invalid",
2269
+ ra_max_seconds_required: "incomplete",
2270
+ ra_max_seconds_out_of_range: "invalid",
2271
+ ra_min_seconds_out_of_range: "invalid",
2272
+ ra_max_takes_out_of_range: "invalid",
2273
+ ra_dimensions_required: "incomplete",
2274
+ ra_dimension_name_invalid: "invalid",
2275
+ ra_dimension_duplicate: "invalid",
2276
+ ra_dimension_weight_invalid: "invalid",
2277
+ ra_dimension_weights_zero: "invalid",
2278
+ ra_media_kind: "invalid",
2279
+ ra_slow_media_without_media: "incomplete",
2280
+ ra_slow_media_same_recording: "invalid",
2281
+ ra_slow_media_beside_play_limit: "invalid"
2041
2282
  };
2042
2283
  function issue(code, path, message) {
2043
2284
  return { path: path.map(String), message, code, severity: DRAFT_ISSUE_SEVERITY[code] };
@@ -3178,6 +3419,283 @@ function checkOptionMedia(media, index, ordinal) {
3178
3419
  return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
3179
3420
  }
3180
3421
 
3422
+ // src/authoring/read-aloud.ts
3423
+ var readAloudAuthoring = {
3424
+ createDraft: ({ newId }) => ({
3425
+ schemaVersion: "1.0",
3426
+ type: "read-aloud",
3427
+ id: newId(),
3428
+ title: "",
3429
+ referenceText: "",
3430
+ locale: "",
3431
+ recording: { maxSeconds: 0 },
3432
+ scoring: { dimensions: [] }
3433
+ }),
3434
+ checkDraft: checkReadAloudDraft
3435
+ };
3436
+ function checkReadAloudDraft(draft) {
3437
+ const issues = checkIdentity(draft, "read-aloud");
3438
+ issues.push(...checkReferenceText(draft.referenceText));
3439
+ issues.push(...checkLocale(draft.locale));
3440
+ issues.push(...checkRecording2(draft.recording));
3441
+ issues.push(...checkDimensions(draft.scoring));
3442
+ issues.push(...checkModelRecording(draft.media));
3443
+ issues.push(...checkSlowRecording2(draft));
3444
+ issues.push(...checkSharedOptional(draft));
3445
+ return issues;
3446
+ }
3447
+ function checkReferenceText(referenceText) {
3448
+ if (isUnwritten(referenceText)) {
3449
+ return [
3450
+ issue("ra_reference_text_required", ["referenceText"], "Write the text to be read aloud.")
3451
+ ];
3452
+ }
3453
+ if (typeof referenceText !== "string") {
3454
+ return [];
3455
+ }
3456
+ const issues = [];
3457
+ const measured = dictationNormalizer(void 0).measure(referenceText);
3458
+ if (measured.tooLong) {
3459
+ issues.push(
3460
+ issue(
3461
+ "ra_reference_text_too_long",
3462
+ ["referenceText"],
3463
+ `The text is longer than ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before or after normalisation. A read-aloud item is a sentence or a short paragraph.`
3464
+ )
3465
+ );
3466
+ }
3467
+ if (measured.normalized === "") {
3468
+ issues.push(
3469
+ issue(
3470
+ "ra_reference_text_unreadable",
3471
+ ["referenceText"],
3472
+ "The text has no word to read: once punctuation and spacing are ignored, nothing is left. A reading of it would be marked against no word at all."
3473
+ )
3474
+ );
3475
+ }
3476
+ if (containsUnspacedScript(referenceText)) {
3477
+ issues.push(
3478
+ issue(
3479
+ "ra_reference_text_unspaced_script",
3480
+ ["referenceText"],
3481
+ "The text is written in a script that does not separate its words with spaces. Each word is marked on its own, and a run of Han, kana, Thai, Lao, Khmer or Myanmar letters would be marked as one word."
3482
+ )
3483
+ );
3484
+ }
3485
+ return issues;
3486
+ }
3487
+ function checkLocale(locale) {
3488
+ if (isUnwritten(locale)) {
3489
+ return [
3490
+ issue(
3491
+ "ra_locale_required",
3492
+ ["locale"],
3493
+ 'Choose the language the learner reads in, with its region \u2014 "en-US", not "en".'
3494
+ )
3495
+ ];
3496
+ }
3497
+ if (typeof locale !== "string" || CANONICAL_LOCALE_RE.test(locale)) {
3498
+ return [];
3499
+ }
3500
+ return [
3501
+ issue(
3502
+ "ra_locale_invalid",
3503
+ ["locale"],
3504
+ `The language must be a BCP 47 tag in canonical form, with a region: "en-US", "es-419", "zh-Hant-TW". Pronunciation is assessed against one locale's speech.`
3505
+ )
3506
+ ];
3507
+ }
3508
+ function checkRecording2(recording) {
3509
+ if (isUnset(recording)) {
3510
+ return [maxSecondsRequired()];
3511
+ }
3512
+ if (!isRecord(recording)) {
3513
+ return [];
3514
+ }
3515
+ const issues = [];
3516
+ const max = recording.maxSeconds;
3517
+ if (isUnset(max) || max === 0) {
3518
+ issues.push(maxSecondsRequired());
3519
+ } else if (typeof max === "number" && !(Number.isFinite(max) && max > 0 && max <= READ_ALOUD_MAX_SECONDS)) {
3520
+ issues.push(
3521
+ issue(
3522
+ "ra_max_seconds_out_of_range",
3523
+ ["recording", "maxSeconds"],
3524
+ `A take is at most ${READ_ALOUD_MAX_SECONDS} seconds long.`
3525
+ )
3526
+ );
3527
+ }
3528
+ const min = recording.minSeconds;
3529
+ if (typeof min === "number" && !(Number.isFinite(min) && min >= 0)) {
3530
+ issues.push(minSecondsOutOfRange("The shortest take is a number of seconds, 0 or more."));
3531
+ } else if (typeof min === "number" && typeof max === "number" && max > 0 && min >= max) {
3532
+ issues.push(minSecondsOutOfRange("The shortest take must be shorter than the longest."));
3533
+ }
3534
+ const takes = recording.maxTakes;
3535
+ if (typeof takes === "number" && !(isWholeNumber(takes) && takes >= 1 && takes <= READ_ALOUD_MAX_TAKES)) {
3536
+ issues.push(
3537
+ issue(
3538
+ "ra_max_takes_out_of_range",
3539
+ ["recording", "maxTakes"],
3540
+ `How many takes a learner may record is a whole number from 1 to ${READ_ALOUD_MAX_TAKES}.`
3541
+ )
3542
+ );
3543
+ }
3544
+ return issues;
3545
+ }
3546
+ function maxSecondsRequired() {
3547
+ return issue(
3548
+ "ra_max_seconds_required",
3549
+ ["recording", "maxSeconds"],
3550
+ "Set how long a take may be, in seconds."
3551
+ );
3552
+ }
3553
+ function minSecondsOutOfRange(message) {
3554
+ return issue("ra_min_seconds_out_of_range", ["recording", "minSeconds"], message);
3555
+ }
3556
+ var DIMENSION_NAMES = READ_ALOUD_DIMENSIONS;
3557
+ function checkDimensions(scoring) {
3558
+ if (isUnset(scoring)) {
3559
+ return [dimensionsRequired()];
3560
+ }
3561
+ if (!isRecord(scoring)) {
3562
+ return [];
3563
+ }
3564
+ const dimensions = scoring.dimensions;
3565
+ if (isUnset(dimensions)) {
3566
+ return [dimensionsRequired()];
3567
+ }
3568
+ if (!Array.isArray(dimensions)) {
3569
+ return [];
3570
+ }
3571
+ if (dimensions.length === 0) {
3572
+ return [dimensionsRequired()];
3573
+ }
3574
+ const issues = [];
3575
+ const seen = /* @__PURE__ */ new Set();
3576
+ let everyWeightUsable = true;
3577
+ let totalWeight = 0;
3578
+ for (const [index, dimension] of dimensions.entries()) {
3579
+ if (!isRecord(dimension)) {
3580
+ everyWeightUsable = false;
3581
+ continue;
3582
+ }
3583
+ const path = ["scoring", "dimensions", index];
3584
+ const name = dimension.name;
3585
+ if (!DIMENSION_NAMES.includes(name)) {
3586
+ issues.push(
3587
+ issue(
3588
+ "ra_dimension_name_invalid",
3589
+ [...path, "name"],
3590
+ `Choose what dimension ${index + 1} measures: ${READ_ALOUD_DIMENSIONS.join(", ")}.`
3591
+ )
3592
+ );
3593
+ } else if (seen.has(name)) {
3594
+ issues.push(
3595
+ issue(
3596
+ "ra_dimension_duplicate",
3597
+ [...path, "name"],
3598
+ `"${String(name)}" is already weighed. Remove this one, or measure something else.`
3599
+ )
3600
+ );
3601
+ } else {
3602
+ seen.add(name);
3603
+ }
3604
+ const weight = dimension.weight;
3605
+ if (typeof weight === "number" && Number.isFinite(weight) && weight >= 0 && weight <= READ_ALOUD_MAX_DIMENSION_WEIGHT) {
3606
+ totalWeight += weight;
3607
+ } else {
3608
+ everyWeightUsable = false;
3609
+ issues.push(
3610
+ issue(
3611
+ "ra_dimension_weight_invalid",
3612
+ [...path, "weight"],
3613
+ `Give dimension ${index + 1} a weight: a number from 0 to ${READ_ALOUD_MAX_DIMENSION_WEIGHT}.`
3614
+ )
3615
+ );
3616
+ }
3617
+ }
3618
+ if (everyWeightUsable && totalWeight === 0) {
3619
+ issues.push(
3620
+ issue(
3621
+ "ra_dimension_weights_zero",
3622
+ ["scoring", "dimensions"],
3623
+ "Give at least one dimension a weight above 0. With every weight at 0 there is no weighted total, and every take would be unscorable."
3624
+ )
3625
+ );
3626
+ }
3627
+ return issues;
3628
+ }
3629
+ function dimensionsRequired() {
3630
+ return issue(
3631
+ "ra_dimensions_required",
3632
+ ["scoring", "dimensions"],
3633
+ "Choose what the grade measures: at least one of accuracy, fluency, completeness or prosody, with a weight."
3634
+ );
3635
+ }
3636
+ function checkModelRecording(media) {
3637
+ if (!isRecord(media)) {
3638
+ return [];
3639
+ }
3640
+ const kind = media.type;
3641
+ return isUnwritten(kind) || kind === "audio" ? [] : [
3642
+ issue(
3643
+ "ra_media_kind",
3644
+ ["media", "type"],
3645
+ "A read-aloud model is read aloud: its recording must be audio."
3646
+ )
3647
+ ];
3648
+ }
3649
+ function checkSlowRecording2(draft) {
3650
+ const slow = draft.slowMedia;
3651
+ if (!isRecord(slow)) {
3652
+ return [];
3653
+ }
3654
+ const kind = slow.type;
3655
+ const wrongKind = !isUnwritten(kind) && kind !== "audio";
3656
+ const issues = wrongKind ? [issue("media_invalid", ["slowMedia", "type"], "The slow model recording must be audio.")] : [];
3657
+ issues.push(
3658
+ ...checkMedia(
3659
+ wrongKind ? { ...slow, type: "audio" } : slow,
3660
+ ["slowMedia"],
3661
+ ReadAloudSlowMediaSchema
3662
+ )
3663
+ );
3664
+ const media = draft.media;
3665
+ if (isUnset(media)) {
3666
+ issues.push(
3667
+ issue(
3668
+ "ra_slow_media_without_media",
3669
+ ["media"],
3670
+ "A slow model recording accompanies a recording. Add the recording first."
3671
+ )
3672
+ );
3673
+ return issues;
3674
+ }
3675
+ if (!isRecord(media)) {
3676
+ return issues;
3677
+ }
3678
+ if (isRecord(media.playback) && media.playback.maxPlays !== void 0) {
3679
+ issues.push(
3680
+ issue(
3681
+ "ra_slow_media_beside_play_limit",
3682
+ ["slowMedia"],
3683
+ "A play budget on the recording cannot coexist with a slow recording that has none. Remove the slow recording, or remove the play limit."
3684
+ )
3685
+ );
3686
+ }
3687
+ if (typeof slow.url === "string" && slow.url !== "" && slow.url === media.url) {
3688
+ issues.push(
3689
+ issue(
3690
+ "ra_slow_media_same_recording",
3691
+ ["slowMedia", "url"],
3692
+ "The slow model recording is the same file as the recording. Point it at the slower reading."
3693
+ )
3694
+ );
3695
+ }
3696
+ return issues;
3697
+ }
3698
+
3181
3699
  // src/authoring/written-response.ts
3182
3700
  var writtenResponseAuthoring = {
3183
3701
  createDraft: ({ newId }) => ({
@@ -3717,11 +4235,48 @@ var dictationType = defineActivityType({
3717
4235
  interactions: ["text-changed", "hint-requested", "submitted"],
3718
4236
  authoring: dictationAuthoring
3719
4237
  });
4238
+ var READ_ALOUD_FIELD_POLICY = {
4239
+ ...SHARED_PUBLIC_FIELDS,
4240
+ instructions: "public",
4241
+ referenceText: "public",
4242
+ recording: { maxSeconds: "public", minSeconds: "public", maxTakes: "public" },
4243
+ slowMedia: { type: "public", url: "public", alt: "public" },
4244
+ scoring: { dimensions: { name: "public", weight: "public" } },
4245
+ feedback: FEEDBACK_FIELD_POLICY
4246
+ };
4247
+ function hasRecording(response) {
4248
+ const recording = response?.recording;
4249
+ return typeof recording === "object" && recording !== null && typeof recording.key === "string" && recording.key !== "";
4250
+ }
4251
+ var readAloudType = defineActivityType({
4252
+ type: "read-aloud",
4253
+ schema: ReadAloudDataSchema,
4254
+ scoring: {
4255
+ kind: "deferred",
4256
+ reason: "requires_async_grading",
4257
+ partial: (_data, response) => ({ hasRecording: hasRecording(response) })
4258
+ },
4259
+ isAnswered: (response) => hasRecording(response),
4260
+ fieldPolicy: READ_ALOUD_FIELD_POLICY,
4261
+ redactedSchema: RedactedReadAloudDataSchema,
4262
+ interop: {
4263
+ xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
4264
+ // `other`, not `performance`: xAPI's performance interaction describes a set
4265
+ // of steps with their own responses, and a reading has one. There is no
4266
+ // pattern to publish either — the "correct response" is a pronunciation, not
4267
+ // a string a statement can carry.
4268
+ xapiInteractionType: "other",
4269
+ correctResponsesPattern: () => []
4270
+ },
4271
+ interactions: ["recording-started", "recording-stopped", "recording-uploaded", "submitted"],
4272
+ authoring: readAloudAuthoring
4273
+ });
3720
4274
  registerActivityType(multipleChoiceType);
3721
4275
  registerActivityType(fillInTheBlanksType);
3722
4276
  registerActivityType(writtenResponseType);
3723
4277
  registerActivityType(gapSelectType);
3724
4278
  registerActivityType(dictationType);
4279
+ registerActivityType(readAloudType);
3725
4280
 
3726
4281
  export {
3727
4282
  ActivitySchemaError,
@@ -3734,6 +4289,8 @@ export {
3734
4289
  DICTATION_MAX_EQUIVALENCE_LENGTH,
3735
4290
  DICTATION_MAX_EQUIVALENCES,
3736
4291
  DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
4292
+ codePointLength,
4293
+ normalizeDictationText,
3737
4294
  FeedbackSchema,
3738
4295
  MediaUrlSchema,
3739
4296
  NativeControlHintSchema,
@@ -3763,6 +4320,15 @@ export {
3763
4320
  MultipleChoiceOptionMediaSchema,
3764
4321
  MultipleChoiceOptionSchema,
3765
4322
  MultipleChoiceDataSchema,
4323
+ READ_ALOUD_MAX_REFERENCE_LENGTH,
4324
+ READ_ALOUD_MAX_SECONDS,
4325
+ READ_ALOUD_MAX_TAKES,
4326
+ READ_ALOUD_MAX_DIMENSION_WEIGHT,
4327
+ SPEECH_ASSESSMENT_MAX_WORDS,
4328
+ SPEECH_ASSESSMENT_MAX_TEXT_LENGTH,
4329
+ CANONICAL_LOCALE_RE,
4330
+ ReadAloudSlowMediaSchema,
4331
+ ReadAloudDataSchema,
3766
4332
  countWords,
3767
4333
  GapSelectChoiceSchema,
3768
4334
  GapSelectBankSchema,
@@ -3783,6 +4349,7 @@ export {
3783
4349
  RedactedWrittenResponseDataSchema,
3784
4350
  RedactedDictationSlowMediaSchema,
3785
4351
  RedactedDictationDataSchema,
4352
+ RedactedReadAloudDataSchema,
3786
4353
  dictationReferenceWords,
3787
4354
  alignDictation,
3788
4355
  STEP,
@@ -3798,6 +4365,7 @@ export {
3798
4365
  fillInTheBlanksType,
3799
4366
  writtenResponseType,
3800
4367
  gapSelectType,
3801
- dictationType
4368
+ dictationType,
4369
+ readAloudType
3802
4370
  };
3803
- //# sourceMappingURL=chunk-XKKKWKRH.js.map
4371
+ //# sourceMappingURL=chunk-6RND47QE.js.map