mslxdff 0.1.155 → 0.1.158

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 (151) hide show
  1. package/bin/mslxdff.js +4 -4
  2. package/docs/ARCHITECTURE.md +426 -0
  3. package/docs/FEATURE_TREE.md +164 -0
  4. package/docs/MOBILE.md +82 -0
  5. package/docs/adr/0001-reasoning-content-injection.md +14 -0
  6. package/docs/adr/0002-models-free-filter.md +12 -0
  7. package/docs/adr/0003-zero-state-no-auth.md +10 -0
  8. package/docs/adr/0004-bearer-token.md +18 -0
  9. package/docs/adr/0005-peer-mesh.md +53 -0
  10. package/docs/adr/0006-broadband-member.md +103 -0
  11. package/docs/adr/0007-multi-provider-prefix.md +25 -0
  12. package/docs/adr/0008-share-keys-to-peers.md +52 -0
  13. package/docs/adr/0009-chat-repl.md +37 -0
  14. package/docs/adr/0010-allowlist.md +36 -0
  15. package/docs/adr/0011-broadband-stream.md +30 -0
  16. package/docs/adr/0012-responses-endpoint-codex-sync.md +48 -0
  17. package/docs/adr/0013-node16-compat.md +41 -0
  18. package/docs/adr/0014-deepseek-provider.md +52 -0
  19. package/docs/adr/0015-upstream-probe-routing.md +50 -0
  20. package/docs/adr/0016-model-capabilities.md +27 -0
  21. package/docs/adr/0017-ai-sdk-upstream-engine.md +59 -0
  22. package/docs/adr/0018-zen-client-identity.md +52 -0
  23. package/docs/adr/0019-share-keys-always-lend.md +59 -0
  24. package/docs/adr/0020-zen-free-lane-agent-shape.md +52 -0
  25. package/docs/adr/0021-usage-report-jsonl.md +47 -0
  26. package/docs/adr/0022-models-capability-merge.md +61 -0
  27. package/docs/adr/0023-key-provider-default-direct.md +58 -0
  28. package/docs/adr/0024-node18-baseline.md +63 -0
  29. package/docs/adr/0025-workbuddy-authdir-follows-state.md +71 -0
  30. package/docs/adr/0026-cline-provider-id-unify.md +67 -0
  31. package/docs/adr/0027-codearts-provider.md +48 -0
  32. package/docs/adr/0028-traework-provider.md +34 -0
  33. package/docs/adr/0029-qoder-native-provider.md +60 -0
  34. package/docs/adr/0030-models-list-scoped-by-picks.md +53 -0
  35. package/docs/adr/0031-qoder-true-streaming.md +50 -0
  36. package/docs/adr/0032-generic-responses-channel.md +72 -0
  37. package/docs/adr/0033-cline-allowlist-auto-sync.md +75 -0
  38. package/docs/adr/0034-request-level-human-readable-observability.md +60 -0
  39. package/docs/adr/0035-sdk-channel-headers-timeout.md +49 -0
  40. package/docs/adr/0036-qoder-per-request-sticky-account.md +82 -0
  41. package/docs/adr/0037-qwenwork-independent-provider.md +82 -0
  42. package/docs/adr/0038-zcode-provider.md +140 -0
  43. package/docs/agents/domain.md +51 -0
  44. package/docs/agents/issue-tracker.md +30 -0
  45. package/docs/agents/triage-labels.md +15 -0
  46. package/docs/cli_help.md +1391 -0
  47. package/docs/cli_help_mini.md +133 -0
  48. package/docs/plans/bench-via-latency-2026-09-01.md +215 -0
  49. package/docs/plugins.md +187 -0
  50. package/package.json +1 -1
  51. package/src/auto.js +254 -254
  52. package/src/bench/cline-bench.js +42 -42
  53. package/src/bench/probe.js +70 -70
  54. package/src/bench/report.js +162 -162
  55. package/src/bench/runner.js +77 -77
  56. package/src/bench/via-probe.js +124 -124
  57. package/src/bench/via-routes.js +87 -87
  58. package/src/bench/workbuddy-bench.js +54 -54
  59. package/src/chat/engine.js +160 -160
  60. package/src/chat/gateway.js +163 -163
  61. package/src/chat/orchestrator.js +234 -234
  62. package/src/chat/prompt.js +70 -70
  63. package/src/chat/repl.js +88 -88
  64. package/src/chat/terminal.js +135 -135
  65. package/src/chat/tools.js +306 -306
  66. package/src/chat-pipeline/index.js +123 -123
  67. package/src/chat-pipeline/policy.js +76 -76
  68. package/src/chat-pipeline/serial-trial.js +210 -210
  69. package/src/cli/commands/group.js +249 -249
  70. package/src/cli/commands/model/list-providers.js +4 -3
  71. package/src/cli/commands/model/list-render.js +7 -9
  72. package/src/cli/commands/model/list-sort.js +52 -0
  73. package/src/cli/commands/model/list-tty.js +76 -0
  74. package/src/cli/commands/model/list.js +5 -62
  75. package/src/cli/commands/model/picks.js +50 -50
  76. package/src/cli/commands/provider/bench-via.js +247 -247
  77. package/src/cli/commands/provider/bench.js +141 -141
  78. package/src/cli/commands/provider/index.js +124 -116
  79. package/src/cli/commands/provider/models.js +139 -128
  80. package/src/cli/commands/provider/qwenwork-login.js +119 -0
  81. package/src/cli/commands/provider/zcode-login.js +77 -0
  82. package/src/cli/commands/provider/zcode-quota.js +55 -0
  83. package/src/cli/commands/sync.js +232 -232
  84. package/src/cli/provider-row.js +2 -2
  85. package/src/cli/status.js +279 -279
  86. package/src/daemon.js +96 -96
  87. package/src/model-capabilities/enrich.js +86 -86
  88. package/src/model-capabilities/index.js +183 -183
  89. package/src/model-capabilities/parse.js +70 -70
  90. package/src/model-trace.js +1 -0
  91. package/src/models.js +225 -225
  92. package/src/providers/classify.js +1 -1
  93. package/src/providers/cline/auth.js +228 -228
  94. package/src/providers/cline/chat.js +307 -307
  95. package/src/providers/cline.js +2 -2
  96. package/src/providers/keyring.js +60 -56
  97. package/src/providers/qoder/chat.js +183 -100
  98. package/src/providers/qoder/index.js +230 -184
  99. package/src/providers/qoder/sse.js +103 -46
  100. package/src/providers/qoder/sticky.js +1 -0
  101. package/src/providers/qoder/stream.js +32 -9
  102. package/src/providers/qwenwork/account-store.js +133 -0
  103. package/src/providers/qwenwork/constants.js +67 -0
  104. package/src/providers/qwenwork/cosy.js +120 -0
  105. package/src/providers/qwenwork/crypto.js +218 -0
  106. package/src/providers/qwenwork/http.js +20 -0
  107. package/src/providers/qwenwork/index.js +327 -0
  108. package/src/providers/qwenwork/payload.js +142 -0
  109. package/src/providers/qwenwork/rsa.js +54 -0
  110. package/src/providers/qwenwork/sse.js +268 -0
  111. package/src/providers/qwenwork/stream.js +130 -0
  112. package/src/providers/qwenwork/upstream.js +120 -0
  113. package/src/providers/qwenwork.js +1 -0
  114. package/src/providers/registry.js +66 -56
  115. package/src/providers/share-keys.js +2 -2
  116. package/src/providers/workbuddy/chat.js +248 -248
  117. package/src/providers/workbuddy/reshape.js +152 -152
  118. package/src/providers/workbuddy.js +2 -2
  119. package/src/providers/zcode/account-store.js +129 -0
  120. package/src/providers/zcode/auth.js +28 -0
  121. package/src/providers/zcode/chat.js +171 -0
  122. package/src/providers/zcode/const.js +54 -0
  123. package/src/providers/zcode/headers.js +55 -0
  124. package/src/providers/zcode/index.js +124 -0
  125. package/src/providers/zcode/models.js +65 -0
  126. package/src/providers/zcode/oauth.js +120 -0
  127. package/src/providers/zcode/quota.js +176 -0
  128. package/src/providers/zcode/sse.js +179 -0
  129. package/src/reasoning.js +32 -32
  130. package/src/routes/chat/gateway.js +46 -46
  131. package/src/routes/chat/relay-pipeline.js +250 -246
  132. package/src/routes/chat/via-route-handler.js +144 -144
  133. package/src/routes/hedge.js +255 -255
  134. package/src/routes/models-route.js +167 -167
  135. package/src/routes/peers.js +273 -273
  136. package/src/routes/stream.js +438 -399
  137. package/src/runtime/bootstrap.js +45 -45
  138. package/src/runtime/provider-gate.js +33 -30
  139. package/src/runtime/providers-setup.js +165 -165
  140. package/src/server.js +64 -64
  141. package/src/state/schemas/allowlist.js +92 -92
  142. package/src/sync-opencode.js +280 -280
  143. package/src/transport/index.js +244 -244
  144. package/src/transport/pool.js +56 -56
  145. package/src/transport/retry.js +24 -24
  146. package/src/transport/sse.js +93 -93
  147. package/src/upstream-probe/display.js +52 -52
  148. package/src/upstream-probe/probe.js +49 -49
  149. package/src/upstream-probe/rotate.js +110 -110
  150. package/src/upstream-probe/start.js +45 -45
  151. package/src/upstream.js +289 -289
@@ -0,0 +1,268 @@
1
+ // vendor 自 _shared/sse.js 的解帧/清洗/聚合段(逐字搬),另并入上游 completions.js 的额度措辞判定。
2
+ // 关键:qwenwork 上游的内层 body **本身就是 OpenAI chunk**(不像 qoder 需要转译),
3
+ // 外层是 `{"headers":{},"body":"<内层JSON字符串>","statusCodeValue":200}` 的信封。
4
+ import { cleanErrorText } from "./http.js";
5
+
6
+ function fmtBodyText(value) {
7
+ if (typeof value === "string") return value;
8
+ if (value && typeof value === "object") return JSON.stringify(value);
9
+ return "";
10
+ }
11
+
12
+ function bodyCodeMessage(raw, status) {
13
+ let inner;
14
+ try {
15
+ inner = JSON.parse(raw);
16
+ } catch {
17
+ return status >= 400 ? `upstream ${status}: ${String(raw).slice(0, 200)}` : "";
18
+ }
19
+ if (!inner || typeof inner !== "object") return "";
20
+ const code = inner.code;
21
+ if (code === "400" || code === "401" || code === "403") {
22
+ return inner.message ? String(inner.message) : `upstream ${code}`;
23
+ }
24
+ if (status >= 400) {
25
+ return inner.message ? String(inner.message) : `upstream ${status}: ${String(raw).slice(0, 200)}`;
26
+ }
27
+ return "";
28
+ }
29
+
30
+ export function unwrapFrame(payload) {
31
+ if (typeof payload !== "string") return { ok: false };
32
+ const text = String(payload).trim();
33
+ if (!text || text === "[DONE]" || text === "{}") return { ok: false };
34
+ let outer;
35
+ try {
36
+ outer = JSON.parse(text);
37
+ } catch {
38
+ return { ok: true, body: text };
39
+ }
40
+ if (!outer || typeof outer !== "object") return { ok: false };
41
+ if (Object.prototype.hasOwnProperty.call(outer, "choices")) return { ok: true, body: text };
42
+ if (outer.object === "chat.completion.chunk" || outer.object === "chat.completion") return { ok: true, body: text };
43
+ if (typeof outer.statusCodeValue === "number" && outer.statusCodeValue >= 400) {
44
+ return { ok: false, error: bodyCodeMessage(fmtBodyText(outer.body), outer.statusCodeValue) };
45
+ }
46
+ if (!Object.prototype.hasOwnProperty.call(outer, "body")) return { ok: false };
47
+ const inner = outer.body;
48
+ if (typeof inner === "string") {
49
+ if (!inner || inner === "[DONE]" || inner === "{}" || inner === "null") return { ok: false };
50
+ const msg = bodyCodeMessage(inner, 0);
51
+ if (msg) return { ok: false, error: msg };
52
+ return { ok: true, body: inner };
53
+ }
54
+ if (inner && typeof inner === "object") {
55
+ const s = JSON.stringify(inner);
56
+ if (s === "{}" || s === "[]") return { ok: false };
57
+ return { ok: true, body: s };
58
+ }
59
+ return { ok: false };
60
+ }
61
+
62
+ function isEmptyValue(v) {
63
+ if (v === null || v === undefined) return true;
64
+ if (typeof v === "string") return v === "";
65
+ if (Array.isArray(v)) return v.length === 0;
66
+ if (typeof v === "object") {
67
+ for (const key of Object.keys(v)) {
68
+ if (!isEmptyValue(v[key])) return false;
69
+ }
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+
75
+ export function cleanChunk(text) {
76
+ let obj;
77
+ try {
78
+ obj = JSON.parse(text);
79
+ } catch {
80
+ return text;
81
+ }
82
+ if (!obj || typeof obj !== "object") return text;
83
+
84
+ if (obj.usage && typeof obj.usage === "object") {
85
+ let changed = false;
86
+ for (const noise of ["raw_usage", "sub_usages"]) {
87
+ if (Object.prototype.hasOwnProperty.call(obj.usage, noise)) {
88
+ delete obj.usage[noise];
89
+ changed = true;
90
+ }
91
+ if (Object.prototype.hasOwnProperty.call(obj, noise)) {
92
+ delete obj[noise];
93
+ changed = true;
94
+ }
95
+ }
96
+ return changed ? JSON.stringify(obj) : text;
97
+ }
98
+
99
+ let changed = false;
100
+ if (Array.isArray(obj.choices)) {
101
+ for (const choice of obj.choices) {
102
+ if (!choice || typeof choice !== "object") continue;
103
+ const delta = choice.delta;
104
+ if (!delta || typeof delta !== "object") continue;
105
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls.length === 0) {
106
+ delete delta.tool_calls;
107
+ changed = true;
108
+ }
109
+ for (const noise of ["content", "reasoning_content", "function_call", "refusal", "extra_fields"]) {
110
+ if (Object.prototype.hasOwnProperty.call(delta, noise) && isEmptyValue(delta[noise])) {
111
+ delete delta[noise];
112
+ changed = true;
113
+ }
114
+ }
115
+ if (Object.keys(delta).length === 0 && !choice.finish_reason) return "";
116
+ }
117
+ }
118
+ return changed ? JSON.stringify(obj) : text;
119
+ }
120
+
121
+ function mergeToolCallDelta(merged, delta) {
122
+ for (const key of ["id", "type"]) {
123
+ if (merged[key] === undefined && typeof delta[key] === "string" && delta[key] !== "") merged[key] = delta[key];
124
+ }
125
+ const dfn = delta.function;
126
+ if (!dfn || typeof dfn !== "object") return;
127
+ if (!merged.function || typeof merged.function !== "object") merged.function = {};
128
+ if (typeof dfn.name === "string" && dfn.name !== "") merged.function.name = (merged.function.name || "") + dfn.name;
129
+ if (typeof dfn.arguments === "string" && dfn.arguments !== "") {
130
+ merged.function.arguments = (merged.function.arguments || "") + dfn.arguments;
131
+ }
132
+ }
133
+
134
+ export function aggregate(chunks, model) {
135
+ let content = "";
136
+ let reasoning = "";
137
+ let role = "";
138
+ let respModel = "";
139
+ let respID = "";
140
+ let finish = "";
141
+ let created = 0;
142
+ let usage = null;
143
+ const toolCalls = new Map();
144
+ const toolOrder = [];
145
+
146
+ for (const text of chunks) {
147
+ let chunk;
148
+ try {
149
+ chunk = JSON.parse(text);
150
+ } catch {
151
+ continue;
152
+ }
153
+ if (!chunk || typeof chunk !== "object") continue;
154
+ if (typeof chunk.id === "string" && chunk.id !== "") respID = chunk.id;
155
+ if (typeof chunk.model === "string" && chunk.model !== "") respModel = chunk.model;
156
+ if (typeof chunk.created === "number") created = chunk.created;
157
+ if (chunk.usage && typeof chunk.usage === "object") usage = chunk.usage;
158
+ if (!Array.isArray(chunk.choices)) continue;
159
+ for (const choice of chunk.choices) {
160
+ if (!choice || typeof choice !== "object") continue;
161
+ const delta = choice.delta;
162
+ if (delta && typeof delta === "object") {
163
+ if (typeof delta.role === "string" && delta.role !== "") role = delta.role;
164
+ if (typeof delta.content === "string") content += delta.content;
165
+ if (typeof delta.reasoning_content === "string") reasoning += delta.reasoning_content;
166
+ if (Array.isArray(delta.tool_calls)) {
167
+ for (const call of delta.tool_calls) {
168
+ if (!call || typeof call !== "object") continue;
169
+ const idx = typeof call.index === "number" ? call.index : 0;
170
+ if (!toolCalls.has(idx)) {
171
+ toolCalls.set(idx, { index: idx });
172
+ toolOrder.push(idx);
173
+ }
174
+ mergeToolCallDelta(toolCalls.get(idx), call);
175
+ }
176
+ }
177
+ }
178
+ if (typeof choice.finish_reason === "string" && choice.finish_reason !== "") finish = choice.finish_reason;
179
+ }
180
+ }
181
+
182
+ const message = { role: role || "assistant", content };
183
+ if (reasoning !== "") message.reasoning_content = reasoning;
184
+ if (toolOrder.length > 0) {
185
+ toolOrder.sort((a, b) => a - b);
186
+ message.tool_calls = toolOrder.map((idx) => toolCalls.get(idx));
187
+ }
188
+ const result = {
189
+ id: respID || "chatcmpl-qwenwork",
190
+ object: "chat.completion",
191
+ created: created || Math.floor(Date.now() / 1000),
192
+ model: respModel || model,
193
+ choices: [{ index: 0, message, finish_reason: finish || "stop" }],
194
+ };
195
+ if (usage) result.usage = usage;
196
+ return result;
197
+ }
198
+
199
+ /** 单行 SSE → OpenAI chunk 文本(null = 跳过);错误写进 state.error。 */
200
+ export function handleLine(line, state) {
201
+ if (!line.startsWith("data:")) return null;
202
+ const frame = unwrapFrame(line.slice(5));
203
+ if (frame.error) {
204
+ state.error = frame.error;
205
+ return null;
206
+ }
207
+ if (!frame.ok) return null;
208
+ try {
209
+ const parsed = JSON.parse(frame.body);
210
+ if (parsed && typeof parsed === "object" && parsed.usage && typeof parsed.usage === "object") state.usage = parsed.usage;
211
+ } catch {
212
+ /* keep raw chunk */
213
+ }
214
+ const cleaned = cleanChunk(frame.body);
215
+ if (!cleaned) return null;
216
+ return `data: ${cleaned}\n\n`;
217
+ }
218
+
219
+ export async function collectSync(upstreamBody, model) {
220
+ const reader = upstreamBody.getReader();
221
+ const decoder = new TextDecoder();
222
+ let buffer = "";
223
+ const chunks = [];
224
+ const state = { usage: null, error: null };
225
+
226
+ try {
227
+ for (;;) {
228
+ const { done, value } = await reader.read();
229
+ if (done) break;
230
+ buffer += decoder.decode(value, { stream: true });
231
+ const lines = buffer.split("\n");
232
+ buffer = lines.pop() || "";
233
+ for (const line of lines) {
234
+ const out = handleLine(line, state);
235
+ if (out) chunks.push(out.slice(6).trim());
236
+ }
237
+ }
238
+ if (buffer) {
239
+ const out = handleLine(buffer, state);
240
+ if (out) chunks.push(out.slice(6).trim());
241
+ }
242
+ } catch (err) {
243
+ state.error = `upstream stream read error: ${err && err.message ? err.message : err}`;
244
+ }
245
+
246
+ if (state.error) return { error: cleanErrorText(state.error) };
247
+ return { completion: aggregate(chunks, model), usage: state.usage };
248
+ }
249
+
250
+ // ---- 额度耗尽识别(上游措辞表逐字搬自原作者 completions.js,含 code 14018)----
251
+ const CREDIT_EXHAUSTED_MARKERS = [
252
+ "credits exhausted", "insufficient credit", "no credit", "credit exhausted", "out of credit",
253
+ "quota exceeded", "quota exhaust", "credit not enough", "not enough credit", "payment required",
254
+ "积分不足", "额度不足", "余额不足", "积分用完", "额度用尽", "没有积分",
255
+ ];
256
+
257
+ export function creditsExhaustedText(value) {
258
+ const s = String(value === undefined || value === null ? "" : value);
259
+ if (/\bcode["'\s:=]*14018\b/.test(s)) return true;
260
+ const lower = s.toLowerCase();
261
+ return CREDIT_EXHAUSTED_MARKERS.some((m) => lower.includes(m.toLowerCase()));
262
+ }
263
+
264
+ export function isCreditsExhausted(status, text) {
265
+ if (status !== 429 && status !== 402) return false;
266
+ if (status === 402) return true;
267
+ return creditsExhaustedText(text);
268
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1 @@
1
+ export * from "./qwenwork/index.js";
@@ -1,56 +1,66 @@
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
-
35
- // 供 bench/probe 等需要定制化解析模型列表的场景
36
- export const customNormalizers = [
37
- {
38
- id: "cline",
39
- match: (baseUrl) => String(baseUrl).includes("cline.bot"),
40
- normalize: (json) => Array.isArray(json?.free) ? json.free : null,
41
- },
42
- ];
43
-
44
- export async function getCustomProviderFactory(id, baseUrl) {
45
- for (const entry of customProviders) {
46
- try { if (entry.match(id, baseUrl)) return await entry.load(); } catch {}
47
- }
48
- return null;
49
- }
50
-
51
- export function getCustomNormalizer(baseUrl) {
52
- for (const entry of customNormalizers) {
53
- try { if (entry.match(baseUrl)) return entry.normalize; } catch {}
54
- }
55
- return null;
56
- }
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
+ id: "qwenwork",
35
+ match: (id, baseUrl) => id === "qwenwork" || String(baseUrl).includes("qwenwork"),
36
+ load: () => import("./qwenwork/index.js").then((m) => m.createQwenworkProvider),
37
+ },
38
+ {
39
+ id: "zcode",
40
+ match: (id, baseUrl) => id === "zcode" || String(baseUrl).includes("zcode.z.ai"),
41
+ load: () => import("./zcode/index.js").then((m) => m.createZcodeProvider),
42
+ },
43
+ ];
44
+
45
+ // 供 bench/probe 等需要定制化解析模型列表的场景
46
+ export const customNormalizers = [
47
+ {
48
+ id: "cline",
49
+ match: (baseUrl) => String(baseUrl).includes("cline.bot"),
50
+ normalize: (json) => Array.isArray(json?.free) ? json.free : null,
51
+ },
52
+ ];
53
+
54
+ export async function getCustomProviderFactory(id, baseUrl) {
55
+ for (const entry of customProviders) {
56
+ try { if (entry.match(id, baseUrl)) return await entry.load(); } catch {}
57
+ }
58
+ return null;
59
+ }
60
+
61
+ export function getCustomNormalizer(baseUrl) {
62
+ for (const entry of customNormalizers) {
63
+ try { if (entry.match(baseUrl)) return entry.normalize; } catch {}
64
+ }
65
+ return null;
66
+ }
@@ -9,8 +9,8 @@ export const SHARE_KEYS_HEADER = "x-mslxdff-share-keys";
9
9
 
10
10
  // 硬排除:refresh-token 型凭据(对端刷新会轮换 token,与本机互相踢下线)——正确性问题。
11
11
  // 二层保护:① 按 id 硬排除(cline 恒 local-only);② 按凭据形状兜底(见 shareableProviderIds)。
12
- // traework 与 workbuddy 同为本机账号绑定型(auths/*.json + uid),恒不借出。
13
- const NEVER_SHARE_IDS = new Set(["cline", "codearts", "traework", "qoder"]);
12
+ // traework/workbuddy/zcode 同为本机账号绑定型(auths/*.json + uid;zcode 还带 per-account deviceMid),恒不借出。
13
+ const NEVER_SHARE_IDS = new Set(["cline", "codearts", "traework", "qoder", "zcode"]);
14
14
  // 本节点应 cast key 到出站转发的供应商 id 集合 = 所有「本机有 key」的供应商,
15
15
  // 减去:opencode(无 key 且恒排除)、local-only(workbuddy/cline系,本就不走组员)、刷新型凭据。
16
16
  export function shareableProviderIds({ file } = {}) {