@discomedia/utils 1.0.67 → 1.0.68

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/test.js CHANGED
@@ -146,18 +146,12 @@ const marketEarlyCloses = {
146
146
 
147
147
  // Constants for NY market times (Eastern Time)
148
148
  const MARKET_CONFIG = {
149
- TIMEZONE: 'America/New_York',
150
149
  UTC_OFFSET_STANDARD: -5, // EST
151
150
  UTC_OFFSET_DST: -4, // EDT
152
151
  TIMES: {
153
- EXTENDED_START: { hour: 4, minute: 0 },
154
152
  MARKET_OPEN: { hour: 9, minute: 30 },
155
- EARLY_MARKET_END: { hour: 10, minute: 0 },
156
153
  MARKET_CLOSE: { hour: 16, minute: 0 },
157
- EARLY_CLOSE: { hour: 13, minute: 0 },
158
- EXTENDED_END: { hour: 20, minute: 0 },
159
- EARLY_EXTENDED_END: { hour: 17, minute: 0 },
160
- },
154
+ EARLY_CLOSE: { hour: 13, minute: 0 }},
161
155
  };
162
156
  // Helper: Get NY offset for a given UTC date (DST rules for US)
163
157
  /**
@@ -225,33 +219,6 @@ function fromNYTime(date) {
225
219
  const utcMillis = nyMillis - offset * 60 * 60 * 1000;
226
220
  return new Date(utcMillis);
227
221
  }
228
- // Helper: Format date in ISO, unix, etc.
229
- /**
230
- * Formats a date in ISO, unix-seconds, or unix-ms format.
231
- * @param date - Date object
232
- * @param outputFormat - Output format ('iso', 'unix-seconds', 'unix-ms')
233
- * @returns Formatted date string or number
234
- */
235
- function formatDate(date, outputFormat = 'iso') {
236
- switch (outputFormat) {
237
- case 'unix-seconds':
238
- return Math.floor(date.getTime() / 1000);
239
- case 'unix-ms':
240
- return date.getTime();
241
- case 'iso':
242
- default:
243
- return date.toISOString();
244
- }
245
- }
246
- // Helper: Format date in NY locale string
247
- /**
248
- * Formats a date in NY locale string.
249
- * @param date - Date object
250
- * @returns NY locale string
251
- */
252
- function formatNYLocale(date) {
253
- return date.toLocaleString('en-US', { timeZone: 'America/New_York' });
254
- }
255
222
  // Market calendar logic
256
223
  /**
257
224
  * Market calendar logic for holidays, weekends, and market days.
@@ -347,82 +314,6 @@ class MarketCalendar {
347
314
  return prevDay;
348
315
  }
349
316
  }
350
- // Market open/close times
351
- /**
352
- * Returns market open/close times for a given date, including extended and early closes.
353
- * @param date - Date object
354
- * @returns MarketOpenCloseResult
355
- */
356
- function getMarketTimes(date) {
357
- const calendar = new MarketCalendar();
358
- const nyDate = toNYTime(date);
359
- if (!calendar.isMarketDay(date)) {
360
- return {
361
- marketOpen: false,
362
- open: null,
363
- close: null,
364
- openExt: null,
365
- closeExt: null,
366
- };
367
- }
368
- const year = nyDate.getUTCFullYear();
369
- const month = nyDate.getUTCMonth();
370
- const day = nyDate.getUTCDate();
371
- // Helper to build NY time for a given hour/minute
372
- function buildNYTime(hour, minute) {
373
- const d = new Date(Date.UTC(year, month, day, hour, minute, 0, 0));
374
- return fromNYTime(d);
375
- }
376
- let open = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute);
377
- let close = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute);
378
- let openExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute);
379
- let closeExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute);
380
- if (calendar.isEarlyCloseDay(date)) {
381
- close = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute);
382
- closeExt = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute);
383
- }
384
- return {
385
- marketOpen: true,
386
- open,
387
- close,
388
- openExt,
389
- closeExt,
390
- };
391
- }
392
- // Is within market hours
393
- /**
394
- * Checks if a date/time is within market hours, extended hours, or continuous.
395
- * @param date - Date object
396
- * @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
397
- * @returns true if within hours, false otherwise
398
- */
399
- function isWithinMarketHours(date, intradayReporting = 'market_hours') {
400
- const calendar = new MarketCalendar();
401
- if (!calendar.isMarketDay(date))
402
- return false;
403
- const nyDate = toNYTime(date);
404
- const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
405
- switch (intradayReporting) {
406
- case 'extended_hours': {
407
- let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
408
- if (calendar.isEarlyCloseDay(date)) {
409
- endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
410
- }
411
- const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
412
- return minutes >= startMinutes && minutes <= endMinutes;
413
- }
414
- case 'continuous':
415
- return true;
416
- default: {
417
- let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
418
- if (calendar.isEarlyCloseDay(date)) {
419
- endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
420
- }
421
- const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
422
- return minutes >= startMinutes && minutes <= endMinutes;
423
- }
424
- }
425
- }
426
317
  // Get last full trading date
427
318
  /**
428
319
  * Returns the last full trading date (market close) for a given date.
@@ -463,244 +354,6 @@ function getLastFullTradingDateImpl(currentDate = new Date()) {
463
354
  const closeMinute = marketCloseMinutes % 60;
464
355
  return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
465
356
  }
466
- // Get day boundaries
467
- /**
468
- * Returns the start and end boundaries for a market day, extended hours, or continuous.
469
- * @param date - Date object
470
- * @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
471
- * @returns Object with start and end Date
472
- */
473
- function getDayBoundaries(date, intradayReporting = 'market_hours') {
474
- const calendar = new MarketCalendar();
475
- const nyDate = toNYTime(date);
476
- const year = nyDate.getUTCFullYear();
477
- const month = nyDate.getUTCMonth();
478
- const day = nyDate.getUTCDate();
479
- function buildNYTime(hour, minute, sec = 0, ms = 0) {
480
- const d = new Date(Date.UTC(year, month, day, hour, minute, sec, ms));
481
- return fromNYTime(d);
482
- }
483
- let start;
484
- let end;
485
- switch (intradayReporting) {
486
- case 'extended_hours':
487
- start = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute, 0, 0);
488
- end = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute, 59, 999);
489
- if (calendar.isEarlyCloseDay(date)) {
490
- end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute, 59, 999);
491
- }
492
- break;
493
- case 'continuous':
494
- start = new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
495
- end = new Date(Date.UTC(year, month, day, 23, 59, 59, 999));
496
- break;
497
- default:
498
- start = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute, 0, 0);
499
- end = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute, 59, 999);
500
- if (calendar.isEarlyCloseDay(date)) {
501
- end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute, 59, 999);
502
- }
503
- break;
504
- }
505
- return { start, end };
506
- }
507
- // Period calculator
508
- /**
509
- * Calculates the start date for a given period ending at endDate.
510
- * @param endDate - Date object
511
- * @param period - Period string
512
- * @returns Date object for period start
513
- */
514
- function calculatePeriodStartDate(endDate, period) {
515
- const calendar = new MarketCalendar();
516
- let startDate;
517
- switch (period) {
518
- case 'YTD':
519
- startDate = new Date(Date.UTC(endDate.getUTCFullYear(), 0, 1));
520
- break;
521
- case '1D':
522
- startDate = calendar.getPreviousMarketDay(endDate);
523
- break;
524
- case '3D':
525
- startDate = new Date(endDate.getTime() - 3 * 24 * 60 * 60 * 1000);
526
- break;
527
- case '1W':
528
- startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
529
- break;
530
- case '2W':
531
- startDate = new Date(endDate.getTime() - 14 * 24 * 60 * 60 * 1000);
532
- break;
533
- case '1M':
534
- startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 1, endDate.getUTCDate()));
535
- break;
536
- case '3M':
537
- startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 3, endDate.getUTCDate()));
538
- break;
539
- case '6M':
540
- startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 6, endDate.getUTCDate()));
541
- break;
542
- case '1Y':
543
- startDate = new Date(Date.UTC(endDate.getUTCFullYear() - 1, endDate.getUTCMonth(), endDate.getUTCDate()));
544
- break;
545
- default:
546
- throw new Error(`Invalid period: ${period}`);
547
- }
548
- // Ensure start date is a market day
549
- while (!calendar.isMarketDay(startDate)) {
550
- startDate = calendar.getNextMarketDay(startDate);
551
- }
552
- return startDate;
553
- }
554
- // Get market time period
555
- /**
556
- * Returns the start and end dates for a market time period.
557
- * @param params - MarketTimeParams
558
- * @returns PeriodDates object
559
- */
560
- function getMarketTimePeriod(params) {
561
- const { period, end = new Date(), intraday_reporting = 'market_hours', outputFormat = 'iso' } = params;
562
- if (!period)
563
- throw new Error('Period is required');
564
- const calendar = new MarketCalendar();
565
- const nyEndDate = toNYTime(end);
566
- let endDate;
567
- const isCurrentMarketDay = calendar.isMarketDay(end);
568
- const isWithinHours = isWithinMarketHours(end, intraday_reporting);
569
- const minutes = nyEndDate.getUTCHours() * 60 + nyEndDate.getUTCMinutes();
570
- const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
571
- if (isCurrentMarketDay) {
572
- if (minutes < marketStartMinutes) {
573
- // Before market open - use previous day's close
574
- const lastMarketDay = calendar.getPreviousMarketDay(end);
575
- const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
576
- endDate = dayEnd;
577
- }
578
- else if (isWithinHours) {
579
- // During market hours - use current time
580
- endDate = end;
581
- }
582
- else {
583
- // After market close - use today's close
584
- const { end: dayEnd } = getDayBoundaries(end, intraday_reporting);
585
- endDate = dayEnd;
586
- }
587
- }
588
- else {
589
- // Not a market day - use previous market day's close
590
- const lastMarketDay = calendar.getPreviousMarketDay(end);
591
- const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
592
- endDate = dayEnd;
593
- }
594
- // Calculate start date
595
- const periodStartDate = calculatePeriodStartDate(endDate, period);
596
- const { start: dayStart } = getDayBoundaries(periodStartDate, intraday_reporting);
597
- if (endDate.getTime() < dayStart.getTime()) {
598
- throw new Error('Start date cannot be after end date');
599
- }
600
- return {
601
- start: formatDate(dayStart, outputFormat),
602
- end: formatDate(endDate, outputFormat),
603
- };
604
- }
605
- // Market status
606
- /**
607
- * Returns the current market status for a given date.
608
- * @param date - Date object (default: now)
609
- * @returns MarketStatus object
610
- */
611
- function getMarketStatusImpl(date = new Date()) {
612
- const calendar = new MarketCalendar();
613
- const nyDate = toNYTime(date);
614
- const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
615
- const isMarketDay = calendar.isMarketDay(date);
616
- const isEarlyCloseDay = calendar.isEarlyCloseDay(date);
617
- const extendedStartMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
618
- const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
619
- const earlyMarketEndMinutes = MARKET_CONFIG.TIMES.EARLY_MARKET_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_MARKET_END.minute;
620
- let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
621
- let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
622
- if (isEarlyCloseDay) {
623
- marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
624
- extendedEndMinutes =
625
- MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
626
- }
627
- let status;
628
- let nextStatus;
629
- let nextStatusTime;
630
- let marketPeriod;
631
- if (!isMarketDay) {
632
- status = 'closed';
633
- nextStatus = 'extended hours';
634
- marketPeriod = 'closed';
635
- const nextMarketDay = calendar.getNextMarketDay(date);
636
- nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
637
- }
638
- else if (minutes < extendedStartMinutes) {
639
- status = 'closed';
640
- nextStatus = 'extended hours';
641
- marketPeriod = 'closed';
642
- nextStatusTime = getDayBoundaries(date, 'extended_hours').start;
643
- }
644
- else if (minutes < marketStartMinutes) {
645
- status = 'extended hours';
646
- nextStatus = 'open';
647
- marketPeriod = 'preMarket';
648
- nextStatusTime = getDayBoundaries(date, 'market_hours').start;
649
- }
650
- else if (minutes < marketCloseMinutes) {
651
- status = 'open';
652
- nextStatus = 'extended hours';
653
- marketPeriod = minutes < earlyMarketEndMinutes ? 'earlyMarket' : 'regularMarket';
654
- nextStatusTime = getDayBoundaries(date, 'market_hours').end;
655
- }
656
- else if (minutes < extendedEndMinutes) {
657
- status = 'extended hours';
658
- nextStatus = 'closed';
659
- marketPeriod = 'afterMarket';
660
- nextStatusTime = getDayBoundaries(date, 'extended_hours').end;
661
- }
662
- else {
663
- status = 'closed';
664
- nextStatus = 'extended hours';
665
- marketPeriod = 'closed';
666
- const nextMarketDay = calendar.getNextMarketDay(date);
667
- nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
668
- }
669
- // I think using nyDate here may be wrong - should use current time? i.e. date.getTime()
670
- const nextStatusTimeDifference = nextStatusTime.getTime() - date.getTime();
671
- return {
672
- time: date,
673
- timeString: formatNYLocale(nyDate),
674
- status,
675
- nextStatus,
676
- marketPeriod,
677
- nextStatusTime,
678
- nextStatusTimeDifference,
679
- nextStatusTimeString: formatNYLocale(nextStatusTime),
680
- };
681
- }
682
- // API exports
683
- /**
684
- * Returns market open/close times for a given date.
685
- * @param options - { date?: Date }
686
- * @returns MarketOpenCloseResult
687
- */
688
- function getMarketOpenClose(options = {}) {
689
- const { date = new Date() } = options;
690
- return getMarketTimes(date);
691
- }
692
- /**
693
- * Returns the start and end dates for a market time period as Date objects.
694
- * @param params - MarketTimeParams
695
- * @returns Object with start and end Date
696
- */
697
- function getStartAndEndDates(params = {}) {
698
- const { start, end } = getMarketTimePeriod(params);
699
- return {
700
- start: typeof start === 'string' || typeof start === 'number' ? new Date(start) : start,
701
- end: typeof end === 'string' || typeof end === 'number' ? new Date(end) : end,
702
- };
703
- }
704
357
  /**
705
358
  * Returns the last full trading date as a Date object.
706
359
  */
@@ -712,432 +365,6 @@ function getStartAndEndDates(params = {}) {
712
365
  function getLastFullTradingDate(currentDate = new Date()) {
713
366
  return getLastFullTradingDateImpl(currentDate);
714
367
  }
715
- /**
716
- * Returns the next market day after the reference date.
717
- * @param referenceDate - Date object (default: now)
718
- * @returns Object with date, yyyymmdd string, and ISO string
719
- */
720
- function getNextMarketDay({ referenceDate } = {}) {
721
- const calendar = new MarketCalendar();
722
- const startDate = referenceDate ?? new Date();
723
- // Find the next trading day (UTC Date object)
724
- const nextDate = calendar.getNextMarketDay(startDate);
725
- // Convert to NY time before extracting Y-M-D parts
726
- const nyNext = toNYTime(nextDate);
727
- const yyyymmdd = `${nyNext.getUTCFullYear()}-${String(nyNext.getUTCMonth() + 1).padStart(2, '0')}-${String(nyNext.getUTCDate()).padStart(2, '0')}`;
728
- return {
729
- date: nextDate, // raw Date, unchanged
730
- yyyymmdd, // correct trading date string
731
- dateISOString: nextDate.toISOString(),
732
- };
733
- }
734
- /**
735
- * Returns the previous market day before the reference date.
736
- * @param referenceDate - Date object (default: now)
737
- * @returns Object with date, yyyymmdd string, and ISO string
738
- */
739
- function getPreviousMarketDay({ referenceDate } = {}) {
740
- const calendar = new MarketCalendar();
741
- const startDate = referenceDate || new Date();
742
- const prevDate = calendar.getPreviousMarketDay(startDate);
743
- // convert to NY time first
744
- const nyPrev = toNYTime(prevDate); // ← already in this file
745
- const yyyymmdd = `${nyPrev.getUTCFullYear()}-${String(nyPrev.getUTCMonth() + 1).padStart(2, '0')}-${String(nyPrev.getUTCDate()).padStart(2, '0')}`;
746
- return {
747
- date: prevDate,
748
- yyyymmdd,
749
- dateISOString: prevDate.toISOString(),
750
- };
751
- }
752
- /**
753
- * Returns the trading date for a given time. Note: Just trims the date string; does not validate if the date is a market day.
754
- * @param time - a string, number (unix timestamp), or Date object representing the time
755
- * @returns the trading date as a string in YYYY-MM-DD format
756
- */
757
- /**
758
- * Returns the trading date for a given time in YYYY-MM-DD format (NY time).
759
- * @param time - string, number, or Date
760
- * @returns trading date string
761
- */
762
- function getTradingDate(time) {
763
- const date = typeof time === 'number' ? new Date(time) : typeof time === 'string' ? new Date(time) : time;
764
- const nyDate = toNYTime(date);
765
- return `${nyDate.getUTCFullYear()}-${String(nyDate.getUTCMonth() + 1).padStart(2, '0')}-${String(nyDate.getUTCDate()).padStart(2, '0')}`;
766
- }
767
- /**
768
- * Returns the NY timezone offset string for a given date.
769
- * @param date - Date object (default: now)
770
- * @returns '-04:00' for EDT, '-05:00' for EST
771
- */
772
- function getNYTimeZone(date) {
773
- const offset = getNYOffset(date || new Date());
774
- return offset === -4 ? '-04:00' : '-05:00';
775
- }
776
- /**
777
- * Returns the regular market open and close Date objects for a given trading day string in the
778
- * America/New_York timezone (NYSE/NASDAQ calendar).
779
- *
780
- * This helper is convenient when you have a calendar date like '2025-10-03' and want the precise
781
- * open and close Date values for that day. It internally:
782
- * - Determines the NY offset for the day using `getNYTimeZone()`.
783
- * - Anchors a noon-time Date on that day in NY time to avoid DST edge cases.
784
- * - Verifies the day is a market day via `isMarketDay()`.
785
- * - Fetches the open/close times via `getMarketOpenClose()`.
786
- *
787
- * Throws if the provided day is not a market day or if open/close times are unavailable.
788
- *
789
- * See also:
790
- * - `getNYTimeZone(date?: Date)`
791
- * - `isMarketDay(date: Date)`
792
- * - `getMarketOpenClose(options?: { date?: Date })`
793
- *
794
- * @param dateStr - Trading day string in 'YYYY-MM-DD' format (Eastern Time date)
795
- * @returns An object containing `{ open: Date; close: Date }`
796
- * @example
797
- * ```ts
798
- * const { open, close } = disco.time.getOpenCloseForTradingDay('2025-10-03');
799
- * ```
800
- */
801
- function getOpenCloseForTradingDay(dateStr) {
802
- // Build a UTC midnight anchor for the date, then derive the NY offset for that day.
803
- const utcAnchor = new Date(`${dateStr}T00:00:00Z`);
804
- const nyOffset = getNYTimeZone(utcAnchor); // '-04:00' | '-05:00'
805
- // Create a NY-local noon date to avoid DST midnight transitions.
806
- const nyNoon = new Date(`${dateStr}T12:00:00${nyOffset}`);
807
- if (!isMarketDay(nyNoon)) {
808
- throw new Error(`Not a market day in ET: ${dateStr}`);
809
- }
810
- const { open, close } = getMarketOpenClose({ date: nyNoon });
811
- if (!open || !close) {
812
- throw new Error(`No market times available for ${dateStr}`);
813
- }
814
- return { open, close };
815
- }
816
- /**
817
- * Converts any date to the market time zone (America/New_York, Eastern Time).
818
- * Returns a new Date object representing the same moment in time but adjusted to NY/Eastern timezone.
819
- * Automatically handles daylight saving time transitions (EST/EDT).
820
- *
821
- * @param date - Date object to convert to market time zone
822
- * @returns Date object in NY/Eastern time zone
823
- * @example
824
- * ```typescript
825
- * const utcDate = new Date('2024-01-15T15:30:00Z'); // 3:30 PM UTC
826
- * const nyDate = convertDateToMarketTimeZone(utcDate); // 10:30 AM EST (winter) or 11:30 AM EDT (summer)
827
- * ```
828
- */
829
- function convertDateToMarketTimeZone(date) {
830
- return toNYTime(date);
831
- }
832
- /**
833
- * Returns the current market status for a given date.
834
- * @param options - { date?: Date }
835
- * @returns MarketStatus object
836
- */
837
- function getMarketStatus(options = {}) {
838
- const { date = new Date() } = options;
839
- return getMarketStatusImpl(date);
840
- }
841
- /**
842
- * Checks if a date is a market day.
843
- * @param date - Date object
844
- * @returns true if market day, false otherwise
845
- */
846
- function isMarketDay(date) {
847
- const calendar = new MarketCalendar();
848
- return calendar.isMarketDay(date);
849
- }
850
- /**
851
- * Returns full trading days from market open to market close.
852
- * endDate is always the most recent market close (previous day's close if before open, today's close if after open).
853
- * days: 1 or not specified = that day's open; 2 = previous market day's open, etc.
854
- */
855
- /**
856
- * Returns full trading days from market open to market close.
857
- * @param options - { endDate?: Date, days?: number }
858
- * @returns Object with startDate and endDate
859
- */
860
- function getTradingStartAndEndDates(options = {}) {
861
- const { endDate = new Date(), days = 1 } = options;
862
- const calendar = new MarketCalendar();
863
- // Find the most recent market close
864
- let endMarketDay = endDate;
865
- const nyEnd = toNYTime(endDate);
866
- const marketOpenMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
867
- const marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
868
- const minutes = nyEnd.getUTCHours() * 60 + nyEnd.getUTCMinutes();
869
- if (!calendar.isMarketDay(endDate) ||
870
- minutes < marketOpenMinutes ||
871
- (minutes >= marketOpenMinutes && minutes < marketCloseMinutes)) {
872
- // Before market open, not a market day, or during market hours: use previous market day
873
- endMarketDay = calendar.getPreviousMarketDay(endDate);
874
- }
875
- else {
876
- // After market close: use today
877
- endMarketDay = endDate;
878
- }
879
- // Get market close for endMarketDay
880
- const endClose = getMarketOpenClose({ date: endMarketDay }).close;
881
- // Find start market day by iterating back over market days
882
- let startMarketDay = endMarketDay;
883
- let count = Math.max(1, days);
884
- for (let i = 1; i < count; i++) {
885
- startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
886
- }
887
- // If days > 1, we need to go back (days-1) market days from endMarketDay
888
- if (days > 1) {
889
- startMarketDay = endMarketDay;
890
- for (let i = 1; i < days; i++) {
891
- startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
892
- }
893
- }
894
- const startOpen = getMarketOpenClose({ date: startMarketDay }).open;
895
- return { startDate: startOpen, endDate: endClose };
896
- }
897
- /**
898
- * Counts trading time between two dates (passed as standard Date objects), excluding weekends and holidays, and closed market hours, using other functions in this library.
899
- *
900
- * This function calculates the actual trading time between two dates by:
901
- * 1. Iterating through each calendar day between startDate and endDate (inclusive)
902
- * 2. For each day that is a market day (not weekend/holiday), getting market open/close times
903
- * 3. Calculating the overlap between the time range and market hours for that day
904
- * 4. Summing up all the trading minutes across all days
905
- *
906
- * The function automatically handles:
907
- * - Weekends (Saturday/Sunday) - skipped entirely
908
- * - Market holidays - skipped entirely
909
- * - Early close days (e.g. day before holidays) - uses early close time
910
- * - Times outside market hours - only counts time within 9:30am-4pm ET (or early close)
911
- *
912
- * Examples:
913
- * - 12pm to 3:30pm same day = 3.5 hours = 210 minutes = 0.54 days
914
- * - 9:30am to 4pm same day = 6.5 hours = 390 minutes = 1 day
915
- * - Friday 2pm to Monday 2pm = 6.5 hours (Friday 2pm-4pm + Monday 9:30am-2pm)
916
- *
917
- * @param startDate - Start date/time
918
- * @param endDate - End date/time (default: now)
919
- * @returns Object containing:
920
- * - days: Trading time as fraction of full trading days (6.5 hours = 1 day)
921
- * - hours: Trading time in hours
922
- * - minutes: Trading time in minutes
923
- */
924
- function countTradingDays(startDate, endDate = new Date()) {
925
- const calendar = new MarketCalendar();
926
- // Ensure start is before end
927
- if (startDate.getTime() > endDate.getTime()) {
928
- throw new Error('Start date must be before end date');
929
- }
930
- let totalMinutes = 0;
931
- // Get the NY dates for iteration
932
- const startNY = toNYTime(startDate);
933
- const endNY = toNYTime(endDate);
934
- // Create date at start of first day (in NY time)
935
- const currentNY = new Date(Date.UTC(startNY.getUTCFullYear(), startNY.getUTCMonth(), startNY.getUTCDate(), 0, 0, 0, 0));
936
- // Iterate through each calendar day
937
- while (currentNY.getTime() <= endNY.getTime()) {
938
- const currentUTC = fromNYTime(currentNY);
939
- // Check if this is a market day
940
- if (calendar.isMarketDay(currentUTC)) {
941
- // Get market hours for this day
942
- const marketTimes = getMarketTimes(currentUTC);
943
- if (marketTimes.marketOpen && marketTimes.open && marketTimes.close) {
944
- // Calculate the overlap between our time range and market hours
945
- const dayStart = Math.max(startDate.getTime(), marketTimes.open.getTime());
946
- const dayEnd = Math.min(endDate.getTime(), marketTimes.close.getTime());
947
- // Only count if there's actual overlap
948
- if (dayStart < dayEnd) {
949
- totalMinutes += (dayEnd - dayStart) / (1000 * 60);
950
- }
951
- }
952
- }
953
- // Move to next day
954
- currentNY.setUTCDate(currentNY.getUTCDate() + 1);
955
- }
956
- // Convert to days, hours, minutes
957
- const MINUTES_PER_TRADING_DAY = 390; // 6.5 hours
958
- const days = totalMinutes / MINUTES_PER_TRADING_DAY;
959
- const hours = totalMinutes / 60;
960
- const minutes = totalMinutes;
961
- return {
962
- days: Math.round(days * 1000) / 1000, // Round to 3 decimal places
963
- hours: Math.round(hours * 100) / 100, // Round to 2 decimal places
964
- minutes: Math.round(minutes),
965
- };
966
- }
967
- /**
968
- * Returns the trading day N days back from a reference date, along with its market open time.
969
- * Trading days are counted as full or half trading days (days that end count as 1 full trading day).
970
- * By default, the most recent completed trading day counts as day 1.
971
- * Set includeMostRecentFullDay to false to count strictly before that day.
972
- *
973
- * @param options - Object with:
974
- * - referenceDate: Date to count back from (default: now)
975
- * - days: Number of trading days to go back (must be an integer >= 1)
976
- * - includeMostRecentFullDay: Whether to include the most recent completed trading day (default: true)
977
- * @returns Object containing:
978
- * - date: Trading date in YYYY-MM-DD format
979
- * - marketOpenISO: Market open time as ISO string (e.g., "2025-11-15T13:30:00.000Z")
980
- * - unixTimestamp: Market open time as Unix timestamp in seconds
981
- * @example
982
- * ```typescript
983
- * // Get the trading day 1 day back (most recent full trading day)
984
- * const result = getTradingDaysBack({ days: 1 });
985
- * console.log(result.date); // "2025-11-01"
986
- * console.log(result.marketOpenISO); // "2025-11-01T13:30:00.000Z"
987
- * console.log(result.unixTimestamp); // 1730466600
988
- *
989
- * // Get the trading day 5 days back from a specific date
990
- * const result2 = getTradingDaysBack({
991
- * referenceDate: new Date('2025-11-15T12:00:00-05:00'),
992
- * days: 5
993
- * });
994
- * ```
995
- */
996
- function getTradingDaysBack(options) {
997
- const calendar = new MarketCalendar();
998
- const { referenceDate, days, includeMostRecentFullDay = true } = options;
999
- const refDate = referenceDate || new Date();
1000
- const daysBack = days;
1001
- if (!Number.isInteger(daysBack) || daysBack < 1) {
1002
- throw new Error('days must be an integer >= 1');
1003
- }
1004
- // Start from the last full trading date relative to reference
1005
- let targetDate = getLastFullTradingDateImpl(refDate);
1006
- if (!includeMostRecentFullDay) {
1007
- targetDate = calendar.getPreviousMarketDay(targetDate);
1008
- }
1009
- // Go back the specified number of days (we're already at day 1, so go back days-1 more)
1010
- for (let i = 1; i < daysBack; i++) {
1011
- targetDate = calendar.getPreviousMarketDay(targetDate);
1012
- }
1013
- // Get market open time for this date
1014
- const marketTimes = getMarketTimes(targetDate);
1015
- if (!marketTimes.open) {
1016
- throw new Error(`No market open time for target date`);
1017
- }
1018
- // Format the date string (YYYY-MM-DD) in NY time
1019
- const nyDate = toNYTime(marketTimes.open);
1020
- const dateStr = `${nyDate.getUTCFullYear()}-${String(nyDate.getUTCMonth() + 1).padStart(2, '0')}-${String(nyDate.getUTCDate()).padStart(2, '0')}`;
1021
- const marketOpenISO = marketTimes.open.toISOString();
1022
- const unixTimestamp = Math.floor(marketTimes.open.getTime() / 1000);
1023
- return {
1024
- date: dateStr,
1025
- marketOpenISO,
1026
- unixTimestamp,
1027
- };
1028
- }
1029
- // Export MARKET_TIMES for compatibility
1030
- const MARKET_TIMES = {
1031
- TIMEZONE: MARKET_CONFIG.TIMEZONE,
1032
- PRE: {
1033
- START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
1034
- END: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
1035
- },
1036
- EARLY_MORNING: {
1037
- START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
1038
- END: { HOUR: 10, MINUTE: 0, MINUTES: 600 },
1039
- },
1040
- EARLY_CLOSE_BEFORE_HOLIDAY: {
1041
- START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
1042
- END: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
1043
- },
1044
- EARLY_EXTENDED_BEFORE_HOLIDAY: {
1045
- START: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
1046
- END: { HOUR: 17, MINUTE: 0, MINUTES: 1020 },
1047
- },
1048
- REGULAR: {
1049
- START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
1050
- END: { HOUR: 16, MINUTE: 0, MINUTES: 960 },
1051
- },
1052
- EXTENDED: {
1053
- START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
1054
- END: { HOUR: 20, MINUTE: 0, MINUTES: 1200 },
1055
- },
1056
- };
1057
-
1058
- // format-tools.ts
1059
- /**
1060
- * Capitalizes the first letter of a string
1061
- * @param {string} str - The string to capitalize
1062
- * @returns {string} The capitalized string, or original value if not a string
1063
- * @example
1064
- * capitalize('hello') // 'Hello'
1065
- * capitalize(123) // 123
1066
- */
1067
- function capFirstLetter(str) {
1068
- if (!str || typeof str !== 'string')
1069
- return str;
1070
- return str.charAt(0).toUpperCase() + str.slice(1);
1071
- }
1072
- /**
1073
- * Formats a number as US currency
1074
- * @param {number} value - The number to format
1075
- * @returns {string} The formatted currency string (e.g. '$1,234.56')
1076
- * @example
1077
- * formatCurrency(1234.56) // '$1,234.56'
1078
- * formatCurrency(NaN) // '$0.00'
1079
- */
1080
- function formatCurrency(value) {
1081
- if (isNaN(value)) {
1082
- return '$0.00';
1083
- }
1084
- return new Intl.NumberFormat('en-US', {
1085
- style: 'currency',
1086
- currency: 'USD',
1087
- }).format(value);
1088
- }
1089
- /**
1090
- * Formats a number with commas
1091
- * @param {number} value - The number to format
1092
- * @returns {string} The formatted number string (e.g. '1,234.56')
1093
- * @example
1094
- * formatNumber(1234.56) // '1,234.56'
1095
- * formatNumber(NaN) // '0'
1096
- */
1097
- function formatNumber(value) {
1098
- if (isNaN(value)) {
1099
- return '0';
1100
- }
1101
- return new Intl.NumberFormat('en-US').format(value);
1102
- }
1103
- /**
1104
- * Formats a number as a percentage
1105
- * @param {number} value - The number to format (e.g. 0.75 for 75%)
1106
- * @param {number} [decimalPlaces=2] - Number of decimal places to show
1107
- * @returns {string} The formatted percentage string (e.g. '75.00%')
1108
- * @example
1109
- * formatPercentage(0.75) // '75.00%'
1110
- * formatPercentage(0.753, 1) // '75.3%'
1111
- */
1112
- function formatPercentage(value, decimalPlaces = 2) {
1113
- if (isNaN(value)) {
1114
- return '0%';
1115
- }
1116
- return new Intl.NumberFormat('en-US', {
1117
- style: 'percent',
1118
- minimumFractionDigits: decimalPlaces,
1119
- }).format(value);
1120
- }
1121
- /**
1122
- * Formats a Date object to Australian datetime format for Google Sheets
1123
- * @param {Date} date - The date to format
1124
- * @returns {string} The formatted datetime string in 'DD/MM/YYYY HH:MM:SS' format
1125
- * @example
1126
- * dateTimeForGS(new Date('2025-01-01T12:34:56')) // '01/01/2025 12:34:56'
1127
- */
1128
- function dateTimeForGS(date) {
1129
- return date
1130
- .toLocaleString('en-AU', {
1131
- day: '2-digit',
1132
- month: '2-digit',
1133
- year: 'numeric',
1134
- hour: '2-digit',
1135
- minute: '2-digit',
1136
- second: '2-digit',
1137
- hour12: false,
1138
- })
1139
- .replace(/\./g, '/');
1140
- }
1141
368
 
1142
369
  /**
1143
370
  * Type guard to check if a model is an OpenRouter model
@@ -1151,6 +378,8 @@ function isOpenRouterModel(model) {
1151
378
  'openai/gpt-5.1',
1152
379
  'openai/gpt-5.4',
1153
380
  'openai/gpt-5.4-pro',
381
+ 'openai/gpt-5.5',
382
+ 'openai/gpt-5.5-pro',
1154
383
  'openai/gpt-5.2',
1155
384
  'openai/gpt-5.2-pro',
1156
385
  'openai/gpt-5.1-codex',
@@ -1166,250 +395,32 @@ function isOpenRouterModel(model) {
1166
395
  return openRouterModels.includes(model);
1167
396
  }
1168
397
 
1169
- var Types = /*#__PURE__*/Object.freeze({
1170
- __proto__: null,
1171
- isOpenRouterModel: isOpenRouterModel
1172
- });
398
+ function __classPrivateFieldSet(receiver, state, value, kind, f) {
399
+ if (typeof state === "function" ? receiver !== state || true : !state.has(receiver))
400
+ throw new TypeError("Cannot write private member to an object whose class did not declare it");
401
+ return state.set(receiver, value), value;
402
+ }
403
+ function __classPrivateFieldGet(receiver, state, kind, f) {
404
+ if (kind === "a" && !f)
405
+ throw new TypeError("Private accessor was defined without a getter");
406
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
407
+ throw new TypeError("Cannot read private member from an object whose class did not declare it");
408
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
409
+ }
1173
410
 
1174
- // Utility function for debug logging
1175
- // Define the possible log types as a const array for better type inference
411
+ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
1176
412
  /**
1177
- * Debug logging utility that respects environment debug flags.
1178
- * Logs messages to the console based on the specified log level.
1179
- *
1180
- * @param message - The message to log.
1181
- * @param data - Optional data to log alongside the message. This can be any type of data.
1182
- * @param type - Log level. One of: 'info' | 'warn' | 'error' | 'debug' | 'trace'. Defaults to 'info'.
1183
- *
1184
- * @example
1185
- * logIfDebug("User login failed", { userId: 123 }, "error");
1186
- * logIfDebug("Cache miss", undefined, "warn");
1187
- * logIfDebug("Processing request", { requestId: "abc" }, "debug");
413
+ * https://stackoverflow.com/a/2117523
1188
414
  */
1189
- const logIfDebug = (message, data, type = 'info') => {
1190
- const prefix = `[DEBUG][${type.toUpperCase()}]`;
1191
- const formattedData = data !== undefined ? JSON.stringify(data, null, 2) : '';
1192
- switch (type) {
1193
- case 'error':
1194
- console.error(prefix, message, formattedData);
1195
- break;
1196
- case 'warn':
1197
- console.warn(prefix, message, formattedData);
1198
- break;
1199
- case 'debug':
1200
- console.debug(prefix, message, formattedData);
1201
- break;
1202
- case 'trace':
1203
- console.trace(prefix, message, formattedData);
1204
- break;
1205
- case 'info':
1206
- default:
1207
- console.info(prefix, message, formattedData);
415
+ let uuid4 = function () {
416
+ const { crypto } = globalThis;
417
+ if (crypto?.randomUUID) {
418
+ uuid4 = crypto.randomUUID.bind(crypto);
419
+ return crypto.randomUUID();
1208
420
  }
1209
- };
1210
- /**
1211
- * Masks the middle part of an API key, returning only the first 2 and last 2 characters.
1212
- * If the API key is very short (<= 4 characters), it will be returned as is.
1213
- *
1214
- * @param keyValue - The API key to mask.
1215
- * @returns The masked API key.
1216
- *
1217
- * @example
1218
- * maskApiKey("12341239856677"); // Returns "12****77"
1219
- */
1220
- function maskApiKey(keyValue) {
1221
- if (keyValue.length <= 4) {
1222
- return keyValue;
1223
- }
1224
- const firstTwo = keyValue.slice(0, 2);
1225
- const lastTwo = keyValue.slice(-2);
1226
- return `${firstTwo}****${lastTwo}`;
1227
- }
1228
- /**
1229
- * Hides (masks) the value of any query parameter that is "apiKey" (case-insensitive),
1230
- * replacing the middle part with **** and keeping only the first 2 and last 2 characters.
1231
- *
1232
- * @param url - The URL containing the query parameters.
1233
- * @returns The URL with the masked API key.
1234
- *
1235
- * @example
1236
- * hideApiKeyFromurl("https://xxx.com/s/23/fdsa/?apiKey=12341239856677");
1237
- * // Returns "https://xxx.com/s/23/fdsa/?apiKey=12****77"
1238
- */
1239
- function hideApiKeyFromurl(url) {
1240
- try {
1241
- const parsedUrl = new URL(url);
1242
- // We iterate over all search params and look for one named 'apikey' (case-insensitive)
1243
- for (const [key, value] of parsedUrl.searchParams.entries()) {
1244
- if (key.toLowerCase() === 'apikey') {
1245
- const masked = maskApiKey(value);
1246
- parsedUrl.searchParams.set(key, masked);
1247
- }
1248
- }
1249
- return parsedUrl.toString();
1250
- }
1251
- catch {
1252
- // If we can't parse it as a valid URL, just return the original string
1253
- return url;
1254
- }
1255
- }
1256
- /**
1257
- * Extracts meaningful error information from various error types.
1258
- * @param error - The error to analyze.
1259
- * @param response - Optional response object for HTTP errors.
1260
- * @returns Structured error details.
1261
- */
1262
- function extractErrorDetails(error, response) {
1263
- const errMsg = error instanceof Error ? error.message : String(error);
1264
- const errName = error instanceof Error ? error.name : 'Error';
1265
- if (errName === 'TypeError' && errMsg.includes('fetch')) {
1266
- return { type: 'NETWORK_ERROR', reason: 'Network connectivity issue', status: null };
1267
- }
1268
- if (errMsg.includes('HTTP error: 429')) {
1269
- const match = errMsg.match(/RATE_LIMIT: 429:(\d+)/);
1270
- const retryAfter = match ? parseInt(match[1]) : undefined;
1271
- return { type: 'RATE_LIMIT', reason: 'Rate limit exceeded', status: 429, retryAfter };
1272
- }
1273
- if (errMsg.includes('HTTP error: 401') || errMsg.includes('AUTH_ERROR: 401')) {
1274
- return { type: 'AUTH_ERROR', reason: 'Authentication failed - invalid API key', status: 401 };
1275
- }
1276
- if (errMsg.includes('HTTP error: 403') || errMsg.includes('AUTH_ERROR: 403')) {
1277
- return { type: 'AUTH_ERROR', reason: 'Access forbidden - insufficient permissions', status: 403 };
1278
- }
1279
- if (errMsg.includes('SERVER_ERROR:')) {
1280
- const status = parseInt(errMsg.split('SERVER_ERROR: ')[1]) || 500;
1281
- return { type: 'SERVER_ERROR', reason: `Server error (${status})`, status };
1282
- }
1283
- if (errMsg.includes('CLIENT_ERROR:')) {
1284
- const status = parseInt(errMsg.split('CLIENT_ERROR: ')[1]) || 400;
1285
- return { type: 'CLIENT_ERROR', reason: `Client error (${status})`, status };
1286
- }
1287
- return { type: 'UNKNOWN', reason: errMsg || 'Unknown error', status: null };
1288
- }
1289
- /**
1290
- * Fetches a resource with intelligent retry logic for handling transient errors.
1291
- * Features enhanced error logging, rate limit detection, and adaptive backoff.
1292
- *
1293
- * @param url - The URL to fetch.
1294
- * @param options - Optional fetch options.
1295
- * @param retries - The number of retry attempts. Defaults to 3.
1296
- * @param initialBackoff - The initial backoff time in milliseconds. Defaults to 1000.
1297
- * @returns A promise that resolves to the response.
1298
- *
1299
- * @throws Will throw an error if the fetch fails after the specified number of retries.
1300
- */
1301
- async function fetchWithRetry(url, options = {}, retries = 3, initialBackoff = 1000) {
1302
- let backoff = initialBackoff;
1303
- for (let attempt = 1; attempt <= retries; attempt++) {
1304
- try {
1305
- const response = await fetch(url, options);
1306
- if (!response.ok) {
1307
- // Enhanced HTTP error handling with specific error types
1308
- if (response.status === 429) {
1309
- // Check for Retry-After header
1310
- const retryAfter = response.headers.get('Retry-After');
1311
- const retryDelay = retryAfter ? parseInt(retryAfter) * 1000 : null;
1312
- throw new Error(`RATE_LIMIT: ${response.status}${retryDelay ? `:${retryDelay}` : ''}`);
1313
- }
1314
- if ([500, 502, 503, 504].includes(response.status)) {
1315
- throw new Error(`SERVER_ERROR: ${response.status}`);
1316
- }
1317
- if ([401, 403].includes(response.status)) {
1318
- throw new Error(`AUTH_ERROR: ${response.status}`);
1319
- }
1320
- if (response.status >= 400 && response.status < 500) {
1321
- // Don't retry most 4xx client errors
1322
- throw new Error(`CLIENT_ERROR: ${response.status}`);
1323
- }
1324
- throw new Error(`HTTP_ERROR: ${response.status}`);
1325
- }
1326
- return response;
1327
- }
1328
- catch (error) {
1329
- if (attempt === retries) {
1330
- throw error;
1331
- }
1332
- // Extract meaningful error information
1333
- const errorDetails = extractErrorDetails(error);
1334
- let adaptiveBackoff = backoff;
1335
- // Adaptive backoff based on error type
1336
- if (errorDetails.type === 'RATE_LIMIT') {
1337
- // Use Retry-After header if available, otherwise use minimum 5s for rate limits
1338
- if (errorDetails.retryAfter) {
1339
- adaptiveBackoff = errorDetails.retryAfter;
1340
- }
1341
- else {
1342
- adaptiveBackoff = Math.max(backoff, 5000);
1343
- }
1344
- }
1345
- else if (errorDetails.type === 'AUTH_ERROR') {
1346
- // Don't retry auth errors - fail fast
1347
- console.error(`Authentication error for ${hideApiKeyFromurl(url)}: ${errorDetails.reason}`, {
1348
- attemptNumber: attempt,
1349
- errorType: errorDetails.type,
1350
- httpStatus: errorDetails.status,
1351
- url: hideApiKeyFromurl(url),
1352
- source: 'fetchWithRetry',
1353
- timestamp: new Date().toISOString(),
1354
- });
1355
- throw error;
1356
- }
1357
- else if (errorDetails.type === 'CLIENT_ERROR') {
1358
- // Don't retry client errors (except 429 which is handled above)
1359
- console.error(`Client error for ${hideApiKeyFromurl(url)}: ${errorDetails.reason}`, {
1360
- attemptNumber: attempt,
1361
- errorType: errorDetails.type,
1362
- httpStatus: errorDetails.status,
1363
- url: hideApiKeyFromurl(url),
1364
- source: 'fetchWithRetry',
1365
- timestamp: new Date().toISOString(),
1366
- });
1367
- throw error;
1368
- }
1369
- // Enhanced error logging with structured data
1370
- console.warn(`Fetch attempt ${attempt} of ${retries} for ${hideApiKeyFromurl(url)} failed: ${errorDetails.reason}. Retrying in ${adaptiveBackoff}ms...`, {
1371
- attemptNumber: attempt,
1372
- totalRetries: retries,
1373
- errorType: errorDetails.type,
1374
- httpStatus: errorDetails.status,
1375
- retryDelay: adaptiveBackoff,
1376
- url: hideApiKeyFromurl(url),
1377
- source: 'fetchWithRetry',
1378
- timestamp: new Date().toISOString(),
1379
- });
1380
- await new Promise((resolve) => setTimeout(resolve, adaptiveBackoff));
1381
- backoff = Math.min(backoff * 2, 30000); // Cap at 30 seconds
1382
- }
1383
- }
1384
- throw new Error('Failed to fetch after multiple attempts');
1385
- }
1386
-
1387
- function __classPrivateFieldSet(receiver, state, value, kind, f) {
1388
- if (typeof state === "function" ? receiver !== state || true : !state.has(receiver))
1389
- throw new TypeError("Cannot write private member to an object whose class did not declare it");
1390
- return state.set(receiver, value), value;
1391
- }
1392
- function __classPrivateFieldGet(receiver, state, kind, f) {
1393
- if (kind === "a" && !f)
1394
- throw new TypeError("Private accessor was defined without a getter");
1395
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
1396
- throw new TypeError("Cannot read private member from an object whose class did not declare it");
1397
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
1398
- }
1399
-
1400
- // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
1401
- /**
1402
- * https://stackoverflow.com/a/2117523
1403
- */
1404
- let uuid4 = function () {
1405
- const { crypto } = globalThis;
1406
- if (crypto?.randomUUID) {
1407
- uuid4 = crypto.randomUUID.bind(crypto);
1408
- return crypto.randomUUID();
1409
- }
1410
- const u8 = new Uint8Array(1);
1411
- const randomByte = crypto ? () => crypto.getRandomValues(u8)[0] : () => (Math.random() * 0xff) & 0xff;
1412
- return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16));
421
+ const u8 = new Uint8Array(1);
422
+ const randomByte = crypto ? () => crypto.getRandomValues(u8)[0] : () => (Math.random() * 0xff) & 0xff;
423
+ return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16));
1413
424
  };
1414
425
 
1415
426
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
@@ -1646,7 +657,7 @@ const safeJSON = (text) => {
1646
657
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
1647
658
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1648
659
 
1649
- const VERSION = '6.34.0'; // x-release-please-version
660
+ const VERSION = '6.35.0'; // x-release-please-version
1650
661
 
1651
662
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
1652
663
  const isRunningInBrowser = () => {
@@ -4974,8 +3985,8 @@ class Speech extends APIResource {
4974
3985
  * ```ts
4975
3986
  * const speech = await client.audio.speech.create({
4976
3987
  * input: 'input',
4977
- * model: 'string',
4978
- * voice: 'string',
3988
+ * model: 'tts-1',
3989
+ * voice: 'alloy',
4979
3990
  * });
4980
3991
  *
4981
3992
  * const content = await speech.blob();
@@ -5453,15 +4464,15 @@ const toFloat32Array = (base64Str) => {
5453
4464
  */
5454
4465
  const readEnv = (env) => {
5455
4466
  if (typeof globalThis.process !== 'undefined') {
5456
- return globalThis.process.env?.[env]?.trim() ?? undefined;
4467
+ return globalThis.process.env?.[env]?.trim() || undefined;
5457
4468
  }
5458
4469
  if (typeof globalThis.Deno !== 'undefined') {
5459
- return globalThis.Deno.env?.get?.(env)?.trim();
4470
+ return globalThis.Deno.env?.get?.(env)?.trim() || undefined;
5460
4471
  }
5461
4472
  return undefined;
5462
4473
  };
5463
4474
 
5464
- var _AssistantStream_instances, _a$2, _AssistantStream_events, _AssistantStream_runStepSnapshots, _AssistantStream_messageSnapshots, _AssistantStream_messageSnapshot, _AssistantStream_finalRun, _AssistantStream_currentContentIndex, _AssistantStream_currentContent, _AssistantStream_currentToolCallIndex, _AssistantStream_currentToolCall, _AssistantStream_currentEvent, _AssistantStream_currentRunSnapshot, _AssistantStream_currentRunStepSnapshot, _AssistantStream_addEvent, _AssistantStream_endRequest, _AssistantStream_handleMessage, _AssistantStream_handleRunStep, _AssistantStream_handleEvent, _AssistantStream_accumulateRunStep, _AssistantStream_accumulateMessage, _AssistantStream_accumulateContent, _AssistantStream_handleRun;
4475
+ var _AssistantStream_instances, _a$3, _AssistantStream_events, _AssistantStream_runStepSnapshots, _AssistantStream_messageSnapshots, _AssistantStream_messageSnapshot, _AssistantStream_finalRun, _AssistantStream_currentContentIndex, _AssistantStream_currentContent, _AssistantStream_currentToolCallIndex, _AssistantStream_currentToolCall, _AssistantStream_currentEvent, _AssistantStream_currentRunSnapshot, _AssistantStream_currentRunStepSnapshot, _AssistantStream_addEvent, _AssistantStream_endRequest, _AssistantStream_handleMessage, _AssistantStream_handleRunStep, _AssistantStream_handleEvent, _AssistantStream_accumulateRunStep, _AssistantStream_accumulateMessage, _AssistantStream_accumulateContent, _AssistantStream_handleRun;
5465
4476
  class AssistantStream extends EventStream {
5466
4477
  constructor() {
5467
4478
  super(...arguments);
@@ -5536,7 +4547,7 @@ class AssistantStream extends EventStream {
5536
4547
  };
5537
4548
  }
5538
4549
  static fromReadableStream(stream) {
5539
- const runner = new _a$2();
4550
+ const runner = new _a$3();
5540
4551
  runner._run(() => runner._fromReadableStream(stream));
5541
4552
  return runner;
5542
4553
  }
@@ -5562,7 +4573,7 @@ class AssistantStream extends EventStream {
5562
4573
  return stream.toReadableStream();
5563
4574
  }
5564
4575
  static createToolAssistantStream(runId, runs, params, options) {
5565
- const runner = new _a$2();
4576
+ const runner = new _a$3();
5566
4577
  runner._run(() => runner._runToolAssistantStream(runId, runs, params, {
5567
4578
  ...options,
5568
4579
  headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
@@ -5591,7 +4602,7 @@ class AssistantStream extends EventStream {
5591
4602
  return this._addRun(__classPrivateFieldGet(this, _AssistantStream_instances, "m", _AssistantStream_endRequest).call(this));
5592
4603
  }
5593
4604
  static createThreadAssistantStream(params, thread, options) {
5594
- const runner = new _a$2();
4605
+ const runner = new _a$3();
5595
4606
  runner._run(() => runner._threadAssistantStream(params, thread, {
5596
4607
  ...options,
5597
4608
  headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
@@ -5599,7 +4610,7 @@ class AssistantStream extends EventStream {
5599
4610
  return runner;
5600
4611
  }
5601
4612
  static createAssistantStream(threadId, runs, params, options) {
5602
- const runner = new _a$2();
4613
+ const runner = new _a$3();
5603
4614
  runner._run(() => runner._runAssistantStream(threadId, runs, params, {
5604
4615
  ...options,
5605
4616
  headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
@@ -5741,7 +4752,7 @@ class AssistantStream extends EventStream {
5741
4752
  return await this._createToolAssistantStream(runs, runId, params, options);
5742
4753
  }
5743
4754
  }
5744
- _a$2 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_addEvent(event) {
4755
+ _a$3 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_addEvent(event) {
5745
4756
  if (this.ended)
5746
4757
  return;
5747
4758
  __classPrivateFieldSet(this, _AssistantStream_currentEvent, event);
@@ -5919,7 +4930,7 @@ _a$2 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_ad
5919
4930
  }
5920
4931
  let data = event.data;
5921
4932
  if (data.delta) {
5922
- const accumulated = _a$2.accumulateDelta(snapshot, data.delta);
4933
+ const accumulated = _a$3.accumulateDelta(snapshot, data.delta);
5923
4934
  __classPrivateFieldGet(this, _AssistantStream_runStepSnapshots, "f")[event.data.id] = accumulated;
5924
4935
  }
5925
4936
  return __classPrivateFieldGet(this, _AssistantStream_runStepSnapshots, "f")[event.data.id];
@@ -5973,7 +4984,7 @@ _a$2 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_ad
5973
4984
  }
5974
4985
  throw Error('Tried to accumulate a non-message event');
5975
4986
  }, _AssistantStream_accumulateContent = function _AssistantStream_accumulateContent(contentElement, currentContent) {
5976
- return _a$2.accumulateDelta(currentContent, contentElement);
4987
+ return _a$3.accumulateDelta(currentContent, contentElement);
5977
4988
  }, _AssistantStream_handleRun = function _AssistantStream_handleRun(event) {
5978
4989
  __classPrivateFieldSet(this, _AssistantStream_currentRunSnapshot, event.data);
5979
4990
  switch (event.event) {
@@ -8416,7 +7427,7 @@ _Webhooks_instances = new WeakSet(), _Webhooks_validateSecret = function _Webhoo
8416
7427
  };
8417
7428
 
8418
7429
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
8419
- var _OpenAI_instances, _a$1, _OpenAI_encoder, _OpenAI_baseURLOverridden;
7430
+ var _OpenAI_instances, _a$2, _OpenAI_encoder, _OpenAI_baseURLOverridden;
8420
7431
  const WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER = 'workload-identity-auth';
8421
7432
  /**
8422
7433
  * API Client for interfacing with the OpenAI API.
@@ -8515,7 +7526,7 @@ class OpenAI {
8515
7526
  throw new OpenAIError("It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew OpenAI({ apiKey, dangerouslyAllowBrowser: true });\n\nhttps://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n");
8516
7527
  }
8517
7528
  this.baseURL = options.baseURL;
8518
- this.timeout = options.timeout ?? _a$1.DEFAULT_TIMEOUT /* 10 minutes */;
7529
+ this.timeout = options.timeout ?? _a$2.DEFAULT_TIMEOUT /* 10 minutes */;
8519
7530
  this.logger = options.logger ?? console;
8520
7531
  const defaultLogLevel = 'warn';
8521
7532
  // Set default logLevel early so that we can log a warning in parseLogLevel.
@@ -8999,10 +8010,10 @@ class OpenAI {
8999
8010
  }
9000
8011
  }
9001
8012
  }
9002
- _a$1 = OpenAI, _OpenAI_encoder = new WeakMap(), _OpenAI_instances = new WeakSet(), _OpenAI_baseURLOverridden = function _OpenAI_baseURLOverridden() {
8013
+ _a$2 = OpenAI, _OpenAI_encoder = new WeakMap(), _OpenAI_instances = new WeakSet(), _OpenAI_baseURLOverridden = function _OpenAI_baseURLOverridden() {
9003
8014
  return this.baseURL !== 'https://api.openai.com/v1';
9004
8015
  };
9005
- OpenAI.OpenAI = _a$1;
8016
+ OpenAI.OpenAI = _a$2;
9006
8017
  OpenAI.DEFAULT_TIMEOUT = 600000; // 10 minutes
9007
8018
  OpenAI.OpenAIError = OpenAIError;
9008
8019
  OpenAI.APIError = APIError;
@@ -9042,7 +8053,7 @@ OpenAI.Containers = Containers;
9042
8053
  OpenAI.Skills = Skills;
9043
8054
  OpenAI.Videos = Videos;
9044
8055
 
9045
- /** A special constant with type `never` */
8056
+ var _a$1;
9046
8057
  function $constructor(name, initializer, params) {
9047
8058
  function init(inst, def) {
9048
8059
  if (!inst._zod) {
@@ -9107,12 +8118,12 @@ class $ZodEncodeError extends Error {
9107
8118
  this.name = "ZodEncodeError";
9108
8119
  }
9109
8120
  }
9110
- const globalConfig = {};
8121
+ (_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__zod_globalConfig = {});
8122
+ const globalConfig = globalThis.__zod_globalConfig;
9111
8123
  function config$1(newConfig) {
9112
8124
  return globalConfig;
9113
8125
  }
9114
8126
 
9115
- // functions
9116
8127
  function getEnumValues(entries) {
9117
8128
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
9118
8129
  const values = Object.entries(entries)
@@ -9144,7 +8155,7 @@ function cleanRegex(source) {
9144
8155
  const end = source.endsWith("$") ? source.length - 1 : source.length;
9145
8156
  return source.slice(start, end);
9146
8157
  }
9147
- const EVALUATING = Symbol("evaluating");
8158
+ const EVALUATING = /* @__PURE__*/ Symbol("evaluating");
9148
8159
  function defineLazy(object, key, getter) {
9149
8160
  let value = undefined;
9150
8161
  Object.defineProperty(object, key, {
@@ -9200,7 +8211,12 @@ const captureStackTrace = ("captureStackTrace" in Error ? Error.captureStackTrac
9200
8211
  function isObject$1(data) {
9201
8212
  return typeof data === "object" && data !== null && !Array.isArray(data);
9202
8213
  }
9203
- const allowsEval = cached(() => {
8214
+ const allowsEval = /* @__PURE__*/ cached(() => {
8215
+ // Skip the probe under `jitless`: strict CSPs report the caught `new Function`
8216
+ // as a `securitypolicyviolation` even though the throw is swallowed.
8217
+ if (globalConfig.jitless) {
8218
+ return false;
8219
+ }
9204
8220
  // @ts-ignore
9205
8221
  if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
9206
8222
  return false;
@@ -9238,9 +8254,13 @@ function shallowClone(o) {
9238
8254
  return { ...o };
9239
8255
  if (Array.isArray(o))
9240
8256
  return [...o];
8257
+ if (o instanceof Map)
8258
+ return new Map(o);
8259
+ if (o instanceof Set)
8260
+ return new Set(o);
9241
8261
  return o;
9242
8262
  }
9243
- const propertyKeyTypes = new Set(["string", "number", "symbol"]);
8263
+ const propertyKeyTypes = /* @__PURE__*/ new Set(["string", "number", "symbol"]);
9244
8264
  function escapeRegex(str) {
9245
8265
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9246
8266
  }
@@ -9361,6 +8381,9 @@ function safeExtend(schema, shape) {
9361
8381
  return clone(schema, def);
9362
8382
  }
9363
8383
  function merge(a, b) {
8384
+ if (a._zod.def.checks?.length) {
8385
+ throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
8386
+ }
9364
8387
  const def = mergeDefs(a._zod.def, {
9365
8388
  get shape() {
9366
8389
  const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
@@ -9370,7 +8393,7 @@ function merge(a, b) {
9370
8393
  get catchall() {
9371
8394
  return b._zod.def.catchall;
9372
8395
  },
9373
- checks: [], // delete existing checks
8396
+ checks: b._zod.def.checks ?? [],
9374
8397
  });
9375
8398
  return clone(a, def);
9376
8399
  }
@@ -9464,6 +8487,18 @@ function aborted(x, startIndex = 0) {
9464
8487
  }
9465
8488
  return false;
9466
8489
  }
8490
+ // Checks for explicit abort (continue === false), as opposed to implicit abort (continue === undefined).
8491
+ // Used to respect `abort: true` in .refine() even for checks that have a `when` function.
8492
+ function explicitlyAborted(x, startIndex = 0) {
8493
+ if (x.aborted === true)
8494
+ return true;
8495
+ for (let i = startIndex; i < x.issues.length; i++) {
8496
+ if (x.issues[i]?.continue === false) {
8497
+ return true;
8498
+ }
8499
+ }
8500
+ return false;
8501
+ }
9467
8502
  function prefixIssues(path, issues) {
9468
8503
  return issues.map((iss) => {
9469
8504
  var _a;
@@ -9476,23 +8511,20 @@ function unwrapMessage(message) {
9476
8511
  return typeof message === "string" ? message : message?.message;
9477
8512
  }
9478
8513
  function finalizeIssue(iss, ctx, config) {
9479
- const full = { ...iss, path: iss.path ?? [] };
9480
- // for backwards compatibility
9481
- if (!iss.message) {
9482
- const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ??
8514
+ const message = iss.message
8515
+ ? iss.message
8516
+ : (unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ??
9483
8517
  unwrapMessage(ctx?.error?.(iss)) ??
9484
8518
  unwrapMessage(config.customError?.(iss)) ??
9485
8519
  unwrapMessage(config.localeError?.(iss)) ??
9486
- "Invalid input";
9487
- full.message = message;
9488
- }
9489
- // delete (full as any).def;
9490
- delete full.inst;
9491
- delete full.continue;
9492
- if (!ctx?.reportInput) {
9493
- delete full.input;
8520
+ "Invalid input");
8521
+ const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;
8522
+ rest.path ?? (rest.path = []);
8523
+ rest.message = message;
8524
+ if (ctx?.reportInput) {
8525
+ rest.input = _input;
9494
8526
  }
9495
- return full;
8527
+ return rest;
9496
8528
  }
9497
8529
  function getLengthableOrigin(input) {
9498
8530
  if (Array.isArray(input))
@@ -9548,35 +8580,38 @@ function flattenError(error, mapper = (issue) => issue.message) {
9548
8580
  }
9549
8581
  function formatError(error, mapper = (issue) => issue.message) {
9550
8582
  const fieldErrors = { _errors: [] };
9551
- const processError = (error) => {
8583
+ const processError = (error, path = []) => {
9552
8584
  for (const issue of error.issues) {
9553
8585
  if (issue.code === "invalid_union" && issue.errors.length) {
9554
- issue.errors.map((issues) => processError({ issues }));
8586
+ issue.errors.map((issues) => processError({ issues }, [...path, ...issue.path]));
9555
8587
  }
9556
8588
  else if (issue.code === "invalid_key") {
9557
- processError({ issues: issue.issues });
8589
+ processError({ issues: issue.issues }, [...path, ...issue.path]);
9558
8590
  }
9559
8591
  else if (issue.code === "invalid_element") {
9560
- processError({ issues: issue.issues });
9561
- }
9562
- else if (issue.path.length === 0) {
9563
- fieldErrors._errors.push(mapper(issue));
8592
+ processError({ issues: issue.issues }, [...path, ...issue.path]);
9564
8593
  }
9565
8594
  else {
9566
- let curr = fieldErrors;
9567
- let i = 0;
9568
- while (i < issue.path.length) {
9569
- const el = issue.path[i];
9570
- const terminal = i === issue.path.length - 1;
9571
- if (!terminal) {
9572
- curr[el] = curr[el] || { _errors: [] };
9573
- }
9574
- else {
9575
- curr[el] = curr[el] || { _errors: [] };
9576
- curr[el]._errors.push(mapper(issue));
8595
+ const fullpath = [...path, ...issue.path];
8596
+ if (fullpath.length === 0) {
8597
+ fieldErrors._errors.push(mapper(issue));
8598
+ }
8599
+ else {
8600
+ let curr = fieldErrors;
8601
+ let i = 0;
8602
+ while (i < fullpath.length) {
8603
+ const el = fullpath[i];
8604
+ const terminal = i === fullpath.length - 1;
8605
+ if (!terminal) {
8606
+ curr[el] = curr[el] || { _errors: [] };
8607
+ }
8608
+ else {
8609
+ curr[el] = curr[el] || { _errors: [] };
8610
+ curr[el]._errors.push(mapper(issue));
8611
+ }
8612
+ curr = curr[el];
8613
+ i++;
9577
8614
  }
9578
- curr = curr[el];
9579
- i++;
9580
8615
  }
9581
8616
  }
9582
8617
  }
@@ -9586,7 +8621,7 @@ function formatError(error, mapper = (issue) => issue.message) {
9586
8621
  }
9587
8622
 
9588
8623
  const _parse = (_Err) => (schema, value, _ctx, _params) => {
9589
- const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
8624
+ const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
9590
8625
  const result = schema._zod.run({ value, issues: [] }, ctx);
9591
8626
  if (result instanceof Promise) {
9592
8627
  throw new $ZodAsyncError();
@@ -9599,7 +8634,7 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
9599
8634
  return result.value;
9600
8635
  };
9601
8636
  const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
9602
- const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
8637
+ const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
9603
8638
  let result = schema._zod.run({ value, issues: [] }, ctx);
9604
8639
  if (result instanceof Promise)
9605
8640
  result = await result;
@@ -9625,7 +8660,7 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
9625
8660
  };
9626
8661
  const safeParse$1 = /* @__PURE__*/ _safeParse($ZodRealError);
9627
8662
  const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
9628
- const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
8663
+ const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
9629
8664
  let result = schema._zod.run({ value, issues: [] }, ctx);
9630
8665
  if (result instanceof Promise)
9631
8666
  result = await result;
@@ -9638,35 +8673,40 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
9638
8673
  };
9639
8674
  const safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError);
9640
8675
  const _encode = (_Err) => (schema, value, _ctx) => {
9641
- const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
8676
+ const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
9642
8677
  return _parse(_Err)(schema, value, ctx);
9643
8678
  };
9644
8679
  const _decode = (_Err) => (schema, value, _ctx) => {
9645
8680
  return _parse(_Err)(schema, value, _ctx);
9646
8681
  };
9647
8682
  const _encodeAsync = (_Err) => async (schema, value, _ctx) => {
9648
- const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
8683
+ const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
9649
8684
  return _parseAsync(_Err)(schema, value, ctx);
9650
8685
  };
9651
8686
  const _decodeAsync = (_Err) => async (schema, value, _ctx) => {
9652
8687
  return _parseAsync(_Err)(schema, value, _ctx);
9653
8688
  };
9654
8689
  const _safeEncode = (_Err) => (schema, value, _ctx) => {
9655
- const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
8690
+ const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
9656
8691
  return _safeParse(_Err)(schema, value, ctx);
9657
8692
  };
9658
8693
  const _safeDecode = (_Err) => (schema, value, _ctx) => {
9659
8694
  return _safeParse(_Err)(schema, value, _ctx);
9660
8695
  };
9661
8696
  const _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
9662
- const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
8697
+ const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
9663
8698
  return _safeParseAsync(_Err)(schema, value, ctx);
9664
8699
  };
9665
8700
  const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
9666
8701
  return _safeParseAsync(_Err)(schema, value, _ctx);
9667
8702
  };
9668
8703
 
9669
- const cuid = /^[cC][^\s-]{8,}$/;
8704
+ /**
8705
+ * @deprecated CUID v1 is deprecated by its authors due to information leakage
8706
+ * (timestamps embedded in the id). Use {@link cuid2} instead.
8707
+ * See https://github.com/paralleldrive/cuid.
8708
+ */
8709
+ const cuid = /^[cC][0-9a-z]{6,}$/;
9670
8710
  const cuid2 = /^[0-9a-z]+$/;
9671
8711
  const ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
9672
8712
  const xid = /^[0-9a-vA-V]{20}$/;
@@ -9698,6 +8738,7 @@ const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?:
9698
8738
  // https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
9699
8739
  const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
9700
8740
  const base64url = /^[A-Za-z0-9_-]*$/;
8741
+ const httpProtocol = /^https?$/;
9701
8742
  // https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex sans spaces)
9702
8743
  // E.164: leading digit must be 1-9; total digits (excluding '+') between 7-15
9703
8744
  const e164 = /^\+[1-9]\d{6,14}$/;
@@ -10004,8 +9045,8 @@ class Doc {
10004
9045
 
10005
9046
  const version = {
10006
9047
  major: 4,
10007
- minor: 3,
10008
- patch: 6,
9048
+ minor: 4,
9049
+ patch: 2,
10009
9050
  };
10010
9051
 
10011
9052
  const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
@@ -10038,6 +9079,8 @@ const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
10038
9079
  let asyncResult;
10039
9080
  for (const ch of checks) {
10040
9081
  if (ch._zod.def.when) {
9082
+ if (explicitlyAborted(payload))
9083
+ continue;
10041
9084
  const shouldRun = ch._zod.def.when(payload);
10042
9085
  if (!shouldRun)
10043
9086
  continue;
@@ -10190,6 +9233,21 @@ const $ZodURL = /*@__PURE__*/ $constructor("$ZodURL", (inst, def) => {
10190
9233
  try {
10191
9234
  // Trim whitespace from input
10192
9235
  const trimmed = payload.value.trim();
9236
+ // When normalize is off, require :// for http/https URLs
9237
+ // This prevents strings like "http:example.com" or "https:/path" from being silently accepted
9238
+ if (!def.normalize && def.protocol?.source === httpProtocol.source) {
9239
+ if (!/^https?:\/\//i.test(trimmed)) {
9240
+ payload.issues.push({
9241
+ code: "invalid_format",
9242
+ format: "url",
9243
+ note: "Invalid URL format",
9244
+ input: payload.value,
9245
+ inst,
9246
+ continue: !def.abort,
9247
+ });
9248
+ return;
9249
+ }
9250
+ }
10193
9251
  // @ts-ignore
10194
9252
  const url = new URL(trimmed);
10195
9253
  if (def.hostname) {
@@ -10250,6 +9308,11 @@ const $ZodNanoID = /*@__PURE__*/ $constructor("$ZodNanoID", (inst, def) => {
10250
9308
  def.pattern ?? (def.pattern = nanoid);
10251
9309
  $ZodStringFormat.init(inst, def);
10252
9310
  });
9311
+ /**
9312
+ * @deprecated CUID v1 is deprecated by its authors due to information leakage
9313
+ * (timestamps embedded in the id). Use {@link $ZodCUID2} instead.
9314
+ * See https://github.com/paralleldrive/cuid.
9315
+ */
10253
9316
  const $ZodCUID = /*@__PURE__*/ $constructor("$ZodCUID", (inst, def) => {
10254
9317
  def.pattern ?? (def.pattern = cuid);
10255
9318
  $ZodStringFormat.init(inst, def);
@@ -10350,6 +9413,9 @@ const $ZodCIDRv6 = /*@__PURE__*/ $constructor("$ZodCIDRv6", (inst, def) => {
10350
9413
  function isValidBase64(data) {
10351
9414
  if (data === "")
10352
9415
  return true;
9416
+ // atob ignores whitespace, so reject it up front.
9417
+ if (/\s/.test(data))
9418
+ return false;
10353
9419
  if (data.length % 4 !== 0)
10354
9420
  return false;
10355
9421
  try {
@@ -10498,16 +9564,28 @@ const $ZodArray = /*@__PURE__*/ $constructor("$ZodArray", (inst, def) => {
10498
9564
  return payload; //handleArrayResultsAsync(parseResults, final);
10499
9565
  };
10500
9566
  });
10501
- function handlePropertyResult(result, final, key, input, isOptionalOut) {
9567
+ function handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) {
9568
+ const isPresent = key in input;
10502
9569
  if (result.issues.length) {
10503
- // For optional-out schemas, ignore errors on absent keys
10504
- if (isOptionalOut && !(key in input)) {
9570
+ // For optional-in/out schemas, ignore errors on absent keys.
9571
+ if (isOptionalIn && isOptionalOut && !isPresent) {
10505
9572
  return;
10506
9573
  }
10507
9574
  final.issues.push(...prefixIssues(key, result.issues));
10508
9575
  }
9576
+ if (!isPresent && !isOptionalIn) {
9577
+ if (!result.issues.length) {
9578
+ final.issues.push({
9579
+ code: "invalid_type",
9580
+ expected: "nonoptional",
9581
+ input: undefined,
9582
+ path: [key],
9583
+ });
9584
+ }
9585
+ return;
9586
+ }
10509
9587
  if (result.value === undefined) {
10510
- if (key in input) {
9588
+ if (isPresent) {
10511
9589
  final.value[key] = undefined;
10512
9590
  }
10513
9591
  }
@@ -10533,12 +9611,16 @@ function normalizeDef(def) {
10533
9611
  }
10534
9612
  function handleCatchall(proms, input, payload, ctx, def, inst) {
10535
9613
  const unrecognized = [];
10536
- // iterate over input keys
10537
9614
  const keySet = def.keySet;
10538
9615
  const _catchall = def.catchall._zod;
10539
9616
  const t = _catchall.def.type;
9617
+ const isOptionalIn = _catchall.optin === "optional";
10540
9618
  const isOptionalOut = _catchall.optout === "optional";
10541
9619
  for (const key in input) {
9620
+ // skip __proto__ so it can't replace the result prototype via the
9621
+ // assignment setter on the plain {} we build into
9622
+ if (key === "__proto__")
9623
+ continue;
10542
9624
  if (keySet.has(key))
10543
9625
  continue;
10544
9626
  if (t === "never") {
@@ -10547,10 +9629,10 @@ function handleCatchall(proms, input, payload, ctx, def, inst) {
10547
9629
  }
10548
9630
  const r = _catchall.run({ value: input[key], issues: [] }, ctx);
10549
9631
  if (r instanceof Promise) {
10550
- proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));
9632
+ proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut)));
10551
9633
  }
10552
9634
  else {
10553
- handlePropertyResult(r, payload, key, input, isOptionalOut);
9635
+ handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);
10554
9636
  }
10555
9637
  }
10556
9638
  if (unrecognized.length) {
@@ -10618,13 +9700,14 @@ const $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
10618
9700
  const shape = value.shape;
10619
9701
  for (const key of value.keys) {
10620
9702
  const el = shape[key];
9703
+ const isOptionalIn = el._zod.optin === "optional";
10621
9704
  const isOptionalOut = el._zod.optout === "optional";
10622
9705
  const r = el._zod.run({ value: input[key], issues: [] }, ctx);
10623
9706
  if (r instanceof Promise) {
10624
- proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));
9707
+ proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut)));
10625
9708
  }
10626
9709
  else {
10627
- handlePropertyResult(r, payload, key, input, isOptionalOut);
9710
+ handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);
10628
9711
  }
10629
9712
  }
10630
9713
  if (!catchall) {
@@ -10657,10 +9740,11 @@ const $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) =>
10657
9740
  const id = ids[key];
10658
9741
  const k = esc(key);
10659
9742
  const schema = shape[key];
9743
+ const isOptionalIn = schema?._zod?.optin === "optional";
10660
9744
  const isOptionalOut = schema?._zod?.optout === "optional";
10661
9745
  doc.write(`const ${id} = ${parseStr(key)};`);
10662
- if (isOptionalOut) {
10663
- // For optional-out schemas, ignore errors on absent keys
9746
+ if (isOptionalIn && isOptionalOut) {
9747
+ // For optional-in/out schemas, ignore errors on absent keys
10664
9748
  doc.write(`
10665
9749
  if (${id}.issues.length) {
10666
9750
  if (${k} in input) {
@@ -10679,6 +9763,34 @@ const $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) =>
10679
9763
  newResult[${k}] = ${id}.value;
10680
9764
  }
10681
9765
 
9766
+ `);
9767
+ }
9768
+ else if (!isOptionalIn) {
9769
+ doc.write(`
9770
+ const ${id}_present = ${k} in input;
9771
+ if (${id}.issues.length) {
9772
+ payload.issues = payload.issues.concat(${id}.issues.map(iss => ({
9773
+ ...iss,
9774
+ path: iss.path ? [${k}, ...iss.path] : [${k}]
9775
+ })));
9776
+ }
9777
+ if (!${id}_present && !${id}.issues.length) {
9778
+ payload.issues.push({
9779
+ code: "invalid_type",
9780
+ expected: "nonoptional",
9781
+ input: undefined,
9782
+ path: [${k}]
9783
+ });
9784
+ }
9785
+
9786
+ if (${id}_present) {
9787
+ if (${id}.value === undefined) {
9788
+ newResult[${k}] = undefined;
9789
+ } else {
9790
+ newResult[${k}] = ${id}.value;
9791
+ }
9792
+ }
9793
+
10682
9794
  `);
10683
9795
  }
10684
9796
  else {
@@ -10774,10 +9886,9 @@ const $ZodUnion = /*@__PURE__*/ $constructor("$ZodUnion", (inst, def) => {
10774
9886
  }
10775
9887
  return undefined;
10776
9888
  });
10777
- const single = def.options.length === 1;
10778
- const first = def.options[0]._zod.run;
9889
+ const first = def.options.length === 1 ? def.options[0]._zod.run : null;
10779
9890
  inst._zod.parse = (payload, ctx) => {
10780
- if (single) {
9891
+ if (first) {
10781
9892
  return first(payload, ctx);
10782
9893
  }
10783
9894
  let async = false;
@@ -11355,6 +10466,11 @@ function _nanoid(Class, params) {
11355
10466
  ...normalizeParams(params),
11356
10467
  });
11357
10468
  }
10469
+ /**
10470
+ * @deprecated CUID v1 is deprecated by its authors due to information leakage
10471
+ * (timestamps embedded in the id). Use {@link _cuid2} instead.
10472
+ * See https://github.com/paralleldrive/cuid.
10473
+ */
11358
10474
  // @__NO_SIDE_EFFECTS__
11359
10475
  function _cuid(Class, params) {
11360
10476
  return new Class({
@@ -11670,7 +10786,7 @@ function _refine(Class, fn, _params) {
11670
10786
  return schema;
11671
10787
  }
11672
10788
  // @__NO_SIDE_EFFECTS__
11673
- function _superRefine(fn) {
10789
+ function _superRefine(fn, params) {
11674
10790
  const ch = _check((payload) => {
11675
10791
  payload.addIssue = (issue$1) => {
11676
10792
  if (typeof issue$1 === "string") {
@@ -11689,7 +10805,7 @@ function _superRefine(fn) {
11689
10805
  }
11690
10806
  };
11691
10807
  return fn(payload.value, payload);
11692
- });
10808
+ }, params);
11693
10809
  return ch;
11694
10810
  }
11695
10811
  // @__NO_SIDE_EFFECTS__
@@ -11789,7 +10905,7 @@ function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
11789
10905
  delete result.schema.default;
11790
10906
  }
11791
10907
  // set prefault as default
11792
- if (ctx.io === "input" && result.schema._prefault)
10908
+ if (ctx.io === "input" && "_prefault" in result.schema)
11793
10909
  (_a = result.schema).default ?? (_a.default = result.schema._prefault);
11794
10910
  delete result.schema._prefault;
11795
10911
  // pulling fresh from ctx.seen in case it was overwritten
@@ -12017,11 +11133,20 @@ function finalize(ctx, schema) {
12017
11133
  result.$id = ctx.external.uri(id);
12018
11134
  }
12019
11135
  Object.assign(result, root.def ?? root.schema);
11136
+ // The `id` in `.meta()` is a Zod-specific registration tag used to extract
11137
+ // schemas into $defs — it is not user-facing JSON Schema metadata. Strip it
11138
+ // from the output body where it would otherwise leak. The id is preserved
11139
+ // implicitly via the $defs key (and via $ref paths).
11140
+ const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
11141
+ if (rootMetaId !== undefined && result.id === rootMetaId)
11142
+ delete result.id;
12020
11143
  // build defs object
12021
11144
  const defs = ctx.external?.defs ?? {};
12022
11145
  for (const entry of ctx.seen.entries()) {
12023
11146
  const seen = entry[1];
12024
11147
  if (seen.def && seen.defId) {
11148
+ if (seen.def.id === seen.defId)
11149
+ delete seen.def.id;
12025
11150
  defs[seen.defId] = seen.def;
12026
11151
  }
12027
11152
  }
@@ -12089,6 +11214,8 @@ function isTransforming(_schema, _ctx) {
12089
11214
  return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
12090
11215
  }
12091
11216
  if (def.type === "pipe") {
11217
+ if (_schema._zod.traits.has("$ZodCodec"))
11218
+ return true;
12092
11219
  return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
12093
11220
  }
12094
11221
  if (def.type === "object") {
@@ -12187,8 +11314,12 @@ const numberProcessor = (schema, ctx, _json, _params) => {
12187
11314
  json.type = "integer";
12188
11315
  else
12189
11316
  json.type = "number";
12190
- if (typeof exclusiveMinimum === "number") {
12191
- if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
11317
+ // when both minimum and exclusiveMinimum exist, pick the more restrictive one
11318
+ const exMin = typeof exclusiveMinimum === "number" && exclusiveMinimum >= (minimum ?? Number.NEGATIVE_INFINITY);
11319
+ const exMax = typeof exclusiveMaximum === "number" && exclusiveMaximum <= (maximum ?? Number.POSITIVE_INFINITY);
11320
+ const legacy = ctx.target === "draft-04" || ctx.target === "openapi-3.0";
11321
+ if (exMin) {
11322
+ if (legacy) {
12192
11323
  json.minimum = exclusiveMinimum;
12193
11324
  json.exclusiveMinimum = true;
12194
11325
  }
@@ -12196,17 +11327,11 @@ const numberProcessor = (schema, ctx, _json, _params) => {
12196
11327
  json.exclusiveMinimum = exclusiveMinimum;
12197
11328
  }
12198
11329
  }
12199
- if (typeof minimum === "number") {
11330
+ else if (typeof minimum === "number") {
12200
11331
  json.minimum = minimum;
12201
- if (typeof exclusiveMinimum === "number" && ctx.target !== "draft-04") {
12202
- if (exclusiveMinimum >= minimum)
12203
- delete json.minimum;
12204
- else
12205
- delete json.exclusiveMinimum;
12206
- }
12207
11332
  }
12208
- if (typeof exclusiveMaximum === "number") {
12209
- if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
11333
+ if (exMax) {
11334
+ if (legacy) {
12210
11335
  json.maximum = exclusiveMaximum;
12211
11336
  json.exclusiveMaximum = true;
12212
11337
  }
@@ -12214,14 +11339,8 @@ const numberProcessor = (schema, ctx, _json, _params) => {
12214
11339
  json.exclusiveMaximum = exclusiveMaximum;
12215
11340
  }
12216
11341
  }
12217
- if (typeof maximum === "number") {
11342
+ else if (typeof maximum === "number") {
12218
11343
  json.maximum = maximum;
12219
- if (typeof exclusiveMaximum === "number" && ctx.target !== "draft-04") {
12220
- if (exclusiveMaximum <= maximum)
12221
- delete json.maximum;
12222
- else
12223
- delete json.exclusiveMaximum;
12224
- }
12225
11344
  }
12226
11345
  if (typeof multipleOf === "number")
12227
11346
  json.multipleOf = multipleOf;
@@ -12404,7 +11523,10 @@ const arrayProcessor = (schema, ctx, _json, params) => {
12404
11523
  if (typeof maximum === "number")
12405
11524
  json.maxItems = maximum;
12406
11525
  json.type = "array";
12407
- json.items = process$1(def.element, ctx, { ...params, path: [...params.path, "items"] });
11526
+ json.items = process$1(def.element, ctx, {
11527
+ ...params,
11528
+ path: [...params.path, "items"],
11529
+ });
12408
11530
  };
12409
11531
  const objectProcessor = (schema, ctx, _json, params) => {
12410
11532
  const json = _json;
@@ -12621,7 +11743,8 @@ const catchProcessor = (schema, ctx, json, params) => {
12621
11743
  };
12622
11744
  const pipeProcessor = (schema, ctx, _json, params) => {
12623
11745
  const def = schema._zod.def;
12624
- const innerType = ctx.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
11746
+ const inIsTransform = def.in._zod.traits.has("$ZodTransform");
11747
+ const innerType = ctx.io === "input" ? (inIsTransform ? def.out : def.in) : def.out;
12625
11748
  process$1(innerType, ctx, params);
12626
11749
  const seen = ctx.seen.get(schema);
12627
11750
  seen.ref = innerType;
@@ -12801,7 +11924,7 @@ const initializer = (inst, issues) => {
12801
11924
  // },
12802
11925
  // });
12803
11926
  };
12804
- const ZodRealError = $constructor("ZodError", initializer, {
11927
+ const ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, {
12805
11928
  Parent: Error,
12806
11929
  });
12807
11930
  // /** @deprecated Use `z.core.$ZodErrorMapCtx` instead. */
@@ -12821,6 +11944,54 @@ const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
12821
11944
  const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
12822
11945
  const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
12823
11946
 
11947
+ // Lazy-bind builder methods.
11948
+ //
11949
+ // Builder methods (`.optional`, `.array`, `.refine`, ...) live as
11950
+ // non-enumerable getters on each concrete schema constructor's
11951
+ // prototype. On first access from an instance the getter allocates
11952
+ // `fn.bind(this)` and caches it as an own property on that instance,
11953
+ // so detached usage (`const m = schema.optional; m()`) still works
11954
+ // and the per-instance allocation only happens for methods actually
11955
+ // touched.
11956
+ //
11957
+ // One install per (prototype, group), memoized by `_installedGroups`.
11958
+ const _installedGroups = /* @__PURE__ */ new WeakMap();
11959
+ function _installLazyMethods(inst, group, methods) {
11960
+ const proto = Object.getPrototypeOf(inst);
11961
+ let installed = _installedGroups.get(proto);
11962
+ if (!installed) {
11963
+ installed = new Set();
11964
+ _installedGroups.set(proto, installed);
11965
+ }
11966
+ if (installed.has(group))
11967
+ return;
11968
+ installed.add(group);
11969
+ for (const key in methods) {
11970
+ const fn = methods[key];
11971
+ Object.defineProperty(proto, key, {
11972
+ configurable: true,
11973
+ enumerable: false,
11974
+ get() {
11975
+ const bound = fn.bind(this);
11976
+ Object.defineProperty(this, key, {
11977
+ configurable: true,
11978
+ writable: true,
11979
+ enumerable: true,
11980
+ value: bound,
11981
+ });
11982
+ return bound;
11983
+ },
11984
+ set(v) {
11985
+ Object.defineProperty(this, key, {
11986
+ configurable: true,
11987
+ writable: true,
11988
+ enumerable: true,
11989
+ value: v,
11990
+ });
11991
+ },
11992
+ });
11993
+ }
11994
+ }
12824
11995
  const ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
12825
11996
  $ZodType.init(inst, def);
12826
11997
  Object.assign(inst["~standard"], {
@@ -12833,31 +12004,16 @@ const ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
12833
12004
  inst.def = def;
12834
12005
  inst.type = def.type;
12835
12006
  Object.defineProperty(inst, "_def", { value: def });
12836
- // base methods
12837
- inst.check = (...checks) => {
12838
- return inst.clone(mergeDefs(def, {
12839
- checks: [
12840
- ...(def.checks ?? []),
12841
- ...checks.map((ch) => typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch),
12842
- ],
12843
- }), {
12844
- parent: true,
12845
- });
12846
- };
12847
- inst.with = inst.check;
12848
- inst.clone = (def, params) => clone(inst, def, params);
12849
- inst.brand = () => inst;
12850
- inst.register = ((reg, meta) => {
12851
- reg.add(inst, meta);
12852
- return inst;
12853
- });
12854
- // parsing
12007
+ // Parse-family is intentionally kept as per-instance closures: these are
12008
+ // the hot path AND the most-detached methods (`arr.map(schema.parse)`,
12009
+ // `const { parse } = schema`, etc.). Eager closures here mean callers pay
12010
+ // ~12 closure allocations per schema but get monomorphic call sites and
12011
+ // detached usage that "just works".
12855
12012
  inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
12856
12013
  inst.safeParse = (data, params) => safeParse(inst, data, params);
12857
12014
  inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
12858
12015
  inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
12859
12016
  inst.spa = inst.safeParseAsync;
12860
- // encoding/decoding
12861
12017
  inst.encode = (data, params) => encode(inst, data, params);
12862
12018
  inst.decode = (data, params) => decode(inst, data, params);
12863
12019
  inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);
@@ -12866,50 +12022,118 @@ const ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
12866
12022
  inst.safeDecode = (data, params) => safeDecode(inst, data, params);
12867
12023
  inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);
12868
12024
  inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);
12869
- // refinements
12870
- inst.refine = (check, params) => inst.check(refine(check, params));
12871
- inst.superRefine = (refinement) => inst.check(superRefine(refinement));
12872
- inst.overwrite = (fn) => inst.check(_overwrite(fn));
12873
- // wrappers
12874
- inst.optional = () => optional(inst);
12875
- inst.exactOptional = () => exactOptional(inst);
12876
- inst.nullable = () => nullable(inst);
12877
- inst.nullish = () => optional(nullable(inst));
12878
- inst.nonoptional = (params) => nonoptional(inst, params);
12879
- inst.array = () => array(inst);
12880
- inst.or = (arg) => union([inst, arg]);
12881
- inst.and = (arg) => intersection(inst, arg);
12882
- inst.transform = (tx) => pipe(inst, transform(tx));
12883
- inst.default = (def) => _default(inst, def);
12884
- inst.prefault = (def) => prefault(inst, def);
12885
- // inst.coalesce = (def, params) => coalesce(inst, def, params);
12886
- inst.catch = (params) => _catch(inst, params);
12887
- inst.pipe = (target) => pipe(inst, target);
12888
- inst.readonly = () => readonly(inst);
12889
- // meta
12890
- inst.describe = (description) => {
12891
- const cl = inst.clone();
12892
- globalRegistry.add(cl, { description });
12893
- return cl;
12894
- };
12025
+ // All builder methods are placed on the internal prototype as lazy-bind
12026
+ // getters. On first access per-instance, a bound thunk is allocated and
12027
+ // cached as an own property; subsequent accesses skip the getter. This
12028
+ // means: no per-instance allocation for unused methods, full
12029
+ // detachability preserved (`const m = schema.optional; m()` works), and
12030
+ // shared underlying function references across all instances.
12031
+ _installLazyMethods(inst, "ZodType", {
12032
+ check(...chks) {
12033
+ const def = this.def;
12034
+ return this.clone(mergeDefs(def, {
12035
+ checks: [
12036
+ ...(def.checks ?? []),
12037
+ ...chks.map((ch) => typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch),
12038
+ ],
12039
+ }), { parent: true });
12040
+ },
12041
+ with(...chks) {
12042
+ return this.check(...chks);
12043
+ },
12044
+ clone(def, params) {
12045
+ return clone(this, def, params);
12046
+ },
12047
+ brand() {
12048
+ return this;
12049
+ },
12050
+ register(reg, meta) {
12051
+ reg.add(this, meta);
12052
+ return this;
12053
+ },
12054
+ refine(check, params) {
12055
+ return this.check(refine(check, params));
12056
+ },
12057
+ superRefine(refinement, params) {
12058
+ return this.check(superRefine(refinement, params));
12059
+ },
12060
+ overwrite(fn) {
12061
+ return this.check(_overwrite(fn));
12062
+ },
12063
+ optional() {
12064
+ return optional(this);
12065
+ },
12066
+ exactOptional() {
12067
+ return exactOptional(this);
12068
+ },
12069
+ nullable() {
12070
+ return nullable(this);
12071
+ },
12072
+ nullish() {
12073
+ return optional(nullable(this));
12074
+ },
12075
+ nonoptional(params) {
12076
+ return nonoptional(this, params);
12077
+ },
12078
+ array() {
12079
+ return array(this);
12080
+ },
12081
+ or(arg) {
12082
+ return union([this, arg]);
12083
+ },
12084
+ and(arg) {
12085
+ return intersection(this, arg);
12086
+ },
12087
+ transform(tx) {
12088
+ return pipe(this, transform(tx));
12089
+ },
12090
+ default(d) {
12091
+ return _default(this, d);
12092
+ },
12093
+ prefault(d) {
12094
+ return prefault(this, d);
12095
+ },
12096
+ catch(params) {
12097
+ return _catch(this, params);
12098
+ },
12099
+ pipe(target) {
12100
+ return pipe(this, target);
12101
+ },
12102
+ readonly() {
12103
+ return readonly(this);
12104
+ },
12105
+ describe(description) {
12106
+ const cl = this.clone();
12107
+ globalRegistry.add(cl, { description });
12108
+ return cl;
12109
+ },
12110
+ meta(...args) {
12111
+ // overloaded: meta() returns the registered metadata, meta(data)
12112
+ // returns a clone with `data` registered. The mapped type picks
12113
+ // up the second overload, so we accept variadic any-args and
12114
+ // return `any` to satisfy both at runtime.
12115
+ if (args.length === 0)
12116
+ return globalRegistry.get(this);
12117
+ const cl = this.clone();
12118
+ globalRegistry.add(cl, args[0]);
12119
+ return cl;
12120
+ },
12121
+ isOptional() {
12122
+ return this.safeParse(undefined).success;
12123
+ },
12124
+ isNullable() {
12125
+ return this.safeParse(null).success;
12126
+ },
12127
+ apply(fn) {
12128
+ return fn(this);
12129
+ },
12130
+ });
12895
12131
  Object.defineProperty(inst, "description", {
12896
12132
  get() {
12897
12133
  return globalRegistry.get(inst)?.description;
12898
12134
  },
12899
12135
  configurable: true,
12900
12136
  });
12901
- inst.meta = (...args) => {
12902
- if (args.length === 0) {
12903
- return globalRegistry.get(inst);
12904
- }
12905
- const cl = inst.clone();
12906
- globalRegistry.add(cl, args[0]);
12907
- return cl;
12908
- };
12909
- // helpers
12910
- inst.isOptional = () => inst.safeParse(undefined).success;
12911
- inst.isNullable = () => inst.safeParse(null).success;
12912
- inst.apply = (fn) => fn(inst);
12913
12137
  return inst;
12914
12138
  });
12915
12139
  /** @internal */
@@ -12921,23 +12145,53 @@ const _ZodString = /*@__PURE__*/ $constructor("_ZodString", (inst, def) => {
12921
12145
  inst.format = bag.format ?? null;
12922
12146
  inst.minLength = bag.minimum ?? null;
12923
12147
  inst.maxLength = bag.maximum ?? null;
12924
- // validations
12925
- inst.regex = (...args) => inst.check(_regex(...args));
12926
- inst.includes = (...args) => inst.check(_includes(...args));
12927
- inst.startsWith = (...args) => inst.check(_startsWith(...args));
12928
- inst.endsWith = (...args) => inst.check(_endsWith(...args));
12929
- inst.min = (...args) => inst.check(_minLength(...args));
12930
- inst.max = (...args) => inst.check(_maxLength(...args));
12931
- inst.length = (...args) => inst.check(_length(...args));
12932
- inst.nonempty = (...args) => inst.check(_minLength(1, ...args));
12933
- inst.lowercase = (params) => inst.check(_lowercase(params));
12934
- inst.uppercase = (params) => inst.check(_uppercase(params));
12935
- // transforms
12936
- inst.trim = () => inst.check(_trim());
12937
- inst.normalize = (...args) => inst.check(_normalize(...args));
12938
- inst.toLowerCase = () => inst.check(_toLowerCase());
12939
- inst.toUpperCase = () => inst.check(_toUpperCase());
12940
- inst.slugify = () => inst.check(_slugify());
12148
+ _installLazyMethods(inst, "_ZodString", {
12149
+ regex(...args) {
12150
+ return this.check(_regex(...args));
12151
+ },
12152
+ includes(...args) {
12153
+ return this.check(_includes(...args));
12154
+ },
12155
+ startsWith(...args) {
12156
+ return this.check(_startsWith(...args));
12157
+ },
12158
+ endsWith(...args) {
12159
+ return this.check(_endsWith(...args));
12160
+ },
12161
+ min(...args) {
12162
+ return this.check(_minLength(...args));
12163
+ },
12164
+ max(...args) {
12165
+ return this.check(_maxLength(...args));
12166
+ },
12167
+ length(...args) {
12168
+ return this.check(_length(...args));
12169
+ },
12170
+ nonempty(...args) {
12171
+ return this.check(_minLength(1, ...args));
12172
+ },
12173
+ lowercase(params) {
12174
+ return this.check(_lowercase(params));
12175
+ },
12176
+ uppercase(params) {
12177
+ return this.check(_uppercase(params));
12178
+ },
12179
+ trim() {
12180
+ return this.check(_trim());
12181
+ },
12182
+ normalize(...args) {
12183
+ return this.check(_normalize(...args));
12184
+ },
12185
+ toLowerCase() {
12186
+ return this.check(_toLowerCase());
12187
+ },
12188
+ toUpperCase() {
12189
+ return this.check(_toUpperCase());
12190
+ },
12191
+ slugify() {
12192
+ return this.check(_slugify());
12193
+ },
12194
+ });
12941
12195
  });
12942
12196
  const ZodString = /*@__PURE__*/ $constructor("ZodString", (inst, def) => {
12943
12197
  $ZodString.init(inst, def);
@@ -13008,6 +12262,11 @@ const ZodNanoID = /*@__PURE__*/ $constructor("ZodNanoID", (inst, def) => {
13008
12262
  $ZodNanoID.init(inst, def);
13009
12263
  ZodStringFormat.init(inst, def);
13010
12264
  });
12265
+ /**
12266
+ * @deprecated CUID v1 is deprecated by its authors due to information leakage
12267
+ * (timestamps embedded in the id). Use {@link ZodCUID2} instead.
12268
+ * See https://github.com/paralleldrive/cuid.
12269
+ */
13011
12270
  const ZodCUID = /*@__PURE__*/ $constructor("ZodCUID", (inst, def) => {
13012
12271
  // ZodStringFormat.init(inst, def);
13013
12272
  $ZodCUID.init(inst, def);
@@ -13092,11 +12351,23 @@ const ZodArray = /*@__PURE__*/ $constructor("ZodArray", (inst, def) => {
13092
12351
  ZodType.init(inst, def);
13093
12352
  inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);
13094
12353
  inst.element = def.element;
13095
- inst.min = (minLength, params) => inst.check(_minLength(minLength, params));
13096
- inst.nonempty = (params) => inst.check(_minLength(1, params));
13097
- inst.max = (maxLength, params) => inst.check(_maxLength(maxLength, params));
13098
- inst.length = (len, params) => inst.check(_length(len, params));
13099
- inst.unwrap = () => inst.element;
12354
+ _installLazyMethods(inst, "ZodArray", {
12355
+ min(n, params) {
12356
+ return this.check(_minLength(n, params));
12357
+ },
12358
+ nonempty(params) {
12359
+ return this.check(_minLength(1, params));
12360
+ },
12361
+ max(n, params) {
12362
+ return this.check(_maxLength(n, params));
12363
+ },
12364
+ length(n, params) {
12365
+ return this.check(_length(n, params));
12366
+ },
12367
+ unwrap() {
12368
+ return this.element;
12369
+ },
12370
+ });
13100
12371
  });
13101
12372
  function array(element, params) {
13102
12373
  return _array(ZodArray, element, params);
@@ -13108,23 +12379,47 @@ const ZodObject = /*@__PURE__*/ $constructor("ZodObject", (inst, def) => {
13108
12379
  defineLazy(inst, "shape", () => {
13109
12380
  return def.shape;
13110
12381
  });
13111
- inst.keyof = () => _enum(Object.keys(inst._zod.def.shape));
13112
- inst.catchall = (catchall) => inst.clone({ ...inst._zod.def, catchall: catchall });
13113
- inst.passthrough = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
13114
- inst.loose = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
13115
- inst.strict = () => inst.clone({ ...inst._zod.def, catchall: never() });
13116
- inst.strip = () => inst.clone({ ...inst._zod.def, catchall: undefined });
13117
- inst.extend = (incoming) => {
13118
- return extend(inst, incoming);
13119
- };
13120
- inst.safeExtend = (incoming) => {
13121
- return safeExtend(inst, incoming);
13122
- };
13123
- inst.merge = (other) => merge(inst, other);
13124
- inst.pick = (mask) => pick(inst, mask);
13125
- inst.omit = (mask) => omit(inst, mask);
13126
- inst.partial = (...args) => partial(ZodOptional, inst, args[0]);
13127
- inst.required = (...args) => required(ZodNonOptional, inst, args[0]);
12382
+ _installLazyMethods(inst, "ZodObject", {
12383
+ keyof() {
12384
+ return _enum(Object.keys(this._zod.def.shape));
12385
+ },
12386
+ catchall(catchall) {
12387
+ return this.clone({ ...this._zod.def, catchall: catchall });
12388
+ },
12389
+ passthrough() {
12390
+ return this.clone({ ...this._zod.def, catchall: unknown() });
12391
+ },
12392
+ loose() {
12393
+ return this.clone({ ...this._zod.def, catchall: unknown() });
12394
+ },
12395
+ strict() {
12396
+ return this.clone({ ...this._zod.def, catchall: never() });
12397
+ },
12398
+ strip() {
12399
+ return this.clone({ ...this._zod.def, catchall: undefined });
12400
+ },
12401
+ extend(incoming) {
12402
+ return extend(this, incoming);
12403
+ },
12404
+ safeExtend(incoming) {
12405
+ return safeExtend(this, incoming);
12406
+ },
12407
+ merge(other) {
12408
+ return merge(this, other);
12409
+ },
12410
+ pick(mask) {
12411
+ return pick(this, mask);
12412
+ },
12413
+ omit(mask) {
12414
+ return omit(this, mask);
12415
+ },
12416
+ partial(...args) {
12417
+ return partial(ZodOptional, this, args[0]);
12418
+ },
12419
+ required(...args) {
12420
+ return required(ZodNonOptional, this, args[0]);
12421
+ },
12422
+ });
13128
12423
  });
13129
12424
  function object(shape, params) {
13130
12425
  const def = {
@@ -13378,8 +12673,8 @@ function refine(fn, _params = {}) {
13378
12673
  return _refine(ZodCustom, fn, _params);
13379
12674
  }
13380
12675
  // superRefine
13381
- function superRefine(fn) {
13382
- return _superRefine(fn);
12676
+ function superRefine(fn, params) {
12677
+ return _superRefine(fn, params);
13383
12678
  }
13384
12679
 
13385
12680
  const ignoreOverride = Symbol('Let zodToJsonSchema decide on which parser to use');
@@ -13468,235 +12763,6 @@ function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
13468
12763
  addErrorMessage(res, key, errorMessage, refs);
13469
12764
  }
13470
12765
 
13471
- var util;
13472
- (function (util) {
13473
- util.assertEqual = (_) => { };
13474
- function assertIs(_arg) { }
13475
- util.assertIs = assertIs;
13476
- function assertNever(_x) {
13477
- throw new Error();
13478
- }
13479
- util.assertNever = assertNever;
13480
- util.arrayToEnum = (items) => {
13481
- const obj = {};
13482
- for (const item of items) {
13483
- obj[item] = item;
13484
- }
13485
- return obj;
13486
- };
13487
- util.getValidEnumValues = (obj) => {
13488
- const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
13489
- const filtered = {};
13490
- for (const k of validKeys) {
13491
- filtered[k] = obj[k];
13492
- }
13493
- return util.objectValues(filtered);
13494
- };
13495
- util.objectValues = (obj) => {
13496
- return util.objectKeys(obj).map(function (e) {
13497
- return obj[e];
13498
- });
13499
- };
13500
- util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban
13501
- ? (obj) => Object.keys(obj) // eslint-disable-line ban/ban
13502
- : (object) => {
13503
- const keys = [];
13504
- for (const key in object) {
13505
- if (Object.prototype.hasOwnProperty.call(object, key)) {
13506
- keys.push(key);
13507
- }
13508
- }
13509
- return keys;
13510
- };
13511
- util.find = (arr, checker) => {
13512
- for (const item of arr) {
13513
- if (checker(item))
13514
- return item;
13515
- }
13516
- return undefined;
13517
- };
13518
- util.isInteger = typeof Number.isInteger === "function"
13519
- ? (val) => Number.isInteger(val) // eslint-disable-line ban/ban
13520
- : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
13521
- function joinValues(array, separator = " | ") {
13522
- return array.map((val) => (typeof val === "string" ? `'${val}'` : val)).join(separator);
13523
- }
13524
- util.joinValues = joinValues;
13525
- util.jsonStringifyReplacer = (_, value) => {
13526
- if (typeof value === "bigint") {
13527
- return value.toString();
13528
- }
13529
- return value;
13530
- };
13531
- })(util || (util = {}));
13532
- var objectUtil;
13533
- (function (objectUtil) {
13534
- objectUtil.mergeShapes = (first, second) => {
13535
- return {
13536
- ...first,
13537
- ...second, // second overwrites first
13538
- };
13539
- };
13540
- })(objectUtil || (objectUtil = {}));
13541
- util.arrayToEnum([
13542
- "string",
13543
- "nan",
13544
- "number",
13545
- "integer",
13546
- "float",
13547
- "boolean",
13548
- "date",
13549
- "bigint",
13550
- "symbol",
13551
- "function",
13552
- "undefined",
13553
- "null",
13554
- "array",
13555
- "object",
13556
- "unknown",
13557
- "promise",
13558
- "void",
13559
- "never",
13560
- "map",
13561
- "set",
13562
- ]);
13563
-
13564
- util.arrayToEnum([
13565
- "invalid_type",
13566
- "invalid_literal",
13567
- "custom",
13568
- "invalid_union",
13569
- "invalid_union_discriminator",
13570
- "invalid_enum_value",
13571
- "unrecognized_keys",
13572
- "invalid_arguments",
13573
- "invalid_return_type",
13574
- "invalid_date",
13575
- "invalid_string",
13576
- "too_small",
13577
- "too_big",
13578
- "invalid_intersection_types",
13579
- "not_multiple_of",
13580
- "not_finite",
13581
- ]);
13582
- class ZodError extends Error {
13583
- get errors() {
13584
- return this.issues;
13585
- }
13586
- constructor(issues) {
13587
- super();
13588
- this.issues = [];
13589
- this.addIssue = (sub) => {
13590
- this.issues = [...this.issues, sub];
13591
- };
13592
- this.addIssues = (subs = []) => {
13593
- this.issues = [...this.issues, ...subs];
13594
- };
13595
- const actualProto = new.target.prototype;
13596
- if (Object.setPrototypeOf) {
13597
- // eslint-disable-next-line ban/ban
13598
- Object.setPrototypeOf(this, actualProto);
13599
- }
13600
- else {
13601
- this.__proto__ = actualProto;
13602
- }
13603
- this.name = "ZodError";
13604
- this.issues = issues;
13605
- }
13606
- format(_mapper) {
13607
- const mapper = _mapper ||
13608
- function (issue) {
13609
- return issue.message;
13610
- };
13611
- const fieldErrors = { _errors: [] };
13612
- const processError = (error) => {
13613
- for (const issue of error.issues) {
13614
- if (issue.code === "invalid_union") {
13615
- issue.unionErrors.map(processError);
13616
- }
13617
- else if (issue.code === "invalid_return_type") {
13618
- processError(issue.returnTypeError);
13619
- }
13620
- else if (issue.code === "invalid_arguments") {
13621
- processError(issue.argumentsError);
13622
- }
13623
- else if (issue.path.length === 0) {
13624
- fieldErrors._errors.push(mapper(issue));
13625
- }
13626
- else {
13627
- let curr = fieldErrors;
13628
- let i = 0;
13629
- while (i < issue.path.length) {
13630
- const el = issue.path[i];
13631
- const terminal = i === issue.path.length - 1;
13632
- if (!terminal) {
13633
- curr[el] = curr[el] || { _errors: [] };
13634
- // if (typeof el === "string") {
13635
- // curr[el] = curr[el] || { _errors: [] };
13636
- // } else if (typeof el === "number") {
13637
- // const errorArray: any = [];
13638
- // errorArray._errors = [];
13639
- // curr[el] = curr[el] || errorArray;
13640
- // }
13641
- }
13642
- else {
13643
- curr[el] = curr[el] || { _errors: [] };
13644
- curr[el]._errors.push(mapper(issue));
13645
- }
13646
- curr = curr[el];
13647
- i++;
13648
- }
13649
- }
13650
- }
13651
- };
13652
- processError(this);
13653
- return fieldErrors;
13654
- }
13655
- static assert(value) {
13656
- if (!(value instanceof ZodError)) {
13657
- throw new Error(`Not a ZodError: ${value}`);
13658
- }
13659
- }
13660
- toString() {
13661
- return this.message;
13662
- }
13663
- get message() {
13664
- return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
13665
- }
13666
- get isEmpty() {
13667
- return this.issues.length === 0;
13668
- }
13669
- flatten(mapper = (issue) => issue.message) {
13670
- const fieldErrors = Object.create(null);
13671
- const formErrors = [];
13672
- for (const sub of this.issues) {
13673
- if (sub.path.length > 0) {
13674
- const firstEl = sub.path[0];
13675
- fieldErrors[firstEl] = fieldErrors[firstEl] || [];
13676
- fieldErrors[firstEl].push(mapper(sub));
13677
- }
13678
- else {
13679
- formErrors.push(mapper(sub));
13680
- }
13681
- }
13682
- return { formErrors, fieldErrors };
13683
- }
13684
- get formErrors() {
13685
- return this.flatten();
13686
- }
13687
- }
13688
- ZodError.create = (issues) => {
13689
- const error = new ZodError(issues);
13690
- return error;
13691
- };
13692
-
13693
- var errorUtil;
13694
- (function (errorUtil) {
13695
- errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {};
13696
- // biome-ignore lint:
13697
- errorUtil.toString = (message) => typeof message === "string" ? message : message?.message;
13698
- })(errorUtil || (errorUtil = {}));
13699
-
13700
12766
  var ZodFirstPartyTypeKind;
13701
12767
  (function (ZodFirstPartyTypeKind) {
13702
12768
  ZodFirstPartyTypeKind["ZodString"] = "ZodString";
@@ -15078,10 +14144,10 @@ function zodTextFormat(zodObject, name, props) {
15078
14144
 
15079
14145
  // llm-openai-config.ts
15080
14146
  const DEFAULT_MODEL = 'gpt-4.1-mini';
15081
- const GPT_5_4_HIGH_CONTEXT_THRESHOLD_TOKENS = 272_000;
15082
- const GPT_5_4_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
15083
- const GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
15084
- /** Token costs in USD per 1M tokens. Last updated Mar 2026. */
14147
+ const GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS = 272_000;
14148
+ const GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
14149
+ const GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
14150
+ /** Token costs in USD per 1M tokens. Last updated May 2026. */
15085
14151
  const openAiModelCosts = {
15086
14152
  'gpt-4o': {
15087
14153
  inputCost: 2.5 / 1_000_000,
@@ -15162,6 +14228,15 @@ const openAiModelCosts = {
15162
14228
  inputCost: 30 / 1_000_000,
15163
14229
  outputCost: 180 / 1_000_000,
15164
14230
  },
14231
+ 'gpt-5.5': {
14232
+ inputCost: 5 / 1_000_000,
14233
+ cacheHitCost: 0.5 / 1_000_000,
14234
+ outputCost: 30 / 1_000_000,
14235
+ },
14236
+ 'gpt-5.5-pro': {
14237
+ inputCost: 30 / 1_000_000,
14238
+ outputCost: 180 / 1_000_000,
14239
+ },
15165
14240
  'gpt-5.2': {
15166
14241
  inputCost: 1.75 / 1_000_000,
15167
14242
  cacheHitCost: 0.175 / 1_000_000,
@@ -15199,8 +14274,9 @@ const deepseekModelCosts = {
15199
14274
  outputCost: 2.19 / 1_000_000, // $2.19 per 1M tokens
15200
14275
  },
15201
14276
  };
15202
- function shouldUseGPT54HighContextPricing(model, inputTokens) {
15203
- return (model === 'gpt-5.4' || model === 'gpt-5.4-pro') && inputTokens > GPT_5_4_HIGH_CONTEXT_THRESHOLD_TOKENS;
14277
+ function shouldUseGPT5HighContextPricing(model, inputTokens) {
14278
+ return ((model === 'gpt-5.4' || model === 'gpt-5.4-pro' || model === 'gpt-5.5') &&
14279
+ inputTokens > GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS);
15204
14280
  }
15205
14281
  /** Image generation costs in USD per image. Based on OpenAI pricing as of Feb 2025. */
15206
14282
  const openAiImageCosts = {
@@ -15253,10 +14329,10 @@ function calculateCost(provider, model, inputTokens, outputTokens, reasoningToke
15253
14329
  }
15254
14330
  let outputCost = outputTokens * modelCosts.outputCost;
15255
14331
  let reasoningCost = (reasoningTokens || 0) * modelCosts.outputCost;
15256
- if (provider === 'openai' && shouldUseGPT54HighContextPricing(model, inputTokens)) {
15257
- inputCost *= GPT_5_4_HIGH_CONTEXT_INPUT_MULTIPLIER;
15258
- outputCost *= GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
15259
- reasoningCost *= GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
14332
+ if (provider === 'openai' && shouldUseGPT5HighContextPricing(model, inputTokens)) {
14333
+ inputCost *= GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER;
14334
+ outputCost *= GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
14335
+ reasoningCost *= GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
15260
14336
  }
15261
14337
  return inputCost + outputCost + reasoningCost;
15262
14338
  }
@@ -15658,6 +14734,8 @@ const isSupportedModel = (model) => {
15658
14734
  'gpt-5.1',
15659
14735
  'gpt-5.4',
15660
14736
  'gpt-5.4-pro',
14737
+ 'gpt-5.5',
14738
+ 'gpt-5.5-pro',
15661
14739
  'gpt-5.2',
15662
14740
  'gpt-5.2-pro',
15663
14741
  'gpt-5.1-codex',
@@ -15687,6 +14765,8 @@ function supportsTemperature(model) {
15687
14765
  'gpt-5.1',
15688
14766
  'gpt-5.4',
15689
14767
  'gpt-5.4-pro',
14768
+ 'gpt-5.5',
14769
+ 'gpt-5.5-pro',
15690
14770
  'gpt-5.2',
15691
14771
  'gpt-5.2-pro',
15692
14772
  'gpt-5.1-codex',
@@ -15717,6 +14797,8 @@ function isGPT5Model(model) {
15717
14797
  'gpt-5.1',
15718
14798
  'gpt-5.4',
15719
14799
  'gpt-5.4-pro',
14800
+ 'gpt-5.5',
14801
+ 'gpt-5.5-pro',
15720
14802
  'gpt-5.2',
15721
14803
  'gpt-5.2-pro',
15722
14804
  'gpt-5.1-codex',
@@ -16027,6 +15109,8 @@ const MULTIMODAL_VISION_MODELS = new Set([
16027
15109
  'gpt-5.1',
16028
15110
  'gpt-5.4',
16029
15111
  'gpt-5.4-pro',
15112
+ 'gpt-5.5',
15113
+ 'gpt-5.5-pro',
16030
15114
  'gpt-5.2',
16031
15115
  'gpt-5.2-pro',
16032
15116
  'gpt-5.1-codex',
@@ -16544,128 +15628,6 @@ async function makeOpenRouterCall(input, options = {}) {
16544
15628
  };
16545
15629
  }
16546
15630
 
16547
- /**
16548
- * A class to measure performance of code execution.
16549
- *
16550
- * This utility records elapsed time during execution and provides a detailed
16551
- * breakdown of elapsed time between checkpoints.
16552
- *
16553
- * Usage example:
16554
- * const timer = new PerformanceTimer();
16555
- * timer.checkpoint('Start Processing');
16556
- * // ... code for processing
16557
- * timer.checkpoint('After Task 1');
16558
- * // ... additional processing code
16559
- * timer.stop();
16560
- * console.log(timer.generateReport());
16561
- *
16562
- * Methods:
16563
- * - checkpoint(label: string): Record a timestamped checkpoint.
16564
- * - stop(): Stop the timer and compute the total elapsed time.
16565
- * - generateReport(): Return a performance report with total time and phases.
16566
- * - formatPerformanceReport(): Return a formatted string of the performance report.
16567
- * - getElapsedSeconds(): Get the current elapsed time in seconds.
16568
- */
16569
- class PerformanceTimer {
16570
- startTime;
16571
- checkpoints;
16572
- totalTime;
16573
- constructor() {
16574
- this.startTime = performance.now();
16575
- this.checkpoints = new Map();
16576
- this.totalTime = null;
16577
- }
16578
- /**
16579
- * Gets the current elapsed time in seconds.
16580
- *
16581
- * @returns The elapsed time in seconds with 3 decimal places of precision.
16582
- */
16583
- getElapsedSeconds() {
16584
- const elapsedMs = this.totalTime ?? (performance.now() - this.startTime);
16585
- return Number((elapsedMs / 1000).toFixed(3));
16586
- }
16587
- /**
16588
- * Records a checkpoint with the given label.
16589
- *
16590
- * @param label - A descriptive label for the checkpoint.
16591
- */
16592
- checkpoint(label) {
16593
- this.checkpoints.set(label, performance.now());
16594
- }
16595
- /**
16596
- * Stops the timer and sets the total elapsed time.
16597
- */
16598
- stop() {
16599
- this.totalTime = performance.now() - this.startTime;
16600
- }
16601
- /**
16602
- * Generates a performance report containing the total elapsed time and
16603
- * breakdown of phases between checkpoints.
16604
- *
16605
- * @returns A performance report object.
16606
- * @throws Error if the timer is not stopped before generating the report.
16607
- */
16608
- analyseReportData() {
16609
- if (this.totalTime === null) {
16610
- throw new Error('Timer must be stopped before generating report');
16611
- }
16612
- const report = {
16613
- totalTime: this.totalTime,
16614
- phases: []
16615
- };
16616
- let lastTime = this.startTime;
16617
- // Convert checkpoints to a sorted array by time
16618
- const sortedCheckpoints = Array.from(this.checkpoints.entries())
16619
- .sort((a, b) => a[1] - b[1]);
16620
- for (const [label, time] of sortedCheckpoints) {
16621
- const duration = time - lastTime;
16622
- if (duration < 0) {
16623
- throw new Error(`Negative duration detected for checkpoint: ${label}`);
16624
- }
16625
- report.phases.push({
16626
- label,
16627
- duration
16628
- });
16629
- lastTime = time;
16630
- }
16631
- // Add final phase from last checkpoint to stop
16632
- if (sortedCheckpoints.length > 0) {
16633
- const finalDuration = this.totalTime - lastTime;
16634
- report.phases.push({
16635
- label: 'Final Processing',
16636
- duration: Math.max(finalDuration, 0) // Prevent negative duration
16637
- });
16638
- }
16639
- return report;
16640
- }
16641
- /**
16642
- * Returns a formatted string of the performance report.
16643
- *
16644
- * @returns A string detailing the total execution time and phase breakdown.
16645
- */
16646
- generateReport() {
16647
- const report = this.analyseReportData();
16648
- // Format numbers with thousands separators and 2 decimal places
16649
- const formatNumber = (num) => num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
16650
- // Calculate column widths
16651
- const maxLabelLength = Math.max(...report.phases.map(p => p.label.length), 30);
16652
- const maxDurationLength = Math.max(...report.phases.map(p => formatNumber(p.duration).length), 10);
16653
- // Create table header
16654
- let output = `╔${'═'.repeat(maxLabelLength + 2)}╦${'═'.repeat(maxDurationLength + 2)}╗\n`;
16655
- output += `║ ${'Phase'.padEnd(maxLabelLength)} ║ ${'Time (ms)'.padEnd(maxDurationLength)} ║\n`;
16656
- output += `╠${'═'.repeat(maxLabelLength + 2)}╬${'═'.repeat(maxDurationLength + 2)}╣\n`;
16657
- // Add table rows
16658
- report.phases.forEach(phase => {
16659
- output += `║ ${phase.label.padEnd(maxLabelLength)} ║ ${formatNumber(phase.duration).padStart(maxDurationLength)} ║\n`;
16660
- });
16661
- // Add table footer with total
16662
- output += `╠${'═'.repeat(maxLabelLength + 2)}╬${'═'.repeat(maxDurationLength + 2)}╣\n`;
16663
- output += `║ ${'Total'.padEnd(maxLabelLength)} ║ ${formatNumber(report.totalTime).padStart(maxDurationLength)} ║\n`;
16664
- output += `╚${'═'.repeat(maxLabelLength + 2)}╩${'═'.repeat(maxDurationLength + 2)}╝\n`;
16665
- return output;
16666
- }
16667
- }
16668
-
16669
15631
  function getDefaultExportFromCjs (x) {
16670
15632
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
16671
15633
  }
@@ -23420,1844 +22382,87 @@ class AlpacaMarketDataAPI extends EventEmitter {
23420
22382
  }
23421
22383
  }
23422
22384
  // Export the singleton instance
23423
- const marketDataAPI = AlpacaMarketDataAPI.getInstance();
23424
-
23425
- const limitPriceSlippagePercent100 = 0.1; // 0.1%
23426
- class AlpacaRequestError extends Error {
23427
- method;
23428
- status;
23429
- url;
23430
- responseCode;
23431
- responseDetails;
23432
- rawResponse;
23433
- symbol;
23434
- constructor(params) {
23435
- super(params.message);
23436
- this.name = 'AlpacaRequestError';
23437
- this.method = params.method;
23438
- this.status = params.status ?? null;
23439
- this.url = params.url;
23440
- this.responseCode = params.responseCode ?? null;
23441
- this.responseDetails = params.responseDetails ?? null;
23442
- this.rawResponse = params.rawResponse ?? null;
23443
- this.symbol = params.symbol;
23444
- }
23445
- }
23446
- /**
23447
- Websocket example
23448
- const alpacaAPI = createAlpacaTradingAPI(credentials); // type AlpacaCredentials
23449
- alpacaAPI.onTradeUpdate((update: TradeUpdate) => {
23450
- this.log(`Received trade update: event ${update.event} for an order to ${update.order.side} ${update.order.qty} of ${update.order.symbol}`);
23451
- });
23452
- alpacaAPI.connectWebsocket(); // necessary to connect to the WebSocket
23453
-
23454
- Portfolio History examples
23455
- // Get standard portfolio history
23456
- const portfolioHistory = await alpacaAPI.getPortfolioHistory({
23457
- timeframe: '1D',
23458
- period: '1M'
23459
- });
23460
-
23461
- // Get daily portfolio history with current day included (if available from hourly data)
23462
- const dailyHistory = await alpacaAPI.getPortfolioDailyHistory({
23463
- period: '1M'
23464
- });
23465
- */
23466
- class AlpacaTradingAPI {
23467
- static new(credentials) {
23468
- return new AlpacaTradingAPI(credentials);
23469
- }
23470
- static getInstance(credentials) {
23471
- return new AlpacaTradingAPI(credentials);
23472
- }
23473
- ws = null;
23474
- headers;
23475
- tradeUpdateCallback = null;
23476
- credentials;
23477
- apiBaseUrl;
23478
- wsUrl;
23479
- authenticated = false;
23480
- connecting = false;
23481
- reconnectDelay = 10000; // 10 seconds between reconnection attempts
23482
- reconnectTimeout = null;
23483
- messageHandlers = new Map();
23484
- debugLogging = false;
23485
- manualDisconnect = false;
23486
- /**
23487
- * Constructor for AlpacaTradingAPI
23488
- * @param credentials - Alpaca credentials,
23489
- * accountName: string; // The account identifier used inthis.logs and tracking
23490
- * apiKey: string; // Alpaca API key
23491
- * apiSecret: string; // Alpaca API secret
23492
- * type: AlpacaAccountType;
23493
- * orderType: AlpacaOrderType;
23494
- * @param options - Optional options
23495
- * debugLogging: boolean; // Whether to log messages of type 'debug'
23496
- */
23497
- constructor(credentials, options) {
23498
- this.credentials = credentials;
23499
- // Set URLs based on account type
23500
- this.apiBaseUrl =
23501
- credentials.type === 'PAPER' ? 'https://paper-api.alpaca.markets/v2' : 'https://api.alpaca.markets/v2';
23502
- this.wsUrl =
23503
- credentials.type === 'PAPER' ? 'wss://paper-api.alpaca.markets/stream' : 'wss://api.alpaca.markets/stream';
23504
- this.headers = {
23505
- 'APCA-API-KEY-ID': credentials.apiKey,
23506
- 'APCA-API-SECRET-KEY': credentials.apiSecret,
23507
- 'Content-Type': 'application/json',
23508
- };
23509
- // Initialize message handlers
23510
- this.messageHandlers.set('authorization', this.handleAuthMessage.bind(this));
23511
- this.messageHandlers.set('listening', this.handleListenMessage.bind(this));
23512
- this.messageHandlers.set('trade_updates', this.handleTradeUpdate.bind(this));
23513
- this.debugLogging = options?.debugLogging || false;
23514
- }
23515
- log(message, options = { type: 'info' }) {
23516
- if (this.debugLogging && options.type === 'debug') {
23517
- return;
23518
- }
23519
- log$1(message, { ...options, source: 'AlpacaTradingAPI', account: this.credentials.accountName });
23520
- }
23521
- /**
23522
- * Round a price to the nearest 2 decimal places for Alpaca, or 4 decimal places for prices less than $1
23523
- * @param price - The price to round
23524
- * @returns The rounded price
23525
- */
23526
- roundPriceForAlpaca = (price) => {
23527
- return price >= 1 ? Math.round(price * 100) / 100 : Math.round(price * 10000) / 10000;
23528
- };
23529
- isJsonObject(value) {
23530
- return value !== null && !Array.isArray(value) && typeof value === 'object';
23531
- }
23532
- parseAlpacaAPIErrorResponse(rawResponse) {
23533
- if (rawResponse.trim() === '') {
23534
- return null;
23535
- }
23536
- try {
23537
- const parsedValue = JSON.parse(rawResponse);
23538
- if (!this.isJsonObject(parsedValue)) {
23539
- return null;
23540
- }
23541
- const code = parsedValue['code'];
23542
- const message = parsedValue['message'];
23543
- const symbol = parsedValue['symbol'];
23544
- if (typeof code !== 'number' || typeof message !== 'string') {
23545
- return null;
23546
- }
23547
- if (symbol !== undefined && typeof symbol !== 'string') {
23548
- return null;
23549
- }
23550
- return parsedValue;
23551
- }
23552
- catch {
23553
- return null;
23554
- }
23555
- }
23556
- formatJsonValueForLog(value) {
23557
- if (value === undefined) {
23558
- return 'undefined';
23559
- }
23560
- if (Array.isArray(value)) {
23561
- return value.map((entry) => this.formatJsonValueForLog(entry)).join(', ');
23562
- }
23563
- if (value === null) {
23564
- return 'null';
23565
- }
23566
- if (typeof value === 'object') {
23567
- return JSON.stringify(value);
23568
- }
23569
- return `${value}`;
23570
- }
23571
- formatAlpacaAPIErrorDetails(errorResponse) {
23572
- return Object.entries(errorResponse)
23573
- .filter(([key]) => key !== 'code' && key !== 'message' && key !== 'symbol')
23574
- .map(([key, value]) => `${key}=${this.formatJsonValueForLog(value)}`)
23575
- .join(', ');
23576
- }
23577
- formatRequestAction(requestContext) {
23578
- return requestContext?.action ? `${requestContext.action} failed` : 'Alpaca request failed';
23579
- }
23580
- buildAlpacaAPIErrorMessage(params) {
23581
- const { requestContext, status, url, errorResponse, rawResponse } = params;
23582
- const action = this.formatRequestAction(requestContext);
23583
- const message = errorResponse?.message ?? (rawResponse || 'No error body returned');
23584
- const responseCode = errorResponse?.code ? ` (code ${errorResponse.code})` : '';
23585
- const detailSummary = errorResponse ? this.formatAlpacaAPIErrorDetails(errorResponse) : '';
23586
- const detailText = detailSummary !== ''
23587
- ? ` Details: ${detailSummary}.`
23588
- : rawResponse.trim() !== '' && errorResponse === null
23589
- ? ` Response: ${rawResponse}.`
23590
- : '';
23591
- return `${action}: Alpaca API ${status}${responseCode}: ${message}.${detailText} Url: ${url}`;
23592
- }
23593
- buildRequestExecutionErrorMessage(params) {
23594
- const { requestContext, url, errorMessage } = params;
23595
- const action = this.formatRequestAction(requestContext);
23596
- return `${action}: ${errorMessage}. Url: ${url}`;
23597
- }
23598
- handleAuthMessage(data) {
23599
- if (data.status === 'authorized') {
23600
- this.authenticated = true;
23601
- this.log('WebSocket authenticated');
23602
- }
23603
- else {
23604
- this.log(`Authentication failed: ${data.message || 'Unknown error'}`, {
23605
- type: 'error',
23606
- });
23607
- }
23608
- }
23609
- handleListenMessage(data) {
23610
- if (data.streams?.includes('trade_updates')) {
23611
- this.log('Successfully subscribed to trade updates');
23612
- }
23613
- }
23614
- handleTradeUpdate(data) {
23615
- if (this.tradeUpdateCallback) {
23616
- this.log(`Trade update: ${data.event} to ${data.order.side} ${data.order.qty} shares${data.event === 'partial_fill' ? ` (filled shares: ${data.order.filled_qty})` : ''}, type ${data.order.type}`, {
23617
- symbol: data.order.symbol,
23618
- type: 'debug',
23619
- });
23620
- this.tradeUpdateCallback(data);
23621
- }
23622
- }
23623
- handleMessage(message) {
23624
- try {
23625
- const data = JSON.parse(message);
23626
- const handler = this.messageHandlers.get(data.stream);
23627
- if (handler) {
23628
- handler(data.data);
23629
- }
23630
- else {
23631
- this.log(`Received message for unknown stream: ${data.stream}`, {
23632
- type: 'warn',
23633
- });
23634
- }
23635
- }
23636
- catch (error) {
23637
- this.log('Failed to parse WebSocket message', {
23638
- type: 'error',
23639
- metadata: { error: error instanceof Error ? error.message : 'Unknown error' },
23640
- });
23641
- }
22385
+ AlpacaMarketDataAPI.getInstance();
22386
+
22387
+ const disco = {
22388
+ llm: {
22389
+ call: makeLLMCall,
22390
+ seek: makeDeepseekCall,
22391
+ images: makeImagesCall,
22392
+ open: makeOpenRouterCall,
22393
+ }};
22394
+
22395
+ // Test file for context functionality
22396
+ process.env['DEBUG'] === 'true' || false;
22397
+ async function testLLM() {
22398
+ if (!process.env.OPENAI_API_KEY) {
22399
+ console.log('Skipping OpenAI LLM tests: OPENAI_API_KEY not set');
22400
+ return;
23642
22401
  }
23643
- connectWebsocket() {
23644
- // Reset manual disconnect flag to allow reconnection logic
23645
- this.manualDisconnect = false;
23646
- if (this.connecting) {
23647
- this.log('Connection attempt skipped - already connecting');
23648
- return;
22402
+ const models = ['gpt-5.5'];
22403
+ for (const model of models) {
22404
+ console.log(`\nTesting model: ${model}`);
22405
+ // 1. Basic call
22406
+ const basic = await disco.llm.call('What is the capital of France? Respond with only the city name.', { model });
22407
+ if (!basic.response || !basic.response.toLowerCase().includes('paris')) {
22408
+ throw new Error(`Expected Paris from basic LLM response, received: ${basic.response}`);
23649
22409
  }
23650
- if (this.ws?.readyState === WebSocket.OPEN) {
23651
- this.log('Connection attempt skipped - already connected');
23652
- return;
22410
+ console.log(`Response: ${basic.response}`);
22411
+ const jsonPrompt = 'Return a JSON object with keys country and capital for France.';
22412
+ const json = await disco.llm.call(jsonPrompt, { model, responseFormat: 'json' });
22413
+ if (json.response.country.toLowerCase() !== 'france' || json.response.capital.toLowerCase() !== 'paris') {
22414
+ throw new Error(`Unexpected JSON response from LLM: ${JSON.stringify(json.response)}`);
23653
22415
  }
23654
- this.connecting = true;
23655
- if (this.ws) {
23656
- this.ws.removeAllListeners();
23657
- this.ws.terminate();
23658
- this.ws = null;
22416
+ console.log(`Response: ${JSON.stringify(json.response)}`);
22417
+ // 3. Web search
22418
+ const searchPrompt = 'Use web search to identify the official OpenAI model ID for GPT-5.5. Respond with the model ID only.';
22419
+ const tool = await disco.llm.call(searchPrompt, { model, useWebSearch: true });
22420
+ if (!tool.response || !tool.response.includes('gpt-5.5')) {
22421
+ throw new Error(`Expected web search response to include gpt-5.5, received: ${tool.response}`);
23659
22422
  }
23660
- this.log(`Connecting to WebSocket at ${this.wsUrl}...`);
23661
- this.ws = new WebSocket(this.wsUrl);
23662
- this.ws.on('open', async () => {
23663
- try {
23664
- this.log('WebSocket connected');
23665
- await this.authenticate();
23666
- await this.subscribeToTradeUpdates();
23667
- this.connecting = false;
23668
- }
23669
- catch (error) {
23670
- this.log('Failed to setup WebSocket connection', {
23671
- type: 'error',
23672
- metadata: { error: error instanceof Error ? error.message : 'Unknown error' },
23673
- });
23674
- this.ws?.close();
23675
- }
23676
- });
23677
- this.ws.on('message', (data) => {
23678
- this.handleMessage(data.toString());
23679
- });
23680
- this.ws.on('error', (error) => {
23681
- this.log('WebSocket error', {
23682
- type: 'error',
23683
- metadata: { error: error instanceof Error ? error.message : 'Unknown error' },
23684
- });
23685
- this.connecting = false;
23686
- });
23687
- this.ws.on('close', () => {
23688
- this.log('WebSocket connection closed');
23689
- this.authenticated = false;
23690
- this.connecting = false;
23691
- // Clear any existing reconnect timeout
23692
- if (this.reconnectTimeout) {
23693
- clearTimeout(this.reconnectTimeout);
23694
- this.reconnectTimeout = null;
23695
- }
23696
- // Schedule reconnection unless this was a manual disconnect
23697
- if (!this.manualDisconnect) {
23698
- this.reconnectTimeout = setTimeout(() => {
23699
- this.log('Attempting to reconnect...');
23700
- this.connectWebsocket();
23701
- }, this.reconnectDelay);
23702
- }
23703
- });
22423
+ console.log(`Response: ${tool.response}`);
23704
22424
  }
23705
- /**
23706
- * Cleanly disconnect from the WebSocket and stop auto-reconnects
23707
- */
23708
- disconnect() {
23709
- // Prevent auto-reconnect scheduling
23710
- this.manualDisconnect = true;
23711
- // Clear any scheduled reconnect
23712
- if (this.reconnectTimeout) {
23713
- clearTimeout(this.reconnectTimeout);
23714
- this.reconnectTimeout = null;
23715
- }
23716
- if (this.ws) {
23717
- this.log('Disconnecting WebSocket...');
23718
- // Remove listeners first to avoid duplicate handlers after reconnects
23719
- this.ws.removeAllListeners();
23720
- try {
23721
- // Attempt graceful close
23722
- if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
23723
- this.ws.close(1000, 'Client disconnect');
23724
- }
23725
- else {
23726
- this.ws.terminate();
23727
- }
23728
- }
23729
- catch {
23730
- // Fallback terminate on any error
23731
- try {
23732
- this.ws.terminate();
23733
- }
23734
- catch {
23735
- /* no-op */
23736
- }
23737
- }
23738
- this.ws = null;
23739
- }
23740
- this.authenticated = false;
23741
- this.connecting = false;
23742
- this.log('WebSocket disconnected');
22425
+ }
22426
+ async function testLLMStructuredOutputWithZod() {
22427
+ if (!process.env.OPENAI_API_KEY) {
22428
+ console.log('Skipping OpenAI structured output Zod test: OPENAI_API_KEY not set');
22429
+ return;
23743
22430
  }
23744
- async authenticate() {
23745
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
23746
- throw new Error('WebSocket not ready for authentication');
23747
- }
23748
- const authMessage = {
23749
- action: 'auth',
23750
- key: this.credentials.apiKey,
23751
- secret: this.credentials.apiSecret,
23752
- };
23753
- this.ws.send(JSON.stringify(authMessage));
23754
- return new Promise((resolve, reject) => {
23755
- const authTimeout = setTimeout(() => {
23756
- this.log('Authentication timeout', { type: 'error' });
23757
- reject(new Error('Authentication timed out'));
23758
- }, 10000);
23759
- const handleAuthResponse = (data) => {
23760
- try {
23761
- const message = JSON.parse(data.toString());
23762
- if (message.stream === 'authorization') {
23763
- this.ws?.removeListener('message', handleAuthResponse);
23764
- clearTimeout(authTimeout);
23765
- if (message.data?.status === 'authorized') {
23766
- this.authenticated = true;
23767
- resolve();
23768
- }
23769
- else {
23770
- const error = `Authentication failed: ${message.data?.message || 'Unknown error'}`;
23771
- this.log(error, { type: 'error' });
23772
- reject(new Error(error));
23773
- }
23774
- }
23775
- }
23776
- catch (error) {
23777
- this.log('Failed to parse auth response', {
23778
- type: 'error',
23779
- metadata: { error: error instanceof Error ? error.message : 'Unknown error' },
23780
- });
23781
- }
23782
- };
23783
- this.ws?.on('message', handleAuthResponse);
23784
- });
22431
+ const structuredSchema = object({
22432
+ country: string(),
22433
+ capital: string(),
22434
+ region: string(),
22435
+ countryCode: string(),
22436
+ });
22437
+ const prompt = 'Return details for France using the exact schema.';
22438
+ const result = await disco.llm.call(prompt, {
22439
+ model: 'gpt-5.5',
22440
+ schema: structuredSchema,
22441
+ schemaName: 'capital_response',
22442
+ schemaDescription: 'Country and capital metadata',
22443
+ schemaStrict: true,
22444
+ });
22445
+ const response = result.response;
22446
+ if (!response.country || !response.capital || !response.region || !response.countryCode) {
22447
+ throw new Error(`Structured response is missing required fields: ${JSON.stringify(response)}`);
23785
22448
  }
23786
- async subscribeToTradeUpdates() {
23787
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.authenticated) {
23788
- throw new Error('WebSocket not ready for subscription');
23789
- }
23790
- const listenMessage = {
23791
- action: 'listen',
23792
- data: {
23793
- streams: ['trade_updates'],
23794
- },
23795
- };
23796
- this.ws.send(JSON.stringify(listenMessage));
23797
- return new Promise((resolve, reject) => {
23798
- const listenTimeout = setTimeout(() => {
23799
- reject(new Error('Subscribe timeout'));
23800
- }, 10000);
23801
- const handleListenResponse = (data) => {
23802
- try {
23803
- const message = JSON.parse(data.toString());
23804
- if (message.stream === 'listening') {
23805
- this.ws?.removeListener('message', handleListenResponse);
23806
- clearTimeout(listenTimeout);
23807
- if (message.data?.streams?.includes('trade_updates')) {
23808
- resolve();
23809
- }
23810
- else {
23811
- reject(new Error('Failed to subscribe to trade updates'));
23812
- }
23813
- }
23814
- }
23815
- catch (error) {
23816
- this.log('Failed to parse listen response', {
23817
- type: 'error',
23818
- metadata: { error: error instanceof Error ? error.message : 'Unknown error' },
23819
- });
23820
- }
23821
- };
23822
- this.ws?.on('message', handleListenResponse);
23823
- });
22449
+ if (response.country.toLowerCase() !== 'france') {
22450
+ throw new Error(`Expected country to be France, received: ${response.country}`);
23824
22451
  }
23825
- async makeRequest(endpoint, method = 'GET', body, queryString = '', requestContext) {
23826
- const url = `${this.apiBaseUrl}${endpoint}${queryString}`;
23827
- try {
23828
- const response = await fetch(url, {
23829
- method,
23830
- headers: this.headers,
23831
- body: body ? JSON.stringify(body) : undefined,
23832
- });
23833
- if (!response.ok) {
23834
- const rawResponse = await response.text();
23835
- const errorResponse = this.parseAlpacaAPIErrorResponse(rawResponse);
23836
- const symbol = requestContext?.symbol ?? errorResponse?.symbol;
23837
- const error = new AlpacaRequestError({
23838
- message: this.buildAlpacaAPIErrorMessage({
23839
- requestContext,
23840
- status: response.status,
23841
- url,
23842
- errorResponse,
23843
- rawResponse,
23844
- }),
23845
- method,
23846
- status: response.status,
23847
- url,
23848
- responseCode: errorResponse?.code ?? null,
23849
- responseDetails: errorResponse,
23850
- rawResponse,
23851
- symbol,
23852
- });
23853
- this.log(error.message, { symbol, type: 'error' });
23854
- throw error;
23855
- }
23856
- // Handle responses with no content (e.g., 204 No Content)
23857
- if (response.status === 204 || response.headers.get('content-length') === '0') {
23858
- return null;
23859
- }
23860
- const contentType = response.headers.get('content-type');
23861
- if (contentType && contentType.includes('application/json')) {
23862
- return (await response.json());
23863
- }
23864
- // For non-JSON responses, return the text content
23865
- const textContent = await response.text();
23866
- return (textContent || null);
23867
- }
23868
- catch (error) {
23869
- if (error instanceof AlpacaRequestError) {
23870
- throw error;
23871
- }
23872
- const normalizedError = error instanceof Error ? error : new Error(`${error}`);
23873
- const symbol = requestContext?.symbol;
23874
- const requestError = new AlpacaRequestError({
23875
- message: this.buildRequestExecutionErrorMessage({
23876
- requestContext,
23877
- url,
23878
- errorMessage: normalizedError.message,
23879
- }),
23880
- method,
23881
- url,
23882
- rawResponse: null,
23883
- symbol,
23884
- });
23885
- this.log(requestError.message, { symbol, type: 'error' });
23886
- throw requestError;
23887
- }
22452
+ if (response.capital.toLowerCase() !== 'paris') {
22453
+ throw new Error(`Expected capital to be Paris, received: ${response.capital}`);
23888
22454
  }
23889
- async getPositions(assetClass) {
23890
- const positions = await this.makeRequest('/positions', 'GET', undefined, '', {
23891
- action: 'Get positions',
23892
- });
23893
- if (assetClass) {
23894
- return positions.filter((position) => position.asset_class === assetClass);
23895
- }
23896
- return positions;
23897
- }
23898
- /**
23899
- * Get all orders
23900
- * @param params (GetOrdersParams) - optional parameters to filter the orders
23901
- * - status: 'open' | 'closed' | 'all'
23902
- * - limit: number
23903
- * - after: string
23904
- * - until: string
23905
- * - direction: 'asc' | 'desc'
23906
- * - nested: boolean
23907
- * - symbols: string[], an array of all the symbols
23908
- * - side: 'buy' | 'sell'
23909
- * @returns all orders
23910
- */
23911
- async getOrders(params = {}) {
23912
- const queryParams = new URLSearchParams();
23913
- if (params.status)
23914
- queryParams.append('status', params.status);
23915
- if (params.limit)
23916
- queryParams.append('limit', params.limit.toString());
23917
- if (params.after)
23918
- queryParams.append('after', params.after);
23919
- if (params.until)
23920
- queryParams.append('until', params.until);
23921
- if (params.direction)
23922
- queryParams.append('direction', params.direction);
23923
- if (params.nested)
23924
- queryParams.append('nested', params.nested.toString());
23925
- if (params.symbols)
23926
- queryParams.append('symbols', params.symbols.join(','));
23927
- if (params.side)
23928
- queryParams.append('side', params.side);
23929
- const endpoint = `/orders${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
23930
- return this.makeRequest(endpoint, 'GET', undefined, '', {
23931
- action: 'Get orders',
23932
- symbol: params.symbols?.join(','),
23933
- });
23934
- }
23935
- async getAccountDetails() {
23936
- return this.makeRequest('/account', 'GET', undefined, '', {
23937
- action: 'Get account details',
23938
- });
23939
- }
23940
- /**
23941
- * Create a trailing stop order
23942
- * @param symbol (string) - the symbol of the order
23943
- * @param qty (number) - the quantity of the order
23944
- * @param side (string) - the side of the order
23945
- * @param trailPercent100 (number) - the trail percent of the order (scale 100, i.e. 0.5 = 0.5%)
23946
- * @param position_intent (string) - the position intent of the order
23947
- * @param client_order_id (string) - optional client order id
23948
- * @returns The created trailing stop order
23949
- */
23950
- async createTrailingStop(symbol, qty, side, trailPercent100, position_intent, client_order_id) {
23951
- this.log(`Creating trailing stop ${side.toUpperCase()} ${qty} shares for ${symbol} with trail percent ${trailPercent100}%`, {
23952
- symbol,
23953
- });
23954
- const body = {
23955
- symbol,
23956
- qty: Math.abs(qty).toString(),
23957
- side,
23958
- position_intent,
23959
- order_class: 'simple',
23960
- type: 'trailing_stop',
23961
- trail_percent: trailPercent100.toString(),
23962
- time_in_force: 'gtc',
23963
- };
23964
- if (client_order_id !== undefined) {
23965
- body.client_order_id = client_order_id;
23966
- }
23967
- return this.makeRequest('/orders', 'POST', body, '', {
23968
- action: 'Create trailing stop order',
23969
- symbol,
23970
- });
23971
- }
23972
- /**
23973
- * Create a stop order (stop or stop-limit)
23974
- * @param symbol (string) - the symbol of the order
23975
- * @param qty (number) - the quantity of the order
23976
- * @param side (string) - the side of the order
23977
- * @param stopPrice (number) - the stop price that triggers the order
23978
- * @param position_intent (string) - the position intent of the order
23979
- * @param limitPrice (number) - optional limit price (if provided, creates a stop-limit order)
23980
- * @param client_order_id (string) - optional client order id
23981
- * @returns The created stop order
23982
- */
23983
- async createStopOrder(symbol, qty, side, stopPrice, position_intent, limitPrice, client_order_id) {
23984
- const isStopLimit = limitPrice !== undefined;
23985
- const orderType = isStopLimit ? 'stop-limit' : 'stop';
23986
- this.log(`Creating ${orderType} ${side.toUpperCase()} ${qty} shares for ${symbol} with stop price ${stopPrice}${isStopLimit ? ` and limit price ${limitPrice}` : ''}`, {
23987
- symbol,
23988
- });
23989
- const body = {
23990
- symbol,
23991
- qty: Math.abs(qty).toString(),
23992
- side,
23993
- position_intent,
23994
- order_class: 'simple',
23995
- type: isStopLimit ? 'stop_limit' : 'stop',
23996
- stop_price: this.roundPriceForAlpaca(stopPrice).toString(),
23997
- time_in_force: 'gtc',
23998
- };
23999
- if (limitPrice !== undefined) {
24000
- body.limit_price = this.roundPriceForAlpaca(limitPrice).toString();
24001
- }
24002
- if (client_order_id !== undefined) {
24003
- body.client_order_id = client_order_id;
24004
- }
24005
- return this.makeRequest('/orders', 'POST', body, '', {
24006
- action: `Create ${orderType} order`,
24007
- symbol,
24008
- });
24009
- }
24010
- /**
24011
- * Create a market order
24012
- * @param symbol (string) - the symbol of the order
24013
- * @param qty (number) - the quantity of the order
24014
- * @param side (string) - the side of the order
24015
- * @param position_intent (string) - the position intent of the order. Important for knowing if a position needs a trailing stop.
24016
- * @param client_order_id (string) - optional client order id
24017
- */
24018
- async createMarketOrder(symbol, qty, side, position_intent, client_order_id) {
24019
- this.log(`Creating market order for ${symbol}: ${side} ${qty} shares (${position_intent})`, {
24020
- symbol,
24021
- });
24022
- const body = {
24023
- symbol,
24024
- qty: Math.abs(qty).toString(),
24025
- side,
24026
- position_intent,
24027
- type: 'market',
24028
- time_in_force: 'day',
24029
- order_class: 'simple',
24030
- };
24031
- if (client_order_id !== undefined) {
24032
- body.client_order_id = client_order_id;
24033
- }
24034
- return this.makeRequest('/orders', 'POST', body, '', {
24035
- action: 'Create market order',
24036
- symbol,
24037
- });
24038
- }
24039
- /**
24040
- * Create a Market on Open (MOO) order - executes in the opening auction
24041
- *
24042
- * IMPORTANT TIMING CONSTRAINTS:
24043
- * - Valid submission window: After 7:00pm ET and before 9:28am ET
24044
- * - Orders submitted between 9:28am and 7:00pm ET will be REJECTED
24045
- * - Orders submitted after 7:00pm ET are queued for the next trading day's opening auction
24046
- * - Example: An order at 8:00pm Monday will execute at Tuesday's market open (9:30am)
24047
- *
24048
- * @param symbol - The symbol of the order
24049
- * @param qty - The quantity of shares
24050
- * @param side - Buy or sell
24051
- * @param position_intent - The position intent (buy_to_open, sell_to_close, etc.)
24052
- * @param client_order_id - Optional client order id
24053
- * @returns The created order
24054
- */
24055
- async createMOOOrder(symbol, qty, side, position_intent, client_order_id) {
24056
- this.log(`Creating Market on Open order for ${symbol}: ${side} ${qty} shares (${position_intent})`, {
24057
- symbol,
24058
- });
24059
- const body = {
24060
- symbol,
24061
- qty: Math.abs(qty).toString(),
24062
- side,
24063
- position_intent,
24064
- type: 'market',
24065
- time_in_force: 'opg',
24066
- order_class: 'simple',
24067
- };
24068
- if (client_order_id !== undefined) {
24069
- body.client_order_id = client_order_id;
24070
- }
24071
- return this.makeRequest('/orders', 'POST', body, '', {
24072
- action: 'Create market on open order',
24073
- symbol,
24074
- });
24075
- }
24076
- /**
24077
- * Create a Market on Close (MOC) order - executes in the closing auction
24078
- *
24079
- * IMPORTANT TIMING CONSTRAINTS:
24080
- * - Valid submission window: After 7:00pm ET (previous day) and before 3:50pm ET (same day)
24081
- * - Orders submitted between 3:50pm and 7:00pm ET will be REJECTED
24082
- * - Orders submitted after 7:00pm ET are queued for the next trading day's closing auction
24083
- * - Example: An order at 8:00pm Monday will execute at Tuesday's market close (4:00pm)
24084
- *
24085
- * @param symbol - The symbol of the order
24086
- * @param qty - The quantity of shares
24087
- * @param side - Buy or sell
24088
- * @param position_intent - The position intent (buy_to_open, sell_to_close, etc.)
24089
- * @param client_order_id - Optional client order id
24090
- * @returns The created order
24091
- */
24092
- async createMOCOrder(symbol, qty, side, position_intent, client_order_id) {
24093
- this.log(`Creating Market on Close order for ${symbol}: ${side} ${qty} shares (${position_intent})`, {
24094
- symbol,
24095
- });
24096
- const body = {
24097
- symbol,
24098
- qty: Math.abs(qty).toString(),
24099
- side,
24100
- position_intent,
24101
- type: 'market',
24102
- time_in_force: 'cls',
24103
- order_class: 'simple',
24104
- };
24105
- if (client_order_id !== undefined) {
24106
- body.client_order_id = client_order_id;
24107
- }
24108
- return this.makeRequest('/orders', 'POST', body, '', {
24109
- action: 'Create market on close order',
24110
- symbol,
24111
- });
24112
- }
24113
- /**
24114
- * Create an OCO (One-Cancels-Other) order with take profit and stop loss
24115
- * @param symbol (string) - the symbol of the order
24116
- * @param qty (number) - the quantity of the order
24117
- * @param side (string) - the side of the order (buy or sell)
24118
- * @param position_intent (string) - the position intent of the order
24119
- * @param limitPrice (number) - the limit price for the entry order (OCO orders must be limit orders)
24120
- * @param takeProfitPrice (number) - the take profit price
24121
- * @param stopLossPrice (number) - the stop loss price
24122
- * @param stopLossLimitPrice (number) - optional limit price for stop loss (creates stop-limit instead of stop)
24123
- * @param client_order_id (string) - optional client order id
24124
- * @returns The created OCO order
24125
- */
24126
- async createOCOOrder(symbol, qty, side, position_intent, limitPrice, takeProfitPrice, stopLossPrice, stopLossLimitPrice, client_order_id) {
24127
- this.log(`Creating OCO order ${side.toUpperCase()} ${qty} shares for ${symbol} at limit ${limitPrice} with take profit ${takeProfitPrice} and stop loss ${stopLossPrice}`, {
24128
- symbol,
24129
- });
24130
- const body = {
24131
- symbol,
24132
- qty: Math.abs(qty).toString(),
24133
- side,
24134
- position_intent,
24135
- order_class: 'oco',
24136
- type: 'limit',
24137
- limit_price: this.roundPriceForAlpaca(limitPrice).toString(),
24138
- time_in_force: 'gtc',
24139
- take_profit: {
24140
- limit_price: this.roundPriceForAlpaca(takeProfitPrice).toString(),
24141
- },
24142
- stop_loss: {
24143
- stop_price: this.roundPriceForAlpaca(stopLossPrice).toString(),
24144
- },
24145
- };
24146
- // If stop loss limit price is provided, create stop-limit order
24147
- if (stopLossLimitPrice !== undefined) {
24148
- body.stop_loss = {
24149
- stop_price: this.roundPriceForAlpaca(stopLossPrice).toString(),
24150
- limit_price: this.roundPriceForAlpaca(stopLossLimitPrice).toString(),
24151
- };
24152
- }
24153
- if (client_order_id !== undefined) {
24154
- body.client_order_id = client_order_id;
24155
- }
24156
- return this.makeRequest('/orders', 'POST', body, '', {
24157
- action: 'Create OCO order',
24158
- symbol,
24159
- });
24160
- }
24161
- /**
24162
- * Get the current trail percent for a symbol, assuming that it has an open position and a trailing stop order to close it. Because this relies on an orders request for one symbol, you can't do it too often.
24163
- * @param symbol (string) - the symbol of the order
24164
- * @returns the current trail percent
24165
- */
24166
- async getCurrentTrailPercent(symbol) {
24167
- const orders = await this.getOrders({
24168
- status: 'open',
24169
- symbols: [symbol],
24170
- });
24171
- const trailingStopOrder = orders.find((order) => order.type === 'trailing_stop' &&
24172
- (order.position_intent === 'sell_to_close' || order.position_intent === 'buy_to_close'));
24173
- if (!trailingStopOrder) {
24174
- this.log(`No closing trailing stop order found for ${symbol}`, {
24175
- symbol,
24176
- });
24177
- return null;
24178
- }
24179
- if (!trailingStopOrder.trail_percent) {
24180
- this.log(`Trailing stop order found for ${symbol} but no trail_percent value`, {
24181
- symbol,
24182
- });
24183
- return null;
24184
- }
24185
- const trailPercent = parseFloat(trailingStopOrder.trail_percent);
24186
- return trailPercent;
24187
- }
24188
- /**
24189
- * Update the trail percent for a trailing stop order
24190
- * @param symbol (string) - the symbol of the order
24191
- * @param trailPercent100 (number) - the trail percent of the order (scale 100, i.e. 0.5 = 0.5%)
24192
- */
24193
- async updateTrailingStop(symbol, trailPercent100) {
24194
- // First get all open orders for this symbol
24195
- const orders = await this.getOrders({
24196
- status: 'open',
24197
- symbols: [symbol],
24198
- });
24199
- // Find the trailing stop order
24200
- const trailingStopOrder = orders.find((order) => order.type === 'trailing_stop');
24201
- if (!trailingStopOrder) {
24202
- this.log(`No open trailing stop order found for ${symbol}`, { type: 'error', symbol });
24203
- return;
24204
- }
24205
- // Check if the trail_percent is already set to the desired value
24206
- const currentTrailPercent = trailingStopOrder.trail_percent ? parseFloat(trailingStopOrder.trail_percent) : null;
24207
- // Compare with a small epsilon to handle floating point precision
24208
- const epsilon = 0.0001;
24209
- if (currentTrailPercent !== null && Math.abs(currentTrailPercent - trailPercent100) < epsilon) {
24210
- this.log(`Trailing stop for ${symbol} already set to ${trailPercent100}% (current: ${currentTrailPercent}%), skipping update`, {
24211
- symbol,
24212
- });
24213
- return;
24214
- }
24215
- this.log(`Updating trailing stop for ${symbol} from ${currentTrailPercent}% to ${trailPercent100}%`, {
24216
- symbol,
24217
- });
24218
- await this.makeRequest(`/orders/${trailingStopOrder.id}`, 'PATCH', { trail: trailPercent100.toString() }, '', {
24219
- action: 'Update trailing stop order',
24220
- symbol,
24221
- });
24222
- }
24223
- /**
24224
- * Cancel all open orders
24225
- */
24226
- async cancelAllOrders() {
24227
- this.log(`Canceling all open orders`);
24228
- try {
24229
- await this.makeRequest('/orders', 'DELETE', undefined, '', {
24230
- action: 'Cancel all open orders',
24231
- });
24232
- }
24233
- catch (error) {
24234
- if (!(error instanceof AlpacaRequestError)) {
24235
- this.log(`Error canceling all orders: ${error instanceof Error ? error.message : `${error}`}`, {
24236
- type: 'error',
24237
- });
24238
- }
24239
- }
24240
- }
24241
- /**
24242
- * Cancel a specific order by its ID
24243
- * @param orderId The id of the order to cancel
24244
- * @throws Error if the order is not cancelable (status 422) or if the order doesn't exist
24245
- * @returns Promise that resolves when the order is successfully canceled
24246
- */
24247
- async cancelOrder(orderId) {
24248
- this.log(`Attempting to cancel order ${orderId}`);
24249
- try {
24250
- await this.makeRequest(`/orders/${orderId}`, 'DELETE', undefined, '', {
24251
- action: `Cancel order ${orderId}`,
24252
- });
24253
- this.log(`Successfully canceled order ${orderId}`);
24254
- }
24255
- catch (error) {
24256
- // If the error is a 422, it means the order is not cancelable
24257
- if (error instanceof AlpacaRequestError && error.status === 422) {
24258
- throw new Error(`Order ${orderId} is not cancelable`);
24259
- }
24260
- // Re-throw other errors
24261
- throw error;
24262
- }
24263
- }
24264
- /**
24265
- * Create a limit order
24266
- * @param symbol (string) - the symbol of the order
24267
- * @param qty (number) - the quantity of the order
24268
- * @param side (string) - the side of the order
24269
- * @param limitPrice (number) - the limit price of the order
24270
- * @param position_intent (string) - the position intent of the order
24271
- * @param extended_hours (boolean) - whether the order is in extended hours
24272
- * @param client_order_id (string) - the client order id of the order
24273
- */
24274
- async createLimitOrder(symbol, qty, side, limitPrice, position_intent, extended_hours = false, client_order_id) {
24275
- this.log(`Creating limit order for ${symbol}: ${side} ${qty} shares at $${limitPrice.toFixed(2)} (${position_intent})`, {
24276
- symbol,
24277
- });
24278
- const body = {
24279
- symbol,
24280
- qty: Math.abs(qty).toString(),
24281
- side,
24282
- position_intent,
24283
- type: 'limit',
24284
- limit_price: this.roundPriceForAlpaca(limitPrice).toString(),
24285
- time_in_force: 'day',
24286
- order_class: 'simple',
24287
- extended_hours,
24288
- };
24289
- if (client_order_id !== undefined) {
24290
- body.client_order_id = client_order_id;
24291
- }
24292
- return this.makeRequest('/orders', 'POST', body, '', {
24293
- action: 'Create limit order',
24294
- symbol,
24295
- });
24296
- }
24297
- /**
24298
- * Close all equities positions
24299
- * @param options (object) - the options for closing the positions
24300
- * - cancel_orders (boolean) - whether to cancel related orders
24301
- * - useLimitOrders (boolean) - whether to use limit orders to close the positions
24302
- */
24303
- async closeAllPositions(options = { cancel_orders: true, useLimitOrders: false }) {
24304
- this.log(`Closing all positions${options.useLimitOrders ? ' using limit orders' : ''}${options.cancel_orders ? ' and canceling open orders' : ''}`);
24305
- if (options.useLimitOrders) {
24306
- // Get all positions
24307
- const positions = await this.getPositions('us_equity');
24308
- if (positions.length === 0) {
24309
- this.log('No positions to close');
24310
- return;
24311
- }
24312
- this.log(`Found ${positions.length} positions to close`);
24313
- // Get latest quotes for all positions
24314
- const symbols = positions.map((position) => position.symbol);
24315
- const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
24316
- const lengthOfQuotes = Object.keys(quotesResponse.quotes).length;
24317
- if (lengthOfQuotes === 0) {
24318
- this.log('No quotes available for positions, received 0 quotes', {
24319
- type: 'error',
24320
- });
24321
- return;
24322
- }
24323
- if (lengthOfQuotes !== positions.length) {
24324
- this.log(`Received ${lengthOfQuotes} quotes for ${positions.length} positions, expected ${positions.length} quotes`, { type: 'warn' });
24325
- return;
24326
- }
24327
- // Create limit orders to close each position
24328
- for (const position of positions) {
24329
- const quote = quotesResponse.quotes[position.symbol];
24330
- if (!quote) {
24331
- this.log(`No quote available for ${position.symbol}, skipping limit order`, {
24332
- symbol: position.symbol,
24333
- type: 'warn',
24334
- });
24335
- continue;
24336
- }
24337
- const qty = Math.abs(parseFloat(position.qty));
24338
- const side = position.side === 'long' ? 'sell' : 'buy';
24339
- const positionIntent = side === 'sell' ? 'sell_to_close' : 'buy_to_close';
24340
- // Get the current price from the quote
24341
- const currentPrice = side === 'sell' ? quote.bp : quote.ap; // Use bid for sells, ask for buys
24342
- if (!currentPrice) {
24343
- this.log(`No valid price available for ${position.symbol}, skipping limit order`, {
24344
- symbol: position.symbol,
24345
- type: 'warn',
24346
- });
24347
- continue;
24348
- }
24349
- // Apply slippage from config
24350
- const limitSlippagePercent1 = limitPriceSlippagePercent100 / 100;
24351
- const limitPrice = side === 'sell'
24352
- ? this.roundPriceForAlpaca(currentPrice * (1 - limitSlippagePercent1)) // Sell slightly lower
24353
- : this.roundPriceForAlpaca(currentPrice * (1 + limitSlippagePercent1)); // Buy slightly higher
24354
- this.log(`Creating limit order to close ${position.symbol} position: ${side} ${qty} shares at $${limitPrice.toFixed(2)}`, {
24355
- symbol: position.symbol,
24356
- });
24357
- await this.createLimitOrder(position.symbol, qty, side, limitPrice, positionIntent);
24358
- }
24359
- }
24360
- else {
24361
- await this.makeRequest('/positions', 'DELETE', undefined, options.cancel_orders ? '?cancel_orders=true' : '', {
24362
- action: 'Close all positions',
24363
- });
24364
- }
24365
- }
24366
- /**
24367
- * Close all equities positions using limit orders during extended hours trading
24368
- * @param cancelOrders Whether to cancel related orders (default: true)
24369
- * @returns Promise that resolves when all positions are closed
24370
- */
24371
- async closeAllPositionsAfterHours() {
24372
- this.log('Closing all positions using limit orders during extended hours trading');
24373
- // Get all positions
24374
- const positions = await this.getPositions();
24375
- this.log(`Found ${positions.length} positions to close`);
24376
- if (positions.length === 0) {
24377
- this.log('No positions to close');
24378
- return;
24379
- }
24380
- await this.cancelAllOrders();
24381
- this.log(`Cancelled all open orders`);
24382
- // Get latest quotes for all positions
24383
- const symbols = positions.map((position) => position.symbol);
24384
- const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
24385
- // Create limit orders to close each position
24386
- for (const position of positions) {
24387
- const quote = quotesResponse.quotes[position.symbol];
24388
- if (!quote) {
24389
- this.log(`No quote available for ${position.symbol}, skipping limit order`, {
24390
- symbol: position.symbol,
24391
- type: 'warn',
24392
- });
24393
- continue;
24394
- }
24395
- const qty = Math.abs(parseFloat(position.qty));
24396
- const side = position.side === 'long' ? 'sell' : 'buy';
24397
- const positionIntent = side === 'sell' ? 'sell_to_close' : 'buy_to_close';
24398
- // Get the current price from the quote
24399
- const currentPrice = side === 'sell' ? quote.bp : quote.ap; // Use bid for sells, ask for buys
24400
- if (!currentPrice) {
24401
- this.log(`No valid price available for ${position.symbol}, skipping limit order`, {
24402
- symbol: position.symbol,
24403
- type: 'warn',
24404
- });
24405
- continue;
24406
- }
24407
- // Apply slippage from config
24408
- const limitSlippagePercent1 = limitPriceSlippagePercent100 / 100;
24409
- const limitPrice = side === 'sell'
24410
- ? this.roundPriceForAlpaca(currentPrice * (1 - limitSlippagePercent1)) // Sell slightly lower
24411
- : this.roundPriceForAlpaca(currentPrice * (1 + limitSlippagePercent1)); // Buy slightly higher
24412
- this.log(`Creating extended hours limit order to close ${position.symbol} position: ${side} ${qty} shares at $${limitPrice.toFixed(2)}`, {
24413
- symbol: position.symbol,
24414
- });
24415
- await this.createLimitOrder(position.symbol, qty, side, limitPrice, positionIntent, true // Enable extended hours trading
24416
- );
24417
- }
24418
- this.log(`All positions closed: ${positions.map((p) => p.symbol).join(', ')}`);
24419
- }
24420
- onTradeUpdate(callback) {
24421
- this.tradeUpdateCallback = callback;
24422
- }
24423
- /**
24424
- * Get portfolio history for the account
24425
- * @param params Parameters for the portfolio history request
24426
- * @returns Portfolio history data
24427
- */
24428
- async getPortfolioHistory(params) {
24429
- const queryParams = new URLSearchParams();
24430
- if (params.timeframe)
24431
- queryParams.append('timeframe', params.timeframe);
24432
- if (params.period)
24433
- queryParams.append('period', params.period);
24434
- if (params.extended_hours !== undefined)
24435
- queryParams.append('extended_hours', params.extended_hours.toString());
24436
- if (params.start)
24437
- queryParams.append('start', params.start);
24438
- if (params.end)
24439
- queryParams.append('end', params.end);
24440
- if (params.date_end)
24441
- queryParams.append('date_end', params.date_end);
24442
- const response = await this.makeRequest(`/account/portfolio/history?${queryParams.toString()}`, 'GET', undefined, '', {
24443
- action: 'Get portfolio history',
24444
- });
24445
- return response;
24446
- }
24447
- /**
24448
- * Get portfolio daily history for the account, ensuring the most recent day is included
24449
- * by combining daily and hourly history if needed.
24450
- *
24451
- * This function performs two API calls:
24452
- * 1. Retrieves daily portfolio history
24453
- * 2. Retrieves hourly portfolio history to check for more recent data
24454
- *
24455
- * If hourly history has timestamps more recent than the last timestamp in daily history,
24456
- * it appends one additional day to the daily history using the most recent hourly values.
24457
- *
24458
- * @param params Parameters for the portfolio history request (same as getPortfolioHistory except timeframe is forced to '1D')
24459
- * @returns Portfolio history data with daily timeframe, including the most recent day if available from hourly data
24460
- */
24461
- async getPortfolioDailyHistory(params) {
24462
- // Get daily and hourly history in parallel
24463
- const dailyParams = { ...params, timeframe: '1D' };
24464
- const hourlyParams = { timeframe: '1Min', period: '1D' };
24465
- const [dailyHistory, hourlyHistory] = await Promise.all([
24466
- this.getPortfolioHistory(dailyParams),
24467
- this.getPortfolioHistory(hourlyParams),
24468
- ]);
24469
- // If no hourly history, return daily as-is
24470
- if (!hourlyHistory.timestamp || hourlyHistory.timestamp.length === 0) {
24471
- return dailyHistory;
24472
- }
24473
- // Get the last timestamp from daily history
24474
- const lastDailyTimestamp = dailyHistory.timestamp[dailyHistory.timestamp.length - 1];
24475
- // Check if hourly history has more recent data
24476
- const recentHourlyData = hourlyHistory.timestamp
24477
- .map((timestamp, index) => ({ timestamp, index }))
24478
- .filter(({ timestamp }) => timestamp > lastDailyTimestamp);
24479
- // If no more recent hourly data, return daily history as-is
24480
- if (recentHourlyData.length === 0) {
24481
- return dailyHistory;
24482
- }
24483
- // Get the most recent hourly data point
24484
- const mostRecentHourly = recentHourlyData[recentHourlyData.length - 1];
24485
- const mostRecentIndex = mostRecentHourly.index;
24486
- // Calculate the timestamp for the new daily entry.
24487
- // Alpaca's daily history timestamps are at 00:00:00Z for the calendar day
24488
- // following the NY trading date. Derive the trading date in NY time from the
24489
- // most recent intraday timestamp, then set the new daily timestamp to
24490
- // midnight UTC of the next calendar day.
24491
- const mostRecentMs = mostRecentHourly.timestamp * 1000; // hourly timestamps are seconds
24492
- const tradingDateStr = getTradingDate(new Date(mostRecentMs)); // e.g., '2025-09-05' (NY trading date)
24493
- const [yearStr, monthStr, dayStr] = tradingDateStr.split('-');
24494
- const year = Number(yearStr);
24495
- const month = Number(monthStr); // 1-based
24496
- const day = Number(dayStr);
24497
- const newDailyTimestamp = Math.floor(Date.UTC(year, month - 1, day + 1, 0, 0, 0, 0) / 1000);
24498
- // Create a new daily history entry with the most recent hourly values
24499
- const updatedDailyHistory = {
24500
- ...dailyHistory,
24501
- timestamp: [...dailyHistory.timestamp, newDailyTimestamp],
24502
- equity: [...dailyHistory.equity, hourlyHistory.equity[mostRecentIndex]],
24503
- profit_loss: [...dailyHistory.profit_loss, hourlyHistory.profit_loss[mostRecentIndex]],
24504
- profit_loss_pct: [...dailyHistory.profit_loss_pct, hourlyHistory.profit_loss_pct[mostRecentIndex]],
24505
- };
24506
- return updatedDailyHistory;
24507
- }
24508
- /**
24509
- * Get option contracts based on specified parameters
24510
- * @param params Parameters to filter option contracts
24511
- * @returns Option contracts matching the criteria
24512
- */
24513
- async getOptionContracts(params) {
24514
- const queryParams = new URLSearchParams();
24515
- queryParams.append('underlying_symbols', params.underlying_symbols.join(','));
24516
- if (params.expiration_date_gte)
24517
- queryParams.append('expiration_date_gte', params.expiration_date_gte);
24518
- if (params.expiration_date_lte)
24519
- queryParams.append('expiration_date_lte', params.expiration_date_lte);
24520
- if (params.strike_price_gte)
24521
- queryParams.append('strike_price_gte', params.strike_price_gte);
24522
- if (params.strike_price_lte)
24523
- queryParams.append('strike_price_lte', params.strike_price_lte);
24524
- if (params.type)
24525
- queryParams.append('type', params.type);
24526
- if (params.status)
24527
- queryParams.append('status', params.status);
24528
- if (params.limit)
24529
- queryParams.append('limit', params.limit.toString());
24530
- if (params.page_token)
24531
- queryParams.append('page_token', params.page_token);
24532
- this.log(`Fetching option contracts for ${params.underlying_symbols.join(', ')}`, {
24533
- symbol: params.underlying_symbols.join(', '),
24534
- });
24535
- const response = await this.makeRequest(`/options/contracts?${queryParams.toString()}`, 'GET', undefined, '', {
24536
- action: 'Get option contracts',
24537
- symbol: params.underlying_symbols.join(', '),
24538
- });
24539
- this.log(`Found ${response.option_contracts.length} option contracts`, {
24540
- symbol: params.underlying_symbols.join(', '),
24541
- });
24542
- return response;
24543
- }
24544
- /**
24545
- * Get a specific option contract by symbol or ID
24546
- * @param symbolOrId The symbol or ID of the option contract
24547
- * @returns The option contract details
24548
- */
24549
- async getOptionContract(symbolOrId) {
24550
- this.log(`Fetching option contract details for ${symbolOrId}`, {
24551
- symbol: symbolOrId,
24552
- });
24553
- const response = await this.makeRequest(`/options/contracts/${symbolOrId}`, 'GET', undefined, '', {
24554
- action: 'Get option contract details',
24555
- symbol: symbolOrId,
24556
- });
24557
- this.log(`Found option contract details for ${symbolOrId}: ${response.name}`, {
24558
- symbol: symbolOrId,
24559
- });
24560
- return response;
24561
- }
24562
- /**
24563
- * Create a simple option order (market or limit)
24564
- * @param symbol Option contract symbol
24565
- * @param qty Quantity of contracts (must be a whole number)
24566
- * @param side Buy or sell
24567
- * @param position_intent Position intent (buy_to_open, buy_to_close, sell_to_open, sell_to_close)
24568
- * @param type Order type (market or limit)
24569
- * @param limitPrice Limit price (required for limit orders)
24570
- * @returns The created order
24571
- */
24572
- async createOptionOrder(symbol, qty, side, position_intent, type, limitPrice) {
24573
- if (!Number.isInteger(qty) || qty <= 0) {
24574
- this.log('Quantity must be a positive whole number for option orders', { symbol, type: 'error' });
24575
- }
24576
- if (type === 'limit' && limitPrice === undefined) {
24577
- this.log('Limit price is required for limit orders', { symbol, type: 'error' });
24578
- }
24579
- this.log(`Creating ${type} option order for ${symbol}: ${side} ${qty} contracts (${position_intent})${type === 'limit' ? ` at $${limitPrice?.toFixed(2)}` : ''}`, {
24580
- symbol,
24581
- });
24582
- const orderData = {
24583
- symbol,
24584
- qty: qty.toString(),
24585
- side,
24586
- position_intent,
24587
- type,
24588
- time_in_force: 'day',
24589
- order_class: 'simple',
24590
- extended_hours: false,
24591
- };
24592
- if (type === 'limit' && limitPrice !== undefined) {
24593
- orderData.limit_price = this.roundPriceForAlpaca(limitPrice).toString();
24594
- }
24595
- return this.makeRequest('/orders', 'POST', orderData, '', {
24596
- action: 'Create option order',
24597
- symbol,
24598
- });
24599
- }
24600
- /**
24601
- * Create a multi-leg option order
24602
- * @param legs Array of order legs
24603
- * @param qty Quantity of the multi-leg order (must be a whole number)
24604
- * @param type Order type (market or limit)
24605
- * @param limitPrice Limit price (required for limit orders)
24606
- * @returns The created multi-leg order
24607
- */
24608
- async createMultiLegOptionOrder(legs, qty, type, limitPrice) {
24609
- const legSymbols = legs.map((leg) => leg.symbol).join(', ');
24610
- if (!Number.isInteger(qty) || qty <= 0) {
24611
- this.log('Quantity must be a positive whole number for option orders', {
24612
- symbol: legSymbols,
24613
- type: 'error',
24614
- });
24615
- }
24616
- if (type === 'limit' && limitPrice === undefined) {
24617
- this.log('Limit price is required for limit orders', { symbol: legSymbols, type: 'error' });
24618
- }
24619
- if (legs.length < 2) {
24620
- this.log('Multi-leg orders require at least 2 legs', { symbol: legSymbols, type: 'error' });
24621
- }
24622
- this.log(`Creating multi-leg ${type} option order with ${legs.length} legs (${legSymbols})${type === 'limit' ? ` at $${limitPrice?.toFixed(2)}` : ''}`, {
24623
- symbol: legSymbols,
24624
- });
24625
- const orderData = {
24626
- order_class: 'mleg',
24627
- qty: qty.toString(),
24628
- type,
24629
- time_in_force: 'day',
24630
- legs,
24631
- };
24632
- if (type === 'limit' && limitPrice !== undefined) {
24633
- orderData.limit_price = this.roundPriceForAlpaca(limitPrice).toString();
24634
- }
24635
- return this.makeRequest('/orders', 'POST', orderData, '', {
24636
- action: 'Create multi-leg option order',
24637
- symbol: legSymbols,
24638
- });
24639
- }
24640
- /**
24641
- * Exercise an option contract
24642
- * @param symbolOrContractId The symbol or ID of the option contract to exercise
24643
- * @returns Response from the exercise request
24644
- */
24645
- async exerciseOption(symbolOrContractId) {
24646
- this.log(`Exercising option contract ${symbolOrContractId}`, {
24647
- symbol: symbolOrContractId,
24648
- });
24649
- return this.makeRequest(`/positions/${symbolOrContractId}/exercise`, 'POST', undefined, '', {
24650
- action: 'Exercise option contract',
24651
- symbol: symbolOrContractId,
24652
- });
24653
- }
24654
- /**
24655
- * Get option positions
24656
- * @returns Array of option positions
24657
- */
24658
- async getOptionPositions() {
24659
- this.log('Fetching option positions');
24660
- const positions = await this.getPositions('us_option');
24661
- return positions;
24662
- }
24663
- async getOptionsOpenSpreadTrades() {
24664
- this.log('Fetching option open trades');
24665
- // this function will get all open positions, extract the symbol and see when they were created.
24666
- // figures out when the earliest date was (should be today)
24667
- // then it pulls all orders after the earliest date that were closed and that were of class 'mleg'
24668
- // Each of these contains two orders. they look like this:
24669
- }
24670
- /**
24671
- * Get option account activities (exercises, assignments, expirations)
24672
- * @param activityType Type of option activity to filter by
24673
- * @param date Date to filter activities (YYYY-MM-DD format)
24674
- * @returns Array of option account activities
24675
- */
24676
- async getOptionActivities(activityType, date) {
24677
- const queryParams = new URLSearchParams();
24678
- if (activityType) {
24679
- queryParams.append('activity_types', activityType);
24680
- }
24681
- else {
24682
- queryParams.append('activity_types', 'OPEXC,OPASN,OPEXP');
24683
- }
24684
- if (date) {
24685
- queryParams.append('date', date);
24686
- }
24687
- this.log(`Fetching option activities${activityType ? ` of type ${activityType}` : ''}${date ? ` for date ${date}` : ''}`);
24688
- return this.makeRequest(`/account/activities?${queryParams.toString()}`, 'GET', undefined, '', {
24689
- action: 'Get option activities',
24690
- });
24691
- }
24692
- /**
24693
- * Create a long call spread (buy lower strike call, sell higher strike call)
24694
- * @param lowerStrikeCallSymbol Symbol of the lower strike call option
24695
- * @param higherStrikeCallSymbol Symbol of the higher strike call option
24696
- * @param qty Quantity of spreads to create (must be a whole number)
24697
- * @param limitPrice Limit price for the spread
24698
- * @returns The created multi-leg order
24699
- */
24700
- async createLongCallSpread(lowerStrikeCallSymbol, higherStrikeCallSymbol, qty, limitPrice) {
24701
- this.log(`Creating long call spread: Buy ${lowerStrikeCallSymbol}, Sell ${higherStrikeCallSymbol}, Qty: ${qty}, Price: $${limitPrice.toFixed(2)}`, {
24702
- symbol: `${lowerStrikeCallSymbol},${higherStrikeCallSymbol}`,
24703
- });
24704
- const legs = [
24705
- {
24706
- symbol: lowerStrikeCallSymbol,
24707
- ratio_qty: '1',
24708
- side: 'buy',
24709
- position_intent: 'buy_to_open',
24710
- },
24711
- {
24712
- symbol: higherStrikeCallSymbol,
24713
- ratio_qty: '1',
24714
- side: 'sell',
24715
- position_intent: 'sell_to_open',
24716
- },
24717
- ];
24718
- return this.createMultiLegOptionOrder(legs, qty, 'limit', limitPrice);
24719
- }
24720
- /**
24721
- * Create a long put spread (buy higher strike put, sell lower strike put)
24722
- * @param higherStrikePutSymbol Symbol of the higher strike put option
24723
- * @param lowerStrikePutSymbol Symbol of the lower strike put option
24724
- * @param qty Quantity of spreads to create (must be a whole number)
24725
- * @param limitPrice Limit price for the spread
24726
- * @returns The created multi-leg order
24727
- */
24728
- async createLongPutSpread(higherStrikePutSymbol, lowerStrikePutSymbol, qty, limitPrice) {
24729
- this.log(`Creating long put spread: Buy ${higherStrikePutSymbol}, Sell ${lowerStrikePutSymbol}, Qty: ${qty}, Price: $${limitPrice.toFixed(2)}`, {
24730
- symbol: `${higherStrikePutSymbol},${lowerStrikePutSymbol}`,
24731
- });
24732
- const legs = [
24733
- {
24734
- symbol: higherStrikePutSymbol,
24735
- ratio_qty: '1',
24736
- side: 'buy',
24737
- position_intent: 'buy_to_open',
24738
- },
24739
- {
24740
- symbol: lowerStrikePutSymbol,
24741
- ratio_qty: '1',
24742
- side: 'sell',
24743
- position_intent: 'sell_to_open',
24744
- },
24745
- ];
24746
- return this.createMultiLegOptionOrder(legs, qty, 'limit', limitPrice);
24747
- }
24748
- /**
24749
- * Create an iron condor (sell call spread and put spread)
24750
- * @param longPutSymbol Symbol of the lower strike put (long)
24751
- * @param shortPutSymbol Symbol of the higher strike put (short)
24752
- * @param shortCallSymbol Symbol of the lower strike call (short)
24753
- * @param longCallSymbol Symbol of the higher strike call (long)
24754
- * @param qty Quantity of iron condors to create (must be a whole number)
24755
- * @param limitPrice Limit price for the iron condor (credit)
24756
- * @returns The created multi-leg order
24757
- */
24758
- async createIronCondor(longPutSymbol, shortPutSymbol, shortCallSymbol, longCallSymbol, qty, limitPrice) {
24759
- this.log(`Creating iron condor with ${qty} contracts at $${limitPrice.toFixed(2)}`, {
24760
- symbol: `${longPutSymbol},${shortPutSymbol},${shortCallSymbol},${longCallSymbol}`,
24761
- });
24762
- const legs = [
24763
- {
24764
- symbol: longPutSymbol,
24765
- ratio_qty: '1',
24766
- side: 'buy',
24767
- position_intent: 'buy_to_open',
24768
- },
24769
- {
24770
- symbol: shortPutSymbol,
24771
- ratio_qty: '1',
24772
- side: 'sell',
24773
- position_intent: 'sell_to_open',
24774
- },
24775
- {
24776
- symbol: shortCallSymbol,
24777
- ratio_qty: '1',
24778
- side: 'sell',
24779
- position_intent: 'sell_to_open',
24780
- },
24781
- {
24782
- symbol: longCallSymbol,
24783
- ratio_qty: '1',
24784
- side: 'buy',
24785
- position_intent: 'buy_to_open',
24786
- },
24787
- ];
24788
- return this.createMultiLegOptionOrder(legs, qty, 'limit', limitPrice);
24789
- }
24790
- /**
24791
- * Create a covered call (sell call option against owned stock)
24792
- * @param stockSymbol Symbol of the underlying stock
24793
- * @param callOptionSymbol Symbol of the call option to sell
24794
- * @param qty Quantity of covered calls to create (must be a whole number)
24795
- * @param limitPrice Limit price for the call option
24796
- * @returns The created order
24797
- */
24798
- async createCoveredCall(stockSymbol, callOptionSymbol, qty, limitPrice) {
24799
- this.log(`Creating covered call: Sell ${callOptionSymbol} against ${stockSymbol}, Qty: ${qty}, Price: $${limitPrice.toFixed(2)}`, {
24800
- symbol: `${stockSymbol},${callOptionSymbol}`,
24801
- });
24802
- // For covered calls, we don't need to include the stock leg if we already own the shares
24803
- // We just create a simple sell order for the call option
24804
- return this.createOptionOrder(callOptionSymbol, qty, 'sell', 'sell_to_open', 'limit', limitPrice);
24805
- }
24806
- /**
24807
- * Roll an option position to a new expiration or strike
24808
- * @param currentOptionSymbol Symbol of the current option position
24809
- * @param newOptionSymbol Symbol of the new option to roll to
24810
- * @param qty Quantity of options to roll (must be a whole number)
24811
- * @param currentPositionSide Side of the current position ('buy' or 'sell')
24812
- * @param limitPrice Net limit price for the roll
24813
- * @returns The created multi-leg order
24814
- */
24815
- async rollOptionPosition(currentOptionSymbol, newOptionSymbol, qty, currentPositionSide, limitPrice) {
24816
- this.log(`Rolling ${qty} ${currentOptionSymbol} to ${newOptionSymbol} at net price $${limitPrice.toFixed(2)}`, {
24817
- symbol: `${currentOptionSymbol},${newOptionSymbol}`,
24818
- });
24819
- // If current position is long, we need to sell to close and buy to open
24820
- // If current position is short, we need to buy to close and sell to open
24821
- const closePositionSide = currentPositionSide === 'buy' ? 'sell' : 'buy';
24822
- const openPositionSide = currentPositionSide;
24823
- const closePositionIntent = closePositionSide === 'buy' ? 'buy_to_close' : 'sell_to_close';
24824
- const openPositionIntent = openPositionSide === 'buy' ? 'buy_to_open' : 'sell_to_open';
24825
- const legs = [
24826
- {
24827
- symbol: currentOptionSymbol,
24828
- ratio_qty: '1',
24829
- side: closePositionSide,
24830
- position_intent: closePositionIntent,
24831
- },
24832
- {
24833
- symbol: newOptionSymbol,
24834
- ratio_qty: '1',
24835
- side: openPositionSide,
24836
- position_intent: openPositionIntent,
24837
- },
24838
- ];
24839
- return this.createMultiLegOptionOrder(legs, qty, 'limit', limitPrice);
24840
- }
24841
- /**
24842
- * Get option chain for a specific underlying symbol and expiration date
24843
- * @param underlyingSymbol The underlying stock symbol
24844
- * @param expirationDate The expiration date (YYYY-MM-DD format)
24845
- * @returns Option contracts for the specified symbol and expiration date
24846
- */
24847
- async getOptionChain(underlyingSymbol, expirationDate) {
24848
- this.log(`Fetching option chain for ${underlyingSymbol} with expiration date ${expirationDate}`, {
24849
- symbol: underlyingSymbol,
24850
- });
24851
- try {
24852
- const params = {
24853
- underlying_symbols: [underlyingSymbol],
24854
- expiration_date_gte: expirationDate,
24855
- expiration_date_lte: expirationDate,
24856
- status: 'active',
24857
- limit: 500, // Get a large number to ensure we get all strikes
24858
- };
24859
- const response = await this.getOptionContracts(params);
24860
- return response.option_contracts || [];
24861
- }
24862
- catch (error) {
24863
- if (!(error instanceof AlpacaRequestError)) {
24864
- this.log(`Failed to fetch option chain for ${underlyingSymbol}: ${error instanceof Error ? error.message : `${error}`}`, {
24865
- type: 'error',
24866
- symbol: underlyingSymbol,
24867
- });
24868
- }
24869
- return [];
24870
- }
24871
- }
24872
- /**
24873
- * Get all available expiration dates for a specific underlying symbol
24874
- * @param underlyingSymbol The underlying stock symbol
24875
- * @returns Array of available expiration dates
24876
- */
24877
- async getOptionExpirationDates(underlyingSymbol) {
24878
- this.log(`Fetching available expiration dates for ${underlyingSymbol}`, {
24879
- symbol: underlyingSymbol,
24880
- });
24881
- try {
24882
- const params = {
24883
- underlying_symbols: [underlyingSymbol],
24884
- status: 'active',
24885
- limit: 1000, // Get a large number to ensure we get contracts with all expiration dates
24886
- };
24887
- const response = await this.getOptionContracts(params);
24888
- // Extract unique expiration dates
24889
- const expirationDates = new Set();
24890
- if (response.option_contracts) {
24891
- response.option_contracts.forEach((contract) => {
24892
- expirationDates.add(contract.expiration_date);
24893
- });
24894
- }
24895
- // Convert to array and sort
24896
- return Array.from(expirationDates).sort();
24897
- }
24898
- catch (error) {
24899
- if (!(error instanceof AlpacaRequestError)) {
24900
- this.log(`Failed to fetch expiration dates for ${underlyingSymbol}: ${error instanceof Error ? error.message : `${error}`}`, {
24901
- type: 'error',
24902
- symbol: underlyingSymbol,
24903
- });
24904
- }
24905
- return [];
24906
- }
24907
- }
24908
- /**
24909
- * Get the current options trading level for the account
24910
- * @returns The options trading level (0-3)
24911
- */
24912
- async getOptionsTradingLevel() {
24913
- this.log('Fetching options trading level');
24914
- const accountDetails = await this.getAccountDetails();
24915
- return accountDetails.options_trading_level || 0;
24916
- }
24917
- /**
24918
- * Check if the account has options trading enabled
24919
- * @returns Boolean indicating if options trading is enabled
24920
- */
24921
- async isOptionsEnabled() {
24922
- this.log('Checking if options trading is enabled');
24923
- const accountDetails = await this.getAccountDetails();
24924
- // Check if options trading level is 2 or higher (Level 2+ allows buying calls/puts)
24925
- // Level 0: Options disabled
24926
- // Level 1: Only covered calls and cash-secured puts
24927
- // Level 2+: Can buy calls and puts (required for executeOptionsOrder)
24928
- const optionsLevel = accountDetails.options_trading_level || 0;
24929
- const isEnabled = optionsLevel >= 2;
24930
- this.log(`Options trading level: ${optionsLevel}, enabled: ${isEnabled}`);
24931
- return isEnabled;
24932
- }
24933
- /**
24934
- * Close all option positions
24935
- * @param cancelOrders Whether to cancel related orders (default: true)
24936
- * @returns Response from the close positions request
24937
- */
24938
- async closeAllOptionPositions(cancelOrders = true) {
24939
- this.log(`Closing all option positions${cancelOrders ? ' and canceling related orders' : ''}`);
24940
- const optionPositions = await this.getOptionPositions();
24941
- if (optionPositions.length === 0) {
24942
- this.log('No option positions to close');
24943
- return;
24944
- }
24945
- // Create market orders to close each position
24946
- for (const position of optionPositions) {
24947
- const side = position.side === 'long' ? 'sell' : 'buy';
24948
- const positionIntent = side === 'sell' ? 'sell_to_close' : 'buy_to_close';
24949
- this.log(`Closing ${position.side} position of ${position.qty} contracts for ${position.symbol}`, {
24950
- symbol: position.symbol,
24951
- });
24952
- await this.createOptionOrder(position.symbol, parseInt(position.qty), side, positionIntent, 'market');
24953
- }
24954
- if (cancelOrders) {
24955
- // Get all open option orders
24956
- const orders = await this.getOrders({ status: 'open' });
24957
- const optionOrders = orders.filter((order) => order.asset_class === 'us_option');
24958
- // Cancel each open option order
24959
- for (const order of optionOrders) {
24960
- this.log(`Canceling open order for ${order.symbol}`, {
24961
- symbol: order.symbol,
24962
- });
24963
- await this.makeRequest(`/orders/${order.id}`, 'DELETE', undefined, '', {
24964
- action: `Cancel option order ${order.id}`,
24965
- symbol: order.symbol,
24966
- });
24967
- }
24968
- }
24969
- }
24970
- /**
24971
- * Close a specific option position
24972
- * @param symbol The option contract symbol
24973
- * @param qty Optional quantity to close (defaults to entire position)
24974
- * @returns The created order
24975
- */
24976
- async closeOptionPosition(symbol, qty) {
24977
- this.log(`Closing option position for ${symbol}${qty ? ` (${qty} contracts)` : ''}`, {
24978
- symbol,
24979
- });
24980
- // Get the position details
24981
- const positions = await this.getOptionPositions();
24982
- const position = positions.find((p) => p.symbol === symbol);
24983
- if (!position) {
24984
- throw new Error(`No position found for option contract ${symbol}`);
24985
- }
24986
- const quantityToClose = qty || parseInt(position.qty);
24987
- const side = position.side === 'long' ? 'sell' : 'buy';
24988
- const positionIntent = side === 'sell' ? 'sell_to_close' : 'buy_to_close';
24989
- return this.createOptionOrder(symbol, quantityToClose, side, positionIntent, 'market');
24990
- }
24991
- /**
24992
- * Create a complete equities trade with optional stop loss and take profit
24993
- * @param params Trade parameters including symbol, qty, side, and optional referencePrice
24994
- * @param options Trade options including order type, extended hours, stop loss, and take profit settings
24995
- * @returns The created order
24996
- */
24997
- async createEquitiesTrade(params, options) {
24998
- const { symbol, qty, side, referencePrice } = params;
24999
- const { type = 'market', limitPrice, extendedHours = false, useStopLoss = false, stopPrice, stopPercent100, useTakeProfit = false, takeProfitPrice, takeProfitPercent100, clientOrderId, } = options || {};
25000
- // Validation: Extended hours + market order is not allowed
25001
- if (extendedHours && type === 'market') {
25002
- this.log('Cannot create market order with extended hours enabled', {
25003
- symbol,
25004
- type: 'error',
25005
- });
25006
- throw new Error('Cannot create market order with extended hours enabled');
25007
- }
25008
- // Validation: Limit orders require limit price
25009
- if (type === 'limit' && limitPrice === undefined) {
25010
- this.log('Limit price is required for limit orders', {
25011
- symbol,
25012
- type: 'error',
25013
- });
25014
- throw new Error('Limit price is required for limit orders');
25015
- }
25016
- let calculatedStopPrice;
25017
- let calculatedTakeProfitPrice;
25018
- // Handle stop loss validation and calculation
25019
- if (useStopLoss) {
25020
- if (stopPrice === undefined && stopPercent100 === undefined) {
25021
- this.log('Either stopPrice or stopPercent100 must be provided when useStopLoss is true', {
25022
- symbol,
25023
- type: 'error',
25024
- });
25025
- throw new Error('Either stopPrice or stopPercent100 must be provided when useStopLoss is true');
25026
- }
25027
- if (stopPercent100 !== undefined) {
25028
- if (referencePrice === undefined) {
25029
- this.log('referencePrice is required when using stopPercent100', {
25030
- symbol,
25031
- type: 'error',
25032
- });
25033
- throw new Error('referencePrice is required when using stopPercent100');
25034
- }
25035
- // Calculate stop price based on percentage and side
25036
- const stopPercentDecimal = stopPercent100 / 100;
25037
- if (side === 'buy') {
25038
- // For buy orders, stop loss is below the reference price
25039
- calculatedStopPrice = referencePrice * (1 - stopPercentDecimal);
25040
- }
25041
- else {
25042
- // For sell orders, stop loss is above the reference price
25043
- calculatedStopPrice = referencePrice * (1 + stopPercentDecimal);
25044
- }
25045
- }
25046
- else {
25047
- calculatedStopPrice = stopPrice;
25048
- }
25049
- }
25050
- // Handle take profit validation and calculation
25051
- if (useTakeProfit) {
25052
- if (takeProfitPrice === undefined && takeProfitPercent100 === undefined) {
25053
- this.log('Either takeProfitPrice or takeProfitPercent100 must be provided when useTakeProfit is true', {
25054
- symbol,
25055
- type: 'error',
25056
- });
25057
- throw new Error('Either takeProfitPrice or takeProfitPercent100 must be provided when useTakeProfit is true');
25058
- }
25059
- if (takeProfitPercent100 !== undefined) {
25060
- if (referencePrice === undefined) {
25061
- this.log('referencePrice is required when using takeProfitPercent100', {
25062
- symbol,
25063
- type: 'error',
25064
- });
25065
- throw new Error('referencePrice is required when using takeProfitPercent100');
25066
- }
25067
- // Calculate take profit price based on percentage and side
25068
- const takeProfitPercentDecimal = takeProfitPercent100 / 100;
25069
- if (side === 'buy') {
25070
- // For buy orders, take profit is above the reference price
25071
- calculatedTakeProfitPrice = referencePrice * (1 + takeProfitPercentDecimal);
25072
- }
25073
- else {
25074
- // For sell orders, take profit is below the reference price
25075
- calculatedTakeProfitPrice = referencePrice * (1 - takeProfitPercentDecimal);
25076
- }
25077
- }
25078
- else {
25079
- calculatedTakeProfitPrice = takeProfitPrice;
25080
- }
25081
- }
25082
- // Determine order class based on what's enabled
25083
- let orderClass = 'simple';
25084
- if (useStopLoss && useTakeProfit) {
25085
- orderClass = 'bracket';
25086
- }
25087
- else if (useStopLoss || useTakeProfit) {
25088
- orderClass = 'oto';
25089
- }
25090
- // Build the order request
25091
- const orderData = {
25092
- symbol,
25093
- qty: Math.abs(qty).toString(),
25094
- side,
25095
- type,
25096
- time_in_force: 'day',
25097
- order_class: orderClass,
25098
- extended_hours: extendedHours,
25099
- position_intent: side === 'buy' ? 'buy_to_open' : 'sell_to_open',
25100
- };
25101
- if (clientOrderId) {
25102
- orderData.client_order_id = clientOrderId;
25103
- }
25104
- // Add limit price for limit orders
25105
- if (type === 'limit' && limitPrice !== undefined) {
25106
- orderData.limit_price = this.roundPriceForAlpaca(limitPrice).toString();
25107
- }
25108
- // Add stop loss if enabled
25109
- if (useStopLoss && calculatedStopPrice !== undefined) {
25110
- orderData.stop_loss = {
25111
- stop_price: this.roundPriceForAlpaca(calculatedStopPrice).toString(),
25112
- };
25113
- }
25114
- // Add take profit if enabled
25115
- if (useTakeProfit && calculatedTakeProfitPrice !== undefined) {
25116
- orderData.take_profit = {
25117
- limit_price: this.roundPriceForAlpaca(calculatedTakeProfitPrice).toString(),
25118
- };
25119
- }
25120
- const logMessage = `Creating ${orderClass} ${type} ${side} order for ${symbol}: ${qty} shares${type === 'limit' ? ` at $${limitPrice?.toFixed(2)}` : ''}${useStopLoss ? ` with stop loss at $${calculatedStopPrice?.toFixed(2)}` : ''}${useTakeProfit ? ` with take profit at $${calculatedTakeProfitPrice?.toFixed(2)}` : ''}${extendedHours ? ' (extended hours)' : ''}`;
25121
- this.log(logMessage, {
25122
- symbol,
25123
- });
25124
- return this.makeRequest('/orders', 'POST', orderData, '', {
25125
- action: 'Create equities trade',
25126
- symbol,
25127
- });
25128
- }
25129
- }
25130
-
25131
- const disco = {
25132
- types: Types,
25133
- alpaca: {
25134
- TradingAPI: AlpacaTradingAPI,
25135
- MarketDataAPI: AlpacaMarketDataAPI,
25136
- },
25137
- format: {
25138
- capFirstLetter: capFirstLetter,
25139
- currency: formatCurrency,
25140
- number: formatNumber,
25141
- pct: formatPercentage,
25142
- dateTimeForGS: dateTimeForGS,
25143
- },
25144
- llm: {
25145
- call: makeLLMCall,
25146
- seek: makeDeepseekCall,
25147
- images: makeImagesCall,
25148
- open: makeOpenRouterCall,
25149
- },
25150
- time: {
25151
- convertDateToMarketTimeZone: convertDateToMarketTimeZone,
25152
- getStartAndEndDates: getStartAndEndDates,
25153
- getMarketOpenClose: getMarketOpenClose,
25154
- getOpenCloseForTradingDay: getOpenCloseForTradingDay,
25155
- getLastFullTradingDate: getLastFullTradingDate,
25156
- getNextMarketDay: getNextMarketDay,
25157
- getPreviousMarketDay: getPreviousMarketDay,
25158
- getMarketTimePeriod: getMarketTimePeriod,
25159
- getMarketStatus: getMarketStatus,
25160
- getNYTimeZone: getNYTimeZone,
25161
- getTradingDate: getTradingDate,
25162
- getTradingStartAndEndDates: getTradingStartAndEndDates,
25163
- getTradingDaysBack: getTradingDaysBack,
25164
- isMarketDay: isMarketDay,
25165
- isWithinMarketHours: isWithinMarketHours,
25166
- countTradingDays: countTradingDays,
25167
- MARKET_TIMES: MARKET_TIMES,
25168
- },
25169
- utils: {
25170
- logIfDebug: logIfDebug,
25171
- fetchWithRetry: fetchWithRetry,
25172
- Timer: PerformanceTimer,
25173
- },
25174
- };
25175
-
25176
- // Test file for context functionality
25177
- process.env['DEBUG'] === 'true' || false;
25178
- async function testLLM() {
25179
- //const models: LLMModel[] = ['gpt-5', 'gpt-5-mini', 'gpt-5.4-mini', 'gpt-5-nano'];
25180
- const models = ['gpt-5.4-mini'];
25181
- for (const model of models) {
25182
- console.log(`\nTesting model: ${model}`);
25183
- // 1. Basic call
25184
- try {
25185
- const basic = await disco.llm.call('What is the capital of France?', { model });
25186
- if (!basic || !basic.response) {
25187
- throw new Error('No response from LLM');
25188
- }
25189
- console.log(`Response: ${basic.response}`);
25190
- }
25191
- catch (e) {
25192
- console.error(` Basic call error:`, e);
25193
- }
25194
- // 2. JSON call
25195
- try {
25196
- const jsonPrompt = 'Return a JSON object with keys country and capital for France.';
25197
- const json = await disco.llm.call(jsonPrompt, { model, responseFormat: 'json' });
25198
- if (!json || !json.response) {
25199
- throw new Error('No response from LLM');
25200
- }
25201
- console.log(`Response: ${JSON.stringify(json.response)}`);
25202
- }
25203
- catch (e) {
25204
- console.error(` JSON call error:`, e);
25205
- }
25206
- // 3. Web search
25207
- try {
25208
- const searchPrompt = 'What is the latest news about artificial intelligence? Respond with 3 sentences max.';
25209
- const tool = await disco.llm.call(searchPrompt, { model, useWebSearch: true });
25210
- if (!tool || !tool.response) {
25211
- throw new Error('No response from LLM');
25212
- }
25213
- console.log(`Response: ${tool.response}`);
25214
- }
25215
- catch (e) {
25216
- console.error(` Web search error:`, e);
25217
- }
22455
+ if (response.countryCode.length !== 2) {
22456
+ throw new Error(`Expected 2-letter countryCode, received: ${response.countryCode}`);
25218
22457
  }
22458
+ console.log('\n[OpenAI] Structured output with Zod test');
22459
+ console.log(' Model:', 'gpt-5.5');
22460
+ console.log(' Response:', response);
22461
+ console.log(' Usage:', result.usage);
25219
22462
  }
25220
- async function testLLMStructuredOutputWithZod() {
25221
- if (!process.env.OPENAI_API_KEY) {
25222
- console.log('Skipping OpenAI structured output Zod test: OPENAI_API_KEY not set');
25223
- return;
25224
- }
25225
- const structuredSchema = object({
25226
- country: string(),
25227
- capital: string(),
25228
- region: string(),
25229
- countryCode: string(),
25230
- });
25231
- try {
25232
- const prompt = 'Return details for France using the exact schema.';
25233
- const result = await disco.llm.call(prompt, {
25234
- model: 'gpt-5.4-mini',
25235
- schema: structuredSchema,
25236
- schemaName: 'capital_response',
25237
- schemaDescription: 'Country and capital metadata',
25238
- schemaStrict: true,
25239
- });
25240
- const response = result.response;
25241
- if (!response.country || !response.capital || !response.region || !response.countryCode) {
25242
- throw new Error(`Structured response is missing required fields: ${JSON.stringify(response)}`);
25243
- }
25244
- if (response.country.toLowerCase() !== 'france') {
25245
- throw new Error(`Expected country to be France, received: ${response.country}`);
25246
- }
25247
- if (response.capital.toLowerCase() !== 'paris') {
25248
- throw new Error(`Expected capital to be Paris, received: ${response.capital}`);
25249
- }
25250
- if (response.countryCode.length !== 2) {
25251
- throw new Error(`Expected 2-letter countryCode, received: ${response.countryCode}`);
25252
- }
25253
- console.log('\n[OpenAI] Structured output with Zod test');
25254
- console.log(' Model:', 'gpt-5-mini');
25255
- console.log(' Response:', response);
25256
- console.log(' Usage:', result.usage);
25257
- }
25258
- catch (error) {
25259
- console.error(' OpenAI structured output with Zod error:', error);
25260
- }
22463
+ async function runActiveTests() {
22464
+ await testLLM();
22465
+ await testLLMStructuredOutputWithZod();
25261
22466
  }
25262
22467
  // testGetPortfolioDailyHistory();
25263
22468
  // testWebSocketConnectAndDisconnect();
@@ -25271,6 +22476,8 @@ async function testLLMStructuredOutputWithZod() {
25271
22476
  // testMarketDataSubscription('SPY');
25272
22477
  // testMarketDataSubscription('FAKEPACA');
25273
22478
  // testGetTradingDate();
25274
- testLLM();
25275
- testLLMStructuredOutputWithZod();
22479
+ runActiveTests().catch((error) => {
22480
+ console.error(`Active test run failed: ${error}`);
22481
+ process.exitCode = 1;
22482
+ });
25276
22483
  //# sourceMappingURL=test.js.map