pi-provider-qoder-cn 0.2.5 → 0.2.6
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 +4 -12
- package/dist/index.js +132 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ pi install npm:pi-provider-qoder-cn
|
|
|
18
18
|
| Qoder CN provider (`qoder-cn`) | ✅ | ✅ |
|
|
19
19
|
| Qoder Global provider (`qoder`) | ✅ | ➖ not registered (see below) |
|
|
20
20
|
| `/qoder-cn.usage` quota command | ❌ | ✅ |
|
|
21
|
-
|
|
|
21
|
+
| Enterprise (VPC) selection during `/login qoder-cn` | ❌ | ✅ |
|
|
22
22
|
| Current CN model catalog (`qwen3.8-max` / `qwen3.8-flash`, …) | ❌ | ✅ |
|
|
23
23
|
| Stable CN model ids for `enabledModels` | ❌ | ✅ |
|
|
24
24
|
|
|
@@ -106,18 +106,10 @@ Notes:
|
|
|
106
106
|
- The command uses the same stored token as chat requests, refreshing it first,
|
|
107
107
|
so it reports the same quota the model is actually billed against.
|
|
108
108
|
|
|
109
|
-
###
|
|
109
|
+
### Enterprise (VPC) endpoint support
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```text
|
|
115
|
-
/qoder-endpoint # show the active endpoint
|
|
116
|
-
/qoder-endpoint acme # enterprise instance "acme"
|
|
117
|
-
/qoder-endpoint acme-gateway.vpc.qoder.com.cn
|
|
118
|
-
/qoder-endpoint https://qoder.internal.example.com
|
|
119
|
-
/qoder-endpoint default # back to the public gateway
|
|
120
|
-
```
|
|
111
|
+
Enterprises on a private (VPC) deployment can select their endpoint directly
|
|
112
|
+
during `/login qoder-cn` or set `QODER_VPC_ENDPOINT`.
|
|
121
113
|
|
|
122
114
|
A bare instance label `acme` expands to the standard enterprise hostnames:
|
|
123
115
|
|
package/dist/index.js
CHANGED
|
@@ -1754,51 +1754,121 @@ async function fetchQoderUsageForMode(credentials, mode) {
|
|
|
1754
1754
|
// src/index.ts
|
|
1755
1755
|
init_catalog();
|
|
1756
1756
|
|
|
1757
|
-
// src/
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
return `Enterprise Endpoint [${current.raw}]: ${current.baseUrl}`;
|
|
1768
|
-
}
|
|
1769
|
-
async function runEndpointCommand(args, ctx) {
|
|
1770
|
-
const input = (args || "").trim();
|
|
1771
|
-
if (!input) {
|
|
1772
|
-
ctx?.ui?.notify(
|
|
1773
|
-
`Current Qoder CN endpoint: ${describeEndpoint()}
|
|
1774
|
-
To set: /qoder-endpoint <domain>
|
|
1775
|
-
To reset: /qoder-endpoint default`,
|
|
1776
|
-
"info"
|
|
1777
|
-
);
|
|
1778
|
-
return;
|
|
1757
|
+
// src/protocol/errors.ts
|
|
1758
|
+
function parseQoderErrorPayload(payload) {
|
|
1759
|
+
if (!payload) return null;
|
|
1760
|
+
let parsed = payload;
|
|
1761
|
+
if (typeof payload === "string") {
|
|
1762
|
+
try {
|
|
1763
|
+
parsed = JSON.parse(payload);
|
|
1764
|
+
} catch {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1779
1767
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
"
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
credentials.email,
|
|
1794
|
-
"cn"
|
|
1795
|
-
).catch(() => void 0);
|
|
1796
|
-
ctx?.ui?.notify("Qoder CN model catalog refreshed.", "info");
|
|
1768
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1769
|
+
const obj = parsed;
|
|
1770
|
+
if (typeof obj.message === "string" && obj.message.trim().startsWith("{")) {
|
|
1771
|
+
try {
|
|
1772
|
+
const nested = JSON.parse(obj.message);
|
|
1773
|
+
if (typeof nested === "object" && nested !== null) {
|
|
1774
|
+
const nestedObj = nested;
|
|
1775
|
+
return {
|
|
1776
|
+
code: String(nestedObj.code || nestedObj.errorCode || obj.code || ""),
|
|
1777
|
+
message: String(nestedObj.message || nestedObj.errorMessage || obj.message || "")
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
} catch {
|
|
1797
1781
|
}
|
|
1798
|
-
} catch (error) {
|
|
1799
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1800
|
-
ctx?.ui?.notify(`Failed to set Qoder CN endpoint: ${message}`, "error");
|
|
1801
1782
|
}
|
|
1783
|
+
const code = obj.code ?? obj.errorCode;
|
|
1784
|
+
const message = obj.message ?? obj.errorMessage;
|
|
1785
|
+
if (!code && !message) return null;
|
|
1786
|
+
return {
|
|
1787
|
+
code: code !== void 0 ? String(code) : void 0,
|
|
1788
|
+
message: message !== void 0 ? String(message) : void 0
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
function msUntilBeijingMidnight(now = Date.now()) {
|
|
1792
|
+
const beijingOffset = 8 * 36e5;
|
|
1793
|
+
const beijingTime = now + beijingOffset;
|
|
1794
|
+
const beijingDate = new Date(beijingTime);
|
|
1795
|
+
const nextMidnightUtc = Date.UTC(
|
|
1796
|
+
beijingDate.getUTCFullYear(),
|
|
1797
|
+
beijingDate.getUTCMonth(),
|
|
1798
|
+
beijingDate.getUTCDate() + 1,
|
|
1799
|
+
0,
|
|
1800
|
+
0,
|
|
1801
|
+
0
|
|
1802
|
+
);
|
|
1803
|
+
return nextMidnightUtc - beijingTime;
|
|
1804
|
+
}
|
|
1805
|
+
function getBeijingDailyResetCountdown(now = Date.now()) {
|
|
1806
|
+
const diffMs = msUntilBeijingMidnight(now);
|
|
1807
|
+
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
1808
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
1809
|
+
const minutes = totalMinutes % 60;
|
|
1810
|
+
return hours > 0 ? `${hours}\u5C0F\u65F6${minutes}\u5206\u949F` : `${minutes}\u5206\u949F`;
|
|
1811
|
+
}
|
|
1812
|
+
function formatQoderStreamError(statusCode, rawBody, now = Date.now()) {
|
|
1813
|
+
const parsed = parseQoderErrorPayload(rawBody);
|
|
1814
|
+
const code = parsed?.code;
|
|
1815
|
+
const message = parsed?.message || (typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody));
|
|
1816
|
+
const countdown = getBeijingDailyResetCountdown(now);
|
|
1817
|
+
if (code === "110" || /billing daily count exceeded/i.test(message) || /daily usage limit reached/i.test(message)) {
|
|
1818
|
+
return [
|
|
1819
|
+
`[Qoder CN \u989D\u5EA6\u9650\u5236] \u4ECA\u65E5\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 (Billing daily count exceeded, \u9519\u8BEF\u7801 110)`,
|
|
1820
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
1821
|
+
`- \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`,
|
|
1822
|
+
`- \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`
|
|
1823
|
+
].join("\n");
|
|
1824
|
+
}
|
|
1825
|
+
if (code === "117" || /team member credits exhausted/i.test(message)) {
|
|
1826
|
+
return [
|
|
1827
|
+
`[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)`,
|
|
1828
|
+
`- \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`,
|
|
1829
|
+
`- \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`
|
|
1830
|
+
].join("\n");
|
|
1831
|
+
}
|
|
1832
|
+
if (code === "116" || /team administrator credits exhausted/i.test(message)) {
|
|
1833
|
+
return [
|
|
1834
|
+
`[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)`,
|
|
1835
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u4F01\u4E1A\u7EC4\u7EC7\u8D26\u6237\u7684 Credits \u989D\u5EA6\u5DF2\u5168\u90E8\u7528\u5B8C\u3002`,
|
|
1836
|
+
`- \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`
|
|
1837
|
+
].join("\n");
|
|
1838
|
+
}
|
|
1839
|
+
if (code === "122" || /billing-group credits limit reached/i.test(message)) {
|
|
1840
|
+
return [
|
|
1841
|
+
`[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)`,
|
|
1842
|
+
`- \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`,
|
|
1843
|
+
`- \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`
|
|
1844
|
+
].join("\n");
|
|
1845
|
+
}
|
|
1846
|
+
if (code === "119" || /free usage limit for the selected model reached/i.test(message)) {
|
|
1847
|
+
return [
|
|
1848
|
+
`[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)`,
|
|
1849
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
1850
|
+
`- \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`
|
|
1851
|
+
].join("\n");
|
|
1852
|
+
}
|
|
1853
|
+
if (code === "113" || code === "118" || /quota exhausted/i.test(message) || /credits exhausted/i.test(message)) {
|
|
1854
|
+
return [
|
|
1855
|
+
`[Qoder CN \u989D\u5EA6\u8017\u5C3D] \u8D26\u6237 Credits \u4F59\u989D\u5DF2\u5168\u90E8\u7528\u5C3D (\u9519\u8BEF\u7801 ${code || 113})`,
|
|
1856
|
+
`- \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`,
|
|
1857
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u8BF7\u524D\u5F80 qoder.com.cn \u8D2D\u4E70\u8D44\u6E90\u5305/\u52A0\u6CB9\u5305\uFF0C\u6216\u7B49\u5F85\u4E0B\u4E00\u8BA1\u8D39\u5468\u671F\u5237\u65B0\u3002`
|
|
1858
|
+
].join("\n");
|
|
1859
|
+
}
|
|
1860
|
+
if (code === "105" || /token expired/i.test(message) || /login expired/i.test(message)) {
|
|
1861
|
+
return [
|
|
1862
|
+
`[Qoder CN \u51ED\u8BC1\u5931\u6548] \u767B\u5F55\u6001\u5DF2\u8FC7\u671F\u6216 Token \u5931\u6548 (\u9519\u8BEF\u7801 105)`,
|
|
1863
|
+
`- \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`
|
|
1864
|
+
].join("\n");
|
|
1865
|
+
}
|
|
1866
|
+
if (rawBody === void 0 || rawBody === null) {
|
|
1867
|
+
return `Upstream status ${statusCode}`;
|
|
1868
|
+
}
|
|
1869
|
+
const bodyStr = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
1870
|
+
const truncated = bodyStr.length > 500 ? `${bodyStr.slice(0, 500)}...` : bodyStr;
|
|
1871
|
+
return `Upstream status ${statusCode}: ${truncated}`;
|
|
1802
1872
|
}
|
|
1803
1873
|
|
|
1804
1874
|
// src/commands/usage.ts
|
|
@@ -1844,6 +1914,14 @@ function formatPercent(value) {
|
|
|
1844
1914
|
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
1845
1915
|
return Math.round(value * 10) / 10;
|
|
1846
1916
|
}
|
|
1917
|
+
function formatDailyResetCountdown(now = Date.now()) {
|
|
1918
|
+
const diffMs = msUntilBeijingMidnight(now);
|
|
1919
|
+
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
1920
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
1921
|
+
const minutes = totalMinutes % 60;
|
|
1922
|
+
const countdown = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
|
1923
|
+
return `00:00 UTC+8 (in ${countdown})`;
|
|
1924
|
+
}
|
|
1847
1925
|
function formatResetTime(expiresAt, now = Date.now()) {
|
|
1848
1926
|
const ms = Number(expiresAt);
|
|
1849
1927
|
if (!Number.isFinite(ms) || ms <= 0) return "n/a";
|
|
@@ -1932,8 +2010,14 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
|
|
|
1932
2010
|
if (showAddOnQuota) rows.push(bucketRow("Add-on", pickBucket(raw, "addOnQuota"), danger));
|
|
1933
2011
|
if (showOrg && org)
|
|
1934
2012
|
rows.push(bucketRow("Enterprise", { ...org, percentage: void 0, total: org.cap ?? org.total }, danger));
|
|
2013
|
+
const isPersonalStandard = raw?.userType === "personal_standard" || raw?.userType === "free";
|
|
1935
2014
|
const notes = [];
|
|
1936
|
-
if (danger)
|
|
2015
|
+
if (danger) {
|
|
2016
|
+
notes.push({ label: "Status", value: "quota exceeded", color: ANSI.red });
|
|
2017
|
+
if (!showOrg || isPersonalStandard) {
|
|
2018
|
+
notes.push({ label: "Daily reset", value: formatDailyResetCountdown(now) });
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
1937
2021
|
if (!showUserQuota && !showAddOnQuota && !showOrg) notes.push({ label: "Buckets", value: "none returned" });
|
|
1938
2022
|
if (raw?.isPlanQuotaProrated) notes.push({ label: "Note", value: "plan quota is prorated" });
|
|
1939
2023
|
notes.push({ label: "Expires", value: formatResetTime(raw?.expiresAt, now) });
|
|
@@ -2729,7 +2813,7 @@ function streamQoder(model, context, options) {
|
|
|
2729
2813
|
});
|
|
2730
2814
|
if (!response.ok) {
|
|
2731
2815
|
const errText = await response.text();
|
|
2732
|
-
throw new Error(
|
|
2816
|
+
throw new Error(formatQoderStreamError(response.status, errText));
|
|
2733
2817
|
}
|
|
2734
2818
|
const body = response.body;
|
|
2735
2819
|
if (!body) throw new Error("No response body");
|
|
@@ -2783,7 +2867,7 @@ function streamQoder(model, context, options) {
|
|
|
2783
2867
|
try {
|
|
2784
2868
|
const envelope = JSON.parse(dataStr);
|
|
2785
2869
|
if (envelope.statusCodeValue && envelope.statusCodeValue !== 200) {
|
|
2786
|
-
throw new Error(
|
|
2870
|
+
throw new Error(formatQoderStreamError(envelope.statusCodeValue, envelope.body));
|
|
2787
2871
|
}
|
|
2788
2872
|
const innerStr = envelope.body;
|
|
2789
2873
|
if (innerStr === "[DONE]") {
|
|
@@ -3074,19 +3158,10 @@ async function index_default(pi) {
|
|
|
3074
3158
|
registerCommands(pi);
|
|
3075
3159
|
}
|
|
3076
3160
|
function registerCommands(pi) {
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
pi.registerCommand(`${providerID}.usage`, {
|
|
3080
|
-
description: `Show ${providerID} quota: plan + add-on credits, used/limit and reset (append 'json' for the raw payload)`,
|
|
3081
|
-
handler: async (args, ctx) => {
|
|
3082
|
-
await runUsageCommand(mode, args, ctx);
|
|
3083
|
-
}
|
|
3084
|
-
});
|
|
3085
|
-
}
|
|
3086
|
-
pi.registerCommand("qoder-endpoint", {
|
|
3087
|
-
description: "Show or set the Qoder CN gateway endpoint (use 'default' to reset)",
|
|
3161
|
+
pi.registerCommand("qoder-cn.usage", {
|
|
3162
|
+
description: "Show qoder-cn quota: plan + add-on credits, used/limit and reset (append 'json' for the raw payload)",
|
|
3088
3163
|
handler: async (args, ctx) => {
|
|
3089
|
-
await
|
|
3164
|
+
await runUsageCommand("cn", args, ctx);
|
|
3090
3165
|
}
|
|
3091
3166
|
});
|
|
3092
3167
|
}
|
package/package.json
CHANGED