@thanh01.pmt/curriculum-kit 1.0.14 → 1.0.16

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.
@@ -15,6 +15,10 @@ interface StreamRunnerOptions {
15
15
  maxTokens?: number;
16
16
  temperature?: number;
17
17
  reasoningEffort?: 'off' | 'low' | 'medium' | 'high' | 'max';
18
+ /** Thời gian tối đa cho phép không nhận thêm token mới trước khi abort (mặc định 45_000ms = 45s) */
19
+ idleTimeoutMs?: number;
20
+ /** Thời gian hard cap tối đa cho toàn bộ lượt stream trước khi abort (mặc định 420_000ms = 7 phút) */
21
+ timeoutMs?: number;
18
22
  /**
19
23
  * Host-provided inference override (Vercel AI SDK gateway, test stubs, …).
20
24
  * When set, ALL built-in provider routes (raw-fetch NVIDIA/OpenRouter/
@@ -67,6 +71,31 @@ declare function extractThoughtAndContent(rawText: string): {
67
71
  thought: string;
68
72
  content: string;
69
73
  };
74
+ declare const DEFAULT_ARTIFACT_STREAM_IDLE_MS = 45000;
75
+ declare const DEFAULT_ARTIFACT_STREAM_TOTAL_MS = 420000;
76
+ interface StreamAbortController {
77
+ signal: AbortSignal;
78
+ /** Reset the idle window (call on every received chunk). */
79
+ kick: () => void;
80
+ /** Clear all pending timers once the request lifecycle is over. */
81
+ dispose: () => void;
82
+ }
83
+ /**
84
+ * Budget-aware stream abort controller. Combines an idle window (kicked on
85
+ * every received chunk — covers slow TTFT and hung connections) with a generous
86
+ * total hard cap (defaults to 420s / 7 minutes).
87
+ *
88
+ * Configurable via options or process.env:
89
+ * - ARTIFACT_STREAM_IDLE_TIMEOUT_MS / STREAM_IDLE_TIMEOUT_MS
90
+ * - ARTIFACT_STREAM_TOTAL_TIMEOUT_MS / STREAM_TOTAL_TIMEOUT_MS
91
+ */
92
+ declare function createStreamAbortController(options?: {
93
+ idleMs?: number;
94
+ totalMs?: number;
95
+ }): StreamAbortController;
96
+ /** Backward-compatible alias */
97
+ declare function createIdleAbortController(idleMs?: number, totalMs?: number): StreamAbortController;
98
+ type IdleAbort = StreamAbortController;
70
99
  /**
71
100
  * Real-time Streaming Multi-Provider Inference Runner with Thinking & Tool Streaming
72
101
  */
@@ -76,4 +105,4 @@ declare function streamCurriculumAIInference(messages: ChatMessage[], projectCon
76
105
  */
77
106
  declare function runCurriculumAIInference(messages: ChatMessage[], projectContext: string, options?: StreamRunnerOptions, onChunk?: (token: string, type: ChunkType) => void): Promise<string>;
78
107
 
79
- export { type ChatMessage as C, type FallbackTarget as F, type StreamRunnerOptions as S, isModelAllowed as a, getDesignatedFallbackConfig as b, type ChunkType as c, extractThoughtAndContent as e, getDesignatedFallbackChain as g, isProviderEnabled as i, runCurriculumAIInference as r, streamCurriculumAIInference as s };
108
+ export { type ChatMessage as C, DEFAULT_ARTIFACT_STREAM_IDLE_MS as D, type FallbackTarget as F, type IdleAbort as I, type StreamRunnerOptions as S, isModelAllowed as a, getDesignatedFallbackConfig as b, type ChunkType as c, DEFAULT_ARTIFACT_STREAM_TOTAL_MS as d, extractThoughtAndContent as e, type StreamAbortController as f, getDesignatedFallbackChain as g, createStreamAbortController as h, isProviderEnabled as i, createIdleAbortController as j, runCurriculumAIInference as r, streamCurriculumAIInference as s };
@@ -15,6 +15,10 @@ interface StreamRunnerOptions {
15
15
  maxTokens?: number;
16
16
  temperature?: number;
17
17
  reasoningEffort?: 'off' | 'low' | 'medium' | 'high' | 'max';
18
+ /** Thời gian tối đa cho phép không nhận thêm token mới trước khi abort (mặc định 45_000ms = 45s) */
19
+ idleTimeoutMs?: number;
20
+ /** Thời gian hard cap tối đa cho toàn bộ lượt stream trước khi abort (mặc định 420_000ms = 7 phút) */
21
+ timeoutMs?: number;
18
22
  /**
19
23
  * Host-provided inference override (Vercel AI SDK gateway, test stubs, …).
20
24
  * When set, ALL built-in provider routes (raw-fetch NVIDIA/OpenRouter/
@@ -67,6 +71,31 @@ declare function extractThoughtAndContent(rawText: string): {
67
71
  thought: string;
68
72
  content: string;
69
73
  };
74
+ declare const DEFAULT_ARTIFACT_STREAM_IDLE_MS = 45000;
75
+ declare const DEFAULT_ARTIFACT_STREAM_TOTAL_MS = 420000;
76
+ interface StreamAbortController {
77
+ signal: AbortSignal;
78
+ /** Reset the idle window (call on every received chunk). */
79
+ kick: () => void;
80
+ /** Clear all pending timers once the request lifecycle is over. */
81
+ dispose: () => void;
82
+ }
83
+ /**
84
+ * Budget-aware stream abort controller. Combines an idle window (kicked on
85
+ * every received chunk — covers slow TTFT and hung connections) with a generous
86
+ * total hard cap (defaults to 420s / 7 minutes).
87
+ *
88
+ * Configurable via options or process.env:
89
+ * - ARTIFACT_STREAM_IDLE_TIMEOUT_MS / STREAM_IDLE_TIMEOUT_MS
90
+ * - ARTIFACT_STREAM_TOTAL_TIMEOUT_MS / STREAM_TOTAL_TIMEOUT_MS
91
+ */
92
+ declare function createStreamAbortController(options?: {
93
+ idleMs?: number;
94
+ totalMs?: number;
95
+ }): StreamAbortController;
96
+ /** Backward-compatible alias */
97
+ declare function createIdleAbortController(idleMs?: number, totalMs?: number): StreamAbortController;
98
+ type IdleAbort = StreamAbortController;
70
99
  /**
71
100
  * Real-time Streaming Multi-Provider Inference Runner with Thinking & Tool Streaming
72
101
  */
@@ -76,4 +105,4 @@ declare function streamCurriculumAIInference(messages: ChatMessage[], projectCon
76
105
  */
77
106
  declare function runCurriculumAIInference(messages: ChatMessage[], projectContext: string, options?: StreamRunnerOptions, onChunk?: (token: string, type: ChunkType) => void): Promise<string>;
78
107
 
79
- export { type ChatMessage as C, type FallbackTarget as F, type StreamRunnerOptions as S, isModelAllowed as a, getDesignatedFallbackConfig as b, type ChunkType as c, extractThoughtAndContent as e, getDesignatedFallbackChain as g, isProviderEnabled as i, runCurriculumAIInference as r, streamCurriculumAIInference as s };
108
+ export { type ChatMessage as C, DEFAULT_ARTIFACT_STREAM_IDLE_MS as D, type FallbackTarget as F, type IdleAbort as I, type StreamRunnerOptions as S, isModelAllowed as a, getDesignatedFallbackConfig as b, type ChunkType as c, DEFAULT_ARTIFACT_STREAM_TOTAL_MS as d, extractThoughtAndContent as e, type StreamAbortController as f, getDesignatedFallbackChain as g, createStreamAbortController as h, isProviderEnabled as i, createIdleAbortController as j, runCurriculumAIInference as r, streamCurriculumAIInference as s };
@@ -271,26 +271,38 @@ function extractThoughtAndContent(rawText) {
271
271
  content: content.trim()
272
272
  };
273
273
  }
274
- function createIdleAbortController(idleMs) {
274
+ function createStreamAbortController(options) {
275
+ const envIdle = Number(process.env.ARTIFACT_STREAM_IDLE_TIMEOUT_MS || process.env.STREAM_IDLE_TIMEOUT_MS);
276
+ const envTotal = Number(process.env.ARTIFACT_STREAM_TOTAL_TIMEOUT_MS || process.env.STREAM_TOTAL_TIMEOUT_MS);
277
+ const idleMs = Number.isFinite(options?.idleMs) && options.idleMs > 0 ? options.idleMs : Number.isFinite(envIdle) && envIdle > 0 ? envIdle : DEFAULT_ARTIFACT_STREAM_IDLE_MS;
278
+ const totalMs = Number.isFinite(options?.totalMs) && options.totalMs > 0 ? options.totalMs : Number.isFinite(envTotal) && envTotal > 0 ? envTotal : DEFAULT_ARTIFACT_STREAM_TOTAL_MS;
275
279
  const controller = new AbortController();
276
- let timer = null;
277
- const arm = () => {
278
- if (timer) clearTimeout(timer);
279
- timer = setTimeout(
280
+ let idleTimer = null;
281
+ let totalTimer = null;
282
+ const armIdle = () => {
283
+ if (idleTimer) clearTimeout(idleTimer);
284
+ idleTimer = setTimeout(
280
285
  () => controller.abort(new Error(`Idle timeout: no data for ${Math.round(idleMs / 1e3)}s`)),
281
286
  idleMs
282
287
  );
283
- timer?.unref?.();
288
+ idleTimer?.unref?.();
284
289
  };
285
- arm();
290
+ armIdle();
291
+ if (totalMs > 0) {
292
+ totalTimer = setTimeout(
293
+ () => controller.abort(new Error(`Total stream timeout after ${Math.round(totalMs / 1e3)}s`)),
294
+ totalMs
295
+ );
296
+ totalTimer?.unref?.();
297
+ }
286
298
  return {
287
299
  signal: controller.signal,
288
- /** Reset the idle window (call on every received chunk). */
289
- kick: arm,
290
- /** Clear the pending timer once the request lifecycle is over. */
300
+ kick: armIdle,
291
301
  dispose: () => {
292
- if (timer) clearTimeout(timer);
293
- timer = null;
302
+ if (idleTimer) clearTimeout(idleTimer);
303
+ if (totalTimer) clearTimeout(totalTimer);
304
+ idleTimer = null;
305
+ totalTimer = null;
294
306
  }
295
307
  };
296
308
  }
@@ -477,7 +489,7 @@ Guidelines:
477
489
  if (isNvidiaPreferred || uniqueNvidiaModels.length > 0) {
478
490
  for (const model of uniqueNvidiaModels) {
479
491
  try {
480
- const idle = createIdleAbortController(18e4);
492
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
481
493
  const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
482
494
  method: "POST",
483
495
  headers: {
@@ -521,7 +533,7 @@ Guidelines:
521
533
  const uniqueOrModels = Array.from(new Set(openrouterModels));
522
534
  for (const model of uniqueOrModels) {
523
535
  try {
524
- const idle = createIdleAbortController(18e4);
536
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
525
537
  const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
526
538
  method: "POST",
527
539
  headers: {
@@ -560,7 +572,7 @@ Guidelines:
560
572
  const dsModel = options.model?.includes("deepseek-reasoner") ? "deepseek-reasoner" : "deepseek-chat";
561
573
  if (isModelAllowed(dsModel)) {
562
574
  try {
563
- const idle = createIdleAbortController(18e4);
575
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
564
576
  const res = await fetch("https://api.deepseek.com/chat/completions", {
565
577
  method: "POST",
566
578
  headers: {
@@ -595,7 +607,7 @@ Guidelines:
595
607
  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";
596
608
  for (const qwenModel of qwenModels) {
597
609
  try {
598
- const idle = createIdleAbortController(18e4);
610
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
599
611
  const payload = {
600
612
  model: qwenModel,
601
613
  messages: formattedMessages,
@@ -638,7 +650,7 @@ Guidelines:
638
650
  ];
639
651
  for (const gModel of geminiModels) {
640
652
  try {
641
- const idle = createIdleAbortController(18e4);
653
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
642
654
  const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${gModel}:streamGenerateContent?alt=sse&key=${geminiKey}`, {
643
655
  method: "POST",
644
656
  headers: { "Content-Type": "application/json" },
@@ -674,7 +686,7 @@ Guidelines:
674
686
  const { provider, model } = target;
675
687
  if (provider === "nvidia" && nvidiaKey && isProviderEnabled("nvidia")) {
676
688
  try {
677
- const idle = createIdleAbortController(18e4);
689
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
678
690
  const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
679
691
  method: "POST",
680
692
  headers: {
@@ -706,7 +718,7 @@ Guidelines:
706
718
  }
707
719
  } else if (provider === "openrouter" && openrouterKey && isProviderEnabled("openrouter")) {
708
720
  try {
709
- const idle = createIdleAbortController(18e4);
721
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
710
722
  const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
711
723
  method: "POST",
712
724
  headers: {
@@ -741,7 +753,7 @@ Guidelines:
741
753
  } else if ((provider === "alibaba" || provider === "dashscope") && alibabaKey && isProviderEnabled("alibaba")) {
742
754
  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";
743
755
  try {
744
- const idle = createIdleAbortController(18e4);
756
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
745
757
  const res = await fetch(baseUrl, {
746
758
  method: "POST",
747
759
  headers: {
@@ -773,7 +785,7 @@ Guidelines:
773
785
  }
774
786
  } else if ((provider === "google" || provider === "gemini") && geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
775
787
  try {
776
- const idle = createIdleAbortController(18e4);
788
+ const idle = createStreamAbortController({ idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs });
777
789
  const res = await fetch(
778
790
  `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${geminiKey}`,
779
791
  {
@@ -821,9 +833,12 @@ Guidelines:
821
833
  rawError: providerErrors
822
834
  });
823
835
  }
836
+ var DEFAULT_ARTIFACT_STREAM_IDLE_MS, DEFAULT_ARTIFACT_STREAM_TOTAL_MS;
824
837
  var init_streamRunner = __esm({
825
838
  "src/ai/streamRunner.ts"() {
826
839
  init_errors();
840
+ DEFAULT_ARTIFACT_STREAM_IDLE_MS = 45e3;
841
+ DEFAULT_ARTIFACT_STREAM_TOTAL_MS = 42e4;
827
842
  }
828
843
  });
829
844