@jsonstudio/rcc 0.89.1348 → 0.89.1488

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 (126) hide show
  1. package/README.md +51 -1427
  2. package/configsamples/config.json +12 -4
  3. package/dist/build-info.js +2 -2
  4. package/dist/cli/commands/config.js +3 -0
  5. package/dist/cli/commands/config.js.map +1 -1
  6. package/dist/cli/commands/init.js +3 -0
  7. package/dist/cli/commands/init.js.map +1 -1
  8. package/dist/cli/config/bundled-docs.js +2 -2
  9. package/dist/cli/config/bundled-docs.js.map +1 -1
  10. package/dist/cli/config/init-config.d.ts +2 -1
  11. package/dist/cli/config/init-config.js +33 -1
  12. package/dist/cli/config/init-config.js.map +1 -1
  13. package/dist/client/gemini/gemini-protocol-client.js +2 -1
  14. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  15. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +67 -16
  16. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  17. package/dist/client/openai/chat-protocol-client.js +2 -1
  18. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  19. package/dist/client/responses/responses-protocol-client.js +2 -1
  20. package/dist/client/responses/responses-protocol-client.js.map +1 -1
  21. package/dist/error-handling/quiet-error-handling-center.js +46 -8
  22. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  23. package/dist/manager/modules/quota/antigravity-quota-manager.d.ts +4 -0
  24. package/dist/manager/modules/quota/antigravity-quota-manager.js +130 -2
  25. package/dist/manager/modules/quota/antigravity-quota-manager.js.map +1 -1
  26. package/dist/manager/modules/quota/provider-quota-daemon.events.js +67 -4
  27. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  28. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
  29. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  30. package/dist/modules/llmswitch/bridge.js +17 -4
  31. package/dist/modules/llmswitch/bridge.js.map +1 -1
  32. package/dist/modules/llmswitch/core-loader.d.ts +1 -1
  33. package/dist/modules/llmswitch/core-loader.js +15 -3
  34. package/dist/modules/llmswitch/core-loader.js.map +1 -1
  35. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +5 -2
  36. package/dist/providers/auth/antigravity-userinfo-helper.js +63 -8
  37. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  38. package/dist/providers/auth/gemini-cli-userinfo-helper.js +66 -4
  39. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  40. package/dist/providers/auth/oauth-lifecycle.js +112 -1
  41. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  42. package/dist/providers/auth/tokenfile-auth.d.ts +14 -0
  43. package/dist/providers/auth/tokenfile-auth.js +125 -2
  44. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  45. package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
  46. package/dist/providers/core/config/camoufox-launcher.js +5 -0
  47. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  48. package/dist/providers/core/config/service-profiles.js +7 -18
  49. package/dist/providers/core/config/service-profiles.js.map +1 -1
  50. package/dist/providers/core/runtime/base-provider.d.ts +0 -5
  51. package/dist/providers/core/runtime/base-provider.js +26 -112
  52. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  53. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +6 -0
  54. package/dist/providers/core/runtime/gemini-cli-http-provider.js +409 -100
  55. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  56. package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
  57. package/dist/providers/core/runtime/http-request-executor.js +110 -38
  58. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  59. package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
  60. package/dist/providers/core/runtime/http-transport-provider.js +89 -39
  61. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  62. package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
  63. package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
  64. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  65. package/dist/providers/core/utils/http-client.js +20 -43
  66. package/dist/providers/core/utils/http-client.js.map +1 -1
  67. package/dist/runtime/wasm-runtime/wasm-config.d.ts +73 -0
  68. package/dist/runtime/wasm-runtime/wasm-config.js +124 -0
  69. package/dist/runtime/wasm-runtime/wasm-config.js.map +1 -0
  70. package/dist/runtime/wasm-runtime/wasm-loader.d.ts +40 -0
  71. package/dist/runtime/wasm-runtime/wasm-loader.js +62 -0
  72. package/dist/runtime/wasm-runtime/wasm-loader.js.map +1 -0
  73. package/dist/server/handlers/handler-utils.js +5 -1
  74. package/dist/server/handlers/handler-utils.js.map +1 -1
  75. package/dist/server/handlers/responses-handler.js +1 -1
  76. package/dist/server/handlers/responses-handler.js.map +1 -1
  77. package/dist/server/runtime/http-server/index.js +121 -30
  78. package/dist/server/runtime/http-server/index.js.map +1 -1
  79. package/dist/server/runtime/http-server/request-executor.js +50 -6
  80. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  81. package/dist/server/runtime/http-server/routes.js +4 -1
  82. package/dist/server/runtime/http-server/routes.js.map +1 -1
  83. package/dist/utils/strip-internal-keys.d.ts +12 -0
  84. package/dist/utils/strip-internal-keys.js +28 -0
  85. package/dist/utils/strip-internal-keys.js.map +1 -0
  86. package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
  87. package/docs/antigravity-gemini-format-cleanup.md +143 -0
  88. package/docs/antigravity-routing-contract.md +31 -0
  89. package/docs/chat-semantic-expansion-plan.md +8 -6
  90. package/docs/glm-chat-completions.md +1 -1
  91. package/docs/llms-wasm-migration.md +331 -0
  92. package/docs/llms-wasm-module-boundaries.md +588 -0
  93. package/docs/llms-wasm-replay-baseline.md +171 -0
  94. package/docs/plans/llms-wasm-migration-plan.md +401 -0
  95. package/docs/servertool-framework.md +65 -0
  96. package/docs/v2-architecture/README.md +6 -8
  97. package/docs/verified-configs/README.md +60 -0
  98. package/docs/verified-configs/v0.45.0/README.md +244 -0
  99. package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
  100. package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
  101. package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
  102. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
  103. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
  104. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
  105. package/package.json +17 -11
  106. package/scripts/antigravity-token-bridge.mjs +283 -0
  107. package/scripts/build-core.mjs +3 -1
  108. package/scripts/ci/repo-sanity.mjs +138 -0
  109. package/scripts/mock-provider/run-regressions.mjs +157 -1
  110. package/scripts/run-bg.sh +0 -14
  111. package/scripts/tests/ci-jest.mjs +119 -0
  112. package/scripts/tools-dev/responses-debug-client/README.md +23 -0
  113. package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
  114. package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
  115. package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
  116. package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
  117. package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
  118. package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
  119. package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
  120. package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
  121. package/scripts/vendor-core.mjs +13 -3
  122. package/scripts/test-fc-responses.mjs +0 -66
  123. package/scripts/test-guidance.mjs +0 -100
  124. package/scripts/test-iflow-web-search.mjs +0 -141
  125. package/scripts/test-iflow.mjs +0 -379
  126. 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.1488",
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.1397",
144
150
  "@lmstudio/sdk": "^1.5.0",
145
151
  "@radix-ui/react-switch": "^1.2.6",
146
152
  "@types/socket.io": "^3.0.1",
@@ -0,0 +1,283 @@
1
+ import fs from 'fs/promises';
2
+ import { existsSync, readdirSync } from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import crypto from 'crypto';
6
+
7
+ const args = process.argv.slice(2);
8
+ const opts = new Set(args);
9
+
10
+ const direction = args.includes('--to-routecodex') ? 'ag-to-rc' : 'rc-to-ag';
11
+ const dryRun = opts.has('--dry-run');
12
+
13
+ const home = os.homedir();
14
+ const rcAuthDir = path.join(home, '.routecodex', 'auth');
15
+ const agDataDir = path.join(home, '.antigravity_tools');
16
+ const agAccountsDir = path.join(agDataDir, 'accounts');
17
+ const agIndexPath = path.join(agDataDir, 'accounts.json');
18
+
19
+ function nowSeconds() {
20
+ return Math.floor(Date.now() / 1000);
21
+ }
22
+
23
+ function normalizeExpirySeconds(token) {
24
+ const expiry = token.expiry_timestamp;
25
+ if (typeof expiry === 'number') {
26
+ return expiry > 10_000_000_000 ? Math.floor(expiry / 1000) : Math.floor(expiry);
27
+ }
28
+ const expiresAt = token.expires_at;
29
+ if (typeof expiresAt === 'number') {
30
+ return expiresAt > 10_000_000_000 ? Math.floor(expiresAt / 1000) : Math.floor(expiresAt);
31
+ }
32
+ const expiresIn = token.expires_in;
33
+ if (typeof expiresIn === 'number') {
34
+ return nowSeconds() + Math.max(0, Math.floor(expiresIn));
35
+ }
36
+ return nowSeconds() + 3600;
37
+ }
38
+
39
+ function normalizeRcToken(raw) {
40
+ if (!raw || typeof raw !== 'object') {
41
+ return null;
42
+ }
43
+ if (raw.token && typeof raw.token === 'object' && raw.token.access_token) {
44
+ return { ...raw.token, ...raw };
45
+ }
46
+ return raw;
47
+ }
48
+
49
+ async function ensureDir(p) {
50
+ await fs.mkdir(p, { recursive: true });
51
+ }
52
+
53
+ function pickEmail(token) {
54
+ if (typeof token.email === 'string' && token.email.trim()) {
55
+ return token.email.trim();
56
+ }
57
+ if (token.id_token && typeof token.id_token === 'string') {
58
+ // avoid parsing JWT here to keep script simple
59
+ }
60
+ return '';
61
+ }
62
+
63
+ async function loadExistingAccounts() {
64
+ if (!existsSync(agAccountsDir)) {
65
+ return { byEmail: new Map(), accounts: [] };
66
+ }
67
+ const byEmail = new Map();
68
+ const accounts = [];
69
+ for (const name of readdirSync(agAccountsDir)) {
70
+ if (!name.endsWith('.json')) {
71
+ continue;
72
+ }
73
+ const filePath = path.join(agAccountsDir, name);
74
+ try {
75
+ const raw = JSON.parse(await fs.readFile(filePath, 'utf-8'));
76
+ accounts.push({ filePath, data: raw });
77
+ if (raw && typeof raw.email === 'string') {
78
+ byEmail.set(raw.email, { filePath, data: raw });
79
+ }
80
+ } catch {
81
+ // ignore malformed file
82
+ }
83
+ }
84
+ return { byEmail, accounts };
85
+ }
86
+
87
+ async function loadAccountsIndex() {
88
+ try {
89
+ const raw = JSON.parse(await fs.readFile(agIndexPath, 'utf-8'));
90
+ if (raw && typeof raw === 'object' && Array.isArray(raw.accounts)) {
91
+ return raw;
92
+ }
93
+ } catch {
94
+ // ignore
95
+ }
96
+ return { version: '2.0', accounts: [], current_account_id: null };
97
+ }
98
+
99
+ function upsertIndexEntry(index, account) {
100
+ const idx = index.accounts.findIndex((item) => item.id === account.id);
101
+ const summary = {
102
+ id: account.id,
103
+ email: account.email,
104
+ name: account.name ?? null,
105
+ disabled: !!account.disabled,
106
+ proxy_disabled: !!account.proxy_disabled,
107
+ created_at: account.created_at,
108
+ last_used: account.last_used
109
+ };
110
+ if (idx >= 0) {
111
+ index.accounts[idx] = summary;
112
+ } else {
113
+ index.accounts.push(summary);
114
+ }
115
+ }
116
+
117
+ async function rcToAg() {
118
+ if (!existsSync(rcAuthDir)) {
119
+ throw new Error(`RouteCodex auth dir not found: ${rcAuthDir}`);
120
+ }
121
+ await ensureDir(agAccountsDir);
122
+
123
+ const { byEmail } = await loadExistingAccounts();
124
+ const index = await loadAccountsIndex();
125
+
126
+ const files = readdirSync(rcAuthDir).filter((name) => /^antigravity-oauth-.*\.json$/i.test(name));
127
+ if (!files.length) {
128
+ console.log('No antigravity token files found in ~/.routecodex/auth');
129
+ return;
130
+ }
131
+
132
+ for (const name of files) {
133
+ const filePath = path.join(rcAuthDir, name);
134
+ let tokenRaw;
135
+ try {
136
+ tokenRaw = JSON.parse(await fs.readFile(filePath, 'utf-8'));
137
+ } catch {
138
+ console.log(`Skip unreadable token: ${filePath}`);
139
+ continue;
140
+ }
141
+ const token = normalizeRcToken(tokenRaw);
142
+ if (!token || typeof token.access_token !== 'string' || typeof token.refresh_token !== 'string') {
143
+ console.log(`Skip invalid token (missing access/refresh): ${filePath}`);
144
+ continue;
145
+ }
146
+
147
+ const email = pickEmail(token);
148
+ if (!email) {
149
+ console.log(`Skip token without email: ${filePath}`);
150
+ continue;
151
+ }
152
+
153
+ const now = nowSeconds();
154
+ const expiryTimestamp = normalizeExpirySeconds(token);
155
+ const projectId = typeof token.project_id === 'string' ? token.project_id : (typeof token.projectId === 'string' ? token.projectId : undefined);
156
+
157
+ const baseAccount = {
158
+ created_at: now,
159
+ disabled: token.disabled === true,
160
+ email,
161
+ id: crypto.randomUUID(),
162
+ last_used: now,
163
+ name: typeof token.name === 'string' ? token.name : null,
164
+ proxy_disabled: token.proxy_disabled === true || token.proxyDisabled === true,
165
+ proxy_disabled_at: token.proxy_disabled_at ?? null,
166
+ proxy_disabled_reason: token.proxy_disabled_reason ?? null,
167
+ quota: null,
168
+ protected_models: Array.isArray(token.protected_models)
169
+ ? token.protected_models
170
+ : Array.isArray(token.protectedModels)
171
+ ? token.protectedModels
172
+ : [],
173
+ disabled_reason: token.disabled_reason ?? null,
174
+ disabled_at: token.disabled_at ?? null,
175
+ token: {
176
+ access_token: token.access_token,
177
+ refresh_token: token.refresh_token,
178
+ expires_in: typeof token.expires_in === 'number' ? token.expires_in : 3600,
179
+ expiry_timestamp: expiryTimestamp,
180
+ token_type: typeof token.token_type === 'string' ? token.token_type : 'Bearer',
181
+ email,
182
+ project_id: projectId
183
+ }
184
+ };
185
+
186
+ const existing = byEmail.get(email);
187
+ let account = baseAccount;
188
+ let targetPath;
189
+ if (existing) {
190
+ account = { ...existing.data, ...baseAccount };
191
+ account.id = existing.data.id || baseAccount.id;
192
+ account.created_at = existing.data.created_at || baseAccount.created_at;
193
+ account.last_used = now;
194
+ account.token = { ...(existing.data.token || {}), ...(baseAccount.token || {}) };
195
+ targetPath = existing.filePath;
196
+ } else {
197
+ targetPath = path.join(agAccountsDir, `${account.id}.json`);
198
+ }
199
+
200
+ if (dryRun) {
201
+ console.log(`[dry-run] write ${targetPath}`);
202
+ } else {
203
+ await fs.writeFile(targetPath, `${JSON.stringify(account, null, 2)}\n`, 'utf-8');
204
+ upsertIndexEntry(index, account);
205
+ console.log(`Wrote ${targetPath}`);
206
+ }
207
+ }
208
+
209
+ if (!dryRun) {
210
+ await fs.writeFile(agIndexPath, `${JSON.stringify(index, null, 2)}\n`, 'utf-8');
211
+ console.log(`Updated ${agIndexPath}`);
212
+ }
213
+ }
214
+
215
+ async function agToRc() {
216
+ if (!existsSync(agAccountsDir)) {
217
+ throw new Error(`Antigravity accounts dir not found: ${agAccountsDir}`);
218
+ }
219
+ await ensureDir(rcAuthDir);
220
+
221
+ const files = readdirSync(agAccountsDir).filter((name) => name.endsWith('.json'));
222
+ if (!files.length) {
223
+ console.log('No antigravity account files found in ~/.antigravity_tools/accounts');
224
+ return;
225
+ }
226
+
227
+ let seq = 1;
228
+ for (const name of files) {
229
+ const filePath = path.join(agAccountsDir, name);
230
+ let account;
231
+ try {
232
+ account = JSON.parse(await fs.readFile(filePath, 'utf-8'));
233
+ } catch {
234
+ console.log(`Skip unreadable account: ${filePath}`);
235
+ continue;
236
+ }
237
+ const token = account?.token;
238
+ if (!token || typeof token.access_token !== 'string') {
239
+ console.log(`Skip account without token: ${filePath}`);
240
+ continue;
241
+ }
242
+ const alias = typeof account.email === 'string' ? account.email.split('@')[0] : 'imported';
243
+ const rcPath = path.join(rcAuthDir, `antigravity-oauth-${seq}-${alias}.json`);
244
+ seq += 1;
245
+
246
+ const payload = {
247
+ access_token: token.access_token,
248
+ refresh_token: token.refresh_token,
249
+ token_type: token.token_type || 'Bearer',
250
+ expires_in: token.expires_in,
251
+ expires_at: typeof token.expiry_timestamp === 'number' ? token.expiry_timestamp * 1000 : undefined,
252
+ email: account.email,
253
+ name: account.name ?? undefined,
254
+ project_id: token.project_id || account.project_id || account.projectId,
255
+ projectId: token.project_id || account.projectId || account.project_id,
256
+ disabled: account.disabled === true,
257
+ disabled_reason: account.disabled_reason ?? undefined,
258
+ disabled_at: account.disabled_at ?? undefined,
259
+ proxy_disabled: account.proxy_disabled === true,
260
+ proxy_disabled_reason: account.proxy_disabled_reason ?? undefined,
261
+ proxy_disabled_at: account.proxy_disabled_at ?? undefined,
262
+ protected_models: Array.isArray(account.protected_models) ? account.protected_models : undefined
263
+ };
264
+
265
+ if (dryRun) {
266
+ console.log(`[dry-run] write ${rcPath}`);
267
+ } else {
268
+ await fs.writeFile(rcPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
269
+ console.log(`Wrote ${rcPath}`);
270
+ }
271
+ }
272
+ }
273
+
274
+ try {
275
+ if (direction === 'ag-to-rc') {
276
+ await agToRc();
277
+ } else {
278
+ await rcToAg();
279
+ }
280
+ } catch (error) {
281
+ console.error(error instanceof Error ? error.message : String(error));
282
+ process.exit(1);
283
+ }
@@ -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');