@thanh01.pmt/curriculum-kit 1.0.1 → 1.0.3

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.
@@ -1,17 +1,20 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path4 = require('path');
3
5
  var ai = require('ai');
4
6
  var google = require('@ai-sdk/google');
5
7
  var openai = require('@ai-sdk/openai');
6
8
  var deepseek = require('@ai-sdk/deepseek');
7
- var fs = require('fs');
8
- var path = require('path');
9
9
  var zod = require('zod');
10
10
  var workflowPkg = require('workflow');
11
11
  var fs2 = require('fs/promises');
12
12
  var rest = require('@octokit/rest');
13
13
  var supabaseJs = require('@supabase/supabase-js');
14
+ var url = require('url');
15
+ require('crypto');
14
16
 
17
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
19
 
17
20
  function _interopNamespace(e) {
@@ -33,26 +36,133 @@ function _interopNamespace(e) {
33
36
  }
34
37
 
35
38
  var fs__default = /*#__PURE__*/_interopDefault(fs);
36
- var path__default = /*#__PURE__*/_interopDefault(path);
39
+ var path4__default = /*#__PURE__*/_interopDefault(path4);
37
40
  var workflowPkg__namespace = /*#__PURE__*/_interopNamespace(workflowPkg);
38
41
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
39
42
 
40
- // src/ai/flows/generateLessonMasterFlow.ts
43
+ var __getOwnPropNames = Object.getOwnPropertyNames;
44
+ var __esm = (fn, res) => function __init() {
45
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
46
+ };
47
+
48
+ // src/errors/index.ts
49
+ function createAiInferenceError(params) {
50
+ const {
51
+ errorCode,
52
+ message,
53
+ provider,
54
+ statusCode,
55
+ agent,
56
+ artifactType,
57
+ lessonId,
58
+ rawError,
59
+ suggestedAction = getSuggestedActionForCode(errorCode),
60
+ retryable = isCodeRetryable(errorCode)
61
+ } = params;
62
+ return new CurriculumError({
63
+ errorCode,
64
+ message,
65
+ provider,
66
+ statusCode,
67
+ agent,
68
+ artifactType,
69
+ lessonId,
70
+ suggestedAction,
71
+ retryable,
72
+ rawError: typeof rawError === "string" ? rawError : rawError?.message || JSON.stringify(rawError)
73
+ });
74
+ }
75
+ function getSuggestedActionForCode(code) {
76
+ switch (code) {
77
+ case "ERR_AI_NO_API_KEY":
78
+ return "V\xE0o Settings > API Keys \u0111\u1EC3 c\u1EA5u h\xECnh \xEDt nh\u1EA5t m\u1ED9t kh\xF3a API (Gemini, DeepSeek, OpenRouter, Alibaba ho\u1EB7c NVIDIA).";
79
+ case "ERR_AI_RATE_LIMIT":
80
+ return "API \u0111\xE3 v\u01B0\u1EE3t qu\xE1 gi\u1EDBi h\u1EA1n l\u01B0\u1EE3t g\u1ECDi (Rate Limit HTTP 429). H\xE3y ch\u1EDD 30-60 gi\xE2y r\u1ED3i b\u1EA5m Rerun ho\u1EB7c chuy\u1EC3n sang Provider d\u1EF1 ph\xF2ng.";
81
+ case "ERR_AI_AUTH_FAILED":
82
+ return "API Key kh\xF4ng h\u1EE3p l\u1EC7 ho\u1EB7c \u0111\xE3 h\u1EBFt h\u1EA1n (HTTP 401/403). Vui l\xF2ng ki\u1EC3m tra l\u1EA1i Key trong m\u1EE5c C\xE0i \u0111\u1EB7t.";
83
+ case "ERR_AI_NETWORK_TIMEOUT":
84
+ return "K\u1EBFt n\u1ED1i m\u1EA1ng t\u1EDBi m\xE1y ch\u1EE7 AI b\u1ECB qu\xE1 th\u1EDDi gian ch\u1EDD (Timeout). Vui l\xF2ng ki\u1EC3m tra \u0111\u01B0\u1EDDng truy\u1EC1n v\xE0 b\u1EA5m Rerun.";
85
+ case "ERR_AI_RESPONSE_MALFORMED":
86
+ return "M\xF4 h\xECnh AI tr\u1EA3 v\u1EC1 k\u1EBFt qu\u1EA3 r\u1ED7ng ho\u1EB7c kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng markdown. B\u1EA5m Rerun \u0111\u1EC3 y\xEAu c\u1EA7u AI sinh l\u1EA1i.";
87
+ case "ERR_AI_ALL_PROVIDERS_FAILED":
88
+ return "To\xE0n b\u1ED9 c\xE1c nh\xE0 cung c\u1EA5p trong chu\u1ED7i Fallback \u0111\u1EC1u th\u1EA5t b\u1EA1i. Vui l\xF2ng ki\u1EC3m tra k\u1EBFt n\u1ED1i Internet v\xE0 h\u1EA1n m\u1EE9c Token.";
89
+ case "ERR_SOT_MISSING":
90
+ return "Thi\u1EBFu t\xE0i li\u1EC7u n\u1EC1n t\u1EA3ng SOT (CURRICULUM_FRAMEWORK.md ho\u1EB7c LEARNER_PROFILE.md). H\xE3y ch\u1EA1y workflow Kh\u1EDFi t\u1EA1o d\u1EF1 \xE1n tr\u01B0\u1EDBc.";
91
+ case "ERR_TASK_GATE_BLOCKED":
92
+ return "H\u1ECDc li\u1EC7u ph\xEDa sau b\u1ECB ch\u1EB7n do Gi\xE1o \xE1n (LESSON) ph\xEDa tr\u01B0\u1EDBc ch\u01B0a \u0111\u01B0\u1EE3c Ph\xEA duy\u1EC7t (Approval Gate). H\xE3y b\u1EA5m Ph\xEA duy\u1EC7t Gi\xE1o \xE1n \u0111\u1EC3 m\u1EDF kh\xF3a.";
93
+ case "ERR_METERED_LIMIT_EXCEEDED":
94
+ return "Ch\xEDnh s\xE1ch Metered Execution gi\u1EDBi h\u1EA1n t\u1ED1i \u0111a 2 b\xE0i h\u1ECDc m\u1ED7i \u0111\u1EE3t. Vui l\xF2ng ph\xEA duy\u1EC7t c\xE1c b\xE0i hi\u1EC7n t\u1EA1i tr\u01B0\u1EDBc khi t\u1EA1o th\xEAm.";
95
+ case "ERR_SCHEMA_CONTRACT_VIOLATION":
96
+ return "N\u1ED9i dung sinh ra thi\u1EBFu c\xE1c ph\u1EA7n b\u1EAFt bu\u1ED9c [REQUIRED]. H\xE3y k\xEDch ho\u1EA1t ki\u1EC3m th\u1EED ho\u1EB7c b\u1EA5m Rerun.";
97
+ case "ERR_STORAGE_IO_FAILED":
98
+ return "L\u1ED7i \u0111\u1ECDc/ghi t\u1EC7p tin tr\xEAn h\u1EC7 th\u1ED1ng l\u01B0u tr\u1EEF. Ki\u1EC3m tra quy\u1EC1n ghi th\u01B0 m\u1EE5c output/projects.";
99
+ default:
100
+ return "B\u1EA5m Rerun \u0111\u1EC3 th\u1EED l\u1EA1i ho\u1EB7c ki\u1EC3m tra nh\u1EADt k\xFD h\u1EC7 th\u1ED1ng.";
101
+ }
102
+ }
103
+ function isCodeRetryable(code) {
104
+ switch (code) {
105
+ case "ERR_AI_RATE_LIMIT":
106
+ case "ERR_AI_NETWORK_TIMEOUT":
107
+ case "ERR_AI_RESPONSE_MALFORMED":
108
+ case "ERR_STORAGE_IO_FAILED":
109
+ return true;
110
+ case "ERR_AI_NO_API_KEY":
111
+ case "ERR_AI_AUTH_FAILED":
112
+ case "ERR_SOT_MISSING":
113
+ case "ERR_TASK_GATE_BLOCKED":
114
+ case "ERR_METERED_LIMIT_EXCEEDED":
115
+ case "ERR_SCHEMA_CONTRACT_VIOLATION":
116
+ return false;
117
+ default:
118
+ return true;
119
+ }
120
+ }
121
+ var CurriculumError;
122
+ var init_errors = __esm({
123
+ "src/errors/index.ts"() {
124
+ CurriculumError = class _CurriculumError extends Error {
125
+ detail;
126
+ constructor(detail) {
127
+ super(`[${detail.errorCode}] ${detail.message}`);
128
+ this.name = "CurriculumError";
129
+ this.detail = {
130
+ ...detail,
131
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
132
+ };
133
+ Object.setPrototypeOf(this, _CurriculumError.prototype);
134
+ }
135
+ get errorCode() {
136
+ return this.detail.errorCode;
137
+ }
138
+ get retryable() {
139
+ return this.detail.retryable;
140
+ }
141
+ get suggestedAction() {
142
+ return this.detail.suggestedAction;
143
+ }
144
+ toJSON() {
145
+ return this.detail;
146
+ }
147
+ };
148
+ }
149
+ });
41
150
  function resolveApiKey(keyName, explicitKey) {
151
+ if (explicitKey && explicitKey.trim()) return explicitKey.trim();
42
152
  if (typeof process !== "undefined" && process.env && process.env[keyName]) {
43
153
  return process.env[keyName];
44
154
  }
45
155
  try {
46
156
  const currentDir = process.cwd();
47
157
  const envPaths = [
48
- path__default.default.resolve(currentDir, ".env.local"),
49
- path__default.default.resolve(currentDir, ".env"),
50
- path__default.default.resolve(currentDir, "apps/web/.env.local"),
51
- path__default.default.resolve(currentDir, "packages/learnwell-platform/.env"),
52
- path__default.default.resolve(currentDir, "..", ".env.local"),
53
- path__default.default.resolve(currentDir, "..", ".env"),
54
- path__default.default.resolve(currentDir, "../..", ".env.local"),
55
- path__default.default.resolve(currentDir, "../..", ".env")
158
+ path4__default.default.resolve(currentDir, ".env.local"),
159
+ path4__default.default.resolve(currentDir, ".env"),
160
+ path4__default.default.resolve(currentDir, "apps/web/.env.local"),
161
+ path4__default.default.resolve(currentDir, "packages/learnwell-platform/.env"),
162
+ path4__default.default.resolve(currentDir, "..", ".env.local"),
163
+ path4__default.default.resolve(currentDir, "..", ".env"),
164
+ path4__default.default.resolve(currentDir, "../..", ".env.local"),
165
+ path4__default.default.resolve(currentDir, "../..", ".env")
56
166
  ];
57
167
  for (const envPath of envPaths) {
58
168
  if (fs__default.default.existsSync(envPath)) {
@@ -147,8 +257,571 @@ function getDesignatedFallbackConfig() {
147
257
  if (chain.length > 0) return chain[0];
148
258
  return { provider: "nvidia", model: "nvidia/nemotron-3-ultra-550b-a55b" };
149
259
  }
260
+ function extractThoughtAndContent(rawText) {
261
+ let text = rawText || "";
262
+ let thought = "";
263
+ const thinkMatch = text.match(/<think>([\s\S]*?)<\/think>/i);
264
+ if (thinkMatch) {
265
+ thought = thinkMatch[1].trim();
266
+ text = text.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
267
+ } else if (text.includes("<think>")) {
268
+ const parts = text.split("<think>");
269
+ text = parts[0].trim();
270
+ thought = parts[1].trim();
271
+ }
272
+ if (!thought && (text.startsWith("Here's a thinking process:") || text.startsWith("Thinking Process:"))) {
273
+ const splitRegex = /\n(?=(?:#|##|###|🎯|🧭|📋|🚀|1\.\s+[A-Z\u00C0-\u1EF9]|---))/m;
274
+ const match = text.search(splitRegex);
275
+ if (match > 0) {
276
+ thought = text.slice(0, match).trim();
277
+ text = text.slice(match).trim();
278
+ }
279
+ }
280
+ let content = text.trim();
281
+ if (content.startsWith("```markdown")) {
282
+ content = content.replace(/^```markdown\s*\n/i, "").replace(/\n```\s*$/i, "");
283
+ } else if (content.startsWith("```md")) {
284
+ content = content.replace(/^```md\s*\n/i, "").replace(/\n```\s*$/i, "");
285
+ } else if (content.startsWith("```") && content.endsWith("```") && !content.includes("---")) {
286
+ content = content.replace(/^```\w*\s*\n/i, "").replace(/\n```\s*$/i, "");
287
+ }
288
+ return {
289
+ thought: thought.trim(),
290
+ content: content.trim()
291
+ };
292
+ }
293
+ function createIdleAbortController(idleMs) {
294
+ const controller = new AbortController();
295
+ let timer = null;
296
+ const arm = () => {
297
+ if (timer) clearTimeout(timer);
298
+ timer = setTimeout(
299
+ () => controller.abort(new Error(`Idle timeout: no data for ${Math.round(idleMs / 1e3)}s`)),
300
+ idleMs
301
+ );
302
+ timer?.unref?.();
303
+ };
304
+ arm();
305
+ return {
306
+ signal: controller.signal,
307
+ /** Reset the idle window (call on every received chunk). */
308
+ kick: arm,
309
+ /** Clear the pending timer once the request lifecycle is over. */
310
+ dispose: () => {
311
+ if (timer) clearTimeout(timer);
312
+ timer = null;
313
+ }
314
+ };
315
+ }
316
+ async function processOpenAISSEStream(response, onChunk, idle, toolCallCollector) {
317
+ if (!response.body) return "";
318
+ const reader = response.body.getReader();
319
+ const decoder = new TextDecoder("utf-8");
320
+ let fullRaw = "";
321
+ let buffer = "";
322
+ let insideThinkTag = false;
323
+ while (true) {
324
+ const { done, value } = await reader.read();
325
+ if (done) break;
326
+ idle?.kick();
327
+ buffer += decoder.decode(value, { stream: true });
328
+ const lines = buffer.split("\n");
329
+ buffer = lines.pop() || "";
330
+ for (const line of lines) {
331
+ const trimmed = line.trim();
332
+ if (!trimmed || trimmed.startsWith(":")) continue;
333
+ if (trimmed === "data: [DONE]") continue;
334
+ if (trimmed.startsWith("data: ")) {
335
+ try {
336
+ const json = JSON.parse(trimmed.substring(6));
337
+ if (json.usage) {
338
+ const promptTokens = json.usage.prompt_tokens || json.usage.input_tokens || 0;
339
+ const completionTokens = json.usage.completion_tokens || json.usage.output_tokens || 0;
340
+ const totalTokens = json.usage.total_tokens || promptTokens + completionTokens;
341
+ const reasoningTokens = json.usage.completion_tokens_details?.reasoning_tokens || 0;
342
+ if (onChunk) {
343
+ onChunk(JSON.stringify({ promptTokens, completionTokens, totalTokens, reasoningTokens }), "usage");
344
+ }
345
+ }
346
+ const delta = json.choices?.[0]?.delta;
347
+ if (!delta) continue;
348
+ const reasoning = delta.reasoning_content || delta.thought || delta.reasoning || delta.thought_process || "";
349
+ if (reasoning) {
350
+ if (onChunk) onChunk(reasoning, "thought");
351
+ }
352
+ if (delta.tool_calls) {
353
+ const toolStr = JSON.stringify(delta.tool_calls);
354
+ if (onChunk) onChunk(toolStr, "tool_call");
355
+ if (toolCallCollector) ;
356
+ }
357
+ let content = delta.content || "";
358
+ if (content) {
359
+ fullRaw += content;
360
+ if (content.includes("<think>")) {
361
+ insideThinkTag = true;
362
+ const parts = content.split("<think>");
363
+ if (parts[0] && onChunk) onChunk(parts[0], "content");
364
+ content = parts[1] || "";
365
+ }
366
+ if (content.includes("</think>")) {
367
+ insideThinkTag = false;
368
+ const parts = content.split("</think>");
369
+ if (parts[0] && onChunk) onChunk(parts[0], "thought");
370
+ content = parts[1] || "";
371
+ }
372
+ if (insideThinkTag) {
373
+ if (onChunk && content) onChunk(content, "thought");
374
+ } else if (content) {
375
+ if (onChunk) onChunk(content, "content");
376
+ }
377
+ }
378
+ } catch {
379
+ }
380
+ }
381
+ }
382
+ }
383
+ idle?.dispose();
384
+ const extracted = extractThoughtAndContent(fullRaw);
385
+ return extracted.content || fullRaw;
386
+ }
387
+ async function processGeminiSSEStream(response, onChunk, idle) {
388
+ if (!response.body) return "";
389
+ const reader = response.body.getReader();
390
+ const decoder = new TextDecoder("utf-8");
391
+ let fullText = "";
392
+ let buffer = "";
393
+ while (true) {
394
+ const { done, value } = await reader.read();
395
+ if (done) break;
396
+ idle?.kick();
397
+ buffer += decoder.decode(value, { stream: true });
398
+ const lines = buffer.split("\n");
399
+ buffer = lines.pop() || "";
400
+ for (const line of lines) {
401
+ const trimmed = line.trim();
402
+ if (!trimmed || trimmed.startsWith(":")) continue;
403
+ if (trimmed.startsWith("data: ")) {
404
+ try {
405
+ const json = JSON.parse(trimmed.substring(6));
406
+ if (json.usageMetadata) {
407
+ const promptTokens = json.usageMetadata.promptTokenCount || 0;
408
+ const completionTokens = json.usageMetadata.candidatesTokenCount || 0;
409
+ const totalTokens = json.usageMetadata.totalTokenCount || promptTokens + completionTokens;
410
+ if (onChunk) {
411
+ onChunk(JSON.stringify({ promptTokens, completionTokens, totalTokens }), "usage");
412
+ }
413
+ }
414
+ const parts = json.candidates?.[0]?.content?.parts || [];
415
+ for (const part of parts) {
416
+ if (part.thought) {
417
+ if (onChunk) onChunk(part.text || "", "thought");
418
+ } else if (part.text) {
419
+ fullText += part.text;
420
+ if (onChunk) onChunk(part.text, "content");
421
+ }
422
+ }
423
+ } catch {
424
+ }
425
+ }
426
+ }
427
+ }
428
+ idle?.dispose();
429
+ return fullText;
430
+ }
431
+ async function streamCurriculumAIInference(messages, projectContext, options = {}, onChunk) {
432
+ const openrouterKey = resolveApiKey("OPENROUTER_API_KEY", options.openrouterKey);
433
+ const deepseekKey = resolveApiKey("DEEPSEEK_API_KEY", options.deepseekKey);
434
+ const alibabaKey = resolveApiKey("ALIBABA_API_KEY", options.alibabaKey) || resolveApiKey("DASHSCOPE_API_KEY");
435
+ const geminiKey = resolveApiKey("GEMINI_API_KEY", options.geminiKey) || resolveApiKey("NEXT_PUBLIC_GEMINI_API_KEY");
436
+ const nvidiaKey = resolveApiKey("NVIDIA_API_KEY", options.nvidiaKey);
437
+ const temperature = options.temperature ?? 0.3;
438
+ const maxTokens = options.maxTokens ?? 32768;
439
+ const basePersona = options.systemPersona || "You are Curriculum OS Assistant \u2014 Senior AI Technical Curriculum Architect & Educational Content Specialist.";
440
+ const systemInstruction = `${basePersona}
441
+
442
+ Project Context:
443
+ ${projectContext}
444
+
445
+ Guidelines:
446
+ 1. You have full context of the project hierarchy, syllabus, lessons, and learning objectives. Answer precisely, deeply, and directly.
447
+ 2. Complete responses fully without truncation. When generating tables or roadmaps, include all sections/milestones.
448
+ 3. Keep technical terminology and official API symbols intact.
449
+ 4. Format output using clean Markdown, clear sections, bullet points, and code blocks where required.
450
+ 5. Do NOT use emojis in headings, tab titles, or UI navigation labels.`;
451
+ const formattedMessages = [
452
+ { role: "system", content: systemInstruction },
453
+ ...messages.map((m) => ({
454
+ role: m.role === "assistant" || m.role === "model" ? "assistant" : "user",
455
+ content: m.content
456
+ }))
457
+ ];
458
+ const providerErrors = {};
459
+ const requestedModel = options.model || "@preset/coding-free";
460
+ const isNvidiaPreferred = requestedModel.startsWith("nvidia/") || Boolean(nvidiaKey && !openrouterKey) && isProviderEnabled("nvidia");
461
+ if (nvidiaKey && isProviderEnabled("nvidia")) {
462
+ const candidateNemotronModels = [
463
+ requestedModel.startsWith("nvidia/") ? requestedModel : "nvidia/nemotron-3-ultra-550b-a55b",
464
+ "nvidia/nemotron-3-ultra-550b-a55b",
465
+ "nvidia/nemotron-3.5-lightning-30b-a3b",
466
+ "nvidia/nemotron-3-super-120b-a12b",
467
+ "nvidia/llama-3.1-nemotron-70b-instruct"
468
+ ].filter((m) => isModelAllowed(m));
469
+ const uniqueNvidiaModels = Array.from(new Set(candidateNemotronModels));
470
+ if (isNvidiaPreferred || uniqueNvidiaModels.length > 0) {
471
+ for (const model of uniqueNvidiaModels) {
472
+ try {
473
+ const idle = createIdleAbortController(18e4);
474
+ const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
475
+ method: "POST",
476
+ headers: {
477
+ "Authorization": `Bearer ${nvidiaKey}`,
478
+ "Content-Type": "application/json"
479
+ },
480
+ body: JSON.stringify({
481
+ model,
482
+ messages: formattedMessages,
483
+ max_tokens: maxTokens,
484
+ temperature,
485
+ stream: true,
486
+ stream_options: { include_usage: true }
487
+ }),
488
+ signal: idle.signal
489
+ });
490
+ if (res.ok) {
491
+ const text = await processOpenAISSEStream(res, onChunk, idle);
492
+ if (text && text.trim()) return text;
493
+ } else {
494
+ const errText = await res.text().catch(() => "");
495
+ providerErrors[`nvidia_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
496
+ }
497
+ } catch (e) {
498
+ providerErrors[`nvidia_${model}`] = e?.message || "Network error";
499
+ console.warn(`[NVIDIA Stream] Failover for ${model}:`, e?.message || e);
500
+ }
501
+ }
502
+ }
503
+ }
504
+ if (openrouterKey && isProviderEnabled("openrouter")) {
505
+ const openrouterModels = [
506
+ requestedModel,
507
+ "openrouter/free",
508
+ "nvidia/nemotron-3-super-120b-a12b:free",
509
+ "nvidia/nemotron-3-ultra-550b-a55b:free",
510
+ "nvidia/nemotron-3.5-lightning:free",
511
+ "minimax/minimax-m3:free",
512
+ "cohere/north-mini-code:free"
513
+ ].filter((m) => isModelAllowed(m));
514
+ const uniqueOrModels = Array.from(new Set(openrouterModels));
515
+ for (const model of uniqueOrModels) {
516
+ try {
517
+ const idle = createIdleAbortController(18e4);
518
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
519
+ method: "POST",
520
+ headers: {
521
+ "Authorization": `Bearer ${openrouterKey}`,
522
+ "Content-Type": "application/json",
523
+ "HTTP-Referer": "https://orchable.composer",
524
+ "X-Title": "Curriculum OS Studio"
525
+ },
526
+ body: JSON.stringify({
527
+ model,
528
+ messages: formattedMessages,
529
+ max_tokens: maxTokens,
530
+ temperature,
531
+ stream: true,
532
+ stream_options: { include_usage: true }
533
+ }),
534
+ signal: idle.signal
535
+ });
536
+ if (res.ok) {
537
+ const text = await processOpenAISSEStream(res, onChunk, idle);
538
+ if (text && text.trim()) return text;
539
+ } else {
540
+ const errText = await res.text().catch(() => "");
541
+ providerErrors[`openrouter_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
542
+ if (res.status === 429) {
543
+ console.warn(`[OpenRouter] Rate limited (429) for ${model}, trying fallback...`);
544
+ }
545
+ }
546
+ } catch (e) {
547
+ providerErrors[`openrouter_${model}`] = e?.message || "Network error";
548
+ console.warn(`[OpenRouter Stream] Failover for ${model}:`, e?.message || e);
549
+ }
550
+ }
551
+ }
552
+ if (deepseekKey && isProviderEnabled("deepseek")) {
553
+ const dsModel = options.model?.includes("deepseek-reasoner") ? "deepseek-reasoner" : "deepseek-chat";
554
+ if (isModelAllowed(dsModel)) {
555
+ try {
556
+ const idle = createIdleAbortController(18e4);
557
+ const res = await fetch("https://api.deepseek.com/chat/completions", {
558
+ method: "POST",
559
+ headers: {
560
+ "Authorization": `Bearer ${deepseekKey}`,
561
+ "Content-Type": "application/json"
562
+ },
563
+ body: JSON.stringify({
564
+ model: dsModel,
565
+ messages: formattedMessages,
566
+ max_tokens: maxTokens,
567
+ temperature,
568
+ stream: true,
569
+ stream_options: { include_usage: true }
570
+ }),
571
+ signal: idle.signal
572
+ });
573
+ if (res.ok) {
574
+ const text = await processOpenAISSEStream(res, onChunk, idle);
575
+ if (text && text.trim()) return text;
576
+ } else {
577
+ const errText = await res.text().catch(() => "");
578
+ providerErrors["deepseek"] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
579
+ }
580
+ } catch (e) {
581
+ providerErrors["deepseek"] = e?.message || "Network error";
582
+ console.warn(`[DeepSeek Stream] Failover:`, e?.message || e);
583
+ }
584
+ }
585
+ }
586
+ if (alibabaKey && isProviderEnabled("alibaba")) {
587
+ const qwenModels = (options.model?.startsWith("qwen") ? [options.model, "qwen3.7-plus", "qwen3.6-flash"] : ["qwen3.7-plus", "qwen3.6-flash"]).filter((m) => isModelAllowed(m));
588
+ const baseUrl = alibabaKey.startsWith("sk-sp-") ? "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions" : "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions";
589
+ for (const qwenModel of qwenModels) {
590
+ try {
591
+ const idle = createIdleAbortController(18e4);
592
+ const payload = {
593
+ model: qwenModel,
594
+ messages: formattedMessages,
595
+ max_tokens: maxTokens,
596
+ temperature,
597
+ stream: true,
598
+ stream_options: { include_usage: true }
599
+ };
600
+ const res = await fetch(baseUrl, {
601
+ method: "POST",
602
+ headers: {
603
+ "Authorization": `Bearer ${alibabaKey}`,
604
+ "Content-Type": "application/json"
605
+ },
606
+ body: JSON.stringify(payload),
607
+ signal: idle.signal
608
+ });
609
+ if (res.ok) {
610
+ const text = await processOpenAISSEStream(res, onChunk, idle);
611
+ if (text && text.trim()) return text;
612
+ } else {
613
+ const errText = await res.text().catch(() => "");
614
+ providerErrors[`alibaba_${qwenModel}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
615
+ }
616
+ } catch (e) {
617
+ providerErrors[`alibaba_${qwenModel}`] = e?.message || "Network error";
618
+ console.warn(`[Alibaba Stream] Failover for ${qwenModel}:`, e?.message || e);
619
+ }
620
+ }
621
+ }
622
+ if (geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
623
+ const geminiModels = (options.model?.startsWith("gemini") ? [options.model, "gemini-1.5-flash", "gemini-2.0-flash"] : ["gemini-1.5-flash", "gemini-2.0-flash"]).filter((m) => isModelAllowed(m));
624
+ const geminiContents = [
625
+ { role: "user", parts: [{ text: systemInstruction }] },
626
+ { role: "model", parts: [{ text: "T\xF4i \u0111\xE3 hi\u1EC3u r\xF5 to\xE0n b\u1ED9 b\u1ED1i c\u1EA3nh d\u1EF1 \xE1n v\xE0 s\u1EB5n s\xE0ng h\u1ED7 tr\u1EE3 b\u1EA1n m\u1ED9t c\xE1ch chuy\xEAn s\xE2u, ch\xEDnh x\xE1c nh\u1EA5t." }] },
627
+ ...messages.map((m) => ({
628
+ role: m.role === "assistant" || m.role === "model" ? "model" : "user",
629
+ parts: [{ text: m.content }]
630
+ }))
631
+ ];
632
+ for (const gModel of geminiModels) {
633
+ try {
634
+ const idle = createIdleAbortController(18e4);
635
+ const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${gModel}:streamGenerateContent?alt=sse&key=${geminiKey}`, {
636
+ method: "POST",
637
+ headers: { "Content-Type": "application/json" },
638
+ body: JSON.stringify({
639
+ contents: geminiContents,
640
+ generationConfig: {
641
+ temperature,
642
+ maxOutputTokens: maxTokens
643
+ }
644
+ }),
645
+ signal: idle.signal
646
+ });
647
+ if (res.ok) {
648
+ const text = await processGeminiSSEStream(res, onChunk, idle);
649
+ if (text && text.trim()) return text;
650
+ } else {
651
+ const errText = await res.text().catch(() => "");
652
+ providerErrors[`gemini_${gModel}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
653
+ }
654
+ } catch (e) {
655
+ providerErrors[`gemini_${gModel}`] = e?.message || "Network error";
656
+ console.warn(`[Gemini Stream] Failover for ${gModel}:`, e?.message || e);
657
+ }
658
+ }
659
+ }
660
+ const fallbackChain = getDesignatedFallbackChain();
661
+ if (fallbackChain.length > 0) {
662
+ console.log(
663
+ `[streamRunner] \u{1F6E1}\uFE0F Primary routes failed/exhausted. Invoking designated last-resort fallback chain:`,
664
+ fallbackChain.map((c) => `${c.provider}(${c.model})`).join(" -> ")
665
+ );
666
+ for (const target of fallbackChain) {
667
+ const { provider, model } = target;
668
+ if (provider === "nvidia" && nvidiaKey && isProviderEnabled("nvidia")) {
669
+ try {
670
+ const idle = createIdleAbortController(18e4);
671
+ const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
672
+ method: "POST",
673
+ headers: {
674
+ "Authorization": `Bearer ${nvidiaKey}`,
675
+ "Content-Type": "application/json"
676
+ },
677
+ body: JSON.stringify({
678
+ model,
679
+ messages: formattedMessages,
680
+ max_tokens: maxTokens,
681
+ temperature,
682
+ stream: true,
683
+ stream_options: { include_usage: true }
684
+ }),
685
+ signal: idle.signal
686
+ });
687
+ if (res.ok) {
688
+ const text = await processOpenAISSEStream(res, onChunk, idle);
689
+ if (text && text.trim()) {
690
+ console.log(`[streamRunner] \u2705 Designated fallback NVIDIA (${model}) succeeded!`);
691
+ return text;
692
+ }
693
+ } else {
694
+ const errText = await res.text().catch(() => "");
695
+ providerErrors[`designated_fallback_nvidia_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
696
+ }
697
+ } catch (e) {
698
+ providerErrors[`designated_fallback_nvidia_${model}`] = e?.message || "Network error";
699
+ }
700
+ } else if (provider === "openrouter" && openrouterKey && isProviderEnabled("openrouter")) {
701
+ try {
702
+ const idle = createIdleAbortController(18e4);
703
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
704
+ method: "POST",
705
+ headers: {
706
+ "Authorization": `Bearer ${openrouterKey}`,
707
+ "Content-Type": "application/json",
708
+ "HTTP-Referer": "https://orchable.composer",
709
+ "X-Title": "Curriculum OS Studio"
710
+ },
711
+ body: JSON.stringify({
712
+ model,
713
+ messages: formattedMessages,
714
+ max_tokens: maxTokens,
715
+ temperature,
716
+ stream: true,
717
+ stream_options: { include_usage: true }
718
+ }),
719
+ signal: idle.signal
720
+ });
721
+ if (res.ok) {
722
+ const text = await processOpenAISSEStream(res, onChunk, idle);
723
+ if (text && text.trim()) {
724
+ console.log(`[streamRunner] \u2705 Designated fallback OpenRouter (${model}) succeeded!`);
725
+ return text;
726
+ }
727
+ } else {
728
+ const errText = await res.text().catch(() => "");
729
+ providerErrors[`designated_fallback_openrouter_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
730
+ }
731
+ } catch (e) {
732
+ providerErrors[`designated_fallback_openrouter_${model}`] = e?.message || "Network error";
733
+ }
734
+ } else if ((provider === "alibaba" || provider === "dashscope") && alibabaKey && isProviderEnabled("alibaba")) {
735
+ const baseUrl = alibabaKey.startsWith("sk-sp-") ? "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions" : "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions";
736
+ try {
737
+ const idle = createIdleAbortController(18e4);
738
+ const res = await fetch(baseUrl, {
739
+ method: "POST",
740
+ headers: {
741
+ "Authorization": `Bearer ${alibabaKey}`,
742
+ "Content-Type": "application/json"
743
+ },
744
+ body: JSON.stringify({
745
+ model,
746
+ messages: formattedMessages,
747
+ max_tokens: maxTokens,
748
+ temperature,
749
+ stream: true,
750
+ stream_options: { include_usage: true }
751
+ }),
752
+ signal: idle.signal
753
+ });
754
+ if (res.ok) {
755
+ const text = await processOpenAISSEStream(res, onChunk, idle);
756
+ if (text && text.trim()) {
757
+ console.log(`[streamRunner] \u2705 Designated fallback Alibaba (${model}) succeeded!`);
758
+ return text;
759
+ }
760
+ } else {
761
+ const errText = await res.text().catch(() => "");
762
+ providerErrors[`designated_fallback_alibaba_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
763
+ }
764
+ } catch (e) {
765
+ providerErrors[`designated_fallback_alibaba_${model}`] = e?.message || "Network error";
766
+ }
767
+ } else if ((provider === "google" || provider === "gemini") && geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
768
+ try {
769
+ const idle = createIdleAbortController(18e4);
770
+ const res = await fetch(
771
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${geminiKey}`,
772
+ {
773
+ method: "POST",
774
+ headers: { "Content-Type": "application/json" },
775
+ body: JSON.stringify({
776
+ contents: [
777
+ { role: "user", parts: [{ text: systemInstruction }] },
778
+ { role: "model", parts: [{ text: "Understood. I will follow the project context and guidelines." }] },
779
+ ...messages.map((m) => ({
780
+ role: m.role === "assistant" || m.role === "model" ? "model" : "user",
781
+ parts: [{ text: m.content }]
782
+ }))
783
+ ],
784
+ generationConfig: {
785
+ temperature,
786
+ maxOutputTokens: maxTokens
787
+ }
788
+ }),
789
+ signal: idle.signal
790
+ }
791
+ );
792
+ if (res.ok) {
793
+ const text = await processGeminiSSEStream(res, onChunk, idle);
794
+ if (text && text.trim()) {
795
+ console.log(`[streamRunner] \u2705 Designated fallback Gemini (${model}) succeeded!`);
796
+ return text;
797
+ }
798
+ } else {
799
+ const errText = await res.text().catch(() => "");
800
+ providerErrors[`designated_fallback_gemini_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
801
+ }
802
+ } catch (e) {
803
+ providerErrors[`designated_fallback_gemini_${model}`] = e?.message || "Network error";
804
+ }
805
+ }
806
+ }
807
+ }
808
+ const isRateLimited = Object.values(providerErrors).some((msg) => msg.includes("429"));
809
+ const isAuthFailed = Object.values(providerErrors).some((msg) => msg.includes("401") || msg.includes("403"));
810
+ const errorCode = isRateLimited ? "ERR_AI_RATE_LIMIT" : isAuthFailed ? "ERR_AI_AUTH_FAILED" : "ERR_AI_ALL_PROVIDERS_FAILED";
811
+ throw createAiInferenceError({
812
+ errorCode,
813
+ message: `Kh\xF4ng th\u1EC3 k\u1EBFt n\u1ED1i t\u1EDBi c\xE1c nh\xE0 cung c\u1EA5p AI trong chu\u1ED7i Fallback. Chi ti\u1EBFt: ${JSON.stringify(providerErrors)}`,
814
+ rawError: providerErrors
815
+ });
816
+ }
817
+ var init_streamRunner = __esm({
818
+ "src/ai/streamRunner.ts"() {
819
+ init_errors();
820
+ }
821
+ });
150
822
 
151
823
  // src/ai/provider-factory.ts
824
+ init_streamRunner();
152
825
  var DEFAULT_MODELS = {
153
826
  nvidia: "nvidia/nemotron-3-ultra-550b-a55b",
154
827
  openrouter: "@preset/coding-free",
@@ -3928,10 +4601,10 @@ ${answerKeySection}
3928
4601
  var LocalWorkspaceManager = class {
3929
4602
  baseDir;
3930
4603
  constructor(baseDir) {
3931
- this.baseDir = baseDir || path__default.default.resolve(process.cwd(), "output", "workspace-jobs");
4604
+ this.baseDir = baseDir || path4__default.default.resolve(process.cwd(), "output", "workspace-jobs");
3932
4605
  }
3933
4606
  getJobDirectory(jobId) {
3934
- return path__default.default.join(this.baseDir, jobId);
4607
+ return path4__default.default.join(this.baseDir, jobId);
3935
4608
  }
3936
4609
  async initJobWorkspace(jobId, initialInfo) {
3937
4610
  const jobDir = this.getJobDirectory(jobId);
@@ -3954,9 +4627,9 @@ var LocalWorkspaceManager = class {
3954
4627
  }
3955
4628
  async saveArtifact(jobId, milestoneSlug, filename, content, artifactType) {
3956
4629
  const jobDir = this.getJobDirectory(jobId);
3957
- const targetDir = path__default.default.join(jobDir, milestoneSlug);
4630
+ const targetDir = path4__default.default.join(jobDir, milestoneSlug);
3958
4631
  await fs2__default.default.mkdir(targetDir, { recursive: true });
3959
- const filePath = path__default.default.join(targetDir, filename);
4632
+ const filePath = path4__default.default.join(targetDir, filename);
3960
4633
  await fs2__default.default.writeFile(filePath, content, "utf-8");
3961
4634
  const stat = await fs2__default.default.stat(filePath);
3962
4635
  const manifest = await this.getManifest(jobId);
@@ -3964,7 +4637,7 @@ var LocalWorkspaceManager = class {
3964
4637
  manifest.artifacts.push({
3965
4638
  milestoneId: milestoneSlug,
3966
4639
  artifactType,
3967
- filePath: path__default.default.relative(jobDir, filePath),
4640
+ filePath: path4__default.default.relative(jobDir, filePath),
3968
4641
  sizeBytes: stat.size,
3969
4642
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3970
4643
  });
@@ -3974,7 +4647,7 @@ var LocalWorkspaceManager = class {
3974
4647
  return filePath;
3975
4648
  }
3976
4649
  async getManifest(jobId) {
3977
- const manifestPath = path__default.default.join(this.getJobDirectory(jobId), "manifest.json");
4650
+ const manifestPath = path4__default.default.join(this.getJobDirectory(jobId), "manifest.json");
3978
4651
  try {
3979
4652
  const data = await fs2__default.default.readFile(manifestPath, "utf-8");
3980
4653
  return JSON.parse(data);
@@ -3983,7 +4656,7 @@ var LocalWorkspaceManager = class {
3983
4656
  }
3984
4657
  }
3985
4658
  async saveManifest(jobId, manifest) {
3986
- const manifestPath = path__default.default.join(this.getJobDirectory(jobId), "manifest.json");
4659
+ const manifestPath = path4__default.default.join(this.getJobDirectory(jobId), "manifest.json");
3987
4660
  manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3988
4661
  await fs2__default.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3989
4662
  }
@@ -4024,12 +4697,12 @@ async function publishToGitHub(options) {
4024
4697
  async function scanDir(currentDir) {
4025
4698
  const entries = await fs2__default.default.readdir(currentDir, { withFileTypes: true });
4026
4699
  for (const entry of entries) {
4027
- const fullPath = path__default.default.join(currentDir, entry.name);
4700
+ const fullPath = path4__default.default.join(currentDir, entry.name);
4028
4701
  if (entry.isDirectory()) {
4029
4702
  await scanDir(fullPath);
4030
4703
  } else if (entry.isFile() && !entry.name.startsWith(".")) {
4031
4704
  const content = await fs2__default.default.readFile(fullPath, "utf-8");
4032
- const relPath = path__default.default.relative(options.localJobDir, fullPath);
4705
+ const relPath = path4__default.default.relative(options.localJobDir, fullPath);
4033
4706
  filesToUpload.push({ path: relPath, content });
4034
4707
  }
4035
4708
  }
@@ -5237,8 +5910,480 @@ async function generateRoadmapWorkflow(input) {
5237
5910
  };
5238
5911
  }
5239
5912
 
5913
+ // src/services/singleArtifactService.ts
5914
+ init_streamRunner();
5915
+
5916
+ // src/services/lessonProductionService.ts
5917
+ init_streamRunner();
5918
+ init_errors();
5919
+
5920
+ // src/services/lessonProductionService.ts
5921
+ var __filename$1 = typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) : "";
5922
+ var _resolvedDir = __filename$1 ? path4__default.default.dirname(__filename$1) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
5923
+ function loadCurriculumTemplate(templateName) {
5924
+ const candidatePaths = [
5925
+ path4__default.default.resolve("/Users/tonypham/MEGA/WebApp/WIP/learnwell-platform/packages/curriculum-kit/docs/refs/templates", templateName),
5926
+ path4__default.default.resolve(process.cwd(), "packages/curriculum-kit/docs/refs/templates", templateName),
5927
+ path4__default.default.resolve(process.cwd(), "_templates", templateName),
5928
+ path4__default.default.resolve(process.cwd(), "docs/refs/templates", templateName),
5929
+ path4__default.default.resolve(_resolvedDir, "../docs/refs/templates", templateName),
5930
+ path4__default.default.resolve(_resolvedDir, "../../docs/refs/templates", templateName),
5931
+ path4__default.default.resolve(_resolvedDir, "../../../docs/refs/templates", templateName),
5932
+ path4__default.default.resolve("/Users/tonypham/MEGA/WebApp/content-gen/the-ultimate-curriculum-agent-os/_templates", templateName),
5933
+ path4__default.default.resolve(_resolvedDir, "../../../../the-ultimate-curriculum-agent-os/_templates", templateName)
5934
+ ];
5935
+ for (const p of candidatePaths) {
5936
+ if (fs__default.default.existsSync(p)) {
5937
+ try {
5938
+ return fs__default.default.readFileSync(p, "utf-8");
5939
+ } catch {
5940
+ }
5941
+ }
5942
+ }
5943
+ console.warn(`[curriculum-kit:loadCurriculumTemplate] \u26A0\uFE0F Template "${templateName}" not found across candidate paths. Falling back to inline structural scaffolding.`);
5944
+ return "";
5945
+ }
5946
+
5947
+ // src/services/singleArtifactService.ts
5948
+ init_errors();
5949
+ function getArtifactMetadata(type, lessonId, pedagogy) {
5950
+ const id = lessonId || "ON_DEMAND";
5951
+ switch (type) {
5952
+ case "lesson": {
5953
+ const p = (pedagogy || "").toLowerCase();
5954
+ const templateFile = p === "edp" ? "LESSON_EDP_template.md" : p === "general" ? "LESSON_GENERAL_template.md" : "LESSON_5E_template.md";
5955
+ return { filename: `LESSON_${id}.md`, persona: "@content", templateFile };
5956
+ }
5957
+ case "slide":
5958
+ return { filename: `SLIDE_${id}.md`, persona: "@illustrator", templateFile: "SLIDE_template.md" };
5959
+ case "act":
5960
+ return { filename: `ACT_${id}.md`, persona: "@activity", templateFile: "ACT_template.md" };
5961
+ case "quiz":
5962
+ return { filename: `QUIZ_${id}.md`, persona: "@assessor", templateFile: "QUIZ_v4.6_template.md" };
5963
+ case "guide":
5964
+ return { filename: `GUIDE_${id}.md`, persona: "@content", templateFile: "GUIDE_template.md" };
5965
+ case "handout":
5966
+ return { filename: `HANDOUT_${id}.md`, persona: "@content", templateFile: "HANDOUT_template.md" };
5967
+ case "worksheet":
5968
+ return { filename: `WKS_${id}.md`, persona: "@content", templateFile: "WKS_template.md" };
5969
+ case "rubric":
5970
+ return { filename: `RUBRIC_${id}.md`, persona: "@assessor", templateFile: "RUBRIC_template.md" };
5971
+ case "ext":
5972
+ return { filename: `EXT_${id}.md`, persona: "@activity", templateFile: "EXT_template.md" };
5973
+ case "glossary":
5974
+ return { filename: `GLOSSARY_${id}.md`, persona: "@content", templateFile: "GLOSSARY_template.md" };
5975
+ case "codelab":
5976
+ return { filename: `CODE_${id}.md`, persona: "@activity", templateFile: "ACT_template.md" };
5977
+ case "bom":
5978
+ return { filename: `BOM_${id}.md`, persona: "@activity", templateFile: "PROJECT_BRIEF_template.md" };
5979
+ case "exit_ticket":
5980
+ return { filename: `EXIT_TICKET_${id}.md`, persona: "@assessor", templateFile: "EXIT_TICKET_template.md" };
5981
+ case "tiered_practice":
5982
+ return { filename: `TIERED_PRACTICE_${id}.md`, persona: "@activity", templateFile: "TIERED_PRACTICE_template.md" };
5983
+ case "choice_board":
5984
+ return { filename: `CHOICE_BOARD_${id}.md`, persona: "@activity", templateFile: "CHOICE_BOARD_template.md" };
5985
+ case "science_lab":
5986
+ return { filename: `SCIENCE_LAB_${id}.md`, persona: "@activity", templateFile: "SCIENCE_LAB_template.md" };
5987
+ case "task_cards":
5988
+ return { filename: `TASK_CARDS_${id}.md`, persona: "@activity", templateFile: "TASK_CARDS_template.md" };
5989
+ case "station_rotation":
5990
+ return { filename: `STATION_ROTATION_${id}.md`, persona: "@content", templateFile: "STATION_ROTATION_template.md" };
5991
+ case "bell_ringer":
5992
+ return { filename: `BELL_RINGER_${id}.md`, persona: "@content", templateFile: "BELL_RINGER_template.md" };
5993
+ case "review_game":
5994
+ return { filename: `REVIEW_GAME_${id}.md`, persona: "@assessor", templateFile: "REVIEW_GAME_template.md" };
5995
+ case "graphic_organizer":
5996
+ return { filename: `GRAPHIC_ORGANIZER_${id}.md`, persona: "@illustrator", templateFile: "GRAPHIC_ORGANIZER_template.md" };
5997
+ default:
5998
+ return { filename: `ARTIFACT_${id}.md`, persona: "@content", templateFile: "LESSON_5E_template.md" };
5999
+ }
6000
+ }
6001
+ async function generateSingleArtifact(req) {
6002
+ const {
6003
+ artifactType,
6004
+ topic,
6005
+ lessonId,
6006
+ lessonTitle,
6007
+ pedagogy = "5e",
6008
+ targetAge = "14-18 years old (High School / Early College)",
6009
+ scaffoldingLevel = "balanced",
6010
+ hardwarePlatform = ["MacBook Lab (macOS 14+ / Swift 6)"],
6011
+ gradeLevel,
6012
+ studentCount,
6013
+ language = "Vietnamese",
6014
+ extraContext,
6015
+ config,
6016
+ contextSot = {},
6017
+ options = {},
6018
+ onChunk
6019
+ } = req;
6020
+ const meta = getArtifactMetadata(artifactType, lessonId, pedagogy || config?.pedagogicalModel);
6021
+ const displayTitle = lessonTitle || topic;
6022
+ const loadedTemplate = loadCurriculumTemplate(meta.templateFile);
6023
+ const templateContent = loadedTemplate || getDefaultFallbackTemplate(artifactType, displayTitle, lessonId || "ON_DEMAND");
6024
+ const hwStr = hardwarePlatform.join(", ");
6025
+ const selectedDevices = (config?.devices || (hardwarePlatform[0]?.toLowerCase().includes("paper") ? "paper-only" : "laptop")).toLowerCase();
6026
+ const isBilingual = language.toLowerCase().includes("bilingual") || language.toLowerCase().includes("song");
6027
+ const isVietnamese = !isBilingual && language.toLowerCase().includes("viet");
6028
+ const langRule = isBilingual ? `1. AUTHORING LANGUAGE: Bilingual (Vietnamese - English). Headings and core conceptual definitions should be presented in Vietnamese with English technical terms in parentheses. Explanations and instructional text are in Vietnamese. Key code terms, comments, and challenge summaries are presented bilingually.` : isVietnamese ? `1. AUTHORING LANGUAGE: Vietnamese for all instructional text, explanations, questions, rubrics, and presenter notes. Programming code, syntax keywords, and standard API identifiers remain in standard English (with Vietnamese explanation comments/notes).` : `1. AUTHORING LANGUAGE: 100% English (Plan C Canonical Specification).`;
6029
+ const isPaperOnly = selectedDevices === "paper-only" || hwStr.toLowerCase().includes("paper") || hwStr.toLowerCase().includes("print-ready");
6030
+ const deviceRule = isPaperOnly ? `
6031
+ - \u{1F4C4} PAPER-ONLY / NO-DEVICE ENVIRONMENT: The learning space has NO computers or screens. All exercises, worksheets, task cards, exit tickets, and code analyses MUST be 100% printable. Provide write-in blank boxes, printed code snippets with line numbers, tracing tables, and physical manipulative instructions.` : selectedDevices === "tablet" || selectedDevices === "smartphone" ? `
6032
+ - \u{1F4F1} MOBILE/TABLET ENVIRONMENT: Activities should be optimized for touch interfaces and mobile/tablet sandboxes (e.g. Swift Playgrounds, Web IDEs, tap/touch interactions).` : `
6033
+ - \u{1F4BB} LAPTOP/DESKTOP ENVIRONMENT: Full keyboard, terminal, multi-file code editing, and browser developer tooling are available.`;
6034
+ const classDynamicsRule = studentCount ? `
6035
+ - \u{1F465} CLASS SIZE LOGISTICS: Class has exactly ${studentCount} students. Structure group sizes, pair-programming pairs, station rotations, and material distribution to realistically accommodate ${studentCount} students without bottlenecks.` : "";
6036
+ const scaffoldingRule = scaffoldingLevel === "foundation" ? `
6037
+ - \u{1FA9C} FOUNDATION SCAFFOLDING: High instructional support. Provide step-by-step numbered breakdowns, concrete real-world analogies, hints for every problem, scaffolded starter code/fill-in-the-blanks, and frequent check-for-understanding prompts.` : scaffoldingLevel === "advanced" ? `
6038
+ - \u{1F680} ADVANCED CHALLENGE: Minimal scaffolding. Focus on deeper system architecture, edge case handling, computational complexity, performance optimization, and open-ended extension challenges.` : `
6039
+ - \u2696\uFE0F BALANCED SCAFFOLDING: Balanced approach combining structured teacher guidance with independent inquiry and problem solving.`;
6040
+ const extraContextRule = extraContext ? `
6041
+ - \u{1F3AF} CRITICAL USER INSTRUCTIONS (MANDATORY): "${extraContext}". You MUST directly reflect and satisfy every point of this user guidance in the generated artifact!` : "";
6042
+ const artifactSpecializedPrompt = getArtifactSpecializedInvariants(artifactType, {
6043
+ ...req,
6044
+ studentCount});
6045
+ const systemPrompt = `You are ${meta.persona}, an expert Curriculum & Pedagogical Specialist in the Curriculum OS Multi-Agent Team.
6046
+ Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
6047
+
6048
+ ### CORE OPERATIONAL INVARIANTS:
6049
+ ${langRule}
6050
+ 2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
6051
+ 3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
6052
+ 4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
6053
+ 5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
6054
+ 6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}`;
6055
+ const userPrompt = `### CONTEXT GROUND TRUTH:
6056
+ - Topic / Concept Domain: "${topic}"
6057
+ - Lesson ID: "${lessonId || "ON_DEMAND"}"
6058
+ - Artifact Title: "${displayTitle}"
6059
+ - Pedagogy: "${pedagogy.toUpperCase()}"
6060
+ - Scaffolding Level: "${scaffoldingLevel}"
6061
+ - Hardware Environment: "${hwStr}"
6062
+ ${gradeLevel ? `- Target Grade Level: "${gradeLevel}"
6063
+ ` : ""}${studentCount ? `- Class Size: "${studentCount} students"
6064
+ ` : ""}- Target Language: "${language}"
6065
+ ${selectedDevices ? `- Device Mode: "${selectedDevices}"
6066
+ ` : ""}${extraContext ? `- User Specific Guidance & Requirements: "${extraContext}"
6067
+ ` : ""}${config ? `- Full Task Config: ${JSON.stringify(config)}
6068
+ ` : ""}
6069
+ ${contextSot.framework ? `### SOT CURRICULUM FRAMEWORK:
6070
+ ${contextSot.framework.slice(0, 1500)}
6071
+ ` : ""}
6072
+ ${contextSot.lessonPlan ? `### SOT CANONICAL LESSON PLAN:
6073
+ ${contextSot.lessonPlan.slice(0, 1500)}
6074
+ ` : ""}
6075
+ ${contextSot.handout ? `### SOT STUDENT HANDOUT:
6076
+ ${contextSot.handout.slice(0, 1e3)}
6077
+ ` : ""}
6078
+
6079
+ ### MANDATORY CANONICAL TEMPLATE CONTRACT:
6080
+ Use the following reference template structure as the exact specification:
6081
+
6082
+ ${templateContent}
6083
+
6084
+ ### TASK INSTRUCTION:
6085
+ Generate the complete, thorough, publication-grade "${artifactType.toUpperCase()}" artifact for "${displayTitle}". Ensure all [REQUIRED] sections are fully developed with realistic, detailed pedagogical and technical content. Strictly follow the specialized invariants for "${artifactType.toUpperCase()}".`;
6086
+ let accumulatedContent = "";
6087
+ let accumulatedThought = "";
6088
+ try {
6089
+ const rawResult = await streamCurriculumAIInference(
6090
+ [
6091
+ { role: "system", content: systemPrompt },
6092
+ { role: "user", content: userPrompt }
6093
+ ],
6094
+ userPrompt,
6095
+ {
6096
+ ...options,
6097
+ systemPersona: systemPrompt
6098
+ },
6099
+ (chunk, type) => {
6100
+ if (type === "thought") {
6101
+ accumulatedThought += chunk;
6102
+ } else if (type === "content") {
6103
+ accumulatedContent += chunk;
6104
+ }
6105
+ if (onChunk) onChunk(chunk, type === "thought" ? "thought" : "content");
6106
+ }
6107
+ );
6108
+ const { thought, content } = extractThoughtAndContent(rawResult);
6109
+ return {
6110
+ artifactType,
6111
+ filename: meta.filename,
6112
+ content: content || accumulatedContent,
6113
+ persona: meta.persona,
6114
+ thought: thought || accumulatedThought
6115
+ };
6116
+ } catch (error) {
6117
+ throw createAiInferenceError({
6118
+ errorCode: "ERR_AI_ALL_PROVIDERS_FAILED",
6119
+ message: `Failed to generate single artifact ${artifactType}: ${error?.message || "Unknown error"}`,
6120
+ rawError: error
6121
+ });
6122
+ }
6123
+ }
6124
+ function getArtifactSpecializedInvariants(type, req) {
6125
+ const cfg = req.config || {};
6126
+ const isPaperOnly = cfg.devices === "paper-only" || req.hardwarePlatform?.some((h) => h.toLowerCase().includes("paper"));
6127
+ const studentCount = req.studentCount || "25";
6128
+ switch (type) {
6129
+ case "slide": {
6130
+ const slideCount = cfg.slideCount || 10;
6131
+ const presentationStyle = cfg.presentationStyle || "modern";
6132
+ const presentationDuration = cfg.presentationDuration || 45;
6133
+ const includeSpeakerNotes = cfg.includeSpeakerNotes !== false;
6134
+ return `
6135
+ ### \u{1F3AC} SLIDE PRESENTATION & MARP ARCHITECTURE INVARIANTS:
6136
+ 1. **SLIDE COUNT & TIMING TARGET**:
6137
+ - Generate EXACTLY ${slideCount} slides separated by \`---\` horizontal rules.
6138
+ - Pacing: Target total duration is ${presentationDuration} minutes (~${Math.round(Number(presentationDuration) / Number(slideCount))} mins per slide).
6139
+ - Visual Style: "${presentationStyle.toUpperCase()}". Maintain high visual clarity, contrasting accents, and executive polish.
6140
+ 2. **STRICT CONCISENESS & 6x6 RULE (ABSOLUTELY NO TEXT WALLS)**:
6141
+ - A slide is a VISUAL PROJECTION CANVAS, NOT a textbook or lesson plan!
6142
+ - Maximum 4\u20136 bullet points per slide.
6143
+ - Maximum 8\u201312 words per bullet point. Forbid dense multi-sentence paragraphs on the slide surface.
6144
+ 3. **SPEAKER NOTES FOR ALL SPOKEN WORDS (<!-- Presenter Notes: ... -->)**:
6145
+ ${includeSpeakerNotes ? "- MANDATORY: Every single slide MUST contain comprehensive presenter notes in `<!-- Presenter Notes: ... -->` at the bottom, giving the teacher the exact verbal talking points, analogies, and pacing tips." : "- Optional brief presenter notes."}
6146
+ 4. **CLICK-TO-REVEAL SYSTEM**:
6147
+ - Use \`<!-- clicks -->\` before bulleted lists (\`<ul>\` or \`<ol>\`) for incremental click-to-reveal.
6148
+ - Use \`<!-- click -->\` before answers, code reveals, or key callouts.
6149
+ 5. **2-COLUMN / SPLIT LAYOUT**:
6150
+ - Use <div class="columns"><div>...</div><div>...</div></div> for side-by-side concept vs. code or problem vs. solution comparisons.
6151
+ 6. **CONCISE CODE SNIPPETS**:
6152
+ - Maximum 6\u201310 lines of clean code per snippet with inline comments. Never paste whole files.
6153
+ 7. **MERMAID VISUALS**:
6154
+ - Use Mermaid flowcharts or sequence diagrams for workflows and control loops.`;
6155
+ }
6156
+ case "tiered_practice": {
6157
+ const techStack = cfg.techStack || (req.hardwarePlatform?.[0] || "Modern Programming Language");
6158
+ const includeUnitTests = cfg.includeUnitTests !== false;
6159
+ return `
6160
+ ### \u{1FA9C} TIERED PRACTICE ARCHITECTURE INVARIANTS (3-TIER ADAPTIVE):
6161
+ - Target Technology Stack: "${techStack}"
6162
+ ${includeUnitTests ? "- Automated Testing: Include explicit executable Unit Tests / Assertions for verifying Bronze, Silver, and Gold tiers." : ""}
6163
+ 1. **3 EXPLICIT RIGOR TIERS**:
6164
+ - **\u{1F949} Tier 1 (Bronze / C\u01A1 b\u1EA3n)**: 70% starter boilerplate / 30% fill-in. Direct recall, scaffolded fill-in-the-blanks, single-concept recognition with starter hints.
6165
+ - **\u{1F948} Tier 2 (Silver / Ti\xEAu chu\u1EA9n)**: 30% starter boilerplate / 70% implementation. Independent multi-step problem solving, bug identification, synthesis of 2+ concepts.
6166
+ - **\u{1F947} Tier 3 (Gold / Th\u1EED th\xE1ch)**: 0% code / open architecture. Edge case handling, algorithmic optimization, open-ended architectural design or extension.
6167
+ 2. **COMPLETE WORKED SOLUTIONS & ANSWER KEYS**:
6168
+ - Provide step-by-step worked solutions for every problem across all 3 tiers.
6169
+ 3. **SELF-DIAGNOSTIC RUBRIC**:
6170
+ - Include a clear student rubric with criteria for when a student is ready to level up from Bronze \u2794 Silver \u2794 Gold.`;
6171
+ }
6172
+ case "exit_ticket": {
6173
+ const questionCount = cfg.questionCount || 3;
6174
+ const diagnosticFocus = cfg.diagnosticFocus || "misconceptions";
6175
+ return `
6176
+ ### \u{1F39F}\uFE0F EXIT TICKET ARCHITECTURE INVARIANTS:
6177
+ 1. **EXACTLY ${questionCount} TARGETED ITEMS (3\u20135 MINUTE DURATION)**:
6178
+ - Item 1: Quick diagnostic checking mastery of the core objective.
6179
+ - Item 2: Application with diagnostic trap focusing on: "${diagnosticFocus}".
6180
+ - Item 3: Metacognitive self-check (Confidence rating 1\u20135 + "Explain what is still fuzzy").
6181
+ 2. **TEACHER DIAGNOSTIC INTERVENTION GUIDE**:
6182
+ - Provide a quick lookup table: "If student chooses/writes X \u2794 Misconception is Y \u2794 Quick remedy is Z".${isPaperOnly ? "\n3. **PRINTABLE CUT-OUT FORMAT**: Include student name/date line and cut-border dashed line." : ""}`;
6183
+ }
6184
+ case "quiz": {
6185
+ const questionCount = cfg.questionCount || 5;
6186
+ const questionTypes = Array.isArray(cfg.questionTypes) ? cfg.questionTypes.join(", ") : cfg.questionTypes || "multiple_choice";
6187
+ const diagnosticFocus = cfg.diagnosticFocus || "balanced";
6188
+ return `
6189
+ ### \u{1F4DD} BLOOM-ALIGNED QUIZ ARCHITECTURE INVARIANTS:
6190
+ 1. **EXACTLY ${questionCount} QUESTIONS (${questionTypes.toUpperCase()})**:
6191
+ - Diagnostic emphasis: "${diagnosticFocus}".
6192
+ - 40% Remember/Understand (Conceptual foundations).
6193
+ - 40% Apply/Analyze (Bug finding, code tracing, scenario application).
6194
+ - 20% Evaluate/Create (Algorithmic reasoning, trade-off evaluation).
6195
+ 2. **HIGH-QUALITY DISTRACTORS WITH RATIONALE**:
6196
+ - For every multiple choice item, explain WHY each incorrect option is a plausible misconception and WHY the key is correct.
6197
+ 3. **COMPLETE ANSWER KEY & SCORING RUBRIC**:
6198
+ - Explicit point distribution, partial credit guidelines, and sample ideal answers for open-ended questions.`;
6199
+ }
6200
+ case "task_cards": {
6201
+ const cardCount = cfg.cardCount || 12;
6202
+ const cardUsage = cfg.cardUsage || "stations";
6203
+ return `
6204
+ ### \u{1F4CB} TASK CARDS ARCHITECTURE INVARIANTS:
6205
+ 1. **SET OF EXACTLY ${cardCount} NUMBERED CARDS (Optimized for ${cardUsage.toUpperCase()})**:
6206
+ - Clearly mark each card: Card # (1 to ${cardCount}), Difficulty (Foundation / Standard / Challenge), and Target Skill.
6207
+ 2. **STUDENT RECORDING SHEET (B\u1EA2NG GHI \u0110\xC1P \xC1N)**:
6208
+ - Include a structured printable grid where students record answers for Cards 1\u2013${cardCount}.
6209
+ 3. **TEACHER MASTER KEY**:
6210
+ - Quick reference key with verified answers and explanations.${isPaperOnly ? "\n4. **PRINT-READY CARD BORDERS**: Layout with clean visual borders ready for physical printing and cutting." : ""}`;
6211
+ }
6212
+ case "graphic_organizer": {
6213
+ const diagramType = cfg.diagramType || "concept_map";
6214
+ const includeFillIn = cfg.includeFillInExercise !== false;
6215
+ return `
6216
+ ### \u{1F4CA} GRAPHIC ORGANIZER ARCHITECTURE INVARIANTS:
6217
+ 1. **VISUAL MODEL TYPE: "${diagramType.toUpperCase()}"**:
6218
+ - Structure a clear cognitive thinking tool: Concept Map, Flowchart, Venn Diagram, Hierarchy Tree, or Cause-Effect Matrix.
6219
+ 2. **VALID MERMAID SYNTAX**:
6220
+ - Embed complete, 100% syntactically correct Mermaid diagrams showing the knowledge structure.
6221
+ 3. **ACTIVE STUDENT INTERACTION PROMPTS**:
6222
+ ${includeFillIn ? "- Include interactive fill-in-the-blank nodes and connection articulation challenges." : "- Include synthesis and reflection prompts based on the visual model."}`;
6223
+ }
6224
+ case "choice_board": {
6225
+ const gridSize = cfg.gridSize || "3x3";
6226
+ const completionRule = cfg.completionRule || "row_column_diagonal";
6227
+ return `
6228
+ ### \u{1F3AF} CHOICE BOARD ARCHITECTURE INVARIANTS (UDL ${gridSize.toUpperCase()} MATRIX):
6229
+ 1. **${gridSize.toUpperCase()} MENU (UDL MULTIPLE MEANS OF EXPRESSION)**:
6230
+ - Option tasks spanning practical coding, architectural diagrams, analytical writing, and real-world scenarios.
6231
+ 2. **COMPLETION CONTRACT: "${completionRule.toUpperCase()}"**:
6232
+ - Explicit rule (e.g. Tic-Tac-Toe 3-in-a-row/column/diagonal, or Point Bounty system).
6233
+ 3. **UNIFIED EVALUATION RUBRIC**:
6234
+ - Consistent grading criteria applicable across all task choices on the board.`;
6235
+ }
6236
+ case "station_rotation": {
6237
+ const stationCount = cfg.stationCount || 3;
6238
+ const stationDuration = cfg.stationDuration || 15;
6239
+ return `
6240
+ ### \u{1F504} STATION ROTATION ARCHITECTURE INVARIANTS:
6241
+ 1. **EXACTLY ${stationCount} STATIONS SCALED FOR ${studentCount} STUDENTS**:
6242
+ - Group size: ~${Math.ceil(Number(studentCount) / Number(stationCount)) || 7} students per group.
6243
+ - Distinct station roles: Teacher-Led Small Group, Independent Practical Lab, Collaborative / Unplugged Peer Challenge.
6244
+ 2. **TIMING & TRANSITIONS (${stationDuration} MINS/STATION)**:
6245
+ - ${stationDuration} minutes per station + 3-minute structured rotation protocol.
6246
+ 3. **STATION DELIVERABLES & ACCOUNTABILITY**:
6247
+ - Clear check-in deliverable required at each station to ensure on-task accountability.`;
6248
+ }
6249
+ case "act":
6250
+ case "codelab":
6251
+ case "science_lab": {
6252
+ const techStack = cfg.techStack || (req.hardwarePlatform?.[0] || "Modern Programming Language");
6253
+ const includeUnitTests = cfg.includeUnitTests !== false;
6254
+ return `
6255
+ ### \u{1F4BB} HANDS-ON LAB & CODE ACTIVITY ARCHITECTURE INVARIANTS:
6256
+ - Technology Stack: "${techStack}"
6257
+ ${includeUnitTests ? "- Unit Testing: Provide automated assertions or test runner checks." : ""}
6258
+ 1. **100% RUNNABLE CODE / RIGOROUS PROCEDURE**:
6259
+ - Absolutely no fake APIs, mock functions, or non-executable pseudocode.
6260
+ 2. **STRUCTURED LAB PHASES**:
6261
+ - Objective & Environmental Prerequisites.
6262
+ - Starter Skeleton Code with clear \`// TODO [Student Task]\` comments.
6263
+ - Step-by-step Guided Challenges (Level 1 \u2794 Level 2 \u2794 Level 3).
6264
+ - Automated Verification / Test Cases.
6265
+ - Complete Reference Solution with design explanation.`;
6266
+ }
6267
+ case "bell_ringer":
6268
+ return `
6269
+ ### \u{1F514} BELL RINGER ARCHITECTURE INVARIANTS (3\u20135 MIN WARM-UP):
6270
+ 1. **3 QUICK HOOK QUESTIONS**:
6271
+ - 2 rapid-fire recall questions activating previous knowledge.
6272
+ - 1 curiosity hook question bridging into today's core topic.
6273
+ 2. **60-SECOND TEACHER REVIEW KEY**:
6274
+ - Quick-glance answer key for instant whole-class review.`;
6275
+ case "review_game":
6276
+ return `
6277
+ ### \u{1F3AE} REVIEW GAME ARCHITECTURE INVARIANTS:
6278
+ 1. **GAMIFIED STRUCTURE**:
6279
+ - Jeopardy-style board (3\u20134 Categories x 3 Point Values: 100 / 200 / 300) OR 3-Round Interactive Trivia.
6280
+ 2. **CLEAR RULES & TEAM DYNAMICS**:
6281
+ - Team scoring rules, buzz-in protocol, and tie-breaker challenge.
6282
+ 3. **COMPLETE QUESTION & ANSWER DECK**:
6283
+ - Verified answers and point values for the host/teacher.`;
6284
+ case "guide": {
6285
+ const lessonDuration = cfg.lessonDuration || 90;
6286
+ return `
6287
+ ### \u{1F4D6} TEACHER GUIDE ARCHITECTURE INVARIANTS (${lessonDuration} MINS LESSON):
6288
+ 1. **MINUTE-BY-MINUTE PACING TIMELINE**:
6289
+ - Pacing broken down for ${lessonDuration} minutes (Hook, Modeling, Guided Practice, Independent Practice, Debrief).
6290
+ 2. **SOCRATIC QUESTIONS & PROMPTS**:
6291
+ - Cold-call questions with anticipated student misconceptions and remediation scripts.
6292
+ 3. **DIFFERENTIATION STRATEGIES**:
6293
+ - Specific adaptations for students needing support vs. high-flyers needing extension.`;
6294
+ }
6295
+ case "worksheet":
6296
+ case "handout":
6297
+ return `
6298
+ ### \u{1F4D1} WORKSHEET / HANDOUT ARCHITECTURE INVARIANTS:
6299
+ 1. **I DO \u2794 WE DO \u2794 YOU DO SCAFFOLDING**:
6300
+ - Part 1 (I Do): Fully worked and annotated example.
6301
+ - Part 2 (We Do): Guided problem with fill-in hints and scaffolding scaffolds.
6302
+ - Part 3 (You Do): Independent mastery challenge.
6303
+ 2. **SELF-CHECK RUBRIC**:
6304
+ - End-of-worksheet self-evaluation checklist.${isPaperOnly ? "\n3. **PRINTABLE WORKSPACES**: Include spacious, clean write-in boxes and workspace for calculations/code." : ""}`;
6305
+ case "rubric": {
6306
+ const rubricScale = cfg.rubricScale || "4_levels";
6307
+ return `
6308
+ ### \u{1F4CA} ANALYTIC RUBRIC ARCHITECTURE INVARIANTS (${rubricScale.toUpperCase()} SCALE):
6309
+ 1. **RIGOROUS PROFICIENCY LEVELS**:
6310
+ - 4 Proficiency Levels: Level 4 (Exemplary) | Level 3 (Proficient) | Level 2 (Developing) | Level 1 (Beginning).
6311
+ 2. **CONCRETE OBSERVABLE CRITERIA**:
6312
+ - Each cell MUST describe concrete, observable behavioral evidence, not vague words like "good" or "average".`;
6313
+ }
6314
+ case "glossary":
6315
+ return `
6316
+ ### \u{1F4DA} TECHNICAL GLOSSARY ARCHITECTURE INVARIANTS:
6317
+ 1. **COMPREHENSIVE TERM PROFILES**:
6318
+ - For every key term: Technical Definition, Real-world Analogy, Code Syntax Example, and Common Misconception.`;
6319
+ case "bom":
6320
+ return `
6321
+ ### \u{1F4E6} BILL OF MATERIALS (BOM) ARCHITECTURE INVARIANTS:
6322
+ 1. **CLASS-SCALED INVENTORY FOR ${studentCount} STUDENTS**:
6323
+ - Exact quantities calculated for ${studentCount} students.
6324
+ - Component specifications, estimated unit cost, and affordable alternatives.`;
6325
+ case "lesson": {
6326
+ const lessonDuration = cfg.lessonDuration || 90;
6327
+ const pedagogicalModel = cfg.pedagogicalModel || req.pedagogy || "5e";
6328
+ return `
6329
+ ### \u{1F393} CANONICAL LESSON PLAN INVARIANTS (${pedagogicalModel.toUpperCase()} MODEL, ${lessonDuration} MINS):
6330
+ 1. **A. LESSON DESIGN PLAN**:
6331
+ - Learning Objectives (Bloom-aligned) & Observable Student Evidence.
6332
+ - Activity Sequence mapping every phase with timing totaling ${lessonDuration} minutes.
6333
+ - Resource Map & Assessment Map with explicit Artifact Contracts.
6334
+ 2. **B. LESSON FLOW**:
6335
+ - Detailed teacher moves, student actions, scripted key questions, and check-for-understanding points for every activity phase.`;
6336
+ }
6337
+ default:
6338
+ return "";
6339
+ }
6340
+ }
6341
+ function getDefaultFallbackTemplate(type, title, id) {
6342
+ const upperType = type.toUpperCase();
6343
+ return `---
6344
+ id: "${id}"
6345
+ title: "${title}"
6346
+ type: "${upperType}"
6347
+ version: "1.0.0"
6348
+ date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
6349
+ ---
6350
+
6351
+ # ${title}
6352
+
6353
+ ## 1. Overview & Learning Objectives
6354
+ <!-- [REQUIRED] State 2-3 measurable, Bloom-aligned learning objectives -->
6355
+
6356
+ ## 2. Core Pedagogical Content
6357
+ <!-- [REQUIRED] Provide the core educational substance, questions, problems, and diagrams -->
6358
+
6359
+ ## 3. Practice & Differentiation
6360
+ <!-- [REQUIRED] Tiered support, worked examples, and challenges -->
6361
+
6362
+ ## 4. Assessment & Answer Key
6363
+ <!-- [REQUIRED] Complete solutions, rubrics, and diagnostic teacher feedback -->
6364
+ `;
6365
+ }
6366
+
6367
+ // src/workflow/workflows/singleArtifactWorkflow.ts
6368
+ async function executeSingleArtifactStep(request) {
6369
+ "use step";
6370
+ const t0 = Date.now();
6371
+ console.log(` \u23F3 [Step: Single Artifact] Generating ${request.artifactType.toUpperCase()} for "${request.topic.slice(0, 35)}"...`);
6372
+ const res = await withRateLimitBackoff({
6373
+ stepName: `generate-single-artifact-${request.artifactType}-${(request.topic || "topic").slice(0, 20)}`,
6374
+ fn: async () => generateSingleArtifact(request)
6375
+ });
6376
+ console.log(` \u2705 [Step: Single Artifact] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (File: "${res.filename}")`);
6377
+ return res;
6378
+ }
6379
+ async function generateSingleArtifactWorkflow(request) {
6380
+ "use workflow";
6381
+ return executeSingleArtifactStep(request);
6382
+ }
6383
+
5240
6384
  exports.DEFAULT_GATE_SETTINGS = DEFAULT_GATE_SETTINGS;
5241
6385
  exports.approvalPayloadSchema = approvalPayloadSchema;
6386
+ exports.executeSingleArtifactStep = executeSingleArtifactStep;
5242
6387
  exports.gateModeFor = gateModeFor;
5243
6388
  exports.generateActivityStep = generateActivityStep;
5244
6389
  exports.generateCodeLabStep = generateCodeLabStep;
@@ -5249,6 +6394,7 @@ exports.generateMasterLessonStep = generateMasterLessonStep;
5249
6394
  exports.generateMilestoneWorkflow = generateMilestoneWorkflow;
5250
6395
  exports.generateRoadmapWorkflow = generateRoadmapWorkflow;
5251
6396
  exports.generateSelfLabStep = generateSelfLabStep;
6397
+ exports.generateSingleArtifactWorkflow = generateSingleArtifactWorkflow;
5252
6398
  exports.generateSlidesStep = generateSlidesStep;
5253
6399
  exports.generateTeacherGuideStep = generateTeacherGuideStep;
5254
6400
  exports.generateWorksheetStep = generateWorksheetStep;