@wengine-ai/ccr-provider-qoder 0.2.0 → 0.3.1
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 +22 -2
- package/dist/README.md +22 -2
- package/dist/card.js +73 -127
- package/dist/plugin.js +285 -175
- package/dist/plugin.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Everything below exists once per site; the two sites are independent (own accoun
|
|
|
18
18
|
- **Transformer** (`qoder` / `qoder-cn`): unified → chat-completions request conversion (leading system message, thinking → `reasoning_content`, stable session id → `prompt_cache_key`, reasoning effort passthrough) and strict SSE/JSON response validation (error-frame classification, `[DONE]` integrity, usage mapping). The conversion is site-independent; each managed provider locks its own transformer name.
|
|
19
19
|
- **Managed runtime** (`qoder` / `qoder-cn`): pins that site's fixed chat upstream, routes through the host's subscription dispatcher, owns abort/deadline wiring and the error taxonomy (`qoder_reauth_required`, `qoder_rate_limited` with `retry_after`-derived cooldown, `qoder_upstream_error`). A runtime claims only its own managed provider.
|
|
20
20
|
- **Auth service**: PAT sign-in with the job-token engine, per-site credential file (0600, atomic, size-capped), model sync/visibility, usage reads with a 60s cache, cooling circuit breaker and reconnect.
|
|
21
|
-
- **Admin routes** (loopback-only, per site): status, connect, sync, usage, model visibility, disconnect.
|
|
21
|
+
- **Admin routes** (loopback-only, per site): status, connect, sync, usage, checkin, model visibility, disconnect.
|
|
22
22
|
- **One config protector**: re-locks BOTH managed providers in a single pass and re-adds any of them that vanish from the config.
|
|
23
23
|
- **UI card + locales** (en/zh): one card with two independent sections (Qoder / Qoder CN) — PAT connect form, status, quota windows, model management.
|
|
24
24
|
|
|
@@ -38,9 +38,29 @@ Then, in the CCR settings page, open the Qoder card and use the matching section
|
|
|
38
38
|
|
|
39
39
|
Both sections can be connected at the same time; disconnecting one never touches the other.
|
|
40
40
|
|
|
41
|
+
## Manual daily check-in
|
|
42
|
+
|
|
43
|
+
The Qoder CN card has a **Check in** button. It lists campaigns, claims the first
|
|
44
|
+
`CLAIM_BENEFIT` marked `CLAIMABLE`, and shows the awarded credits. Already claimed
|
|
45
|
+
and unavailable campaigns are ordinary results, with no automatic retry or timer.
|
|
46
|
+
|
|
47
|
+
Loopback-only `POST /api/qoder/cn/checkin` returns
|
|
48
|
+
`{ outcome, credit?, message? }`, where outcome is `claimed`, `done`, `inactive`,
|
|
49
|
+
`ineligible`, or `failed`. Credits are a positive finite number when supplied.
|
|
50
|
+
`POST /api/qoder/checkin` returns `ineligible`: international campaigns require
|
|
51
|
+
Qoder's signed desktop identity, which this PAT-only plugin does not read. The
|
|
52
|
+
international card directs users to claim in Qoder desktop.
|
|
53
|
+
|
|
54
|
+
CN PAT requests use the exchanged job token and the upstream's supported empty
|
|
55
|
+
native-identity fallback. No machine token or machine type is fabricated.
|
|
56
|
+
Requests use the subscription dispatcher (direct fallback), identity encoding,
|
|
57
|
+
and reject redirects. Upstream response bodies and transport errors are never
|
|
58
|
+
returned in failure messages. Admin authorization failures retain HTTP 403;
|
|
59
|
+
check-in outcomes return HTTP 200.
|
|
60
|
+
|
|
41
61
|
## Scope limits
|
|
42
62
|
|
|
43
|
-
- Remote origins are fixed per site: `openapi.qoder.sh` / `api3.qoder.sh` for qoder.com and `openapi.qoder.com.cn` / `gateway.qoder.com.cn` for qoder.cn. No device flow, no
|
|
63
|
+
- Remote origins are fixed per site: `openapi.qoder.sh` / `api3.qoder.sh` for qoder.com and `openapi.qoder.com.cn` / `gateway.qoder.com.cn` for qoder.cn. No device flow, no VPC, no QwenWork.
|
|
44
64
|
- Sign-in is PAT-only; the job token is refreshed from its refresh token when present, and a lapsed/absent refresh falls back to PAT re-exchange (a refused PAT marks that site's account expired until you reconnect). The PAT is kept in the plugin's private 0600 credential file.
|
|
45
65
|
|
|
46
66
|
## Development
|
package/dist/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Everything below exists once per site; the two sites are independent (own accoun
|
|
|
18
18
|
- **Transformer** (`qoder` / `qoder-cn`): unified → chat-completions request conversion (leading system message, thinking → `reasoning_content`, stable session id → `prompt_cache_key`, reasoning effort passthrough) and strict SSE/JSON response validation (error-frame classification, `[DONE]` integrity, usage mapping). The conversion is site-independent; each managed provider locks its own transformer name.
|
|
19
19
|
- **Managed runtime** (`qoder` / `qoder-cn`): pins that site's fixed chat upstream, routes through the host's subscription dispatcher, owns abort/deadline wiring and the error taxonomy (`qoder_reauth_required`, `qoder_rate_limited` with `retry_after`-derived cooldown, `qoder_upstream_error`). A runtime claims only its own managed provider.
|
|
20
20
|
- **Auth service**: PAT sign-in with the job-token engine, per-site credential file (0600, atomic, size-capped), model sync/visibility, usage reads with a 60s cache, cooling circuit breaker and reconnect.
|
|
21
|
-
- **Admin routes** (loopback-only, per site): status, connect, sync, usage, model visibility, disconnect.
|
|
21
|
+
- **Admin routes** (loopback-only, per site): status, connect, sync, usage, checkin, model visibility, disconnect.
|
|
22
22
|
- **One config protector**: re-locks BOTH managed providers in a single pass and re-adds any of them that vanish from the config.
|
|
23
23
|
- **UI card + locales** (en/zh): one card with two independent sections (Qoder / Qoder CN) — PAT connect form, status, quota windows, model management.
|
|
24
24
|
|
|
@@ -38,9 +38,29 @@ Then, in the CCR settings page, open the Qoder card and use the matching section
|
|
|
38
38
|
|
|
39
39
|
Both sections can be connected at the same time; disconnecting one never touches the other.
|
|
40
40
|
|
|
41
|
+
## Manual daily check-in
|
|
42
|
+
|
|
43
|
+
The Qoder CN card has a **Check in** button. It lists campaigns, claims the first
|
|
44
|
+
`CLAIM_BENEFIT` marked `CLAIMABLE`, and shows the awarded credits. Already claimed
|
|
45
|
+
and unavailable campaigns are ordinary results, with no automatic retry or timer.
|
|
46
|
+
|
|
47
|
+
Loopback-only `POST /api/qoder/cn/checkin` returns
|
|
48
|
+
`{ outcome, credit?, message? }`, where outcome is `claimed`, `done`, `inactive`,
|
|
49
|
+
`ineligible`, or `failed`. Credits are a positive finite number when supplied.
|
|
50
|
+
`POST /api/qoder/checkin` returns `ineligible`: international campaigns require
|
|
51
|
+
Qoder's signed desktop identity, which this PAT-only plugin does not read. The
|
|
52
|
+
international card directs users to claim in Qoder desktop.
|
|
53
|
+
|
|
54
|
+
CN PAT requests use the exchanged job token and the upstream's supported empty
|
|
55
|
+
native-identity fallback. No machine token or machine type is fabricated.
|
|
56
|
+
Requests use the subscription dispatcher (direct fallback), identity encoding,
|
|
57
|
+
and reject redirects. Upstream response bodies and transport errors are never
|
|
58
|
+
returned in failure messages. Admin authorization failures retain HTTP 403;
|
|
59
|
+
check-in outcomes return HTTP 200.
|
|
60
|
+
|
|
41
61
|
## Scope limits
|
|
42
62
|
|
|
43
|
-
- Remote origins are fixed per site: `openapi.qoder.sh` / `api3.qoder.sh` for qoder.com and `openapi.qoder.com.cn` / `gateway.qoder.com.cn` for qoder.cn. No device flow, no
|
|
63
|
+
- Remote origins are fixed per site: `openapi.qoder.sh` / `api3.qoder.sh` for qoder.com and `openapi.qoder.com.cn` / `gateway.qoder.com.cn` for qoder.cn. No device flow, no VPC, no QwenWork.
|
|
44
64
|
- Sign-in is PAT-only; the job token is refreshed from its refresh token when present, and a lapsed/absent refresh falls back to PAT re-exchange (a refused PAT marks that site's account expired until you reconnect). The PAT is kept in the plugin's private 0600 credential file.
|
|
45
65
|
|
|
46
66
|
## Development
|
package/dist/card.js
CHANGED
|
@@ -12,23 +12,26 @@ var qoderLocales = {
|
|
|
12
12
|
error: "Management request failed",
|
|
13
13
|
account: "Account",
|
|
14
14
|
models: "Models",
|
|
15
|
-
models_count: "{{count}} models",
|
|
16
15
|
pat: "Personal access token",
|
|
17
16
|
patPlaceholder: "pt-\u2026",
|
|
18
17
|
connect: "Connect",
|
|
18
|
+
checkin: "Check in",
|
|
19
|
+
checkin_action: "Check in",
|
|
20
|
+
checkin_pending: "Checking in\u2026",
|
|
21
|
+
checkin_claimed: "Credits claimed",
|
|
22
|
+
checkin_done: "Already claimed today",
|
|
23
|
+
checkin_inactive: "No campaign today",
|
|
24
|
+
checkin_ineligible: "Unavailable",
|
|
25
|
+
checkin_failed: "Check-in failed",
|
|
26
|
+
checkinDesktop: "International check-in requires Qoder desktop device identity; claim in Qoder desktop.",
|
|
19
27
|
sync: "Refresh models",
|
|
20
28
|
usage: "Refresh quota",
|
|
21
29
|
unknown: "Quota unknown",
|
|
22
30
|
stale: "Last known quota (stale)",
|
|
23
31
|
used: "Allowance",
|
|
24
|
-
restore_partial: "Partially completed: restored {{count}} of {{total}} models. Management request failed.",
|
|
25
|
-
hide_partial: "Partially completed: hid {{count}} of {{total}} models. Management request failed.",
|
|
26
|
-
restore_model: "Restore {{model}}",
|
|
27
32
|
disconnect: "Disconnect",
|
|
28
|
-
|
|
29
|
-
hidden_models: "Hidden models ({{count}})",
|
|
33
|
+
hide: "Hide",
|
|
30
34
|
restore: "Restore",
|
|
31
|
-
restore_all: "Restore all",
|
|
32
35
|
cooling: "Rate limited until",
|
|
33
36
|
noModels: "Refresh models after connecting",
|
|
34
37
|
free: "free",
|
|
@@ -46,23 +49,26 @@ var qoderLocales = {
|
|
|
46
49
|
error: "\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25",
|
|
47
50
|
account: "\u8D26\u53F7",
|
|
48
51
|
models: "\u6A21\u578B",
|
|
49
|
-
models_count: "{{count}} \u4E2A\u6A21\u578B",
|
|
50
52
|
pat: "\u4E2A\u4EBA\u8BBF\u95EE\u4EE4\u724C",
|
|
51
53
|
patPlaceholder: "pt-\u2026",
|
|
52
54
|
connect: "\u8FDE\u63A5",
|
|
55
|
+
checkin: "\u7B7E\u5230",
|
|
56
|
+
checkin_action: "\u7B7E\u5230",
|
|
57
|
+
checkin_pending: "\u7B7E\u5230\u4E2D\u2026",
|
|
58
|
+
checkin_claimed: "\u9886\u53D6\u6210\u529F",
|
|
59
|
+
checkin_done: "\u4ECA\u65E5\u5DF2\u9886",
|
|
60
|
+
checkin_inactive: "\u4ECA\u65E5\u65E0\u6D3B\u52A8",
|
|
61
|
+
checkin_ineligible: "\u4E0D\u53EF\u7528",
|
|
62
|
+
checkin_failed: "\u7B7E\u5230\u5931\u8D25",
|
|
63
|
+
checkinDesktop: "\u56FD\u9645\u7AD9\u9700\u8981\u5728 Qoder \u684C\u9762\u7AEF\u5185\u7B7E\u5230\u3002",
|
|
53
64
|
sync: "\u5237\u65B0\u6A21\u578B",
|
|
54
65
|
usage: "\u5237\u65B0\u989D\u5EA6",
|
|
55
66
|
unknown: "\u989D\u5EA6\u672A\u77E5",
|
|
56
67
|
stale: "\u4E0A\u6B21\u989D\u5EA6\uFF08\u5DF2\u8FC7\u671F\uFF09",
|
|
57
68
|
used: "\u5DF2\u7528\u989D\u5EA6",
|
|
58
|
-
restore_partial: "\u90E8\u5206\u5B8C\u6210\uFF1A\u5DF2\u6062\u590D {{count}} \u4E2A\u6A21\u578B\uFF0C\u5171 {{total}} \u4E2A\u3002\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25\u3002",
|
|
59
|
-
hide_partial: "\u90E8\u5206\u5B8C\u6210\uFF1A\u5DF2\u9690\u85CF {{count}} \u4E2A\u6A21\u578B\uFF0C\u5171 {{total}} \u4E2A\u3002\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25\u3002",
|
|
60
|
-
restore_model: "\u6062\u590D {{model}}",
|
|
61
69
|
disconnect: "\u65AD\u5F00\u8FDE\u63A5",
|
|
62
|
-
|
|
63
|
-
hidden_models: "\u5DF2\u9690\u85CF\u6A21\u578B\uFF08{{count}}\uFF09",
|
|
70
|
+
hide: "\u9690\u85CF",
|
|
64
71
|
restore: "\u6062\u590D",
|
|
65
|
-
restore_all: "\u6062\u590D\u5168\u90E8",
|
|
66
72
|
cooling: "\u9650\u6D41\u81F3",
|
|
67
73
|
noModels: "\u8FDE\u63A5\u540E\u8BF7\u5237\u65B0\u6A21\u578B",
|
|
68
74
|
free: "\u514D\u8D39",
|
|
@@ -156,36 +162,9 @@ function mount(el, props) {
|
|
|
156
162
|
n.className = cls;
|
|
157
163
|
return n;
|
|
158
164
|
};
|
|
159
|
-
const CHIP = "inline-flex max-w-full items-center gap-1 break-all whitespace-normal rounded-lg border border-white/5 bg-white/5 px-2 py-0.5 text-[10px] font-bold text-muted-foreground";
|
|
160
|
-
const CHIP_HIDE = "rounded p-0.5 text-muted-foreground/60 transition-colors hover:bg-white/10 hover:text-foreground disabled:opacity-40";
|
|
161
|
-
const BUTTON_BASE = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-semibold ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 active:scale-95";
|
|
162
|
-
const BUTTON_OUTLINE = "border border-input bg-input/50 backdrop-blur-md shadow-sm hover:bg-input/80 hover:border-primary/50 hover:shadow-md hover:text-accent-foreground";
|
|
163
|
-
const xIcon = (className) => {
|
|
164
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
165
|
-
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
166
|
-
svg.setAttribute("width", "24");
|
|
167
|
-
svg.setAttribute("height", "24");
|
|
168
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
169
|
-
svg.setAttribute("fill", "none");
|
|
170
|
-
svg.setAttribute("stroke", "currentColor");
|
|
171
|
-
svg.setAttribute("stroke-width", "2");
|
|
172
|
-
svg.setAttribute("stroke-linecap", "round");
|
|
173
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
174
|
-
svg.setAttribute("aria-hidden", "true");
|
|
175
|
-
svg.setAttribute("class", className);
|
|
176
|
-
for (const d of ["M18 6 6 18", "m6 6 12 12"]) {
|
|
177
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
178
|
-
path.setAttribute("d", d);
|
|
179
|
-
svg.appendChild(path);
|
|
180
|
-
}
|
|
181
|
-
return svg;
|
|
182
|
-
};
|
|
183
|
-
const format = (key, vars = {}) => String(words[key] ?? key).replace(/\{\{(\w+)\}\}/g, (_, name) => String(vars[name] ?? ""));
|
|
184
|
-
const sectionBusy = () => disposed || props.disabled || sections.some((s) => s.busy);
|
|
185
165
|
const sectionWords = (section) => section.site.id === "qoder" ? { title: words.title, description: words.description } : { title: words.cnTitle, description: words.cnDescription };
|
|
186
166
|
const finish = (section) => {
|
|
187
167
|
section.busy = false;
|
|
188
|
-
section.pendingModel = null;
|
|
189
168
|
section.release?.();
|
|
190
169
|
section.release = void 0;
|
|
191
170
|
props.onBusy?.(false);
|
|
@@ -203,7 +182,6 @@ function mount(el, props) {
|
|
|
203
182
|
section.release = lease;
|
|
204
183
|
section.busy = true;
|
|
205
184
|
section.error = false;
|
|
206
|
-
section.errorMessage = void 0;
|
|
207
185
|
props.onBusy?.(true);
|
|
208
186
|
render();
|
|
209
187
|
try {
|
|
@@ -230,35 +208,33 @@ function mount(el, props) {
|
|
|
230
208
|
});
|
|
231
209
|
const action = (section, label, fn) => {
|
|
232
210
|
const button = node("button", words[label], "px-3 py-2 rounded-md bg-secondary text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50");
|
|
233
|
-
button.disabled =
|
|
211
|
+
button.disabled = disposed || props.disabled || sections.some((s) => s.busy);
|
|
234
212
|
button.onclick = fn;
|
|
235
213
|
return button;
|
|
236
214
|
};
|
|
237
|
-
const
|
|
238
|
-
const
|
|
215
|
+
const checkinButton = (section) => {
|
|
216
|
+
const pending = !!section.checkinPending;
|
|
217
|
+
const button = node("button", pending ? words.checkin_pending : words.checkin_action, "inline-flex h-8 w-full items-center justify-center gap-2 whitespace-nowrap rounded-xl border border-input bg-input/50 text-xs font-semibold shadow-sm hover:bg-input/80 disabled:pointer-events-none disabled:opacity-50");
|
|
239
218
|
button.type = "button";
|
|
240
|
-
button.disabled =
|
|
241
|
-
button.
|
|
219
|
+
button.disabled = disposed || props.disabled || sections.some((s) => s.busy) || section.status?.status !== "connected";
|
|
220
|
+
button.setAttribute("aria-busy", String(pending));
|
|
221
|
+
button.onclick = () => {
|
|
222
|
+
section.checkinPending = true;
|
|
223
|
+
void run(section, async () => {
|
|
224
|
+
try {
|
|
225
|
+
section.checkin = void 0;
|
|
226
|
+
try {
|
|
227
|
+
section.checkin = await call(section, "/checkin", {});
|
|
228
|
+
} catch {
|
|
229
|
+
section.checkin = { outcome: "failed" };
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
section.checkinPending = false;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
};
|
|
242
236
|
return button;
|
|
243
237
|
};
|
|
244
|
-
const setModelVisibility = (section, ids, hidden) => void run(section, async () => {
|
|
245
|
-
let completed = 0;
|
|
246
|
-
try {
|
|
247
|
-
for (const id of ids) {
|
|
248
|
-
await call(section, "/models/visibility", { model: id, hidden });
|
|
249
|
-
completed += 1;
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (completed > 0) section.errorMessage = format(hidden ? "hide_partial" : "restore_partial", { count: completed, total: ids.length });
|
|
253
|
-
throw error;
|
|
254
|
-
} finally {
|
|
255
|
-
try {
|
|
256
|
-
await reconcile(section);
|
|
257
|
-
} finally {
|
|
258
|
-
await refresh(section);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
238
|
const renderSection = (section) => {
|
|
263
239
|
const panel = node("div", "", "space-y-3 border-t border-border pt-4 first:border-t-0 first:pt-0");
|
|
264
240
|
panel.append(
|
|
@@ -268,7 +244,7 @@ function mount(el, props) {
|
|
|
268
244
|
panel.append(node("p", section.status ? words[section.status.status] ?? words.error : words.loading));
|
|
269
245
|
if (section.status?.expired) panel.append(node("p", words.expired, "text-destructive"));
|
|
270
246
|
if (section.status?.account) panel.append(node("p", `${words.account}: ${section.status.account}`));
|
|
271
|
-
if (section.error) panel.append(node("p",
|
|
247
|
+
if (section.error) panel.append(node("p", words.error, "text-destructive"));
|
|
272
248
|
const buttons = node("div", "", "flex flex-wrap gap-2");
|
|
273
249
|
if (section.status?.status === "not_connected") {
|
|
274
250
|
const input = node("input", "", "w-full rounded-md border border-input bg-background px-3 py-2 text-sm");
|
|
@@ -292,18 +268,37 @@ function mount(el, props) {
|
|
|
292
268
|
await reconcile(section);
|
|
293
269
|
await refresh(section);
|
|
294
270
|
})));
|
|
271
|
+
if (section.site.id === "qoder-cn") {
|
|
272
|
+
panel.append(checkinButton(section));
|
|
273
|
+
if (section.checkin) {
|
|
274
|
+
const result = section.checkin;
|
|
275
|
+
const amount = result.credit;
|
|
276
|
+
const detail = result.outcome === "claimed" && typeof amount === "number" && Number.isFinite(amount) && amount > 0 ? lang === "zh" ? `+${amount} \u79EF\u5206` : `+${amount} credits` : words[`checkin_${result.outcome}`] ?? words.checkin_failed;
|
|
277
|
+
const note = node("p", detail + (result.outcome === "failed" && result.message ? `: ${result.message}` : ""), result.outcome === "failed" ? "text-destructive" : "text-xs text-muted-foreground");
|
|
278
|
+
note.setAttribute("role", "status");
|
|
279
|
+
panel.append(note);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
295
282
|
buttons.append(action(section, "usage", () => void run(section, async () => {
|
|
296
283
|
section.usage = await call(section, "/usage", {});
|
|
297
284
|
})));
|
|
298
285
|
buttons.append(action(section, "disconnect", () => void run(section, async () => {
|
|
299
286
|
await call(section, "/disconnect", {});
|
|
300
287
|
section.usage = void 0;
|
|
288
|
+
section.checkin = void 0;
|
|
301
289
|
await reconcile(section);
|
|
302
290
|
await refresh(section);
|
|
303
291
|
})));
|
|
304
292
|
}
|
|
305
293
|
buttons.append(action(section, "refresh", () => void run(section, () => refresh(section))));
|
|
306
294
|
panel.append(buttons);
|
|
295
|
+
if (section.status?.status === "connected" && section.site.id === "qoder") panel.append(node("p", words.checkinDesktop, "text-sm text-muted-foreground"));
|
|
296
|
+
if (section.checkin) {
|
|
297
|
+
const result = section.checkin;
|
|
298
|
+
const amount = result.credit;
|
|
299
|
+
const detail = result.outcome === "claimed" && typeof amount === "number" && Number.isFinite(amount) && amount > 0 ? lang === "zh" ? `+${amount} \u79EF\u5206` : `+${amount} credits` : words[`checkin_${result.outcome}`] ?? words.checkin_failed;
|
|
300
|
+
panel.append(node("p", detail + (result.outcome === "failed" && result.message ? `: ${result.message}` : ""), result.outcome === "failed" ? "text-destructive" : "text-sm"));
|
|
301
|
+
}
|
|
307
302
|
const usageLine = node("p", "", "text-sm text-muted-foreground");
|
|
308
303
|
if (section.usage?.known && Array.isArray(section.usage.windows) && section.usage.windows.length) {
|
|
309
304
|
usageLine.textContent = `${section.usage.stale ? words.stale : words.used}: ` + section.usage.windows.map((w) => {
|
|
@@ -314,67 +309,18 @@ function mount(el, props) {
|
|
|
314
309
|
} else usageLine.textContent = words.unknown;
|
|
315
310
|
panel.append(usageLine);
|
|
316
311
|
if (section.status?.coolingUntil > Date.now()) panel.append(node("p", `${words.cooling}: ${new Date(section.status.coolingUntil).toLocaleString()}`, "text-sm text-muted-foreground"));
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (visible.length) {
|
|
330
|
-
const chips = node("div", "", "flex flex-wrap gap-2 pt-1");
|
|
331
|
-
for (const model of visible) {
|
|
332
|
-
const id = String(model.id);
|
|
333
|
-
const chip = node("span", id, CHIP);
|
|
334
|
-
if (model.context) chip.append(node("span", String(model.context), "font-medium text-muted-foreground/60"));
|
|
335
|
-
if (model.free) chip.append(node("span", words.free, "font-medium text-muted-foreground/60"));
|
|
336
|
-
const hide = node("button", "", CHIP_HIDE);
|
|
337
|
-
hide.type = "button";
|
|
338
|
-
hide.disabled = sectionBusy();
|
|
339
|
-
hide.setAttribute("aria-label", format("hide_model", { model: id }));
|
|
340
|
-
hide.setAttribute("aria-busy", String(section.busy && section.pendingModel === id));
|
|
341
|
-
hide.append(xIcon("h-3 w-3"));
|
|
342
|
-
hide.onclick = () => {
|
|
343
|
-
section.pendingModel = id;
|
|
344
|
-
setModelVisibility(section, [id], true);
|
|
345
|
-
};
|
|
346
|
-
chip.append(hide);
|
|
347
|
-
chips.append(chip);
|
|
348
|
-
}
|
|
349
|
-
panel.append(chips);
|
|
350
|
-
}
|
|
351
|
-
if (hiddenModels.length) {
|
|
352
|
-
const details = node("details", "", "max-w-md text-xs text-muted-foreground");
|
|
353
|
-
details.append(node("summary", format("hidden_models", { count: hiddenModels.length }), "cursor-pointer select-none font-medium"));
|
|
354
|
-
const list = node("ul", "", "mt-2 space-y-1");
|
|
355
|
-
for (const model of hiddenModels) {
|
|
356
|
-
const id = String(model.id);
|
|
357
|
-
const item = node("li", "", "flex items-center justify-between gap-2");
|
|
358
|
-
item.append(node("span", id, "break-all font-mono"));
|
|
359
|
-
const restore = smallButton(
|
|
360
|
-
words.restore,
|
|
361
|
-
"h-6 shrink-0 px-2 text-[11px]",
|
|
362
|
-
() => {
|
|
363
|
-
section.pendingModel = id;
|
|
364
|
-
setModelVisibility(section, [id], false);
|
|
365
|
-
}
|
|
366
|
-
);
|
|
367
|
-
restore.setAttribute("aria-label", format("restore_model", { model: id }));
|
|
368
|
-
item.append(restore);
|
|
369
|
-
list.append(item);
|
|
370
|
-
}
|
|
371
|
-
details.append(list);
|
|
372
|
-
details.append(smallButton(
|
|
373
|
-
words.restore_all,
|
|
374
|
-
"mt-2 h-7 px-2 text-[11px]",
|
|
375
|
-
() => setModelVisibility(section, hiddenModels.map((model) => String(model.id)), false)
|
|
376
|
-
));
|
|
377
|
-
panel.append(details);
|
|
312
|
+
panel.append(node("h4", words.models, "font-medium"));
|
|
313
|
+
if (!section.status?.models?.length) panel.append(node("p", words.noModels, "text-sm text-muted-foreground"));
|
|
314
|
+
for (const model of section.status?.models ?? []) {
|
|
315
|
+
const row = node("div", "", "flex items-center justify-between gap-2");
|
|
316
|
+
const label = `${model.id}${model.context ? ` \xB7 ${model.context}` : ""}${model.free ? ` \xB7 ${words.free}` : ""}`;
|
|
317
|
+
row.append(node("span", label, "text-sm"));
|
|
318
|
+
row.append(action(section, model.hidden ? "restore" : "hide", () => void run(section, async () => {
|
|
319
|
+
await call(section, "/models/visibility", { model: model.id, hidden: !model.hidden });
|
|
320
|
+
await reconcile(section);
|
|
321
|
+
await refresh(section);
|
|
322
|
+
})));
|
|
323
|
+
panel.append(row);
|
|
378
324
|
}
|
|
379
325
|
return panel;
|
|
380
326
|
};
|
package/dist/plugin.js
CHANGED
|
@@ -1,3 +1,211 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
var CHAT_PATH = "/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1";
|
|
3
|
+
var QODER_SITES = Object.freeze([
|
|
4
|
+
{
|
|
5
|
+
id: "qoder",
|
|
6
|
+
label: "Qoder",
|
|
7
|
+
openapi: "https://openapi.qoder.sh",
|
|
8
|
+
api: "https://api3.qoder.sh",
|
|
9
|
+
web: "https://qoder.com",
|
|
10
|
+
chatUrl: `https://api3.qoder.sh${CHAT_PATH}`,
|
|
11
|
+
provider: "qoder-subscription",
|
|
12
|
+
marker: "ccr-managed-qoder",
|
|
13
|
+
credentialFile: "qoder-credentials.json",
|
|
14
|
+
routeBase: "/api/qoder"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "qoder-cn",
|
|
18
|
+
label: "Qoder CN",
|
|
19
|
+
openapi: "https://openapi.qoder.com.cn",
|
|
20
|
+
api: "https://gateway.qoder.com.cn",
|
|
21
|
+
web: "https://qoder.cn",
|
|
22
|
+
chatUrl: `https://gateway.qoder.com.cn${CHAT_PATH}`,
|
|
23
|
+
provider: "qoder-cn-subscription",
|
|
24
|
+
marker: "ccr-managed-qoder-cn",
|
|
25
|
+
credentialFile: "qoder-cn-credentials.json",
|
|
26
|
+
routeBase: "/api/qoder/cn"
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
var QODER_CHAT = QODER_SITES[0].chatUrl;
|
|
30
|
+
var QODER_PROVIDER = QODER_SITES[0].provider;
|
|
31
|
+
var QODER_MARKER = QODER_SITES[0].marker;
|
|
32
|
+
function qoderSite(site) {
|
|
33
|
+
return QODER_SITES.find((s) => s.id === site) ?? QODER_SITES[0];
|
|
34
|
+
}
|
|
35
|
+
function qoderError(code, status = 422) {
|
|
36
|
+
return Object.assign(new Error(code), { code, statusCode: status, nonRetryable: true });
|
|
37
|
+
}
|
|
38
|
+
function isQoderManaged(p, site = "qoder") {
|
|
39
|
+
const managedBy = qoderSite(site);
|
|
40
|
+
const name = p?.name;
|
|
41
|
+
const owner = typeof name === "string" ? QODER_SITES.find((s) => s.provider === name) : void 0;
|
|
42
|
+
if (owner) {
|
|
43
|
+
return owner.id === managedBy.id && (p?.qoder_managed === true || p?.qoderManaged === true || (p?.api_key ?? p?.apiKey) === managedBy.marker);
|
|
44
|
+
}
|
|
45
|
+
if (managedBy.id !== QODER_SITES[0].id) return false;
|
|
46
|
+
return p?.qoder_managed === true || p?.qoderManaged === true;
|
|
47
|
+
}
|
|
48
|
+
function lockQoderProvider(p, site = "qoder") {
|
|
49
|
+
const managedBy = qoderSite(site);
|
|
50
|
+
return {
|
|
51
|
+
...p,
|
|
52
|
+
name: managedBy.provider,
|
|
53
|
+
qoder_managed: true,
|
|
54
|
+
qoderManaged: true,
|
|
55
|
+
api_base_url: managedBy.chatUrl,
|
|
56
|
+
api_key: managedBy.marker,
|
|
57
|
+
transformer: { use: [managedBy.id] }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function protectQoderConfig(next, previous) {
|
|
61
|
+
if (!next || typeof next !== "object") return;
|
|
62
|
+
const key = Array.isArray(next?.providers) && !Array.isArray(next?.Providers) ? "providers" : "Providers";
|
|
63
|
+
const old = previous?.Providers ?? previous?.providers ?? [];
|
|
64
|
+
const list = Array.isArray(next[key]) ? next[key] : [];
|
|
65
|
+
next[key] = list.map((p) => {
|
|
66
|
+
for (const managedBy of QODER_SITES) {
|
|
67
|
+
const previousEntry = old.find((o) => isQoderManaged(o, managedBy.id));
|
|
68
|
+
if (isQoderManaged(p, managedBy.id) || previousEntry && p?.name === managedBy.provider) {
|
|
69
|
+
return lockQoderProvider(p, managedBy.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return p;
|
|
73
|
+
});
|
|
74
|
+
for (const managedBy of QODER_SITES) {
|
|
75
|
+
const previousEntry = old.find((o) => isQoderManaged(o, managedBy.id));
|
|
76
|
+
if (previousEntry && !next[key].some((p) => isQoderManaged(p, managedBy.id))) {
|
|
77
|
+
next[key].push(lockQoderProvider(previousEntry, managedBy.id));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var configWriteQueues = /* @__PURE__ */ new Map();
|
|
82
|
+
function serializeQoderConfigWrite(configDir, fn) {
|
|
83
|
+
const previous = configWriteQueues.get(configDir) ?? Promise.resolve();
|
|
84
|
+
const job = previous.then(fn, fn);
|
|
85
|
+
configWriteQueues.set(configDir, job.catch(() => {
|
|
86
|
+
}));
|
|
87
|
+
return job;
|
|
88
|
+
}
|
|
89
|
+
var RETRY_AFTER_MIN_MS = 3e4;
|
|
90
|
+
var RETRY_AFTER_MAX_MS = 30 * 6e4;
|
|
91
|
+
var DEFAULT_429_COOLDOWN_MS = 3e5;
|
|
92
|
+
function classifyQoderError(status, body) {
|
|
93
|
+
if (status === 0) return { code: "qoder_upstream_error", message: "qoder_upstream_error", httpStatus: 502, retryable: true };
|
|
94
|
+
let hint = body;
|
|
95
|
+
if (typeof body === "string") {
|
|
96
|
+
try {
|
|
97
|
+
hint = JSON.parse(body);
|
|
98
|
+
} catch {
|
|
99
|
+
hint = void 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (status === 401) return { code: "qoder_reauth_required", message: "qoder_reauth_required", httpStatus: 401, retryable: false };
|
|
103
|
+
if (status === 429) {
|
|
104
|
+
const seconds = Number(hint?.retry_after);
|
|
105
|
+
const cooldownMs = Number.isFinite(seconds) && seconds > 0 ? Math.min(Math.max(seconds * 1e3, RETRY_AFTER_MIN_MS), RETRY_AFTER_MAX_MS) : DEFAULT_429_COOLDOWN_MS;
|
|
106
|
+
return { code: "qoder_rate_limited", message: "qoder_rate_limited", httpStatus: 429, cooldownMs, retryable: false };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
code: "qoder_upstream_error",
|
|
110
|
+
message: "qoder_upstream_error",
|
|
111
|
+
httpStatus: status,
|
|
112
|
+
retryable: status >= 500
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function isQoderErrorEnvelope(body) {
|
|
116
|
+
return body?.error?.type === "qoder_error";
|
|
117
|
+
}
|
|
118
|
+
function validPAT(value) {
|
|
119
|
+
return typeof value === "string" && value.startsWith("pt-") && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/checkin.ts
|
|
123
|
+
var CAMPAIGNS = "/sash/api/v1/me/campaigns";
|
|
124
|
+
var failed = (message) => ({ outcome: "failed", message });
|
|
125
|
+
var record = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
126
|
+
var creditOf = (benefit) => {
|
|
127
|
+
const amount = record(benefit).amount;
|
|
128
|
+
const n = typeof amount === "number" || typeof amount === "string" ? Number(amount) : NaN;
|
|
129
|
+
return Number.isFinite(n) && n > 0 ? { credit: n } : {};
|
|
130
|
+
};
|
|
131
|
+
var RFC3339 = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|[+-]\d\d:\d\d)$/i;
|
|
132
|
+
var at = (v) => {
|
|
133
|
+
if (typeof v === "string" && RFC3339.test(v)) {
|
|
134
|
+
const date = Date.parse(v);
|
|
135
|
+
return Number.isFinite(date) ? date : void 0;
|
|
136
|
+
}
|
|
137
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
|
|
138
|
+
return Number.isFinite(n) && n > 0 ? n < 1e12 ? n * 1e3 : n : void 0;
|
|
139
|
+
};
|
|
140
|
+
async function checkinQoder(credential, options) {
|
|
141
|
+
const machine = options.machine ?? {};
|
|
142
|
+
if (options.site === "qoder" && (!machine["Cosy-MachineToken"] || !machine["Cosy-MachineType"])) {
|
|
143
|
+
return { outcome: "ineligible", message: "International check-in requires Qoder desktop device identity" };
|
|
144
|
+
}
|
|
145
|
+
const origin = qoderSite(options.site).openapi;
|
|
146
|
+
let token = credential.deviceToken || credential.access;
|
|
147
|
+
if (!token) return failed("Check-in requires a connected account");
|
|
148
|
+
let rotated = false;
|
|
149
|
+
const network = options.fetch ?? fetch;
|
|
150
|
+
const call = async (url, method) => {
|
|
151
|
+
const request = () => network(url, {
|
|
152
|
+
method,
|
|
153
|
+
redirect: "error",
|
|
154
|
+
signal: AbortSignal.timeout(2e4),
|
|
155
|
+
dispatcher: options.host.getSubscriptionDispatcher?.() ?? options.host.getDirectDispatcher(),
|
|
156
|
+
headers: { ...machine, Accept: "application/json", "Accept-Encoding": "identity", "Content-Type": "application/json", Authorization: `Bearer ${token}`, "Cosy-ClientType": "10", "User-Agent": "Qoder" },
|
|
157
|
+
...method === "POST" ? { body: "{}" } : {}
|
|
158
|
+
});
|
|
159
|
+
let response = await request();
|
|
160
|
+
if ((response.status === 401 || response.status === 403) && credential.deviceToken && options.refreshDeviceToken && !rotated) {
|
|
161
|
+
rotated = true;
|
|
162
|
+
await response.body?.cancel();
|
|
163
|
+
token = await options.refreshDeviceToken();
|
|
164
|
+
if (!token) throw new Error("missing rotated token");
|
|
165
|
+
response = await request();
|
|
166
|
+
}
|
|
167
|
+
return response;
|
|
168
|
+
};
|
|
169
|
+
try {
|
|
170
|
+
const listResponse = await call(origin + CAMPAIGNS, "GET");
|
|
171
|
+
if (listResponse.status !== 200) {
|
|
172
|
+
await listResponse.body?.cancel();
|
|
173
|
+
return failed(`Campaigns: HTTP ${listResponse.status}`);
|
|
174
|
+
}
|
|
175
|
+
let list;
|
|
176
|
+
try {
|
|
177
|
+
list = record(await listResponse.json());
|
|
178
|
+
} catch {
|
|
179
|
+
return failed("Campaigns: unreadable reply");
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(list.campaigns)) return failed("Campaigns: invalid reply");
|
|
182
|
+
const campaigns = list.campaigns.map(record).filter((c) => c.actionType === "CLAIM_BENEFIT");
|
|
183
|
+
const campaign = campaigns.find((c) => c.claimStatus === "CLAIMABLE");
|
|
184
|
+
if (!campaign) {
|
|
185
|
+
const now = options.now?.() ?? Date.now();
|
|
186
|
+
const done = campaigns.find((c) => c.claimStatus === "CLAIMED" && (at(c.startAt) ?? -Infinity) <= now && now < (at(c.endAt) ?? Infinity));
|
|
187
|
+
return done ? { outcome: "done", ...creditOf(done.benefit) } : { outcome: "inactive" };
|
|
188
|
+
}
|
|
189
|
+
if (typeof campaign.campaignId !== "string" || !campaign.campaignId || campaign.campaignId.length > 1024) return failed("Campaigns: missing campaign id");
|
|
190
|
+
const claimResponse = await call(`${origin}${CAMPAIGNS}/${encodeURIComponent(campaign.campaignId)}/claim`, "POST");
|
|
191
|
+
if (claimResponse.status !== 200) {
|
|
192
|
+
await claimResponse.body?.cancel();
|
|
193
|
+
return failed(`Claim: HTTP ${claimResponse.status}`);
|
|
194
|
+
}
|
|
195
|
+
let claim;
|
|
196
|
+
try {
|
|
197
|
+
claim = record(await claimResponse.json());
|
|
198
|
+
} catch {
|
|
199
|
+
return failed("Claim: unreadable reply");
|
|
200
|
+
}
|
|
201
|
+
const data = record(claim.data);
|
|
202
|
+
if ((claim.status ?? data.status) !== "CLAIMED") return failed("Claim: unexpected reply");
|
|
203
|
+
return { outcome: "claimed", ...creditOf(claim.benefit ?? data.benefit ?? campaign.benefit) };
|
|
204
|
+
} catch {
|
|
205
|
+
return failed("Check-in request failed");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
1
209
|
// src/auth-service.ts
|
|
2
210
|
import { constants as constants2 } from "node:fs";
|
|
3
211
|
import { open, mkdir, rename, rm, unlink } from "node:fs/promises";
|
|
@@ -161,8 +369,8 @@ ${path}`).digest("hex");
|
|
|
161
369
|
const expiresAt = (jt) => now() + (jt.expires_in > 0 ? jt.expires_in : DAY);
|
|
162
370
|
function exchangeExpiry(jt) {
|
|
163
371
|
for (const k of ["expire_time", "expires_at"]) {
|
|
164
|
-
const
|
|
165
|
-
if (!isNaN(
|
|
372
|
+
const at2 = Date.parse(String(jt?.[k] ?? "").trim());
|
|
373
|
+
if (!isNaN(at2)) return at2;
|
|
166
374
|
}
|
|
167
375
|
if (jt?.expires_in > 0) return now() + jt.expires_in;
|
|
168
376
|
return now() + DAY;
|
|
@@ -343,15 +551,15 @@ ${path}`).digest("hex");
|
|
|
343
551
|
function fitEffort(want, levels) {
|
|
344
552
|
if (want === "ultra" && !levels.includes(want)) want = "max";
|
|
345
553
|
if (!levels.length || levels.includes(want)) return want;
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
554
|
+
const at2 = EFFORT_RANK.indexOf(want);
|
|
555
|
+
if (at2 < 0) return want;
|
|
348
556
|
let best = want;
|
|
349
557
|
let dist = EFFORT_RANK.length;
|
|
350
558
|
for (const l of levels) {
|
|
351
559
|
const i = EFFORT_RANK.indexOf(l);
|
|
352
560
|
if (i < 0 || l === "none") continue;
|
|
353
|
-
const d = Math.abs(i -
|
|
354
|
-
if (d < dist || d === dist && i >
|
|
561
|
+
const d = Math.abs(i - at2);
|
|
562
|
+
if (d < dist || d === dist && i > at2) [best, dist] = [l, d];
|
|
355
563
|
}
|
|
356
564
|
return best;
|
|
357
565
|
}
|
|
@@ -381,10 +589,10 @@ ${path}`).digest("hex");
|
|
|
381
589
|
return out;
|
|
382
590
|
}
|
|
383
591
|
const textOf = (c) => typeof c === "string" ? c : blocksOf(c).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
384
|
-
function callID(
|
|
592
|
+
function callID(at2, k, c) {
|
|
385
593
|
const args = c.function?.arguments;
|
|
386
594
|
const said = typeof args === "string" ? args : JSON.stringify(args ?? {});
|
|
387
|
-
return "call_" + createHash("sha256").update(`${
|
|
595
|
+
return "call_" + createHash("sha256").update(`${at2}\0${k}\0${c.function?.name ?? ""}\0${said}`).digest("hex").slice(0, 32);
|
|
388
596
|
}
|
|
389
597
|
function qoderMessages(msgs) {
|
|
390
598
|
const out = [];
|
|
@@ -410,8 +618,8 @@ ${path}`).digest("hex");
|
|
|
410
618
|
let txt = textOf(m.content);
|
|
411
619
|
const ims = Array.isArray(m.content) ? m.content.filter((p) => p?.type === "image_url").map(imageBlock).filter(Boolean) : [];
|
|
412
620
|
const named = String(m.tool_call_id ?? "").trim();
|
|
413
|
-
const
|
|
414
|
-
const pick =
|
|
621
|
+
const at2 = named ? turn?.open.findIndex((id) => id === named) ?? -1 : -1;
|
|
622
|
+
const pick = at2 >= 0 ? at2 : turn ? turn.open.findIndex((id) => !turn.kept.has(id)) : -1;
|
|
415
623
|
if (pick < 0) {
|
|
416
624
|
asText(txt, ims);
|
|
417
625
|
continue;
|
|
@@ -467,10 +675,10 @@ ${path}`).digest("hex");
|
|
|
467
675
|
const out = msgs.map((m) => m.role === "user" && Array.isArray(m.content) ? { ...m, contents: m.content } : m);
|
|
468
676
|
const last = out.at(-1);
|
|
469
677
|
if (last?.role !== "user" || !Array.isArray(last.content)) return out;
|
|
470
|
-
const
|
|
471
|
-
if (
|
|
678
|
+
const at2 = last.content.findLastIndex((b) => b?.type && !UNCACHED.has(b.type));
|
|
679
|
+
if (at2 < 0) return out;
|
|
472
680
|
const blocks = last.content.slice();
|
|
473
|
-
blocks[
|
|
681
|
+
blocks[at2] = { ...blocks[at2], cache_control: { type: "ephemeral" } };
|
|
474
682
|
out[out.length - 1] = { ...last, content: blocks, contents: blocks };
|
|
475
683
|
return out;
|
|
476
684
|
}
|
|
@@ -491,8 +699,8 @@ ${path}`).digest("hex");
|
|
|
491
699
|
const taskName = (prompt) => prompt.length > 10 ? prompt.slice(0, 10) : prompt;
|
|
492
700
|
function taskOf(sid, chat2, atTime = now()) {
|
|
493
701
|
const msgs = chat2.messages ?? [];
|
|
494
|
-
const
|
|
495
|
-
const key = createHash("sha256").update(JSON.stringify([sid,
|
|
702
|
+
const at2 = msgs.findLastIndex((x) => x?.role === "user");
|
|
703
|
+
const key = createHash("sha256").update(JSON.stringify([sid, at2, at2 < 0 ? null : msgs[at2].content ?? null])).digest("hex");
|
|
496
704
|
for (const [k, t2] of TASKS) {
|
|
497
705
|
if (atTime - t2.used <= TASK_IDLE) break;
|
|
498
706
|
TASKS.delete(k);
|
|
@@ -500,7 +708,7 @@ ${path}`).digest("hex");
|
|
|
500
708
|
let t = TASKS.get(key);
|
|
501
709
|
if (t && atTime - t.used > TASK_IDLE) t = void 0;
|
|
502
710
|
TASKS.delete(key);
|
|
503
|
-
t ??= { id: randomUUID(), begin: atTime, answered: false, name: taskName(
|
|
711
|
+
t ??= { id: randomUUID(), begin: atTime, answered: false, name: taskName(at2 < 0 ? "" : textOf(msgs[at2].content)) };
|
|
504
712
|
t.used = atTime;
|
|
505
713
|
TASKS.set(key, t);
|
|
506
714
|
while (TASKS.size > TASK_MAX) TASKS.delete(TASKS.keys().next().value);
|
|
@@ -515,9 +723,9 @@ ${path}`).digest("hex");
|
|
|
515
723
|
const isObj = (v) => v && typeof v === "object" && !Array.isArray(v);
|
|
516
724
|
const target = (ref) => {
|
|
517
725
|
if (!ref.startsWith("#/")) return void 0;
|
|
518
|
-
let
|
|
519
|
-
for (const k of ref.slice(2).split("/"))
|
|
520
|
-
return isObj(
|
|
726
|
+
let at2 = root;
|
|
727
|
+
for (const k of ref.slice(2).split("/")) at2 = at2?.[k.replaceAll("~1", "/").replaceAll("~0", "~")];
|
|
728
|
+
return isObj(at2) ? at2 : void 0;
|
|
521
729
|
};
|
|
522
730
|
const object = (b, seen = []) => {
|
|
523
731
|
if (!isObj(b)) return false;
|
|
@@ -718,7 +926,7 @@ ${path}`).digest("hex");
|
|
|
718
926
|
async function queueWait(api, cred, ask, first, signal, deadline, site2) {
|
|
719
927
|
let q = first ?? {};
|
|
720
928
|
let asked = false;
|
|
721
|
-
let
|
|
929
|
+
let failed2 = 0;
|
|
722
930
|
for (; ; ) {
|
|
723
931
|
checkAbort(signal);
|
|
724
932
|
if (QUEUE.now() >= deadline) return false;
|
|
@@ -731,11 +939,11 @@ ${path}`).digest("hex");
|
|
|
731
939
|
} catch (e) {
|
|
732
940
|
checkAbort(signal);
|
|
733
941
|
if (e instanceof QueueEnd) return false;
|
|
734
|
-
if (++
|
|
942
|
+
if (++failed2 >= QUEUE.failures) return false;
|
|
735
943
|
q = { ...q, retryAfterSeconds: void 0 };
|
|
736
944
|
continue;
|
|
737
945
|
}
|
|
738
|
-
|
|
946
|
+
failed2 = 0;
|
|
739
947
|
q = { ...st, modelKey: st.modelKey ?? q.modelKey, queueType: st.queueType ?? q.queueType };
|
|
740
948
|
if (st.isQueued === false) {
|
|
741
949
|
if (st.retryAfterSeconds > 0) await QUEUE.pause(Math.min(queuePause(st.retryAfterSeconds), Math.max(0, deadline - QUEUE.now())), signal);
|
|
@@ -863,21 +1071,21 @@ ${path}`).digest("hex");
|
|
|
863
1071
|
// when the turn ended in an error
|
|
864
1072
|
bare(out, comb, x, end) {
|
|
865
1073
|
const head = comb.slice(0, x);
|
|
866
|
-
const
|
|
867
|
-
const opener =
|
|
1074
|
+
const at2 = comb.slice(x);
|
|
1075
|
+
const opener = at2.match(/^<[^<>]*>?/)[0];
|
|
868
1076
|
if (quoted(this.sent + head)) {
|
|
869
1077
|
this.text(out, head + opener);
|
|
870
|
-
return
|
|
1078
|
+
return at2.slice(opener.length);
|
|
871
1079
|
}
|
|
872
|
-
const b = xmlCall(
|
|
1080
|
+
const b = xmlCall(at2, end, this.tools);
|
|
873
1081
|
if (b.wait) {
|
|
874
1082
|
if (head) this.text(out, head);
|
|
875
|
-
this.textBuf =
|
|
1083
|
+
this.textBuf = at2;
|
|
876
1084
|
return null;
|
|
877
1085
|
}
|
|
878
1086
|
if (b.text) {
|
|
879
1087
|
this.text(out, head + opener);
|
|
880
|
-
return
|
|
1088
|
+
return at2.slice(opener.length);
|
|
881
1089
|
}
|
|
882
1090
|
if (head) this.text(out, head);
|
|
883
1091
|
if (b.error) {
|
|
@@ -1220,9 +1428,9 @@ ${path}`).digest("hex");
|
|
|
1220
1428
|
if (x < -4 || x >= 6) return `${m}e${x < 0 ? "-" : "+"}${String(Math.abs(x)).padStart(2, "0")}`;
|
|
1221
1429
|
return String(n);
|
|
1222
1430
|
}
|
|
1223
|
-
const
|
|
1431
|
+
const RFC33392 = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|[+-]\d\d:\d\d)$/i;
|
|
1224
1432
|
function when(v) {
|
|
1225
|
-
if (typeof v === "string" &&
|
|
1433
|
+
if (typeof v === "string" && RFC33392.test(v) && !isNaN(Date.parse(v))) return new Date(v).toISOString();
|
|
1226
1434
|
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v.trim()) : NaN;
|
|
1227
1435
|
if (!isFinite(n) || n <= 0) return void 0;
|
|
1228
1436
|
return new Date(Math.trunc(n < 1e12 ? n * 1e3 : n)).toISOString();
|
|
@@ -1471,127 +1679,6 @@ ${path}`).digest("hex");
|
|
|
1471
1679
|
};
|
|
1472
1680
|
}
|
|
1473
1681
|
|
|
1474
|
-
// src/protocol.ts
|
|
1475
|
-
var CHAT_PATH = "/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1";
|
|
1476
|
-
var QODER_SITES = Object.freeze([
|
|
1477
|
-
{
|
|
1478
|
-
id: "qoder",
|
|
1479
|
-
label: "Qoder",
|
|
1480
|
-
openapi: "https://openapi.qoder.sh",
|
|
1481
|
-
api: "https://api3.qoder.sh",
|
|
1482
|
-
web: "https://qoder.com",
|
|
1483
|
-
chatUrl: `https://api3.qoder.sh${CHAT_PATH}`,
|
|
1484
|
-
provider: "qoder-subscription",
|
|
1485
|
-
marker: "ccr-managed-qoder",
|
|
1486
|
-
credentialFile: "qoder-credentials.json",
|
|
1487
|
-
routeBase: "/api/qoder"
|
|
1488
|
-
},
|
|
1489
|
-
{
|
|
1490
|
-
id: "qoder-cn",
|
|
1491
|
-
label: "Qoder CN",
|
|
1492
|
-
openapi: "https://openapi.qoder.com.cn",
|
|
1493
|
-
api: "https://gateway.qoder.com.cn",
|
|
1494
|
-
web: "https://qoder.cn",
|
|
1495
|
-
chatUrl: `https://gateway.qoder.com.cn${CHAT_PATH}`,
|
|
1496
|
-
provider: "qoder-cn-subscription",
|
|
1497
|
-
marker: "ccr-managed-qoder-cn",
|
|
1498
|
-
credentialFile: "qoder-cn-credentials.json",
|
|
1499
|
-
routeBase: "/api/qoder/cn"
|
|
1500
|
-
}
|
|
1501
|
-
]);
|
|
1502
|
-
var QODER_CHAT = QODER_SITES[0].chatUrl;
|
|
1503
|
-
var QODER_PROVIDER = QODER_SITES[0].provider;
|
|
1504
|
-
var QODER_MARKER = QODER_SITES[0].marker;
|
|
1505
|
-
function qoderSite(site) {
|
|
1506
|
-
return QODER_SITES.find((s) => s.id === site) ?? QODER_SITES[0];
|
|
1507
|
-
}
|
|
1508
|
-
function qoderError(code, status = 422) {
|
|
1509
|
-
return Object.assign(new Error(code), { code, statusCode: status, nonRetryable: true });
|
|
1510
|
-
}
|
|
1511
|
-
function isQoderManaged(p, site = "qoder") {
|
|
1512
|
-
const managedBy = qoderSite(site);
|
|
1513
|
-
const name = p?.name;
|
|
1514
|
-
const owner = typeof name === "string" ? QODER_SITES.find((s) => s.provider === name) : void 0;
|
|
1515
|
-
if (owner) {
|
|
1516
|
-
return owner.id === managedBy.id && (p?.qoder_managed === true || p?.qoderManaged === true || (p?.api_key ?? p?.apiKey) === managedBy.marker);
|
|
1517
|
-
}
|
|
1518
|
-
if (managedBy.id !== QODER_SITES[0].id) return false;
|
|
1519
|
-
return p?.qoder_managed === true || p?.qoderManaged === true;
|
|
1520
|
-
}
|
|
1521
|
-
function lockQoderProvider(p, site = "qoder") {
|
|
1522
|
-
const managedBy = qoderSite(site);
|
|
1523
|
-
return {
|
|
1524
|
-
...p,
|
|
1525
|
-
name: managedBy.provider,
|
|
1526
|
-
qoder_managed: true,
|
|
1527
|
-
qoderManaged: true,
|
|
1528
|
-
api_base_url: managedBy.chatUrl,
|
|
1529
|
-
api_key: managedBy.marker,
|
|
1530
|
-
transformer: { use: [managedBy.id] }
|
|
1531
|
-
};
|
|
1532
|
-
}
|
|
1533
|
-
function protectQoderConfig(next, previous) {
|
|
1534
|
-
if (!next || typeof next !== "object") return;
|
|
1535
|
-
const key = Array.isArray(next?.providers) && !Array.isArray(next?.Providers) ? "providers" : "Providers";
|
|
1536
|
-
const old = previous?.Providers ?? previous?.providers ?? [];
|
|
1537
|
-
const list = Array.isArray(next[key]) ? next[key] : [];
|
|
1538
|
-
next[key] = list.map((p) => {
|
|
1539
|
-
for (const managedBy of QODER_SITES) {
|
|
1540
|
-
const previousEntry = old.find((o) => isQoderManaged(o, managedBy.id));
|
|
1541
|
-
if (isQoderManaged(p, managedBy.id) || previousEntry && p?.name === managedBy.provider) {
|
|
1542
|
-
return lockQoderProvider(p, managedBy.id);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
return p;
|
|
1546
|
-
});
|
|
1547
|
-
for (const managedBy of QODER_SITES) {
|
|
1548
|
-
const previousEntry = old.find((o) => isQoderManaged(o, managedBy.id));
|
|
1549
|
-
if (previousEntry && !next[key].some((p) => isQoderManaged(p, managedBy.id))) {
|
|
1550
|
-
next[key].push(lockQoderProvider(previousEntry, managedBy.id));
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
var configWriteQueues = /* @__PURE__ */ new Map();
|
|
1555
|
-
function serializeQoderConfigWrite(configDir, fn) {
|
|
1556
|
-
const previous = configWriteQueues.get(configDir) ?? Promise.resolve();
|
|
1557
|
-
const job = previous.then(fn, fn);
|
|
1558
|
-
configWriteQueues.set(configDir, job.catch(() => {
|
|
1559
|
-
}));
|
|
1560
|
-
return job;
|
|
1561
|
-
}
|
|
1562
|
-
var RETRY_AFTER_MIN_MS = 3e4;
|
|
1563
|
-
var RETRY_AFTER_MAX_MS = 30 * 6e4;
|
|
1564
|
-
var DEFAULT_429_COOLDOWN_MS = 3e5;
|
|
1565
|
-
function classifyQoderError(status, body) {
|
|
1566
|
-
if (status === 0) return { code: "qoder_upstream_error", message: "qoder_upstream_error", httpStatus: 502, retryable: true };
|
|
1567
|
-
let hint = body;
|
|
1568
|
-
if (typeof body === "string") {
|
|
1569
|
-
try {
|
|
1570
|
-
hint = JSON.parse(body);
|
|
1571
|
-
} catch {
|
|
1572
|
-
hint = void 0;
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
if (status === 401) return { code: "qoder_reauth_required", message: "qoder_reauth_required", httpStatus: 401, retryable: false };
|
|
1576
|
-
if (status === 429) {
|
|
1577
|
-
const seconds = Number(hint?.retry_after);
|
|
1578
|
-
const cooldownMs = Number.isFinite(seconds) && seconds > 0 ? Math.min(Math.max(seconds * 1e3, RETRY_AFTER_MIN_MS), RETRY_AFTER_MAX_MS) : DEFAULT_429_COOLDOWN_MS;
|
|
1579
|
-
return { code: "qoder_rate_limited", message: "qoder_rate_limited", httpStatus: 429, cooldownMs, retryable: false };
|
|
1580
|
-
}
|
|
1581
|
-
return {
|
|
1582
|
-
code: "qoder_upstream_error",
|
|
1583
|
-
message: "qoder_upstream_error",
|
|
1584
|
-
httpStatus: status,
|
|
1585
|
-
retryable: status >= 500
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
function isQoderErrorEnvelope(body) {
|
|
1589
|
-
return body?.error?.type === "qoder_error";
|
|
1590
|
-
}
|
|
1591
|
-
function validPAT(value) {
|
|
1592
|
-
return typeof value === "string" && value.startsWith("pt-") && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
1682
|
// src/auth-service.ts
|
|
1596
1683
|
var REFRESH_LEAD_MS = 5 * 6e4;
|
|
1597
1684
|
var USAGE_CACHE_MS = 6e4;
|
|
@@ -1624,9 +1711,9 @@ function validate(value) {
|
|
|
1624
1711
|
}
|
|
1625
1712
|
function maskEmail(email) {
|
|
1626
1713
|
if (!email) return null;
|
|
1627
|
-
const
|
|
1628
|
-
if (
|
|
1629
|
-
return `${email[0]}***${email.slice(
|
|
1714
|
+
const at2 = email.indexOf("@");
|
|
1715
|
+
if (at2 <= 0) return null;
|
|
1716
|
+
return `${email[0]}***${email.slice(at2)}`;
|
|
1630
1717
|
}
|
|
1631
1718
|
function toRecord(m) {
|
|
1632
1719
|
return { key: String(m.key), name: String(m.name ?? m.key), context: Number(m.context) || 0, free: m.free === true };
|
|
@@ -1780,17 +1867,17 @@ var QoderAuthService = class {
|
|
|
1780
1867
|
const before = structuredClone(cfg);
|
|
1781
1868
|
const key = Array.isArray(cfg.providers) && !Array.isArray(cfg.Providers) ? "providers" : "Providers";
|
|
1782
1869
|
const list = cfg[key] ?? [];
|
|
1783
|
-
const
|
|
1870
|
+
const at2 = list.findIndex((p) => isQoderManaged(p, this.site.id));
|
|
1784
1871
|
if (saved) {
|
|
1785
|
-
if (
|
|
1872
|
+
if (at2 < 0 && list.some((p) => p?.name === this.site.provider)) throw qoderError("qoder_provider_name_conflict");
|
|
1786
1873
|
const provider = lockQoderProvider({
|
|
1787
|
-
...
|
|
1874
|
+
...at2 >= 0 ? list[at2] : {},
|
|
1788
1875
|
models: saved.models.filter((m) => !saved.hiddenModels.includes(m.key)).map((m) => m.key),
|
|
1789
|
-
enabled:
|
|
1876
|
+
enabled: at2 >= 0 ? list[at2].enabled !== false : true
|
|
1790
1877
|
}, this.site.id);
|
|
1791
|
-
if (
|
|
1878
|
+
if (at2 >= 0) list[at2] = provider;
|
|
1792
1879
|
else list.push(provider);
|
|
1793
|
-
} else if (
|
|
1880
|
+
} else if (at2 >= 0) list.splice(at2, 1);
|
|
1794
1881
|
cfg[key] = list;
|
|
1795
1882
|
try {
|
|
1796
1883
|
await this.options.host.writeConfig(cfg);
|
|
@@ -1946,6 +2033,22 @@ var QoderAuthService = class {
|
|
|
1946
2033
|
return { success: true };
|
|
1947
2034
|
});
|
|
1948
2035
|
}
|
|
2036
|
+
/** Manual check-in is independent of chat cooldown and model selection.
|
|
2037
|
+
* CN transactions join the exclusive lane so concurrent requests cannot
|
|
2038
|
+
* double-claim (the second observes the upstream's post-claim state).
|
|
2039
|
+
* The intl gate stays outside the lane: it does no network work and must
|
|
2040
|
+
* return immediately even while a CN transaction is queued. */
|
|
2041
|
+
async checkin() {
|
|
2042
|
+
if (this.site.id === "qoder") return { outcome: "ineligible", message: "International check-in requires Qoder desktop device identity" };
|
|
2043
|
+
return this.exclusive(async () => {
|
|
2044
|
+
try {
|
|
2045
|
+
const credential = await this.freshCredential();
|
|
2046
|
+
return await checkinQoder(credential, { site: this.site.id, host: this.options.host, fetch: this.options.fetch });
|
|
2047
|
+
} catch {
|
|
2048
|
+
return { outcome: "failed", message: "Check-in requires a usable connected account" };
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
1949
2052
|
async getUsage() {
|
|
1950
2053
|
await this.load();
|
|
1951
2054
|
if (!this.saved || this.disconnecting) throw qoderError("qoder_not_connected", 409);
|
|
@@ -2409,6 +2512,7 @@ function registerQoderAdminRoutes(app, service, site = "qoder") {
|
|
|
2409
2512
|
}],
|
|
2410
2513
|
["POST", "sync", () => service.syncModels()],
|
|
2411
2514
|
["POST", "usage", () => service.getUsage()],
|
|
2515
|
+
["POST", "checkin", () => service.checkin()],
|
|
2412
2516
|
["POST", "models/visibility", (body) => {
|
|
2413
2517
|
if (typeof body?.model !== "string" || typeof body.hidden !== "boolean") throw Object.assign(new Error(), { code: "qoder_model_unknown", statusCode: 422 });
|
|
2414
2518
|
return service.setModelHidden(body.model, body.hidden);
|
|
@@ -2444,23 +2548,26 @@ var qoderLocales = {
|
|
|
2444
2548
|
error: "Management request failed",
|
|
2445
2549
|
account: "Account",
|
|
2446
2550
|
models: "Models",
|
|
2447
|
-
models_count: "{{count}} models",
|
|
2448
2551
|
pat: "Personal access token",
|
|
2449
2552
|
patPlaceholder: "pt-\u2026",
|
|
2450
2553
|
connect: "Connect",
|
|
2554
|
+
checkin: "Check in",
|
|
2555
|
+
checkin_action: "Check in",
|
|
2556
|
+
checkin_pending: "Checking in\u2026",
|
|
2557
|
+
checkin_claimed: "Credits claimed",
|
|
2558
|
+
checkin_done: "Already claimed today",
|
|
2559
|
+
checkin_inactive: "No campaign today",
|
|
2560
|
+
checkin_ineligible: "Unavailable",
|
|
2561
|
+
checkin_failed: "Check-in failed",
|
|
2562
|
+
checkinDesktop: "International check-in requires Qoder desktop device identity; claim in Qoder desktop.",
|
|
2451
2563
|
sync: "Refresh models",
|
|
2452
2564
|
usage: "Refresh quota",
|
|
2453
2565
|
unknown: "Quota unknown",
|
|
2454
2566
|
stale: "Last known quota (stale)",
|
|
2455
2567
|
used: "Allowance",
|
|
2456
|
-
restore_partial: "Partially completed: restored {{count}} of {{total}} models. Management request failed.",
|
|
2457
|
-
hide_partial: "Partially completed: hid {{count}} of {{total}} models. Management request failed.",
|
|
2458
|
-
restore_model: "Restore {{model}}",
|
|
2459
2568
|
disconnect: "Disconnect",
|
|
2460
|
-
|
|
2461
|
-
hidden_models: "Hidden models ({{count}})",
|
|
2569
|
+
hide: "Hide",
|
|
2462
2570
|
restore: "Restore",
|
|
2463
|
-
restore_all: "Restore all",
|
|
2464
2571
|
cooling: "Rate limited until",
|
|
2465
2572
|
noModels: "Refresh models after connecting",
|
|
2466
2573
|
free: "free",
|
|
@@ -2478,23 +2585,26 @@ var qoderLocales = {
|
|
|
2478
2585
|
error: "\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25",
|
|
2479
2586
|
account: "\u8D26\u53F7",
|
|
2480
2587
|
models: "\u6A21\u578B",
|
|
2481
|
-
models_count: "{{count}} \u4E2A\u6A21\u578B",
|
|
2482
2588
|
pat: "\u4E2A\u4EBA\u8BBF\u95EE\u4EE4\u724C",
|
|
2483
2589
|
patPlaceholder: "pt-\u2026",
|
|
2484
2590
|
connect: "\u8FDE\u63A5",
|
|
2591
|
+
checkin: "\u7B7E\u5230",
|
|
2592
|
+
checkin_action: "\u7B7E\u5230",
|
|
2593
|
+
checkin_pending: "\u7B7E\u5230\u4E2D\u2026",
|
|
2594
|
+
checkin_claimed: "\u9886\u53D6\u6210\u529F",
|
|
2595
|
+
checkin_done: "\u4ECA\u65E5\u5DF2\u9886",
|
|
2596
|
+
checkin_inactive: "\u4ECA\u65E5\u65E0\u6D3B\u52A8",
|
|
2597
|
+
checkin_ineligible: "\u4E0D\u53EF\u7528",
|
|
2598
|
+
checkin_failed: "\u7B7E\u5230\u5931\u8D25",
|
|
2599
|
+
checkinDesktop: "\u56FD\u9645\u7AD9\u9700\u8981\u5728 Qoder \u684C\u9762\u7AEF\u5185\u7B7E\u5230\u3002",
|
|
2485
2600
|
sync: "\u5237\u65B0\u6A21\u578B",
|
|
2486
2601
|
usage: "\u5237\u65B0\u989D\u5EA6",
|
|
2487
2602
|
unknown: "\u989D\u5EA6\u672A\u77E5",
|
|
2488
2603
|
stale: "\u4E0A\u6B21\u989D\u5EA6\uFF08\u5DF2\u8FC7\u671F\uFF09",
|
|
2489
2604
|
used: "\u5DF2\u7528\u989D\u5EA6",
|
|
2490
|
-
restore_partial: "\u90E8\u5206\u5B8C\u6210\uFF1A\u5DF2\u6062\u590D {{count}} \u4E2A\u6A21\u578B\uFF0C\u5171 {{total}} \u4E2A\u3002\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25\u3002",
|
|
2491
|
-
hide_partial: "\u90E8\u5206\u5B8C\u6210\uFF1A\u5DF2\u9690\u85CF {{count}} \u4E2A\u6A21\u578B\uFF0C\u5171 {{total}} \u4E2A\u3002\u7BA1\u7406\u64CD\u4F5C\u5931\u8D25\u3002",
|
|
2492
|
-
restore_model: "\u6062\u590D {{model}}",
|
|
2493
2605
|
disconnect: "\u65AD\u5F00\u8FDE\u63A5",
|
|
2494
|
-
|
|
2495
|
-
hidden_models: "\u5DF2\u9690\u85CF\u6A21\u578B\uFF08{{count}}\uFF09",
|
|
2606
|
+
hide: "\u9690\u85CF",
|
|
2496
2607
|
restore: "\u6062\u590D",
|
|
2497
|
-
restore_all: "\u6062\u590D\u5168\u90E8",
|
|
2498
2608
|
cooling: "\u9650\u6D41\u81F3",
|
|
2499
2609
|
noModels: "\u8FDE\u63A5\u540E\u8BF7\u5237\u65B0\u6A21\u578B",
|
|
2500
2610
|
free: "\u514D\u8D39",
|
|
@@ -2503,7 +2613,7 @@ var qoderLocales = {
|
|
|
2503
2613
|
};
|
|
2504
2614
|
|
|
2505
2615
|
// src/plugin.ts
|
|
2506
|
-
var manifest = { name: "qoder", version: "0.
|
|
2616
|
+
var manifest = { name: "qoder", version: "0.3.1", coreApi: "1", description: "Qoder (qoder.com / qoder.cn) subscription provider with PAT sign-in, model management and usage." };
|
|
2507
2617
|
function activatedTransformer(site, service) {
|
|
2508
2618
|
return class ActivatedQoderTransformer extends QoderTransformer {
|
|
2509
2619
|
static TransformerName = site;
|
package/dist/plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wengine-ai/ccr-provider-qoder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Qoder subscription provider (qoder.com and qoder.cn): Personal Access Token sign-in, fixed agent_chat_generation upstream, model management and usage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.js",
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
"dist/*.json",
|
|
30
30
|
"dist/*.md"
|
|
31
31
|
]
|
|
32
|
-
}
|
|
32
|
+
}
|