traderclaw-cli 1.0.51 → 1.0.53
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/bin/installer-step-engine.mjs +16 -3
- package/bin/llm-model-preference.mjs +10 -3
- package/bin/openclaw-trader.mjs +110 -22
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, spawn } from "child_process";
|
|
1
|
+
import { execFileSync, execSync, spawn } from "child_process";
|
|
2
2
|
import { randomBytes } from "crypto";
|
|
3
3
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, statSync, writeFileSync } from "fs";
|
|
4
4
|
import { homedir, tmpdir } from "os";
|
|
@@ -1214,8 +1214,21 @@ function persistXProfileIdentities(configPath, modeConfig, identities) {
|
|
|
1214
1214
|
}
|
|
1215
1215
|
|
|
1216
1216
|
function listProviderModels(provider) {
|
|
1217
|
-
|
|
1218
|
-
|
|
1217
|
+
let raw;
|
|
1218
|
+
try {
|
|
1219
|
+
raw = execFileSync(
|
|
1220
|
+
"openclaw",
|
|
1221
|
+
["models", "list", "--all", "--provider", provider, "--json"],
|
|
1222
|
+
{
|
|
1223
|
+
encoding: "utf-8",
|
|
1224
|
+
maxBuffer: 25 * 1024 * 1024,
|
|
1225
|
+
timeout: 20_000,
|
|
1226
|
+
env: NO_COLOR_ENV,
|
|
1227
|
+
},
|
|
1228
|
+
).trim();
|
|
1229
|
+
} catch {
|
|
1230
|
+
return [];
|
|
1231
|
+
}
|
|
1219
1232
|
if (!raw) return [];
|
|
1220
1233
|
const parsed = extractJson(raw);
|
|
1221
1234
|
if (!parsed) return [];
|
|
@@ -211,14 +211,21 @@ export function modelPreferenceScore(provider, modelId) {
|
|
|
211
211
|
return score;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** Cap list size after scoring so huge catalogs stay fast for sort + JSON payloads. */
|
|
215
|
+
export const MAX_MODELS_PER_PROVIDER_SORT = 1500;
|
|
216
|
+
|
|
214
217
|
/**
|
|
215
218
|
* Best-first order for dropdowns and validation.
|
|
219
|
+
* Uses a score cache because sort() may compare the same ids many times.
|
|
216
220
|
*/
|
|
217
221
|
export function sortModelsByPreference(provider, modelIds) {
|
|
218
222
|
const items = [...new Set((modelIds || []).filter(Boolean))];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
const cache = new Map();
|
|
224
|
+
const score = (id) => {
|
|
225
|
+
if (!cache.has(id)) cache.set(id, modelPreferenceScore(provider, id));
|
|
226
|
+
return cache.get(id);
|
|
227
|
+
};
|
|
228
|
+
return items.sort((a, b) => score(b) - score(a) || String(a).localeCompare(String(b)));
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
export function choosePreferredProviderModel(provider, models = []) {
|
package/bin/openclaw-trader.mjs
CHANGED
|
@@ -5,11 +5,17 @@ import { readFileSync, writeFileSync, mkdirSync, appendFileSync, existsSync } fr
|
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { randomUUID, createPrivateKey, sign as cryptoSign } from "crypto";
|
|
8
|
-
import { execSync } from "child_process";
|
|
8
|
+
import { execFile, execSync } from "child_process";
|
|
9
|
+
import { promisify } from "util";
|
|
9
10
|
import { createServer } from "http";
|
|
10
|
-
import { sortModelsByPreference } from "./llm-model-preference.mjs";
|
|
11
|
+
import { sortModelsByPreference, MAX_MODELS_PER_PROVIDER_SORT } from "./llm-model-preference.mjs";
|
|
11
12
|
import { resolvePluginPackageRoot } from "./resolve-plugin-root.mjs";
|
|
12
13
|
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
|
|
16
|
+
/** Parallel per-provider `openclaw models list` — wall time ~max(single), not sum of all providers. */
|
|
17
|
+
const OPENCLAW_MODELS_PER_PROVIDER_TIMEOUT_MS = 16_000;
|
|
18
|
+
|
|
13
19
|
const PLUGIN_ROOT = resolvePluginPackageRoot(import.meta.url);
|
|
14
20
|
const PACKAGE_JSON = JSON.parse(readFileSync(join(PLUGIN_ROOT, "package.json"), "utf-8"));
|
|
15
21
|
const VERSION = PACKAGE_JSON.version;
|
|
@@ -1766,7 +1772,7 @@ function parseJsonBody(req) {
|
|
|
1766
1772
|
});
|
|
1767
1773
|
}
|
|
1768
1774
|
|
|
1769
|
-
function
|
|
1775
|
+
async function loadWizardLlmCatalogAsync() {
|
|
1770
1776
|
const supportedProviders = new Set([
|
|
1771
1777
|
"anthropic",
|
|
1772
1778
|
"openai",
|
|
@@ -1830,24 +1836,62 @@ function loadWizardLlmCatalog() {
|
|
|
1830
1836
|
return { ...fallback, warning: "openclaw_not_found" };
|
|
1831
1837
|
}
|
|
1832
1838
|
|
|
1833
|
-
|
|
1839
|
+
const providerIds = [...supportedProviders].sort((a, b) => a.localeCompare(b));
|
|
1840
|
+
|
|
1841
|
+
async function fetchModelsForProvider(provider) {
|
|
1842
|
+
try {
|
|
1843
|
+
const { stdout } = await execFileAsync(
|
|
1844
|
+
"openclaw",
|
|
1845
|
+
["models", "list", "--all", "--provider", provider, "--json"],
|
|
1846
|
+
{
|
|
1847
|
+
encoding: "utf-8",
|
|
1848
|
+
maxBuffer: 25 * 1024 * 1024,
|
|
1849
|
+
timeout: OPENCLAW_MODELS_PER_PROVIDER_TIMEOUT_MS,
|
|
1850
|
+
env: NO_COLOR_ENV,
|
|
1851
|
+
},
|
|
1852
|
+
);
|
|
1853
|
+
return { provider, stdout };
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
return { provider, error: err };
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function buildProvidersFromMap(providerMap) {
|
|
1860
|
+
return providerIds
|
|
1861
|
+
.map((id) => {
|
|
1862
|
+
const rawModels = providerMap.get(id) || [];
|
|
1863
|
+
const sortedIds = sortModelsByPreference(
|
|
1864
|
+
id,
|
|
1865
|
+
rawModels.map((m) => m.id),
|
|
1866
|
+
);
|
|
1867
|
+
const byId = new Map(rawModels.map((m) => [m.id, m]));
|
|
1868
|
+
const limitedIds = sortedIds.slice(0, MAX_MODELS_PER_PROVIDER_SORT);
|
|
1869
|
+
const models = limitedIds.map((mid) => byId.get(mid)).filter(Boolean);
|
|
1870
|
+
return { id, models };
|
|
1871
|
+
})
|
|
1872
|
+
.filter((entry) => supportedProviders.has(entry.id))
|
|
1873
|
+
.filter((entry) => entry.models.length > 0);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/** When `openclaw models list --all --json` returns models; used if per-provider calls yield nothing. */
|
|
1877
|
+
function mergeFlatCatalogIntoMap(providerMap) {
|
|
1834
1878
|
const raw = execSync("openclaw models list --all --json", {
|
|
1835
1879
|
encoding: "utf-8",
|
|
1836
1880
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1837
1881
|
maxBuffer: 50 * 1024 * 1024,
|
|
1838
|
-
timeout:
|
|
1882
|
+
timeout: 45_000,
|
|
1839
1883
|
env: NO_COLOR_ENV,
|
|
1840
1884
|
});
|
|
1841
1885
|
const parsed = extractJson(raw);
|
|
1842
|
-
if (!parsed)
|
|
1886
|
+
if (!parsed) return;
|
|
1843
1887
|
const models = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
1844
|
-
const providerMap = new Map();
|
|
1845
1888
|
for (const entry of models) {
|
|
1846
1889
|
if (!entry || typeof entry.key !== "string") continue;
|
|
1847
1890
|
const modelId = String(entry.key);
|
|
1848
1891
|
const slash = modelId.indexOf("/");
|
|
1849
1892
|
if (slash <= 0 || slash === modelId.length - 1) continue;
|
|
1850
1893
|
const provider = modelId.slice(0, slash);
|
|
1894
|
+
if (!supportedProviders.has(provider)) continue;
|
|
1851
1895
|
const existing = providerMap.get(provider) || [];
|
|
1852
1896
|
existing.push({
|
|
1853
1897
|
id: modelId,
|
|
@@ -1855,30 +1899,66 @@ function loadWizardLlmCatalog() {
|
|
|
1855
1899
|
});
|
|
1856
1900
|
providerMap.set(provider, existing);
|
|
1857
1901
|
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
try {
|
|
1905
|
+
const t0 = Date.now();
|
|
1906
|
+
const batches = await Promise.all(providerIds.map((p) => fetchModelsForProvider(p)));
|
|
1907
|
+
const providerMap = new Map();
|
|
1908
|
+
|
|
1909
|
+
for (const batch of batches) {
|
|
1910
|
+
if (batch.error || !batch.stdout) continue;
|
|
1911
|
+
const parsed = extractJson(batch.stdout);
|
|
1912
|
+
if (!parsed) continue;
|
|
1913
|
+
const models = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
1914
|
+
const want = batch.provider;
|
|
1915
|
+
for (const entry of models) {
|
|
1916
|
+
if (!entry || typeof entry.key !== "string") continue;
|
|
1917
|
+
const modelId = String(entry.key);
|
|
1918
|
+
const slash = modelId.indexOf("/");
|
|
1919
|
+
if (slash <= 0 || slash === modelId.length - 1) continue;
|
|
1920
|
+
const provider = modelId.slice(0, slash);
|
|
1921
|
+
if (provider !== want) continue;
|
|
1922
|
+
const existing = providerMap.get(provider) || [];
|
|
1923
|
+
existing.push({
|
|
1924
|
+
id: modelId,
|
|
1925
|
+
name: typeof entry.name === "string" && entry.name.trim() ? entry.name : modelId,
|
|
1926
|
+
});
|
|
1927
|
+
providerMap.set(provider, existing);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1858
1930
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1931
|
+
let providers = buildProvidersFromMap(providerMap);
|
|
1932
|
+
let catalogStrategy = "parallel";
|
|
1933
|
+
|
|
1934
|
+
if (providers.length === 0) {
|
|
1935
|
+
catalogStrategy = "legacy_fallback";
|
|
1936
|
+
try {
|
|
1937
|
+
mergeFlatCatalogIntoMap(providerMap);
|
|
1938
|
+
providers = buildProvidersFromMap(providerMap);
|
|
1939
|
+
} catch (legacyErr) {
|
|
1940
|
+
console.error(
|
|
1941
|
+
`[traderclaw] loadWizardLlmCatalog legacy fallback failed: ${legacyErr instanceof Error ? legacyErr.message : String(legacyErr)}`,
|
|
1866
1942
|
);
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
return { id, models };
|
|
1870
|
-
})
|
|
1871
|
-
.filter((entry) => supportedProviders.has(entry.id))
|
|
1872
|
-
.filter((entry) => entry.models.length > 0);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1873
1945
|
|
|
1874
1946
|
if (providers.length === 0) {
|
|
1875
|
-
|
|
1947
|
+
const failedParallel = batches.filter((b) => b.error).length;
|
|
1948
|
+
const hint =
|
|
1949
|
+
failedParallel > 0
|
|
1950
|
+
? ` (${failedParallel}/${batches.length} per-provider openclaw calls failed — check OpenClaw version supports: openclaw models list --all --provider <id> --json)`
|
|
1951
|
+
: "";
|
|
1952
|
+
return { ...fallback, warning: `openclaw_model_catalog_empty${hint}` };
|
|
1876
1953
|
}
|
|
1877
1954
|
|
|
1955
|
+
const elapsedMs = Date.now() - t0;
|
|
1878
1956
|
return {
|
|
1879
1957
|
source: "openclaw",
|
|
1880
1958
|
providers,
|
|
1881
1959
|
generatedAt: new Date().toISOString(),
|
|
1960
|
+
catalogFetchMs: elapsedMs,
|
|
1961
|
+
catalogStrategy,
|
|
1882
1962
|
};
|
|
1883
1963
|
} catch (err) {
|
|
1884
1964
|
const detail = err?.message || String(err);
|
|
@@ -2629,7 +2709,15 @@ async function cmdInstall(args) {
|
|
|
2629
2709
|
}
|
|
2630
2710
|
|
|
2631
2711
|
if (req.method === "GET" && req.url === "/api/llm/options") {
|
|
2632
|
-
|
|
2712
|
+
try {
|
|
2713
|
+
const payload = await loadWizardLlmCatalogAsync();
|
|
2714
|
+
respondJson(200, payload);
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
respondJson(500, {
|
|
2717
|
+
source: "error",
|
|
2718
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2633
2721
|
return;
|
|
2634
2722
|
}
|
|
2635
2723
|
|
package/package.json
CHANGED