@intellectif/lk-core 1.1.0 → 1.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 (64) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -1
  3. package/dist/{activity-MBOYLBjR.d.cts → activity-D705CXng.d.cts} +324 -2
  4. package/dist/{activity-MBOYLBjR.d.ts → activity-D705CXng.d.ts} +324 -2
  5. package/dist/ai-check.cjs +174 -12
  6. package/dist/ai-check.cjs.map +1 -1
  7. package/dist/ai-check.d.cts +15 -3
  8. package/dist/ai-check.d.ts +15 -3
  9. package/dist/ai-check.js +166 -4
  10. package/dist/ai-check.js.map +1 -1
  11. package/dist/{chunk-TJJFKKH2.cjs → chunk-46BXIGLG.cjs} +4 -4
  12. package/dist/{chunk-TJJFKKH2.cjs.map → chunk-46BXIGLG.cjs.map} +1 -1
  13. package/dist/{chunk-VSH7ECQL.cjs → chunk-4YANYTFN.cjs} +43 -43
  14. package/dist/{chunk-VSH7ECQL.cjs.map → chunk-4YANYTFN.cjs.map} +1 -1
  15. package/dist/chunk-7KOWU4RO.js +2207 -0
  16. package/dist/chunk-7KOWU4RO.js.map +1 -0
  17. package/dist/{chunk-DSDXL3JO.cjs → chunk-7THXD7AN.cjs} +657 -197
  18. package/dist/chunk-7THXD7AN.cjs.map +1 -0
  19. package/dist/chunk-CYCTIXUN.cjs +118 -0
  20. package/dist/chunk-CYCTIXUN.cjs.map +1 -0
  21. package/dist/chunk-CYPA5BW5.js +118 -0
  22. package/dist/chunk-CYPA5BW5.js.map +1 -0
  23. package/dist/{chunk-SJVRUPJT.js → chunk-JTNDIBZO.js} +2 -2
  24. package/dist/{chunk-XS6U2ARZ.js → chunk-MV2N2WNS.js} +6 -112
  25. package/dist/chunk-MV2N2WNS.js.map +1 -0
  26. package/dist/chunk-MXI26H62.cjs +2207 -0
  27. package/dist/chunk-MXI26H62.cjs.map +1 -0
  28. package/dist/{chunk-FBKTION6.js → chunk-QS7B42ZR.js} +640 -180
  29. package/dist/chunk-QS7B42ZR.js.map +1 -0
  30. package/dist/{chunk-IARLJ5MW.js → chunk-QX2ERL72.js} +2 -2
  31. package/dist/{chunk-2JT2WUQ2.cjs → chunk-ZWXQSKCX.cjs} +14 -120
  32. package/dist/chunk-ZWXQSKCX.cjs.map +1 -0
  33. package/dist/{index-DlcqkM_L.d.ts → index-BChYtrEe.d.ts} +1 -1
  34. package/dist/{index-DlCW0XaQ.d.cts → index-VXt_v3YI.d.cts} +1 -1
  35. package/dist/index.cjs +68 -593
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +245 -60
  38. package/dist/index.d.ts +245 -60
  39. package/dist/index.js +68 -593
  40. package/dist/index.js.map +1 -1
  41. package/dist/schemas.cjs +5 -3
  42. package/dist/schemas.cjs.map +1 -1
  43. package/dist/schemas.d.cts +2 -2
  44. package/dist/schemas.d.ts +2 -2
  45. package/dist/schemas.js +5 -3
  46. package/dist/scoring.cjs +3 -3
  47. package/dist/scoring.d.cts +2 -2
  48. package/dist/scoring.d.ts +2 -2
  49. package/dist/scoring.js +2 -2
  50. package/dist/xapi.cjs +3 -3
  51. package/dist/xapi.d.cts +1 -1
  52. package/dist/xapi.d.ts +1 -1
  53. package/dist/xapi.js +2 -2
  54. package/package.json +1 -1
  55. package/dist/chunk-2JT2WUQ2.cjs.map +0 -1
  56. package/dist/chunk-DSDXL3JO.cjs.map +0 -1
  57. package/dist/chunk-FBKTION6.js.map +0 -1
  58. package/dist/chunk-LDFQUZII.cjs +0 -950
  59. package/dist/chunk-LDFQUZII.cjs.map +0 -1
  60. package/dist/chunk-NQHETFDA.js +0 -950
  61. package/dist/chunk-NQHETFDA.js.map +0 -1
  62. package/dist/chunk-XS6U2ARZ.js.map +0 -1
  63. /package/dist/{chunk-SJVRUPJT.js.map → chunk-JTNDIBZO.js.map} +0 -0
  64. /package/dist/{chunk-IARLJ5MW.js.map → chunk-QX2ERL72.js.map} +0 -0
package/dist/index.js CHANGED
@@ -4,33 +4,28 @@ import {
4
4
  validateXAPIStatement,
5
5
  xAPIBuilder,
6
6
  xapiDefinitionFor
7
- } from "./chunk-SJVRUPJT.js";
7
+ } from "./chunk-JTNDIBZO.js";
8
8
  import {
9
- INTERACTIVE_VIDEO_ITEM_TYPES,
10
- ItemGroupSchema,
11
- RedactedItemGroupSchema,
12
- TIMELINE_MAX_CHAPTERS,
13
- TIMELINE_MAX_ITEMS,
14
- TIMELINE_MAX_QUIZZES,
15
- TIMELINE_MAX_TITLE_LENGTH,
16
9
  dictationJsonSchema,
17
10
  fillInTheBlanksJsonSchema,
18
11
  gapSelectJsonSchema,
19
- isInteractiveVideoItemType,
20
12
  itemGroupJsonSchema,
21
13
  jsonSchemaFor,
22
14
  multipleChoiceJsonSchema,
23
15
  readAloudJsonSchema,
24
16
  stimulusJsonSchema,
25
17
  validateActivity,
26
- validateItemGroup,
27
18
  validateMedia,
28
19
  validateOptionMedia,
29
20
  writtenResponseJsonSchema
30
- } from "./chunk-XS6U2ARZ.js";
21
+ } from "./chunk-CYPA5BW5.js";
31
22
  import {
32
23
  AI_COACHING_MAX_TIP_LENGTH,
33
24
  AI_COACHING_MAX_WORDS,
25
+ AI_CRITIQUE_MAX_FIELD_LENGTH,
26
+ AI_CRITIQUE_MAX_FINDINGS,
27
+ AI_DRAFTS_MAX_COUNT,
28
+ AI_DRAFTS_MAX_REPAIRS,
34
29
  AI_EXPLANATION_TYPES,
35
30
  AI_HINT_TYPES,
36
31
  AI_TEXT_MAX_LENGTH,
@@ -38,6 +33,9 @@ import {
38
33
  AI_WRITING_MAX_FIELD_LENGTH,
39
34
  aiAllowedByContent,
40
35
  aiCoachingRequest,
36
+ aiCritiqueRequest,
37
+ aiDraftsRepairRequest,
38
+ aiDraftsRequest,
41
39
  aiExplanationRequest,
42
40
  aiGradeOf,
43
41
  aiHintRequest,
@@ -45,12 +43,24 @@ import {
45
43
  aiWritingFeedbackRequest,
46
44
  buildAiFacts,
47
45
  checkAiCoaching,
46
+ checkAiCritique,
47
+ checkAiDrafts,
48
48
  checkAiExplanation,
49
49
  checkAiHint,
50
50
  checkAiWritingFeedback,
51
+ createDraft,
52
+ createInteractiveVideoDraft,
53
+ createItemGroupDraft,
54
+ critiqueDraft,
55
+ critiqueDrafts,
56
+ critiqueItemGroupDraft,
57
+ generateDrafts,
51
58
  hintRevealsAnswer,
52
- readAiTextResult
53
- } from "./chunk-NQHETFDA.js";
59
+ interactiveVideoFromDrafts,
60
+ readAiTextResult,
61
+ validateDraft,
62
+ validateItemGroupDraft
63
+ } from "./chunk-7KOWU4RO.js";
54
64
  import {
55
65
  DEFAULT_ITEM_SCORING_POLICY,
56
66
  DEFAULT_PASS_THRESHOLD,
@@ -75,7 +85,17 @@ import {
75
85
  scoreTries,
76
86
  validateItemScoringPolicy,
77
87
  validateSpeechAssessment
78
- } from "./chunk-IARLJ5MW.js";
88
+ } from "./chunk-QX2ERL72.js";
89
+ import {
90
+ INTERACTIVE_VIDEO_ITEM_TYPES,
91
+ RedactedItemGroupSchema,
92
+ TIMELINE_MAX_CHAPTERS,
93
+ TIMELINE_MAX_ITEMS,
94
+ TIMELINE_MAX_QUIZZES,
95
+ TIMELINE_MAX_TITLE_LENGTH,
96
+ isInteractiveVideoItemType,
97
+ validateItemGroup
98
+ } from "./chunk-MV2N2WNS.js";
79
99
  import {
80
100
  ActivitySchemaError,
81
101
  DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
@@ -83,9 +103,7 @@ import {
83
103
  DICTATION_MAX_EQUIVALENCE_LENGTH,
84
104
  DICTATION_MAX_TEXT_LENGTH,
85
105
  DICTATION_MAX_TRANSCRIPT_LENGTH,
86
- DRAFT_ISSUE_SEVERITY,
87
106
  DeferredScoringError,
88
- GROUP_CAPTIONS_REVEAL_DICTATION,
89
107
  MEDIA_FIELD_POLICY,
90
108
  READ_ALOUD_MAX_DIMENSION_WEIGHT,
91
109
  READ_ALOUD_MAX_REFERENCE_LENGTH,
@@ -95,34 +113,22 @@ import {
95
113
  SPEECH_ASSESSMENT_MAX_WORDS,
96
114
  UnknownActivityTypeError,
97
115
  alignDictation,
98
- checkMedia,
99
116
  countWords,
100
- coveredPathsOf,
101
- covers,
102
117
  defineActivityType,
103
118
  dictationReferenceWords,
104
119
  dictationType,
105
120
  fillInTheBlanksType,
106
121
  gapSelectType,
107
122
  getActivityTypeDescriptor,
108
- groupCaptionsField,
109
- isMissingId,
110
- isRecord,
111
- isUnset,
112
- isUnwritten,
113
- issue,
114
123
  levenshteinDistance,
115
124
  matchText,
116
125
  multipleChoiceType,
117
- pathKey,
118
126
  readAloudType,
119
127
  readSchema,
120
- refusesEmpty,
121
128
  registerActivityType,
122
129
  registeredActivityTypes,
123
- withValidationScope,
124
130
  writtenResponseType
125
- } from "./chunk-FBKTION6.js";
131
+ } from "./chunk-QS7B42ZR.js";
126
132
 
127
133
  // src/content-hash.ts
128
134
  function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
@@ -813,551 +819,6 @@ function diffResponses(before, after) {
813
819
  return entries;
814
820
  }
815
821
 
816
- // src/authoring/item-group.ts
817
- function createItemGroupDraft(context) {
818
- const issued = /* @__PURE__ */ new Set();
819
- const newId = () => {
820
- const id = context.newId();
821
- if (typeof id !== "string" || id === "") {
822
- throw new Error("createItemGroupDraft: newId() must return a non-empty string.");
823
- }
824
- if (issued.has(id)) {
825
- throw new Error(
826
- `createItemGroupDraft: newId() returned "${id}" twice. Ids within one draft must differ.`
827
- );
828
- }
829
- issued.add(id);
830
- return id;
831
- };
832
- return {
833
- schemaVersion: "1.0",
834
- type: "item-group",
835
- id: newId(),
836
- stimulus: { id: newId(), kind: "text", body: "" },
837
- items: []
838
- };
839
- }
840
- function createInteractiveVideoDraft(context) {
841
- const draft = createItemGroupDraft(context);
842
- return {
843
- ...draft,
844
- stimulus: { id: draft.stimulus.id, kind: "video", media: { type: "video", url: "" } },
845
- timeline: { cues: [] }
846
- };
847
- }
848
- function validateItemGroupDraft(draft) {
849
- return withValidationScope(() => validateItemGroupDraftInScope(draft));
850
- }
851
- function validateItemGroupDraftInScope(draft) {
852
- const issues = [];
853
- if (!isRecord(draft)) {
854
- issues.push(
855
- issue("ig_not_an_object", [], "A group must be an object with a stimulus and its items.")
856
- );
857
- return { status: "invalid", issues };
858
- }
859
- issues.push(...checkGroupIdentity(draft));
860
- if (isRecord(draft.stimulus)) {
861
- issues.push(...checkStimulus(draft.stimulus));
862
- } else if (isUnset(draft.stimulus)) {
863
- issues.push(
864
- issue(
865
- "ig_stimulus_required",
866
- ["stimulus"],
867
- "Add the passage, recording or image the questions are about."
868
- )
869
- );
870
- }
871
- issues.push(...checkItems(draft));
872
- if (draft.timeline !== void 0 && draft.timeline !== null) {
873
- issues.push(...checkTimeline(draft));
874
- }
875
- const captions = groupCaptionsField(draft, (captionsUrl) => !isUnwritten(captionsUrl));
876
- if (captions !== void 0) {
877
- issues.push(
878
- issue(
879
- "dc_captions_not_allowed",
880
- ["stimulus", "media", captions],
881
- GROUP_CAPTIONS_REVEAL_DICTATION
882
- )
883
- );
884
- }
885
- const parsed = ItemGroupSchema.safeParse(draft, { reportInput: true });
886
- if (!parsed.success) {
887
- for (const schemaIssue of parsed.error.issues) {
888
- const path = schemaIssue.path.map(String);
889
- if (issues.some((known) => covers(known.path, path))) {
890
- continue;
891
- }
892
- if (path.length > 0 && refusesEmpty(draft, schemaIssue)) {
893
- issues.push(
894
- issue(
895
- "null_not_allowed",
896
- path,
897
- "This field cannot be null. Leave it out if it is optional, or give it a value."
898
- )
899
- );
900
- continue;
901
- }
902
- issues.push({
903
- path,
904
- message: schemaIssue.message,
905
- code: schemaIssue.code,
906
- severity: "invalid"
907
- });
908
- }
909
- }
910
- const stored = validateItemGroup(draft);
911
- if (issues.length === 0 && stored.success) {
912
- return { status: "complete", data: stored.data, issues };
913
- }
914
- if (issues.length === 0) {
915
- for (const error of stored.success ? [] : stored.errors) {
916
- issues.push({ ...error, severity: "invalid" });
917
- }
918
- }
919
- const invalid = issues.some((found) => found.severity !== "incomplete");
920
- return { status: invalid ? "invalid" : "incomplete", issues };
921
- }
922
- function checkTimeline(draft) {
923
- const issues = [];
924
- const timeline = draft.timeline;
925
- if (!isRecord(timeline)) {
926
- return issues;
927
- }
928
- const stimulus = isRecord(draft.stimulus) ? draft.stimulus : void 0;
929
- if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") {
930
- issues.push(
931
- issue(
932
- "ig_timeline_stimulus_kind",
933
- ["stimulus", "kind"],
934
- "An interactive video needs a video: quizzes open at moments of it."
935
- )
936
- );
937
- } else if (stimulus !== void 0 && isRecord(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") {
938
- issues.push(
939
- issue(
940
- "ig_timeline_stimulus_kind",
941
- ["stimulus", "media", "type"],
942
- "Upload a video file. An embedded player from another site cannot be paused when a quiz opens."
943
- )
944
- );
945
- }
946
- if (draft.shuffle === "within-group") {
947
- issues.push(
948
- issue(
949
- "ig_timeline_shuffle",
950
- ["shuffle"],
951
- "The questions of an interactive video appear in the order their quizzes open, so they cannot be shuffled."
952
- )
953
- );
954
- }
955
- const items = Array.isArray(draft.items) ? draft.items : [];
956
- const itemIndexById = /* @__PURE__ */ new Map();
957
- items.forEach((item, index) => {
958
- if (!isRecord(item)) {
959
- return;
960
- }
961
- if (typeof item.id === "string" && item.id !== "") {
962
- itemIndexById.set(item.id, index);
963
- }
964
- const type = item.type;
965
- if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) {
966
- issues.push(
967
- issue(
968
- "ig_timeline_item_type",
969
- ["items", index, "type"],
970
- `An interactive video can hold ${INTERACTIVE_VIDEO_ITEM_TYPES.join(", ")} questions only.`
971
- )
972
- );
973
- }
974
- if (type === "dictation" && isUnset(item.media)) {
975
- issues.push(
976
- issue(
977
- "ig_timeline_dictation_media",
978
- ["items", index, "media"],
979
- "Add the recording this dictation plays: inside a video it cannot play the video."
980
- )
981
- );
982
- }
983
- });
984
- const cues = Array.isArray(timeline.cues) ? timeline.cues : [];
985
- const quizIds = /* @__PURE__ */ new Set();
986
- const placed = /* @__PURE__ */ new Set();
987
- cues.forEach((cue, cueIndex) => {
988
- if (!isRecord(cue)) {
989
- return;
990
- }
991
- const at = (field, ...rest) => [
992
- "timeline",
993
- "cues",
994
- cueIndex,
995
- field,
996
- ...rest
997
- ];
998
- if (isMissingId(cue.id)) {
999
- issues.push(issue("ig_timeline_quiz_id_required", at("id"), "This quiz has no id."));
1000
- } else if (typeof cue.id === "string") {
1001
- if (quizIds.has(cue.id)) {
1002
- issues.push(
1003
- issue("ig_timeline_quiz_id_duplicate", at("id"), `Two quizzes share the id "${cue.id}".`)
1004
- );
1005
- }
1006
- quizIds.add(cue.id);
1007
- }
1008
- if (isUnset(cue.at)) {
1009
- issues.push(
1010
- issue(
1011
- "ig_timeline_quiz_time_required",
1012
- at("at"),
1013
- "Choose when in the video this quiz opens."
1014
- )
1015
- );
1016
- } else if (typeof cue.at !== "number" || !Number.isFinite(cue.at) || cue.at < 0) {
1017
- issues.push(
1018
- issue(
1019
- "ig_timeline_quiz_time_invalid",
1020
- at("at"),
1021
- "A quiz opens at a number of seconds, 0 or more."
1022
- )
1023
- );
1024
- }
1025
- if (isUnset(cue.itemIds) || Array.isArray(cue.itemIds) && cue.itemIds.length === 0) {
1026
- issues.push(
1027
- issue("ig_timeline_quiz_empty", at("itemIds"), "Add at least one question to this quiz.")
1028
- );
1029
- return;
1030
- }
1031
- if (!Array.isArray(cue.itemIds)) {
1032
- return;
1033
- }
1034
- cue.itemIds.forEach((itemId, position) => {
1035
- if (typeof itemId !== "string") {
1036
- return;
1037
- }
1038
- if (!itemIndexById.has(itemId)) {
1039
- issues.push(
1040
- issue(
1041
- "ig_timeline_quiz_unknown_item",
1042
- at("itemIds", position),
1043
- `This quiz names a question that is not in the video ("${itemId}").`
1044
- )
1045
- );
1046
- } else if (placed.has(itemId)) {
1047
- issues.push(
1048
- issue(
1049
- "ig_timeline_item_duplicate",
1050
- at("itemIds", position),
1051
- "This question is already in a quiz: each question belongs to one quiz."
1052
- )
1053
- );
1054
- }
1055
- placed.add(itemId);
1056
- });
1057
- });
1058
- for (const [itemId, index] of itemIndexById) {
1059
- if (!placed.has(itemId)) {
1060
- issues.push(
1061
- issue(
1062
- "ig_timeline_item_unplaced",
1063
- ["items", index],
1064
- "Add this question to a quiz, or the video never shows it."
1065
- )
1066
- );
1067
- }
1068
- }
1069
- const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
1070
- let previousAt;
1071
- chapters.forEach((chapter, index) => {
1072
- if (!isRecord(chapter)) {
1073
- return;
1074
- }
1075
- if (isUnwritten(chapter.title)) {
1076
- issues.push(
1077
- issue(
1078
- "ig_timeline_chapter_title",
1079
- ["timeline", "chapters", index, "title"],
1080
- "Give this chapter a title."
1081
- )
1082
- );
1083
- }
1084
- if (typeof chapter.at === "number" && Number.isFinite(chapter.at)) {
1085
- if (previousAt !== void 0 && chapter.at <= previousAt) {
1086
- issues.push(
1087
- issue(
1088
- "ig_timeline_chapter_order",
1089
- ["timeline", "chapters", index, "at"],
1090
- "Chapters must start in order, each after the one before it."
1091
- )
1092
- );
1093
- }
1094
- previousAt = chapter.at;
1095
- }
1096
- });
1097
- return issues;
1098
- }
1099
- function checkGroupIdentity(draft) {
1100
- const issues = [];
1101
- if (draft.schemaVersion !== "1.0") {
1102
- issues.push(
1103
- issue(
1104
- "schema_version_invalid",
1105
- ["schemaVersion"],
1106
- 'The draft must have schemaVersion "1.0". A draft from createItemGroupDraft has it.'
1107
- )
1108
- );
1109
- }
1110
- if (draft.type !== "item-group") {
1111
- issues.push(issue("type_mismatch", ["type"], `The draft's type must be "item-group".`));
1112
- }
1113
- if (isMissingId(draft.id)) {
1114
- issues.push(issue("id_required", ["id"], "The group has no id."));
1115
- }
1116
- if (!isUnset(draft.shuffle) && draft.shuffle !== "none" && draft.shuffle !== "within-group") {
1117
- issues.push(
1118
- issue(
1119
- "ig_shuffle_invalid",
1120
- ["shuffle"],
1121
- 'Shuffling is either "none" or "within-group". Leave it out to keep the authored order.'
1122
- )
1123
- );
1124
- }
1125
- return issues;
1126
- }
1127
- var KINDS = /* @__PURE__ */ new Set(["text", "audio", "video", "image", "mixed"]);
1128
- var MEDIA_FOR_KIND = {
1129
- audio: ["audio"],
1130
- video: ["video", "embed"],
1131
- image: ["image"],
1132
- mixed: ["audio", "video", "image", "embed"]
1133
- };
1134
- function checkStimulus(stimulus) {
1135
- const issues = [];
1136
- const at = (...rest) => ["stimulus", ...rest];
1137
- if (isMissingId(stimulus.id)) {
1138
- issues.push(issue("ig_stimulus_id_required", at("id"), "The stimulus has no id."));
1139
- }
1140
- const kind = stimulus.kind;
1141
- if (isUnwritten(kind)) {
1142
- issues.push(
1143
- issue(
1144
- "ig_stimulus_kind_required",
1145
- at("kind"),
1146
- "Choose what the questions are about: a text, a recording, a video, an image, or a mix."
1147
- )
1148
- );
1149
- } else if (typeof kind !== "string" || !KINDS.has(kind)) {
1150
- if (typeof kind === "string") {
1151
- issues.push(
1152
- issue(
1153
- "ig_stimulus_kind_invalid",
1154
- at("kind"),
1155
- `"${kind}" is not a stimulus kind. Use text, audio, video, image or mixed.`
1156
- )
1157
- );
1158
- }
1159
- if (isRecord(stimulus.media)) {
1160
- issues.push(...checkMedia(stimulus.media, at("media")));
1161
- }
1162
- return issues;
1163
- }
1164
- const hasBody = typeof stimulus.body === "string" && stimulus.body.trim() !== "";
1165
- const htmlPresent = !isUnset(stimulus.bodyHtml);
1166
- if (!hasBody && (kind === "text" || kind === "mixed" || htmlPresent)) {
1167
- issues.push(
1168
- issue(
1169
- "ig_stimulus_body_required",
1170
- at("body"),
1171
- isUnwritten(stimulus.bodyHtml) ? "Write the passage." : "Write the passage as plain text too. The rich-text version is shown only where a sanitiser is supplied."
1172
- )
1173
- );
1174
- }
1175
- if (kind !== "text" && isUnset(stimulus.media)) {
1176
- issues.push(
1177
- issue("ig_stimulus_media_required", at("media"), "Add the recording, video or image.")
1178
- );
1179
- } else if (isRecord(stimulus.media)) {
1180
- const allowed = MEDIA_FOR_KIND[kind];
1181
- const mediaType = stimulus.media.type;
1182
- if (allowed !== void 0 && typeof mediaType === "string" && !allowed.includes(mediaType)) {
1183
- issues.push(
1184
- issue(
1185
- "ig_stimulus_media_kind",
1186
- at("media", "type"),
1187
- `A "${kind}" stimulus cannot carry ${mediaType} media. Use ${allowed.join(" or ")}.`
1188
- )
1189
- );
1190
- }
1191
- issues.push(...checkMedia(stimulus.media, at("media")));
1192
- }
1193
- return issues;
1194
- }
1195
- function checkItems(draft) {
1196
- const issues = [];
1197
- const items = isUnset(draft.items) ? [] : draft.items;
1198
- if (!Array.isArray(items)) {
1199
- return issues;
1200
- }
1201
- if (items.length === 0) {
1202
- issues.push(
1203
- issue("ig_items_required", ["items"], "Add at least one question about this material.")
1204
- );
1205
- return issues;
1206
- }
1207
- const seen = /* @__PURE__ */ new Set();
1208
- const repeated = /* @__PURE__ */ new Set();
1209
- for (const [index, item] of items.entries()) {
1210
- if (!isRecord(item)) {
1211
- continue;
1212
- }
1213
- const ordinal = index + 1;
1214
- if (isMissingId(item.id)) {
1215
- issues.push(
1216
- issue("ig_item_id_required", ["items", index, "id"], `Question ${ordinal} has no id.`)
1217
- );
1218
- } else if (typeof item.id === "string") {
1219
- if (seen.has(item.id)) {
1220
- repeated.add(item.id);
1221
- }
1222
- seen.add(item.id);
1223
- }
1224
- if (item.type === "item-group") {
1225
- issues.push(
1226
- issue(
1227
- "ig_item_nested_group",
1228
- ["items", index, "type"],
1229
- "Groups do not nest: every item must be an activity."
1230
- )
1231
- );
1232
- continue;
1233
- }
1234
- if (isUnwritten(item.type)) {
1235
- issues.push(
1236
- issue(
1237
- "ig_item_type_required",
1238
- ["items", index, "type"],
1239
- `Choose what kind of question ${ordinal} is.`
1240
- )
1241
- );
1242
- continue;
1243
- }
1244
- if (typeof item.type !== "string") {
1245
- continue;
1246
- }
1247
- let result;
1248
- try {
1249
- result = validateDraft(item.type, item);
1250
- } catch (error) {
1251
- if (!(error instanceof UnknownActivityTypeError) || error.activityType !== item.type) {
1252
- throw error;
1253
- }
1254
- issues.push(
1255
- issue(
1256
- "ig_item_type_unknown",
1257
- ["items", index, "type"],
1258
- `"${item.type}" is not a registered activity type.`
1259
- )
1260
- );
1261
- continue;
1262
- }
1263
- for (const found of result.issues) {
1264
- issues.push({ ...found, path: ["items", String(index), ...found.path] });
1265
- }
1266
- }
1267
- for (const id of repeated) {
1268
- issues.push(
1269
- issue("ig_item_id_duplicate", ["items"], `More than one question has the id "${id}".`)
1270
- );
1271
- }
1272
- return issues;
1273
- }
1274
-
1275
- // src/authoring/index.ts
1276
- function validateDraft(type, draft) {
1277
- return withValidationScope(() => validateDraftInScope(type, draft));
1278
- }
1279
- function validateDraftInScope(type, draft) {
1280
- const descriptor = getActivityTypeDescriptor(type);
1281
- if (descriptor === void 0) {
1282
- throw new UnknownActivityTypeError(String(type));
1283
- }
1284
- const reported = isRecord(draft) && descriptor.authoring?.checkDraft !== void 0 ? descriptor.authoring.checkDraft(draft).map(normalise) : [];
1285
- const issues = [...reported];
1286
- const parsed = readSchema(descriptor.schema, draft, `Activity type "${descriptor.type}"`, {
1287
- reportInput: true
1288
- });
1289
- if (!parsed.success) {
1290
- const covered = coveredPathsOf(reported);
1291
- const reportedEmpty = /* @__PURE__ */ new Set();
1292
- for (const schemaIssue of parsed.issues) {
1293
- const path = schemaIssue.path.map(String);
1294
- if (covered.has(pathKey(path))) {
1295
- continue;
1296
- }
1297
- if (path.length > 0 && refusesEmpty(draft, schemaIssue)) {
1298
- const key = JSON.stringify(path);
1299
- if (!reportedEmpty.has(key)) {
1300
- reportedEmpty.add(key);
1301
- issues.push(
1302
- issue(
1303
- "null_not_allowed",
1304
- path,
1305
- typeof schemaIssue.path.at(-1) === "number" ? "This entry has no value. Remove it, or fill it in." : "This field cannot be null. Leave it out if it is optional, or give it a value."
1306
- )
1307
- );
1308
- }
1309
- continue;
1310
- }
1311
- issues.push({
1312
- path,
1313
- message: schemaIssue.message,
1314
- code: schemaIssue.code,
1315
- severity: "invalid"
1316
- });
1317
- }
1318
- }
1319
- if (parsed.success && issues.length === 0) {
1320
- return { status: "complete", data: parsed.data, issues };
1321
- }
1322
- const invalid = issues.some((found) => found.severity !== "incomplete");
1323
- return { status: invalid ? "invalid" : "incomplete", issues };
1324
- }
1325
- function createDraft(type, context) {
1326
- const descriptor = getActivityTypeDescriptor(type);
1327
- if (descriptor === void 0) {
1328
- throw new UnknownActivityTypeError(String(type));
1329
- }
1330
- const create = descriptor.authoring?.createDraft;
1331
- if (create === void 0) {
1332
- throw new Error(
1333
- `Activity type "${String(type)}" declares no authoring.createDraft, so there is no empty draft to create. Build the draft yourself; validateDraft works for every registered type.`
1334
- );
1335
- }
1336
- const issued = /* @__PURE__ */ new Set();
1337
- const newId = () => {
1338
- const id = context.newId();
1339
- if (typeof id !== "string" || id === "") {
1340
- throw new Error("createDraft: newId() must return a non-empty string.");
1341
- }
1342
- if (issued.has(id)) {
1343
- throw new Error(
1344
- `createDraft: newId() returned "${id}" twice. Ids within one draft must differ \u2014 two options sharing an id cannot be scored apart.`
1345
- );
1346
- }
1347
- issued.add(id);
1348
- return id;
1349
- };
1350
- return create({ newId });
1351
- }
1352
- function normalise(found) {
1353
- const documented = Object.hasOwn(DRAFT_ISSUE_SEVERITY, found.code) ? DRAFT_ISSUE_SEVERITY[found.code] : void 0;
1354
- return {
1355
- ...found,
1356
- path: found.path.map(String),
1357
- severity: documented ?? (found.severity === "incomplete" ? "incomplete" : "invalid")
1358
- };
1359
- }
1360
-
1361
822
  // src/redact.ts
1362
823
  function mergePolicy(base, overrides) {
1363
824
  if (overrides === void 0) {
@@ -1435,10 +896,10 @@ function redact(data, options = {}) {
1435
896
  if (!parsed.success) {
1436
897
  throw new ActivitySchemaError(
1437
898
  descriptor.type,
1438
- parsed.issues.map((issue2) => ({
1439
- path: issue2.path.map(String),
1440
- message: issue2.message,
1441
- code: issue2.code
899
+ parsed.issues.map((issue) => ({
900
+ path: issue.path.map(String),
901
+ message: issue.message,
902
+ code: issue.code
1442
903
  }))
1443
904
  );
1444
905
  }
@@ -1483,10 +944,10 @@ function assertRedacted(data) {
1483
944
  if (!parsed.success) {
1484
945
  throw new ActivitySchemaError(
1485
946
  descriptor.type,
1486
- parsed.issues.map((issue2) => ({
1487
- path: issue2.path.map(String),
1488
- message: issue2.message,
1489
- code: issue2.code
947
+ parsed.issues.map((issue) => ({
948
+ path: issue.path.map(String),
949
+ message: issue.message,
950
+ code: issue.code
1490
951
  }))
1491
952
  );
1492
953
  }
@@ -1545,10 +1006,10 @@ function redactItemGroup(group, options = {}) {
1545
1006
  if (!parsed.success) {
1546
1007
  throw new ActivitySchemaError(
1547
1008
  "item-group",
1548
- parsed.error.issues.map((issue2) => ({
1549
- path: issue2.path.map(String),
1550
- message: issue2.message,
1551
- code: issue2.code
1009
+ parsed.error.issues.map((issue) => ({
1010
+ path: issue.path.map(String),
1011
+ message: issue.message,
1012
+ code: issue.code
1552
1013
  }))
1553
1014
  );
1554
1015
  }
@@ -1575,10 +1036,10 @@ function assertRedactedItemGroup(data) {
1575
1036
  if (!parsed.success) {
1576
1037
  throw new ActivitySchemaError(
1577
1038
  "item-group",
1578
- parsed.error.issues.map((issue2) => ({
1579
- path: issue2.path.map(String),
1580
- message: issue2.message,
1581
- code: issue2.code
1039
+ parsed.error.issues.map((issue) => ({
1040
+ path: issue.path.map(String),
1041
+ message: issue.message,
1042
+ code: issue.code
1582
1043
  }))
1583
1044
  );
1584
1045
  }
@@ -1589,9 +1050,9 @@ function assertRedactedItemGroup(data) {
1589
1050
  if (error instanceof ActivitySchemaError) {
1590
1051
  throw new ActivitySchemaError(
1591
1052
  "item-group",
1592
- error.errors.map((issue2) => ({
1593
- ...issue2,
1594
- path: ["items", String(index), ...issue2.path]
1053
+ error.errors.map((issue) => ({
1054
+ ...issue,
1055
+ path: ["items", String(index), ...issue.path]
1595
1056
  }))
1596
1057
  );
1597
1058
  }
@@ -1637,6 +1098,10 @@ function composeTimelineScore(entryKey, items, policy) {
1637
1098
  export {
1638
1099
  AI_COACHING_MAX_TIP_LENGTH,
1639
1100
  AI_COACHING_MAX_WORDS,
1101
+ AI_CRITIQUE_MAX_FIELD_LENGTH,
1102
+ AI_CRITIQUE_MAX_FINDINGS,
1103
+ AI_DRAFTS_MAX_COUNT,
1104
+ AI_DRAFTS_MAX_REPAIRS,
1640
1105
  AI_EXPLANATION_TYPES,
1641
1106
  AI_HINT_TYPES,
1642
1107
  AI_TEXT_MAX_LENGTH,
@@ -1669,6 +1134,9 @@ export {
1669
1134
  XAPI_VERB_DISPLAY,
1670
1135
  aiAllowedByContent,
1671
1136
  aiCoachingRequest,
1137
+ aiCritiqueRequest,
1138
+ aiDraftsRepairRequest,
1139
+ aiDraftsRequest,
1672
1140
  aiExplanationRequest,
1673
1141
  aiGradeOf,
1674
1142
  aiHintRequest,
@@ -1681,6 +1149,8 @@ export {
1681
1149
  buildAiFacts,
1682
1150
  canonicalJson,
1683
1151
  checkAiCoaching,
1152
+ checkAiCritique,
1153
+ checkAiDrafts,
1684
1154
  checkAiExplanation,
1685
1155
  checkAiHint,
1686
1156
  checkAiWritingFeedback,
@@ -1694,6 +1164,9 @@ export {
1694
1164
  createDraft,
1695
1165
  createInteractiveVideoDraft,
1696
1166
  createItemGroupDraft,
1167
+ critiqueDraft,
1168
+ critiqueDrafts,
1169
+ critiqueItemGroupDraft,
1697
1170
  defineActivityType,
1698
1171
  dictationJsonSchema,
1699
1172
  dictationReferenceWords,
@@ -1709,6 +1182,7 @@ export {
1709
1182
  flattenSequence,
1710
1183
  gapSelectJsonSchema,
1711
1184
  gapSelectType,
1185
+ generateDrafts,
1712
1186
  getActivityTypeDescriptor,
1713
1187
  gradeFromRubric,
1714
1188
  gradeReadAloud,
@@ -1717,6 +1191,7 @@ export {
1717
1191
  hashSeed,
1718
1192
  hintRevealsAnswer,
1719
1193
  inspectWav,
1194
+ interactiveVideoFromDrafts,
1720
1195
  isInteractiveVideoItemType,
1721
1196
  isItemGroup,
1722
1197
  itemGroupJsonSchema,