@thanh01.pmt/curriculum-kit 1.0.0 → 1.0.2

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,18 +1,127 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
1
3
  import { generateObject } from 'ai';
2
4
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
3
5
  import { createOpenAI } from '@ai-sdk/openai';
4
6
  import { createDeepSeek } from '@ai-sdk/deepseek';
5
- import fs from 'fs';
6
- import path from 'path';
7
7
  import { z } from 'zod';
8
8
  import * as workflowPkg from 'workflow';
9
9
  import { getStepMetadata, RetryableError, sleep } from 'workflow';
10
10
  import fs2 from 'fs/promises';
11
11
  import { Octokit } from '@octokit/rest';
12
12
  import { createClient } from '@supabase/supabase-js';
13
+ import { fileURLToPath } from 'url';
14
+ import 'crypto';
13
15
 
14
- // src/ai/flows/generateLessonMasterFlow.ts
16
+ var __getOwnPropNames = Object.getOwnPropertyNames;
17
+ var __esm = (fn, res) => function __init() {
18
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
19
+ };
20
+
21
+ // src/errors/index.ts
22
+ function createAiInferenceError(params) {
23
+ const {
24
+ errorCode,
25
+ message,
26
+ provider,
27
+ statusCode,
28
+ agent,
29
+ artifactType,
30
+ lessonId,
31
+ rawError,
32
+ suggestedAction = getSuggestedActionForCode(errorCode),
33
+ retryable = isCodeRetryable(errorCode)
34
+ } = params;
35
+ return new CurriculumError({
36
+ errorCode,
37
+ message,
38
+ provider,
39
+ statusCode,
40
+ agent,
41
+ artifactType,
42
+ lessonId,
43
+ suggestedAction,
44
+ retryable,
45
+ rawError: typeof rawError === "string" ? rawError : rawError?.message || JSON.stringify(rawError)
46
+ });
47
+ }
48
+ function getSuggestedActionForCode(code) {
49
+ switch (code) {
50
+ case "ERR_AI_NO_API_KEY":
51
+ 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).";
52
+ case "ERR_AI_RATE_LIMIT":
53
+ 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.";
54
+ case "ERR_AI_AUTH_FAILED":
55
+ 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.";
56
+ case "ERR_AI_NETWORK_TIMEOUT":
57
+ 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.";
58
+ case "ERR_AI_RESPONSE_MALFORMED":
59
+ 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.";
60
+ case "ERR_AI_ALL_PROVIDERS_FAILED":
61
+ 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.";
62
+ case "ERR_SOT_MISSING":
63
+ 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.";
64
+ case "ERR_TASK_GATE_BLOCKED":
65
+ 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.";
66
+ case "ERR_METERED_LIMIT_EXCEEDED":
67
+ 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.";
68
+ case "ERR_SCHEMA_CONTRACT_VIOLATION":
69
+ 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.";
70
+ case "ERR_STORAGE_IO_FAILED":
71
+ 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.";
72
+ default:
73
+ return "B\u1EA5m Rerun \u0111\u1EC3 th\u1EED l\u1EA1i ho\u1EB7c ki\u1EC3m tra nh\u1EADt k\xFD h\u1EC7 th\u1ED1ng.";
74
+ }
75
+ }
76
+ function isCodeRetryable(code) {
77
+ switch (code) {
78
+ case "ERR_AI_RATE_LIMIT":
79
+ case "ERR_AI_NETWORK_TIMEOUT":
80
+ case "ERR_AI_RESPONSE_MALFORMED":
81
+ case "ERR_STORAGE_IO_FAILED":
82
+ return true;
83
+ case "ERR_AI_NO_API_KEY":
84
+ case "ERR_AI_AUTH_FAILED":
85
+ case "ERR_SOT_MISSING":
86
+ case "ERR_TASK_GATE_BLOCKED":
87
+ case "ERR_METERED_LIMIT_EXCEEDED":
88
+ case "ERR_SCHEMA_CONTRACT_VIOLATION":
89
+ return false;
90
+ default:
91
+ return true;
92
+ }
93
+ }
94
+ var CurriculumError;
95
+ var init_errors = __esm({
96
+ "src/errors/index.ts"() {
97
+ CurriculumError = class _CurriculumError extends Error {
98
+ detail;
99
+ constructor(detail) {
100
+ super(`[${detail.errorCode}] ${detail.message}`);
101
+ this.name = "CurriculumError";
102
+ this.detail = {
103
+ ...detail,
104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
105
+ };
106
+ Object.setPrototypeOf(this, _CurriculumError.prototype);
107
+ }
108
+ get errorCode() {
109
+ return this.detail.errorCode;
110
+ }
111
+ get retryable() {
112
+ return this.detail.retryable;
113
+ }
114
+ get suggestedAction() {
115
+ return this.detail.suggestedAction;
116
+ }
117
+ toJSON() {
118
+ return this.detail;
119
+ }
120
+ };
121
+ }
122
+ });
15
123
  function resolveApiKey(keyName, explicitKey) {
124
+ if (explicitKey && explicitKey.trim()) return explicitKey.trim();
16
125
  if (typeof process !== "undefined" && process.env && process.env[keyName]) {
17
126
  return process.env[keyName];
18
127
  }
@@ -121,8 +230,571 @@ function getDesignatedFallbackConfig() {
121
230
  if (chain.length > 0) return chain[0];
122
231
  return { provider: "nvidia", model: "nvidia/nemotron-3-ultra-550b-a55b" };
123
232
  }
233
+ function extractThoughtAndContent(rawText) {
234
+ let text = rawText || "";
235
+ let thought = "";
236
+ const thinkMatch = text.match(/<think>([\s\S]*?)<\/think>/i);
237
+ if (thinkMatch) {
238
+ thought = thinkMatch[1].trim();
239
+ text = text.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
240
+ } else if (text.includes("<think>")) {
241
+ const parts = text.split("<think>");
242
+ text = parts[0].trim();
243
+ thought = parts[1].trim();
244
+ }
245
+ if (!thought && (text.startsWith("Here's a thinking process:") || text.startsWith("Thinking Process:"))) {
246
+ const splitRegex = /\n(?=(?:#|##|###|🎯|🧭|📋|🚀|1\.\s+[A-Z\u00C0-\u1EF9]|---))/m;
247
+ const match = text.search(splitRegex);
248
+ if (match > 0) {
249
+ thought = text.slice(0, match).trim();
250
+ text = text.slice(match).trim();
251
+ }
252
+ }
253
+ let content = text.trim();
254
+ if (content.startsWith("```markdown")) {
255
+ content = content.replace(/^```markdown\s*\n/i, "").replace(/\n```\s*$/i, "");
256
+ } else if (content.startsWith("```md")) {
257
+ content = content.replace(/^```md\s*\n/i, "").replace(/\n```\s*$/i, "");
258
+ } else if (content.startsWith("```") && content.endsWith("```") && !content.includes("---")) {
259
+ content = content.replace(/^```\w*\s*\n/i, "").replace(/\n```\s*$/i, "");
260
+ }
261
+ return {
262
+ thought: thought.trim(),
263
+ content: content.trim()
264
+ };
265
+ }
266
+ function createIdleAbortController(idleMs) {
267
+ const controller = new AbortController();
268
+ let timer = null;
269
+ const arm = () => {
270
+ if (timer) clearTimeout(timer);
271
+ timer = setTimeout(
272
+ () => controller.abort(new Error(`Idle timeout: no data for ${Math.round(idleMs / 1e3)}s`)),
273
+ idleMs
274
+ );
275
+ timer?.unref?.();
276
+ };
277
+ arm();
278
+ return {
279
+ signal: controller.signal,
280
+ /** Reset the idle window (call on every received chunk). */
281
+ kick: arm,
282
+ /** Clear the pending timer once the request lifecycle is over. */
283
+ dispose: () => {
284
+ if (timer) clearTimeout(timer);
285
+ timer = null;
286
+ }
287
+ };
288
+ }
289
+ async function processOpenAISSEStream(response, onChunk, idle, toolCallCollector) {
290
+ if (!response.body) return "";
291
+ const reader = response.body.getReader();
292
+ const decoder = new TextDecoder("utf-8");
293
+ let fullRaw = "";
294
+ let buffer = "";
295
+ let insideThinkTag = false;
296
+ while (true) {
297
+ const { done, value } = await reader.read();
298
+ if (done) break;
299
+ idle?.kick();
300
+ buffer += decoder.decode(value, { stream: true });
301
+ const lines = buffer.split("\n");
302
+ buffer = lines.pop() || "";
303
+ for (const line of lines) {
304
+ const trimmed = line.trim();
305
+ if (!trimmed || trimmed.startsWith(":")) continue;
306
+ if (trimmed === "data: [DONE]") continue;
307
+ if (trimmed.startsWith("data: ")) {
308
+ try {
309
+ const json = JSON.parse(trimmed.substring(6));
310
+ if (json.usage) {
311
+ const promptTokens = json.usage.prompt_tokens || json.usage.input_tokens || 0;
312
+ const completionTokens = json.usage.completion_tokens || json.usage.output_tokens || 0;
313
+ const totalTokens = json.usage.total_tokens || promptTokens + completionTokens;
314
+ const reasoningTokens = json.usage.completion_tokens_details?.reasoning_tokens || 0;
315
+ if (onChunk) {
316
+ onChunk(JSON.stringify({ promptTokens, completionTokens, totalTokens, reasoningTokens }), "usage");
317
+ }
318
+ }
319
+ const delta = json.choices?.[0]?.delta;
320
+ if (!delta) continue;
321
+ const reasoning = delta.reasoning_content || delta.thought || delta.reasoning || delta.thought_process || "";
322
+ if (reasoning) {
323
+ if (onChunk) onChunk(reasoning, "thought");
324
+ }
325
+ if (delta.tool_calls) {
326
+ const toolStr = JSON.stringify(delta.tool_calls);
327
+ if (onChunk) onChunk(toolStr, "tool_call");
328
+ if (toolCallCollector) ;
329
+ }
330
+ let content = delta.content || "";
331
+ if (content) {
332
+ fullRaw += content;
333
+ if (content.includes("<think>")) {
334
+ insideThinkTag = true;
335
+ const parts = content.split("<think>");
336
+ if (parts[0] && onChunk) onChunk(parts[0], "content");
337
+ content = parts[1] || "";
338
+ }
339
+ if (content.includes("</think>")) {
340
+ insideThinkTag = false;
341
+ const parts = content.split("</think>");
342
+ if (parts[0] && onChunk) onChunk(parts[0], "thought");
343
+ content = parts[1] || "";
344
+ }
345
+ if (insideThinkTag) {
346
+ if (onChunk && content) onChunk(content, "thought");
347
+ } else if (content) {
348
+ if (onChunk) onChunk(content, "content");
349
+ }
350
+ }
351
+ } catch {
352
+ }
353
+ }
354
+ }
355
+ }
356
+ idle?.dispose();
357
+ const extracted = extractThoughtAndContent(fullRaw);
358
+ return extracted.content || fullRaw;
359
+ }
360
+ async function processGeminiSSEStream(response, onChunk, idle) {
361
+ if (!response.body) return "";
362
+ const reader = response.body.getReader();
363
+ const decoder = new TextDecoder("utf-8");
364
+ let fullText = "";
365
+ let buffer = "";
366
+ while (true) {
367
+ const { done, value } = await reader.read();
368
+ if (done) break;
369
+ idle?.kick();
370
+ buffer += decoder.decode(value, { stream: true });
371
+ const lines = buffer.split("\n");
372
+ buffer = lines.pop() || "";
373
+ for (const line of lines) {
374
+ const trimmed = line.trim();
375
+ if (!trimmed || trimmed.startsWith(":")) continue;
376
+ if (trimmed.startsWith("data: ")) {
377
+ try {
378
+ const json = JSON.parse(trimmed.substring(6));
379
+ if (json.usageMetadata) {
380
+ const promptTokens = json.usageMetadata.promptTokenCount || 0;
381
+ const completionTokens = json.usageMetadata.candidatesTokenCount || 0;
382
+ const totalTokens = json.usageMetadata.totalTokenCount || promptTokens + completionTokens;
383
+ if (onChunk) {
384
+ onChunk(JSON.stringify({ promptTokens, completionTokens, totalTokens }), "usage");
385
+ }
386
+ }
387
+ const parts = json.candidates?.[0]?.content?.parts || [];
388
+ for (const part of parts) {
389
+ if (part.thought) {
390
+ if (onChunk) onChunk(part.text || "", "thought");
391
+ } else if (part.text) {
392
+ fullText += part.text;
393
+ if (onChunk) onChunk(part.text, "content");
394
+ }
395
+ }
396
+ } catch {
397
+ }
398
+ }
399
+ }
400
+ }
401
+ idle?.dispose();
402
+ return fullText;
403
+ }
404
+ async function streamCurriculumAIInference(messages, projectContext, options = {}, onChunk) {
405
+ const openrouterKey = resolveApiKey("OPENROUTER_API_KEY", options.openrouterKey);
406
+ const deepseekKey = resolveApiKey("DEEPSEEK_API_KEY", options.deepseekKey);
407
+ const alibabaKey = resolveApiKey("ALIBABA_API_KEY", options.alibabaKey) || resolveApiKey("DASHSCOPE_API_KEY");
408
+ const geminiKey = resolveApiKey("GEMINI_API_KEY", options.geminiKey) || resolveApiKey("NEXT_PUBLIC_GEMINI_API_KEY");
409
+ const nvidiaKey = resolveApiKey("NVIDIA_API_KEY", options.nvidiaKey);
410
+ const temperature = options.temperature ?? 0.3;
411
+ const maxTokens = options.maxTokens ?? 32768;
412
+ const basePersona = options.systemPersona || "You are Curriculum OS Assistant \u2014 Senior AI Technical Curriculum Architect & Educational Content Specialist.";
413
+ const systemInstruction = `${basePersona}
414
+
415
+ Project Context:
416
+ ${projectContext}
417
+
418
+ Guidelines:
419
+ 1. You have full context of the project hierarchy, syllabus, lessons, and learning objectives. Answer precisely, deeply, and directly.
420
+ 2. Complete responses fully without truncation. When generating tables or roadmaps, include all sections/milestones.
421
+ 3. Keep technical terminology and official API symbols intact.
422
+ 4. Format output using clean Markdown, clear sections, bullet points, and code blocks where required.
423
+ 5. Do NOT use emojis in headings, tab titles, or UI navigation labels.`;
424
+ const formattedMessages = [
425
+ { role: "system", content: systemInstruction },
426
+ ...messages.map((m) => ({
427
+ role: m.role === "assistant" || m.role === "model" ? "assistant" : "user",
428
+ content: m.content
429
+ }))
430
+ ];
431
+ const providerErrors = {};
432
+ const requestedModel = options.model || "@preset/coding-free";
433
+ const isNvidiaPreferred = requestedModel.startsWith("nvidia/") || Boolean(nvidiaKey && !openrouterKey) && isProviderEnabled("nvidia");
434
+ if (nvidiaKey && isProviderEnabled("nvidia")) {
435
+ const candidateNemotronModels = [
436
+ requestedModel.startsWith("nvidia/") ? requestedModel : "nvidia/nemotron-3-ultra-550b-a55b",
437
+ "nvidia/nemotron-3-ultra-550b-a55b",
438
+ "nvidia/nemotron-3.5-lightning-30b-a3b",
439
+ "nvidia/nemotron-3-super-120b-a12b",
440
+ "nvidia/llama-3.1-nemotron-70b-instruct"
441
+ ].filter((m) => isModelAllowed(m));
442
+ const uniqueNvidiaModels = Array.from(new Set(candidateNemotronModels));
443
+ if (isNvidiaPreferred || uniqueNvidiaModels.length > 0) {
444
+ for (const model of uniqueNvidiaModels) {
445
+ try {
446
+ const idle = createIdleAbortController(18e4);
447
+ const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
448
+ method: "POST",
449
+ headers: {
450
+ "Authorization": `Bearer ${nvidiaKey}`,
451
+ "Content-Type": "application/json"
452
+ },
453
+ body: JSON.stringify({
454
+ model,
455
+ messages: formattedMessages,
456
+ max_tokens: maxTokens,
457
+ temperature,
458
+ stream: true,
459
+ stream_options: { include_usage: true }
460
+ }),
461
+ signal: idle.signal
462
+ });
463
+ if (res.ok) {
464
+ const text = await processOpenAISSEStream(res, onChunk, idle);
465
+ if (text && text.trim()) return text;
466
+ } else {
467
+ const errText = await res.text().catch(() => "");
468
+ providerErrors[`nvidia_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
469
+ }
470
+ } catch (e) {
471
+ providerErrors[`nvidia_${model}`] = e?.message || "Network error";
472
+ console.warn(`[NVIDIA Stream] Failover for ${model}:`, e?.message || e);
473
+ }
474
+ }
475
+ }
476
+ }
477
+ if (openrouterKey && isProviderEnabled("openrouter")) {
478
+ const openrouterModels = [
479
+ requestedModel,
480
+ "openrouter/free",
481
+ "nvidia/nemotron-3-super-120b-a12b:free",
482
+ "nvidia/nemotron-3-ultra-550b-a55b:free",
483
+ "nvidia/nemotron-3.5-lightning:free",
484
+ "minimax/minimax-m3:free",
485
+ "cohere/north-mini-code:free"
486
+ ].filter((m) => isModelAllowed(m));
487
+ const uniqueOrModels = Array.from(new Set(openrouterModels));
488
+ for (const model of uniqueOrModels) {
489
+ try {
490
+ const idle = createIdleAbortController(18e4);
491
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
492
+ method: "POST",
493
+ headers: {
494
+ "Authorization": `Bearer ${openrouterKey}`,
495
+ "Content-Type": "application/json",
496
+ "HTTP-Referer": "https://orchable.composer",
497
+ "X-Title": "Curriculum OS Studio"
498
+ },
499
+ body: JSON.stringify({
500
+ model,
501
+ messages: formattedMessages,
502
+ max_tokens: maxTokens,
503
+ temperature,
504
+ stream: true,
505
+ stream_options: { include_usage: true }
506
+ }),
507
+ signal: idle.signal
508
+ });
509
+ if (res.ok) {
510
+ const text = await processOpenAISSEStream(res, onChunk, idle);
511
+ if (text && text.trim()) return text;
512
+ } else {
513
+ const errText = await res.text().catch(() => "");
514
+ providerErrors[`openrouter_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
515
+ if (res.status === 429) {
516
+ console.warn(`[OpenRouter] Rate limited (429) for ${model}, trying fallback...`);
517
+ }
518
+ }
519
+ } catch (e) {
520
+ providerErrors[`openrouter_${model}`] = e?.message || "Network error";
521
+ console.warn(`[OpenRouter Stream] Failover for ${model}:`, e?.message || e);
522
+ }
523
+ }
524
+ }
525
+ if (deepseekKey && isProviderEnabled("deepseek")) {
526
+ const dsModel = options.model?.includes("deepseek-reasoner") ? "deepseek-reasoner" : "deepseek-chat";
527
+ if (isModelAllowed(dsModel)) {
528
+ try {
529
+ const idle = createIdleAbortController(18e4);
530
+ const res = await fetch("https://api.deepseek.com/chat/completions", {
531
+ method: "POST",
532
+ headers: {
533
+ "Authorization": `Bearer ${deepseekKey}`,
534
+ "Content-Type": "application/json"
535
+ },
536
+ body: JSON.stringify({
537
+ model: dsModel,
538
+ messages: formattedMessages,
539
+ max_tokens: maxTokens,
540
+ temperature,
541
+ stream: true,
542
+ stream_options: { include_usage: true }
543
+ }),
544
+ signal: idle.signal
545
+ });
546
+ if (res.ok) {
547
+ const text = await processOpenAISSEStream(res, onChunk, idle);
548
+ if (text && text.trim()) return text;
549
+ } else {
550
+ const errText = await res.text().catch(() => "");
551
+ providerErrors["deepseek"] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
552
+ }
553
+ } catch (e) {
554
+ providerErrors["deepseek"] = e?.message || "Network error";
555
+ console.warn(`[DeepSeek Stream] Failover:`, e?.message || e);
556
+ }
557
+ }
558
+ }
559
+ if (alibabaKey && isProviderEnabled("alibaba")) {
560
+ const qwenModels = (options.model?.startsWith("qwen") ? [options.model, "qwen3.7-plus", "qwen3.6-flash"] : ["qwen3.7-plus", "qwen3.6-flash"]).filter((m) => isModelAllowed(m));
561
+ 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";
562
+ for (const qwenModel of qwenModels) {
563
+ try {
564
+ const idle = createIdleAbortController(18e4);
565
+ const payload = {
566
+ model: qwenModel,
567
+ messages: formattedMessages,
568
+ max_tokens: maxTokens,
569
+ temperature,
570
+ stream: true,
571
+ stream_options: { include_usage: true }
572
+ };
573
+ const res = await fetch(baseUrl, {
574
+ method: "POST",
575
+ headers: {
576
+ "Authorization": `Bearer ${alibabaKey}`,
577
+ "Content-Type": "application/json"
578
+ },
579
+ body: JSON.stringify(payload),
580
+ signal: idle.signal
581
+ });
582
+ if (res.ok) {
583
+ const text = await processOpenAISSEStream(res, onChunk, idle);
584
+ if (text && text.trim()) return text;
585
+ } else {
586
+ const errText = await res.text().catch(() => "");
587
+ providerErrors[`alibaba_${qwenModel}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
588
+ }
589
+ } catch (e) {
590
+ providerErrors[`alibaba_${qwenModel}`] = e?.message || "Network error";
591
+ console.warn(`[Alibaba Stream] Failover for ${qwenModel}:`, e?.message || e);
592
+ }
593
+ }
594
+ }
595
+ if (geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
596
+ 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));
597
+ const geminiContents = [
598
+ { role: "user", parts: [{ text: systemInstruction }] },
599
+ { 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." }] },
600
+ ...messages.map((m) => ({
601
+ role: m.role === "assistant" || m.role === "model" ? "model" : "user",
602
+ parts: [{ text: m.content }]
603
+ }))
604
+ ];
605
+ for (const gModel of geminiModels) {
606
+ try {
607
+ const idle = createIdleAbortController(18e4);
608
+ const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${gModel}:streamGenerateContent?alt=sse&key=${geminiKey}`, {
609
+ method: "POST",
610
+ headers: { "Content-Type": "application/json" },
611
+ body: JSON.stringify({
612
+ contents: geminiContents,
613
+ generationConfig: {
614
+ temperature,
615
+ maxOutputTokens: maxTokens
616
+ }
617
+ }),
618
+ signal: idle.signal
619
+ });
620
+ if (res.ok) {
621
+ const text = await processGeminiSSEStream(res, onChunk, idle);
622
+ if (text && text.trim()) return text;
623
+ } else {
624
+ const errText = await res.text().catch(() => "");
625
+ providerErrors[`gemini_${gModel}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
626
+ }
627
+ } catch (e) {
628
+ providerErrors[`gemini_${gModel}`] = e?.message || "Network error";
629
+ console.warn(`[Gemini Stream] Failover for ${gModel}:`, e?.message || e);
630
+ }
631
+ }
632
+ }
633
+ const fallbackChain = getDesignatedFallbackChain();
634
+ if (fallbackChain.length > 0) {
635
+ console.log(
636
+ `[streamRunner] \u{1F6E1}\uFE0F Primary routes failed/exhausted. Invoking designated last-resort fallback chain:`,
637
+ fallbackChain.map((c) => `${c.provider}(${c.model})`).join(" -> ")
638
+ );
639
+ for (const target of fallbackChain) {
640
+ const { provider, model } = target;
641
+ if (provider === "nvidia" && nvidiaKey && isProviderEnabled("nvidia")) {
642
+ try {
643
+ const idle = createIdleAbortController(18e4);
644
+ const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
645
+ method: "POST",
646
+ headers: {
647
+ "Authorization": `Bearer ${nvidiaKey}`,
648
+ "Content-Type": "application/json"
649
+ },
650
+ body: JSON.stringify({
651
+ model,
652
+ messages: formattedMessages,
653
+ max_tokens: maxTokens,
654
+ temperature,
655
+ stream: true,
656
+ stream_options: { include_usage: true }
657
+ }),
658
+ signal: idle.signal
659
+ });
660
+ if (res.ok) {
661
+ const text = await processOpenAISSEStream(res, onChunk, idle);
662
+ if (text && text.trim()) {
663
+ console.log(`[streamRunner] \u2705 Designated fallback NVIDIA (${model}) succeeded!`);
664
+ return text;
665
+ }
666
+ } else {
667
+ const errText = await res.text().catch(() => "");
668
+ providerErrors[`designated_fallback_nvidia_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
669
+ }
670
+ } catch (e) {
671
+ providerErrors[`designated_fallback_nvidia_${model}`] = e?.message || "Network error";
672
+ }
673
+ } else if (provider === "openrouter" && openrouterKey && isProviderEnabled("openrouter")) {
674
+ try {
675
+ const idle = createIdleAbortController(18e4);
676
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
677
+ method: "POST",
678
+ headers: {
679
+ "Authorization": `Bearer ${openrouterKey}`,
680
+ "Content-Type": "application/json",
681
+ "HTTP-Referer": "https://orchable.composer",
682
+ "X-Title": "Curriculum OS Studio"
683
+ },
684
+ body: JSON.stringify({
685
+ model,
686
+ messages: formattedMessages,
687
+ max_tokens: maxTokens,
688
+ temperature,
689
+ stream: true,
690
+ stream_options: { include_usage: true }
691
+ }),
692
+ signal: idle.signal
693
+ });
694
+ if (res.ok) {
695
+ const text = await processOpenAISSEStream(res, onChunk, idle);
696
+ if (text && text.trim()) {
697
+ console.log(`[streamRunner] \u2705 Designated fallback OpenRouter (${model}) succeeded!`);
698
+ return text;
699
+ }
700
+ } else {
701
+ const errText = await res.text().catch(() => "");
702
+ providerErrors[`designated_fallback_openrouter_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
703
+ }
704
+ } catch (e) {
705
+ providerErrors[`designated_fallback_openrouter_${model}`] = e?.message || "Network error";
706
+ }
707
+ } else if ((provider === "alibaba" || provider === "dashscope") && alibabaKey && isProviderEnabled("alibaba")) {
708
+ 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";
709
+ try {
710
+ const idle = createIdleAbortController(18e4);
711
+ const res = await fetch(baseUrl, {
712
+ method: "POST",
713
+ headers: {
714
+ "Authorization": `Bearer ${alibabaKey}`,
715
+ "Content-Type": "application/json"
716
+ },
717
+ body: JSON.stringify({
718
+ model,
719
+ messages: formattedMessages,
720
+ max_tokens: maxTokens,
721
+ temperature,
722
+ stream: true,
723
+ stream_options: { include_usage: true }
724
+ }),
725
+ signal: idle.signal
726
+ });
727
+ if (res.ok) {
728
+ const text = await processOpenAISSEStream(res, onChunk, idle);
729
+ if (text && text.trim()) {
730
+ console.log(`[streamRunner] \u2705 Designated fallback Alibaba (${model}) succeeded!`);
731
+ return text;
732
+ }
733
+ } else {
734
+ const errText = await res.text().catch(() => "");
735
+ providerErrors[`designated_fallback_alibaba_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
736
+ }
737
+ } catch (e) {
738
+ providerErrors[`designated_fallback_alibaba_${model}`] = e?.message || "Network error";
739
+ }
740
+ } else if ((provider === "google" || provider === "gemini") && geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
741
+ try {
742
+ const idle = createIdleAbortController(18e4);
743
+ const res = await fetch(
744
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${geminiKey}`,
745
+ {
746
+ method: "POST",
747
+ headers: { "Content-Type": "application/json" },
748
+ body: JSON.stringify({
749
+ contents: [
750
+ { role: "user", parts: [{ text: systemInstruction }] },
751
+ { role: "model", parts: [{ text: "Understood. I will follow the project context and guidelines." }] },
752
+ ...messages.map((m) => ({
753
+ role: m.role === "assistant" || m.role === "model" ? "model" : "user",
754
+ parts: [{ text: m.content }]
755
+ }))
756
+ ],
757
+ generationConfig: {
758
+ temperature,
759
+ maxOutputTokens: maxTokens
760
+ }
761
+ }),
762
+ signal: idle.signal
763
+ }
764
+ );
765
+ if (res.ok) {
766
+ const text = await processGeminiSSEStream(res, onChunk, idle);
767
+ if (text && text.trim()) {
768
+ console.log(`[streamRunner] \u2705 Designated fallback Gemini (${model}) succeeded!`);
769
+ return text;
770
+ }
771
+ } else {
772
+ const errText = await res.text().catch(() => "");
773
+ providerErrors[`designated_fallback_gemini_${model}`] = `HTTP ${res.status}: ${errText.slice(0, 120)}`;
774
+ }
775
+ } catch (e) {
776
+ providerErrors[`designated_fallback_gemini_${model}`] = e?.message || "Network error";
777
+ }
778
+ }
779
+ }
780
+ }
781
+ const isRateLimited = Object.values(providerErrors).some((msg) => msg.includes("429"));
782
+ const isAuthFailed = Object.values(providerErrors).some((msg) => msg.includes("401") || msg.includes("403"));
783
+ const errorCode = isRateLimited ? "ERR_AI_RATE_LIMIT" : isAuthFailed ? "ERR_AI_AUTH_FAILED" : "ERR_AI_ALL_PROVIDERS_FAILED";
784
+ throw createAiInferenceError({
785
+ errorCode,
786
+ 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)}`,
787
+ rawError: providerErrors
788
+ });
789
+ }
790
+ var init_streamRunner = __esm({
791
+ "src/ai/streamRunner.ts"() {
792
+ init_errors();
793
+ }
794
+ });
124
795
 
125
796
  // src/ai/provider-factory.ts
797
+ init_streamRunner();
126
798
  var DEFAULT_MODELS = {
127
799
  nvidia: "nvidia/nemotron-3-ultra-550b-a55b",
128
800
  openrouter: "@preset/coding-free",
@@ -5211,6 +5883,201 @@ async function generateRoadmapWorkflow(input) {
5211
5883
  };
5212
5884
  }
5213
5885
 
5214
- export { DEFAULT_GATE_SETTINGS, approvalPayloadSchema, gateModeFor, generateActivityStep, generateCodeLabStep, generateDiagnosticQuizStep, generateExtensionStep, generateHandoutStep, generateMasterLessonStep, generateMilestoneWorkflow, generateRoadmapWorkflow, generateSelfLabStep, generateSlidesStep, generateTeacherGuideStep, generateWorksheetStep, judgeMasterLessonStep, judgeSatelliteStep, lessonApprovalHook, parseGateSettings, publishToGitStep, publishToSupabaseStep, repairMasterLessonStep, resolveGateSettings, saveMilestoneToWorkspaceStep, withRateLimitBackoff };
5886
+ // src/services/singleArtifactService.ts
5887
+ init_streamRunner();
5888
+
5889
+ // src/services/lessonProductionService.ts
5890
+ init_streamRunner();
5891
+ init_errors();
5892
+
5893
+ // src/services/lessonProductionService.ts
5894
+ var __filename$1 = typeof import.meta?.url === "string" ? fileURLToPath(import.meta.url) : "";
5895
+ var _resolvedDir = __filename$1 ? path.dirname(__filename$1) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
5896
+ function loadCurriculumTemplate(templateName) {
5897
+ const candidatePaths = [
5898
+ path.resolve(process.cwd(), "_templates", templateName),
5899
+ path.resolve(process.cwd(), "packages/curriculum-kit/docs/refs/templates", templateName),
5900
+ path.resolve(process.cwd(), "docs/refs/templates", templateName),
5901
+ path.resolve(_resolvedDir, "../docs/refs/templates", templateName),
5902
+ path.resolve(_resolvedDir, "../../docs/refs/templates", templateName),
5903
+ path.resolve(_resolvedDir, "../../../docs/refs/templates", templateName),
5904
+ path.resolve(_resolvedDir, "../../../../the-ultimate-curriculum-agent-os/_templates", templateName)
5905
+ ];
5906
+ for (const p of candidatePaths) {
5907
+ if (fs.existsSync(p)) {
5908
+ try {
5909
+ return fs.readFileSync(p, "utf-8");
5910
+ } catch {
5911
+ }
5912
+ }
5913
+ }
5914
+ console.warn(`[curriculum-kit:loadCurriculumTemplate] \u26A0\uFE0F Template "${templateName}" not found across candidate paths. Falling back to inline structural scaffolding.`);
5915
+ return "";
5916
+ }
5917
+
5918
+ // src/services/singleArtifactService.ts
5919
+ init_errors();
5920
+ function getArtifactMetadata(type, lessonId) {
5921
+ const id = lessonId || "ON_DEMAND";
5922
+ switch (type) {
5923
+ case "lesson":
5924
+ return { filename: `LESSON_${id}.md`, persona: "@content", templateFile: "LESSON_5E_template.md" };
5925
+ case "slide":
5926
+ return { filename: `SLIDE_${id}.md`, persona: "@illustrator", templateFile: "SLIDE_template.md" };
5927
+ case "act":
5928
+ return { filename: `ACT_${id}.md`, persona: "@activity", templateFile: "ACT_template.md" };
5929
+ case "quiz":
5930
+ return { filename: `QUIZ_${id}.md`, persona: "@assessor", templateFile: "QUIZ_v4.6_template.md" };
5931
+ case "guide":
5932
+ return { filename: `GUIDE_${id}.md`, persona: "@content", templateFile: "GUIDE_template.md" };
5933
+ case "handout":
5934
+ return { filename: `HANDOUT_${id}.md`, persona: "@content", templateFile: "HANDOUT_template.md" };
5935
+ case "worksheet":
5936
+ return { filename: `WKS_${id}.md`, persona: "@content", templateFile: "WKS_template.md" };
5937
+ case "rubric":
5938
+ return { filename: `RUBRIC_${id}.md`, persona: "@assessor", templateFile: "RUBRIC_template.md" };
5939
+ case "ext":
5940
+ return { filename: `EXT_${id}.md`, persona: "@activity", templateFile: "EXT_template.md" };
5941
+ case "glossary":
5942
+ return { filename: `GLOSSARY_${id}.md`, persona: "@content", templateFile: "GLOSSARY_template.md" };
5943
+ case "codelab":
5944
+ return { filename: `CODE_${id}.md`, persona: "@activity", templateFile: "ACT_template.md" };
5945
+ case "bom":
5946
+ return { filename: `BOM_${id}.md`, persona: "@activity", templateFile: "PROJECT_BRIEF_template.md" };
5947
+ case "exit_ticket":
5948
+ return { filename: `EXIT_TICKET_${id}.md`, persona: "@assessor", templateFile: "EXIT_TICKET_template.md" };
5949
+ case "tiered_practice":
5950
+ return { filename: `TIERED_PRACTICE_${id}.md`, persona: "@activity", templateFile: "TIERED_PRACTICE_template.md" };
5951
+ case "choice_board":
5952
+ return { filename: `CHOICE_BOARD_${id}.md`, persona: "@activity", templateFile: "CHOICE_BOARD_template.md" };
5953
+ case "science_lab":
5954
+ return { filename: `SCIENCE_LAB_${id}.md`, persona: "@activity", templateFile: "SCIENCE_LAB_template.md" };
5955
+ case "task_cards":
5956
+ return { filename: `TASK_CARDS_${id}.md`, persona: "@activity", templateFile: "TASK_CARDS_template.md" };
5957
+ case "station_rotation":
5958
+ return { filename: `STATION_ROTATION_${id}.md`, persona: "@content", templateFile: "STATION_ROTATION_template.md" };
5959
+ case "bell_ringer":
5960
+ return { filename: `BELL_RINGER_${id}.md`, persona: "@content", templateFile: "BELL_RINGER_template.md" };
5961
+ case "review_game":
5962
+ return { filename: `REVIEW_GAME_${id}.md`, persona: "@assessor", templateFile: "REVIEW_GAME_template.md" };
5963
+ case "graphic_organizer":
5964
+ return { filename: `GRAPHIC_ORGANIZER_${id}.md`, persona: "@illustrator", templateFile: "GRAPHIC_ORGANIZER_template.md" };
5965
+ }
5966
+ }
5967
+ async function generateSingleArtifact(req) {
5968
+ const {
5969
+ artifactType,
5970
+ topic,
5971
+ lessonId,
5972
+ lessonTitle,
5973
+ pedagogy = "5e",
5974
+ targetAge = "14-18 years old (High School / Early College)",
5975
+ scaffoldingLevel = "balanced",
5976
+ hardwarePlatform = ["MacBook Lab (macOS 14+ / Swift 6)"],
5977
+ contextSot = {},
5978
+ options = {},
5979
+ onChunk
5980
+ } = req;
5981
+ const meta = getArtifactMetadata(artifactType, lessonId);
5982
+ const templateContent = loadCurriculumTemplate(meta.templateFile);
5983
+ const displayTitle = lessonTitle || topic;
5984
+ const hwStr = hardwarePlatform.join(", ");
5985
+ const systemPrompt = `You are ${meta.persona}, an expert Curriculum & Pedagogical Specialist in the Curriculum OS Multi-Agent Team.
5986
+ Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
5987
+
5988
+ ### CORE OPERATIONAL INVARIANTS:
5989
+ 1. AUTHORING LANGUAGE: 100% English (Plan C Canonical Specification).
5990
+ 2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
5991
+ 3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
5992
+ 4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}" (Foundation: High support & concrete analogies; Balanced: Application & problem solving; Advanced: Systems architecture & concurrency invariants).
5993
+ 5. TARGET AUDIENCE: ${targetAge} on ${hwStr}.
5994
+ 6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.`;
5995
+ const userPrompt = `### CONTEXT GROUND TRUTH:
5996
+ - Topic / Concept Domain: "${topic}"
5997
+ - Lesson ID: "${lessonId || "ON_DEMAND"}"
5998
+ - Artifact Title: "${displayTitle}"
5999
+ - Pedagogy: "${pedagogy.toUpperCase()}"
6000
+ - Scaffolding Level: "${scaffoldingLevel}"
6001
+ - Hardware Environment: "${hwStr}"
6002
+
6003
+ ${contextSot.framework ? `### SOT CURRICULUM FRAMEWORK:
6004
+ ${contextSot.framework.slice(0, 1500)}
6005
+ ` : ""}
6006
+ ${contextSot.lessonPlan ? `### SOT CANONICAL LESSON PLAN:
6007
+ ${contextSot.lessonPlan.slice(0, 1500)}
6008
+ ` : ""}
6009
+ ${contextSot.handout ? `### SOT STUDENT HANDOUT:
6010
+ ${contextSot.handout.slice(0, 1e3)}
6011
+ ` : ""}
6012
+
6013
+ ### MANDATORY CANONICAL TEMPLATE CONTRACT:
6014
+ Use the following reference template structure as the exact specification:
6015
+
6016
+ ${templateContent || `---
6017
+ id: "${lessonId || "ON_DEMAND"}"
6018
+ title: "${displayTitle}"
6019
+ type: "${artifactType.toUpperCase()}"
6020
+ ---
6021
+ # ${displayTitle}`}
6022
+
6023
+ ### TASK INSTRUCTION:
6024
+ Generate the complete, thorough, production-grade "${artifactType.toUpperCase()}" artifact for "${displayTitle}". Ensure all [REQUIRED] sections are fully developed with realistic, detailed engineering content.`;
6025
+ let accumulatedContent = "";
6026
+ let accumulatedThought = "";
6027
+ try {
6028
+ const rawResult = await streamCurriculumAIInference(
6029
+ [
6030
+ { role: "system", content: systemPrompt },
6031
+ { role: "user", content: userPrompt }
6032
+ ],
6033
+ userPrompt,
6034
+ {
6035
+ ...options,
6036
+ systemPersona: systemPrompt
6037
+ },
6038
+ (chunk, type) => {
6039
+ if (type === "thought") {
6040
+ accumulatedThought += chunk;
6041
+ } else if (type === "content") {
6042
+ accumulatedContent += chunk;
6043
+ }
6044
+ if (onChunk) onChunk(chunk, type === "thought" ? "thought" : "content");
6045
+ }
6046
+ );
6047
+ const { thought, content } = extractThoughtAndContent(rawResult);
6048
+ return {
6049
+ artifactType,
6050
+ filename: meta.filename,
6051
+ content: content || accumulatedContent,
6052
+ persona: meta.persona,
6053
+ thought: thought || accumulatedThought
6054
+ };
6055
+ } catch (error) {
6056
+ throw createAiInferenceError({
6057
+ errorCode: "ERR_AI_ALL_PROVIDERS_FAILED",
6058
+ message: `Failed to generate single artifact ${artifactType}: ${error?.message || "Unknown error"}`,
6059
+ rawError: error
6060
+ });
6061
+ }
6062
+ }
6063
+
6064
+ // src/workflow/workflows/singleArtifactWorkflow.ts
6065
+ async function executeSingleArtifactStep(request) {
6066
+ "use step";
6067
+ const t0 = Date.now();
6068
+ console.log(` \u23F3 [Step: Single Artifact] Generating ${request.artifactType.toUpperCase()} for "${request.topic.slice(0, 35)}"...`);
6069
+ const res = await withRateLimitBackoff({
6070
+ stepName: `generate-single-artifact-${request.artifactType}-${(request.topic || "topic").slice(0, 20)}`,
6071
+ fn: async () => generateSingleArtifact(request)
6072
+ });
6073
+ console.log(` \u2705 [Step: Single Artifact] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (File: "${res.filename}")`);
6074
+ return res;
6075
+ }
6076
+ async function generateSingleArtifactWorkflow(request) {
6077
+ "use workflow";
6078
+ return executeSingleArtifactStep(request);
6079
+ }
6080
+
6081
+ export { DEFAULT_GATE_SETTINGS, approvalPayloadSchema, executeSingleArtifactStep, gateModeFor, generateActivityStep, generateCodeLabStep, generateDiagnosticQuizStep, generateExtensionStep, generateHandoutStep, generateMasterLessonStep, generateMilestoneWorkflow, generateRoadmapWorkflow, generateSelfLabStep, generateSingleArtifactWorkflow, generateSlidesStep, generateTeacherGuideStep, generateWorksheetStep, judgeMasterLessonStep, judgeSatelliteStep, lessonApprovalHook, parseGateSettings, publishToGitStep, publishToSupabaseStep, repairMasterLessonStep, resolveGateSettings, saveMilestoneToWorkspaceStep, withRateLimitBackoff };
5215
6082
  //# sourceMappingURL=index.mjs.map
5216
6083
  //# sourceMappingURL=index.mjs.map