@jsonstudio/rcc 0.89.1086 → 0.89.1136

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.
Files changed (101) hide show
  1. package/dist/build-info.js +2 -2
  2. package/dist/cli.js +39 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/gemini/gemini-protocol-client.js +5 -0
  5. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  6. package/dist/commands/provider-update.js +355 -5
  7. package/dist/commands/provider-update.js.map +1 -1
  8. package/dist/docs/daemon-admin-ui.html +604 -91
  9. package/dist/index.js +33 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/manager/modules/quota/index.d.ts +37 -1
  12. package/dist/manager/modules/quota/index.js +378 -18
  13. package/dist/manager/modules/quota/index.js.map +1 -1
  14. package/dist/manager/quota/provider-quota-center.d.ts +3 -0
  15. package/dist/manager/quota/provider-quota-center.js +88 -24
  16. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  17. package/dist/manager/quota/provider-quota-store.js +5 -2
  18. package/dist/manager/quota/provider-quota-store.js.map +1 -1
  19. package/dist/manager/types.d.ts +5 -0
  20. package/dist/providers/core/config/service-profiles.js +1 -1
  21. package/dist/providers/core/config/service-profiles.js.map +1 -1
  22. package/dist/providers/core/runtime/gemini-cli-http-provider.js +5 -0
  23. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  24. package/dist/providers/core/runtime/http-transport-provider.js +26 -38
  25. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  26. package/dist/providers/core/utils/http-client.js +10 -1
  27. package/dist/providers/core/utils/http-client.js.map +1 -1
  28. package/dist/server/handlers/handler-utils.d.ts +1 -1
  29. package/dist/server/handlers/handler-utils.js +82 -4
  30. package/dist/server/handlers/handler-utils.js.map +1 -1
  31. package/dist/server/handlers/responses-handler.js +26 -3
  32. package/dist/server/handlers/responses-handler.js.map +1 -1
  33. package/dist/server/handlers/sse-dispatcher.js +1 -4
  34. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  35. package/dist/server/runtime/http-server/colored-logger.d.ts +1 -1
  36. package/dist/server/runtime/http-server/colored-logger.js +22 -10
  37. package/dist/server/runtime/http-server/colored-logger.js.map +1 -1
  38. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.d.ts +1 -1
  39. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +10 -14
  40. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  41. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +108 -115
  42. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  43. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +132 -7
  44. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  45. package/dist/server/runtime/http-server/daemon-admin/restart-handler.d.ts +3 -0
  46. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +22 -0
  47. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -0
  48. package/dist/server/runtime/http-server/daemon-admin/stats-handler.d.ts +3 -0
  49. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +56 -0
  50. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -0
  51. package/dist/server/runtime/http-server/daemon-admin/status-handler.js +100 -4
  52. package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
  53. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +24 -0
  54. package/dist/server/runtime/http-server/daemon-admin-routes.js +25 -0
  55. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  56. package/dist/server/runtime/http-server/executor-provider.js +74 -0
  57. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  58. package/dist/server/runtime/http-server/index.d.ts +7 -1
  59. package/dist/server/runtime/http-server/index.js +171 -14
  60. package/dist/server/runtime/http-server/index.js.map +1 -1
  61. package/dist/server/runtime/http-server/middleware.d.ts +2 -1
  62. package/dist/server/runtime/http-server/middleware.js +7 -6
  63. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  64. package/dist/server/runtime/http-server/provider-utils.d.ts +1 -1
  65. package/dist/server/runtime/http-server/provider-utils.js +19 -2
  66. package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
  67. package/dist/server/runtime/http-server/request-executor.js +9 -10
  68. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  69. package/dist/server/runtime/http-server/routes.d.ts +10 -0
  70. package/dist/server/runtime/http-server/routes.js +14 -1
  71. package/dist/server/runtime/http-server/routes.js.map +1 -1
  72. package/dist/server/runtime/http-server/stats-manager.d.ts +7 -0
  73. package/dist/server/runtime/http-server/stats-manager.js +22 -3
  74. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  75. package/dist/server/runtime/http-server/types.d.ts +10 -0
  76. package/dist/server/utils/http-error-mapper.js +85 -7
  77. package/dist/server/utils/http-error-mapper.js.map +1 -1
  78. package/dist/server/utils/request-id-manager.js +9 -5
  79. package/dist/server/utils/request-id-manager.js.map +1 -1
  80. package/dist/server/utils/sse-request-parser.js +2 -1
  81. package/dist/server/utils/sse-request-parser.js.map +1 -1
  82. package/dist/server/utils/utf8-chunk-buffer.d.ts +15 -30
  83. package/dist/server/utils/utf8-chunk-buffer.js +78 -88
  84. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
  85. package/dist/server/utils/warmup-storm-tracker.js +1 -1
  86. package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
  87. package/dist/token-daemon/token-daemon.js +7 -1
  88. package/dist/token-daemon/token-daemon.js.map +1 -1
  89. package/dist/tools/provider-update/fetch-models.js +8 -5
  90. package/dist/tools/provider-update/fetch-models.js.map +1 -1
  91. package/dist/tools/provider-update/probe-context.d.ts +24 -0
  92. package/dist/tools/provider-update/probe-context.js +199 -0
  93. package/dist/tools/provider-update/probe-context.js.map +1 -0
  94. package/dist/tools/provider-update/types.d.ts +1 -0
  95. package/package.json +6 -4
  96. package/scripts/scan-apply-patch-samples.mjs +362 -0
  97. package/scripts/scan-exec-command-samples.mjs +269 -0
  98. package/scripts/scan-tool-shape-samples.mjs +291 -0
  99. package/scripts/tools/sync-apply-patch-regressions.mjs +86 -0
  100. package/scripts/verify-apply-patch-regressions.mjs +119 -0
  101. package/scripts/verify-tool-arguments.mjs +1 -2
@@ -2,10 +2,24 @@ import { registerStatusRoutes } from './daemon-admin/status-handler.js';
2
2
  import { registerCredentialRoutes } from './daemon-admin/credentials-handler.js';
3
3
  import { registerQuotaRoutes } from './daemon-admin/quota-handler.js';
4
4
  import { registerProviderRoutes } from './daemon-admin/providers-handler.js';
5
+ import { registerRestartRoutes } from './daemon-admin/restart-handler.js';
6
+ import { registerStatsRoutes } from './daemon-admin/stats-handler.js';
7
+ import { extractApiKeyFromRequest } from './middleware.js';
5
8
  export function isLocalRequest(req) {
6
9
  const ip = req.socket?.remoteAddress || '';
7
10
  return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
8
11
  }
12
+ export function isDaemonAdminRequestAllowed(req, expectedApiKey) {
13
+ if (isLocalRequest(req)) {
14
+ return true;
15
+ }
16
+ const expected = typeof expectedApiKey === 'string' ? expectedApiKey.trim() : '';
17
+ if (!expected) {
18
+ return false;
19
+ }
20
+ const provided = extractApiKeyFromRequest(req);
21
+ return Boolean(provided && provided === expected);
22
+ }
9
23
  export function rejectNonLocal(req, res) {
10
24
  if (isLocalRequest(req)) {
11
25
  return false;
@@ -13,15 +27,26 @@ export function rejectNonLocal(req, res) {
13
27
  res.status(403).json({ error: { message: 'forbidden', code: 'forbidden' } });
14
28
  return true;
15
29
  }
30
+ export function rejectNonLocalOrUnauthorizedAdmin(req, res, expectedApiKey) {
31
+ if (isDaemonAdminRequestAllowed(req, expectedApiKey)) {
32
+ return false;
33
+ }
34
+ res.status(403).json({ error: { message: 'forbidden', code: 'forbidden' } });
35
+ return true;
36
+ }
16
37
  export function registerDaemonAdminRoutes(options) {
17
38
  const { app } = options;
18
39
  // Daemon / manager 状态
19
40
  registerStatusRoutes(app, options);
41
+ // Token usage / provider stats
42
+ registerStatsRoutes(app, options);
20
43
  // Credentials / token 视图
21
44
  registerCredentialRoutes(app, options);
22
45
  // Quota / 429 冷却视图
23
46
  registerQuotaRoutes(app, options);
24
47
  // Providers 运行时 + Config V2 视图
25
48
  registerProviderRoutes(app, options);
49
+ // Reload / restart runtime (reload config from disk)
50
+ registerRestartRoutes(app, options);
26
51
  }
27
52
  //# sourceMappingURL=daemon-admin-routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-admin-routes.js","sourceRoot":"","sources":["../../../../src/server/runtime/http-server/daemon-admin-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAkB7E,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,EAAE,CAAC;IAC3C,OAAO,EAAE,KAAK,WAAW,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,kBAAkB,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,GAAa;IACxD,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAgC;IACxE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAExB,sBAAsB;IACtB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAEnC,yBAAyB;IACzB,wBAAwB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAEvC,mBAAmB;IACnB,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAElC,+BAA+B;IAC/B,sBAAsB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"daemon-admin-routes.js","sourceRoot":"","sources":["../../../../src/server/runtime/http-server/daemon-admin-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAqC3D,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,EAAE,CAAC;IAC3C,OAAO,EAAE,KAAK,WAAW,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,kBAAkB,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,GAAY,EAAE,cAAuB;IAC/E,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO,OAAO,CAAC,QAAQ,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,GAAa;IACxD,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC/C,GAAY,EACZ,GAAa,EACb,cAAuB;IAEvB,IAAI,2BAA2B,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAgC;IACxE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAExB,sBAAsB;IACtB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAEnC,+BAA+B;IAC/B,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAElC,yBAAyB;IACzB,wBAAwB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAEvC,mBAAmB;IACnB,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAElC,+BAA+B;IAC/B,sBAAsB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAErC,qDAAqD;IACrD,qBAAqB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC"}
@@ -23,6 +23,13 @@ export function shouldRetryProviderError(error) {
23
23
  if (!error || typeof error !== 'object') {
24
24
  return false;
25
25
  }
26
+ // Deterministic context overflow: retrying the *same* model will keep failing,
27
+ // but a different model/provider in the same route pool might accept it.
28
+ // This matches the virtual-router strategy: if a target is over its context window,
29
+ // switch to the next candidate before giving up.
30
+ if (isPromptTooLongError(error)) {
31
+ return true;
32
+ }
26
33
  // virtualRouterSeriesCooldown 表示 provider 已经把 alias 从池子里拉黑,需要切换到下一位。
27
34
  // 这类错误仍然属于「可恢复」,因为虚拟路由会根据 cooldown 信息选择新的目标。
28
35
  if (hasVirtualRouterSeriesCooldown(error)) {
@@ -41,6 +48,55 @@ export function shouldRetryProviderError(error) {
41
48
  }
42
49
  return false;
43
50
  }
51
+ function isPromptTooLongError(error) {
52
+ const status = extractErrorStatusCode(error);
53
+ // Most upstreams return 400 for context overflow; keep this narrow to avoid retries on generic 400s.
54
+ // Some error shims only expose the message (or nest status inside response.data.error.status),
55
+ // so allow missing status when the message is a strong match.
56
+ if (status !== undefined && status !== 400) {
57
+ return false;
58
+ }
59
+ const messages = [];
60
+ const rawMessage = error.message;
61
+ if (typeof rawMessage === 'string' && rawMessage.trim()) {
62
+ messages.push(rawMessage);
63
+ }
64
+ const upstreamMessage = error.upstreamMessage;
65
+ if (typeof upstreamMessage === 'string' && upstreamMessage.trim()) {
66
+ messages.push(upstreamMessage);
67
+ }
68
+ const details = error.details;
69
+ if (details && typeof details === 'object') {
70
+ const msg = details.upstreamMessage;
71
+ if (typeof msg === 'string' && msg.trim()) {
72
+ messages.push(msg);
73
+ }
74
+ }
75
+ const response = error.response;
76
+ if (response && typeof response === 'object') {
77
+ const data = response.data;
78
+ if (data && typeof data === 'object') {
79
+ const err = data.error;
80
+ if (err && typeof err === 'object') {
81
+ const msg = err.message;
82
+ if (typeof msg === 'string' && msg.trim()) {
83
+ messages.push(msg);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ const combined = messages.join(' | ').toLowerCase();
89
+ if (!combined) {
90
+ return false;
91
+ }
92
+ return (combined.includes('prompt is too long') ||
93
+ combined.includes('maximum context') ||
94
+ combined.includes('max context') ||
95
+ combined.includes('context length') ||
96
+ combined.includes('context_window_exceeded') ||
97
+ combined.includes('token limit') ||
98
+ combined.includes('too many tokens'));
99
+ }
44
100
  export function extractErrorStatusCode(error) {
45
101
  if (!error || typeof error !== 'object') {
46
102
  return undefined;
@@ -60,6 +116,14 @@ export function extractErrorStatusCode(error) {
60
116
  if (typeof nestedStatus === 'number') {
61
117
  statusCandidates.push(nestedStatus);
62
118
  }
119
+ const upstreamStatus = detailStatus.upstreamStatus;
120
+ if (typeof upstreamStatus === 'number') {
121
+ statusCandidates.push(upstreamStatus);
122
+ }
123
+ const upstreamStatusSnake = detailStatus.upstream_status;
124
+ if (typeof upstreamStatusSnake === 'number') {
125
+ statusCandidates.push(upstreamStatusSnake);
126
+ }
63
127
  }
64
128
  const response = error.response;
65
129
  if (response && typeof response === 'object') {
@@ -71,6 +135,16 @@ export function extractErrorStatusCode(error) {
71
135
  if (typeof respStatusCode === 'number') {
72
136
  statusCandidates.push(respStatusCode);
73
137
  }
138
+ const data = response.data;
139
+ if (data && typeof data === 'object') {
140
+ const errNode = data.error;
141
+ if (errNode && typeof errNode === 'object') {
142
+ const nested = errNode.status;
143
+ if (typeof nested === 'number') {
144
+ statusCandidates.push(nested);
145
+ }
146
+ }
147
+ }
74
148
  }
75
149
  const explicit = statusCandidates.find((candidate) => typeof candidate === 'number');
76
150
  if (typeof explicit === 'number') {
@@ -1 +1 @@
1
- {"version":3,"file":"executor-provider.js","sourceRoot":"","sources":["../../../../src/server/runtime/http-server/executor-provider.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,8BAA8B,CAAC,KAAc;IAC3D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAI,KAA+B,CAAC,OAAO,CAAC;IACzD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,YAAY,GAAI,OAAqD,CAAC,2BAA2B,CAAC;IACxG,IAAI,CAAC,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,YAId,CAAC;IACF,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAClG,MAAM,YAAY,GAAG,MAAM,KAAK,mBAAmB,IAAI,OAAO,MAAM,CAAC,eAAe,KAAK,QAAQ,CAAC;IAClG,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,qEAAqE;IACrE,6CAA6C;IAC7C,IAAI,8BAA8B,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,aAAa,GAAG,KAAsB,CAAC;IAC7C,IAAI,aAAa,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,gBAAgB,GAA8B,EAAE,CAAC;IACvD,MAAM,YAAY,GAAI,KAAkC,CAAC,UAAU,CAAC;IACpE,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACrC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,eAAe,GAAI,KAA8B,CAAC,MAAM,CAAC;IAC/D,IAAI,OAAO,eAAe,KAAK,QAAQ,EAAE,CAAC;QACxC,gBAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,YAAY,GAAI,KAA+B,CAAC,OAAO,CAAC;IAC9D,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACrD,MAAM,YAAY,GAAI,YAAqC,CAAC,MAAM,CAAC;QACnE,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAI,KAAgC,CAAC,QAAQ,CAAC;IAC5D,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,UAAU,GAAI,QAAiC,CAAC,MAAM,CAAC;QAC7D,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,cAAc,GAAI,QAAqC,CAAC,UAAU,CAAC;QACzE,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;YACvC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAuB,EAAE,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC;IAC1G,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAI,KAA+B,CAAC,OAAO,CAAC;IACzD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC/C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,IAAI,OAAQ,KAA+B,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACjE,OAAQ,KAA6B,CAAC,OAAO,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"executor-provider.js","sourceRoot":"","sources":["../../../../src/server/runtime/http-server/executor-provider.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@ import type { ServerConfigV2, ServerStatusV2 } from './types.js';
18
18
  export declare class RouteCodexHttpServer {
19
19
  private app;
20
20
  private server?;
21
+ private activeSockets;
21
22
  private config;
22
23
  private errorHandling;
23
24
  private _isInitialized;
@@ -39,12 +40,15 @@ export declare class RouteCodexHttpServer {
39
40
  private readonly coloredLogger;
40
41
  private managerDaemon;
41
42
  private readonly stats;
43
+ private restartChain;
42
44
  constructor(config: ServerConfigV2);
43
45
  private resolveVirtualRouterInput;
44
46
  private getModuleDependencies;
45
47
  /**
46
48
  * Register Daemon Admin UI route.
47
- * Serves docs/daemon-admin-ui.html as a static page; localhost-only.
49
+ * Serves docs/daemon-admin-ui.html as a static page.
50
+ * - If `httpserver.apikey` is not configured: localhost-only.
51
+ * - If `httpserver.apikey` is configured: allow remote UI access (API calls still require apikey).
48
52
  */
49
53
  private registerDaemonAdminUiRoute;
50
54
  private getErrorHandlingShim;
@@ -63,10 +67,12 @@ export declare class RouteCodexHttpServer {
63
67
  private bootstrapVirtualRouter;
64
68
  private ensureHubPipelineCtor;
65
69
  private isPipelineReady;
70
+ private isQuotaRoutingEnabled;
66
71
  /**
67
72
  * 初始化服务器
68
73
  */
69
74
  initialize(): Promise<void>;
75
+ private restartRuntimeFromDisk;
70
76
  /**
71
77
  * 启动服务器
72
78
  */
@@ -33,6 +33,7 @@ import { HealthManagerModule } from '../../../manager/modules/health/index.js';
33
33
  import { RoutingStateManagerModule } from '../../../manager/modules/routing/index.js';
34
34
  import { TokenManagerModule } from '../../../manager/modules/token/index.js';
35
35
  import { StatsManager } from './stats-manager.js';
36
+ import { loadRouteCodexConfig } from '../../../config/routecodex-config-loader.js';
36
37
  /**
37
38
  * RouteCodex Server V2
38
39
  *
@@ -41,6 +42,7 @@ import { StatsManager } from './stats-manager.js';
41
42
  export class RouteCodexHttpServer {
42
43
  app;
43
44
  server;
45
+ activeSockets = new Set();
44
46
  config;
45
47
  errorHandling;
46
48
  _isInitialized = false;
@@ -63,6 +65,7 @@ export class RouteCodexHttpServer {
63
65
  coloredLogger = createServerColoredLogger();
64
66
  managerDaemon = null;
65
67
  stats = new StatsManager();
68
+ restartChain = Promise.resolve();
66
69
  constructor(config) {
67
70
  this.config = config;
68
71
  this.app = express();
@@ -107,14 +110,18 @@ export class RouteCodexHttpServer {
107
110
  }
108
111
  /**
109
112
  * Register Daemon Admin UI route.
110
- * Serves docs/daemon-admin-ui.html as a static page; localhost-only.
113
+ * Serves docs/daemon-admin-ui.html as a static page.
114
+ * - If `httpserver.apikey` is not configured: localhost-only.
115
+ * - If `httpserver.apikey` is configured: allow remote UI access (API calls still require apikey).
111
116
  */
112
117
  registerDaemonAdminUiRoute() {
113
118
  this.app.get('/daemon/admin', async (req, res) => {
114
119
  try {
115
120
  const ip = req.socket?.remoteAddress || '';
116
- const allowed = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
117
- if (!allowed) {
121
+ const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
122
+ const expectedKey = typeof this.config?.server?.apikey === 'string' ? this.config.server.apikey.trim() : '';
123
+ const hasConfiguredKey = Boolean(expectedKey);
124
+ if (!isLocal && !hasConfiguredKey) {
118
125
  res.status(403).json({ error: { message: 'forbidden', code: 'forbidden' } });
119
126
  return;
120
127
  }
@@ -131,6 +138,10 @@ export class RouteCodexHttpServer {
131
138
  html = await fs.readFile(fallback, 'utf8');
132
139
  }
133
140
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
141
+ // Avoid stale admin UI in browsers / proxies after upgrades.
142
+ res.setHeader('Cache-Control', 'no-store, max-age=0');
143
+ res.setHeader('Pragma', 'no-cache');
144
+ res.setHeader('X-RouteCodex-Version', String(process.env.ROUTECODEX_VERSION || 'dev'));
134
145
  res.send(html);
135
146
  }
136
147
  catch (error) {
@@ -345,6 +356,13 @@ export class RouteCodexHttpServer {
345
356
  isPipelineReady() {
346
357
  return Boolean(this.hubPipeline);
347
358
  }
359
+ isQuotaRoutingEnabled() {
360
+ const flag = this.config.server.quotaRoutingEnabled;
361
+ if (typeof flag === 'boolean') {
362
+ return flag;
363
+ }
364
+ return true;
365
+ }
348
366
  /**
349
367
  * 初始化服务器
350
368
  */
@@ -357,7 +375,7 @@ export class RouteCodexHttpServer {
357
375
  // 初始化 ManagerDaemon 骨架(当前模块为占位实现,不改变行为)
358
376
  if (!this.managerDaemon) {
359
377
  const serverId = `${this.config.server.host}:${this.config.server.port}`;
360
- const daemon = new ManagerDaemon({ serverId });
378
+ const daemon = new ManagerDaemon({ serverId, quotaRoutingEnabled: this.isQuotaRoutingEnabled() });
361
379
  daemon.registerModule(new TokenManagerModule());
362
380
  daemon.registerModule(new RoutingStateManagerModule());
363
381
  daemon.registerModule(new HealthManagerModule());
@@ -385,6 +403,7 @@ export class RouteCodexHttpServer {
385
403
  buildHandlerContext: () => this.buildHandlerContext(),
386
404
  getPipelineReady: () => this.isPipelineReady(),
387
405
  handleError: (error, context) => this.handleError(error, context),
406
+ restartRuntimeFromDisk: async () => await this.restartRuntimeFromDisk(),
388
407
  getHealthSnapshot: () => {
389
408
  const healthModule = this.managerDaemon?.getModule('health');
390
409
  return healthModule?.getCurrentSnapshot() ?? null;
@@ -400,6 +419,10 @@ export class RouteCodexHttpServer {
400
419
  },
401
420
  getManagerDaemon: () => this.managerDaemon,
402
421
  getVirtualRouterArtifacts: () => this.currentRouterArtifacts,
422
+ getStatsSnapshot: () => ({
423
+ session: this.stats.snapshot(Math.round(process.uptime() * 1000)),
424
+ historical: this.stats.snapshotHistorical()
425
+ }),
403
426
  getServerId: () => `${this.config.server.host}:${this.config.server.port}`
404
427
  });
405
428
  this._isInitialized = true;
@@ -410,6 +433,38 @@ export class RouteCodexHttpServer {
410
433
  throw error;
411
434
  }
412
435
  }
436
+ async restartRuntimeFromDisk() {
437
+ // Serialize restarts to avoid racing provider disposals / hub updates.
438
+ const run = async () => {
439
+ const loaded = await loadRouteCodexConfig(this.config?.configPath);
440
+ const userConfig = asRecord(loaded.userConfig) ?? {};
441
+ const httpServerNode = asRecord(userConfig.httpserver) ??
442
+ asRecord(asRecord(userConfig.modules)?.httpserver)?.config ??
443
+ null;
444
+ const nextApiKey = httpServerNode ? (typeof httpServerNode.apikey === 'string' ? String(httpServerNode.apikey).trim() : '') : '';
445
+ const nextHost = httpServerNode ? (typeof httpServerNode.host === 'string' ? String(httpServerNode.host).trim() : '') : '';
446
+ const nextPort = httpServerNode ? (typeof httpServerNode.port === 'number' ? Number(httpServerNode.port) : NaN) : NaN;
447
+ const warnings = [];
448
+ // Best-effort: allow rotating apikey at runtime by mutating config object.
449
+ if (typeof nextApiKey === 'string' && nextApiKey !== String(this.config.server.apikey || '')) {
450
+ this.config.server.apikey = nextApiKey || undefined;
451
+ }
452
+ // host/port changes require rebind; record warnings but do not attempt to re-listen.
453
+ if (nextHost && nextHost !== this.config.server.host) {
454
+ warnings.push(`httpserver.host changed to "${nextHost}" but live server keeps "${this.config.server.host}" until process restart`);
455
+ }
456
+ if (Number.isFinite(nextPort) && nextPort > 0 && nextPort !== this.config.server.port) {
457
+ warnings.push(`httpserver.port changed to ${nextPort} but live server keeps ${this.config.server.port} until process restart`);
458
+ }
459
+ // Keep the server's configPath aligned with what was loaded.
460
+ this.config.configPath = loaded.configPath;
461
+ await this.reloadRuntime(loaded.userConfig, { providerProfiles: loaded.providerProfiles });
462
+ return { reloadedAt: Date.now(), configPath: loaded.configPath, ...(warnings.length ? { warnings } : {}) };
463
+ };
464
+ const slot = this.restartChain.then(run);
465
+ this.restartChain = slot.then(() => undefined, () => undefined);
466
+ return await slot;
467
+ }
413
468
  /**
414
469
  * 启动服务器
415
470
  */
@@ -423,6 +478,22 @@ export class RouteCodexHttpServer {
423
478
  console.log(`[RouteCodexHttpServer] Server started on ${this.config.server.host}:${this.config.server.port}`);
424
479
  resolve();
425
480
  });
481
+ // In test runners (Jest), prevent the listen handle from keeping the process alive
482
+ // in case some keep-alive sockets linger.
483
+ if (process.env.JEST_WORKER_ID || process.env.NODE_ENV === 'test') {
484
+ try {
485
+ this.server.unref?.();
486
+ }
487
+ catch {
488
+ // ignore
489
+ }
490
+ }
491
+ this.server.on('connection', (socket) => {
492
+ this.activeSockets.add(socket);
493
+ socket.on('close', () => {
494
+ this.activeSockets.delete(socket);
495
+ });
496
+ });
426
497
  this.server.on('error', async (error) => {
427
498
  await this.handleError(error, 'server_start');
428
499
  reject(error);
@@ -434,6 +505,24 @@ export class RouteCodexHttpServer {
434
505
  */
435
506
  async stop() {
436
507
  if (this.server) {
508
+ // Best-effort: close any open keep-alive sockets so server.close can finish.
509
+ for (const socket of this.activeSockets) {
510
+ try {
511
+ socket.destroy();
512
+ }
513
+ catch {
514
+ // ignore
515
+ }
516
+ }
517
+ this.activeSockets.clear();
518
+ try {
519
+ const srv = this.server;
520
+ srv.closeIdleConnections?.();
521
+ srv.closeAllConnections?.();
522
+ }
523
+ catch {
524
+ // ignore
525
+ }
437
526
  return new Promise(resolve => {
438
527
  this.server?.close(async () => {
439
528
  this._isRunning = false;
@@ -450,6 +539,13 @@ export class RouteCodexHttpServer {
450
539
  catch {
451
540
  // ignore manager shutdown failures
452
541
  }
542
+ try {
543
+ this.server?.removeAllListeners();
544
+ }
545
+ catch {
546
+ // ignore
547
+ }
548
+ this.server = undefined;
453
549
  await this.errorHandling.destroy();
454
550
  try {
455
551
  const uptimeMs = Math.round(process.uptime() * 1000);
@@ -551,15 +647,24 @@ export class RouteCodexHttpServer {
551
647
  hubConfig.routingStateStore = routingStateStore;
552
648
  }
553
649
  const quotaModule = this.managerDaemon?.getModule('provider-quota');
554
- const quotaFlagRaw = String(process.env.ROUTECODEX_QUOTA_ENABLED || '').trim().toLowerCase();
555
- const quotaEnabled = quotaFlagRaw === '1' || quotaFlagRaw === 'true';
556
- if (quotaEnabled && quotaModule && typeof quotaModule.getQuotaView === 'function') {
650
+ if (this.isQuotaRoutingEnabled() && quotaModule && typeof quotaModule.getQuotaView === 'function') {
557
651
  hubConfig.quotaView = quotaModule.getQuotaView();
558
652
  }
559
653
  if (!this.hubPipeline) {
560
654
  this.hubPipeline = new hubCtor(hubConfig);
561
655
  }
562
656
  else {
657
+ const existing = this.hubPipeline;
658
+ try {
659
+ existing.updateRuntimeDeps?.({
660
+ ...(healthStore ? { healthStore } : {}),
661
+ ...(routingStateStore ? { routingStateStore } : {}),
662
+ ...('quotaView' in hubConfig ? { quotaView: hubConfig.quotaView } : {})
663
+ });
664
+ }
665
+ catch {
666
+ // best-effort: runtime deps updates must never block reload
667
+ }
563
668
  this.hubPipeline.updateVirtualRouterConfig(bootstrapArtifacts.config);
564
669
  }
565
670
  await this.initializeProviderRuntimes(bootstrapArtifacts);
@@ -577,17 +682,51 @@ export class RouteCodexHttpServer {
577
682
  }
578
683
  await this.disposeProviders();
579
684
  this.providerKeyToRuntimeKey.clear();
685
+ const quotaModule = this.managerDaemon?.getModule('provider-quota');
686
+ // Multiple providerKeys may share the same runtimeKey (single provider handle with multiple models).
687
+ // Quota is tracked by providerKey, so we need a stable way to derive authType for every key,
688
+ // even when the handle has already been created for this runtimeKey.
689
+ const runtimeKeyAuthType = new Map();
580
690
  for (const [providerKey, runtime] of Object.entries(runtimeMap)) {
581
691
  if (!runtime) {
582
692
  continue;
583
693
  }
584
694
  const runtimeKey = runtime.runtimeKey || providerKey;
695
+ const authTypeFromRuntime = runtime && runtime.auth && typeof runtime.auth.type === 'string'
696
+ ? String(runtime.auth.type).trim()
697
+ : null;
585
698
  if (!this.providerHandles.has(runtimeKey)) {
586
699
  const resolvedRuntime = await this.materializeRuntimeProfile(runtime);
587
700
  const patchedRuntime = this.applyProviderProfileOverrides(resolvedRuntime);
701
+ try {
702
+ const authTypeFromPatched = patchedRuntime && patchedRuntime.auth && typeof patchedRuntime.auth.type === 'string'
703
+ ? String(patchedRuntime.auth.type).trim()
704
+ : null;
705
+ if (authTypeFromPatched) {
706
+ runtimeKeyAuthType.set(runtimeKey, authTypeFromPatched);
707
+ }
708
+ else if (authTypeFromRuntime) {
709
+ runtimeKeyAuthType.set(runtimeKey, authTypeFromRuntime);
710
+ }
711
+ else if (!runtimeKeyAuthType.has(runtimeKey)) {
712
+ runtimeKeyAuthType.set(runtimeKey, null);
713
+ }
714
+ }
715
+ catch {
716
+ // ignore authType derivation failures
717
+ }
588
718
  const handle = await this.createProviderHandle(runtimeKey, patchedRuntime);
589
719
  this.providerHandles.set(runtimeKey, handle);
590
720
  }
721
+ // Register static quota metadata for every providerKey (not just per runtimeKey).
722
+ // Use the runtimeKey authType when available so shared runtimeKey models inherit correct policy.
723
+ try {
724
+ const authType = runtimeKeyAuthType.get(runtimeKey) ?? authTypeFromRuntime;
725
+ quotaModule?.registerProviderStaticConfig?.(providerKey, { authType: authType ?? null });
726
+ }
727
+ catch {
728
+ // best-effort: quota static config registration must never block runtime init
729
+ }
591
730
  this.providerKeyToRuntimeKey.set(providerKey, runtimeKey);
592
731
  }
593
732
  }
@@ -756,16 +895,16 @@ export class RouteCodexHttpServer {
756
895
  // snapshot failure should not block request path
757
896
  }
758
897
  const pipelineLabel = 'hub';
759
- let iterationMetadata = initialMetadata;
760
- let followupTriggered = false;
898
+ const iterationMetadata = initialMetadata;
899
+ const _followupTriggered = false;
761
900
  // Provider 级别不再在单个 HTTP 请求内执行重复尝试,
762
901
  // 429/配额/熔断逻辑统一交由 llmswitch-core VirtualRouter 处理。
763
- const maxAttempts = 1;
764
- let attempt = 0;
902
+ const _maxAttempts = 1;
903
+ let _attempt = 0;
765
904
  const originalBodySnapshot = this.cloneRequestPayload(input.body);
766
905
  const excludedProviderKeys = new Set();
767
906
  while (true) {
768
- attempt += 1;
907
+ _attempt += 1;
769
908
  // 每次尝试前重置请求 body,避免上一轮 HubPipeline 的就地改写导致
770
909
  // 第二轮出现 ChatEnvelopeValidationError(messages_missing) 之类的问题。
771
910
  if (originalBodySnapshot && typeof originalBodySnapshot === 'object') {
@@ -907,7 +1046,7 @@ export class RouteCodexHttpServer {
907
1046
  });
908
1047
  const usage = this.extractUsageFromResult(converted, mergedMetadata);
909
1048
  const quotaModule = this.managerDaemon?.getModule('provider-quota');
910
- if (quotaModule) {
1049
+ if (this.isQuotaRoutingEnabled() && quotaModule) {
911
1050
  const totalTokens = typeof usage?.total_tokens === 'number' && Number.isFinite(usage.total_tokens)
912
1051
  ? Math.max(0, usage.total_tokens)
913
1052
  : Math.max(0, (typeof usage?.prompt_tokens === 'number' && Number.isFinite(usage.prompt_tokens) ? usage.prompt_tokens : 0) +
@@ -929,6 +1068,24 @@ export class RouteCodexHttpServer {
929
1068
  }
930
1069
  }
931
1070
  this.stats.recordCompletion(input.requestId, { usage, error: false });
1071
+ // 回传 session_id 和 conversation_id 到响应头(如果存在)
1072
+ const sessionId = typeof mergedMetadata.sessionId === "string" && mergedMetadata.sessionId.trim()
1073
+ ? mergedMetadata.sessionId.trim()
1074
+ : undefined;
1075
+ const conversationId = typeof mergedMetadata.conversationId === "string" && mergedMetadata.conversationId.trim()
1076
+ ? mergedMetadata.conversationId.trim()
1077
+ : undefined;
1078
+ if (sessionId || conversationId) {
1079
+ if (!converted.headers) {
1080
+ converted.headers = {};
1081
+ }
1082
+ if (sessionId && !converted.headers["session_id"]) {
1083
+ converted.headers["session_id"] = sessionId;
1084
+ }
1085
+ if (conversationId && !converted.headers["conversation_id"]) {
1086
+ converted.headers["conversation_id"] = conversationId;
1087
+ }
1088
+ }
932
1089
  return converted;
933
1090
  }
934
1091
  catch (error) {
@@ -941,7 +1098,7 @@ export class RouteCodexHttpServer {
941
1098
  providerLabel
942
1099
  });
943
1100
  const quotaModule = this.managerDaemon?.getModule('provider-quota');
944
- if (quotaModule) {
1101
+ if (this.isQuotaRoutingEnabled() && quotaModule) {
945
1102
  try {
946
1103
  quotaModule.recordProviderUsage({ providerKey: target.providerKey, requestedTokens: 0 });
947
1104
  }