@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.
Files changed (102) hide show
  1. package/README.md +51 -1427
  2. package/dist/build-info.js +2 -2
  3. package/dist/cli/commands/config.js +3 -0
  4. package/dist/cli/commands/config.js.map +1 -1
  5. package/dist/cli/commands/init.js +3 -0
  6. package/dist/cli/commands/init.js.map +1 -1
  7. package/dist/cli/config/bundled-docs.js +2 -2
  8. package/dist/cli/config/bundled-docs.js.map +1 -1
  9. package/dist/cli/config/init-config.d.ts +2 -1
  10. package/dist/cli/config/init-config.js +33 -1
  11. package/dist/cli/config/init-config.js.map +1 -1
  12. package/dist/client/gemini/gemini-protocol-client.js +2 -1
  13. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  14. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +39 -15
  15. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  16. package/dist/client/openai/chat-protocol-client.js +2 -1
  17. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  18. package/dist/client/responses/responses-protocol-client.js +2 -1
  19. package/dist/client/responses/responses-protocol-client.js.map +1 -1
  20. package/dist/error-handling/quiet-error-handling-center.js +46 -8
  21. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  22. package/dist/manager/modules/quota/provider-quota-daemon.events.js +4 -2
  23. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  24. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
  25. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  26. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -1
  27. package/dist/providers/auth/antigravity-userinfo-helper.js +25 -4
  28. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  29. package/dist/providers/auth/tokenfile-auth.d.ts +2 -0
  30. package/dist/providers/auth/tokenfile-auth.js +33 -1
  31. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  32. package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
  33. package/dist/providers/core/config/camoufox-launcher.js +5 -0
  34. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  35. package/dist/providers/core/config/service-profiles.js +7 -18
  36. package/dist/providers/core/config/service-profiles.js.map +1 -1
  37. package/dist/providers/core/runtime/base-provider.d.ts +0 -5
  38. package/dist/providers/core/runtime/base-provider.js +26 -112
  39. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  40. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +7 -0
  41. package/dist/providers/core/runtime/gemini-cli-http-provider.js +362 -93
  42. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  43. package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
  44. package/dist/providers/core/runtime/http-request-executor.js +110 -38
  45. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  46. package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
  47. package/dist/providers/core/runtime/http-transport-provider.js +80 -37
  48. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  49. package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
  50. package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
  51. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  52. package/dist/providers/core/utils/http-client.js +20 -43
  53. package/dist/providers/core/utils/http-client.js.map +1 -1
  54. package/dist/server/handlers/handler-utils.js +5 -1
  55. package/dist/server/handlers/handler-utils.js.map +1 -1
  56. package/dist/server/handlers/responses-handler.js +1 -1
  57. package/dist/server/handlers/responses-handler.js.map +1 -1
  58. package/dist/server/runtime/http-server/index.js +68 -29
  59. package/dist/server/runtime/http-server/index.js.map +1 -1
  60. package/dist/server/runtime/http-server/request-executor.js +50 -6
  61. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  62. package/dist/server/runtime/http-server/routes.js +4 -1
  63. package/dist/server/runtime/http-server/routes.js.map +1 -1
  64. package/dist/utils/strip-internal-keys.d.ts +12 -0
  65. package/dist/utils/strip-internal-keys.js +28 -0
  66. package/dist/utils/strip-internal-keys.js.map +1 -0
  67. package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
  68. package/docs/antigravity-gemini-format-cleanup.md +102 -0
  69. package/docs/antigravity-routing-contract.md +31 -0
  70. package/docs/chat-semantic-expansion-plan.md +8 -6
  71. package/docs/glm-chat-completions.md +1 -1
  72. package/docs/servertool-framework.md +65 -0
  73. package/docs/v2-architecture/README.md +6 -8
  74. package/docs/verified-configs/README.md +60 -0
  75. package/docs/verified-configs/v0.45.0/README.md +244 -0
  76. package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
  77. package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
  78. package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
  79. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
  80. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
  81. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
  82. package/package.json +17 -11
  83. package/scripts/build-core.mjs +3 -1
  84. package/scripts/ci/repo-sanity.mjs +138 -0
  85. package/scripts/mock-provider/run-regressions.mjs +157 -1
  86. package/scripts/run-bg.sh +0 -14
  87. package/scripts/tests/ci-jest.mjs +119 -0
  88. package/scripts/tools-dev/responses-debug-client/README.md +23 -0
  89. package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
  90. package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
  91. package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
  92. package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
  93. package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
  94. package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
  95. package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
  96. package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
  97. package/scripts/vendor-core.mjs +13 -3
  98. package/scripts/test-fc-responses.mjs +0 -66
  99. package/scripts/test-guidance.mjs +0 -100
  100. package/scripts/test-iflow-web-search.mjs +0 -141
  101. package/scripts/test-iflow.mjs +0 -379
  102. 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.1348",
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: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/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/unified-hub/hub-v1-single-path-imports.spec.ts",
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 --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache",
55
- "lint:fix": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --fix --no-cache",
56
- "lint:strict": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --max-warnings 0 --no-cache",
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/lmstudio-text.json --baseURL ${LMSTUDIO_BASEURL:-http://127.0.0.1:1234/v1}",
86
- "debug:responses:lmstudio:tool": "tsx tools/responses-debug-client/src/index.ts --file tools/responses-debug-client/payloads/lmstudio-tool.json --baseURL ${LMSTUDIO_BASEURL:-http://127.0.0.1:1234/v1}",
87
- "capture:responses:lmstudio": "node scripts/capture-responses-sse.mjs --file tools/responses-debug-client/payloads/lmstudio-tool.json",
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.1172",
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",
@@ -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
- return;
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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "model": "gpt-5",
3
+ "input": [
4
+ {
5
+ "role": "user",
6
+ "content": [
7
+ { "type": "input_text", "text": "写一首五言绝句" }
8
+ ]
9
+ }
10
+ ],
11
+ "stream": true
12
+ }
13
+