@discomedia/utils 1.0.9 → 1.0.11

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/dist/index.cjs CHANGED
@@ -9387,21 +9387,66 @@ const makeResponsesAPICall = async (input, options = {}) => {
9387
9387
  * Makes a call to the OpenAI Responses API for advanced use cases with built-in tools.
9388
9388
  *
9389
9389
  * @param input The text prompt to send to the model (e.g., "What's in this image?")
9390
- * @param options The options for the Responses API call, including optional image data.
9390
+ * @param options The options for the Responses API call, including optional image data and context.
9391
9391
  * @return A promise that resolves to the response from the Responses API.
9392
+ *
9393
+ * @example
9394
+ * // With conversation context
9395
+ * const response = await makeLLMCall("What did I ask about earlier?", {
9396
+ * context: [
9397
+ * { role: 'user', content: 'What is the capital of France?' },
9398
+ * { role: 'assistant', content: 'The capital of France is Paris.' }
9399
+ * ]
9400
+ * });
9392
9401
  */
9393
9402
  async function makeLLMCall(input, options = {}) {
9394
- const { apiKey, model = DEFAULT_MODEL$1, responseFormat = 'text', tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high' } = options;
9403
+ const { apiKey, model = DEFAULT_MODEL$1, responseFormat = 'text', tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high', context } = options;
9395
9404
  // Validate model
9396
9405
  const normalizedModel = normalizeModelName(model);
9397
9406
  if (!isSupportedModel(normalizedModel)) {
9398
9407
  throw new Error(`Unsupported model: ${normalizedModel}. Please use one of the supported models.`);
9399
9408
  }
9400
- // Process input for image analysis
9409
+ // Process input for conversation context and image analysis
9401
9410
  let processedInput;
9402
- if (imageBase64) {
9403
- // Combine text prompt with image for multi-modal input
9404
- // Based on OpenAI's sample code structure
9411
+ if (context && context.length > 0) {
9412
+ // Build conversation array with context
9413
+ const conversationMessages = [];
9414
+ // Add context messages
9415
+ for (const contextMsg of context) {
9416
+ conversationMessages.push({
9417
+ role: contextMsg.role,
9418
+ content: contextMsg.content,
9419
+ type: 'message'
9420
+ });
9421
+ }
9422
+ // Add current input message
9423
+ if (imageBase64) {
9424
+ // Current message includes both text and image
9425
+ conversationMessages.push({
9426
+ role: 'user',
9427
+ content: [
9428
+ { type: 'input_text', text: input },
9429
+ {
9430
+ type: 'input_image',
9431
+ detail: imageDetail,
9432
+ image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
9433
+ }
9434
+ ],
9435
+ type: 'message'
9436
+ });
9437
+ }
9438
+ else {
9439
+ // Current message is just text
9440
+ conversationMessages.push({
9441
+ role: 'user',
9442
+ content: input,
9443
+ type: 'message'
9444
+ });
9445
+ }
9446
+ processedInput = conversationMessages;
9447
+ }
9448
+ else if (imageBase64) {
9449
+ // No context, but has image - use the original image logic
9405
9450
  processedInput = [
9406
9451
  {
9407
9452
  role: 'user',
@@ -9412,11 +9457,13 @@ async function makeLLMCall(input, options = {}) {
9412
9457
  detail: imageDetail,
9413
9458
  image_url: imageBase64.startsWith('data:') ? imageBase64 : `data:image/webp;base64,${imageBase64}`,
9414
9459
  }
9415
- ]
9460
+ ],
9461
+ type: 'message'
9416
9462
  }
9417
9463
  ];
9418
9464
  }
9419
9465
  else {
9466
+ // No context, no image - simple string input
9420
9467
  processedInput = input;
9421
9468
  }
9422
9469
  // Build the options object for makeResponsesAPICall
@@ -9835,6 +9882,128 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
9835
9882
  }
9836
9883
  };
9837
9884
 
9885
+ /**
9886
+ * A class to measure performance of code execution.
9887
+ *
9888
+ * This utility records elapsed time during execution and provides a detailed
9889
+ * breakdown of elapsed time between checkpoints.
9890
+ *
9891
+ * Usage example:
9892
+ * const timer = new PerformanceTimer();
9893
+ * timer.checkpoint('Start Processing');
9894
+ * // ... code for processing
9895
+ * timer.checkpoint('After Task 1');
9896
+ * // ... additional processing code
9897
+ * timer.stop();
9898
+ * console.log(timer.generateReport());
9899
+ *
9900
+ * Methods:
9901
+ * - checkpoint(label: string): Record a timestamped checkpoint.
9902
+ * - stop(): Stop the timer and compute the total elapsed time.
9903
+ * - generateReport(): Return a performance report with total time and phases.
9904
+ * - formatPerformanceReport(): Return a formatted string of the performance report.
9905
+ * - getElapsedSeconds(): Get the current elapsed time in seconds.
9906
+ */
9907
+ class PerformanceTimer {
9908
+ startTime;
9909
+ checkpoints;
9910
+ totalTime;
9911
+ constructor() {
9912
+ this.startTime = performance.now();
9913
+ this.checkpoints = new Map();
9914
+ this.totalTime = null;
9915
+ }
9916
+ /**
9917
+ * Gets the current elapsed time in seconds.
9918
+ *
9919
+ * @returns The elapsed time in seconds with 3 decimal places of precision.
9920
+ */
9921
+ getElapsedSeconds() {
9922
+ const elapsedMs = this.totalTime ?? (performance.now() - this.startTime);
9923
+ return Number((elapsedMs / 1000).toFixed(3));
9924
+ }
9925
+ /**
9926
+ * Records a checkpoint with the given label.
9927
+ *
9928
+ * @param label - A descriptive label for the checkpoint.
9929
+ */
9930
+ checkpoint(label) {
9931
+ this.checkpoints.set(label, performance.now());
9932
+ }
9933
+ /**
9934
+ * Stops the timer and sets the total elapsed time.
9935
+ */
9936
+ stop() {
9937
+ this.totalTime = performance.now() - this.startTime;
9938
+ }
9939
+ /**
9940
+ * Generates a performance report containing the total elapsed time and
9941
+ * breakdown of phases between checkpoints.
9942
+ *
9943
+ * @returns A performance report object.
9944
+ * @throws Error if the timer is not stopped before generating the report.
9945
+ */
9946
+ analyseReportData() {
9947
+ if (this.totalTime === null) {
9948
+ throw new Error('Timer must be stopped before generating report');
9949
+ }
9950
+ const report = {
9951
+ totalTime: this.totalTime,
9952
+ phases: []
9953
+ };
9954
+ let lastTime = this.startTime;
9955
+ // Convert checkpoints to a sorted array by time
9956
+ const sortedCheckpoints = Array.from(this.checkpoints.entries())
9957
+ .sort((a, b) => a[1] - b[1]);
9958
+ for (const [label, time] of sortedCheckpoints) {
9959
+ const duration = time - lastTime;
9960
+ if (duration < 0) {
9961
+ throw new Error(`Negative duration detected for checkpoint: ${label}`);
9962
+ }
9963
+ report.phases.push({
9964
+ label,
9965
+ duration
9966
+ });
9967
+ lastTime = time;
9968
+ }
9969
+ // Add final phase from last checkpoint to stop
9970
+ if (sortedCheckpoints.length > 0) {
9971
+ const finalDuration = this.totalTime - lastTime;
9972
+ report.phases.push({
9973
+ label: 'Final Processing',
9974
+ duration: Math.max(finalDuration, 0) // Prevent negative duration
9975
+ });
9976
+ }
9977
+ return report;
9978
+ }
9979
+ /**
9980
+ * Returns a formatted string of the performance report.
9981
+ *
9982
+ * @returns A string detailing the total execution time and phase breakdown.
9983
+ */
9984
+ generateReport() {
9985
+ const report = this.analyseReportData();
9986
+ // Format numbers with thousands separators and 2 decimal places
9987
+ const formatNumber = (num) => num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
9988
+ // Calculate column widths
9989
+ const maxLabelLength = Math.max(...report.phases.map(p => p.label.length), 30);
9990
+ const maxDurationLength = Math.max(...report.phases.map(p => formatNumber(p.duration).length), 10);
9991
+ // Create table header
9992
+ let output = `╔${'═'.repeat(maxLabelLength + 2)}╦${'═'.repeat(maxDurationLength + 2)}╗\n`;
9993
+ output += `║ ${'Phase'.padEnd(maxLabelLength)} ║ ${'Time (ms)'.padEnd(maxDurationLength)} ║\n`;
9994
+ output += `╠${'═'.repeat(maxLabelLength + 2)}╬${'═'.repeat(maxDurationLength + 2)}╣\n`;
9995
+ // Add table rows
9996
+ report.phases.forEach(phase => {
9997
+ output += `║ ${phase.label.padEnd(maxLabelLength)} ║ ${formatNumber(phase.duration).padStart(maxDurationLength)} ║\n`;
9998
+ });
9999
+ // Add table footer with total
10000
+ output += `╠${'═'.repeat(maxLabelLength + 2)}╬${'═'.repeat(maxDurationLength + 2)}╣\n`;
10001
+ output += `║ ${'Total'.padEnd(maxLabelLength)} ║ ${formatNumber(report.totalTime).padStart(maxDurationLength)} ║\n`;
10002
+ output += `╚${'═'.repeat(maxLabelLength + 2)}╩${'═'.repeat(maxDurationLength + 2)}╝\n`;
10003
+ return output;
10004
+ }
10005
+ }
10006
+
9838
10007
  /**
9839
10008
  * Calculates Bollinger Bands for a given set of price data.
9840
10009
  * Bollinger Bands consist of a middle band (SMA) and two outer bands
@@ -18034,6 +18203,7 @@ const disco = {
18034
18203
  logIfDebug: logIfDebug,
18035
18204
  fetchWithRetry: fetchWithRetry,
18036
18205
  validatePolygonApiKey: validatePolygonApiKey,
18206
+ Timer: PerformanceTimer,
18037
18207
  },
18038
18208
  };
18039
18209