@tradejs/node 3.0.0 → 3.1.0

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.
@@ -70,7 +70,9 @@ __export(strategies_exports, {
70
70
  getRegisteredManifests: () => getRegisteredManifests,
71
71
  getRegisteredStrategies: () => getRegisteredStrategies,
72
72
  getStrategyCreator: () => getStrategyCreator,
73
+ getStrategyDefaults: () => getStrategyDefaults,
73
74
  getStrategyManifest: () => getStrategyManifest,
75
+ invokeAiChat: () => invokeAiChat,
74
76
  isKnownStrategy: () => isKnownStrategy,
75
77
  isTrackedHyperliquidPerp: () => isTrackedHyperliquidPerp,
76
78
  isTrackedHyperliquidWhale: () => isTrackedHyperliquidWhale,
@@ -89,11 +91,8 @@ module.exports = __toCommonJS(strategies_exports);
89
91
  __reExport(strategies_exports, require("@tradejs/core/strategies"), module.exports);
90
92
 
91
93
  // src/ai.ts
92
- var import_aiLanguages = require("@tradejs/core/aiLanguages");
93
- var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
94
- var import_aiModels = require("@tradejs/core/aiModels");
94
+ var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
95
95
  var import_redis = require("@tradejs/infra/redis");
96
- var import_userSettings = require("@tradejs/infra/userSettings");
97
96
 
98
97
  // src/aiShared.ts
99
98
  var MAX_AI_SERIES_POINTS = 5;
@@ -1153,6 +1152,11 @@ var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
1153
1152
  const { state } = getStrategyRegistryState(cwd);
1154
1153
  return state.strategyCreators.get(name);
1155
1154
  };
1155
+ var getStrategyDefaults = async (name, cwd = getTradejsProjectCwd()) => {
1156
+ await ensureStrategyPluginsLoaded(cwd);
1157
+ const { state } = getStrategyRegistryState(cwd);
1158
+ return state.strategyEntriesMap.get(name)?.defaults;
1159
+ };
1156
1160
  var getAvailableStrategyNames = async (cwd = getTradejsProjectCwd()) => {
1157
1161
  await ensureStrategyPluginsLoaded(cwd);
1158
1162
  const { state } = getStrategyRegistryState(cwd);
@@ -1335,7 +1339,149 @@ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiP
1335
1339
  }) ?? strategyAnalysis;
1336
1340
  };
1337
1341
 
1342
+ // src/aiProvider.ts
1343
+ var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
1344
+ var import_aiModels = require("@tradejs/core/aiModels");
1345
+ var import_aiLanguages = require("@tradejs/core/aiLanguages");
1346
+ var import_userSettings = require("@tradejs/infra/userSettings");
1347
+ var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
1348
+ var userSettingsCache = /* @__PURE__ */ new Map();
1349
+ var aiModelCache = /* @__PURE__ */ new Map();
1350
+ var normalizeResponseContent = (content) => {
1351
+ if (typeof content === "string") return content;
1352
+ if (content && typeof content === "object" && !Array.isArray(content)) {
1353
+ return content;
1354
+ }
1355
+ if (Array.isArray(content)) {
1356
+ return content.map(
1357
+ (part) => typeof part?.text === "string" ? part.text : ""
1358
+ ).join("\n").trim();
1359
+ }
1360
+ return String(content ?? "");
1361
+ };
1362
+ var getAiInvocationError = (error) => {
1363
+ const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
1364
+ const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
1365
+ details
1366
+ );
1367
+ const wrapped = new Error(
1368
+ isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
1369
+ );
1370
+ wrapped.cause = error;
1371
+ return wrapped;
1372
+ };
1373
+ var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
1374
+ var getAiModelCacheKey = (userName, modelName, temperature) => `${userName}::${modelName}::${temperature}`;
1375
+ var resolveAiModelName = (settings, requestedModelName) => {
1376
+ const explicitModelName = requestedModelName?.trim() ?? "";
1377
+ if (explicitModelName) return explicitModelName;
1378
+ return settings.AI_MODEL?.trim() || DEFAULT_AI_MODEL;
1379
+ };
1380
+ var getOpenRouterModelKwargs = (apiEndpoint) => {
1381
+ const endpoint = String(apiEndpoint ?? "").trim();
1382
+ if (!endpoint) return {};
1383
+ let hostname = "";
1384
+ try {
1385
+ hostname = new URL(endpoint).hostname;
1386
+ } catch {
1387
+ hostname = endpoint;
1388
+ }
1389
+ return hostname.toLowerCase().includes("openrouter") ? { provider: { ignore: ["azure"] } } : {};
1390
+ };
1391
+ var getAiUserSettings = async (userName = "root") => {
1392
+ let settingsPromise = userSettingsCache.get(userName);
1393
+ if (!settingsPromise) {
1394
+ settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
1395
+ const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
1396
+ return {
1397
+ ...settings2,
1398
+ AI_API_ENDPOINT: endpoint,
1399
+ AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
1400
+ AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
1401
+ settings2.AI_RESPONSE_LANGUAGE
1402
+ )
1403
+ };
1404
+ });
1405
+ settingsPromise.catch(() => userSettingsCache.delete(userName));
1406
+ userSettingsCache.set(userName, settingsPromise);
1407
+ }
1408
+ const settings = await settingsPromise;
1409
+ if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
1410
+ throw new Error(`AI settings are incomplete for user ${userName}`);
1411
+ }
1412
+ return settings;
1413
+ };
1414
+ var getAiModel = async (userName = "root", requestedModelName, temperature = 0.2) => {
1415
+ const settings = await getAiUserSettings(userName);
1416
+ const modelName = resolveAiModelName(settings, requestedModelName);
1417
+ const cacheKey = getAiModelCacheKey(userName, modelName, temperature);
1418
+ let modelPromise = aiModelCache.get(cacheKey);
1419
+ if (!modelPromise) {
1420
+ modelPromise = import("@langchain/openai").then(({ ChatOpenAI }) => {
1421
+ const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
1422
+ return new ChatOpenAI({
1423
+ temperature,
1424
+ modelName,
1425
+ apiKey: settings.AI_API_KEY,
1426
+ ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
1427
+ configuration: {
1428
+ baseURL: settings.AI_API_ENDPOINT,
1429
+ defaultHeaders: {
1430
+ "HTTP-Referer": "https://tradejs.dev",
1431
+ "X-Title": "Inv"
1432
+ }
1433
+ }
1434
+ });
1435
+ });
1436
+ modelPromise.catch(() => aiModelCache.delete(cacheKey));
1437
+ aiModelCache.set(cacheKey, modelPromise);
1438
+ }
1439
+ try {
1440
+ return await modelPromise;
1441
+ } catch (error) {
1442
+ aiModelCache.delete(cacheKey);
1443
+ userSettingsCache.delete(userName);
1444
+ throw error;
1445
+ }
1446
+ };
1447
+ var resetAiRuntimeCache = () => {
1448
+ aiModelCache.clear();
1449
+ userSettingsCache.clear();
1450
+ };
1451
+ var invokeAiChatWithUserMessageEncoding = async ({
1452
+ messages,
1453
+ userName = "root",
1454
+ model,
1455
+ temperature = 0.2
1456
+ }, resolveUserMessageEncoding) => {
1457
+ const [{ HumanMessage, SystemMessage }, aiModel] = await Promise.all([
1458
+ import("@langchain/core/messages"),
1459
+ getAiModel(userName, model, temperature)
1460
+ ]);
1461
+ const providerMessages = messages.map(
1462
+ (message) => message.role === "system" ? new SystemMessage(message.content) : new HumanMessage(
1463
+ resolveUserMessageEncoding(message) === "text-block" ? { content: [{ type: "text", text: message.content }] } : message.content
1464
+ )
1465
+ );
1466
+ try {
1467
+ const response = await aiModel.invoke(providerMessages);
1468
+ const content = normalizeResponseContent(response?.content);
1469
+ if (isEmptyResponseContent(content)) {
1470
+ throw new Error("AI provider returned an empty chat completion");
1471
+ }
1472
+ return { content };
1473
+ } catch (error) {
1474
+ throw getAiInvocationError(error);
1475
+ }
1476
+ };
1477
+ var invokeAiPromptChat = (options) => invokeAiChatWithUserMessageEncoding(options, () => "text-block");
1478
+ var invokeCompatibleAiChat = (options) => invokeAiChatWithUserMessageEncoding(
1479
+ options,
1480
+ (message) => message.format ?? "plain"
1481
+ );
1482
+
1338
1483
  // src/ai.ts
1484
+ var invokeAiChat = (options) => invokeCompatibleAiChat(options);
1339
1485
  var parseAIResponse = (input) => {
1340
1486
  try {
1341
1487
  if (typeof input === "object" && input !== null) return input;
@@ -1348,18 +1494,6 @@ var parseAIResponse = (input) => {
1348
1494
  return {};
1349
1495
  }
1350
1496
  };
1351
- var normalizeResponseContent = (content) => {
1352
- if (typeof content === "string" || content && typeof content === "object") {
1353
- if (typeof content !== "object" || !Array.isArray(content)) {
1354
- return content;
1355
- }
1356
- }
1357
- if (Array.isArray(content)) {
1358
- const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
1359
- return text;
1360
- }
1361
- return String(content ?? "");
1362
- };
1363
1497
  var normalizeAnalysis = (raw) => {
1364
1498
  const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
1365
1499
  const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
@@ -1593,120 +1727,6 @@ Trade payload:
1593
1727
  ${JSON.stringify(payload)}
1594
1728
  ${buildAiHumanPromptAddonByStrategy(signal, payload)}
1595
1729
  `;
1596
- var getAiInvocationError = (error) => {
1597
- const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
1598
- const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
1599
- details
1600
- );
1601
- const wrapped = new Error(
1602
- isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
1603
- );
1604
- wrapped.cause = error;
1605
- return wrapped;
1606
- };
1607
- var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
1608
- var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
1609
- var userSettingsCache = /* @__PURE__ */ new Map();
1610
- var aiModelCache = /* @__PURE__ */ new Map();
1611
- var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
1612
- var resolveAiModelName = (settings, requestedModelName) => {
1613
- const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
1614
- if (explicitModelName) {
1615
- return explicitModelName;
1616
- }
1617
- const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
1618
- return settingsModelName || DEFAULT_AI_MODEL;
1619
- };
1620
- var getOpenRouterModelKwargs = (apiEndpoint) => {
1621
- const endpoint = String(apiEndpoint ?? "").trim();
1622
- if (!endpoint) {
1623
- return {};
1624
- }
1625
- let hostname = "";
1626
- try {
1627
- hostname = new URL(endpoint).hostname;
1628
- } catch {
1629
- hostname = endpoint;
1630
- }
1631
- if (!hostname.toLowerCase().includes("openrouter")) {
1632
- return {};
1633
- }
1634
- return {
1635
- provider: {
1636
- ignore: ["azure"]
1637
- }
1638
- };
1639
- };
1640
- var getAiSettings = async (userName = "root") => {
1641
- let settingsPromise = userSettingsCache.get(userName);
1642
- if (!settingsPromise) {
1643
- settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
1644
- const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
1645
- return {
1646
- ...settings2,
1647
- AI_API_ENDPOINT: endpoint,
1648
- AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
1649
- AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
1650
- settings2.AI_RESPONSE_LANGUAGE
1651
- )
1652
- };
1653
- });
1654
- settingsPromise.catch(() => {
1655
- userSettingsCache.delete(userName);
1656
- });
1657
- userSettingsCache.set(userName, settingsPromise);
1658
- }
1659
- const settings = await settingsPromise;
1660
- if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
1661
- throw new Error(`AI settings are incomplete for user ${userName}`);
1662
- }
1663
- return settings;
1664
- };
1665
- var createAiModel = async (userName = "root", requestedModelName) => {
1666
- const settings = await getAiSettings(userName);
1667
- const modelName = resolveAiModelName(settings, requestedModelName);
1668
- const cacheKey = getAiModelCacheKey(userName, modelName);
1669
- let modelPromise = aiModelCache.get(cacheKey);
1670
- if (!modelPromise) {
1671
- modelPromise = (async () => {
1672
- const { ChatOpenAI } = await import("@langchain/openai");
1673
- const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
1674
- return new ChatOpenAI({
1675
- temperature: 0.2,
1676
- modelName,
1677
- apiKey: settings.AI_API_KEY,
1678
- ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
1679
- configuration: {
1680
- baseURL: settings.AI_API_ENDPOINT,
1681
- defaultHeaders: {
1682
- "HTTP-Referer": "https://tradejs.dev",
1683
- "X-Title": "Inv"
1684
- }
1685
- }
1686
- });
1687
- })();
1688
- modelPromise.catch(() => {
1689
- aiModelCache.delete(cacheKey);
1690
- });
1691
- aiModelCache.set(cacheKey, modelPromise);
1692
- }
1693
- return modelPromise;
1694
- };
1695
- var getAiModel = async (userName = "root", requestedModelName) => {
1696
- const settings = await getAiSettings(userName);
1697
- const resolvedModelName = resolveAiModelName(settings, requestedModelName);
1698
- try {
1699
- return await createAiModel(userName, resolvedModelName);
1700
- } catch (error) {
1701
- aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
1702
- userSettingsCache.delete(userName);
1703
- throw error;
1704
- }
1705
- };
1706
- var resetAiRuntimeCache = () => {
1707
- aiModelCache.clear();
1708
- userSettingsCache.clear();
1709
- };
1710
1730
  var buildAiPrompts = (signal) => {
1711
1731
  const payload = buildAiPayload(signal);
1712
1732
  return {
@@ -1715,42 +1735,23 @@ var buildAiPrompts = (signal) => {
1715
1735
  };
1716
1736
  };
1717
1737
  var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
1718
- const [{ HumanMessage, SystemMessage }, model, settings] = await Promise.all([
1719
- import("@langchain/core/messages"),
1720
- getAiModel(options.userName, options.model),
1721
- getAiSettings(options.userName)
1722
- ]);
1723
- const messages = [];
1724
- const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
1725
- settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
1726
- );
1727
- messages.push(new SystemMessage(systemPrompt));
1728
- messages.push(
1729
- new SystemMessage(
1730
- `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
1731
- )
1738
+ const settings = await getAiUserSettings(options.userName);
1739
+ const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
1740
+ settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
1732
1741
  );
1733
- messages.push(
1734
- new HumanMessage({
1735
- content: [
1736
- {
1737
- type: "text",
1738
- text: humanPrompt
1739
- }
1740
- ]
1741
- })
1742
- );
1743
- let response;
1744
- try {
1745
- response = await model.invoke(messages);
1746
- } catch (error) {
1747
- throw getAiInvocationError(error);
1748
- }
1749
- const responseContent = normalizeResponseContent(response?.content);
1750
- if (isEmptyResponseContent(responseContent)) {
1751
- throw new Error("AI provider returned an empty chat completion");
1752
- }
1753
- const parsed = parseAIResponse(responseContent);
1742
+ const response = await invokeAiPromptChat({
1743
+ userName: options.userName,
1744
+ model: options.model,
1745
+ messages: [
1746
+ { role: "system", content: systemPrompt },
1747
+ {
1748
+ role: "system",
1749
+ content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
1750
+ },
1751
+ { role: "user", content: humanPrompt }
1752
+ ]
1753
+ });
1754
+ const parsed = parseAIResponse(response.content);
1754
1755
  const normalized = normalizeAnalysis(parsed);
1755
1756
  if (!options.signal) {
1756
1757
  return normalized;
@@ -6749,7 +6750,9 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
6749
6750
  getRegisteredManifests,
6750
6751
  getRegisteredStrategies,
6751
6752
  getStrategyCreator,
6753
+ getStrategyDefaults,
6752
6754
  getStrategyManifest,
6755
+ invokeAiChat,
6753
6756
  isKnownStrategy,
6754
6757
  isTrackedHyperliquidPerp,
6755
6758
  isTrackedHyperliquidWhale,