qwenproxy-cli 1.0.27 → 1.0.29
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/package.json +3 -3
- package/src/core/account-manager.ts +1 -1
- package/src/core/server-log-buffer.ts +7 -1
- package/src/services/media-generation.ts +2 -2
- package/src/services/playwright.ts +4 -1
- package/src/sync/aider.ts +118 -0
- package/src/sync/cline.ts +110 -0
- package/src/sync/hermes.ts +105 -0
- package/src/sync/index.ts +323 -8
- package/src/sync/kilo.ts +230 -0
- package/src/sync/openclaw.ts +237 -0
- package/src/sync/types.ts +29 -3
- package/src/sync/zed.ts +210 -0
- package/src/sync-clients.ts +32 -18
- package/src/tui/server-manager.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
25
25
|
"purge": "npx tsx src/delete-chats.ts",
|
|
26
26
|
"test": "npm run test:mock && npm run test:live",
|
|
27
|
-
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
|
|
28
|
-
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
|
|
27
|
+
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
|
|
28
|
+
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
|
|
29
29
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
30
30
|
"benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
@@ -29,7 +29,7 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
|
|
|
29
29
|
|
|
30
30
|
const lines = clean.split(/\r?\n/);
|
|
31
31
|
for (const raw of lines) {
|
|
32
|
-
|
|
32
|
+
let line = raw.trim();
|
|
33
33
|
if (!line) continue;
|
|
34
34
|
// Filter out raw ASCII box frames and border rows
|
|
35
35
|
if (/^[+\-=#]{5,}$/.test(line)) continue;
|
|
@@ -49,6 +49,12 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
|
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// Clean redundant leading level tags (e.g. "WARN [Qwen]" -> "[Qwen]")
|
|
53
|
+
// and normalize multi-space gaps after emojis
|
|
54
|
+
line = line
|
|
55
|
+
.replace(/^(?:\[?(?:INFO|WARN|WARNING|ERROR|ERR|DEBUG)\]?\s+)+/i, "")
|
|
56
|
+
.replace(/([\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}]\uFE0F?)\s{2,}/gu, "$1 ");
|
|
57
|
+
if (!line) continue;
|
|
52
58
|
const entry: ServerLogMessage = { time, level, message: line };
|
|
53
59
|
logHistory.push(entry);
|
|
54
60
|
if (logHistory.length > MAX_LOG_HISTORY) {
|
|
@@ -116,7 +116,7 @@ export function logMediaDebug(message: string): void {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
export function logMediaWarn(message: string): void {
|
|
119
|
-
console.warn(`⚠️
|
|
119
|
+
console.warn(`⚠️ ${message}`);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export function logMediaError(message: string): void {
|
|
@@ -658,7 +658,7 @@ async function requestCompletionsWithBrowserFallback(params: {
|
|
|
658
658
|
headers,
|
|
659
659
|
referrer: qwenUrl(`/c/${encodeURIComponent(chatId)}`),
|
|
660
660
|
streaming,
|
|
661
|
-
timeoutMs: Math.min(timeoutMs, IMAGE_TIMEOUT_MS),
|
|
661
|
+
timeoutMs: Math.min(timeoutMs, kind === "video" ? VIDEO_TIMEOUT_MS : IMAGE_TIMEOUT_MS),
|
|
662
662
|
});
|
|
663
663
|
|
|
664
664
|
if (browserResult.error) {
|
|
@@ -2676,7 +2676,10 @@ export async function withAccountPage<T>(
|
|
|
2676
2676
|
return result;
|
|
2677
2677
|
} catch (error) {
|
|
2678
2678
|
const message = getErrorMessage(error);
|
|
2679
|
-
if (
|
|
2679
|
+
if (
|
|
2680
|
+
recoverOnTimeout &&
|
|
2681
|
+
message.includes("Playwright page operation timed out")
|
|
2682
|
+
) {
|
|
2680
2683
|
console.warn(
|
|
2681
2684
|
`⏱️ [Playwright] Resetting account context after a stuck page operation: ${accountId}`,
|
|
2682
2685
|
);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ClientSyncResult, SyncOptions } from "./types.ts";
|
|
4
|
+
import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
|
|
5
|
+
|
|
6
|
+
function updateYamlKeyValue(content: string, key: string, value: string): string {
|
|
7
|
+
const regex = new RegExp(`^${key}:.*$`, "m");
|
|
8
|
+
const line = `${key}: ${value}`;
|
|
9
|
+
if (regex.test(content)) {
|
|
10
|
+
return content.replace(regex, line);
|
|
11
|
+
}
|
|
12
|
+
return content.trimEnd() + (content.length > 0 ? "\n" : "") + line + "\n";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function syncAider(options: SyncOptions): ClientSyncResult {
|
|
16
|
+
const {
|
|
17
|
+
filePath,
|
|
18
|
+
apiKey,
|
|
19
|
+
baseUrl,
|
|
20
|
+
model = "qwen3.8-max",
|
|
21
|
+
reasoningEffort = "high",
|
|
22
|
+
modelSettingsPath,
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
27
|
+
|
|
28
|
+
let backupPath: string | undefined;
|
|
29
|
+
let confContent = "";
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(filePath)) {
|
|
32
|
+
backupPath = createTimestampBackup(filePath);
|
|
33
|
+
confContent = fs.readFileSync(filePath, "utf-8");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fullModelName = model.startsWith("openai/") ? model : `openai/${model}`;
|
|
37
|
+
const weakModelName = "openai/qwen3.7-plus";
|
|
38
|
+
|
|
39
|
+
confContent = updateYamlKeyValue(confContent, "openai-api-base", baseUrl);
|
|
40
|
+
confContent = updateYamlKeyValue(confContent, "openai-api-key", apiKey);
|
|
41
|
+
confContent = updateYamlKeyValue(confContent, "model", fullModelName);
|
|
42
|
+
confContent = updateYamlKeyValue(confContent, "editor-model", fullModelName);
|
|
43
|
+
confContent = updateYamlKeyValue(confContent, "weak-model", weakModelName);
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(filePath, confContent.trimEnd() + "\n", "utf-8");
|
|
46
|
+
|
|
47
|
+
// Configure model settings YAML if path provided
|
|
48
|
+
let extraBackupPath: string | undefined;
|
|
49
|
+
const resolvedSettingsPath =
|
|
50
|
+
modelSettingsPath || path.join(path.dirname(filePath), ".aider.model.settings.yml");
|
|
51
|
+
|
|
52
|
+
if (resolvedSettingsPath) {
|
|
53
|
+
fs.mkdirSync(path.dirname(resolvedSettingsPath), { recursive: true });
|
|
54
|
+
let settingsContent = "";
|
|
55
|
+
|
|
56
|
+
if (fs.existsSync(resolvedSettingsPath)) {
|
|
57
|
+
extraBackupPath = createTimestampBackup(resolvedSettingsPath);
|
|
58
|
+
settingsContent = fs.readFileSync(resolvedSettingsPath, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const modelBlock = `- name: ${fullModelName}
|
|
62
|
+
extra_params:
|
|
63
|
+
extra_body:
|
|
64
|
+
reasoning_effort: ${reasoningEffort}
|
|
65
|
+
streaming: true
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const existingModelRegex = new RegExp(`- name: ${fullModelName}[\\s\\S]*?(?=(?:^-[ \\t]+name:|\\Z))`, "m");
|
|
69
|
+
if (existingModelRegex.test(settingsContent)) {
|
|
70
|
+
settingsContent = settingsContent.replace(existingModelRegex, modelBlock.trimEnd() + "\n");
|
|
71
|
+
} else {
|
|
72
|
+
settingsContent = settingsContent.trimEnd() + (settingsContent.length > 0 ? "\n\n" : "") + modelBlock;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(resolvedSettingsPath, settingsContent.trimEnd() + "\n", "utf-8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
client: "aider",
|
|
80
|
+
filePath,
|
|
81
|
+
backupPath,
|
|
82
|
+
extraBackupPath,
|
|
83
|
+
success: true,
|
|
84
|
+
action: backupPath ? "updated" : "created",
|
|
85
|
+
message: `Configured Aider with model ${fullModelName} and reasoning effort ${reasoningEffort}`,
|
|
86
|
+
};
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
return {
|
|
89
|
+
client: "aider",
|
|
90
|
+
filePath,
|
|
91
|
+
success: false,
|
|
92
|
+
action: "failed",
|
|
93
|
+
error: err?.message || String(err),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function restoreAider(
|
|
99
|
+
filePath: string,
|
|
100
|
+
backupPath?: string,
|
|
101
|
+
modelSettingsPath?: string,
|
|
102
|
+
extraBackupPath?: string,
|
|
103
|
+
): ClientSyncResult {
|
|
104
|
+
const mainRestored = restoreFromBackup(filePath, backupPath);
|
|
105
|
+
if (modelSettingsPath && extraBackupPath) {
|
|
106
|
+
restoreFromBackup(modelSettingsPath, extraBackupPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
client: "aider",
|
|
111
|
+
filePath,
|
|
112
|
+
backupPath,
|
|
113
|
+
extraBackupPath,
|
|
114
|
+
success: mainRestored,
|
|
115
|
+
action: mainRestored ? "restored" : "failed",
|
|
116
|
+
message: mainRestored ? "Restored Aider settings from backup" : "Backup file not found",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import type { ClientSyncResult, SyncOptions } from "./types.ts";
|
|
5
|
+
import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
|
|
6
|
+
|
|
7
|
+
const TARGET_KEYS = [
|
|
8
|
+
"saoudrizwan.claude-dev",
|
|
9
|
+
"ZooCodeOrganization.zoo-code",
|
|
10
|
+
"RooVeterinaryInc.roo-cline",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function syncCline(options: SyncOptions): ClientSyncResult {
|
|
14
|
+
const { filePath, baseUrl, model = "qwen3.8-max", reasoningEffort = "high" } = options;
|
|
15
|
+
try {
|
|
16
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
17
|
+
|
|
18
|
+
let backupPath: string | undefined;
|
|
19
|
+
if (fs.existsSync(filePath)) {
|
|
20
|
+
backupPath = createTimestampBackup(filePath);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const db = new Database(filePath);
|
|
24
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT)`);
|
|
25
|
+
|
|
26
|
+
const selectStmt = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`);
|
|
27
|
+
const insertStmt = db.prepare(`INSERT INTO ItemTable (key, value) VALUES (?, ?)`);
|
|
28
|
+
|
|
29
|
+
const updateConfig = (existingVal?: string): string => {
|
|
30
|
+
let parsed: Record<string, any> = {};
|
|
31
|
+
if (existingVal) {
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(existingVal);
|
|
34
|
+
} catch {
|
|
35
|
+
parsed = {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
parsed.apiProvider = "openai";
|
|
40
|
+
parsed.openAiBaseUrl = baseUrl;
|
|
41
|
+
parsed.openAiModelId = model;
|
|
42
|
+
parsed.enableReasoningEffort = true;
|
|
43
|
+
parsed.reasoningEffort = reasoningEffort;
|
|
44
|
+
parsed.openAiCustomModelInfo = {
|
|
45
|
+
...(parsed.openAiCustomModelInfo || {}),
|
|
46
|
+
contextWindow: 1000000,
|
|
47
|
+
maxTokens: -1,
|
|
48
|
+
supportsImages: true,
|
|
49
|
+
supportsPromptCache: false,
|
|
50
|
+
inputPrice: 0,
|
|
51
|
+
outputPrice: 0,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (Array.isArray(parsed.listApiConfigMeta)) {
|
|
55
|
+
if (!parsed.listApiConfigMeta.some((p: any) => p?.name === "QwenProxy")) {
|
|
56
|
+
parsed.listApiConfigMeta.push({
|
|
57
|
+
name: "QwenProxy",
|
|
58
|
+
id: "qwenproxy-local",
|
|
59
|
+
apiProvider: "openai",
|
|
60
|
+
modelId: model,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
parsed.currentApiConfigName = "QwenProxy";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return JSON.stringify(parsed);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Update keys
|
|
70
|
+
for (const key of TARGET_KEYS) {
|
|
71
|
+
const row = selectStmt.get(key) as { value: string } | undefined;
|
|
72
|
+
// If row exists, update it. If it's saoudrizwan.claude-dev or ZooCode, ensure it is written.
|
|
73
|
+
if (row || key === "saoudrizwan.claude-dev" || key === "ZooCodeOrganization.zoo-code") {
|
|
74
|
+
const newVal = updateConfig(row?.value);
|
|
75
|
+
insertStmt.run(key, newVal);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
db.close();
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
client: "cline",
|
|
83
|
+
filePath,
|
|
84
|
+
backupPath,
|
|
85
|
+
success: true,
|
|
86
|
+
action: backupPath ? "updated" : "created",
|
|
87
|
+
message: `Configured Cline & Zoo Code in state.vscdb with model ${model} and reasoning effort ${reasoningEffort}`,
|
|
88
|
+
};
|
|
89
|
+
} catch (err: any) {
|
|
90
|
+
return {
|
|
91
|
+
client: "cline",
|
|
92
|
+
filePath,
|
|
93
|
+
success: false,
|
|
94
|
+
action: "failed",
|
|
95
|
+
error: err?.message || String(err),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function restoreCline(filePath: string, backupPath?: string): ClientSyncResult {
|
|
101
|
+
const restored = restoreFromBackup(filePath, backupPath);
|
|
102
|
+
return {
|
|
103
|
+
client: "cline",
|
|
104
|
+
filePath,
|
|
105
|
+
backupPath,
|
|
106
|
+
success: restored,
|
|
107
|
+
action: restored ? "restored" : "failed",
|
|
108
|
+
message: restored ? "Restored Cline settings from backup" : "Backup file not found",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ClientSyncResult, SyncOptions } from "./types.ts";
|
|
4
|
+
import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
|
|
5
|
+
|
|
6
|
+
function buildHermesModelBlock(
|
|
7
|
+
baseUrl: string,
|
|
8
|
+
apiKey: string,
|
|
9
|
+
model: string = "qwen3.8-max",
|
|
10
|
+
reasoningEffort: string = "high",
|
|
11
|
+
): string {
|
|
12
|
+
return `model:
|
|
13
|
+
default: "${model}"
|
|
14
|
+
provider: "custom"
|
|
15
|
+
base_url: "${baseUrl}"
|
|
16
|
+
api_key: "${apiKey}"
|
|
17
|
+
reasoning_effort: "${reasoningEffort}"
|
|
18
|
+
reasoning_overrides:
|
|
19
|
+
"qwen3.8-max": "${reasoningEffort}"
|
|
20
|
+
"qwen3.7-plus": "medium"
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildHermesProviderBlock(baseUrl: string, apiKey: string): string {
|
|
25
|
+
return ` qwenproxy:
|
|
26
|
+
base_url: "${baseUrl}"
|
|
27
|
+
api_mode: chat_completions
|
|
28
|
+
api_key: "${apiKey}"
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function syncHermes(options: SyncOptions): ClientSyncResult {
|
|
33
|
+
const { filePath, apiKey, baseUrl, model = "qwen3.8-max", reasoningEffort = "high" } = options;
|
|
34
|
+
try {
|
|
35
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
36
|
+
|
|
37
|
+
let backupPath: string | undefined;
|
|
38
|
+
let content = "";
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(filePath)) {
|
|
41
|
+
backupPath = createTimestampBackup(filePath);
|
|
42
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const modelBlock = buildHermesModelBlock(baseUrl, apiKey, model, reasoningEffort);
|
|
46
|
+
const providerBlock = buildHermesProviderBlock(baseUrl, apiKey);
|
|
47
|
+
|
|
48
|
+
// Replace or insert model: block
|
|
49
|
+
const modelRegex = /^model:[\s\S]*?(?=(?:^[a-zA-Z0-9_-]+:|\Z))/m;
|
|
50
|
+
if (modelRegex.test(content)) {
|
|
51
|
+
content = content.replace(modelRegex, modelBlock);
|
|
52
|
+
} else {
|
|
53
|
+
content = modelBlock + "\n" + content.trimStart();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Replace or insert qwenproxy under providers:
|
|
57
|
+
if (!content.includes("providers:")) {
|
|
58
|
+
content = content.trimEnd() + "\n\nproviders:\n" + providerBlock;
|
|
59
|
+
} else {
|
|
60
|
+
const existingQwenRegex = /^ {2}qwenproxy:[\s\S]*?(?=(?:^ {2}[a-zA-Z0-9_-]+:|^[a-zA-Z0-9_-]+:|\Z))/m;
|
|
61
|
+
if (existingQwenRegex.test(content)) {
|
|
62
|
+
content = content.replace(existingQwenRegex, providerBlock);
|
|
63
|
+
} else {
|
|
64
|
+
const providersMatch = content.match(/^providers:\s*$/m);
|
|
65
|
+
if (providersMatch && providersMatch.index !== undefined) {
|
|
66
|
+
const insertIdx = providersMatch.index + providersMatch[0].length;
|
|
67
|
+
content = content.slice(0, insertIdx) + "\n" + providerBlock + content.slice(insertIdx);
|
|
68
|
+
} else {
|
|
69
|
+
content = content.trimEnd() + "\n\nproviders:\n" + providerBlock;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(filePath, content.trimEnd() + "\n", "utf-8");
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
client: "hermes",
|
|
78
|
+
filePath,
|
|
79
|
+
backupPath,
|
|
80
|
+
success: true,
|
|
81
|
+
action: backupPath ? "updated" : "created",
|
|
82
|
+
message: `Configured Hermes Agent with model ${model} and reasoning effort ${reasoningEffort}`,
|
|
83
|
+
};
|
|
84
|
+
} catch (err: any) {
|
|
85
|
+
return {
|
|
86
|
+
client: "hermes",
|
|
87
|
+
filePath,
|
|
88
|
+
success: false,
|
|
89
|
+
action: "failed",
|
|
90
|
+
error: err?.message || String(err),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function restoreHermes(filePath: string, backupPath?: string): ClientSyncResult {
|
|
96
|
+
const restored = restoreFromBackup(filePath, backupPath);
|
|
97
|
+
return {
|
|
98
|
+
client: "hermes",
|
|
99
|
+
filePath,
|
|
100
|
+
backupPath,
|
|
101
|
+
success: restored,
|
|
102
|
+
action: restored ? "restored" : "failed",
|
|
103
|
+
message: restored ? "Restored Hermes config from backup" : "Backup file not found",
|
|
104
|
+
};
|
|
105
|
+
}
|