@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/index-frontend.cjs +63 -266
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +63 -266
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/index.cjs +63 -266
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +63 -266
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +4 -4
- package/dist/test.js +615 -3408
- package/dist/test.js.map +1 -1
- package/dist/types/llm-config.d.ts +1 -1
- package/dist/types/llm-config.d.ts.map +1 -1
- package/dist/types/llm-images.d.ts.map +1 -1
- package/dist/types/llm-openai.d.ts.map +1 -1
- package/dist/types/types/llm-types.d.ts +2 -2
- package/dist/types/types/llm-types.d.ts.map +1 -1
- package/dist/types-frontend/llm-config.d.ts +1 -1
- package/dist/types-frontend/llm-config.d.ts.map +1 -1
- package/dist/types-frontend/llm-images.d.ts.map +1 -1
- package/dist/types-frontend/llm-openai.d.ts.map +1 -1
- package/dist/types-frontend/types/llm-types.d.ts +2 -2
- package/dist/types-frontend/types/llm-types.d.ts.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
-
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
-
|
|
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.
|
|
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: '
|
|
4978
|
-
* voice: '
|
|
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()
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
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: [],
|
|
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
|
|
9480
|
-
|
|
9481
|
-
|
|
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
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
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
|
|
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
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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:
|
|
10008
|
-
patch:
|
|
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 && !
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
12191
|
-
|
|
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 (
|
|
12209
|
-
if (
|
|
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, {
|
|
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
|
|
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
|
-
//
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
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
|
-
//
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
//
|
|
12874
|
-
|
|
12875
|
-
inst
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
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
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
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
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
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
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
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
|
|
15082
|
-
const
|
|
15083
|
-
const
|
|
15084
|
-
/** Token costs in USD per 1M tokens. Last updated
|
|
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
|
|
15203
|
-
return (model === 'gpt-5.4' || model === 'gpt-5.4-pro'
|
|
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' &&
|
|
15257
|
-
inputCost *=
|
|
15258
|
-
outputCost *=
|
|
15259
|
-
reasoningCost *=
|
|
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
|
-
|
|
23424
|
-
|
|
23425
|
-
const
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
23429
|
-
|
|
23430
|
-
|
|
23431
|
-
|
|
23432
|
-
|
|
23433
|
-
|
|
23434
|
-
|
|
23435
|
-
|
|
23436
|
-
|
|
23437
|
-
|
|
23438
|
-
|
|
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
|
-
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
23648
|
-
|
|
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
|
-
|
|
23651
|
-
|
|
23652
|
-
|
|
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
|
-
|
|
23655
|
-
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23707
|
-
|
|
23708
|
-
|
|
23709
|
-
|
|
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
|
-
|
|
23745
|
-
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
23749
|
-
|
|
23750
|
-
|
|
23751
|
-
|
|
23752
|
-
|
|
23753
|
-
|
|
23754
|
-
|
|
23755
|
-
|
|
23756
|
-
|
|
23757
|
-
|
|
23758
|
-
|
|
23759
|
-
|
|
23760
|
-
|
|
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
|
-
|
|
23787
|
-
|
|
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
|
-
|
|
23826
|
-
|
|
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
|
-
|
|
23890
|
-
|
|
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
|
|
25221
|
-
|
|
25222
|
-
|
|
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
|
-
|
|
25275
|
-
|
|
22479
|
+
runActiveTests().catch((error) => {
|
|
22480
|
+
console.error(`Active test run failed: ${error}`);
|
|
22481
|
+
process.exitCode = 1;
|
|
22482
|
+
});
|
|
25276
22483
|
//# sourceMappingURL=test.js.map
|