qwenproxy-cli 1.0.0 → 1.0.2
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/LICENSE +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +77 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2878 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +259 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/update-cli.ts +121 -0
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- package/src/utils/types.ts +109 -109
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { config } from "../core/config.ts";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
closeIdlePlaywrightAccounts,
|
|
5
|
-
evictIdlePlaywrightContextsToLimit,
|
|
6
|
-
getActivePlaywrightAccountIds,
|
|
7
|
-
isPlaywrightAlreadyClosedError,
|
|
8
|
-
keepAlivePlaywrightAccount,
|
|
9
|
-
} from "./playwright.ts";
|
|
10
|
-
import { humanDelay, sleep } from "./human-behavior.ts";
|
|
11
|
-
|
|
12
|
-
let running = false;
|
|
13
|
-
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
14
|
-
let cycleInProgress = false;
|
|
15
|
-
|
|
16
|
-
export function isSessionKeeperRunning(): boolean {
|
|
17
|
-
return running;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function runKeepAliveCycle(): Promise<void> {
|
|
21
|
-
if (cycleInProgress) return;
|
|
22
|
-
cycleInProgress = true;
|
|
23
|
-
try {
|
|
24
|
-
if (config.sessionKeeper.enabled) {
|
|
25
|
-
const accountIds = getActivePlaywrightAccountIds();
|
|
26
|
-
for (const accountId of accountIds) {
|
|
27
|
-
await keepAlivePlaywrightAccount(accountId).catch((error) => {
|
|
28
|
-
// Shutdown/eviction closes contexts while a cycle is in flight; the
|
|
29
|
-
// resulting "already closed" rejection is benign. The old substring
|
|
30
|
-
// filter ("Target closed"/"Page is closed") missed Playwright's real
|
|
31
|
-
// message ("Target page, context or browser has been closed") and
|
|
32
|
-
// leaked a warning on every Ctrl+C.
|
|
33
|
-
if (isPlaywrightAlreadyClosedError(error)) return;
|
|
34
|
-
const message =
|
|
35
|
-
error instanceof Error ? error.message : String(error);
|
|
36
|
-
console.warn(
|
|
37
|
-
`[SessionKeeper] Keep-alive failed for ${accountId}: ${message}`,
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
await sleep(humanDelay(250, 900));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const closed = await closeIdlePlaywrightAccounts(
|
|
45
|
-
config.playwright.idleContextTtlMs,
|
|
46
|
-
);
|
|
47
|
-
const evicted = await evictIdlePlaywrightContextsToLimit();
|
|
48
|
-
const totalClosed = closed + evicted;
|
|
49
|
-
if (totalClosed > 0) {
|
|
50
|
-
console.log(
|
|
51
|
-
`🧹 [SessionKeeper] Closed ${totalClosed} idle Playwright context(s)`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
} finally {
|
|
55
|
-
cycleInProgress = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function startSessionKeeper(): void {
|
|
60
|
-
const hasKeepAliveWork = config.sessionKeeper.enabled;
|
|
61
|
-
const hasIdleCleanupWork = config.playwright.idleContextTtlMs > 0;
|
|
62
|
-
if (running || (!hasKeepAliveWork && !hasIdleCleanupWork)) return;
|
|
63
|
-
|
|
64
|
-
running = true;
|
|
65
|
-
intervalId = setInterval(() => {
|
|
66
|
-
if (running) void runKeepAliveCycle();
|
|
67
|
-
}, config.sessionKeeper.intervalMs);
|
|
68
|
-
intervalId.unref?.();
|
|
69
|
-
|
|
70
|
-
if (config.sessionKeeper.enabled) {
|
|
71
|
-
console.log(
|
|
72
|
-
`💓 [SessionKeeper] Keep-alive enabled | interval=${config.sessionKeeper.intervalMs}ms idle=${config.sessionKeeper.idleMs}ms`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function stopSessionKeeper(): void {
|
|
78
|
-
running = false;
|
|
79
|
-
if (intervalId) {
|
|
80
|
-
clearInterval(intervalId);
|
|
81
|
-
intervalId = null;
|
|
82
|
-
}
|
|
83
|
-
cycleInProgress = false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function runSessionKeeperOnceForTesting(): Promise<void> {
|
|
87
|
-
await runKeepAliveCycle();
|
|
88
|
-
}
|
|
1
|
+
import { config } from "../core/config.ts";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closeIdlePlaywrightAccounts,
|
|
5
|
+
evictIdlePlaywrightContextsToLimit,
|
|
6
|
+
getActivePlaywrightAccountIds,
|
|
7
|
+
isPlaywrightAlreadyClosedError,
|
|
8
|
+
keepAlivePlaywrightAccount,
|
|
9
|
+
} from "./playwright.ts";
|
|
10
|
+
import { humanDelay, sleep } from "./human-behavior.ts";
|
|
11
|
+
|
|
12
|
+
let running = false;
|
|
13
|
+
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
14
|
+
let cycleInProgress = false;
|
|
15
|
+
|
|
16
|
+
export function isSessionKeeperRunning(): boolean {
|
|
17
|
+
return running;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runKeepAliveCycle(): Promise<void> {
|
|
21
|
+
if (cycleInProgress) return;
|
|
22
|
+
cycleInProgress = true;
|
|
23
|
+
try {
|
|
24
|
+
if (config.sessionKeeper.enabled) {
|
|
25
|
+
const accountIds = getActivePlaywrightAccountIds();
|
|
26
|
+
for (const accountId of accountIds) {
|
|
27
|
+
await keepAlivePlaywrightAccount(accountId).catch((error) => {
|
|
28
|
+
// Shutdown/eviction closes contexts while a cycle is in flight; the
|
|
29
|
+
// resulting "already closed" rejection is benign. The old substring
|
|
30
|
+
// filter ("Target closed"/"Page is closed") missed Playwright's real
|
|
31
|
+
// message ("Target page, context or browser has been closed") and
|
|
32
|
+
// leaked a warning on every Ctrl+C.
|
|
33
|
+
if (isPlaywrightAlreadyClosedError(error)) return;
|
|
34
|
+
const message =
|
|
35
|
+
error instanceof Error ? error.message : String(error);
|
|
36
|
+
console.warn(
|
|
37
|
+
`[SessionKeeper] Keep-alive failed for ${accountId}: ${message}`,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
await sleep(humanDelay(250, 900));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const closed = await closeIdlePlaywrightAccounts(
|
|
45
|
+
config.playwright.idleContextTtlMs,
|
|
46
|
+
);
|
|
47
|
+
const evicted = await evictIdlePlaywrightContextsToLimit();
|
|
48
|
+
const totalClosed = closed + evicted;
|
|
49
|
+
if (totalClosed > 0) {
|
|
50
|
+
console.log(
|
|
51
|
+
`🧹 [SessionKeeper] Closed ${totalClosed} idle Playwright context(s)`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
cycleInProgress = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function startSessionKeeper(): void {
|
|
60
|
+
const hasKeepAliveWork = config.sessionKeeper.enabled;
|
|
61
|
+
const hasIdleCleanupWork = config.playwright.idleContextTtlMs > 0;
|
|
62
|
+
if (running || (!hasKeepAliveWork && !hasIdleCleanupWork)) return;
|
|
63
|
+
|
|
64
|
+
running = true;
|
|
65
|
+
intervalId = setInterval(() => {
|
|
66
|
+
if (running) void runKeepAliveCycle();
|
|
67
|
+
}, config.sessionKeeper.intervalMs);
|
|
68
|
+
intervalId.unref?.();
|
|
69
|
+
|
|
70
|
+
if (config.sessionKeeper.enabled) {
|
|
71
|
+
console.log(
|
|
72
|
+
`💓 [SessionKeeper] Keep-alive enabled | interval=${config.sessionKeeper.intervalMs}ms idle=${config.sessionKeeper.idleMs}ms`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function stopSessionKeeper(): void {
|
|
78
|
+
running = false;
|
|
79
|
+
if (intervalId) {
|
|
80
|
+
clearInterval(intervalId);
|
|
81
|
+
intervalId = null;
|
|
82
|
+
}
|
|
83
|
+
cycleInProgress = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function runSessionKeeperOnceForTesting(): Promise<void> {
|
|
87
|
+
await runKeepAliveCycle();
|
|
88
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import { logger } from "../core/logger.ts";
|
|
2
|
-
import { estimateTokenCount } from "../utils/context-truncation.ts";
|
|
3
|
-
import type { Usage } from "../utils/types.ts";
|
|
4
|
-
import type { ContextMeterSnapshot } from "./context-meter.ts";
|
|
5
|
-
|
|
6
|
-
export interface PersonalizationEstimationInfo {
|
|
7
|
-
accountId: string;
|
|
8
|
-
model: string | null;
|
|
9
|
-
toolCount: number;
|
|
10
|
-
chars: number;
|
|
11
|
-
bytes: number;
|
|
12
|
-
hash: string;
|
|
13
|
-
estimatedTokens: number;
|
|
14
|
-
source: "memory" | "db" | "verified" | "synced";
|
|
15
|
-
updatedAt: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface TokenEstimationContext {
|
|
19
|
-
requestDeclaredToolCount?: number;
|
|
20
|
-
activePersonalization?: PersonalizationEstimationInfo | null;
|
|
21
|
-
qwenPayloadBytes?: number;
|
|
22
|
-
qwenPayloadPromptChars?: number;
|
|
23
|
-
qwenPayloadMessageCount?: number;
|
|
24
|
-
contextMeter?: ContextMeterSnapshot | null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isEnabled(): boolean {
|
|
28
|
-
return process.env.TOKEN_ESTIMATION_LOG === "true";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function ratio(estimated: number, actual: number): number | null {
|
|
32
|
-
if (!Number.isFinite(actual) || actual <= 0) return null;
|
|
33
|
-
return Number((estimated / actual).toFixed(4));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function logTokenEstimationSample(input: {
|
|
37
|
-
model: string;
|
|
38
|
-
finalPrompt: string;
|
|
39
|
-
userPrompt?: string;
|
|
40
|
-
assistantContent?: string;
|
|
41
|
-
reasoningContent?: string;
|
|
42
|
-
usage?: Usage | null;
|
|
43
|
-
mode: "stream" | "non-stream";
|
|
44
|
-
context?: TokenEstimationContext;
|
|
45
|
-
}): void {
|
|
46
|
-
if (!isEnabled()) return;
|
|
47
|
-
|
|
48
|
-
const actualPromptTokens = input.usage?.prompt_tokens;
|
|
49
|
-
if (
|
|
50
|
-
typeof actualPromptTokens !== "number" ||
|
|
51
|
-
!Number.isFinite(actualPromptTokens) ||
|
|
52
|
-
actualPromptTokens <= 0
|
|
53
|
-
) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const assistantContent = input.assistantContent ?? "";
|
|
58
|
-
const reasoningContent = input.reasoningContent ?? "";
|
|
59
|
-
const estimatedPromptTokens = estimateTokenCount(input.finalPrompt);
|
|
60
|
-
const estimatedUserPromptTokens = input.userPrompt
|
|
61
|
-
? estimateTokenCount(input.userPrompt)
|
|
62
|
-
: null;
|
|
63
|
-
const estimatedCompletionTokens =
|
|
64
|
-
reasoningContent || assistantContent
|
|
65
|
-
? estimateTokenCount(reasoningContent, assistantContent)
|
|
66
|
-
: 0;
|
|
67
|
-
const personalization = input.context?.activePersonalization ?? null;
|
|
68
|
-
const estimatedPersonalizationTokens = personalization?.estimatedTokens ?? 0;
|
|
69
|
-
const estimatedEffectivePromptTokens =
|
|
70
|
-
estimatedPromptTokens + estimatedPersonalizationTokens;
|
|
71
|
-
|
|
72
|
-
logger.info("token_estimation_sample", {
|
|
73
|
-
model: input.model,
|
|
74
|
-
mode: input.mode,
|
|
75
|
-
localFinalPromptChars: input.finalPrompt.length,
|
|
76
|
-
userPromptChars: input.userPrompt?.length ?? 0,
|
|
77
|
-
completionChars: assistantContent.length,
|
|
78
|
-
reasoningChars: reasoningContent.length,
|
|
79
|
-
requestDeclaredToolCount: input.context?.requestDeclaredToolCount ?? 0,
|
|
80
|
-
activePersonalizationToolCount: personalization?.toolCount ?? 0,
|
|
81
|
-
personalizationChars: personalization?.chars ?? 0,
|
|
82
|
-
personalizationBytes: personalization?.bytes ?? 0,
|
|
83
|
-
personalizationHash: personalization?.hash ?? null,
|
|
84
|
-
personalizationSource: personalization?.source ?? "none",
|
|
85
|
-
personalizationModel: personalization?.model ?? null,
|
|
86
|
-
qwenPayloadBytes: input.context?.qwenPayloadBytes ?? null,
|
|
87
|
-
qwenPayloadPromptChars: input.context?.qwenPayloadPromptChars ?? null,
|
|
88
|
-
qwenPayloadMessageCount: input.context?.qwenPayloadMessageCount ?? null,
|
|
89
|
-
contextMeterMode: input.context?.contextMeter?.mode ?? null,
|
|
90
|
-
contextMeterEstimatedTokens:
|
|
91
|
-
input.context?.contextMeter?.estimatedContextTokens ?? null,
|
|
92
|
-
contextMeterWindowTokens:
|
|
93
|
-
input.context?.contextMeter?.contextWindowTokens ?? null,
|
|
94
|
-
contextMeterPercent:
|
|
95
|
-
input.context?.contextMeter?.estimatedContextPercent ?? null,
|
|
96
|
-
contextMeterFullPromptBytes:
|
|
97
|
-
input.context?.contextMeter?.fullPromptBytes ?? null,
|
|
98
|
-
contextMeterRequestPromptBytes:
|
|
99
|
-
input.context?.contextMeter?.requestPromptBytes ?? null,
|
|
100
|
-
estimatedLocalPromptTokens: estimatedPromptTokens,
|
|
101
|
-
estimatedUserPromptTokens,
|
|
102
|
-
estimatedPersonalizationTokens,
|
|
103
|
-
estimatedEffectivePromptTokens,
|
|
104
|
-
estimatedCompletionTokens,
|
|
105
|
-
actualPromptTokens,
|
|
106
|
-
actualCompletionTokens: input.usage?.completion_tokens ?? null,
|
|
107
|
-
actualTotalTokens: input.usage?.total_tokens ?? null,
|
|
108
|
-
localPromptEstimateRatio: ratio(estimatedPromptTokens, actualPromptTokens),
|
|
109
|
-
effectivePromptEstimateRatio: ratio(
|
|
110
|
-
estimatedEffectivePromptTokens,
|
|
111
|
-
actualPromptTokens,
|
|
112
|
-
),
|
|
113
|
-
completionEstimateRatio: ratio(
|
|
114
|
-
estimatedCompletionTokens,
|
|
115
|
-
input.usage?.completion_tokens ?? 0,
|
|
116
|
-
),
|
|
117
|
-
});
|
|
118
|
-
}
|
|
1
|
+
import { logger } from "../core/logger.ts";
|
|
2
|
+
import { estimateTokenCount } from "../utils/context-truncation.ts";
|
|
3
|
+
import type { Usage } from "../utils/types.ts";
|
|
4
|
+
import type { ContextMeterSnapshot } from "./context-meter.ts";
|
|
5
|
+
|
|
6
|
+
export interface PersonalizationEstimationInfo {
|
|
7
|
+
accountId: string;
|
|
8
|
+
model: string | null;
|
|
9
|
+
toolCount: number;
|
|
10
|
+
chars: number;
|
|
11
|
+
bytes: number;
|
|
12
|
+
hash: string;
|
|
13
|
+
estimatedTokens: number;
|
|
14
|
+
source: "memory" | "db" | "verified" | "synced";
|
|
15
|
+
updatedAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TokenEstimationContext {
|
|
19
|
+
requestDeclaredToolCount?: number;
|
|
20
|
+
activePersonalization?: PersonalizationEstimationInfo | null;
|
|
21
|
+
qwenPayloadBytes?: number;
|
|
22
|
+
qwenPayloadPromptChars?: number;
|
|
23
|
+
qwenPayloadMessageCount?: number;
|
|
24
|
+
contextMeter?: ContextMeterSnapshot | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isEnabled(): boolean {
|
|
28
|
+
return process.env.TOKEN_ESTIMATION_LOG === "true";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ratio(estimated: number, actual: number): number | null {
|
|
32
|
+
if (!Number.isFinite(actual) || actual <= 0) return null;
|
|
33
|
+
return Number((estimated / actual).toFixed(4));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function logTokenEstimationSample(input: {
|
|
37
|
+
model: string;
|
|
38
|
+
finalPrompt: string;
|
|
39
|
+
userPrompt?: string;
|
|
40
|
+
assistantContent?: string;
|
|
41
|
+
reasoningContent?: string;
|
|
42
|
+
usage?: Usage | null;
|
|
43
|
+
mode: "stream" | "non-stream";
|
|
44
|
+
context?: TokenEstimationContext;
|
|
45
|
+
}): void {
|
|
46
|
+
if (!isEnabled()) return;
|
|
47
|
+
|
|
48
|
+
const actualPromptTokens = input.usage?.prompt_tokens;
|
|
49
|
+
if (
|
|
50
|
+
typeof actualPromptTokens !== "number" ||
|
|
51
|
+
!Number.isFinite(actualPromptTokens) ||
|
|
52
|
+
actualPromptTokens <= 0
|
|
53
|
+
) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const assistantContent = input.assistantContent ?? "";
|
|
58
|
+
const reasoningContent = input.reasoningContent ?? "";
|
|
59
|
+
const estimatedPromptTokens = estimateTokenCount(input.finalPrompt);
|
|
60
|
+
const estimatedUserPromptTokens = input.userPrompt
|
|
61
|
+
? estimateTokenCount(input.userPrompt)
|
|
62
|
+
: null;
|
|
63
|
+
const estimatedCompletionTokens =
|
|
64
|
+
reasoningContent || assistantContent
|
|
65
|
+
? estimateTokenCount(reasoningContent, assistantContent)
|
|
66
|
+
: 0;
|
|
67
|
+
const personalization = input.context?.activePersonalization ?? null;
|
|
68
|
+
const estimatedPersonalizationTokens = personalization?.estimatedTokens ?? 0;
|
|
69
|
+
const estimatedEffectivePromptTokens =
|
|
70
|
+
estimatedPromptTokens + estimatedPersonalizationTokens;
|
|
71
|
+
|
|
72
|
+
logger.info("token_estimation_sample", {
|
|
73
|
+
model: input.model,
|
|
74
|
+
mode: input.mode,
|
|
75
|
+
localFinalPromptChars: input.finalPrompt.length,
|
|
76
|
+
userPromptChars: input.userPrompt?.length ?? 0,
|
|
77
|
+
completionChars: assistantContent.length,
|
|
78
|
+
reasoningChars: reasoningContent.length,
|
|
79
|
+
requestDeclaredToolCount: input.context?.requestDeclaredToolCount ?? 0,
|
|
80
|
+
activePersonalizationToolCount: personalization?.toolCount ?? 0,
|
|
81
|
+
personalizationChars: personalization?.chars ?? 0,
|
|
82
|
+
personalizationBytes: personalization?.bytes ?? 0,
|
|
83
|
+
personalizationHash: personalization?.hash ?? null,
|
|
84
|
+
personalizationSource: personalization?.source ?? "none",
|
|
85
|
+
personalizationModel: personalization?.model ?? null,
|
|
86
|
+
qwenPayloadBytes: input.context?.qwenPayloadBytes ?? null,
|
|
87
|
+
qwenPayloadPromptChars: input.context?.qwenPayloadPromptChars ?? null,
|
|
88
|
+
qwenPayloadMessageCount: input.context?.qwenPayloadMessageCount ?? null,
|
|
89
|
+
contextMeterMode: input.context?.contextMeter?.mode ?? null,
|
|
90
|
+
contextMeterEstimatedTokens:
|
|
91
|
+
input.context?.contextMeter?.estimatedContextTokens ?? null,
|
|
92
|
+
contextMeterWindowTokens:
|
|
93
|
+
input.context?.contextMeter?.contextWindowTokens ?? null,
|
|
94
|
+
contextMeterPercent:
|
|
95
|
+
input.context?.contextMeter?.estimatedContextPercent ?? null,
|
|
96
|
+
contextMeterFullPromptBytes:
|
|
97
|
+
input.context?.contextMeter?.fullPromptBytes ?? null,
|
|
98
|
+
contextMeterRequestPromptBytes:
|
|
99
|
+
input.context?.contextMeter?.requestPromptBytes ?? null,
|
|
100
|
+
estimatedLocalPromptTokens: estimatedPromptTokens,
|
|
101
|
+
estimatedUserPromptTokens,
|
|
102
|
+
estimatedPersonalizationTokens,
|
|
103
|
+
estimatedEffectivePromptTokens,
|
|
104
|
+
estimatedCompletionTokens,
|
|
105
|
+
actualPromptTokens,
|
|
106
|
+
actualCompletionTokens: input.usage?.completion_tokens ?? null,
|
|
107
|
+
actualTotalTokens: input.usage?.total_tokens ?? null,
|
|
108
|
+
localPromptEstimateRatio: ratio(estimatedPromptTokens, actualPromptTokens),
|
|
109
|
+
effectivePromptEstimateRatio: ratio(
|
|
110
|
+
estimatedEffectivePromptTokens,
|
|
111
|
+
actualPromptTokens,
|
|
112
|
+
),
|
|
113
|
+
completionEstimateRatio: ratio(
|
|
114
|
+
estimatedCompletionTokens,
|
|
115
|
+
input.usage?.completion_tokens ?? 0,
|
|
116
|
+
),
|
|
117
|
+
});
|
|
118
|
+
}
|
package/src/sync/claude-code.ts
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
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
|
-
export function syncClaudeCode(options: SyncOptions): ClientSyncResult {
|
|
7
|
-
const { filePath, apiKey, baseUrl, model = "qwen3.8-max" } = options;
|
|
8
|
-
try {
|
|
9
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
10
|
-
|
|
11
|
-
let backupPath: string | undefined;
|
|
12
|
-
let existingSettings: Record<string, any> = {};
|
|
13
|
-
|
|
14
|
-
if (fs.existsSync(filePath)) {
|
|
15
|
-
backupPath = createTimestampBackup(filePath);
|
|
16
|
-
try {
|
|
17
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
18
|
-
existingSettings = JSON.parse(content);
|
|
19
|
-
} catch {
|
|
20
|
-
existingSettings = {};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const env = {
|
|
25
|
-
...(existingSettings.env || {}),
|
|
26
|
-
ANTHROPIC_BASE_URL: baseUrl,
|
|
27
|
-
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
28
|
-
ANTHROPIC_MODEL: model,
|
|
29
|
-
ANTHROPIC_CUSTOM_MODEL_OPTION: model,
|
|
30
|
-
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: "Qwen 3.8 Max (1M Context)",
|
|
31
|
-
ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION: "QwenProxy model qwen3.8-max - 1M context window",
|
|
32
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
|
33
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3.7-plus",
|
|
34
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
|
35
|
-
CLAUDE_CODE_MAX_CONTEXT_TOKENS: "1000000",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const updatedSettings = {
|
|
39
|
-
...existingSettings,
|
|
40
|
-
env,
|
|
41
|
-
model,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
fs.writeFileSync(filePath, JSON.stringify(updatedSettings, null, 2) + "\n", "utf-8");
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
client: "claude-code",
|
|
48
|
-
filePath,
|
|
49
|
-
backupPath,
|
|
50
|
-
success: true,
|
|
51
|
-
action: backupPath ? "updated" : "created",
|
|
52
|
-
message: `Configured Claude Code with model ${model} and baseUrl ${baseUrl}`,
|
|
53
|
-
};
|
|
54
|
-
} catch (err: any) {
|
|
55
|
-
return {
|
|
56
|
-
client: "claude-code",
|
|
57
|
-
filePath,
|
|
58
|
-
success: false,
|
|
59
|
-
action: "failed",
|
|
60
|
-
error: err?.message || String(err),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function restoreClaudeCode(filePath: string, backupPath?: string): ClientSyncResult {
|
|
66
|
-
const restored = restoreFromBackup(filePath, backupPath);
|
|
67
|
-
return {
|
|
68
|
-
client: "claude-code",
|
|
69
|
-
filePath,
|
|
70
|
-
backupPath,
|
|
71
|
-
success: restored,
|
|
72
|
-
action: restored ? "restored" : "failed",
|
|
73
|
-
message: restored ? "Restored Claude Code settings from backup" : "Backup file not found",
|
|
74
|
-
};
|
|
75
|
-
}
|
|
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
|
+
export function syncClaudeCode(options: SyncOptions): ClientSyncResult {
|
|
7
|
+
const { filePath, apiKey, baseUrl, model = "qwen3.8-max" } = options;
|
|
8
|
+
try {
|
|
9
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
10
|
+
|
|
11
|
+
let backupPath: string | undefined;
|
|
12
|
+
let existingSettings: Record<string, any> = {};
|
|
13
|
+
|
|
14
|
+
if (fs.existsSync(filePath)) {
|
|
15
|
+
backupPath = createTimestampBackup(filePath);
|
|
16
|
+
try {
|
|
17
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
existingSettings = JSON.parse(content);
|
|
19
|
+
} catch {
|
|
20
|
+
existingSettings = {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const env = {
|
|
25
|
+
...(existingSettings.env || {}),
|
|
26
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
27
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
28
|
+
ANTHROPIC_MODEL: model,
|
|
29
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: model,
|
|
30
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: "Qwen 3.8 Max (1M Context)",
|
|
31
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION: "QwenProxy model qwen3.8-max - 1M context window",
|
|
32
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
|
33
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3.7-plus",
|
|
34
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
|
35
|
+
CLAUDE_CODE_MAX_CONTEXT_TOKENS: "1000000",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const updatedSettings = {
|
|
39
|
+
...existingSettings,
|
|
40
|
+
env,
|
|
41
|
+
model,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
fs.writeFileSync(filePath, JSON.stringify(updatedSettings, null, 2) + "\n", "utf-8");
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
client: "claude-code",
|
|
48
|
+
filePath,
|
|
49
|
+
backupPath,
|
|
50
|
+
success: true,
|
|
51
|
+
action: backupPath ? "updated" : "created",
|
|
52
|
+
message: `Configured Claude Code with model ${model} and baseUrl ${baseUrl}`,
|
|
53
|
+
};
|
|
54
|
+
} catch (err: any) {
|
|
55
|
+
return {
|
|
56
|
+
client: "claude-code",
|
|
57
|
+
filePath,
|
|
58
|
+
success: false,
|
|
59
|
+
action: "failed",
|
|
60
|
+
error: err?.message || String(err),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function restoreClaudeCode(filePath: string, backupPath?: string): ClientSyncResult {
|
|
66
|
+
const restored = restoreFromBackup(filePath, backupPath);
|
|
67
|
+
return {
|
|
68
|
+
client: "claude-code",
|
|
69
|
+
filePath,
|
|
70
|
+
backupPath,
|
|
71
|
+
success: restored,
|
|
72
|
+
action: restored ? "restored" : "failed",
|
|
73
|
+
message: restored ? "Restored Claude Code settings from backup" : "Backup file not found",
|
|
74
|
+
};
|
|
75
|
+
}
|