@intellectif/lk-core 1.2.0 → 1.3.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 (103) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -2
  3. package/dist/ai-B222dtHH.d.cts +2154 -0
  4. package/dist/ai-B222dtHH.d.cts.map +1 -0
  5. package/dist/ai-B222dtHH.d.ts +2154 -0
  6. package/dist/ai-B222dtHH.d.ts.map +1 -0
  7. package/dist/ai-check.cjs +749 -617
  8. package/dist/ai-check.cjs.map +1 -1
  9. package/dist/ai-check.d.cts +82 -80
  10. package/dist/ai-check.d.cts.map +1 -0
  11. package/dist/ai-check.d.ts +82 -80
  12. package/dist/ai-check.d.ts.map +1 -0
  13. package/dist/ai-check.js +747 -618
  14. package/dist/ai-check.js.map +1 -1
  15. package/dist/ai-writing-CCBjTOM3.js +2496 -0
  16. package/dist/ai-writing-CCBjTOM3.js.map +1 -0
  17. package/dist/ai-writing-DCVkv4Zj.cjs +2735 -0
  18. package/dist/ai-writing-DCVkv4Zj.cjs.map +1 -0
  19. package/dist/{index-BChYtrEe.d.ts → index-BWPRPIXL.d.ts} +263 -258
  20. package/dist/index-BWPRPIXL.d.ts.map +1 -0
  21. package/dist/index-CXyOV8sm.d.cts +153 -0
  22. package/dist/index-CXyOV8sm.d.cts.map +1 -0
  23. package/dist/index-CegM8o15.d.ts +153 -0
  24. package/dist/index-CegM8o15.d.ts.map +1 -0
  25. package/dist/index-Dyu66R4-.d.cts +1071 -0
  26. package/dist/index-Dyu66R4-.d.cts.map +1 -0
  27. package/dist/{index-VXt_v3YI.d.cts → index-O4_ExlAD.d.cts} +263 -258
  28. package/dist/index-O4_ExlAD.d.cts.map +1 -0
  29. package/dist/index-UGxRSopE.d.ts +1071 -0
  30. package/dist/index-UGxRSopE.d.ts.map +1 -0
  31. package/dist/index.cjs +1210 -1146
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +909 -857
  34. package/dist/index.d.cts.map +1 -0
  35. package/dist/index.d.ts +909 -857
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1063 -1147
  38. package/dist/index.js.map +1 -1
  39. package/dist/item-group-DXiR8nOr.js +445 -0
  40. package/dist/item-group-DXiR8nOr.js.map +1 -0
  41. package/dist/item-group-DroFXgLb.cjs +505 -0
  42. package/dist/item-group-DroFXgLb.cjs.map +1 -0
  43. package/dist/registry-DVi0LR_i.cjs +5915 -0
  44. package/dist/registry-DVi0LR_i.cjs.map +1 -0
  45. package/dist/registry-Dsx9mNsS.js +5449 -0
  46. package/dist/registry-Dsx9mNsS.js.map +1 -0
  47. package/dist/schemas-Bdw0nGPo.js +152 -0
  48. package/dist/schemas-Bdw0nGPo.js.map +1 -0
  49. package/dist/schemas-DVbAOrtd.cjs +224 -0
  50. package/dist/schemas-DVbAOrtd.cjs.map +1 -0
  51. package/dist/schemas.cjs +16 -34
  52. package/dist/schemas.d.cts +2 -2
  53. package/dist/schemas.d.ts +2 -2
  54. package/dist/schemas.js +3 -34
  55. package/dist/scoring-B2_HEhMT.cjs +1748 -0
  56. package/dist/scoring-B2_HEhMT.cjs.map +1 -0
  57. package/dist/scoring-BZqUkuDF.js +1604 -0
  58. package/dist/scoring-BZqUkuDF.js.map +1 -0
  59. package/dist/scoring.cjs +37 -75
  60. package/dist/scoring.d.cts +3 -1103
  61. package/dist/scoring.d.ts +3 -1103
  62. package/dist/scoring.js +3 -75
  63. package/dist/xapi-CMTph7wG.js +332 -0
  64. package/dist/xapi-CMTph7wG.js.map +1 -0
  65. package/dist/xapi-DNzkSJ0S.cjs +362 -0
  66. package/dist/xapi-DNzkSJ0S.cjs.map +1 -0
  67. package/dist/xapi.cjs +7 -16
  68. package/dist/xapi.d.cts +2 -150
  69. package/dist/xapi.d.ts +2 -150
  70. package/dist/xapi.js +2 -16
  71. package/package.json +4 -4
  72. package/dist/activity-D705CXng.d.cts +0 -2162
  73. package/dist/activity-D705CXng.d.ts +0 -2162
  74. package/dist/chunk-46BXIGLG.cjs +0 -272
  75. package/dist/chunk-46BXIGLG.cjs.map +0 -1
  76. package/dist/chunk-4YANYTFN.cjs +0 -1256
  77. package/dist/chunk-4YANYTFN.cjs.map +0 -1
  78. package/dist/chunk-7KOWU4RO.js +0 -2207
  79. package/dist/chunk-7KOWU4RO.js.map +0 -1
  80. package/dist/chunk-7THXD7AN.cjs +0 -5057
  81. package/dist/chunk-7THXD7AN.cjs.map +0 -1
  82. package/dist/chunk-CYCTIXUN.cjs +0 -118
  83. package/dist/chunk-CYCTIXUN.cjs.map +0 -1
  84. package/dist/chunk-CYPA5BW5.js +0 -118
  85. package/dist/chunk-CYPA5BW5.js.map +0 -1
  86. package/dist/chunk-JTNDIBZO.js +0 -272
  87. package/dist/chunk-JTNDIBZO.js.map +0 -1
  88. package/dist/chunk-MV2N2WNS.js +0 -352
  89. package/dist/chunk-MV2N2WNS.js.map +0 -1
  90. package/dist/chunk-MXI26H62.cjs +0 -2207
  91. package/dist/chunk-MXI26H62.cjs.map +0 -1
  92. package/dist/chunk-QS7B42ZR.js +0 -5057
  93. package/dist/chunk-QS7B42ZR.js.map +0 -1
  94. package/dist/chunk-QX2ERL72.js +0 -1256
  95. package/dist/chunk-QX2ERL72.js.map +0 -1
  96. package/dist/chunk-ZWXQSKCX.cjs +0 -352
  97. package/dist/chunk-ZWXQSKCX.cjs.map +0 -1
  98. package/dist/schemas.cjs.map +0 -1
  99. package/dist/schemas.js.map +0 -1
  100. package/dist/scoring.cjs.map +0 -1
  101. package/dist/scoring.js.map +0 -1
  102. package/dist/xapi.cjs.map +0 -1
  103. package/dist/xapi.js.map +0 -1
@@ -1,2162 +0,0 @@
1
- /**
2
- * Configurable free-text answer matching (R3.3).
3
- *
4
- * Grade-stability contract: the DEFAULTS reproduce the v1 fill-in-the-blanks
5
- * semantics exactly (trim, then locale-insensitive lowercase, strict `===`).
6
- * Every tolerance below is opt-in, because changing a default here changes
7
- * historical grades — and this SDK powers real summative exams.
8
- */
9
- interface TextMatchPolicy {
10
- /** Compare case-sensitively. Default `false` (v1 behaviour). */
11
- caseSensitive?: boolean;
12
- /** Strip leading/trailing whitespace before comparing. Default `true` (v1 behaviour). */
13
- trim?: boolean;
14
- /**
15
- * Unicode normalization applied to both sides before comparing. Default
16
- * `'none'` (v1 behaviour). `'NFC'` makes a decomposed `está` (combining
17
- * U+0301) equal its composed form — the fix for accent-input mismatches in
18
- * EN/ES/PT content.
19
- */
20
- normalize?: 'none' | 'NFC' | 'NFKC';
21
- /** Treat diacritics as equal to their base letters (`está` ≡ `esta`). Default `false`. */
22
- foldDiacritics?: boolean;
23
- /** Collapse runs of inner whitespace to a single space. Default `false`. */
24
- collapseInnerWhitespace?: boolean;
25
- /** Ignore Unicode punctuation on both sides. Default `false`. */
26
- ignorePunctuation?: boolean;
27
- /** Maximum Levenshtein edit distance still accepted as a match. Default `0`. */
28
- levenshtein?: number;
29
- /**
30
- * BCP 47 tag for locale-aware case folding (e.g. `'tr'` for Turkish dotted /
31
- * dotless I). Default: locale-insensitive `toLowerCase()` (v1 behaviour).
32
- */
33
- locale?: string;
34
- }
35
- /**
36
- * How a match was achieved. Lets a consumer award full credit for an `exact`
37
- * match and partial credit for a `folded` or `fuzzy` one (e.g. diacritic
38
- * tolerance in a listening gap-fill vs. a spelling test).
39
- * - `exact` — equal under the baseline trim/case rules alone.
40
- * - `normalized` — required Unicode normalization, whitespace collapse, or punctuation tolerance.
41
- * - `folded` — required diacritic folding.
42
- * - `fuzzy` — required Levenshtein tolerance.
43
- * - `none` — no accepted answer matched.
44
- */
45
- interface TextMatchResult {
46
- matched: boolean;
47
- via: 'exact' | 'normalized' | 'folded' | 'fuzzy' | 'none';
48
- }
49
- /**
50
- * Levenshtein distance with an early-exit bound: returns `max + 1` as soon as
51
- * the distance provably exceeds `max`.
52
- */
53
- declare function levenshteinDistance(a: string, b: string, max: number): number;
54
- /**
55
- * Matches a learner's input against one or more accepted answers under a
56
- * {@link TextMatchPolicy}. Tolerances are evaluated in stages — baseline
57
- * (trim/case), then normalization, then diacritic folding, then Levenshtein —
58
- * and {@link TextMatchResult.via} reports the first stage that produced the
59
- * match, so graders can award credit by match quality.
60
- *
61
- * With no policy (or an empty one) this is byte-for-byte the v1 matching
62
- * semantics: `trim` + locale-insensitive `toLowerCase` + strict equality.
63
- */
64
- declare function matchText(input: string, accepted: string | readonly string[], policy?: TextMatchPolicy): TextMatchResult;
65
-
66
- /**
67
- * Why a draft is not an activity yet.
68
- *
69
- * - `incomplete` — something the author has not written yet: an empty title, an
70
- * option with no text, no option marked correct. The fix is to add it.
71
- * - `invalid` — something is wrong in a way that writing more cannot fix: two
72
- * correct options on a single-selection question, a maximum word count below
73
- * the minimum, a media address with a scheme the SDK refuses, a `null` in an
74
- * optional field that accepts none. The fix is to change it.
75
- */
76
- type DraftSeverity = 'incomplete' | 'invalid';
77
- /**
78
- * One problem with a draft. A {@link ValidationError} with a severity, so code
79
- * that already lists `validateActivity` errors can list these unchanged.
80
- */
81
- interface DraftIssue extends ValidationError {
82
- severity: DraftSeverity;
83
- }
84
- /** A draft that is a valid activity and has nothing left to write. */
85
- interface DraftComplete<T> {
86
- status: 'complete';
87
- /** The validated activity, exactly as `validateActivity` returns it. */
88
- data: T;
89
- /** Always empty. Present so that every result can be read the same way. */
90
- issues: DraftIssue[];
91
- }
92
- /**
93
- * A draft that is not an activity yet: `invalid` when any issue is invalid,
94
- * otherwise `incomplete`. It carries no `data`, because nothing here may be
95
- * stored or rendered as an activity.
96
- */
97
- interface DraftNotComplete {
98
- status: 'incomplete' | 'invalid';
99
- issues: DraftIssue[];
100
- }
101
- /**
102
- * The result of `validateDraft`.
103
- *
104
- * There is deliberately no `success` boolean. A boolean has to call an
105
- * incomplete draft either a success, which lets a half-written answer key
106
- * through anything that checks it, or a failure, which is the conflation this
107
- * result exists to remove. Branch on `status`.
108
- */
109
- type DraftValidationResult<T> = DraftComplete<T> | DraftNotComplete;
110
- /**
111
- * How much a finding of the item critic matters. Neither makes a draft
112
- * incomplete or invalid: an item with findings is a valid item, stored and
113
- * served like any other, and whether to change it is the author's call.
114
- *
115
- * - `warning` — a known flaw a learner can exploit or trip over: the right
116
- * option is the longest, an answer printed in the passage, a hint that gives
117
- * the answer away, two options that read the same.
118
- * - `advice` — an item-writing guideline the item departs from: "all of the
119
- * above", two accepted answers the matcher already treats as one.
120
- */
121
- type ItemFindingSeverity = 'warning' | 'advice';
122
- /**
123
- * One thing the item critic found. A {@link ValidationError} with a severity,
124
- * as a {@link DraftIssue} is, so an editor lists both in one list — sorted by
125
- * severity: `invalid`, `incomplete`, `warning`, `advice`.
126
- */
127
- interface ItemFinding extends ValidationError {
128
- severity: ItemFindingSeverity;
129
- }
130
- /** What `createDraft` hands an activity type's `authoring.createDraft`. */
131
- interface DraftContext {
132
- /**
133
- * Returns a new id on every call. The SDK invents none: an id made here would
134
- * need `crypto`, which plain-http origins do not have, or would be positional —
135
- * and option ids reach the learner, so ids like `"a"` and `"b"` would tell them
136
- * which option was written first.
137
- */
138
- newId: () => string;
139
- }
140
-
141
- /**
142
- * The return trip for deferred grading.
143
- *
144
- * `evaluate()` can say a submission is `deferred` — graded later by an AI or a
145
- * human — but until now there was no type for the grade that comes BACK, so
146
- * every consumer invented one. The shapes here are the intersection of two
147
- * independent production graders (a rubric-based essay grader and a CEFR
148
- * speaking grader) that converged on the same envelope: a normalised total,
149
- * per-criterion scores with comments, grader-authored artefacts, a confidence
150
- * signal, an explicit human-review flag, and provenance.
151
- *
152
- * The SDK models the SHAPE and the arithmetic. It never calls a model, never
153
- * holds a key, and never decides what a rubric means — that stays yours.
154
- */
155
-
156
- /**
157
- * Lifecycle of a deferred grade. A grade exists only in the `graded` state;
158
- * every other state means "no grade, and here is why" — which is precisely the
159
- * distinction that stops an ungraded submission being rendered as a zero.
160
- */
161
- type GradingState = 'queued' | 'running' | 'graded' | 'failed' | 'skipped';
162
- /**
163
- * Who produced a grade.
164
- *
165
- * - `'auto'` — a non-generative measurement engine, for example a
166
- * pronunciation-assessment service.
167
- * - `'ai'` — a generative model.
168
- * - `'human'` — a person.
169
- */
170
- type GraderKind = 'ai' | 'human' | 'auto';
171
- /** Provenance of a grade, so a re-grade two years later is explicable. */
172
- interface Grader {
173
- kind: GraderKind;
174
- /** Identifier of the human grader, when `kind` is `'human'`. */
175
- id?: string;
176
- /** Model identifier, when `kind` is `'ai'` (e.g. a model name and version). */
177
- model?: string;
178
- /** Hash or id of the prompt/rubric revision used, for auditability. */
179
- promptHash?: string;
180
- }
181
- /** Optional cost/usage telemetry emitted by an AI grader. */
182
- interface GraderUsage {
183
- promptTokens?: number;
184
- completionTokens?: number;
185
- costUsd?: number;
186
- }
187
- /** A grader's verdict on one rubric criterion. */
188
- interface CriterionScore {
189
- /** Matches a `WrittenResponseRubricCriterion.name` when a rubric is known. */
190
- name: string;
191
- /**
192
- * The grader's score for this criterion, out of {@link CriterionScore.maxScore}
193
- * (which defaults to 1, the SDK's scaled convention). Absent when the
194
- * criterion is judged on an ordinal `band`.
195
- */
196
- score?: number;
197
- /**
198
- * What `score` is out of. Defaults to `1`.
199
- *
200
- * Real graders rarely work in [0,1]: rubrics come back out of 100, out of 9
201
- * (CEFR/IELTS bands), or out of a points total that differs between
202
- * criteria. Before this field the only way to use {@link gradeFromRubric}
203
- * was to normalise by hand first — and a grader that skipped that step got
204
- * its scores REJECTED, which pushed integrators back to letting the model
205
- * compute the weighted total itself. That is precisely the arithmetic this
206
- * SDK exists to take away from a language model.
207
- *
208
- * Set it per criterion, so a rubric mixing a /100 criterion with a /9 band
209
- * score still composes. `gradeFromRubric` normalises `score / maxScore`
210
- * before weighting; the resulting ratio must land in [0,1].
211
- */
212
- maxScore?: number;
213
- /** Ordinal verdict when the rubric is banded rather than numeric (e.g. `"B1"`). */
214
- band?: string;
215
- /** The grader's comment for this criterion, addressed to the learner. */
216
- comment?: string;
217
- /**
218
- * Weight actually applied, echoed from the rubric so the total is checkable.
219
- * Defaults to `1`. Zero or more: `gradeFromRubric` refuses a negative weight
220
- * rather than let it carry the total outside [0,1].
221
- */
222
- weight?: number;
223
- /** Not applicable to this submission (e.g. interaction on a monologue task). */
224
- notApplicable?: boolean;
225
- }
226
- /** A suggested correction anchored in the learner's own text. */
227
- interface InlineCorrection {
228
- /** Verbatim span from the submission. */
229
- original: string;
230
- /** Suggested replacement. */
231
- corrected: string;
232
- explanation?: string;
233
- /** Character offsets into the submitted text, so a UI can anchor it inline. */
234
- range?: {
235
- start: number;
236
- end: number;
237
- };
238
- /** Error-type tag (`"article"`, `"tense"`, `"register"`). */
239
- category?: string;
240
- }
241
- /**
242
- * A grade that came back from an asynchronous grader. `score` is scaled
243
- * [0,1] against `maxScore`, matching every other score in the SDK. A grader
244
- * that works in points does not have to normalise by hand: declare
245
- * `maxScore` on each {@link CriterionScore} and let `gradeFromRubric` do the
246
- * arithmetic, which is the whole point of handing it the judgements.
247
- *
248
- * `maxScore` is a positive, finite number and `score` a finite number from 0
249
- * to it. `outcomeFromGrade` refuses a record that is not, rather than mirror
250
- * numbers nobody can defend onto an outcome.
251
- */
252
- interface GradeRecord {
253
- score: number;
254
- maxScore: number;
255
- passed: boolean;
256
- /** Narrative feedback addressed to the learner. */
257
- feedback: string | null;
258
- /** Per-criterion breakdown, when the grader worked against a rubric. */
259
- criteria?: CriterionScore[];
260
- /**
261
- * Per-item marks carried by a grade — for example the per-word marks of a
262
- * read-aloud grade. `outcomeFromGrade` does not mirror them onto the
263
- * outcome; read `outcome.grade.details`.
264
- */
265
- details?: ScoringDetail[];
266
- /** Corrections anchored in the learner's text. */
267
- corrections?: InlineCorrection[];
268
- /** Supporting observations the grader cited. */
269
- evidence?: string[];
270
- /** How the grader reached this verdict. */
271
- rationale?: string;
272
- /** The grader's own confidence — a signal for routing to human review. */
273
- confidence?: 'high' | 'medium' | 'low';
274
- /** The grader is unsure or the submission is atypical; route to a human. */
275
- requiresHumanReview?: boolean;
276
- grader?: Grader;
277
- usage?: GraderUsage;
278
- /** ISO 8601 timestamp of when the grade was produced. */
279
- gradedAt?: string;
280
- }
281
-
282
- /**
283
- * The contract between an activity and a model the HOST runs, for help a
284
- * learner reads: an explanation of a graded answer, and hints before one.
285
- *
286
- * The SDK never calls a model and holds no key, prompt or provider. It builds
287
- * the facts a model is given — from the item, the learner's answer and the
288
- * SDK's own scorer — and checks what comes back. The model explains a verdict
289
- * the SDK reached; it never reaches one.
290
- */
291
-
292
- /**
293
- * What an author allows AI to do for one item. Absent, or a feature left out,
294
- * leaves the choice to the deployment. `false` switches a feature off for this
295
- * item wherever it is delivered; an author can only switch a feature off,
296
- * never force one on.
297
- *
298
- * Switch hints off where any hint would give the answer away — a
299
- * one-word vocabulary item, say.
300
- */
301
- interface ActivityAiPermissions {
302
- /**
303
- * An explanation of the learner's graded answer — and, on a written
304
- * response, feedback on a draft, and on a read-aloud, coaching on the
305
- * marks: each is a model's words about the learner's own answer.
306
- */
307
- explanations?: boolean;
308
- /** Hints before the learner submits. */
309
- hints?: boolean;
310
- }
311
- /**
312
- * An activity as a renderer holds it: full data or a `redact()` projection.
313
- * Structural rather than the data union, so a renderer's own view of an item —
314
- * whose answer-key fields may be absent — is accepted as it is. The AI
315
- * functions read every field defensively.
316
- */
317
- interface AiActivityInput {
318
- readonly type: string;
319
- readonly id: string;
320
- readonly title: string;
321
- readonly locale?: unknown;
322
- readonly redacted?: unknown;
323
- readonly ai?: unknown;
324
- }
325
- /** The kinds of help a learner can be given. */
326
- type AiFeature = 'explanation' | 'hint' | 'writing-feedback' | 'pronunciation-coaching';
327
- /** The activity types the SDK builds facts for. */
328
- type AiSupportedActivityType = 'multiple-choice' | 'fill-in-the-blanks' | 'gap-select' | 'dictation';
329
- /**
330
- * A whole answer's grade, from the SDK's scorer. `category` is the verdict an
331
- * explanation must agree with: all marks, some, or none.
332
- */
333
- interface AiGrade {
334
- score: number;
335
- maxScore: number;
336
- passed: boolean;
337
- category: AiVerdict;
338
- }
339
- /** The three verdicts an explanation can speak to. */
340
- type AiVerdict = 'correct' | 'partly-correct' | 'incorrect';
341
- interface AiFactsBase {
342
- activityId: string;
343
- title: string;
344
- /** The language the item is written in, when the author gave one. */
345
- locale?: string;
346
- }
347
- /** One option of a multiple-choice question, and what the learner did with it. */
348
- interface AiOptionFact {
349
- id: string;
350
- text: string;
351
- chosen: boolean;
352
- /** Whether this option is part of the answer; `null` when the key is not available. */
353
- correct: boolean | null;
354
- /** The author's feedback on this option. */
355
- feedback?: string;
356
- }
357
- interface AiMultipleChoiceFacts extends AiFactsBase {
358
- activityType: 'multiple-choice';
359
- question: string;
360
- mode: 'single' | 'multi';
361
- options: AiOptionFact[];
362
- }
363
- /** One blank, in the order it appears in the passage. */
364
- interface AiBlankFact {
365
- id: string;
366
- /** 1-based, in passage order. */
367
- position: number;
368
- /** What the learner typed, or `''`. */
369
- typed: string;
370
- /** The answers that count; `null` when the key is not available. */
371
- accepted: string[] | null;
372
- /** The SDK's verdict on this blank; `null` before it is graded or without a key. */
373
- correct: boolean | null;
374
- /** The author's hint for this blank. */
375
- hint?: string;
376
- /** The author's feedback for this blank. */
377
- feedback?: string;
378
- }
379
- interface AiFillInTheBlanksFacts extends AiFactsBase {
380
- activityType: 'fill-in-the-blanks';
381
- /** The passage, each blank written as `[1]`, `[2]`… in passage order. */
382
- passage: string;
383
- blanks: AiBlankFact[];
384
- }
385
- /** One gap, in the order it appears in the passage. */
386
- interface AiGapFact {
387
- id: string;
388
- /** 1-based, in passage order. */
389
- position: number;
390
- /** The words offered at this gap. */
391
- choices: string[];
392
- /** The word the learner chose, or `null`. */
393
- chosen: string | null;
394
- /** The right word; `null` when the key is not available. */
395
- answer: string | null;
396
- /** The SDK's verdict on this gap; `null` before it is graded or without a key. */
397
- correct: boolean | null;
398
- /** The author's feedback for this gap. */
399
- feedback?: string;
400
- }
401
- interface AiGapSelectFacts extends AiFactsBase {
402
- activityType: 'gap-select';
403
- /** The passage, each gap written as `[1]`, `[2]`… in passage order. */
404
- passage: string;
405
- gaps: AiGapFact[];
406
- }
407
- /** One word of a dictation, as the SDK's alignment paired it. */
408
- interface AiWordFact {
409
- /** The word the recording says; `''` for an extra word the learner added. */
410
- expected: string;
411
- /** The word the learner typed; `''` for a word they left out. */
412
- typed: string;
413
- status: 'correct' | 'incorrect' | 'missing' | 'extra';
414
- }
415
- interface AiDictationFacts extends AiFactsBase {
416
- activityType: 'dictation';
417
- /** What the recording says; `null` when the key is not available. */
418
- transcript: string | null;
419
- /** What the learner typed. */
420
- typed: string;
421
- /** The alignment word by word; `null` when the key is not available. */
422
- words: AiWordFact[] | null;
423
- }
424
- /**
425
- * What a model is told about one item and one answer: the question as the
426
- * learner saw it, what they answered, and — part by part — the SDK's verdict.
427
- */
428
- type AiItemFacts = AiMultipleChoiceFacts | AiFillInTheBlanksFacts | AiGapSelectFacts | AiDictationFacts;
429
- /**
430
- * Asks for an explanation of a graded answer. Sent after the SDK has graded
431
- * it, so `grade` is always present, and an explanation that contradicts
432
- * `grade.category` is refused.
433
- */
434
- interface AiExplanationRequest {
435
- feature: 'explanation';
436
- facts: AiItemFacts;
437
- grade: AiGrade;
438
- /** The language to answer in: the learner's interface language, when known. */
439
- learnerLocale?: string;
440
- }
441
- /**
442
- * Asks for the next hint before the learner submits. `facts` carries the key
443
- * so a model can hint toward the answer; the SDK refuses any hint that
444
- * contains it.
445
- */
446
- interface AiHintRequest {
447
- feature: 'hint';
448
- facts: AiItemFacts;
449
- /** 1-based: the hint being asked for. */
450
- hintNumber: number;
451
- /** The hints already shown for this item, oldest first. */
452
- previousHints: string[];
453
- learnerLocale?: string;
454
- }
455
- /** What produced an AI text, for the record a host keeps. */
456
- interface AiProvenance {
457
- /** The model's identifier, e.g. a name and version. */
458
- model?: string;
459
- /** A hash or id of the prompt revision. */
460
- promptHash?: string;
461
- /** ISO 8601. */
462
- generatedAt?: string;
463
- }
464
- /**
465
- * What a host's port returns. Plain text: the SDK renders it as text, never
466
- * as HTML. An explanation may state the verdict it assumed; one that
467
- * disagrees with the SDK's is refused.
468
- */
469
- interface AiTextResult {
470
- text: string;
471
- /** For an explanation: the verdict the model explained. */
472
- verdict?: AiVerdict;
473
- provenance?: AiProvenance;
474
- /**
475
- * What the call cost, as the provider reported it to your port: the same
476
- * {@link GraderUsage} a returned grade carries, so one shape covers help for
477
- * a learner and marking by a grader.
478
- *
479
- * The SDK never estimates it and never adds it up. It carries what a port
480
- * sends through to `onInteraction`, beside the provenance, so the record of
481
- * what a learner was shown and the record of what it cost are the same
482
- * record. Numbers that are not finite and zero or more are dropped.
483
- */
484
- usage?: GraderUsage;
485
- }
486
- /**
487
- * Why a result was not shown. `misquotes-answer` is writing feedback that
488
- * corrects words the learner did not write: a correction whose quote is not in
489
- * the draft, or not where it claims to be. `contradicts-marks` is coaching on
490
- * a word the engine did not mark, or on a sound it did not report.
491
- * `contradicts-item` is a critique of an item that points at a field the item
492
- * does not have, or quotes words that field does not contain.
493
- */
494
- type AiRefusal = 'malformed' | 'empty' | 'too-long' | 'contradicts-grade' | 'reveals-answer' | 'misquotes-answer' | 'contradicts-marks' | 'contradicts-item';
495
- /** One criterion of a written response's rubric, as the author wrote it. */
496
- interface AiRubricCriterionFact {
497
- name: string;
498
- description?: string;
499
- weight: number;
500
- }
501
- /**
502
- * What a model is told about a draft of a written response: the task as the
503
- * learner saw it, the draft verbatim, and the rubric it will be graded on.
504
- */
505
- interface AiWritingFacts {
506
- activityType: 'written-response';
507
- activityId: string;
508
- title: string;
509
- /** The language the item is written in, when the author gave one. */
510
- locale?: string;
511
- prompt: string;
512
- /** The draft, exactly as the learner wrote it. Every correction quotes it. */
513
- text: string;
514
- /** Recomputed with `countWords`, as the grader will count it. */
515
- wordCount: number;
516
- minWords: number;
517
- maxWords: number;
518
- withinWordBounds: boolean;
519
- /** The rubric the answer will be graded on; `null` when the item has none. */
520
- rubric: AiRubricCriterionFact[] | null;
521
- /** The target language and level, when the author gave one (e.g. `"en-A2"`). */
522
- languageTarget?: string;
523
- }
524
- /**
525
- * Asks for feedback on a draft of a written response, before the learner
526
- * submits it. Practice only.
527
- */
528
- interface AiWritingFeedbackRequest {
529
- feature: 'writing-feedback';
530
- facts: AiWritingFacts;
531
- /** 1-based: which draft this is. */
532
- draftNumber: number;
533
- /** The text of the feedback already shown on earlier drafts, oldest first. */
534
- previousFeedback: string[];
535
- /** The language to write in: the learner's interface language, when known. */
536
- learnerLocale?: string;
537
- }
538
- /**
539
- * One correction a model proposes. `original` must be words the learner
540
- * wrote; the SDK finds them in the draft itself, so a model need not count
541
- * characters — leave `range` out unless you are sure of it, because a range
542
- * that does not hold the quote is refused.
543
- */
544
- interface AiWritingCorrection {
545
- original: string;
546
- corrected: string;
547
- explanation?: string;
548
- /** An error-type tag, e.g. `"tense"`, `"article"`. */
549
- category?: string;
550
- range?: {
551
- start: number;
552
- end: number;
553
- };
554
- }
555
- /**
556
- * A model's judgement of one rubric criterion. The SDK attaches the author's
557
- * weight; a weight sent here is ignored.
558
- */
559
- type AiCriterionJudgement = Omit<CriterionScore, 'weight'> & {
560
- weight?: number;
561
- };
562
- /**
563
- * What a host's `writingFeedback` port returns: the overall feedback as plain
564
- * text, and optionally corrections and a judgement per rubric criterion.
565
- */
566
- interface AiWritingFeedbackResult {
567
- /** The overall feedback, addressed to the learner. Plain text, never HTML. */
568
- text: string;
569
- corrections?: AiWritingCorrection[];
570
- criteria?: AiCriterionJudgement[];
571
- provenance?: AiProvenance;
572
- usage?: GraderUsage;
573
- }
574
- /**
575
- * Feedback on a draft, as the SDK accepted it: what a learner is shown.
576
- */
577
- interface AiWritingFeedback {
578
- text: string;
579
- /**
580
- * Every correction, anchored in the draft: `range` is where the SDK found
581
- * the quote, and `original` is the draft's own text at that range.
582
- */
583
- corrections: InlineCorrection[];
584
- /** The model's judgements, with the rubric's weights. */
585
- criteria: CriterionScore[];
586
- /**
587
- * The rubric's weighted total of the judgements, from 0 to 1, computed by
588
- * the SDK as `gradeFromRubric` computes a grade — when every criterion of
589
- * the rubric was judged; `null` otherwise, and on an item with no rubric.
590
- *
591
- * **An indication, never a grade:** it is a model's reading of a draft the
592
- * learner has not submitted, and nothing in the SDK scores an answer with
593
- * it. `ai-writing-feedback-shown` reports it, so a record can say what the
594
- * learner was shown.
595
- */
596
- indicativeScore: number | null;
597
- provenance?: AiProvenance;
598
- usage?: GraderUsage;
599
- }
600
- /** One sound of a word, as the engine reported it. */
601
- interface AiSoundFact {
602
- /** The sound expected, in the assessment's phoneme alphabet. */
603
- symbol: string;
604
- /** How close it came, 0..100, when the engine scored it. */
605
- accuracy?: number;
606
- /** What the engine heard instead, best first, each with its score 0..100. */
607
- heardAs?: {
608
- symbol: string;
609
- score: number;
610
- }[];
611
- }
612
- /** One word of a reading, as the engine marked it. */
613
- interface AiReadingWordFact {
614
- /** `w<n>` for a word of the text; absent for a word the learner added. */
615
- itemId?: string;
616
- /** The word of the text, normalised; `''` for a word the learner added. */
617
- word: string;
618
- /** What was heard, normalised; `''` for an omitted word. */
619
- heard: string;
620
- state: 'correct' | 'mispronounced' | 'omitted' | 'inserted';
621
- /** The engine's accuracy for the word, 0..100, when it has one. */
622
- accuracy?: number;
623
- /** The word's sounds, from the full assessment only. */
624
- sounds?: AiSoundFact[];
625
- }
626
- /**
627
- * What a model is told about a read-aloud: the text, the engine's marks word
628
- * by word, and its scores. The marks are the engine's; a model explains them
629
- * and never makes its own.
630
- */
631
- interface AiReadingFacts {
632
- activityType: 'read-aloud';
633
- activityId: string;
634
- title: string;
635
- /** The language the text is in. */
636
- locale: string;
637
- referenceText: string;
638
- instructions?: string;
639
- /** Who made the marks: a measurement engine, a model, or a person. */
640
- assessor: 'auto' | 'ai' | 'human' | 'unknown';
641
- /** The alphabet `sounds` are written in, when any are. */
642
- phonemeAlphabet?: 'ipa' | 'sapi';
643
- /** The engine's dimension scores, each 0..100, where it measured them. */
644
- scores: {
645
- accuracy?: number;
646
- fluency?: number;
647
- completeness?: number;
648
- prosody?: number;
649
- };
650
- words: AiReadingWordFact[];
651
- }
652
- /**
653
- * Asks for coaching on a graded reading: what to work on, word by word, and
654
- * how. Never in an exam.
655
- */
656
- interface AiCoachingRequest {
657
- feature: 'pronunciation-coaching';
658
- facts: AiReadingFacts;
659
- /** The grade of the reading, when there is one. */
660
- grade?: {
661
- score: number;
662
- maxScore: number;
663
- passed: boolean;
664
- };
665
- /** The language to write in: the learner's interface language, when known. */
666
- learnerLocale?: string;
667
- }
668
- /** One word a model coaches, as it returns it. */
669
- interface AiCoachingWord {
670
- /** The `itemId` of a word the engine marked mispronounced or omitted. */
671
- itemId: string;
672
- /** What to do about it, addressed to the learner. */
673
- tip: string;
674
- /**
675
- * A sound the engine reported for this word — `expected` — and, if the
676
- * model names one, what the engine heard instead.
677
- */
678
- sound?: {
679
- expected: string;
680
- heard?: string;
681
- };
682
- }
683
- /** What a host's `pronunciationCoaching` port returns. */
684
- interface AiCoachingResult {
685
- /** The coaching as a whole, addressed to the learner. Plain text, never HTML. */
686
- text: string;
687
- words?: AiCoachingWord[];
688
- provenance?: AiProvenance;
689
- usage?: GraderUsage;
690
- }
691
- /** Coaching, as the SDK accepted it: what a learner is shown. */
692
- interface AiCoaching {
693
- text: string;
694
- /**
695
- * The words to work on, in reading order, each with the word of the text as
696
- * the SDK has it — never the model's spelling of it.
697
- */
698
- words: (AiCoachingWord & {
699
- word: string;
700
- })[];
701
- provenance?: AiProvenance;
702
- usage?: GraderUsage;
703
- }
704
- /**
705
- * What a model may find wrong with an item, in the SDK's words:
706
- *
707
- * - `ambiguous` — a stem, passage or blank that reads two ways;
708
- * - `second-answer` — a distractor that is defensibly right, or an answer the
709
- * key does not accept;
710
- * - `wrong-key` — the answer marked right is not;
711
- * - `implausible-distractor` — an option nobody who read the question would pick;
712
- * - `cue` — grammar or wording that points at the answer;
713
- * - `level` — language above or below the level the item is for;
714
- * - `language` — a mistake in the item's own language;
715
- * - `sensitivity` — content a learner may find loaded or unfamiliar for
716
- * reasons that have nothing to do with what is assessed;
717
- * - `other`.
718
- */
719
- type AiCritiqueKind = 'ambiguous' | 'second-answer' | 'wrong-key' | 'implausible-distractor' | 'cue' | 'level' | 'language' | 'sensitivity' | 'other';
720
- /** One field of an item a critique may point at, and what it says. */
721
- interface AiCritiqueField {
722
- path: string[];
723
- text: string;
724
- }
725
- /**
726
- * What a host's model is given to review an item. Built by
727
- * `aiCritiqueRequest`; for an author, never a learner, so it carries the
728
- * answer key.
729
- */
730
- interface AiCritiqueRequest {
731
- feature: 'item-critique';
732
- facts: {
733
- activityType: string;
734
- /** The item as the author wrote it, answer key included. */
735
- item: Readonly<Record<string, unknown>>;
736
- /** Every text field a finding may point at, with its text: the only paths a finding may name. */
737
- fields: AiCritiqueField[];
738
- /**
739
- * What the SDK's own critic already found, so a model does not say it
740
- * again: code, path and message.
741
- */
742
- findings: {
743
- path: string[];
744
- code: string;
745
- message: string;
746
- }[];
747
- /** The level the item is for, as the host names it (`A2`, `B1`), when it gave one. */
748
- level?: string;
749
- };
750
- /** The language to write findings in: the author's. */
751
- authorLocale?: string;
752
- }
753
- /** One finding as a host's port returns it. */
754
- interface AiCritiqueFindingResult {
755
- /** A path from `facts.fields`, exactly. */
756
- path: (string | number)[];
757
- kind: AiCritiqueKind;
758
- /** What is wrong, and what to do, addressed to the author. */
759
- message: string;
760
- /** Words copied from that field, when the finding is about some of them. */
761
- quote?: string;
762
- }
763
- /** What a host's critique port returns. */
764
- interface AiCritiqueResult {
765
- findings: AiCritiqueFindingResult[];
766
- provenance?: AiProvenance;
767
- usage?: GraderUsage;
768
- }
769
- /**
770
- * A model's finding, as the SDK accepted it: an `ItemFinding` — always
771
- * `advice`, since it is a model's opinion — whose code is `ai_` and the kind
772
- * (`ai_second_answer`), with the quote it pointed at.
773
- */
774
- interface AiCritiqueFinding extends ItemFinding {
775
- severity: 'advice';
776
- quote?: string;
777
- }
778
- /** A critique, as the SDK accepted it. */
779
- interface AiCritique {
780
- findings: AiCritiqueFinding[];
781
- provenance?: AiProvenance;
782
- usage?: GraderUsage;
783
- }
784
- /**
785
- * The types a model can draft from a source. An interactive video takes the
786
- * first five; a written response is for a quiz of its own.
787
- */
788
- type AiDraftType = 'multiple-choice' | 'fill-in-the-blanks' | 'gap-select' | 'dictation' | 'read-aloud' | 'written-response';
789
- /**
790
- * What drafts are written from: a passage or a script as text — an item
791
- * group's `stimulus.transcript` is one — or a video's captions, each with its
792
- * start and end in seconds, so a question can be placed where its caption ends.
793
- */
794
- type AiDraftSource = {
795
- kind: 'passage' | 'transcript';
796
- text: string;
797
- } | {
798
- kind: 'captions';
799
- cues: {
800
- start: number;
801
- end: number;
802
- text: string;
803
- }[];
804
- };
805
- /** A caption as a model is given it: numbered, so a draft can name the one it is about. */
806
- interface AiDraftCaption {
807
- index: number;
808
- start: number;
809
- end: number;
810
- text: string;
811
- }
812
- /** One problem with a draft, as a model is told it on a repair. */
813
- interface AiDraftProblem {
814
- path: string[];
815
- code: string;
816
- message: string;
817
- severity: 'incomplete' | 'invalid' | 'warning';
818
- }
819
- /**
820
- * Fields of the host's for every draft of a type — settings a model never
821
- * writes: a read-aloud's `recording`, `scoring` and region-tagged `locale`, a
822
- * question's `shuffle` or `scoringStrategy`. They are put on each draft of
823
- * that type before it is checked, and never override what the model wrote, nor
824
- * the draft's `id`, `type` or `schemaVersion`.
825
- */
826
- type AiDraftSettings = Partial<Record<AiDraftType, Readonly<Record<string, unknown>>>>;
827
- /**
828
- * What a host's model is given to draft items. Built by `aiDraftsRequest`, and
829
- * by `aiDraftsRepairRequest` for a second pass over the drafts that came back
830
- * unfinished or flawed in ways the model can fix.
831
- */
832
- interface AiDraftsRequest {
833
- feature: 'draft-generation';
834
- facts: {
835
- /**
836
- * The types the author chose, in the author's order: the ones to lean on
837
- * first, and the order questions are shown in where several share a moment
838
- * of a video — or share the end of one.
839
- */
840
- activityTypes: AiDraftType[];
841
- source: {
842
- kind: 'passage' | 'transcript';
843
- text: string;
844
- } | {
845
- kind: 'captions';
846
- captions: AiDraftCaption[];
847
- };
848
- /** How many drafts to write, at most. Absent: as many as the source is worth, up to `AI_DRAFTS_MAX_COUNT`. */
849
- count?: number;
850
- /** The language the items are written in. */
851
- locale?: string;
852
- /** The level they are for, as the host names it (`A2`, `B1`). */
853
- level?: string;
854
- /** What the author asked for, in their words. */
855
- instructions?: string;
856
- };
857
- /**
858
- * The JSON Schema of a reply — `{ drafts: [...] }` — for a model's structured
859
- * output, or its prompt. Plain JSON Schema a model can follow: types, required
860
- * fields, enums and descriptions, nothing a provider's structured output
861
- * commonly refuses. Each draft names its `type`, and fills the fields that
862
- * type uses.
863
- */
864
- shape: Readonly<Record<string, unknown>>;
865
- /** The host's settings for every draft of a type. Not for the model; `checkAiDrafts` applies them. */
866
- settings?: AiDraftSettings;
867
- /**
868
- * On a repair: the drafts to fix, each as the model wrote it, with what is
869
- * wrong with it that the model can fix. The reply holds one draft for each,
870
- * in this order.
871
- */
872
- repair?: {
873
- index: number;
874
- draft: Readonly<Record<string, unknown>>;
875
- problems: AiDraftProblem[];
876
- }[];
877
- }
878
- /** A draft as the SDK made it from a reply: ids minted, settings applied, checked, and critiqued. */
879
- interface AiGeneratedDraft {
880
- /** Its position in the first reply; a repaired draft keeps the position it replaces. */
881
- index: number;
882
- type: AiDraftType;
883
- /** The draft, an activity of its type once `validation` says `complete`. */
884
- draft: Record<string, unknown>;
885
- /** What `validateDraft` says of it. `complete` means valid — never approved. */
886
- validation: DraftValidationResult<unknown>;
887
- /** What the item critic says of it. */
888
- findings: ItemFinding[];
889
- /**
890
- * From captions: where the question goes, in seconds — the end of the
891
- * caption it named. Absent when it named no caption the source has: it
892
- * belongs at the end of the video.
893
- */
894
- at?: number;
895
- /**
896
- * A dictation from captions whose transcript is words of the caption it
897
- * named: that caption's start and end, in seconds — the stretch of the
898
- * video that says it, for a host that cuts the recording from the video.
899
- */
900
- clip?: {
901
- start: number;
902
- end: number;
903
- };
904
- /** The draft as the model wrote it, for a repair. */
905
- generated: Readonly<Record<string, unknown>>;
906
- }
907
- /** What `checkAiDrafts` makes of a reply. */
908
- interface AiDrafts {
909
- /** The drafts in the author's order of types, and in the order the model wrote them within a type. */
910
- drafts: AiGeneratedDraft[];
911
- /** What only the set shows: every right option in one place (`set_key_position_same`). */
912
- findings: ItemFinding[];
913
- provenance?: AiProvenance;
914
- usage?: GraderUsage;
915
- }
916
- /** One call `generateDrafts` made: what it cost, or why it went no further. */
917
- type AiDraftsCall = {
918
- ok: true;
919
- provenance?: AiProvenance;
920
- usage?: GraderUsage;
921
- } | {
922
- ok: false;
923
- refusal?: AiRefusal;
924
- error?: string;
925
- };
926
- /** What `generateDrafts` came to. */
927
- interface AiDraftsRun {
928
- /** The drafts, in the author's order of types, each the best pass of it. */
929
- drafts: AiGeneratedDraft[];
930
- /** What only the set shows, over the drafts as they ended. */
931
- findings: ItemFinding[];
932
- /** Every call, in order: the first, then each repair. */
933
- calls: AiDraftsCall[];
934
- }
935
- /**
936
- * An interactive video made of drafts: an item group whose stimulus is the
937
- * video and whose timeline opens a quiz at each moment a draft was placed —
938
- * and one at the end for every draft placed nowhere. A draft, like every
939
- * draft: checked, critiqued, and approved by a person, never by the SDK.
940
- */
941
- interface AiVideoDraft {
942
- /** The item group, an interactive video once `validation` says `complete`. */
943
- group: Record<string, unknown>;
944
- /** What `validateItemGroupDraft` says of it. */
945
- validation: DraftValidationResult<unknown>;
946
- /** What `critiqueItemGroupDraft` says of it. */
947
- findings: ItemFinding[];
948
- }
949
-
950
- /**
951
- * An xAPI Activity object — the thing a statement is about.
952
- * This SDK only builds Activity-type objects (xAPI 1.0.3 §4.1.4.1).
953
- */
954
- interface XAPIObject {
955
- objectType: 'Activity';
956
- /** IRI uniquely identifying the activity. */
957
- id: string;
958
- definition?: {
959
- /** Language-map of human-readable names for the activity. */
960
- name?: Record<string, string>;
961
- /** Language-map of human-readable descriptions. */
962
- description?: Record<string, string>;
963
- /** IRI identifying the activity type. */
964
- type?: string;
965
- /** xAPI 1.0.3 cmi.interaction type (e.g. `choice`, `fill-in`, `long-fill-in`). */
966
- interactionType?: string;
967
- /** Patterns describing the correct response(s), per the xAPI CRP format. */
968
- correctResponsesPattern?: string[];
969
- /** For `choice`-family interactions: the available components. */
970
- choices?: Array<{
971
- id: string;
972
- description?: Record<string, string>;
973
- }>;
974
- extensions?: Record<string, unknown>;
975
- };
976
- }
977
- /**
978
- * An xAPI Agent (person or system) that performed the statement's verb.
979
- * Exactly one of `mbox` or `account` must be present.
980
- */
981
- interface XAPIActor {
982
- objectType: 'Agent';
983
- /** Display name for the actor. */
984
- name?: string;
985
- /** Mailto IRI uniquely identifying the actor (e.g. `mailto:user@example.com`). */
986
- mbox?: string;
987
- /** Account-based actor identifier. */
988
- account?: {
989
- /** IRI of the home page of the account's service provider. */
990
- homePage: string;
991
- /** The actor's unique name on that service. */
992
- name: string;
993
- };
994
- }
995
- /** An xAPI Verb — the action performed in the statement. */
996
- interface XAPIVerbObject {
997
- /** IRI uniquely identifying this verb (e.g. from adlnet.gov/expapi/verbs). */
998
- id: string;
999
- /** Language-map of human-readable verb labels. */
1000
- display: Record<string, string>;
1001
- }
1002
- /** Score sub-object within an xAPI Result. */
1003
- interface XAPIScore {
1004
- /** Normalized score in the range [0, 1]. */
1005
- scaled: number;
1006
- /** Raw score in the instrument's native scale. */
1007
- raw?: number;
1008
- /** Minimum possible raw score. */
1009
- min?: number;
1010
- /** Maximum possible raw score. */
1011
- max?: number;
1012
- }
1013
- /** Result information attached to an xAPI Statement. */
1014
- interface XAPIResult {
1015
- score?: XAPIScore;
1016
- /** Whether the actor's performance was successful. */
1017
- success?: boolean;
1018
- /** Whether the activity was completed. */
1019
- completion?: boolean;
1020
- /** ISO 8601 duration string representing time spent (e.g. `PT5M30S`). */
1021
- duration?: string;
1022
- /** A string representation of the learner's response. */
1023
- response?: string;
1024
- extensions?: Record<string, unknown>;
1025
- }
1026
- /** Related-activity references attached to a statement's context. */
1027
- interface XAPIContextActivities {
1028
- /** Activities this statement's activity is directly part of (e.g. the exam section). */
1029
- parent?: XAPIObject[];
1030
- /** Broader groupings (e.g. the course). */
1031
- grouping?: XAPIObject[];
1032
- /** Categorisations (e.g. a profile IRI). */
1033
- category?: XAPIObject[];
1034
- /** Other contextually related activities. */
1035
- other?: XAPIObject[];
1036
- }
1037
- /** Context information attached to an xAPI Statement. */
1038
- interface XAPIContext {
1039
- /** Name of the software used to record the statement. */
1040
- platform?: string;
1041
- /** BCP 47 language tag for the activity content. */
1042
- language?: string;
1043
- /** Related activities (parent/grouping/category/other). */
1044
- contextActivities?: XAPIContextActivities;
1045
- extensions?: Record<string, unknown>;
1046
- }
1047
- /** A complete xAPI 1.0.3 Statement. */
1048
- interface XAPIStatement {
1049
- /** UUID v4 uniquely identifying this statement. */
1050
- id: string;
1051
- actor: XAPIActor;
1052
- verb: XAPIVerbObject;
1053
- object: XAPIObject;
1054
- result?: XAPIResult;
1055
- context?: XAPIContext;
1056
- /** ISO 8601 timestamp of when the statement occurred. */
1057
- timestamp: string;
1058
- version: '1.0.3';
1059
- }
1060
- /**
1061
- * Configuration passed to `useXAPI` to connect to an LRS endpoint.
1062
- * The `auth` field supports both HTTP Basic and Bearer token authentication.
1063
- */
1064
- interface XAPIConfig {
1065
- /** Full URL of the LRS statements endpoint. */
1066
- endpoint: string;
1067
- /** Authentication credentials for the LRS. */
1068
- auth: {
1069
- type: 'basic';
1070
- username: string;
1071
- password: string;
1072
- } | {
1073
- type: 'bearer';
1074
- token: string;
1075
- };
1076
- /**
1077
- * Replaces the SDK's placeholder object id (`urn:learning-kit:activity:*`)
1078
- * before a statement is sent.
1079
- *
1080
- * - `string` — the IRI for THE activity this hook instance serves. A hook
1081
- * instance is per-activity by contract; with several activities sharing
1082
- * one hook, a plain string would collapse their statements onto one IRI —
1083
- * use the function form instead.
1084
- * - `(sdkObjectId) => string` — maps each placeholder id (which embeds the
1085
- * activity's `data.id`) to its deployment IRI, for pages that send many
1086
- * activities through one hook.
1087
- *
1088
- * Object ids the consumer already rewrote (non-`urn:learning-kit:` ids) are
1089
- * never touched.
1090
- */
1091
- activityId: string | ((sdkObjectId: string) => string);
1092
- actor: XAPIActor;
1093
- /** Called after all retry attempts are exhausted. */
1094
- onError?: (err: XAPIError) => void;
1095
- }
1096
- /** Error payload delivered to `XAPIConfig.onError` after retries are exhausted. */
1097
- interface XAPIError {
1098
- /** The statement that failed to send. */
1099
- statement: XAPIStatement;
1100
- /** The 1-based attempt number on which the final failure occurred. */
1101
- attempt: number;
1102
- /** HTTP status code from the LRS, if a response was received. */
1103
- statusCode?: number;
1104
- /** Human-readable error description. */
1105
- message: string;
1106
- }
1107
-
1108
- /**
1109
- * Maps each ActivityType string to its corresponding data shape.
1110
- *
1111
- * Open for extension via TypeScript module augmentation: a consumer that
1112
- * registers a custom activity type (`registerActivityType`) can augment this
1113
- * interface so `ActivityType`, `ActivityData`, `validateActivity`, and
1114
- * `evaluate` pick the new type up without an SDK release:
1115
- *
1116
- * ```ts
1117
- * declare module '@intellectif/lk-core' {
1118
- * interface ActivityDataMap { 'my-type': MyTypeData }
1119
- * interface LearnerResponseMap { 'my-type': MyTypeLearnerResponse }
1120
- * }
1121
- * ```
1122
- */
1123
- interface ActivityDataMap {
1124
- 'multiple-choice': MultipleChoiceData;
1125
- 'fill-in-the-blanks': FillInTheBlanksData;
1126
- 'written-response': WrittenResponseData;
1127
- 'gap-select': GapSelectData;
1128
- dictation: DictationData;
1129
- 'read-aloud': ReadAloudData;
1130
- }
1131
- /**
1132
- * The set of activity types supported by learning-kit. Derived from
1133
- * {@link ActivityDataMap}, so module augmentation widens it automatically.
1134
- */
1135
- type ActivityType = keyof ActivityDataMap;
1136
- /** Union of all valid activity data shapes. */
1137
- type ActivityData = ActivityDataMap[ActivityType];
1138
- /** A text track for an `audio` or `video` recording. */
1139
- interface MediaTrack {
1140
- kind: 'captions' | 'subtitles';
1141
- /** WebVTT URL. A player may fetch it through a loader the host supplies, with the host's credentials. */
1142
- src: string;
1143
- /** BCP 47 language tag of the text (`en`, `es`, `pt-BR`). */
1144
- srclang: string;
1145
- /** Shown in the captions menu. Non-empty, at most 60 characters. */
1146
- label: string;
1147
- /** Selected when the learner has chosen no language yet. At most one track may be the default. */
1148
- default?: boolean;
1149
- }
1150
- /**
1151
- * Optional media (image/audio/video) shown above a question or passage.
1152
- * URL-only: hosting/delivery is the consuming application's responsibility.
1153
- */
1154
- interface ActivityMedia {
1155
- /** The kind of media; selects the rendered element. `embed` → sandboxed iframe. */
1156
- type: 'image' | 'audio' | 'video' | 'embed';
1157
- /**
1158
- * Source URL. For `embed` this MUST be the provider's embeddable URL
1159
- * (e.g. `https://www.youtube.com/embed/<id>`), not the watch page.
1160
- */
1161
- url: string;
1162
- /** Alternative text. Required for `image` and `embed`; optional label for audio/video. */
1163
- alt?: string;
1164
- /** Optional WebVTT captions track URL for `audio`/`video`. */
1165
- captionsUrl?: string;
1166
- /**
1167
- * Text tracks for `audio`/`video`, one per language: what `captionsUrl`
1168
- * cannot say — a language, a label, more than one. When present, a player
1169
- * that reads it ignores `captionsUrl`. At most 12, one `default`, and no two
1170
- * sharing a `kind` and a `srclang`.
1171
- */
1172
- tracks?: MediaTrack[];
1173
- /** An image shown before the first play. `video` only. */
1174
- poster?: string;
1175
- /**
1176
- * How the recording may be played. `audio` only — see
1177
- * {@link MediaPlaybackPolicy}. Absent means today's behaviour exactly: the
1178
- * browser's own control bar, unlimited plays, free seeking and free speed.
1179
- */
1180
- playback?: MediaPlaybackPolicy;
1181
- }
1182
- /**
1183
- * A hint to the browser's own control bar, emitted as a `controlsList` token.
1184
- *
1185
- * Advisory, and engine-dependent. `hide-download` removes a menu item where
1186
- * `controlsList` is implemented; it never prevents a download, because the URL
1187
- * is in the page and the bytes are in the network panel. The real control is a
1188
- * short-lived signed URL, which belongs to the consuming application.
1189
- */
1190
- type NativeControlHint = 'hide-download' | 'hide-rate';
1191
- /**
1192
- * How an audio recording may be played.
1193
- *
1194
- * Every field is optional and every default reproduces the SDK's pre-0.8.0
1195
- * behaviour, so adding this key is the only thing that changes anything.
1196
- *
1197
- * Setting any *enforcement* field (`maxPlays`, `seek: 'none'`, `rate: 'fixed'`)
1198
- * resolves `controls` to `'minimal'`: the browser's bar cannot express a spent
1199
- * budget — its play button stays enabled — so the SDK renders its own
1200
- * transport rather than leave a control that looks operable and does nothing.
1201
- */
1202
- interface MediaPlaybackPolicy {
1203
- /**
1204
- * `native` renders the browser's control bar. `minimal` renders the SDK
1205
- * transport. Resolved automatically; set it explicitly only to keep the
1206
- * native bar on a recording that has nothing to enforce.
1207
- */
1208
- controls?: 'native' | 'minimal';
1209
- /**
1210
- * How many times the recording may be STARTED (1–20). A play is consumed
1211
- * when playback begins from anywhere other than where it last stopped, so
1212
- * pausing, resuming, and paging between the questions of one listening group
1213
- * are free.
1214
- *
1215
- * The SDK refuses the play; it does not remember it. The count is durable
1216
- * only if the consuming application persists it — see `mediaBudget` on
1217
- * `<ActivitySequence>` in `@intellectif/lk-react`.
1218
- */
1219
- maxPlays?: number;
1220
- /** `none` renders no scrubber and reverts an out-of-band seek. Resolves to `none` under a budget. */
1221
- seek?: 'allow' | 'none';
1222
- /** `fixed` renders no speed control and snaps `playbackRate` back to 1. */
1223
- rate?: 'allow' | 'fixed';
1224
- /** Advisory hints to the native bar. See {@link NativeControlHint}. */
1225
- nativeControlHints?: NativeControlHint[];
1226
- }
1227
- /**
1228
- * Optional authored "overall feedback" shown after submission, chosen by
1229
- * whether the learner passed (h5p-style overall feedback). Distinct from
1230
- * per-option feedback; either field may be omitted.
1231
- */
1232
- interface ActivityFeedback {
1233
- /** Shown when the learner passes (score ≥ pass threshold). */
1234
- correct?: string;
1235
- /** Shown when the learner does not pass. */
1236
- incorrect?: string;
1237
- }
1238
- /** A single selectable option within a Multiple Choice activity. */
1239
- /**
1240
- * A picture or a recording carried by ONE multiple-choice option — the
1241
- * A1/A2 picture-choice item, and the minimal-pair listening item.
1242
- *
1243
- * Narrower than {@link ActivityMedia}, which sits above the question, and
1244
- * deliberately so. An option is a click target, and two of the activity-level
1245
- * kinds cannot be one:
1246
- *
1247
- * - **`embed` is refused.** A provider iframe swallows pointer events, so
1248
- * clicking the option would play the video instead of selecting the answer.
1249
- * The learner could not choose it at all.
1250
- * - **`video` is refused** for the same reason one step down: a native control
1251
- * bar inside the option's label eats the clicks meant for the radio.
1252
- * - **No `playback` policy.** `maxPlays` binds per slot through a
1253
- * `MediaBudgetBinding`, and four recordings in one question raise a question
1254
- * nothing has answered yet — whether the budget belongs to the option or to
1255
- * the item. Until something asks, an option plays through the browser's own
1256
- * bar and counts nothing.
1257
- *
1258
- * `alt` follows the same rule {@link ActivityMedia} uses, so there is only one
1259
- * rule to learn: required and non-empty for an image, optional for audio, where
1260
- * it becomes the player's accessible label.
1261
- *
1262
- * **On an image option, `alt` is part of the item.** It joins the option's
1263
- * `text` in the radio's accessible name, so on "which picture shows a cat?" an
1264
- * `alt` of "a cat" hands a screen-reader user the answer that a sighted learner
1265
- * has to work out. That is a property of picture-choice items, not something a
1266
- * schema can fix — the SDK requires `alt` so an option is never SILENTLY
1267
- * inaccessible, and leaves the wording, and the item's validity, to the author.
1268
- */
1269
- interface MultipleChoiceOptionMedia {
1270
- /** `image` for a picture-choice option, `audio` for a listening option. */
1271
- type: 'image' | 'audio';
1272
- /** Address of the file. Same scheme allow-list as {@link ActivityMedia}. */
1273
- url: string;
1274
- /** Required and non-empty for `image`; an optional accessible label for `audio`. */
1275
- alt?: string;
1276
- /** WebVTT captions for an audio option, rendered as a `<track>`. */
1277
- captionsUrl?: string;
1278
- }
1279
- interface MultipleChoiceOption {
1280
- /** Unique identifier for this option within the activity. */
1281
- id: string;
1282
- /**
1283
- * Display text shown to the learner.
1284
- *
1285
- * Required even when the option carries {@link media}: it names the option in
1286
- * the radio's accessible name and in the xAPI statement, and it is what the
1287
- * learner sees if a picture fails to load. For a pure picture-choice option,
1288
- * a neutral label ("Picture 1") keeps the naming out of the answer.
1289
- */
1290
- text: string;
1291
- /** Whether this option is part of the correct answer. */
1292
- isCorrect: boolean;
1293
- /** Optional per-option feedback shown after submission. */
1294
- feedback?: string;
1295
- /** An optional picture or recording shown as part of this option. */
1296
- media?: MultipleChoiceOptionMedia;
1297
- }
1298
- /** Data contract for a Multiple Choice activity. */
1299
- interface MultipleChoiceData {
1300
- schemaVersion: '1.0';
1301
- type: 'multiple-choice';
1302
- /** Unique identifier for this activity. */
1303
- id: string;
1304
- /** Human-readable title used in xAPI statements and error boundaries. */
1305
- title: string;
1306
- /** The question stem presented to the learner, as plain text. */
1307
- question: string;
1308
- /**
1309
- * Optional sanitised rich-HTML rendering of the question, parallel to
1310
- * `WrittenResponseData.promptHtml`. `<MultipleChoice>` renders it in place
1311
- * of `question` when — and only when — a `sanitizeHtml` function is
1312
- * supplied; without one the plain `question` is rendered and escaped.
1313
- * Sanitisation is the application's responsibility: the SDK ships none.
1314
- */
1315
- questionHtml?: string;
1316
- /** `single` allows one selection; `multi` allows multiple. */
1317
- mode: 'single' | 'multi';
1318
- /** Ordered list of answer options. */
1319
- options: MultipleChoiceOption[];
1320
- /** Scoring algorithm applied when the learner submits. */
1321
- scoringStrategy: 'all-or-nothing' | 'partial';
1322
- /** Optional media shown above the question. */
1323
- media?: ActivityMedia;
1324
- /** Optional authored overall feedback shown after submission. */
1325
- feedback?: ActivityFeedback;
1326
- /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1327
- passThreshold?: number;
1328
- /**
1329
- * When true, option order is shuffled deterministically.
1330
- *
1331
- * The order is derived from the renderer's `shuffleSeed` when one is given —
1332
- * pass the attempt id, and a review render reproduces exactly the order the
1333
- * learner sat. Without a seed the order is stable for the life of the mount
1334
- * only, and is NOT reproducible afterwards, so never grade or appeal against
1335
- * a remembered position.
1336
- */
1337
- shuffle?: boolean;
1338
- /** BCP 47 language tag for the activity content. */
1339
- locale?: string;
1340
- /** IRI references to learning objectives addressed by this activity. */
1341
- learningObjectives?: string[];
1342
- /** Subjective difficulty on a 1–5 scale. */
1343
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1344
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1345
- ai?: ActivityAiPermissions;
1346
- }
1347
- /** Configuration for a single fill-in-the-blank slot. */
1348
- interface BlankConfig {
1349
- /** Unique identifier matching the `{{blank_id}}` placeholder in the passage. */
1350
- id: string;
1351
- /** List of strings accepted as correct answers for this blank. */
1352
- acceptedAnswers: string[];
1353
- /** Whether answer matching is case-sensitive. Defaults to false. */
1354
- caseSensitive?: boolean;
1355
- /** Whether leading/trailing whitespace is stripped before matching. Defaults to true. */
1356
- trimWhitespace?: boolean;
1357
- /**
1358
- * Optional matching-tolerance policy for this blank (Unicode normalization,
1359
- * diacritic folding, whitespace collapse, punctuation tolerance, typo
1360
- * tolerance). Every tolerance is opt-in; when absent, matching reproduces
1361
- * the v1 trim + case-fold semantics exactly. Fields set here take
1362
- * precedence over the legacy `caseSensitive` / `trimWhitespace` flags.
1363
- */
1364
- match?: TextMatchPolicy;
1365
- /** Optional hint text revealed on learner request. */
1366
- hint?: string;
1367
- /** Optional feedback shown inline next to this blank after submission. */
1368
- feedback?: string;
1369
- }
1370
- /** Data contract for a Fill-in-the-Blanks activity. */
1371
- /** One selectable option in a Gap Select activity. */
1372
- interface GapSelectChoice {
1373
- /** Unique identifier within the choice set the gap resolves to. */
1374
- id: string;
1375
- /** The text the learner reads in the selector. */
1376
- text: string;
1377
- }
1378
- /**
1379
- * A named set of choices several gaps can draw from — a word bank.
1380
- *
1381
- * A bank is what makes distractors possible: three gaps sharing a bank of five
1382
- * choices means every selector offers all five, and two of them answer no gap
1383
- * at all. That is the difference between a reading-comprehension item and a
1384
- * sequence of three-way guesses, so it is modelled once here rather than
1385
- * retrofitted after per-gap lists ship.
1386
- */
1387
- interface GapSelectBank {
1388
- /** Unique identifier referenced by {@link GapSelectGap.bankId}. */
1389
- id: string;
1390
- /** The choices every gap on this bank offers. */
1391
- choices: GapSelectChoice[];
1392
- }
1393
- /**
1394
- * One gap in the passage, matching a `{{gap_id}}` placeholder.
1395
- *
1396
- * Its choices come from exactly one place: `choices` for a list of its own, or
1397
- * `bankId` for a shared word bank. Both, or neither, is an authoring error the
1398
- * schema rejects — with two sources a reader cannot tell which list the learner
1399
- * is offered, and the answer key means nothing without one.
1400
- */
1401
- interface GapSelectGap {
1402
- /** Unique identifier matching the `{{gap_id}}` placeholder in the passage. */
1403
- id: string;
1404
- /** This gap's own choices. Mutually exclusive with {@link bankId}. */
1405
- choices?: GapSelectChoice[];
1406
- /** The {@link GapSelectBank} this gap draws from. Mutually exclusive with {@link choices}. */
1407
- bankId?: string;
1408
- /** The `id` of the one choice that is correct. Must exist in the resolved choice set. */
1409
- correctChoiceId: string;
1410
- /** Optional feedback shown inline next to this gap after submission. */
1411
- feedback?: string;
1412
- }
1413
- /**
1414
- * Data contract for a Gap Select activity: a passage whose gaps the learner
1415
- * fills by choosing from a list rather than typing.
1416
- *
1417
- * It is deliberately **not** a mode of {@link FillInTheBlanksData}, though the
1418
- * passage and its `{{id}}` placeholders are authored the same way. A learner
1419
- * picking from a selector cannot mistype, so the whole `TextMatchPolicy`
1420
- * surface — diacritic folding, typo tolerance, locale-aware case — is not just
1421
- * unused but misleading. Scoring is identity comparison of a choice id.
1422
- * Redaction inverts too: in Fill-in-the-Blanks the candidate answers ARE the
1423
- * key, while here the learner must be shown every choice and only
1424
- * `correctChoiceId` is withheld.
1425
- */
1426
- interface GapSelectData {
1427
- schemaVersion: '1.0';
1428
- type: 'gap-select';
1429
- /** Unique identifier for this activity. */
1430
- id: string;
1431
- /** Human-readable title used in xAPI statements and error boundaries. */
1432
- title: string;
1433
- /** Passage text containing `{{gap_id}}` placeholders. */
1434
- passage: string;
1435
- /**
1436
- * Optional sanitised rich-HTML rendering of the passage. Carried and redacted
1437
- * as learner-visible content; not rendered by the SDK, for the same reason
1438
- * `FillInTheBlanksData.passageHtml` is not — slicing sanitised HTML at the
1439
- * placeholders to host the selectors is both lossy and unsafe.
1440
- */
1441
- passageHtml?: string;
1442
- /** Every gap in the passage, one per distinct `{{id}}` placeholder. */
1443
- gaps: GapSelectGap[];
1444
- /** Shared word banks. Only needed by gaps that set `bankId`. */
1445
- banks?: GapSelectBank[];
1446
- /** Scoring algorithm applied when the learner submits. */
1447
- scoringStrategy: 'all-or-nothing' | 'partial';
1448
- /**
1449
- * How the choices are presented. Only `'dropdown'` exists today, and it is
1450
- * the default.
1451
- *
1452
- * The field is here rather than assumed because a drag-and-drop presentation
1453
- * is the obvious next request, and WCAG 2.5.7 requires that a drag interface
1454
- * always keep a non-drag path — so the choice has to be expressible in the
1455
- * content, not decided by a component prop. `'drag'` is NOT accepted yet:
1456
- * shipping a value nothing renders would freeze an API this repository has
1457
- * not validated. Widening the union later is additive.
1458
- */
1459
- presentation?: 'dropdown';
1460
- /**
1461
- * Whether the SDK shuffles each selector's choices. Off by default.
1462
- *
1463
- * Like `MultipleChoiceData.shuffle`, this needs a seed to be reproducible at
1464
- * a remark — `<ActivitySequence>` refuses to shuffle without one outside
1465
- * `practice` mode.
1466
- */
1467
- shuffleChoices?: boolean;
1468
- /** Optional media shown above the passage. */
1469
- media?: ActivityMedia;
1470
- /** Optional authored overall feedback shown after submission. */
1471
- feedback?: ActivityFeedback;
1472
- /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1473
- passThreshold?: number;
1474
- /** BCP 47 language tag for the activity content. */
1475
- locale?: string;
1476
- /** IRI references to learning objectives addressed by this activity. */
1477
- learningObjectives?: string[];
1478
- /** Subjective difficulty on a 1–5 scale. */
1479
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1480
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1481
- ai?: ActivityAiPermissions;
1482
- }
1483
- interface FillInTheBlanksData {
1484
- schemaVersion: '1.0';
1485
- type: 'fill-in-the-blanks';
1486
- /** Unique identifier for this activity. */
1487
- id: string;
1488
- /** Human-readable title used in xAPI statements and error boundaries. */
1489
- title: string;
1490
- /** Passage text containing `{{blank_id}}` placeholders. */
1491
- passage: string;
1492
- /**
1493
- * Optional sanitised rich-HTML rendering of the passage, parallel to
1494
- * `WrittenResponseData.promptHtml`. Carried and redacted as learner-visible
1495
- * content; not rendered by the SDK yet. Sanitisation is the application's.
1496
- */
1497
- passageHtml?: string;
1498
- /** Configuration for each blank in the passage. */
1499
- blanks: BlankConfig[];
1500
- /** Scoring algorithm applied when the learner submits. */
1501
- scoringStrategy: 'all-or-nothing' | 'partial';
1502
- /** Optional media shown above the passage. */
1503
- media?: ActivityMedia;
1504
- /** Optional authored overall feedback shown after submission. */
1505
- feedback?: ActivityFeedback;
1506
- /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1507
- passThreshold?: number;
1508
- /** BCP 47 language tag for the activity content. */
1509
- locale?: string;
1510
- /** IRI references to learning objectives addressed by this activity. */
1511
- learningObjectives?: string[];
1512
- /** Subjective difficulty on a 1–5 scale. */
1513
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1514
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1515
- ai?: ActivityAiPermissions;
1516
- }
1517
- /** A single criterion within a written-response grading rubric. */
1518
- interface WrittenResponseRubricCriterion {
1519
- /** Criterion name (e.g. "Task achievement", "Grammar range"). */
1520
- name: string;
1521
- /** Optional longer description of what the criterion assesses. */
1522
- description?: string;
1523
- /** Non-negative weight of this criterion in the overall grade. */
1524
- weight: number;
1525
- }
1526
- /** Grading rubric attached to a written-response activity. */
1527
- interface WrittenResponseRubric {
1528
- /** Optional display label for the rubric as a whole. */
1529
- label?: string;
1530
- /** The criteria the response is graded against. Non-empty when present. */
1531
- criteria: WrittenResponseRubricCriterion[];
1532
- }
1533
- /**
1534
- * Data contract for a Written Response activity (free-text writing graded
1535
- * asynchronously — by an AI or human grader — after submission).
1536
- *
1537
- * Wire-format note (Req 22.9): field names and casing are locked for
1538
- * byte-compatibility with consumer-stored JSONB rows. `feedback` and
1539
- * `passThreshold` are SDK-side optional additions for component parity
1540
- * (Req 22.8 / Req 3.9) — being optional, their absence keeps stored payloads
1541
- * byte-identical.
1542
- */
1543
- interface WrittenResponseData {
1544
- schemaVersion: '1.0';
1545
- type: 'written-response';
1546
- /** Unique identifier for this activity. */
1547
- id: string;
1548
- /** Human-readable title used in xAPI statements and error boundaries. */
1549
- title: string;
1550
- /** The writing prompt, as plain text. */
1551
- prompt: string;
1552
- /**
1553
- * Optional sanitised rich-HTML sidecar of the prompt. `<WrittenResponse>`
1554
- * renders it in place of `prompt` when a `sanitizeHtml` function is
1555
- * supplied; without one the plain `prompt` is rendered and escaped.
1556
- */
1557
- promptHtml?: string;
1558
- /** Minimum acceptable word count (≥ 0). */
1559
- minWords: number;
1560
- /** Maximum acceptable word count (≥ 1, and ≥ `minWords`). */
1561
- maxWords: number;
1562
- /** Optional grading rubric consumed by the asynchronous grader. */
1563
- rubric?: WrittenResponseRubric;
1564
- /** Optional target language/level for the response (e.g. `"en-A2"`, `"es-B1"`). */
1565
- languageTarget?: string;
1566
- /** Optional media shown above the prompt. */
1567
- media?: ActivityMedia;
1568
- /** Optional authored overall feedback (surfaced once the deferred grade exists). */
1569
- feedback?: ActivityFeedback;
1570
- /** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1571
- passThreshold?: number;
1572
- /** BCP 47 language tag for the activity content. */
1573
- locale?: string;
1574
- /** IRI references to learning objectives addressed by this activity. */
1575
- learningObjectives?: string[];
1576
- /** Subjective difficulty on a 1–5 scale. */
1577
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1578
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1579
- ai?: ActivityAiPermissions;
1580
- }
1581
- /**
1582
- * A second, slower recording of a dictation — a separate file (a text-to-speech
1583
- * render at reduced speed, or a slower human reading), not a `playbackRate`
1584
- * change. It carries no `captionsUrl` (a caption track of a dictation is the
1585
- * transcript on screen) and no `playback` policy of its own: it follows the
1586
- * policy on `media`, except the play budget, which it never has.
1587
- */
1588
- interface DictationSlowMedia {
1589
- type: 'audio';
1590
- /** Source URL, under the same policy as {@link ActivityMedia.url}. */
1591
- url: string;
1592
- /** Accessible label — a description ("Recording, slow"), never a transcription. */
1593
- alt?: string;
1594
- }
1595
- /**
1596
- * A whole-word rewrite applied to BOTH the transcript and the attempt before
1597
- * they are compared. `from` and `to` are literal text: a rule is applied to
1598
- * every whole-word occurrence, exactly once, in the order the rules are listed,
1599
- * after spacing is collapsed and before punctuation is ignored — so a rule may
1600
- * name a symbol (`{ from: '&', to: 'and' }`) and a multi-word `from` matches
1601
- * across any whitespace.
1602
- */
1603
- interface DictationEquivalence {
1604
- /** What to rewrite: at least one code point that survives case, quote and whitespace folding. */
1605
- from: string;
1606
- /** What it becomes: 1..200 code points, something of which survives normalisation. A rule cannot delete a word. */
1607
- to: string;
1608
- }
1609
- /** Grading tolerances for a dictation. Part of the answer key. */
1610
- interface DictationTolerance {
1611
- /** Whole-word rewrites, applied in listed order, each exactly once. */
1612
- equivalences?: DictationEquivalence[];
1613
- }
1614
- /**
1615
- * Data contract for a Dictation activity: the learner listens to a recording
1616
- * and types what they hear; the grade is how close their text is to the
1617
- * transcript.
1618
- *
1619
- * Scoring is character-level similarity over the whole sentence —
1620
- * `(length − edit distance) / length` over NFC code points after case,
1621
- * punctuation and spacing are ignored — with one {@link ScoringDetail} per
1622
- * transcript word carrying that word's own {@link ScoringDetail.score}. It is
1623
- * deliberately not a mode of {@link FillInTheBlanksData}: a blank is right or
1624
- * wrong, a dictation is graded on a continuum, and the whole `TextMatchPolicy`
1625
- * surface (typo tolerance, diacritic folding) would be a second, competing
1626
- * notion of "close enough".
1627
- */
1628
- interface DictationData {
1629
- schemaVersion: '1.0';
1630
- type: 'dictation';
1631
- /** Unique identifier for this activity. */
1632
- id: string;
1633
- /**
1634
- * Human-readable title used in xAPI statements and error boundaries. It is
1635
- * the stem the learner sees, so it must not contain the transcript.
1636
- */
1637
- title: string;
1638
- /**
1639
- * What the learner is expected to type. ANSWER KEY. 1..2000 code points,
1640
- * before and after its equivalences are applied, and something must survive
1641
- * normalisation.
1642
- */
1643
- transcript: string;
1644
- /**
1645
- * Alternative transcriptions accepted at full credit — a regional spelling, a
1646
- * numeral, a whole-sentence contraction variant. ANSWER KEY. At most 10, each
1647
- * 1..2000 code points, none normalising equal to the transcript or to each
1648
- * other. The attempt is scored against every candidate and the best
1649
- * similarity wins; a tie goes to `transcript`.
1650
- */
1651
- acceptedTranscripts?: string[];
1652
- /**
1653
- * The recording. Audio only, never with captions. Optional in the schema — a
1654
- * dictation inside an item group may draw on the group's stimulus recording —
1655
- * but required whenever `slowMedia` is present. Budgeted per slot like any
1656
- * other activity media, with the usual playback policy.
1657
- */
1658
- media?: ActivityMedia;
1659
- /** A second, slower recording. See {@link DictationSlowMedia}. */
1660
- slowMedia?: DictationSlowMedia;
1661
- /**
1662
- * Progressive word hints, in `practice` only (the transcript is absent in
1663
- * `exam` mode, so there is nothing to reveal). Public: it reveals nothing by
1664
- * itself. `progressive-words` reveals the transcript's words left to right,
1665
- * one per request; a revealed hint never changes the score.
1666
- */
1667
- hints?: {
1668
- mode: 'progressive-words';
1669
- };
1670
- /** Grading tolerances. ANSWER KEY. */
1671
- tolerance?: DictationTolerance;
1672
- /** Optional authored overall feedback shown after submission. */
1673
- feedback?: ActivityFeedback;
1674
- /** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1675
- passThreshold?: number;
1676
- /** BCP 47 language tag for the activity content. Presentation only: scoring never reads it. */
1677
- locale?: string;
1678
- /** IRI references to learning objectives addressed by this activity. */
1679
- learningObjectives?: string[];
1680
- /** Subjective difficulty on a 1–5 scale. */
1681
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1682
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1683
- ai?: ActivityAiPermissions;
1684
- }
1685
- /** A dimension of speech a pronunciation assessor scores, on a 0–100 scale. */
1686
- type ReadAloudDimension = 'accuracy' | 'fluency' | 'completeness' | 'prosody';
1687
- /** One assessor dimension counted towards a read-aloud grade, and its weight. */
1688
- interface ReadAloudDimensionWeight {
1689
- name: ReadAloudDimension;
1690
- /**
1691
- * Relative weight, 0..1000 (`READ_ALOUD_MAX_DIMENSION_WEIGHT`). Weights need
1692
- * not add up to anything; a weight of 0 leaves the dimension out of the grade.
1693
- */
1694
- weight: number;
1695
- }
1696
- /**
1697
- * The limits on a read-aloud take, applied where the learner records. No grade
1698
- * reads them: whether a take is plausible speech is decided from the server's
1699
- * own measurement of the recording, never from a bound the client enforced.
1700
- */
1701
- interface RecordingBounds {
1702
- /** The longest take, in seconds: above 0, and at most `READ_ALOUD_MAX_SECONDS` (300). */
1703
- maxSeconds: number;
1704
- /** The shortest take, in seconds: at least 0, and below `maxSeconds`. */
1705
- minSeconds?: number;
1706
- /** How many takes the learner may record: a whole number from 1 to `READ_ALOUD_MAX_TAKES` (20). */
1707
- maxTakes?: number;
1708
- }
1709
- /**
1710
- * A second, slower model recording of the reference text — a separate file,
1711
- * not a `playbackRate` change. It carries no `captionsUrl` and no `playback`
1712
- * policy of its own, requires `media`, must be a different file from it, and
1713
- * cannot accompany a play limit on `media`.
1714
- */
1715
- interface ReadAloudSlowMedia {
1716
- type: 'audio';
1717
- /** Source URL, under the same policy as {@link ActivityMedia.url}. */
1718
- url: string;
1719
- /** Accessible label — a description ("Model recording, slow"). */
1720
- alt?: string;
1721
- }
1722
- /**
1723
- * Data contract for a Read Aloud activity: the learner reads a text aloud, an
1724
- * assessor measures the recording, and the grade is the weighted total of the
1725
- * assessor's dimension scores.
1726
- *
1727
- * Grading is deferred — `evaluate()` returns `deferred` until the application
1728
- * holds an assessment — and nothing here is an answer key: the learner is shown
1729
- * the text they are asked to read.
1730
- */
1731
- interface ReadAloudData {
1732
- schemaVersion: '1.0';
1733
- type: 'read-aloud';
1734
- /** Unique identifier for this activity. */
1735
- id: string;
1736
- /** Human-readable title used in xAPI statements and error boundaries. */
1737
- title: string;
1738
- /** Optional instructions shown to the learner, as plain text. */
1739
- instructions?: string;
1740
- /**
1741
- * The text the learner reads aloud. Shown to the learner (public). At most
1742
- * `READ_ALOUD_MAX_REFERENCE_LENGTH` code points before and after
1743
- * normalisation, in a script written with spaces between words.
1744
- */
1745
- referenceText: string;
1746
- /**
1747
- * BCP 47 tag in canonical form with a region (e.g. `en-US`). GRADE-DECIDING:
1748
- * an assessment made for another locale is unscorable.
1749
- */
1750
- locale: string;
1751
- /** A model recording of the text. Audio only; a playback policy is allowed. */
1752
- media?: ActivityMedia;
1753
- /** A second, slower model recording. See {@link ReadAloudSlowMedia}. */
1754
- slowMedia?: ReadAloudSlowMedia;
1755
- /** The limits on a take. See {@link RecordingBounds}. */
1756
- recording: RecordingBounds;
1757
- /**
1758
- * The assessor dimensions the grade is made of, with their weights: 1 to 4
1759
- * entries, each name at most once, at least one weight above 0. A weighted
1760
- * dimension the assessment has no score for makes the take unscorable, never
1761
- * a zero. Public.
1762
- */
1763
- scoring: {
1764
- dimensions: ReadAloudDimensionWeight[];
1765
- };
1766
- /** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
1767
- passThreshold?: number;
1768
- /** Optional authored overall feedback, selected once the grade exists. */
1769
- feedback?: ActivityFeedback;
1770
- /** IRI references to learning objectives addressed by this activity. */
1771
- learningObjectives?: string[];
1772
- /** Subjective difficulty on a 1–5 scale. */
1773
- difficultyLevel?: 1 | 2 | 3 | 4 | 5;
1774
- /** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
1775
- ai?: ActivityAiPermissions;
1776
- }
1777
- /**
1778
- * Maps each ActivityType string to its learner-response shape. Open for
1779
- * extension via module augmentation, mirroring {@link ActivityDataMap}.
1780
- */
1781
- interface LearnerResponseMap {
1782
- 'multiple-choice': MultipleChoiceLearnerResponse;
1783
- 'fill-in-the-blanks': FillInTheBlanksLearnerResponse;
1784
- 'written-response': WrittenResponseLearnerResponse;
1785
- 'gap-select': GapSelectLearnerResponse;
1786
- dictation: DictationLearnerResponse;
1787
- 'read-aloud': ReadAloudLearnerResponse;
1788
- }
1789
- /** Union of all learner response shapes. */
1790
- type LearnerResponse = LearnerResponseMap[keyof LearnerResponseMap];
1791
- /** Learner response for a Multiple Choice activity. */
1792
- interface MultipleChoiceLearnerResponse {
1793
- type: 'multiple-choice';
1794
- /** IDs of the options the learner selected. */
1795
- selectedOptionIds: string[];
1796
- /**
1797
- * Hints the learner was shown before submitting this answer, counted from
1798
- * the start of the question — the author's and AI hints alike. Written by
1799
- * the SDK's components in `practice`; absent or 0 means none. A scoring
1800
- * policy's `hintPenalty` charges for them. Client-reported, like any count
1801
- * a browser makes.
1802
- */
1803
- hintsRevealed?: number;
1804
- }
1805
- /** Learner response for a Fill-in-the-Blanks activity. */
1806
- interface FillInTheBlanksLearnerResponse {
1807
- type: 'fill-in-the-blanks';
1808
- /** Map of blank ID to the learner's typed answer. */
1809
- answers: Record<string, string>;
1810
- /**
1811
- * Hints the learner was shown before submitting this answer, counted from
1812
- * the start of the question — the author's and AI hints alike. Written by
1813
- * the SDK's components in `practice`; absent or 0 means none. A scoring
1814
- * policy's `hintPenalty` charges for them. Client-reported, like any count
1815
- * a browser makes.
1816
- */
1817
- hintsRevealed?: number;
1818
- }
1819
- /**
1820
- * Learner response for a Gap Select activity.
1821
- *
1822
- * A gap the learner has not answered is **absent from the map, or holds an
1823
- * empty string** — the two are equivalent, because a `<select>` whose
1824
- * placeholder is still showing submits `''`. Neither is a wrong answer: the
1825
- * scorer reports it as `incorrect-omission`, which is how a blank the learner
1826
- * never reached is told apart from one they got wrong.
1827
- */
1828
- interface GapSelectLearnerResponse {
1829
- type: 'gap-select';
1830
- /** Map of gap ID to the ID of the choice the learner selected. */
1831
- selections: Record<string, string>;
1832
- /**
1833
- * Hints the learner was shown before submitting this answer, counted from
1834
- * the start of the question — the author's and AI hints alike. Written by
1835
- * the SDK's components in `practice`; absent or 0 means none. A scoring
1836
- * policy's `hintPenalty` charges for them. Client-reported, like any count
1837
- * a browser makes.
1838
- */
1839
- hintsRevealed?: number;
1840
- }
1841
- /** Learner response for a Written Response activity. */
1842
- interface WrittenResponseLearnerResponse {
1843
- type: 'written-response';
1844
- /** The learner's free-text response. */
1845
- text: string;
1846
- /** Word count of `text`, computed with the canonical `countWords()` helper. */
1847
- wordCount: number;
1848
- }
1849
- /**
1850
- * Learner response for a Dictation activity.
1851
- *
1852
- * `text` is exactly what the learner typed; every normalisation happens in the
1853
- * scorer. `hintsRevealed` is client-reported — how many hint words were shown
1854
- * before submitting. The scorer ignores it; a scoring policy's `hintPenalty`
1855
- * charges for it, knowing it cannot be verified.
1856
- */
1857
- interface DictationLearnerResponse {
1858
- type: 'dictation';
1859
- /** The learner's text, as typed. */
1860
- text: string;
1861
- /**
1862
- * Hint words shown before submitting (practice only), counted from the start
1863
- * of the question. Absent or 0 means none.
1864
- */
1865
- hintsRevealed?: number;
1866
- }
1867
- /**
1868
- * A learner recording held by the application. `key` is opaque,
1869
- * application-issued storage identity: never a URL or bytes. `durationMs` is a
1870
- * client claim no grade reads.
1871
- */
1872
- interface RecordingRef {
1873
- key: string;
1874
- /** The media type the recording was stored as, e.g. `audio/wav`. */
1875
- mimeType: string;
1876
- durationMs?: number;
1877
- }
1878
- /**
1879
- * Learner response for a Read Aloud activity: a reference to the recording the
1880
- * application stored, never the audio itself.
1881
- */
1882
- interface ReadAloudLearnerResponse {
1883
- type: 'read-aloud';
1884
- /** `null`: the learner submitted without recording (a blank). A take that failed to upload is never `null`. */
1885
- recording: RecordingRef | null;
1886
- /** Telemetry: takes used. No grade reads it. */
1887
- takes?: number;
1888
- }
1889
- /**
1890
- * Fine-grained outcome of the learner's action on a single item:
1891
- * - `correct` — the learner selected/entered the right answer.
1892
- * - `incorrect` — the learner selected/entered a wrong answer.
1893
- * - `correct-omission` — the learner correctly left a non-answer unselected (multiple-choice only).
1894
- * - `incorrect-omission` — a correct answer the learner did not give: an unselected
1895
- * correct option, an unanswered gap, a missing dictation word.
1896
- */
1897
- type ScoringOutcome = 'correct' | 'incorrect' | 'correct-omission' | 'incorrect-omission';
1898
- /** Per-item scoring breakdown returned by the scoring engine. */
1899
- interface ScoringDetail {
1900
- /** ID of the option, blank, gap or dictation word (`w1`…`wN`) this detail refers to. */
1901
- itemId: string;
1902
- /**
1903
- * The outcome of the learner's action on this item. On a multiple-choice
1904
- * option it says both whether the option was chosen and whether it is part
1905
- * of the answer.
1906
- *
1907
- * Until 1.0 a detail also carried `correct`, which on a multiple-choice
1908
- * option meant "the learner acted correctly on it" — an unselected wrong
1909
- * option read `correct: true` — and a detail stored before 0.3 carries only
1910
- * that. The SDK's components still read such a stored detail; a type written
1911
- * against 1.0 does not describe it.
1912
- */
1913
- outcome: ScoringOutcome;
1914
- /** The learner's actual response for this item. */
1915
- learnerResponse: string | string[];
1916
- /** The expected correct response(s) for this item. */
1917
- correctResponse: string | string[];
1918
- /**
1919
- * Weight applied to this item's contribution to the overall score. Every
1920
- * built-in scorer currently weights every item equally and writes `1`.
1921
- */
1922
- weight?: number;
1923
- /**
1924
- * The item's own scaled score in [0, 1], for a type that grades its items on
1925
- * a continuum rather than right or wrong. Written by the dictation scorer
1926
- * (the similarity of each transcript word to what was typed for it) and by
1927
- * `gradeReadAloud` (the assessor's accuracy for each reference word as a
1928
- * fraction, 0 for an omitted word, and absent where the assessor gave none).
1929
- * Absent, an item is right or wrong: read `outcome`.
1930
- */
1931
- score?: number;
1932
- }
1933
- /** Full scoring result returned by the scoring engine. */
1934
- interface ScoringResult {
1935
- /** Scaled score in the range [0, 1]. */
1936
- score: number;
1937
- /** Maximum possible scaled score (always 1). */
1938
- maxScore: number;
1939
- /** Whether the score meets or exceeds the activity's pass threshold. */
1940
- passed: boolean;
1941
- /**
1942
- * The authored overall feedback selected for this result: `feedback.correct`
1943
- * when the learner passed, `feedback.incorrect` otherwise; `null` when the
1944
- * activity authored no matching feedback.
1945
- */
1946
- feedback: string | null;
1947
- /** Per-item scoring breakdown. */
1948
- details: ScoringDetail[];
1949
- }
1950
- /**
1951
- * Progress facts about a deferred (asynchronously graded) submission that are
1952
- * computable synchronously at submit time. For written-response, both fields
1953
- * are always present and `wordCount` is recomputed from the submitted text
1954
- * with the canonical `countWords()` (the client-supplied count is not trusted).
1955
- */
1956
- interface DeferredScoringPartial {
1957
- /** Whether the recomputed word count falls within `[minWords, maxWords]`. */
1958
- withinWordBounds?: boolean;
1959
- /** Recomputed word count of the submitted text. */
1960
- wordCount?: number;
1961
- [key: string]: unknown;
1962
- }
1963
- /**
1964
- * Why an item has no grade *yet* — a state that is explicitly not final, and
1965
- * so keeps a composed assessment `provisional`.
1966
- *
1967
- * - `requires_async_grading` — the type is graded later by an AI or a human.
1968
- * - `no_response_recorded` — nothing was stored against this slot at all. It
1969
- * is NOT the same as a zero: a learner who left a question blank on a
1970
- * submitted paper has earned zero, but a slot missing because a save failed,
1971
- * a grade has not landed, or the attempt is still open has earned nothing
1972
- * yet, and recording it as zero is how an incomplete attempt becomes a
1973
- * plausible-looking fail.
1974
- * - `grade_rejected` — a grade came back, and `outcomeFromGrade` refused it
1975
- * because its numbers cannot be a grade (a `score` or `maxScore` that is not
1976
- * a finite number, a `maxScore` of 0 or less, or a score outside 0 to
1977
- * `maxScore`). The record is kept on `rejectedGrade`. A real grade is still
1978
- * owed, so re-grade the slot: waiting will not bring one.
1979
- */
1980
- type DeferredReason = 'requires_async_grading' | 'no_response_recorded' | 'grade_rejected';
1981
- /**
1982
- * The outcome of evaluating a learner response against an activity — the
1983
- * union `evaluate()` returns. Unlike {@link ScoringResult}, it can express
1984
- * "not gradable yet" (`deferred`) and "not gradable at all" (`unscorable`),
1985
- * so an ungraded written response is never conflated with a score of 0.
1986
- */
1987
- type ItemOutcome = {
1988
- status: 'scored';
1989
- /** Scaled score in the range [0, 1]. */
1990
- score: number;
1991
- /** Maximum possible scaled score. */
1992
- maxScore: number;
1993
- /** Whether the score meets or exceeds the activity's pass threshold. */
1994
- passed: boolean;
1995
- /** Authored overall feedback selected by pass state, or null. */
1996
- feedback: string | null;
1997
- /** Per-item scoring breakdown. */
1998
- details: ScoringDetail[];
1999
- } | {
2000
- status: 'deferred';
2001
- /** Why no grade exists yet. See {@link DeferredReason}. */
2002
- reason: DeferredReason;
2003
- /** Maximum possible scaled score once graded. */
2004
- maxScore: number;
2005
- /** Synchronously computable progress facts (word bounds, counts). */
2006
- partial?: DeferredScoringPartial;
2007
- /**
2008
- * The record `outcomeFromGrade` refused, verbatim, when `reason` is
2009
- * `grade_rejected` — kept for audit and for the re-grade. Nothing on it
2010
- * is mirrored onto the outcome, and nothing reads it as a grade.
2011
- */
2012
- rejectedGrade?: GradeRecord;
2013
- } | {
2014
- status: 'graded';
2015
- /**
2016
- * A grade produced by an asynchronous grader (AI or human) and handed
2017
- * back to the SDK. This is the state a `deferred` outcome transitions
2018
- * to once grading completes; nothing in the SDK ever manufactures it.
2019
- */
2020
- grade: GradeRecord;
2021
- /** Scaled score in the range [0, 1], mirrored from `grade` for uniform reads. */
2022
- score: number;
2023
- maxScore: number;
2024
- passed: boolean;
2025
- /** Narrative feedback from the grader, mirrored from `grade`. */
2026
- feedback: string | null;
2027
- } | {
2028
- status: 'unscorable';
2029
- /** Why no grade can be produced (e.g. unregistered activity type). */
2030
- reason: string;
2031
- /** Maximum possible scaled score, when known. */
2032
- maxScore: number;
2033
- /**
2034
- * A machine-readable reason, written by `outcomeFromUnscorable` — for
2035
- * example a read-aloud `SpeechUnscorableCode`. `evaluate` never writes it.
2036
- */
2037
- code?: string;
2038
- };
2039
- /** A single validation error produced by `validateActivity`. */
2040
- interface ValidationError {
2041
- /** JSON-path-style location of the invalid field. */
2042
- path: string[];
2043
- /** Human-readable description of the validation failure. */
2044
- message: string;
2045
- /** Machine-readable error code. */
2046
- code: string;
2047
- }
2048
- /**
2049
- * Result of a `validateActivity` call.
2050
- * On success, `data` is the validated and typed activity data.
2051
- * On failure, `errors` contains one entry per violated constraint.
2052
- */
2053
- type ValidationResult<T> = {
2054
- success: true;
2055
- data: T;
2056
- } | {
2057
- success: false;
2058
- errors: ValidationError[];
2059
- };
2060
- /** The payload delivered to an activity's `onComplete` callback. */
2061
- interface ActivityResult {
2062
- /** Scaled score in the range [0, 1]. */
2063
- score: number;
2064
- /** Maximum possible scaled score (always 1). */
2065
- maxScore: number;
2066
- /** Whether the learner passed based on the activity's pass threshold. */
2067
- passed: boolean;
2068
- /** Time in milliseconds from first interaction to submission. */
2069
- timeSpent: number;
2070
- /** The xAPI statement built and (optionally) sent for this attempt. */
2071
- xapiStatement: XAPIStatement;
2072
- }
2073
- /**
2074
- * The kinds of interaction the built-in components emit. Custom activity types
2075
- * registered by consumers may emit their own kinds, so any string is accepted;
2076
- * the named literals are kept for autocompletion.
2077
- */
2078
- type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled' | 'hint-requested' | 'text-changed' | 'submitted'
2079
- /**
2080
- * A play was charged against a recording's budget, before any audio was
2081
- * audible. Emitted only when a budget is in force, so a `review`-mode replay
2082
- * never pollutes the record an appeal reads.
2083
- */
2084
- | 'media-play-consumed'
2085
- /** An exhausted budget refused a play. The artifact an appeal asks for. */
2086
- | 'media-play-refused'
2087
- /** A charged play produced no audio — a failed or expired media URL. */
2088
- | 'media-play-errored'
2089
- /** A charged play was returned, because the consumer accepted the refund. */
2090
- | 'media-play-refunded'
2091
- /** The microphone began capturing a take. */
2092
- | 'recording-started'
2093
- /** A take ended, by the learner or at its time limit. */
2094
- | 'recording-stopped'
2095
- /** A take was thrown away, to record another. */
2096
- | 'recording-discarded'
2097
- /** The application stored a take and returned its reference. */
2098
- | 'recording-uploaded'
2099
- /** Storing a take failed. A take that failed to upload is never a blank answer. */
2100
- | 'recording-upload-failed'
2101
- /** A stored take was sent for assessment. */
2102
- | 'assessment-requested'
2103
- /** An assessment request failed: the take has no grade. */
2104
- | 'assessment-failed'
2105
- /** Interactive video: the learner started playback. */
2106
- | 'video-played'
2107
- /** Interactive video: playback paused, by the learner or by a quiz. */
2108
- | 'video-paused'
2109
- /** Interactive video: the playhead moved (`from`, `to`, and whether navigation `clamped` it). */
2110
- | 'video-seeked' | 'video-rate-changed' | 'video-ended'
2111
- /** Interactive video: a quiz opened (`cueId`, `at`). */
2112
- | 'video-quiz-opened'
2113
- /** Interactive video: one question of an open quiz was shown (`cueId`, `slotId`). */
2114
- | 'video-quiz-question-shown' | 'video-quiz-skipped' | 'video-quiz-closed'
2115
- /**
2116
- * Interactive video: the captions a learner chose. `payload.srclang` is the
2117
- * primary line's language, or `null` with captions off; `payload.secondary` is
2118
- * the second line's, or `null` with none showing. Fires when either changes.
2119
- */
2120
- | 'video-captions-changed' | 'video-fullscreen-changed' | 'video-pip-changed'
2121
- /**
2122
- * An AI hint was shown to the learner (`hintNumber`, and the port's
2123
- * `provenance` when it sent one). Only a hint the SDK accepted: a refused one
2124
- * is never shown and never reported here.
2125
- */
2126
- | 'ai-hint-shown'
2127
- /** An AI explanation of the graded answer was shown (`provenance`, `usage` when sent). */
2128
- | 'ai-explanation-shown'
2129
- /**
2130
- * An AI answer was refused, and the learner was told help was not available
2131
- * (`feature`, `reason` — an {@link AiRefusal} — and `hintNumber` for a hint).
2132
- *
2133
- * **The text itself is never carried**: a hint refused for revealing the
2134
- * answer contains the answer, and this event is logged. It is how a host
2135
- * watches its own model in production, where the development console warning
2136
- * it accompanies is silent.
2137
- */
2138
- | 'ai-help-refused'
2139
- /**
2140
- * Feedback on a draft of a written response was shown (`draftNumber`, the
2141
- * number of `corrections`, the `indicativeScore` when there was one, and the
2142
- * port's `provenance` and `usage` when it sent them). Never the text.
2143
- */
2144
- | 'ai-writing-feedback-shown'
2145
- /**
2146
- * Coaching on a read-aloud was shown (the number of `words` it coached, and
2147
- * the port's `provenance` and `usage` when it sent them). Never the text.
2148
- */
2149
- | 'ai-coaching-shown' | (string & {});
2150
- /** Fired by activity components on every discrete learner interaction. */
2151
- interface InteractionEvent {
2152
- /** The kind of interaction that occurred. */
2153
- type: InteractionKind;
2154
- /** ID of the activity that produced the event. */
2155
- activityId: string;
2156
- /** Unix timestamp (ms) of when the interaction occurred. */
2157
- timestamp: number;
2158
- /** Additional event-specific data. */
2159
- payload: Record<string, unknown>;
2160
- }
2161
-
2162
- export { type AiWritingCorrection as $, type ActivityAiPermissions as A, type AiDrafts as B, type AiDraftsCall as C, type AiDraftsRequest as D, type AiDraftsRun as E, type AiExplanationRequest as F, type AiFeature as G, type AiFillInTheBlanksFacts as H, type AiGapFact as I, type AiGapSelectFacts as J, type AiGeneratedDraft as K, type AiGrade as L, type AiHintRequest as M, type AiItemFacts as N, type AiMultipleChoiceFacts as O, type AiOptionFact as P, type AiProvenance as Q, type AiReadingFacts as R, type AiReadingWordFact as S, type AiRefusal as T, type AiRubricCriterionFact as U, type AiSoundFact as V, type AiSupportedActivityType as W, type AiTextResult as X, type AiVerdict as Y, type AiVideoDraft as Z, type AiWordFact as _, type ActivityData as a, type WrittenResponseRubricCriterion as a$, type AiWritingFacts as a0, type AiWritingFeedback as a1, type AiWritingFeedbackRequest as a2, type AiWritingFeedbackResult as a3, type BlankConfig as a4, type CriterionScore as a5, type DeferredReason as a6, type DeferredScoringPartial as a7, type DictationData as a8, type DictationEquivalence as a9, type ItemOutcome as aA, type LearnerResponse as aB, type LearnerResponseMap as aC, type MediaPlaybackPolicy as aD, type MediaTrack as aE, type MultipleChoiceData as aF, type MultipleChoiceLearnerResponse as aG, type MultipleChoiceOption as aH, type MultipleChoiceOptionMedia as aI, type NativeControlHint as aJ, type ReadAloudData as aK, type ReadAloudDimension as aL, type ReadAloudDimensionWeight as aM, type ReadAloudLearnerResponse as aN, type ReadAloudSlowMedia as aO, type RecordingBounds as aP, type RecordingRef as aQ, type ScoringDetail as aR, type ScoringOutcome as aS, type ScoringResult as aT, type TextMatchPolicy as aU, type TextMatchResult as aV, type ValidationError as aW, type ValidationResult as aX, type WrittenResponseData as aY, type WrittenResponseLearnerResponse as aZ, type WrittenResponseRubric as a_, type DictationLearnerResponse as aa, type DictationSlowMedia as ab, type DictationTolerance as ac, type DraftComplete as ad, type DraftContext as ae, type DraftIssue as af, type DraftNotComplete as ag, type DraftSeverity as ah, type DraftValidationResult as ai, type FillInTheBlanksData as aj, type FillInTheBlanksLearnerResponse as ak, type GapSelectBank as al, type GapSelectChoice as am, type GapSelectData as an, type GapSelectGap as ao, type GapSelectLearnerResponse as ap, type GradeRecord as aq, type Grader as ar, type GraderKind as as, type GraderUsage as at, type GradingState as au, type InlineCorrection as av, type InteractionEvent as aw, type InteractionKind as ax, type ItemFinding as ay, type ItemFindingSeverity as az, type ActivityDataMap as b, type XAPIActor as b0, type XAPIConfig as b1, type XAPIContext as b2, type XAPIContextActivities as b3, type XAPIError as b4, type XAPIObject as b5, type XAPIResult as b6, type XAPIScore as b7, type XAPIStatement as b8, type XAPIVerbObject as b9, levenshteinDistance as ba, matchText as bb, type ActivityFeedback as c, type ActivityMedia as d, type ActivityResult as e, type ActivityType as f, type AiActivityInput as g, type AiBlankFact as h, type AiCoaching as i, type AiCoachingRequest as j, type AiCoachingResult as k, type AiCoachingWord as l, type AiCriterionJudgement as m, type AiCritique as n, type AiCritiqueField as o, type AiCritiqueFinding as p, type AiCritiqueFindingResult as q, type AiCritiqueKind as r, type AiCritiqueRequest as s, type AiCritiqueResult as t, type AiDictationFacts as u, type AiDraftCaption as v, type AiDraftProblem as w, type AiDraftSettings as x, type AiDraftSource as y, type AiDraftType as z };