pi-provider-qoder-cn 0.2.23 → 0.2.25
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 +7 -0
- package/dist/index.js +789 -740
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1791,814 +1791,860 @@ var init_oauth = __esm({
|
|
|
1791
1791
|
// src/index.ts
|
|
1792
1792
|
init_oauth();
|
|
1793
1793
|
|
|
1794
|
-
// src/
|
|
1795
|
-
function quotaNumber(value) {
|
|
1796
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1797
|
-
}
|
|
1798
|
-
function bucketLimit(bucket) {
|
|
1799
|
-
return quotaNumber(bucket.total ?? bucket.cap);
|
|
1800
|
-
}
|
|
1801
|
-
function hasAllowance(bucket) {
|
|
1802
|
-
if (!bucket) return false;
|
|
1803
|
-
return bucketLimit(bucket) > 0 || quotaNumber(bucket.used) > 0 || quotaNumber(bucket.remaining) > 0;
|
|
1804
|
-
}
|
|
1805
|
-
function pickBucket(raw, camel) {
|
|
1806
|
-
if (!raw) return void 0;
|
|
1807
|
-
const source = raw;
|
|
1808
|
-
const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
1809
|
-
return source[snake] ?? source[camel];
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
// src/auth/usage.ts
|
|
1794
|
+
// src/protocol/errors.ts
|
|
1813
1795
|
init_region();
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
Authorization: `Bearer ${credentials.access}`,
|
|
1830
|
-
Accept: "application/json",
|
|
1831
|
-
"User-Agent": "pi-provider-qoder-cn"
|
|
1796
|
+
init_vpc();
|
|
1797
|
+
import { createHash } from "node:crypto";
|
|
1798
|
+
|
|
1799
|
+
// src/protocol/queue.ts
|
|
1800
|
+
var MAX_WAIT_MS = 12e4;
|
|
1801
|
+
var DEFAULT_RETRY_SECONDS = 30;
|
|
1802
|
+
function digForQueue(node, depth) {
|
|
1803
|
+
if (depth > 6) return null;
|
|
1804
|
+
if (typeof node === "string") {
|
|
1805
|
+
const trimmed = node.trim();
|
|
1806
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
1807
|
+
try {
|
|
1808
|
+
return digForQueue(JSON.parse(trimmed), depth + 1);
|
|
1809
|
+
} catch {
|
|
1810
|
+
return null;
|
|
1832
1811
|
}
|
|
1833
|
-
});
|
|
1834
|
-
if (!response.ok) {
|
|
1835
|
-
throw new Error(`Failed to fetch Qoder usage: ${response.status} ${response.statusText}`);
|
|
1836
1812
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
{ id: "org-resource-package", label: "Org Resource Package", quota: org }
|
|
1844
|
-
].filter((entry) => hasAllowance(entry.quota));
|
|
1845
|
-
const usageBuckets = buckets.map(({ id, label, quota }) => ({
|
|
1846
|
-
id,
|
|
1847
|
-
label,
|
|
1848
|
-
usedDisplay: round2(quotaNumber(quota?.used)).toFixed(2),
|
|
1849
|
-
limitDisplay: round2(quotaNumber(quota?.total ?? quota?.cap)).toFixed(2),
|
|
1850
|
-
unit: quota?.unit || "credits",
|
|
1851
|
-
resetAt
|
|
1852
|
-
}));
|
|
1853
|
-
const remaining = buckets.reduce((sum, { quota }) => sum + Math.max(0, quotaNumber(quota?.remaining)), 0);
|
|
1854
|
-
const unit = buckets[0]?.quota?.unit || "credits";
|
|
1855
|
-
return {
|
|
1856
|
-
summary: buckets.length > 0 ? `${round2(remaining)} ${unit} remaining` : "",
|
|
1857
|
-
subscriptionTitle: region.usageTitle,
|
|
1858
|
-
resetAt,
|
|
1859
|
-
manageUrl: region.manageUrl,
|
|
1860
|
-
usageBuckets,
|
|
1861
|
-
raw
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
// src/index.ts
|
|
1866
|
-
init_catalog();
|
|
1867
|
-
|
|
1868
|
-
// src/commands/claim.ts
|
|
1869
|
-
init_expiry();
|
|
1870
|
-
init_cosy();
|
|
1871
|
-
init_region();
|
|
1872
|
-
import { spawnSync } from "node:child_process";
|
|
1873
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1874
|
-
import { homedir as homedir5, hostname } from "node:os";
|
|
1875
|
-
import { basename, dirname as dirname4, join as join5 } from "node:path";
|
|
1876
|
-
import { fileURLToPath } from "node:url";
|
|
1877
|
-
function piAgentDir2() {
|
|
1878
|
-
const home = process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
1879
|
-
return join5(home, ".pi", "agent");
|
|
1880
|
-
}
|
|
1881
|
-
function getCheckinCachePath(mode = "cn") {
|
|
1882
|
-
const file = mode === "cn" ? "qoder-cn-checkin.json" : "qoder-checkin.json";
|
|
1883
|
-
return join5(piAgentDir2(), file);
|
|
1884
|
-
}
|
|
1885
|
-
function readCheckinCache(mode = "cn") {
|
|
1886
|
-
try {
|
|
1887
|
-
const file = getCheckinCachePath(mode);
|
|
1888
|
-
if (!existsSync5(file)) return null;
|
|
1889
|
-
return JSON.parse(readFileSync5(file, "utf8"));
|
|
1890
|
-
} catch {
|
|
1813
|
+
if (node === null || typeof node !== "object") return null;
|
|
1814
|
+
if (Array.isArray(node)) {
|
|
1815
|
+
for (const item of node) {
|
|
1816
|
+
const found = digForQueue(item, depth + 1);
|
|
1817
|
+
if (found) return found;
|
|
1818
|
+
}
|
|
1891
1819
|
return null;
|
|
1892
1820
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
if (!existsSync5(dir)) mkdirSync5(dir, { recursive: true });
|
|
1899
|
-
writeFileSync5(file, JSON.stringify(info, null, 2), "utf8");
|
|
1900
|
-
return true;
|
|
1901
|
-
} catch {
|
|
1902
|
-
return false;
|
|
1821
|
+
const record = node;
|
|
1822
|
+
if (record.isQueued === true) return record;
|
|
1823
|
+
for (const value of Object.values(record)) {
|
|
1824
|
+
const found = digForQueue(value, depth + 1);
|
|
1825
|
+
if (found) return found;
|
|
1903
1826
|
}
|
|
1827
|
+
return null;
|
|
1904
1828
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
bold: "\x1B[1m",
|
|
1908
|
-
green: "\x1B[32m",
|
|
1909
|
-
yellow: "\x1B[33m",
|
|
1910
|
-
red: "\x1B[31m",
|
|
1911
|
-
cyan: "\x1B[36m"
|
|
1912
|
-
};
|
|
1913
|
-
function paint(text, code) {
|
|
1914
|
-
return code && text ? `${code}${text}${ANSI.reset}` : text;
|
|
1829
|
+
function positiveNumber(value, fallback) {
|
|
1830
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1915
1831
|
}
|
|
1916
|
-
function
|
|
1917
|
-
|
|
1918
|
-
const beijingTime = now + beijingOffset;
|
|
1919
|
-
const beijingDate = new Date(beijingTime);
|
|
1920
|
-
let targetUtc = Date.UTC(beijingDate.getUTCFullYear(), beijingDate.getUTCMonth(), beijingDate.getUTCDate(), 10, 0, 0);
|
|
1921
|
-
if (beijingTime >= targetUtc) {
|
|
1922
|
-
targetUtc += 24 * 36e5;
|
|
1923
|
-
}
|
|
1924
|
-
return targetUtc - beijingTime;
|
|
1832
|
+
function textOf(value) {
|
|
1833
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1925
1834
|
}
|
|
1926
|
-
function
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
const
|
|
1930
|
-
return
|
|
1835
|
+
function parseQueueNotice(envelope) {
|
|
1836
|
+
const payload = digForQueue(envelope, 0);
|
|
1837
|
+
if (!payload) return null;
|
|
1838
|
+
const retryMs = positiveNumber(payload.retry_after_ms, 0) || positiveNumber(payload.retryAfterSeconds, DEFAULT_RETRY_SECONDS) * 1e3;
|
|
1839
|
+
return {
|
|
1840
|
+
retryAfterMs: Math.min(retryMs, MAX_WAIT_MS),
|
|
1841
|
+
waitTimeSeconds: positiveNumber(payload.waitTime, 0),
|
|
1842
|
+
queueCount: positiveNumber(payload.queueCount, 0),
|
|
1843
|
+
// Only a real boolean is surfaced; a missing field must not be read as
|
|
1844
|
+
// "unavailable" (that would collapse the normal queue into a hard stop).
|
|
1845
|
+
serviceAvailable: typeof payload.serviceAvailable === "boolean" ? payload.serviceAvailable : void 0,
|
|
1846
|
+
// `subCode` sits inside the same record as `isQueued` in the payloads seen
|
|
1847
|
+
// live, so one source is enough; the wrapper levels carry no extra names.
|
|
1848
|
+
subCode: textOf(payload.subCode),
|
|
1849
|
+
queueType: textOf(payload.queueType),
|
|
1850
|
+
modelKey: textOf(payload.modelKey)
|
|
1851
|
+
};
|
|
1931
1852
|
}
|
|
1932
|
-
function
|
|
1933
|
-
if (!
|
|
1934
|
-
const
|
|
1935
|
-
if (
|
|
1936
|
-
return
|
|
1937
|
-
timeZone: "Asia/Shanghai",
|
|
1938
|
-
year: "numeric",
|
|
1939
|
-
month: "2-digit",
|
|
1940
|
-
day: "2-digit",
|
|
1941
|
-
hour: "2-digit",
|
|
1942
|
-
minute: "2-digit",
|
|
1943
|
-
second: "2-digit",
|
|
1944
|
-
hour12: false
|
|
1945
|
-
}).format(d);
|
|
1853
|
+
function formatWaitSecondsShort(ms) {
|
|
1854
|
+
if (!Number.isFinite(ms) || ms <= 0) return "0";
|
|
1855
|
+
const seconds = Math.round(ms / 1e3);
|
|
1856
|
+
if (seconds < 100) return String(seconds);
|
|
1857
|
+
return `${formatWaitMinutes(seconds)}\u5206\u949F`;
|
|
1946
1858
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1859
|
+
function formatQueueFailureMessage(notice, ctx) {
|
|
1860
|
+
const isCn = ctx.mode === "cn";
|
|
1861
|
+
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1862
|
+
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1863
|
+
const modelKey = notice.modelKey ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1864
|
+
const hard = notice.serviceAvailable === false;
|
|
1865
|
+
const waited = formatWaitSecondsShort(ctx.waitedMs ?? 0);
|
|
1866
|
+
const queueLabel = formatQueueFigure(notice.queueCount ?? 0);
|
|
1867
|
+
const tried = ctx.attempts === void 0 ? void 0 : isCn ? `\u5DF2\u5C1D\u8BD5 ${formatQueueFigure(ctx.attempts)} \u6B21\uFF08\u7D2F\u8BA1\u7B49\u5F85 ${waited} \u79D2\uFF09\uFF0C\u7ED3\u8BBA\u76F8\u540C\u3002` : `${formatQueueFigure(ctx.attempts)} attempts (about ${waited} seconds of waiting) ended in the same verdict. `;
|
|
1868
|
+
if (hard) {
|
|
1869
|
+
return isCn ? [
|
|
1870
|
+
`[Qoder CN \u65E0\u53EF\u7528\u5BB9\u91CF] \u7F51\u5173\u56DE serviceAvailable:false\uFF08\u5B50\u7801 ${sub}\uFF0C\u961F\u5217\u957F\u5EA6 ${queueLabel}\uFF0C\u4F18\u5148\u7EA7\u901A\u9053 ${lane}\uFF0C\u6A21\u578B ${modelKey}\uFF09`,
|
|
1871
|
+
`- \u8BF4\u660E\uFF1A${tried ?? ""}\u8FD9\u662F\u8D26\u53F7\u4F18\u5148\u7EA7\u901A\u9053\u7684\u5BB9\u91CF\u9650\u5236\uFF0C\u4E0D\u662F\u51ED\u8BC1\u95EE\u9898\u3002\u6392\u961F\u5C1D\u8BD5\u4E0D\u4EA7\u751F\u8F93\u51FA\uFF0C\u5B9E\u6D4B\u4E5F\u4E0D\u6263 credits\uFF08\u4EE3\u4EF7\u53EA\u6709\u65F6\u5EF6\uFF09\u3002`,
|
|
1872
|
+
`- \u6362\u6A21\u578B\u65E0\u6548\uFF1A\u540C\u4E00\u8D26\u53F7\u5728 ${lane} \u901A\u9053\u4E0B\u5404\u6A21\u578B\u8FD4\u56DE\u540C\u4E00\u7ED3\u8BBA\uFF08\u5B9E\u6D4B auto / qmodel_38max / efficient / ultimate \u4E00\u81F4\uFF09\uFF0C\u8BF7\u52FF\u5728\u540C\u4E00 Provider \u5185\u53CD\u590D\u6311\u6A21\u578B\u3002`,
|
|
1873
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u6216\u5176\u4ED6 Provider\uFF0C\u6216\u7A0D\u540E\u91CD\u8BD5\u3002\u989D\u5EA6\u4E0E\u8BA1\u5212\uFF1A${ctx.manageUrl}`
|
|
1874
|
+
].join("\n") : [
|
|
1875
|
+
`[Qoder international has no spare capacity] The gateway answered serviceAvailable:false (sub code ${sub}, queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
1876
|
+
`- Meaning: ${tried ?? ""}This is the account's priority-lane capacity, not a credential problem. Queued attempts produce no output and are not billed (measured: 19 queued requests, credits unchanged) \u2014 the only cost is latency.`,
|
|
1877
|
+
`- Switching model will not help: every model of this account answers on the same ${lane} lane (measured across auto / qmodel_38max / efficient / ultimate).`,
|
|
1878
|
+
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry later. Plan and quota: ${ctx.manageUrl}`
|
|
1879
|
+
].join("\n");
|
|
1962
1880
|
}
|
|
1963
|
-
return
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
return String(res.stdout || "");
|
|
1881
|
+
return isCn ? [
|
|
1882
|
+
`[Qoder CN \u4E0A\u6E38\u6392\u961F] \u6A21\u578B\u7F51\u5173\u5F53\u524D\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u6C42\u8FDB\u5165\u7B49\u5F85\u961F\u5217\uFF08\u961F\u5217\u957F\u5EA6 ${queueLabel}\uFF0C\u4F18\u5148\u7EA7\u901A\u9053 ${lane}\uFF0C\u6A21\u578B ${modelKey}\uFF09`,
|
|
1883
|
+
`- \u8BF4\u660E\uFF1A${tried ?? ""}\u961F\u5217\u5C1A\u672A\u653E\u884C\u3002\u51ED\u8BC1\u4E0E\u989D\u5EA6\u6B63\u5E38\uFF0C\u7EAF\u7CB9\u662F\u4E0A\u6E38\u6392\u961F\uFF1B\u6392\u961F\u5C1D\u8BD5\u4E0D\u6D88\u8017 credits\u3002`,
|
|
1884
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u7B49\u5176\u4ED6 Provider\u3002\u989D\u5EA6\u4E0E\u8BA1\u5212\uFF1A${ctx.manageUrl}`
|
|
1885
|
+
].join("\n") : [
|
|
1886
|
+
// "is at capacity" is the established wording for the drainable case and
|
|
1887
|
+
// the only queue text allowed to carry it; the hard refusal above must
|
|
1888
|
+
// never read as "wait it out".
|
|
1889
|
+
`[Qoder upstream queue] The model gateway is at capacity and parked this request (queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
1890
|
+
`- Meaning: ${tried ?? ""}the queue has not cleared yet. Credentials and quota are fine; queued attempts are not billed.`,
|
|
1891
|
+
`- Next step: retry shortly, or switch provider with /model to ${ctx.otherProviderID} while the queue drains. Plan and quota: ${ctx.manageUrl}`
|
|
1892
|
+
].join("\n");
|
|
1976
1893
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
const
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
const identity = {
|
|
1992
|
-
machineToken: parsed.machineToken.trim(),
|
|
1993
|
-
machineCode: parsed.machineCode.trim(),
|
|
1994
|
-
machineType: parsed.machineType.trim()
|
|
1995
|
-
};
|
|
1996
|
-
identityMemCache.set(key, { identity, expiresAt: Date.now() + IDENTITY_TTL_MS });
|
|
1997
|
-
return identity;
|
|
1998
|
-
} catch {
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
return null;
|
|
1894
|
+
function formatLaneCooldownMessage(notice, remainingMs, ctx) {
|
|
1895
|
+
const isCn = ctx.mode === "cn";
|
|
1896
|
+
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1897
|
+
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1898
|
+
const left = Math.min(99, Math.max(1, Math.ceil(remainingMs / 1e3)));
|
|
1899
|
+
return isCn ? [
|
|
1900
|
+
`[Qoder CN \u5DF2\u8DF3\u8FC7\u672C\u6B21\u8BF7\u6C42] \u4F18\u5148\u7EA7\u901A\u9053 ${lane} \u521A\u521A\u56DE\u8FC7 serviceAvailable:false\uFF08\u5B50\u7801 ${sub}\uFF09\uFF0C${left} \u79D2\u5185\u4E0D\u518D\u91CD\u590D\u7B49\u5F85\u3002`,
|
|
1901
|
+
`- \u8BF4\u660E\uFF1A\u8FD9\u6B21\u8BF7\u6C42\u5C1A\u672A\u53D1\u51FA\u3002\u4E0A\u4E00\u8F6E\u540C\u901A\u9053\u8BF7\u6C42\u6309 Retry-After \u7B49\u5F85\u540E\u4ECD\u672A\u653E\u884C\uFF0C\u6392\u961F\u53EA\u4F1A\u518D\u6D88\u8017\u540C\u6837\u7684\u65F6\u5EF6\u3002`,
|
|
1902
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u6216\u5176\u4ED6 Provider\uFF0C\u6216 ${left} \u79D2\u540E\u91CD\u8BD5\u3002`
|
|
1903
|
+
].join("\n") : [
|
|
1904
|
+
`[Qoder request skipped] Priority lane ${lane} just refused service (sub code ${sub}, serviceAvailable:false), so this request was not sent; repeats are skipped for ${left} more seconds.`,
|
|
1905
|
+
`- Meaning: the previous request on this lane already waited through Retry-After and was still refused, so waiting again buys the same verdict.`,
|
|
1906
|
+
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry in ${left} seconds.`
|
|
1907
|
+
].join("\n");
|
|
2002
1908
|
}
|
|
2003
|
-
function
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
"Cosy-ClientType": "10",
|
|
2009
|
-
"Cosy-Version": "1.13.3",
|
|
2010
|
-
"Cosy-MachineId": machineID,
|
|
2011
|
-
"Cosy-MachineOS": "x86_64_windows",
|
|
2012
|
-
"Cosy-MachineHostname": hostname(),
|
|
2013
|
-
"User-Agent": "Qoder"
|
|
2014
|
-
};
|
|
2015
|
-
if (identity) {
|
|
2016
|
-
headers["Cosy-MachineToken"] = identity.machineToken;
|
|
2017
|
-
headers["Cosy-MachineCode"] = identity.machineCode;
|
|
2018
|
-
headers["Cosy-MachineType"] = identity.machineType;
|
|
2019
|
-
}
|
|
2020
|
-
return headers;
|
|
1909
|
+
function formatQueueFigure(n) {
|
|
1910
|
+
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
1911
|
+
if (n < 100) return String(Math.round(n));
|
|
1912
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1913
|
+
return `${(n / 1e3).toFixed(1)}k`;
|
|
2021
1914
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
method: "GET",
|
|
2026
|
-
headers: buildSashHeaders(accessToken, machineID, identity),
|
|
2027
|
-
signal: AbortSignal.timeout(1e4)
|
|
2028
|
-
});
|
|
2029
|
-
const text = await response.text();
|
|
2030
|
-
let data;
|
|
2031
|
-
try {
|
|
2032
|
-
data = JSON.parse(text);
|
|
2033
|
-
} catch {
|
|
2034
|
-
data = void 0;
|
|
2035
|
-
}
|
|
2036
|
-
if (!response.ok) {
|
|
2037
|
-
const detail = data?.message || text.slice(0, 200);
|
|
2038
|
-
throw new Error(`Failed to query Qoder campaigns (${response.status}): ${detail}`);
|
|
2039
|
-
}
|
|
2040
|
-
if (!data) throw new Error("Qoder campaigns response was not JSON.");
|
|
2041
|
-
return data;
|
|
1915
|
+
function formatWaitMinutes(seconds) {
|
|
1916
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "<1";
|
|
1917
|
+
return String(Math.min(99, Math.max(1, Math.round(seconds / 60))));
|
|
2042
1918
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
1919
|
+
function sleep(ms, signal) {
|
|
1920
|
+
return new Promise((resolve, reject) => {
|
|
1921
|
+
if (signal?.aborted) {
|
|
1922
|
+
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
let timer;
|
|
1926
|
+
const onAbort = () => {
|
|
1927
|
+
clearTimeout(timer);
|
|
1928
|
+
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
1929
|
+
};
|
|
1930
|
+
timer = setTimeout(() => {
|
|
1931
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1932
|
+
resolve();
|
|
1933
|
+
}, ms);
|
|
1934
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2050
1935
|
});
|
|
2051
|
-
const text = await response.text();
|
|
2052
|
-
let data;
|
|
2053
|
-
try {
|
|
2054
|
-
data = JSON.parse(text);
|
|
2055
|
-
} catch {
|
|
2056
|
-
data = void 0;
|
|
2057
|
-
}
|
|
2058
|
-
if (!response.ok) {
|
|
2059
|
-
const detail = data?.message || text.slice(0, 200);
|
|
2060
|
-
throw new Error(`Failed to claim Qoder benefit (${response.status}): ${detail}`);
|
|
2061
|
-
}
|
|
2062
|
-
if (!data) throw new Error("Qoder claim response was not JSON.");
|
|
2063
|
-
return data;
|
|
2064
1936
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
1937
|
+
|
|
1938
|
+
// src/protocol/errors.ts
|
|
1939
|
+
function parseQoderErrorPayload(payload) {
|
|
1940
|
+
if (!payload) return null;
|
|
1941
|
+
let parsed = payload;
|
|
1942
|
+
if (typeof payload === "string") {
|
|
1943
|
+
try {
|
|
1944
|
+
parsed = JSON.parse(payload);
|
|
1945
|
+
} catch {
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
2069
1948
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const endAtMs = toEpochMs(dailyCreditCampaign.endAt);
|
|
2081
|
-
const cacheUntil = endAtMs && endAtMs > Date.now() ? endAtMs : Date.now() + msUntilBeijing10AM();
|
|
2082
|
-
let info;
|
|
2083
|
-
if (dailyCreditCampaign.claimStatus === "CLAIMED") {
|
|
2084
|
-
try {
|
|
2085
|
-
const claimRes = await claimQoderCampaign(
|
|
2086
|
-
accessToken,
|
|
2087
|
-
machineID,
|
|
2088
|
-
dailyCreditCampaign.campaignId,
|
|
2089
|
-
mode,
|
|
2090
|
-
identity
|
|
2091
|
-
);
|
|
2092
|
-
info = {
|
|
2093
|
-
claimed: true,
|
|
2094
|
-
amount: claimRes.benefit?.amount || amount,
|
|
2095
|
-
claimedAt: claimRes.claimedAt || cached?.claimedAt,
|
|
2096
|
-
expiresAt: claimRes.expiresAt || cached?.expiresAt,
|
|
2097
|
-
validityDays: claimRes.benefit?.validity?.days || validityDays,
|
|
2098
|
-
campaignId: dailyCreditCampaign.campaignId,
|
|
2099
|
-
campaignTitle: title,
|
|
2100
|
-
cacheUntil
|
|
2101
|
-
};
|
|
2102
|
-
} catch {
|
|
2103
|
-
info = {
|
|
2104
|
-
claimed: true,
|
|
2105
|
-
amount,
|
|
2106
|
-
claimedAt: cached?.claimedAt,
|
|
2107
|
-
expiresAt: cached?.expiresAt,
|
|
2108
|
-
validityDays,
|
|
2109
|
-
campaignId: dailyCreditCampaign.campaignId,
|
|
2110
|
-
campaignTitle: title,
|
|
2111
|
-
cacheUntil
|
|
1949
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1950
|
+
const obj = parsed;
|
|
1951
|
+
if (typeof obj.message === "string" && obj.message.trim().startsWith("{")) {
|
|
1952
|
+
try {
|
|
1953
|
+
const nested = JSON.parse(obj.message);
|
|
1954
|
+
if (typeof nested === "object" && nested !== null) {
|
|
1955
|
+
const nestedObj = nested;
|
|
1956
|
+
return {
|
|
1957
|
+
code: String(nestedObj.code || nestedObj.errorCode || obj.code || ""),
|
|
1958
|
+
message: String(nestedObj.message || nestedObj.errorMessage || obj.message || "")
|
|
2112
1959
|
};
|
|
2113
1960
|
}
|
|
2114
|
-
}
|
|
2115
|
-
info = {
|
|
2116
|
-
claimed: false,
|
|
2117
|
-
amount,
|
|
2118
|
-
validityDays,
|
|
2119
|
-
campaignId: dailyCreditCampaign.campaignId,
|
|
2120
|
-
campaignTitle: title,
|
|
2121
|
-
cacheUntil
|
|
2122
|
-
};
|
|
1961
|
+
} catch {
|
|
2123
1962
|
}
|
|
2124
|
-
writeCheckinCache(info, mode);
|
|
2125
|
-
return info;
|
|
2126
|
-
} catch {
|
|
2127
|
-
return readCheckinCache(mode);
|
|
2128
1963
|
}
|
|
1964
|
+
const code = obj.code ?? obj.errorCode;
|
|
1965
|
+
const message = obj.message ?? obj.errorMessage;
|
|
1966
|
+
if (!code && !message) return null;
|
|
1967
|
+
return {
|
|
1968
|
+
code: code !== void 0 ? String(code) : void 0,
|
|
1969
|
+
message: message !== void 0 ? String(message) : void 0
|
|
1970
|
+
};
|
|
2129
1971
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
return
|
|
1972
|
+
function msUntilBeijingMidnight(now = Date.now()) {
|
|
1973
|
+
const beijingOffset = 8 * 36e5;
|
|
1974
|
+
const beijingTime = now + beijingOffset;
|
|
1975
|
+
const beijingDate = new Date(beijingTime);
|
|
1976
|
+
const nextMidnightUtc = Date.UTC(
|
|
1977
|
+
beijingDate.getUTCFullYear(),
|
|
1978
|
+
beijingDate.getUTCMonth(),
|
|
1979
|
+
beijingDate.getUTCDate() + 1,
|
|
1980
|
+
0,
|
|
1981
|
+
0,
|
|
1982
|
+
0
|
|
1983
|
+
);
|
|
1984
|
+
return nextMidnightUtc - beijingTime;
|
|
2143
1985
|
}
|
|
2144
|
-
|
|
1986
|
+
function getBeijingDailyResetCountdown(now = Date.now()) {
|
|
1987
|
+
const diffMs = msUntilBeijingMidnight(now);
|
|
1988
|
+
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
1989
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
1990
|
+
const minutes = totalMinutes % 60;
|
|
1991
|
+
return hours > 0 ? `${hours}\u5C0F\u65F6${minutes}\u5206\u949F` : `${minutes}\u5206\u949F`;
|
|
1992
|
+
}
|
|
1993
|
+
var CREDENTIAL_EXPIRED_CODES = /* @__PURE__ */ new Set(["105", "TOKEN_EXPIRE", "TOKEN_EXPIRED"]);
|
|
1994
|
+
var CREDENTIAL_EXPIRED_MESSAGE = /token expired|login expired|token is not active|token has expired/i;
|
|
1995
|
+
function isCredentialExpiredVerdict(code, message) {
|
|
1996
|
+
return code !== void 0 && CREDENTIAL_EXPIRED_CODES.has(code) || CREDENTIAL_EXPIRED_MESSAGE.test(message);
|
|
1997
|
+
}
|
|
1998
|
+
function isOpenApiInactiveCredential(status, body) {
|
|
1999
|
+
if (status !== 401) return false;
|
|
2000
|
+
const parsed = parseQoderErrorPayload(body);
|
|
2001
|
+
return parsed?.code === "TOKEN_EXPIRE" || /token is not active/i.test(parsed?.message ?? body);
|
|
2002
|
+
}
|
|
2003
|
+
function formatInactiveCredentialMessage(endpoint, accessToken, mode = "cn") {
|
|
2145
2004
|
const region = getQoderRegionConfig(mode);
|
|
2146
|
-
const
|
|
2147
|
-
const
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
const cached = readCheckinCache(mode);
|
|
2184
|
-
writeCheckinCache(
|
|
2185
|
-
{
|
|
2186
|
-
claimed: true,
|
|
2187
|
-
amount,
|
|
2188
|
-
validityDays,
|
|
2189
|
-
campaignId: dailyCreditCampaign.campaignId,
|
|
2190
|
-
campaignTitle,
|
|
2191
|
-
claimedAt: cached?.claimedAt,
|
|
2192
|
-
expiresAt: cached?.expiresAt,
|
|
2193
|
-
cacheUntil: Date.now() + remainingToReset
|
|
2194
|
-
},
|
|
2195
|
-
mode
|
|
2196
|
-
);
|
|
2197
|
-
if (wantsRaw) {
|
|
2198
|
-
const rawJson = JSON.stringify(dailyCreditCampaign, null, 2);
|
|
2199
|
-
ctx?.ui?.notify(rawJson, "info");
|
|
2200
|
-
if (!ctx?.ui) console.log(rawJson);
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
const lines2 = mode === "cn" ? [
|
|
2204
|
-
paint(`\u2139\uFE0F \u4ECA\u65E5 ${amount} Credits \u5DF2\u7ECF\u9886\u53D6\u8FC7\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C`, ANSI.cyan),
|
|
2205
|
-
`- \u6D3B\u52A8\u540D\u79F0\uFF1A${campaignTitle}`,
|
|
2206
|
-
`- \u989D\u5EA6\u8BF4\u660E\uFF1A${amount} Credits\uFF08\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF0C${validityDays} \u5929\u6709\u6548\uFF09`,
|
|
2207
|
-
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2208
|
-
] : [
|
|
2209
|
-
paint(`\u2139\uFE0F Today's ${amount} Credits already claimed.`, ANSI.cyan),
|
|
2210
|
-
`- Campaign: ${campaignTitle}`,
|
|
2211
|
-
`- Benefit: ${amount} Credits (${validityDays}-day validity)`,
|
|
2212
|
-
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2213
|
-
];
|
|
2214
|
-
const output2 = lines2.join("\n");
|
|
2215
|
-
ctx?.ui?.notify(output2, "info");
|
|
2216
|
-
if (!ctx?.ui) console.log(output2);
|
|
2217
|
-
return;
|
|
2005
|
+
const fingerprint = createHash("sha256").update(accessToken).digest("hex").slice(0, 8);
|
|
2006
|
+
const onPublicGateway = endpoint.startsWith(
|
|
2007
|
+
mode === "cn" ? QODER_CN_OFFICIAL.openApiUrl : "https://openapi.qoder.sh"
|
|
2008
|
+
);
|
|
2009
|
+
const where = onPublicGateway ? mode === "cn" ? "\u672C\u8BF7\u6C42\u6253\u5230\u516C\u7F51\u7F51\u5173\uFF0C\u800C\u4E0D\u662F\u4F01\u4E1A\u5B9E\u4F8B\u7684 openapi" : "this request went to the public gateway, not your instance's openapi host" : mode === "cn" ? "\u672C\u8BF7\u6C42\u6253\u5230 VPC \u5B9E\u4F8B\u7684 openapi" : "this request went to a VPC instance's openapi host";
|
|
2010
|
+
const cause = onPublicGateway ? mode === "cn" ? "\u9996\u8981\u6000\u7591\uFF1A\u90E8\u7F72\u4E0D\u5339\u914D\u3002VPC\uFF08\u4F01\u4E1A\uFF09\u8D26\u53F7\u7684 token \u53EA\u5728\u81EA\u5DF1\u5B9E\u4F8B\u7684 openapi \u4E0A\u6709\u6548\uFF0C\u516C\u7F51\u7F51\u5173\u4F1A\u628A\u5B83\u5F53\u6210 not active\uFF1B\u540C\u4E00 token \u7684\u6A21\u578B\u8BF7\u6C42\u4ECD\u53EF\u6B63\u5E38\uFF0C\u56E0\u4E3A\u804A\u5929\u8D70\u7684\u662F\u5DF2\u914D\u7F6E\u7684\u5B9E\u4F8B\u7F51\u5173\u3002\u5728\u8FD9\u79CD\u9519\u914D\u4E0B\u91CD\u65B0\u6362\u53D6 token\uFF08/login \u6B63\u662F\u8FD9\u4E00\u6B65\uFF09\u4E0D\u4F1A\u6D88\u9664 401\u3002" : "most likely a deployment mismatch: a VPC account's token is only active on its own instance's openapi, so the public gateway reports it as not active, while model requests keep working because they use the configured instance gateway. Exchanging a fresh token (what /login does) does not clear that mismatch." : mode === "cn" ? `\u8BE5\u5B9E\u4F8B\u4E0D\u8BA4\u8FD9\u4E2A token\u3002\u786E\u8BA4\u8D26\u53F7\u786E\u5B9E\u5C5E\u4E8E\u8FD9\u4E2A\u5B9E\u4F8B\u3001\u4E14\u5B9E\u4F8B\u672A\u505C\u7528\u5B83\uFF1B\u82E5 endpoint \u914D\u7F6E\u5DF2\u8FC7\u65F6\uFF0C\u8BF7\u7528 /login ${region.providerID} \u91CD\u65B0\u9009\u62E9\u5B9E\u4F8B\u3002` : "this instance does not know the token. Confirm the account belongs to it and that the instance has not revoked the token; if the endpoint setting is stale, reselect it with /login.";
|
|
2011
|
+
const remedy = onPublicGateway ? mode === "cn" ? `\u7528 /login ${region.providerID} \u9009\u62E9\u4F01\u4E1A\u5B9E\u4F8B\uFF08\u6216\u4FEE\u6B63 endpoint\uFF09\u540E\u91CD\u8BD5 /${region.providerID}.usage\u3002` : `select the enterprise instance with /login ${region.providerID} (or fix the endpoint), then retry /${region.providerID}.usage.` : mode === "cn" ? `\u91CD\u8BD5 /${region.providerID}.usage\uFF1B\u4ECD\u88AB\u62D2\u5219\u8FD0\u884C /login ${region.providerID} \u91CD\u65B0\u767B\u5F55\uFF0C\u5E76\u5230 ${region.manageUrl} \u786E\u8BA4 PAT \u672A\u88AB\u540A\u9500\u3002` : `retry /${region.providerID}.usage; if it still refuses, run /login ${region.providerID} and confirm at ${region.manageUrl} that the PAT was not revoked.`;
|
|
2012
|
+
const lines = mode === "cn" ? [
|
|
2013
|
+
`[Qoder CN \u989D\u5EA6\u7AEF\u70B9\u62D2\u7EDD\u5F53\u524D token] code TOKEN_EXPIRE (token is not active)`,
|
|
2014
|
+
`- \u7AEF\u70B9\uFF1A${endpoint} - ${where}`,
|
|
2015
|
+
`- \u539F\u56E0\uFF1A${cause}`,
|
|
2016
|
+
`- \u5904\u7F6E\uFF1A${remedy}`,
|
|
2017
|
+
`- \u8BCA\u65AD\uFF1Atoken sha256:${fingerprint}`
|
|
2018
|
+
] : [
|
|
2019
|
+
`[Qoder quota endpoint refused this token] code TOKEN_EXPIRE (token is not active)`,
|
|
2020
|
+
`- Endpoint: ${endpoint} - ${where}`,
|
|
2021
|
+
`- Cause: ${cause}`,
|
|
2022
|
+
`- Next step: ${remedy}`,
|
|
2023
|
+
`- Diagnostics: token sha256:${fingerprint}`
|
|
2024
|
+
];
|
|
2025
|
+
return lines.join("\n");
|
|
2026
|
+
}
|
|
2027
|
+
function formatQoderStreamError(statusCode, rawBody, now = Date.now(), mode = "cn") {
|
|
2028
|
+
const parsed = parseQoderErrorPayload(rawBody);
|
|
2029
|
+
const code = parsed?.code;
|
|
2030
|
+
const message = parsed?.message || (typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody));
|
|
2031
|
+
const bodyForMatch = `${message} ${typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody ?? "")}`;
|
|
2032
|
+
const countdown = getBeijingDailyResetCountdown(now);
|
|
2033
|
+
const isCn = mode !== "global";
|
|
2034
|
+
if (code === "110" || /billing daily count exceeded/i.test(message) || /daily usage limit reached/i.test(message)) {
|
|
2035
|
+
if (!isCn) {
|
|
2036
|
+
return [
|
|
2037
|
+
`[Qoder quota limit] Daily call allowance exhausted (Billing daily count exceeded, code 110)`,
|
|
2038
|
+
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
2039
|
+
`- Reason: the account is on the free personal standard plan or tripped the daily frequency fuse. Add-on credits stay unspendable while the daily cap is active.`,
|
|
2040
|
+
`- Fix: wait for the reset; switch provider with /model; or upgrade at https://qoder.com/pricing to lift the daily limit.`
|
|
2041
|
+
].join("\n");
|
|
2218
2042
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
claimed: true,
|
|
2226
|
-
amount: grantAmount,
|
|
2227
|
-
claimedAt: claimRes.claimedAt,
|
|
2228
|
-
expiresAt: claimRes.expiresAt,
|
|
2229
|
-
validityDays: grantDays,
|
|
2230
|
-
campaignId: dailyCreditCampaign.campaignId,
|
|
2231
|
-
campaignTitle,
|
|
2232
|
-
cacheUntil: Date.now() + remainingToReset
|
|
2233
|
-
},
|
|
2234
|
-
mode
|
|
2235
|
-
);
|
|
2236
|
-
const lines = mode === "cn" ? [
|
|
2237
|
-
paint(`\u{1F389} \u6210\u529F\u9886\u53D6\u4ECA\u65E5 ${grantAmount} Credits\uFF01`, `${ANSI.green}${ANSI.bold}`),
|
|
2238
|
-
`- \u989D\u5EA6\u7C7B\u578B\uFF1A\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF08Add-on Credits\uFF09`,
|
|
2239
|
-
`- \u6709\u6548\u671F\u9650\uFF1A${grantDays} \u5929\uFF08\u6709\u6548\u671F\u81F3 ${expiresText}\uFF09`,
|
|
2240
|
-
`- \u9886\u53D6\u6D41\u6C34\uFF1A${claimRes.grantId || "ok"}`,
|
|
2241
|
-
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2242
|
-
] : [
|
|
2243
|
-
paint(`\u{1F389} Successfully claimed ${grantAmount} Credits!`, `${ANSI.green}${ANSI.bold}`),
|
|
2244
|
-
`- Type: Universal Add-on Credits`,
|
|
2245
|
-
`- Validity: ${grantDays} days (expires ${expiresText})`,
|
|
2246
|
-
`- Grant ID: ${claimRes.grantId || "ok"}`,
|
|
2247
|
-
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2248
|
-
];
|
|
2249
|
-
const output = lines.join("\n");
|
|
2250
|
-
ctx?.ui?.notify(output, "info");
|
|
2251
|
-
if (!ctx?.ui) console.log(output);
|
|
2252
|
-
} catch (error) {
|
|
2253
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2254
|
-
const errText = mode === "cn" ? `\u274C \u6BCF\u65E5\u7B7E\u5230\u9886\u53D6\u5931\u8D25: ${message}` : `\u274C Daily check-in claim failed: ${message}`;
|
|
2255
|
-
ctx?.ui?.notify(errText, "error");
|
|
2256
|
-
if (!ctx?.ui) console.error(errText);
|
|
2043
|
+
return [
|
|
2044
|
+
`[Qoder CN \u989D\u5EA6\u9650\u5236] \u4ECA\u65E5\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 (Billing daily count exceeded, \u9519\u8BEF\u7801 110)`,
|
|
2045
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
2046
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u5904\u4E8E\u4E2A\u4EBA\u6807\u51C6\u7248\uFF08\u514D\u8D39\u7248\uFF09\u6216\u89E6\u53D1\u4E86\u5355\u65E5\u9891\u6B21\u7194\u65AD\u3002\u82E5\u8D26\u53F7\u5185\u6709\u8D44\u6E90\u5305/\u52A0\u6CB9\u5305\uFF08Add-on Credits\uFF09\uFF0C\u56E0\u514D\u8D39\u7248\u5355\u65E5\u9650\u5236\u672A\u89E3\u9664\u6682\u65E0\u6CD5\u6263\u51CF\u3002`,
|
|
2047
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7B49\u5F85\u6B21\u65E5\u91CD\u7F6E\uFF1B\u6216\u4F7F\u7528 /model \u5207\u6362\u81F3\u5176\u4ED6 Provider\uFF1B\u4E2A\u4EBA\u7248\u53EF\u5F00\u901A Pro \u8BA2\u9605\u89E3\u9664\u6BCF\u65E5\u9650\u5236\uFF0C\u4F01\u4E1A\u7248\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u8C03\u9AD8\u5355\u65E5\u9650\u989D\u3002`
|
|
2048
|
+
].join("\n");
|
|
2257
2049
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
if (
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2050
|
+
if (code === "117" || /team member credits exhausted/i.test(message)) {
|
|
2051
|
+
return isCn ? [
|
|
2052
|
+
`[Qoder CN \u4F01\u4E1A\u989D\u5EA6\u9650\u5236] \u4F01\u4E1A\u5206\u914D\u7ED9\u60A8\u7684\u4E2A\u4EBA Credits \u989D\u5EA6\u5DF2\u7528\u5C3D (\u9519\u8BEF\u7801 117)`,
|
|
2053
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u7BA1\u7406\u5458\u5728\u4F01\u4E1A\u63A7\u5236\u53F0\u5206\u914D\u7ED9\u60A8\u4E2A\u4EBA\u7684\u53EF\u7528 Credits \u989D\u5EA6\u5DF2\u8017\u5C3D\u3002`,
|
|
2054
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u8054\u7CFB\u4F01\u4E1A\u7BA1\u7406\u5458\u5728 Qoder \u56E2\u961F\u7BA1\u7406\u540E\u53F0\u4E3A\u60A8\u589E\u52A0\u6210\u5458 Credits \u914D\u989D\u3002`
|
|
2055
|
+
].join("\n") : [
|
|
2056
|
+
`[Qoder enterprise quota] Your member credit allowance is exhausted (code 117)`,
|
|
2057
|
+
`- Reason: the admin-assigned per-member credit cap has been consumed.`,
|
|
2058
|
+
`- Fix: ask your organization admin to raise your member quota in the team console.`
|
|
2059
|
+
].join("\n");
|
|
2060
|
+
}
|
|
2061
|
+
if (code === "116" || /team administrator credits exhausted/i.test(message)) {
|
|
2062
|
+
return isCn ? [
|
|
2063
|
+
`[Qoder CN \u4F01\u4E1A\u989D\u5EA6\u9650\u5236] \u4F01\u4E1A/\u56E2\u961F\u7BA1\u7406\u5458\u7684 Credits \u603B\u4F59\u989D\u5DF2\u8017\u5C3D (\u9519\u8BEF\u7801 116)`,
|
|
2064
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u4F01\u4E1A\u7EC4\u7EC7\u8D26\u6237\u7684 Credits \u989D\u5EA6\u5DF2\u5168\u90E8\u7528\u5B8C\u3002`,
|
|
2065
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u8054\u7CFB\u4F01\u4E1A\u7BA1\u7406\u5458\u5728\u63A7\u5236\u53F0\u4E3A\u7EC4\u7EC7\u8D26\u6237\u5145\u503C\u6216\u7EED\u671F\u3002`
|
|
2066
|
+
].join("\n") : [
|
|
2067
|
+
`[Qoder enterprise quota] The organization's credit pool is exhausted (code 116)`,
|
|
2068
|
+
`- Reason: every credit in the organization account has been consumed.`,
|
|
2069
|
+
`- Fix: ask your organization admin to purchase or renew the org pool.`
|
|
2070
|
+
].join("\n");
|
|
2071
|
+
}
|
|
2072
|
+
if (code === "122" || /billing-group credits limit reached/i.test(message)) {
|
|
2073
|
+
return isCn ? [
|
|
2074
|
+
`[Qoder CN \u4F01\u4E1A\u8BA1\u8D39\u7EC4\u9650\u5236] \u60A8\u6240\u5728\u7684\u4F01\u4E1A\u8BA1\u8D39\u7EC4\u5DF2\u8FBE\u5230\u672C\u671F\u652F\u51FA\u4E0A\u9650 (\u9519\u8BEF\u7801 122)`,
|
|
2075
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u8BA1\u8D39\u7EC4\u5468\u671F\u5185\u5DF2\u6D88\u8017\u5B8C\u7BA1\u7406\u5458\u8BBE\u5B9A\u7684\u4E0A\u9650\u989D\u5EA6\u3002`,
|
|
2076
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u8054\u7CFB\u8BA1\u8D39\u7BA1\u7406\u5458\u6216\u4F01\u4E1A\u7BA1\u7406\u5458\u8C03\u6574\u8BE5\u8BA1\u8D39\u7EC4\u7684\u5468\u671F\u652F\u51FA\u4E0A\u9650\u3002`
|
|
2077
|
+
].join("\n") : [
|
|
2078
|
+
`[Qoder billing group] The billing group reached its period spend cap (code 122)`,
|
|
2079
|
+
`- Reason: the admin-set limit for this billing group is consumed for the period.`,
|
|
2080
|
+
`- Fix: ask the billing administrator to raise the group's period cap.`
|
|
2081
|
+
].join("\n");
|
|
2082
|
+
}
|
|
2083
|
+
if (code === "119" || /free usage limit for the selected model reached/i.test(message)) {
|
|
2084
|
+
return isCn ? [
|
|
2085
|
+
`[Qoder CN \u6A21\u578B\u9650\u514D\u989D\u5EA6\u5DF2\u6EE1] \u5F53\u524D\u6A21\u578B\u7684\u514D\u8D39\u4F53\u9A8C\u989D\u5EA6\u5DF2\u7528\u5B8C (\u9519\u8BEF\u7801 119)`,
|
|
2086
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
2087
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u53EF\u4F7F\u7528 /model \u5207\u6362\u81F3\u5176\u4ED6\u4ED8\u8D39\u6A21\u578B\uFF08\u5982 deepseek-v4-pro / glm-5.3\uFF09\u6D88\u8017 Credits \u989D\u5EA6\uFF0C\u6216\u6B21\u65E5\u91CD\u7F6E\u540E\u7EE7\u7EED\u4F7F\u7528\u3002`
|
|
2088
|
+
].join("\n") : [
|
|
2089
|
+
`[Qoder model free tier] This model's free daily trial allowance is used up (code 119)`,
|
|
2090
|
+
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
2091
|
+
`- Fix: switch to a paid model via /model to spend credits, or wait for the reset.`
|
|
2092
|
+
].join("\n");
|
|
2093
|
+
}
|
|
2094
|
+
if (code === "113" || code === "118" || /quota exhausted/i.test(message) || /credits exhausted/i.test(message)) {
|
|
2095
|
+
const store = isCn ? QODER_CN_OFFICIAL.manageUrl : "https://qoder.com";
|
|
2096
|
+
return isCn ? [
|
|
2097
|
+
`[Qoder CN \u989D\u5EA6\u8017\u5C3D] \u8D26\u6237 Credits \u4F59\u989D\u5DF2\u5168\u90E8\u7528\u5C3D (\u9519\u8BEF\u7801 ${code || 113})`,
|
|
2098
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u8BA2\u9605\u5957\u9910\u53CA\u8D44\u6E90\u5305\u5185\u7684\u53EF\u7528\u989D\u5EA6\u5DF2\u5168\u90E8\u6263\u51CF\u5B8C\u6BD5\u3002`,
|
|
2099
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u524D\u5F80 ${store} \u8D2D\u4E70\u8D44\u6E90\u5305/\u52A0\u6CB9\u5305\uFF0C\u6216\u7B49\u5F85\u4E0B\u4E00\u8BA1\u8D39\u5468\u671F\u5237\u65B0\u3002`
|
|
2100
|
+
].join("\n") : [
|
|
2101
|
+
`[Qoder credits exhausted] The account has no remaining credits (code ${code || 113})`,
|
|
2102
|
+
`- Reason: the plan allowance and every add-on pack are consumed.`,
|
|
2103
|
+
`- Fix: purchase a credit pack at ${store}, or wait for the next billing cycle.`
|
|
2104
|
+
].join("\n");
|
|
2105
|
+
}
|
|
2106
|
+
if (isCredentialExpiredVerdict(code, message)) {
|
|
2107
|
+
if (isCn) {
|
|
2108
|
+
return [
|
|
2109
|
+
`[Qoder CN \u51ED\u8BC1\u5931\u6548] \u767B\u5F55\u6001\u5DF2\u8FC7\u671F\u6216 Token \u5931\u6548 (\u9519\u8BEF\u7801 105)`,
|
|
2110
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u91CD\u65B0\u8FD0\u884C /login qoder-cn\uFF0C\u6216\u66F4\u65B0\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u4E2A\u4EBA\u8BBF\u95EE\u4EE4\u724C\uFF08PAT\uFF09\u3002`
|
|
2111
|
+
].join("\n");
|
|
2276
2112
|
}
|
|
2113
|
+
return [
|
|
2114
|
+
`[Qoder \u51ED\u8BC1\u5931\u6548 | Credential expired] The login session or token has expired (\u9519\u8BEF\u7801 105)`,
|
|
2115
|
+
`- Next step: run /login qoder. You can also refresh credentials by exporting QODER_PERSONAL_ACCESS_TOKEN (PAT).`
|
|
2116
|
+
].join("\n");
|
|
2277
2117
|
}
|
|
2278
|
-
if (
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2118
|
+
if (code === "103" || /duplicate request/i.test(message)) {
|
|
2119
|
+
return isCn ? [
|
|
2120
|
+
`[Qoder CN \u91CD\u590D\u8BF7\u6C42] \u7F51\u5173\u628A\u8FD9\u6B21\u8BF7\u6C42\u5224\u5B9A\u4E3A\u91CD\u653E\u5E76\u62D2\u7EDD (\u9519\u8BEF\u7801 103 Duplicate request)`,
|
|
2121
|
+
`- \u539F\u56E0\uFF1A\u540C\u4E00\u4E2A\u5DF2\u7B7E\u540D\u7684\u8BF7\u6C42\u88AB\u518D\u6B21\u63D0\u4EA4\uFF08COSY Authorization/\u65F6\u95F4\u6233/\u7B7E\u540D \u672A\u5237\u65B0\uFF09\uFF0C\u5E76\u975E\u989D\u5EA6\u6216\u51ED\u8BC1\u95EE\u9898\u3002`,
|
|
2122
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u5BA2\u6237\u7AEF\u5E94\u4E3A\u6BCF\u6B21\u91CD\u8BD5\u91CD\u65B0\u7B7E\u540D\uFF1B\u82E5\u4ECD\u590D\u73B0\uFF0C\u8BF7\u7528 /model \u5207\u6362 Provider \u7A0D\u540E\u91CD\u8BD5\u3002`
|
|
2123
|
+
].join("\n") : [
|
|
2124
|
+
`[Qoder duplicate request] The gateway rejected this request as a replay (code 103 Duplicate request)`,
|
|
2125
|
+
`- Reason: an already-signed request (same COSY Authorization/timestamp/signature) was submitted again. This is a transient server error, not a quota or credential problem.`,
|
|
2126
|
+
`- Next step: every retry must be re-signed; if it keeps repeating, switch provider with /model and try again shortly.`
|
|
2127
|
+
].join("\n");
|
|
2128
|
+
}
|
|
2129
|
+
if (code === "10605" || /"isQueued"\s*:\s*true/.test(message)) {
|
|
2130
|
+
const notice = parseQueueNotice(rawBody) ?? parseQueueNotice(message);
|
|
2131
|
+
if (notice) {
|
|
2132
|
+
return formatQueueFailureMessage(notice, {
|
|
2133
|
+
mode,
|
|
2134
|
+
// This call does not own the retry loop, so it claims no attempt count.
|
|
2135
|
+
waitedMs: 0,
|
|
2136
|
+
manageUrl: getQoderRegionConfig(mode).manageUrl,
|
|
2137
|
+
otherProviderID: mode === "cn" ? "qoder" : "qoder-cn"
|
|
2138
|
+
});
|
|
2283
2139
|
}
|
|
2284
|
-
return
|
|
2140
|
+
return isCn ? [
|
|
2141
|
+
`[Qoder CN \u4E0A\u6E38\u6392\u961F] \u6A21\u578B\u7F51\u5173\u5F53\u524D\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u6C42\u8FDB\u5165\u7B49\u5F85\u961F\u5217 (\u9519\u8BEF\u7801 10605)`,
|
|
2142
|
+
`- \u8BF4\u660E\uFF1A\u51ED\u8BC1\u4E0E\u989D\u5EA6\u5747\u6B63\u5E38\uFF0C\u7EAF\u7CB9\u662F\u4E0A\u6E38\u6392\u961F\uFF1B\u5DF2\u6309\u7F51\u5173\u7ED9\u51FA\u7684 Retry-After \u91CD\u8BD5\u4ECD\u672A\u653E\u884C\u3002`,
|
|
2143
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u7528 /model \u5207\u6362\u5230\u5176\u4ED6 Provider\uFF08\u5982\u5176\u4ED6\u6A21\u578B/\u6E20\u9053\uFF09\u3002`
|
|
2144
|
+
].join("\n") : [
|
|
2145
|
+
`[Qoder upstream queue] The model gateway is at capacity and parked this request (code 10605 isQueued)`,
|
|
2146
|
+
`- Meaning: credentials and quota are fine \u2014 this is pure upstream queuing; the gateway's own Retry-After wait did not clear it.`,
|
|
2147
|
+
`- Next step: retry shortly, or switch provider with /model while the queue drains.`
|
|
2148
|
+
].join("\n");
|
|
2285
2149
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2150
|
+
if (code === "1210" || /不支持关闭思考|cannot disable thinking|always.{0,12}think/i.test(bodyForMatch)) {
|
|
2151
|
+
return isCn ? [
|
|
2152
|
+
`[Qoder CN \u4E0D\u652F\u6301\u5173\u95ED\u601D\u8003] \u8BE5\u6A21\u578B\u59CB\u7EC8\u601D\u8003 (\u9519\u8BEF\u7801 1210)`,
|
|
2153
|
+
`- \u8BF4\u660E\uFF1A\u8FD9\u4E2A\u6A21\u578B\u6CA1\u6709\u201C\u5173\u95ED\u601D\u8003\u201D\u9009\u9879\uFF0C\u8BF7\u6C42\u91CC\u4E0D\u80FD\u5E26 enable_thinking=false\u3002`,
|
|
2154
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 thinking \u5F00\u5173\u9009 low / medium / high / max \u4EFB\u4E00\u6863\u4F4D\uFF1B\u5BA2\u6237\u7AEF\u5E94\u4E3A\u8BE5\u6A21\u578B\u7701\u7565\u601D\u8003\u5F00\u5173\u3002`
|
|
2155
|
+
].join("\n") : [
|
|
2156
|
+
`[Qoder model always thinks] This model cannot disable thinking (code 1210)`,
|
|
2157
|
+
`- Meaning: the model has no "off" switch, so a request carrying enable_thinking=false is refused. This will not change on retry.`,
|
|
2158
|
+
`- Next step: pick a thinking level (low / medium / high / max) instead of turning thinking off.`
|
|
2159
|
+
].join("\n");
|
|
2291
2160
|
}
|
|
2292
|
-
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2161
|
+
if (rawBody === void 0 || rawBody === null) {
|
|
2162
|
+
return `Upstream status ${statusCode}`;
|
|
2163
|
+
}
|
|
2164
|
+
const bodyStr = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
2165
|
+
const truncated = bodyStr.length > 500 ? `${bodyStr.slice(0, 500)}...` : bodyStr;
|
|
2166
|
+
return `Upstream status ${statusCode}: ${truncated}`;
|
|
2296
2167
|
}
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2168
|
+
function isCredentialExpiredError(error) {
|
|
2169
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2170
|
+
if (message.includes("\u9519\u8BEF\u7801 105") || message.includes("\u51ED\u8BC1\u5931\u6548")) return true;
|
|
2171
|
+
return isCredentialExpiredVerdict(void 0, message) || /TOKEN_EXPIRED?/i.test(message);
|
|
2299
2172
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
return {
|
|
2305
|
-
retryAfterMs: Math.min(retryMs, MAX_WAIT_MS),
|
|
2306
|
-
waitTimeSeconds: positiveNumber(payload.waitTime, 0),
|
|
2307
|
-
queueCount: positiveNumber(payload.queueCount, 0),
|
|
2308
|
-
// Only a real boolean is surfaced; a missing field must not be read as
|
|
2309
|
-
// "unavailable" (that would collapse the normal queue into a hard stop).
|
|
2310
|
-
serviceAvailable: typeof payload.serviceAvailable === "boolean" ? payload.serviceAvailable : void 0,
|
|
2311
|
-
// `subCode` sits inside the same record as `isQueued` in the payloads seen
|
|
2312
|
-
// live, so one source is enough; the wrapper levels carry no extra names.
|
|
2313
|
-
subCode: textOf(payload.subCode),
|
|
2314
|
-
queueType: textOf(payload.queueType),
|
|
2315
|
-
modelKey: textOf(payload.modelKey)
|
|
2316
|
-
};
|
|
2173
|
+
|
|
2174
|
+
// src/quota.ts
|
|
2175
|
+
function quotaNumber(value) {
|
|
2176
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2317
2177
|
}
|
|
2318
|
-
function
|
|
2319
|
-
|
|
2320
|
-
const seconds = Math.round(ms / 1e3);
|
|
2321
|
-
if (seconds < 100) return String(seconds);
|
|
2322
|
-
return `${formatWaitMinutes(seconds)}\u5206\u949F`;
|
|
2178
|
+
function bucketLimit(bucket) {
|
|
2179
|
+
return quotaNumber(bucket.total ?? bucket.cap);
|
|
2323
2180
|
}
|
|
2324
|
-
function
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
2328
|
-
const modelKey = notice.modelKey ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
2329
|
-
const hard = notice.serviceAvailable === false;
|
|
2330
|
-
const waited = formatWaitSecondsShort(ctx.waitedMs ?? 0);
|
|
2331
|
-
const queueLabel = formatQueueFigure(notice.queueCount ?? 0);
|
|
2332
|
-
const tried = ctx.attempts === void 0 ? void 0 : isCn ? `\u5DF2\u5C1D\u8BD5 ${formatQueueFigure(ctx.attempts)} \u6B21\uFF08\u7D2F\u8BA1\u7B49\u5F85 ${waited} \u79D2\uFF09\uFF0C\u7ED3\u8BBA\u76F8\u540C\u3002` : `${formatQueueFigure(ctx.attempts)} attempts (about ${waited} seconds of waiting) ended in the same verdict. `;
|
|
2333
|
-
if (hard) {
|
|
2334
|
-
return isCn ? [
|
|
2335
|
-
`[Qoder CN \u65E0\u53EF\u7528\u5BB9\u91CF] \u7F51\u5173\u56DE serviceAvailable:false\uFF08\u5B50\u7801 ${sub}\uFF0C\u961F\u5217\u957F\u5EA6 ${queueLabel}\uFF0C\u4F18\u5148\u7EA7\u901A\u9053 ${lane}\uFF0C\u6A21\u578B ${modelKey}\uFF09`,
|
|
2336
|
-
`- \u8BF4\u660E\uFF1A${tried ?? ""}\u8FD9\u662F\u8D26\u53F7\u4F18\u5148\u7EA7\u901A\u9053\u7684\u5BB9\u91CF\u9650\u5236\uFF0C\u4E0D\u662F\u51ED\u8BC1\u95EE\u9898\u3002\u6392\u961F\u5C1D\u8BD5\u4E0D\u4EA7\u751F\u8F93\u51FA\uFF0C\u5B9E\u6D4B\u4E5F\u4E0D\u6263 credits\uFF08\u4EE3\u4EF7\u53EA\u6709\u65F6\u5EF6\uFF09\u3002`,
|
|
2337
|
-
`- \u6362\u6A21\u578B\u65E0\u6548\uFF1A\u540C\u4E00\u8D26\u53F7\u5728 ${lane} \u901A\u9053\u4E0B\u5404\u6A21\u578B\u8FD4\u56DE\u540C\u4E00\u7ED3\u8BBA\uFF08\u5B9E\u6D4B auto / qmodel_38max / efficient / ultimate \u4E00\u81F4\uFF09\uFF0C\u8BF7\u52FF\u5728\u540C\u4E00 Provider \u5185\u53CD\u590D\u6311\u6A21\u578B\u3002`,
|
|
2338
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u6216\u5176\u4ED6 Provider\uFF0C\u6216\u7A0D\u540E\u91CD\u8BD5\u3002\u989D\u5EA6\u4E0E\u8BA1\u5212\uFF1A${ctx.manageUrl}`
|
|
2339
|
-
].join("\n") : [
|
|
2340
|
-
`[Qoder international has no spare capacity] The gateway answered serviceAvailable:false (sub code ${sub}, queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
2341
|
-
`- Meaning: ${tried ?? ""}This is the account's priority-lane capacity, not a credential problem. Queued attempts produce no output and are not billed (measured: 19 queued requests, credits unchanged) \u2014 the only cost is latency.`,
|
|
2342
|
-
`- Switching model will not help: every model of this account answers on the same ${lane} lane (measured across auto / qmodel_38max / efficient / ultimate).`,
|
|
2343
|
-
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry later. Plan and quota: ${ctx.manageUrl}`
|
|
2344
|
-
].join("\n");
|
|
2345
|
-
}
|
|
2346
|
-
return isCn ? [
|
|
2347
|
-
`[Qoder CN \u4E0A\u6E38\u6392\u961F] \u6A21\u578B\u7F51\u5173\u5F53\u524D\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u6C42\u8FDB\u5165\u7B49\u5F85\u961F\u5217\uFF08\u961F\u5217\u957F\u5EA6 ${queueLabel}\uFF0C\u4F18\u5148\u7EA7\u901A\u9053 ${lane}\uFF0C\u6A21\u578B ${modelKey}\uFF09`,
|
|
2348
|
-
`- \u8BF4\u660E\uFF1A${tried ?? ""}\u961F\u5217\u5C1A\u672A\u653E\u884C\u3002\u51ED\u8BC1\u4E0E\u989D\u5EA6\u6B63\u5E38\uFF0C\u7EAF\u7CB9\u662F\u4E0A\u6E38\u6392\u961F\uFF1B\u6392\u961F\u5C1D\u8BD5\u4E0D\u6D88\u8017 credits\u3002`,
|
|
2349
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u7B49\u5176\u4ED6 Provider\u3002\u989D\u5EA6\u4E0E\u8BA1\u5212\uFF1A${ctx.manageUrl}`
|
|
2350
|
-
].join("\n") : [
|
|
2351
|
-
// "is at capacity" is the established wording for the drainable case and
|
|
2352
|
-
// the only queue text allowed to carry it; the hard refusal above must
|
|
2353
|
-
// never read as "wait it out".
|
|
2354
|
-
`[Qoder upstream queue] The model gateway is at capacity and parked this request (queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
2355
|
-
`- Meaning: ${tried ?? ""}the queue has not cleared yet. Credentials and quota are fine; queued attempts are not billed.`,
|
|
2356
|
-
`- Next step: retry shortly, or switch provider with /model to ${ctx.otherProviderID} while the queue drains. Plan and quota: ${ctx.manageUrl}`
|
|
2357
|
-
].join("\n");
|
|
2181
|
+
function hasAllowance(bucket) {
|
|
2182
|
+
if (!bucket) return false;
|
|
2183
|
+
return bucketLimit(bucket) > 0 || quotaNumber(bucket.used) > 0 || quotaNumber(bucket.remaining) > 0;
|
|
2358
2184
|
}
|
|
2359
|
-
function
|
|
2360
|
-
|
|
2361
|
-
const
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
return isCn ? [
|
|
2365
|
-
`[Qoder CN \u5DF2\u8DF3\u8FC7\u672C\u6B21\u8BF7\u6C42] \u4F18\u5148\u7EA7\u901A\u9053 ${lane} \u521A\u521A\u56DE\u8FC7 serviceAvailable:false\uFF08\u5B50\u7801 ${sub}\uFF09\uFF0C${left} \u79D2\u5185\u4E0D\u518D\u91CD\u590D\u7B49\u5F85\u3002`,
|
|
2366
|
-
`- \u8BF4\u660E\uFF1A\u8FD9\u6B21\u8BF7\u6C42\u5C1A\u672A\u53D1\u51FA\u3002\u4E0A\u4E00\u8F6E\u540C\u901A\u9053\u8BF7\u6C42\u6309 Retry-After \u7B49\u5F85\u540E\u4ECD\u672A\u653E\u884C\uFF0C\u6392\u961F\u53EA\u4F1A\u518D\u6D88\u8017\u540C\u6837\u7684\u65F6\u5EF6\u3002`,
|
|
2367
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u6216\u5176\u4ED6 Provider\uFF0C\u6216 ${left} \u79D2\u540E\u91CD\u8BD5\u3002`
|
|
2368
|
-
].join("\n") : [
|
|
2369
|
-
`[Qoder request skipped] Priority lane ${lane} just refused service (sub code ${sub}, serviceAvailable:false), so this request was not sent; repeats are skipped for ${left} more seconds.`,
|
|
2370
|
-
`- Meaning: the previous request on this lane already waited through Retry-After and was still refused, so waiting again buys the same verdict.`,
|
|
2371
|
-
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry in ${left} seconds.`
|
|
2372
|
-
].join("\n");
|
|
2185
|
+
function pickBucket(raw, camel) {
|
|
2186
|
+
if (!raw) return void 0;
|
|
2187
|
+
const source = raw;
|
|
2188
|
+
const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
2189
|
+
return source[snake] ?? source[camel];
|
|
2373
2190
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
return
|
|
2191
|
+
|
|
2192
|
+
// src/auth/usage.ts
|
|
2193
|
+
init_region();
|
|
2194
|
+
function round2(value) {
|
|
2195
|
+
return Math.round(value * 100) / 100;
|
|
2379
2196
|
}
|
|
2380
|
-
function
|
|
2381
|
-
|
|
2382
|
-
|
|
2197
|
+
function resetTimestamp(expiresAt) {
|
|
2198
|
+
const ms = quotaNumber(expiresAt);
|
|
2199
|
+
if (ms <= 0) return void 0;
|
|
2200
|
+
const timestamp = ms < 1e12 ? ms * 1e3 : ms;
|
|
2201
|
+
if (new Date(timestamp).getUTCFullYear() >= 9999) return void 0;
|
|
2202
|
+
return new Date(timestamp).toISOString();
|
|
2383
2203
|
}
|
|
2384
|
-
function
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2204
|
+
async function fetchQoderUsageForMode(credentials, mode) {
|
|
2205
|
+
const region = getQoderRegionConfig(mode);
|
|
2206
|
+
const response = await fetch(getQoderUsageURL(mode), {
|
|
2207
|
+
method: "GET",
|
|
2208
|
+
headers: {
|
|
2209
|
+
Authorization: `Bearer ${credentials.access}`,
|
|
2210
|
+
Accept: "application/json",
|
|
2211
|
+
"User-Agent": "pi-provider-qoder-cn"
|
|
2389
2212
|
}
|
|
2390
|
-
let timer;
|
|
2391
|
-
const onAbort = () => {
|
|
2392
|
-
clearTimeout(timer);
|
|
2393
|
-
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
2394
|
-
};
|
|
2395
|
-
timer = setTimeout(() => {
|
|
2396
|
-
signal?.removeEventListener("abort", onAbort);
|
|
2397
|
-
resolve();
|
|
2398
|
-
}, ms);
|
|
2399
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2400
2213
|
});
|
|
2214
|
+
if (!response.ok) {
|
|
2215
|
+
const body = await response.text().catch(() => "");
|
|
2216
|
+
if (isOpenApiInactiveCredential(response.status, body)) {
|
|
2217
|
+
throw new Error(formatInactiveCredentialMessage(getQoderUsageURL(mode), credentials.access, mode));
|
|
2218
|
+
}
|
|
2219
|
+
throw new Error(`Failed to fetch Qoder usage: ${response.status} ${response.statusText}`);
|
|
2220
|
+
}
|
|
2221
|
+
const raw = await response.json();
|
|
2222
|
+
const org = pickBucket(raw, "orgResourcePackage") ?? pickBucket(raw, "sharedQuota");
|
|
2223
|
+
const resetAt = resetTimestamp(raw.expiresAt);
|
|
2224
|
+
const buckets = [
|
|
2225
|
+
{ id: "user-quota", label: "User Quota", quota: pickBucket(raw, "userQuota") },
|
|
2226
|
+
{ id: "add-on-quota", label: "Add-on", quota: pickBucket(raw, "addOnQuota") },
|
|
2227
|
+
{ id: "org-resource-package", label: "Org Resource Package", quota: org }
|
|
2228
|
+
].filter((entry) => hasAllowance(entry.quota));
|
|
2229
|
+
const usageBuckets = buckets.map(({ id, label, quota }) => ({
|
|
2230
|
+
id,
|
|
2231
|
+
label,
|
|
2232
|
+
usedDisplay: round2(quotaNumber(quota?.used)).toFixed(2),
|
|
2233
|
+
limitDisplay: round2(quotaNumber(quota?.total ?? quota?.cap)).toFixed(2),
|
|
2234
|
+
unit: quota?.unit || "credits",
|
|
2235
|
+
resetAt
|
|
2236
|
+
}));
|
|
2237
|
+
const remaining = buckets.reduce((sum, { quota }) => sum + Math.max(0, quotaNumber(quota?.remaining)), 0);
|
|
2238
|
+
const unit = buckets[0]?.quota?.unit || "credits";
|
|
2239
|
+
return {
|
|
2240
|
+
summary: buckets.length > 0 ? `${round2(remaining)} ${unit} remaining` : "",
|
|
2241
|
+
subscriptionTitle: region.usageTitle,
|
|
2242
|
+
resetAt,
|
|
2243
|
+
manageUrl: region.manageUrl,
|
|
2244
|
+
usageBuckets,
|
|
2245
|
+
raw
|
|
2246
|
+
};
|
|
2401
2247
|
}
|
|
2402
2248
|
|
|
2403
|
-
// src/
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2249
|
+
// src/index.ts
|
|
2250
|
+
init_catalog();
|
|
2251
|
+
|
|
2252
|
+
// src/commands/claim.ts
|
|
2253
|
+
init_expiry();
|
|
2254
|
+
init_cosy();
|
|
2255
|
+
import { spawnSync } from "node:child_process";
|
|
2256
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2257
|
+
import { homedir as homedir5, hostname } from "node:os";
|
|
2258
|
+
import { basename, dirname as dirname4, join as join5 } from "node:path";
|
|
2259
|
+
import { fileURLToPath } from "node:url";
|
|
2260
|
+
init_region();
|
|
2261
|
+
function piAgentDir2() {
|
|
2262
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
2263
|
+
return join5(home, ".pi", "agent");
|
|
2264
|
+
}
|
|
2265
|
+
function getCheckinCachePath(mode = "cn") {
|
|
2266
|
+
const file = mode === "cn" ? "qoder-cn-checkin.json" : "qoder-checkin.json";
|
|
2267
|
+
return join5(piAgentDir2(), file);
|
|
2268
|
+
}
|
|
2269
|
+
function readCheckinCache(mode = "cn") {
|
|
2270
|
+
try {
|
|
2271
|
+
const file = getCheckinCachePath(mode);
|
|
2272
|
+
if (!existsSync5(file)) return null;
|
|
2273
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
2274
|
+
} catch {
|
|
2275
|
+
return null;
|
|
2413
2276
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
} catch {
|
|
2427
|
-
}
|
|
2277
|
+
}
|
|
2278
|
+
function writeCheckinCache(info, mode = "cn") {
|
|
2279
|
+
try {
|
|
2280
|
+
const file = getCheckinCachePath(mode);
|
|
2281
|
+
const dir = dirname4(file);
|
|
2282
|
+
if (!existsSync5(dir)) mkdirSync5(dir, { recursive: true });
|
|
2283
|
+
writeFileSync5(file, JSON.stringify(info, null, 2), "utf8");
|
|
2284
|
+
return true;
|
|
2285
|
+
} catch {
|
|
2286
|
+
return false;
|
|
2428
2287
|
}
|
|
2429
|
-
const code = obj.code ?? obj.errorCode;
|
|
2430
|
-
const message = obj.message ?? obj.errorMessage;
|
|
2431
|
-
if (!code && !message) return null;
|
|
2432
|
-
return {
|
|
2433
|
-
code: code !== void 0 ? String(code) : void 0,
|
|
2434
|
-
message: message !== void 0 ? String(message) : void 0
|
|
2435
|
-
};
|
|
2436
2288
|
}
|
|
2437
|
-
|
|
2289
|
+
var ANSI = {
|
|
2290
|
+
reset: "\x1B[0m",
|
|
2291
|
+
bold: "\x1B[1m",
|
|
2292
|
+
green: "\x1B[32m",
|
|
2293
|
+
yellow: "\x1B[33m",
|
|
2294
|
+
red: "\x1B[31m",
|
|
2295
|
+
cyan: "\x1B[36m"
|
|
2296
|
+
};
|
|
2297
|
+
function paint(text, code) {
|
|
2298
|
+
return code && text ? `${code}${text}${ANSI.reset}` : text;
|
|
2299
|
+
}
|
|
2300
|
+
function msUntilBeijing10AM(now = Date.now()) {
|
|
2438
2301
|
const beijingOffset = 8 * 36e5;
|
|
2439
2302
|
const beijingTime = now + beijingOffset;
|
|
2440
2303
|
const beijingDate = new Date(beijingTime);
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
0,
|
|
2447
|
-
0
|
|
2448
|
-
);
|
|
2449
|
-
return nextMidnightUtc - beijingTime;
|
|
2304
|
+
let targetUtc = Date.UTC(beijingDate.getUTCFullYear(), beijingDate.getUTCMonth(), beijingDate.getUTCDate(), 10, 0, 0);
|
|
2305
|
+
if (beijingTime >= targetUtc) {
|
|
2306
|
+
targetUtc += 24 * 36e5;
|
|
2307
|
+
}
|
|
2308
|
+
return targetUtc - beijingTime;
|
|
2450
2309
|
}
|
|
2451
|
-
function
|
|
2452
|
-
const
|
|
2453
|
-
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
2310
|
+
function formatCountdownBeijing(ms) {
|
|
2311
|
+
const totalMinutes = Math.max(1, Math.round(ms / 6e4));
|
|
2454
2312
|
const hours = Math.floor(totalMinutes / 60);
|
|
2455
2313
|
const minutes = totalMinutes % 60;
|
|
2456
2314
|
return hours > 0 ? `${hours}\u5C0F\u65F6${minutes}\u5206\u949F` : `${minutes}\u5206\u949F`;
|
|
2457
2315
|
}
|
|
2458
|
-
function
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2316
|
+
function formatDateTime(isoOrMs) {
|
|
2317
|
+
if (!isoOrMs) return "n/a";
|
|
2318
|
+
const d = new Date(isoOrMs);
|
|
2319
|
+
if (Number.isNaN(d.getTime())) return String(isoOrMs);
|
|
2320
|
+
return new Intl.DateTimeFormat("zh-CN", {
|
|
2321
|
+
timeZone: "Asia/Shanghai",
|
|
2322
|
+
year: "numeric",
|
|
2323
|
+
month: "2-digit",
|
|
2324
|
+
day: "2-digit",
|
|
2325
|
+
hour: "2-digit",
|
|
2326
|
+
minute: "2-digit",
|
|
2327
|
+
second: "2-digit",
|
|
2328
|
+
hour12: false
|
|
2329
|
+
}).format(d);
|
|
2330
|
+
}
|
|
2331
|
+
var printableAscii = (v) => typeof v === "string" && v.trim().length > 0 && /^[\x20-\x7e]+$/.test(v.trim());
|
|
2332
|
+
var identityMemCache = /* @__PURE__ */ new Map();
|
|
2333
|
+
var IDENTITY_TTL_MS = 30 * 6e4;
|
|
2334
|
+
function candidateUmidExes() {
|
|
2335
|
+
const custom = process.env.QODER_UMID_EXE;
|
|
2336
|
+
if (custom) return [custom];
|
|
2337
|
+
const candidates = [];
|
|
2338
|
+
if (process.platform === "win32") {
|
|
2339
|
+
const moduleDir = dirname4(fileURLToPath(import.meta.url));
|
|
2340
|
+
const pkgRoot = basename(moduleDir) === "commands" ? dirname4(dirname4(moduleDir)) : dirname4(moduleDir);
|
|
2341
|
+
candidates.push(join5(pkgRoot, "bin", "runtime-info.exe"));
|
|
2480
2342
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
`- \u9650\u5236\u539F\u56E0\uFF1A\u7BA1\u7406\u5458\u5728\u4F01\u4E1A\u63A7\u5236\u53F0\u5206\u914D\u7ED9\u60A8\u4E2A\u4EBA\u7684\u53EF\u7528 Credits \u989D\u5EA6\u5DF2\u8017\u5C3D\u3002`,
|
|
2485
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u8054\u7CFB\u4F01\u4E1A\u7BA1\u7406\u5458\u5728 Qoder \u56E2\u961F\u7BA1\u7406\u540E\u53F0\u4E3A\u60A8\u589E\u52A0\u6210\u5458 Credits \u914D\u989D\u3002`
|
|
2486
|
-
].join("\n") : [
|
|
2487
|
-
`[Qoder enterprise quota] Your member credit allowance is exhausted (code 117)`,
|
|
2488
|
-
`- Reason: the admin-assigned per-member credit cap has been consumed.`,
|
|
2489
|
-
`- Fix: ask your organization admin to raise your member quota in the team console.`
|
|
2490
|
-
].join("\n");
|
|
2343
|
+
const localAppData = process.env.LOCALAPPDATA || join5(process.env.HOME || process.env.USERPROFILE || homedir5(), "AppData", "Local");
|
|
2344
|
+
for (const r of [join5(localAppData, "Programs", "Qoder"), "C:\\Program Files\\Qoder", "D:\\Qoder"]) {
|
|
2345
|
+
candidates.push(join5(r, "resources", "umid", "runtime-info.exe"));
|
|
2491
2346
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2347
|
+
return candidates;
|
|
2348
|
+
}
|
|
2349
|
+
function runUmidExe(exe, environment, accountId) {
|
|
2350
|
+
const res = spawnSync(exe, [String(environment), "--account-stdin"], {
|
|
2351
|
+
input: JSON.stringify({ account: accountId }),
|
|
2352
|
+
timeout: 1e4,
|
|
2353
|
+
windowsHide: true,
|
|
2354
|
+
encoding: "utf8",
|
|
2355
|
+
maxBuffer: 1024 * 1024
|
|
2356
|
+
});
|
|
2357
|
+
if (res.error) throw res.error;
|
|
2358
|
+
if (res.status !== 0) throw new Error(`runtime-info exited ${res.status}: ${String(res.stderr || "").slice(0, 120)}`);
|
|
2359
|
+
return String(res.stdout || "");
|
|
2360
|
+
}
|
|
2361
|
+
async function resolveSashMachineIdentity(mode, accountId) {
|
|
2362
|
+
if (!accountId) return null;
|
|
2363
|
+
const key = `${mode}:${accountId}`;
|
|
2364
|
+
const hit = identityMemCache.get(key);
|
|
2365
|
+
if (hit && hit.expiresAt > Date.now()) return hit.identity;
|
|
2366
|
+
const environment = mode === "global" ? 3 : 0;
|
|
2367
|
+
for (const exe of candidateUmidExes()) {
|
|
2368
|
+
if (!existsSync5(exe)) continue;
|
|
2369
|
+
try {
|
|
2370
|
+
const stdout = await runUmidExe(exe, environment, accountId);
|
|
2371
|
+
const parsed = JSON.parse(stdout);
|
|
2372
|
+
if (!printableAscii(parsed.machineToken) || !printableAscii(parsed.machineCode) || !printableAscii(parsed.machineType)) {
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
const identity = {
|
|
2376
|
+
machineToken: parsed.machineToken.trim(),
|
|
2377
|
+
machineCode: parsed.machineCode.trim(),
|
|
2378
|
+
machineType: parsed.machineType.trim()
|
|
2379
|
+
};
|
|
2380
|
+
identityMemCache.set(key, { identity, expiresAt: Date.now() + IDENTITY_TTL_MS });
|
|
2381
|
+
return identity;
|
|
2382
|
+
} catch {
|
|
2383
|
+
}
|
|
2502
2384
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2385
|
+
return null;
|
|
2386
|
+
}
|
|
2387
|
+
function buildSashHeaders(accessToken, machineID, identity) {
|
|
2388
|
+
const headers = {
|
|
2389
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2390
|
+
Accept: "application/json",
|
|
2391
|
+
"Content-Type": "application/json",
|
|
2392
|
+
"Cosy-ClientType": "10",
|
|
2393
|
+
"Cosy-Version": "1.13.3",
|
|
2394
|
+
"Cosy-MachineId": machineID,
|
|
2395
|
+
"Cosy-MachineOS": "x86_64_windows",
|
|
2396
|
+
"Cosy-MachineHostname": hostname(),
|
|
2397
|
+
"User-Agent": "Qoder"
|
|
2398
|
+
};
|
|
2399
|
+
if (identity) {
|
|
2400
|
+
headers["Cosy-MachineToken"] = identity.machineToken;
|
|
2401
|
+
headers["Cosy-MachineCode"] = identity.machineCode;
|
|
2402
|
+
headers["Cosy-MachineType"] = identity.machineType;
|
|
2513
2403
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2404
|
+
return headers;
|
|
2405
|
+
}
|
|
2406
|
+
async function fetchQoderCampaigns(accessToken, machineID, mode, identity) {
|
|
2407
|
+
const url = `${getQoderOpenApiUrl(mode)}/sash/api/v1/me/campaigns?forceRefresh=true`;
|
|
2408
|
+
const response = await fetch(url, {
|
|
2409
|
+
method: "GET",
|
|
2410
|
+
headers: buildSashHeaders(accessToken, machineID, identity),
|
|
2411
|
+
signal: AbortSignal.timeout(1e4)
|
|
2412
|
+
});
|
|
2413
|
+
const text = await response.text();
|
|
2414
|
+
let data;
|
|
2415
|
+
try {
|
|
2416
|
+
data = JSON.parse(text);
|
|
2417
|
+
} catch {
|
|
2418
|
+
data = void 0;
|
|
2524
2419
|
}
|
|
2525
|
-
if (
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2420
|
+
if (!response.ok) {
|
|
2421
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2422
|
+
throw new Error(formatInactiveCredentialMessage(url, accessToken, mode));
|
|
2423
|
+
}
|
|
2424
|
+
const detail = data?.message || text.slice(0, 200);
|
|
2425
|
+
throw new Error(`Failed to query Qoder campaigns (${response.status}): ${detail}`);
|
|
2426
|
+
}
|
|
2427
|
+
if (!data) throw new Error("Qoder campaigns response was not JSON.");
|
|
2428
|
+
return data;
|
|
2429
|
+
}
|
|
2430
|
+
async function claimQoderCampaign(accessToken, machineID, campaignId, mode, identity) {
|
|
2431
|
+
const url = `${getQoderOpenApiUrl(mode)}/sash/api/v1/me/campaigns/${encodeURIComponent(campaignId)}/claim`;
|
|
2432
|
+
const response = await fetch(url, {
|
|
2433
|
+
method: "POST",
|
|
2434
|
+
headers: buildSashHeaders(accessToken, machineID, identity),
|
|
2435
|
+
body: JSON.stringify({}),
|
|
2436
|
+
signal: AbortSignal.timeout(1e4)
|
|
2437
|
+
});
|
|
2438
|
+
const text = await response.text();
|
|
2439
|
+
let data;
|
|
2440
|
+
try {
|
|
2441
|
+
data = JSON.parse(text);
|
|
2442
|
+
} catch {
|
|
2443
|
+
data = void 0;
|
|
2536
2444
|
}
|
|
2537
|
-
if (
|
|
2538
|
-
if (
|
|
2539
|
-
|
|
2540
|
-
`[Qoder CN \u51ED\u8BC1\u5931\u6548] \u767B\u5F55\u6001\u5DF2\u8FC7\u671F\u6216 Token \u5931\u6548 (\u9519\u8BEF\u7801 105)`,
|
|
2541
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u91CD\u65B0\u8FD0\u884C /login qoder-cn\uFF0C\u6216\u66F4\u65B0\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u4E2A\u4EBA\u8BBF\u95EE\u4EE4\u724C\uFF08PAT\uFF09\u3002`
|
|
2542
|
-
].join("\n");
|
|
2445
|
+
if (!response.ok) {
|
|
2446
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2447
|
+
throw new Error(formatInactiveCredentialMessage(url, accessToken, mode));
|
|
2543
2448
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
`- Next step: run /login qoder. You can also refresh credentials by exporting QODER_PERSONAL_ACCESS_TOKEN (PAT).`
|
|
2547
|
-
].join("\n");
|
|
2449
|
+
const detail = data?.message || text.slice(0, 200);
|
|
2450
|
+
throw new Error(`Failed to claim Qoder benefit (${response.status}): ${detail}`);
|
|
2548
2451
|
}
|
|
2549
|
-
if (
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
`- Reason: an already-signed request (same COSY Authorization/timestamp/signature) was submitted again. This is a transient server error, not a quota or credential problem.`,
|
|
2557
|
-
`- Next step: every retry must be re-signed; if it keeps repeating, switch provider with /model and try again shortly.`
|
|
2558
|
-
].join("\n");
|
|
2452
|
+
if (!data) throw new Error("Qoder claim response was not JSON.");
|
|
2453
|
+
return data;
|
|
2454
|
+
}
|
|
2455
|
+
async function fetchCheckinGrantInfo(accessToken, machineID, mode, identity) {
|
|
2456
|
+
const cached = readCheckinCache(mode);
|
|
2457
|
+
if (cached?.claimed && cached.expiresAt && cached.cacheUntil && Date.now() < cached.cacheUntil) {
|
|
2458
|
+
return cached;
|
|
2559
2459
|
}
|
|
2560
|
-
|
|
2561
|
-
const
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2460
|
+
try {
|
|
2461
|
+
const campaignsData = await fetchQoderCampaigns(accessToken, machineID, mode, identity);
|
|
2462
|
+
const campaigns = campaignsData.campaigns || [];
|
|
2463
|
+
const dailyCreditCampaign = campaigns.find(
|
|
2464
|
+
(c) => c.actionType === "CLAIM_BENEFIT" && (c.benefit?.kind === "CREDITS" || c.benefit?.amount === 100)
|
|
2465
|
+
) || campaigns.find((c) => c.actionType === "CLAIM_BENEFIT");
|
|
2466
|
+
if (!dailyCreditCampaign) return null;
|
|
2467
|
+
const title = dailyCreditCampaign.placements?.[0]?.content?.zh?.title || "\u6BCF\u5929\u9886 100 Credits";
|
|
2468
|
+
const amount = dailyCreditCampaign.benefit?.amount || 100;
|
|
2469
|
+
const validityDays = dailyCreditCampaign.benefit?.validity?.days || 30;
|
|
2470
|
+
const endAtMs = toEpochMs(dailyCreditCampaign.endAt);
|
|
2471
|
+
const cacheUntil = endAtMs && endAtMs > Date.now() ? endAtMs : Date.now() + msUntilBeijing10AM();
|
|
2472
|
+
let info;
|
|
2473
|
+
if (dailyCreditCampaign.claimStatus === "CLAIMED") {
|
|
2474
|
+
try {
|
|
2475
|
+
const claimRes = await claimQoderCampaign(
|
|
2476
|
+
accessToken,
|
|
2477
|
+
machineID,
|
|
2478
|
+
dailyCreditCampaign.campaignId,
|
|
2479
|
+
mode,
|
|
2480
|
+
identity
|
|
2481
|
+
);
|
|
2482
|
+
info = {
|
|
2483
|
+
claimed: true,
|
|
2484
|
+
amount: claimRes.benefit?.amount || amount,
|
|
2485
|
+
claimedAt: claimRes.claimedAt || cached?.claimedAt,
|
|
2486
|
+
expiresAt: claimRes.expiresAt || cached?.expiresAt,
|
|
2487
|
+
validityDays: claimRes.benefit?.validity?.days || validityDays,
|
|
2488
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2489
|
+
campaignTitle: title,
|
|
2490
|
+
cacheUntil
|
|
2491
|
+
};
|
|
2492
|
+
} catch {
|
|
2493
|
+
info = {
|
|
2494
|
+
claimed: true,
|
|
2495
|
+
amount,
|
|
2496
|
+
claimedAt: cached?.claimedAt,
|
|
2497
|
+
expiresAt: cached?.expiresAt,
|
|
2498
|
+
validityDays,
|
|
2499
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2500
|
+
campaignTitle: title,
|
|
2501
|
+
cacheUntil
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
} else {
|
|
2505
|
+
info = {
|
|
2506
|
+
claimed: false,
|
|
2507
|
+
amount,
|
|
2508
|
+
validityDays,
|
|
2509
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2510
|
+
campaignTitle: title,
|
|
2511
|
+
cacheUntil
|
|
2512
|
+
};
|
|
2570
2513
|
}
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
].join("\n") : [
|
|
2576
|
-
`[Qoder upstream queue] The model gateway is at capacity and parked this request (code 10605 isQueued)`,
|
|
2577
|
-
`- Meaning: credentials and quota are fine \u2014 this is pure upstream queuing; the gateway's own Retry-After wait did not clear it.`,
|
|
2578
|
-
`- Next step: retry shortly, or switch provider with /model while the queue drains.`
|
|
2579
|
-
].join("\n");
|
|
2580
|
-
}
|
|
2581
|
-
if (code === "1210" || /不支持关闭思考|cannot disable thinking|always.{0,12}think/i.test(bodyForMatch)) {
|
|
2582
|
-
return isCn ? [
|
|
2583
|
-
`[Qoder CN \u4E0D\u652F\u6301\u5173\u95ED\u601D\u8003] \u8BE5\u6A21\u578B\u59CB\u7EC8\u601D\u8003 (\u9519\u8BEF\u7801 1210)`,
|
|
2584
|
-
`- \u8BF4\u660E\uFF1A\u8FD9\u4E2A\u6A21\u578B\u6CA1\u6709\u201C\u5173\u95ED\u601D\u8003\u201D\u9009\u9879\uFF0C\u8BF7\u6C42\u91CC\u4E0D\u80FD\u5E26 enable_thinking=false\u3002`,
|
|
2585
|
-
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 thinking \u5F00\u5173\u9009 low / medium / high / max \u4EFB\u4E00\u6863\u4F4D\uFF1B\u5BA2\u6237\u7AEF\u5E94\u4E3A\u8BE5\u6A21\u578B\u7701\u7565\u601D\u8003\u5F00\u5173\u3002`
|
|
2586
|
-
].join("\n") : [
|
|
2587
|
-
`[Qoder model always thinks] This model cannot disable thinking (code 1210)`,
|
|
2588
|
-
`- Meaning: the model has no "off" switch, so a request carrying enable_thinking=false is refused. This will not change on retry.`,
|
|
2589
|
-
`- Next step: pick a thinking level (low / medium / high / max) instead of turning thinking off.`
|
|
2590
|
-
].join("\n");
|
|
2514
|
+
writeCheckinCache(info, mode);
|
|
2515
|
+
return info;
|
|
2516
|
+
} catch {
|
|
2517
|
+
return readCheckinCache(mode);
|
|
2591
2518
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2519
|
+
}
|
|
2520
|
+
async function resolveCredentials(providerID, ctx) {
|
|
2521
|
+
let accessToken;
|
|
2522
|
+
try {
|
|
2523
|
+
accessToken = await ctx?.modelRegistry?.getApiKeyForProvider(providerID) || void 0;
|
|
2524
|
+
} catch {
|
|
2594
2525
|
}
|
|
2595
|
-
const
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2526
|
+
const { getCachedCredentials: getCachedCredentials2 } = await Promise.resolve().then(() => (init_oauth(), oauth_exports));
|
|
2527
|
+
const stored = getCachedCredentials2("", providerID);
|
|
2528
|
+
accessToken = accessToken || stored?.access || void 0;
|
|
2529
|
+
if (!accessToken) return null;
|
|
2530
|
+
const machineID = stored?.machineID || getMachineId();
|
|
2531
|
+
const userID = stored?.userID || "";
|
|
2532
|
+
return { accessToken, machineID, userID };
|
|
2598
2533
|
}
|
|
2599
|
-
function
|
|
2600
|
-
const
|
|
2601
|
-
|
|
2534
|
+
async function runClaimCommand(mode, args, ctx) {
|
|
2535
|
+
const region = getQoderRegionConfig(mode);
|
|
2536
|
+
const providerID = region.providerID;
|
|
2537
|
+
const wantsRaw = ["json", "raw", "debug"].includes((args || "").trim().toLowerCase());
|
|
2538
|
+
try {
|
|
2539
|
+
const creds = await resolveCredentials(providerID, ctx);
|
|
2540
|
+
if (!creds) {
|
|
2541
|
+
const msg = mode === "cn" ? `\u672A\u627E\u5230 ${providerID} \u767B\u5F55\u51ED\u636E\uFF0C\u8BF7\u5148\u8FD0\u884C /login ${providerID}` : `No ${providerID} credentials found. Run /login ${providerID} first.`;
|
|
2542
|
+
ctx?.ui?.notify(msg, "warning");
|
|
2543
|
+
if (!ctx?.ui) console.warn(msg);
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
const { accessToken, machineID, userID } = creds;
|
|
2547
|
+
const identity = await resolveSashMachineIdentity(mode, userID).catch(() => null);
|
|
2548
|
+
const campaignsData = await fetchQoderCampaigns(accessToken, machineID, mode, identity);
|
|
2549
|
+
const campaigns = campaignsData.campaigns || [];
|
|
2550
|
+
const dailyCreditCampaign = campaigns.find(
|
|
2551
|
+
(c) => c.actionType === "CLAIM_BENEFIT" && (c.benefit?.kind === "CREDITS" || c.benefit?.amount === 100)
|
|
2552
|
+
) || campaigns.find((c) => c.actionType === "CLAIM_BENEFIT");
|
|
2553
|
+
if (!dailyCreditCampaign) {
|
|
2554
|
+
if (wantsRaw) {
|
|
2555
|
+
const rawJson = JSON.stringify(campaignsData, null, 2);
|
|
2556
|
+
ctx?.ui?.notify(rawJson, "info");
|
|
2557
|
+
if (!ctx?.ui) console.log(rawJson);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
const msg = identity === null ? mode === "cn" ? "\u26A0\uFE0F \u672A\u627E\u5230 Qoder \u684C\u9762\u7AEF\u8BBE\u5907\u6307\u7EB9\u7EC4\u4EF6\uFF08runtime-info.exe\uFF09\uFF0C\u670D\u52A1\u7AEF\u56E0\u6B64\u4E0D\u4E0B\u53D1\u53EF\u9886\u53D6\u7684\u7B7E\u5230\u6D3B\u52A8\u3002\u8BF7\u5B89\u88C5\u5E76\u6253\u5F00 Qoder \u684C\u9762\u7AEF\uFF0C\u6216\u8BBE\u7F6E QODER_UMID_EXE \u6307\u5411\u5176 resources/umid/runtime-info.exe \u540E\u91CD\u8BD5\u3002" : "\u26A0\uFE0F Daily check-in campaigns are hidden without desktop device attestation, and the Qoder desktop UMID component (runtime-info.exe) was not found on this machine.\n- Install/launch the Qoder desktop app, or set QODER_UMID_EXE to its resources/umid/runtime-info.exe, then retry." : mode === "cn" ? "\u2139\uFE0F \u5F53\u524D\u6682\u65E0\u53EF\u9886\u53D6\u7684\u7B7E\u5230\u6D3B\u52A8\uFF08\u6BCF\u65E5 10:00 UTC+8 \u5F00\u653E\u5237\u65B0\uFF09" : "\u2139\uFE0F Device verified, but no claimable check-in campaign is offered for this account/region right now (the campaign list carries banners only; claims reset at 10:00 UTC+8 when offered).";
|
|
2561
|
+
ctx?.ui?.notify(msg, identity === null ? "warning" : "info");
|
|
2562
|
+
if (!ctx?.ui) console.log(msg);
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
const content = mode === "cn" ? dailyCreditCampaign.placements?.[0]?.content?.zh || dailyCreditCampaign.placements?.[0]?.content?.en : dailyCreditCampaign.placements?.[0]?.content?.en || dailyCreditCampaign.placements?.[0]?.content?.zh;
|
|
2566
|
+
const campaignTitle = content?.title || (mode === "cn" ? "\u6BCF\u5929\u9886 100 Credits" : "Claim 100 Credits Daily");
|
|
2567
|
+
const amount = dailyCreditCampaign.benefit?.amount || 100;
|
|
2568
|
+
const validityDays = dailyCreditCampaign.benefit?.validity?.days || 30;
|
|
2569
|
+
const endAtMs = toEpochMs(dailyCreditCampaign.endAt);
|
|
2570
|
+
const remainingToReset = endAtMs && endAtMs > Date.now() ? endAtMs - Date.now() : msUntilBeijing10AM();
|
|
2571
|
+
const countdown = formatCountdownBeijing(remainingToReset);
|
|
2572
|
+
if (dailyCreditCampaign.claimStatus === "CLAIMED") {
|
|
2573
|
+
const cached = readCheckinCache(mode);
|
|
2574
|
+
writeCheckinCache(
|
|
2575
|
+
{
|
|
2576
|
+
claimed: true,
|
|
2577
|
+
amount,
|
|
2578
|
+
validityDays,
|
|
2579
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2580
|
+
campaignTitle,
|
|
2581
|
+
claimedAt: cached?.claimedAt,
|
|
2582
|
+
expiresAt: cached?.expiresAt,
|
|
2583
|
+
cacheUntil: Date.now() + remainingToReset
|
|
2584
|
+
},
|
|
2585
|
+
mode
|
|
2586
|
+
);
|
|
2587
|
+
if (wantsRaw) {
|
|
2588
|
+
const rawJson = JSON.stringify(dailyCreditCampaign, null, 2);
|
|
2589
|
+
ctx?.ui?.notify(rawJson, "info");
|
|
2590
|
+
if (!ctx?.ui) console.log(rawJson);
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
const lines2 = mode === "cn" ? [
|
|
2594
|
+
paint(`\u2139\uFE0F \u4ECA\u65E5 ${amount} Credits \u5DF2\u7ECF\u9886\u53D6\u8FC7\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C`, ANSI.cyan),
|
|
2595
|
+
`- \u6D3B\u52A8\u540D\u79F0\uFF1A${campaignTitle}`,
|
|
2596
|
+
`- \u989D\u5EA6\u8BF4\u660E\uFF1A${amount} Credits\uFF08\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF0C${validityDays} \u5929\u6709\u6548\uFF09`,
|
|
2597
|
+
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2598
|
+
] : [
|
|
2599
|
+
paint(`\u2139\uFE0F Today's ${amount} Credits already claimed.`, ANSI.cyan),
|
|
2600
|
+
`- Campaign: ${campaignTitle}`,
|
|
2601
|
+
`- Benefit: ${amount} Credits (${validityDays}-day validity)`,
|
|
2602
|
+
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2603
|
+
];
|
|
2604
|
+
const output2 = lines2.join("\n");
|
|
2605
|
+
ctx?.ui?.notify(output2, "info");
|
|
2606
|
+
if (!ctx?.ui) console.log(output2);
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
const claimRes = await claimQoderCampaign(accessToken, machineID, dailyCreditCampaign.campaignId, mode, identity);
|
|
2610
|
+
const grantAmount = claimRes.benefit?.amount || amount;
|
|
2611
|
+
const grantDays = claimRes.benefit?.validity?.days || validityDays;
|
|
2612
|
+
const expiresText = claimRes.expiresAt ? formatDateTime(claimRes.expiresAt) : `${grantDays} \u5929\u540E`;
|
|
2613
|
+
writeCheckinCache(
|
|
2614
|
+
{
|
|
2615
|
+
claimed: true,
|
|
2616
|
+
amount: grantAmount,
|
|
2617
|
+
claimedAt: claimRes.claimedAt,
|
|
2618
|
+
expiresAt: claimRes.expiresAt,
|
|
2619
|
+
validityDays: grantDays,
|
|
2620
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2621
|
+
campaignTitle,
|
|
2622
|
+
cacheUntil: Date.now() + remainingToReset
|
|
2623
|
+
},
|
|
2624
|
+
mode
|
|
2625
|
+
);
|
|
2626
|
+
const lines = mode === "cn" ? [
|
|
2627
|
+
paint(`\u{1F389} \u6210\u529F\u9886\u53D6\u4ECA\u65E5 ${grantAmount} Credits\uFF01`, `${ANSI.green}${ANSI.bold}`),
|
|
2628
|
+
`- \u989D\u5EA6\u7C7B\u578B\uFF1A\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF08Add-on Credits\uFF09`,
|
|
2629
|
+
`- \u6709\u6548\u671F\u9650\uFF1A${grantDays} \u5929\uFF08\u6709\u6548\u671F\u81F3 ${expiresText}\uFF09`,
|
|
2630
|
+
`- \u9886\u53D6\u6D41\u6C34\uFF1A${claimRes.grantId || "ok"}`,
|
|
2631
|
+
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2632
|
+
] : [
|
|
2633
|
+
paint(`\u{1F389} Successfully claimed ${grantAmount} Credits!`, `${ANSI.green}${ANSI.bold}`),
|
|
2634
|
+
`- Type: Universal Add-on Credits`,
|
|
2635
|
+
`- Validity: ${grantDays} days (expires ${expiresText})`,
|
|
2636
|
+
`- Grant ID: ${claimRes.grantId || "ok"}`,
|
|
2637
|
+
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2638
|
+
];
|
|
2639
|
+
const output = lines.join("\n");
|
|
2640
|
+
ctx?.ui?.notify(output, "info");
|
|
2641
|
+
if (!ctx?.ui) console.log(output);
|
|
2642
|
+
} catch (error) {
|
|
2643
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2644
|
+
const errText = mode === "cn" ? `\u274C \u6BCF\u65E5\u7B7E\u5230\u9886\u53D6\u5931\u8D25: ${message}` : `\u274C Daily check-in claim failed: ${message}`;
|
|
2645
|
+
ctx?.ui?.notify(errText, "error");
|
|
2646
|
+
if (!ctx?.ui) console.error(errText);
|
|
2647
|
+
}
|
|
2602
2648
|
}
|
|
2603
2649
|
|
|
2604
2650
|
// src/commands/usage.ts
|
|
@@ -2852,6 +2898,9 @@ async function fetchQoderQuota(accessToken, mode) {
|
|
|
2852
2898
|
data = void 0;
|
|
2853
2899
|
}
|
|
2854
2900
|
if (!response.ok) {
|
|
2901
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2902
|
+
throw new Error(formatInactiveCredentialMessage(getQoderUsageURL(mode), accessToken, mode));
|
|
2903
|
+
}
|
|
2855
2904
|
const detail = data?.message || data?.error || text.slice(0, 200);
|
|
2856
2905
|
const hint = response.status === 401 || response.status === 403 ? `Re-login with /login ${getQoderRegionConfig(mode).providerID}.` : "";
|
|
2857
2906
|
const parts = [`Qoder usage request failed: ${response.status} ${response.statusText}`, hint, detail];
|
|
@@ -3520,7 +3569,7 @@ async function peekCredentialExpired(reader, decoder) {
|
|
|
3520
3569
|
if (typeof envelope.statusCodeValue === "number" && envelope.statusCodeValue !== 200) {
|
|
3521
3570
|
const parsed = parseQoderErrorPayload(envelope.body);
|
|
3522
3571
|
const message = parsed?.message ?? (typeof envelope.body === "string" ? envelope.body : "");
|
|
3523
|
-
return parsed?.code
|
|
3572
|
+
return isCredentialExpiredVerdict(parsed?.code, message);
|
|
3524
3573
|
}
|
|
3525
3574
|
return false;
|
|
3526
3575
|
} catch {
|