@thanh01.pmt/curriculum-kit 1.4.9 → 1.4.11

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.d.cts CHANGED
@@ -365,6 +365,8 @@ interface LayerPrefillOptions {
365
365
  timeoutMs?: number;
366
366
  /** Idle (no-data) budget override for this prefill run (ms). */
367
367
  idleTimeoutMs?: number;
368
+ /** Optional parent abort signal (e.g. from HTTP request) to stop immediately on client disconnect. */
369
+ signal?: AbortSignal;
368
370
  }
369
371
  interface PreliminaryResearchResult {
370
372
  groundTruthSummary: string;
@@ -388,7 +390,7 @@ interface StreamBudget {
388
390
  /** Abort when the whole stream exceeds this total wall time (ms). 0 disables. */
389
391
  totalMs?: number;
390
392
  }
391
- declare const DEFAULT_STREAM_IDLE_MS = 90000;
393
+ declare const DEFAULT_STREAM_IDLE_MS = 180000;
392
394
  declare const DEFAULT_STREAM_TOTAL_MS = 480000;
393
395
  declare const LAYER_IDLE_BUDGET_MS: Record<number, number>;
394
396
  /**
@@ -455,7 +457,7 @@ declare function createStreamChunkExtractor(): StreamChunkExtractor;
455
457
  * createStreamChunkExtractor, whose latch is the authoritative dedupe.
456
458
  */
457
459
  declare function extractStreamChunk(part: any): ExtractedStreamChunk;
458
- declare function createStreamAbortSignal(budget: Required<StreamBudget>): StreamAbortHandle;
460
+ declare function createStreamAbortSignal(budget: Required<StreamBudget>, parentSignal?: AbortSignal): StreamAbortHandle;
459
461
  /**
460
462
  * Close an unterminated JSON object/array: append the delimiters that are
461
463
  * still open, closing any open string first. Handles streams aborted
@@ -485,7 +487,7 @@ declare function safeParseJson<T = any>(rawText: string | null): T | null;
485
487
  /**
486
488
  * Multi-tiered resilient streaming inference helper using 100% Vercel AI SDK.
487
489
  */
488
- declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget): Promise<string | null>;
490
+ declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget, signal?: AbortSignal): Promise<string | null>;
489
491
  /**
490
492
  * Conducts preliminary ecosystem research for a course project.
491
493
  */
package/dist/index.d.ts CHANGED
@@ -365,6 +365,8 @@ interface LayerPrefillOptions {
365
365
  timeoutMs?: number;
366
366
  /** Idle (no-data) budget override for this prefill run (ms). */
367
367
  idleTimeoutMs?: number;
368
+ /** Optional parent abort signal (e.g. from HTTP request) to stop immediately on client disconnect. */
369
+ signal?: AbortSignal;
368
370
  }
369
371
  interface PreliminaryResearchResult {
370
372
  groundTruthSummary: string;
@@ -388,7 +390,7 @@ interface StreamBudget {
388
390
  /** Abort when the whole stream exceeds this total wall time (ms). 0 disables. */
389
391
  totalMs?: number;
390
392
  }
391
- declare const DEFAULT_STREAM_IDLE_MS = 90000;
393
+ declare const DEFAULT_STREAM_IDLE_MS = 180000;
392
394
  declare const DEFAULT_STREAM_TOTAL_MS = 480000;
393
395
  declare const LAYER_IDLE_BUDGET_MS: Record<number, number>;
394
396
  /**
@@ -455,7 +457,7 @@ declare function createStreamChunkExtractor(): StreamChunkExtractor;
455
457
  * createStreamChunkExtractor, whose latch is the authoritative dedupe.
456
458
  */
457
459
  declare function extractStreamChunk(part: any): ExtractedStreamChunk;
458
- declare function createStreamAbortSignal(budget: Required<StreamBudget>): StreamAbortHandle;
460
+ declare function createStreamAbortSignal(budget: Required<StreamBudget>, parentSignal?: AbortSignal): StreamAbortHandle;
459
461
  /**
460
462
  * Close an unterminated JSON object/array: append the delimiters that are
461
463
  * still open, closing any open string first. Handles streams aborted
@@ -485,7 +487,7 @@ declare function safeParseJson<T = any>(rawText: string | null): T | null;
485
487
  /**
486
488
  * Multi-tiered resilient streaming inference helper using 100% Vercel AI SDK.
487
489
  */
488
- declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget): Promise<string | null>;
490
+ declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget, signal?: AbortSignal): Promise<string | null>;
489
491
  /**
490
492
  * Conducts preliminary ecosystem research for a course project.
491
493
  */
package/dist/index.mjs CHANGED
@@ -5448,6 +5448,11 @@ Every slide MUST include notes with:
5448
5448
  - Cold-Call: 1 check question
5449
5449
  - Scaffolding Tip: 1 analogy or hint
5450
5450
 
5451
+ ### \u26A0\uFE0F STRICT SLIDE FORMATTING INVARIANTS:
5452
+ 1. Every slide MUST store its content fields inside "slots": { ... }. NEVER put "title", "content", or points at the top level of a slide!
5453
+ 2. All code slots MUST contain real, executable, high-fidelity code. Never use placeholders like "<CODE>" or "...".
5454
+ 3. All point and card slots MUST contain concrete, rich pedagogical explanations. Never use placeholder dots or empty cards.
5455
+
5451
5456
  ### Output JSON Format:
5452
5457
  \`\`\`json
5453
5458
  {
@@ -25315,10 +25320,15 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
25315
25320
  const isHtmlEngine = options.slidesEngine === "html";
25316
25321
  if (isHtmlEngine) {
25317
25322
  let presentationKitAi = null;
25323
+ let presentationKitCore = null;
25318
25324
  try {
25319
25325
  presentationKitAi = await import('@thanh01.pmt/presentation-kit/ai');
25320
25326
  } catch {
25321
25327
  }
25328
+ try {
25329
+ presentationKitCore = await import('@thanh01.pmt/presentation-kit');
25330
+ } catch {
25331
+ }
25322
25332
  const slidePrompt = buildHtmlDeckSlidePrompt({
25323
25333
  lessonCode,
25324
25334
  lessonTitle,
@@ -25353,12 +25363,21 @@ ${glossaryContext}` : void 0,
25353
25363
  const candidate = fenceMatch ? fenceMatch[1] : str;
25354
25364
  return candidate.trim();
25355
25365
  };
25366
+ const normalizeDeckSlides = (deck) => {
25367
+ const normalizer = presentationKitCore?.normalizeSlideSlots || presentationKitAi?.normalizeSlideSlots;
25368
+ if (deck && Array.isArray(deck.slides) && normalizer) {
25369
+ deck.slides = deck.slides.map((s) => normalizer(s));
25370
+ }
25371
+ return deck;
25372
+ };
25356
25373
  try {
25357
25374
  deckJson = JSON.parse(extractCleanJson(rawSlide));
25375
+ deckJson = normalizeDeckSlides(deckJson);
25358
25376
  } catch {
25359
25377
  try {
25360
25378
  const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
25361
25379
  deckJson = JSON.parse(jsonrepair2(extractCleanJson(rawSlide)));
25380
+ deckJson = normalizeDeckSlides(deckJson);
25362
25381
  } catch {
25363
25382
  }
25364
25383
  }
@@ -25380,11 +25399,13 @@ Fix ALL issues and output the complete corrected JSON object strictly matching t
25380
25399
  );
25381
25400
  try {
25382
25401
  deckJson = JSON.parse(extractCleanJson(repairedRaw));
25402
+ deckJson = normalizeDeckSlides(deckJson);
25383
25403
  validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
25384
25404
  } catch {
25385
25405
  try {
25386
25406
  const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
25387
25407
  deckJson = JSON.parse(jsonrepair2(extractCleanJson(repairedRaw)));
25408
+ deckJson = normalizeDeckSlides(deckJson);
25388
25409
  validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
25389
25410
  } catch {
25390
25411
  }
@@ -25412,9 +25433,10 @@ ${JSON.stringify(deckJson || {}, null, 2)}
25412
25433
  const deckJsonStr = JSON.stringify(deckJson, null, 2);
25413
25434
  await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.deck.json`, deckJsonStr);
25414
25435
  producedArtifacts.push(`SLIDE_${lessonCode}.deck.json`);
25415
- if (presentationKitAi?.compileHtmlDeck) {
25436
+ const compiler = presentationKitCore?.compileHtmlDeck || presentationKitAi?.compileHtmlDeck;
25437
+ if (compiler) {
25416
25438
  try {
25417
- const compiled = presentationKitAi.compileHtmlDeck(deckJson);
25439
+ const compiled = compiler(deckJson);
25418
25440
  if (compiled?.html) {
25419
25441
  await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.html`, compiled.html);
25420
25442
  producedArtifacts.push(`SLIDE_${lessonCode}.html`);
@@ -27291,13 +27313,13 @@ var DeterministicPipelineRunner = class {
27291
27313
 
27292
27314
  // src/services/prefillService.ts
27293
27315
  init_streamRunner();
27294
- var DEFAULT_STREAM_IDLE_MS = 9e4;
27316
+ var DEFAULT_STREAM_IDLE_MS = 18e4;
27295
27317
  var DEFAULT_STREAM_TOTAL_MS = 48e4;
27296
27318
  var LAYER_IDLE_BUDGET_MS = {
27297
- 1: 9e4,
27298
- 2: 9e4,
27319
+ 1: 18e4,
27320
+ 2: 18e4,
27299
27321
  3: 18e4
27300
- // Layer 3 synthesizes all accumulated context + pathways (3 minutes idle protection)
27322
+ // 3 minutes idle protection across all layers for free-tier reasoning models
27301
27323
  };
27302
27324
  var LAYER_TOTAL_BUDGET_MS = {
27303
27325
  1: 48e4,
@@ -27358,10 +27380,20 @@ function extractStreamChunk(part) {
27358
27380
  }
27359
27381
  return {};
27360
27382
  }
27361
- function createStreamAbortSignal(budget) {
27383
+ function createStreamAbortSignal(budget, parentSignal) {
27362
27384
  const controller = new AbortController();
27363
27385
  let idleTimer = null;
27364
27386
  let totalTimer = null;
27387
+ const onParentAbort = () => {
27388
+ controller.abort(parentSignal?.reason || new Error("Aborted by parent signal"));
27389
+ };
27390
+ if (parentSignal) {
27391
+ if (parentSignal.aborted) {
27392
+ controller.abort(parentSignal.reason || new Error("Parent signal already aborted"));
27393
+ } else {
27394
+ parentSignal.addEventListener("abort", onParentAbort, { once: true });
27395
+ }
27396
+ }
27365
27397
  const armIdle = () => {
27366
27398
  if (idleTimer) clearTimeout(idleTimer);
27367
27399
  if (budget.idleMs > 0) {
@@ -27388,6 +27420,9 @@ function createStreamAbortSignal(budget) {
27388
27420
  dispose: () => {
27389
27421
  if (idleTimer) clearTimeout(idleTimer);
27390
27422
  if (totalTimer) clearTimeout(totalTimer);
27423
+ if (parentSignal) {
27424
+ parentSignal.removeEventListener("abort", onParentAbort);
27425
+ }
27391
27426
  idleTimer = null;
27392
27427
  totalTimer = null;
27393
27428
  }
@@ -27499,7 +27534,7 @@ function safeParseJson(rawText) {
27499
27534
  }
27500
27535
  return null;
27501
27536
  }
27502
- async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget) {
27537
+ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget, signal) {
27503
27538
  const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
27504
27539
  const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
27505
27540
  const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
@@ -27531,7 +27566,6 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
27531
27566
  const nemotronModels = [
27532
27567
  "nvidia/nemotron-3-ultra-550b-a55b",
27533
27568
  "nvidia/nemotron-3.5-lightning-30b-a3b",
27534
- "nvidia/nemotron-3-super-120b-a12b",
27535
27569
  "nvidia/llama-3.1-nemotron-70b-instruct"
27536
27570
  ].filter((m) => isModelAllowed(m));
27537
27571
  for (const m of nemotronModels) {
@@ -27582,8 +27616,12 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
27582
27616
  totalMs: budget?.totalMs ?? DEFAULT_STREAM_TOTAL_MS
27583
27617
  };
27584
27618
  for (const candidate of candidates) {
27619
+ if (signal?.aborted) {
27620
+ console.log(`[curriculum-kit:VercelAI] AbortSignal already aborted, cancelling candidate loop.`);
27621
+ break;
27622
+ }
27585
27623
  const t0 = Date.now();
27586
- const abort = createStreamAbortSignal(resolvedBudget);
27624
+ const abort = createStreamAbortSignal(resolvedBudget, signal);
27587
27625
  try {
27588
27626
  const modelInstance = getAIModel({
27589
27627
  provider: candidate.provider,
@@ -27615,6 +27653,13 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
27615
27653
  }
27616
27654
  } catch (e) {
27617
27655
  console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
27656
+ const isClientAborted = Boolean(
27657
+ signal?.aborted || e?.name === "AbortError" && signal?.aborted || e?.message && (e.message.includes("Controller is already closed") || e.message.includes("The operation was aborted") || e.message.includes("Aborted by parent signal"))
27658
+ );
27659
+ if (isClientAborted) {
27660
+ console.log(`[curriculum-kit:VercelAI] Client aborted or stream closed, halting candidate fallback loop.`);
27661
+ break;
27662
+ }
27618
27663
  } finally {
27619
27664
  abort.dispose();
27620
27665
  }
@@ -27666,7 +27711,8 @@ Return concise JSON matching:
27666
27711
  totalMs: options.timeoutMs,
27667
27712
  model: options.model,
27668
27713
  provider: options.provider
27669
- })
27714
+ }),
27715
+ options.signal
27670
27716
  );
27671
27717
  let parsedResearch = {
27672
27718
  groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
@@ -27876,7 +27922,8 @@ REQUIREMENT: Synthesize ALL parameters above \u2014 every single line in FULL AC
27876
27922
  totalMs: options.timeoutMs,
27877
27923
  model: options.model,
27878
27924
  provider: options.provider
27879
- })
27925
+ }),
27926
+ options.signal
27880
27927
  );
27881
27928
  const parsed = safeParseJson(rawContent);
27882
27929
  return {