@thanh01.pmt/curriculum-kit 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -13549,7 +13549,7 @@ function safeParseJson(rawText) {
13549
13549
  return null;
13550
13550
  }
13551
13551
  }
13552
- async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk) {
13552
+ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider) {
13553
13553
  const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
13554
13554
  const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
13555
13555
  const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
@@ -13557,6 +13557,11 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
13557
13557
  const geminiKey = apiKeys.gemini || process.env.GEMINI_API_KEY;
13558
13558
  const openrouterPreset = process.env.OPENROUTER_FREE_PRESET || "@preset/coding-free";
13559
13559
  const candidates = [];
13560
+ if (preferredModel) {
13561
+ const prov = preferredProvider || "openrouter";
13562
+ const key = apiKeys[prov] || (prov === "openrouter" ? openrouterKey : prov === "nvidia" ? nvidiaKey : prov === "alibaba" ? alibabaKey : geminiKey) || "";
13563
+ candidates.push({ provider: prov, model: preferredModel, apiKey: key });
13564
+ }
13560
13565
  if (openrouterKey && isProviderEnabled("openrouter")) {
13561
13566
  const orModels = [
13562
13567
  openrouterPreset,
@@ -13564,40 +13569,49 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
13564
13569
  "nvidia/nemotron-3-super-120b-a12b:free",
13565
13570
  "nvidia/nemotron-3-ultra-550b-a55b:free",
13566
13571
  "nvidia/nemotron-3.5-lightning:free",
13567
- "minimax/minimax-m3:free",
13568
13572
  "cohere/north-mini-code:free"
13569
13573
  ].filter((m) => isModelAllowed(m));
13570
13574
  for (const m of Array.from(new Set(orModels))) {
13571
- candidates.push({ provider: "openrouter", model: m, apiKey: openrouterKey });
13575
+ if (!candidates.some((c) => c.provider === "openrouter" && c.model === m)) {
13576
+ candidates.push({ provider: "openrouter", model: m, apiKey: openrouterKey });
13577
+ }
13572
13578
  }
13573
13579
  }
13574
13580
  if (nvidiaKey && isProviderEnabled("nvidia")) {
13575
13581
  const nemotronModels = [
13576
- "nvidia/nemotron-3-ultra-550b-a55b:free",
13582
+ "nvidia/nemotron-3-ultra-550b-a55b",
13577
13583
  "nvidia/nemotron-3.5-lightning-30b-a3b",
13578
13584
  "nvidia/nemotron-3-super-120b-a12b",
13579
13585
  "nvidia/llama-3.1-nemotron-70b-instruct"
13580
13586
  ].filter((m) => isModelAllowed(m));
13581
13587
  for (const m of nemotronModels) {
13582
- candidates.push({ provider: "nvidia", model: m, apiKey: nvidiaKey });
13588
+ if (!candidates.some((c) => c.provider === "nvidia" && c.model === m)) {
13589
+ candidates.push({ provider: "nvidia", model: m, apiKey: nvidiaKey });
13590
+ }
13583
13591
  }
13584
13592
  }
13585
13593
  if (alibabaKey && isProviderEnabled("alibaba")) {
13586
13594
  const aliModels = ["qwen-plus", "qwen-turbo", "qwen3.7-plus"].filter((m) => isModelAllowed(m));
13587
13595
  for (const m of aliModels) {
13588
- candidates.push({ provider: "alibaba", model: m, apiKey: alibabaKey });
13596
+ if (!candidates.some((c) => c.provider === "alibaba" && c.model === m)) {
13597
+ candidates.push({ provider: "alibaba", model: m, apiKey: alibabaKey });
13598
+ }
13589
13599
  }
13590
13600
  }
13591
13601
  if (deepseekKey && isProviderEnabled("deepseek")) {
13592
13602
  const dsModels = ["deepseek-chat", "deepseek-coder"].filter((m) => isModelAllowed(m));
13593
13603
  for (const m of dsModels) {
13594
- candidates.push({ provider: "deepseek", model: m, apiKey: deepseekKey });
13604
+ if (!candidates.some((c) => c.provider === "deepseek" && c.model === m)) {
13605
+ candidates.push({ provider: "deepseek", model: m, apiKey: deepseekKey });
13606
+ }
13595
13607
  }
13596
13608
  }
13597
13609
  if (geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
13598
13610
  const gModels = ["gemini-1.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"].filter((m) => isModelAllowed(m));
13599
13611
  for (const m of gModels) {
13600
- candidates.push({ provider: "google", model: m, apiKey: geminiKey });
13612
+ if (!candidates.some((c) => c.provider === "google" && c.model === m)) {
13613
+ candidates.push({ provider: "google", model: m, apiKey: geminiKey });
13614
+ }
13601
13615
  }
13602
13616
  }
13603
13617
  const fallbackChain = getDesignatedFallbackChain();
@@ -13626,15 +13640,27 @@ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on
13626
13640
  system: systemInstructions,
13627
13641
  prompt: userPrompt,
13628
13642
  temperature: 0.2,
13629
- abortSignal: AbortSignal.timeout(25e3)
13643
+ includeRawChunks: true,
13644
+ abortSignal: AbortSignal.timeout(6e4)
13630
13645
  });
13631
13646
  let fullContent = "";
13632
13647
  for await (const part of streamResult.fullStream) {
13633
13648
  if (part.type === "reasoning-delta") {
13634
- onChunk?.(part.text, "thought");
13649
+ const thoughtText = part.text ?? part.delta ?? "";
13650
+ if (thoughtText) onChunk?.(thoughtText, "thought");
13651
+ } else if (part.type === "raw") {
13652
+ const raw = part.rawValue;
13653
+ const delta = raw?.choices?.[0]?.delta;
13654
+ const reasoning = delta?.reasoning_content || delta?.reasoning;
13655
+ if (reasoning) {
13656
+ onChunk?.(reasoning, "thought");
13657
+ }
13635
13658
  } else if (part.type === "text-delta") {
13636
- fullContent += part.text;
13637
- onChunk?.(part.text, "content");
13659
+ const textDelta = part.text ?? part.delta ?? "";
13660
+ if (textDelta) {
13661
+ fullContent += textDelta;
13662
+ onChunk?.(textDelta, "content");
13663
+ }
13638
13664
  }
13639
13665
  }
13640
13666
  if (fullContent.trim()) {
@@ -13697,6 +13723,68 @@ Return concise JSON matching:
13697
13723
  }
13698
13724
  return parsedResearch;
13699
13725
  }
13726
+ var CONTEXT_KEY_LABELS = {
13727
+ domain: "Domain",
13728
+ customDomain: "Custom Domain",
13729
+ context: "Educational Context (Tier)",
13730
+ contextLabel: "Educational Context",
13731
+ customContext: "Custom Educational Context",
13732
+ targetAgeTier: "Target Age Tier",
13733
+ targetAge: "Target Age & Audience",
13734
+ customAge: "Custom Age Group",
13735
+ courseDurationTier: "Course Duration Tier",
13736
+ courseDuration: "Planned Course Duration",
13737
+ customDuration: "Custom Course Duration",
13738
+ hardwareReadinessTier: "Hardware Readiness Tier",
13739
+ hardwareReadiness: "Hardware & Tool Readiness",
13740
+ customHardware: "Custom Hardware Setup",
13741
+ language: "Artifact Output Language",
13742
+ totalWeeks: "Total Weeks",
13743
+ sessionsPerWeek: "Sessions Per Week",
13744
+ sessionDurationMinutes: "Session Duration (minutes)",
13745
+ totalSessions: "Total Sessions",
13746
+ isConsecutiveSessions: "Consecutive (Block) Sessions",
13747
+ projectName: "Course Title",
13748
+ projectCode: "Project Identifier",
13749
+ courseDescription: "Idea Description"
13750
+ };
13751
+ var STRUCTURED_PROMPT_KEYS = /* @__PURE__ */ new Set([
13752
+ "projectName",
13753
+ "projectCode",
13754
+ "courseDescription",
13755
+ "language",
13756
+ "primaryGoal",
13757
+ "exitVision",
13758
+ "cognitiveDepth",
13759
+ "valueAndCertification",
13760
+ "entryBridge",
13761
+ "pedagogy",
13762
+ "classDynamic",
13763
+ "hardwareDeployment",
13764
+ "teacherRole",
13765
+ "lessonPacingModel",
13766
+ "voiceAndTone"
13767
+ ]);
13768
+ function buildRemainingContextBlock(accumulatedData) {
13769
+ const lines = [];
13770
+ for (const [key, rawVal] of Object.entries(accumulatedData)) {
13771
+ if (STRUCTURED_PROMPT_KEYS.has(key)) continue;
13772
+ if (rawVal === void 0 || rawVal === null || rawVal === "") continue;
13773
+ if (key.endsWith("_ids") || key.endsWith("_id")) continue;
13774
+ const label = CONTEXT_KEY_LABELS[key] || key.replace(/([A-Z])/g, " $1").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase()).trim();
13775
+ let valueStr;
13776
+ if (Array.isArray(rawVal)) {
13777
+ valueStr = rawVal.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join("; ");
13778
+ } else if (typeof rawVal === "object") {
13779
+ valueStr = JSON.stringify(rawVal);
13780
+ } else {
13781
+ valueStr = String(rawVal);
13782
+ }
13783
+ if (!valueStr.trim()) continue;
13784
+ lines.push(`- ${label}: ${valueStr}`);
13785
+ }
13786
+ return lines.join("\n");
13787
+ }
13700
13788
  async function streamLayerPrefillWithFallback(options, onChunk) {
13701
13789
  const { action = "prefill", targetLayer = 1, accumulatedData = {}, apiKeys = {} } = options;
13702
13790
  if (action === "research") {
@@ -13707,26 +13795,21 @@ async function streamLayerPrefillWithFallback(options, onChunk) {
13707
13795
  projectName = "Curriculum Course",
13708
13796
  projectCode = "curriculum-course",
13709
13797
  courseDescription = "",
13710
- domain = "tech",
13711
- context = "center",
13712
- contextLabel = "STEM Academy",
13713
13798
  targetAgeTier = "secondary",
13714
13799
  targetAge = "Secondary (Ages 11-15 / Grades 6-9)",
13715
13800
  courseDurationTier = "standard",
13716
13801
  courseDuration = "Standard Course (16-24 sessions / 24-36 hours)",
13717
- hardwareReadinessTier = "none_budget",
13718
- hardwareReadiness = "No dedicated hardware (Plug-and-play simulator or economical kit)",
13719
13802
  primaryGoal = "",
13720
13803
  exitVision = "",
13721
13804
  valueAndCertification = "",
13722
- coreLearningOutcomes = "",
13805
+ entryBridge = "",
13723
13806
  pedagogy = "5E Instructional Model",
13724
- capstoneTheme = "",
13725
- teacherProfile = "Facilitator / STEM Teacher",
13726
- instructionalModality = "Teacher-led step-by-step",
13727
- cognitiveDepth = "Apply",
13728
- budgetOrExistingSpecs = "",
13729
- cognitiveScaffolding = ""
13807
+ lessonPacingModel = "",
13808
+ voiceAndTone = "",
13809
+ classDynamic = "",
13810
+ hardwareDeployment = "",
13811
+ teacherRole = "",
13812
+ cognitiveDepth = "Apply"
13730
13813
  } = accumulatedData;
13731
13814
  const language = typeof options.language === "string" && options.language ? options.language : typeof accumulatedData.language === "string" && accumulatedData.language ? accumulatedData.language : "vi";
13732
13815
  const textLangMandate = language === "vi" ? "natural, fluent Vietnamese (Ti\u1EBFng Vi\u1EC7t)" : language === "en" ? "natural, fluent English" : `natural, fluent ${language}`;
@@ -13762,7 +13845,7 @@ ${targetLayer === 1 ? `
13762
13845
  - Field 2 (id: 'brandingAndTone', type: 'single_choice'): Art Direction & Visual Mood (ART_DIRECTION: Future Maker Lab high-contrast vs Clean Minimalist Studio vs Gamified Playful).
13763
13846
  - Field 3 (id: 'differentiationStrategy', type: 'single_choice'): Differentiation & Scaffolding Strategy (CONTENT_STYLE_GUIDE: Tier 1/2/3 Bronze/Silver/Gold + EXT Challenges vs Open-ended Rubric Studio).
13764
13847
  - Field 4 (id: 'cognitiveScaffolding', type: 'textarea'): Concrete scaffolding methods (Starter Code, color-coded breadboards, 4-step debug poster, simulator preview).
13765
- - Field 5 (id: 'recommendedToolchain', type: 'single_choice'): 3 concrete toolchain/IDE options matching '${hardwareReadiness}'.
13848
+ - Field 5 (id: 'recommendedToolchain', type: 'single_choice'): 3 concrete toolchain/IDE options matching the confirmed Hardware & Tool Readiness from FULL ACCUMULATED CONTEXT.
13766
13849
  - Field 6 (id: 'artifactScope', type: 'multi_choice'): Deliverable artifacts (LESSON mandatory + ACT, QUIZ, SLIDE, WKS, HANDOUT, GUIDE, CODE).
13767
13850
  - Field 7 (id: 'assessmentStrategy', type: 'single_choice'): Grading weights (e.g. 40% Formative Process + 60% Capstone Rubric).
13768
13851
  `}
@@ -13792,10 +13875,8 @@ RETURN STRICT VALID JSON ONLY adhering exactly to this format:
13792
13875
  - Course Title: "${projectName}"
13793
13876
  - Project Identifier: "${projectCode}"
13794
13877
  - Idea Description: "${courseDescription}"
13795
- - Target Age & Audience: ${targetAge} (Tier: ${targetAgeTier})
13796
- - Planned Course Duration: ${courseDuration} (Tier: ${courseDurationTier})
13797
- - Hardware & Tool Readiness: ${hardwareReadiness} (Tier: ${hardwareReadinessTier})
13798
- - Educational Context: ${contextLabel || context} | Domain: ${domain}
13878
+ - Target Age & Audience: ${targetAge}
13879
+ - Planned Course Duration: ${courseDuration}
13799
13880
 
13800
13881
  ${targetLayer >= 2 ? `
13801
13882
  APPROVED PARAMETERS FROM LAYER 1 (STRATEGIC INTENT & OUTCOMES):
@@ -13803,17 +13884,23 @@ APPROVED PARAMETERS FROM LAYER 1 (STRATEGIC INTENT & OUTCOMES):
13803
13884
  - Competency Exit Vision: "${exitVision}"
13804
13885
  - Target Cognitive Depth: "${cognitiveDepth}"
13805
13886
  - Value & Recognition: "${valueAndCertification}"
13887
+ - Prerequisite Entry Bridge: "${entryBridge}"
13806
13888
  ` : ""}
13807
13889
 
13808
13890
  ${targetLayer >= 3 ? `
13809
13891
  APPROVED PARAMETERS FROM LAYER 2 (PEDAGOGY & LOGISTICS):
13810
- - Selected Pedagogy Model: ${pedagogy}
13811
- - Class Dynamics / Hardware Ratio: ${accumulatedData.classDynamic || "1:1 Individual"}
13812
- - Lab Deployment Mode: ${accumulatedData.hardwareDeployment || "Lab provided"}
13813
- - Instructional Role: ${accumulatedData.teacherRole || "Teacher-led"}
13892
+ - Lesson Pacing Model: "${lessonPacingModel}"
13893
+ - Selected Pedagogy Model: "${pedagogy}"
13894
+ - Voice & Tone: "${voiceAndTone}"
13895
+ - Class Dynamics / Hardware Ratio: "${classDynamic}"
13896
+ - Lab Deployment Mode: "${hardwareDeployment}"
13897
+ - Instructional Role: "${teacherRole}"
13814
13898
  ` : ""}
13815
13899
 
13816
- REQUIREMENT: Synthesize all parameters above and produce the high-fidelity pre-fill configuration for LAYER ${targetLayer}.`;
13900
+ FULL ACCUMULATED CONTEXT (ALL CONFIRMED PARAMETERS FROM EVERY PREVIOUS STEP \u2014 user decisions, organization/instructor/template profiles, schedules and constraints; every line below MUST be honored in your output):
13901
+ ${buildRemainingContextBlock(accumulatedData) || "(no additional context)"}
13902
+
13903
+ REQUIREMENT: Synthesize ALL parameters above \u2014 every single line in FULL ACCUMULATED CONTEXT is a confirmed user decision or system constraint and MUST influence the generated configuration. Produce the high-fidelity pre-fill configuration for LAYER ${targetLayer}.`;
13817
13904
  let rawContent = "";
13818
13905
  await streamLLMWithFallback(
13819
13906
  systemPrompt,
@@ -13822,7 +13909,9 @@ REQUIREMENT: Synthesize all parameters above and produce the high-fidelity pre-f
13822
13909
  (chunk, type) => {
13823
13910
  if (type === "content") rawContent += chunk;
13824
13911
  onChunk?.(chunk, type);
13825
- }
13912
+ },
13913
+ options.model,
13914
+ options.provider
13826
13915
  );
13827
13916
  const parsed = safeParseJson(rawContent);
13828
13917
  return {