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.
@@ -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
- const cmd = `openclaw models list --all --provider ${shellQuote(provider)} --json`;
1218
- const raw = getCommandOutput(cmd);
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
- return items.sort(
220
- (a, b) => modelPreferenceScore(provider, b) - modelPreferenceScore(provider, a) || String(a).localeCompare(String(b)),
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 = []) {
@@ -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 loadWizardLlmCatalog() {
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
- try {
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: 30_000,
1882
+ timeout: 45_000,
1839
1883
  env: NO_COLOR_ENV,
1840
1884
  });
1841
1885
  const parsed = extractJson(raw);
1842
- if (!parsed) throw new Error(`Could not extract JSON from openclaw models list output (first 200 chars): ${stripAnsi(raw).slice(0, 200)}`);
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
- const providers = [...providerMap.keys()]
1860
- .sort((a, b) => a.localeCompare(b))
1861
- .map((id) => {
1862
- const rawModels = providerMap.get(id) || [];
1863
- const sortedIds = sortModelsByPreference(
1864
- id,
1865
- rawModels.map((m) => m.id),
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
- const byId = new Map(rawModels.map((m) => [m.id, m]));
1868
- const models = sortedIds.map((mid) => byId.get(mid)).filter(Boolean);
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
- return { ...fallback, warning: "openclaw_model_catalog_empty" };
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
- respondJson(200, loadWizardLlmCatalog());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "Global TraderClaw CLI (install --wizard, setup, precheck). Installs solana-traderclaw as a dependency for OpenClaw plugin files.",
5
5
  "type": "module",
6
6
  "bin": {