qwenproxy-cli 1.2.3 → 1.2.4
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/routes/anthropic/index.ts +12 -1
- package/src/routes/chat/streaming.ts +13 -3
- package/src/tools/parser.ts +35 -13
- package/src/tui/theme.ts +87 -55
- package/src/tui/views/logs-view.ts +99 -98
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
27
27
|
"purge": "npx tsx src/delete-chats.ts",
|
|
28
28
|
"test": "npm run test:mock && npm run test:live",
|
|
29
|
-
"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 src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts",
|
|
30
|
-
"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 src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts",
|
|
29
|
+
"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 src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts",
|
|
30
|
+
"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 src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts",
|
|
31
31
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
32
32
|
"benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
@@ -261,7 +261,18 @@ app.post("/v1/messages", async (c) => {
|
|
|
261
261
|
`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`,
|
|
262
262
|
);
|
|
263
263
|
} catch (error: any) {
|
|
264
|
-
|
|
264
|
+
const isClientAbort =
|
|
265
|
+
c.req.raw.signal.aborted ||
|
|
266
|
+
error?.name === "AbortError" ||
|
|
267
|
+
error?.code === "ECONNRESET" ||
|
|
268
|
+
(typeof error?.message === "string" &&
|
|
269
|
+
(error.message.includes("prematurely closed") ||
|
|
270
|
+
error.message.includes("aborted") ||
|
|
271
|
+
error.message.includes("canceled") ||
|
|
272
|
+
error.message.includes("cancelled")));
|
|
273
|
+
if (!isClientAbort) {
|
|
274
|
+
console.error("❌ [Anthropic] Stream error:", error?.message || error);
|
|
275
|
+
}
|
|
265
276
|
try {
|
|
266
277
|
await write(
|
|
267
278
|
`event: error\ndata: ${JSON.stringify({
|
|
@@ -1485,6 +1485,7 @@ export async function processStreamingResponse(
|
|
|
1485
1485
|
}
|
|
1486
1486
|
|
|
1487
1487
|
const previousUiSessionId = currentUiSessionId;
|
|
1488
|
+
const previousAccountId = currentAccountId;
|
|
1488
1489
|
currentAccountId = newStreamResult.activeAccountId;
|
|
1489
1490
|
currentUiSessionId = newStreamResult.uiSessionId;
|
|
1490
1491
|
retryContext.releaseAccountLease =
|
|
@@ -1517,9 +1518,18 @@ export async function processStreamingResponse(
|
|
|
1517
1518
|
});
|
|
1518
1519
|
}
|
|
1519
1520
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1521
|
+
const switched =
|
|
1522
|
+
previousUiSessionId !== currentUiSessionId ||
|
|
1523
|
+
previousAccountId !== currentAccountId;
|
|
1524
|
+
if (switched) {
|
|
1525
|
+
console.log(
|
|
1526
|
+
`🔄 [Chat] Stream recovery switched account | old=${previousUiSessionId.substring(0, 12)} | new=${currentUiSessionId.substring(0, 12)} | account=${currentAccountId}`,
|
|
1527
|
+
);
|
|
1528
|
+
} else {
|
|
1529
|
+
console.log(
|
|
1530
|
+
`🔄 [Chat] Stream recovery resumed | chat=${currentUiSessionId.substring(0, 12)} | account=${currentAccountId}`,
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1523
1533
|
reader = newStreamResult.stream.getReader();
|
|
1524
1534
|
activeReader = reader;
|
|
1525
1535
|
return true;
|
package/src/tools/parser.ts
CHANGED
|
@@ -2453,18 +2453,24 @@ export class StreamingToolParser {
|
|
|
2453
2453
|
recoveryAttempts,
|
|
2454
2454
|
});
|
|
2455
2455
|
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2456
|
+
if (isToolcallDebugEnabled()) {
|
|
2457
|
+
logger.warn(
|
|
2458
|
+
`[parser] Dropping malformed tool call (${t.length} chars): ${t.substring(0, 80).replace(/\n/g, " ")}...`,
|
|
2459
|
+
{
|
|
2460
|
+
toolName: droppedToolName,
|
|
2461
|
+
category: "malformed",
|
|
2462
|
+
contentLength: t.length,
|
|
2463
|
+
content: t.substring(0, 2000),
|
|
2464
|
+
failureReason: "all recovery stages failed to produce valid JSON",
|
|
2465
|
+
recoveryAttempts,
|
|
2466
|
+
declaredTools: [...this.declaredToolNames].slice(0, 10),
|
|
2467
|
+
},
|
|
2468
|
+
);
|
|
2469
|
+
} else {
|
|
2470
|
+
logger.warn(
|
|
2471
|
+
`[parser] Dropping malformed tool call (${t.length} chars)${droppedToolName ? ` [${droppedToolName}]` : ""}: ${t.substring(0, 80).replace(/\n/g, " ")}...`,
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2468
2474
|
if (
|
|
2469
2475
|
this.emittedToolCallCount === 0 &&
|
|
2470
2476
|
this.pendingLeadIn.trim().length > 0
|
|
@@ -3018,8 +3024,24 @@ export class StreamingToolParser {
|
|
|
3018
3024
|
private parseToolCall(parsed: any): ParsedToolCall | null {
|
|
3019
3025
|
if (!parsed || typeof parsed !== "object") return null;
|
|
3020
3026
|
|
|
3021
|
-
|
|
3027
|
+
let name =
|
|
3022
3028
|
parsed.name || parsed.function?.name || parsed.tool_name || parsed.tool;
|
|
3029
|
+
if (!name || typeof name !== "string" || name.length === 0) {
|
|
3030
|
+
const candidateArgs =
|
|
3031
|
+
parsed.arguments ||
|
|
3032
|
+
parsed.function?.arguments ||
|
|
3033
|
+
parsed.args ||
|
|
3034
|
+
parsed.parameters ||
|
|
3035
|
+
parsed.input ||
|
|
3036
|
+
parsed;
|
|
3037
|
+
const parsedCandidateArgs =
|
|
3038
|
+
typeof candidateArgs === "string"
|
|
3039
|
+
? parseJsonishString(candidateArgs) ?? {}
|
|
3040
|
+
: typeof candidateArgs === "object" && candidateArgs !== null
|
|
3041
|
+
? candidateArgs
|
|
3042
|
+
: {};
|
|
3043
|
+
name = inferToolNameFromParameters(parsedCandidateArgs, this.tools);
|
|
3044
|
+
}
|
|
3023
3045
|
if (!name || typeof name !== "string" || name.length === 0) return null;
|
|
3024
3046
|
|
|
3025
3047
|
// Drop hallucinated tool calls where the model split a value vertically
|
package/src/tui/theme.ts
CHANGED
|
@@ -191,67 +191,81 @@ export function stripAnsi(str: string): string {
|
|
|
191
191
|
}
|
|
192
192
|
const graphemeSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
193
193
|
|
|
194
|
+
function singleGraphemeWidth(segment: string): number {
|
|
195
|
+
const code = segment.codePointAt(0) || 0;
|
|
196
|
+
// Standalone zero-width characters (variation selectors, zero-width space/joiner)
|
|
197
|
+
if (
|
|
198
|
+
segment === "\u200b" ||
|
|
199
|
+
segment === "\u200c" ||
|
|
200
|
+
segment === "\u200d" ||
|
|
201
|
+
(code >= 0xfe00 && code <= 0xfe0f && segment.length === 1)
|
|
202
|
+
) {
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
// Graphemes containing variation selector 16 (emoji presentation)
|
|
206
|
+
if (segment.includes("\ufe0f")) {
|
|
207
|
+
return 2;
|
|
208
|
+
}
|
|
209
|
+
// Specific BMP emojis and symbols that occupy 2 visual terminal cells (e.g. ⚠️, ⏱, ⚡, ✅, ❌, ✨, ☕, ⚙)
|
|
210
|
+
const isBmpEmoji =
|
|
211
|
+
code === 0x26a0 || // ⚠️ (WARNING SIGN)
|
|
212
|
+
code === 0x23f1 || // ⏱ (STOPWATCH)
|
|
213
|
+
code === 0x23f0 || // ⏰
|
|
214
|
+
code === 0x23f3 || // ⏳
|
|
215
|
+
code === 0x231a || // ⌚
|
|
216
|
+
code === 0x231b || // ⌛
|
|
217
|
+
code === 0x2705 || // ✅
|
|
218
|
+
code === 0x2728 || // ✨
|
|
219
|
+
code === 0x274c || // ❌
|
|
220
|
+
code === 0x274e || // ❎
|
|
221
|
+
code === 0x2753 || // ❓
|
|
222
|
+
code === 0x2757 || // ❗
|
|
223
|
+
code === 0x2b50 || // ⭐
|
|
224
|
+
code === 0x2b55 || // ⭕
|
|
225
|
+
code === 0x26a1 || // ⚡
|
|
226
|
+
code === 0x2615 || // ☕
|
|
227
|
+
code === 0x2699 || // ⚙
|
|
228
|
+
code === 0x2709; // ✉
|
|
229
|
+
|
|
230
|
+
// Common emoji and CJK full-width ranges (SMP Emojis 0x1f300 - 0x1faff)
|
|
231
|
+
if (
|
|
232
|
+
isBmpEmoji ||
|
|
233
|
+
(code >= 0x1100 && code <= 0x115f) ||
|
|
234
|
+
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
235
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
236
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
237
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
238
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
239
|
+
(code >= 0xff01 && code <= 0xff60) ||
|
|
240
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
241
|
+
(code >= 0x1f300 && code <= 0x1faff)
|
|
242
|
+
) {
|
|
243
|
+
return 2;
|
|
244
|
+
}
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
194
248
|
/**
|
|
195
249
|
* Computes visual display width of string, accounting for wide characters,
|
|
196
250
|
* grapheme clusters, variation selectors (VS16), and terminal-wide emojis.
|
|
197
251
|
*/
|
|
198
252
|
export function stringWidth(str: string): number {
|
|
253
|
+
if (!str) return 0;
|
|
254
|
+
// Fast path for pure ASCII strings without ANSI escapes
|
|
255
|
+
let isAscii = true;
|
|
256
|
+
for (let i = 0; i < str.length; i++) {
|
|
257
|
+
const c = str.charCodeAt(i);
|
|
258
|
+
if (c < 32 || c > 126) {
|
|
259
|
+
isAscii = false;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (isAscii) return str.length;
|
|
264
|
+
|
|
199
265
|
const clean = stripAnsi(str).replace(/\t/g, " ").replace(/\r/g, "");
|
|
200
266
|
let width = 0;
|
|
201
267
|
for (const { segment } of graphemeSegmenter.segment(clean)) {
|
|
202
|
-
|
|
203
|
-
// Standalone zero-width characters (variation selectors, zero-width space/joiner)
|
|
204
|
-
if (
|
|
205
|
-
segment === "\u200b" ||
|
|
206
|
-
segment === "\u200c" ||
|
|
207
|
-
segment === "\u200d" ||
|
|
208
|
-
(code >= 0xfe00 && code <= 0xfe0f && segment.length === 1)
|
|
209
|
-
) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
// Graphemes containing variation selector 16 (emoji presentation)
|
|
213
|
-
if (segment.includes("\ufe0f")) {
|
|
214
|
-
width += 2;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
// Specific BMP emojis and symbols that occupy 2 visual terminal cells (e.g. ⚠️, ⏱, ⚡, ✅, ❌, ✨, ☕, ⚙)
|
|
218
|
-
const isBmpEmoji =
|
|
219
|
-
code === 0x26a0 || // ⚠️ (WARNING SIGN)
|
|
220
|
-
code === 0x23f1 || // ⏱ (STOPWATCH)
|
|
221
|
-
code === 0x23f0 || // ⏰
|
|
222
|
-
code === 0x23f3 || // ⏳
|
|
223
|
-
code === 0x231a || // ⌚
|
|
224
|
-
code === 0x231b || // ⌛
|
|
225
|
-
code === 0x2705 || // ✅
|
|
226
|
-
code === 0x2728 || // ✨
|
|
227
|
-
code === 0x274c || // ❌
|
|
228
|
-
code === 0x274e || // ❎
|
|
229
|
-
code === 0x2753 || // ❓
|
|
230
|
-
code === 0x2757 || // ❗
|
|
231
|
-
code === 0x2b50 || // ⭐
|
|
232
|
-
code === 0x2b55 || // ⭕
|
|
233
|
-
code === 0x26a1 || // ⚡
|
|
234
|
-
code === 0x2615 || // ☕
|
|
235
|
-
code === 0x2699 || // ⚙
|
|
236
|
-
code === 0x2709; // ✉
|
|
237
|
-
|
|
238
|
-
// Common emoji and CJK full-width ranges (SMP Emojis 0x1f300 - 0x1faff)
|
|
239
|
-
if (
|
|
240
|
-
isBmpEmoji ||
|
|
241
|
-
(code >= 0x1100 && code <= 0x115f) ||
|
|
242
|
-
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
243
|
-
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
244
|
-
(code >= 0xf900 && code <= 0xfaff) ||
|
|
245
|
-
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
246
|
-
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
247
|
-
(code >= 0xff01 && code <= 0xff60) ||
|
|
248
|
-
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
249
|
-
(code >= 0x1f300 && code <= 0x1faff)
|
|
250
|
-
) {
|
|
251
|
-
width += 2;
|
|
252
|
-
} else {
|
|
253
|
-
width += 1;
|
|
254
|
-
}
|
|
268
|
+
width += singleGraphemeWidth(segment);
|
|
255
269
|
}
|
|
256
270
|
return width;
|
|
257
271
|
}
|
|
@@ -261,8 +275,26 @@ export function stringWidth(str: string): number {
|
|
|
261
275
|
*/
|
|
262
276
|
export function truncate(str: string, maxWidth: number, ellipsis = "…"): string {
|
|
263
277
|
if (maxWidth <= 0) return "";
|
|
278
|
+
if (!str) return "";
|
|
279
|
+
|
|
280
|
+
// Fast path for pure ASCII strings
|
|
281
|
+
let isAscii = true;
|
|
282
|
+
for (let i = 0; i < str.length; i++) {
|
|
283
|
+
const c = str.charCodeAt(i);
|
|
284
|
+
if (c < 32 || c > 126) {
|
|
285
|
+
isAscii = false;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (isAscii) {
|
|
290
|
+
if (str.length <= maxWidth) return str;
|
|
291
|
+
const targetW = Math.max(0, maxWidth - 1);
|
|
292
|
+
return str.slice(0, targetW) + ellipsis;
|
|
293
|
+
}
|
|
294
|
+
|
|
264
295
|
const clean = stripAnsi(str).replace(/\t/g, " ").replace(/\r/g, "");
|
|
265
|
-
|
|
296
|
+
const cleanW = stringWidth(clean);
|
|
297
|
+
if (cleanW <= maxWidth) return str;
|
|
266
298
|
|
|
267
299
|
const ellipsisW = stringWidth(ellipsis);
|
|
268
300
|
const targetW = Math.max(0, maxWidth - ellipsisW);
|
|
@@ -270,7 +302,7 @@ export function truncate(str: string, maxWidth: number, ellipsis = "…"): strin
|
|
|
270
302
|
let currentW = 0;
|
|
271
303
|
let result = "";
|
|
272
304
|
for (const { segment } of graphemeSegmenter.segment(clean)) {
|
|
273
|
-
const segW =
|
|
305
|
+
const segW = singleGraphemeWidth(segment);
|
|
274
306
|
if (currentW + segW > targetW) break;
|
|
275
307
|
currentW += segW;
|
|
276
308
|
result += segment;
|
|
@@ -89,58 +89,56 @@ export class LogsView implements TuiView {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
public handleKey(key: KeyEvent): boolean | void {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Mouse hover on chips (accepts rows 3 to 6 for generous vertical target)
|
|
96
|
-
if (key.name === "hover" && key.mouse) {
|
|
92
|
+
// Mouse hover or click on chips (only compute chips when mouse is on chip rows 3 to 6)
|
|
93
|
+
if ((key.name === "hover" || key.name === "click") && key.mouse) {
|
|
97
94
|
if (key.mouse.row >= 3 && key.mouse.row <= 6) {
|
|
95
|
+
const rawCount = ServerManager.getInstance().getLogEntries(this.filter).length;
|
|
96
|
+
const { chips } = this.getChips(rawCount);
|
|
98
97
|
const col = key.mouse.col;
|
|
99
|
-
let target: "all" | "warn" | "error" | "copy" | "clear" | null = null;
|
|
100
|
-
for (const c of chips) {
|
|
101
|
-
if (col >= c.startCol && col <= c.endCol) {
|
|
102
|
-
target = c.id;
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (this.hoveredChip !== target) {
|
|
107
|
-
this.hoveredChip = target;
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
} else if (this.hoveredChip !== null) {
|
|
111
|
-
this.hoveredChip = null;
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
98
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return true;
|
|
99
|
+
if (key.name === "hover") {
|
|
100
|
+
let target: "all" | "warn" | "error" | "copy" | "clear" | null = null;
|
|
101
|
+
for (const c of chips) {
|
|
102
|
+
if (col >= c.startCol && col <= c.endCol) {
|
|
103
|
+
target = c.id;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
124
106
|
}
|
|
125
|
-
if (
|
|
126
|
-
this.
|
|
107
|
+
if (this.hoveredChip !== target) {
|
|
108
|
+
this.hoveredChip = target;
|
|
127
109
|
return true;
|
|
128
110
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
111
|
+
} else if (key.name === "click") {
|
|
112
|
+
for (const c of chips) {
|
|
113
|
+
if (col >= c.startCol && col <= c.endCol) {
|
|
114
|
+
if (c.id === "all") {
|
|
115
|
+
this.setFilter("all");
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (c.id === "warn") {
|
|
119
|
+
this.setFilter("warn");
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (c.id === "error") {
|
|
123
|
+
this.setFilter("error");
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (c.id === "copy") {
|
|
127
|
+
this.copyLogs();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (c.id === "clear") {
|
|
131
|
+
ServerManager.getInstance().clearLogs();
|
|
132
|
+
this.scrollOffset = 0;
|
|
133
|
+
this.selectedLogIndex = null;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
142
137
|
}
|
|
143
138
|
}
|
|
139
|
+
} else if (this.hoveredChip !== null) {
|
|
140
|
+
this.hoveredChip = null;
|
|
141
|
+
return true;
|
|
144
142
|
}
|
|
145
143
|
}
|
|
146
144
|
|
|
@@ -351,39 +349,11 @@ export class LogsView implements TuiView {
|
|
|
351
349
|
? theme.bgHover(" [ C ] Limpar ")
|
|
352
350
|
: theme.muted(" [ C ] Limpar ");
|
|
353
351
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
if (rawEntries.length === 0) {
|
|
357
|
-
formattedLines.push("");
|
|
358
|
-
formattedLines.push(
|
|
359
|
-
theme.muted(` Nenhum log registrado para o filtro atual [${this.filter}].`),
|
|
360
|
-
);
|
|
361
|
-
formattedLines.push(
|
|
362
|
-
theme.muted(" Eventos de inicialização, requisições e alertas do proxy aparecerão aqui."),
|
|
363
|
-
);
|
|
364
|
-
} else {
|
|
365
|
-
for (const entry of rawEntries) {
|
|
366
|
-
if (!entry || !entry.message || !entry.message.trim()) continue;
|
|
367
|
-
let tag = theme.dim("[INFO]");
|
|
368
|
-
if (entry.level === "WARN") {
|
|
369
|
-
tag = theme.yellow("[WARN]");
|
|
370
|
-
} else if (entry.level === "ERROR") {
|
|
371
|
-
tag = theme.red("[ERR]");
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const prefix = `${theme.dim(entry.time)} ${tag} `;
|
|
375
|
-
const prefixW = stringWidth(prefix);
|
|
376
|
-
const maxMsgW = Math.max(20, innerW - prefixW - 4);
|
|
377
|
-
const truncatedMsg = truncate(entry.message, maxMsgW);
|
|
378
|
-
formattedLines.push(`${prefix}${truncatedMsg}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
this.lastTotalCount = formattedLines.length;
|
|
352
|
+
const total = rawEntries.length;
|
|
353
|
+
this.lastTotalCount = total;
|
|
383
354
|
|
|
384
355
|
// Scroll Window with top margin breathing room (2 rows reserved at the top)
|
|
385
356
|
const visibleCapacity = Math.max(1, contentH - 4);
|
|
386
|
-
const total = formattedLines.length;
|
|
387
357
|
const maxOffset = Math.max(0, total - visibleCapacity);
|
|
388
358
|
this.lastWidth = width;
|
|
389
359
|
this.lastHeight = height;
|
|
@@ -394,10 +364,10 @@ export class LogsView implements TuiView {
|
|
|
394
364
|
const scrollFromTop = maxOffset - clampedOffset;
|
|
395
365
|
|
|
396
366
|
const startIndex = Math.max(0, total - visibleCapacity - clampedOffset);
|
|
397
|
-
const
|
|
367
|
+
const visibleEntries = total === 0 ? [] : rawEntries.slice(startIndex, startIndex + visibleCapacity);
|
|
398
368
|
|
|
399
369
|
this.lastStartIndex = startIndex;
|
|
400
|
-
this.lastVisibleCount =
|
|
370
|
+
this.lastVisibleCount = visibleEntries.length;
|
|
401
371
|
|
|
402
372
|
// Scrollbar calculation
|
|
403
373
|
const hasScrollbar = total > visibleCapacity;
|
|
@@ -416,33 +386,64 @@ export class LogsView implements TuiView {
|
|
|
416
386
|
const finalRows: string[] = ["", ""];
|
|
417
387
|
const boxInnerW = Math.max(1, width - 2);
|
|
418
388
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
389
|
+
if (total === 0) {
|
|
390
|
+
finalRows.push("");
|
|
391
|
+
finalRows.push(
|
|
392
|
+
theme.muted(` Nenhum log registrado para o filtro atual [${this.filter}].`),
|
|
393
|
+
);
|
|
394
|
+
finalRows.push(
|
|
395
|
+
theme.muted(" Eventos de inicialização, requisições e alertas do proxy aparecerão aqui."),
|
|
396
|
+
);
|
|
397
|
+
while (finalRows.length < visibleCapacity + 2) {
|
|
398
|
+
finalRows.push("");
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
for (let r = 0; r < visibleCapacity; r++) {
|
|
402
|
+
if (r < visibleEntries.length) {
|
|
403
|
+
const actualIdx = startIndex + r;
|
|
404
|
+
const entry = visibleEntries[r];
|
|
405
|
+
let formattedLine = "";
|
|
406
|
+
if (entry && entry.message && entry.message.trim()) {
|
|
407
|
+
let tag = theme.dim("[INFO]");
|
|
408
|
+
if (entry.level === "WARN") {
|
|
409
|
+
tag = theme.yellow("[WARN]");
|
|
410
|
+
} else if (entry.level === "ERROR") {
|
|
411
|
+
tag = theme.red("[ERR]");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const prefix = `${theme.dim(entry.time)} ${tag} `;
|
|
415
|
+
const prefixW = 16;
|
|
416
|
+
const maxMsgW = Math.max(20, innerW - prefixW - 4);
|
|
417
|
+
const truncatedMsg = truncate(entry.message, maxMsgW);
|
|
418
|
+
formattedLine = `${prefix}${truncatedMsg}`;
|
|
419
|
+
}
|
|
429
420
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
let scrollChar: string;
|
|
434
|
-
if (isThumb) {
|
|
435
|
-
scrollChar = isHighlighted ? `\x1b[48;2;45;35;85m\x1b[38;2;0;255;255m\x1b[1m█\x1b[0m` : theme.cyan("█");
|
|
421
|
+
let styledText = formattedLine;
|
|
422
|
+
if (this.selectedLogIndex === actualIdx) {
|
|
423
|
+
styledText = theme.bgSelected(`▸ ${stripAnsi(formattedLine)} `);
|
|
436
424
|
} else {
|
|
437
|
-
|
|
425
|
+
styledText = ` ${formattedLine}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (hasScrollbar) {
|
|
429
|
+
const isThumb = r >= thumbTop && r < thumbTop + thumbSize;
|
|
430
|
+
const isHighlighted = this.isScrollbarHovered || this.isDraggingScrollbar;
|
|
431
|
+
let scrollChar: string;
|
|
432
|
+
if (isThumb) {
|
|
433
|
+
scrollChar = isHighlighted
|
|
434
|
+
? `\x1b[48;2;45;35;85m\x1b[38;2;0;255;255m\x1b[1m█\x1b[0m`
|
|
435
|
+
: theme.cyan("█");
|
|
436
|
+
} else {
|
|
437
|
+
scrollChar = isHighlighted ? theme.cyan("│") : theme.dark("│");
|
|
438
|
+
}
|
|
439
|
+
const padded = pad(styledText, boxInnerW - 1);
|
|
440
|
+
finalRows.push(`${padded}${scrollChar}`);
|
|
441
|
+
} else {
|
|
442
|
+
finalRows.push(styledText);
|
|
438
443
|
}
|
|
439
|
-
const padded = pad(styledText, boxInnerW - 1);
|
|
440
|
-
finalRows.push(`${padded}${scrollChar}`);
|
|
441
444
|
} else {
|
|
442
|
-
finalRows.push(
|
|
445
|
+
finalRows.push("");
|
|
443
446
|
}
|
|
444
|
-
} else {
|
|
445
|
-
finalRows.push("");
|
|
446
447
|
}
|
|
447
448
|
}
|
|
448
449
|
|