@jsonstudio/rcc 0.89.1348 → 0.89.1457
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/README.md +51 -1427
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/config.js +3 -0
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/init.js +3 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/config/bundled-docs.js +2 -2
- package/dist/cli/config/bundled-docs.js.map +1 -1
- package/dist/cli/config/init-config.d.ts +2 -1
- package/dist/cli/config/init-config.js +33 -1
- package/dist/cli/config/init-config.js.map +1 -1
- package/dist/client/gemini/gemini-protocol-client.js +2 -1
- package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +39 -15
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/client/openai/chat-protocol-client.js +2 -1
- package/dist/client/openai/chat-protocol-client.js.map +1 -1
- package/dist/client/responses/responses-protocol-client.js +2 -1
- package/dist/client/responses/responses-protocol-client.js.map +1 -1
- package/dist/error-handling/quiet-error-handling-center.js +46 -8
- package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.events.js +4 -2
- package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
- package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -1
- package/dist/providers/auth/antigravity-userinfo-helper.js +25 -4
- package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/tokenfile-auth.d.ts +2 -0
- package/dist/providers/auth/tokenfile-auth.js +33 -1
- package/dist/providers/auth/tokenfile-auth.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
- package/dist/providers/core/config/camoufox-launcher.js +5 -0
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +7 -18
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +0 -5
- package/dist/providers/core/runtime/base-provider.js +26 -112
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +7 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +362 -93
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
- package/dist/providers/core/runtime/http-request-executor.js +110 -38
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
- package/dist/providers/core/runtime/http-transport-provider.js +80 -37
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
- package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +20 -43
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/server/handlers/handler-utils.js +5 -1
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +1 -1
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/index.js +68 -29
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +50 -6
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +4 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/utils/strip-internal-keys.d.ts +12 -0
- package/dist/utils/strip-internal-keys.js +28 -0
- package/dist/utils/strip-internal-keys.js.map +1 -0
- package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
- package/docs/antigravity-gemini-format-cleanup.md +102 -0
- package/docs/antigravity-routing-contract.md +31 -0
- package/docs/chat-semantic-expansion-plan.md +8 -6
- package/docs/glm-chat-completions.md +1 -1
- package/docs/servertool-framework.md +65 -0
- package/docs/v2-architecture/README.md +6 -8
- package/docs/verified-configs/README.md +60 -0
- package/docs/verified-configs/v0.45.0/README.md +244 -0
- package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
- package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
- package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
- package/package.json +17 -11
- package/scripts/build-core.mjs +3 -1
- package/scripts/ci/repo-sanity.mjs +138 -0
- package/scripts/mock-provider/run-regressions.mjs +157 -1
- package/scripts/run-bg.sh +0 -14
- package/scripts/tests/ci-jest.mjs +119 -0
- package/scripts/tools-dev/responses-debug-client/README.md +23 -0
- package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
- package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
- package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
- package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
- package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
- package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
- package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
- package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
- package/scripts/vendor-core.mjs +13 -3
- package/scripts/test-fc-responses.mjs +0 -66
- package/scripts/test-guidance.mjs +0 -100
- package/scripts/test-iflow-web-search.mjs +0 -141
- package/scripts/test-iflow.mjs +0 -379
- package/scripts/test-tool-exec.mjs +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonstudio/rcc",
|
|
3
|
-
"version": "0.89.
|
|
3
|
+
"version": "0.89.1457",
|
|
4
4
|
"description": "Multi-provider OpenAI proxy server with anthropic/responses/chat support (release)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -39,7 +39,12 @@
|
|
|
39
39
|
"dev": "tsx watch src/index.ts",
|
|
40
40
|
"jest:run": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
41
41
|
"test": "npm run test:routing-instructions && npm run mock:regressions",
|
|
42
|
-
"test:
|
|
42
|
+
"test:ci:jest": "node scripts/tests/ci-jest.mjs",
|
|
43
|
+
"test:ci:jest:coverage": "node scripts/tests/ci-jest.mjs --coverage",
|
|
44
|
+
"test:ci": "npm run test:ci:jest && npm run mock:regressions",
|
|
45
|
+
"test:ci:coverage": "npm run test:ci:jest:coverage",
|
|
46
|
+
"verify:repo-sanity": "node scripts/ci/repo-sanity.mjs",
|
|
47
|
+
"test:routing-instructions": "npm run jest:run -- --runTestsByPath tests/server/runtime/request-executor.single-attempt.spec.ts tests/server/runtime/executor-provider.retryable.spec.ts tests/providers/auth/tokenfile-auth.iflow.spec.ts tests/providers/core/runtime/gemini-cli-http-provider.unit.test.ts tests/providers/core/runtime/antigravity-quota-client.unit.test.ts tests/manager/quota/provider-quota-center.spec.ts tests/manager/quota/provider-quota-store.spec.ts tests/manager/quota/quota-manager-refresh.spec.ts tests/manager/quota/provider-quota-daemon-module.spec.ts tests/manager/quota/provider-key-normalization.spec.ts tests/server/http-server/daemon-admin.e2e.spec.ts tests/server/http-server/quota-view-injection.spec.ts tests/server/http-server/quota-refresh-triggers.e2e.spec.ts tests/server/http-server/hub-policy-injection.spec.ts tests/server/http-server/session-header-injection.spec.ts tests/server/http-server/session-dir.spec.ts tests/server/handlers/sse-timeout.spec.ts tests/utils/is-direct-execution.test.ts tests/utils/windows-netstat.test.ts tests/servertool/virtual-router-context-fallback.spec.ts tests/servertool/virtual-router-longcontext-fallback.spec.ts tests/servertool/virtual-router-series-cooldown.spec.ts tests/servertool/recursive-detection-guard.spec.ts tests/servertool/routing-instructions.spec.ts tests/servertool/stop-message-auto.spec.ts tests/servertool/stopmessage-session-scope.spec.ts tests/servertool/stopmessage-anthropic-stop-sequence.spec.ts tests/servertool/servertool-progress-logging.spec.ts tests/servertool/servertool-clock.spec.ts tests/sharedmodule/gemini-mapper-functioncall-args.spec.ts tests/sharedmodule/mcp-tool-descriptions.spec.ts tests/unified-hub/hub-v1-single-path-imports.spec.ts",
|
|
43
48
|
"test:cli": "npm run jest:run -- --runTestsByPath tests/cli/clean-command.spec.ts tests/cli/code-command.spec.ts tests/cli/config-command.spec.ts tests/cli/env-command.spec.ts tests/cli/env-output.spec.ts tests/cli/examples-command.spec.ts tests/cli/port-command.spec.ts tests/cli/port-utils.spec.ts tests/cli/restart-command.spec.ts tests/cli/smoke.spec.ts tests/cli/start-command.spec.ts tests/cli/status-command.spec.ts tests/cli/stop-command.spec.ts",
|
|
44
49
|
"test:watch": "npm run jest:run -- --watch",
|
|
45
50
|
"test:coverage": "npm run jest:run -- --coverage",
|
|
@@ -51,11 +56,11 @@
|
|
|
51
56
|
"test:lmstudio-dryrun": "node tests/lmstudio-tools-bidir-dry-run.mjs",
|
|
52
57
|
"test:unified-hub-shadow": "node scripts/tests/unified-hub-shadow-regression.mjs",
|
|
53
58
|
"test:unified-hub-responses-enforce": "node scripts/tests/unified-hub-responses-enforce-safe.mjs",
|
|
54
|
-
"lint": "eslint
|
|
55
|
-
"lint:fix": "eslint
|
|
56
|
-
"lint:strict": "eslint
|
|
59
|
+
"lint": "ESLINT_USE_FLAT_CONFIG=1 eslint \"src/**/*.ts\" --no-cache",
|
|
60
|
+
"lint:fix": "ESLINT_USE_FLAT_CONFIG=1 eslint \"src/**/*.ts\" --fix --no-cache",
|
|
61
|
+
"lint:strict": "ESLINT_USE_FLAT_CONFIG=1 eslint \"src/**/*.ts\" --max-warnings 0 --no-cache",
|
|
57
62
|
"clean": "rm -rf dist coverage",
|
|
58
|
-
"prebuild": "echo skip-lint",
|
|
63
|
+
"prebuild": "npm run verify:repo-sanity && echo skip-lint",
|
|
59
64
|
"prepare": "",
|
|
60
65
|
"postinstall": "chmod +x dist/cli.js || true",
|
|
61
66
|
"verify:apply-patch": "node scripts/verify-apply-patch.mjs",
|
|
@@ -65,6 +70,7 @@
|
|
|
65
70
|
"verify:e2e-gemini-followup-sample": "node scripts/verify-e2e-gemini-followup-sample.mjs",
|
|
66
71
|
"install:global": "./scripts/install-global.sh",
|
|
67
72
|
"install:release": "./scripts/install-release.sh",
|
|
73
|
+
"release:rcc": "npm run pack:rcc",
|
|
68
74
|
"audit:tool-text": "node scripts/audit-tool-text.mjs",
|
|
69
75
|
"start:bg": "bash scripts/run-bg.sh -- 'node dist/index.js'",
|
|
70
76
|
"start:fg": "bash scripts/run-fg-gtimeout.sh 12 -- 'node dist/index.js'",
|
|
@@ -81,10 +87,10 @@
|
|
|
81
87
|
"verify:sse-loop": "node scripts/verify-sse-loop.mjs",
|
|
82
88
|
"snapshot:inspect": "node scripts/snapshot-inspect.mjs",
|
|
83
89
|
"policy:report": "node scripts/policy-violations-report.mjs",
|
|
84
|
-
"debug:responses": "tsx tools/responses-debug-client/src/index.ts",
|
|
85
|
-
"debug:responses:lmstudio:text": "tsx tools/responses-debug-client/src/index.ts --file tools/responses-debug-client/payloads/
|
|
86
|
-
"debug:responses:lmstudio:tool": "tsx tools/responses-debug-client/src/index.ts --file tools/responses-debug-client/payloads/
|
|
87
|
-
"capture:responses:lmstudio": "node scripts/capture-responses-sse.mjs --file tools/responses-debug-client/payloads/
|
|
90
|
+
"debug:responses": "tsx scripts/tools-dev/responses-debug-client/src/index.ts",
|
|
91
|
+
"debug:responses:lmstudio:text": "tsx scripts/tools-dev/responses-debug-client/src/index.ts --file scripts/tools-dev/responses-debug-client/payloads/text.json --baseURL ${LMSTUDIO_BASEURL:-http://127.0.0.1:1234/v1}",
|
|
92
|
+
"debug:responses:lmstudio:tool": "tsx scripts/tools-dev/responses-debug-client/src/index.ts --file scripts/tools-dev/responses-debug-client/payloads/tool.json --baseURL ${LMSTUDIO_BASEURL:-http://127.0.0.1:1234/v1}",
|
|
93
|
+
"capture:responses:lmstudio": "node scripts/capture-responses-sse.mjs --file scripts/tools-dev/responses-debug-client/payloads/tool.json",
|
|
88
94
|
"llmswitch:ensure": "node scripts/ensure-llmswitch-mode.mjs",
|
|
89
95
|
"llmswitch:link": "node scripts/link-llmswitch.mjs",
|
|
90
96
|
"llmswitch:unlink": "node scripts/link-llmswitch.mjs unlink",
|
|
@@ -140,7 +146,7 @@
|
|
|
140
146
|
},
|
|
141
147
|
"dependencies": {
|
|
142
148
|
"@anthropic-ai/sdk": "^0.65.0",
|
|
143
|
-
"@jsonstudio/llms": "^0.6.
|
|
149
|
+
"@jsonstudio/llms": "^0.6.1354",
|
|
144
150
|
"@lmstudio/sdk": "^1.5.0",
|
|
145
151
|
"@radix-ui/react-switch": "^1.2.6",
|
|
146
152
|
"@types/socket.io": "^3.0.1",
|
package/scripts/build-core.mjs
CHANGED
|
@@ -13,7 +13,9 @@ const coreRoot = path.join(root, 'sharedmodule', 'llmswitch-core');
|
|
|
13
13
|
const outDir = path.join(coreRoot, 'dist');
|
|
14
14
|
const requiredOutputs = [
|
|
15
15
|
path.join(outDir, 'bridge', 'routecodex-adapter.js'),
|
|
16
|
-
path.join(outDir, 'conversion', 'hub', 'response', 'provider-response.js')
|
|
16
|
+
path.join(outDir, 'conversion', 'hub', 'response', 'provider-response.js'),
|
|
17
|
+
// RouteCodex runtime loads this module via llmswitch bridge; ensure dev builds produce it.
|
|
18
|
+
path.join(outDir, 'router', 'virtual-router', 'error-center.js')
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
function fail(msg){ console.error(`[build-core] ${msg}`); process.exit(2); }
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function runGit(args) {
|
|
6
|
+
const out = spawnSync('git', args, { encoding: 'utf8' });
|
|
7
|
+
if (out.status !== 0) {
|
|
8
|
+
throw new Error(`git ${args.join(' ')} failed: ${out.stderr || out.stdout}`);
|
|
9
|
+
}
|
|
10
|
+
return String(out.stdout || '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isIgnoredByGit(p) {
|
|
14
|
+
const out = spawnSync('git', ['check-ignore', '-q', p], { encoding: 'utf8' });
|
|
15
|
+
return out.status === 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function listRootEntries() {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
return fs
|
|
21
|
+
.readdirSync(cwd, { withFileTypes: true })
|
|
22
|
+
.filter((d) => d.name !== '.git')
|
|
23
|
+
.filter((d) => !isIgnoredByGit(d.name))
|
|
24
|
+
.map((d) => d.name)
|
|
25
|
+
.sort();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isForbiddenRootFile(p) {
|
|
29
|
+
const base = path.posix.basename(p);
|
|
30
|
+
const allow = new Set(['AGENTS.md', 'README.md', 'task.md']);
|
|
31
|
+
if (allow.has(base)) return false;
|
|
32
|
+
if (/^test-.*\.(mjs|js|ts|py)$/i.test(base)) return true;
|
|
33
|
+
if (/^debug-.*\.(mjs|js|ts)$/i.test(base)) return true;
|
|
34
|
+
if (/\.pid$/i.test(base)) return true;
|
|
35
|
+
if (/\.tgz$/i.test(base)) return true;
|
|
36
|
+
if (base === 'plan.md') return true;
|
|
37
|
+
if (base === 'task-fallback.md') return true;
|
|
38
|
+
if (base === 'task.archive.md') return true;
|
|
39
|
+
if (base === 'WARP.md') return true;
|
|
40
|
+
if (base === 'CLAUDE.md') return true;
|
|
41
|
+
if (/(_SUMMARY|_FIX)_/i.test(base) && base.toLowerCase().endsWith('.md')) return true;
|
|
42
|
+
// Disallow ad-hoc root markdown by default (docs belong under docs/).
|
|
43
|
+
if (base.toLowerCase().endsWith('.md')) return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isForbiddenTrackedPath(p) {
|
|
48
|
+
// Keep the rules narrow and explicit: this is an audit guard, not a policy engine.
|
|
49
|
+
if (p.startsWith('docs/archive/')) return true;
|
|
50
|
+
if (p.startsWith('scripts/dev/')) return true;
|
|
51
|
+
if (p.startsWith('scripts/test-') && p.endsWith('.mjs')) return true;
|
|
52
|
+
if (p.startsWith('tools/')) return true;
|
|
53
|
+
if (p.startsWith('replay/')) return true;
|
|
54
|
+
if (p.startsWith('servertool/')) return true;
|
|
55
|
+
if (p.startsWith('verified-configs/')) return true;
|
|
56
|
+
if (p.startsWith('interpreter/')) return true;
|
|
57
|
+
if (p.startsWith('exporters/')) return true;
|
|
58
|
+
if (p.startsWith('bin/')) return true;
|
|
59
|
+
if (p.startsWith('.npm-cache-local/')) return true;
|
|
60
|
+
if (p.startsWith('.claude/')) return true;
|
|
61
|
+
if (p.startsWith('.iflow/')) return true;
|
|
62
|
+
if (p === '.secrets.baseline') return true;
|
|
63
|
+
if (p.endsWith('/.DS_Store') || p === '.DS_Store') return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkRootLayout() {
|
|
68
|
+
// Fixed top-level layout. Adding new root entries requires an explicit policy change.
|
|
69
|
+
const allowed = new Set([
|
|
70
|
+
'eslint.config.js',
|
|
71
|
+
'.github',
|
|
72
|
+
'.gitignore',
|
|
73
|
+
'AGENTS.md',
|
|
74
|
+
'README.md',
|
|
75
|
+
'config',
|
|
76
|
+
'configsamples',
|
|
77
|
+
'dist',
|
|
78
|
+
'docs',
|
|
79
|
+
'jest.config.js',
|
|
80
|
+
'node_modules',
|
|
81
|
+
'package',
|
|
82
|
+
'package-lock.json',
|
|
83
|
+
'package.json',
|
|
84
|
+
'rcc',
|
|
85
|
+
'samples',
|
|
86
|
+
'scripts',
|
|
87
|
+
'sharedmodule',
|
|
88
|
+
'src',
|
|
89
|
+
'task.md',
|
|
90
|
+
'tests',
|
|
91
|
+
'tmp',
|
|
92
|
+
'tsconfig.json',
|
|
93
|
+
'vendor',
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const rootEntries = listRootEntries();
|
|
97
|
+
const unexpected = rootEntries.filter((name) => !allowed.has(name));
|
|
98
|
+
if (unexpected.length) {
|
|
99
|
+
console.error('[repo-sanity] unexpected root entries (top-level is fixed):');
|
|
100
|
+
for (const name of unexpected) console.error(`- ${name}`);
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function checkUntrackedNotIgnored() {
|
|
106
|
+
// Fail fast if anything new appears outside gitignore (anywhere in repo).
|
|
107
|
+
const out = runGit(['ls-files', '--others', '--exclude-standard']);
|
|
108
|
+
const paths = out
|
|
109
|
+
.split('\n')
|
|
110
|
+
.map((s) => s.trim())
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
if (paths.length) {
|
|
113
|
+
console.error('[repo-sanity] untracked files not ignored (add them to git or gitignore):');
|
|
114
|
+
for (const p of paths.slice(0, 200)) console.error(`- ${p}`);
|
|
115
|
+
if (paths.length > 200) console.error(`- ... (${paths.length - 200} more)`);
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const files = runGit(['ls-files']).split('\n').map((s) => s.trim()).filter(Boolean);
|
|
121
|
+
const forbidden = [];
|
|
122
|
+
for (const p of files) {
|
|
123
|
+
if (isForbiddenTrackedPath(p)) forbidden.push(p);
|
|
124
|
+
if (!p.includes('/') && isForbiddenRootFile(p)) forbidden.push(p);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (forbidden.length) {
|
|
128
|
+
console.error('[repo-sanity] forbidden tracked files detected:');
|
|
129
|
+
for (const p of Array.from(new Set(forbidden)).sort()) {
|
|
130
|
+
console.error(`- ${p}`);
|
|
131
|
+
}
|
|
132
|
+
process.exit(2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
checkRootLayout();
|
|
136
|
+
checkUntrackedNotIgnored();
|
|
137
|
+
|
|
138
|
+
console.log('[repo-sanity] ok');
|
|
@@ -6,6 +6,7 @@ import os from 'os';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
9
|
+
import http from 'node:http';
|
|
9
10
|
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
@@ -251,7 +252,17 @@ async function waitForHealth(port, serverProc, timeoutMs = 20000) {
|
|
|
251
252
|
try {
|
|
252
253
|
const res = await fetch(`http://127.0.0.1:${port}/health`, { method: 'GET' });
|
|
253
254
|
if (res.ok) {
|
|
254
|
-
|
|
255
|
+
// /health becomes reachable before runtime is fully initialized (server starts listening first
|
|
256
|
+
// to support token portal). Mock regressions must wait until hub pipeline is ready.
|
|
257
|
+
try {
|
|
258
|
+
const data = await res.json();
|
|
259
|
+
const ready = data && (data.ready === true || data.pipelineReady === true || data.status === 'ok');
|
|
260
|
+
if (ready) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// ignore JSON errors, retry
|
|
265
|
+
}
|
|
255
266
|
}
|
|
256
267
|
} catch {
|
|
257
268
|
// retry
|
|
@@ -276,6 +287,150 @@ async function stopServer(child, forceTimeout = 5000) {
|
|
|
276
287
|
child.kill('SIGKILL');
|
|
277
288
|
}
|
|
278
289
|
|
|
290
|
+
async function createLocalUpstreamServer(handler) {
|
|
291
|
+
return await new Promise((resolve, reject) => {
|
|
292
|
+
const server = http.createServer(handler);
|
|
293
|
+
server.on('error', reject);
|
|
294
|
+
server.listen(0, '127.0.0.1', () => {
|
|
295
|
+
const address = server.address();
|
|
296
|
+
if (!address || typeof address !== 'object') {
|
|
297
|
+
reject(new Error('Failed to obtain listen address for upstream server'));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
resolve({
|
|
301
|
+
server,
|
|
302
|
+
port: address.port
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function buildIflowUaProbeConfig(port, upstreamPort) {
|
|
309
|
+
return {
|
|
310
|
+
version: '1.0.0',
|
|
311
|
+
virtualrouter: {
|
|
312
|
+
inputProtocol: 'openai',
|
|
313
|
+
outputProtocol: 'openai',
|
|
314
|
+
providers: {
|
|
315
|
+
iflow: {
|
|
316
|
+
id: 'iflow',
|
|
317
|
+
enabled: true,
|
|
318
|
+
type: 'iflow',
|
|
319
|
+
baseURL: `http://127.0.0.1:${upstreamPort}/v1`,
|
|
320
|
+
compatibilityProfile: 'chat:iflow',
|
|
321
|
+
auth: {
|
|
322
|
+
type: 'apikey',
|
|
323
|
+
apiKey: 'test-upstream-token'
|
|
324
|
+
},
|
|
325
|
+
models: {
|
|
326
|
+
'glm-4.7': { supportsStreaming: false }
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
routing: {
|
|
331
|
+
default: ['iflow.glm-4.7']
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
httpserver: {
|
|
335
|
+
host: '127.0.0.1',
|
|
336
|
+
port
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function runIflowUserAgentRegression() {
|
|
342
|
+
const seen = {
|
|
343
|
+
path: '',
|
|
344
|
+
headers: {},
|
|
345
|
+
body: ''
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const { server: upstream, port: upstreamPort } = await createLocalUpstreamServer(async (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
seen.path = String(req.url || '');
|
|
351
|
+
seen.headers = req.headers || {};
|
|
352
|
+
let raw = '';
|
|
353
|
+
req.setEncoding('utf8');
|
|
354
|
+
req.on('data', (chunk) => {
|
|
355
|
+
raw += chunk;
|
|
356
|
+
});
|
|
357
|
+
await new Promise((resolve) => req.on('end', resolve));
|
|
358
|
+
seen.body = raw;
|
|
359
|
+
} catch {
|
|
360
|
+
// ignore
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
364
|
+
res.end(
|
|
365
|
+
JSON.stringify({
|
|
366
|
+
id: 'chatcmpl_mock_iflow_ua',
|
|
367
|
+
object: 'chat.completion',
|
|
368
|
+
created: Math.floor(Date.now() / 1000),
|
|
369
|
+
model: 'glm-4.7',
|
|
370
|
+
choices: [
|
|
371
|
+
{
|
|
372
|
+
index: 0,
|
|
373
|
+
message: { role: 'assistant', content: 'ok' },
|
|
374
|
+
finish_reason: 'stop'
|
|
375
|
+
}
|
|
376
|
+
],
|
|
377
|
+
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
|
|
378
|
+
})
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const port = 5750;
|
|
383
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'routecodex-iflow-ua-'));
|
|
384
|
+
const configPath = path.join(dir, 'config.json');
|
|
385
|
+
await fs.writeFile(configPath, JSON.stringify(buildIflowUaProbeConfig(port, upstreamPort), null, 2), 'utf-8');
|
|
386
|
+
|
|
387
|
+
const entry = path.join(PROJECT_ROOT, 'dist', 'index.js');
|
|
388
|
+
const child = spawn(process.execPath, [entry], {
|
|
389
|
+
cwd: PROJECT_ROOT,
|
|
390
|
+
env: {
|
|
391
|
+
...process.env,
|
|
392
|
+
ROUTECODEX_PORT: String(port),
|
|
393
|
+
ROUTECODEX_CONFIG_PATH: configPath,
|
|
394
|
+
RCC_PORT: String(port),
|
|
395
|
+
RCC_CONFIG_PATH: configPath
|
|
396
|
+
},
|
|
397
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
await waitForHealth(port, child);
|
|
402
|
+
const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
|
403
|
+
method: 'POST',
|
|
404
|
+
headers: {
|
|
405
|
+
'Content-Type': 'application/json',
|
|
406
|
+
// If UA precedence is wrong, this inbound UA will leak to upstream and break iFlow glm-4.7.
|
|
407
|
+
'User-Agent': 'curl/8.7.1'
|
|
408
|
+
},
|
|
409
|
+
body: JSON.stringify({
|
|
410
|
+
model: 'iflow.glm-4.7',
|
|
411
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
412
|
+
max_tokens: 16,
|
|
413
|
+
stream: false
|
|
414
|
+
})
|
|
415
|
+
});
|
|
416
|
+
const text = await res.text();
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
throw new Error(`ua probe request failed: HTTP ${res.status}: ${text}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const upstreamUa = typeof seen.headers['user-agent'] === 'string' ? seen.headers['user-agent'] : '';
|
|
422
|
+
if (upstreamUa !== 'iFlow-Cli') {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`iflow UA regression: expected upstream user-agent="iFlow-Cli", got ${JSON.stringify(upstreamUa)} (path=${seen.path})`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
} finally {
|
|
428
|
+
await stopServer(child);
|
|
429
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
430
|
+
await new Promise((resolve) => upstream.close(() => resolve()));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
279
434
|
function collectInvalidNames(payload) {
|
|
280
435
|
const failures = [];
|
|
281
436
|
const check = (value, location) => {
|
|
@@ -546,6 +701,7 @@ async function runSample(sample, index) {
|
|
|
546
701
|
|
|
547
702
|
async function main() {
|
|
548
703
|
await ensureCliAvailable();
|
|
704
|
+
await runIflowUserAgentRegression();
|
|
549
705
|
const samples = await loadRegistry();
|
|
550
706
|
const watchedTags = new Set(['invalid_name', 'missing_output', 'missing_tool_call_id', 'require_fc_call_ids', 'regression']);
|
|
551
707
|
const regressionSamples = samples.filter(
|
package/scripts/run-bg.sh
CHANGED
|
@@ -40,19 +40,6 @@ if [[ -z "${CMD_STR}" ]]; then
|
|
|
40
40
|
exit 2
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
-
cleanup_existing_servers() {
|
|
44
|
-
local killed=0
|
|
45
|
-
if [[ "${CMD_STR}" == *"dist/index.js"* || "${CMD_STR}" == *"routecodex"* ]]; then
|
|
46
|
-
echo "[run-bg] ensuring no previous RouteCodex server is running" >&2
|
|
47
|
-
pkill -f "/opt/homebrew/lib/node_modules/routecodex/dist/index.js" 2>/dev/null && killed=1 || true
|
|
48
|
-
pkill -f "$(pwd)/dist/index.js" 2>/dev/null && killed=1 || true
|
|
49
|
-
pkill -f "routecodex/dist/index.js" 2>/dev/null && killed=1 || true
|
|
50
|
-
if [[ "${killed}" -eq 1 ]]; then
|
|
51
|
-
sleep 1
|
|
52
|
-
fi
|
|
53
|
-
fi
|
|
54
|
-
}
|
|
55
|
-
|
|
56
43
|
detect_port() {
|
|
57
44
|
local p="${TARGET_PORT}"
|
|
58
45
|
if [[ -n "$p" ]]; then echo "$p"; return; fi
|
|
@@ -97,7 +84,6 @@ ensure_singleton() {
|
|
|
97
84
|
fi
|
|
98
85
|
}
|
|
99
86
|
|
|
100
|
-
cleanup_existing_servers
|
|
101
87
|
ensure_singleton
|
|
102
88
|
|
|
103
89
|
ts=$(date +%s)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
// Keep CI Jest fast and deterministic:
|
|
6
|
+
// - Prefer pure unit tests / mock-provider tests (no external network).
|
|
7
|
+
// - Expand coverage by adding more suites here (don’t run `jest --all` in CI).
|
|
8
|
+
const routingInstructionTests = [
|
|
9
|
+
'tests/client/gemini/gemini-cli-protocol-client.test.ts',
|
|
10
|
+
'tests/server/runtime/request-executor.single-attempt.spec.ts',
|
|
11
|
+
'tests/server/runtime/executor-provider.retryable.spec.ts',
|
|
12
|
+
'tests/providers/auth/tokenfile-auth.iflow.spec.ts',
|
|
13
|
+
'tests/providers/core/runtime/gemini-http-provider.unit.test.ts',
|
|
14
|
+
'tests/providers/core/runtime/gemini-cli-http-provider.unit.test.ts',
|
|
15
|
+
'tests/providers/core/runtime/base-provider.spec.ts',
|
|
16
|
+
'tests/providers/core/runtime/http-transport-provider.headers.test.ts',
|
|
17
|
+
'tests/providers/core/runtime/protocol-http-providers.unit.test.ts',
|
|
18
|
+
'tests/providers/core/runtime/provider-error-classifier.spec.ts',
|
|
19
|
+
'tests/providers/core/runtime/antigravity-quota-client.unit.test.ts',
|
|
20
|
+
'tests/providers/core/utils/http-client.postStream.idle-timeout.spec.ts',
|
|
21
|
+
'tests/providers/core/utils/snapshot-writer.sse-error-propagation.spec.ts',
|
|
22
|
+
'tests/manager/quota/provider-quota-center.spec.ts',
|
|
23
|
+
'tests/manager/quota/provider-quota-store.spec.ts',
|
|
24
|
+
'tests/manager/quota/quota-manager-refresh.spec.ts',
|
|
25
|
+
'tests/manager/quota/provider-quota-daemon-module.spec.ts',
|
|
26
|
+
'tests/manager/quota/provider-key-normalization.spec.ts',
|
|
27
|
+
'tests/responses/responses-openai-bridge.spec.ts',
|
|
28
|
+
'tests/server/http-server/daemon-admin.e2e.spec.ts',
|
|
29
|
+
'tests/server/http-server/daemon-admin-provider-pool.e2e.spec.ts',
|
|
30
|
+
'tests/server/http-server/apikey-auth.e2e.spec.ts',
|
|
31
|
+
'tests/server/http-server/quota-view-injection.spec.ts',
|
|
32
|
+
'tests/server/http-server/quota-refresh-triggers.e2e.spec.ts',
|
|
33
|
+
'tests/server/http-server/hub-policy-injection.spec.ts',
|
|
34
|
+
'tests/server/http-server/session-header-injection.spec.ts',
|
|
35
|
+
'tests/server/http-server/session-dir.spec.ts',
|
|
36
|
+
'tests/server/http-server/execute-pipeline-failover.spec.ts',
|
|
37
|
+
'tests/server/runtime/http-server/executor-provider.spec.ts',
|
|
38
|
+
'tests/server/runtime/http-server/request-executor.spec.ts',
|
|
39
|
+
'tests/server/utils/http-error-mapper-timeout.spec.ts',
|
|
40
|
+
'tests/server/utils/http-error-mapper-malformed-request.spec.ts',
|
|
41
|
+
'tests/server/handlers/handler-utils.apikey-denylist.spec.ts',
|
|
42
|
+
'tests/server/handlers/sse-timeout.spec.ts',
|
|
43
|
+
'tests/utils/is-direct-execution.test.ts',
|
|
44
|
+
'tests/utils/windows-netstat.test.ts',
|
|
45
|
+
'tests/servertool/virtual-router-context-fallback.spec.ts',
|
|
46
|
+
'tests/servertool/virtual-router-longcontext-fallback.spec.ts',
|
|
47
|
+
'tests/servertool/virtual-router-series-cooldown.spec.ts',
|
|
48
|
+
'tests/servertool/virtual-router-context-weighted.spec.ts',
|
|
49
|
+
'tests/servertool/virtual-router-engine-update-deps.spec.ts',
|
|
50
|
+
'tests/servertool/virtual-router-health-weighted-rr.spec.ts',
|
|
51
|
+
'tests/servertool/virtual-router-priority-selection.spec.ts',
|
|
52
|
+
'tests/servertool/virtual-router-quota-routing.spec.ts',
|
|
53
|
+
'tests/servertool/virtual-router-routing-instructions.spec.ts',
|
|
54
|
+
'tests/servertool/virtual-router-servertool-routing.spec.ts',
|
|
55
|
+
'tests/servertool/routing-instructions.spec.ts',
|
|
56
|
+
'tests/servertool/server-side-web-search.spec.ts',
|
|
57
|
+
'tests/servertool/vision-flow.spec.ts',
|
|
58
|
+
'tests/servertool/gemini-empty-reply-continue.spec.ts',
|
|
59
|
+
'tests/servertool/iflow-model-error-retry.spec.ts',
|
|
60
|
+
'tests/servertool/apply-patch-guard.spec.ts',
|
|
61
|
+
'tests/servertool/exec-command-guard.spec.ts',
|
|
62
|
+
'tests/servertool/hub-pipeline-session-headers.spec.ts',
|
|
63
|
+
'tests/servertool/stopmessage-anthropic-stop-sequence.spec.ts',
|
|
64
|
+
'tests/servertool/stop-message-auto.spec.ts',
|
|
65
|
+
'tests/servertool/servertool-clock.spec.ts',
|
|
66
|
+
'tests/servertool/servertool-progress-logging.spec.ts',
|
|
67
|
+
'tests/sharedmodule/gemini-mapper-functioncall-args.spec.ts',
|
|
68
|
+
'tests/sharedmodule/mcp-tool-descriptions.spec.ts',
|
|
69
|
+
'tests/unified-hub/hub-v1-single-path-imports.spec.ts',
|
|
70
|
+
'tests/unified-hub/policy-errorsample-write.spec.ts',
|
|
71
|
+
'tests/unified-hub/policy-observe-shadow.spec.ts',
|
|
72
|
+
'tests/unified-hub/shadow-runtime-compare.errorsamples.spec.ts'
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const cliTests = [
|
|
76
|
+
'tests/cli/clean-command.spec.ts',
|
|
77
|
+
'tests/cli/code-command.spec.ts',
|
|
78
|
+
'tests/cli/config-command.spec.ts',
|
|
79
|
+
'tests/cli/env-command.spec.ts',
|
|
80
|
+
'tests/cli/env-output.spec.ts',
|
|
81
|
+
'tests/cli/examples-command.spec.ts',
|
|
82
|
+
'tests/cli/port-command.spec.ts',
|
|
83
|
+
'tests/cli/port-utils.spec.ts',
|
|
84
|
+
'tests/cli/restart-command.spec.ts',
|
|
85
|
+
'tests/cli/smoke.spec.ts',
|
|
86
|
+
'tests/cli/start-command.spec.ts',
|
|
87
|
+
'tests/cli/status-command.spec.ts',
|
|
88
|
+
'tests/cli/stop-command.spec.ts'
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const wantsCoverage = process.argv.includes('--coverage') || process.env.ROUTECODEX_CI_COVERAGE === '1';
|
|
92
|
+
const allTests = [...routingInstructionTests, ...cliTests];
|
|
93
|
+
|
|
94
|
+
const jestBin = path.join(process.cwd(), 'node_modules', 'jest', 'bin', 'jest.js');
|
|
95
|
+
|
|
96
|
+
const args = [
|
|
97
|
+
'--runTestsByPath',
|
|
98
|
+
...allTests,
|
|
99
|
+
...(wantsCoverage ? ['--testTimeout=20000'] : []),
|
|
100
|
+
...(wantsCoverage
|
|
101
|
+
? [
|
|
102
|
+
'--coverage',
|
|
103
|
+
...(process.env.ROUTECODEX_CI_MAX_WORKERS
|
|
104
|
+
? [`--maxWorkers=${process.env.ROUTECODEX_CI_MAX_WORKERS}`]
|
|
105
|
+
: []),
|
|
106
|
+
'--coverageReporters=json-summary',
|
|
107
|
+
'--coverageReporters=text',
|
|
108
|
+
'--coverageDirectory=coverage'
|
|
109
|
+
]
|
|
110
|
+
: [])
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const result = spawnSync(
|
|
114
|
+
process.execPath,
|
|
115
|
+
['--experimental-vm-modules', jestBin, ...args],
|
|
116
|
+
{ stdio: 'inherit' }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Responses Debug Client (SSE + Tool Loop)
|
|
2
|
+
|
|
3
|
+
Purpose
|
|
4
|
+
- Minimal client to debug OpenAI Responses API through your RCC server.
|
|
5
|
+
- Starts with SSE event consumption, then completes a basic tool-calls loop.
|
|
6
|
+
|
|
7
|
+
Run
|
|
8
|
+
- npm run debug:responses -- --file scripts/tools-dev/responses-debug-client/payloads/text.json
|
|
9
|
+
- npm run debug:responses -- --file scripts/tools-dev/responses-debug-client/payloads/tool.json
|
|
10
|
+
|
|
11
|
+
Options
|
|
12
|
+
- --file <path> Required. JSON request payload (Responses shape)
|
|
13
|
+
- --baseURL <url> Default http://127.0.0.1:5520/v1
|
|
14
|
+
- --apiKey <key> Default dummy
|
|
15
|
+
- --timeout <sec> Default 120
|
|
16
|
+
- --raw Print raw events (default off)
|
|
17
|
+
- --save Save JSONL to logs/ (default off)
|
|
18
|
+
- --maxRounds <n> Tool rounds limit (default 3)
|
|
19
|
+
|
|
20
|
+
Notes
|
|
21
|
+
- Only Responses payloads are supported (no Chat conversion).
|
|
22
|
+
- Listens to named SSE events (response.output_text.delta, etc.).
|
|
23
|
+
- Implements minimal local tools: echo, sum, time.
|