pi-provider-qoder-cn 0.2.24 → 0.2.26
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 +38 -3
- package/dist/index.js +600 -667
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,16 +15,16 @@ var __export = (target, all) => {
|
|
|
15
15
|
|
|
16
16
|
// src/cn-naming.ts
|
|
17
17
|
function slugifyQoderCNModel(displayName) {
|
|
18
|
-
const slug = (displayName || "").trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9._-]/g, "").replace(/^-+|-+$/g, "");
|
|
18
|
+
const slug = (displayName || "").trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9._-]/g, "").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
19
19
|
return slug || "qodermodel";
|
|
20
20
|
}
|
|
21
21
|
function prettifyQoderCNModelName(displayName) {
|
|
22
|
-
const pretty = (displayName || "Qoder CN Model").replace(/Qwen(\d)/g, "Qwen $1").replace(/Qwen([\d.]+)-/g, "Qwen $1 ").replace(/DeepSeek
|
|
22
|
+
const pretty = (displayName || "Qoder CN Model").replace(/Qwen(\d)/g, "Qwen $1").replace(/Qwen([\d.]+)-/g, "Qwen $1 ").replace(/DeepSeek([Vv]\d)-/g, "DeepSeek $1 ").replace(/\s+/g, " ").trim();
|
|
23
23
|
return pretty.includes("Qoder CN") ? pretty : `${pretty} \xB7 Qoder CN`;
|
|
24
24
|
}
|
|
25
25
|
function toQoderCNModelInfo(upstreamKey, displayName) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
26
|
+
const fixed = upstreamKey ? FIXED_CN_MODEL_IDS[upstreamKey] : void 0;
|
|
27
|
+
if (fixed) return fixed;
|
|
28
28
|
return {
|
|
29
29
|
id: slugifyQoderCNModel(displayName || upstreamKey),
|
|
30
30
|
name: prettifyQoderCNModelName(displayName || upstreamKey)
|
|
@@ -32,10 +32,9 @@ function toQoderCNModelInfo(upstreamKey, displayName) {
|
|
|
32
32
|
}
|
|
33
33
|
function toQoderCNUpstreamKey(modelId) {
|
|
34
34
|
const id = modelId || "";
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
38
|
-
if (Object.hasOwn(QODER_CN_FRIENDLY_MODELS, id)) return id;
|
|
35
|
+
const legacy = LEGACY_CN_MODEL_IDS[id];
|
|
36
|
+
if (legacy) return legacy;
|
|
37
|
+
if (FIXED_CN_MODEL_IDS[id]) return id;
|
|
39
38
|
return id || "auto";
|
|
40
39
|
}
|
|
41
40
|
function isQoderCNModelId(entry, modelId) {
|
|
@@ -43,52 +42,33 @@ function isQoderCNModelId(entry, modelId) {
|
|
|
43
42
|
const info = toQoderCNModelInfo(entry.key, entry.display_name);
|
|
44
43
|
if (info.id === modelId) return true;
|
|
45
44
|
if (entry.key === modelId) return true;
|
|
45
|
+
const legacyKey = LEGACY_CN_MODEL_IDS[modelId];
|
|
46
|
+
if (legacyKey !== void 0 && legacyKey === entry.key) return true;
|
|
46
47
|
const stripped = (entry.display_name || "").replace(/\s+/g, "");
|
|
47
48
|
return stripped.toLowerCase() === modelId.toLowerCase();
|
|
48
49
|
}
|
|
49
|
-
var
|
|
50
|
+
var LEGACY_CN_MODEL_IDS, FIXED_CN_MODEL_IDS;
|
|
50
51
|
var init_cn_naming = __esm({
|
|
51
52
|
"src/cn-naming.ts"() {
|
|
52
53
|
"use strict";
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
qmodel_38max: { id: "qwen3.8-max", name: "Qwen 3.8-Max \xB7 Qoder CN" },
|
|
57
|
-
qfmodel: { id: "qwen3.8-flash", name: "Qwen 3.8-Flash \xB7 Qoder CN" },
|
|
58
|
-
qmodel_latest: { id: "qwen3.7-max", name: "Qwen 3.7-Max \xB7 Qoder CN" },
|
|
59
|
-
qmodel: { id: "qwen3.7-plus", name: "Qwen 3.7-Plus \xB7 Qoder CN" },
|
|
60
|
-
q37fmodel: { id: "qwen3.7-flash", name: "Qwen 3.7-Flash \xB7 Qoder CN" },
|
|
61
|
-
dmodel: { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro \xB7 Qoder CN" },
|
|
62
|
-
dfmodel: { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash \xB7 Qoder CN" },
|
|
63
|
-
gmodel: { id: "glm-5.3", name: "GLM-5.3 \xB7 Qoder CN" },
|
|
64
|
-
gfmodel: { id: "glm-5.3-flash", name: "GLM-5.3-Flash \xB7 Qoder CN" },
|
|
65
|
-
gm51model: { id: "glm-5.2", name: "GLM 5.2 \xB7 Qoder CN" },
|
|
66
|
-
kmodel_latest: { id: "kimi-k3", name: "Kimi-K3 \xB7 Qoder CN" },
|
|
67
|
-
kmodel: { id: "kimi-k2.7-code", name: "Kimi-K2.7-Code \xB7 Qoder CN" },
|
|
68
|
-
mmodel: { id: "minimax-m2.7", name: "MiniMax M2.7 \xB7 Qoder CN" }
|
|
69
|
-
});
|
|
70
|
-
CN_MODEL_KEY_ALIASES = Object.freeze({
|
|
71
|
-
"qwen3.8-max": "qmodel_38max",
|
|
72
|
-
"qwen3.8-flash": "qfmodel",
|
|
73
|
-
"qwen3.7-max": "qmodel_latest",
|
|
74
|
-
"qwen3.7-plus": "qmodel",
|
|
54
|
+
LEGACY_CN_MODEL_IDS = Object.freeze({
|
|
55
|
+
"qwen3.6-flash": "qfmodel",
|
|
56
|
+
"qwen3.6-plus": "qmodel",
|
|
75
57
|
"qwen3.7-flash": "q37fmodel",
|
|
76
|
-
"
|
|
77
|
-
"deepseek-v4-flash": "dfmodel",
|
|
78
|
-
"glm-5.3": "gmodel",
|
|
79
|
-
"glm-5.3-flash": "gfmodel",
|
|
80
|
-
"glm-5.2": "gm51model",
|
|
58
|
+
"qwen3.7-max": "qmodel_latest",
|
|
81
59
|
"glm-5.1": "gm51model",
|
|
82
|
-
"
|
|
83
|
-
"kimi-k2.7-code": "kmodel",
|
|
84
|
-
"kimi-k2.7": "kmodel",
|
|
60
|
+
"glm-5.2": "gm51model",
|
|
85
61
|
"kimi-k2.6": "kmodel",
|
|
62
|
+
"kimi-k2.7": "kmodel",
|
|
63
|
+
"kimi-k2.7-code": "kmodel",
|
|
86
64
|
"kimi-k2.8-preview": "kmodel",
|
|
87
|
-
"
|
|
88
|
-
"qwen3.6-plus": "qmodel",
|
|
65
|
+
"kimi-k3": "kmodel_latest",
|
|
89
66
|
"minimax-m2.7": "mmodel",
|
|
90
67
|
"minimax-m3": "mmodel"
|
|
91
68
|
});
|
|
69
|
+
FIXED_CN_MODEL_IDS = Object.freeze({
|
|
70
|
+
auto: { id: "auto", name: "Auto \xB7 Qoder CN" }
|
|
71
|
+
});
|
|
92
72
|
}
|
|
93
73
|
});
|
|
94
74
|
|
|
@@ -453,6 +433,9 @@ function getHomeDir3() {
|
|
|
453
433
|
function getQoderCachePath(mode) {
|
|
454
434
|
return join3(getHomeDir3(), ".pi", "agent", getQoderRegionConfig(mode).modelCacheFile);
|
|
455
435
|
}
|
|
436
|
+
function clearQoderModelsMemCache() {
|
|
437
|
+
modelCacheMem.clear();
|
|
438
|
+
}
|
|
456
439
|
function readParsedModelCache(mode) {
|
|
457
440
|
const cachePath = getQoderCachePath(mode);
|
|
458
441
|
if (modelCacheMem.has(cachePath)) {
|
|
@@ -629,11 +612,11 @@ async function updateQoderModelsCache(authToken, userID, name, email, mode) {
|
|
|
629
612
|
}
|
|
630
613
|
});
|
|
631
614
|
if (!response.ok) {
|
|
632
|
-
return;
|
|
615
|
+
return null;
|
|
633
616
|
}
|
|
634
617
|
const resData = await response.json();
|
|
635
618
|
const chatModels = resData.chat || [];
|
|
636
|
-
if (chatModels.length === 0) return;
|
|
619
|
+
if (chatModels.length === 0) return null;
|
|
637
620
|
const newModels = [];
|
|
638
621
|
const configs = {};
|
|
639
622
|
for (const entry of chatModels) {
|
|
@@ -664,14 +647,28 @@ async function updateQoderModelsCache(authToken, userID, name, email, mode) {
|
|
|
664
647
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
665
648
|
});
|
|
666
649
|
}
|
|
667
|
-
if (newModels.length === 0) return;
|
|
650
|
+
if (newModels.length === 0) return null;
|
|
651
|
+
const previous = readParsedModelCache(mode);
|
|
652
|
+
const previousModels = new Map((previous?.models ?? []).map((model) => [model.id, model.name]));
|
|
653
|
+
const nextIds = new Set(newModels.map((model) => model.id));
|
|
654
|
+
const diff = {
|
|
655
|
+
added: newModels.filter((model) => !previousModels.has(model.id)).map((model) => ({ id: model.id, name: model.name })),
|
|
656
|
+
renamed: newModels.filter((model) => {
|
|
657
|
+
const before = previousModels.get(model.id);
|
|
658
|
+
return before !== void 0 && before !== model.name;
|
|
659
|
+
}).map((model) => ({ id: model.id, from: previousModels.get(model.id) ?? "", to: model.name })),
|
|
660
|
+
removed: [...previousModels].filter(([id]) => !nextIds.has(id)).map(([id, previousName]) => ({ id, name: previousName })),
|
|
661
|
+
total: newModels.length
|
|
662
|
+
};
|
|
668
663
|
const cacheData = {
|
|
669
664
|
updatedAt: Date.now(),
|
|
670
665
|
models: newModels,
|
|
671
666
|
configs
|
|
672
667
|
};
|
|
673
668
|
writeParsedModelCache(mode, cacheData);
|
|
669
|
+
return diff;
|
|
674
670
|
} catch {
|
|
671
|
+
return null;
|
|
675
672
|
}
|
|
676
673
|
}
|
|
677
674
|
var ZERO_COST, MAX_OUTPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, QODER_CACHE_WRITER, modelCacheMem, staticModels, staticCnModels, PI_THINKING_LEVELS;
|
|
@@ -915,203 +912,6 @@ var init_catalog = __esm({
|
|
|
915
912
|
contextWindow: 2e5,
|
|
916
913
|
maxTokens: MAX_OUTPUT_TOKENS,
|
|
917
914
|
description: "Qoder CN smart routing; live catalog reports 200K max input."
|
|
918
|
-
},
|
|
919
|
-
{
|
|
920
|
-
id: "qwen3.8-max",
|
|
921
|
-
upstreamKey: "qmodel_38max",
|
|
922
|
-
name: "Qwen 3.8-Max \xB7 Qoder CN",
|
|
923
|
-
api: "qoder-api",
|
|
924
|
-
provider: "qoder-cn",
|
|
925
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
926
|
-
reasoning: true,
|
|
927
|
-
supportsEffort: true,
|
|
928
|
-
input: ["text", "image"],
|
|
929
|
-
cost: ZERO_COST,
|
|
930
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
931
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
932
|
-
description: "Qwen 3.8-Max (qmodel_38max); 1M context."
|
|
933
|
-
},
|
|
934
|
-
{
|
|
935
|
-
id: "qwen3.8-flash",
|
|
936
|
-
upstreamKey: "qfmodel",
|
|
937
|
-
name: "Qwen 3.8-Flash \xB7 Qoder CN",
|
|
938
|
-
api: "qoder-api",
|
|
939
|
-
provider: "qoder-cn",
|
|
940
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
941
|
-
reasoning: true,
|
|
942
|
-
supportsEffort: true,
|
|
943
|
-
input: ["text", "image"],
|
|
944
|
-
cost: ZERO_COST,
|
|
945
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
946
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
947
|
-
description: "Qwen 3.8-Flash (qfmodel); multimodal MoE, 1M context."
|
|
948
|
-
},
|
|
949
|
-
{
|
|
950
|
-
id: "qwen3.7-max",
|
|
951
|
-
upstreamKey: "qmodel_latest",
|
|
952
|
-
name: "Qwen 3.7-Max \xB7 Qoder CN",
|
|
953
|
-
api: "qoder-api",
|
|
954
|
-
provider: "qoder-cn",
|
|
955
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
956
|
-
reasoning: true,
|
|
957
|
-
supportsEffort: false,
|
|
958
|
-
input: ["text", "image"],
|
|
959
|
-
cost: ZERO_COST,
|
|
960
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
961
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
962
|
-
description: "Qwen 3.7-Max (qmodel_latest); context options 200K/400K/1M."
|
|
963
|
-
},
|
|
964
|
-
{
|
|
965
|
-
id: "qwen3.7-plus",
|
|
966
|
-
upstreamKey: "qmodel",
|
|
967
|
-
name: "Qwen 3.7-Plus \xB7 Qoder CN",
|
|
968
|
-
api: "qoder-api",
|
|
969
|
-
provider: "qoder-cn",
|
|
970
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
971
|
-
reasoning: true,
|
|
972
|
-
supportsEffort: false,
|
|
973
|
-
input: ["text", "image"],
|
|
974
|
-
cost: ZERO_COST,
|
|
975
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
976
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
977
|
-
description: "Qwen 3.7-Plus (qmodel); context options 200K/400K/1M."
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
id: "qwen3.7-flash",
|
|
981
|
-
upstreamKey: "q37fmodel",
|
|
982
|
-
name: "Qwen 3.7-Flash \xB7 Qoder CN",
|
|
983
|
-
api: "qoder-api",
|
|
984
|
-
provider: "qoder-cn",
|
|
985
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
986
|
-
reasoning: true,
|
|
987
|
-
supportsEffort: false,
|
|
988
|
-
input: ["text", "image"],
|
|
989
|
-
cost: ZERO_COST,
|
|
990
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
991
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
992
|
-
description: "Qwen 3.7-Flash (q37fmodel); vision-language Flash, 1M context."
|
|
993
|
-
},
|
|
994
|
-
{
|
|
995
|
-
id: "deepseek-v4-pro",
|
|
996
|
-
upstreamKey: "dmodel",
|
|
997
|
-
name: "DeepSeek V4 Pro \xB7 Qoder CN",
|
|
998
|
-
api: "qoder-api",
|
|
999
|
-
provider: "qoder-cn",
|
|
1000
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1001
|
-
reasoning: true,
|
|
1002
|
-
supportsEffort: true,
|
|
1003
|
-
input: ["text", "image"],
|
|
1004
|
-
cost: ZERO_COST,
|
|
1005
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1006
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1007
|
-
description: "DeepSeek V4 Pro (dmodel); 1M context."
|
|
1008
|
-
},
|
|
1009
|
-
{
|
|
1010
|
-
id: "deepseek-v4-flash",
|
|
1011
|
-
upstreamKey: "dfmodel",
|
|
1012
|
-
name: "DeepSeek V4 Flash \xB7 Qoder CN",
|
|
1013
|
-
api: "qoder-api",
|
|
1014
|
-
provider: "qoder-cn",
|
|
1015
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1016
|
-
reasoning: true,
|
|
1017
|
-
supportsEffort: true,
|
|
1018
|
-
input: ["text", "image"],
|
|
1019
|
-
cost: ZERO_COST,
|
|
1020
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1021
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1022
|
-
description: "DeepSeek V4 Flash (dfmodel); 1M context."
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
id: "glm-5.3",
|
|
1026
|
-
upstreamKey: "gmodel",
|
|
1027
|
-
name: "GLM-5.3 \xB7 Qoder CN",
|
|
1028
|
-
api: "qoder-api",
|
|
1029
|
-
provider: "qoder-cn",
|
|
1030
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1031
|
-
reasoning: true,
|
|
1032
|
-
supportsEffort: true,
|
|
1033
|
-
input: ["text", "image"],
|
|
1034
|
-
cost: ZERO_COST,
|
|
1035
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1036
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1037
|
-
description: "GLM-5.3 (gmodel); Zhipu flagship open-source, 1M context."
|
|
1038
|
-
},
|
|
1039
|
-
{
|
|
1040
|
-
id: "glm-5.3-flash",
|
|
1041
|
-
upstreamKey: "gfmodel",
|
|
1042
|
-
name: "GLM-5.3-Flash \xB7 Qoder CN",
|
|
1043
|
-
api: "qoder-api",
|
|
1044
|
-
provider: "qoder-cn",
|
|
1045
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1046
|
-
reasoning: true,
|
|
1047
|
-
supportsEffort: true,
|
|
1048
|
-
input: ["text", "image"],
|
|
1049
|
-
cost: ZERO_COST,
|
|
1050
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1051
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1052
|
-
description: "GLM-5.3-Flash (gfmodel); 1M context."
|
|
1053
|
-
},
|
|
1054
|
-
{
|
|
1055
|
-
id: "glm-5.2",
|
|
1056
|
-
upstreamKey: "gm51model",
|
|
1057
|
-
name: "GLM 5.2 \xB7 Qoder CN",
|
|
1058
|
-
api: "qoder-api",
|
|
1059
|
-
provider: "qoder-cn",
|
|
1060
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1061
|
-
reasoning: true,
|
|
1062
|
-
supportsEffort: true,
|
|
1063
|
-
input: ["text", "image"],
|
|
1064
|
-
cost: ZERO_COST,
|
|
1065
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1066
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1067
|
-
description: "GLM 5.2 (gm51model); 1M context."
|
|
1068
|
-
},
|
|
1069
|
-
{
|
|
1070
|
-
id: "kimi-k3",
|
|
1071
|
-
upstreamKey: "kmodel_latest",
|
|
1072
|
-
name: "Kimi-K3 \xB7 Qoder CN",
|
|
1073
|
-
api: "qoder-api",
|
|
1074
|
-
provider: "qoder-cn",
|
|
1075
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1076
|
-
reasoning: true,
|
|
1077
|
-
supportsEffort: true,
|
|
1078
|
-
input: ["text", "image"],
|
|
1079
|
-
cost: ZERO_COST,
|
|
1080
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
1081
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1082
|
-
description: "Kimi-K3 (kmodel_latest); 1M context."
|
|
1083
|
-
},
|
|
1084
|
-
{
|
|
1085
|
-
id: "kimi-k2.7-code",
|
|
1086
|
-
upstreamKey: "kmodel",
|
|
1087
|
-
name: "Kimi-K2.7-Code \xB7 Qoder CN",
|
|
1088
|
-
api: "qoder-api",
|
|
1089
|
-
provider: "qoder-cn",
|
|
1090
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1091
|
-
reasoning: true,
|
|
1092
|
-
supportsEffort: false,
|
|
1093
|
-
input: ["text", "image"],
|
|
1094
|
-
cost: ZERO_COST,
|
|
1095
|
-
// Live catalog advertises 256K for this entry.
|
|
1096
|
-
contextWindow: 256e3,
|
|
1097
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1098
|
-
description: "Kimi-K2.7-Code (kmodel); 256K context."
|
|
1099
|
-
},
|
|
1100
|
-
{
|
|
1101
|
-
id: "minimax-m2.7",
|
|
1102
|
-
upstreamKey: "mmodel",
|
|
1103
|
-
name: "MiniMax M2.7 \xB7 Qoder CN",
|
|
1104
|
-
api: "qoder-api",
|
|
1105
|
-
provider: "qoder-cn",
|
|
1106
|
-
baseUrl: getQoderBaseUrl("cn"),
|
|
1107
|
-
reasoning: false,
|
|
1108
|
-
supportsEffort: false,
|
|
1109
|
-
input: ["text"],
|
|
1110
|
-
cost: ZERO_COST,
|
|
1111
|
-
// Live CN catalog reports 200K; not confirmed at 1M.
|
|
1112
|
-
contextWindow: 2e5,
|
|
1113
|
-
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1114
|
-
description: "MiniMax M2.7 (mmodel); 200K context."
|
|
1115
915
|
}
|
|
1116
916
|
];
|
|
1117
917
|
PI_THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -1791,35 +1591,415 @@ var init_oauth = __esm({
|
|
|
1791
1591
|
// src/index.ts
|
|
1792
1592
|
init_oauth();
|
|
1793
1593
|
|
|
1794
|
-
// src/
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1594
|
+
// src/protocol/errors.ts
|
|
1595
|
+
init_region();
|
|
1596
|
+
init_vpc();
|
|
1597
|
+
import { createHash } from "node:crypto";
|
|
1598
|
+
|
|
1599
|
+
// src/protocol/queue.ts
|
|
1600
|
+
var MAX_WAIT_MS = 12e4;
|
|
1601
|
+
var DEFAULT_RETRY_SECONDS = 30;
|
|
1602
|
+
function digForQueue(node, depth) {
|
|
1603
|
+
if (depth > 6) return null;
|
|
1604
|
+
if (typeof node === "string") {
|
|
1605
|
+
const trimmed = node.trim();
|
|
1606
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
1607
|
+
try {
|
|
1608
|
+
return digForQueue(JSON.parse(trimmed), depth + 1);
|
|
1609
|
+
} catch {
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (node === null || typeof node !== "object") return null;
|
|
1614
|
+
if (Array.isArray(node)) {
|
|
1615
|
+
for (const item of node) {
|
|
1616
|
+
const found = digForQueue(item, depth + 1);
|
|
1617
|
+
if (found) return found;
|
|
1618
|
+
}
|
|
1619
|
+
return null;
|
|
1620
|
+
}
|
|
1621
|
+
const record = node;
|
|
1622
|
+
if (record.isQueued === true) return record;
|
|
1623
|
+
for (const value of Object.values(record)) {
|
|
1624
|
+
const found = digForQueue(value, depth + 1);
|
|
1625
|
+
if (found) return found;
|
|
1626
|
+
}
|
|
1627
|
+
return null;
|
|
1800
1628
|
}
|
|
1801
|
-
function
|
|
1802
|
-
|
|
1803
|
-
return bucketLimit(bucket) > 0 || quotaNumber(bucket.used) > 0 || quotaNumber(bucket.remaining) > 0;
|
|
1629
|
+
function positiveNumber(value, fallback) {
|
|
1630
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1804
1631
|
}
|
|
1805
|
-
function
|
|
1806
|
-
|
|
1807
|
-
const source = raw;
|
|
1808
|
-
const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
1809
|
-
return source[snake] ?? source[camel];
|
|
1632
|
+
function textOf(value) {
|
|
1633
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1810
1634
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
return
|
|
1635
|
+
function parseQueueNotice(envelope) {
|
|
1636
|
+
const payload = digForQueue(envelope, 0);
|
|
1637
|
+
if (!payload) return null;
|
|
1638
|
+
const retryMs = positiveNumber(payload.retry_after_ms, 0) || positiveNumber(payload.retryAfterSeconds, DEFAULT_RETRY_SECONDS) * 1e3;
|
|
1639
|
+
return {
|
|
1640
|
+
retryAfterMs: Math.min(retryMs, MAX_WAIT_MS),
|
|
1641
|
+
waitTimeSeconds: positiveNumber(payload.waitTime, 0),
|
|
1642
|
+
queueCount: positiveNumber(payload.queueCount, 0),
|
|
1643
|
+
// Only a real boolean is surfaced; a missing field must not be read as
|
|
1644
|
+
// "unavailable" (that would collapse the normal queue into a hard stop).
|
|
1645
|
+
serviceAvailable: typeof payload.serviceAvailable === "boolean" ? payload.serviceAvailable : void 0,
|
|
1646
|
+
// `subCode` sits inside the same record as `isQueued` in the payloads seen
|
|
1647
|
+
// live, so one source is enough; the wrapper levels carry no extra names.
|
|
1648
|
+
subCode: textOf(payload.subCode),
|
|
1649
|
+
queueType: textOf(payload.queueType),
|
|
1650
|
+
modelKey: textOf(payload.modelKey)
|
|
1651
|
+
};
|
|
1816
1652
|
}
|
|
1817
|
-
function
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1653
|
+
function formatWaitSecondsShort(ms) {
|
|
1654
|
+
if (!Number.isFinite(ms) || ms <= 0) return "0";
|
|
1655
|
+
const seconds = Math.round(ms / 1e3);
|
|
1656
|
+
if (seconds < 100) return String(seconds);
|
|
1657
|
+
return `${formatWaitMinutes(seconds)}\u5206\u949F`;
|
|
1658
|
+
}
|
|
1659
|
+
function formatQueueFailureMessage(notice, ctx) {
|
|
1660
|
+
const isCn = ctx.mode === "cn";
|
|
1661
|
+
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1662
|
+
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1663
|
+
const modelKey = notice.modelKey ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1664
|
+
const hard = notice.serviceAvailable === false;
|
|
1665
|
+
const waited = formatWaitSecondsShort(ctx.waitedMs ?? 0);
|
|
1666
|
+
const queueLabel = formatQueueFigure(notice.queueCount ?? 0);
|
|
1667
|
+
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. `;
|
|
1668
|
+
if (hard) {
|
|
1669
|
+
return isCn ? [
|
|
1670
|
+
`[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`,
|
|
1671
|
+
`- \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`,
|
|
1672
|
+
`- \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`,
|
|
1673
|
+
`- \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}`
|
|
1674
|
+
].join("\n") : [
|
|
1675
|
+
`[Qoder international has no spare capacity] The gateway answered serviceAvailable:false (sub code ${sub}, queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
1676
|
+
`- 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.`,
|
|
1677
|
+
`- Switching model will not help: every model of this account answers on the same ${lane} lane (measured across auto / qmodel_38max / efficient / ultimate).`,
|
|
1678
|
+
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry later. Plan and quota: ${ctx.manageUrl}`
|
|
1679
|
+
].join("\n");
|
|
1680
|
+
}
|
|
1681
|
+
return isCn ? [
|
|
1682
|
+
`[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`,
|
|
1683
|
+
`- \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`,
|
|
1684
|
+
`- \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}`
|
|
1685
|
+
].join("\n") : [
|
|
1686
|
+
// "is at capacity" is the established wording for the drainable case and
|
|
1687
|
+
// the only queue text allowed to carry it; the hard refusal above must
|
|
1688
|
+
// never read as "wait it out".
|
|
1689
|
+
`[Qoder upstream queue] The model gateway is at capacity and parked this request (queue length ${queueLabel}, priority lane ${lane}, model ${modelKey})`,
|
|
1690
|
+
`- Meaning: ${tried ?? ""}the queue has not cleared yet. Credentials and quota are fine; queued attempts are not billed.`,
|
|
1691
|
+
`- Next step: retry shortly, or switch provider with /model to ${ctx.otherProviderID} while the queue drains. Plan and quota: ${ctx.manageUrl}`
|
|
1692
|
+
].join("\n");
|
|
1693
|
+
}
|
|
1694
|
+
function formatLaneCooldownMessage(notice, remainingMs, ctx) {
|
|
1695
|
+
const isCn = ctx.mode === "cn";
|
|
1696
|
+
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1697
|
+
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
1698
|
+
const left = Math.min(99, Math.max(1, Math.ceil(remainingMs / 1e3)));
|
|
1699
|
+
return isCn ? [
|
|
1700
|
+
`[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`,
|
|
1701
|
+
`- \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`,
|
|
1702
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7528 /model \u5207\u6362\u5230 ${ctx.otherProviderID} \u6216\u5176\u4ED6 Provider\uFF0C\u6216 ${left} \u79D2\u540E\u91CD\u8BD5\u3002`
|
|
1703
|
+
].join("\n") : [
|
|
1704
|
+
`[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.`,
|
|
1705
|
+
`- Meaning: the previous request on this lane already waited through Retry-After and was still refused, so waiting again buys the same verdict.`,
|
|
1706
|
+
`- Next step: use /model to switch to ${ctx.otherProviderID} or another provider, or retry in ${left} seconds.`
|
|
1707
|
+
].join("\n");
|
|
1708
|
+
}
|
|
1709
|
+
function formatQueueFigure(n) {
|
|
1710
|
+
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
1711
|
+
if (n < 100) return String(Math.round(n));
|
|
1712
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1713
|
+
return `${(n / 1e3).toFixed(1)}k`;
|
|
1714
|
+
}
|
|
1715
|
+
function formatWaitMinutes(seconds) {
|
|
1716
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "<1";
|
|
1717
|
+
return String(Math.min(99, Math.max(1, Math.round(seconds / 60))));
|
|
1718
|
+
}
|
|
1719
|
+
function sleep(ms, signal) {
|
|
1720
|
+
return new Promise((resolve, reject) => {
|
|
1721
|
+
if (signal?.aborted) {
|
|
1722
|
+
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
let timer;
|
|
1726
|
+
const onAbort = () => {
|
|
1727
|
+
clearTimeout(timer);
|
|
1728
|
+
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
1729
|
+
};
|
|
1730
|
+
timer = setTimeout(() => {
|
|
1731
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1732
|
+
resolve();
|
|
1733
|
+
}, ms);
|
|
1734
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/protocol/errors.ts
|
|
1739
|
+
function parseQoderErrorPayload(payload) {
|
|
1740
|
+
if (!payload) return null;
|
|
1741
|
+
let parsed = payload;
|
|
1742
|
+
if (typeof payload === "string") {
|
|
1743
|
+
try {
|
|
1744
|
+
parsed = JSON.parse(payload);
|
|
1745
|
+
} catch {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1750
|
+
const obj = parsed;
|
|
1751
|
+
if (typeof obj.message === "string" && obj.message.trim().startsWith("{")) {
|
|
1752
|
+
try {
|
|
1753
|
+
const nested = JSON.parse(obj.message);
|
|
1754
|
+
if (typeof nested === "object" && nested !== null) {
|
|
1755
|
+
const nestedObj = nested;
|
|
1756
|
+
return {
|
|
1757
|
+
code: String(nestedObj.code || nestedObj.errorCode || obj.code || ""),
|
|
1758
|
+
message: String(nestedObj.message || nestedObj.errorMessage || obj.message || "")
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
} catch {
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const code = obj.code ?? obj.errorCode;
|
|
1765
|
+
const message = obj.message ?? obj.errorMessage;
|
|
1766
|
+
if (!code && !message) return null;
|
|
1767
|
+
return {
|
|
1768
|
+
code: code !== void 0 ? String(code) : void 0,
|
|
1769
|
+
message: message !== void 0 ? String(message) : void 0
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
function msUntilBeijingMidnight(now = Date.now()) {
|
|
1773
|
+
const beijingOffset = 8 * 36e5;
|
|
1774
|
+
const beijingTime = now + beijingOffset;
|
|
1775
|
+
const beijingDate = new Date(beijingTime);
|
|
1776
|
+
const nextMidnightUtc = Date.UTC(
|
|
1777
|
+
beijingDate.getUTCFullYear(),
|
|
1778
|
+
beijingDate.getUTCMonth(),
|
|
1779
|
+
beijingDate.getUTCDate() + 1,
|
|
1780
|
+
0,
|
|
1781
|
+
0,
|
|
1782
|
+
0
|
|
1783
|
+
);
|
|
1784
|
+
return nextMidnightUtc - beijingTime;
|
|
1785
|
+
}
|
|
1786
|
+
function getBeijingDailyResetCountdown(now = Date.now()) {
|
|
1787
|
+
const diffMs = msUntilBeijingMidnight(now);
|
|
1788
|
+
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
1789
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
1790
|
+
const minutes = totalMinutes % 60;
|
|
1791
|
+
return hours > 0 ? `${hours}\u5C0F\u65F6${minutes}\u5206\u949F` : `${minutes}\u5206\u949F`;
|
|
1792
|
+
}
|
|
1793
|
+
var CREDENTIAL_EXPIRED_CODES = /* @__PURE__ */ new Set(["105", "TOKEN_EXPIRE", "TOKEN_EXPIRED"]);
|
|
1794
|
+
var CREDENTIAL_EXPIRED_MESSAGE = /token expired|login expired|token is not active|token has expired/i;
|
|
1795
|
+
function isCredentialExpiredVerdict(code, message) {
|
|
1796
|
+
return code !== void 0 && CREDENTIAL_EXPIRED_CODES.has(code) || CREDENTIAL_EXPIRED_MESSAGE.test(message);
|
|
1797
|
+
}
|
|
1798
|
+
function isOpenApiInactiveCredential(status, body) {
|
|
1799
|
+
if (status !== 401) return false;
|
|
1800
|
+
const parsed = parseQoderErrorPayload(body);
|
|
1801
|
+
return parsed?.code === "TOKEN_EXPIRE" || /token is not active/i.test(parsed?.message ?? body);
|
|
1802
|
+
}
|
|
1803
|
+
function formatInactiveCredentialMessage(endpoint, accessToken, mode = "cn") {
|
|
1804
|
+
const region = getQoderRegionConfig(mode);
|
|
1805
|
+
const fingerprint = createHash("sha256").update(accessToken).digest("hex").slice(0, 8);
|
|
1806
|
+
const onPublicGateway = endpoint.startsWith(
|
|
1807
|
+
mode === "cn" ? QODER_CN_OFFICIAL.openApiUrl : "https://openapi.qoder.sh"
|
|
1808
|
+
);
|
|
1809
|
+
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";
|
|
1810
|
+
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.";
|
|
1811
|
+
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.`;
|
|
1812
|
+
const lines = mode === "cn" ? [
|
|
1813
|
+
`[Qoder CN \u989D\u5EA6\u7AEF\u70B9\u62D2\u7EDD\u5F53\u524D token] code TOKEN_EXPIRE (token is not active)`,
|
|
1814
|
+
`- \u7AEF\u70B9\uFF1A${endpoint} - ${where}`,
|
|
1815
|
+
`- \u539F\u56E0\uFF1A${cause}`,
|
|
1816
|
+
`- \u5904\u7F6E\uFF1A${remedy}`,
|
|
1817
|
+
`- \u8BCA\u65AD\uFF1Atoken sha256:${fingerprint}`
|
|
1818
|
+
] : [
|
|
1819
|
+
`[Qoder quota endpoint refused this token] code TOKEN_EXPIRE (token is not active)`,
|
|
1820
|
+
`- Endpoint: ${endpoint} - ${where}`,
|
|
1821
|
+
`- Cause: ${cause}`,
|
|
1822
|
+
`- Next step: ${remedy}`,
|
|
1823
|
+
`- Diagnostics: token sha256:${fingerprint}`
|
|
1824
|
+
];
|
|
1825
|
+
return lines.join("\n");
|
|
1826
|
+
}
|
|
1827
|
+
function formatQoderStreamError(statusCode, rawBody, now = Date.now(), mode = "cn") {
|
|
1828
|
+
const parsed = parseQoderErrorPayload(rawBody);
|
|
1829
|
+
const code = parsed?.code;
|
|
1830
|
+
const message = parsed?.message || (typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody));
|
|
1831
|
+
const bodyForMatch = `${message} ${typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody ?? "")}`;
|
|
1832
|
+
const countdown = getBeijingDailyResetCountdown(now);
|
|
1833
|
+
const isCn = mode !== "global";
|
|
1834
|
+
if (code === "110" || /billing daily count exceeded/i.test(message) || /daily usage limit reached/i.test(message)) {
|
|
1835
|
+
if (!isCn) {
|
|
1836
|
+
return [
|
|
1837
|
+
`[Qoder quota limit] Daily call allowance exhausted (Billing daily count exceeded, code 110)`,
|
|
1838
|
+
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
1839
|
+
`- 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.`,
|
|
1840
|
+
`- Fix: wait for the reset; switch provider with /model; or upgrade at https://qoder.com/pricing to lift the daily limit.`
|
|
1841
|
+
].join("\n");
|
|
1842
|
+
}
|
|
1843
|
+
return [
|
|
1844
|
+
`[Qoder CN \u989D\u5EA6\u9650\u5236] \u4ECA\u65E5\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 (Billing daily count exceeded, \u9519\u8BEF\u7801 110)`,
|
|
1845
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
1846
|
+
`- \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`,
|
|
1847
|
+
`- \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`
|
|
1848
|
+
].join("\n");
|
|
1849
|
+
}
|
|
1850
|
+
if (code === "117" || /team member credits exhausted/i.test(message)) {
|
|
1851
|
+
return isCn ? [
|
|
1852
|
+
`[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)`,
|
|
1853
|
+
`- \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`,
|
|
1854
|
+
`- \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`
|
|
1855
|
+
].join("\n") : [
|
|
1856
|
+
`[Qoder enterprise quota] Your member credit allowance is exhausted (code 117)`,
|
|
1857
|
+
`- Reason: the admin-assigned per-member credit cap has been consumed.`,
|
|
1858
|
+
`- Fix: ask your organization admin to raise your member quota in the team console.`
|
|
1859
|
+
].join("\n");
|
|
1860
|
+
}
|
|
1861
|
+
if (code === "116" || /team administrator credits exhausted/i.test(message)) {
|
|
1862
|
+
return isCn ? [
|
|
1863
|
+
`[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)`,
|
|
1864
|
+
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u4F01\u4E1A\u7EC4\u7EC7\u8D26\u6237\u7684 Credits \u989D\u5EA6\u5DF2\u5168\u90E8\u7528\u5B8C\u3002`,
|
|
1865
|
+
`- \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`
|
|
1866
|
+
].join("\n") : [
|
|
1867
|
+
`[Qoder enterprise quota] The organization's credit pool is exhausted (code 116)`,
|
|
1868
|
+
`- Reason: every credit in the organization account has been consumed.`,
|
|
1869
|
+
`- Fix: ask your organization admin to purchase or renew the org pool.`
|
|
1870
|
+
].join("\n");
|
|
1871
|
+
}
|
|
1872
|
+
if (code === "122" || /billing-group credits limit reached/i.test(message)) {
|
|
1873
|
+
return isCn ? [
|
|
1874
|
+
`[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)`,
|
|
1875
|
+
`- \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`,
|
|
1876
|
+
`- \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`
|
|
1877
|
+
].join("\n") : [
|
|
1878
|
+
`[Qoder billing group] The billing group reached its period spend cap (code 122)`,
|
|
1879
|
+
`- Reason: the admin-set limit for this billing group is consumed for the period.`,
|
|
1880
|
+
`- Fix: ask the billing administrator to raise the group's period cap.`
|
|
1881
|
+
].join("\n");
|
|
1882
|
+
}
|
|
1883
|
+
if (code === "119" || /free usage limit for the selected model reached/i.test(message)) {
|
|
1884
|
+
return isCn ? [
|
|
1885
|
+
`[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)`,
|
|
1886
|
+
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
1887
|
+
`- \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`
|
|
1888
|
+
].join("\n") : [
|
|
1889
|
+
`[Qoder model free tier] This model's free daily trial allowance is used up (code 119)`,
|
|
1890
|
+
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
1891
|
+
`- Fix: switch to a paid model via /model to spend credits, or wait for the reset.`
|
|
1892
|
+
].join("\n");
|
|
1893
|
+
}
|
|
1894
|
+
if (code === "113" || code === "118" || /quota exhausted/i.test(message) || /credits exhausted/i.test(message)) {
|
|
1895
|
+
const store = isCn ? QODER_CN_OFFICIAL.manageUrl : "https://qoder.com";
|
|
1896
|
+
return isCn ? [
|
|
1897
|
+
`[Qoder CN \u989D\u5EA6\u8017\u5C3D] \u8D26\u6237 Credits \u4F59\u989D\u5DF2\u5168\u90E8\u7528\u5C3D (\u9519\u8BEF\u7801 ${code || 113})`,
|
|
1898
|
+
`- \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`,
|
|
1899
|
+
`- \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`
|
|
1900
|
+
].join("\n") : [
|
|
1901
|
+
`[Qoder credits exhausted] The account has no remaining credits (code ${code || 113})`,
|
|
1902
|
+
`- Reason: the plan allowance and every add-on pack are consumed.`,
|
|
1903
|
+
`- Fix: purchase a credit pack at ${store}, or wait for the next billing cycle.`
|
|
1904
|
+
].join("\n");
|
|
1905
|
+
}
|
|
1906
|
+
if (isCredentialExpiredVerdict(code, message)) {
|
|
1907
|
+
if (isCn) {
|
|
1908
|
+
return [
|
|
1909
|
+
`[Qoder CN \u51ED\u8BC1\u5931\u6548] \u767B\u5F55\u6001\u5DF2\u8FC7\u671F\u6216 Token \u5931\u6548 (\u9519\u8BEF\u7801 105)`,
|
|
1910
|
+
`- \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`
|
|
1911
|
+
].join("\n");
|
|
1912
|
+
}
|
|
1913
|
+
return [
|
|
1914
|
+
`[Qoder \u51ED\u8BC1\u5931\u6548 | Credential expired] The login session or token has expired (\u9519\u8BEF\u7801 105)`,
|
|
1915
|
+
`- Next step: run /login qoder. You can also refresh credentials by exporting QODER_PERSONAL_ACCESS_TOKEN (PAT).`
|
|
1916
|
+
].join("\n");
|
|
1917
|
+
}
|
|
1918
|
+
if (code === "103" || /duplicate request/i.test(message)) {
|
|
1919
|
+
return isCn ? [
|
|
1920
|
+
`[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)`,
|
|
1921
|
+
`- \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`,
|
|
1922
|
+
`- \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`
|
|
1923
|
+
].join("\n") : [
|
|
1924
|
+
`[Qoder duplicate request] The gateway rejected this request as a replay (code 103 Duplicate request)`,
|
|
1925
|
+
`- 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.`,
|
|
1926
|
+
`- Next step: every retry must be re-signed; if it keeps repeating, switch provider with /model and try again shortly.`
|
|
1927
|
+
].join("\n");
|
|
1928
|
+
}
|
|
1929
|
+
if (code === "10605" || /"isQueued"\s*:\s*true/.test(message)) {
|
|
1930
|
+
const notice = parseQueueNotice(rawBody) ?? parseQueueNotice(message);
|
|
1931
|
+
if (notice) {
|
|
1932
|
+
return formatQueueFailureMessage(notice, {
|
|
1933
|
+
mode,
|
|
1934
|
+
// This call does not own the retry loop, so it claims no attempt count.
|
|
1935
|
+
waitedMs: 0,
|
|
1936
|
+
manageUrl: getQoderRegionConfig(mode).manageUrl,
|
|
1937
|
+
otherProviderID: mode === "cn" ? "qoder" : "qoder-cn"
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
return isCn ? [
|
|
1941
|
+
`[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)`,
|
|
1942
|
+
`- \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`,
|
|
1943
|
+
`- \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`
|
|
1944
|
+
].join("\n") : [
|
|
1945
|
+
`[Qoder upstream queue] The model gateway is at capacity and parked this request (code 10605 isQueued)`,
|
|
1946
|
+
`- Meaning: credentials and quota are fine \u2014 this is pure upstream queuing; the gateway's own Retry-After wait did not clear it.`,
|
|
1947
|
+
`- Next step: retry shortly, or switch provider with /model while the queue drains.`
|
|
1948
|
+
].join("\n");
|
|
1949
|
+
}
|
|
1950
|
+
if (code === "1210" || /不支持关闭思考|cannot disable thinking|always.{0,12}think/i.test(bodyForMatch)) {
|
|
1951
|
+
return isCn ? [
|
|
1952
|
+
`[Qoder CN \u4E0D\u652F\u6301\u5173\u95ED\u601D\u8003] \u8BE5\u6A21\u578B\u59CB\u7EC8\u601D\u8003 (\u9519\u8BEF\u7801 1210)`,
|
|
1953
|
+
`- \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`,
|
|
1954
|
+
`- \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`
|
|
1955
|
+
].join("\n") : [
|
|
1956
|
+
`[Qoder model always thinks] This model cannot disable thinking (code 1210)`,
|
|
1957
|
+
`- Meaning: the model has no "off" switch, so a request carrying enable_thinking=false is refused. This will not change on retry.`,
|
|
1958
|
+
`- Next step: pick a thinking level (low / medium / high / max) instead of turning thinking off.`
|
|
1959
|
+
].join("\n");
|
|
1960
|
+
}
|
|
1961
|
+
if (rawBody === void 0 || rawBody === null) {
|
|
1962
|
+
return `Upstream status ${statusCode}`;
|
|
1963
|
+
}
|
|
1964
|
+
const bodyStr = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
1965
|
+
const truncated = bodyStr.length > 500 ? `${bodyStr.slice(0, 500)}...` : bodyStr;
|
|
1966
|
+
return `Upstream status ${statusCode}: ${truncated}`;
|
|
1967
|
+
}
|
|
1968
|
+
function isCredentialExpiredError(error) {
|
|
1969
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1970
|
+
if (message.includes("\u9519\u8BEF\u7801 105") || message.includes("\u51ED\u8BC1\u5931\u6548")) return true;
|
|
1971
|
+
return isCredentialExpiredVerdict(void 0, message) || /TOKEN_EXPIRED?/i.test(message);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// src/quota.ts
|
|
1975
|
+
function quotaNumber(value) {
|
|
1976
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1977
|
+
}
|
|
1978
|
+
function bucketLimit(bucket) {
|
|
1979
|
+
return quotaNumber(bucket.total ?? bucket.cap);
|
|
1980
|
+
}
|
|
1981
|
+
function hasAllowance(bucket) {
|
|
1982
|
+
if (!bucket) return false;
|
|
1983
|
+
return bucketLimit(bucket) > 0 || quotaNumber(bucket.used) > 0 || quotaNumber(bucket.remaining) > 0;
|
|
1984
|
+
}
|
|
1985
|
+
function pickBucket(raw, camel) {
|
|
1986
|
+
if (!raw) return void 0;
|
|
1987
|
+
const source = raw;
|
|
1988
|
+
const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
1989
|
+
return source[snake] ?? source[camel];
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// src/auth/usage.ts
|
|
1993
|
+
init_region();
|
|
1994
|
+
function round2(value) {
|
|
1995
|
+
return Math.round(value * 100) / 100;
|
|
1996
|
+
}
|
|
1997
|
+
function resetTimestamp(expiresAt) {
|
|
1998
|
+
const ms = quotaNumber(expiresAt);
|
|
1999
|
+
if (ms <= 0) return void 0;
|
|
2000
|
+
const timestamp = ms < 1e12 ? ms * 1e3 : ms;
|
|
2001
|
+
if (new Date(timestamp).getUTCFullYear() >= 9999) return void 0;
|
|
2002
|
+
return new Date(timestamp).toISOString();
|
|
1823
2003
|
}
|
|
1824
2004
|
async function fetchQoderUsageForMode(credentials, mode) {
|
|
1825
2005
|
const region = getQoderRegionConfig(mode);
|
|
@@ -1832,6 +2012,10 @@ async function fetchQoderUsageForMode(credentials, mode) {
|
|
|
1832
2012
|
}
|
|
1833
2013
|
});
|
|
1834
2014
|
if (!response.ok) {
|
|
2015
|
+
const body = await response.text().catch(() => "");
|
|
2016
|
+
if (isOpenApiInactiveCredential(response.status, body)) {
|
|
2017
|
+
throw new Error(formatInactiveCredentialMessage(getQoderUsageURL(mode), credentials.access, mode));
|
|
2018
|
+
}
|
|
1835
2019
|
throw new Error(`Failed to fetch Qoder usage: ${response.status} ${response.statusText}`);
|
|
1836
2020
|
}
|
|
1837
2021
|
const raw = await response.json();
|
|
@@ -1868,12 +2052,12 @@ init_catalog();
|
|
|
1868
2052
|
// src/commands/claim.ts
|
|
1869
2053
|
init_expiry();
|
|
1870
2054
|
init_cosy();
|
|
1871
|
-
init_region();
|
|
1872
2055
|
import { spawnSync } from "node:child_process";
|
|
1873
2056
|
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1874
2057
|
import { homedir as homedir5, hostname } from "node:os";
|
|
1875
2058
|
import { basename, dirname as dirname4, join as join5 } from "node:path";
|
|
1876
2059
|
import { fileURLToPath } from "node:url";
|
|
2060
|
+
init_region();
|
|
1877
2061
|
function piAgentDir2() {
|
|
1878
2062
|
const home = process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
1879
2063
|
return join5(home, ".pi", "agent");
|
|
@@ -2034,6 +2218,9 @@ async function fetchQoderCampaigns(accessToken, machineID, mode, identity) {
|
|
|
2034
2218
|
data = void 0;
|
|
2035
2219
|
}
|
|
2036
2220
|
if (!response.ok) {
|
|
2221
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2222
|
+
throw new Error(formatInactiveCredentialMessage(url, accessToken, mode));
|
|
2223
|
+
}
|
|
2037
2224
|
const detail = data?.message || text.slice(0, 200);
|
|
2038
2225
|
throw new Error(`Failed to query Qoder campaigns (${response.status}): ${detail}`);
|
|
2039
2226
|
}
|
|
@@ -2056,6 +2243,9 @@ async function claimQoderCampaign(accessToken, machineID, campaignId, mode, iden
|
|
|
2056
2243
|
data = void 0;
|
|
2057
2244
|
}
|
|
2058
2245
|
if (!response.ok) {
|
|
2246
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2247
|
+
throw new Error(formatInactiveCredentialMessage(url, accessToken, mode));
|
|
2248
|
+
}
|
|
2059
2249
|
const detail = data?.message || text.slice(0, 200);
|
|
2060
2250
|
throw new Error(`Failed to claim Qoder benefit (${response.status}): ${detail}`);
|
|
2061
2251
|
}
|
|
@@ -2197,414 +2387,139 @@ async function runClaimCommand(mode, args, ctx) {
|
|
|
2197
2387
|
if (wantsRaw) {
|
|
2198
2388
|
const rawJson = JSON.stringify(dailyCreditCampaign, null, 2);
|
|
2199
2389
|
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;
|
|
2218
|
-
}
|
|
2219
|
-
const claimRes = await claimQoderCampaign(accessToken, machineID, dailyCreditCampaign.campaignId, mode, identity);
|
|
2220
|
-
const grantAmount = claimRes.benefit?.amount || amount;
|
|
2221
|
-
const grantDays = claimRes.benefit?.validity?.days || validityDays;
|
|
2222
|
-
const expiresText = claimRes.expiresAt ? formatDateTime(claimRes.expiresAt) : `${grantDays} \u5929\u540E`;
|
|
2223
|
-
writeCheckinCache(
|
|
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);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
// src/protocol/errors.ts
|
|
2261
|
-
init_region();
|
|
2262
|
-
init_vpc();
|
|
2263
|
-
|
|
2264
|
-
// src/protocol/queue.ts
|
|
2265
|
-
var MAX_WAIT_MS = 12e4;
|
|
2266
|
-
var DEFAULT_RETRY_SECONDS = 30;
|
|
2267
|
-
function digForQueue(node, depth) {
|
|
2268
|
-
if (depth > 6) return null;
|
|
2269
|
-
if (typeof node === "string") {
|
|
2270
|
-
const trimmed = node.trim();
|
|
2271
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
2272
|
-
try {
|
|
2273
|
-
return digForQueue(JSON.parse(trimmed), depth + 1);
|
|
2274
|
-
} catch {
|
|
2275
|
-
return null;
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
if (node === null || typeof node !== "object") return null;
|
|
2279
|
-
if (Array.isArray(node)) {
|
|
2280
|
-
for (const item of node) {
|
|
2281
|
-
const found = digForQueue(item, depth + 1);
|
|
2282
|
-
if (found) return found;
|
|
2283
|
-
}
|
|
2284
|
-
return null;
|
|
2285
|
-
}
|
|
2286
|
-
const record = node;
|
|
2287
|
-
if (record.isQueued === true) return record;
|
|
2288
|
-
for (const value of Object.values(record)) {
|
|
2289
|
-
const found = digForQueue(value, depth + 1);
|
|
2290
|
-
if (found) return found;
|
|
2291
|
-
}
|
|
2292
|
-
return null;
|
|
2293
|
-
}
|
|
2294
|
-
function positiveNumber(value, fallback) {
|
|
2295
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2296
|
-
}
|
|
2297
|
-
function textOf(value) {
|
|
2298
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2299
|
-
}
|
|
2300
|
-
function parseQueueNotice(envelope) {
|
|
2301
|
-
const payload = digForQueue(envelope, 0);
|
|
2302
|
-
if (!payload) return null;
|
|
2303
|
-
const retryMs = positiveNumber(payload.retry_after_ms, 0) || positiveNumber(payload.retryAfterSeconds, DEFAULT_RETRY_SECONDS) * 1e3;
|
|
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
|
-
};
|
|
2317
|
-
}
|
|
2318
|
-
function formatWaitSecondsShort(ms) {
|
|
2319
|
-
if (!Number.isFinite(ms) || ms <= 0) return "0";
|
|
2320
|
-
const seconds = Math.round(ms / 1e3);
|
|
2321
|
-
if (seconds < 100) return String(seconds);
|
|
2322
|
-
return `${formatWaitMinutes(seconds)}\u5206\u949F`;
|
|
2323
|
-
}
|
|
2324
|
-
function formatQueueFailureMessage(notice, ctx) {
|
|
2325
|
-
const isCn = ctx.mode === "cn";
|
|
2326
|
-
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
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");
|
|
2358
|
-
}
|
|
2359
|
-
function formatLaneCooldownMessage(notice, remainingMs, ctx) {
|
|
2360
|
-
const isCn = ctx.mode === "cn";
|
|
2361
|
-
const lane = notice.queueType ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
2362
|
-
const sub = notice.subCode ?? (isCn ? "\u672A\u63D0\u4F9B" : "unset");
|
|
2363
|
-
const left = Math.min(99, Math.max(1, Math.ceil(remainingMs / 1e3)));
|
|
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");
|
|
2373
|
-
}
|
|
2374
|
-
function formatQueueFigure(n) {
|
|
2375
|
-
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
2376
|
-
if (n < 100) return String(Math.round(n));
|
|
2377
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2378
|
-
return `${(n / 1e3).toFixed(1)}k`;
|
|
2379
|
-
}
|
|
2380
|
-
function formatWaitMinutes(seconds) {
|
|
2381
|
-
if (!Number.isFinite(seconds) || seconds <= 0) return "<1";
|
|
2382
|
-
return String(Math.min(99, Math.max(1, Math.round(seconds / 60))));
|
|
2383
|
-
}
|
|
2384
|
-
function sleep(ms, signal) {
|
|
2385
|
-
return new Promise((resolve, reject) => {
|
|
2386
|
-
if (signal?.aborted) {
|
|
2387
|
-
reject(new Error("Aborted while waiting for the Qoder queue."));
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
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
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// src/protocol/errors.ts
|
|
2404
|
-
function parseQoderErrorPayload(payload) {
|
|
2405
|
-
if (!payload) return null;
|
|
2406
|
-
let parsed = payload;
|
|
2407
|
-
if (typeof payload === "string") {
|
|
2408
|
-
try {
|
|
2409
|
-
parsed = JSON.parse(payload);
|
|
2410
|
-
} catch {
|
|
2411
|
-
return null;
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2415
|
-
const obj = parsed;
|
|
2416
|
-
if (typeof obj.message === "string" && obj.message.trim().startsWith("{")) {
|
|
2417
|
-
try {
|
|
2418
|
-
const nested = JSON.parse(obj.message);
|
|
2419
|
-
if (typeof nested === "object" && nested !== null) {
|
|
2420
|
-
const nestedObj = nested;
|
|
2421
|
-
return {
|
|
2422
|
-
code: String(nestedObj.code || nestedObj.errorCode || obj.code || ""),
|
|
2423
|
-
message: String(nestedObj.message || nestedObj.errorMessage || obj.message || "")
|
|
2424
|
-
};
|
|
2390
|
+
if (!ctx?.ui) console.log(rawJson);
|
|
2391
|
+
return;
|
|
2425
2392
|
}
|
|
2426
|
-
|
|
2393
|
+
const lines2 = mode === "cn" ? [
|
|
2394
|
+
paint(`\u2139\uFE0F \u4ECA\u65E5 ${amount} Credits \u5DF2\u7ECF\u9886\u53D6\u8FC7\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C`, ANSI.cyan),
|
|
2395
|
+
`- \u6D3B\u52A8\u540D\u79F0\uFF1A${campaignTitle}`,
|
|
2396
|
+
`- \u989D\u5EA6\u8BF4\u660E\uFF1A${amount} Credits\uFF08\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF0C${validityDays} \u5929\u6709\u6548\uFF09`,
|
|
2397
|
+
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2398
|
+
] : [
|
|
2399
|
+
paint(`\u2139\uFE0F Today's ${amount} Credits already claimed.`, ANSI.cyan),
|
|
2400
|
+
`- Campaign: ${campaignTitle}`,
|
|
2401
|
+
`- Benefit: ${amount} Credits (${validityDays}-day validity)`,
|
|
2402
|
+
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2403
|
+
];
|
|
2404
|
+
const output2 = lines2.join("\n");
|
|
2405
|
+
ctx?.ui?.notify(output2, "info");
|
|
2406
|
+
if (!ctx?.ui) console.log(output2);
|
|
2407
|
+
return;
|
|
2427
2408
|
}
|
|
2409
|
+
const claimRes = await claimQoderCampaign(accessToken, machineID, dailyCreditCampaign.campaignId, mode, identity);
|
|
2410
|
+
const grantAmount = claimRes.benefit?.amount || amount;
|
|
2411
|
+
const grantDays = claimRes.benefit?.validity?.days || validityDays;
|
|
2412
|
+
const expiresText = claimRes.expiresAt ? formatDateTime(claimRes.expiresAt) : `${grantDays} \u5929\u540E`;
|
|
2413
|
+
writeCheckinCache(
|
|
2414
|
+
{
|
|
2415
|
+
claimed: true,
|
|
2416
|
+
amount: grantAmount,
|
|
2417
|
+
claimedAt: claimRes.claimedAt,
|
|
2418
|
+
expiresAt: claimRes.expiresAt,
|
|
2419
|
+
validityDays: grantDays,
|
|
2420
|
+
campaignId: dailyCreditCampaign.campaignId,
|
|
2421
|
+
campaignTitle,
|
|
2422
|
+
cacheUntil: Date.now() + remainingToReset
|
|
2423
|
+
},
|
|
2424
|
+
mode
|
|
2425
|
+
);
|
|
2426
|
+
const lines = mode === "cn" ? [
|
|
2427
|
+
paint(`\u{1F389} \u6210\u529F\u9886\u53D6\u4ECA\u65E5 ${grantAmount} Credits\uFF01`, `${ANSI.green}${ANSI.bold}`),
|
|
2428
|
+
`- \u989D\u5EA6\u7C7B\u578B\uFF1A\u5168\u6A21\u578B\u901A\u7528\u8D44\u6E90\u5305\uFF08Add-on Credits\uFF09`,
|
|
2429
|
+
`- \u6709\u6548\u671F\u9650\uFF1A${grantDays} \u5929\uFF08\u6709\u6548\u671F\u81F3 ${expiresText}\uFF09`,
|
|
2430
|
+
`- \u9886\u53D6\u6D41\u6C34\uFF1A${claimRes.grantId || "ok"}`,
|
|
2431
|
+
`- \u4E0B\u6B21\u5237\u65B0\uFF1A\u660E\u65E5 10:00 UTC+8\uFF08\u8DDD\u5237\u65B0\u7EA6 ${countdown}\uFF09`
|
|
2432
|
+
] : [
|
|
2433
|
+
paint(`\u{1F389} Successfully claimed ${grantAmount} Credits!`, `${ANSI.green}${ANSI.bold}`),
|
|
2434
|
+
`- Type: Universal Add-on Credits`,
|
|
2435
|
+
`- Validity: ${grantDays} days (expires ${expiresText})`,
|
|
2436
|
+
`- Grant ID: ${claimRes.grantId || "ok"}`,
|
|
2437
|
+
`- Next reset: Daily at 10:00 UTC+8 (in ~${countdown})`
|
|
2438
|
+
];
|
|
2439
|
+
const output = lines.join("\n");
|
|
2440
|
+
ctx?.ui?.notify(output, "info");
|
|
2441
|
+
if (!ctx?.ui) console.log(output);
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2444
|
+
const errText = mode === "cn" ? `\u274C \u6BCF\u65E5\u7B7E\u5230\u9886\u53D6\u5931\u8D25: ${message}` : `\u274C Daily check-in claim failed: ${message}`;
|
|
2445
|
+
ctx?.ui?.notify(errText, "error");
|
|
2446
|
+
if (!ctx?.ui) console.error(errText);
|
|
2428
2447
|
}
|
|
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
|
-
}
|
|
2437
|
-
function msUntilBeijingMidnight(now = Date.now()) {
|
|
2438
|
-
const beijingOffset = 8 * 36e5;
|
|
2439
|
-
const beijingTime = now + beijingOffset;
|
|
2440
|
-
const beijingDate = new Date(beijingTime);
|
|
2441
|
-
const nextMidnightUtc = Date.UTC(
|
|
2442
|
-
beijingDate.getUTCFullYear(),
|
|
2443
|
-
beijingDate.getUTCMonth(),
|
|
2444
|
-
beijingDate.getUTCDate() + 1,
|
|
2445
|
-
0,
|
|
2446
|
-
0,
|
|
2447
|
-
0
|
|
2448
|
-
);
|
|
2449
|
-
return nextMidnightUtc - beijingTime;
|
|
2450
|
-
}
|
|
2451
|
-
function getBeijingDailyResetCountdown(now = Date.now()) {
|
|
2452
|
-
const diffMs = msUntilBeijingMidnight(now);
|
|
2453
|
-
const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
|
|
2454
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
2455
|
-
const minutes = totalMinutes % 60;
|
|
2456
|
-
return hours > 0 ? `${hours}\u5C0F\u65F6${minutes}\u5206\u949F` : `${minutes}\u5206\u949F`;
|
|
2457
|
-
}
|
|
2458
|
-
var CREDENTIAL_EXPIRED_CODES = /* @__PURE__ */ new Set(["105", "TOKEN_EXPIRE", "TOKEN_EXPIRED"]);
|
|
2459
|
-
var CREDENTIAL_EXPIRED_MESSAGE = /token expired|login expired|token is not active|token has expired/i;
|
|
2460
|
-
function isCredentialExpiredVerdict(code, message) {
|
|
2461
|
-
return code !== void 0 && CREDENTIAL_EXPIRED_CODES.has(code) || CREDENTIAL_EXPIRED_MESSAGE.test(message);
|
|
2462
2448
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
const
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
2475
|
-
`- 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.`,
|
|
2476
|
-
`- Fix: wait for the reset; switch provider with /model; or upgrade at https://qoder.com/pricing to lift the daily limit.`
|
|
2477
|
-
].join("\n");
|
|
2478
|
-
}
|
|
2479
|
-
return [
|
|
2480
|
-
`[Qoder CN \u989D\u5EA6\u9650\u5236] \u4ECA\u65E5\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 (Billing daily count exceeded, \u9519\u8BEF\u7801 110)`,
|
|
2481
|
-
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
2482
|
-
`- \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`,
|
|
2483
|
-
`- \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`
|
|
2484
|
-
].join("\n");
|
|
2485
|
-
}
|
|
2486
|
-
if (code === "117" || /team member credits exhausted/i.test(message)) {
|
|
2487
|
-
return isCn ? [
|
|
2488
|
-
`[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)`,
|
|
2489
|
-
`- \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`,
|
|
2490
|
-
`- \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`
|
|
2491
|
-
].join("\n") : [
|
|
2492
|
-
`[Qoder enterprise quota] Your member credit allowance is exhausted (code 117)`,
|
|
2493
|
-
`- Reason: the admin-assigned per-member credit cap has been consumed.`,
|
|
2494
|
-
`- Fix: ask your organization admin to raise your member quota in the team console.`
|
|
2495
|
-
].join("\n");
|
|
2449
|
+
|
|
2450
|
+
// src/commands/models.ts
|
|
2451
|
+
init_catalog();
|
|
2452
|
+
init_region();
|
|
2453
|
+
function formatCatalogDiff(diff, mode, color = false) {
|
|
2454
|
+
const region = getQoderRegionConfig(mode);
|
|
2455
|
+
const paint3 = (text, code) => color ? `${code}${text}\x1B[0m` : text;
|
|
2456
|
+
const lines = [`${region.usageTitle} models \xB7 ${diff.total} offered by the catalog`];
|
|
2457
|
+
if (diff.added.length > 0) {
|
|
2458
|
+
lines.push("", paint3(`New (${diff.added.length})`, "\x1B[32m"));
|
|
2459
|
+
for (const model of diff.added) lines.push(` + ${model.id} ${model.name}`);
|
|
2496
2460
|
}
|
|
2497
|
-
if (
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
`- \u9650\u5236\u539F\u56E0\uFF1A\u5F53\u524D\u4F01\u4E1A\u7EC4\u7EC7\u8D26\u6237\u7684 Credits \u989D\u5EA6\u5DF2\u5168\u90E8\u7528\u5B8C\u3002`,
|
|
2501
|
-
`- \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`
|
|
2502
|
-
].join("\n") : [
|
|
2503
|
-
`[Qoder enterprise quota] The organization's credit pool is exhausted (code 116)`,
|
|
2504
|
-
`- Reason: every credit in the organization account has been consumed.`,
|
|
2505
|
-
`- Fix: ask your organization admin to purchase or renew the org pool.`
|
|
2506
|
-
].join("\n");
|
|
2461
|
+
if (diff.renamed.length > 0) {
|
|
2462
|
+
lines.push("", paint3(`Renamed upstream (${diff.renamed.length})`, "\x1B[33m"));
|
|
2463
|
+
for (const model of diff.renamed) lines.push(` ~ ${model.id} ${model.from} -> ${model.to}`);
|
|
2507
2464
|
}
|
|
2508
|
-
if (
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
`- \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`,
|
|
2512
|
-
`- \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`
|
|
2513
|
-
].join("\n") : [
|
|
2514
|
-
`[Qoder billing group] The billing group reached its period spend cap (code 122)`,
|
|
2515
|
-
`- Reason: the admin-set limit for this billing group is consumed for the period.`,
|
|
2516
|
-
`- Fix: ask the billing administrator to raise the group's period cap.`
|
|
2517
|
-
].join("\n");
|
|
2465
|
+
if (diff.removed.length > 0) {
|
|
2466
|
+
lines.push("", paint3(`Gone (${diff.removed.length})`, "\x1B[31m"));
|
|
2467
|
+
for (const model of diff.removed) lines.push(` - ${model.id} ${model.name}`);
|
|
2518
2468
|
}
|
|
2519
|
-
if (
|
|
2520
|
-
|
|
2521
|
-
`[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)`,
|
|
2522
|
-
`- \u5237\u65B0\u65F6\u95F4\uFF1A\u5C06\u5728\u5317\u4EAC\u65F6\u95F4 00:00 \u91CD\u7F6E\uFF08\u7EA6 ${countdown}\u540E\uFF09`,
|
|
2523
|
-
`- \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`
|
|
2524
|
-
].join("\n") : [
|
|
2525
|
-
`[Qoder model free tier] This model's free daily trial allowance is used up (code 119)`,
|
|
2526
|
-
`- Reset: at 00:00 UTC+8 (in ${countdown})`,
|
|
2527
|
-
`- Fix: switch to a paid model via /model to spend credits, or wait for the reset.`
|
|
2528
|
-
].join("\n");
|
|
2469
|
+
if (diff.added.length === 0 && diff.renamed.length === 0 && diff.removed.length === 0) {
|
|
2470
|
+
lines.push("", "No change since the last refresh.");
|
|
2529
2471
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2472
|
+
lines.push("", `Provider list refreshed in this session; /model now shows ${diff.total} entries.`);
|
|
2473
|
+
return lines;
|
|
2474
|
+
}
|
|
2475
|
+
async function refreshCatalog(mode, accessToken, identity, refreshProvider) {
|
|
2476
|
+
const region = getQoderRegionConfig(mode);
|
|
2477
|
+
clearQoderModelsMemCache();
|
|
2478
|
+
const diff = await updateQoderModelsCache(
|
|
2479
|
+
accessToken,
|
|
2480
|
+
identity.userID || "qoder-user",
|
|
2481
|
+
identity.name || region.userNameFallback,
|
|
2482
|
+
identity.email || region.userEmailFallback,
|
|
2483
|
+
mode
|
|
2484
|
+
);
|
|
2485
|
+
if (diff) {
|
|
2486
|
+
clearQoderModelsMemCache();
|
|
2487
|
+
refreshProvider?.();
|
|
2541
2488
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2489
|
+
return diff;
|
|
2490
|
+
}
|
|
2491
|
+
async function runModelsCommand(mode, args, ctx, refreshProvider) {
|
|
2492
|
+
const region = getQoderRegionConfig(mode);
|
|
2493
|
+
const providerID = region.providerID;
|
|
2494
|
+
const target = (args || "").trim().toLowerCase();
|
|
2495
|
+
const plain = target === "plain";
|
|
2496
|
+
const wantsList = target === "list";
|
|
2497
|
+
try {
|
|
2498
|
+
const accessToken = await ctx?.modelRegistry?.getApiKeyForProvider(providerID);
|
|
2499
|
+
if (!accessToken) {
|
|
2500
|
+
ctx?.ui?.notify(`No ${providerID} credentials. Run /login ${providerID} first.`, "warning");
|
|
2501
|
+
return;
|
|
2548
2502
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
return isCn ? [
|
|
2556
|
-
`[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)`,
|
|
2557
|
-
`- \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`,
|
|
2558
|
-
`- \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`
|
|
2559
|
-
].join("\n") : [
|
|
2560
|
-
`[Qoder duplicate request] The gateway rejected this request as a replay (code 103 Duplicate request)`,
|
|
2561
|
-
`- 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.`,
|
|
2562
|
-
`- Next step: every retry must be re-signed; if it keeps repeating, switch provider with /model and try again shortly.`
|
|
2563
|
-
].join("\n");
|
|
2564
|
-
}
|
|
2565
|
-
if (code === "10605" || /"isQueued"\s*:\s*true/.test(message)) {
|
|
2566
|
-
const notice = parseQueueNotice(rawBody) ?? parseQueueNotice(message);
|
|
2567
|
-
if (notice) {
|
|
2568
|
-
return formatQueueFailureMessage(notice, {
|
|
2569
|
-
mode,
|
|
2570
|
-
// This call does not own the retry loop, so it claims no attempt count.
|
|
2571
|
-
waitedMs: 0,
|
|
2572
|
-
manageUrl: getQoderRegionConfig(mode).manageUrl,
|
|
2573
|
-
otherProviderID: mode === "cn" ? "qoder" : "qoder-cn"
|
|
2574
|
-
});
|
|
2503
|
+
if (wantsList) {
|
|
2504
|
+
const models = getCachedModels(mode);
|
|
2505
|
+
ctx?.ui?.notify(
|
|
2506
|
+
[`${region.usageTitle} models`, ...models.map((model) => ` ${model.id} ${model.name}`)].join("\n")
|
|
2507
|
+
);
|
|
2508
|
+
return;
|
|
2575
2509
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
`[Qoder CN \u4E0D\u652F\u6301\u5173\u95ED\u601D\u8003] \u8BE5\u6A21\u578B\u59CB\u7EC8\u601D\u8003 (\u9519\u8BEF\u7801 1210)`,
|
|
2589
|
-
`- \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`,
|
|
2590
|
-
`- \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`
|
|
2591
|
-
].join("\n") : [
|
|
2592
|
-
`[Qoder model always thinks] This model cannot disable thinking (code 1210)`,
|
|
2593
|
-
`- Meaning: the model has no "off" switch, so a request carrying enable_thinking=false is refused. This will not change on retry.`,
|
|
2594
|
-
`- Next step: pick a thinking level (low / medium / high / max) instead of turning thinking off.`
|
|
2595
|
-
].join("\n");
|
|
2596
|
-
}
|
|
2597
|
-
if (rawBody === void 0 || rawBody === null) {
|
|
2598
|
-
return `Upstream status ${statusCode}`;
|
|
2510
|
+
const diff = await refreshCatalog(mode, accessToken, {}, refreshProvider);
|
|
2511
|
+
if (!diff) {
|
|
2512
|
+
ctx?.ui?.notify(
|
|
2513
|
+
`Could not refresh the ${providerID} model catalog (network, credentials, or an unexpected payload). The existing list is unchanged.`,
|
|
2514
|
+
"warning"
|
|
2515
|
+
);
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
ctx?.ui?.notify(formatCatalogDiff(diff, mode, !plain).join("\n"));
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2521
|
+
ctx?.ui?.notify(`Model refresh failed: ${message}`, "error");
|
|
2599
2522
|
}
|
|
2600
|
-
const bodyStr = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
2601
|
-
const truncated = bodyStr.length > 500 ? `${bodyStr.slice(0, 500)}...` : bodyStr;
|
|
2602
|
-
return `Upstream status ${statusCode}: ${truncated}`;
|
|
2603
|
-
}
|
|
2604
|
-
function isCredentialExpiredError(error) {
|
|
2605
|
-
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2606
|
-
if (message.includes("\u9519\u8BEF\u7801 105") || message.includes("\u51ED\u8BC1\u5931\u6548")) return true;
|
|
2607
|
-
return isCredentialExpiredVerdict(void 0, message) || /TOKEN_EXPIRED?/i.test(message);
|
|
2608
2523
|
}
|
|
2609
2524
|
|
|
2610
2525
|
// src/commands/usage.ts
|
|
@@ -2615,7 +2530,7 @@ function positiveOr(value, fallback) {
|
|
|
2615
2530
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2616
2531
|
}
|
|
2617
2532
|
function estimateRollingAddOnExpiry(addOn, grant, now = Date.now()) {
|
|
2618
|
-
if (!addOn
|
|
2533
|
+
if (!addOn?.total || addOn.total <= 0 || !addOn.remaining || addOn.remaining <= 0) {
|
|
2619
2534
|
return null;
|
|
2620
2535
|
}
|
|
2621
2536
|
const shape = typeof grant === "number" ? { expiryMs: grant } : grant ?? {};
|
|
@@ -2858,6 +2773,9 @@ async function fetchQoderQuota(accessToken, mode) {
|
|
|
2858
2773
|
data = void 0;
|
|
2859
2774
|
}
|
|
2860
2775
|
if (!response.ok) {
|
|
2776
|
+
if (isOpenApiInactiveCredential(response.status, text)) {
|
|
2777
|
+
throw new Error(formatInactiveCredentialMessage(getQoderUsageURL(mode), accessToken, mode));
|
|
2778
|
+
}
|
|
2861
2779
|
const detail = data?.message || data?.error || text.slice(0, 200);
|
|
2862
2780
|
const hint = response.status === 401 || response.status === 403 ? `Re-login with /login ${getQoderRegionConfig(mode).providerID}.` : "";
|
|
2863
2781
|
const parts = [`Qoder usage request failed: ${response.status} ${response.statusText}`, hint, detail];
|
|
@@ -4115,13 +4033,16 @@ function registerQoderProvider(pi, mode) {
|
|
|
4115
4033
|
streamSimple: streamQoder
|
|
4116
4034
|
});
|
|
4117
4035
|
}
|
|
4036
|
+
function reregisterQoderProvider(pi, mode) {
|
|
4037
|
+
registerQoderProvider(pi, mode);
|
|
4038
|
+
}
|
|
4118
4039
|
async function refreshModelsAtStartup(mode) {
|
|
4119
4040
|
const providerID = getQoderRegionConfig(mode).providerID;
|
|
4120
|
-
if (!isCacheStale(mode)) return;
|
|
4041
|
+
if (!isCacheStale(mode)) return null;
|
|
4121
4042
|
const credentials = getCachedCredentials("", providerID);
|
|
4122
|
-
if (!credentials?.access) return;
|
|
4043
|
+
if (!credentials?.access) return null;
|
|
4123
4044
|
const region = getQoderRegionConfig(mode);
|
|
4124
|
-
|
|
4045
|
+
return updateQoderModelsCache(
|
|
4125
4046
|
credentials.access,
|
|
4126
4047
|
credentials.userID || "qoder-user",
|
|
4127
4048
|
credentials.name || region.userNameFallback,
|
|
@@ -4173,6 +4094,12 @@ function registerCommands(pi) {
|
|
|
4173
4094
|
await runClaimCommand("cn", args, ctx);
|
|
4174
4095
|
}
|
|
4175
4096
|
});
|
|
4097
|
+
pi.registerCommand("qoder-cn.models", {
|
|
4098
|
+
description: "Refresh the Qoder CN model catalog and report new, renamed and retired models (append 'list' for the cached list)",
|
|
4099
|
+
handler: async (args, ctx) => {
|
|
4100
|
+
await runModelsCommand("cn", args, ctx, () => reregisterQoderProvider(pi, "cn"));
|
|
4101
|
+
}
|
|
4102
|
+
});
|
|
4176
4103
|
pi.registerCommand("qoder.usage", {
|
|
4177
4104
|
description: "Show Qoder Global quota: plan + add-on credits and check-in status (append 'json' for raw payload)",
|
|
4178
4105
|
handler: async (args, ctx) => {
|
|
@@ -4185,6 +4112,12 @@ function registerCommands(pi) {
|
|
|
4185
4112
|
await runClaimCommand("global", args, ctx);
|
|
4186
4113
|
}
|
|
4187
4114
|
});
|
|
4115
|
+
pi.registerCommand("qoder.models", {
|
|
4116
|
+
description: "Refresh the Qoder Global model catalog and report new, renamed and retired models (append 'list' for the cached list)",
|
|
4117
|
+
handler: async (args, ctx) => {
|
|
4118
|
+
await runModelsCommand("global", args, ctx, () => reregisterQoderProvider(pi, "global"));
|
|
4119
|
+
}
|
|
4120
|
+
});
|
|
4188
4121
|
}
|
|
4189
4122
|
export {
|
|
4190
4123
|
index_default as default
|