@jsonstudio/rcc 0.90.814 → 0.90.876
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/configsamples/provider-default/ali-coding-plan/config.v2.json +76 -0
- package/configsamples/provider-default/antigravity/config.v2.json +142 -0
- package/configsamples/provider-default/ark-coding-plan/config.v2.json +64 -0
- package/configsamples/provider-default/crs/config.v2.json +54 -0
- package/configsamples/provider-default/deepseek-web/config.v2.json +56 -0
- package/configsamples/provider-default/gemini/config.v2.json +43 -0
- package/configsamples/provider-default/gemini-cli/config.v2.json +45 -0
- package/configsamples/provider-default/gemini-native/config.v2.json +208 -0
- package/configsamples/provider-default/glm/config.v2.json +33 -0
- package/configsamples/provider-default/glm-anthropic/config.v2.json +29 -0
- package/configsamples/provider-default/kimi/config.v2.json +25 -0
- package/configsamples/provider-default/lmstudio/config.v2.json +79 -0
- package/configsamples/provider-default/lmstudio-proxy/config.v2.json +78 -0
- package/configsamples/provider-default/manifest.json +31 -0
- package/configsamples/provider-default/meituan/config.v2.json +20 -0
- package/configsamples/provider-default/mimo/config.v2.json +26 -0
- package/configsamples/provider-default/modelscope/config.v2.json +81 -0
- package/configsamples/provider-default/my-openai/config.v2.json +20 -0
- package/configsamples/provider-default/nvidia/config.v2.json +32 -0
- package/configsamples/provider-default/opencode-zen-free/config.v2.json +56 -0
- package/configsamples/provider-default/openrouter/config.v2.json +210 -0
- package/configsamples/provider-default/qwen/config.v2.json +38 -0
- package/configsamples/provider-default/qwenchat/config.v2.json +53 -0
- package/configsamples/provider-default/tab/config.v2.json +26 -0
- package/configsamples/provider-default/tabglm/config.v2.json +77 -0
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/config.d.ts +5 -0
- package/dist/cli/commands/config.js +369 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/examples.js +3 -0
- package/dist/cli/commands/examples.js.map +1 -1
- package/dist/cli/commands/init.js +25 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/launcher-kernel.js +122 -46
- package/dist/cli/commands/launcher-kernel.js.map +1 -1
- package/dist/cli/commands/start.js +60 -3
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/bundled-provider-pack.d.ts +20 -0
- package/dist/cli/config/bundled-provider-pack.js +146 -0
- package/dist/cli/config/bundled-provider-pack.js.map +1 -0
- package/dist/cli/register/status-config-commands.d.ts +2 -0
- package/dist/cli/register/status-config-commands.js.map +1 -1
- package/dist/cli.js +81 -28
- package/dist/cli.js.map +1 -1
- package/dist/debug/snapshot-store.js +2 -1
- package/dist/debug/snapshot-store.js.map +1 -1
- package/dist/index.js +23 -14
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.js +1 -1
- package/dist/manager/quota/provider-quota-center.js.map +1 -1
- package/dist/manager/storage/file-store.js +10 -0
- package/dist/manager/storage/file-store.js.map +1 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js +18 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js.map +1 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder.js +132 -51
- package/dist/modules/llmswitch/bridge/snapshot-recorder.js.map +1 -1
- package/dist/provider-sdk/provider-runtime-inference.js +2 -2
- package/dist/provider-sdk/provider-runtime-inference.js.map +1 -1
- package/dist/providers/auth/deepseek-account-token-acquirer.js +32 -3
- package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -1
- package/dist/providers/core/api/provider-types.d.ts +11 -0
- package/dist/providers/core/config/service-profiles.js +1 -1
- package/dist/providers/core/runtime/deepseek-http-provider.d.ts +2 -0
- package/dist/providers/core/runtime/deepseek-http-provider.js +31 -1
- package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.d.ts +3 -2
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js +513 -96
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js.map +1 -1
- package/dist/providers/core/runtime/standard-tool-text-harvest.d.ts +8 -0
- package/dist/providers/core/runtime/standard-tool-text-harvest.js +24 -0
- package/dist/providers/core/runtime/standard-tool-text-harvest.js.map +1 -0
- package/dist/providers/core/runtime/standard-tool-text-request-transform.d.ts +4 -1
- package/dist/providers/core/runtime/standard-tool-text-request-transform.js +129 -3
- package/dist/providers/core/runtime/standard-tool-text-request-transform.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.js +5 -2
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +52 -1
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +3 -0
- package/dist/server/handlers/handler-response-utils.js +1 -0
- package/dist/server/handlers/handler-response-utils.js.map +1 -1
- package/dist/server/handlers/images-handler.d.ts +9 -0
- package/dist/server/handlers/images-handler.js +258 -0
- package/dist/server/handlers/images-handler.js.map +1 -0
- package/dist/server/handlers/types.d.ts +7 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.d.ts +3 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.js +16 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -18
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.d.ts +7 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js +17 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +50 -17
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.js +32 -2
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-converter.js +42 -13
- package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-utils.js +41 -3
- package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
- package/dist/server/runtime/http-server/executor/usage-aggregator.js +7 -7
- package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
- package/dist/server/runtime/http-server/executor/usage-logger.d.ts +9 -0
- package/dist/server/runtime/http-server/executor/usage-logger.js +35 -2
- package/dist/server/runtime/http-server/executor/usage-logger.js.map +1 -1
- package/dist/server/runtime/http-server/executor-metadata.js +12 -4
- package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
- package/dist/server/runtime/http-server/executor-pipeline.js +24 -15
- package/dist/server/runtime/http-server/executor-pipeline.js.map +1 -1
- package/dist/server/runtime/http-server/executor-provider.d.ts +6 -1
- package/dist/server/runtime/http-server/executor-provider.js +137 -5
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-bootstrap.js +6 -0
- package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-lifecycle.js +23 -15
- package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-runtime-providers.js +14 -4
- package/dist/server/runtime/http-server/http-server-runtime-providers.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-runtime-setup.js +83 -1
- package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
- package/dist/server/runtime/http-server/hub-shadow-compare.js +2 -41
- package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
- package/dist/server/runtime/http-server/provider-routing-scope.d.ts +9 -0
- package/dist/server/runtime/http-server/provider-routing-scope.js +20 -0
- package/dist/server/runtime/http-server/provider-routing-scope.js.map +1 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.d.ts +67 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.js +467 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.js.map +1 -0
- package/dist/server/runtime/http-server/request-executor.d.ts +8 -0
- package/dist/server/runtime/http-server/request-executor.js +446 -21
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +13 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-registry.js +30 -4
- package/dist/server/runtime/http-server/session-client-registry.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-route-utils.d.ts +7 -0
- package/dist/server/runtime/http-server/session-client-route-utils.js +38 -0
- package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-routes.js +12 -2
- package/dist/server/runtime/http-server/session-client-routes.js.map +1 -1
- package/dist/server/utils/request-id-manager.js +42 -5
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/server/utils/stage-logger.d.ts +1 -0
- package/dist/server/utils/stage-logger.js +27 -0
- package/dist/server/utils/stage-logger.js.map +1 -1
- package/dist/utils/errorsamples.js +3 -1
- package/dist/utils/errorsamples.js.map +1 -1
- package/dist/utils/sensitive-redaction.d.ts +1 -0
- package/dist/utils/sensitive-redaction.js +122 -0
- package/dist/utils/sensitive-redaction.js.map +1 -0
- package/dist/utils/snapshot-writer.js +162 -2
- package/dist/utils/snapshot-writer.js.map +1 -1
- package/docs/INSTALLATION_AND_QUICKSTART.md +14 -1
- package/docs/PORTS.md +12 -0
- package/docs/lmstudio-tool-calling.md +25 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.d.ts +3 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.js +62 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwenchat-web.json +47 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/node-support.js +5 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/operation-table-runner.js +68 -7
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper-from-chat.js +138 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-chat-process-request-utils.js +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-chat-process-entry.js +7 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-request-stage.js +7 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.d.ts +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.js +203 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-route-and-outbound.js +17 -12
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.js +82 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +47 -14
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +43 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +222 -19
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/policy/policy-engine.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process-pending-tool-sync.js +24 -7
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +90 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.js +252 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge/utils.js +5 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +44 -4
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.d.ts +3 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.js +20 -23
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +68 -30
- package/node_modules/@jsonstudio/llms/dist/conversion/snapshot-utils.js +194 -10
- package/node_modules/@jsonstudio/llms/dist/native/router_hotpath_napi.node +0 -0
- package/node_modules/@jsonstudio/llms/dist/quota/quota-state.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine/routing-state/store.js +35 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +9 -9
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/sticky-session-store.js +104 -18
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +79 -32
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +49 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/pending-session.js +48 -2
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +14 -1
- package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/package.json +1 -1
- package/node_modules/ajv/dist/compile/jtd/serialize.js +9 -2
- package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -1
- package/node_modules/ajv/dist/core.d.ts +1 -0
- package/node_modules/ajv/dist/core.js.map +1 -1
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js +13 -4
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -1
- package/node_modules/ajv/lib/compile/jtd/serialize.ts +13 -2
- package/node_modules/ajv/lib/core.ts +1 -0
- package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +15 -4
- package/node_modules/ajv/package.json +2 -1
- package/package.json +15 -10
- package/scripts/ci/repo-sanity.mjs +23 -2
- package/scripts/ci/secrets-check.mjs +48 -0
- package/scripts/ci/silent-failure-audit.mjs +192 -0
- package/scripts/mock-provider/run-regressions.mjs +1 -0
- package/scripts/monitor/memory-guard.mjs +207 -0
- package/scripts/pack-mode.mjs +32 -36
- package/scripts/publish-rcc.mjs +38 -60
- package/scripts/tests/apply-patch-loop.mjs +1 -0
- package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +2 -0
- package/scripts/tools-dev/responses-debug-client/src/index.ts +8 -3
- package/scripts/verify-e2e-toolcall.mjs +1 -0
- package/scripts/verify-install-e2e.mjs +2 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash, createHmac, randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
2
3
|
import { PassThrough } from 'node:stream';
|
|
3
|
-
import { processChatResponseTools } from '@jsonstudio/llms/conversion';
|
|
4
4
|
import { applyStandardToolTextRequestTransform } from './standard-tool-text-request-transform.js';
|
|
5
|
+
import { applyStandardToolTextHarvestToChatPayload } from './standard-tool-text-harvest.js';
|
|
5
6
|
export const DEFAULT_QWENCHAT_BASE_URL = 'https://chat.qwen.ai';
|
|
6
7
|
export const DEFAULT_QWENCHAT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
|
|
7
8
|
export const DEFAULT_QWENCHAT_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9,en;q=0.8';
|
|
@@ -9,6 +10,8 @@ export const DEFAULT_QWENCHAT_COMPLETION_ENDPOINT = '/api/v2/chat/completions';
|
|
|
9
10
|
export const DEFAULT_QWENCHAT_CHAT_CREATE_ENDPOINT = '/api/v2/chats/new';
|
|
10
11
|
const BAXIA_VERSION = '2.5.36';
|
|
11
12
|
const BAXIA_CACHE_TTL_MS = 4 * 60 * 1000;
|
|
13
|
+
const DEFAULT_ATTACHMENT_FETCH_TIMEOUT_MS = 15_000;
|
|
14
|
+
const DEFAULT_ATTACHMENT_MAX_BYTES = 20 * 1024 * 1024;
|
|
12
15
|
function isRecord(value) {
|
|
13
16
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
17
|
}
|
|
@@ -26,6 +29,214 @@ function normalizeInputString(value) {
|
|
|
26
29
|
}
|
|
27
30
|
return trimmed;
|
|
28
31
|
}
|
|
32
|
+
function clampInteger(value, fallback, min, max) {
|
|
33
|
+
const parsed = typeof value === 'number' && Number.isFinite(value)
|
|
34
|
+
? Math.trunc(value)
|
|
35
|
+
: Number.parseInt(normalizeInputString(value), 10);
|
|
36
|
+
if (!Number.isFinite(parsed)) {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
return Math.min(max, Math.max(min, parsed));
|
|
40
|
+
}
|
|
41
|
+
function readBooleanEnv(keys, fallback) {
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
const raw = normalizeInputString(process.env[key]);
|
|
44
|
+
if (!raw) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const normalized = raw.trim().toLowerCase();
|
|
48
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
function parseImageSizeToQwenRatio(size) {
|
|
58
|
+
const text = normalizeInputString(size);
|
|
59
|
+
if (!text) {
|
|
60
|
+
return '1:1';
|
|
61
|
+
}
|
|
62
|
+
const ratioMatch = text.match(/^(\d{1,2}):(\d{1,2})$/);
|
|
63
|
+
if (ratioMatch) {
|
|
64
|
+
const w = Number.parseInt(ratioMatch[1], 10);
|
|
65
|
+
const h = Number.parseInt(ratioMatch[2], 10);
|
|
66
|
+
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
67
|
+
const ratio = `${w}:${h}`;
|
|
68
|
+
if (['1:1', '16:9', '9:16', '4:3', '3:4'].includes(ratio)) {
|
|
69
|
+
return ratio;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const sizeMatch = text.toLowerCase().match(/^(\d{2,5})\s*x\s*(\d{2,5})$/);
|
|
74
|
+
if (!sizeMatch) {
|
|
75
|
+
return '1:1';
|
|
76
|
+
}
|
|
77
|
+
const width = Number.parseInt(sizeMatch[1], 10);
|
|
78
|
+
const height = Number.parseInt(sizeMatch[2], 10);
|
|
79
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
80
|
+
return '1:1';
|
|
81
|
+
}
|
|
82
|
+
const ratio = width / height;
|
|
83
|
+
const candidates = [
|
|
84
|
+
{ key: '1:1', ratio: 1 },
|
|
85
|
+
{ key: '16:9', ratio: 16 / 9 },
|
|
86
|
+
{ key: '9:16', ratio: 9 / 16 },
|
|
87
|
+
{ key: '4:3', ratio: 4 / 3 },
|
|
88
|
+
{ key: '3:4', ratio: 3 / 4 }
|
|
89
|
+
];
|
|
90
|
+
let best = candidates[0];
|
|
91
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
92
|
+
for (const candidate of candidates) {
|
|
93
|
+
const diff = Math.abs(ratio - candidate.ratio);
|
|
94
|
+
if (diff < bestDiff) {
|
|
95
|
+
best = candidate;
|
|
96
|
+
bestDiff = diff;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return best.key;
|
|
100
|
+
}
|
|
101
|
+
function parseQwenImageGenerationOptions(metadata) {
|
|
102
|
+
const node = metadata && isRecord(metadata.qwenImageGeneration)
|
|
103
|
+
? metadata.qwenImageGeneration
|
|
104
|
+
: undefined;
|
|
105
|
+
const enabled = metadata?.qwenImageGeneration === true
|
|
106
|
+
|| metadata?.imageGeneration === true
|
|
107
|
+
|| normalizeInputString(metadata?.generationMode).toLowerCase() === 'image'
|
|
108
|
+
|| Boolean(node);
|
|
109
|
+
const count = clampInteger(node?.n ?? metadata?.n, 1, 1, 10);
|
|
110
|
+
const sizeRatio = parseImageSizeToQwenRatio(node?.size ?? metadata?.size);
|
|
111
|
+
const responseFormatRaw = normalizeInputString(node?.responseFormat ?? node?.response_format ?? metadata?.response_format).toLowerCase();
|
|
112
|
+
const responseFormat = responseFormatRaw === 'b64_json' ? 'b64_json' : 'url';
|
|
113
|
+
return {
|
|
114
|
+
enabled,
|
|
115
|
+
count,
|
|
116
|
+
sizeRatio,
|
|
117
|
+
responseFormat
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function readPositiveIntegerEnv(keys, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
const raw = normalizeInputString(process.env[key]);
|
|
123
|
+
if (!raw) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const parsed = Number.parseInt(raw, 10);
|
|
127
|
+
if (!Number.isFinite(parsed)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const normalized = Math.floor(parsed);
|
|
131
|
+
if (normalized < min || normalized > max) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
function isPrivateIpv4(host) {
|
|
139
|
+
const segments = host.split('.');
|
|
140
|
+
if (segments.length !== 4) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const numbers = segments.map((segment) => Number.parseInt(segment, 10));
|
|
144
|
+
if (numbers.some((value) => !Number.isFinite(value) || value < 0 || value > 255)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const [a, b] = numbers;
|
|
148
|
+
if (a === 10 || a === 127 || a === 0) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (a === 169 && b === 254) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (a === 192 && b === 168) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function isPrivateIpv6(host) {
|
|
163
|
+
const normalized = host.toLowerCase();
|
|
164
|
+
return normalized === '::1'
|
|
165
|
+
|| normalized.startsWith('fc')
|
|
166
|
+
|| normalized.startsWith('fd')
|
|
167
|
+
|| normalized.startsWith('fe80:');
|
|
168
|
+
}
|
|
169
|
+
function validateAttachmentSourceUrl(input) {
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = new URL(input);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw new Error('Invalid attachment URL');
|
|
176
|
+
}
|
|
177
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
178
|
+
if (protocol !== 'https:' && protocol !== 'http:') {
|
|
179
|
+
throw new Error(`Unsupported attachment URL protocol: ${protocol}`);
|
|
180
|
+
}
|
|
181
|
+
if (parsed.username || parsed.password) {
|
|
182
|
+
throw new Error('Attachment URL must not include username/password');
|
|
183
|
+
}
|
|
184
|
+
const hostname = parsed.hostname.trim().toLowerCase();
|
|
185
|
+
if (!hostname) {
|
|
186
|
+
throw new Error('Attachment URL hostname is required');
|
|
187
|
+
}
|
|
188
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname.endsWith('.local')) {
|
|
189
|
+
throw new Error('Attachment URL localhost/local domains are not allowed');
|
|
190
|
+
}
|
|
191
|
+
const ipType = isIP(hostname);
|
|
192
|
+
if (ipType === 4 && isPrivateIpv4(hostname)) {
|
|
193
|
+
throw new Error('Attachment URL private IPv4 is not allowed');
|
|
194
|
+
}
|
|
195
|
+
if (ipType === 6 && isPrivateIpv6(hostname)) {
|
|
196
|
+
throw new Error('Attachment URL private IPv6 is not allowed');
|
|
197
|
+
}
|
|
198
|
+
return parsed;
|
|
199
|
+
}
|
|
200
|
+
async function readResponseBytesWithLimit(response, maxBytes) {
|
|
201
|
+
const contentLengthText = normalizeInputString(response.headers.get('content-length'));
|
|
202
|
+
if (contentLengthText) {
|
|
203
|
+
const contentLength = Number.parseInt(contentLengthText, 10);
|
|
204
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes) {
|
|
205
|
+
throw new Error(`Attachment exceeds max size (${contentLength} > ${maxBytes})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const reader = response.body?.getReader();
|
|
209
|
+
if (!reader) {
|
|
210
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
211
|
+
}
|
|
212
|
+
const chunks = [];
|
|
213
|
+
let total = 0;
|
|
214
|
+
while (true) {
|
|
215
|
+
const step = await reader.read();
|
|
216
|
+
if (step.done) {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
const chunk = step.value;
|
|
220
|
+
total += chunk.byteLength;
|
|
221
|
+
if (total > maxBytes) {
|
|
222
|
+
try {
|
|
223
|
+
await reader.cancel('attachment_too_large');
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// ignore
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`Attachment exceeds max size (${total} > ${maxBytes})`);
|
|
229
|
+
}
|
|
230
|
+
chunks.push(chunk);
|
|
231
|
+
}
|
|
232
|
+
const merged = new Uint8Array(total);
|
|
233
|
+
let offset = 0;
|
|
234
|
+
for (const chunk of chunks) {
|
|
235
|
+
merged.set(chunk, offset);
|
|
236
|
+
offset += chunk.byteLength;
|
|
237
|
+
}
|
|
238
|
+
return merged;
|
|
239
|
+
}
|
|
29
240
|
function randomString(length) {
|
|
30
241
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
31
242
|
const bytes = randomBytes(length);
|
|
@@ -340,11 +551,19 @@ async function loadAttachmentBytes(attachment) {
|
|
|
340
551
|
};
|
|
341
552
|
}
|
|
342
553
|
if (/^https?:\/\//i.test(attachment.source)) {
|
|
343
|
-
const
|
|
554
|
+
const parsedUrl = validateAttachmentSourceUrl(attachment.source);
|
|
555
|
+
const timeoutMs = readPositiveIntegerEnv(['ROUTECODEX_QWENCHAT_ATTACHMENT_FETCH_TIMEOUT_MS', 'RCC_QWENCHAT_ATTACHMENT_FETCH_TIMEOUT_MS'], DEFAULT_ATTACHMENT_FETCH_TIMEOUT_MS, 1_000, 300_000);
|
|
556
|
+
const maxBytes = readPositiveIntegerEnv(['ROUTECODEX_QWENCHAT_ATTACHMENT_MAX_BYTES', 'RCC_QWENCHAT_ATTACHMENT_MAX_BYTES'], DEFAULT_ATTACHMENT_MAX_BYTES, 1024);
|
|
557
|
+
const controller = new AbortController();
|
|
558
|
+
const timer = setTimeout(() => controller.abort('attachment_fetch_timeout'), timeoutMs);
|
|
559
|
+
if (typeof timer.unref === 'function') {
|
|
560
|
+
timer.unref();
|
|
561
|
+
}
|
|
562
|
+
const resp = await fetch(parsedUrl, { signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
344
563
|
if (!resp.ok) {
|
|
345
564
|
throw new Error(`Failed to fetch attachment URL: HTTP ${resp.status}`);
|
|
346
565
|
}
|
|
347
|
-
const bytes =
|
|
566
|
+
const bytes = await readResponseBytesWithLimit(resp, maxBytes);
|
|
348
567
|
const mimeType = normalizeMimeType(attachment.mimeType || resp.headers.get('content-type'));
|
|
349
568
|
return {
|
|
350
569
|
bytes,
|
|
@@ -363,23 +582,45 @@ async function loadAttachmentBytes(attachment) {
|
|
|
363
582
|
explicitType: attachment.explicitType
|
|
364
583
|
};
|
|
365
584
|
}
|
|
366
|
-
function qwenCommonHeaders(baxiaTokens, authHeaders) {
|
|
585
|
+
function qwenCommonHeaders(baxiaTokens, authHeaders, options) {
|
|
586
|
+
const forwardAuthHeaders = readBooleanEnv(['ROUTECODEX_QWENCHAT_FORWARD_AUTH_HEADERS', 'RCC_QWENCHAT_FORWARD_AUTH_HEADERS'], false);
|
|
587
|
+
const normalizedBase = normalizeInputString(options?.baseUrl) || DEFAULT_QWENCHAT_BASE_URL;
|
|
588
|
+
const base = normalizedBase.replace(/\/$/, '');
|
|
589
|
+
const referer = options?.refererMode === 'guest' ? `${base}/c/guest` : `${base}/`;
|
|
590
|
+
const accept = options?.acceptMode === 'json' ? 'application/json' : 'application/json, text/plain, */*';
|
|
367
591
|
return {
|
|
368
|
-
'Accept':
|
|
592
|
+
'Accept': accept,
|
|
369
593
|
'Content-Type': 'application/json',
|
|
370
594
|
'bx-ua': baxiaTokens.bxUa,
|
|
371
595
|
'bx-umidtoken': baxiaTokens.bxUmidToken,
|
|
372
596
|
'bx-v': baxiaTokens.bxV,
|
|
373
597
|
'source': 'web',
|
|
374
598
|
'timezone': new Date().toUTCString(),
|
|
375
|
-
'Referer':
|
|
599
|
+
'Referer': referer,
|
|
376
600
|
'User-Agent': DEFAULT_QWENCHAT_USER_AGENT,
|
|
377
601
|
'Accept-Language': DEFAULT_QWENCHAT_ACCEPT_LANGUAGE,
|
|
378
602
|
'x-request-id': randomUUID(),
|
|
379
|
-
...(authHeaders || {})
|
|
603
|
+
...(forwardAuthHeaders ? authHeaders || {} : {})
|
|
380
604
|
};
|
|
381
605
|
}
|
|
382
606
|
function extractQwenErrorMessage(payload) {
|
|
607
|
+
const readNestedMessage = (value) => {
|
|
608
|
+
if (typeof value === 'string') {
|
|
609
|
+
return normalizeInputString(value);
|
|
610
|
+
}
|
|
611
|
+
if (!isRecord(value)) {
|
|
612
|
+
return '';
|
|
613
|
+
}
|
|
614
|
+
const nested = [
|
|
615
|
+
normalizeInputString(value.message),
|
|
616
|
+
normalizeInputString(value.msg),
|
|
617
|
+
normalizeInputString(value.details),
|
|
618
|
+
normalizeInputString(value.template),
|
|
619
|
+
normalizeInputString(value.code),
|
|
620
|
+
isRecord(value.error) ? normalizeInputString(value.error.message) : ''
|
|
621
|
+
].filter(Boolean);
|
|
622
|
+
return nested[0] || '';
|
|
623
|
+
};
|
|
383
624
|
const readNode = (node) => {
|
|
384
625
|
if (!node)
|
|
385
626
|
return '';
|
|
@@ -387,7 +628,10 @@ function extractQwenErrorMessage(payload) {
|
|
|
387
628
|
normalizeInputString(node.msg),
|
|
388
629
|
normalizeInputString(node.message),
|
|
389
630
|
normalizeInputString(node.error),
|
|
390
|
-
normalizeInputString(node.err_msg)
|
|
631
|
+
normalizeInputString(node.err_msg),
|
|
632
|
+
normalizeInputString(node.details),
|
|
633
|
+
normalizeInputString(node.template),
|
|
634
|
+
normalizeInputString(node.code)
|
|
391
635
|
].filter(Boolean);
|
|
392
636
|
if (direct.length > 0) {
|
|
393
637
|
return direct[0];
|
|
@@ -402,10 +646,17 @@ function extractQwenErrorMessage(payload) {
|
|
|
402
646
|
return nested[0];
|
|
403
647
|
}
|
|
404
648
|
}
|
|
649
|
+
if (isRecord(node.details)) {
|
|
650
|
+
const nestedDetails = readNestedMessage(node.details);
|
|
651
|
+
if (nestedDetails) {
|
|
652
|
+
return nestedDetails;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
405
655
|
return '';
|
|
406
656
|
};
|
|
407
657
|
const dataNode = isRecord(payload.data) ? payload.data : undefined;
|
|
408
|
-
|
|
658
|
+
const detailsNode = isRecord(dataNode?.details) ? dataNode.details : undefined;
|
|
659
|
+
return readNode(payload) || readNode(dataNode) || readNode(detailsNode) || '';
|
|
409
660
|
}
|
|
410
661
|
function extractQwenUploadTokenData(payload) {
|
|
411
662
|
const tryReadNode = (node) => {
|
|
@@ -462,7 +713,7 @@ async function requestUploadToken(baseUrl, file, baxiaTokens, authHeaders) {
|
|
|
462
713
|
const filetype = inferFileCategory(file.mimeType, file.explicitType);
|
|
463
714
|
const resp = await fetch(joinUrl(baseUrl, '/api/v2/files/getstsToken'), {
|
|
464
715
|
method: 'POST',
|
|
465
|
-
headers: qwenCommonHeaders(baxiaTokens, authHeaders),
|
|
716
|
+
headers: qwenCommonHeaders(baxiaTokens, authHeaders, { baseUrl, refererMode: 'web', acceptMode: 'default' }),
|
|
466
717
|
body: JSON.stringify({
|
|
467
718
|
filename: file.filename,
|
|
468
719
|
filesize: file.bytes.length,
|
|
@@ -653,7 +904,7 @@ async function ensureUploadStatusForNonVideo(baseUrl, filetype, baxiaTokens, aut
|
|
|
653
904
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
654
905
|
const resp = await fetch(joinUrl(baseUrl, '/api/v2/users/status'), {
|
|
655
906
|
method: 'POST',
|
|
656
|
-
headers: qwenCommonHeaders(baxiaTokens, authHeaders),
|
|
907
|
+
headers: qwenCommonHeaders(baxiaTokens, authHeaders, { baseUrl, refererMode: 'web', acceptMode: 'default' }),
|
|
657
908
|
body: JSON.stringify({
|
|
658
909
|
typarms: {
|
|
659
910
|
typarm1: 'web',
|
|
@@ -689,7 +940,7 @@ async function parseDocumentIfNeeded(baseUrl, qwenFilePayload, filetype, baxiaTo
|
|
|
689
940
|
}
|
|
690
941
|
const resp = await fetch(joinUrl(baseUrl, '/api/v2/files/parse'), {
|
|
691
942
|
method: 'POST',
|
|
692
|
-
headers: qwenCommonHeaders(baxiaTokens, authHeaders),
|
|
943
|
+
headers: qwenCommonHeaders(baxiaTokens, authHeaders, { baseUrl, refererMode: 'web', acceptMode: 'default' }),
|
|
693
944
|
body: JSON.stringify({ file_id: fileId })
|
|
694
945
|
});
|
|
695
946
|
const text = await resp.text().catch(() => '');
|
|
@@ -730,10 +981,17 @@ export async function uploadAttachments(args) {
|
|
|
730
981
|
export function extractQwenChatPayload(request) {
|
|
731
982
|
const container = isRecord(request) ? request : {};
|
|
732
983
|
const payload = isRecord(container.data) ? container.data : container;
|
|
733
|
-
const model = normalizeInputString(payload.model) || 'qwen3.
|
|
734
|
-
const
|
|
984
|
+
const model = normalizeInputString(payload.model) || 'qwen3.6-plus';
|
|
985
|
+
const directMessages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
986
|
+
const compatPrompt = normalizeInputString(payload.prompt);
|
|
987
|
+
const messages = directMessages.length > 0
|
|
988
|
+
? directMessages
|
|
989
|
+
: compatPrompt
|
|
990
|
+
? [{ role: 'user', content: compatPrompt }]
|
|
991
|
+
: [];
|
|
735
992
|
const streamFlag = payload.stream;
|
|
736
|
-
|
|
993
|
+
// Follow OpenAI semantics: stream defaults to false unless explicitly true.
|
|
994
|
+
const stream = streamFlag === true;
|
|
737
995
|
const metadata = isRecord(payload.metadata)
|
|
738
996
|
? payload.metadata
|
|
739
997
|
: isRecord(container.metadata)
|
|
@@ -748,12 +1006,29 @@ export function extractQwenChatPayload(request) {
|
|
|
748
1006
|
};
|
|
749
1007
|
}
|
|
750
1008
|
export function shouldUseSearchMode(payload) {
|
|
1009
|
+
if (parseQwenImageGenerationOptions(payload.metadata).enabled) {
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
751
1012
|
if (payload.metadata?.qwenWebSearch === true || payload.metadata?.webSearch === true) {
|
|
752
1013
|
return true;
|
|
753
1014
|
}
|
|
754
1015
|
return false;
|
|
755
1016
|
}
|
|
1017
|
+
function shouldUseImageGenerationMode(payload) {
|
|
1018
|
+
return parseQwenImageGenerationOptions(payload.metadata).enabled;
|
|
1019
|
+
}
|
|
756
1020
|
function extractChatIdFromCreatePayload(payload) {
|
|
1021
|
+
const readChatIdFromText = (value) => {
|
|
1022
|
+
const text = normalizeInputString(value);
|
|
1023
|
+
if (!text) {
|
|
1024
|
+
return '';
|
|
1025
|
+
}
|
|
1026
|
+
const namedMatch = text.match(/(?:chat[_-]?id|session[_-]?id|conversation[_-]?id)\s*[:=]\s*["']?([a-zA-Z0-9._:-]{8,})["']?/i);
|
|
1027
|
+
if (namedMatch?.[1]) {
|
|
1028
|
+
return namedMatch[1].trim();
|
|
1029
|
+
}
|
|
1030
|
+
return '';
|
|
1031
|
+
};
|
|
757
1032
|
const readDirect = (node) => {
|
|
758
1033
|
if (!node) {
|
|
759
1034
|
return '';
|
|
@@ -764,27 +1039,122 @@ function extractChatIdFromCreatePayload(payload) {
|
|
|
764
1039
|
normalizeInputString(node.session_id) ||
|
|
765
1040
|
normalizeInputString(node.sessionId) ||
|
|
766
1041
|
normalizeInputString(node.conversation_id) ||
|
|
767
|
-
normalizeInputString(node.conversationId)
|
|
1042
|
+
normalizeInputString(node.conversationId) ||
|
|
1043
|
+
readChatIdFromText(node.details) ||
|
|
1044
|
+
readChatIdFromText(node.message) ||
|
|
1045
|
+
readChatIdFromText(node.msg));
|
|
768
1046
|
};
|
|
769
1047
|
const root = payload;
|
|
770
1048
|
const dataNode = isRecord(root.data) ? root.data : undefined;
|
|
771
1049
|
const resultNode = isRecord(root.result) ? root.result : undefined;
|
|
772
1050
|
const chatNode = isRecord(dataNode?.chat) ? dataNode.chat : undefined;
|
|
773
|
-
|
|
1051
|
+
const dataDetailsNode = isRecord(dataNode?.details) ? dataNode.details : undefined;
|
|
1052
|
+
const resultDetailsNode = isRecord(resultNode?.details) ? resultNode.details : undefined;
|
|
1053
|
+
const rootDetailsNode = isRecord(root.details) ? root.details : undefined;
|
|
1054
|
+
const nestedDataNode = isRecord(dataNode?.data) ? dataNode.data : undefined;
|
|
1055
|
+
const nestedResultNode = isRecord(dataNode?.result) ? dataNode.result : undefined;
|
|
1056
|
+
const conversationNode = isRecord(dataNode?.conversation) ? dataNode.conversation : undefined;
|
|
1057
|
+
const sessionNode = isRecord(dataNode?.session) ? dataNode.session : undefined;
|
|
1058
|
+
return (readDirect(dataNode) ||
|
|
1059
|
+
readDirect(chatNode) ||
|
|
1060
|
+
readDirect(conversationNode) ||
|
|
1061
|
+
readDirect(sessionNode) ||
|
|
1062
|
+
readDirect(nestedDataNode) ||
|
|
1063
|
+
readDirect(nestedResultNode) ||
|
|
1064
|
+
readDirect(resultNode) ||
|
|
1065
|
+
readDirect(dataDetailsNode) ||
|
|
1066
|
+
readDirect(resultDetailsNode) ||
|
|
1067
|
+
readDirect(rootDetailsNode) ||
|
|
1068
|
+
readDirect(root));
|
|
774
1069
|
}
|
|
775
1070
|
function extractCreateErrorMessage(payload) {
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1071
|
+
const reason = extractQwenErrorMessage(payload);
|
|
1072
|
+
if (reason) {
|
|
1073
|
+
return reason;
|
|
1074
|
+
}
|
|
1075
|
+
const dataNode = isRecord(payload.data) ? payload.data : undefined;
|
|
1076
|
+
const detailsNode = isRecord(dataNode?.details) ? dataNode.details : undefined;
|
|
1077
|
+
const detailsParts = [
|
|
1078
|
+
detailsNode ? normalizeInputString(detailsNode.message) : '',
|
|
1079
|
+
detailsNode ? normalizeInputString(detailsNode.msg) : '',
|
|
1080
|
+
detailsNode ? normalizeInputString(detailsNode.details) : '',
|
|
1081
|
+
detailsNode ? normalizeInputString(detailsNode.template) : '',
|
|
1082
|
+
detailsNode ? normalizeInputString(detailsNode.code) : ''
|
|
781
1083
|
].filter(Boolean);
|
|
782
|
-
return
|
|
1084
|
+
return detailsParts[0] || '';
|
|
1085
|
+
}
|
|
1086
|
+
function inferCreateSessionStatusCode(payload, reason) {
|
|
1087
|
+
const dataNode = isRecord(payload.data) ? payload.data : undefined;
|
|
1088
|
+
const normalized = [
|
|
1089
|
+
normalizeInputString(payload.code),
|
|
1090
|
+
normalizeInputString(dataNode?.code),
|
|
1091
|
+
reason
|
|
1092
|
+
]
|
|
1093
|
+
.filter(Boolean)
|
|
1094
|
+
.join(' ')
|
|
1095
|
+
.toLowerCase();
|
|
1096
|
+
if (normalized.includes('ratelimit') ||
|
|
1097
|
+
normalized.includes('rate_limit') ||
|
|
1098
|
+
normalized.includes('daily usage limit') ||
|
|
1099
|
+
normalized.includes('too many request') ||
|
|
1100
|
+
normalized.includes('达到今日的使用上限') ||
|
|
1101
|
+
normalized.includes('请求过于频繁')) {
|
|
1102
|
+
return 429;
|
|
1103
|
+
}
|
|
1104
|
+
if (normalized.includes('forbidden') ||
|
|
1105
|
+
normalized.includes('permission denied') ||
|
|
1106
|
+
normalized.includes('没有权限') ||
|
|
1107
|
+
normalized.includes('无权限') ||
|
|
1108
|
+
normalized.includes('permission')) {
|
|
1109
|
+
return 403;
|
|
1110
|
+
}
|
|
1111
|
+
if (normalized.includes('unauthorized') ||
|
|
1112
|
+
normalized.includes('invalid token') ||
|
|
1113
|
+
normalized.includes('login') ||
|
|
1114
|
+
normalized.includes('auth') ||
|
|
1115
|
+
normalized.includes('未登录')) {
|
|
1116
|
+
return 401;
|
|
1117
|
+
}
|
|
1118
|
+
return 502;
|
|
1119
|
+
}
|
|
1120
|
+
function parseQwenUpstreamBusinessErrorFromRaw(rawPayload) {
|
|
1121
|
+
const trimmed = rawPayload.trim();
|
|
1122
|
+
if (!trimmed || trimmed.startsWith('data:')) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const parsed = JSON.parse(trimmed);
|
|
1127
|
+
if (!isRecord(parsed)) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
const success = parsed.success;
|
|
1131
|
+
const hasErrorNode = isRecord(parsed.error);
|
|
1132
|
+
const hasCode = Boolean(normalizeInputString(parsed.code));
|
|
1133
|
+
// Only treat as business rejection when payload explicitly signals failure.
|
|
1134
|
+
if (success !== false && !hasErrorNode && !hasCode) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
const reason = extractQwenErrorMessage(parsed) || normalizeInputString(parsed.message) || 'upstream rejected request';
|
|
1138
|
+
const statusCode = inferCreateSessionStatusCode(parsed, reason);
|
|
1139
|
+
const code = statusCode === 429 ? 'QWENCHAT_RATE_LIMITED' : 'QWENCHAT_COMPLETION_REJECTED';
|
|
1140
|
+
return {
|
|
1141
|
+
message: `QwenChat upstream rejected completion request: ${reason}`,
|
|
1142
|
+
statusCode,
|
|
1143
|
+
code
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
catch {
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
783
1149
|
}
|
|
784
1150
|
export async function createQwenChatSession(args) {
|
|
785
1151
|
const resp = await fetch(joinUrl(args.baseUrl, DEFAULT_QWENCHAT_CHAT_CREATE_ENDPOINT), {
|
|
786
1152
|
method: 'POST',
|
|
787
|
-
headers: qwenCommonHeaders(args.baxiaTokens, args.authHeaders
|
|
1153
|
+
headers: qwenCommonHeaders(args.baxiaTokens, args.authHeaders, {
|
|
1154
|
+
baseUrl: args.baseUrl,
|
|
1155
|
+
refererMode: 'guest',
|
|
1156
|
+
acceptMode: 'json'
|
|
1157
|
+
}),
|
|
788
1158
|
body: JSON.stringify({
|
|
789
1159
|
title: '新建对话',
|
|
790
1160
|
models: [args.model],
|
|
@@ -803,13 +1173,27 @@ export async function createQwenChatSession(args) {
|
|
|
803
1173
|
err.code = 'QWENCHAT_CREATE_SESSION_FAILED';
|
|
804
1174
|
throw err;
|
|
805
1175
|
}
|
|
1176
|
+
const chatId = extractChatIdFromCreatePayload(parsed.data);
|
|
806
1177
|
const success = parsed.data.success === true;
|
|
807
|
-
|
|
1178
|
+
if (chatId) {
|
|
1179
|
+
return chatId;
|
|
1180
|
+
}
|
|
1181
|
+
if (parsed.data.success === false) {
|
|
1182
|
+
const reason = extractCreateErrorMessage(parsed.data);
|
|
1183
|
+
const dataNode = isRecord(parsed.data.data) ? parsed.data.data : undefined;
|
|
1184
|
+
const dataKeys = dataNode ? Object.keys(dataNode).slice(0, 8).join(',') : '';
|
|
1185
|
+
const detail = [reason, dataKeys ? `keys=${dataKeys}` : ''].filter(Boolean).join(' ');
|
|
1186
|
+
const suffix = detail ? ` (${detail})` : '';
|
|
1187
|
+
const err = new Error(`Failed to create qwenchat session: upstream rejected request${suffix}`);
|
|
1188
|
+
err.statusCode = inferCreateSessionStatusCode(parsed.data, reason);
|
|
1189
|
+
err.code = 'QWENCHAT_CREATE_SESSION_REJECTED';
|
|
1190
|
+
throw err;
|
|
1191
|
+
}
|
|
808
1192
|
if (!chatId) {
|
|
809
1193
|
const reason = extractCreateErrorMessage(parsed.data);
|
|
810
1194
|
const dataNode = isRecord(parsed.data.data) ? parsed.data.data : undefined;
|
|
811
1195
|
const dataKeys = dataNode ? Object.keys(dataNode).slice(0, 8).join(',') : '';
|
|
812
|
-
const detail = reason || (dataKeys ? `keys=${dataKeys}` : '');
|
|
1196
|
+
const detail = reason || (dataKeys ? `keys=${dataKeys}` : '') || (success ? 'success=true' : '');
|
|
813
1197
|
const suffix = detail ? ` (${detail})` : '';
|
|
814
1198
|
const err = new Error(`Failed to create qwenchat session: missing chat id${suffix}`);
|
|
815
1199
|
err.statusCode = 502;
|
|
@@ -819,7 +1203,7 @@ export async function createQwenChatSession(args) {
|
|
|
819
1203
|
return chatId;
|
|
820
1204
|
}
|
|
821
1205
|
export function buildQwenChatCompletionRequest(args) {
|
|
822
|
-
|
|
1206
|
+
const request = {
|
|
823
1207
|
stream: true,
|
|
824
1208
|
version: '2.1',
|
|
825
1209
|
incremental_output: true,
|
|
@@ -855,6 +1239,10 @@ export function buildQwenChatCompletionRequest(args) {
|
|
|
855
1239
|
],
|
|
856
1240
|
timestamp: Date.now()
|
|
857
1241
|
};
|
|
1242
|
+
if (args.chatType === 't2i' && args.imageSize) {
|
|
1243
|
+
request.size = args.imageSize;
|
|
1244
|
+
}
|
|
1245
|
+
return request;
|
|
858
1246
|
}
|
|
859
1247
|
function extractReasoningContentFromDelta(delta) {
|
|
860
1248
|
const direct = normalizeInputString(delta.reasoning_content || delta.reasoning);
|
|
@@ -942,30 +1330,6 @@ function createOpenAiChunk(args) {
|
|
|
942
1330
|
}
|
|
943
1331
|
return chunk;
|
|
944
1332
|
}
|
|
945
|
-
function looksLikeToolMarkupNoise(text) {
|
|
946
|
-
const normalized = normalizeInputString(text);
|
|
947
|
-
if (!normalized) {
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
return (/<\s*\/?\s*function_calls\b/i.test(normalized) ||
|
|
951
|
-
/<\s*\/?\s*tool_call\b/i.test(normalized) ||
|
|
952
|
-
/<\s*\/?\s*tool_name\b/i.test(normalized) ||
|
|
953
|
-
/<\s*\/?\s*arg_key\b/i.test(normalized) ||
|
|
954
|
-
/<\s*\/?\s*arg_value\b/i.test(normalized) ||
|
|
955
|
-
/"tool_calls"\s*:/i.test(normalized));
|
|
956
|
-
}
|
|
957
|
-
function sanitizeDeltaForStreamEmit(delta) {
|
|
958
|
-
const next = { ...delta };
|
|
959
|
-
const content = normalizeInputString(next.content);
|
|
960
|
-
if (content && looksLikeToolMarkupNoise(content)) {
|
|
961
|
-
delete next.content;
|
|
962
|
-
}
|
|
963
|
-
const reasoning = normalizeInputString(next.reasoning_content);
|
|
964
|
-
if (reasoning && looksLikeToolMarkupNoise(reasoning)) {
|
|
965
|
-
delete next.reasoning_content;
|
|
966
|
-
}
|
|
967
|
-
return next;
|
|
968
|
-
}
|
|
969
1333
|
function processQwenSsePayloadLines(args) {
|
|
970
1334
|
for (const line of args.payload.split('\n')) {
|
|
971
1335
|
const trimmed = line.trimStart();
|
|
@@ -1001,8 +1365,15 @@ function processQwenSsePayloadLines(args) {
|
|
|
1001
1365
|
if (reasoning)
|
|
1002
1366
|
args.collect.reasoningParts.push(reasoning);
|
|
1003
1367
|
}
|
|
1368
|
+
if (args.collect && deltaNode) {
|
|
1369
|
+
const phase = normalizeInputString(deltaNode.phase);
|
|
1370
|
+
const rawContent = normalizeInputString(deltaNode.content);
|
|
1371
|
+
if (phase === 'image_gen' && /^https?:\/\//i.test(rawContent)) {
|
|
1372
|
+
args.collect.imageUrls.push(rawContent);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1004
1375
|
if (delta || finishReason || usage) {
|
|
1005
|
-
const deltaForEmit = delta ?
|
|
1376
|
+
const deltaForEmit = delta ? { ...delta } : {};
|
|
1006
1377
|
args.onChunk(createOpenAiChunk({
|
|
1007
1378
|
id: args.responseId,
|
|
1008
1379
|
created: args.created,
|
|
@@ -1026,6 +1397,7 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1026
1397
|
let buffer = '';
|
|
1027
1398
|
const contentParts = [];
|
|
1028
1399
|
const reasoningParts = [];
|
|
1400
|
+
const imageUrls = [];
|
|
1029
1401
|
const usageRef = {};
|
|
1030
1402
|
let lastUpstreamFinishReason = null;
|
|
1031
1403
|
const writeDone = () => {
|
|
@@ -1051,7 +1423,7 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1051
1423
|
lastUpstreamFinishReason = reason;
|
|
1052
1424
|
},
|
|
1053
1425
|
includeFinishReason: false,
|
|
1054
|
-
collect: { contentParts, reasoningParts, usageRef },
|
|
1426
|
+
collect: { contentParts, reasoningParts, imageUrls, usageRef },
|
|
1055
1427
|
responseId,
|
|
1056
1428
|
created,
|
|
1057
1429
|
model: input.model
|
|
@@ -1060,6 +1432,19 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1060
1432
|
});
|
|
1061
1433
|
input.upstreamStream.on('end', () => {
|
|
1062
1434
|
if (buffer.trim()) {
|
|
1435
|
+
const upstreamBusinessError = parseQwenUpstreamBusinessErrorFromRaw(buffer);
|
|
1436
|
+
if (upstreamBusinessError) {
|
|
1437
|
+
output.write(`data: ${JSON.stringify({
|
|
1438
|
+
error: {
|
|
1439
|
+
message: upstreamBusinessError.message,
|
|
1440
|
+
code: upstreamBusinessError.code,
|
|
1441
|
+
status: upstreamBusinessError.statusCode
|
|
1442
|
+
}
|
|
1443
|
+
})}\n\n`);
|
|
1444
|
+
writeDone();
|
|
1445
|
+
output.end();
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1063
1448
|
processQwenSsePayloadLines({
|
|
1064
1449
|
payload: buffer,
|
|
1065
1450
|
onChunk: (mappedChunk) => {
|
|
@@ -1072,13 +1457,17 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1072
1457
|
lastUpstreamFinishReason = reason;
|
|
1073
1458
|
},
|
|
1074
1459
|
includeFinishReason: false,
|
|
1075
|
-
collect: { contentParts, reasoningParts, usageRef },
|
|
1460
|
+
collect: { contentParts, reasoningParts, imageUrls, usageRef },
|
|
1076
1461
|
responseId,
|
|
1077
1462
|
created,
|
|
1078
1463
|
model: input.model
|
|
1079
1464
|
});
|
|
1080
1465
|
}
|
|
1081
|
-
const
|
|
1466
|
+
const dedupImageUrls = Array.from(new Set(imageUrls));
|
|
1467
|
+
const aggregatedContent = dedupImageUrls.length > 0
|
|
1468
|
+
? dedupImageUrls.join('\n')
|
|
1469
|
+
: contentParts.join('');
|
|
1470
|
+
const harvested = applyStandardToolTextHarvestToChatPayload({
|
|
1082
1471
|
id: responseId,
|
|
1083
1472
|
object: 'chat.completion',
|
|
1084
1473
|
created,
|
|
@@ -1089,7 +1478,7 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1089
1478
|
finish_reason: 'stop',
|
|
1090
1479
|
message: {
|
|
1091
1480
|
role: 'assistant',
|
|
1092
|
-
content:
|
|
1481
|
+
content: aggregatedContent,
|
|
1093
1482
|
...(reasoningParts.length ? { reasoning_content: reasoningParts.join('') } : {})
|
|
1094
1483
|
}
|
|
1095
1484
|
}
|
|
@@ -1132,6 +1521,7 @@ export function createOpenAiMappedSseStream(input) {
|
|
|
1132
1521
|
export async function collectQwenSseAsOpenAiResult(args) {
|
|
1133
1522
|
const contentParts = [];
|
|
1134
1523
|
const reasoningParts = [];
|
|
1524
|
+
const imageUrls = [];
|
|
1135
1525
|
const usageRef = {};
|
|
1136
1526
|
let buffer = '';
|
|
1137
1527
|
await new Promise((resolve, reject) => {
|
|
@@ -1141,6 +1531,13 @@ export async function collectQwenSseAsOpenAiResult(args) {
|
|
|
1141
1531
|
args.upstreamStream.on('end', resolve);
|
|
1142
1532
|
args.upstreamStream.on('error', reject);
|
|
1143
1533
|
});
|
|
1534
|
+
const upstreamBusinessError = parseQwenUpstreamBusinessErrorFromRaw(buffer);
|
|
1535
|
+
if (upstreamBusinessError) {
|
|
1536
|
+
const err = new Error(upstreamBusinessError.message);
|
|
1537
|
+
err.statusCode = upstreamBusinessError.statusCode;
|
|
1538
|
+
err.code = upstreamBusinessError.code;
|
|
1539
|
+
throw err;
|
|
1540
|
+
}
|
|
1144
1541
|
processQwenSsePayloadLines({
|
|
1145
1542
|
payload: buffer,
|
|
1146
1543
|
onChunk: () => {
|
|
@@ -1149,11 +1546,15 @@ export async function collectQwenSseAsOpenAiResult(args) {
|
|
|
1149
1546
|
onDone: () => {
|
|
1150
1547
|
// no-op in aggregate mode
|
|
1151
1548
|
},
|
|
1152
|
-
collect: { contentParts, reasoningParts, usageRef },
|
|
1549
|
+
collect: { contentParts, reasoningParts, imageUrls, usageRef },
|
|
1153
1550
|
responseId: `chatcmpl-${randomUUID()}`,
|
|
1154
1551
|
created: Math.floor(Date.now() / 1000),
|
|
1155
1552
|
model: args.model
|
|
1156
1553
|
});
|
|
1554
|
+
const dedupImageUrls = Array.from(new Set(imageUrls));
|
|
1555
|
+
const aggregatedContent = dedupImageUrls.length > 0
|
|
1556
|
+
? dedupImageUrls.join('\n')
|
|
1557
|
+
: contentParts.join('');
|
|
1157
1558
|
const result = {
|
|
1158
1559
|
id: `chatcmpl-${randomUUID()}`,
|
|
1159
1560
|
object: 'chat.completion',
|
|
@@ -1164,7 +1565,7 @@ export async function collectQwenSseAsOpenAiResult(args) {
|
|
|
1164
1565
|
index: 0,
|
|
1165
1566
|
message: {
|
|
1166
1567
|
role: 'assistant',
|
|
1167
|
-
content:
|
|
1568
|
+
content: aggregatedContent,
|
|
1168
1569
|
...(reasoningParts.length ? { reasoning_content: reasoningParts.join('') } : {})
|
|
1169
1570
|
},
|
|
1170
1571
|
finish_reason: 'stop'
|
|
@@ -1174,7 +1575,22 @@ export async function collectQwenSseAsOpenAiResult(args) {
|
|
|
1174
1575
|
if (usageRef.usage) {
|
|
1175
1576
|
result.usage = usageRef.usage;
|
|
1176
1577
|
}
|
|
1177
|
-
|
|
1578
|
+
const harvested = applyStandardToolTextHarvestToChatPayload(result);
|
|
1579
|
+
const firstChoice = Array.isArray(harvested.choices) && harvested.choices.length > 0 && isRecord(harvested.choices[0])
|
|
1580
|
+
? harvested.choices[0]
|
|
1581
|
+
: undefined;
|
|
1582
|
+
const messageNode = firstChoice && isRecord(firstChoice.message) ? firstChoice.message : undefined;
|
|
1583
|
+
const finishReason = normalizeInputString(firstChoice?.finish_reason) || 'stop';
|
|
1584
|
+
const content = normalizeInputString(messageNode?.content);
|
|
1585
|
+
const reasoning = normalizeInputString(messageNode?.reasoning_content);
|
|
1586
|
+
const toolCalls = messageNode && Array.isArray(messageNode.tool_calls) ? messageNode.tool_calls : [];
|
|
1587
|
+
if (finishReason === 'stop' && !content && !reasoning && toolCalls.length === 0) {
|
|
1588
|
+
const err = new Error('QwenChat upstream returned an empty assistant message');
|
|
1589
|
+
err.statusCode = 502;
|
|
1590
|
+
err.code = 'QWENCHAT_EMPTY_ASSISTANT';
|
|
1591
|
+
throw err;
|
|
1592
|
+
}
|
|
1593
|
+
return harvested;
|
|
1178
1594
|
}
|
|
1179
1595
|
export async function buildQwenChatSendPlan(input) {
|
|
1180
1596
|
const normalizedPayload = applyStandardToolTextRequest(input.payload);
|
|
@@ -1184,7 +1600,12 @@ export async function buildQwenChatSendPlan(input) {
|
|
|
1184
1600
|
err.code = 'QWENCHAT_INVALID_REQUEST';
|
|
1185
1601
|
throw err;
|
|
1186
1602
|
}
|
|
1187
|
-
const
|
|
1603
|
+
const imageGenOptions = parseQwenImageGenerationOptions(normalizedPayload.metadata);
|
|
1604
|
+
const chatType = shouldUseImageGenerationMode(normalizedPayload)
|
|
1605
|
+
? 't2i'
|
|
1606
|
+
: shouldUseSearchMode(normalizedPayload)
|
|
1607
|
+
? 'search'
|
|
1608
|
+
: 't2t';
|
|
1188
1609
|
const chatId = await createQwenChatSession({
|
|
1189
1610
|
baseUrl: input.baseUrl,
|
|
1190
1611
|
model: normalizedPayload.model,
|
|
@@ -1193,6 +1614,9 @@ export async function buildQwenChatSendPlan(input) {
|
|
|
1193
1614
|
authHeaders: input.authHeaders
|
|
1194
1615
|
});
|
|
1195
1616
|
const parsedMessages = parseIncomingMessages(normalizedPayload.messages);
|
|
1617
|
+
const finalContent = chatType === 't2i' && imageGenOptions.count > 1
|
|
1618
|
+
? `${parsedMessages.content}\n\n(Generate ${imageGenOptions.count} images.)`
|
|
1619
|
+
: parsedMessages.content;
|
|
1196
1620
|
const uploaded = parsedMessages.attachments.length
|
|
1197
1621
|
? await uploadAttachments({
|
|
1198
1622
|
baseUrl: input.baseUrl,
|
|
@@ -1204,14 +1628,18 @@ export async function buildQwenChatSendPlan(input) {
|
|
|
1204
1628
|
const completionBody = buildQwenChatCompletionRequest({
|
|
1205
1629
|
chatId,
|
|
1206
1630
|
model: normalizedPayload.model,
|
|
1207
|
-
content:
|
|
1631
|
+
content: finalContent,
|
|
1208
1632
|
uploadedFiles: uploaded.files,
|
|
1209
|
-
chatType
|
|
1633
|
+
chatType,
|
|
1634
|
+
...(chatType === 't2i' ? { imageSize: imageGenOptions.sizeRatio } : {})
|
|
1210
1635
|
});
|
|
1211
1636
|
const completionHeaders = {
|
|
1212
|
-
...qwenCommonHeaders(input.baxiaTokens, input.authHeaders
|
|
1213
|
-
|
|
1214
|
-
|
|
1637
|
+
...qwenCommonHeaders(input.baxiaTokens, input.authHeaders, {
|
|
1638
|
+
baseUrl: input.baseUrl,
|
|
1639
|
+
refererMode: 'guest',
|
|
1640
|
+
acceptMode: 'json'
|
|
1641
|
+
}),
|
|
1642
|
+
version: '0.2.9'
|
|
1215
1643
|
};
|
|
1216
1644
|
return {
|
|
1217
1645
|
completionUrl: `${joinUrl(input.baseUrl, DEFAULT_QWENCHAT_COMPLETION_ENDPOINT)}?chat_id=${encodeURIComponent(chatId)}`,
|
|
@@ -1223,29 +1651,27 @@ function applyStandardToolTextRequest(payload) {
|
|
|
1223
1651
|
if (!Array.isArray(payload.tools) || payload.tools.length === 0) {
|
|
1224
1652
|
return payload;
|
|
1225
1653
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
...payload,
|
|
1242
|
-
messages: [{ role: 'user', content: prompt }],
|
|
1243
|
-
tools: undefined
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
catch {
|
|
1247
|
-
return payload;
|
|
1654
|
+
const transformed = applyStandardToolTextRequestTransform({
|
|
1655
|
+
model: payload.model,
|
|
1656
|
+
messages: payload.messages,
|
|
1657
|
+
tools: payload.tools,
|
|
1658
|
+
metadata: payload.metadata
|
|
1659
|
+
}, {
|
|
1660
|
+
providerProtocol: 'openai-chat',
|
|
1661
|
+
entryEndpoint: '/v1/chat/completions'
|
|
1662
|
+
});
|
|
1663
|
+
const prompt = normalizeInputString(transformed.prompt);
|
|
1664
|
+
if (!prompt) {
|
|
1665
|
+
const err = new Error('QwenChat tool-text transform failed: prompt is empty');
|
|
1666
|
+
err.statusCode = 422;
|
|
1667
|
+
err.code = 'QWENCHAT_TOOL_TEXT_TRANSFORM_FAILED';
|
|
1668
|
+
throw err;
|
|
1248
1669
|
}
|
|
1670
|
+
return {
|
|
1671
|
+
...payload,
|
|
1672
|
+
messages: [{ role: 'user', content: prompt }],
|
|
1673
|
+
tools: undefined
|
|
1674
|
+
};
|
|
1249
1675
|
}
|
|
1250
1676
|
export function extractForwardAuthHeaders(headers) {
|
|
1251
1677
|
const out = {};
|
|
@@ -1269,13 +1695,4 @@ export function classifyQwenChatProviderIdentity(input) {
|
|
|
1269
1695
|
value.includes('qwenchat') ||
|
|
1270
1696
|
value === 'chat:qwenchat-web');
|
|
1271
1697
|
}
|
|
1272
|
-
function applyStandardizedTextHarvestToChatPayload(payload) {
|
|
1273
|
-
try {
|
|
1274
|
-
const harvested = processChatResponseTools(payload);
|
|
1275
|
-
return isRecord(harvested) ? harvested : payload;
|
|
1276
|
-
}
|
|
1277
|
-
catch {
|
|
1278
|
-
return payload;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
1698
|
//# sourceMappingURL=qwenchat-http-provider-helpers.js.map
|