@jsonstudio/rcc 0.89.683 → 0.89.912
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 +44 -0
- package/dist/build-info.js +2 -2
- package/dist/cli.js +164 -116
- package/dist/cli.js.map +1 -1
- package/dist/client/anthropic/anthropic-protocol-client.js +42 -1
- package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +4 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/commands/camoufox-backfill.d.ts +2 -0
- package/dist/commands/camoufox-backfill.js +33 -0
- package/dist/commands/camoufox-backfill.js.map +1 -0
- package/dist/commands/camoufox-fp.d.ts +2 -0
- package/dist/commands/camoufox-fp.js +86 -0
- package/dist/commands/camoufox-fp.js.map +1 -0
- package/dist/commands/oauth.d.ts +2 -0
- package/dist/commands/oauth.js +170 -0
- package/dist/commands/oauth.js.map +1 -0
- package/dist/commands/provider-update.js +439 -2
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/commands/quota-status.d.ts +2 -0
- package/dist/commands/quota-status.js +80 -0
- package/dist/commands/quota-status.js.map +1 -0
- package/dist/commands/token-daemon.js +12 -1
- package/dist/commands/token-daemon.js.map +1 -1
- package/dist/config/provider-v2-loader.d.ts +16 -0
- package/dist/config/provider-v2-loader.js +84 -0
- package/dist/config/provider-v2-loader.js.map +1 -0
- package/dist/config/routecodex-config-loader.js +27 -4
- package/dist/config/routecodex-config-loader.js.map +1 -1
- package/dist/config/system-prompts/codex-cli.txt +1 -0
- package/dist/config/virtual-router-builder.d.ts +9 -0
- package/dist/config/virtual-router-builder.js +34 -0
- package/dist/config/virtual-router-builder.js.map +1 -0
- package/dist/config/virtual-router-types.d.ts +25 -0
- package/dist/config/virtual-router-types.js +30 -0
- package/dist/config/virtual-router-types.js.map +1 -0
- package/dist/manager/index.d.ts +10 -0
- package/dist/manager/index.js +27 -0
- package/dist/manager/index.js.map +1 -0
- package/dist/manager/modules/health/index.d.ts +22 -0
- package/dist/manager/modules/health/index.js +82 -0
- package/dist/manager/modules/health/index.js.map +1 -0
- package/dist/manager/modules/quota/index.d.ts +57 -0
- package/dist/manager/modules/quota/index.js +426 -0
- package/dist/manager/modules/quota/index.js.map +1 -0
- package/dist/manager/modules/routing/index.d.ts +17 -0
- package/dist/manager/modules/routing/index.js +61 -0
- package/dist/manager/modules/routing/index.js.map +1 -0
- package/dist/manager/modules/token/index.d.ts +10 -0
- package/dist/manager/modules/token/index.js +58 -0
- package/dist/manager/modules/token/index.js.map +1 -0
- package/dist/manager/storage/base-store.d.ts +6 -0
- package/dist/manager/storage/base-store.js +2 -0
- package/dist/manager/storage/base-store.js.map +1 -0
- package/dist/manager/storage/file-store.d.ts +25 -0
- package/dist/manager/storage/file-store.js +117 -0
- package/dist/manager/storage/file-store.js.map +1 -0
- package/dist/manager/types.d.ts +9 -0
- package/dist/manager/types.js +2 -0
- package/dist/manager/types.js.map +1 -0
- package/dist/message-center/index.d.ts +5 -0
- package/dist/message-center/index.js +6 -0
- package/dist/message-center/index.js.map +1 -0
- package/dist/message-center/message-center.d.ts +93 -0
- package/dist/message-center/message-center.js +189 -0
- package/dist/message-center/message-center.js.map +1 -0
- package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js +102 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/iflow-cookie-auth.d.ts +27 -0
- package/dist/providers/auth/iflow-cookie-auth.js +209 -0
- package/dist/providers/auth/iflow-cookie-auth.js.map +1 -0
- package/dist/providers/auth/oauth-lifecycle.js +29 -22
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/token-scanner/index.js +16 -1
- package/dist/providers/auth/token-scanner/index.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +16 -0
- package/dist/providers/core/config/camoufox-launcher.js +314 -0
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -0
- package/dist/providers/core/config/oauth-flows.d.ts +9 -0
- package/dist/providers/core/config/oauth-flows.js +50 -19
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.d.ts +6 -0
- package/dist/providers/core/config/provider-oauth-configs.js +12 -0
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +26 -3
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/antigravity-quota-client.d.ts +10 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js +88 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -0
- package/dist/providers/core/runtime/base-provider.d.ts +2 -1
- package/dist/providers/core/runtime/base-provider.js +93 -34
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +42 -10
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.js +24 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +0 -3
- package/dist/providers/core/runtime/http-transport-provider.js +32 -136
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.js +18 -10
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +6 -0
- package/dist/providers/core/runtime/rate-limit-manager.js +23 -0
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.js +17 -19
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-device-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-hybrid-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-hybrid-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-hybrid-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +43 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +4 -4
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +13 -1
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +5 -0
- package/dist/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/dist/scripts/camoufox/launch-auth.mjs +617 -0
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +138 -0
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +166 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +109 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js +43 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +19 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js +27 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -0
- package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
- package/dist/server/runtime/http-server/executor-provider.js +26 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.d.ts +16 -0
- package/dist/server/runtime/http-server/executor-response.js +164 -0
- package/dist/server/runtime/http-server/executor-response.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +6 -0
- package/dist/server/runtime/http-server/index.js +121 -53
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +3 -0
- package/dist/server/runtime/http-server/request-executor.js +73 -21
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +5 -0
- package/dist/server/runtime/http-server/routes.js +45 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +1 -0
- package/dist/server/utils/client-connection-state.d.ts +8 -0
- package/dist/server/utils/client-connection-state.js +52 -0
- package/dist/server/utils/client-connection-state.js.map +1 -0
- package/dist/server/utils/request-id-manager.js +21 -3
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/token-daemon/history-store.d.ts +2 -0
- package/dist/token-daemon/history-store.js +6 -2
- package/dist/token-daemon/history-store.js.map +1 -1
- package/dist/token-daemon/index.js +36 -5
- package/dist/token-daemon/index.js.map +1 -1
- package/dist/token-daemon/leader-lock.d.ts +11 -0
- package/dist/token-daemon/leader-lock.js +79 -0
- package/dist/token-daemon/leader-lock.js.map +1 -0
- package/dist/token-daemon/message-bus-integrator.d.ts +98 -0
- package/dist/token-daemon/message-bus-integrator.js +144 -0
- package/dist/token-daemon/message-bus-integrator.js.map +1 -0
- package/dist/token-daemon/provider-registry.d.ts +22 -0
- package/dist/token-daemon/provider-registry.js +201 -0
- package/dist/token-daemon/provider-registry.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +8 -0
- package/dist/token-daemon/token-daemon.js +196 -11
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/token-portal/local-token-portal.d.ts +1 -0
- package/dist/token-portal/local-token-portal.js +18 -0
- package/dist/token-portal/local-token-portal.js.map +1 -1
- package/dist/token-portal/render.js +1 -0
- package/dist/token-portal/render.js.map +1 -1
- package/dist/tools/error-log.d.ts +31 -0
- package/dist/tools/error-log.js +117 -0
- package/dist/tools/error-log.js.map +1 -0
- package/dist/tools/stats-request-events.d.ts +2 -0
- package/dist/tools/stats-request-events.js +16 -0
- package/dist/tools/stats-request-events.js.map +1 -0
- package/dist/tools/stats-usage.d.ts +31 -0
- package/dist/tools/stats-usage.js +206 -0
- package/dist/tools/stats-usage.js.map +1 -0
- package/package.json +9 -4
- package/scripts/analyze-codex-error-failures.mjs +111 -0
- package/scripts/analyze-usage-estimate.mjs +240 -0
- package/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/scripts/camoufox/launch-auth.mjs +617 -0
- package/scripts/classify-codex-samples.mjs +251 -0
- package/scripts/cleanup-codex-error-samples.mjs +88 -0
- package/scripts/compare-codex-rccx.mjs +268 -0
- package/scripts/copy-compat-assets.mjs +18 -0
- package/scripts/install-release.sh +1 -1
- package/scripts/local-replay-openai-response.mjs +1 -2
- package/scripts/pack-mode.mjs +16 -6
- package/scripts/replay-codex-sample.mjs +24 -2
- package/scripts/responses-compare-server.mjs +119 -0
- package/scripts/tests/apply-patch-loop.mjs +266 -7
- package/scripts/tests/exec-command-loop.mjs +165 -0
- package/scripts/tool-classification-report.ts +281 -0
- package/scripts/verification/samples/openai-chat-list-local-files.json +1 -1
- package/scripts/verify-apply-patch.mjs +28 -17
- package/scripts/verify-codex-error-samples.mjs +102 -0
- package/scripts/verify-e2e-toolcall.mjs +71 -4
- package/scripts/virtual-router-shadow-v2-real.mjs +143 -0
- package/scripts/virtual-router-shadow-v2.mjs +122 -0
package/README.md
CHANGED
|
@@ -228,6 +228,50 @@ RouteCodex 会按以下优先级查找配置:
|
|
|
228
228
|
|
|
229
229
|
---
|
|
230
230
|
|
|
231
|
+
## TOON 工具协议与 CLI 解码说明
|
|
232
|
+
|
|
233
|
+
RouteCodex / llmswitch-core 对「模型看到的工具参数」与「CLI/执行器真正消费的参数」做了明确分层:
|
|
234
|
+
|
|
235
|
+
- **模型视角(统一协议)**
|
|
236
|
+
- 所有支持 TOON 的工具(例如 `exec_command`、`apply_patch`)都可以通过 `arguments.toon` 传参:
|
|
237
|
+
- 形如 `command: ...\nworkdir: ...\n` 的多行 `key: value`。
|
|
238
|
+
- 模型无需关心 CLI 的内部 JSON 结构(`cmd` / `input` 等字段名)。
|
|
239
|
+
- **执行视角(CLI 黑盒)**
|
|
240
|
+
- Codex CLI 的工具实现不会理解 TOON,只接受传统 JSON 形态:
|
|
241
|
+
- `exec_command` 只认 `{ cmd: string, workdir?, command? ... }`。
|
|
242
|
+
- `apply_patch` 只认 `{ input: string, patch?: string }`,且 `input` 必须是标准统一 diff(`*** Begin Patch` 开头)。
|
|
243
|
+
- CLI 被视为黑盒:不能指望它去解析 TOON 或结构化 `changes`。
|
|
244
|
+
|
|
245
|
+
为此,llmswitch-core 在 **响应侧** 增加了成对的解码过滤器,用于在把响应发回 CLI 之前“翻译”工具参数:
|
|
246
|
+
|
|
247
|
+
- `ResponseToolArgumentsToonDecodeFilter`
|
|
248
|
+
- 作用于所有协议(包括 `/v1/responses`),在响应处理阶段对 `choices[].message.tool_calls[*].function.arguments` 解码:
|
|
249
|
+
- 对 shell/exec 类工具(`shell` / `shell_command` / `exec_command`):
|
|
250
|
+
- 从 `toon` 中解析 `command`/`cmd`、`workdir`/`cwd`、`timeout_ms`、`with_escalated_permissions`、`justification` 等字段。
|
|
251
|
+
- 统一输出为 JSON 字符串:`{"cmd":"...","command":"...","workdir":"...","timeout_ms":...,"with_escalated_permissions":...,"justification":"..."}`。
|
|
252
|
+
- 对其它工具(如 `view_image`、MCP 工具等):
|
|
253
|
+
- 将所有 TOON `key: value` 对映射为普通 JSON 字段,并做轻量类型推断(`true/false`→布尔,数字→number,可解析的 `{}`/`[]`→JSON 对象/数组)。
|
|
254
|
+
- 对 `apply_patch`:
|
|
255
|
+
- 交由专门的 `ResponseApplyPatchToonDecodeFilter` 处理,当前过滤器只负责 shell/exec 与通用工具,避免相互覆盖。
|
|
256
|
+
- `ResponseApplyPatchToonDecodeFilter`
|
|
257
|
+
- 专门负责 `apply_patch` 的响应参数规范化:
|
|
258
|
+
- 支持两类输入:
|
|
259
|
+
- `{"toon":"*** Begin Patch ... *** End Patch"}`;
|
|
260
|
+
- 结构化 `changes` payload(多种 `kind`:insert_after / insert_before / replace / delete / create_file / delete_file)。
|
|
261
|
+
- 将其统一转换为 `{ input: "<统一 diff>", patch: "<统一 diff>" }` 的 JSON 字符串挂回 `function.arguments`,以兼容 CLI 旧语义。
|
|
262
|
+
|
|
263
|
+
整体约束可以概括为:
|
|
264
|
+
|
|
265
|
+
- **对模型**:可以使用 TOON 或结构化 JSON(例如 `changes`);RouteCodex 会在 Hub Pipeline 内对齐为统一 JSON 结构。
|
|
266
|
+
- **对 CLI / 客户端**:始终看到历史兼容形态:
|
|
267
|
+
- `exec_command`:具备 `cmd` 字段的 JSON;
|
|
268
|
+
- `apply_patch`:具备 `input`(统一 diff)的 JSON。
|
|
269
|
+
- **对维护者**:
|
|
270
|
+
- 所有 TOON → JSON 的解码逻辑集中在 `sharedmodule/llmswitch-core/src/filters/special/` 及响应工具治理路径中;
|
|
271
|
+
- CLI 侧不需要理解 TOON,也无需修改其内部工具实现;一切转换在 Hub 层完成。
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
231
275
|
## 参考文档
|
|
232
276
|
|
|
233
277
|
- `docs/ARCHITECTURE.md` – 全量架构细节与数据流
|
package/dist/build-info.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -118,84 +118,64 @@ program
|
|
|
118
118
|
.description('RouteCodex CLI - Multi-provider OpenAI proxy server and Claude Code interface')
|
|
119
119
|
.version(cliVersion);
|
|
120
120
|
async function ensureTokenDaemonAutoStart() {
|
|
121
|
+
// Token 刷新逻辑已经在服务器进程内通过 ManagerDaemon/TokenManagerModule 执行。
|
|
122
|
+
// 为避免重复启动独立的 token-daemon 进程,这里不再自动拉起后台守护,仅保留显式 CLI 命令。
|
|
123
|
+
const disabledEnv = String(process.env.ROUTECODEX_TOKEN_DAEMON_DISABLED || process.env.RCC_TOKEN_DAEMON_DISABLED || '')
|
|
124
|
+
.trim()
|
|
125
|
+
.toLowerCase();
|
|
126
|
+
if (disabledEnv !== '1' && disabledEnv !== 'true' && disabledEnv !== 'yes') {
|
|
127
|
+
logger.info('Token manager is now integrated into the server process; automatic external token-daemon auto-start is disabled.');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function stopTokenDaemonIfRunning() {
|
|
121
131
|
try {
|
|
122
|
-
|
|
123
|
-
.trim()
|
|
124
|
-
.toLowerCase();
|
|
125
|
-
if (disabledEnv === '1' || disabledEnv === 'true' || disabledEnv === 'yes') {
|
|
126
|
-
logger.info('Token daemon auto-start disabled via env (ROUTECODEX_TOKEN_DAEMON_DISABLED/RCC_TOKEN_DAEMON_DISABLED)');
|
|
132
|
+
if (!fs.existsSync(TOKEN_DAEMON_PID_FILE)) {
|
|
127
133
|
return;
|
|
128
134
|
}
|
|
129
|
-
|
|
135
|
+
const txt = fs.readFileSync(TOKEN_DAEMON_PID_FILE, 'utf8');
|
|
136
|
+
const parsed = Number(String(txt || '').trim());
|
|
137
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const pid = parsed;
|
|
141
|
+
let running = false;
|
|
130
142
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const parsed = Number(String(txt || '').trim());
|
|
134
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
135
|
-
existingPid = parsed;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
143
|
+
process.kill(pid, 0);
|
|
144
|
+
running = true;
|
|
138
145
|
}
|
|
139
146
|
catch {
|
|
140
|
-
|
|
147
|
+
running = false;
|
|
141
148
|
}
|
|
142
|
-
|
|
143
|
-
const deadline = Date.now() + timeoutMs;
|
|
144
|
-
while (Date.now() < deadline) {
|
|
145
|
-
try {
|
|
146
|
-
process.kill(pid, 0);
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
if (existingPid) {
|
|
155
|
-
let running = false;
|
|
156
|
-
try {
|
|
157
|
-
process.kill(existingPid, 0);
|
|
158
|
-
running = true;
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
running = false;
|
|
162
|
-
}
|
|
163
|
-
if (running) {
|
|
164
|
-
logger.info(`Restarting token daemon to refresh environment (pid=${existingPid})`);
|
|
165
|
-
try {
|
|
166
|
-
process.kill(existingPid, 'SIGTERM');
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// ignore
|
|
170
|
-
}
|
|
171
|
-
await waitForProcessExit(existingPid, 2000);
|
|
172
|
-
}
|
|
149
|
+
if (!running) {
|
|
173
150
|
try {
|
|
174
151
|
fs.unlinkSync(TOKEN_DAEMON_PID_FILE);
|
|
175
152
|
}
|
|
176
|
-
catch {
|
|
177
|
-
|
|
178
|
-
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
return;
|
|
179
155
|
}
|
|
180
|
-
const nodeBin = process.execPath;
|
|
181
|
-
const cliEntry = path.resolve(__dirname, 'cli.js');
|
|
182
|
-
const args = [cliEntry, 'token-daemon', 'start'];
|
|
183
|
-
const { spawn } = await import('child_process');
|
|
184
|
-
const child = spawn(nodeBin, args, {
|
|
185
|
-
stdio: 'ignore',
|
|
186
|
-
detached: true,
|
|
187
|
-
env: { ...process.env }
|
|
188
|
-
});
|
|
189
156
|
try {
|
|
190
|
-
|
|
157
|
+
process.kill(pid, 'SIGTERM');
|
|
191
158
|
}
|
|
192
159
|
catch {
|
|
193
160
|
// ignore
|
|
194
161
|
}
|
|
195
|
-
|
|
162
|
+
const deadline = Date.now() + 2000;
|
|
163
|
+
while (Date.now() < deadline) {
|
|
164
|
+
try {
|
|
165
|
+
process.kill(pid, 0);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
fs.unlinkSync(TOKEN_DAEMON_PID_FILE);
|
|
174
|
+
}
|
|
175
|
+
catch { /* ignore */ }
|
|
196
176
|
}
|
|
197
|
-
catch
|
|
198
|
-
|
|
177
|
+
catch {
|
|
178
|
+
// best-effort: failures here must not break CLI shutdown
|
|
199
179
|
}
|
|
200
180
|
}
|
|
201
181
|
// Provider command group - update models and generate minimal provider config
|
|
@@ -204,12 +184,36 @@ try {
|
|
|
204
184
|
program.addCommand(createProviderUpdateCommand());
|
|
205
185
|
}
|
|
206
186
|
catch { /* optional: command not available in some builds */ }
|
|
187
|
+
// Camoufox fingerprint debug command (optional)
|
|
188
|
+
try {
|
|
189
|
+
const { createCamoufoxFpCommand } = await import('./commands/camoufox-fp.js');
|
|
190
|
+
program.addCommand(createCamoufoxFpCommand());
|
|
191
|
+
}
|
|
192
|
+
catch { /* optional */ }
|
|
193
|
+
// Camoufox fingerprint backfill command (optional)
|
|
194
|
+
try {
|
|
195
|
+
const { createCamoufoxBackfillCommand } = await import('./commands/camoufox-backfill.js');
|
|
196
|
+
program.addCommand(createCamoufoxBackfillCommand());
|
|
197
|
+
}
|
|
198
|
+
catch { /* optional */ }
|
|
207
199
|
// Token daemon command group - manage OAuth tokens
|
|
208
200
|
try {
|
|
209
201
|
const { createTokenDaemonCommand } = await import('./commands/token-daemon.js');
|
|
210
202
|
program.addCommand(createTokenDaemonCommand());
|
|
211
203
|
}
|
|
212
204
|
catch { /* optional: command not available in some builds */ }
|
|
205
|
+
// Quota status command - inspect daemon-managed quota snapshot
|
|
206
|
+
try {
|
|
207
|
+
const { createQuotaStatusCommand } = await import('./commands/quota-status.js');
|
|
208
|
+
program.addCommand(createQuotaStatusCommand());
|
|
209
|
+
}
|
|
210
|
+
catch { /* optional */ }
|
|
211
|
+
// OAuth command - force re-auth for a specific token (Camoufox-aware when enabled)
|
|
212
|
+
try {
|
|
213
|
+
const { createOauthCommand } = await import('./commands/oauth.js');
|
|
214
|
+
program.addCommand(createOauthCommand());
|
|
215
|
+
}
|
|
216
|
+
catch { /* optional: command not available in some builds */ }
|
|
213
217
|
// Validate command - auto start server then run E2E checks
|
|
214
218
|
try {
|
|
215
219
|
const { createValidateCommand } = await import('./commands/validate.js');
|
|
@@ -796,6 +800,9 @@ program
|
|
|
796
800
|
catch { /* ignore */ }
|
|
797
801
|
}
|
|
798
802
|
}
|
|
803
|
+
if (IS_DEV_PACKAGE) {
|
|
804
|
+
await stopTokenDaemonIfRunning();
|
|
805
|
+
}
|
|
799
806
|
// Ensure parent exits even if child fails to exit
|
|
800
807
|
try {
|
|
801
808
|
process.exit(0);
|
|
@@ -1072,37 +1079,54 @@ program
|
|
|
1072
1079
|
.action(async () => {
|
|
1073
1080
|
const spinner = await createSpinner('Stopping RouteCodex server...');
|
|
1074
1081
|
try {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
let config;
|
|
1087
|
-
try {
|
|
1088
|
-
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
1089
|
-
config = JSON.parse(configContent);
|
|
1090
|
-
}
|
|
1091
|
-
catch (error) {
|
|
1092
|
-
spinner.fail('Failed to parse configuration file');
|
|
1093
|
-
logger.error(`Invalid JSON in configuration file: ${configPath}`);
|
|
1094
|
-
process.exit(1);
|
|
1082
|
+
let resolvedPort;
|
|
1083
|
+
if (IS_DEV_PACKAGE) {
|
|
1084
|
+
const envPort = Number(process.env.ROUTECODEX_PORT || process.env.RCC_PORT || NaN);
|
|
1085
|
+
if (!Number.isNaN(envPort) && envPort > 0) {
|
|
1086
|
+
logger.info(`Using port ${envPort} from environment (ROUTECODEX_PORT/RCC_PORT) [dev package: routecodex]`);
|
|
1087
|
+
resolvedPort = envPort;
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
resolvedPort = DEFAULT_DEV_PORT;
|
|
1091
|
+
logger.info(`Using dev default port ${resolvedPort} (routecodex dev package)`);
|
|
1092
|
+
}
|
|
1095
1093
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1094
|
+
else {
|
|
1095
|
+
// Resolve config path and port
|
|
1096
|
+
const configPath = path.join(homedir(), '.routecodex', 'config.json');
|
|
1097
|
+
// Check if config exists
|
|
1098
|
+
if (!fs.existsSync(configPath)) {
|
|
1099
|
+
spinner.fail(`Configuration file not found: ${configPath}`);
|
|
1100
|
+
logger.error('Cannot determine server port without configuration file');
|
|
1101
|
+
logger.info('Please create a configuration file first:');
|
|
1102
|
+
logger.info(' rcc config init');
|
|
1103
|
+
process.exit(1);
|
|
1104
|
+
}
|
|
1105
|
+
// Load configuration to get port
|
|
1106
|
+
let config;
|
|
1107
|
+
try {
|
|
1108
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
1109
|
+
config = JSON.parse(configContent);
|
|
1110
|
+
}
|
|
1111
|
+
catch (error) {
|
|
1112
|
+
spinner.fail('Failed to parse configuration file');
|
|
1113
|
+
logger.error(`Invalid JSON in configuration file: ${configPath}`);
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
const port = (config?.httpserver?.port ?? config?.server?.port ?? config?.port);
|
|
1117
|
+
if (!port || typeof port !== 'number' || port <= 0) {
|
|
1118
|
+
spinner.fail('Invalid or missing port configuration');
|
|
1119
|
+
logger.error('Configuration file must specify a valid port number');
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
resolvedPort = port;
|
|
1101
1123
|
}
|
|
1102
|
-
const resolvedPort = port;
|
|
1103
1124
|
const pids = findListeningPids(resolvedPort);
|
|
1104
1125
|
if (!pids.length) {
|
|
1105
1126
|
spinner.succeed(`No server listening on ${resolvedPort}.`);
|
|
1127
|
+
if (IS_DEV_PACKAGE) {
|
|
1128
|
+
await stopTokenDaemonIfRunning();
|
|
1129
|
+
}
|
|
1106
1130
|
return;
|
|
1107
1131
|
}
|
|
1108
1132
|
for (const pid of pids) {
|
|
@@ -1115,6 +1139,9 @@ program
|
|
|
1115
1139
|
while (Date.now() < deadline) {
|
|
1116
1140
|
if (findListeningPids(resolvedPort).length === 0) {
|
|
1117
1141
|
spinner.succeed(`Stopped server on ${resolvedPort}.`);
|
|
1142
|
+
if (IS_DEV_PACKAGE) {
|
|
1143
|
+
await stopTokenDaemonIfRunning();
|
|
1144
|
+
}
|
|
1118
1145
|
return;
|
|
1119
1146
|
}
|
|
1120
1147
|
await sleep(100);
|
|
@@ -1129,6 +1156,9 @@ program
|
|
|
1129
1156
|
}
|
|
1130
1157
|
}
|
|
1131
1158
|
spinner.succeed(`Force stopped server on ${resolvedPort}.`);
|
|
1159
|
+
if (IS_DEV_PACKAGE) {
|
|
1160
|
+
await stopTokenDaemonIfRunning();
|
|
1161
|
+
}
|
|
1132
1162
|
}
|
|
1133
1163
|
catch (e) {
|
|
1134
1164
|
spinner.fail(`Failed to stop: ${e.message}`);
|
|
@@ -1146,34 +1176,51 @@ program
|
|
|
1146
1176
|
.action(async (options) => {
|
|
1147
1177
|
const spinner = await createSpinner('Restarting RouteCodex server...');
|
|
1148
1178
|
try {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
try {
|
|
1162
|
-
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
1163
|
-
config = JSON.parse(configContent);
|
|
1164
|
-
}
|
|
1165
|
-
catch (error) {
|
|
1166
|
-
spinner.fail('Failed to parse configuration file');
|
|
1167
|
-
logger.error(`Invalid JSON in configuration file: ${configPath}`);
|
|
1168
|
-
process.exit(1);
|
|
1179
|
+
let resolvedPort;
|
|
1180
|
+
let resolvedHost = LOCAL_HOSTS.LOCALHOST;
|
|
1181
|
+
if (IS_DEV_PACKAGE) {
|
|
1182
|
+
const envPort = Number(process.env.ROUTECODEX_PORT || process.env.RCC_PORT || NaN);
|
|
1183
|
+
if (!Number.isNaN(envPort) && envPort > 0) {
|
|
1184
|
+
logger.info(`Using port ${envPort} from environment (ROUTECODEX_PORT/RCC_PORT) [dev package: routecodex]`);
|
|
1185
|
+
resolvedPort = envPort;
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
resolvedPort = DEFAULT_DEV_PORT;
|
|
1189
|
+
logger.info(`Using dev default port ${resolvedPort} (routecodex dev package)`);
|
|
1190
|
+
}
|
|
1169
1191
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1192
|
+
else {
|
|
1193
|
+
// Resolve config path
|
|
1194
|
+
const configPath = options.config || path.join(homedir(), '.routecodex', 'config.json');
|
|
1195
|
+
// Check if config exists
|
|
1196
|
+
if (!fs.existsSync(configPath)) {
|
|
1197
|
+
spinner.fail(`Configuration file not found: ${configPath}`);
|
|
1198
|
+
logger.error('Cannot determine server port without configuration file');
|
|
1199
|
+
logger.info('Please create a configuration file first:');
|
|
1200
|
+
logger.info(' rcc config init');
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
// Load configuration to get port
|
|
1204
|
+
let config;
|
|
1205
|
+
try {
|
|
1206
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
1207
|
+
config = JSON.parse(configContent);
|
|
1208
|
+
}
|
|
1209
|
+
catch (error) {
|
|
1210
|
+
spinner.fail('Failed to parse configuration file');
|
|
1211
|
+
logger.error(`Invalid JSON in configuration file: ${configPath}`);
|
|
1212
|
+
process.exit(1);
|
|
1213
|
+
}
|
|
1214
|
+
const port = (config?.httpserver?.port ?? config?.server?.port ?? config?.port);
|
|
1215
|
+
if (!port || typeof port !== 'number' || port <= 0) {
|
|
1216
|
+
spinner.fail('Invalid or missing port configuration');
|
|
1217
|
+
logger.error('Configuration file must specify a valid port number');
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
resolvedPort = port;
|
|
1221
|
+
resolvedHost =
|
|
1222
|
+
(config?.httpserver?.host || config?.server?.host || config?.host || LOCAL_HOSTS.LOCALHOST);
|
|
1175
1223
|
}
|
|
1176
|
-
const resolvedPort = port;
|
|
1177
1224
|
// Stop current instance (if any)
|
|
1178
1225
|
const pids = findListeningPids(resolvedPort);
|
|
1179
1226
|
if (pids.length) {
|
|
@@ -1221,8 +1268,7 @@ program
|
|
|
1221
1268
|
fs.writeFileSync(path.join(homedir(), '.routecodex', 'server.cli.pid'), String(child.pid ?? ''), 'utf8');
|
|
1222
1269
|
}
|
|
1223
1270
|
catch (error) { /* ignore */ }
|
|
1224
|
-
|
|
1225
|
-
spinner.succeed(`RouteCodex server restarting on ${host}:${resolvedPort}`);
|
|
1271
|
+
spinner.succeed(`RouteCodex server restarting on ${resolvedHost}:${resolvedPort}`);
|
|
1226
1272
|
logger.info(`Server will run on port: ${resolvedPort}`);
|
|
1227
1273
|
logger.info('Press Ctrl+C to stop the server');
|
|
1228
1274
|
const shutdown = async (sig) => {
|
|
@@ -1622,7 +1668,9 @@ async function ensurePortAvailable(port, parentSpinner, opts = {}) {
|
|
|
1622
1668
|
}
|
|
1623
1669
|
function findListeningPids(port) {
|
|
1624
1670
|
try {
|
|
1625
|
-
|
|
1671
|
+
// macOS/BSD lsof expects either "-i TCP:port" or "-tiTCP:port" as a single argument.
|
|
1672
|
+
// Use the compact form to avoid treating ":port" as a filename.
|
|
1673
|
+
const result = spawnSync('lsof', [`-tiTCP:${port}`, '-sTCP:LISTEN'], { encoding: 'utf8' });
|
|
1626
1674
|
if (result.error) {
|
|
1627
1675
|
logger.warning(`lsof not available to inspect port usage: ${result.error.message}`);
|
|
1628
1676
|
return [];
|