@tradejs/infra 1.0.6 → 1.0.9

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.
@@ -36,9 +36,315 @@ __export(userSettings_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(userSettings_exports);
38
38
 
39
+ // src/aiLanguages.ts
40
+ var AI_RESPONSE_LANGUAGE_OPTIONS = [
41
+ { label: "English", value: "en", promptName: "English" },
42
+ { label: "Chinese", value: "zh", promptName: "Chinese" },
43
+ { label: "Hindi", value: "hi", promptName: "Hindi" },
44
+ { label: "Spanish", value: "es", promptName: "Spanish" },
45
+ { label: "French", value: "fr", promptName: "French" },
46
+ { label: "Arabic", value: "ar", promptName: "Arabic" },
47
+ { label: "Bengali", value: "bn", promptName: "Bengali" },
48
+ { label: "Portuguese", value: "pt", promptName: "Portuguese" },
49
+ { label: "Russian", value: "ru", promptName: "Russian" },
50
+ { label: "Urdu", value: "ur", promptName: "Urdu" },
51
+ { label: "Indonesian", value: "id", promptName: "Indonesian" },
52
+ { label: "German", value: "de", promptName: "German" },
53
+ { label: "Japanese", value: "ja", promptName: "Japanese" },
54
+ { label: "Swahili", value: "sw", promptName: "Swahili" },
55
+ { label: "Marathi", value: "mr", promptName: "Marathi" },
56
+ { label: "Telugu", value: "te", promptName: "Telugu" },
57
+ { label: "Turkish", value: "tr", promptName: "Turkish" },
58
+ { label: "Tamil", value: "ta", promptName: "Tamil" },
59
+ { label: "Vietnamese", value: "vi", promptName: "Vietnamese" },
60
+ { label: "Korean", value: "ko", promptName: "Korean" }
61
+ ];
62
+ var KNOWN_AI_RESPONSE_LANGUAGES = new Set(
63
+ AI_RESPONSE_LANGUAGE_OPTIONS.map((option) => option.value)
64
+ );
65
+ var DEFAULT_AI_RESPONSE_LANGUAGE = AI_RESPONSE_LANGUAGE_OPTIONS[0].value;
66
+ var normalizeAiResponseLanguage = (value) => {
67
+ if (typeof value !== "string") {
68
+ return DEFAULT_AI_RESPONSE_LANGUAGE;
69
+ }
70
+ const trimmed = value.trim().toLowerCase();
71
+ return KNOWN_AI_RESPONSE_LANGUAGES.has(trimmed) ? trimmed : DEFAULT_AI_RESPONSE_LANGUAGE;
72
+ };
73
+
74
+ // src/aiEndpoints.ts
75
+ var AI_CUSTOM_ENDPOINT_VALUE = "__custom__";
76
+ var AI_ENDPOINT_OPTIONS = [
77
+ {
78
+ label: "OpenAI",
79
+ value: "https://api.openai.com/v1"
80
+ },
81
+ {
82
+ label: "Claude",
83
+ value: "https://api.anthropic.com/v1"
84
+ },
85
+ {
86
+ label: "OpenRouter",
87
+ value: "https://openrouter.ai/api/v1"
88
+ },
89
+ {
90
+ label: "Gemini",
91
+ value: "https://generativelanguage.googleapis.com/v1beta/openai"
92
+ },
93
+ {
94
+ label: "Together AI",
95
+ value: "https://api.together.xyz/v1"
96
+ },
97
+ {
98
+ label: "Groq",
99
+ value: "https://api.groq.com/openai/v1"
100
+ },
101
+ {
102
+ label: "DeepInfra",
103
+ value: "https://api.deepinfra.com/v1/openai"
104
+ },
105
+ {
106
+ label: "xAI",
107
+ value: "https://api.x.ai/v1"
108
+ },
109
+ {
110
+ label: "Qwen (DashScope Intl)",
111
+ value: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
112
+ },
113
+ {
114
+ label: "Qwen (DashScope CN)",
115
+ value: "https://dashscope.aliyuncs.com/compatible-mode/v1"
116
+ },
117
+ {
118
+ label: "Qwen (DashScope US)",
119
+ value: "https://dashscope-us.aliyuncs.com/compatible-mode/v1"
120
+ },
121
+ {
122
+ label: "Perplexity",
123
+ value: "https://api.perplexity.ai"
124
+ },
125
+ {
126
+ label: "Fireworks",
127
+ value: "https://api.fireworks.ai/inference/v1"
128
+ },
129
+ {
130
+ label: "SambaNova",
131
+ value: "https://api.sambanova.ai/v1"
132
+ },
133
+ {
134
+ label: "Hyperbolic",
135
+ value: "https://api.hyperbolic.xyz/v1"
136
+ },
137
+ {
138
+ label: "Kimi",
139
+ value: "https://api.moonshot.ai/v1"
140
+ },
141
+ {
142
+ label: "ProxyAPI",
143
+ value: "https://openai.api.proxyapi.ru/v1"
144
+ },
145
+ {
146
+ label: "Custom",
147
+ value: AI_CUSTOM_ENDPOINT_VALUE
148
+ }
149
+ ];
150
+ var KNOWN_AI_ENDPOINTS = new Set(
151
+ AI_ENDPOINT_OPTIONS.map((option) => option.value).filter(
152
+ (value) => value !== AI_CUSTOM_ENDPOINT_VALUE
153
+ )
154
+ );
155
+ var normalizeUrl = (value) => value.replace(/\/+$/, "");
156
+ var isIpv4Address = (value) => /^(?:\d{1,3}\.){3}\d{1,3}$/.test(value.trim());
157
+ var parseIpv4Address = (value) => value.trim().split(".").map((part) => Number(part));
158
+ var isPrivateIpv4Address = (value) => {
159
+ if (!isIpv4Address(value)) {
160
+ return false;
161
+ }
162
+ const [a, b, c, d] = parseIpv4Address(value);
163
+ if ([a, b, c, d].some(
164
+ (part) => !Number.isInteger(part) || part < 0 || part > 255
165
+ )) {
166
+ return false;
167
+ }
168
+ return a === 10 || a === 127 || a === 0 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
169
+ };
170
+ var isPrivateHostname = (hostname) => {
171
+ const normalized = hostname.trim().toLowerCase();
172
+ if (!normalized) {
173
+ return true;
174
+ }
175
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || normalized.endsWith(".lan") || normalized === "::1" || normalized === "[::1]" || isPrivateIpv4Address(normalized);
176
+ };
177
+ var isValidAiEndpointUrl = (value) => {
178
+ try {
179
+ const url = new URL(value);
180
+ return url.protocol === "https:" && !isPrivateHostname(url.hostname);
181
+ } catch {
182
+ return false;
183
+ }
184
+ };
185
+ var normalizeAiEndpoint = (value) => {
186
+ if (typeof value !== "string") {
187
+ return "";
188
+ }
189
+ const trimmed = normalizeUrl(value.trim());
190
+ if (!trimmed) {
191
+ return "";
192
+ }
193
+ if (KNOWN_AI_ENDPOINTS.has(trimmed)) {
194
+ return trimmed;
195
+ }
196
+ return isValidAiEndpointUrl(trimmed) ? trimmed : "";
197
+ };
198
+
199
+ // src/aiModels.ts
200
+ var OPENAI_ENDPOINT = "https://api.openai.com/v1";
201
+ var ANTHROPIC_ENDPOINT = "https://api.anthropic.com/v1";
202
+ var OPENROUTER_ENDPOINT = "https://openrouter.ai/api/v1";
203
+ var GEMINI_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/openai";
204
+ var TOGETHER_ENDPOINT = "https://api.together.xyz/v1";
205
+ var GROQ_ENDPOINT = "https://api.groq.com/openai/v1";
206
+ var XAI_ENDPOINT = "https://api.x.ai/v1";
207
+ var QWEN_INTL_ENDPOINT = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
208
+ var QWEN_CN_ENDPOINT = "https://dashscope.aliyuncs.com/compatible-mode/v1";
209
+ var QWEN_US_ENDPOINT = "https://dashscope-us.aliyuncs.com/compatible-mode/v1";
210
+ var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai";
211
+ var KIMI_ENDPOINT = "https://api.moonshot.ai/v1";
212
+ var PROXY_API_ENDPOINT = "https://openai.api.proxyapi.ru/v1";
213
+ var AI_MODEL_OPTIONS_BY_ENDPOINT = {
214
+ [OPENAI_ENDPOINT]: [
215
+ { label: "GPT-5 mini", value: "gpt-5-mini" },
216
+ { label: "GPT-5", value: "gpt-5" },
217
+ { label: "GPT-5.2", value: "gpt-5.2" },
218
+ { label: "GPT-4.1", value: "gpt-4.1" },
219
+ { label: "GPT-4o", value: "gpt-4o" }
220
+ ],
221
+ [ANTHROPIC_ENDPOINT]: [
222
+ { label: "Claude Sonnet 4", value: "claude-sonnet-4-20250514" },
223
+ { label: "Claude Opus 4.1", value: "claude-opus-4-1-20250805" },
224
+ { label: "Claude Opus 4", value: "claude-opus-4-20250514" },
225
+ { label: "Claude 3.7 Sonnet", value: "claude-3-7-sonnet-20250219" },
226
+ { label: "Claude 3.5 Haiku", value: "claude-3-5-haiku-20241022" }
227
+ ],
228
+ [OPENROUTER_ENDPOINT]: [
229
+ { label: "OpenAI GPT-5", value: "openai/gpt-5" },
230
+ { label: "Anthropic Claude Sonnet 4", value: "anthropic/claude-sonnet-4" },
231
+ { label: "Google Gemini 2.5 Pro", value: "google/gemini-2.5-pro" },
232
+ { label: "OpenAI GPT-5 mini", value: "openai/gpt-5-mini" },
233
+ {
234
+ label: "DeepSeek V3.1",
235
+ value: "deepseek/deepseek-chat-v3.1"
236
+ }
237
+ ],
238
+ [GEMINI_ENDPOINT]: [
239
+ { label: "Gemini 2.5 Pro", value: "gemini-2.5-pro" },
240
+ { label: "Gemini 2.5 Flash", value: "gemini-2.5-flash" },
241
+ { label: "Gemini 2.5 Flash-Lite", value: "gemini-2.5-flash-lite" },
242
+ { label: "Gemini 2.0 Flash", value: "gemini-2.0-flash" },
243
+ { label: "Gemini 2.0 Flash-Lite", value: "gemini-2.0-flash-lite" }
244
+ ],
245
+ [TOGETHER_ENDPOINT]: [
246
+ { label: "DeepSeek V3.1", value: "deepseek-ai/DeepSeek-V3.1" },
247
+ {
248
+ label: "Qwen3 Coder 480B",
249
+ value: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8"
250
+ },
251
+ {
252
+ label: "Llama 4 Maverick",
253
+ value: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
254
+ },
255
+ { label: "DeepSeek V3", value: "deepseek-ai/DeepSeek-V3" },
256
+ {
257
+ label: "Llama 3.3 70B Turbo",
258
+ value: "meta-llama/Llama-3.3-70B-Instruct-Turbo"
259
+ }
260
+ ],
261
+ [GROQ_ENDPOINT]: [
262
+ { label: "GPT-OSS 120B", value: "openai/gpt-oss-120b" },
263
+ {
264
+ label: "Llama 4 Scout",
265
+ value: "meta-llama/llama-4-scout-17b-16e-instruct"
266
+ },
267
+ { label: "Qwen3 32B", value: "qwen/qwen3-32b" },
268
+ { label: "Llama 3.3 70B", value: "llama-3.3-70b-versatile" },
269
+ { label: "Llama 3.1 8B Instant", value: "llama-3.1-8b-instant" }
270
+ ],
271
+ [XAI_ENDPOINT]: [
272
+ { label: "Grok 4 Fast Reasoning", value: "grok-4-fast-reasoning" },
273
+ { label: "Grok 4 Fast Non-Reasoning", value: "grok-4-fast-non-reasoning" },
274
+ { label: "Grok Code Fast 1", value: "grok-code-fast-1" },
275
+ { label: "Grok 4", value: "grok-4-0709" },
276
+ { label: "Grok 3 Mini", value: "grok-3-mini" }
277
+ ],
278
+ [QWEN_INTL_ENDPOINT]: [
279
+ { label: "Qwen Plus Latest", value: "qwen-plus-latest" },
280
+ { label: "Qwen Turbo Latest", value: "qwen-turbo-latest" },
281
+ { label: "Qwen Max Latest", value: "qwen-max-latest" },
282
+ { label: "Qwen3 Coder Plus", value: "qwen3-coder-plus" },
283
+ { label: "Qwen3 Coder Next", value: "qwen3-coder-next" }
284
+ ],
285
+ [QWEN_CN_ENDPOINT]: [
286
+ { label: "Qwen Plus Latest", value: "qwen-plus-latest" },
287
+ { label: "Qwen Turbo Latest", value: "qwen-turbo-latest" },
288
+ { label: "Qwen Max Latest", value: "qwen-max-latest" },
289
+ { label: "Qwen3 Coder Plus", value: "qwen3-coder-plus" },
290
+ { label: "Qwen3 Coder Next", value: "qwen3-coder-next" }
291
+ ],
292
+ [QWEN_US_ENDPOINT]: [
293
+ { label: "Qwen Plus Latest", value: "qwen-plus-latest" },
294
+ { label: "Qwen Turbo Latest", value: "qwen-turbo-latest" },
295
+ { label: "Qwen Max Latest", value: "qwen-max-latest" },
296
+ { label: "Qwen3 Coder Plus", value: "qwen3-coder-plus" },
297
+ { label: "Qwen3 Coder Next", value: "qwen3-coder-next" }
298
+ ],
299
+ [PERPLEXITY_ENDPOINT]: [
300
+ { label: "Sonar Pro", value: "sonar-pro" },
301
+ { label: "Sonar", value: "sonar" },
302
+ { label: "Sonar Reasoning Pro", value: "sonar-reasoning-pro" },
303
+ { label: "Sonar Deep Research", value: "sonar-deep-research" }
304
+ ],
305
+ [KIMI_ENDPOINT]: [
306
+ { label: "Kimi K2.5", value: "kimi-k2.5" },
307
+ { label: "Kimi K2 Thinking", value: "kimi-k2-thinking" },
308
+ { label: "Kimi K2 Turbo Preview", value: "kimi-k2-turbo-preview" },
309
+ { label: "Kimi K2 0905 Preview", value: "kimi-k2-0905-preview" },
310
+ { label: "Kimi K2", value: "kimi-k2" }
311
+ ],
312
+ [PROXY_API_ENDPOINT]: [
313
+ {
314
+ label: "Anthropic Claude Sonnet 4",
315
+ value: "anthropic/claude-sonnet-4-20250514"
316
+ },
317
+ { label: "Gemini 2.5 Flash", value: "gemini/gemini-2.5-flash" },
318
+ { label: "OpenAI GPT-5 mini", value: "openai/gpt-5-mini" },
319
+ { label: "OpenAI GPT-4o", value: "openai/gpt-4o" },
320
+ {
321
+ label: "OpenRouter DeepSeek Chat V3.1",
322
+ value: "openrouter/deepseek/deepseek-chat-v3.1"
323
+ }
324
+ ]
325
+ };
326
+ var DEFAULT_AI_MODEL_BY_ENDPOINT = Object.fromEntries(
327
+ Object.entries(AI_MODEL_OPTIONS_BY_ENDPOINT).map(([endpoint, options]) => [
328
+ endpoint,
329
+ options[0]?.value ?? ""
330
+ ])
331
+ );
332
+ var normalizeModel = (value) => value.trim();
333
+ var getDefaultAiModelForEndpoint = (endpoint) => DEFAULT_AI_MODEL_BY_ENDPOINT[endpoint] ?? "";
334
+ var normalizeAiModel = (value, endpoint) => {
335
+ if (typeof value === "string") {
336
+ const trimmed = normalizeModel(value);
337
+ if (trimmed) {
338
+ return trimmed;
339
+ }
340
+ }
341
+ return getDefaultAiModelForEndpoint(endpoint);
342
+ };
343
+
39
344
  // src/redis.ts
40
345
  var import_ioredis = __toESM(require("ioredis"));
41
346
  var TTL_1D = 86400;
347
+ var SCREENSHOT_TOKEN_TTL_SECONDS = 15 * 60;
42
348
  var toJson = (value) => JSON.stringify(value);
43
349
  var logger = {
44
350
  log: (level, message, ...args) => {
@@ -243,14 +549,34 @@ var redisKeys = {
243
549
  testOrders: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:orders`,
244
550
  testConfig: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:config`,
245
551
  testStat: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:stat`,
552
+ testSummaries: (userName) => `users:${userName}:tests:index:summary`,
246
553
  cacheChunk: (userName, chunkId) => `users:${userName}:cache:tests:chunks:${chunkId}`,
247
554
  cacheOrders: (userName, orderLogId) => `users:${userName}:cache:tests:orders:${orderLogId}`,
248
555
  cachePositions: (userName, orderLogId) => `users:${userName}:cache:tests:positions:${orderLogId}`,
249
556
  signal: (symbol, signalId) => `signals:${symbol}:${signalId}`,
250
557
  signalsBySymbol: (symbol) => `signals:${symbol}:`,
251
558
  storeSignal: (symbol, signalId) => `store:signals:${symbol}:${signalId}`,
559
+ runtimeSignals: (userName) => `users:${userName}:runtime:signals:`,
560
+ runtimeSignal: (userName, signalId) => `users:${userName}:runtime:signals:${signalId}`,
561
+ runtimeSignalBuckets: (userName) => `users:${userName}:runtime:signals:days:`,
562
+ runtimeSignalBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signals:days:${dayKey}:${strategyName}`,
563
+ runtimeSignalEvaluations: (userName) => `users:${userName}:runtime:signal-evaluations:`,
564
+ runtimeSignalEvaluation: (userName, evaluationId) => `users:${userName}:runtime:signal-evaluations:${evaluationId}`,
565
+ runtimeSignalEvaluationBuckets: (userName) => `users:${userName}:runtime:signal-evaluations:days:`,
566
+ runtimeSignalEvaluationBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signal-evaluations:days:${dayKey}:${strategyName}`,
567
+ runtimeSignalEvaluationStatsBuckets: (userName) => `users:${userName}:runtime:signal-evaluation-stats:days:`,
568
+ runtimeSignalEvaluationStatsBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signal-evaluation-stats:days:${dayKey}:${strategyName}`,
569
+ runtimeTrades: (userName) => `users:${userName}:runtime:trade-records:`,
570
+ runtimeTrade: (userName, orderId) => `users:${userName}:runtime:trade-records:${orderId}`,
571
+ runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
572
+ runtimeActiveTrade: (userName, symbol) => `users:${userName}:runtime:active-trades:${symbol}`,
573
+ aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
252
574
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
575
+ screenshotSessionToken: (token) => `auth:screenshot:${token}`,
253
576
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
577
+ researchRuns: (userName) => `users:${userName}:research:runs:`,
578
+ researchRun: (userName, runId) => `users:${userName}:research:runs:${runId}`,
579
+ researchLatestRun: (userName, strategyName) => `users:${userName}:research:latest:${strategyName}`,
254
580
  mlSignalsByStrategy: (strategyName) => `ml:${strategyName}:signals:`,
255
581
  mlSignals: () => "ml:",
256
582
  mlSignal: (strategyName, signalId) => `ml:${strategyName}:signals:${signalId}`,
@@ -271,26 +597,38 @@ var getUserRecord = async (userName) => {
271
597
  };
272
598
  var getUserSettings = async (userName) => {
273
599
  const record = await getUserRecord(userName);
600
+ const aiApiEndpoint = normalizeAiEndpoint(
601
+ readUserString(record, "AI_API_ENDPOINT")
602
+ );
274
603
  return {
275
604
  userName,
276
605
  BYBIT_API_KEY: readUserString(record, "BYBIT_API_KEY"),
277
606
  BYBIT_API_SECRET: readUserString(record, "BYBIT_API_SECRET"),
278
- token: readUserString(record, "token"),
279
607
  COINALYZE_API_KEY: readUserString(record, "COINALYZE_API_KEY"),
280
- OPENAI_API_KEY: readUserString(record, "OPENAI_API_KEY"),
281
- OPENAI_API_ENDPOINT: readUserString(record, "OPENAI_API_ENDPOINT"),
608
+ AI_API_KEY: readUserString(record, "AI_API_KEY"),
609
+ AI_API_ENDPOINT: aiApiEndpoint,
610
+ AI_MODEL: normalizeAiModel(
611
+ readUserString(record, "AI_MODEL"),
612
+ aiApiEndpoint
613
+ ),
614
+ AI_RESPONSE_LANGUAGE: normalizeAiResponseLanguage(
615
+ readUserString(record, "AI_RESPONSE_LANGUAGE")
616
+ ) || DEFAULT_AI_RESPONSE_LANGUAGE,
282
617
  TG_BOT_TOKEN: readUserString(record, "TG_BOT_TOKEN"),
283
618
  TG_CHAT_ID: readUserString(record, "TG_CHAT_ID")
284
619
  };
285
620
  };
286
621
  var updateUserRecord = async (userName, patch) => {
287
622
  const existing = await getUserRecord(userName) ?? {};
288
- const next = {
623
+ const merged = {
289
624
  ...existing,
290
625
  ...patch,
291
626
  userName,
292
627
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
293
628
  };
629
+ const next = Object.fromEntries(
630
+ Object.entries(merged).filter(([, value]) => value !== void 0)
631
+ );
294
632
  await setData(redisKeys.user(userName), next, {
295
633
  expire: 0
296
634
  });
@@ -2,7 +2,17 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-EFARW5QE.mjs";
5
+ } from "./chunk-MLVWC2I2.mjs";
6
+ import {
7
+ DEFAULT_AI_RESPONSE_LANGUAGE,
8
+ normalizeAiResponseLanguage
9
+ } from "./chunk-CCC7DX2T.mjs";
10
+ import {
11
+ normalizeAiModel
12
+ } from "./chunk-DTCLZIBM.mjs";
13
+ import {
14
+ normalizeAiEndpoint
15
+ } from "./chunk-XQ3YBULV.mjs";
6
16
 
7
17
  // src/userSettings.ts
8
18
  var readString = (value) => typeof value === "string" ? value.trim() : "";
@@ -16,26 +26,38 @@ var getUserRecord = async (userName) => {
16
26
  };
17
27
  var getUserSettings = async (userName) => {
18
28
  const record = await getUserRecord(userName);
29
+ const aiApiEndpoint = normalizeAiEndpoint(
30
+ readUserString(record, "AI_API_ENDPOINT")
31
+ );
19
32
  return {
20
33
  userName,
21
34
  BYBIT_API_KEY: readUserString(record, "BYBIT_API_KEY"),
22
35
  BYBIT_API_SECRET: readUserString(record, "BYBIT_API_SECRET"),
23
- token: readUserString(record, "token"),
24
36
  COINALYZE_API_KEY: readUserString(record, "COINALYZE_API_KEY"),
25
- OPENAI_API_KEY: readUserString(record, "OPENAI_API_KEY"),
26
- OPENAI_API_ENDPOINT: readUserString(record, "OPENAI_API_ENDPOINT"),
37
+ AI_API_KEY: readUserString(record, "AI_API_KEY"),
38
+ AI_API_ENDPOINT: aiApiEndpoint,
39
+ AI_MODEL: normalizeAiModel(
40
+ readUserString(record, "AI_MODEL"),
41
+ aiApiEndpoint
42
+ ),
43
+ AI_RESPONSE_LANGUAGE: normalizeAiResponseLanguage(
44
+ readUserString(record, "AI_RESPONSE_LANGUAGE")
45
+ ) || DEFAULT_AI_RESPONSE_LANGUAGE,
27
46
  TG_BOT_TOKEN: readUserString(record, "TG_BOT_TOKEN"),
28
47
  TG_CHAT_ID: readUserString(record, "TG_CHAT_ID")
29
48
  };
30
49
  };
31
50
  var updateUserRecord = async (userName, patch) => {
32
51
  const existing = await getUserRecord(userName) ?? {};
33
- const next = {
52
+ const merged = {
34
53
  ...existing,
35
54
  ...patch,
36
55
  userName,
37
56
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
38
57
  };
58
+ const next = Object.fromEntries(
59
+ Object.entries(merged).filter(([, value]) => value !== void 0)
60
+ );
39
61
  await setData(redisKeys.user(userName), next, {
40
62
  expire: 0
41
63
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/infra",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "Server-only infrastructure adapters for the TradeJS open-source framework: Redis, Timescale, ML, logging, and IO.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -29,6 +29,21 @@
29
29
  "import": "./dist/ai.mjs",
30
30
  "require": "./dist/ai.js"
31
31
  },
32
+ "./aiEndpoints": {
33
+ "types": "./dist/aiEndpoints.d.ts",
34
+ "import": "./dist/aiEndpoints.mjs",
35
+ "require": "./dist/aiEndpoints.js"
36
+ },
37
+ "./aiLanguages": {
38
+ "types": "./dist/aiLanguages.d.ts",
39
+ "import": "./dist/aiLanguages.mjs",
40
+ "require": "./dist/aiLanguages.js"
41
+ },
42
+ "./aiModels": {
43
+ "types": "./dist/aiModels.d.ts",
44
+ "import": "./dist/aiModels.mjs",
45
+ "require": "./dist/aiModels.js"
46
+ },
32
47
  "./files": {
33
48
  "types": "./dist/files.d.ts",
34
49
  "import": "./dist/files.mjs",
@@ -67,8 +82,8 @@
67
82
  },
68
83
  "dependencies": {
69
84
  "@grpc/grpc-js": "^1.10.7",
70
- "@grpc/proto-loader": "^0.7.13",
71
- "@tradejs/types": "^1.0.6",
85
+ "@grpc/proto-loader": "^0.8.0",
86
+ "@tradejs/types": "^1.0.9",
72
87
  "chalk": "4.1.2",
73
88
  "ioredis": "5.8.0",
74
89
  "pg": "8.16.3",