traderclaw-cli 1.0.51

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.
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Ranks OpenClaw model keys so defaults pick current, working IDs — not alphabetically-first
3
+ * or legacy IDs that "win" only because of a large YYYYMMDD suffix in the name.
4
+ *
5
+ * References (verify periodically): Anthropic models overview, OpenAI models docs, xAI docs.
6
+ * Last verified: 2026-03-29.
7
+ */
8
+
9
+ /** IDs that appear in catalogs but are retired, deprecated, or misbehave for chat/agent use */
10
+ export const KNOWN_PROBLEMATIC_MODEL_IDS = new Set([
11
+ "anthropic/claude-3-5-haiku-20241022",
12
+ "anthropic/claude-3-haiku-20240307",
13
+ "anthropic/claude-3-opus-20240229",
14
+ "anthropic/claude-3-sonnet-20240229",
15
+ "anthropic/claude-3-5-sonnet-20240620",
16
+ "openai/gpt-4o",
17
+ "openai/gpt-4o-mini",
18
+ "openai/gpt-4-turbo",
19
+ "openai/gpt-4",
20
+ "openai/gpt-3.5-turbo",
21
+ "openai/gpt-5",
22
+ "openai/gpt-5.1",
23
+ "openai/gpt-5.1-codex",
24
+ "openai/gpt-5.1-codex-max",
25
+ "openai/gpt-5.1-codex-mini",
26
+ "openai/gpt-5.1-instant",
27
+ "openai-codex/gpt-5.1",
28
+ "openai-codex/gpt-5.1-codex",
29
+ "openai-codex/gpt-5.1-codex-max",
30
+ "openai-codex/gpt-5.1-codex-mini",
31
+ ]);
32
+
33
+ const VARIANT_WEIGHT = { sonnet: 80_000, opus: 60_000, haiku: 40_000 };
34
+
35
+ function tieBreakerSnapshot(id) {
36
+ const m = String(id).match(/-(\d{8})$/);
37
+ if (!m) return 0;
38
+ const n = Number.parseInt(m[1], 10);
39
+ return Number.isFinite(n) ? n : 0;
40
+ }
41
+
42
+ function stripPrefix(provider, modelId) {
43
+ const p = `${provider}/`;
44
+ const s = String(modelId);
45
+ return s.toLowerCase().startsWith(p) ? s.slice(p.length).toLowerCase() : s.toLowerCase();
46
+ }
47
+
48
+ /**
49
+ * Parse Anthropic API-style ids (after provider prefix): claude-sonnet-4-6, claude-3-5-sonnet-20241022, etc.
50
+ */
51
+ function parseAnthropicLocal(local) {
52
+ let m = local.match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)(?:-(\d{8}))?$/);
53
+ if (m) {
54
+ return {
55
+ variant: m[1],
56
+ major: Number(m[2]),
57
+ minor: Number(m[3]),
58
+ snapshot: m[4] ? Number(m[4]) : 0,
59
+ };
60
+ }
61
+ m = local.match(/^claude-3-5-(opus|sonnet|haiku)-(\d{8})$/);
62
+ if (m) {
63
+ return { variant: m[1], major: 3, minor: 5, snapshot: Number(m[2]) };
64
+ }
65
+ m = local.match(/^claude-3-(opus|sonnet|haiku)-(\d{8})$/);
66
+ if (m) {
67
+ return { variant: m[1], major: 3, minor: 0, snapshot: Number(m[2]) };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function anthropicPreferenceScore(modelId) {
73
+ const local = stripPrefix("anthropic", modelId);
74
+ const meta = parseAnthropicLocal(local);
75
+ if (!meta) {
76
+ return 1_000_000 + tieBreakerSnapshot(local);
77
+ }
78
+ const generation = meta.major * 10_000 + meta.minor;
79
+ const v = VARIANT_WEIGHT[meta.variant] || 0;
80
+ return generation * 1_000_000_000_000 + v + meta.snapshot;
81
+ }
82
+
83
+ function openaiFamilyScore(local) {
84
+ const g5 = local.match(/^gpt-5(?:\.(\d+))?/);
85
+ if (g5) {
86
+ const sub = g5[1] ? Number.parseInt(g5[1], 10) : 0;
87
+ let s = 500 * 1_000_000_000 + sub * 1_000_000;
88
+ if (local.includes("pro") || local.includes("thinking")) s += 400_000;
89
+ else if (local.includes("nano")) s -= 200_000;
90
+ else if (local.includes("mini")) s -= 100_000;
91
+ else if (local.includes("instant")) s -= 150_000;
92
+ return s;
93
+ }
94
+ const oSeries = local.match(/^o(\d+)/);
95
+ if (oSeries) {
96
+ const gen = Number.parseInt(oSeries[1], 10);
97
+ let score = 490 * 1_000_000_000 + gen * 1_000_000;
98
+ if (local.includes("mini")) score -= 500_000;
99
+ if (local.includes("pro")) score += 200_000;
100
+ return score;
101
+ }
102
+ if (local.startsWith("gpt-4o-mini")) return 380 * 1_000_000_000;
103
+ if (local.startsWith("gpt-4o")) return 385 * 1_000_000_000;
104
+ if (local.includes("gpt-4-turbo")) return 370 * 1_000_000_000;
105
+ if (local.startsWith("gpt-4")) return 360 * 1_000_000_000;
106
+ if (local.includes("gpt-3.5")) return 250 * 1_000_000_000;
107
+ return 100 * 1_000_000_000;
108
+ }
109
+
110
+ function openaiPreferenceScore(modelId) {
111
+ const raw = String(modelId).toLowerCase();
112
+ const local = raw.includes("/") ? raw.split("/").slice(1).join("/") : raw;
113
+ let score = openaiFamilyScore(local);
114
+ score += tieBreakerSnapshot(local);
115
+ if (local.includes("preview")) score -= 50_000_000;
116
+ return score;
117
+ }
118
+
119
+ function xaiPreferenceScore(modelId) {
120
+ const local = stripPrefix("xai", modelId);
121
+ const grokMatch = local.match(/^grok-(\d+)(?:\.(\d+))?/);
122
+ if (!grokMatch) return 100_000_000 + tieBreakerSnapshot(modelId);
123
+ const major = Number.parseInt(grokMatch[1], 10);
124
+ const minor = grokMatch[2] ? Number.parseInt(grokMatch[2], 10) : 0;
125
+ let score = (major * 100 + minor) * 1_000_000_000;
126
+ if (local.includes("non-reasoning")) score -= 100_000;
127
+ else if (local.includes("reasoning")) score += 200_000;
128
+ if (local.includes("fast")) score -= 50_000;
129
+ if (local.includes("beta")) score -= 500_000;
130
+ if (local.includes("code")) score += 100_000;
131
+ score += tieBreakerSnapshot(modelId);
132
+ return score;
133
+ }
134
+
135
+ function deepseekPreferenceScore(modelId) {
136
+ const local = stripPrefix("deepseek", modelId);
137
+ if (local.includes("reasoner")) return 600_000_000;
138
+ if (local.includes("chat")) return 500_000_000;
139
+ return 100_000_000 + tieBreakerSnapshot(modelId);
140
+ }
141
+
142
+ function googlePreferenceScore(modelId) {
143
+ const local = stripPrefix("google", modelId).replace(/^google-vertex\//, "");
144
+ const gemini = local.match(/gemini-(\d+)(?:\.(\d+))?/);
145
+ if (!gemini) return 100_000_000 + tieBreakerSnapshot(modelId);
146
+ const major = Number.parseInt(gemini[1], 10);
147
+ const minor = gemini[2] ? Number.parseInt(gemini[2], 10) : 0;
148
+ let score = (major * 100 + minor) * 1_000_000_000;
149
+ if (local.includes("pro")) score += 200_000_000;
150
+ else if (local.includes("ultra")) score += 300_000_000;
151
+ else if (local.includes("flash")) score += 100_000_000;
152
+ else if (local.includes("nano")) score += 50_000_000;
153
+ if (local.includes("preview") || local.includes("experimental")) score -= 50_000_000;
154
+ if (local.includes("thinking")) score += 10_000_000;
155
+ if (local.includes("latest")) score += 5_000_000;
156
+ score += tieBreakerSnapshot(modelId);
157
+ return score;
158
+ }
159
+
160
+ function extractParamCountBonus(local) {
161
+ const m = local.match(/(\d+)b(?:\b|-)/i);
162
+ if (!m) return 0;
163
+ const b = Number.parseInt(m[1], 10);
164
+ return Math.min(b, 2000) * 100;
165
+ }
166
+
167
+ function genericPreferenceScore(provider, modelId) {
168
+ const local = stripPrefix(provider, modelId);
169
+ let score = tieBreakerSnapshot(modelId);
170
+ if (local.includes("latest")) score += 500_000_000;
171
+ if (local.includes("pro") || local.includes("large")) score += 100_000_000;
172
+ if (local.includes("preview") || local.includes("deprecated")) score -= 200_000_000;
173
+ if (local.includes("beta")) score -= 50_000_000;
174
+ const verMatch = local.match(/(?:^|[a-z-])(\d+)(?:\.(\d+))?/);
175
+ if (verMatch) {
176
+ const maj = Number.parseInt(verMatch[1], 10);
177
+ const min = verMatch[2] ? Number.parseInt(verMatch[2], 10) : 0;
178
+ if (maj <= 99) {
179
+ score += (maj * 100 + min) * 1_000_000;
180
+ }
181
+ }
182
+ score += extractParamCountBonus(local);
183
+ return score;
184
+ }
185
+
186
+ /**
187
+ * Higher = more preferred as default.
188
+ */
189
+ export function modelPreferenceScore(provider, modelId) {
190
+ const id = String(modelId);
191
+ let score = 0;
192
+
193
+ if (KNOWN_PROBLEMATIC_MODEL_IDS.has(id)) {
194
+ score -= 10_000_000_000_000;
195
+ }
196
+
197
+ if (provider === "anthropic") {
198
+ score += anthropicPreferenceScore(id);
199
+ } else if (provider === "openai" || provider === "openai-codex") {
200
+ score += openaiPreferenceScore(id);
201
+ } else if (provider === "google" || provider === "google-vertex") {
202
+ score += googlePreferenceScore(id);
203
+ } else if (provider === "xai") {
204
+ score += xaiPreferenceScore(id);
205
+ } else if (provider === "deepseek") {
206
+ score += deepseekPreferenceScore(id);
207
+ } else {
208
+ score += genericPreferenceScore(provider, id);
209
+ }
210
+
211
+ return score;
212
+ }
213
+
214
+ /**
215
+ * Best-first order for dropdowns and validation.
216
+ */
217
+ export function sortModelsByPreference(provider, modelIds) {
218
+ const items = [...new Set((modelIds || []).filter(Boolean))];
219
+ return items.sort(
220
+ (a, b) => modelPreferenceScore(provider, b) - modelPreferenceScore(provider, a) || String(a).localeCompare(String(b)),
221
+ );
222
+ }
223
+
224
+ export function choosePreferredProviderModel(provider, models = []) {
225
+ const items = Array.isArray(models) ? models.filter(Boolean) : [];
226
+ if (items.length === 0) return "";
227
+ const sorted = sortModelsByPreference(provider, items);
228
+ return sorted[0] || items[0];
229
+ }