@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.
- package/dist/build-info.js +3 -3
- package/dist/build-info.js.map +1 -1
- package/dist/cli.js +110 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/token-daemon.d.ts +2 -0
- package/dist/commands/token-daemon.js +183 -0
- package/dist/commands/token-daemon.js.map +1 -0
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +1 -1
- package/dist/modules/llmswitch/bridge.js +3 -2
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.js +3 -1
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +337 -25
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/core/config/oauth-flows.d.ts +23 -0
- package/dist/providers/core/config/oauth-flows.js +92 -5
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.js +9 -3
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +18 -10
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +2 -0
- package/dist/providers/core/runtime/base-provider.js +35 -1
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
- package/dist/providers/core/runtime/http-request-executor.js +75 -1
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
- package/dist/providers/core/runtime/http-transport-provider.js +60 -2
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/iflow-http-provider.d.ts +4 -0
- package/dist/providers/core/runtime/iflow-http-provider.js +28 -0
- package/dist/providers/core/runtime/iflow-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +30 -0
- package/dist/providers/core/runtime/rate-limit-manager.js +136 -0
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -0
- package/dist/providers/core/runtime/responses-provider.js +8 -3
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
- package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
- package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +2 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +31 -5
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +1 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/server/handlers/sse-dispatcher.js +22 -2
- package/dist/server/handlers/sse-dispatcher.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +9 -0
- package/dist/server/runtime/http-server/index.js +512 -144
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.js +1 -1
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
- package/dist/server/runtime/http-server/request-executor.js +553 -159
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +5 -0
- package/dist/server/runtime/http-server/routes.js +29 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/runtime-manager.js +33 -0
- package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
- package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
- package/dist/server/utils/utf8-chunk-buffer.js +132 -0
- package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
- package/dist/token-daemon/history-store.d.ts +75 -0
- package/dist/token-daemon/history-store.js +207 -0
- package/dist/token-daemon/history-store.js.map +1 -0
- package/dist/token-daemon/index.d.ts +7 -0
- package/dist/token-daemon/index.js +336 -0
- package/dist/token-daemon/index.js.map +1 -0
- package/dist/token-daemon/server-utils.d.ts +33 -0
- package/dist/token-daemon/server-utils.js +155 -0
- package/dist/token-daemon/server-utils.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +23 -0
- package/dist/token-daemon/token-daemon.js +249 -0
- package/dist/token-daemon/token-daemon.js.map +1 -0
- package/dist/token-daemon/token-types.d.ts +44 -0
- package/dist/token-daemon/token-types.js +18 -0
- package/dist/token-daemon/token-types.js.map +1 -0
- package/dist/token-daemon/token-utils.d.ts +17 -0
- package/dist/token-daemon/token-utils.js +153 -0
- package/dist/token-daemon/token-utils.js.map +1 -0
- package/dist/token-portal/local-token-portal.d.ts +1 -0
- package/dist/token-portal/local-token-portal.js +89 -0
- package/dist/token-portal/local-token-portal.js.map +1 -0
- package/dist/token-portal/render.d.ts +10 -0
- package/dist/token-portal/render.js +56 -0
- package/dist/token-portal/render.js.map +1 -0
- package/dist/tools/semantic-replay.js +7 -6
- package/dist/tools/semantic-replay.js.map +1 -1
- package/dist/utils/error-handler-registry.d.ts +36 -0
- package/dist/utils/error-handler-registry.js +93 -7
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/README.md +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
- package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
- package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
- package/node_modules/@jsonstudio/llms/package.json +1 -1
- package/package.json +3 -2
- package/scripts/pack-mode.mjs +2 -1
- package/scripts/publish-rcc.mjs +20 -4
- package/scripts/test-iflow-web-search.mjs +141 -0
- package/scripts/test-iflow.mjs +93 -1
- package/scripts/tests/virtual-router-health.mjs +141 -6
- package/dist/tools/replay-request.d.ts +0 -0
- package/dist/tools/replay-request.js +0 -2
- 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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonstudio/rcc",
|
|
3
|
-
"version": "0.89.
|
|
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.
|
|
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",
|
package/scripts/pack-mode.mjs
CHANGED
|
@@ -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":
|
|
67
|
+
"@jsonstudio/llms": llmsVersion
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
fs.writeFileSync(pkgPath, JSON.stringify(mutated, null, 2));
|
package/scripts/publish-rcc.mjs
CHANGED
|
@@ -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
|
|
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
|
+
|
package/scripts/test-iflow.mjs
CHANGED
|
@@ -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
|
-
|
|
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();
|