mslxdff 0.1.173 → 0.1.175

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/bin/mslxdff.js +3 -3
  2. package/package.json +1 -1
  3. package/src/auto.js +254 -254
  4. package/src/bench/cline-bench.js +42 -42
  5. package/src/bench/probe.js +70 -70
  6. package/src/bench/report.js +162 -162
  7. package/src/bench/runner.js +77 -77
  8. package/src/bench/via-probe.js +124 -124
  9. package/src/bench/via-routes.js +87 -87
  10. package/src/bench/workbuddy-bench.js +54 -54
  11. package/src/chat/engine.js +160 -160
  12. package/src/chat/gateway.js +163 -163
  13. package/src/chat/orchestrator.js +234 -234
  14. package/src/chat/prompt.js +70 -70
  15. package/src/chat/repl.js +88 -88
  16. package/src/chat/terminal.js +135 -135
  17. package/src/chat/tools.js +306 -306
  18. package/src/chat-pipeline/auto-race.js +26 -1
  19. package/src/chat-pipeline/index.js +131 -131
  20. package/src/chat-pipeline/policy.js +84 -76
  21. package/src/chat-pipeline/serial-trial.js +35 -8
  22. package/src/chat-pipeline/terminal-verdict.js +109 -0
  23. package/src/claude-desktop/apply.js +42 -5
  24. package/src/claude-desktop/context1m.js +38 -0
  25. package/src/claude-desktop/slots.js +66 -0
  26. package/src/cli/commands/claude-desktop.js +27 -7
  27. package/src/cli/commands/group.js +249 -249
  28. package/src/cli/commands/model/picks.js +50 -50
  29. package/src/cli/commands/provider/bench-via.js +247 -247
  30. package/src/cli/commands/provider/bench.js +141 -141
  31. package/src/cli/commands/provider/index.js +136 -133
  32. package/src/cli/commands/provider/keys-guard.js +50 -0
  33. package/src/cli/commands/provider/keys.js +7 -0
  34. package/src/cli/commands/provider/models.js +145 -145
  35. package/src/cli/commands/provider/qwenwork-login.js +119 -119
  36. package/src/cli/commands/provider/traework-checkin.js +144 -0
  37. package/src/cli/commands/provider/traework-login.js +7 -45
  38. package/src/cli/commands/sync.js +310 -310
  39. package/src/cli/help.js +4 -1
  40. package/src/cli/status.js +279 -279
  41. package/src/daemon.js +112 -112
  42. package/src/model-capabilities/enrich.js +86 -86
  43. package/src/model-capabilities/index.js +183 -183
  44. package/src/model-capabilities/parse.js +70 -70
  45. package/src/model-trace.js +2 -0
  46. package/src/models.js +225 -225
  47. package/src/opencode-identity.js +39 -19
  48. package/src/providers/cline/auth.js +228 -228
  49. package/src/providers/cline/chat.js +307 -307
  50. package/src/providers/cline.js +2 -2
  51. package/src/providers/codearts/chat.js +7 -1
  52. package/src/providers/codearts/const.js +6 -1
  53. package/src/providers/codearts/index.js +18 -11
  54. package/src/providers/globalqwenwork/cosy.js +123 -123
  55. package/src/providers/globalqwenwork/crypto.js +218 -218
  56. package/src/providers/globalqwenwork/http.js +20 -20
  57. package/src/providers/globalqwenwork/rsa.js +54 -54
  58. package/src/providers/globalqwenwork/sse.js +268 -268
  59. package/src/providers/globalqwenwork/stream.js +130 -130
  60. package/src/providers/globalqwenwork/upstream.js +122 -122
  61. package/src/providers/keyring.js +60 -60
  62. package/src/providers/qoder/chat.js +203 -203
  63. package/src/providers/qoder/index.js +249 -249
  64. package/src/providers/qoder/sse.js +145 -145
  65. package/src/providers/qwenwork/account-store.js +133 -133
  66. package/src/providers/qwenwork/constants.js +67 -67
  67. package/src/providers/qwenwork/cosy.js +120 -120
  68. package/src/providers/qwenwork/crypto.js +218 -218
  69. package/src/providers/qwenwork/http.js +20 -20
  70. package/src/providers/qwenwork/index.js +327 -327
  71. package/src/providers/qwenwork/payload.js +144 -144
  72. package/src/providers/qwenwork/rsa.js +54 -54
  73. package/src/providers/qwenwork/sse.js +268 -268
  74. package/src/providers/qwenwork/stream.js +130 -130
  75. package/src/providers/qwenwork/upstream.js +120 -120
  76. package/src/providers/qwenwork.js +1 -1
  77. package/src/providers/registry.js +79 -79
  78. package/src/providers/traework/account-store.js +87 -2
  79. package/src/providers/traework/checkin.js +51 -9
  80. package/src/providers/traework/constants.js +20 -0
  81. package/src/providers/traework/headers.js +42 -10
  82. package/src/providers/traework/index.js +14 -4
  83. package/src/providers/traework/market-identity.js +116 -0
  84. package/src/providers/workbuddy/chat.js +248 -248
  85. package/src/providers/workbuddy/reshape.js +152 -152
  86. package/src/providers/workbuddy.js +2 -2
  87. package/src/reasoning.js +32 -32
  88. package/src/routes/chat/gateway.js +46 -46
  89. package/src/routes/chat/relay-pipeline.js +294 -294
  90. package/src/routes/chat/via-route-handler.js +146 -146
  91. package/src/routes/hedge.js +255 -255
  92. package/src/routes/models-route.js +167 -167
  93. package/src/routes/peers.js +273 -273
  94. package/src/routes/stream.js +385 -385
  95. package/src/runtime/bootstrap.js +50 -50
  96. package/src/runtime/provider-gate.js +36 -36
  97. package/src/runtime/providers-setup.js +168 -165
  98. package/src/runtime/traework-checkin.js +26 -11
  99. package/src/server.js +69 -69
  100. package/src/state/schemas/allowlist.js +92 -92
  101. package/src/sync-opencode.js +280 -280
  102. package/src/transport/index.js +244 -244
  103. package/src/transport/pool.js +56 -56
  104. package/src/transport/retry.js +24 -24
  105. package/src/transport/sse.js +93 -93
  106. package/src/upstream-probe/display.js +52 -52
  107. package/src/upstream-probe/probe.js +49 -49
  108. package/src/upstream-probe/rotate.js +110 -110
  109. package/src/upstream-probe/start.js +45 -45
  110. package/src/upstream.js +291 -289
  111. package/src/warp-pool/config.js +71 -0
  112. package/src/warp-pool/health.js +65 -0
  113. package/src/warp-pool/index.js +252 -0
  114. package/src/warp-pool/keys.js +29 -0
  115. package/src/warp-pool/nodes.js +94 -0
  116. package/src/warp-pool/register.js +113 -0
  117. package/src/warp-pool/rotate.js +150 -0
  118. package/src/warp-pool/strategy.js +43 -0
  119. package/src/warp-pool/wireproxy.js +153 -0
  120. package/docs/cli_help_mini.md +0 -143
  121. package/src/providers/AGENTS.md +0 -49
  122. package/src/routes/AGENTS.md +0 -37
@@ -1,130 +1,130 @@
1
- // vendor 自 _shared/sse.js 的 transform(真流式:上游 SSE → OpenAI chunk,边收边吐)。
2
- // 唯一一处**有意偏离**原作者实现:把 chunk 里的 `model` 回写成客户端请求的模型名。
3
- // 上游实际返回的是池名(实测 `qwork-openai-chat-mode-pool`),直接透传会让客户端按模型名匹配失败,
4
- // 违反本仓契约 1「model 原样透传」。原作者的非流式路径同样做了 `completion.model = wantModel`。
5
- import { handleLine } from "./sse.js";
6
-
7
- function rewriteModel(frameText, model) {
8
- if (!model) return frameText;
9
- const body = frameText.slice(6, -2);
10
- let obj;
11
- try {
12
- obj = JSON.parse(body);
13
- } catch {
14
- return frameText;
15
- }
16
- if (!obj || typeof obj !== "object") return frameText;
17
- obj.model = model;
18
- return `data: ${JSON.stringify(obj)}\n\n`;
19
- }
20
-
21
- export function transformStream(upstreamBody, model, onUsage, { heartbeatMs = 15000 } = {}) {
22
- const reader = upstreamBody.getReader();
23
- const decoder = new TextDecoder();
24
- const encoder = new TextEncoder();
25
- let buffer = "";
26
- let notified = false;
27
- let heartbeat = null;
28
- const state = { usage: null, error: null };
29
-
30
- const notify = () => {
31
- if (notified) return;
32
- notified = true;
33
- if (onUsage) onUsage(state.usage);
34
- };
35
-
36
- const emitError = (controller, message) => {
37
- try {
38
- controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: { message, type: "upstream_error" } })}\n\n`));
39
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
40
- controller.close();
41
- } catch {
42
- /* 客户端已断开 */
43
- }
44
- };
45
-
46
- const stopHeartbeat = () => {
47
- if (heartbeat) {
48
- clearInterval(heartbeat);
49
- heartbeat = null;
50
- }
51
- };
52
-
53
- const finish = () => {
54
- stopHeartbeat();
55
- notify();
56
- };
57
-
58
- return new ReadableStream({
59
- start(controller) {
60
- if (heartbeatMs > 0) {
61
- heartbeat = setInterval(() => {
62
- try {
63
- controller.enqueue(encoder.encode(": ping\n\n"));
64
- } catch {
65
- /* 已关闭 */
66
- }
67
- }, heartbeatMs);
68
- if (heartbeat.unref) heartbeat.unref();
69
- }
70
- },
71
- async pull(controller) {
72
- try {
73
- for (;;) {
74
- let read;
75
- try {
76
- read = await reader.read();
77
- } catch (err) {
78
- const message = `upstream stream read error: ${err && err.message ? err.message : err}`;
79
- stopHeartbeat();
80
- emitError(controller, message);
81
- notify();
82
- return;
83
- }
84
- if (read.done) {
85
- stopHeartbeat();
86
- if (buffer) {
87
- const out = handleLine(buffer, state);
88
- if (out) controller.enqueue(encoder.encode(rewriteModel(out, model)));
89
- buffer = "";
90
- }
91
- if (state.error) emitError(controller, state.error);
92
- else {
93
- try {
94
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
95
- controller.close();
96
- } catch {
97
- /* 客户端已断开 */
98
- }
99
- }
100
- finish();
101
- return;
102
- }
103
- buffer += decoder.decode(read.value, { stream: true });
104
- const lines = buffer.split("\n");
105
- buffer = lines.pop() || "";
106
- let out = "";
107
- for (const line of lines) {
108
- const frame = handleLine(line, state);
109
- if (frame) out += rewriteModel(frame, model);
110
- }
111
- if (out) {
112
- controller.enqueue(encoder.encode(out));
113
- return;
114
- }
115
- }
116
- } catch (err) {
117
- finish();
118
- try {
119
- emitError(controller, `stream error: ${err && err.message ? err.message : err}`);
120
- } catch {
121
- /* 已关闭 */
122
- }
123
- }
124
- },
125
- cancel(reason) {
126
- finish();
127
- if (reader.cancel) reader.cancel(reason);
128
- },
129
- });
130
- }
1
+ // vendor 自 _shared/sse.js 的 transform(真流式:上游 SSE → OpenAI chunk,边收边吐)。
2
+ // 唯一一处**有意偏离**原作者实现:把 chunk 里的 `model` 回写成客户端请求的模型名。
3
+ // 上游实际返回的是池名(实测 `qwork-openai-chat-mode-pool`),直接透传会让客户端按模型名匹配失败,
4
+ // 违反本仓契约 1「model 原样透传」。原作者的非流式路径同样做了 `completion.model = wantModel`。
5
+ import { handleLine } from "./sse.js";
6
+
7
+ function rewriteModel(frameText, model) {
8
+ if (!model) return frameText;
9
+ const body = frameText.slice(6, -2);
10
+ let obj;
11
+ try {
12
+ obj = JSON.parse(body);
13
+ } catch {
14
+ return frameText;
15
+ }
16
+ if (!obj || typeof obj !== "object") return frameText;
17
+ obj.model = model;
18
+ return `data: ${JSON.stringify(obj)}\n\n`;
19
+ }
20
+
21
+ export function transformStream(upstreamBody, model, onUsage, { heartbeatMs = 15000 } = {}) {
22
+ const reader = upstreamBody.getReader();
23
+ const decoder = new TextDecoder();
24
+ const encoder = new TextEncoder();
25
+ let buffer = "";
26
+ let notified = false;
27
+ let heartbeat = null;
28
+ const state = { usage: null, error: null };
29
+
30
+ const notify = () => {
31
+ if (notified) return;
32
+ notified = true;
33
+ if (onUsage) onUsage(state.usage);
34
+ };
35
+
36
+ const emitError = (controller, message) => {
37
+ try {
38
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: { message, type: "upstream_error" } })}\n\n`));
39
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
40
+ controller.close();
41
+ } catch {
42
+ /* 客户端已断开 */
43
+ }
44
+ };
45
+
46
+ const stopHeartbeat = () => {
47
+ if (heartbeat) {
48
+ clearInterval(heartbeat);
49
+ heartbeat = null;
50
+ }
51
+ };
52
+
53
+ const finish = () => {
54
+ stopHeartbeat();
55
+ notify();
56
+ };
57
+
58
+ return new ReadableStream({
59
+ start(controller) {
60
+ if (heartbeatMs > 0) {
61
+ heartbeat = setInterval(() => {
62
+ try {
63
+ controller.enqueue(encoder.encode(": ping\n\n"));
64
+ } catch {
65
+ /* 已关闭 */
66
+ }
67
+ }, heartbeatMs);
68
+ if (heartbeat.unref) heartbeat.unref();
69
+ }
70
+ },
71
+ async pull(controller) {
72
+ try {
73
+ for (;;) {
74
+ let read;
75
+ try {
76
+ read = await reader.read();
77
+ } catch (err) {
78
+ const message = `upstream stream read error: ${err && err.message ? err.message : err}`;
79
+ stopHeartbeat();
80
+ emitError(controller, message);
81
+ notify();
82
+ return;
83
+ }
84
+ if (read.done) {
85
+ stopHeartbeat();
86
+ if (buffer) {
87
+ const out = handleLine(buffer, state);
88
+ if (out) controller.enqueue(encoder.encode(rewriteModel(out, model)));
89
+ buffer = "";
90
+ }
91
+ if (state.error) emitError(controller, state.error);
92
+ else {
93
+ try {
94
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
95
+ controller.close();
96
+ } catch {
97
+ /* 客户端已断开 */
98
+ }
99
+ }
100
+ finish();
101
+ return;
102
+ }
103
+ buffer += decoder.decode(read.value, { stream: true });
104
+ const lines = buffer.split("\n");
105
+ buffer = lines.pop() || "";
106
+ let out = "";
107
+ for (const line of lines) {
108
+ const frame = handleLine(line, state);
109
+ if (frame) out += rewriteModel(frame, model);
110
+ }
111
+ if (out) {
112
+ controller.enqueue(encoder.encode(out));
113
+ return;
114
+ }
115
+ }
116
+ } catch (err) {
117
+ finish();
118
+ try {
119
+ emitError(controller, `stream error: ${err && err.message ? err.message : err}`);
120
+ } catch {
121
+ /* 已关闭 */
122
+ }
123
+ }
124
+ },
125
+ cancel(reason) {
126
+ finish();
127
+ if (reader.cancel) reader.cancel(reason);
128
+ },
129
+ });
130
+ }
@@ -1,120 +1,120 @@
1
- // vendor 自 _shared/upstream.js(签名请求装配 + OAuth 设备流),逐字搬;
2
- // 唯一改动:fetch 实现改为注入(默认本仓统一的 compatFetch,走 undici 连接池,勿混用 globalThis.fetch)。
3
- import { compatFetch } from "../../compat.js";
4
- import { uuid, b64url, utf8 } from "./crypto.js";
5
- import { signHeaders } from "./cosy.js";
6
- import {
7
- UA, CLIENT_ID, REDIRECT_URI, DEVICE_AUTH_URL, CHAT_URL, MODELS_URL,
8
- USERINFO_URL, ACCOUNT_CONTEXT_URL, POLL_URL, REFRESH_URL, MODEL_SLICE,
9
- } from "./constants.js";
10
-
11
- function plainHeaders() {
12
- return {
13
- accept: "application/json, text/plain, */*",
14
- "content-type": "application/json",
15
- "user-agent": UA,
16
- };
17
- }
18
-
19
- export function chatRequest(account, modelKey, bodyStr, signal, fetchImpl = compatFetch) {
20
- const headers = signHeaders(account, bodyStr, CHAT_URL, modelKey, "text/event-stream");
21
- return fetchImpl(CHAT_URL, { method: "POST", headers, body: bodyStr, signal });
22
- }
23
-
24
- export async function modelList(account, fetchImpl = compatFetch) {
25
- const headers = signHeaders(account, "", MODELS_URL, "", "application/json");
26
- const res = await fetchImpl(MODELS_URL, { headers });
27
- if (!res.ok) throw new Error(`models http ${res.status}`);
28
- const data = await res.json();
29
- // 实测 11 个根切片只有 `qwork` 非空(其余 n=0);切片名见 constants.MODEL_SLICE
30
- const list = Array.isArray(data && data[MODEL_SLICE]) ? data[MODEL_SLICE] : [];
31
- return list
32
- .filter((m) => m && m.enable !== false && m.key)
33
- .map((m) => ({
34
- key: m.key,
35
- name: m.display_name || m.key,
36
- isReasoning: !!m.is_reasoning,
37
- isVL: !!m.is_vl,
38
- maxInputTokens: m.max_input_tokens || 0,
39
- maxOutputTokens: m.max_output_tokens || 0,
40
- priceFactor: typeof m.price_factor === "number" ? m.price_factor : 0,
41
- description: typeof m.description === "string" ? m.description : "",
42
- }));
43
- }
44
-
45
- export async function userInfo(accessToken, fetchImpl = compatFetch) {
46
- const res = await fetchImpl(USERINFO_URL, {
47
- headers: { ...plainHeaders(), authorization: `Bearer ${accessToken}` },
48
- });
49
- if (!res.ok) throw new Error(`userinfo http ${res.status}`);
50
- return res.json();
51
- }
52
-
53
- /** 额度/套餐:data.{user,plan,quota};quota.remaining 即积分余量。 */
54
- export async function accountContext(accessToken, fetchImpl = compatFetch) {
55
- const res = await fetchImpl(ACCOUNT_CONTEXT_URL, {
56
- headers: { ...plainHeaders(), authorization: `Bearer ${accessToken}` },
57
- });
58
- if (!res.ok) throw new Error(`account-context http ${res.status}`);
59
- const body = await res.json();
60
- if (!body || (body.code && body.code !== "ok")) throw new Error(`account-context code=${body && body.code}`);
61
- return (body && body.data) || {};
62
- }
63
-
64
- export async function refreshDeviceToken(refreshToken, fetchImpl = compatFetch) {
65
- const res = await fetchImpl(REFRESH_URL, {
66
- method: "POST",
67
- headers: plainHeaders(),
68
- body: JSON.stringify({ refresh_token: refreshToken }),
69
- });
70
- if (!res.ok) throw new Error(`refresh http ${res.status}`);
71
- const data = await res.json();
72
- const access = data && (data.token || data.device_token);
73
- if (!access || !data.refresh_token) throw new Error("refresh: incomplete token pair");
74
- return data;
75
- }
76
-
77
- export async function pollGrant(nonce, verifier, fetchImpl = compatFetch) {
78
- const q = new URLSearchParams({ nonce, verifier, challenge_method: "S256" });
79
- const res = await fetchImpl(`${POLL_URL}?${q.toString()}`, {
80
- headers: { accept: "application/json", "user-agent": UA },
81
- });
82
- if (res.status === 404 || res.status === 202) return { pending: true };
83
- if (!res.ok) throw new Error(`poll http ${res.status}`);
84
- const data = await res.json();
85
- if (!data || !(data.token || data.device_token)) return { pending: true };
86
- return { pending: false, grant: data };
87
- }
88
-
89
- export async function newDeviceFlow() {
90
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
91
- const raw = new Uint8Array(64);
92
- crypto.getRandomValues(raw);
93
- let verifier = "";
94
- for (let i = 0; i < raw.length; i++) verifier += alphabet[raw[i] % alphabet.length];
95
- const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", utf8(verifier)));
96
- const challenge = b64url(digest);
97
- const nonce = uuid();
98
- const machineID = uuid();
99
- const q = new URLSearchParams({
100
- challenge,
101
- challenge_method: "S256",
102
- nonce,
103
- machine_id: machineID,
104
- client_id: CLIENT_ID,
105
- redirect_uri: REDIRECT_URI,
106
- });
107
- return { verifier, nonce, url: `${DEVICE_AUTH_URL}?${q.toString()}` };
108
- }
109
-
110
- export function expiryUnix(grant) {
111
- if (grant && grant.expires_at) {
112
- const t = Date.parse(grant.expires_at);
113
- if (!Number.isNaN(t)) return Math.floor(t / 1000);
114
- }
115
- if (grant && grant.expires_in > 0) {
116
- const ms = grant.expires_in > 10000000 ? grant.expires_in : grant.expires_in * 1000;
117
- return Math.floor((Date.now() + ms) / 1000);
118
- }
119
- return Math.floor((Date.now() + 30 * 24 * 3600 * 1000) / 1000);
120
- }
1
+ // vendor 自 _shared/upstream.js(签名请求装配 + OAuth 设备流),逐字搬;
2
+ // 唯一改动:fetch 实现改为注入(默认本仓统一的 compatFetch,走 undici 连接池,勿混用 globalThis.fetch)。
3
+ import { compatFetch } from "../../compat.js";
4
+ import { uuid, b64url, utf8 } from "./crypto.js";
5
+ import { signHeaders } from "./cosy.js";
6
+ import {
7
+ UA, CLIENT_ID, REDIRECT_URI, DEVICE_AUTH_URL, CHAT_URL, MODELS_URL,
8
+ USERINFO_URL, ACCOUNT_CONTEXT_URL, POLL_URL, REFRESH_URL, MODEL_SLICE,
9
+ } from "./constants.js";
10
+
11
+ function plainHeaders() {
12
+ return {
13
+ accept: "application/json, text/plain, */*",
14
+ "content-type": "application/json",
15
+ "user-agent": UA,
16
+ };
17
+ }
18
+
19
+ export function chatRequest(account, modelKey, bodyStr, signal, fetchImpl = compatFetch) {
20
+ const headers = signHeaders(account, bodyStr, CHAT_URL, modelKey, "text/event-stream");
21
+ return fetchImpl(CHAT_URL, { method: "POST", headers, body: bodyStr, signal });
22
+ }
23
+
24
+ export async function modelList(account, fetchImpl = compatFetch) {
25
+ const headers = signHeaders(account, "", MODELS_URL, "", "application/json");
26
+ const res = await fetchImpl(MODELS_URL, { headers });
27
+ if (!res.ok) throw new Error(`models http ${res.status}`);
28
+ const data = await res.json();
29
+ // 实测 11 个根切片只有 `qwork` 非空(其余 n=0);切片名见 constants.MODEL_SLICE
30
+ const list = Array.isArray(data && data[MODEL_SLICE]) ? data[MODEL_SLICE] : [];
31
+ return list
32
+ .filter((m) => m && m.enable !== false && m.key)
33
+ .map((m) => ({
34
+ key: m.key,
35
+ name: m.display_name || m.key,
36
+ isReasoning: !!m.is_reasoning,
37
+ isVL: !!m.is_vl,
38
+ maxInputTokens: m.max_input_tokens || 0,
39
+ maxOutputTokens: m.max_output_tokens || 0,
40
+ priceFactor: typeof m.price_factor === "number" ? m.price_factor : 0,
41
+ description: typeof m.description === "string" ? m.description : "",
42
+ }));
43
+ }
44
+
45
+ export async function userInfo(accessToken, fetchImpl = compatFetch) {
46
+ const res = await fetchImpl(USERINFO_URL, {
47
+ headers: { ...plainHeaders(), authorization: `Bearer ${accessToken}` },
48
+ });
49
+ if (!res.ok) throw new Error(`userinfo http ${res.status}`);
50
+ return res.json();
51
+ }
52
+
53
+ /** 额度/套餐:data.{user,plan,quota};quota.remaining 即积分余量。 */
54
+ export async function accountContext(accessToken, fetchImpl = compatFetch) {
55
+ const res = await fetchImpl(ACCOUNT_CONTEXT_URL, {
56
+ headers: { ...plainHeaders(), authorization: `Bearer ${accessToken}` },
57
+ });
58
+ if (!res.ok) throw new Error(`account-context http ${res.status}`);
59
+ const body = await res.json();
60
+ if (!body || (body.code && body.code !== "ok")) throw new Error(`account-context code=${body && body.code}`);
61
+ return (body && body.data) || {};
62
+ }
63
+
64
+ export async function refreshDeviceToken(refreshToken, fetchImpl = compatFetch) {
65
+ const res = await fetchImpl(REFRESH_URL, {
66
+ method: "POST",
67
+ headers: plainHeaders(),
68
+ body: JSON.stringify({ refresh_token: refreshToken }),
69
+ });
70
+ if (!res.ok) throw new Error(`refresh http ${res.status}`);
71
+ const data = await res.json();
72
+ const access = data && (data.token || data.device_token);
73
+ if (!access || !data.refresh_token) throw new Error("refresh: incomplete token pair");
74
+ return data;
75
+ }
76
+
77
+ export async function pollGrant(nonce, verifier, fetchImpl = compatFetch) {
78
+ const q = new URLSearchParams({ nonce, verifier, challenge_method: "S256" });
79
+ const res = await fetchImpl(`${POLL_URL}?${q.toString()}`, {
80
+ headers: { accept: "application/json", "user-agent": UA },
81
+ });
82
+ if (res.status === 404 || res.status === 202) return { pending: true };
83
+ if (!res.ok) throw new Error(`poll http ${res.status}`);
84
+ const data = await res.json();
85
+ if (!data || !(data.token || data.device_token)) return { pending: true };
86
+ return { pending: false, grant: data };
87
+ }
88
+
89
+ export async function newDeviceFlow() {
90
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
91
+ const raw = new Uint8Array(64);
92
+ crypto.getRandomValues(raw);
93
+ let verifier = "";
94
+ for (let i = 0; i < raw.length; i++) verifier += alphabet[raw[i] % alphabet.length];
95
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", utf8(verifier)));
96
+ const challenge = b64url(digest);
97
+ const nonce = uuid();
98
+ const machineID = uuid();
99
+ const q = new URLSearchParams({
100
+ challenge,
101
+ challenge_method: "S256",
102
+ nonce,
103
+ machine_id: machineID,
104
+ client_id: CLIENT_ID,
105
+ redirect_uri: REDIRECT_URI,
106
+ });
107
+ return { verifier, nonce, url: `${DEVICE_AUTH_URL}?${q.toString()}` };
108
+ }
109
+
110
+ export function expiryUnix(grant) {
111
+ if (grant && grant.expires_at) {
112
+ const t = Date.parse(grant.expires_at);
113
+ if (!Number.isNaN(t)) return Math.floor(t / 1000);
114
+ }
115
+ if (grant && grant.expires_in > 0) {
116
+ const ms = grant.expires_in > 10000000 ? grant.expires_in : grant.expires_in * 1000;
117
+ return Math.floor((Date.now() + ms) / 1000);
118
+ }
119
+ return Math.floor((Date.now() + 30 * 24 * 3600 * 1000) / 1000);
120
+ }
@@ -1 +1 @@
1
- export * from "./qwenwork/index.js";
1
+ export * from "./qwenwork/index.js";
@@ -1,79 +1,79 @@
1
- /**
2
- * 可扩展供应商注册表:新增特殊供应商时,仅在此文件注册 + 新增对应 provider 文件即可
3
- * 每个条目:{ id, match(id, baseUrl) => bool, load() => Promise<factory> }
4
- * 匹配优先级:按数组顺序,首个命中即用;未命中走通用 generic
5
- */
6
-
7
- export const customProviders = [
8
- {
9
- id: "workbuddy",
10
- match: (id, baseUrl) => id === "workbuddy" || String(baseUrl).includes("copilot.tencent"),
11
- load: () => import("./workbuddy.js").then((m) => m.createWorkbuddyProvider),
12
- },
13
- {
14
- id: "traework",
15
- match: (id, baseUrl) => id === "traework" || String(baseUrl).includes("trae"),
16
- load: () => import("./traework.js").then((m) => m.createTraeworkProvider),
17
- },
18
- {
19
- id: "cline",
20
- match: (id, baseUrl) => id === "cline" || String(baseUrl).includes("cline.bot"),
21
- load: () => import("./cline.js").then((m) => m.createClineProvider),
22
- },
23
- {
24
- id: "codearts",
25
- match: (id, baseUrl) => id === "codearts" || String(baseUrl).includes("myhuaweicloud.com"),
26
- load: () => import("./codearts.js").then((m) => m.createCodeartsProvider),
27
- },
28
- {
29
- id: "qoder",
30
- match: (id, baseUrl) => id === "qoder" || String(baseUrl).includes("qoder"),
31
- load: () => import("./qoder/index.js").then((m) => m.createQoderProvider),
32
- },
33
- {
34
- // ⚠ 必须排在 qwenwork 之前:qwenwork 的 match 用 `includes("qwenwork")`,
35
- // 而 "globalqwenwork" 与 "gateway.qwenwork.ai"/"globalqwenwork://native" 都含该子串,
36
- // 若后置会被 cn 条目抢先命中(first-match-wins),把国际站 token 打到 cn 网关。
37
- id: "globalqwenwork",
38
- match: (id, baseUrl) => id === "globalqwenwork" || String(baseUrl).includes("qwenwork.ai"),
39
- load: () => import("./globalqwenwork/index.js").then((m) => m.createGlobalQwenworkProvider),
40
- },
41
- {
42
- id: "qwenwork",
43
- match: (id, baseUrl) => id === "qwenwork" || String(baseUrl).includes("qwenwork"),
44
- load: () => import("./qwenwork/index.js").then((m) => m.createQwenworkProvider),
45
- },
46
- {
47
- id: "zcode",
48
- match: (id, baseUrl) => id === "zcode" || String(baseUrl).includes("zcode.z.ai"),
49
- load: () => import("./zcode/index.js").then((m) => m.createZcodeProvider),
50
- },
51
- {
52
- id: "raccoon",
53
- match: (id, baseUrl) => id === "raccoon" || String(baseUrl).includes("xiaohuanxiong.com"),
54
- load: () => import("./raccoon/index.js").then((m) => m.createRaccoonProvider),
55
- },
56
- ];
57
-
58
- // 供 bench/probe 等需要定制化解析模型列表的场景
59
- export const customNormalizers = [
60
- {
61
- id: "cline",
62
- match: (baseUrl) => String(baseUrl).includes("cline.bot"),
63
- normalize: (json) => Array.isArray(json?.free) ? json.free : null,
64
- },
65
- ];
66
-
67
- export async function getCustomProviderFactory(id, baseUrl) {
68
- for (const entry of customProviders) {
69
- try { if (entry.match(id, baseUrl)) return await entry.load(); } catch {}
70
- }
71
- return null;
72
- }
73
-
74
- export function getCustomNormalizer(baseUrl) {
75
- for (const entry of customNormalizers) {
76
- try { if (entry.match(baseUrl)) return entry.normalize; } catch {}
77
- }
78
- return null;
79
- }
1
+ /**
2
+ * 可扩展供应商注册表:新增特殊供应商时,仅在此文件注册 + 新增对应 provider 文件即可
3
+ * 每个条目:{ id, match(id, baseUrl) => bool, load() => Promise<factory> }
4
+ * 匹配优先级:按数组顺序,首个命中即用;未命中走通用 generic
5
+ */
6
+
7
+ export const customProviders = [
8
+ {
9
+ id: "workbuddy",
10
+ match: (id, baseUrl) => id === "workbuddy" || String(baseUrl).includes("copilot.tencent"),
11
+ load: () => import("./workbuddy.js").then((m) => m.createWorkbuddyProvider),
12
+ },
13
+ {
14
+ id: "traework",
15
+ match: (id, baseUrl) => id === "traework" || String(baseUrl).includes("trae"),
16
+ load: () => import("./traework.js").then((m) => m.createTraeworkProvider),
17
+ },
18
+ {
19
+ id: "cline",
20
+ match: (id, baseUrl) => id === "cline" || String(baseUrl).includes("cline.bot"),
21
+ load: () => import("./cline.js").then((m) => m.createClineProvider),
22
+ },
23
+ {
24
+ id: "codearts",
25
+ match: (id, baseUrl) => id === "codearts" || String(baseUrl).includes("myhuaweicloud.com"),
26
+ load: () => import("./codearts.js").then((m) => m.createCodeartsProvider),
27
+ },
28
+ {
29
+ id: "qoder",
30
+ match: (id, baseUrl) => id === "qoder" || String(baseUrl).includes("qoder"),
31
+ load: () => import("./qoder/index.js").then((m) => m.createQoderProvider),
32
+ },
33
+ {
34
+ // ⚠ 必须排在 qwenwork 之前:qwenwork 的 match 用 `includes("qwenwork")`,
35
+ // 而 "globalqwenwork" 与 "gateway.qwenwork.ai"/"globalqwenwork://native" 都含该子串,
36
+ // 若后置会被 cn 条目抢先命中(first-match-wins),把国际站 token 打到 cn 网关。
37
+ id: "globalqwenwork",
38
+ match: (id, baseUrl) => id === "globalqwenwork" || String(baseUrl).includes("qwenwork.ai"),
39
+ load: () => import("./globalqwenwork/index.js").then((m) => m.createGlobalQwenworkProvider),
40
+ },
41
+ {
42
+ id: "qwenwork",
43
+ match: (id, baseUrl) => id === "qwenwork" || String(baseUrl).includes("qwenwork"),
44
+ load: () => import("./qwenwork/index.js").then((m) => m.createQwenworkProvider),
45
+ },
46
+ {
47
+ id: "zcode",
48
+ match: (id, baseUrl) => id === "zcode" || String(baseUrl).includes("zcode.z.ai"),
49
+ load: () => import("./zcode/index.js").then((m) => m.createZcodeProvider),
50
+ },
51
+ {
52
+ id: "raccoon",
53
+ match: (id, baseUrl) => id === "raccoon" || String(baseUrl).includes("xiaohuanxiong.com"),
54
+ load: () => import("./raccoon/index.js").then((m) => m.createRaccoonProvider),
55
+ },
56
+ ];
57
+
58
+ // 供 bench/probe 等需要定制化解析模型列表的场景
59
+ export const customNormalizers = [
60
+ {
61
+ id: "cline",
62
+ match: (baseUrl) => String(baseUrl).includes("cline.bot"),
63
+ normalize: (json) => Array.isArray(json?.free) ? json.free : null,
64
+ },
65
+ ];
66
+
67
+ export async function getCustomProviderFactory(id, baseUrl) {
68
+ for (const entry of customProviders) {
69
+ try { if (entry.match(id, baseUrl)) return await entry.load(); } catch {}
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export function getCustomNormalizer(baseUrl) {
75
+ for (const entry of customNormalizers) {
76
+ try { if (entry.match(baseUrl)) return entry.normalize; } catch {}
77
+ }
78
+ return null;
79
+ }