@jsonstudio/llms 0.6.3539 → 0.6.3551

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,6 +1,7 @@
1
1
  const COOLDOWN_SCHEDULE_429_MS = [3_000, 10_000, 31_000, 61_000];
2
2
  const COOLDOWN_SCHEDULE_FATAL_MS = [3_000, 10_000, 31_000, 61_000];
3
3
  const COOLDOWN_SCHEDULE_DEFAULT_MS = [3_000, 10_000, 31_000, 61_000];
4
+ const COOLDOWN_SCHEDULE_TRANSIENT_KEEP_POOL_MS = [3_000, 5_000, 10_000, 31_000];
4
5
  const ERROR_CHAIN_WINDOW_MS = 10 * 60_000;
5
6
  const NETWORK_ERROR_CODES = [
6
7
  'ECONNRESET',
@@ -84,10 +85,23 @@ function computeCooldownMsBySeries(series, consecutive) {
84
85
  const idx = Math.min(consecutive - 1, schedule.length - 1);
85
86
  return schedule[idx] ?? null;
86
87
  }
88
+ function shouldKeepProviderInPoolDuringCooldown(series, consecutive) {
89
+ if (consecutive <= 0) {
90
+ return false;
91
+ }
92
+ return (series === 'ENET' || series === 'E5XX' || series === 'EOTHER') && consecutive <= 2;
93
+ }
94
+ function computeTransientKeepPoolCooldownMs(series, consecutive) {
95
+ if (!shouldKeepProviderInPoolDuringCooldown(series, consecutive)) {
96
+ return null;
97
+ }
98
+ const idx = Math.min(consecutive - 1, COOLDOWN_SCHEDULE_TRANSIENT_KEEP_POOL_MS.length - 1);
99
+ return COOLDOWN_SCHEDULE_TRANSIENT_KEEP_POOL_MS[idx] ?? null;
100
+ }
87
101
  export function tickQuotaStateTime(state, nowMs) {
88
102
  let next = state;
89
103
  if (typeof next.cooldownUntil === 'number' && next.cooldownUntil <= nowMs) {
90
- next = { ...next, cooldownUntil: null };
104
+ next = { ...next, cooldownUntil: null, cooldownKeepsPool: undefined };
91
105
  }
92
106
  if (typeof next.blacklistUntil === 'number' && next.blacklistUntil <= nowMs) {
93
107
  next = { ...next, blacklistUntil: null };
@@ -107,14 +121,15 @@ export function tickQuotaStateTime(state, nowMs) {
107
121
  return next;
108
122
  }
109
123
  if (inCooldown) {
110
- if (next.inPool !== false || next.reason !== 'cooldown') {
111
- next = { ...next, inPool: false, reason: 'cooldown' };
124
+ const keepInPool = next.cooldownKeepsPool === true;
125
+ if (next.inPool !== keepInPool || next.reason !== 'cooldown') {
126
+ next = { ...next, inPool: keepInPool, reason: 'cooldown' };
112
127
  }
113
128
  return next;
114
129
  }
115
130
  // TTLs expired: only auto-reset "cooldown/blacklist" back to ok.
116
131
  if (next.reason === 'cooldown' || next.reason === 'blacklist') {
117
- next = { ...next, inPool: true, reason: 'ok' };
132
+ next = { ...next, inPool: true, reason: 'ok', cooldownKeepsPool: undefined };
118
133
  }
119
134
  return next;
120
135
  }
@@ -139,7 +154,7 @@ export function applyErrorEvent(state, event, nowMs = event.timestampMs ?? Date.
139
154
  : COOLDOWN_SCHEDULE_DEFAULT_MS;
140
155
  const rawNextCount = sameErrorKey ? state.consecutiveErrorCount + 1 : 1;
141
156
  const nextCount = rawNextCount > schedule.length ? 1 : rawNextCount;
142
- const cooldownMs = computeCooldownMsBySeries(series, nextCount);
157
+ const cooldownMs = computeTransientKeepPoolCooldownMs(series, nextCount) ?? computeCooldownMsBySeries(series, nextCount);
143
158
  const nextUntil = cooldownMs ? nowMs + cooldownMs : null;
144
159
  const existingUntil = typeof state.cooldownUntil === 'number' ? state.cooldownUntil : null;
145
160
  const cooldownUntil = typeof nextUntil === 'number' && Number.isFinite(nextUntil)
@@ -149,12 +164,14 @@ export function applyErrorEvent(state, event, nowMs = event.timestampMs ?? Date.
149
164
  : existingUntil;
150
165
  const inCooldown = typeof cooldownUntil === 'number' && cooldownUntil > nowMs;
151
166
  const inBlacklist = typeof state.blacklistUntil === 'number' && state.blacklistUntil > nowMs;
152
- const inPool = !inCooldown && !inBlacklist;
167
+ const cooldownKeepsPool = shouldKeepProviderInPoolDuringCooldown(series, nextCount);
168
+ const inPool = !inBlacklist && (!inCooldown || cooldownKeepsPool);
153
169
  return {
154
170
  ...state,
155
171
  inPool,
156
172
  reason: inBlacklist ? 'blacklist' : inCooldown ? 'cooldown' : 'ok',
157
173
  cooldownUntil,
174
+ cooldownKeepsPool: inCooldown ? cooldownKeepsPool : undefined,
158
175
  lastErrorSeries: series,
159
176
  lastErrorCode: errorKey,
160
177
  lastErrorAtMs: nowMs,
@@ -26,6 +26,7 @@ export interface QuotaState {
26
26
  authIssue?: QuotaAuthIssue;
27
27
  priorityTier: number;
28
28
  cooldownUntil: number | null;
29
+ cooldownKeepsPool?: boolean;
29
30
  blacklistUntil: number | null;
30
31
  lastErrorSeries: ErrorSeries | null;
31
32
  lastErrorCode: string | null;
@@ -1,4 +1,5 @@
1
1
  import { buildChatRequestFromResponses, captureResponsesContext } from '../../conversion/responses/responses-openai-bridge.js';
2
+ import { stripHistoricalImageAttachments } from '../../conversion/hub/process/chat-process-media.js';
2
3
  import { cloneJson } from '../server-side-tools.js';
3
4
  import { trimOpenAiMessagesForFollowup } from './followup-message-trimmer.js';
4
5
  function extractResponsesTopLevelParameters(record) {
@@ -74,9 +75,13 @@ export function normalizeFollowupParameters(value) {
74
75
  return undefined;
75
76
  }
76
77
  const cloned = cloneJson(value);
77
- // Followup requests are always non-streaming (servertool orchestration enforces this),
78
- // so remove any inherited stream hints to avoid conflicting flags.
78
+ // Followup requests are always re-entered as a fresh hop:
79
+ // - non-streaming (servertool orchestration enforces this)
80
+ // - no inherited tool-selection hints, otherwise the resumed turn can be biased toward
81
+ // immediately calling tools again instead of consuming the tool outputs that were just injected.
82
+ // Keep `parallel_tool_calls` inherited; provider compat can still disable it selectively.
79
83
  delete cloned.stream;
84
+ delete cloned.tool_choice;
80
85
  return Object.keys(cloned).length ? cloned : undefined;
81
86
  }
82
87
  export function dropToolByFunctionName(tools, dropName) {
@@ -418,6 +423,11 @@ export function buildServerToolFollowupChatPayloadFromInjection(args) {
418
423
  return null;
419
424
  }
420
425
  let messages = Array.isArray(seed.messages) ? cloneJson(seed.messages) : [];
426
+ // ServerTool followups must enter marker/routing/chat-process analysis with the same
427
+ // historical-media invariants as normal chat-process requests:
428
+ // only the latest live user turn may keep inline image payloads; earlier user turns
429
+ // are scrubbed to placeholders before any followup ops append new assistant/user items.
430
+ messages = stripHistoricalImageAttachments(messages);
421
431
  const ops = Array.isArray(args.injection?.ops) ? args.injection.ops : [];
422
432
  // Followup is a normal request hop: inherit tool schema from the captured request and
423
433
  // let compat/tool-governance apply standard sanitization rules.
@@ -1,7 +1,6 @@
1
1
  import type { JsonObject } from '../conversion/hub/types/json.js';
2
2
  import type { ServerSideToolEngineOptions, ServerSideToolEngineResult, ToolCall } from './types.js';
3
3
  import './handlers/iflow-model-error-retry.js';
4
- import './handlers/gemini-empty-reply-continue.js';
5
4
  import './handlers/antigravity-thought-signature-bootstrap.js';
6
5
  import './handlers/stop-message-auto.js';
7
6
  import './handlers/clock.js';
@@ -3,7 +3,6 @@ import { ProviderProtocolError } from '../conversion/provider-protocol-error.js'
3
3
  import { executeWebSearchBackendPlan } from './handlers/web-search.js';
4
4
  import { executeVisionBackendPlan } from './handlers/vision.js';
5
5
  import './handlers/iflow-model-error-retry.js';
6
- import './handlers/gemini-empty-reply-continue.js';
7
6
  import './handlers/antigravity-thought-signature-bootstrap.js';
8
7
  import './handlers/stop-message-auto.js';
9
8
  import './handlers/clock.js';
@@ -22,7 +21,7 @@ function traceAutoHook(options, event) {
22
21
  // best-effort trace callback
23
22
  }
24
23
  }
25
- const OPTIONAL_PRIMARY_HOOK_ORDER = ['empty_reply_continue', 'clock_auto', 'stop_message_auto'];
24
+ const OPTIONAL_PRIMARY_HOOK_ORDER = ['clock_auto', 'stop_message_auto'];
26
25
  const MANDATORY_HOOK_ORDER = [];
27
26
  let fallbackToolCallIdSeq = 0;
28
27
  function ensureToolCallId(record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.3539",
3
+ "version": "0.6.3551",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",