@jsonstudio/llms 0.6.97 → 0.6.104

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.
@@ -62,4 +62,16 @@ function mergeOriginalResponsesPayload(payload, adapterContext) {
62
62
  if (rawStatus === 'requires_action') {
63
63
  payload.status = 'requires_action';
64
64
  }
65
+ // 如果桥接后的 payload 没有 usage,而原始 Responses 载荷带有 usage,则回填原始 usage,
66
+ // 确保 token usage 不在工具/桥接路径中丢失。
67
+ const payloadUsage = payload.usage;
68
+ const rawUsage = raw.usage;
69
+ if ((payloadUsage == null || typeof payloadUsage !== 'object') && rawUsage && typeof rawUsage === 'object') {
70
+ try {
71
+ payload.usage = JSON.parse(JSON.stringify(rawUsage));
72
+ }
73
+ catch {
74
+ payload.usage = rawUsage;
75
+ }
76
+ }
65
77
  }
@@ -35,4 +35,5 @@ export declare class VirtualRouterEngine {
35
35
  private buildRouteCandidates;
36
36
  private sortByPriority;
37
37
  private routeWeight;
38
+ private buildHitReason;
38
39
  }
@@ -35,7 +35,8 @@ export class VirtualRouterEngine {
35
35
  const target = this.providerRegistry.buildTarget(selection.providerKey);
36
36
  this.healthManager.recordSuccess(selection.providerKey);
37
37
  this.incrementRouteStat(selection.routeUsed, selection.providerKey);
38
- this.debug?.log?.('[virtual-router-hit]', selection.routeUsed, selection.providerKey, target.modelId || '');
38
+ const hitReason = this.buildHitReason(selection.routeUsed, classification, features);
39
+ this.debug?.log?.('[virtual-router-hit]', selection.routeUsed, selection.providerKey, target.modelId || '', hitReason ? `reason=${hitReason}` : '');
39
40
  const didFallback = selection.routeUsed !== routeName || classification.fallback;
40
41
  return {
41
42
  target,
@@ -179,22 +180,23 @@ export class VirtualRouterEngine {
179
180
  const code = event.code?.toUpperCase() ?? 'ERR_UNKNOWN';
180
181
  const stage = event.stage?.toLowerCase() ?? 'unknown';
181
182
  const recoverable = event.recoverable === true;
182
- // 默认策略:只有显式可恢复的错误才视为非致命;其余一律按致命处理
183
+ // 默认策略:只有显式可恢复的错误才视为非致命;其余一律按致命处理。
184
+ // 注意:provider 层已经对 429 做了「连续 4 次升级为不可恢复」的判断,这里不再把所有 429 强行当作可恢复。
183
185
  let fatal = !recoverable;
184
186
  let reason = this.deriveReason(code, stage, statusCode);
185
187
  let cooldownOverrideMs;
186
- // 400 / 429 作为明确可恢复池:走限流通道,不做长期拉黑
187
- if (statusCode === 429 || code.includes('429') || statusCode === 400 || code.includes('400')) {
188
- fatal = false;
189
- cooldownOverrideMs = Math.max(30_000, this.providerHealthConfig().cooldownMs);
190
- reason = 'rate_limit';
191
- // 401 / 402 / 500 / 524 以及所有未被标记为可恢复的错误一律视为不可恢复
192
- }
193
- else if (statusCode === 401 || statusCode === 402 || statusCode === 403 || code.includes('AUTH')) {
188
+ // 401 / 402 / 500 / 524 以及所有未被标记为可恢复的错误一律视为不可恢复
189
+ if (statusCode === 401 || statusCode === 402 || statusCode === 403 || code.includes('AUTH')) {
194
190
  fatal = true;
195
191
  cooldownOverrideMs = Math.max(10 * 60_000, this.providerHealthConfig().fatalCooldownMs ?? 10 * 60_000);
196
192
  reason = 'auth';
197
193
  }
194
+ else if (statusCode === 429 && !recoverable) {
195
+ // 连续 429 已在 provider 层被升级为不可恢复:这里按致命限流处理(长冷却,等同熔断)
196
+ fatal = true;
197
+ cooldownOverrideMs = Math.max(10 * 60_000, this.providerHealthConfig().fatalCooldownMs ?? 10 * 60_000);
198
+ reason = 'rate_limit';
199
+ }
198
200
  else if (statusCode && statusCode >= 500) {
199
201
  fatal = true;
200
202
  cooldownOverrideMs = Math.max(5 * 60_000, this.providerHealthConfig().fatalCooldownMs ?? 5 * 60_000);
@@ -213,7 +215,8 @@ export class VirtualRouterEngine {
213
215
  statusCode,
214
216
  errorCode: code,
215
217
  retryable: recoverable,
216
- affectsHealth: true,
218
+ // 是否影响健康由 provider 层决定;这里仅在 event.affectsHealth !== false 时才计入健康状态
219
+ affectsHealth: event.affectsHealth !== false,
217
220
  cooldownOverrideMs,
218
221
  metadata: {
219
222
  ...event.runtime,
@@ -274,4 +277,25 @@ export class VirtualRouterEngine {
274
277
  const idx = ROUTE_PRIORITY.indexOf(routeName);
275
278
  return idx >= 0 ? idx : ROUTE_PRIORITY.length;
276
279
  }
280
+ buildHitReason(routeUsed, classification, features) {
281
+ const reasoning = classification.reasoning || '';
282
+ const primary = reasoning.split('|')[0] || '';
283
+ const lastToolName = features.lastAssistantToolName;
284
+ if (routeUsed === 'tools') {
285
+ if (lastToolName) {
286
+ return primary ? `${primary}(${lastToolName})` : `tools(${lastToolName})`;
287
+ }
288
+ return primary || 'tools';
289
+ }
290
+ if (routeUsed === 'thinking') {
291
+ return primary || 'thinking';
292
+ }
293
+ if (routeUsed === DEFAULT_ROUTE && classification.fallback) {
294
+ return primary || 'fallback:default';
295
+ }
296
+ if (primary) {
297
+ return primary;
298
+ }
299
+ return routeUsed ? `route:${routeUsed}` : 'route:unknown';
300
+ }
277
301
  }
@@ -210,6 +210,7 @@ export interface ProviderErrorEvent {
210
210
  stage: string;
211
211
  status?: number;
212
212
  recoverable?: boolean;
213
+ affectsHealth?: boolean;
213
214
  runtime: ProviderErrorRuntimeMetadata;
214
215
  timestamp: number;
215
216
  details?: Record<string, unknown>;
@@ -0,0 +1,12 @@
1
+ {
2
+ "samplesRoot": "/Users/fanzhang/.routecodex/codex-samples",
3
+ "configPath": "/Users/fanzhang/Documents/github/sharedmodule/llmswitch-core/test/virtual-router/virtual-router.config.json",
4
+ "stats": {
5
+ "totalSamples": 0,
6
+ "processed": 0,
7
+ "routes": {},
8
+ "providers": {},
9
+ "errors": [],
10
+ "scenarios": {}
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.097",
3
+ "version": "0.6.104",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",