solana-traderclaw 1.0.19

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,136 @@
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.
6
+ */
7
+
8
+ /** IDs that often appear in catalogs but fail or misbehave for chat/agent use */
9
+ export const KNOWN_PROBLEMATIC_MODEL_IDS = new Set([
10
+ "anthropic/claude-3-5-haiku-20241022",
11
+ "anthropic/claude-3-haiku-20240307",
12
+ ]);
13
+
14
+ const VARIANT_WEIGHT = { sonnet: 80_000, opus: 60_000, haiku: 40_000 };
15
+
16
+ function tieBreakerSnapshot(id) {
17
+ const m = String(id).match(/-(\d{8})$/);
18
+ if (!m) return 0;
19
+ const n = Number.parseInt(m[1], 10);
20
+ return Number.isFinite(n) ? n : 0;
21
+ }
22
+
23
+ function stripPrefix(provider, modelId) {
24
+ const p = `${provider}/`;
25
+ const s = String(modelId);
26
+ return s.toLowerCase().startsWith(p) ? s.slice(p.length).toLowerCase() : s.toLowerCase();
27
+ }
28
+
29
+ /**
30
+ * Parse Anthropic API-style ids (after provider prefix): claude-sonnet-4-6, claude-3-5-sonnet-20241022, etc.
31
+ */
32
+ function parseAnthropicLocal(local) {
33
+ let m = local.match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)(?:-(\d{8}))?$/);
34
+ if (m) {
35
+ return {
36
+ variant: m[1],
37
+ major: Number(m[2]),
38
+ minor: Number(m[3]),
39
+ snapshot: m[4] ? Number(m[4]) : 0,
40
+ };
41
+ }
42
+ m = local.match(/^claude-3-5-(opus|sonnet|haiku)-(\d{8})$/);
43
+ if (m) {
44
+ return { variant: m[1], major: 3, minor: 5, snapshot: Number(m[2]) };
45
+ }
46
+ m = local.match(/^claude-3-(opus|sonnet|haiku)-(\d{8})$/);
47
+ if (m) {
48
+ return { variant: m[1], major: 3, minor: 0, snapshot: Number(m[2]) };
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function anthropicPreferenceScore(modelId) {
54
+ const local = stripPrefix("anthropic", modelId);
55
+ const meta = parseAnthropicLocal(local);
56
+ if (!meta) {
57
+ return 1_000_000 + tieBreakerSnapshot(local);
58
+ }
59
+ // Generation must dominate: a dated Haiku 4.5 snapshot must not beat Sonnet 4.6 (see Anthropic model lineup).
60
+ const generation = meta.major * 10_000 + meta.minor;
61
+ const v = VARIANT_WEIGHT[meta.variant] || 0;
62
+ return generation * 1_000_000_000_000 + v + meta.snapshot;
63
+ }
64
+
65
+ function openaiFamilyScore(local) {
66
+ // Prefer GPT-5.x over 4.x over 3.5; higher minor (5.4 vs 5.2) wins; bare gpt-5 below explicit minors.
67
+ const g5 = local.match(/^gpt-5(?:\.(\d+))?/);
68
+ if (g5) {
69
+ const sub = g5[1] ? Number.parseInt(g5[1], 10) : 0;
70
+ return 500 * 1_000_000_000 + sub * 1_000_000;
71
+ }
72
+ if (local.startsWith("gpt-4o-mini")) return 480 * 1_000_000_000;
73
+ if (local.startsWith("gpt-4o")) return 485 * 1_000_000_000;
74
+ if (local.includes("gpt-4-turbo")) return 470 * 1_000_000_000;
75
+ if (local.startsWith("gpt-4")) return 460 * 1_000_000_000;
76
+ if (local.includes("gpt-3.5")) return 350 * 1_000_000_000;
77
+ if (local.includes("o3") || local.includes("o1")) return 420 * 1_000_000_000;
78
+ return 100 * 1_000_000_000;
79
+ }
80
+
81
+ function openaiPreferenceScore(modelId) {
82
+ const raw = String(modelId).toLowerCase();
83
+ const local = raw.includes("/") ? raw.split("/").slice(1).join("/") : raw;
84
+ let score = openaiFamilyScore(local);
85
+ score += tieBreakerSnapshot(local);
86
+ // Small bump for non-preview stable names
87
+ if (local.includes("preview")) score -= 50_000_000;
88
+ return score;
89
+ }
90
+
91
+ function genericPreferenceScore(provider, modelId) {
92
+ const local = stripPrefix(provider, modelId);
93
+ let score = tieBreakerSnapshot(modelId);
94
+ if (local.includes("latest")) score += 500_000_000;
95
+ if (local.includes("preview") || local.includes("deprecated")) score -= 200_000_000;
96
+ return score;
97
+ }
98
+
99
+ /**
100
+ * Higher = more preferred as default.
101
+ */
102
+ export function modelPreferenceScore(provider, modelId) {
103
+ const id = String(modelId);
104
+ let score = 0;
105
+
106
+ if (KNOWN_PROBLEMATIC_MODEL_IDS.has(id)) {
107
+ score -= 10_000_000_000_000;
108
+ }
109
+
110
+ if (provider === "anthropic") {
111
+ score += anthropicPreferenceScore(id);
112
+ } else if (provider === "openai" || provider === "openai-codex") {
113
+ score += openaiPreferenceScore(id);
114
+ } else {
115
+ score += genericPreferenceScore(provider, id);
116
+ }
117
+
118
+ return score;
119
+ }
120
+
121
+ /**
122
+ * Best-first order for dropdowns and validation.
123
+ */
124
+ export function sortModelsByPreference(provider, modelIds) {
125
+ const items = [...new Set((modelIds || []).filter(Boolean))];
126
+ return items.sort(
127
+ (a, b) => modelPreferenceScore(provider, b) - modelPreferenceScore(provider, a) || String(a).localeCompare(String(b)),
128
+ );
129
+ }
130
+
131
+ export function choosePreferredProviderModel(provider, models = []) {
132
+ const items = Array.isArray(models) ? models.filter(Boolean) : [];
133
+ if (items.length === 0) return "";
134
+ const sorted = sortModelsByPreference(provider, items);
135
+ return sorted[0] || items[0];
136
+ }