@backtest-kit/ollama 0.2.0 → 1.0.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.
package/build/index.cjs CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  var agentSwarmKit = require('agent-swarm-kit');
4
4
  var diScoped = require('di-scoped');
5
+ var path = require('path');
6
+ var module$1 = require('module');
5
7
  var diKit = require('di-kit');
8
+ var functoolsKit = require('functools-kit');
6
9
  var backtestKit = require('backtest-kit');
7
- var path = require('path');
8
10
  var MarkdownIt = require('markdown-it');
9
11
  var sanitizeHtml = require('sanitize-html');
10
12
  var promise = require('markdownlint/promise');
11
13
  var markdownlint = require('markdownlint');
12
- var functoolsKit = require('functools-kit');
13
- var module$1 = require('module');
14
14
  var fs = require('fs/promises');
15
15
  var OpenAI = require('openai');
16
16
  var messages = require('@langchain/core/messages');
@@ -20,8 +20,6 @@ var inference = require('@huggingface/inference');
20
20
  var openai = require('@langchain/openai');
21
21
  var lodashEs = require('lodash-es');
22
22
  var ollama$1 = require('ollama');
23
- var zod$1 = require('openai/helpers/zod');
24
- var zod = require('zod');
25
23
 
26
24
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
27
25
  /**
@@ -37,7 +35,7 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
37
35
  * const completionType = CompletionName.RunnerCompletion;
38
36
  * ```
39
37
  */
40
- var CompletionName;
38
+ exports.CompletionName = void 0;
41
39
  (function (CompletionName) {
42
40
  /** Standard completion mode (full response at once) */
43
41
  CompletionName["RunnerCompletion"] = "runner_completion";
@@ -45,8 +43,7 @@ var CompletionName;
45
43
  CompletionName["RunnerStreamCompletion"] = "runner_stream_completion";
46
44
  /** Outline completion mode (structured JSON with schema validation) */
47
45
  CompletionName["RunnerOutlineCompletion"] = "runner_outline_completion";
48
- })(CompletionName || (CompletionName = {}));
49
- var CompletionName$1 = CompletionName;
46
+ })(exports.CompletionName || (exports.CompletionName = {}));
50
47
 
51
48
  /**
52
49
  * Scoped context service for isolated execution contexts.
@@ -221,14 +218,6 @@ class LoggerService {
221
218
  */
222
219
  const { provide, inject, init, override } = diKit.createActivator("ollama");
223
220
 
224
- /**
225
- * Common service type identifiers.
226
- * Services used across the entire application.
227
- */
228
- const commonServices$1 = {
229
- /** Logger service for application-wide logging */
230
- loggerService: Symbol("loggerService"),
231
- };
232
221
  /**
233
222
  * Base service type identifiers.
234
223
  * Core foundational services.
@@ -236,6 +225,7 @@ const commonServices$1 = {
236
225
  const baseServices$1 = {
237
226
  /** Context service for scoped execution contexts */
238
227
  contextService: Symbol('contextService'),
228
+ loggerService: Symbol("loggerService"),
239
229
  };
240
230
  /**
241
231
  * Private service type identifiers.
@@ -244,8 +234,6 @@ const baseServices$1 = {
244
234
  const privateServices$1 = {
245
235
  /** Runner private service for AI provider operations */
246
236
  runnerPrivateService: Symbol('runnerPrivateService'),
247
- /** Outline private service for structured completions */
248
- outlinePrivateService: Symbol('outlinePrivateService'),
249
237
  };
250
238
  /**
251
239
  * Public service type identifiers.
@@ -254,20 +242,29 @@ const privateServices$1 = {
254
242
  const publicServices$1 = {
255
243
  /** Runner public service for context-managed AI operations */
256
244
  runnerPublicService: Symbol('runnerPublicService'),
257
- /** Outline public service for simplified structured completions */
258
- outlinePublicService: Symbol('outlinePublicService'),
259
245
  };
260
246
  const promptServices$1 = {
261
- signalPromptService: Symbol('signalPromptService'),
247
+ resolvePromptService: Symbol('resolvePromptService'),
248
+ };
249
+ const cacheServices$1 = {
250
+ promptCacheService: Symbol('promptCacheService'),
262
251
  };
263
252
  const markdownServices$1 = {
264
253
  outlineMarkdownService: Symbol('outlineMarkdownService'),
265
254
  };
266
- const optimizerServices$1 = {
255
+ const templateServices$1 = {
267
256
  optimizerTemplateService: Symbol('optimizerTemplateService'),
257
+ };
258
+ const schemaServices$1 = {
268
259
  optimizerSchemaService: Symbol('optimizerSchemaService'),
260
+ };
261
+ const validationServices$1 = {
269
262
  optimizerValidationService: Symbol('optimizerValidationService'),
263
+ };
264
+ const connectionServices$1 = {
270
265
  optimizerConnectionService: Symbol('optimizerConnectionService'),
266
+ };
267
+ const globalServices$1 = {
271
268
  optimizerGlobalService: Symbol('optimizerGlobalService'),
272
269
  };
273
270
  /**
@@ -286,15 +283,66 @@ const optimizerServices$1 = {
286
283
  * ```
287
284
  */
288
285
  const TYPES = {
289
- ...commonServices$1,
290
286
  ...baseServices$1,
291
287
  ...promptServices$1,
288
+ ...cacheServices$1,
292
289
  ...markdownServices$1,
293
- ...optimizerServices$1,
290
+ ...templateServices$1,
291
+ ...schemaServices$1,
292
+ ...validationServices$1,
293
+ ...connectionServices$1,
294
+ ...globalServices$1,
294
295
  ...privateServices$1,
295
296
  ...publicServices$1,
296
297
  };
297
298
 
299
+ const PROMPT_TYPE_SYMBOL = Symbol("prompt-type");
300
+ class Prompt {
301
+ constructor(source) {
302
+ this.source = source;
303
+ this.__type__ = PROMPT_TYPE_SYMBOL;
304
+ }
305
+ }
306
+ Prompt.fromPrompt = (source) => {
307
+ if (!source || typeof source !== "object") {
308
+ throw new Error("Source must be a valid PromptModel object");
309
+ }
310
+ return new Prompt(source);
311
+ };
312
+ Prompt.isPrompt = (value) => {
313
+ return (value !== null &&
314
+ typeof value === "object" &&
315
+ value.__type__ === PROMPT_TYPE_SYMBOL);
316
+ };
317
+
318
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
319
+ const REQUIRE_MODULE_FN = functoolsKit.memoize(([module]) => module, (module) => {
320
+ const modulePath = require$1.resolve(path.join(module.baseDir, module.path));
321
+ return require$1(modulePath);
322
+ });
323
+ class PromptCacheService {
324
+ constructor() {
325
+ this.loggerService = inject(TYPES.loggerService);
326
+ this.readModule = (module) => {
327
+ this.loggerService.log("promptCacheService readModule", {
328
+ path: module.path,
329
+ baseDir: module.baseDir,
330
+ });
331
+ const source = REQUIRE_MODULE_FN(module);
332
+ return Prompt.fromPrompt(source);
333
+ };
334
+ this.clear = (module) => {
335
+ this.loggerService.log("promptCacheService clear", {
336
+ module,
337
+ });
338
+ if (module) {
339
+ REQUIRE_MODULE_FN.clear(module);
340
+ }
341
+ REQUIRE_MODULE_FN.clear();
342
+ };
343
+ }
344
+ }
345
+
298
346
  /**
299
347
  * Warning threshold for message size in kilobytes.
300
348
  * Messages exceeding this size trigger console warnings.
@@ -451,203 +499,6 @@ class OutlineMarkdownService {
451
499
  }
452
500
  }
453
501
 
454
- /**
455
- * Enumeration of supported JSON schema outlines.
456
- *
457
- * Defines unique identifiers for structured output schemas used with
458
- * LLM providers. Outlines enforce JSON schema validation for critical
459
- * data structures like trading signals.
460
- *
461
- * @example
462
- * ```typescript
463
- * import { OutlineName } from '@backtest-kit/ollama';
464
- *
465
- * const outlineName = OutlineName.SignalOutline;
466
- * ```
467
- */
468
- var OutlineName;
469
- (function (OutlineName) {
470
- /** Trading signal JSON schema for position, TP/SL, and risk parameters */
471
- OutlineName["SignalOutline"] = "signal_outline";
472
- })(OutlineName || (OutlineName = {}));
473
- var OutlineName$1 = OutlineName;
474
-
475
- /**
476
- * Lints and auto-fixes markdown content using markdownlint rules.
477
- *
478
- * Validates markdown syntax and applies automatic fixes for common issues
479
- * like inconsistent list markers, trailing spaces, and heading styles.
480
- * Returns the original content if no errors found or fixes cannot be applied.
481
- *
482
- * @param content - Raw markdown content to lint
483
- * @returns Promise resolving to linted markdown content
484
- *
485
- * @example
486
- * ```typescript
487
- * const markdown = "# Title\n\n\n## Subtitle"; // Multiple blank lines
488
- * const linted = await toLintMarkdown(markdown);
489
- * // Returns: "# Title\n\n## Subtitle" (extra blank line removed)
490
- * ```
491
- */
492
- const toLintMarkdown = async (content) => {
493
- if (!content) {
494
- return "";
495
- }
496
- const { content: errors } = await promise.lint({ strings: { content } });
497
- if (!errors.length) {
498
- return content;
499
- }
500
- const value = markdownlint.applyFixes(content, errors);
501
- return value ? value : content;
502
- };
503
-
504
- /**
505
- * Converts markdown content to plain text with Telegram-compatible HTML formatting.
506
- *
507
- * Processes markdown through three stages:
508
- * 1. Lints and fixes markdown using markdownlint
509
- * 2. Renders markdown to HTML using markdown-it
510
- * 3. Sanitizes HTML to Telegram-compatible subset
511
- *
512
- * Supported tags: b, i, a, code, pre, s, u, tg-spoiler, blockquote, br
513
- * Transforms: headings removed, lists to bullets, multiple newlines collapsed
514
- *
515
- * @param content - Raw markdown content
516
- * @returns Promise resolving to sanitized plain text with HTML formatting
517
- *
518
- * @example
519
- * ```typescript
520
- * const markdown = "# Title\n**Bold** and *italic*\n- Item 1\n- Item 2";
521
- * const plain = await toPlainString(markdown);
522
- * // Returns: "Bold and italic\n• Item 1\n• Item 2"
523
- * ```
524
- */
525
- const toPlainString = async (content) => {
526
- if (!content) {
527
- return "";
528
- }
529
- const markdown = await toLintMarkdown(content);
530
- const md = new MarkdownIt({
531
- html: false,
532
- breaks: true,
533
- linkify: true,
534
- typographer: true,
535
- });
536
- let telegramHtml = md.render(markdown);
537
- telegramHtml = sanitizeHtml(telegramHtml, {
538
- allowedTags: [
539
- "b",
540
- "i",
541
- "a",
542
- "code",
543
- "pre",
544
- "s",
545
- "u",
546
- "tg-spoiler",
547
- "blockquote",
548
- "br",
549
- ],
550
- allowedAttributes: {
551
- a: ["href"],
552
- },
553
- transformTags: {
554
- h1: "",
555
- h2: "",
556
- h3: "",
557
- h4: "",
558
- h5: "",
559
- h6: "",
560
- a: "",
561
- strong: "",
562
- em: "",
563
- p: () => "",
564
- ul: () => "",
565
- li: () => "• ",
566
- ol: () => "",
567
- hr: () => "\n",
568
- br: () => "\n",
569
- div: () => "",
570
- },
571
- });
572
- return telegramHtml.replaceAll(/\n[\s\n]*\n/g, "\n").trim();
573
- };
574
-
575
- /**
576
- * Private service for processing structured outline completions.
577
- *
578
- * Handles the core logic for executing outline-based AI completions with schema validation.
579
- * Processes AI responses through the agent-swarm-kit json function to extract and validate
580
- * structured trading signal data.
581
- *
582
- * Key features:
583
- * - JSON schema validation using agent-swarm-kit
584
- * - Trading signal extraction and transformation
585
- * - Type conversion for numeric fields
586
- * - Markdown formatting cleanup for notes
587
- * - Error handling for validation failures
588
- *
589
- * @example
590
- * ```typescript
591
- * const outlinePrivate = inject<OutlinePrivateService>(TYPES.outlinePrivateService);
592
- * const signal = await outlinePrivate.getCompletion([
593
- * { role: "user", content: "Analyze market" }
594
- * ]);
595
- * ```
596
- */
597
- class OutlinePrivateService {
598
- constructor() {
599
- /** Logger service for operation tracking */
600
- this.loggerService = inject(TYPES.loggerService);
601
- /**
602
- * Processes outline completion messages and extracts structured signal data.
603
- *
604
- * Sends messages to the AI provider, validates the response against the signal schema,
605
- * and transforms the data into a structured format. Returns null if the AI decides
606
- * to wait (no position).
607
- *
608
- * @param messages - Array of conversation messages for the AI
609
- * @returns Promise resolving to structured signal data or null if position is "wait"
610
- * @throws Error if validation fails or AI returns an error
611
- *
612
- * @example
613
- * ```typescript
614
- * const signal = await outlinePrivateService.getCompletion([
615
- * { role: "system", content: "Trading analyst role" },
616
- * { role: "user", content: "Market analysis data..." }
617
- * ]);
618
- *
619
- * if (signal) {
620
- * console.log(`Position: ${signal.position}`);
621
- * console.log(`Entry: ${signal.priceOpen}`);
622
- * console.log(`SL: ${signal.priceStopLoss}`);
623
- * console.log(`TP: ${signal.priceTakeProfit}`);
624
- * }
625
- * ```
626
- */
627
- this.getCompletion = async (messages) => {
628
- this.loggerService.log("outlinePrivateService getCompletion", {
629
- messages,
630
- });
631
- const { data, resultId, error } = await agentSwarmKit.json(OutlineName$1.SignalOutline, messages);
632
- if (error) {
633
- throw new Error(error);
634
- }
635
- if (data.position === "wait") {
636
- return null;
637
- }
638
- return {
639
- id: resultId,
640
- position: data.position,
641
- minuteEstimatedTime: +data.minute_estimated_time,
642
- priceStopLoss: +data.price_stop_loss,
643
- priceTakeProfit: +data.price_take_profit,
644
- note: await toPlainString(data.risk_note),
645
- priceOpen: data.price_open ? +data.price_open : undefined,
646
- };
647
- };
648
- }
649
- }
650
-
651
502
  /**
652
503
  * Private service managing AI inference provider registry and execution.
653
504
  *
@@ -768,47 +619,19 @@ class RunnerPrivateService {
768
619
  }
769
620
  }
770
621
 
771
- const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
772
- /**
773
- * Default fallback prompt configuration.
774
- * Used when signal.prompt.cjs file is not found.
775
- */
776
- const DEFAULT_PROMPT = {
777
- user: "",
778
- system: [],
779
- };
780
- /**
781
- * Lazy-loads and caches signal prompt configuration.
782
- * Attempts to load from config/prompt/signal.prompt.cjs, falls back to DEFAULT_PROMPT if not found.
783
- * Uses singleshot pattern to ensure configuration is loaded only once.
784
- * @returns Prompt configuration with system and user prompts
785
- */
786
- const GET_PROMPT_FN = functoolsKit.singleshot(() => {
787
- try {
788
- const modulePath = require$1.resolve(path.join(process.cwd(), `./config/prompt/signal.prompt.cjs`));
789
- console.log(`Using ${modulePath} implementation as signal.prompt.cjs`);
790
- return require$1(modulePath);
791
- }
792
- catch (error) {
793
- console.log(`Using empty fallback for signal.prompt.cjs`, error);
794
- return DEFAULT_PROMPT;
795
- }
796
- });
797
622
  /**
798
623
  * Service for managing signal prompts for AI/LLM integrations.
799
624
  *
800
- * Provides access to system and user prompts configured in signal.prompt.cjs.
625
+ * Provides access to system and user prompts from Prompt.
801
626
  * Supports both static prompt arrays and dynamic prompt functions.
802
627
  *
803
628
  * Key responsibilities:
804
- * - Lazy-loads prompt configuration from config/prompt/signal.prompt.cjs
805
629
  * - Resolves system prompts (static arrays or async functions)
806
630
  * - Provides user prompt strings
807
- * - Falls back to empty prompts if configuration is missing
808
631
  *
809
632
  * Used for AI-powered signal analysis and strategy recommendations.
810
633
  */
811
- class SignalPromptService {
634
+ class ResolvePromptService {
812
635
  constructor() {
813
636
  this.loggerService = inject(TYPES.loggerService);
814
637
  /**
@@ -819,6 +642,7 @@ class SignalPromptService {
819
642
  * - Async/sync function returning string array (executed and awaited)
820
643
  * - Undefined (returns empty array)
821
644
  *
645
+ * @param prompt - Prompt containing the loaded module
822
646
  * @param symbol - Trading symbol (e.g., "BTCUSDT")
823
647
  * @param strategyName - Strategy identifier
824
648
  * @param exchangeName - Exchange identifier
@@ -826,15 +650,15 @@ class SignalPromptService {
826
650
  * @param backtest - Whether running in backtest mode
827
651
  * @returns Promise resolving to array of system prompt strings
828
652
  */
829
- this.getSystemPrompt = async (symbol, strategyName, exchangeName, frameName, backtest) => {
830
- this.loggerService.log("signalPromptService getSystemPrompt", {
653
+ this.getSystemPrompt = async (prompt, symbol, strategyName, exchangeName, frameName, backtest) => {
654
+ this.loggerService.log("resolvePromptService getSystemPrompt", {
831
655
  symbol,
832
656
  strategyName,
833
657
  exchangeName,
834
658
  frameName,
835
659
  backtest,
836
660
  });
837
- const { system } = GET_PROMPT_FN();
661
+ const { system } = prompt.source;
838
662
  if (Array.isArray(system)) {
839
663
  return system;
840
664
  }
@@ -846,6 +670,7 @@ class SignalPromptService {
846
670
  /**
847
671
  * Retrieves user prompt string for AI input.
848
672
  *
673
+ * @param prompt - Prompt containing the loaded module
849
674
  * @param symbol - Trading symbol (e.g., "BTCUSDT")
850
675
  * @param strategyName - Strategy identifier
851
676
  * @param exchangeName - Exchange identifier
@@ -853,15 +678,15 @@ class SignalPromptService {
853
678
  * @param backtest - Whether running in backtest mode
854
679
  * @returns Promise resolving to user prompt string
855
680
  */
856
- this.getUserPrompt = async (symbol, strategyName, exchangeName, frameName, backtest) => {
857
- this.loggerService.log("signalPromptService getUserPrompt", {
681
+ this.getUserPrompt = async (prompt, symbol, strategyName, exchangeName, frameName, backtest) => {
682
+ this.loggerService.log("resolvePromptService getUserPrompt", {
858
683
  symbol,
859
684
  strategyName,
860
685
  exchangeName,
861
686
  frameName,
862
687
  backtest,
863
688
  });
864
- const { user } = GET_PROMPT_FN();
689
+ const { user } = prompt.source;
865
690
  if (typeof user === "string") {
866
691
  return user;
867
692
  }
@@ -873,92 +698,6 @@ class SignalPromptService {
873
698
  }
874
699
  }
875
700
 
876
- /**
877
- * Public-facing service for structured AI outline completions.
878
- *
879
- * Provides a simplified interface for executing structured AI completions with schema validation.
880
- * Handles context creation and isolation for outline-based operations.
881
- * Used for extracting structured data from AI responses (e.g., trading signals).
882
- *
883
- * Key features:
884
- * - Simplified API with automatic context management
885
- * - JSON schema validation for structured outputs
886
- * - Support for multiple AI providers
887
- * - Optional API key parameter with fallback
888
- * - Logging integration
889
- *
890
- * @example
891
- * ```typescript
892
- * import { engine } from "./lib";
893
- * import { InferenceName } from "./enum/InferenceName";
894
- *
895
- * const signal = await engine.outlinePublicService.getCompletion(
896
- * [{ role: "user", content: "Analyze BTC/USDT and decide position" }],
897
- * InferenceName.ClaudeInference,
898
- * "claude-3-5-sonnet-20240620",
899
- * "sk-ant-..."
900
- * );
901
- *
902
- * // Returns structured signal:
903
- * // {
904
- * // position: "long",
905
- * // priceOpen: 50000,
906
- * // priceStopLoss: 48000,
907
- * // priceTakeProfit: 52000,
908
- * // minuteEstimatedTime: 120,
909
- * // note: "Strong bullish momentum..."
910
- * // }
911
- * ```
912
- */
913
- class OutlinePublicService {
914
- constructor() {
915
- /** Logger service for operation tracking */
916
- this.loggerService = inject(TYPES.loggerService);
917
- /** Private service handling outline completion logic */
918
- this.outlinePrivateService = inject(TYPES.outlinePrivateService);
919
- /**
920
- * Executes a structured outline completion with schema validation.
921
- *
922
- * Creates an isolated execution context and processes messages through the AI provider
923
- * to generate a structured response conforming to a predefined schema.
924
- *
925
- * @param messages - Array of conversation messages for the AI
926
- * @param inference - AI provider identifier
927
- * @param model - Model name/identifier
928
- * @param apiKey - Optional API key(s), required for most providers
929
- * @returns Promise resolving to structured signal data or null if position is "wait"
930
- *
931
- * @example
932
- * ```typescript
933
- * const result = await outlinePublicService.getCompletion(
934
- * [
935
- * { role: "system", content: "You are a trading analyst" },
936
- * { role: "user", content: "Analyze current BTC market" }
937
- * ],
938
- * InferenceName.DeepseekInference,
939
- * "deepseek-chat",
940
- * "sk-..."
941
- * );
942
- * ```
943
- */
944
- this.getCompletion = async (messages, inference, model, apiKey) => {
945
- this.loggerService.log("outlinePublicService getCompletion", {
946
- messages,
947
- model,
948
- apiKey,
949
- inference,
950
- });
951
- return await ContextService.runInContext(async () => {
952
- return await this.outlinePrivateService.getCompletion(messages);
953
- }, {
954
- apiKey: apiKey,
955
- inference,
956
- model,
957
- });
958
- };
959
- }
960
- }
961
-
962
701
  /**
963
702
  * Public-facing service for AI inference operations with context management.
964
703
  *
@@ -1096,6 +835,106 @@ class RunnerPublicService {
1096
835
  }
1097
836
  }
1098
837
 
838
+ /**
839
+ * Lints and auto-fixes markdown content using markdownlint rules.
840
+ *
841
+ * Validates markdown syntax and applies automatic fixes for common issues
842
+ * like inconsistent list markers, trailing spaces, and heading styles.
843
+ * Returns the original content if no errors found or fixes cannot be applied.
844
+ *
845
+ * @param content - Raw markdown content to lint
846
+ * @returns Promise resolving to linted markdown content
847
+ *
848
+ * @example
849
+ * ```typescript
850
+ * const markdown = "# Title\n\n\n## Subtitle"; // Multiple blank lines
851
+ * const linted = await toLintMarkdown(markdown);
852
+ * // Returns: "# Title\n\n## Subtitle" (extra blank line removed)
853
+ * ```
854
+ */
855
+ const toLintMarkdown = async (content) => {
856
+ if (!content) {
857
+ return "";
858
+ }
859
+ const { content: errors } = await promise.lint({ strings: { content } });
860
+ if (!errors.length) {
861
+ return content;
862
+ }
863
+ const value = markdownlint.applyFixes(content, errors);
864
+ return value ? value : content;
865
+ };
866
+
867
+ /**
868
+ * Converts markdown content to plain text with Telegram-compatible HTML formatting.
869
+ *
870
+ * Processes markdown through three stages:
871
+ * 1. Lints and fixes markdown using markdownlint
872
+ * 2. Renders markdown to HTML using markdown-it
873
+ * 3. Sanitizes HTML to Telegram-compatible subset
874
+ *
875
+ * Supported tags: b, i, a, code, pre, s, u, tg-spoiler, blockquote, br
876
+ * Transforms: headings removed, lists to bullets, multiple newlines collapsed
877
+ *
878
+ * @param content - Raw markdown content
879
+ * @returns Promise resolving to sanitized plain text with HTML formatting
880
+ *
881
+ * @example
882
+ * ```typescript
883
+ * const markdown = "# Title\n**Bold** and *italic*\n- Item 1\n- Item 2";
884
+ * const plain = await toPlainString(markdown);
885
+ * // Returns: "Bold and italic\n• Item 1\n• Item 2"
886
+ * ```
887
+ */
888
+ const toPlainString = async (content) => {
889
+ if (!content) {
890
+ return "";
891
+ }
892
+ const markdown = await toLintMarkdown(content);
893
+ const md = new MarkdownIt({
894
+ html: false,
895
+ breaks: true,
896
+ linkify: true,
897
+ typographer: true,
898
+ });
899
+ let telegramHtml = md.render(markdown);
900
+ telegramHtml = sanitizeHtml(telegramHtml, {
901
+ allowedTags: [
902
+ "b",
903
+ "i",
904
+ "a",
905
+ "code",
906
+ "pre",
907
+ "s",
908
+ "u",
909
+ "tg-spoiler",
910
+ "blockquote",
911
+ "br",
912
+ ],
913
+ allowedAttributes: {
914
+ a: ["href"],
915
+ },
916
+ transformTags: {
917
+ h1: "",
918
+ h2: "",
919
+ h3: "",
920
+ h4: "",
921
+ h5: "",
922
+ h6: "",
923
+ a: "",
924
+ strong: "",
925
+ em: "",
926
+ p: () => "",
927
+ ul: () => "",
928
+ li: () => "• ",
929
+ ol: () => "",
930
+ hr: () => "\n",
931
+ br: () => "\n",
932
+ div: () => "",
933
+ },
934
+ });
935
+ return telegramHtml.replaceAll(/\n[\s\n]*\n/g, "\n").trim();
936
+ };
937
+
1099
938
  /**
1100
939
  * Default template service for generating optimizer code snippets.
1101
940
  * Implements all IOptimizerTemplate methods with Ollama LLM integration.
@@ -2470,46 +2309,62 @@ class OptimizerGlobalService {
2470
2309
  * This file is imported by lib/index.ts to ensure services are registered
2471
2310
  * before the DI container is initialized.
2472
2311
  */
2473
- /**
2474
- * Register common services.
2475
- */
2476
- {
2477
- provide(TYPES.loggerService, () => new LoggerService());
2478
- }
2479
2312
  /**
2480
2313
  * Register base services.
2481
2314
  */
2482
2315
  {
2483
2316
  provide(TYPES.contextService, () => new ContextService());
2317
+ provide(TYPES.loggerService, () => new LoggerService());
2484
2318
  }
2485
2319
  /**
2486
2320
  * Register private services.
2487
2321
  */
2488
2322
  {
2489
2323
  provide(TYPES.runnerPrivateService, () => new RunnerPrivateService());
2490
- provide(TYPES.outlinePrivateService, () => new OutlinePrivateService());
2491
2324
  }
2492
2325
  /**
2493
2326
  * Register public services.
2494
2327
  */
2495
2328
  {
2496
2329
  provide(TYPES.runnerPublicService, () => new RunnerPublicService());
2497
- provide(TYPES.outlinePublicService, () => new OutlinePublicService());
2498
2330
  }
2499
2331
  {
2500
- provide(TYPES.signalPromptService, () => new SignalPromptService());
2332
+ provide(TYPES.resolvePromptService, () => new ResolvePromptService());
2333
+ }
2334
+ {
2335
+ provide(TYPES.promptCacheService, () => new PromptCacheService());
2501
2336
  }
2502
2337
  {
2503
2338
  provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
2504
2339
  }
2505
2340
  /**
2506
- * Register optimizer services.
2341
+ * Register template services.
2507
2342
  */
2508
2343
  {
2509
2344
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
2345
+ }
2346
+ /**
2347
+ * Register schema services.
2348
+ */
2349
+ {
2510
2350
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
2351
+ }
2352
+ /**
2353
+ * Register validation services.
2354
+ */
2355
+ {
2511
2356
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
2357
+ }
2358
+ /**
2359
+ * Register connection services.
2360
+ */
2361
+ {
2512
2362
  provide(TYPES.optimizerConnectionService, () => new OptimizerConnectionService());
2363
+ }
2364
+ /**
2365
+ * Register global services.
2366
+ */
2367
+ {
2513
2368
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
2514
2369
  }
2515
2370
 
@@ -6486,43 +6341,47 @@ class GLM4Provider {
6486
6341
  * );
6487
6342
  * ```
6488
6343
  */
6489
- /**
6490
- * Common service instances.
6491
- */
6492
- const commonServices = {
6493
- loggerService: inject(TYPES.loggerService),
6494
- };
6495
6344
  /**
6496
6345
  * Base service instances.
6497
6346
  */
6498
6347
  const baseServices = {
6499
6348
  contextService: inject(TYPES.contextService),
6349
+ loggerService: inject(TYPES.loggerService),
6500
6350
  };
6501
6351
  /**
6502
6352
  * Private service instances.
6503
6353
  */
6504
6354
  const privateServices = {
6505
6355
  runnerPrivateService: inject(TYPES.runnerPrivateService),
6506
- outlinePrivateService: inject(TYPES.outlinePrivateService),
6507
6356
  };
6508
6357
  /**
6509
6358
  * Public service instances.
6510
6359
  */
6511
6360
  const publicServices = {
6512
6361
  runnerPublicService: inject(TYPES.runnerPublicService),
6513
- outlinePublicService: inject(TYPES.outlinePublicService),
6514
6362
  };
6515
6363
  const promptServices = {
6516
- signalPromptService: inject(TYPES.signalPromptService),
6364
+ resolvePromptService: inject(TYPES.resolvePromptService),
6365
+ };
6366
+ const cacheServices = {
6367
+ promptCacheService: inject(TYPES.promptCacheService),
6517
6368
  };
6518
6369
  const markdownServices = {
6519
6370
  outlineMarkdownService: inject(TYPES.outlineMarkdownService),
6520
6371
  };
6521
- const optimizerServices = {
6372
+ const templateServices = {
6522
6373
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
6374
+ };
6375
+ const schemaServices = {
6523
6376
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
6377
+ };
6378
+ const validationServices = {
6524
6379
  optimizerValidationService: inject(TYPES.optimizerValidationService),
6380
+ };
6381
+ const connectionServices = {
6525
6382
  optimizerConnectionService: inject(TYPES.optimizerConnectionService),
6383
+ };
6384
+ const globalServices = {
6526
6385
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
6527
6386
  };
6528
6387
  /**
@@ -6530,13 +6389,17 @@ const optimizerServices = {
6530
6389
  * Provides unified access to the entire service layer.
6531
6390
  */
6532
6391
  const engine = {
6533
- ...commonServices,
6534
6392
  ...baseServices,
6535
6393
  ...privateServices,
6536
6394
  ...publicServices,
6537
6395
  ...promptServices,
6396
+ ...cacheServices,
6538
6397
  ...markdownServices,
6539
- ...optimizerServices,
6398
+ ...templateServices,
6399
+ ...schemaServices,
6400
+ ...validationServices,
6401
+ ...connectionServices,
6402
+ ...globalServices,
6540
6403
  };
6541
6404
  // Initialize DI container
6542
6405
  init();
@@ -6593,11 +6456,11 @@ const LOCAL_RUNNER_FN$2 = functoolsKit.timeout(async (params) => {
6593
6456
  * ```
6594
6457
  */
6595
6458
  agentSwarmKit.addCompletion({
6596
- completionName: CompletionName.RunnerOutlineCompletion,
6459
+ completionName: exports.CompletionName.RunnerOutlineCompletion,
6597
6460
  getCompletion: async (params) => {
6598
6461
  const result = await LOCAL_RUNNER_FN$2(params);
6599
6462
  if (typeof result === "symbol") {
6600
- throw new Error(`${CompletionName.RunnerOutlineCompletion} inference timeout`);
6463
+ throw new Error(`${exports.CompletionName.RunnerOutlineCompletion} inference timeout`);
6601
6464
  }
6602
6465
  return result;
6603
6466
  },
@@ -6636,11 +6499,11 @@ const LOCAL_RUNNER_FN$1 = functoolsKit.timeout(async (params) => {
6636
6499
  * ```
6637
6500
  */
6638
6501
  agentSwarmKit.addCompletion({
6639
- completionName: CompletionName.RunnerStreamCompletion,
6502
+ completionName: exports.CompletionName.RunnerStreamCompletion,
6640
6503
  getCompletion: async (params) => {
6641
6504
  const result = await LOCAL_RUNNER_FN$1(params);
6642
6505
  if (typeof result === "symbol") {
6643
- throw new Error(`${CompletionName.RunnerCompletion} inference timeout`);
6506
+ throw new Error(`${exports.CompletionName.RunnerCompletion} inference timeout`);
6644
6507
  }
6645
6508
  return result;
6646
6509
  },
@@ -6677,457 +6540,367 @@ const LOCAL_RUNNER_FN = functoolsKit.timeout(async (params) => {
6677
6540
  * ```
6678
6541
  */
6679
6542
  agentSwarmKit.addCompletion({
6680
- completionName: CompletionName.RunnerCompletion,
6543
+ completionName: exports.CompletionName.RunnerCompletion,
6681
6544
  getCompletion: async (params) => {
6682
6545
  const result = await LOCAL_RUNNER_FN(params);
6683
6546
  if (typeof result === "symbol") {
6684
- throw new Error(`${CompletionName.RunnerCompletion} inference timeout`);
6547
+ throw new Error(`${exports.CompletionName.RunnerCompletion} inference timeout`);
6685
6548
  }
6686
6549
  return result;
6687
6550
  },
6688
6551
  });
6689
6552
 
6690
6553
  /**
6691
- * Zod schema for trading signal structured output.
6692
- *
6693
- * Defines the JSON schema used for LLM-generated trading signals with
6694
- * comprehensive field descriptions and validation rules. Used with outline
6695
- * completion to enforce structured output from language models.
6696
- *
6697
- * Fields:
6698
- * - position: Trading direction (long/short/wait)
6699
- * - price_open: Entry price in USD
6700
- * - price_stop_loss: Stop-loss price in USD
6701
- * - price_take_profit: Take-profit price in USD
6702
- * - minute_estimated_time: Estimated hold duration in minutes
6703
- * - risk_note: Detailed risk assessment with specific metrics
6704
- *
6705
- * @example
6706
- * ```typescript
6707
- * import { SignalSchema } from './schema/Signal.schema';
6708
- *
6709
- * const signal = SignalSchema.parse({
6710
- * position: 'long',
6711
- * price_open: 50000,
6712
- * price_stop_loss: 49000,
6713
- * price_take_profit: 52000,
6714
- * minute_estimated_time: 120,
6715
- * risk_note: 'RSI oversold at 32%, volume spike +45%'
6716
- * });
6717
- * ```
6718
- */
6719
- const SignalSchema = zod.z.object({
6720
- position: zod.z
6721
- .enum(["long", "short", "wait"])
6722
- .describe(functoolsKit.str.newline("Position direction (ALWAYS required):", "long: market shows consistent bullish signals, uptrend or growth potential", "short: market shows consistent bearish signals, downtrend or decline potential", "wait: conflicting signals between timeframes OR unfavorable trading conditions")),
6723
- price_open: zod.z
6724
- .number()
6725
- .describe(functoolsKit.str.newline("Position entry price in USD", "Can be either:", "- Current market price for immediate entry (market order)", "- Price above/below market to open a limit order and wait for its resolve before entering")),
6726
- price_stop_loss: zod.z
6727
- .number()
6728
- .describe(functoolsKit.str.newline("Stop-loss price in USD", "For LONG: price below price_open (protection against decline)", "For SHORT: price above price_open (protection against rise)", "NEVER set SL in 'empty space' without technical justification")),
6729
- price_take_profit: zod.z
6730
- .number()
6731
- .describe(functoolsKit.str.newline("Take-profit price in USD", "For LONG: price above price_open (growth target)", "For SHORT: price below price_open (decline target)", "NEVER set TP based on trend without technical justification")),
6732
- minute_estimated_time: zod.z
6733
- .number()
6734
- .describe(functoolsKit.str.newline("Estimated time to reach Take Profit in minutes", "Calculated based on HONEST technical analysis, using:", "ATR, ADX, MACD, Momentum, Slope and other metrics")),
6735
- risk_note: zod.z
6736
- .string()
6737
- .describe(functoolsKit.str.newline("Description of current market situation risks:", "", "Analyze and specify applicable risks:", "1. Whale manipulations (volume spikes, long shadows, pin bars, candle engulfing, false breakouts)", "2. Order book (order book walls, spoofing, bid/ask imbalance, low liquidity)", "3. P&L history (recurring mistakes on similar patterns)", "4. Time factors (trading session, low liquidity, upcoming events)", "5. Correlations (overall market trend, conflicting trends across timeframes)", "6. Technical risks (indicator divergences, weak volumes, critical levels)", "7. Gaps and anomalies (price gaps, unfilled gaps, movements without volume)", "", "Provide SPECIFIC numbers, percentages and probabilities.")),
6738
- });
6739
-
6740
- /**
6741
- * Trading signal outline schema registration.
6742
- *
6743
- * Registers a structured outline for trading signal generation with comprehensive
6744
- * validation rules. This outline enforces a strict schema for AI-generated trading
6745
- * signals, ensuring all required fields are present and correctly formatted.
6746
- *
6747
- * Schema fields:
6748
- * - position: Trading direction ("long", "short", or "wait")
6749
- * - price_open: Entry price for the position
6750
- * - price_stop_loss: Stop-loss price level
6751
- * - price_take_profit: Take-profit price level
6752
- * - minute_estimated_time: Estimated time to reach TP (in minutes)
6753
- * - risk_note: Risk assessment and reasoning (markdown format)
6754
- *
6755
- * Validation rules:
6756
- * 1. All required fields must be present
6757
- * 2. Prices must be positive numbers
6758
- * 3. For LONG: SL < entry < TP
6759
- * 4. For SHORT: TP < entry < SL
6760
- * 5. Estimated time must be <= 360 minutes (6 hours)
6761
- * 6. Wait position skips price validations
6762
- *
6763
- * @example
6764
- * ```typescript
6765
- * import { json } from "agent-swarm-kit";
6766
- * import { OutlineName } from "./enum/OutlineName";
6767
- *
6768
- * const { data } = await json(OutlineName.SignalOutline, [
6769
- * { role: "user", content: "Analyze BTC/USDT and decide position" }
6770
- * ]);
6771
- *
6772
- * if (data.position !== "wait") {
6773
- * console.log(`Position: ${data.position}`);
6774
- * console.log(`Entry: ${data.price_open}`);
6775
- * console.log(`SL: ${data.price_stop_loss}`);
6776
- * console.log(`TP: ${data.price_take_profit}`);
6777
- * }
6778
- * ```
6779
- */
6780
- agentSwarmKit.addOutline({
6781
- outlineName: OutlineName.SignalOutline,
6782
- completion: CompletionName.RunnerOutlineCompletion,
6783
- format: zod$1.zodResponseFormat(SignalSchema, "position_open_decision"),
6784
- getOutlineHistory: async ({ history, param: messages = [] }) => {
6785
- await history.push(messages);
6786
- },
6787
- validations: [
6788
- {
6789
- validate: ({ data }) => {
6790
- if (!data.position) {
6791
- throw new Error("The position field is not filled");
6792
- }
6793
- },
6794
- docDescription: "Validates that position direction (long/short/wait) is specified.",
6795
- },
6796
- {
6797
- validate: ({ data }) => {
6798
- if (!data.risk_note) {
6799
- throw new Error("The risk_note field is not filled");
6800
- }
6801
- },
6802
- docDescription: "Validates that risk description is provided.",
6803
- },
6804
- {
6805
- validate: ({ data }) => {
6806
- if (data.position !== "wait" &&
6807
- (!data.price_open || data.price_open <= 0)) {
6808
- throw new Error("When position='long' or 'short', the price_open field is required and must be positive");
6809
- }
6810
- },
6811
- docDescription: "Validates that opening price is specified and positive when opening a position.",
6812
- },
6813
- {
6814
- validate: ({ data }) => {
6815
- if (data.position !== "wait" &&
6816
- (!data.price_stop_loss || data.price_stop_loss <= 0)) {
6817
- throw new Error("When position='long' or 'short', the price_stop_loss field is required and must be positive");
6818
- }
6819
- },
6820
- docDescription: "Validates that stop-loss is specified when opening a position.",
6821
- },
6822
- {
6823
- validate: ({ data }) => {
6824
- if (data.position !== "wait" &&
6825
- (!data.price_take_profit || data.price_take_profit <= 0)) {
6826
- throw new Error("When position='long' or 'short', the price_take_profit field is required and must be positive");
6827
- }
6828
- },
6829
- docDescription: "Validates that take-profit is specified when opening a position.",
6830
- },
6831
- {
6832
- validate: ({ data }) => {
6833
- if (data.position === "long" && data.price_open && data.price_stop_loss && data.price_take_profit) {
6834
- if (data.price_stop_loss >= data.price_open) {
6835
- throw new Error("For LONG position, price_stop_loss must be below price_open");
6836
- }
6837
- if (data.price_take_profit <= data.price_open) {
6838
- throw new Error("For LONG position, price_take_profit must be above price_open");
6839
- }
6840
- }
6841
- },
6842
- docDescription: "Validates price correctness for LONG position.",
6843
- },
6844
- {
6845
- validate: ({ data }) => {
6846
- if (data.position === "short" && data.price_open && data.price_stop_loss && data.price_take_profit) {
6847
- if (data.price_stop_loss <= data.price_open) {
6848
- throw new Error("For SHORT position, price_stop_loss must be above price_open");
6849
- }
6850
- if (data.price_take_profit >= data.price_open) {
6851
- throw new Error("For SHORT position, price_take_profit must be below price_open");
6852
- }
6853
- }
6854
- },
6855
- docDescription: "Validates price correctness for SHORT position.",
6856
- },
6857
- {
6858
- validate: ({ data }) => {
6859
- if (data.position !== "wait" &&
6860
- (!data.minute_estimated_time || data.minute_estimated_time <= 0)) {
6861
- throw new Error("When position='long' or 'short', the minute_estimated_time field is required and must be positive");
6862
- }
6863
- },
6864
- docDescription: "Validates that estimated time to TP is specified when opening a position.",
6865
- },
6866
- {
6867
- validate: ({ data }) => {
6868
- if (data.position !== "wait" && data.minute_estimated_time > 360) {
6869
- throw new Error("Estimated time to reach TP exceeds 6 hours (360 minutes). Use position='wait' for low volatility conditions");
6870
- }
6871
- },
6872
- docDescription: "Validates that estimated time to reach TP does not exceed 6 hours.",
6873
- },
6874
- ],
6875
- });
6876
-
6877
- /**
6878
- * Bootstrap module for agent-swarm-kit validation.
6879
- *
6880
- * Validates that all completion and outline names are properly registered
6881
- * with agent-swarm-kit before the application starts. This ensures that
6882
- * all referenced completions and outlines exist and are correctly configured.
6554
+ * Wrap async function with Ollama inference context.
6883
6555
  *
6884
- * Validation checks:
6885
- * - All CompletionName enum values have corresponding registered handlers
6886
- * - All OutlineName enum values have corresponding registered schemas
6887
- * - No duplicate registrations exist
6556
+ * Creates a higher-order function that executes the provided async function
6557
+ * within an Ollama inference context. Supports token rotation by passing multiple API keys.
6888
6558
  *
6889
- * This file is imported by index.ts to run validation at startup.
6890
- *
6891
- * @throws Error if validation fails (missing or duplicate registrations)
6892
- */
6893
- agentSwarmKit.validate({
6894
- CompletionName: CompletionName$1,
6895
- OutlineName: OutlineName$1,
6896
- });
6897
-
6898
- /**
6899
- * Generate structured trading signal from Ollama models.
6900
- *
6901
- * Supports token rotation by passing multiple API keys. Automatically enforces
6902
- * the signal JSON schema defined in Signal.schema.ts.
6903
- *
6904
- * @param messages - Array of outline messages (user/assistant/system)
6559
+ * @template T - Async function type
6560
+ * @param fn - Async function to wrap
6905
6561
  * @param model - Ollama model name (e.g., "llama3.3:70b")
6906
6562
  * @param apiKey - Single API key or array of keys for rotation
6907
- * @returns Promise resolving to structured trading signal
6563
+ * @returns Wrapped function with same signature as input
6908
6564
  *
6909
6565
  * @example
6910
6566
  * ```typescript
6911
6567
  * import { ollama } from '@backtest-kit/ollama';
6912
6568
  *
6913
- * const signal = await ollama(messages, 'llama3.3:70b', ['key1', 'key2']);
6914
- * console.log(signal.position); // "long" | "short" | "wait"
6569
+ * const wrappedFn = ollama(myAsyncFn, 'llama3.3:70b', ['key1', 'key2']);
6570
+ * const result = await wrappedFn(args);
6915
6571
  * ```
6916
6572
  */
6917
- const ollama = async (messages, model, apiKey) => {
6918
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.OllamaInference, model, apiKey);
6573
+ const ollama = (fn, model, apiKey) => {
6574
+ const wrappedFn = async (args) => {
6575
+ return await ContextService.runInContext(async () => {
6576
+ return await fn(...args);
6577
+ }, {
6578
+ apiKey,
6579
+ inference: InferenceName$1.OllamaInference,
6580
+ model,
6581
+ });
6582
+ };
6583
+ return wrappedFn;
6919
6584
  };
6920
6585
  /**
6921
- * Generate structured trading signal from Grok models.
6586
+ * Wrap async function with Grok inference context.
6922
6587
  *
6923
- * Uses xAI Grok models through direct API access. Does NOT support token rotation.
6588
+ * Creates a higher-order function that executes the provided async function
6589
+ * within a Grok (xAI) inference context.
6924
6590
  *
6925
- * @param messages - Array of outline messages (user/assistant/system)
6591
+ * @template T - Async function type
6592
+ * @param fn - Async function to wrap
6926
6593
  * @param model - Grok model name (e.g., "grok-beta")
6927
- * @param apiKey - Single API key (token rotation not supported)
6928
- * @returns Promise resolving to structured trading signal
6929
- * @throws Error if apiKey is an array (token rotation not supported)
6594
+ * @param apiKey - Single API key or array of keys
6595
+ * @returns Wrapped function with same signature as input
6930
6596
  *
6931
6597
  * @example
6932
6598
  * ```typescript
6933
6599
  * import { grok } from '@backtest-kit/ollama';
6934
6600
  *
6935
- * const signal = await grok(messages, 'grok-beta', process.env.GROK_API_KEY);
6601
+ * const wrappedFn = grok(myAsyncFn, 'grok-beta', process.env.GROK_API_KEY);
6602
+ * const result = await wrappedFn(args);
6936
6603
  * ```
6937
6604
  */
6938
- const grok = async (messages, model, apiKey) => {
6939
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GrokInference, model, apiKey);
6605
+ const grok = (fn, model, apiKey) => {
6606
+ const wrappedFn = async (args) => {
6607
+ return await ContextService.runInContext(async () => {
6608
+ return await fn(...args);
6609
+ }, {
6610
+ apiKey,
6611
+ inference: InferenceName$1.GrokInference,
6612
+ model,
6613
+ });
6614
+ };
6615
+ return wrappedFn;
6940
6616
  };
6941
6617
  /**
6942
- * Generate structured trading signal from Hugging Face models.
6618
+ * Wrap async function with HuggingFace inference context.
6943
6619
  *
6944
- * Uses HuggingFace Router API for model access. Does NOT support token rotation.
6620
+ * Creates a higher-order function that executes the provided async function
6621
+ * within a HuggingFace Router API inference context.
6945
6622
  *
6946
- * @param messages - Array of outline messages (user/assistant/system)
6947
- * @param model - HuggingFace model name
6948
- * @param apiKey - Single API key (token rotation not supported)
6949
- * @returns Promise resolving to structured trading signal
6623
+ * @template T - Async function type
6624
+ * @param fn - Async function to wrap
6625
+ * @param model - HuggingFace model name (e.g., "meta-llama/Llama-3-70b")
6626
+ * @param apiKey - Single API key or array of keys
6627
+ * @returns Wrapped function with same signature as input
6950
6628
  *
6951
6629
  * @example
6952
6630
  * ```typescript
6953
6631
  * import { hf } from '@backtest-kit/ollama';
6954
6632
  *
6955
- * const signal = await hf(messages, 'meta-llama/Llama-3-70b', process.env.HF_API_KEY);
6633
+ * const wrappedFn = hf(myAsyncFn, 'meta-llama/Llama-3-70b', process.env.HF_API_KEY);
6634
+ * const result = await wrappedFn(args);
6956
6635
  * ```
6957
6636
  */
6958
- const hf = async (messages, model, apiKey) => {
6959
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.HfInference, model, apiKey);
6637
+ const hf = (fn, model, apiKey) => {
6638
+ const wrappedFn = async (args) => {
6639
+ return await ContextService.runInContext(async () => {
6640
+ return await fn(...args);
6641
+ }, {
6642
+ apiKey,
6643
+ inference: InferenceName$1.HfInference,
6644
+ model,
6645
+ });
6646
+ };
6647
+ return wrappedFn;
6960
6648
  };
6961
6649
  /**
6962
- * Generate structured trading signal from Claude models.
6650
+ * Wrap async function with Claude inference context.
6963
6651
  *
6964
- * Uses Anthropic Claude through OpenAI-compatible API. Does NOT support token rotation.
6652
+ * Creates a higher-order function that executes the provided async function
6653
+ * within an Anthropic Claude inference context.
6965
6654
  *
6966
- * @param messages - Array of outline messages (user/assistant/system)
6655
+ * @template T - Async function type
6656
+ * @param fn - Async function to wrap
6967
6657
  * @param model - Claude model name (e.g., "claude-3-5-sonnet-20241022")
6968
- * @param apiKey - Single API key (token rotation not supported)
6969
- * @returns Promise resolving to structured trading signal
6970
- * @throws Error if apiKey is an array (token rotation not supported)
6658
+ * @param apiKey - Single API key or array of keys
6659
+ * @returns Wrapped function with same signature as input
6971
6660
  *
6972
6661
  * @example
6973
6662
  * ```typescript
6974
6663
  * import { claude } from '@backtest-kit/ollama';
6975
6664
  *
6976
- * const signal = await claude(messages, 'claude-3-5-sonnet-20241022', process.env.ANTHROPIC_API_KEY);
6665
+ * const wrappedFn = claude(myAsyncFn, 'claude-3-5-sonnet-20241022', process.env.ANTHROPIC_API_KEY);
6666
+ * const result = await wrappedFn(args);
6977
6667
  * ```
6978
6668
  */
6979
- const claude = async (messages, model, apiKey) => {
6980
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.ClaudeInference, model, apiKey);
6669
+ const claude = (fn, model, apiKey) => {
6670
+ const wrappedFn = async (args) => {
6671
+ return await ContextService.runInContext(async () => {
6672
+ return await fn(...args);
6673
+ }, {
6674
+ apiKey,
6675
+ inference: InferenceName$1.ClaudeInference,
6676
+ model,
6677
+ });
6678
+ };
6679
+ return wrappedFn;
6981
6680
  };
6982
6681
  /**
6983
- * Generate structured trading signal from OpenAI GPT models.
6682
+ * Wrap async function with OpenAI GPT inference context.
6984
6683
  *
6985
- * Uses official OpenAI SDK with JSON schema enforcement. Does NOT support token rotation.
6684
+ * Creates a higher-order function that executes the provided async function
6685
+ * within an OpenAI GPT inference context.
6986
6686
  *
6987
- * @param messages - Array of outline messages (user/assistant/system)
6687
+ * @template T - Async function type
6688
+ * @param fn - Async function to wrap
6988
6689
  * @param model - OpenAI model name (e.g., "gpt-4o", "gpt-4-turbo")
6989
- * @param apiKey - Single API key (token rotation not supported)
6990
- * @returns Promise resolving to structured trading signal
6991
- * @throws Error if apiKey is an array (token rotation not supported)
6690
+ * @param apiKey - Single API key or array of keys
6691
+ * @returns Wrapped function with same signature as input
6992
6692
  *
6993
6693
  * @example
6994
6694
  * ```typescript
6995
6695
  * import { gpt5 } from '@backtest-kit/ollama';
6996
6696
  *
6997
- * const signal = await gpt5(messages, 'gpt-4o', process.env.OPENAI_API_KEY);
6697
+ * const wrappedFn = gpt5(myAsyncFn, 'gpt-4o', process.env.OPENAI_API_KEY);
6698
+ * const result = await wrappedFn(args);
6998
6699
  * ```
6999
6700
  */
7000
- const gpt5 = async (messages, model, apiKey) => {
7001
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GPT5Inference, model, apiKey);
6701
+ const gpt5 = (fn, model, apiKey) => {
6702
+ const wrappedFn = async (args) => {
6703
+ return await ContextService.runInContext(async () => {
6704
+ return await fn(...args);
6705
+ }, {
6706
+ apiKey,
6707
+ inference: InferenceName$1.GPT5Inference,
6708
+ model,
6709
+ });
6710
+ };
6711
+ return wrappedFn;
7002
6712
  };
7003
6713
  /**
7004
- * Generate structured trading signal from DeepSeek models.
6714
+ * Wrap async function with DeepSeek inference context.
7005
6715
  *
7006
- * Uses DeepSeek AI through OpenAI-compatible API. Does NOT support token rotation.
6716
+ * Creates a higher-order function that executes the provided async function
6717
+ * within a DeepSeek AI inference context.
7007
6718
  *
7008
- * @param messages - Array of outline messages (user/assistant/system)
6719
+ * @template T - Async function type
6720
+ * @param fn - Async function to wrap
7009
6721
  * @param model - DeepSeek model name (e.g., "deepseek-chat")
7010
- * @param apiKey - Single API key (token rotation not supported)
7011
- * @returns Promise resolving to structured trading signal
7012
- * @throws Error if apiKey is an array (token rotation not supported)
6722
+ * @param apiKey - Single API key or array of keys
6723
+ * @returns Wrapped function with same signature as input
7013
6724
  *
7014
6725
  * @example
7015
6726
  * ```typescript
7016
6727
  * import { deepseek } from '@backtest-kit/ollama';
7017
6728
  *
7018
- * const signal = await deepseek(messages, 'deepseek-chat', process.env.DEEPSEEK_API_KEY);
6729
+ * const wrappedFn = deepseek(myAsyncFn, 'deepseek-chat', process.env.DEEPSEEK_API_KEY);
6730
+ * const result = await wrappedFn(args);
7019
6731
  * ```
7020
6732
  */
7021
- const deepseek = async (messages, model, apiKey) => {
7022
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.DeepseekInference, model, apiKey);
6733
+ const deepseek = (fn, model, apiKey) => {
6734
+ const wrappedFn = async (args) => {
6735
+ return await ContextService.runInContext(async () => {
6736
+ return await fn(...args);
6737
+ }, {
6738
+ apiKey,
6739
+ inference: InferenceName$1.DeepseekInference,
6740
+ model,
6741
+ });
6742
+ };
6743
+ return wrappedFn;
7023
6744
  };
7024
6745
  /**
7025
- * Generate structured trading signal from Mistral AI models.
6746
+ * Wrap async function with Mistral AI inference context.
7026
6747
  *
7027
- * Uses Mistral AI through OpenAI-compatible API. Does NOT support token rotation.
6748
+ * Creates a higher-order function that executes the provided async function
6749
+ * within a Mistral AI inference context.
7028
6750
  *
7029
- * @param messages - Array of outline messages (user/assistant/system)
6751
+ * @template T - Async function type
6752
+ * @param fn - Async function to wrap
7030
6753
  * @param model - Mistral model name (e.g., "mistral-large-latest")
7031
- * @param apiKey - Single API key (token rotation not supported)
7032
- * @returns Promise resolving to structured trading signal
7033
- * @throws Error if apiKey is an array (token rotation not supported)
6754
+ * @param apiKey - Single API key or array of keys
6755
+ * @returns Wrapped function with same signature as input
7034
6756
  *
7035
6757
  * @example
7036
6758
  * ```typescript
7037
6759
  * import { mistral } from '@backtest-kit/ollama';
7038
6760
  *
7039
- * const signal = await mistral(messages, 'mistral-large-latest', process.env.MISTRAL_API_KEY);
6761
+ * const wrappedFn = mistral(myAsyncFn, 'mistral-large-latest', process.env.MISTRAL_API_KEY);
6762
+ * const result = await wrappedFn(args);
7040
6763
  * ```
7041
6764
  */
7042
- const mistral = async (messages, model, apiKey) => {
7043
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.MistralInference, model, apiKey);
6765
+ const mistral = (fn, model, apiKey) => {
6766
+ const wrappedFn = async (args) => {
6767
+ return await ContextService.runInContext(async () => {
6768
+ return await fn(...args);
6769
+ }, {
6770
+ apiKey,
6771
+ inference: InferenceName$1.MistralInference,
6772
+ model,
6773
+ });
6774
+ };
6775
+ return wrappedFn;
7044
6776
  };
7045
6777
  /**
7046
- * Generate structured trading signal from Perplexity AI models.
6778
+ * Wrap async function with Perplexity AI inference context.
7047
6779
  *
7048
- * Uses Perplexity AI through OpenAI-compatible API. Does NOT support token rotation.
6780
+ * Creates a higher-order function that executes the provided async function
6781
+ * within a Perplexity AI inference context.
7049
6782
  *
7050
- * @param messages - Array of outline messages (user/assistant/system)
6783
+ * @template T - Async function type
6784
+ * @param fn - Async function to wrap
7051
6785
  * @param model - Perplexity model name (e.g., "llama-3.1-sonar-huge-128k-online")
7052
- * @param apiKey - Single API key (token rotation not supported)
7053
- * @returns Promise resolving to structured trading signal
7054
- * @throws Error if apiKey is an array (token rotation not supported)
6786
+ * @param apiKey - Single API key or array of keys
6787
+ * @returns Wrapped function with same signature as input
7055
6788
  *
7056
6789
  * @example
7057
6790
  * ```typescript
7058
6791
  * import { perplexity } from '@backtest-kit/ollama';
7059
6792
  *
7060
- * const signal = await perplexity(messages, 'llama-3.1-sonar-huge-128k-online', process.env.PERPLEXITY_API_KEY);
6793
+ * const wrappedFn = perplexity(myAsyncFn, 'llama-3.1-sonar-huge-128k-online', process.env.PERPLEXITY_API_KEY);
6794
+ * const result = await wrappedFn(args);
7061
6795
  * ```
7062
6796
  */
7063
- const perplexity = async (messages, model, apiKey) => {
7064
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.PerplexityInference, model, apiKey);
6797
+ const perplexity = (fn, model, apiKey) => {
6798
+ const wrappedFn = async (args) => {
6799
+ return await ContextService.runInContext(async () => {
6800
+ return await fn(...args);
6801
+ }, {
6802
+ apiKey,
6803
+ inference: InferenceName$1.PerplexityInference,
6804
+ model,
6805
+ });
6806
+ };
6807
+ return wrappedFn;
7065
6808
  };
7066
6809
  /**
7067
- * Generate structured trading signal from Cohere models.
6810
+ * Wrap async function with Cohere inference context.
7068
6811
  *
7069
- * Uses Cohere AI through OpenAI-compatible API. Does NOT support token rotation.
6812
+ * Creates a higher-order function that executes the provided async function
6813
+ * within a Cohere AI inference context.
7070
6814
  *
7071
- * @param messages - Array of outline messages (user/assistant/system)
6815
+ * @template T - Async function type
6816
+ * @param fn - Async function to wrap
7072
6817
  * @param model - Cohere model name (e.g., "command-r-plus")
7073
- * @param apiKey - Single API key (token rotation not supported)
7074
- * @returns Promise resolving to structured trading signal
7075
- * @throws Error if apiKey is an array (token rotation not supported)
6818
+ * @param apiKey - Single API key or array of keys
6819
+ * @returns Wrapped function with same signature as input
7076
6820
  *
7077
6821
  * @example
7078
6822
  * ```typescript
7079
6823
  * import { cohere } from '@backtest-kit/ollama';
7080
6824
  *
7081
- * const signal = await cohere(messages, 'command-r-plus', process.env.COHERE_API_KEY);
6825
+ * const wrappedFn = cohere(myAsyncFn, 'command-r-plus', process.env.COHERE_API_KEY);
6826
+ * const result = await wrappedFn(args);
7082
6827
  * ```
7083
6828
  */
7084
- const cohere = async (messages, model, apiKey) => {
7085
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.CohereInference, model, apiKey);
6829
+ const cohere = (fn, model, apiKey) => {
6830
+ const wrappedFn = async (args) => {
6831
+ return await ContextService.runInContext(async () => {
6832
+ return await fn(...args);
6833
+ }, {
6834
+ apiKey,
6835
+ inference: InferenceName$1.CohereInference,
6836
+ model,
6837
+ });
6838
+ };
6839
+ return wrappedFn;
7086
6840
  };
7087
6841
  /**
7088
- * Generate structured trading signal from Alibaba Cloud Qwen models.
6842
+ * Wrap async function with Alibaba Qwen inference context.
7089
6843
  *
7090
- * Uses Alibaba DashScope API through direct HTTP requests. Does NOT support token rotation.
6844
+ * Creates a higher-order function that executes the provided async function
6845
+ * within an Alibaba DashScope API inference context.
7091
6846
  *
7092
- * @param messages - Array of outline messages (user/assistant/system)
6847
+ * @template T - Async function type
6848
+ * @param fn - Async function to wrap
7093
6849
  * @param model - Qwen model name (e.g., "qwen-max")
7094
- * @param apiKey - Single API key (token rotation not supported)
7095
- * @returns Promise resolving to structured trading signal
7096
- * @throws Error if apiKey is an array (token rotation not supported)
6850
+ * @param apiKey - Single API key or array of keys
6851
+ * @returns Wrapped function with same signature as input
7097
6852
  *
7098
6853
  * @example
7099
6854
  * ```typescript
7100
6855
  * import { alibaba } from '@backtest-kit/ollama';
7101
6856
  *
7102
- * const signal = await alibaba(messages, 'qwen-max', process.env.ALIBABA_API_KEY);
6857
+ * const wrappedFn = alibaba(myAsyncFn, 'qwen-max', process.env.ALIBABA_API_KEY);
6858
+ * const result = await wrappedFn(args);
7103
6859
  * ```
7104
6860
  */
7105
- const alibaba = async (messages, model, apiKey) => {
7106
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.AlibabaInference, model, apiKey);
6861
+ const alibaba = (fn, model, apiKey) => {
6862
+ const wrappedFn = async (args) => {
6863
+ return await ContextService.runInContext(async () => {
6864
+ return await fn(...args);
6865
+ }, {
6866
+ apiKey,
6867
+ inference: InferenceName$1.AlibabaInference,
6868
+ model,
6869
+ });
6870
+ };
6871
+ return wrappedFn;
7107
6872
  };
7108
6873
  /**
7109
- * Generate structured trading signal from Zhipu AI GLM-4 models.
6874
+ * Wrap async function with Zhipu AI GLM-4 inference context.
7110
6875
  *
7111
- * Uses Zhipu AI's GLM-4 through OpenAI-compatible Z.ai API. Does NOT support token rotation.
7112
- * GLM-4 is a powerful Chinese language model with strong reasoning capabilities.
6876
+ * Creates a higher-order function that executes the provided async function
6877
+ * within a Zhipu AI GLM-4 inference context via OpenAI-compatible Z.ai API.
7113
6878
  *
7114
- * @param messages - Array of outline messages (user/assistant/system)
6879
+ * @template T - Async function type
6880
+ * @param fn - Async function to wrap
7115
6881
  * @param model - GLM-4 model name (e.g., "glm-4-plus", "glm-4-air")
7116
- * @param apiKey - Single API key (token rotation not supported)
7117
- * @returns Promise resolving to structured trading signal
7118
- * @throws Error if apiKey is an array (token rotation not supported)
6882
+ * @param apiKey - Single API key or array of keys
6883
+ * @returns Wrapped function with same signature as input
7119
6884
  *
7120
6885
  * @example
7121
6886
  * ```typescript
7122
6887
  * import { glm4 } from '@backtest-kit/ollama';
7123
6888
  *
7124
- * const signal = await glm4(messages, 'glm-4-plus', process.env.ZAI_API_KEY);
7125
- * console.log(`Position: ${signal.position}`);
7126
- * console.log(`Entry: ${signal.priceOpen}`);
6889
+ * const wrappedFn = glm4(myAsyncFn, 'glm-4-plus', process.env.ZAI_API_KEY);
6890
+ * const result = await wrappedFn(args);
7127
6891
  * ```
7128
6892
  */
7129
- const glm4 = async (messages, model, apiKey) => {
7130
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GLM4Inference, model, apiKey);
6893
+ const glm4 = (fn, model, apiKey) => {
6894
+ const wrappedFn = async (args) => {
6895
+ return await ContextService.runInContext(async () => {
6896
+ return await fn(...args);
6897
+ }, {
6898
+ apiKey,
6899
+ inference: InferenceName$1.GLM4Inference,
6900
+ model,
6901
+ });
6902
+ };
6903
+ return wrappedFn;
7131
6904
  };
7132
6905
 
7133
6906
  /**
@@ -7151,64 +6924,6 @@ const setLogger = (logger) => {
7151
6924
  engine$1.loggerService.setLogger(logger);
7152
6925
  };
7153
6926
 
7154
- /**
7155
- * Overrides the default signal format schema for LLM-generated trading signals.
7156
- *
7157
- * This function allows customization of the structured output format used by the
7158
- * SignalOutline. It replaces the default signal schema with a custom Zod schema,
7159
- * enabling flexible signal structure definitions while maintaining type safety.
7160
- *
7161
- * The override affects all subsequent signal generation calls using SignalOutline
7162
- * until the application restarts or the schema is overridden again.
7163
- *
7164
- * @template ZodInput - The Zod schema type used for validation and type inference
7165
- *
7166
- * @param {ZodInput} format - Custom Zod schema defining the signal structure.
7167
- * Must be a valid Zod type (z.object, z.string, etc.)
7168
- *
7169
- * @example
7170
- * ```typescript
7171
- * import { z } from 'zod';
7172
- * import { overrideSignalFormat } from '@backtest-kit/ollama';
7173
- *
7174
- * // Override with custom signal schema
7175
- * const CustomSignalSchema = z.object({
7176
- * position: z.enum(['long', 'short', 'wait']),
7177
- * price_open: z.number(),
7178
- * confidence: z.number().min(0).max(100),
7179
- * custom_field: z.string()
7180
- * });
7181
- *
7182
- * overrideSignalFormat(CustomSignalSchema);
7183
- * ```
7184
- *
7185
- * @example
7186
- * ```typescript
7187
- * // Override with simplified schema
7188
- * const SimpleSignalSchema = z.object({
7189
- * action: z.enum(['buy', 'sell', 'hold']),
7190
- * price: z.number()
7191
- * });
7192
- *
7193
- * overrideSignalFormat(SimpleSignalSchema);
7194
- * ```
7195
- *
7196
- * @remarks
7197
- * - The custom schema replaces the default SignalSchema completely
7198
- * - Schema name in OpenAI format is always "position_open_decision"
7199
- * - Changes persist until application restart or next override
7200
- * - Ensure the custom schema matches your signal processing logic
7201
- *
7202
- * @see {@link SignalSchema} - Default signal schema structure
7203
- * @see {@link OutlineName.SignalOutline} - Outline being overridden
7204
- */
7205
- function overrideSignalFormat(format) {
7206
- agentSwarmKit.overrideOutline({
7207
- outlineName: OutlineName$1.SignalOutline,
7208
- format: zod$1.zodResponseFormat(format, "position_open_decision"),
7209
- });
7210
- }
7211
-
7212
6927
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
7213
6928
  /**
7214
6929
  * Dumps signal data and LLM conversation history to markdown files.
@@ -7376,7 +7091,36 @@ async function validate(args = {}) {
7376
7091
  return await validateInternal(args);
7377
7092
  }
7378
7093
 
7094
+ const MODULE_TYPE_SYMBOL = Symbol("module-type");
7095
+ class Module {
7096
+ constructor(path, baseDir) {
7097
+ this.path = path;
7098
+ this.baseDir = baseDir;
7099
+ this.__type__ = MODULE_TYPE_SYMBOL;
7100
+ }
7101
+ }
7102
+ Module.fromPath = (path$1, baseDir = path.join(process.cwd(), "config/prompt")) => {
7103
+ if (!path$1 || typeof path$1 !== "string" || !path$1.trim()) {
7104
+ throw new Error("Path must be a non-empty string");
7105
+ }
7106
+ return new Module(path$1, baseDir);
7107
+ };
7108
+ Module.isModule = (value) => {
7109
+ return (value !== null &&
7110
+ typeof value === "object" &&
7111
+ value.__type__ === MODULE_TYPE_SYMBOL);
7112
+ };
7113
+
7379
7114
  const METHOD_NAME_SIGNAL = "history.commitSignalPromptHistory";
7115
+ const GET_PROMPT_FN = async (source) => {
7116
+ if (Prompt.isPrompt(source)) {
7117
+ return source;
7118
+ }
7119
+ if (Module.isModule(source)) {
7120
+ return await engine$1.promptCacheService.readModule(source);
7121
+ }
7122
+ throw new Error("Source must be a Prompt or Module instance");
7123
+ };
7380
7124
  /**
7381
7125
  * Commits signal prompt history to the message array.
7382
7126
  *
@@ -7385,40 +7129,40 @@ const METHOD_NAME_SIGNAL = "history.commitSignalPromptHistory";
7385
7129
  * at the end of the history array if they are not empty.
7386
7130
  *
7387
7131
  * Context extraction:
7388
- * - symbol: Provided as parameter for debugging convenience
7389
- * - backtest mode: From ExecutionContext
7390
- * - strategyName, exchangeName, frameName: From MethodContext
7132
+ * - symbol: From getSymbol()
7133
+ * - backtest mode: From getMode()
7134
+ * - strategyName, exchangeName, frameName: From getContext()
7391
7135
  *
7392
- * @param symbol - Trading symbol (e.g., "BTCUSDT") for debugging convenience
7136
+ * @param source - Module object containing path to .cjs module
7393
7137
  * @param history - Message array to append prompts to
7394
7138
  * @returns Promise that resolves when prompts are added
7395
7139
  * @throws Error if ExecutionContext or MethodContext is not active
7396
7140
  *
7397
- * @example
7398
- * ```typescript
7399
- * const messages: MessageModel[] = [];
7400
- * await commitSignalPromptHistory("BTCUSDT", messages);
7401
- * // messages now contains system prompts at start and user prompt at end
7402
7141
  * ```
7403
7142
  */
7404
- async function commitSignalPromptHistory(symbol, history) {
7143
+ async function commitPrompt(source, history) {
7405
7144
  engine$1.loggerService.log(METHOD_NAME_SIGNAL, {
7406
- symbol,
7145
+ source,
7407
7146
  });
7147
+ const symbol = await backtestKit.getSymbol();
7408
7148
  const { strategyName, exchangeName, frameName } = await backtestKit.getContext();
7409
7149
  const mode = await backtestKit.getMode();
7410
7150
  const isBacktest = mode === "backtest";
7411
- const systemPrompts = await engine$1.signalPromptService.getSystemPrompt(symbol, strategyName, exchangeName, frameName, isBacktest);
7412
- const userPrompt = await engine$1.signalPromptService.getUserPrompt(symbol, strategyName, exchangeName, frameName, isBacktest);
7151
+ const prompt = await GET_PROMPT_FN(source);
7152
+ const systemPrompts = await engine$1.resolvePromptService.getSystemPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
7153
+ const userPrompt = await engine$1.resolvePromptService.getUserPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
7413
7154
  if (systemPrompts.length > 0) {
7414
7155
  for (const content of systemPrompts) {
7156
+ if (!content.trim()) {
7157
+ continue;
7158
+ }
7415
7159
  history.unshift({
7416
7160
  role: "system",
7417
7161
  content,
7418
7162
  });
7419
7163
  }
7420
7164
  }
7421
- if (userPrompt && userPrompt.trim() !== "") {
7165
+ if (userPrompt && userPrompt.trim()) {
7422
7166
  history.push({
7423
7167
  role: "user",
7424
7168
  content: userPrompt,
@@ -7725,12 +7469,14 @@ class OptimizerUtils {
7725
7469
  */
7726
7470
  const Optimizer = new OptimizerUtils();
7727
7471
 
7472
+ exports.Module = Module;
7728
7473
  exports.Optimizer = Optimizer;
7474
+ exports.Prompt = Prompt;
7729
7475
  exports.addOptimizerSchema = addOptimizerSchema;
7730
7476
  exports.alibaba = alibaba;
7731
7477
  exports.claude = claude;
7732
7478
  exports.cohere = cohere;
7733
- exports.commitSignalPromptHistory = commitSignalPromptHistory;
7479
+ exports.commitPrompt = commitPrompt;
7734
7480
  exports.deepseek = deepseek;
7735
7481
  exports.dumpSignalData = dumpSignalData;
7736
7482
  exports.getOptimizerSchema = getOptimizerSchema;
@@ -7744,7 +7490,6 @@ exports.listenError = listenError;
7744
7490
  exports.listenOptimizerProgress = listenOptimizerProgress;
7745
7491
  exports.mistral = mistral;
7746
7492
  exports.ollama = ollama;
7747
- exports.overrideSignalFormat = overrideSignalFormat;
7748
7493
  exports.perplexity = perplexity;
7749
7494
  exports.setLogger = setLogger;
7750
7495
  exports.validate = validate;