@jsonstudio/rcc 0.89.333 → 0.89.524

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 (165) hide show
  1. package/dist/build-info.js +3 -3
  2. package/dist/build-info.js.map +1 -1
  3. package/dist/cli.js +62 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/token-daemon.d.ts +2 -0
  6. package/dist/commands/token-daemon.js +183 -0
  7. package/dist/commands/token-daemon.js.map +1 -0
  8. package/dist/index.js +4 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/modules/llmswitch/bridge.d.ts +1 -1
  11. package/dist/modules/llmswitch/bridge.js +3 -2
  12. package/dist/modules/llmswitch/bridge.js.map +1 -1
  13. package/dist/modules/pipeline/utils/colored-logger.js +3 -1
  14. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  15. package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
  16. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  17. package/dist/providers/auth/oauth-lifecycle.js +261 -22
  18. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  19. package/dist/providers/core/config/oauth-flows.d.ts +23 -0
  20. package/dist/providers/core/config/oauth-flows.js +92 -5
  21. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  22. package/dist/providers/core/config/provider-oauth-configs.js +9 -3
  23. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  24. package/dist/providers/core/config/service-profiles.js +18 -10
  25. package/dist/providers/core/config/service-profiles.js.map +1 -1
  26. package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
  27. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  28. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  29. package/dist/providers/core/runtime/http-request-executor.js +41 -1
  30. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  31. package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
  32. package/dist/providers/core/runtime/http-transport-provider.js +37 -2
  33. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  34. package/dist/providers/core/runtime/responses-provider.js +8 -3
  35. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  36. package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
  37. package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
  38. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
  39. package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
  40. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  41. package/dist/providers/core/utils/http-client.js +2 -1
  42. package/dist/providers/core/utils/http-client.js.map +1 -1
  43. package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
  44. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  45. package/dist/server/handlers/sse-dispatcher.js +22 -2
  46. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  47. package/dist/server/runtime/http-server/index.d.ts +9 -0
  48. package/dist/server/runtime/http-server/index.js +512 -144
  49. package/dist/server/runtime/http-server/index.js.map +1 -1
  50. package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
  51. package/dist/server/runtime/http-server/request-executor.js +553 -159
  52. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  53. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  54. package/dist/server/runtime/http-server/routes.js +69 -0
  55. package/dist/server/runtime/http-server/routes.js.map +1 -1
  56. package/dist/server/runtime/http-server/runtime-manager.js +18 -0
  57. package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
  58. package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
  59. package/dist/server/utils/utf8-chunk-buffer.js +132 -0
  60. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
  61. package/dist/token-daemon/index.d.ts +7 -0
  62. package/dist/token-daemon/index.js +242 -0
  63. package/dist/token-daemon/index.js.map +1 -0
  64. package/dist/token-daemon/server-utils.d.ts +33 -0
  65. package/dist/token-daemon/server-utils.js +155 -0
  66. package/dist/token-daemon/server-utils.js.map +1 -0
  67. package/dist/token-daemon/token-daemon.d.ts +20 -0
  68. package/dist/token-daemon/token-daemon.js +144 -0
  69. package/dist/token-daemon/token-daemon.js.map +1 -0
  70. package/dist/token-daemon/token-types.d.ts +44 -0
  71. package/dist/token-daemon/token-types.js +18 -0
  72. package/dist/token-daemon/token-types.js.map +1 -0
  73. package/dist/token-daemon/token-utils.d.ts +17 -0
  74. package/dist/token-daemon/token-utils.js +153 -0
  75. package/dist/token-daemon/token-utils.js.map +1 -0
  76. package/dist/tools/semantic-replay.js +7 -6
  77. package/dist/tools/semantic-replay.js.map +1 -1
  78. package/dist/utils/error-handler-registry.d.ts +36 -0
  79. package/dist/utils/error-handler-registry.js +93 -7
  80. package/dist/utils/error-handler-registry.js.map +1 -1
  81. package/node_modules/@jsonstudio/llms/README.md +2 -0
  82. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
  83. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  84. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  85. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  86. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
  87. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  88. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  89. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
  90. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
  91. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
  92. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  93. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
  94. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
  95. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  96. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  97. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  98. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
  99. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
  100. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  101. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
  102. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
  103. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
  104. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
  105. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
  106. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
  107. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
  108. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
  109. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  110. package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
  111. package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
  112. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
  113. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
  114. package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
  115. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
  116. package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
  117. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  118. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
  119. package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
  120. package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
  121. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
  122. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
  123. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
  124. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
  125. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
  126. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
  127. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
  128. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
  129. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
  130. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
  131. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
  132. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
  133. package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
  134. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
  135. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
  136. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
  137. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
  138. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
  139. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
  140. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
  141. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
  142. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
  143. package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
  144. package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
  145. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
  146. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
  147. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
  148. package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
  149. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
  150. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
  151. package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
  152. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  153. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
  154. package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
  155. package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
  156. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
  157. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
  158. package/node_modules/@jsonstudio/llms/package.json +1 -1
  159. package/package.json +2 -2
  160. package/scripts/pack-mode.mjs +2 -1
  161. package/scripts/publish-rcc.mjs +20 -4
  162. package/scripts/tests/virtual-router-health.mjs +141 -6
  163. package/dist/tools/replay-request.d.ts +0 -0
  164. package/dist/tools/replay-request.js +0 -2
  165. package/dist/tools/replay-request.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ export interface VirtualRouterHitEvent {
2
+ requestId: string;
3
+ timestamp: number;
4
+ entryEndpoint: string;
5
+ routeName: string;
6
+ pool: string;
7
+ providerKey: string;
8
+ runtimeKey?: string;
9
+ providerType?: string;
10
+ modelId?: string;
11
+ }
12
+ export interface ProviderUsageEvent {
13
+ requestId: string;
14
+ timestamp: number;
15
+ providerKey: string;
16
+ runtimeKey?: string;
17
+ providerType: string;
18
+ modelId?: string;
19
+ routeName?: string;
20
+ entryEndpoint?: string;
21
+ success: boolean;
22
+ latencyMs: number;
23
+ promptTokens?: number;
24
+ completionTokens?: number;
25
+ totalTokens?: number;
26
+ }
27
+ export interface RouterStatsBucket {
28
+ requestCount: number;
29
+ poolHitCount: Record<string, number>;
30
+ routeHitCount: Record<string, number>;
31
+ providerHitCount: Record<string, number>;
32
+ }
33
+ export interface RouterStatsSnapshot {
34
+ global: RouterStatsBucket;
35
+ byEntryEndpoint: Record<string, RouterStatsBucket>;
36
+ }
37
+ export interface ProviderStatsBucket {
38
+ requestCount: number;
39
+ successCount: number;
40
+ errorCount: number;
41
+ latencySumMs: number;
42
+ minLatencyMs: number;
43
+ maxLatencyMs: number;
44
+ usage: {
45
+ promptTokens: number;
46
+ completionTokens: number;
47
+ totalTokens: number;
48
+ };
49
+ }
50
+ export interface ProviderStatsSnapshot {
51
+ global: ProviderStatsBucket;
52
+ byProviderKey: Record<string, ProviderStatsBucket>;
53
+ byRoute: Record<string, ProviderStatsBucket>;
54
+ byEntryEndpoint: Record<string, ProviderStatsBucket>;
55
+ }
56
+ export interface StatsSnapshot {
57
+ router: RouterStatsSnapshot;
58
+ providers: ProviderStatsSnapshot;
59
+ }
60
+ export interface StatsCenterOptions {
61
+ enable?: boolean;
62
+ autoPrintOnExit?: boolean;
63
+ persistPath?: string | null;
64
+ }
65
+ export interface StatsCenter {
66
+ recordVirtualRouterHit(ev: VirtualRouterHitEvent): void;
67
+ recordProviderUsage(ev: ProviderUsageEvent): void;
68
+ getSnapshot(): StatsSnapshot;
69
+ flushToDisk(): Promise<void>;
70
+ reset(): void;
71
+ }
72
+ export declare function initStatsCenter(options?: StatsCenterOptions): StatsCenter;
73
+ export declare function getStatsCenter(): StatsCenter;
@@ -0,0 +1,280 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs/promises';
4
+ function createEmptyRouterBucket() {
5
+ return {
6
+ requestCount: 0,
7
+ poolHitCount: {},
8
+ routeHitCount: {},
9
+ providerHitCount: {}
10
+ };
11
+ }
12
+ function createEmptyProviderBucket() {
13
+ return {
14
+ requestCount: 0,
15
+ successCount: 0,
16
+ errorCount: 0,
17
+ latencySumMs: 0,
18
+ minLatencyMs: Number.POSITIVE_INFINITY,
19
+ maxLatencyMs: 0,
20
+ usage: {
21
+ promptTokens: 0,
22
+ completionTokens: 0,
23
+ totalTokens: 0
24
+ }
25
+ };
26
+ }
27
+ function createEmptySnapshot() {
28
+ return {
29
+ router: {
30
+ global: createEmptyRouterBucket(),
31
+ byEntryEndpoint: {}
32
+ },
33
+ providers: {
34
+ global: createEmptyProviderBucket(),
35
+ byProviderKey: {},
36
+ byRoute: {},
37
+ byEntryEndpoint: {}
38
+ }
39
+ };
40
+ }
41
+ class NoopStatsCenter {
42
+ recordVirtualRouterHit() { }
43
+ recordProviderUsage() { }
44
+ getSnapshot() { return createEmptySnapshot(); }
45
+ async flushToDisk() { }
46
+ reset() { }
47
+ }
48
+ class DefaultStatsCenter {
49
+ snapshot = createEmptySnapshot();
50
+ dirty = false;
51
+ flushInFlight = false;
52
+ persistPath;
53
+ constructor(persistPath) {
54
+ if (persistPath === null) {
55
+ this.persistPath = null;
56
+ }
57
+ else if (typeof persistPath === 'string' && persistPath.trim().length) {
58
+ this.persistPath = persistPath.trim();
59
+ }
60
+ else {
61
+ const base = path.join(os.homedir(), '.routecodex', 'stats');
62
+ this.persistPath = path.join(base, 'stats.json');
63
+ }
64
+ }
65
+ recordVirtualRouterHit(ev) {
66
+ if (!ev || !ev.routeName || !ev.providerKey) {
67
+ return;
68
+ }
69
+ const snap = this.snapshot;
70
+ this.applyRouterHitToBucket(snap.router.global, ev);
71
+ const entryKey = ev.entryEndpoint || 'unknown';
72
+ if (!snap.router.byEntryEndpoint[entryKey]) {
73
+ snap.router.byEntryEndpoint[entryKey] = createEmptyRouterBucket();
74
+ }
75
+ this.applyRouterHitToBucket(snap.router.byEntryEndpoint[entryKey], ev);
76
+ this.dirty = true;
77
+ }
78
+ recordProviderUsage(ev) {
79
+ if (!ev || !ev.providerKey || !ev.providerType) {
80
+ return;
81
+ }
82
+ const snap = this.snapshot;
83
+ this.applyProviderUsageToBucket(snap.providers.global, ev);
84
+ const providerKey = ev.providerKey;
85
+ if (!snap.providers.byProviderKey[providerKey]) {
86
+ snap.providers.byProviderKey[providerKey] = createEmptyProviderBucket();
87
+ }
88
+ this.applyProviderUsageToBucket(snap.providers.byProviderKey[providerKey], ev);
89
+ const routeKey = ev.routeName || 'unknown';
90
+ if (!snap.providers.byRoute[routeKey]) {
91
+ snap.providers.byRoute[routeKey] = createEmptyProviderBucket();
92
+ }
93
+ this.applyProviderUsageToBucket(snap.providers.byRoute[routeKey], ev);
94
+ const entryKey = ev.entryEndpoint || 'unknown';
95
+ if (!snap.providers.byEntryEndpoint[entryKey]) {
96
+ snap.providers.byEntryEndpoint[entryKey] = createEmptyProviderBucket();
97
+ }
98
+ this.applyProviderUsageToBucket(snap.providers.byEntryEndpoint[entryKey], ev);
99
+ this.dirty = true;
100
+ }
101
+ getSnapshot() {
102
+ return this.snapshot;
103
+ }
104
+ reset() {
105
+ this.snapshot = createEmptySnapshot();
106
+ this.dirty = false;
107
+ }
108
+ async flushToDisk() {
109
+ if (!this.persistPath || !this.dirty || this.flushInFlight) {
110
+ return;
111
+ }
112
+ this.flushInFlight = true;
113
+ try {
114
+ const dir = path.dirname(this.persistPath);
115
+ await fs.mkdir(dir, { recursive: true });
116
+ const payload = JSON.stringify(this.snapshot, null, 2);
117
+ await fs.writeFile(this.persistPath, payload, 'utf-8');
118
+ this.dirty = false;
119
+ }
120
+ catch {
121
+ // ignore persistence errors
122
+ }
123
+ finally {
124
+ this.flushInFlight = false;
125
+ }
126
+ }
127
+ applyRouterHitToBucket(bucket, ev) {
128
+ bucket.requestCount += 1;
129
+ if (ev.pool) {
130
+ bucket.poolHitCount[ev.pool] = (bucket.poolHitCount[ev.pool] || 0) + 1;
131
+ }
132
+ if (ev.routeName) {
133
+ bucket.routeHitCount[ev.routeName] = (bucket.routeHitCount[ev.routeName] || 0) + 1;
134
+ }
135
+ if (ev.providerKey) {
136
+ bucket.providerHitCount[ev.providerKey] = (bucket.providerHitCount[ev.providerKey] || 0) + 1;
137
+ }
138
+ }
139
+ applyProviderUsageToBucket(bucket, ev) {
140
+ bucket.requestCount += 1;
141
+ if (ev.success) {
142
+ bucket.successCount += 1;
143
+ }
144
+ else {
145
+ bucket.errorCount += 1;
146
+ }
147
+ if (Number.isFinite(ev.latencyMs) && ev.latencyMs >= 0) {
148
+ bucket.latencySumMs += ev.latencyMs;
149
+ if (ev.latencyMs < bucket.minLatencyMs) {
150
+ bucket.minLatencyMs = ev.latencyMs;
151
+ }
152
+ if (ev.latencyMs > bucket.maxLatencyMs) {
153
+ bucket.maxLatencyMs = ev.latencyMs;
154
+ }
155
+ }
156
+ if (typeof ev.promptTokens === 'number' && Number.isFinite(ev.promptTokens)) {
157
+ bucket.usage.promptTokens += Math.max(0, ev.promptTokens);
158
+ }
159
+ if (typeof ev.completionTokens === 'number' && Number.isFinite(ev.completionTokens)) {
160
+ bucket.usage.completionTokens += Math.max(0, ev.completionTokens);
161
+ }
162
+ if (typeof ev.totalTokens === 'number' && Number.isFinite(ev.totalTokens)) {
163
+ bucket.usage.totalTokens += Math.max(0, ev.totalTokens);
164
+ }
165
+ else {
166
+ const derivedTotal = (typeof ev.promptTokens === 'number' ? Math.max(0, ev.promptTokens) : 0) +
167
+ (typeof ev.completionTokens === 'number' ? Math.max(0, ev.completionTokens) : 0);
168
+ bucket.usage.totalTokens += derivedTotal;
169
+ }
170
+ }
171
+ }
172
+ let instance = null;
173
+ function resolveEnableFlag(defaultValue) {
174
+ const raw = process.env.ROUTECODEX_STATS;
175
+ if (!raw)
176
+ return defaultValue;
177
+ const normalized = raw.trim().toLowerCase();
178
+ if (['1', 'true', 'yes', 'on'].includes(normalized))
179
+ return true;
180
+ if (['0', 'false', 'no', 'off'].includes(normalized))
181
+ return false;
182
+ return defaultValue;
183
+ }
184
+ function printStatsToConsole(snapshot) {
185
+ const router = snapshot.router;
186
+ const providers = snapshot.providers;
187
+ const totalRequests = router.global.requestCount;
188
+ const poolEntries = Object.entries(router.global.poolHitCount);
189
+ const providerEntries = Object.entries(router.global.providerHitCount);
190
+ // Router summary
191
+ // eslint-disable-next-line no-console
192
+ console.log('[stats] Virtual Router:');
193
+ // eslint-disable-next-line no-console
194
+ console.log(` total requests: ${totalRequests}`);
195
+ if (poolEntries.length) {
196
+ // eslint-disable-next-line no-console
197
+ console.log(' pools:');
198
+ for (const [pool, count] of poolEntries) {
199
+ const ratio = totalRequests > 0 ? (count / totalRequests) * 100 : 0;
200
+ // eslint-disable-next-line no-console
201
+ console.log(` ${pool}: ${count} (${ratio.toFixed(2)}%)`);
202
+ }
203
+ }
204
+ if (providerEntries.length) {
205
+ // eslint-disable-next-line no-console
206
+ console.log(' top providers:');
207
+ const sorted = providerEntries.sort((a, b) => b[1] - a[1]).slice(0, 5);
208
+ for (const [providerKey, count] of sorted) {
209
+ // eslint-disable-next-line no-console
210
+ console.log(` ${providerKey}: ${count}`);
211
+ }
212
+ }
213
+ const globalProvider = providers.global;
214
+ const totalProviderRequests = globalProvider.requestCount;
215
+ const avgLatency = globalProvider.successCount > 0 ? globalProvider.latencySumMs / globalProvider.successCount : 0;
216
+ // Provider summary
217
+ // eslint-disable-next-line no-console
218
+ console.log('\n[stats] Providers:');
219
+ // eslint-disable-next-line no-console
220
+ console.log(` total requests : ${totalProviderRequests} (success=${globalProvider.successCount}, error=${globalProvider.errorCount})`);
221
+ // eslint-disable-next-line no-console
222
+ console.log(` avg latency : ${avgLatency.toFixed(1)} ms`);
223
+ // eslint-disable-next-line no-console
224
+ console.log(` total tokens : prompt=${globalProvider.usage.promptTokens} completion=${globalProvider.usage.completionTokens} total=${globalProvider.usage.totalTokens}`);
225
+ }
226
+ export function initStatsCenter(options) {
227
+ if (instance) {
228
+ return instance;
229
+ }
230
+ const enabled = resolveEnableFlag(options?.enable ?? true);
231
+ if (!enabled) {
232
+ instance = new NoopStatsCenter();
233
+ return instance;
234
+ }
235
+ const center = new DefaultStatsCenter(options?.persistPath);
236
+ instance = center;
237
+ const autoPrint = options?.autoPrintOnExit !== false;
238
+ if (autoPrint && typeof process !== 'undefined' && typeof process.on === 'function') {
239
+ const handler = async () => {
240
+ try {
241
+ await center.flushToDisk();
242
+ }
243
+ catch {
244
+ // ignore
245
+ }
246
+ try {
247
+ const snapshot = center.getSnapshot();
248
+ printStatsToConsole(snapshot);
249
+ }
250
+ catch {
251
+ // ignore
252
+ }
253
+ };
254
+ try {
255
+ process.once('beforeExit', handler);
256
+ }
257
+ catch {
258
+ // ignore
259
+ }
260
+ try {
261
+ process.once('SIGINT', handler);
262
+ }
263
+ catch {
264
+ // ignore
265
+ }
266
+ try {
267
+ process.once('SIGTERM', handler);
268
+ }
269
+ catch {
270
+ // ignore
271
+ }
272
+ }
273
+ return instance;
274
+ }
275
+ export function getStatsCenter() {
276
+ if (!instance) {
277
+ return initStatsCenter();
278
+ }
279
+ return instance;
280
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.203",
3
+ "version": "0.6.375",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/rcc",
3
- "version": "0.89.333",
3
+ "version": "0.89.524",
4
4
  "description": "Multi-provider OpenAI proxy server with anthropic/responses/chat support (dev)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -126,7 +126,7 @@
126
126
  },
127
127
  "dependencies": {
128
128
  "@anthropic-ai/sdk": "^0.65.0",
129
- "@jsonstudio/llms": "^0.6.203",
129
+ "@jsonstudio/llms": "^0.6.375",
130
130
  "@lmstudio/sdk": "^1.5.0",
131
131
  "@radix-ui/react-switch": "^1.2.6",
132
132
  "@types/socket.io": "^3.0.1",
@@ -59,11 +59,12 @@ try {
59
59
  if (args.name === 'routecodex' || args.name === 'rcc') {
60
60
  mutated.bundledDependencies = [];
61
61
  mutated.bundleDependencies = [];
62
+ const llmsVersion = original.dependencies?.['@jsonstudio/llms'] || '^0.6.230';
62
63
  mutated.dependencies = {
63
64
  ...(original.dependencies || {}),
64
65
  "ajv": original.dependencies?.ajv || "^8.17.1",
65
66
  "zod": original.dependencies?.zod || "^3.23.8",
66
- "@jsonstudio/llms": original.dependencies?.["@jsonstudio/llms"] || "^0.4.3"
67
+ "@jsonstudio/llms": llmsVersion
67
68
  };
68
69
  }
69
70
  fs.writeFileSync(pkgPath, JSON.stringify(mutated, null, 2));
@@ -7,10 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
8
  const PROJECT_ROOT = path.resolve(__dirname, '..');
9
9
  const PACK_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'pack-mode.mjs');
10
- const packageJson = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf-8'));
11
- const version = packageJson.version;
12
- const tarballName = `jsonstudio-rcc-${version}.tgz`;
13
- const tarballPath = path.join(PROJECT_ROOT, tarballName);
10
+ const pkgPath = path.join(PROJECT_ROOT, 'package.json');
14
11
 
15
12
  function run(command, args, options = {}) {
16
13
  const res = spawnSync(command, args, { stdio: 'inherit', ...options });
@@ -20,11 +17,30 @@ function run(command, args, options = {}) {
20
17
  }
21
18
 
22
19
  try {
20
+ // 1) 使用 release 模式构建 dist(依赖 npm 上的 @jsonstudio/llms)
21
+ run('npm', ['run', 'build:min'], {
22
+ cwd: PROJECT_ROOT,
23
+ env: { ...process.env, BUILD_MODE: 'release' }
24
+ });
25
+
26
+ // 2) 通过 pack-mode 生成 rcc tarball(内部会临时切换 package.json.name/bin 并确保 llms 为 release 包)
23
27
  run(process.execPath, [PACK_SCRIPT, '--name', '@jsonstudio/rcc', '--bin', 'rcc'], { cwd: PROJECT_ROOT });
28
+
29
+ // 构建过程中版本号可能被 bump(gen-build-info 会 auto-bump),因此需要在 pack 之后重新读取版本号
30
+ const updatedPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
31
+ const version = updatedPkg.version;
32
+ const tarballName = `jsonstudio-rcc-${version}.tgz`;
33
+ const tarballPath = path.join(PROJECT_ROOT, tarballName);
34
+
24
35
  if (!fs.existsSync(tarballPath)) {
25
36
  throw new Error(`tarball not found: ${tarballPath}`);
26
37
  }
38
+
39
+ // 3) 发布 npm 包
27
40
  run('npm', ['publish', tarballName], { cwd: PROJECT_ROOT });
41
+
42
+ // 4) pack-mode 会在内部检测 dev 链接并调用 ensure-llmswitch-mode 恢复 dev 模式,
43
+ // 因此此处不再额外修改 BUILD_MODE 或重新 link。后续本地如需 dev build,可单独运行 `npm run build:dev`。
28
44
  } catch (err) {
29
45
  console.error('[publish-rcc] failed:', err.message);
30
46
  process.exit(1);
@@ -13,7 +13,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
13
13
 
14
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
15
  const ROOT = path.resolve(__dirname, '../..');
16
- const CORE_DIST = path.join(ROOT, 'sharedmodule', 'llmswitch-core', 'dist', 'v2', 'router', 'virtual-router');
16
+ const CORE_DIST = path.join(ROOT, 'sharedmodule', 'llmswitch-core', 'dist', 'router', 'virtual-router');
17
17
 
18
18
  async function loadCoreModule(rel) {
19
19
  const file = path.join(CORE_DIST, rel);
@@ -21,16 +21,19 @@ async function loadCoreModule(rel) {
21
21
  }
22
22
 
23
23
  const { VirtualRouterEngine } = await loadCoreModule('engine.js');
24
+ const { bootstrapVirtualRouterConfig } = await loadCoreModule('bootstrap.js');
24
25
  const { providerErrorCenter } = await loadCoreModule('error-center.js');
25
26
  const { VirtualRouterError } = await loadCoreModule('types.js');
26
27
 
27
28
  function parseArgs(argv) {
28
- const args = { out: null };
29
+ const args = { out: null, config: null };
29
30
  const list = [...argv];
30
31
  while (list.length) {
31
32
  const cur = list.shift();
32
33
  if (cur === '--out' || cur === '--output') {
33
34
  args.out = list.shift() || null;
35
+ } else if (cur === '--config') {
36
+ args.config = list.shift() || null;
34
37
  } else if (cur === '--help' || cur === '-h') {
35
38
  args.help = true;
36
39
  }
@@ -57,9 +60,18 @@ function createRouterConfig() {
57
60
  'charlie.sim-model': buildProviderProfile('charlie.sim-model', 'https://charlie.local/v1')
58
61
  };
59
62
  const routing = {
60
- default: Object.keys(providers),
61
- coding: ['bravo.sim-model', 'charlie.sim-model'],
62
- thinking: ['charlie.sim-model', 'alpha.sim-model']
63
+ default: [
64
+ { id: 'default-primary', priority: 200, targets: ['alpha.sim-model', 'bravo.sim-model'] },
65
+ { id: 'default-backup', backup: true, priority: 100, targets: ['charlie.sim-model'] }
66
+ ],
67
+ coding: [
68
+ { id: 'coding-primary', priority: 200, targets: ['bravo.sim-model', 'charlie.sim-model'] },
69
+ { id: 'coding-backup', backup: true, priority: 100, targets: ['alpha.sim-model'] }
70
+ ],
71
+ thinking: [
72
+ { id: 'thinking-primary', priority: 200, targets: ['charlie.sim-model'] },
73
+ { id: 'thinking-backup', backup: true, priority: 100, targets: ['bravo.sim-model'] }
74
+ ]
63
75
  };
64
76
  return {
65
77
  routing,
@@ -227,16 +239,128 @@ async function scenarioScheduler(sim) {
227
239
  sim.runRoute('thinking');
228
240
  }
229
241
 
242
+ async function scenarioPriorityPools(sim) {
243
+ const first = sim.runRoute('thinking');
244
+ if (first !== 'charlie.sim-model') {
245
+ throw new Error(`expected primary thinking pool hit charlie.sim-model, got ${first}`);
246
+ }
247
+ sim.engine.handleProviderFailure({
248
+ providerKey: 'charlie.sim-model',
249
+ reason: 'priority-test',
250
+ fatal: true,
251
+ affectsHealth: true,
252
+ cooldownOverrideMs: 60_000
253
+ });
254
+ const second = sim.runRoute('thinking');
255
+ if (second !== 'bravo.sim-model') {
256
+ throw new Error(`expected thinking backup pool hit bravo.sim-model, got ${second}`);
257
+ }
258
+ }
259
+
230
260
  async function scenarioRoutingDirectives(sim) {
231
261
  sim.runRoute('baseline', '普通请求');
232
262
  sim.runRoute('forced-thinking', '请仔细分析这个问题 <**thinking**>');
233
263
  sim.runRoute('forced-provider', '请强制使用这个provider <**charlie.sim-model**> 来回答');
234
264
  }
235
265
 
266
+ async function scenarioRealConfig(configPath) {
267
+ const resolvedPath = path.resolve(configPath);
268
+ console.log(`\n=== Scenario: real-config (${resolvedPath}) ===`);
269
+ const source = JSON.parse(await fs.readFile(resolvedPath, 'utf-8'));
270
+ const section = source.virtualrouter && typeof source.virtualrouter === 'object' ? source.virtualrouter : source;
271
+ const { config } = bootstrapVirtualRouterConfig(section);
272
+ const engine = new VirtualRouterEngine();
273
+ engine.initialize(config);
274
+ const thinkingPools = config.routing.thinking ?? [];
275
+ const primaryPools = thinkingPools.filter((tier) => !tier.backup);
276
+ const backupPools = thinkingPools.filter((tier) => tier.backup);
277
+
278
+ const runThinking = (label) => {
279
+ const reqId = `real-config-${label}-${Date.now()}`;
280
+ const request = {
281
+ model: 'gpt-5.2-codex',
282
+ messages: [
283
+ { role: 'system', content: 'diagnostic' },
284
+ { role: 'user', content: `深入思考:${label}` }
285
+ ]
286
+ };
287
+ const metadata = {
288
+ requestId: reqId,
289
+ entryEndpoint: '/v1/responses',
290
+ processMode: 'chat',
291
+ stream: false,
292
+ direction: 'request',
293
+ providerProtocol: 'openai-responses'
294
+ };
295
+ const result = engine.route(request, metadata);
296
+ console.log(
297
+ JSON.stringify(
298
+ {
299
+ stage: 'real-config-route',
300
+ label,
301
+ providerKey: result.decision.providerKey,
302
+ route: result.decision.routeName,
303
+ poolId: result.decision.poolId
304
+ },
305
+ null,
306
+ 2
307
+ )
308
+ );
309
+ return result.decision.providerKey;
310
+ };
311
+
312
+ const markUnavailable = (targets, reason) => {
313
+ for (const key of targets || []) {
314
+ engine.handleProviderFailure({
315
+ providerKey: key,
316
+ fatal: true,
317
+ affectsHealth: true,
318
+ reason,
319
+ cooldownOverrideMs: 60_000
320
+ });
321
+ }
322
+ };
323
+
324
+ // Primary hit
325
+ const firstProvider = runThinking('primary-hit');
326
+ const primaryTargets = primaryPools.flatMap((pool) => pool.targets ?? []);
327
+ const backupTargets = backupPools.flatMap((pool) => pool.targets ?? []);
328
+ if (primaryTargets.length === 0) {
329
+ console.warn('[real-config] No explicit primary thinking pool configured.');
330
+ } else if (!primaryTargets.includes(firstProvider)) {
331
+ console.warn('[real-config] First provider is not part of primary tier:', firstProvider);
332
+ }
333
+
334
+ // Drain primary
335
+ markUnavailable(primaryTargets, 'primary-exhausted');
336
+ const secondProvider = runThinking('backup-hit');
337
+ if (backupTargets.length && !backupTargets.includes(secondProvider)) {
338
+ throw new Error(
339
+ `[real-config] Expected backup pool provider after draining primary, got ${secondProvider}`
340
+ );
341
+ }
342
+
343
+ // Drain backup -> should fall to default
344
+ markUnavailable(backupTargets, 'backup-exhausted');
345
+ const thirdProvider = runThinking('default-fallback');
346
+ const usedRoute = engine.getStatus().routes;
347
+ console.log(
348
+ JSON.stringify(
349
+ {
350
+ stage: 'real-config-summary',
351
+ thirdProvider,
352
+ routes: usedRoute
353
+ },
354
+ null,
355
+ 2
356
+ )
357
+ );
358
+ }
359
+
236
360
  async function main() {
237
361
  const args = parseArgs(process.argv.slice(2));
238
362
  if (args.help) {
239
- console.log('Usage: node scripts/tests/virtual-router-health.mjs [--out summary.json]');
363
+ console.log('Usage: node scripts/tests/virtual-router-health.mjs [--out summary.json] [--config ~/.routecodex/config.json]');
240
364
  return;
241
365
  }
242
366
 
@@ -247,6 +371,7 @@ async function main() {
247
371
  ['upstream', scenarioUpstream],
248
372
  ['timeout', scenarioTimeout],
249
373
  ['scheduler', scenarioScheduler],
374
+ ['priority-pools', scenarioPriorityPools],
250
375
  ['routing-directives', scenarioRoutingDirectives]
251
376
  ];
252
377
 
@@ -260,6 +385,16 @@ async function main() {
260
385
  });
261
386
  }
262
387
 
388
+ if (args.config) {
389
+ try {
390
+ await scenarioRealConfig(args.config);
391
+ summary.push({ name: 'real-config', status: 'ok', lastHealth: null });
392
+ } catch (error) {
393
+ console.error('[real-config] failed:', error);
394
+ summary.push({ name: 'real-config', status: 'failed', error: error?.message || String(error), lastHealth: null });
395
+ }
396
+ }
397
+
263
398
  if (args.out) {
264
399
  const outFile = path.resolve(process.cwd(), args.out);
265
400
  await fs.mkdir(path.dirname(outFile), { recursive: true });
File without changes
@@ -1,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=replay-request.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"replay-request.js","sourceRoot":"","sources":["../../src/tools/replay-request.ts"],"names":[],"mappings":""}