@jsonstudio/rcc 0.89.333 → 0.89.548

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 (191) hide show
  1. package/dist/build-info.js +3 -3
  2. package/dist/build-info.js.map +1 -1
  3. package/dist/cli.js +110 -1
  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 +20 -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 +337 -25
  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/base-provider.d.ts +2 -0
  27. package/dist/providers/core/runtime/base-provider.js +35 -1
  28. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  29. package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
  30. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  31. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  32. package/dist/providers/core/runtime/http-request-executor.js +75 -1
  33. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  34. package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
  35. package/dist/providers/core/runtime/http-transport-provider.js +60 -2
  36. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  37. package/dist/providers/core/runtime/iflow-http-provider.d.ts +4 -0
  38. package/dist/providers/core/runtime/iflow-http-provider.js +28 -0
  39. package/dist/providers/core/runtime/iflow-http-provider.js.map +1 -1
  40. package/dist/providers/core/runtime/rate-limit-manager.d.ts +30 -0
  41. package/dist/providers/core/runtime/rate-limit-manager.js +136 -0
  42. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -0
  43. package/dist/providers/core/runtime/responses-provider.js +8 -3
  44. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  45. package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
  46. package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
  47. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
  48. package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
  49. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  50. package/dist/providers/core/utils/http-client.js +2 -1
  51. package/dist/providers/core/utils/http-client.js.map +1 -1
  52. package/dist/providers/core/utils/provider-error-reporter.js +31 -5
  53. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  54. package/dist/providers/core/utils/provider-type-utils.js +1 -1
  55. package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
  56. package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
  57. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  58. package/dist/server/handlers/sse-dispatcher.js +22 -2
  59. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  60. package/dist/server/runtime/http-server/index.d.ts +9 -0
  61. package/dist/server/runtime/http-server/index.js +512 -144
  62. package/dist/server/runtime/http-server/index.js.map +1 -1
  63. package/dist/server/runtime/http-server/provider-utils.js +1 -1
  64. package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
  65. package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
  66. package/dist/server/runtime/http-server/request-executor.js +553 -159
  67. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  68. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  69. package/dist/server/runtime/http-server/routes.js +29 -0
  70. package/dist/server/runtime/http-server/routes.js.map +1 -1
  71. package/dist/server/runtime/http-server/runtime-manager.js +33 -0
  72. package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
  73. package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
  74. package/dist/server/utils/utf8-chunk-buffer.js +132 -0
  75. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
  76. package/dist/token-daemon/history-store.d.ts +75 -0
  77. package/dist/token-daemon/history-store.js +207 -0
  78. package/dist/token-daemon/history-store.js.map +1 -0
  79. package/dist/token-daemon/index.d.ts +7 -0
  80. package/dist/token-daemon/index.js +336 -0
  81. package/dist/token-daemon/index.js.map +1 -0
  82. package/dist/token-daemon/server-utils.d.ts +33 -0
  83. package/dist/token-daemon/server-utils.js +155 -0
  84. package/dist/token-daemon/server-utils.js.map +1 -0
  85. package/dist/token-daemon/token-daemon.d.ts +23 -0
  86. package/dist/token-daemon/token-daemon.js +249 -0
  87. package/dist/token-daemon/token-daemon.js.map +1 -0
  88. package/dist/token-daemon/token-types.d.ts +44 -0
  89. package/dist/token-daemon/token-types.js +18 -0
  90. package/dist/token-daemon/token-types.js.map +1 -0
  91. package/dist/token-daemon/token-utils.d.ts +17 -0
  92. package/dist/token-daemon/token-utils.js +153 -0
  93. package/dist/token-daemon/token-utils.js.map +1 -0
  94. package/dist/token-portal/local-token-portal.d.ts +1 -0
  95. package/dist/token-portal/local-token-portal.js +89 -0
  96. package/dist/token-portal/local-token-portal.js.map +1 -0
  97. package/dist/token-portal/render.d.ts +10 -0
  98. package/dist/token-portal/render.js +56 -0
  99. package/dist/token-portal/render.js.map +1 -0
  100. package/dist/tools/semantic-replay.js +7 -6
  101. package/dist/tools/semantic-replay.js.map +1 -1
  102. package/dist/utils/error-handler-registry.d.ts +36 -0
  103. package/dist/utils/error-handler-registry.js +93 -7
  104. package/dist/utils/error-handler-registry.js.map +1 -1
  105. package/node_modules/@jsonstudio/llms/README.md +2 -0
  106. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
  107. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  108. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  109. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  110. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
  111. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  112. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  113. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
  114. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
  115. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
  116. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  117. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
  118. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
  119. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  120. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  121. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  122. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
  123. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
  124. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  125. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
  126. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
  127. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
  128. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
  129. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
  130. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
  131. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
  132. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
  133. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  134. package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
  135. package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
  136. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
  137. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
  138. package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
  139. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
  140. package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
  141. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  142. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
  143. package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
  144. package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
  145. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
  146. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
  147. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
  148. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
  149. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
  150. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
  151. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
  152. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
  153. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
  154. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
  155. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
  156. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
  157. package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
  158. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
  159. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
  160. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
  161. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
  162. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
  163. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
  164. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
  165. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
  166. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
  167. package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
  168. package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
  169. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
  170. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
  171. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
  172. package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
  173. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
  174. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
  175. package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
  176. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  177. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
  178. package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
  179. package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
  180. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
  181. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
  182. package/node_modules/@jsonstudio/llms/package.json +1 -1
  183. package/package.json +3 -2
  184. package/scripts/pack-mode.mjs +2 -1
  185. package/scripts/publish-rcc.mjs +20 -4
  186. package/scripts/test-iflow-web-search.mjs +141 -0
  187. package/scripts/test-iflow.mjs +93 -1
  188. package/scripts/tests/virtual-router-health.mjs +141 -6
  189. package/dist/tools/replay-request.d.ts +0 -0
  190. package/dist/tools/replay-request.js +0 -2
  191. 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.548",
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,8 @@
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
+ "@jsonstudio/rcc": "^0.89.524",
130
131
  "@lmstudio/sdk": "^1.5.0",
131
132
  "@radix-ui/react-switch": "^1.2.6",
132
133
  "@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);
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * IFlow web_search 直连测试脚本(不经过 RouteCodex pipeline)
4
+ *
5
+ * 目标:完全复用 iFlow CLI 内置 WebSearchTool 的请求协议,验证
6
+ * `https://apis.iflow.cn/v1/chat/retrieve` 能否正常返回搜索结果。
7
+ *
8
+ * 用法:
9
+ * node scripts/test-iflow-web-search.mjs
10
+ *
11
+ * 可选环境变量:
12
+ * IFLOW_SETTINGS_PATH 自定义 settings.json 路径(默认 ~/.iflow/settings.json)
13
+ * IFLOW_SEARCH_QUERY 搜索关键词(默认: "today international news 2025")
14
+ */
15
+
16
+ import fs from 'node:fs/promises';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+
20
+ async function main() {
21
+ const settingsPath =
22
+ process.env.IFLOW_SETTINGS_PATH ||
23
+ path.join(os.homedir(), '.iflow', 'settings.json');
24
+ const query =
25
+ process.env.IFLOW_SEARCH_QUERY || 'today international news 2025';
26
+
27
+ console.log(
28
+ `[iflow-web-search] Using settings: ${settingsPath}, query="${query}"`
29
+ );
30
+
31
+ const settingsRaw = await fs.readFile(settingsPath, 'utf-8');
32
+ const settings = JSON.parse(settingsRaw);
33
+
34
+ const baseUrl =
35
+ (typeof settings.baseUrl === 'string' && settings.baseUrl.trim()) ||
36
+ 'https://apis.iflow.cn/v1';
37
+ const searchApiKey =
38
+ (typeof settings.searchApiKey === 'string' &&
39
+ settings.searchApiKey.trim()) ||
40
+ (typeof settings.apiKey === 'string' && settings.apiKey.trim()) ||
41
+ null;
42
+
43
+ if (!searchApiKey) {
44
+ console.error(
45
+ '[iflow-web-search] ERROR: searchApiKey/apiKey not found in settings.json'
46
+ );
47
+ process.exit(1);
48
+ }
49
+
50
+ const url = `${baseUrl.replace(/\/$/, '')}/chat/retrieve`;
51
+
52
+ // Body 对齐 iFlow CLI WebSearchTool.executeInternal
53
+ const body = {
54
+ query,
55
+ history: {},
56
+ userId: 2,
57
+ userIp: '42.120.74.197',
58
+ appCode: 'SEARCH_CHATBOT',
59
+ chatId: Date.now(), // 使用时间戳作为 chatId 即可
60
+ phase: 'UNIFY',
61
+ enableQueryRewrite: false,
62
+ enableRetrievalSecurity: false,
63
+ enableIntention: false,
64
+ searchEngineList: ['GOOGLE', 'BING', 'SCHOLAR', 'AIPGC', 'PDF']
65
+ };
66
+
67
+ console.log(`[iflow-web-search] POST ${url}`);
68
+
69
+ const controller = new AbortController();
70
+ const timeoutMs = Number(process.env.IFLOW_SEARCH_TIMEOUT_MS || '30000');
71
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
72
+
73
+ try {
74
+ const res = await fetch(url, {
75
+ method: 'POST',
76
+ headers: {
77
+ Authorization: `Bearer ${searchApiKey}`,
78
+ 'Content-Type': 'application/json'
79
+ },
80
+ body: JSON.stringify(body),
81
+ signal: controller.signal
82
+ });
83
+
84
+ clearTimeout(timeoutId);
85
+
86
+ console.log(`[iflow-web-search] HTTP ${res.status}`);
87
+ const json = await res.json().catch(async () => {
88
+ const text = await res.text();
89
+ console.error('[iflow-web-search] Non-JSON response:', text.slice(0, 500));
90
+ throw new Error('Response is not valid JSON');
91
+ });
92
+
93
+ const dataArray = Array.isArray(json?.data) ? json.data : [];
94
+ console.log(
95
+ `[iflow-web-search] data.length = ${dataArray.length}, keys=${Object.keys(
96
+ json || {}
97
+ ).join(',')}`
98
+ );
99
+
100
+ if (!res.ok) {
101
+ console.error(
102
+ '[iflow-web-search] ERROR: upstream returned non-200 status',
103
+ JSON.stringify(json, null, 2).slice(0, 2000)
104
+ );
105
+ process.exit(1);
106
+ }
107
+
108
+ if (!dataArray.length) {
109
+ console.warn(
110
+ '[iflow-web-search] WARN: data array is empty, check query or account permissions.'
111
+ );
112
+ } else {
113
+ const first = dataArray[0] || {};
114
+ console.log(
115
+ '[iflow-web-search] First item:',
116
+ JSON.stringify(
117
+ {
118
+ title: first.title,
119
+ url: first.url,
120
+ time: first.time,
121
+ abstractInfo: first.abstractInfo
122
+ },
123
+ null,
124
+ 2
125
+ )
126
+ );
127
+ }
128
+
129
+ console.log('[iflow-web-search] ✅ connectivity OK');
130
+ } catch (error) {
131
+ clearTimeout(timeoutId);
132
+ console.error('[iflow-web-search] ❌ request failed:', error);
133
+ process.exit(1);
134
+ }
135
+ }
136
+
137
+ main().catch((error) => {
138
+ console.error('[iflow-web-search] ❌ unexpected error:', error);
139
+ process.exit(1);
140
+ });
141
+
@@ -32,7 +32,8 @@ const args = Object.fromEntries(process.argv.slice(2).map(kv => {
32
32
  const MODE = String(args.mode || process.env.MODE || 'proxy');
33
33
  const RC_BASE = String(process.env.RC_BASE || 'http://127.0.0.1:5506').replace(/\/$/, '');
34
34
  const RC_ENDPOINT = String(args.endpoint || process.env.RC_ENDPOINT || '/v1/chat/completions');
35
- const IFLOW_MODEL = String(process.env.IFLOW_MODEL || 'gpt-4o-mini');
35
+ // 默认模型更新为 iFlow-ROME-30BA3B,除非通过 IFLOW_MODEL 显式覆盖。
36
+ const IFLOW_MODEL = String(process.env.IFLOW_MODEL || 'iFlow-ROME-30BA3B');
36
37
  const TEXT = String(process.env.TEXT || 'hello from RouteCodex test');
37
38
  const RUN_TOOLS = !!args.tools;
38
39
  const CONFIG_PATH = expandHome(String(process.env.CONFIG || args.config || path.join(os.homedir(), '.routecodex', 'config', 'v2', 'iflow-only.json')));
@@ -170,6 +171,93 @@ async function requestUpstreamChat(oauth, token) {
170
171
  try { console.log(JSON.stringify(JSON.parse(body), null, 2)); } catch { console.log(body); }
171
172
  }
172
173
 
174
+ async function requestUpstreamWebSearch(oauth, token) {
175
+ const url = `${oauth.apiBase.replace(/\/$/, '')}/chat/completions`;
176
+ const payload = {
177
+ model: IFLOW_MODEL,
178
+ messages: [
179
+ {
180
+ role: 'system',
181
+ content: 'You are an up-to-date web search engine. Call the web_search tool to fetch current results, then answer based on the tool output.'
182
+ },
183
+ {
184
+ role: 'user',
185
+ content: `${TEXT}. 请先调用 web_search 工具检索相关信息,再根据搜索结果回答。`
186
+ }
187
+ ],
188
+ tools: [
189
+ {
190
+ type: 'function',
191
+ function: {
192
+ name: 'web_search',
193
+ description: 'Perform web search over the public internet and return up-to-date results.',
194
+ parameters: {
195
+ type: 'object',
196
+ properties: {
197
+ query: {
198
+ type: 'string',
199
+ description: 'Search query string.'
200
+ },
201
+ recency: {
202
+ type: 'string',
203
+ description: 'Optional recency filter such as "day", "week", or "month".'
204
+ },
205
+ count: {
206
+ type: 'integer',
207
+ minimum: 1,
208
+ maximum: 50,
209
+ description: 'Maximum number of search results to retrieve (1-50).'
210
+ }
211
+ },
212
+ required: ['query']
213
+ }
214
+ }
215
+ }
216
+ ],
217
+ tool_choice: {
218
+ type: 'function',
219
+ function: {
220
+ name: 'web_search'
221
+ }
222
+ },
223
+ stream: false
224
+ };
225
+
226
+ const res = await fetch(url, {
227
+ method: 'POST',
228
+ headers: {
229
+ 'Authorization': `Bearer ${token.access_token}`,
230
+ 'Content-Type': 'application/json'
231
+ },
232
+ body: JSON.stringify(payload)
233
+ });
234
+ const text = await res.text();
235
+ console.log(`[UPSTREAM][web_search] status=${res.status}`);
236
+ let json = null;
237
+ try {
238
+ json = JSON.parse(text);
239
+ console.log(JSON.stringify(json, null, 2));
240
+ } catch {
241
+ console.log(text);
242
+ throw new Error('iflow web_search returned non-JSON payload');
243
+ }
244
+ if (!res.ok) {
245
+ throw new Error(`iflow web_search failed: HTTP ${res.status}`);
246
+ }
247
+ const firstChoice = Array.isArray(json.choices) ? json.choices[0] : null;
248
+ const msg = firstChoice?.message || {};
249
+ const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
250
+ console.log(`[UPSTREAM][web_search] tool_calls=${toolCalls.length}`);
251
+ if (!toolCalls.length) {
252
+ console.warn('[UPSTREAM][web_search] no tool_calls returned, web_search tool may not be enabled for this model.');
253
+ } else {
254
+ const names = toolCalls
255
+ .map((tc) => (tc && tc.function && typeof tc.function.name === 'string' ? tc.function.name : ''))
256
+ .filter(Boolean);
257
+ console.log(`[UPSTREAM][web_search] tool names: ${names.join(', ')}`);
258
+ }
259
+ }
260
+
173
261
  async function requestProxyChat() {
174
262
  const url = `${RC_BASE}${RC_ENDPOINT}`;
175
263
  const payload = { model: IFLOW_MODEL, messages: [{ role: 'user', content: TEXT }], stream: false };
@@ -276,6 +364,10 @@ async function main() {
276
364
  await requestUpstreamChat(oauth, token);
277
365
  return;
278
366
  }
367
+ if (MODE === 'websearch') {
368
+ await requestUpstreamWebSearch(oauth, token);
369
+ return;
370
+ }
279
371
  if (RUN_TOOLS) {
280
372
  await requestProxyChat(); // warmup
281
373
  await runResponsesToolLoop();