@yeaft/webchat-agent 1.0.578 → 1.0.580

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/yeaft/models.js CHANGED
@@ -696,6 +696,9 @@ export function normalizeProviderModels(provider) {
696
696
  if (entry && typeof entry === 'object' && typeof entry.id === 'string' && entry.id.trim()) {
697
697
  const norm = { id: entry.id.trim() };
698
698
  if (entry.capabilities && typeof entry.capabilities === 'object') norm.capabilities = { ...entry.capabilities };
699
+ if (Number.isFinite(entry.streamIdleTimeoutMs) && entry.streamIdleTimeoutMs >= 0) {
700
+ norm.streamIdleTimeoutMs = Math.min(600_000, Math.floor(entry.streamIdleTimeoutMs));
701
+ }
699
702
  const ctx = coercePositiveInt(entry.contextWindow);
700
703
  const max = coercePositiveInt(entry.maxOutput);
701
704
  if (ctx !== undefined) norm.contextWindow = ctx;
@@ -734,9 +737,12 @@ export function serializeModelForPersistence(entry) {
734
737
  ? entry.protocol.trim()
735
738
  : undefined;
736
739
  const capabilities = entry.capabilities && typeof entry.capabilities === 'object' ? { ...entry.capabilities } : undefined;
737
- if (ctx === undefined && max === undefined && proto === undefined && capabilities === undefined) return entry.id;
740
+ const streamIdleTimeoutMs = Number.isFinite(entry.streamIdleTimeoutMs) && entry.streamIdleTimeoutMs >= 0
741
+ ? Math.min(600_000, Math.floor(entry.streamIdleTimeoutMs)) : undefined;
742
+ if (ctx === undefined && max === undefined && proto === undefined && capabilities === undefined && streamIdleTimeoutMs === undefined) return entry.id;
738
743
  const obj = { id: entry.id };
739
744
  if (capabilities !== undefined) obj.capabilities = capabilities;
745
+ if (streamIdleTimeoutMs !== undefined) obj.streamIdleTimeoutMs = streamIdleTimeoutMs;
740
746
  if (ctx !== undefined) obj.contextWindow = ctx;
741
747
  if (max !== undefined) obj.maxOutput = max;
742
748
  if (proto !== undefined) obj.protocol = proto;
@@ -1194,19 +1194,35 @@ function createQueryWatchdog({ signal, timeoutMs, onTimeout }) {
1194
1194
  }
1195
1195
  timer = setTimeout(() => {
1196
1196
  stop();
1197
- onTimeout();
1197
+ onTimeout(timeoutMs);
1198
1198
  }, timeoutMs);
1199
1199
  };
1200
+ const setTimeoutMs = (next) => {
1201
+ if (stopped || signal.aborted || !Number.isFinite(next) || next <= 0) return;
1202
+ const armed = !!timer;
1203
+ pause();
1204
+ timeoutMs = next;
1205
+ if (armed) reset();
1206
+ };
1200
1207
  // No throttling or trailing heartbeat: the actual last delta owns idle time.
1201
1208
  const touch = () => { if (timer) reset(); };
1202
1209
  if (!stopped) signal.addEventListener('abort', stop, { once: true });
1203
- return { reset, pause, touch, stop };
1210
+ return { reset, pause, touch, stop, setTimeoutMs };
1204
1211
  }
1205
1212
 
1206
1213
  function queryTimeoutMsForSessionConfig(config = null) {
1207
1214
  return isHighReasoningEffort(config?.modelEffort) ? HIGH_REASONING_QUERY_TIMEOUT_MS : QUERY_TIMEOUT_MS;
1208
1215
  }
1209
1216
 
1217
+ // Request effort can differ from Session settings (prefix, auto, child ceiling,
1218
+ // fallback). Leave transport retries 30s of headroom even for explicit budgets.
1219
+ // Disabling the transport guard does not disable this independent watchdog.
1220
+ function queryTimeoutMsForProviderRequest({ effort, streamIdleTimeoutMs } = {}) {
1221
+ const fallback = queryTimeoutMsForSessionConfig({ modelEffort: effort });
1222
+ return Number.isFinite(streamIdleTimeoutMs) && streamIdleTimeoutMs > 0
1223
+ ? Math.max(fallback, Math.min(600_000, streamIdleTimeoutMs) + 30_000) : fallback;
1224
+ }
1225
+
1210
1226
  function queryTimeoutMsForSession(sessionId = null) {
1211
1227
  if (!sessionId || !session) return queryTimeoutMsForSessionConfig(session?.config);
1212
1228
  try {
@@ -5557,8 +5573,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
5557
5573
  const watchdog = createQueryWatchdog({
5558
5574
  signal: vpAbort.signal,
5559
5575
  timeoutMs: queryTimeoutMs,
5560
- onTimeout: () => {
5561
- console.error(`[Yeaft] query timeout after ${queryTimeoutMs / 1000}s of silence — aborting VP ${vpId}`);
5576
+ onTimeout: timeoutMs => {
5577
+ console.error(`[Yeaft] query timeout after ${timeoutMs / 1000}s of silence — aborting VP ${vpId}`);
5562
5578
  try { vpAbort.abort(); } catch { /* best-effort */ }
5563
5579
  },
5564
5580
  });
@@ -5728,6 +5744,9 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
5728
5744
  return thread.pendingQueries.splice(0).map(item => ({ ...item, persisted: true }));
5729
5745
  },
5730
5746
  ...queryOpts,
5747
+ onProviderRequestStart: policy => {
5748
+ if (policy) watchdog.setTimeoutMs(queryTimeoutMsForProviderRequest(policy));
5749
+ },
5731
5750
  })) {
5732
5751
  // An escalated turn is detached from the per-VP driver. Its stale
5733
5752
  // promise may still resume if a provider/tool ignored AbortSignal;
@@ -8109,6 +8128,7 @@ export const __testHooks = {
8109
8128
  },
8110
8129
  createQueryWatchdog,
8111
8130
  queryTimeoutMsForSessionConfig,
8131
+ queryTimeoutMsForProviderRequest,
8112
8132
  queryTimeoutMsForSession,
8113
8133
  seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
8114
8134
  const key = threadKey(sessionId, vpId, threadId);