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.
Files changed (109) hide show
  1. package/LICENSE +14 -14
  2. package/README.md +906 -906
  3. package/bin/qwenproxy.js +5 -1
  4. package/package.json +77 -78
  5. package/src/api/error-classifier.ts +159 -159
  6. package/src/api/error-helpers.ts +118 -118
  7. package/src/api/models.ts +261 -261
  8. package/src/api/server.ts +860 -859
  9. package/src/cache/memory-cache.ts +385 -385
  10. package/src/clean-cache.ts +204 -204
  11. package/src/core/account-concurrency.ts +671 -671
  12. package/src/core/account-manager.ts +301 -297
  13. package/src/core/account-priority.ts +163 -163
  14. package/src/core/accounts.ts +186 -186
  15. package/src/core/config.ts +383 -383
  16. package/src/core/crypto-utils.ts +79 -79
  17. package/src/core/database.ts +276 -276
  18. package/src/core/errors.ts +118 -118
  19. package/src/core/logger.ts +269 -269
  20. package/src/core/memory-usage.ts +84 -84
  21. package/src/core/metrics.ts +291 -291
  22. package/src/core/model-alias.ts +77 -77
  23. package/src/core/model-registry.ts +544 -544
  24. package/src/core/mutex.ts +119 -119
  25. package/src/core/paths.ts +199 -199
  26. package/src/core/prompt-limits.ts +214 -214
  27. package/src/core/reasoning-effort.ts +102 -102
  28. package/src/core/stream-registry.ts +96 -96
  29. package/src/core/waf-isolation.ts +117 -117
  30. package/src/core/watchdog.ts +195 -195
  31. package/src/delete-chats.ts +23 -23
  32. package/src/index.ts +65 -64
  33. package/src/login.ts +147 -147
  34. package/src/reset-cooldowns.ts +11 -11
  35. package/src/routes/anthropic/index.ts +355 -355
  36. package/src/routes/anthropic/translate.ts +522 -522
  37. package/src/routes/anthropic/types.ts +154 -154
  38. package/src/routes/anthropic/validation.ts +144 -144
  39. package/src/routes/chat/account.ts +1817 -1817
  40. package/src/routes/chat/context.ts +241 -241
  41. package/src/routes/chat/errors.ts +85 -85
  42. package/src/routes/chat/helpers.ts +268 -268
  43. package/src/routes/chat/index.ts +618 -618
  44. package/src/routes/chat/media.ts +285 -285
  45. package/src/routes/chat/retry-policy.ts +754 -754
  46. package/src/routes/chat/stop.ts +98 -98
  47. package/src/routes/chat/streaming.ts +2710 -2710
  48. package/src/routes/chat/validation.ts +526 -526
  49. package/src/routes/chat.ts +2 -2
  50. package/src/routes/completions.ts +290 -290
  51. package/src/routes/images.ts +139 -139
  52. package/src/routes/responses/adapter.ts +503 -503
  53. package/src/routes/responses/index.ts +405 -405
  54. package/src/routes/responses/state.ts +230 -230
  55. package/src/routes/responses/streaming.ts +528 -528
  56. package/src/routes/responses/types.ts +285 -285
  57. package/src/routes/responses/validation.ts +202 -202
  58. package/src/routes/upload.ts +731 -731
  59. package/src/routes/videos.ts +214 -214
  60. package/src/services/auth-playwright.ts +173 -173
  61. package/src/services/captcha-coordinator.ts +161 -161
  62. package/src/services/captcha-solver.ts +553 -553
  63. package/src/services/chat-cleanup.ts +80 -80
  64. package/src/services/context-meter.ts +317 -317
  65. package/src/services/fingerprint.ts +242 -242
  66. package/src/services/human-behavior.ts +173 -173
  67. package/src/services/media-generation.ts +1748 -1748
  68. package/src/services/playwright.ts +2878 -2800
  69. package/src/services/qwen-chat-pool.ts +345 -345
  70. package/src/services/qwen-errors.ts +133 -133
  71. package/src/services/qwen-headers.ts +79 -79
  72. package/src/services/qwen-thread-state.ts +393 -393
  73. package/src/services/qwen-url.ts +19 -19
  74. package/src/services/qwen.ts +3126 -3126
  75. package/src/services/session-keeper.ts +88 -88
  76. package/src/services/token-estimation-metrics.ts +118 -118
  77. package/src/sync/claude-code.ts +75 -75
  78. package/src/sync/codex.ts +123 -123
  79. package/src/sync/index.ts +362 -362
  80. package/src/sync/omp.ts +105 -105
  81. package/src/sync/opencode.ts +214 -214
  82. package/src/sync/types.ts +53 -53
  83. package/src/sync/utils.ts +27 -27
  84. package/src/sync-clients.ts +189 -189
  85. package/src/tools/instructions.ts +137 -137
  86. package/src/tools/manifest.ts +81 -81
  87. package/src/tools/parser.ts +2989 -2989
  88. package/src/tools/toolcall-tags.ts +142 -142
  89. package/src/tui/app.ts +259 -264
  90. package/src/tui/index.ts +61 -61
  91. package/src/tui/markdown.ts +258 -258
  92. package/src/tui/proxy-client.ts +331 -326
  93. package/src/tui/screen.ts +294 -278
  94. package/src/tui/server-manager.ts +270 -270
  95. package/src/tui/theme.ts +432 -432
  96. package/src/tui/types.ts +33 -33
  97. package/src/tui/views/accounts-view.ts +656 -656
  98. package/src/tui/views/chat-view.ts +1018 -823
  99. package/src/tui/views/logs-view.ts +479 -413
  100. package/src/tui/views/status-view.ts +204 -204
  101. package/src/tui/views/storage-view.ts +304 -291
  102. package/src/tui/views/sync-view.ts +409 -409
  103. package/src/types/ali-oss.d.ts +32 -32
  104. package/src/update-cli.ts +121 -0
  105. package/src/utils/context-truncation.ts +84 -84
  106. package/src/utils/json.ts +380 -380
  107. package/src/utils/session-id.ts +37 -37
  108. package/src/utils/tool-call-guard.ts +84 -84
  109. package/src/utils/types.ts +109 -109
@@ -1,269 +1,269 @@
1
- import "dotenv/config";
2
-
3
- /**
4
- * Mask an email address for safe logging.
5
- * "user@example.com" → "user@***"
6
- */
7
- export function maskEmail(email: string | undefined | null): string {
8
- if (!email) return "<unknown>";
9
- const atIndex = email.indexOf("@");
10
- if (atIndex <= 0) return "<invalid>";
11
- return email.substring(0, atIndex);
12
- }
13
-
14
- // ─── Log sanitization ───────────────────────────────────────────────────────────
15
- // Log entries carry raw upstream payloads (headers, cookies, JWT-backed
16
- // sessions, malformed tool-call dumps). Redact known credential keys and any
17
- // loose JWT/API-key shaped string before the entry hits the console, even when
18
- // the value appears nested inside a bigger object or a quoted JSON dump.
19
-
20
- const SENSITIVE_KEY_PATTERN =
21
- /^(authorization|auth|cookie|cookies|set-cookie|api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|token|password|passwd|secret|x5sec|x5secdata|bx-ua|bx-v|bx-umidtoken)$/i;
22
-
23
- /** WAF cookie names come in many variants: x5sec, x5sec_v3, x5sec-cn, bx-temp... */
24
- const WAF_KEY_PREFIX_PATTERN = /^(x5sec|bx[-_])/i;
25
-
26
- /** JWT (3 base64url segments) or OpenAI-style `sk-` API key. */
27
- const LOOSE_SECRET_PATTERN =
28
- /(eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|sk-[A-Za-z0-9_-]{20,})/g;
29
-
30
- /** Credentials embedded in free-form strings: header lines, cookie jars, URLs. */
31
- const EMBEDDED_SECRET_PATTERN =
32
- /((?:x5sec(?:data|_[a-z0-9]+|-[a-z0-9]+)?|bx[-_][a-z0-9_-]+)\s*=\s*[^;\s"']+)|(\bBearer\s+[A-Za-z0-9._~+\/=-]+)/gi;
33
-
34
- function isPassthroughObject(value: object): boolean {
35
- return (
36
- value instanceof Date ||
37
- value instanceof RegExp ||
38
- value instanceof Error ||
39
- value instanceof Map ||
40
- value instanceof Set ||
41
- value instanceof ArrayBuffer ||
42
- ArrayBuffer.isView(value)
43
- );
44
- }
45
-
46
- function redactLogValue(value: unknown): unknown {
47
- if (typeof value === "string") {
48
- return value
49
- .replace(LOOSE_SECRET_PATTERN, "[REDACTED]")
50
- .replace(EMBEDDED_SECRET_PATTERN, "[REDACTED]");
51
- }
52
- if (Array.isArray(value)) {
53
- return value.map((item) => redactLogValue(item));
54
- }
55
- if (value !== null && typeof value === "object" && !isPassthroughObject(value)) {
56
- // Recurse into own enumerable props of BOTH plain objects and class
57
- // instances (sessions, response wrappers) — JSON.stringify serializes
58
- // those the same way, so redacting them loses nothing.
59
- const out: Record<string, unknown> = {};
60
- for (const [key, val] of Object.entries(value)) {
61
- out[key] =
62
- SENSITIVE_KEY_PATTERN.test(key) || WAF_KEY_PREFIX_PATTERN.test(key)
63
- ? "[REDACTED]"
64
- : redactLogValue(val);
65
- }
66
- return out;
67
- }
68
- return value;
69
- }
70
-
71
- function redactLogMessage(message: string): string {
72
- return message
73
- .replace(LOOSE_SECRET_PATTERN, "[REDACTED]")
74
- .replace(EMBEDDED_SECRET_PATTERN, "[REDACTED]");
75
- }
76
-
77
- export type LogLevel = "debug" | "info" | "warn" | "error";
78
-
79
- const LEVEL_RANK: Record<LogLevel, number> = {
80
- debug: 0,
81
- info: 1,
82
- warn: 2,
83
- error: 3,
84
- };
85
-
86
- /**
87
- * Data/hora BR local (sem ms) usado APENAS no campo `until=` das linhas de
88
- * cooldown/quota — informação de negócio, não um carimbo de log.
89
- */
90
- export function formatCooldownUntil(date: Date): string {
91
- const pad = (n: number): string => String(n).padStart(2, "0");
92
- return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
93
- }
94
-
95
- /**
96
- * TOOLCALL_DEBUG levels:
97
- * "0" or undefined = disabled
98
- * "1" = full debug (all toolcall logs)
99
- * "errors" = only on errors (log toolcall details when parser/execution fails)
100
- *
101
- * UPSTREAM_DEBUG:
102
- * "true" = log raw SSE chunks received from Qwen
103
- */
104
- export type ToolcallDebugLevel = "0" | "1" | "errors";
105
-
106
- export interface LogEntry {
107
- timestamp: Date;
108
- level: LogLevel;
109
- message: string;
110
- context?: string;
111
- data?: Record<string, unknown>;
112
- }
113
-
114
- export class Logger {
115
- private minLevel: LogLevel;
116
- private context?: string;
117
-
118
- constructor(level: LogLevel = "warn", context?: string) {
119
- this.minLevel = level;
120
- this.context = context;
121
- }
122
-
123
- private shouldLog(level: LogLevel): boolean {
124
- return LEVEL_RANK[level] >= LEVEL_RANK[this.minLevel];
125
- }
126
-
127
- /** Cheap level check so callers can skip building expensive log payloads. */
128
- isLevelEnabled(level: LogLevel): boolean {
129
- return this.shouldLog(level);
130
- }
131
-
132
- private formatEntry(entry: LogEntry): string {
133
- const pad = (str: string): string => str.padStart(5, " ");
134
- const colorCode =
135
- entry.level === "error"
136
- ? "\x1b[31m"
137
- : entry.level === "warn"
138
- ? "\x1b[33m"
139
- : entry.level === "debug"
140
- ? "\x1b[36m"
141
- : "";
142
- const reset = "\x1b[0m";
143
-
144
- const coloredLevel = colorCode + pad(entry.level.toUpperCase()) + reset;
145
- const contextPart = entry.context ? ` [${entry.context}]` : "";
146
-
147
- // Redact credentials from BOTH the message (payload previews often embed
148
- // cookies/JWTs) and the structured data.
149
- const safeMessage = redactLogMessage(entry.message);
150
- const safeData = entry.data ? redactLogValue(entry.data) : undefined;
151
-
152
- let output = `${coloredLevel}${contextPart} ${safeMessage}`;
153
-
154
- if (safeData !== undefined) {
155
- output += "\n" + JSON.stringify(safeData, null, 2);
156
- }
157
-
158
- return output;
159
- }
160
-
161
- debug(message: string, data?: Record<string, unknown>): void {
162
- if (this.shouldLog("debug")) {
163
- console.log(
164
- this.formatEntry({
165
- timestamp: new Date(),
166
- level: "debug",
167
- message: this.context ? `[${this.context}] ${message}` : message,
168
- data,
169
- }),
170
- );
171
- }
172
- }
173
-
174
- info(message: string, data?: Record<string, unknown>): void {
175
- if (this.shouldLog("info")) {
176
- console.log(
177
- this.formatEntry({
178
- timestamp: new Date(),
179
- level: "info",
180
- message: this.context ? `[${this.context}] ${message}` : message,
181
- data,
182
- }),
183
- );
184
- }
185
- }
186
-
187
- warn(message: string, data?: Record<string, unknown>): void {
188
- if (this.shouldLog("warn")) {
189
- console.warn(
190
- this.formatEntry({
191
- timestamp: new Date(),
192
- level: "warn",
193
- message: this.context ? `[${this.context}] ${message}` : message,
194
- data,
195
- }),
196
- );
197
- }
198
- }
199
-
200
- error(message: string, data?: Record<string, unknown>): void {
201
- if (this.shouldLog("error")) {
202
- console.error(
203
- this.formatEntry({
204
- timestamp: new Date(),
205
- level: "error",
206
- message: this.context ? `[${this.context}] ${message}` : message,
207
- data,
208
- }),
209
- );
210
- }
211
- }
212
- }
213
-
214
- // Determine initial log level from environment
215
- const envLevel = process.env.LOG_LEVEL as LogLevel | undefined;
216
- const toolcallDebugEnv = process.env.TOOLCALL_DEBUG || "errors";
217
-
218
- export const toolcallDebugLevel: ToolcallDebugLevel =
219
- toolcallDebugEnv === "1"
220
- ? "1"
221
- : toolcallDebugEnv === "errors"
222
- ? "errors"
223
- : "0";
224
-
225
- const initialLevel: LogLevel =
226
- toolcallDebugLevel === "1"
227
- ? "debug"
228
- : envLevel && ["debug", "info", "warn", "error"].includes(envLevel)
229
- ? envLevel
230
- // Default for general users: quiet terminal with only warnings/errors
231
- // (+ the always-on request/account console lines). Debugging opt-in via
232
- // LOG_LEVEL=debug / TOOLCALL_DEBUG=1.
233
- : "warn";
234
-
235
- export const logger = new Logger(initialLevel);
236
-
237
- export function isDebugEnabled(): boolean {
238
- return logger.isLevelEnabled("debug");
239
- }
240
-
241
- // Helper to check if toolcall debug is enabled
242
- export function isToolcallDebugEnabled(): boolean {
243
- return toolcallDebugLevel === "1";
244
- }
245
-
246
- export function isToolcallErrorDebugEnabled(): boolean {
247
- return toolcallDebugLevel === "1" || toolcallDebugLevel === "errors";
248
- }
249
-
250
- export const upstreamDebugEnabled = process.env.UPSTREAM_DEBUG === "true";
251
-
252
- // Confirm debug mode on startup (only log if explicitly set)
253
- if (process.env.TOOLCALL_DEBUG) {
254
- if (toolcallDebugLevel === "1") {
255
- console.log("🔍 [Logger] TOOLCALL_DEBUG=1 - full debug logs active");
256
- } else if (toolcallDebugLevel === "errors") {
257
- console.log(
258
- "[Logger] TOOLCALL_DEBUG=errors - toolcall logs on errors only",
259
- );
260
- } else {
261
- console.log("🔇 [Logger] TOOLCALL_DEBUG=0 - toolcall logs disabled");
262
- }
263
- }
264
-
265
- if (upstreamDebugEnabled) {
266
- console.log(
267
- "[Logger] UPSTREAM_DEBUG=true - raw upstream chunks logging active",
268
- );
269
- }
1
+ import "dotenv/config";
2
+
3
+ /**
4
+ * Mask an email address for safe logging.
5
+ * "user@example.com" → "user@***"
6
+ */
7
+ export function maskEmail(email: string | undefined | null): string {
8
+ if (!email) return "<unknown>";
9
+ const atIndex = email.indexOf("@");
10
+ if (atIndex <= 0) return "<invalid>";
11
+ return email.substring(0, atIndex);
12
+ }
13
+
14
+ // ─── Log sanitization ───────────────────────────────────────────────────────────
15
+ // Log entries carry raw upstream payloads (headers, cookies, JWT-backed
16
+ // sessions, malformed tool-call dumps). Redact known credential keys and any
17
+ // loose JWT/API-key shaped string before the entry hits the console, even when
18
+ // the value appears nested inside a bigger object or a quoted JSON dump.
19
+
20
+ const SENSITIVE_KEY_PATTERN =
21
+ /^(authorization|auth|cookie|cookies|set-cookie|api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|token|password|passwd|secret|x5sec|x5secdata|bx-ua|bx-v|bx-umidtoken)$/i;
22
+
23
+ /** WAF cookie names come in many variants: x5sec, x5sec_v3, x5sec-cn, bx-temp... */
24
+ const WAF_KEY_PREFIX_PATTERN = /^(x5sec|bx[-_])/i;
25
+
26
+ /** JWT (3 base64url segments) or OpenAI-style `sk-` API key. */
27
+ const LOOSE_SECRET_PATTERN =
28
+ /(eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|sk-[A-Za-z0-9_-]{20,})/g;
29
+
30
+ /** Credentials embedded in free-form strings: header lines, cookie jars, URLs. */
31
+ const EMBEDDED_SECRET_PATTERN =
32
+ /((?:x5sec(?:data|_[a-z0-9]+|-[a-z0-9]+)?|bx[-_][a-z0-9_-]+)\s*=\s*[^;\s"']+)|(\bBearer\s+[A-Za-z0-9._~+\/=-]+)/gi;
33
+
34
+ function isPassthroughObject(value: object): boolean {
35
+ return (
36
+ value instanceof Date ||
37
+ value instanceof RegExp ||
38
+ value instanceof Error ||
39
+ value instanceof Map ||
40
+ value instanceof Set ||
41
+ value instanceof ArrayBuffer ||
42
+ ArrayBuffer.isView(value)
43
+ );
44
+ }
45
+
46
+ function redactLogValue(value: unknown): unknown {
47
+ if (typeof value === "string") {
48
+ return value
49
+ .replace(LOOSE_SECRET_PATTERN, "[REDACTED]")
50
+ .replace(EMBEDDED_SECRET_PATTERN, "[REDACTED]");
51
+ }
52
+ if (Array.isArray(value)) {
53
+ return value.map((item) => redactLogValue(item));
54
+ }
55
+ if (value !== null && typeof value === "object" && !isPassthroughObject(value)) {
56
+ // Recurse into own enumerable props of BOTH plain objects and class
57
+ // instances (sessions, response wrappers) — JSON.stringify serializes
58
+ // those the same way, so redacting them loses nothing.
59
+ const out: Record<string, unknown> = {};
60
+ for (const [key, val] of Object.entries(value)) {
61
+ out[key] =
62
+ SENSITIVE_KEY_PATTERN.test(key) || WAF_KEY_PREFIX_PATTERN.test(key)
63
+ ? "[REDACTED]"
64
+ : redactLogValue(val);
65
+ }
66
+ return out;
67
+ }
68
+ return value;
69
+ }
70
+
71
+ function redactLogMessage(message: string): string {
72
+ return message
73
+ .replace(LOOSE_SECRET_PATTERN, "[REDACTED]")
74
+ .replace(EMBEDDED_SECRET_PATTERN, "[REDACTED]");
75
+ }
76
+
77
+ export type LogLevel = "debug" | "info" | "warn" | "error";
78
+
79
+ const LEVEL_RANK: Record<LogLevel, number> = {
80
+ debug: 0,
81
+ info: 1,
82
+ warn: 2,
83
+ error: 3,
84
+ };
85
+
86
+ /**
87
+ * Data/hora BR local (sem ms) usado APENAS no campo `until=` das linhas de
88
+ * cooldown/quota — informação de negócio, não um carimbo de log.
89
+ */
90
+ export function formatCooldownUntil(date: Date): string {
91
+ const pad = (n: number): string => String(n).padStart(2, "0");
92
+ return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
93
+ }
94
+
95
+ /**
96
+ * TOOLCALL_DEBUG levels:
97
+ * "0" or undefined = disabled
98
+ * "1" = full debug (all toolcall logs)
99
+ * "errors" = only on errors (log toolcall details when parser/execution fails)
100
+ *
101
+ * UPSTREAM_DEBUG:
102
+ * "true" = log raw SSE chunks received from Qwen
103
+ */
104
+ export type ToolcallDebugLevel = "0" | "1" | "errors";
105
+
106
+ export interface LogEntry {
107
+ timestamp: Date;
108
+ level: LogLevel;
109
+ message: string;
110
+ context?: string;
111
+ data?: Record<string, unknown>;
112
+ }
113
+
114
+ export class Logger {
115
+ private minLevel: LogLevel;
116
+ private context?: string;
117
+
118
+ constructor(level: LogLevel = "warn", context?: string) {
119
+ this.minLevel = level;
120
+ this.context = context;
121
+ }
122
+
123
+ private shouldLog(level: LogLevel): boolean {
124
+ return LEVEL_RANK[level] >= LEVEL_RANK[this.minLevel];
125
+ }
126
+
127
+ /** Cheap level check so callers can skip building expensive log payloads. */
128
+ isLevelEnabled(level: LogLevel): boolean {
129
+ return this.shouldLog(level);
130
+ }
131
+
132
+ private formatEntry(entry: LogEntry): string {
133
+ const pad = (str: string): string => str.padStart(5, " ");
134
+ const colorCode =
135
+ entry.level === "error"
136
+ ? "\x1b[31m"
137
+ : entry.level === "warn"
138
+ ? "\x1b[33m"
139
+ : entry.level === "debug"
140
+ ? "\x1b[36m"
141
+ : "";
142
+ const reset = "\x1b[0m";
143
+
144
+ const coloredLevel = colorCode + pad(entry.level.toUpperCase()) + reset;
145
+ const contextPart = entry.context ? ` [${entry.context}]` : "";
146
+
147
+ // Redact credentials from BOTH the message (payload previews often embed
148
+ // cookies/JWTs) and the structured data.
149
+ const safeMessage = redactLogMessage(entry.message);
150
+ const safeData = entry.data ? redactLogValue(entry.data) : undefined;
151
+
152
+ let output = `${coloredLevel}${contextPart} ${safeMessage}`;
153
+
154
+ if (safeData !== undefined) {
155
+ output += "\n" + JSON.stringify(safeData, null, 2);
156
+ }
157
+
158
+ return output;
159
+ }
160
+
161
+ debug(message: string, data?: Record<string, unknown>): void {
162
+ if (this.shouldLog("debug")) {
163
+ console.log(
164
+ this.formatEntry({
165
+ timestamp: new Date(),
166
+ level: "debug",
167
+ message: this.context ? `[${this.context}] ${message}` : message,
168
+ data,
169
+ }),
170
+ );
171
+ }
172
+ }
173
+
174
+ info(message: string, data?: Record<string, unknown>): void {
175
+ if (this.shouldLog("info")) {
176
+ console.log(
177
+ this.formatEntry({
178
+ timestamp: new Date(),
179
+ level: "info",
180
+ message: this.context ? `[${this.context}] ${message}` : message,
181
+ data,
182
+ }),
183
+ );
184
+ }
185
+ }
186
+
187
+ warn(message: string, data?: Record<string, unknown>): void {
188
+ if (this.shouldLog("warn")) {
189
+ console.warn(
190
+ this.formatEntry({
191
+ timestamp: new Date(),
192
+ level: "warn",
193
+ message: this.context ? `[${this.context}] ${message}` : message,
194
+ data,
195
+ }),
196
+ );
197
+ }
198
+ }
199
+
200
+ error(message: string, data?: Record<string, unknown>): void {
201
+ if (this.shouldLog("error")) {
202
+ console.error(
203
+ this.formatEntry({
204
+ timestamp: new Date(),
205
+ level: "error",
206
+ message: this.context ? `[${this.context}] ${message}` : message,
207
+ data,
208
+ }),
209
+ );
210
+ }
211
+ }
212
+ }
213
+
214
+ // Determine initial log level from environment
215
+ const envLevel = process.env.LOG_LEVEL as LogLevel | undefined;
216
+ const toolcallDebugEnv = process.env.TOOLCALL_DEBUG || "errors";
217
+
218
+ export const toolcallDebugLevel: ToolcallDebugLevel =
219
+ toolcallDebugEnv === "1"
220
+ ? "1"
221
+ : toolcallDebugEnv === "errors"
222
+ ? "errors"
223
+ : "0";
224
+
225
+ const initialLevel: LogLevel =
226
+ toolcallDebugLevel === "1"
227
+ ? "debug"
228
+ : envLevel && ["debug", "info", "warn", "error"].includes(envLevel)
229
+ ? envLevel
230
+ // Default for general users: quiet terminal with only warnings/errors
231
+ // (+ the always-on request/account console lines). Debugging opt-in via
232
+ // LOG_LEVEL=debug / TOOLCALL_DEBUG=1.
233
+ : "warn";
234
+
235
+ export const logger = new Logger(initialLevel);
236
+
237
+ export function isDebugEnabled(): boolean {
238
+ return logger.isLevelEnabled("debug");
239
+ }
240
+
241
+ // Helper to check if toolcall debug is enabled
242
+ export function isToolcallDebugEnabled(): boolean {
243
+ return toolcallDebugLevel === "1";
244
+ }
245
+
246
+ export function isToolcallErrorDebugEnabled(): boolean {
247
+ return toolcallDebugLevel === "1" || toolcallDebugLevel === "errors";
248
+ }
249
+
250
+ export const upstreamDebugEnabled = process.env.UPSTREAM_DEBUG === "true";
251
+
252
+ // Confirm debug mode on startup (only log if explicitly set)
253
+ if (process.env.TOOLCALL_DEBUG) {
254
+ if (toolcallDebugLevel === "1") {
255
+ console.log("🔍 [Logger] TOOLCALL_DEBUG=1 - full debug logs active");
256
+ } else if (toolcallDebugLevel === "errors") {
257
+ console.log(
258
+ "[Logger] TOOLCALL_DEBUG=errors - toolcall logs on errors only",
259
+ );
260
+ } else {
261
+ console.log("🔇 [Logger] TOOLCALL_DEBUG=0 - toolcall logs disabled");
262
+ }
263
+ }
264
+
265
+ if (upstreamDebugEnabled) {
266
+ console.log(
267
+ "[Logger] UPSTREAM_DEBUG=true - raw upstream chunks logging active",
268
+ );
269
+ }