@intellectif/lk-core 0.21.0 → 1.0.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.
- package/CHANGELOG.md +31 -0
- package/README.md +6 -5
- package/dist/{activity-xhGV-Aeh.d.cts → activity-DgpBJeBc.d.cts} +137 -22
- package/dist/{activity-xhGV-Aeh.d.ts → activity-DgpBJeBc.d.ts} +137 -22
- package/dist/ai-check.cjs +104 -16
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +9 -5
- package/dist/ai-check.d.ts +9 -5
- package/dist/ai-check.js +103 -15
- package/dist/ai-check.js.map +1 -1
- package/dist/{chunk-2CV6NU32.cjs → chunk-2JT2WUQ2.cjs} +77 -39
- package/dist/chunk-2JT2WUQ2.cjs.map +1 -0
- package/dist/{chunk-D4TOPSHV.cjs → chunk-DSDXL3JO.cjs} +1216 -1105
- package/dist/chunk-DSDXL3JO.cjs.map +1 -0
- package/dist/{chunk-D3GP5MET.js → chunk-FBKTION6.js} +1093 -982
- package/dist/chunk-FBKTION6.js.map +1 -0
- package/dist/{chunk-Q7MV3IXJ.js → chunk-IARLJ5MW.js} +15 -16
- package/dist/chunk-IARLJ5MW.js.map +1 -0
- package/dist/{chunk-IHOEP4EB.js → chunk-RCZCP5OE.js} +311 -25
- package/dist/chunk-RCZCP5OE.js.map +1 -0
- package/dist/{chunk-X5DAXBJI.js → chunk-SJVRUPJT.js} +2 -2
- package/dist/{chunk-EOFWSGLU.cjs → chunk-TJJFKKH2.cjs} +4 -4
- package/dist/{chunk-EOFWSGLU.cjs.map → chunk-TJJFKKH2.cjs.map} +1 -1
- package/dist/{chunk-WQVZPIN7.cjs → chunk-VSH7ECQL.cjs} +44 -45
- package/dist/chunk-VSH7ECQL.cjs.map +1 -0
- package/dist/{chunk-NRTP7WCW.cjs → chunk-W5KB3DNT.cjs} +315 -29
- package/dist/chunk-W5KB3DNT.cjs.map +1 -0
- package/dist/{chunk-Z7QQZ53L.js → chunk-XS6U2ARZ.js} +70 -32
- package/dist/chunk-XS6U2ARZ.js.map +1 -0
- package/dist/index-BVNeSCWC.d.ts +548 -0
- package/dist/index-BlOLlwOi.d.cts +548 -0
- package/dist/index.cjs +129 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -18
- package/dist/index.d.ts +175 -18
- package/dist/index.js +34 -103
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +3 -94
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -3
- package/dist/schemas.d.ts +2 -3
- package/dist/schemas.js +6 -97
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +4 -4
- package/vectors/README.md +32 -2
- package/vectors/scoring.json +1 -498
- package/vectors/validation.json +6742 -0
- package/vectors/validation.mjs +195 -0
- package/dist/chunk-2CV6NU32.cjs.map +0 -1
- package/dist/chunk-D3GP5MET.js.map +0 -1
- package/dist/chunk-D4TOPSHV.cjs.map +0 -1
- package/dist/chunk-IHOEP4EB.js.map +0 -1
- package/dist/chunk-NRTP7WCW.cjs.map +0 -1
- package/dist/chunk-Q7MV3IXJ.js.map +0 -1
- package/dist/chunk-WQVZPIN7.cjs.map +0 -1
- package/dist/chunk-Z7QQZ53L.js.map +0 -1
- package/dist/index-BuD8Hc8o.d.cts +0 -2303
- package/dist/index-DnJ_PI4d.d.ts +0 -2303
- /package/dist/{chunk-X5DAXBJI.js.map → chunk-SJVRUPJT.js.map} +0 -0
package/dist/ai-check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai-check/cases.ts","../src/ai-check/index.ts"],"sourcesContent":["import { aiExplanationRequest, aiHintRequest } from '../ai.js';\nimport type {\n DictationData,\n FillInTheBlanksData,\n GapSelectData,\n LearnerResponse,\n MultipleChoiceData,\n} from '../types/activity.js';\nimport type { AiExplanationRequest, AiHintRequest } from '../types/ai.js';\n\n/**\n * The items the cases are built from. Small, ordinary language-course content\n * — and deliberately awkward in the places a model slips:\n *\n * - an answer a hint can hardly avoid naming (`Madrid`);\n * - an answer of one short word (`is`), which a hint may write as an ordinary\n * word and must not write beside its neighbour in the passage;\n * - an accented answer (`cañón`), so a hint that spells it without the accent\n * is still caught;\n * - a passage with more than one blank, so a hint can be about the wrong one.\n */\nconst capital: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-mc',\n title: 'Capital cities',\n question: 'What is the capital of Spain?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'madrid', text: 'Madrid', isCorrect: true },\n { id: 'barcelona', text: 'Barcelona', isCorrect: false },\n { id: 'seville', text: 'Seville', isCorrect: false },\n ],\n};\n\nconst introduction: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-fib',\n title: 'Introducing yourself',\n passage: 'My name {{be}} Rossi and I {{live}} in Madrid.',\n blanks: [\n { id: 'be', acceptedAnswers: ['is'] },\n { id: 'live', acceptedAnswers: ['live'], hint: 'Present simple, first person.' },\n ],\n scoringStrategy: 'partial',\n};\n\nconst landscape: GapSelectData = {\n schemaVersion: '1.0',\n type: 'gap-select',\n id: 'ai-check-gap',\n title: 'Geography',\n locale: 'es-ES',\n passage: 'El río pasa por un {{g1}} muy profundo, y el agua {{g2}} fría.',\n gaps: [\n {\n id: 'g1',\n choices: [\n { id: 'canon', text: 'cañón' },\n { id: 'campo', text: 'campo' },\n ],\n correctChoiceId: 'canon',\n },\n {\n id: 'g2',\n choices: [\n { id: 'esta', text: 'está' },\n { id: 'es', text: 'es' },\n ],\n correctChoiceId: 'esta',\n },\n ],\n scoringStrategy: 'partial',\n};\n\nconst listening: DictationData = {\n schemaVersion: '1.0',\n type: 'dictation',\n id: 'ai-check-dictation',\n title: 'Listen and type',\n transcript: 'The train leaves at half past nine.',\n};\n\n/**\n * One call to make against a host's port, and what a learner was looking at\n * when the SDK would have made it.\n */\nexport interface AiCheckCase {\n /** Stable across releases, so a host can name one in an allow-list or a report. */\n id: string;\n /** Which port the case is for. */\n feature: 'explanation' | 'hint';\n /** What the learner did, in a few words, for a report a person reads. */\n about: string;\n /** The request, built by the SDK exactly as a component builds it. */\n request: AiExplanationRequest | AiHintRequest;\n}\n\nconst chose = (ids: string[]): LearnerResponse => ({\n type: 'multiple-choice',\n selectedOptionIds: ids,\n});\nconst typed = (answers: Record<string, string>): LearnerResponse => ({\n type: 'fill-in-the-blanks',\n answers,\n});\nconst picked = (choices: Record<string, string>): LearnerResponse => ({\n type: 'gap-select',\n selections: choices,\n});\nconst wrote = (text: string): LearnerResponse => ({ type: 'dictation', text });\n\n/** Builds a case, failing loudly rather than shipping a case with no request. */\nfunction explanationCase(\n id: string,\n about: string,\n data: Parameters<typeof aiExplanationRequest>[0]['data'],\n response: LearnerResponse,\n): AiCheckCase {\n const request = aiExplanationRequest({ data, response });\n if (request === null) {\n throw new Error(`ai-check: no explanation request for \"${id}\"`);\n }\n return { id, feature: 'explanation', about, request };\n}\n\nfunction hintCase(\n id: string,\n about: string,\n data: Parameters<typeof aiHintRequest>[0]['data'],\n response: LearnerResponse,\n previousHints: string[] = [],\n): AiCheckCase {\n const request = aiHintRequest({ data, response, previousHints });\n if (request === null) {\n throw new Error(`ai-check: no hint request for \"${id}\"`);\n }\n return { id, feature: 'hint', about, request };\n}\n\n/**\n * The cases {@link runAiCheck} runs: every type the SDK explains, answered\n * right, wrong and — where a type can be — partly right; and every type it\n * hints for, before an answer and after a wrong one.\n *\n * They are ordinary calls. What makes them a test is that the SDK knows the\n * answer to each, so it can check what your model writes about it.\n *\n * A fresh array each call, so a caller may filter, slice or add to it without\n * changing what the next caller gets.\n */\nexport function aiCheckCases(): AiCheckCase[] {\n return [\n explanationCase('mc-correct', 'Chose the right city', capital, chose(['madrid'])),\n explanationCase('mc-wrong', 'Chose Barcelona', capital, chose(['barcelona'])),\n explanationCase('mc-blank', 'Chose nothing and submitted', capital, chose([])),\n explanationCase(\n 'fib-correct',\n 'Filled both blanks correctly',\n introduction,\n typed({ be: 'is', live: 'live' }),\n ),\n explanationCase(\n 'fib-partly',\n 'One blank right, one wrong',\n introduction,\n typed({ be: 'is', live: 'lives' }),\n ),\n explanationCase(\n 'fib-wrong',\n 'Both blanks wrong',\n introduction,\n typed({ be: 'are', live: 'living' }),\n ),\n explanationCase(\n 'gap-partly',\n 'One gap right, one wrong, in Spanish',\n landscape,\n picked({ g1: 'canon', g2: 'es' }),\n ),\n explanationCase(\n 'gap-wrong',\n 'Both gaps wrong, in Spanish',\n landscape,\n picked({ g1: 'campo', g2: 'es' }),\n ),\n explanationCase(\n 'dictation-near',\n 'Typed the sentence with two words wrong',\n listening,\n wrote('The train leaves at half past five'),\n ),\n explanationCase(\n 'dictation-correct',\n 'Typed the sentence correctly',\n listening,\n wrote('The train leaves at half past nine.'),\n ),\n hintCase('mc-hint-1', 'Has not answered yet', capital, chose([])),\n hintCase('mc-hint-2', 'Asked again after choosing Seville', capital, chose(['seville']), [\n 'Think about where the government sits.',\n ]),\n hintCase('fib-hint-1', 'Both blanks empty', introduction, typed({})),\n hintCase(\n 'fib-hint-2',\n 'Asked again with one blank wrong',\n introduction,\n typed({ be: 'are', live: '' }),\n ['Look again at the first blank.'],\n ),\n hintCase('gap-hint-1', 'Neither gap chosen, in Spanish', landscape, picked({})),\n hintCase(\n 'gap-hint-2',\n 'Asked again with the first gap wrong',\n landscape,\n picked({ g1: 'campo' }),\n ['Piensa en la forma del terreno.'],\n ),\n ];\n}\n","/**\n * A test kit for the prompts behind your AI ports.\n *\n * The SDK cannot test your model, and will not try. What it can do is make the\n * calls a learner's questions would make — on items whose answers it knows —\n * and run the same checks it runs before a learner sees anything. What comes\n * back is a count of what would have been shown and what would have been\n * refused, and why.\n *\n * Run it where you change a prompt or a model, in your own CI, with your own\n * key. It is a separate entry point (`@intellectif/lk-core/ai-check`) because\n * it is for your test run, not for a learner's browser: nothing here is\n * imported by anything a page renders.\n *\n * ```ts\n * import { aiCheckCases, formatAiCheckReport, runAiCheck } from '@intellectif/lk-core/ai-check';\n *\n * const report = await runAiCheck({\n * explain: (request) => callMyModel(EXPLAIN_PROMPT, request),\n * hint: (request) => callMyModel(HINT_PROMPT, request),\n * });\n * console.log(formatAiCheckReport(report));\n * if (report.refused > 0 || report.errors > 0) {\n * process.exitCode = 1;\n * }\n * ```\n *\n * A refusal is a real failure, not a warning: it is an answer a learner would\n * have asked for and not received. `reveals-answer` on a hint case is the one\n * to take most seriously — that prompt gives answers away.\n */\nimport { checkAiExplanation, checkAiHint } from '../ai.js';\nimport type { AiExplanationRequest, AiHintRequest, AiRefusal, AiTextResult } from '../types/ai.js';\nimport { type AiCheckCase, aiCheckCases } from './cases.js';\n\nexport { type AiCheckCase, aiCheckCases } from './cases.js';\n\n/**\n * Your ports, as this kit calls them. The same functions\n * `<LkAiProvider ai={…}>` takes, minus the `AbortSignal` a browser passes —\n * nothing here abandons a call.\n *\n * Leave one out and its cases are skipped rather than failed.\n */\nexport interface AiCheckPorts {\n explain?(request: AiExplanationRequest): Promise<unknown> | unknown;\n hint?(request: AiHintRequest): Promise<unknown> | unknown;\n}\n\n/** What happened on one case. */\nexport interface AiCheckResult {\n case: AiCheckCase;\n /** Whether a learner would have been shown what came back. */\n ok: boolean;\n /** Why it would not have been shown. */\n refusal?: AiRefusal;\n /** The port threw or rejected. Its message, as a string. */\n error?: string;\n /**\n * What came back, once the SDK had read it — so a refusal can be read beside\n * the text that caused it. Absent when the port failed or answered with\n * nothing the SDK could read.\n */\n result?: AiTextResult;\n /** How long the call took, in milliseconds. */\n ms: number;\n}\n\n/** What a run came to. */\nexport interface AiCheckReport {\n /** Cases run: the ones a port was supplied for. */\n total: number;\n /** Cases whose answer a learner would have been shown. */\n shown: number;\n /** Cases whose answer the SDK refused. */\n refused: number;\n /** Cases where the port itself failed. */\n errors: number;\n /** Cases skipped because no port was supplied for that feature. */\n skipped: number;\n /** How many refusals of each reason. */\n byRefusal: Record<AiRefusal, number>;\n results: AiCheckResult[];\n}\n\nexport interface AiCheckOptions {\n /**\n * The cases to run. Defaults to {@link aiCheckCases}. Pass a filtered list to\n * run one type, or your own cases — built with `aiExplanationRequest` and\n * `aiHintRequest` on your own items, which is how you test a prompt against\n * the content your learners actually see.\n */\n cases?: readonly AiCheckCase[];\n /**\n * How many cases to have in flight at once. Default 1: a run in CI hits a\n * rate limit far more often than it runs out of patience.\n */\n concurrency?: number;\n}\n\nconst REFUSALS: AiRefusal[] = [\n 'malformed',\n 'empty',\n 'too-long',\n 'contradicts-grade',\n 'reveals-answer',\n];\n\n/** Milliseconds, from whichever clock this runtime has. */\nconst now = (): number => Date.now();\n\n/** Runs one case: calls the port, times it, and checks what came back. */\nasync function runCase(ports: AiCheckPorts, one: AiCheckCase): Promise<AiCheckResult> {\n const started = now();\n const explaining = one.request.feature === 'explanation';\n const port = explaining ? ports.explain : ports.hint;\n if (port === undefined) {\n throw new Error(`ai-check: no port for \"${one.id}\"`);\n }\n let raw: unknown;\n try {\n raw = await port(one.request as never);\n } catch (error) {\n return {\n case: one,\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n ms: now() - started,\n };\n }\n const ms = now() - started;\n const checked = explaining\n ? checkAiExplanation(raw, one.request as AiExplanationRequest)\n : checkAiHint(raw, one.request as AiHintRequest);\n if (checked.ok) {\n return { case: one, ok: true, result: checked.result, ms };\n }\n const text =\n typeof (raw as { text?: unknown })?.text === 'string' ? (raw as AiTextResult) : undefined;\n return {\n case: one,\n ok: false,\n refusal: checked.refusal,\n ...(text !== undefined ? { result: text } : {}),\n ms,\n };\n}\n\n/**\n * Calls your ports on every case and reports what a learner would have seen.\n *\n * It never throws for a failing case: a port that rejects is an `error` in the\n * report, so one bad call cannot hide the rest.\n */\nexport async function runAiCheck(\n ports: AiCheckPorts,\n options: AiCheckOptions = {},\n): Promise<AiCheckReport> {\n const all = options.cases ?? aiCheckCases();\n const asked = Math.trunc(options.concurrency ?? 1);\n const lanes = Number.isFinite(asked) && asked >= 1 ? Math.min(asked, 16) : 1;\n const runnable = all.filter((one) =>\n one.request.feature === 'explanation' ? ports.explain !== undefined : ports.hint !== undefined,\n );\n const results: AiCheckResult[] = new Array(runnable.length);\n let next = 0;\n const lane = async (): Promise<void> => {\n while (next < runnable.length) {\n const mine = next;\n next += 1;\n const one = runnable[mine];\n if (one === undefined) {\n return;\n }\n results[mine] = await runCase(ports, one);\n }\n };\n await Promise.all(Array.from({ length: Math.min(lanes, runnable.length) }, lane));\n\n const byRefusal = Object.fromEntries(REFUSALS.map((reason) => [reason, 0])) as Record<\n AiRefusal,\n number\n >;\n let shown = 0;\n let refused = 0;\n let errors = 0;\n for (const result of results) {\n if (result.ok) {\n shown += 1;\n } else if (result.refusal !== undefined) {\n refused += 1;\n byRefusal[result.refusal] += 1;\n } else {\n errors += 1;\n }\n }\n return {\n total: results.length,\n shown,\n refused,\n errors,\n skipped: all.length - runnable.length,\n byRefusal,\n results,\n };\n}\n\n/**\n * The report as a few lines for a CI log: the counts, then every case that\n * failed, with what came back.\n *\n * The refused text is printed — this is your own test run, not a learner's\n * screen, and a hint that gives the answer away is only fixable if you can\n * read it.\n */\nexport function formatAiCheckReport(report: AiCheckReport): string {\n const lines: string[] = [];\n const slowest = report.results.reduce((worst, one) => Math.max(worst, one.ms), 0);\n lines.push(\n `ai-check: ${report.shown}/${report.total} shown, ${report.refused} refused, ${report.errors} failed` +\n `${report.skipped > 0 ? `, ${report.skipped} skipped (no port)` : ''} — slowest ${slowest} ms`,\n );\n for (const reason of REFUSALS) {\n const count = report.byRefusal[reason];\n if (count > 0) {\n lines.push(` ${reason}: ${count}`);\n }\n }\n for (const result of report.results) {\n if (result.ok) {\n continue;\n }\n const why = result.refusal ?? `port failed (${result.error ?? 'no message'})`;\n lines.push(` ✗ ${result.case.id} [${result.case.feature}] ${result.case.about}: ${why}`);\n if (result.result !== undefined) {\n lines.push(` ${result.result.text.replace(/\\s+/g, ' ').slice(0, 200)}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/ai-check/cases.ts","../src/ai-check/index.ts"],"sourcesContent":["import { aiExplanationRequest, aiHintRequest } from '../ai.js';\nimport { aiWritingFeedbackRequest } from '../ai-writing.js';\nimport type {\n DictationData,\n FillInTheBlanksData,\n GapSelectData,\n LearnerResponse,\n MultipleChoiceData,\n WrittenResponseData,\n} from '../types/activity.js';\nimport type { AiExplanationRequest, AiHintRequest, AiWritingFeedbackRequest } from '../types/ai.js';\n\n/**\n * The items the cases are built from. Small, ordinary language-course content\n * — and deliberately awkward in the places a model slips:\n *\n * - an answer a hint can hardly avoid naming (`Madrid`);\n * - an answer of one short word (`is`), which a hint may write as an ordinary\n * word and must not write beside its neighbour in the passage;\n * - an accented answer (`cañón`), so a hint that spells it without the accent\n * is still caught;\n * - a passage with more than one blank, so a hint can be about the wrong one;\n * - a short essay whose mistakes a model must quote exactly as the learner\n * wrote them — straight apostrophes a model likes to curl, a mistake made\n * twice — so a correction of words nobody wrote is caught.\n */\nconst capital: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-mc',\n title: 'Capital cities',\n question: 'What is the capital of Spain?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'madrid', text: 'Madrid', isCorrect: true },\n { id: 'barcelona', text: 'Barcelona', isCorrect: false },\n { id: 'seville', text: 'Seville', isCorrect: false },\n ],\n};\n\nconst introduction: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-fib',\n title: 'Introducing yourself',\n passage: 'My name {{be}} Rossi and I {{live}} in Madrid.',\n blanks: [\n { id: 'be', acceptedAnswers: ['is'] },\n { id: 'live', acceptedAnswers: ['live'], hint: 'Present simple, first person.' },\n ],\n scoringStrategy: 'partial',\n};\n\nconst landscape: GapSelectData = {\n schemaVersion: '1.0',\n type: 'gap-select',\n id: 'ai-check-gap',\n title: 'Geography',\n locale: 'es-ES',\n passage: 'El río pasa por un {{g1}} muy profundo, y el agua {{g2}} fría.',\n gaps: [\n {\n id: 'g1',\n choices: [\n { id: 'canon', text: 'cañón' },\n { id: 'campo', text: 'campo' },\n ],\n correctChoiceId: 'canon',\n },\n {\n id: 'g2',\n choices: [\n { id: 'esta', text: 'está' },\n { id: 'es', text: 'es' },\n ],\n correctChoiceId: 'esta',\n },\n ],\n scoringStrategy: 'partial',\n};\n\nconst listening: DictationData = {\n schemaVersion: '1.0',\n type: 'dictation',\n id: 'ai-check-dictation',\n title: 'Listen and type',\n transcript: 'The train leaves at half past nine.',\n};\n\nconst weekend: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-writing',\n title: 'Your weekend',\n prompt: 'Write a few sentences about what you did last weekend.',\n minWords: 15,\n maxWords: 120,\n languageTarget: 'en-A2',\n rubric: {\n criteria: [\n { name: 'Grammar', description: 'Past tenses, articles, agreement', weight: 2 },\n { name: 'Vocabulary', description: 'Everyday words, used correctly', weight: 1 },\n { name: 'Task', description: 'Says what the learner did, in order', weight: 1 },\n ],\n },\n};\n\n/**\n * One call to make against a host's port, and what a learner was looking at\n * when the SDK would have made it.\n */\nexport interface AiCheckCase {\n /** Stable across releases, so a host can name one in an allow-list or a report. */\n id: string;\n /** Which port the case is for. */\n feature: 'explanation' | 'hint' | 'writing-feedback';\n /** What the learner did, in a few words, for a report a person reads. */\n about: string;\n /** The request, built by the SDK exactly as a component builds it. */\n request: AiExplanationRequest | AiHintRequest | AiWritingFeedbackRequest;\n}\n\nconst chose = (ids: string[]): LearnerResponse => ({\n type: 'multiple-choice',\n selectedOptionIds: ids,\n});\nconst typed = (answers: Record<string, string>): LearnerResponse => ({\n type: 'fill-in-the-blanks',\n answers,\n});\nconst picked = (choices: Record<string, string>): LearnerResponse => ({\n type: 'gap-select',\n selections: choices,\n});\nconst wrote = (text: string): LearnerResponse => ({ type: 'dictation', text });\nconst drafted = (text: string): LearnerResponse => ({\n type: 'written-response',\n text,\n wordCount: 0,\n});\n\n/** Builds a case, failing loudly rather than shipping a case with no request. */\nfunction explanationCase(\n id: string,\n about: string,\n data: Parameters<typeof aiExplanationRequest>[0]['data'],\n response: LearnerResponse,\n): AiCheckCase {\n const request = aiExplanationRequest({ data, response });\n if (request === null) {\n throw new Error(`ai-check: no explanation request for \"${id}\"`);\n }\n return { id, feature: 'explanation', about, request };\n}\n\nfunction hintCase(\n id: string,\n about: string,\n data: Parameters<typeof aiHintRequest>[0]['data'],\n response: LearnerResponse,\n previousHints: string[] = [],\n): AiCheckCase {\n const request = aiHintRequest({ data, response, previousHints });\n if (request === null) {\n throw new Error(`ai-check: no hint request for \"${id}\"`);\n }\n return { id, feature: 'hint', about, request };\n}\n\nfunction writingCase(\n id: string,\n about: string,\n text: string,\n previousFeedback: string[] = [],\n learnerLocale?: string,\n): AiCheckCase {\n const request = aiWritingFeedbackRequest({\n data: weekend,\n response: drafted(text),\n previousFeedback,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n });\n if (request === null) {\n throw new Error(`ai-check: no writing feedback request for \"${id}\"`);\n }\n return { id, feature: 'writing-feedback', about, request };\n}\n\n/**\n * The cases {@link runAiCheck} runs: every type the SDK explains, answered\n * right, wrong and — where a type can be — partly right; every type it hints\n * for, before an answer and after a wrong one; and drafts of an essay, with\n * mistakes, without, and revised.\n *\n * They are ordinary calls. What makes them a test is that the SDK knows the\n * answer to each, so it can check what your model writes about it.\n *\n * A fresh array each call, so a caller may filter, slice or add to it without\n * changing what the next caller gets.\n */\nexport function aiCheckCases(): AiCheckCase[] {\n return [\n explanationCase('mc-correct', 'Chose the right city', capital, chose(['madrid'])),\n explanationCase('mc-wrong', 'Chose Barcelona', capital, chose(['barcelona'])),\n explanationCase('mc-blank', 'Chose nothing and submitted', capital, chose([])),\n explanationCase(\n 'fib-correct',\n 'Filled both blanks correctly',\n introduction,\n typed({ be: 'is', live: 'live' }),\n ),\n explanationCase(\n 'fib-partly',\n 'One blank right, one wrong',\n introduction,\n typed({ be: 'is', live: 'lives' }),\n ),\n explanationCase(\n 'fib-wrong',\n 'Both blanks wrong',\n introduction,\n typed({ be: 'are', live: 'living' }),\n ),\n explanationCase(\n 'gap-partly',\n 'One gap right, one wrong, in Spanish',\n landscape,\n picked({ g1: 'canon', g2: 'es' }),\n ),\n explanationCase(\n 'gap-wrong',\n 'Both gaps wrong, in Spanish',\n landscape,\n picked({ g1: 'campo', g2: 'es' }),\n ),\n explanationCase(\n 'dictation-near',\n 'Typed the sentence with two words wrong',\n listening,\n wrote('The train leaves at half past five'),\n ),\n explanationCase(\n 'dictation-correct',\n 'Typed the sentence correctly',\n listening,\n wrote('The train leaves at half past nine.'),\n ),\n hintCase('mc-hint-1', 'Has not answered yet', capital, chose([])),\n hintCase('mc-hint-2', 'Asked again after choosing Seville', capital, chose(['seville']), [\n 'Think about where the government sits.',\n ]),\n hintCase('fib-hint-1', 'Both blanks empty', introduction, typed({})),\n hintCase(\n 'fib-hint-2',\n 'Asked again with one blank wrong',\n introduction,\n typed({ be: 'are', live: '' }),\n ['Look again at the first blank.'],\n ),\n hintCase('gap-hint-1', 'Neither gap chosen, in Spanish', landscape, picked({})),\n hintCase(\n 'gap-hint-2',\n 'Asked again with the first gap wrong',\n landscape,\n picked({ g1: 'campo' }),\n ['Piensa en la forma del terreno.'],\n ),\n writingCase(\n 'writing-mistakes',\n 'A first draft with tense, article and agreement mistakes',\n \"Last weekend I go to the market and buyed a apple. My friends was there too, so we don't go home early. Then I go to the cinema.\",\n ),\n writingCase(\n 'writing-clean',\n 'A first draft with no mistakes to correct',\n 'Last weekend I went to the market and bought an apple. My friends were there too, so we stayed until the evening. Then we went to the cinema.',\n ),\n writingCase(\n 'writing-revised',\n 'A second draft that fixed some mistakes',\n \"Last weekend I went to the market and bought a apple. My friends was there too, so we didn't go home early.\",\n ['Check your past tenses: \"go\" and \"buyed\" should be past forms.'],\n ),\n writingCase(\n 'writing-feedback-in-spanish',\n 'A first draft, with feedback asked for in Spanish',\n 'Last weekend I visit my grandmother. She cook a big lunch and we eat together in the garden.',\n [],\n 'es',\n ),\n ];\n}\n","/**\n * A test kit for the prompts behind your AI ports.\n *\n * The SDK cannot test your model, and will not try. What it can do is make the\n * calls a learner's questions would make — on items whose answers it knows —\n * and run the same checks it runs before a learner sees anything. What comes\n * back is a count of what would have been shown and what would have been\n * refused, and why.\n *\n * Run it where you change a prompt or a model, in your own CI, with your own\n * key. It is a separate entry point (`@intellectif/lk-core/ai-check`) because\n * it is for your test run, not for a learner's browser: nothing here is\n * imported by anything a page renders.\n *\n * ```ts\n * import { aiCheckCases, formatAiCheckReport, runAiCheck } from '@intellectif/lk-core/ai-check';\n *\n * const report = await runAiCheck({\n * explain: (request) => callMyModel(EXPLAIN_PROMPT, request),\n * hint: (request) => callMyModel(HINT_PROMPT, request),\n * writingFeedback: (request) => callMyModel(FEEDBACK_PROMPT, request),\n * });\n * console.log(formatAiCheckReport(report));\n * if (report.refused > 0 || report.errors > 0) {\n * process.exitCode = 1;\n * }\n * ```\n *\n * A refusal is a real failure, not a warning: it is an answer a learner would\n * have asked for and not received. `reveals-answer` on a hint case is the one\n * to take most seriously — that prompt gives answers away — and\n * `misquotes-answer` on a writing case the next: that prompt corrects words the\n * learner never wrote.\n */\nimport { checkAiExplanation, checkAiHint } from '../ai.js';\nimport { checkAiWritingFeedback } from '../ai-writing.js';\nimport type {\n AiExplanationRequest,\n AiHintRequest,\n AiRefusal,\n AiTextResult,\n AiWritingFeedback,\n AiWritingFeedbackRequest,\n} from '../types/ai.js';\nimport { type AiCheckCase, aiCheckCases } from './cases.js';\n\nexport { type AiCheckCase, aiCheckCases } from './cases.js';\n\n/**\n * Your ports, as this kit calls them. The same functions\n * `<LkAiProvider ai={…}>` takes, minus the `AbortSignal` a browser passes —\n * nothing here abandons a call.\n *\n * Leave one out and its cases are skipped rather than failed.\n */\nexport interface AiCheckPorts {\n explain?(request: AiExplanationRequest): Promise<unknown> | unknown;\n hint?(request: AiHintRequest): Promise<unknown> | unknown;\n writingFeedback?(request: AiWritingFeedbackRequest): Promise<unknown> | unknown;\n}\n\n/** What happened on one case. */\nexport interface AiCheckResult {\n case: AiCheckCase;\n /** Whether a learner would have been shown what came back. */\n ok: boolean;\n /** Why it would not have been shown. */\n refusal?: AiRefusal;\n /** The port threw or rejected. Its message, as a string. */\n error?: string;\n /**\n * What came back, once the SDK had read it — so a refusal can be read beside\n * the text that caused it. Absent when the port failed or answered with\n * nothing the SDK could read.\n */\n result?: AiTextResult;\n /** For a writing case a learner would have been shown: the feedback, as the SDK accepted it. */\n feedback?: AiWritingFeedback;\n /** How long the call took, in milliseconds. */\n ms: number;\n}\n\n/** What a run came to. */\nexport interface AiCheckReport {\n /** Cases run: the ones a port was supplied for. */\n total: number;\n /** Cases whose answer a learner would have been shown. */\n shown: number;\n /** Cases whose answer the SDK refused. */\n refused: number;\n /** Cases where the port itself failed. */\n errors: number;\n /** Cases skipped because no port was supplied for that feature. */\n skipped: number;\n /** How many refusals of each reason. */\n byRefusal: Record<AiRefusal, number>;\n results: AiCheckResult[];\n}\n\nexport interface AiCheckOptions {\n /**\n * The cases to run. Defaults to {@link aiCheckCases}. Pass a filtered list to\n * run one type, or your own cases — built with `aiExplanationRequest` and\n * `aiHintRequest` on your own items, which is how you test a prompt against\n * the content your learners actually see.\n */\n cases?: readonly AiCheckCase[];\n /**\n * How many cases to have in flight at once. Default 1: a run in CI hits a\n * rate limit far more often than it runs out of patience.\n */\n concurrency?: number;\n}\n\nconst REFUSALS: AiRefusal[] = [\n 'malformed',\n 'empty',\n 'too-long',\n 'contradicts-grade',\n 'reveals-answer',\n 'misquotes-answer',\n];\n\n/** The port a case is for. */\nfunction portFor(\n ports: AiCheckPorts,\n one: AiCheckCase,\n): ((request: never) => Promise<unknown> | unknown) | undefined {\n switch (one.request.feature) {\n case 'explanation':\n return ports.explain as never;\n case 'hint':\n return ports.hint as never;\n default:\n return ports.writingFeedback as never;\n }\n}\n\n/** Milliseconds, from whichever clock this runtime has. */\nconst now = (): number => Date.now();\n\n/** Runs one case: calls the port, times it, and checks what came back. */\nasync function runCase(ports: AiCheckPorts, one: AiCheckCase): Promise<AiCheckResult> {\n const started = now();\n const port = portFor(ports, one);\n if (port === undefined) {\n throw new Error(`ai-check: no port for \"${one.id}\"`);\n }\n let raw: unknown;\n try {\n raw = await port(one.request as never);\n } catch (error) {\n return {\n case: one,\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n ms: now() - started,\n };\n }\n const ms = now() - started;\n if (one.request.feature === 'writing-feedback') {\n const checked = checkAiWritingFeedback(raw, one.request);\n if (checked.ok) {\n const { text, provenance, usage } = checked.feedback;\n return {\n case: one,\n ok: true,\n result: {\n text,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n feedback: checked.feedback,\n ms,\n };\n }\n return refused(one, raw, checked.refusal, ms);\n }\n const checked =\n one.request.feature === 'explanation'\n ? checkAiExplanation(raw, one.request)\n : checkAiHint(raw, one.request);\n if (checked.ok) {\n return { case: one, ok: true, result: checked.result, ms };\n }\n return refused(one, raw, checked.refusal, ms);\n}\n\n/** A case whose answer was refused, with the text that came back when there was one to read. */\nfunction refused(one: AiCheckCase, raw: unknown, refusal: AiRefusal, ms: number): AiCheckResult {\n const text =\n typeof (raw as { text?: unknown })?.text === 'string' ? (raw as AiTextResult) : undefined;\n return {\n case: one,\n ok: false,\n refusal,\n ...(text !== undefined ? { result: text } : {}),\n ms,\n };\n}\n\n/**\n * Calls your ports on every case and reports what a learner would have seen.\n *\n * It never throws for a failing case: a port that rejects is an `error` in the\n * report, so one bad call cannot hide the rest.\n */\nexport async function runAiCheck(\n ports: AiCheckPorts,\n options: AiCheckOptions = {},\n): Promise<AiCheckReport> {\n const all = options.cases ?? aiCheckCases();\n const asked = Math.trunc(options.concurrency ?? 1);\n const lanes = Number.isFinite(asked) && asked >= 1 ? Math.min(asked, 16) : 1;\n const runnable = all.filter((one) => portFor(ports, one) !== undefined);\n const results: AiCheckResult[] = new Array(runnable.length);\n let next = 0;\n const lane = async (): Promise<void> => {\n while (next < runnable.length) {\n const mine = next;\n next += 1;\n const one = runnable[mine];\n if (one === undefined) {\n return;\n }\n results[mine] = await runCase(ports, one);\n }\n };\n await Promise.all(Array.from({ length: Math.min(lanes, runnable.length) }, lane));\n\n const byRefusal = Object.fromEntries(REFUSALS.map((reason) => [reason, 0])) as Record<\n AiRefusal,\n number\n >;\n let shown = 0;\n let refused = 0;\n let errors = 0;\n for (const result of results) {\n if (result.ok) {\n shown += 1;\n } else if (result.refusal !== undefined) {\n refused += 1;\n byRefusal[result.refusal] += 1;\n } else {\n errors += 1;\n }\n }\n return {\n total: results.length,\n shown,\n refused,\n errors,\n skipped: all.length - runnable.length,\n byRefusal,\n results,\n };\n}\n\n/**\n * The report as a few lines for a CI log: the counts, then every case that\n * failed, with what came back.\n *\n * The refused text is printed — this is your own test run, not a learner's\n * screen, and a hint that gives the answer away is only fixable if you can\n * read it.\n */\nexport function formatAiCheckReport(report: AiCheckReport): string {\n const lines: string[] = [];\n const slowest = report.results.reduce((worst, one) => Math.max(worst, one.ms), 0);\n lines.push(\n `ai-check: ${report.shown}/${report.total} shown, ${report.refused} refused, ${report.errors} failed` +\n `${report.skipped > 0 ? `, ${report.skipped} skipped (no port)` : ''} — slowest ${slowest} ms`,\n );\n for (const reason of REFUSALS) {\n const count = report.byRefusal[reason];\n if (count > 0) {\n lines.push(` ${reason}: ${count}`);\n }\n }\n for (const result of report.results) {\n if (result.ok) {\n continue;\n }\n const why = result.refusal ?? `port failed (${result.error ?? 'no message'})`;\n lines.push(` ✗ ${result.case.id} [${result.case.feature}] ${result.case.about}: ${why}`);\n if (result.result !== undefined) {\n lines.push(` ${result.result.text.replace(/\\s+/g, ' ').slice(0, 200)}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["checked","refused"]}
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
var _chunkDSDXL3JOcjs = require('./chunk-DSDXL3JO.cjs');
|
|
15
20
|
|
|
16
21
|
// src/schemas/item-group.ts
|
|
17
22
|
var _v4 = require('zod/v4');
|
|
@@ -57,7 +62,7 @@ var StimulusSchema = _v4.z.looseObject({
|
|
|
57
62
|
title: _v4.z.string().optional(),
|
|
58
63
|
body: _v4.z.string().optional(),
|
|
59
64
|
bodyHtml: _v4.z.string().optional(),
|
|
60
|
-
media:
|
|
65
|
+
media: _chunkDSDXL3JOcjs.MediaSchema.optional(),
|
|
61
66
|
transcript: _v4.z.string().optional(),
|
|
62
67
|
locale: _v4.z.string().optional(),
|
|
63
68
|
attribution: _v4.z.string().optional()
|
|
@@ -80,10 +85,13 @@ var StimulusSchema = _v4.z.looseObject({
|
|
|
80
85
|
path: ["media", "type"]
|
|
81
86
|
}
|
|
82
87
|
);
|
|
83
|
-
var TimelineTitleSchema = _v4.z.string()
|
|
88
|
+
var TimelineTitleSchema = _chunkDSDXL3JOcjs.maxUnits.call(void 0, _v4.z.string(), TIMELINE_MAX_TITLE_LENGTH).refine(
|
|
89
|
+
(title) => title.trim().length > 0,
|
|
90
|
+
{ error: "A title must not be empty." }
|
|
91
|
+
);
|
|
84
92
|
var TimelineTimeSchema = _v4.z.number().min(0, { error: "A time is 0 seconds or more." });
|
|
85
93
|
var TimelineCueSchema = _v4.z.strictObject({
|
|
86
|
-
id: _v4.z.string().min(1)
|
|
94
|
+
id: _chunkDSDXL3JOcjs.maxUnits.call(void 0, _v4.z.string().min(1), 64),
|
|
87
95
|
at: TimelineTimeSchema,
|
|
88
96
|
itemIds: _v4.z.array(_v4.z.string().min(1)).min(1).max(TIMELINE_MAX_ITEMS),
|
|
89
97
|
title: TimelineTitleSchema.optional(),
|
|
@@ -267,7 +275,7 @@ var RedactedStimulusSchema = _v4.z.strictObject({
|
|
|
267
275
|
title: _v4.z.string().optional(),
|
|
268
276
|
body: _v4.z.string().optional(),
|
|
269
277
|
bodyHtml: _v4.z.string().optional(),
|
|
270
|
-
media:
|
|
278
|
+
media: _chunkDSDXL3JOcjs.RedactedMediaSchema.optional(),
|
|
271
279
|
locale: _v4.z.string().optional(),
|
|
272
280
|
attribution: _v4.z.string().optional()
|
|
273
281
|
});
|
|
@@ -286,12 +294,12 @@ var RedactedItemGroupSchema = _v4.z.strictObject({
|
|
|
286
294
|
for (const issue of timelineIssues(ctx.value, "redacted")) {
|
|
287
295
|
ctx.issues.push({ code: "custom", ...issue });
|
|
288
296
|
}
|
|
289
|
-
const captions =
|
|
297
|
+
const captions = _chunkDSDXL3JOcjs.groupCaptionsField.call(void 0, ctx.value);
|
|
290
298
|
if (captions !== void 0) {
|
|
291
299
|
ctx.issues.push({
|
|
292
300
|
code: "custom",
|
|
293
301
|
input: _optionalChain([ctx, 'access', _ => _.value, 'access', _2 => _2.stimulus, 'access', _3 => _3.media, 'optionalAccess', _4 => _4[captions]]),
|
|
294
|
-
message:
|
|
302
|
+
message: _chunkDSDXL3JOcjs.GROUP_CAPTIONS_REVEAL_DICTATION,
|
|
295
303
|
path: ["stimulus", "media", captions]
|
|
296
304
|
});
|
|
297
305
|
}
|
|
@@ -311,7 +319,7 @@ function validateItemGroup(data) {
|
|
|
311
319
|
const errors = [];
|
|
312
320
|
const items = [];
|
|
313
321
|
container.data.items.forEach((item, index) => {
|
|
314
|
-
const descriptor =
|
|
322
|
+
const descriptor = _chunkDSDXL3JOcjs.getActivityTypeDescriptor.call(void 0, item.type);
|
|
315
323
|
if (descriptor === void 0) {
|
|
316
324
|
errors.push({
|
|
317
325
|
path: ["items", String(index), "type"],
|
|
@@ -320,18 +328,18 @@ function validateItemGroup(data) {
|
|
|
320
328
|
});
|
|
321
329
|
return;
|
|
322
330
|
}
|
|
323
|
-
const parsed = descriptor.schema.
|
|
331
|
+
const parsed = _chunkDSDXL3JOcjs.readSchema.call(void 0, descriptor.schema, item, `Activity type "${descriptor.type}"`);
|
|
324
332
|
if (!parsed.success) {
|
|
325
|
-
errors.push(...toValidationErrors(parsed.
|
|
333
|
+
errors.push(...toValidationErrors(parsed.issues, ["items", String(index)]));
|
|
326
334
|
return;
|
|
327
335
|
}
|
|
328
336
|
items.push(parsed.data);
|
|
329
337
|
});
|
|
330
|
-
const captions =
|
|
338
|
+
const captions = _chunkDSDXL3JOcjs.groupCaptionsField.call(void 0, container.data);
|
|
331
339
|
if (captions !== void 0) {
|
|
332
340
|
errors.push({
|
|
333
341
|
path: ["stimulus", "media", captions],
|
|
334
|
-
message:
|
|
342
|
+
message: _chunkDSDXL3JOcjs.GROUP_CAPTIONS_REVEAL_DICTATION,
|
|
335
343
|
code: "custom"
|
|
336
344
|
});
|
|
337
345
|
}
|
|
@@ -343,53 +351,86 @@ function validateItemGroup(data) {
|
|
|
343
351
|
|
|
344
352
|
// src/schemas/json-schema.ts
|
|
345
353
|
|
|
346
|
-
var stimulusJsonSchema = _v4.z.toJSONSchema(StimulusSchema, {
|
|
347
|
-
var itemGroupJsonSchema = _v4.z.toJSONSchema(ItemGroupSchema, { target: "draft-7" });
|
|
348
|
-
var gapSelectJsonSchema = _v4.z.toJSONSchema(_chunkD4TOPSHVcjs.GapSelectDataSchema, {
|
|
349
|
-
target: "draft-7"
|
|
350
|
-
});
|
|
351
|
-
var dictationJsonSchema = _v4.z.toJSONSchema(_chunkD4TOPSHVcjs.DictationDataSchema, {
|
|
354
|
+
var stimulusJsonSchema = _v4.z.toJSONSchema(StimulusSchema, {
|
|
352
355
|
target: "draft-7"
|
|
353
356
|
});
|
|
354
|
-
var
|
|
357
|
+
var itemGroupJsonSchema = _v4.z.toJSONSchema(ItemGroupSchema, {
|
|
355
358
|
target: "draft-7"
|
|
356
359
|
});
|
|
357
|
-
var
|
|
360
|
+
var gapSelectJsonSchema = _v4.z.toJSONSchema(_chunkDSDXL3JOcjs.GapSelectDataSchema, {
|
|
358
361
|
target: "draft-7"
|
|
359
362
|
});
|
|
360
|
-
var
|
|
363
|
+
var dictationJsonSchema = _v4.z.toJSONSchema(_chunkDSDXL3JOcjs.DictationDataSchema, {
|
|
361
364
|
target: "draft-7"
|
|
362
365
|
});
|
|
363
|
-
var
|
|
366
|
+
var readAloudJsonSchema = _v4.z.toJSONSchema(_chunkDSDXL3JOcjs.ReadAloudDataSchema, {
|
|
364
367
|
target: "draft-7"
|
|
365
368
|
});
|
|
369
|
+
var multipleChoiceJsonSchema = _v4.z.toJSONSchema(
|
|
370
|
+
_chunkDSDXL3JOcjs.MultipleChoiceDataSchema,
|
|
371
|
+
{
|
|
372
|
+
target: "draft-7"
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
var fillInTheBlanksJsonSchema = _v4.z.toJSONSchema(
|
|
376
|
+
_chunkDSDXL3JOcjs.FillInTheBlanksDataSchema,
|
|
377
|
+
{
|
|
378
|
+
target: "draft-7"
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
var writtenResponseJsonSchema = _v4.z.toJSONSchema(
|
|
382
|
+
_chunkDSDXL3JOcjs.WrittenResponseDataSchema,
|
|
383
|
+
{
|
|
384
|
+
target: "draft-7"
|
|
385
|
+
}
|
|
386
|
+
);
|
|
366
387
|
function jsonSchemaFor(type) {
|
|
367
|
-
const descriptor =
|
|
388
|
+
const descriptor = _chunkDSDXL3JOcjs.getActivityTypeDescriptor.call(void 0, type);
|
|
368
389
|
if (descriptor === void 0) {
|
|
369
|
-
throw new (0,
|
|
390
|
+
throw new (0, _chunkDSDXL3JOcjs.UnknownActivityTypeError)(type);
|
|
391
|
+
}
|
|
392
|
+
if (_chunkDSDXL3JOcjs.isOwnSchema.call(void 0, descriptor.schema)) {
|
|
393
|
+
return _v4.z.toJSONSchema(descriptor.schema, { target: "draft-7" });
|
|
394
|
+
}
|
|
395
|
+
if (descriptor.jsonSchema !== void 0) {
|
|
396
|
+
return structuredClone(descriptor.jsonSchema);
|
|
397
|
+
}
|
|
398
|
+
const generated = _chunkDSDXL3JOcjs.standardJsonSchemaOf.call(void 0, descriptor.schema);
|
|
399
|
+
if (generated === void 0) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`Activity type "${descriptor.type}" has no JSON Schema: its schema implements no Standard JSON Schema converter ('~standard'.jsonSchema). Give its descriptor a \`jsonSchema\`.`
|
|
402
|
+
);
|
|
370
403
|
}
|
|
371
|
-
return
|
|
404
|
+
return generated;
|
|
372
405
|
}
|
|
373
406
|
|
|
374
407
|
// src/schemas/index.ts
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
408
|
+
function validateMedia(value) {
|
|
409
|
+
return asValidation(_chunkDSDXL3JOcjs.readSchema.call(void 0, _chunkDSDXL3JOcjs.MediaSchema, value, "Media"));
|
|
410
|
+
}
|
|
411
|
+
function validateOptionMedia(value) {
|
|
412
|
+
return asValidation(_chunkDSDXL3JOcjs.readSchema.call(void 0, _chunkDSDXL3JOcjs.MultipleChoiceOptionMediaSchema, value, "Option media"));
|
|
413
|
+
}
|
|
414
|
+
function asValidation(reading) {
|
|
415
|
+
if (reading.success) {
|
|
416
|
+
return { success: true, data: reading.data };
|
|
383
417
|
}
|
|
384
418
|
return {
|
|
385
419
|
success: false,
|
|
386
|
-
errors:
|
|
420
|
+
errors: reading.issues.map((issue) => ({
|
|
387
421
|
path: issue.path.map(String),
|
|
388
422
|
message: issue.message,
|
|
389
423
|
code: issue.code
|
|
390
424
|
}))
|
|
391
425
|
};
|
|
392
426
|
}
|
|
427
|
+
function validateActivity(type, data) {
|
|
428
|
+
const descriptor = _chunkDSDXL3JOcjs.getActivityTypeDescriptor.call(void 0, type);
|
|
429
|
+
if (descriptor === void 0) {
|
|
430
|
+
throw new (0, _chunkDSDXL3JOcjs.UnknownActivityTypeError)(String(type));
|
|
431
|
+
}
|
|
432
|
+
return asValidation(_chunkDSDXL3JOcjs.readSchema.call(void 0, descriptor.schema, data, `Activity type "${descriptor.type}"`));
|
|
433
|
+
}
|
|
393
434
|
|
|
394
435
|
|
|
395
436
|
|
|
@@ -413,8 +454,5 @@ function validateActivity(type, data) {
|
|
|
413
454
|
|
|
414
455
|
|
|
415
456
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
exports.INTERACTIVE_VIDEO_ITEM_TYPES = INTERACTIVE_VIDEO_ITEM_TYPES; exports.isInteractiveVideoItemType = isInteractiveVideoItemType; exports.TIMELINE_MAX_QUIZZES = TIMELINE_MAX_QUIZZES; exports.TIMELINE_MAX_ITEMS = TIMELINE_MAX_ITEMS; exports.TIMELINE_MAX_CHAPTERS = TIMELINE_MAX_CHAPTERS; exports.TIMELINE_MAX_TITLE_LENGTH = TIMELINE_MAX_TITLE_LENGTH; exports.StimulusSchema = StimulusSchema; exports.TimelineCueSchema = TimelineCueSchema; exports.TimelineChapterSchema = TimelineChapterSchema; exports.MediaTimelineSchema = MediaTimelineSchema; exports.ItemGroupSchema = ItemGroupSchema; exports.RedactedStimulusSchema = RedactedStimulusSchema; exports.RedactedItemGroupSchema = RedactedItemGroupSchema; exports.validateItemGroup = validateItemGroup; exports.stimulusJsonSchema = stimulusJsonSchema; exports.itemGroupJsonSchema = itemGroupJsonSchema; exports.gapSelectJsonSchema = gapSelectJsonSchema; exports.dictationJsonSchema = dictationJsonSchema; exports.readAloudJsonSchema = readAloudJsonSchema; exports.multipleChoiceJsonSchema = multipleChoiceJsonSchema; exports.fillInTheBlanksJsonSchema = fillInTheBlanksJsonSchema; exports.writtenResponseJsonSchema = writtenResponseJsonSchema; exports.jsonSchemaFor = jsonSchemaFor; exports.validateActivity = validateActivity;
|
|
420
|
-
//# sourceMappingURL=chunk-2CV6NU32.cjs.map
|
|
457
|
+
exports.INTERACTIVE_VIDEO_ITEM_TYPES = INTERACTIVE_VIDEO_ITEM_TYPES; exports.isInteractiveVideoItemType = isInteractiveVideoItemType; exports.TIMELINE_MAX_QUIZZES = TIMELINE_MAX_QUIZZES; exports.TIMELINE_MAX_ITEMS = TIMELINE_MAX_ITEMS; exports.TIMELINE_MAX_CHAPTERS = TIMELINE_MAX_CHAPTERS; exports.TIMELINE_MAX_TITLE_LENGTH = TIMELINE_MAX_TITLE_LENGTH; exports.ItemGroupSchema = ItemGroupSchema; exports.RedactedItemGroupSchema = RedactedItemGroupSchema; exports.validateItemGroup = validateItemGroup; exports.stimulusJsonSchema = stimulusJsonSchema; exports.itemGroupJsonSchema = itemGroupJsonSchema; exports.gapSelectJsonSchema = gapSelectJsonSchema; exports.dictationJsonSchema = dictationJsonSchema; exports.readAloudJsonSchema = readAloudJsonSchema; exports.multipleChoiceJsonSchema = multipleChoiceJsonSchema; exports.fillInTheBlanksJsonSchema = fillInTheBlanksJsonSchema; exports.writtenResponseJsonSchema = writtenResponseJsonSchema; exports.jsonSchemaFor = jsonSchemaFor; exports.validateMedia = validateMedia; exports.validateOptionMedia = validateOptionMedia; exports.validateActivity = validateActivity;
|
|
458
|
+
//# sourceMappingURL=chunk-2JT2WUQ2.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-2JT2WUQ2.cjs","../src/schemas/item-group.ts","../src/timeline-limits.ts","../src/schemas/json-schema.ts","../src/schemas/index.ts"],"names":["z"],"mappings":"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","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-2JT2WUQ2.cjs","sourcesContent":[null,"import { z } from 'zod/v4';\nimport { getActivityTypeDescriptor } from '../registry/index.js';\nimport {\n INTERACTIVE_VIDEO_ITEM_TYPES,\n isInteractiveVideoItemType,\n TIMELINE_MAX_CHAPTERS,\n TIMELINE_MAX_ITEMS,\n TIMELINE_MAX_QUIZZES,\n TIMELINE_MAX_TITLE_LENGTH,\n} from '../timeline-limits.js';\nimport type { ActivityData, ValidationError, ValidationResult } from '../types/activity.js';\nimport type { ItemGroup, StimulusKind } from '../types/item-group.js';\nimport { GROUP_CAPTIONS_REVEAL_DICTATION, groupCaptionsField } from './dictation.js';\nimport { MediaSchema, RedactedMediaSchema } from './media.js';\nimport { readSchema } from './read-schema.js';\nimport { maxUnits } from './text-length.js';\n\n/**\n * An authored slot key.\n *\n * A `.` is rejected here, not at render time: `flattenSequence` uses the first\n * `.` to separate a group from its item, so a key containing one would produce\n * an ambiguous slot id. `keyOf` already threw on it — this moves the rejection\n * to `validateItemGroup`, where an author finds it, instead of leaving it to\n * surface when a learner opens the paper. Content that would newly fail\n * validation already threw at flatten time, so it can never have been sat.\n */\nconst SlotKeySchema = z\n .string()\n .min(1)\n .refine((key) => !key.includes('.'), {\n error:\n 'slotKey must not contain \".\" — that character separates a group from its item in a slot id, so a key containing one would make the slot ambiguous.',\n });\n\nfunction hasBody(stimulus: { body?: string | undefined }): boolean {\n return typeof stimulus.body === 'string' && stimulus.body.trim().length > 0;\n}\n\n/** Which media types can carry a stimulus of each kind. `text`/`mixed` accept any. */\nfunction mediaFits(kind: StimulusKind, mediaType: string | undefined): boolean {\n switch (kind) {\n case 'audio':\n return mediaType === 'audio';\n case 'video':\n return mediaType === 'video' || mediaType === 'embed';\n case 'image':\n return mediaType === 'image';\n default:\n return true;\n }\n}\n\n/**\n * Zod schema for a {@link Stimulus}. Loose: unknown keys preserved.\n *\n * The semantic guards exist because a stimulus that does not carry what its\n * `kind` promises is an authoring error that must not reach an exam: an\n * \"audio\" stimulus with no recording renders as a blank panel above six\n * listening questions. Unrepresentable in JSON Schema; dropped from\n * `stimulusJsonSchema` by design.\n */\nexport const StimulusSchema = z\n .looseObject({\n id: z.string().min(1),\n kind: z.enum(['text', 'audio', 'video', 'image', 'mixed']),\n title: z.string().optional(),\n body: z.string().optional(),\n bodyHtml: z.string().optional(),\n media: MediaSchema.optional(),\n transcript: z.string().optional(),\n locale: z.string().optional(),\n attribution: z.string().optional(),\n })\n .refine((stimulus) => stimulus.bodyHtml === undefined || hasBody(stimulus), {\n error:\n 'bodyHtml requires a plain-text body: it is the accessible fallback rendered when no sanitiser is supplied.',\n path: ['body'],\n })\n .refine(\n (stimulus) => (stimulus.kind !== 'text' && stimulus.kind !== 'mixed') || hasBody(stimulus),\n {\n error: 'A text or mixed stimulus needs a non-empty body.',\n path: ['body'],\n },\n )\n .refine((stimulus) => stimulus.kind === 'text' || stimulus.media !== undefined, {\n error: 'An audio, video, image or mixed stimulus needs media.',\n path: ['media'],\n })\n .refine(\n (stimulus) => stimulus.media === undefined || mediaFits(stimulus.kind, stimulus.media.type),\n {\n error:\n 'media.type does not fit the stimulus kind: audio needs audio; video needs video or embed; image needs image.',\n path: ['media', 'type'],\n },\n );\n\nconst TimelineTitleSchema = maxUnits(z.string(), TIMELINE_MAX_TITLE_LENGTH).refine(\n (title) => title.trim().length > 0,\n { error: 'A title must not be empty.' },\n);\n\n/** Seconds from the start of the video. zod rejects NaN and ±Infinity itself. */\nconst TimelineTimeSchema = z.number().min(0, { error: 'A time is 0 seconds or more.' });\n\n/**\n * A quiz. STRICT, like every part of a timeline: a misspelt `requried` would\n * otherwise be kept and the quiz silently optional on a paper that believed it\n * was required.\n */\nexport const TimelineCueSchema = z.strictObject({\n id: maxUnits(z.string().min(1), 64),\n at: TimelineTimeSchema,\n itemIds: z.array(z.string().min(1)).min(1).max(TIMELINE_MAX_ITEMS),\n title: TimelineTitleSchema.optional(),\n required: z.boolean().optional(),\n});\n\nexport const TimelineChapterSchema = z.strictObject({\n at: TimelineTimeSchema,\n title: TimelineTitleSchema,\n});\n\n/**\n * A timeline on its own: its quizzes, chapters and navigation. The rules that\n * need the group — every item in exactly one quiz, a video stimulus, the five\n * item types — are {@link timelineIssues}, which both group schemas run.\n */\nexport const MediaTimelineSchema = z\n .strictObject({\n cues: z.array(TimelineCueSchema).max(TIMELINE_MAX_QUIZZES),\n chapters: z.array(TimelineChapterSchema).max(TIMELINE_MAX_CHAPTERS).optional(),\n navigation: z.enum(['free', 'no-skip-ahead']).optional(),\n })\n .check((ctx) => {\n const seen = new Set<string>();\n ctx.value.cues.forEach((cue, index) => {\n if (seen.has(cue.id)) {\n ctx.issues.push({\n code: 'custom',\n input: cue.id,\n message: `Quiz id \"${cue.id}\" is used twice: events, drafts and the contents panel name a quiz by its id.`,\n path: ['cues', index, 'id'],\n });\n }\n seen.add(cue.id);\n });\n // Strictly increasing, not merely sorted: two chapters starting at one\n // moment leave the first with no length, and the scrubber no room to draw it.\n const chapters = ctx.value.chapters ?? [];\n chapters.forEach((chapter, index) => {\n const previous = chapters[index - 1];\n if (previous !== undefined && chapter.at <= previous.at) {\n ctx.issues.push({\n code: 'custom',\n input: chapter.at,\n message: 'Chapter times must be strictly increasing.',\n path: ['chapters', index, 'at'],\n });\n }\n });\n });\n\n/** A URL only this document can resolve, or one that inlines the bytes. */\nfunction isLocalOnlyUrl(url: unknown): boolean {\n return typeof url === 'string' && /^(data|blob):/i.test(url);\n}\n\n/**\n * The rules of an interactive video that span the timeline, the stimulus and\n * the items. Run by the content schema and, for everything but the two\n * authoring-only rules, by the strict redacted schema — a projection built by\n * hand never passed through the content schema.\n *\n * Items are read defensively: the redacted schema leaves them opaque, and each\n * is proven against its own type's schema elsewhere.\n */\nfunction timelineIssues(\n group: {\n stimulus: {\n kind: string;\n media?:\n | {\n type: string;\n url: string;\n poster?: string | undefined;\n tracks?: readonly { src: string }[] | undefined;\n }\n | undefined;\n };\n items: readonly unknown[];\n shuffle?: string | undefined;\n timeline?: { cues: readonly { itemIds: readonly string[] }[] } | undefined;\n },\n scope: 'content' | 'redacted',\n): { path: (string | number)[]; message: string; input: unknown }[] {\n const timeline = group.timeline;\n if (timeline === undefined) {\n return [];\n }\n const issues: { path: (string | number)[]; message: string; input: unknown }[] = [];\n const media = group.stimulus.media;\n\n // Reported at the field that is wrong, so an editor's own check at that path\n // can stand in for it without hiding anything else about the stimulus.\n if (group.stimulus.kind !== 'video') {\n issues.push({\n path: ['stimulus', 'kind'],\n input: group.stimulus.kind,\n message:\n 'An interactive video needs a video stimulus: quizzes open at moments of a video, and nothing else has them.',\n });\n } else if (media !== undefined && media.type !== 'video') {\n issues.push({\n path: ['stimulus', 'media', 'type'],\n input: media.type,\n message:\n 'An interactive video needs a video file. An embedded provider player cannot be paused at the moment a quiz opens.',\n });\n }\n if (group.shuffle === 'within-group') {\n issues.push({\n path: ['shuffle'],\n input: group.shuffle,\n message:\n 'An interactive video presents its questions in the order its quizzes open; shuffle: \"within-group\" cannot apply to it.',\n });\n }\n if (group.items.length > TIMELINE_MAX_ITEMS) {\n issues.push({\n path: ['items'],\n input: group.items.length,\n message: `An interactive video holds at most ${TIMELINE_MAX_ITEMS} questions.`,\n });\n }\n\n const itemIds = new Map<string, number>();\n group.items.forEach((item, index) => {\n const {\n id,\n type,\n media: itemMedia,\n } = (item ?? {}) as { id?: unknown; type?: unknown; media?: unknown };\n if (typeof id === 'string') {\n itemIds.set(id, index);\n }\n if (!isInteractiveVideoItemType(type)) {\n issues.push({\n path: ['items', index, 'type'],\n input: type,\n message: `An interactive video may hold only ${INTERACTIVE_VIDEO_ITEM_TYPES.join(', ')} questions.`,\n });\n }\n // The \"play the stimulus\" fallback a dictation has in a listening group\n // means nothing halfway through a video.\n if (scope === 'content' && type === 'dictation' && itemMedia === undefined) {\n issues.push({\n path: ['items', index, 'media'],\n input: itemMedia,\n message: 'A dictation inside an interactive video needs its own recording.',\n });\n }\n });\n\n const placed = new Set<string>();\n timeline.cues.forEach((cue, cueIndex) => {\n cue.itemIds.forEach((itemId, position) => {\n const path = ['timeline', 'cues', cueIndex, 'itemIds', position];\n if (!itemIds.has(itemId)) {\n issues.push({\n path,\n input: itemId,\n message: `No item in this group has the id \"${itemId}\".`,\n });\n } else if (placed.has(itemId)) {\n issues.push({\n path,\n input: itemId,\n message: `Item \"${itemId}\" is placed twice: every question belongs to exactly one quiz.`,\n });\n }\n placed.add(itemId);\n });\n });\n for (const [itemId, index] of itemIds) {\n if (!placed.has(itemId)) {\n issues.push({\n path: ['items', index],\n input: itemId,\n message: `Item \"${itemId}\" is in no quiz, so the video would never show it.`,\n });\n }\n }\n\n // A `data:` video swells the payload and every fingerprint over it; a stored\n // `blob:` URL means nothing in another document.\n if (scope === 'content' && media !== undefined) {\n const urls: [unknown, (string | number)[]][] = [\n [media.url, ['stimulus', 'media', 'url']],\n [media.poster, ['stimulus', 'media', 'poster']],\n ...(media.tracks ?? []).map((track, index): [unknown, (string | number)[]] => [\n track.src,\n ['stimulus', 'media', 'tracks', index, 'src'],\n ]),\n ];\n for (const [url, path] of urls) {\n if (isLocalOnlyUrl(url)) {\n issues.push({\n path,\n input: url,\n message:\n 'An interactive video is stored and served: its URLs cannot be data: or blob: URLs.',\n });\n }\n }\n }\n return issues;\n}\n\n/**\n * The structural minimum of an item as seen by the CONTAINER schema. Each\n * item's own contract is checked against its registered schema by\n * {@link validateItemGroup} — a zod schema cannot dispatch on a registry\n * that consumers extend at runtime.\n */\nconst ItemShapeSchema = z.looseObject({\n type: z.string().min(1),\n id: z.string().min(1),\n slotKey: SlotKeySchema.optional(),\n});\n\n/**\n * Zod schema for an {@link ItemGroup} CONTAINER. Loose: unknown keys\n * preserved. Validates the group's own fields and the stimulus in full, and\n * each item only structurally (`type` and `id`); use {@link validateItemGroup}\n * to validate the items against their registered schemas as well.\n */\nexport const ItemGroupSchema = z\n .looseObject({\n schemaVersion: z.literal('1.0'),\n type: z.literal('item-group'),\n id: z.string().min(1),\n title: z.string().optional(),\n slotKey: SlotKeySchema.optional(),\n stimulus: StimulusSchema,\n items: z.array(ItemShapeSchema).min(1),\n shuffle: z.enum(['none', 'within-group']).optional(),\n timeline: MediaTimelineSchema.optional(),\n })\n .refine((group) => group.items.every((item) => item.type !== 'item-group'), {\n error: 'Item groups do not nest: every item must be an activity.',\n path: ['items'],\n })\n .refine((group) => new Set(group.items.map((item) => item.id)).size === group.items.length, {\n error: 'Item ids must be unique within a group.',\n path: ['items'],\n })\n .check((ctx) => {\n for (const issue of timelineIssues(ctx.value, 'content')) {\n ctx.issues.push({ code: 'custom', ...issue });\n }\n });\n\n/** Strict learner-safe stimulus: everything but the author-only `transcript`. */\nexport const RedactedStimulusSchema = z.strictObject({\n id: z.string().min(1),\n kind: z.enum(['text', 'audio', 'video', 'image', 'mixed']),\n title: z.string().optional(),\n body: z.string().optional(),\n bodyHtml: z.string().optional(),\n media: RedactedMediaSchema.optional(),\n locale: z.string().optional(),\n attribution: z.string().optional(),\n});\n\n/**\n * Strict learner-safe item group. Items are left opaque here — each is proven\n * learner-safe by `assertRedacted` against its OWN type's redacted schema,\n * which is the only place that knowledge lives. The one exception is the one\n * rule that spans the container and an item: a captioned stimulus recording\n * played to a dictation is not learner-safe, because the captions are the\n * dictation's answer.\n */\nexport const RedactedItemGroupSchema = z\n .strictObject({\n redacted: z.literal(true),\n schemaVersion: z.literal('1.0'),\n type: z.literal('item-group'),\n id: z.string().min(1),\n title: z.string().optional(),\n slotKey: SlotKeySchema.optional(),\n stimulus: RedactedStimulusSchema,\n items: z.array(z.unknown()).min(1),\n shuffle: z.enum(['none', 'within-group']).optional(),\n timeline: MediaTimelineSchema.optional(),\n })\n .check((ctx) => {\n for (const issue of timelineIssues(ctx.value, 'redacted')) {\n ctx.issues.push({ code: 'custom', ...issue });\n }\n const captions = groupCaptionsField(ctx.value);\n if (captions !== undefined) {\n ctx.issues.push({\n code: 'custom',\n input: ctx.value.stimulus.media?.[captions],\n message: GROUP_CAPTIONS_REVEAL_DICTATION,\n path: ['stimulus', 'media', captions],\n });\n }\n });\n\nfunction toValidationErrors(\n issues: readonly { path: readonly PropertyKey[]; message: string; code: string }[],\n prefix: string[],\n): ValidationError[] {\n return issues.map((issue) => ({\n path: [...prefix, ...issue.path.map(String)],\n message: issue.message,\n code: issue.code,\n }));\n}\n\n/**\n * Validates an item group in full: the container and stimulus against\n * {@link ItemGroupSchema}, then every item against the schema registered for\n * its `type`. Errors from items are reported at `items.<index>.…`.\n *\n * Unlike `validateActivity`, an item whose type is not registered is REPORTED\n * (code `unknown_activity_type`) rather than thrown: a group is validated as\n * a whole, and an author fixing a six-item group wants every problem listed,\n * not the first one that happened to throw.\n */\nexport function validateItemGroup(data: unknown): ValidationResult<ItemGroup> {\n const container = ItemGroupSchema.safeParse(data);\n if (!container.success) {\n return { success: false, errors: toValidationErrors(container.error.issues, []) };\n }\n\n const errors: ValidationError[] = [];\n const items: ActivityData[] = [];\n container.data.items.forEach((item, index) => {\n const descriptor = getActivityTypeDescriptor(item.type);\n if (descriptor === undefined) {\n errors.push({\n path: ['items', String(index), 'type'],\n message: `Activity type \"${item.type}\" is not registered`,\n code: 'unknown_activity_type',\n });\n return;\n }\n const parsed = readSchema(descriptor.schema, item, `Activity type \"${descriptor.type}\"`);\n if (!parsed.success) {\n errors.push(...toValidationErrors(parsed.issues, ['items', String(index)]));\n return;\n }\n items.push(parsed.data as ActivityData);\n });\n const captions = groupCaptionsField(container.data);\n if (captions !== undefined) {\n errors.push({\n path: ['stimulus', 'media', captions],\n message: GROUP_CAPTIONS_REVEAL_DICTATION,\n code: 'custom',\n });\n }\n\n if (errors.length > 0) {\n return { success: false, errors };\n }\n // zod4 optional outputs are `T | undefined`; the wire type uses exact\n // optionals. Structurally identical at runtime — the cast is type-level.\n return { success: true, data: { ...container.data, items } as unknown as ItemGroup };\n}\n","/**\n * The bounds of an interactive video — an item group with a `timeline`.\n *\n * A leaf module on purpose: the schemas, the authoring checks and the timeline\n * helpers all read these, and nothing here may import any of them back.\n */\n\n/**\n * The item types an interactive video may present. CLOSED on purpose: a type\n * added to the SDK later, or a custom type a host registers, is not silently\n * embeddable. Each one here was chosen, and each works inside a paused video —\n * a read-aloud's microphone included, because every quiz pauses the video\n * first.\n */\nexport const INTERACTIVE_VIDEO_ITEM_TYPES = [\n 'multiple-choice',\n 'fill-in-the-blanks',\n 'gap-select',\n 'dictation',\n 'read-aloud',\n] as const;\n\n/** One of {@link INTERACTIVE_VIDEO_ITEM_TYPES}. */\nexport type InteractiveVideoItemType = (typeof INTERACTIVE_VIDEO_ITEM_TYPES)[number];\n\n/** Whether `type` is one of the item types an interactive video may present. */\nexport function isInteractiveVideoItemType(type: unknown): type is InteractiveVideoItemType {\n return (INTERACTIVE_VIDEO_ITEM_TYPES as readonly unknown[]).includes(type);\n}\n\n/** The most quizzes one video may hold. */\nexport const TIMELINE_MAX_QUIZZES = 100;\n\n/**\n * The most questions one interactive video may hold, across all its quizzes.\n * The player keeps every one of them mounted, so an answer survives the\n * learner rewinding past its quiz.\n */\nexport const TIMELINE_MAX_ITEMS = 200;\n\n/** The most chapters one video may hold. */\nexport const TIMELINE_MAX_CHAPTERS = 100;\n\n/** The longest quiz or chapter title, in UTF-16 units. A title sits on one line of the player. */\nexport const TIMELINE_MAX_TITLE_LENGTH = 120;\n","import { z } from 'zod/v4';\nimport { UnknownActivityTypeError } from '../errors.js';\nimport { getActivityTypeDescriptor } from '../registry/index.js';\nimport { DictationDataSchema } from './dictation.js';\nimport { FillInTheBlanksDataSchema } from './fill-in-the-blanks.js';\nimport { GapSelectDataSchema } from './gap-select.js';\nimport { ItemGroupSchema, StimulusSchema } from './item-group.js';\nimport { MultipleChoiceDataSchema } from './multiple-choice.js';\nimport { ReadAloudDataSchema } from './read-aloud.js';\nimport { isOwnSchema, standardJsonSchemaOf } from './read-schema.js';\nimport { WrittenResponseDataSchema } from './written-response.js';\n\n/**\n * JSON Schema (Draft 7) for a `Stimulus`. Structural contract only — the\n * kind/media/body consistency guards are Zod-only.\n */\nexport const stimulusJsonSchema: Record<string, unknown> = z.toJSONSchema(StimulusSchema, {\n target: 'draft-7',\n});\n\n/**\n * JSON Schema (Draft 7) for an `ItemGroup` CONTAINER. Items appear as objects\n * with `type` and `id` only; each item's own contract is `jsonSchemaFor(type)`.\n * For an AI generation pipeline, ask for the group and each item separately\n * rather than a single nested schema — that keeps the per-type schema the\n * registry's, not a copy.\n */\nexport const itemGroupJsonSchema: Record<string, unknown> = z.toJSONSchema(ItemGroupSchema, {\n target: 'draft-7',\n});\n\n/**\n * JSON Schema (Draft 7) representation of the Multiple Choice activity data\n * contract, generated natively by Zod 4. Draft 7 is mandated by Requirement\n * 2.2 for the widest AI-prompt / OpenAPI tooling compatibility. The semantic\n * `.refine()` guards are not representable in JSON Schema and are\n * intentionally omitted — the export captures the *structural* contract only.\n * Since v0.3 the source schemas are loose, so these no longer emit\n * `additionalProperties: false` — the JSON Schema and `validateActivity` now\n * agree on unknown-key handling.\n */\n/**\n * JSON Schema (Draft 7) representation of the Gap Select activity data\n * contract. Structural contract only: the six semantic guards — the\n * passage/gap bijection, id uniqueness, the one-choice-source rule, and an\n * answer key that names a choice the gap offers — are Zod-only, as they are\n * for the other types.\n */\nexport const gapSelectJsonSchema: Record<string, unknown> = z.toJSONSchema(GapSelectDataSchema, {\n target: 'draft-7',\n});\n\n/**\n * JSON Schema (Draft 7) representation of the Dictation activity data\n * contract. Structural contract, plus the raw length caps JSON Schema can\n * state in code points as zod counts them — `maxLength` 2000 on a transcript\n * and an accepted transcript, 200 on a rule's `from` and `to`. The twelve\n * semantic guards — a transcript that survives normalisation, the caps after\n * equivalences are applied, distinct candidates, audio only, the\n * slow-recording rules, no captions, no transcript in the title — are\n * Zod-only, as they are for the other types.\n */\nexport const dictationJsonSchema: Record<string, unknown> = z.toJSONSchema(DictationDataSchema, {\n target: 'draft-7',\n});\n\n/**\n * JSON Schema (Draft 7) representation of the Read Aloud activity data\n * contract. Structural contract, plus the raw cap JSON Schema can state in code\n * points as zod counts them — `maxLength` 2000 on the reference text — and the\n * canonical locale pattern. The ten semantic guards — the cap after\n * normalisation, a text that survives it, the spaced-script rule, the\n * model-recording rules, unique dimensions, a weight above 0, and the take\n * bounds — are Zod-only, as they are for the other types.\n */\nexport const readAloudJsonSchema: Record<string, unknown> = z.toJSONSchema(ReadAloudDataSchema, {\n target: 'draft-7',\n});\n\nexport const multipleChoiceJsonSchema: Record<string, unknown> = z.toJSONSchema(\n MultipleChoiceDataSchema,\n {\n target: 'draft-7',\n },\n);\n\n/**\n * JSON Schema (Draft 7) representation of the Fill-in-the-Blanks activity data\n * contract, generated natively by Zod 4. Structural contract only (semantic\n * `.refine()` guards are Zod-only and not representable in JSON Schema).\n */\nexport const fillInTheBlanksJsonSchema: Record<string, unknown> = z.toJSONSchema(\n FillInTheBlanksDataSchema,\n {\n target: 'draft-7',\n },\n);\n\n/**\n * JSON Schema (Draft 7) representation of the Written Response activity data\n * contract. Structural contract only.\n */\nexport const writtenResponseJsonSchema: Record<string, unknown> = z.toJSONSchema(\n WrittenResponseDataSchema,\n {\n target: 'draft-7',\n },\n);\n\n/**\n * Derives the JSON Schema (Draft 7) for any REGISTERED activity type — the\n * live, registry-backed replacement for the static per-type exports above,\n * and the building block for AI generation pipelines (R6.1): pass the result\n * as a structured-output schema so a model can only emit valid items.\n *\n * For a type you registered, it is the descriptor's `jsonSchema` when you gave\n * one, or else what the schema produces through the Standard JSON Schema\n * converter (`'~standard'.jsonSchema`) — a zod 4.6 schema has one. A fresh copy\n * every call.\n *\n * @throws UnknownActivityTypeError when `type` has no registered descriptor.\n * @throws Error when a registered type has neither a `jsonSchema` nor a schema\n * that can produce one.\n */\nexport function jsonSchemaFor(type: string): Record<string, unknown> {\n const descriptor = getActivityTypeDescriptor(type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(type);\n }\n if (isOwnSchema(descriptor.schema)) {\n return z.toJSONSchema(descriptor.schema as never, { target: 'draft-7' }) as Record<\n string,\n unknown\n >;\n }\n if (descriptor.jsonSchema !== undefined) {\n return structuredClone(descriptor.jsonSchema) as Record<string, unknown>;\n }\n const generated = standardJsonSchemaOf(descriptor.schema);\n if (generated === undefined) {\n throw new Error(\n `Activity type \"${descriptor.type}\" has no JSON Schema: its schema implements no Standard JSON Schema converter ('~standard'.jsonSchema). Give its descriptor a \\`jsonSchema\\`.`,\n );\n }\n return generated;\n}\n","import { UnknownActivityTypeError } from '../errors.js';\nimport { getActivityTypeDescriptor } from '../registry/index.js';\nimport type {\n ActivityDataMap,\n ActivityMedia,\n ActivityType,\n MultipleChoiceOptionMedia,\n ValidationResult,\n} from '../types/activity.js';\nimport { MediaSchema } from './media.js';\nimport { MultipleChoiceOptionMediaSchema } from './multiple-choice.js';\nimport { readSchema, type SchemaReading } from './read-schema.js';\n\nexport type {\n RedactedActivity,\n RedactedBlankConfig,\n RedactedDictationData,\n RedactedDictationSlowMedia,\n RedactedFillInTheBlanksData,\n RedactedGapSelectBank,\n RedactedGapSelectChoice,\n RedactedGapSelectData,\n RedactedGapSelectGap,\n RedactedMultipleChoiceData,\n RedactedMultipleChoiceOption,\n RedactedMultipleChoiceOptionMedia,\n RedactedReadAloudData,\n RedactedStimulus,\n RedactedWrittenResponseData,\n} from '../types/redacted.js';\nexport { validateItemGroup } from './item-group.js';\nexport {\n dictationJsonSchema,\n fillInTheBlanksJsonSchema,\n gapSelectJsonSchema,\n itemGroupJsonSchema,\n jsonSchemaFor,\n multipleChoiceJsonSchema,\n readAloudJsonSchema,\n stimulusJsonSchema,\n writtenResponseJsonSchema,\n} from './json-schema.js';\n\n/**\n * Checks a piece of activity media — an item's `media`, a stimulus's, a\n * dictation's `slowMedia` — on its own, the way `validateActivity` checks it\n * inside an item: its kind, an address in the allowed schemes, a playback\n * policy only on audio, captions and tracks only where they can play. For an\n * editor's media picker, which holds the media before the item is whole.\n *\n * @returns `{ success: true, data }`, or `{ success: false, errors }` with the\n * same codes, paths and messages `validateActivity` reports.\n */\nexport function validateMedia(value: unknown): ValidationResult<ActivityMedia> {\n return asValidation(readSchema(MediaSchema as never, value, 'Media'));\n}\n\n/**\n * Checks a multiple-choice option's `media` on its own: a picture or a\n * recording, at an address in the allowed schemes, with no playback policy.\n *\n * @returns `{ success: true, data }`, or `{ success: false, errors }` with the\n * same codes, paths and messages `validateActivity` reports.\n */\nexport function validateOptionMedia(value: unknown): ValidationResult<MultipleChoiceOptionMedia> {\n return asValidation(readSchema(MultipleChoiceOptionMediaSchema as never, value, 'Option media'));\n}\n\nfunction asValidation<T>(reading: SchemaReading<unknown>): ValidationResult<T> {\n if (reading.success) {\n return { success: true, data: reading.data as T };\n }\n return {\n success: false,\n errors: reading.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n })),\n };\n}\n\n/**\n * Validates raw activity data against the schema registered for the given\n * activity type (built-in or consumer-registered via `registerActivityType`).\n *\n * Unknown keys are PRESERVED, not stripped: every built-in schema is loose,\n * so consumer sidecar fields and forward-version fields survive validation\n * verbatim in the returned `data`.\n *\n * @returns `{ success: true, data }` with the typed, validated data, or\n * `{ success: false, errors }` with one entry per failed constraint.\n * @throws UnknownActivityTypeError when `type` has no registered descriptor.\n */\nexport function validateActivity<T extends ActivityType>(\n type: T,\n data: unknown,\n): ValidationResult<ActivityDataMap[T]> {\n const descriptor = getActivityTypeDescriptor(type);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(String(type));\n }\n\n return asValidation(readSchema(descriptor.schema, data, `Activity type \"${descriptor.type}\"`));\n}\n"]}
|