a tool to help your Letta AI agents navigate bluesky
1import { agentContext } from "./agentContext.ts";
2import { Temporal } from "@js-temporal/polyfill";
3
4/**
5 * Convert time units to milliseconds
6 */
7export const msFrom = {
8 /**
9 * Convert seconds to milliseconds
10 * @param s - number of seconds
11 */
12 seconds: (seconds: number): number => seconds * 1000,
13 /**
14 * Convert minutes to milliseconds
15 * @param m - number of minutes
16 */
17 minutes: (minutes: number): number => minutes * 60 * 1000,
18 /**
19 * Convert hours to milliseconds
20 * @param h - number of hours
21 */
22 hours: (hours: number): number => hours * 60 * 60 * 1000,
23};
24
25/**
26 * Generate a random time interval in milliseconds within a defined range
27 *
28 * @param minimum - the minimum duration in milliseconds (default: 5 minutes)
29 * @param maximum - the maximum duration in milliseconds (default: 15 minutes)
30 * @returns A random time interval in milliseconds between the min and max range
31 */
32
33export const msRandomOffset = (
34 minimum: number = msFrom.minutes(5),
35 maximum: number = msFrom.minutes(15),
36): number => {
37 if (maximum <= minimum) {
38 throw new Error("Maximum time must be larger than minimum time");
39 }
40
41 if (minimum < 0 || maximum < 0) {
42 throw new Error("Time values must be non-negative");
43 }
44
45 if (Math.max(minimum, maximum) > msFrom.hours(24)) {
46 throw new Error(
47 `time values must not exceed ${
48 msFrom.hours(24)
49 } (24 hours). you entered: [min: ${minimum}ms, max: ${maximum}ms]`,
50 );
51 }
52
53 const min = Math.ceil(minimum);
54 const max = Math.floor(maximum);
55
56 return Math.floor(Math.random() * (max - min) + min);
57};
58
59/**
60 * finds the time in milliseconds until the next wake window
61 *
62 * @param minimumOffset - the minimum duration in milliseconds to offset from the window
63 * @param maximumOffset - the maximum duration in milliseconds to offset from the window
64 * @returns time until next wake window plus random offset, in milliseconds
65 */
66export const msUntilNextWakeWindow = (
67 minimumOffset: number,
68 maximumOffset: number,
69): number => {
70 const current = Temporal.Now.zonedDateTimeISO(agentContext.timeZone);
71
72 if (!agentContext.sleepEnabled) {
73 return 0;
74 }
75
76 if (
77 current.hour >= agentContext.wakeTime &&
78 current.hour < agentContext.sleepTime
79 ) {
80 return 0;
81 } else {
82 let newTime;
83
84 if (current.hour < agentContext.wakeTime) {
85 newTime = current.with({ hour: agentContext.wakeTime });
86 } else {
87 newTime = current.add({ days: 1 }).with({ hour: agentContext.wakeTime });
88 }
89
90 return newTime.toInstant().epochMilliseconds +
91 msRandomOffset(minimumOffset, maximumOffset) -
92 current.toInstant().epochMilliseconds;
93 }
94};
95
96/**
97 * Calculate the time until next configurable window, plus a random offset.
98 * @param window - the hour of the day to wake up at
99 * @param minimumOffset - the minimum duration in milliseconds to offset from the window
100 * @param maximumOffset - the maximum duration in milliseconds to offset from the window
101 * @returns time until next daily window plus random offset, in milliseconds
102 */
103export const msUntilDailyWindow = (
104 window: number,
105 minimumOffset: number,
106 maximumOffset: number,
107): number => {
108 const current = Temporal.Now.zonedDateTimeISO(agentContext.timeZone);
109
110 if (window > 23) {
111 throw Error("window hour cannot exceed 23 (11pm)");
112 }
113
114 let msToWindow;
115 if (current.hour < window) {
116 msToWindow = current.with({ hour: window }).toInstant().epochMilliseconds;
117 } else {
118 msToWindow = current.add({ days: 1 }).with({ hour: window }).toInstant()
119 .epochMilliseconds;
120 }
121
122 return msToWindow +
123 msRandomOffset(minimumOffset, maximumOffset) -
124 current.toInstant().epochMilliseconds;
125};
126
127export const getNow = () => {
128 return Temporal.Now.zonedDateTimeISO(agentContext.timeZone);
129};
130
131/**
132 * Format uptime from milliseconds into a human-readable string
133 * @param ms - uptime in milliseconds
134 * @returns Formatted string like "2 days, 3 hours, 15 minutes" or "3 hours, 15 minutes"
135 */
136export const formatUptime = (ms: number): string => {
137 const days = Math.floor(ms / (1000 * 60 * 60 * 24));
138 const hours = Math.floor((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
139 const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
140
141 const parts: string[] = [];
142
143 if (days > 0) {
144 parts.push(`${days} ${days === 1 ? "day" : "days"}`);
145 }
146 if (hours > 0) {
147 parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`);
148 }
149 if (minutes > 0 || parts.length === 0) {
150 parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
151 }
152
153 return parts.join(", ");
154};