@jaypie/llm 1.1.4 → 1.1.6

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.js CHANGED
@@ -1,8 +1,11 @@
1
- import { getEnvSecret } from '@jaypie/aws';
2
- import { log, JAYPIE, ConfigurationError, placeholders } from '@jaypie/core';
3
- import { OpenAI } from 'openai';
1
+ import { BadGatewayError, NotImplementedError, ConfigurationError as ConfigurationError$1 } from '@jaypie/errors';
2
+ import { log, JAYPIE, ConfigurationError, placeholders, sleep } from '@jaypie/core';
3
+ import { OpenAI, APIUserAbortError, AuthenticationError, BadRequestError, ConflictError, NotFoundError, PermissionDeniedError, RateLimitError, UnprocessableEntityError, APIConnectionError, APIConnectionTimeoutError, InternalServerError } from 'openai';
4
4
  import { zodResponseFormat } from 'openai/helpers/zod';
5
5
  import { z } from 'zod';
6
+ import { getEnvSecret } from '@jaypie/aws';
7
+ import RandomLib from 'random';
8
+ import { fetchWeatherApi } from 'openmeteo';
6
9
 
7
10
  const PROVIDER = {
8
11
  ANTHROPIC: {
@@ -18,7 +21,14 @@ const PROVIDER = {
18
21
  MODEL: {
19
22
  DEFAULT: "gpt-4o",
20
23
  GPT_4: "gpt-4",
24
+ GPT_4_O_MINI: "gpt-4o-mini",
21
25
  GPT_4_O: "gpt-4o",
26
+ GPT_4_5: "gpt-4.5-preview",
27
+ O1: "o1",
28
+ O1_MINI: "o1-mini",
29
+ O1_PRO: "o1-pro",
30
+ O3_MINI: "o3-mini",
31
+ O3_MINI_HIGH: "o3-mini-high",
22
32
  },
23
33
  NAME: "openai",
24
34
  },
@@ -96,72 +106,457 @@ function naturalZodSchema(definition) {
96
106
  }
97
107
  }
98
108
 
109
+ // Logger
110
+ const getLogger = () => log.lib({ lib: JAYPIE.LIB.LLM });
111
+ // Client initialization
112
+ async function initializeClient({ apiKey, } = {}) {
113
+ const logger = getLogger();
114
+ const resolvedApiKey = apiKey || (await getEnvSecret("OPENAI_API_KEY"));
115
+ if (!resolvedApiKey) {
116
+ throw new ConfigurationError("The application could not resolve the requested keys");
117
+ }
118
+ const client = new OpenAI({ apiKey: resolvedApiKey });
119
+ logger.trace("Initialized OpenAI client");
120
+ return client;
121
+ }
122
+ function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
123
+ const content = placeholders$1?.system === false
124
+ ? systemPrompt
125
+ : placeholders(systemPrompt, data);
126
+ return {
127
+ role: "developer",
128
+ content,
129
+ };
130
+ }
131
+ function formatUserMessage(message, { data, placeholders: placeholders$1 } = {}) {
132
+ const content = placeholders$1?.message === false
133
+ ? message
134
+ : placeholders(message, data);
135
+ return {
136
+ role: "user",
137
+ content,
138
+ };
139
+ }
140
+ function prepareMessages(message, { system, data, placeholders } = {}) {
141
+ const logger = getLogger();
142
+ const messages = [];
143
+ if (system) {
144
+ const systemMessage = formatSystemMessage(system, { data, placeholders });
145
+ messages.push(systemMessage);
146
+ logger.trace(`System message: ${systemMessage.content?.length} characters`);
147
+ }
148
+ const userMessage = formatUserMessage(message, { data, placeholders });
149
+ messages.push(userMessage);
150
+ logger.trace(`User message: ${userMessage.content?.length} characters`);
151
+ return messages;
152
+ }
153
+ // Completion requests
154
+ async function createStructuredCompletion(client, { messages, responseSchema, model, }) {
155
+ const logger = getLogger();
156
+ logger.trace("Using structured output");
157
+ const zodSchema = responseSchema instanceof z.ZodType
158
+ ? responseSchema
159
+ : naturalZodSchema(responseSchema);
160
+ const completion = await client.beta.chat.completions.parse({
161
+ messages,
162
+ model,
163
+ response_format: zodResponseFormat(zodSchema, "response"),
164
+ });
165
+ logger.var({ assistantReply: completion.choices[0].message.parsed });
166
+ return completion.choices[0].message.parsed;
167
+ }
168
+ async function createTextCompletion(client, { messages, model, }) {
169
+ const logger = getLogger();
170
+ logger.trace("Using text output (unstructured)");
171
+ const completion = await client.chat.completions.create({
172
+ messages,
173
+ model,
174
+ });
175
+ logger.trace(`Assistant reply: ${completion.choices[0]?.message?.content?.length} characters`);
176
+ return completion.choices[0]?.message?.content || "";
177
+ }
178
+
179
+ const DEFAULT_TOOL_TYPE = "function";
180
+ class Toolkit {
181
+ constructor(tools, options) {
182
+ this._tools = tools;
183
+ this._options = options || {};
184
+ this.explain = this._options.explain ? true : false;
185
+ }
186
+ get tools() {
187
+ return this._tools.map((tool) => {
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ const toolCopy = { ...tool };
190
+ delete toolCopy.call;
191
+ if (this.explain && toolCopy.parameters?.type === "object") {
192
+ if (toolCopy.parameters?.properties) {
193
+ if (!toolCopy.parameters.properties.__Explanation) {
194
+ toolCopy.parameters.properties.__Explanation = {
195
+ type: "string",
196
+ description: "Explain the reasoning behind this function call. What is the expected result? Describe possible next steps and the conditions under which they should be taken.",
197
+ };
198
+ }
199
+ }
200
+ }
201
+ // Set default type if not provided
202
+ if (!toolCopy.type) {
203
+ toolCopy.type = DEFAULT_TOOL_TYPE;
204
+ }
205
+ return toolCopy;
206
+ });
207
+ }
208
+ async call({ name, arguments: args }) {
209
+ const tool = this._tools.find((t) => t.name === name);
210
+ if (!tool) {
211
+ throw new Error(`Tool '${name}' not found`);
212
+ }
213
+ let parsedArgs;
214
+ try {
215
+ parsedArgs = JSON.parse(args);
216
+ if (this.explain) {
217
+ delete parsedArgs.__Explanation;
218
+ }
219
+ }
220
+ catch {
221
+ parsedArgs = args;
222
+ }
223
+ const result = tool.call(parsedArgs);
224
+ if (result instanceof Promise) {
225
+ return await result;
226
+ }
227
+ return result;
228
+ }
229
+ }
230
+
231
+ // Constants
232
+ const MAX_RETRIES_ABSOLUTE_LIMIT = 72;
233
+ const MAX_RETRIES_DEFAULT_LIMIT = 6;
234
+ // Retry policy constants
235
+ const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
236
+ const MAX_RETRY_DELAY_MS = 32000; // 32 seconds
237
+ const RETRY_BACKOFF_FACTOR = 2; // Exponential backoff multiplier
238
+ const RETRYABLE_ERRORS = [
239
+ APIConnectionError,
240
+ APIConnectionTimeoutError,
241
+ InternalServerError,
242
+ ];
243
+ const NOT_RETRYABLE_ERRORS = [
244
+ APIUserAbortError,
245
+ AuthenticationError,
246
+ BadRequestError,
247
+ ConflictError,
248
+ NotFoundError,
249
+ PermissionDeniedError,
250
+ RateLimitError,
251
+ UnprocessableEntityError,
252
+ ];
253
+ // Turn policy constants
254
+ const MAX_TURNS_ABSOLUTE_LIMIT = 72;
255
+ const MAX_TURNS_DEFAULT_LIMIT = 12;
256
+ //
257
+ //
258
+ // Helpers
259
+ //
260
+ function maxTurnsFromOptions(options) {
261
+ // Default to single turn (1) when turns are disabled
262
+ // Handle the turns parameter
263
+ if (options.turns === undefined) {
264
+ // Default to default limit when undefined
265
+ return MAX_TURNS_DEFAULT_LIMIT;
266
+ }
267
+ else if (options.turns === true) {
268
+ // Explicitly set to true
269
+ return MAX_TURNS_DEFAULT_LIMIT;
270
+ }
271
+ else if (typeof options.turns === "number") {
272
+ if (options.turns > 0) {
273
+ // Positive number - use that limit (capped at absolute limit)
274
+ return Math.min(options.turns, MAX_TURNS_ABSOLUTE_LIMIT);
275
+ }
276
+ else if (options.turns < 0) {
277
+ // Negative number - use default limit
278
+ return MAX_TURNS_DEFAULT_LIMIT;
279
+ }
280
+ // If turns is 0, return 1 (disabled)
281
+ return 1;
282
+ }
283
+ // All other values (false, null, etc.) will return 1 (disabled)
284
+ return 1;
285
+ }
286
+ //
287
+ //
288
+ // Main
289
+ //
290
+ async function operate(
291
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
+ input, options = {}, context = {
293
+ client: new OpenAI(),
294
+ }) {
295
+ const log = getLogger();
296
+ const openai = context.client;
297
+ // Validate
298
+ if (!context.maxRetries) {
299
+ context.maxRetries = MAX_RETRIES_DEFAULT_LIMIT;
300
+ }
301
+ const model = options?.model || PROVIDER.OPENAI.MODEL.DEFAULT;
302
+ // Setup
303
+ let retryCount = 0;
304
+ let retryDelay = INITIAL_RETRY_DELAY_MS;
305
+ const maxRetries = Math.min(context.maxRetries, MAX_RETRIES_ABSOLUTE_LIMIT);
306
+ const allResponses = [];
307
+ // Determine max turns from options
308
+ const maxTurns = maxTurnsFromOptions(options);
309
+ const enableMultipleTurns = maxTurns > 1;
310
+ let currentTurn = 0;
311
+ let currentInput = input;
312
+ let toolkit;
313
+ const explain = options?.explain ?? false;
314
+ // Initialize toolkit if tools are provided
315
+ if (options.tools?.length) {
316
+ toolkit = new Toolkit(options.tools, { explain });
317
+ }
318
+ // OpenAI Multi-turn Loop
319
+ while (currentTurn < maxTurns) {
320
+ currentTurn++;
321
+ retryCount = 0;
322
+ retryDelay = INITIAL_RETRY_DELAY_MS;
323
+ // OpenAI Retry Loop
324
+ while (true) {
325
+ try {
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
+ const requestOptions = {
328
+ model,
329
+ input: typeof currentInput === "string" &&
330
+ options?.data &&
331
+ (options.placeholders?.input === undefined ||
332
+ options.placeholders?.input)
333
+ ? placeholders(currentInput, options.data)
334
+ : currentInput,
335
+ };
336
+ if (options?.user) {
337
+ requestOptions.user = options.user;
338
+ }
339
+ // Add any provider-specific options
340
+ if (options?.providerOptions) {
341
+ Object.assign(requestOptions, options.providerOptions);
342
+ }
343
+ if (options?.instructions) {
344
+ // Apply placeholders to instructions if data is provided and placeholders.instructions is undefined or true
345
+ requestOptions.instructions =
346
+ options.data &&
347
+ (options.placeholders?.instructions === undefined ||
348
+ options.placeholders?.instructions)
349
+ ? placeholders(options.instructions, options.data)
350
+ : options.instructions;
351
+ }
352
+ else if (options?.system) {
353
+ // Check for illegal system option, use it as instructions, and log a warning
354
+ log.warn("[operate] Use 'instructions' instead of 'system'.");
355
+ // Apply placeholders to system if data is provided and placeholders.instructions is undefined or true
356
+ requestOptions.instructions =
357
+ options.data &&
358
+ (options.placeholders?.instructions === undefined ||
359
+ options.placeholders?.instructions)
360
+ ? placeholders(options.system, options.data)
361
+ : options.system;
362
+ }
363
+ if (options?.format) {
364
+ // Check if format is a JsonObject with type "json_schema"
365
+ if (typeof options.format === "object" &&
366
+ options.format !== null &&
367
+ !Array.isArray(options.format) &&
368
+ options.format.type === "json_schema") {
369
+ // Direct pass-through for JsonObject with type "json_schema"
370
+ requestOptions.text = {
371
+ format: options.format,
372
+ };
373
+ }
374
+ else {
375
+ // Convert NaturalSchema to Zod schema if needed
376
+ const zodSchema = options.format instanceof z.ZodType
377
+ ? options.format
378
+ : naturalZodSchema(options.format);
379
+ const responseFormat = zodResponseFormat(zodSchema, "response");
380
+ // Set up structured output format in the format expected by the test
381
+ requestOptions.text = {
382
+ format: {
383
+ name: responseFormat.json_schema.name,
384
+ schema: responseFormat.json_schema.schema,
385
+ strict: responseFormat.json_schema.strict,
386
+ type: responseFormat.type,
387
+ },
388
+ };
389
+ }
390
+ }
391
+ // Add tools if toolkit is initialized
392
+ if (toolkit) {
393
+ requestOptions.tools = toolkit.tools;
394
+ }
395
+ if (currentTurn > 1) {
396
+ log.trace(`[operate] Calling OpenAI Responses API - ${currentTurn}`);
397
+ }
398
+ else {
399
+ log.trace("[operate] Calling OpenAI Responses API");
400
+ }
401
+ log.var({ requestOptions });
402
+ // Use type assertion to handle the OpenAI SDK response type
403
+ const currentResponse = (await openai.responses.create(requestOptions));
404
+ if (retryCount > 0) {
405
+ log.debug(`OpenAI API call succeeded after ${retryCount} retries`);
406
+ }
407
+ // Add the entire response to allResponses
408
+ allResponses.push(currentResponse);
409
+ // Check if we need to process function calls for multi-turn conversations
410
+ let hasFunctionCall = false;
411
+ try {
412
+ if (currentResponse.output && Array.isArray(currentResponse.output)) {
413
+ // New OpenAI API format with output array
414
+ for (const output of currentResponse.output) {
415
+ if (output.type === "function_call") {
416
+ hasFunctionCall = true;
417
+ if (toolkit && enableMultipleTurns) {
418
+ try {
419
+ // Parse arguments for validation
420
+ JSON.parse(output.arguments);
421
+ // Call the tool and ensure the result is resolved if it's a Promise
422
+ log.trace(`[operate] Calling tool - ${output.name}`);
423
+ const result = await toolkit.call({
424
+ name: output.name,
425
+ arguments: output.arguments,
426
+ });
427
+ // Prepare for next turn by adding function call and result
428
+ // Add the function call to the input for the next turn
429
+ if (typeof currentInput === "string") {
430
+ // Convert string input to array format for the first turn
431
+ currentInput = [{ content: currentInput, role: "user" }];
432
+ }
433
+ // Add model's function call and result
434
+ if (Array.isArray(currentInput)) {
435
+ currentInput.push(output);
436
+ // Add function call result
437
+ currentInput.push({
438
+ type: "function_call_output",
439
+ call_id: output.call_id,
440
+ output: JSON.stringify(result),
441
+ });
442
+ }
443
+ }
444
+ catch (error) {
445
+ log.error(`Error executing function call ${output.name}:`, error);
446
+ // We don't add error messages to allResponses here as we want to keep the original response objects
447
+ }
448
+ }
449
+ else if (!toolkit) {
450
+ log.warn("Model requested function call but no toolkit available");
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ catch (error) {
457
+ // If there's an error processing the response, log it but don't fail
458
+ // This helps with test mocks that might not have the expected structure
459
+ log.warn("Error processing response for function calls");
460
+ log.var({ error });
461
+ }
462
+ // If there's no function call or we can't take another turn, exit the loop
463
+ if (!hasFunctionCall || !enableMultipleTurns) {
464
+ return allResponses;
465
+ }
466
+ // If we've reached the maximum number of turns, exit the loop
467
+ if (currentTurn >= maxTurns) {
468
+ log.warn(`Model requested function call but exceeded ${maxTurns} turns`);
469
+ return allResponses;
470
+ }
471
+ // Continue to next turn
472
+ break;
473
+ }
474
+ catch (error) {
475
+ // Check if we've reached the maximum number of retries
476
+ if (retryCount >= maxRetries) {
477
+ log.error(`OpenAI API call failed after ${maxRetries} retries`);
478
+ log.var({ error });
479
+ throw new BadGatewayError();
480
+ }
481
+ // Check if the error is not retryable
482
+ let isNotRetryable = false;
483
+ for (const notRetryableError of NOT_RETRYABLE_ERRORS) {
484
+ if (error instanceof notRetryableError) {
485
+ isNotRetryable = true;
486
+ break;
487
+ }
488
+ }
489
+ if (isNotRetryable) {
490
+ log.error("OpenAI API call failed with non-retryable error");
491
+ log.var({ error });
492
+ throw new BadGatewayError();
493
+ }
494
+ // Warn if this error is not in our known retryable errors
495
+ let isUnknownError = true;
496
+ for (const retryableError of RETRYABLE_ERRORS) {
497
+ if (error instanceof retryableError) {
498
+ isUnknownError = false;
499
+ break;
500
+ }
501
+ }
502
+ if (isUnknownError) {
503
+ log.warn("OpenAI API returned unknown error");
504
+ log.var({ error });
505
+ }
506
+ // Log the error and retry
507
+ log.warn(`OpenAI API call failed. Retrying in ${retryDelay}ms...`);
508
+ // Wait before retrying
509
+ await sleep(retryDelay);
510
+ // Increase retry count and delay for next attempt (exponential backoff)
511
+ retryCount++;
512
+ retryDelay = Math.min(retryDelay * RETRY_BACKOFF_FACTOR, MAX_RETRY_DELAY_MS);
513
+ }
514
+ }
515
+ }
516
+ // If we've reached the maximum number of turns, return all responses
517
+ return allResponses;
518
+ }
519
+
99
520
  class OpenAiProvider {
100
521
  constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
522
+ this.log = getLogger();
101
523
  this.model = model;
102
524
  this.apiKey = apiKey;
103
- this.log = log.lib({ lib: JAYPIE.LIB.LLM });
104
525
  }
105
526
  async getClient() {
106
527
  if (this._client) {
107
528
  return this._client;
108
529
  }
109
- const apiKey = this.apiKey || (await getEnvSecret("OPENAI_API_KEY"));
110
- if (!apiKey) {
111
- throw new ConfigurationError("The application could not resolve the requested keys");
112
- }
113
- this._client = new OpenAI({ apiKey });
114
- this.log.trace("Initialized OpenAI client");
530
+ this._client = await initializeClient({ apiKey: this.apiKey });
115
531
  return this._client;
116
532
  }
117
533
  async send(message, options) {
118
534
  const client = await this.getClient();
119
- const messages = [];
120
- if (options?.system) {
121
- const systemMessage = {
122
- role: "developer",
123
- content: options?.placeholders?.system === false
124
- ? options.system
125
- : placeholders(options.system, options?.data),
126
- };
127
- messages.push(systemMessage);
128
- this.log.var({ systemMessage });
129
- }
130
- const formattedMessage = options?.placeholders?.message === false
131
- ? message
132
- : placeholders(message, options?.data);
133
- const userMessage = {
134
- role: "user",
135
- content: formattedMessage,
136
- };
137
- messages.push(userMessage);
138
- this.log.var({ userMessage });
535
+ const messages = prepareMessages(message, options || {});
536
+ const modelToUse = options?.model || this.model;
139
537
  if (options?.response) {
140
- this.log.trace("Using structured output");
141
- const zodSchema = options.response instanceof z.ZodType
142
- ? options.response
143
- : naturalZodSchema(options.response);
144
- const completion = await client.beta.chat.completions.parse({
538
+ return createStructuredCompletion(client, {
145
539
  messages,
146
- model: options?.model || this.model,
147
- response_format: zodResponseFormat(zodSchema, "response"),
540
+ responseSchema: options.response,
541
+ model: modelToUse,
148
542
  });
149
- this.log.var({ assistantReply: completion.choices[0].message.parsed });
150
- return completion.choices[0].message.parsed;
151
543
  }
152
- this.log.trace("Using text output (unstructured)");
153
- const completion = await client.chat.completions.create({
544
+ return createTextCompletion(client, {
154
545
  messages,
155
- model: options?.model || this.model,
546
+ model: modelToUse,
156
547
  });
157
- this.log.var({ assistantReply: completion.choices[0].message.content });
158
- return completion.choices[0]?.message?.content || "";
548
+ }
549
+ async operate(input, options = {}) {
550
+ const client = await this.getClient();
551
+ options.model = options?.model || this.model;
552
+ return operate(input, options, { client });
159
553
  }
160
554
  }
161
555
 
162
556
  class AnthropicProvider {
163
- constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT) {
557
+ constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
164
558
  this.model = model;
559
+ this.apiKey = apiKey;
165
560
  }
166
561
  async send(message) {
167
562
  // TODO: Implement Anthropic API call
@@ -170,16 +565,20 @@ class AnthropicProvider {
170
565
  }
171
566
 
172
567
  class Llm {
173
- constructor(providerName = DEFAULT.PROVIDER.NAME) {
568
+ constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
174
569
  this._provider = providerName;
175
- this._llm = this.createProvider(providerName);
570
+ this._options = options;
571
+ this._llm = this.createProvider(providerName, options);
176
572
  }
177
- createProvider(providerName) {
573
+ createProvider(providerName, options = {}) {
574
+ const { apiKey, model } = options;
178
575
  switch (providerName) {
179
576
  case PROVIDER.OPENAI.NAME:
180
- return new OpenAiProvider();
577
+ return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
578
+ apiKey,
579
+ });
181
580
  case PROVIDER.ANTHROPIC.NAME:
182
- return new AnthropicProvider();
581
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
183
582
  default:
184
583
  throw new Error(`Unsupported provider: ${providerName}`);
185
584
  }
@@ -187,12 +586,374 @@ class Llm {
187
586
  async send(message, options) {
188
587
  return this._llm.send(message, options);
189
588
  }
589
+ async operate(message, options = {}) {
590
+ if (!this._llm.operate) {
591
+ throw new NotImplementedError(`Provider ${this._provider} does not support operate method`);
592
+ }
593
+ return this._llm.operate(message, options);
594
+ }
190
595
  static async send(message, options) {
191
- const { llm, ...messageOptions } = options || {};
192
- const instance = new Llm(llm);
596
+ const { llm, apiKey, model, ...messageOptions } = options || {};
597
+ const instance = new Llm(llm, { apiKey, model });
193
598
  return instance.send(message, messageOptions);
194
599
  }
600
+ static async operate(message, options) {
601
+ const { llm, apiKey, model, ...operateOptions } = options || {};
602
+ const instance = new Llm(llm, { apiKey, model });
603
+ return instance.operate(message, operateOptions);
604
+ }
195
605
  }
196
606
 
197
- export { constants as LLM, Llm };
607
+ //
608
+ // Constants
609
+ //
610
+ const DEFAULT_MIN = 0;
611
+ const DEFAULT_MAX = 1;
612
+ //
613
+ // Helper Functions
614
+ //
615
+ /**
616
+ * Clamps a number between optional minimum and maximum values
617
+ * @param num - The number to clamp
618
+ * @param min - Optional minimum value
619
+ * @param max - Optional maximum value
620
+ * @returns The clamped number
621
+ */
622
+ const clamp = (num, min, max) => {
623
+ let result = num;
624
+ if (typeof min === "number")
625
+ result = Math.max(result, min);
626
+ if (typeof max === "number")
627
+ result = Math.min(result, max);
628
+ return result;
629
+ };
630
+ /**
631
+ * Validates that min is not greater than max
632
+ * @param min - Optional minimum value
633
+ * @param max - Optional maximum value
634
+ * @throws {ConfigurationError} If min is greater than max
635
+ */
636
+ const validateBounds = (min, max) => {
637
+ if (typeof min === "number" && typeof max === "number" && min > max) {
638
+ throw new ConfigurationError$1(`Invalid bounds: min (${min}) cannot be greater than max (${max})`);
639
+ }
640
+ };
641
+ //
642
+ // Main
643
+ //
644
+ /**
645
+ * Creates a random number generator with optional seeding
646
+ *
647
+ * @param defaultSeed - Seed string for the default RNG
648
+ * @returns A function that generates random numbers based on provided options
649
+ *
650
+ * @example
651
+ * const rng = random("default-seed");
652
+ *
653
+ * // Generate a random float between 0 and 1
654
+ * const basic = rng();
655
+ *
656
+ * // Generate an integer between 1 and 10
657
+ * const integer = rng({ min: 1, max: 10, integer: true });
658
+ *
659
+ * // Generate from normal distribution
660
+ * const normal = rng({ mean: 50, stddev: 10 });
661
+ *
662
+ * // Use consistent seeding
663
+ * const seeded = rng({ seed: "my-seed" });
664
+ */
665
+ const random$1 = (defaultSeed) => {
666
+ // Initialize default seeded RNG
667
+ const defaultRng = RandomLib.clone(defaultSeed);
668
+ // Store per-seed RNGs
669
+ const seedMap = new Map();
670
+ const rngFn = ({ min, max: providedMax, mean, stddev, integer = false, start = 0, seed, precision, currency = false, } = {}) => {
671
+ // Select the appropriate RNG based on seed
672
+ const rng = seed
673
+ ? seedMap.get(seed) ||
674
+ (() => {
675
+ const newRng = RandomLib.clone(seed);
676
+ seedMap.set(seed, newRng);
677
+ return newRng;
678
+ })()
679
+ : defaultRng;
680
+ // If only min is set, set max to min*2, but keep track of whether max was provided
681
+ let max = providedMax;
682
+ if (typeof min === "number" && typeof max !== "number") {
683
+ max = min * 2;
684
+ }
685
+ validateBounds(min, max);
686
+ // Determine effective precision based on parameters
687
+ const getEffectivePrecision = () => {
688
+ if (integer)
689
+ return 0;
690
+ const precisions = [];
691
+ if (typeof precision === "number" && precision >= 0) {
692
+ precisions.push(precision);
693
+ }
694
+ if (currency) {
695
+ precisions.push(2);
696
+ }
697
+ // Return the lowest precision if any are set, undefined otherwise
698
+ return precisions.length > 0 ? Math.min(...precisions) : undefined;
699
+ };
700
+ // Helper function to apply precision
701
+ const applyPrecision = (value) => {
702
+ const effectivePrecision = getEffectivePrecision();
703
+ if (typeof effectivePrecision === "number") {
704
+ const factor = Math.pow(10, effectivePrecision);
705
+ return Math.round(value * factor) / factor;
706
+ }
707
+ return value;
708
+ };
709
+ // Use normal distribution if both mean and stddev are provided
710
+ if (typeof mean === "number" && typeof stddev === "number") {
711
+ const normalDist = rng.normal(mean, stddev);
712
+ const value = normalDist();
713
+ // Only clamp if min/max are defined
714
+ const clampedValue = clamp(value, min, providedMax);
715
+ // Switch to uniform distribution only if both bounds were explicitly provided and exceeded
716
+ if (typeof min === "number" &&
717
+ typeof providedMax === "number" &&
718
+ clampedValue !== value) {
719
+ const baseValue = integer ? rng.int(min, max) : rng.float(min, max);
720
+ return applyPrecision(start + baseValue);
721
+ }
722
+ return applyPrecision(start + (integer ? Math.round(clampedValue) : clampedValue));
723
+ }
724
+ // For uniform distribution, use defaults if min/max are undefined
725
+ const uniformMin = typeof min === "number" ? min : DEFAULT_MIN;
726
+ const uniformMax = typeof max === "number" ? max : DEFAULT_MAX;
727
+ const baseValue = integer
728
+ ? rng.int(uniformMin, uniformMax)
729
+ : rng.float(uniformMin, uniformMax);
730
+ return applyPrecision(start + baseValue);
731
+ };
732
+ return rngFn;
733
+ };
734
+
735
+ const random = {
736
+ description: "Generate a random number with optional distribution, precision, range, and seeding",
737
+ name: "random",
738
+ parameters: {
739
+ type: "object",
740
+ properties: {
741
+ min: {
742
+ type: "number",
743
+ description: "Minimum value (inclusive). Default: 0. When used with mean/stddev, acts as a clamp on the normal distribution",
744
+ },
745
+ max: {
746
+ type: "number",
747
+ description: "Maximum value (inclusive). Default: 1. When used with mean/stddev, acts as a clamp on the normal distribution",
748
+ },
749
+ mean: {
750
+ type: "number",
751
+ description: "Mean value for normal distribution. When set with stddev, uses normal distribution (clamped by min/max if provided)",
752
+ },
753
+ stddev: {
754
+ type: "number",
755
+ description: "Standard deviation for normal distribution. When set with mean, uses normal distribution (clamped by min/max if provided)",
756
+ },
757
+ integer: {
758
+ type: "boolean",
759
+ description: "Whether to return an integer value. Default: false",
760
+ },
761
+ seed: {
762
+ type: "string",
763
+ description: "Seed string for consistent random generation. Default: undefined (uses default RNG)",
764
+ },
765
+ precision: {
766
+ type: "number",
767
+ description: "Number of decimal places for the result. Default: undefined (full precision)",
768
+ },
769
+ currency: {
770
+ type: "boolean",
771
+ description: "Whether to format as currency (2 decimal places). Default: false",
772
+ },
773
+ },
774
+ required: [],
775
+ },
776
+ type: "function",
777
+ call: (options) => {
778
+ const rng = random$1();
779
+ return rng(options);
780
+ },
781
+ };
782
+
783
+ const roll = {
784
+ description: "Roll one or more dice with a specified number of sides",
785
+ name: "roll",
786
+ parameters: {
787
+ type: "object",
788
+ properties: {
789
+ number: {
790
+ type: "number",
791
+ description: "Number of dice to roll. Default: 1",
792
+ },
793
+ sides: {
794
+ type: "number",
795
+ description: "Number of sides on each die. Default: 6",
796
+ },
797
+ },
798
+ required: ["number", "sides"],
799
+ },
800
+ type: "function",
801
+ call: ({ number = 1, sides = 6 }) => {
802
+ const rng = random$1();
803
+ const rolls = [];
804
+ let total = 0;
805
+ for (let i = 0; i < number; i++) {
806
+ const rollValue = rng({ min: 1, max: sides, integer: true });
807
+ rolls.push(rollValue);
808
+ total += rollValue;
809
+ }
810
+ return { rolls, total };
811
+ },
812
+ };
813
+
814
+ const time = {
815
+ description: "Returns the provided date as an ISO UTC string or the current time if no date provided.",
816
+ name: "time",
817
+ parameters: {
818
+ type: "object",
819
+ properties: {
820
+ date: {
821
+ type: "string",
822
+ description: "Date string to convert to ISO UTC format. Default: current time",
823
+ },
824
+ },
825
+ required: [],
826
+ },
827
+ type: "function",
828
+ call: ({ date } = {}) => {
829
+ if (date) {
830
+ const parsedDate = new Date(date);
831
+ if (isNaN(parsedDate.getTime())) {
832
+ throw new Error(`Invalid date format: ${date}`);
833
+ }
834
+ return parsedDate.toISOString();
835
+ }
836
+ return new Date().toISOString();
837
+ },
838
+ };
839
+
840
+ const weather = {
841
+ description: "Get current weather and forecast data for a specific location",
842
+ name: "weather",
843
+ parameters: {
844
+ type: "object",
845
+ properties: {
846
+ latitude: {
847
+ type: "number",
848
+ description: "Latitude of the location. Default: 42.051554533384866 (Evanston, IL)",
849
+ },
850
+ longitude: {
851
+ type: "number",
852
+ description: "Longitude of the location. Default: -87.6759911441785 (Evanston, IL)",
853
+ },
854
+ timezone: {
855
+ type: "string",
856
+ description: "Timezone for the location. Default: America/Chicago",
857
+ },
858
+ past_days: {
859
+ type: "number",
860
+ description: "Number of past days to include in the forecast. Default: 1",
861
+ },
862
+ forecast_days: {
863
+ type: "number",
864
+ description: "Number of forecast days to include. Default: 1",
865
+ },
866
+ },
867
+ required: [],
868
+ },
869
+ type: "function",
870
+ call: async ({ latitude = 42.051554533384866, longitude = -87.6759911441785, timezone = "America/Chicago", past_days = 1, forecast_days = 1, } = {}) => {
871
+ try {
872
+ const params = {
873
+ latitude,
874
+ longitude,
875
+ hourly: [
876
+ "temperature_2m",
877
+ "precipitation_probability",
878
+ "precipitation",
879
+ ],
880
+ current: [
881
+ "temperature_2m",
882
+ "is_day",
883
+ "showers",
884
+ "cloud_cover",
885
+ "wind_speed_10m",
886
+ "relative_humidity_2m",
887
+ "precipitation",
888
+ "snowfall",
889
+ "rain",
890
+ "apparent_temperature",
891
+ ],
892
+ timezone,
893
+ past_days,
894
+ forecast_days,
895
+ wind_speed_unit: "mph",
896
+ temperature_unit: "fahrenheit",
897
+ precipitation_unit: "inch",
898
+ };
899
+ const url = "https://api.open-meteo.com/v1/forecast";
900
+ const responses = await fetchWeatherApi(url, params);
901
+ // Helper function to form time ranges
902
+ const range = (start, stop, step) => Array.from({ length: (stop - start) / step }, (_, i) => start + i * step);
903
+ // Process first location
904
+ const response = responses[0];
905
+ // Attributes for timezone and location
906
+ const utcOffsetSeconds = response.utcOffsetSeconds();
907
+ const timezoneAbbreviation = response.timezoneAbbreviation();
908
+ const current = response.current();
909
+ const hourly = response.hourly();
910
+ // Create weather data object
911
+ const weatherData = {
912
+ location: {
913
+ latitude: response.latitude(),
914
+ longitude: response.longitude(),
915
+ timezone: response.timezone(),
916
+ timezoneAbbreviation,
917
+ utcOffsetSeconds,
918
+ },
919
+ current: {
920
+ time: new Date((Number(current.time()) + utcOffsetSeconds) * 1000).toISOString(),
921
+ temperature2m: current.variables(0).value(),
922
+ isDay: current.variables(1).value(),
923
+ showers: current.variables(2).value(),
924
+ cloudCover: current.variables(3).value(),
925
+ windSpeed10m: current.variables(4).value(),
926
+ relativeHumidity2m: current.variables(5).value(),
927
+ precipitation: current.variables(6).value(),
928
+ snowfall: current.variables(7).value(),
929
+ rain: current.variables(8).value(),
930
+ apparentTemperature: current.variables(9).value(),
931
+ },
932
+ hourly: {
933
+ time: range(Number(hourly.time()), Number(hourly.timeEnd()), hourly.interval()).map((t) => new Date((t + utcOffsetSeconds) * 1000).toISOString()),
934
+ temperature2m: Array.from(hourly.variables(0).valuesArray()),
935
+ precipitationProbability: Array.from(hourly.variables(1).valuesArray()),
936
+ precipitation: Array.from(hourly.variables(2).valuesArray()),
937
+ },
938
+ };
939
+ return weatherData;
940
+ }
941
+ catch (error) {
942
+ if (error instanceof Error) {
943
+ throw new Error(`Weather API error: ${error.message}`);
944
+ }
945
+ throw new Error("Unknown error occurred while fetching weather data");
946
+ }
947
+ },
948
+ };
949
+
950
+ const toolkit = {
951
+ random,
952
+ roll,
953
+ time,
954
+ weather,
955
+ };
956
+ const tools = Object.values(toolkit);
957
+
958
+ export { constants as LLM, Llm, toolkit, tools };
198
959
  //# sourceMappingURL=index.js.map