@jaypie/llm 1.1.5 → 1.1.7

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.
Files changed (34) hide show
  1. package/dist/Llm.d.ts +12 -3
  2. package/dist/constants.d.ts +14 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +1063 -54
  5. package/dist/index.js.map +1 -1
  6. package/dist/providers/AnthropicProvider.class.d.ts +4 -1
  7. package/dist/providers/openai/OpenAiProvider.class.d.ts +21 -0
  8. package/dist/providers/openai/index.d.ts +1 -0
  9. package/dist/providers/openai/operate.d.ts +26 -0
  10. package/dist/providers/openai/types.d.ts +92 -0
  11. package/dist/providers/openai/utils.d.ts +59 -0
  12. package/dist/tools/Toolkit.class.d.ts +15 -0
  13. package/dist/tools/index.d.ts +7 -0
  14. package/dist/tools/random.d.ts +2 -0
  15. package/dist/tools/roll.d.ts +2 -0
  16. package/dist/tools/time.d.ts +2 -0
  17. package/dist/tools/weather.d.ts +2 -0
  18. package/dist/types/LlmProvider.interface.d.ts +128 -2
  19. package/dist/types/LlmTool.interface.d.ts +8 -0
  20. package/dist/util/formatOperateInput.d.ts +24 -0
  21. package/dist/util/formatOperateMessage.d.ts +24 -0
  22. package/dist/util/index.d.ts +7 -0
  23. package/dist/util/logger.d.ts +71 -0
  24. package/dist/util/maxTurnsFromOptions.d.ts +10 -0
  25. package/dist/util/naturalZodSchema.d.ts +1 -1
  26. package/dist/util/random.d.ts +39 -0
  27. package/dist/util/tryParseNumber.d.ts +22 -0
  28. package/package.json +7 -5
  29. package/dist/__tests__/Llm.class.spec.d.ts +0 -1
  30. package/dist/__tests__/constants.spec.d.ts +0 -1
  31. package/dist/__tests__/index.spec.d.ts +0 -1
  32. package/dist/providers/OpenAiProvider.class.d.ts +0 -13
  33. package/dist/providers/__tests__/OpenAiProvider.spec.d.ts +0 -1
  34. package/dist/util/__tests__/naturalZodSchema.spec.d.ts +0 -1
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 { ConfigurationError, BadGatewayError, TooManyRequestsError, NotImplementedError } from '@jaypie/errors';
2
+ import { placeholders, log as log$1, JAYPIE, sleep, ConfigurationError as ConfigurationError$1 } 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 RandomLib from 'random';
7
+ import { getEnvSecret } from '@jaypie/aws';
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
  },
@@ -34,6 +44,171 @@ var constants = /*#__PURE__*/Object.freeze({
34
44
  PROVIDER: PROVIDER
35
45
  });
36
46
 
47
+ const DEFAULT_TOOL_TYPE = "function";
48
+ class Toolkit {
49
+ constructor(tools, options) {
50
+ this._tools = tools;
51
+ this._options = options || {};
52
+ this.explain = this._options.explain ? true : false;
53
+ }
54
+ get tools() {
55
+ return this._tools.map((tool) => {
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const toolCopy = { ...tool };
58
+ delete toolCopy.call;
59
+ if (this.explain && toolCopy.parameters?.type === "object") {
60
+ if (toolCopy.parameters?.properties) {
61
+ if (!toolCopy.parameters.properties.__Explanation) {
62
+ toolCopy.parameters.properties.__Explanation = {
63
+ type: "string",
64
+ 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.",
65
+ };
66
+ }
67
+ }
68
+ }
69
+ // Set default type if not provided
70
+ if (!toolCopy.type) {
71
+ toolCopy.type = DEFAULT_TOOL_TYPE;
72
+ }
73
+ return toolCopy;
74
+ });
75
+ }
76
+ async call({ name, arguments: args }) {
77
+ const tool = this._tools.find((t) => t.name === name);
78
+ if (!tool) {
79
+ throw new Error(`Tool '${name}' not found`);
80
+ }
81
+ let parsedArgs;
82
+ try {
83
+ parsedArgs = JSON.parse(args);
84
+ if (this.explain) {
85
+ delete parsedArgs.__Explanation;
86
+ }
87
+ }
88
+ catch {
89
+ parsedArgs = args;
90
+ }
91
+ const result = tool.call(parsedArgs);
92
+ if (result instanceof Promise) {
93
+ return await result;
94
+ }
95
+ return result;
96
+ }
97
+ }
98
+
99
+ // Enums
100
+ var LlmMessageRole;
101
+ (function (LlmMessageRole) {
102
+ LlmMessageRole["Assistant"] = "assistant";
103
+ LlmMessageRole["Developer"] = "developer";
104
+ LlmMessageRole["System"] = "system";
105
+ LlmMessageRole["User"] = "user";
106
+ })(LlmMessageRole || (LlmMessageRole = {}));
107
+ var LlmMessageType;
108
+ (function (LlmMessageType) {
109
+ LlmMessageType["FunctionCall"] = "function_call";
110
+ LlmMessageType["FunctionCallOutput"] = "function_call_output";
111
+ LlmMessageType["InputFile"] = "input_file";
112
+ LlmMessageType["InputImage"] = "input_image";
113
+ LlmMessageType["InputText"] = "input_text";
114
+ LlmMessageType["ItemReference"] = "item_reference";
115
+ LlmMessageType["Message"] = "message";
116
+ LlmMessageType["OutputText"] = "output_text";
117
+ LlmMessageType["Refusal"] = "refusal";
118
+ })(LlmMessageType || (LlmMessageType = {}));
119
+ var LlmResponseStatus;
120
+ (function (LlmResponseStatus) {
121
+ LlmResponseStatus["Completed"] = "completed";
122
+ LlmResponseStatus["Incomplete"] = "incomplete";
123
+ LlmResponseStatus["InProgress"] = "in_progress";
124
+ })(LlmResponseStatus || (LlmResponseStatus = {}));
125
+
126
+ /**
127
+ * Converts a string to a standardized LlmInputMessage
128
+ * @param input - String to format
129
+ * @param options - Optional configuration
130
+ * @param options.data - Data to use for placeholder substitution
131
+ * @param options.role - Role to use for the message (defaults to User)
132
+ * @returns LlmInputMessage
133
+ */
134
+ function formatOperateMessage(input, options) {
135
+ const content = options?.data ? placeholders(input, options.data) : input;
136
+ return {
137
+ content,
138
+ role: options?.role || LlmMessageRole.User,
139
+ type: LlmMessageType.Message,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Converts various input types to a standardized LlmHistory format
145
+ * @param input - String, LlmInputMessage, or LlmHistory to format
146
+ * @param options - Optional configuration
147
+ * @param options.data - Data to use for placeholder substitution
148
+ * @param options.role - Role to use when converting a string to LlmInputMessage (defaults to User)
149
+ * @returns Standardized LlmHistory array
150
+ */
151
+ function formatOperateInput(input, options) {
152
+ // If input is already LlmHistory, return it
153
+ if (Array.isArray(input)) {
154
+ return input;
155
+ }
156
+ // If input is a string, convert it to LlmInputMessage
157
+ if (typeof input === "string") {
158
+ return [formatOperateMessage(input, options)];
159
+ }
160
+ // If input is LlmInputMessage, apply placeholders if data is provided
161
+ if (options?.data && typeof input.content === "string") {
162
+ return [
163
+ formatOperateMessage(input.content, {
164
+ data: options.data,
165
+ role: input.role || options?.role,
166
+ }),
167
+ ];
168
+ }
169
+ // Otherwise, just wrap the input in an array
170
+ return [input];
171
+ }
172
+
173
+ const getLogger$1 = () => log$1.lib({ lib: JAYPIE.LIB.LLM });
174
+ const log = getLogger$1();
175
+
176
+ // Turn policy constants
177
+ const MAX_TURNS_ABSOLUTE_LIMIT = 72;
178
+ const MAX_TURNS_DEFAULT_LIMIT = 12;
179
+ /**
180
+ * Determines the maximum number of turns based on the provided options
181
+ *
182
+ * @param options - The LLM operate options
183
+ * @returns The maximum number of turns
184
+ */
185
+ function maxTurnsFromOptions(options) {
186
+ // Default to single turn (1) when turns are disabled
187
+ // Handle the turns parameter
188
+ if (options.turns === undefined) {
189
+ // Default to default limit when undefined
190
+ return MAX_TURNS_DEFAULT_LIMIT;
191
+ }
192
+ else if (options.turns === true) {
193
+ // Explicitly set to true
194
+ return MAX_TURNS_DEFAULT_LIMIT;
195
+ }
196
+ else if (typeof options.turns === "number") {
197
+ if (options.turns > 0) {
198
+ // Positive number - use that limit (capped at absolute limit)
199
+ return Math.min(options.turns, MAX_TURNS_ABSOLUTE_LIMIT);
200
+ }
201
+ else if (options.turns < 0) {
202
+ // Negative number - use default limit
203
+ return MAX_TURNS_DEFAULT_LIMIT;
204
+ }
205
+ // If turns is 0, return 1 (disabled)
206
+ return 1;
207
+ }
208
+ // All other values (false, null, etc.) will return 1 (disabled)
209
+ return 1;
210
+ }
211
+
37
212
  function naturalZodSchema(definition) {
38
213
  if (Array.isArray(definition)) {
39
214
  if (definition.length === 0) {
@@ -96,73 +271,661 @@ function naturalZodSchema(definition) {
96
271
  }
97
272
  }
98
273
 
274
+ //
275
+ // Constants
276
+ //
277
+ const DEFAULT_MIN = 0;
278
+ const DEFAULT_MAX = 1;
279
+ //
280
+ // Helper Functions
281
+ //
282
+ /**
283
+ * Clamps a number between optional minimum and maximum values
284
+ * @param num - The number to clamp
285
+ * @param min - Optional minimum value
286
+ * @param max - Optional maximum value
287
+ * @returns The clamped number
288
+ */
289
+ const clamp = (num, min, max) => {
290
+ let result = num;
291
+ if (typeof min === "number")
292
+ result = Math.max(result, min);
293
+ if (typeof max === "number")
294
+ result = Math.min(result, max);
295
+ return result;
296
+ };
297
+ /**
298
+ * Validates that min is not greater than max
299
+ * @param min - Optional minimum value
300
+ * @param max - Optional maximum value
301
+ * @throws {ConfigurationError} If min is greater than max
302
+ */
303
+ const validateBounds = (min, max) => {
304
+ if (typeof min === "number" && typeof max === "number" && min > max) {
305
+ throw new ConfigurationError(`Invalid bounds: min (${min}) cannot be greater than max (${max})`);
306
+ }
307
+ };
308
+ //
309
+ // Main
310
+ //
311
+ /**
312
+ * Creates a random number generator with optional seeding
313
+ *
314
+ * @param defaultSeed - Seed string for the default RNG
315
+ * @returns A function that generates random numbers based on provided options
316
+ *
317
+ * @example
318
+ * const rng = random("default-seed");
319
+ *
320
+ * // Generate a random float between 0 and 1
321
+ * const basic = rng();
322
+ *
323
+ * // Generate an integer between 1 and 10
324
+ * const integer = rng({ min: 1, max: 10, integer: true });
325
+ *
326
+ * // Generate from normal distribution
327
+ * const normal = rng({ mean: 50, stddev: 10 });
328
+ *
329
+ * // Use consistent seeding
330
+ * const seeded = rng({ seed: "my-seed" });
331
+ */
332
+ function random$1(defaultSeed) {
333
+ // Initialize default seeded RNG
334
+ const defaultRng = RandomLib.clone(defaultSeed);
335
+ // Store per-seed RNGs
336
+ const seedMap = new Map();
337
+ const rngFn = ({ min, max: providedMax, mean, stddev, integer = false, start = 0, seed, precision, currency = false, } = {}) => {
338
+ // Select the appropriate RNG based on seed
339
+ const rng = seed
340
+ ? seedMap.get(seed) ||
341
+ (() => {
342
+ const newRng = RandomLib.clone(seed);
343
+ seedMap.set(seed, newRng);
344
+ return newRng;
345
+ })()
346
+ : defaultRng;
347
+ // If only min is set, set max to min*2, but keep track of whether max was provided
348
+ let max = providedMax;
349
+ if (typeof min === "number" && typeof max !== "number") {
350
+ max = min * 2;
351
+ }
352
+ validateBounds(min, max);
353
+ // Determine effective precision based on parameters
354
+ const getEffectivePrecision = () => {
355
+ if (integer)
356
+ return 0;
357
+ const precisions = [];
358
+ if (typeof precision === "number" && precision >= 0) {
359
+ precisions.push(precision);
360
+ }
361
+ if (currency) {
362
+ precisions.push(2);
363
+ }
364
+ // Return the lowest precision if any are set, undefined otherwise
365
+ return precisions.length > 0 ? Math.min(...precisions) : undefined;
366
+ };
367
+ // Helper function to apply precision
368
+ const applyPrecision = (value) => {
369
+ const effectivePrecision = getEffectivePrecision();
370
+ if (typeof effectivePrecision === "number") {
371
+ const factor = Math.pow(10, effectivePrecision);
372
+ return Math.round(value * factor) / factor;
373
+ }
374
+ return value;
375
+ };
376
+ // Use normal distribution if both mean and stddev are provided
377
+ if (typeof mean === "number" && typeof stddev === "number") {
378
+ const normalDist = rng.normal(mean, stddev);
379
+ const value = normalDist();
380
+ // Only clamp if min/max are defined
381
+ const clampedValue = clamp(value, min, providedMax);
382
+ // Switch to uniform distribution only if both bounds were explicitly provided and exceeded
383
+ if (typeof min === "number" &&
384
+ typeof providedMax === "number" &&
385
+ clampedValue !== value) {
386
+ const baseValue = integer ? rng.int(min, max) : rng.float(min, max);
387
+ return applyPrecision(start + baseValue);
388
+ }
389
+ return applyPrecision(start + (integer ? Math.round(clampedValue) : clampedValue));
390
+ }
391
+ // For uniform distribution, use defaults if min/max are undefined
392
+ const uniformMin = typeof min === "number" ? min : DEFAULT_MIN;
393
+ const uniformMax = typeof max === "number" ? max : DEFAULT_MAX;
394
+ const baseValue = integer
395
+ ? rng.int(uniformMin, uniformMax)
396
+ : rng.float(uniformMin, uniformMax);
397
+ return applyPrecision(start + baseValue);
398
+ };
399
+ return rngFn;
400
+ }
401
+
402
+ /**
403
+ * Helper function to safely call a function that might throw
404
+ * @param fn Function to call safely
405
+ */
406
+ function callSafely(fn) {
407
+ try {
408
+ fn();
409
+ }
410
+ catch {
411
+ // Silently catch any errors from the function
412
+ }
413
+ }
414
+ /**
415
+ * Attempts to parse a value as a number. Returns the original input if parsing fails or results in NaN.
416
+ * @param input - The value to attempt to parse as a number
417
+ * @param options - Optional configuration
418
+ * @param options.defaultValue - Default value to return if parsing fails or results in NaN
419
+ * @param options.warnFunction - Function to call with warning message if parsing fails or results in NaN
420
+ * @returns The parsed number, defaultValue (if specified and parsing fails), or the original input
421
+ */
422
+ function tryParseNumber(input, options) {
423
+ if (input === null || input === undefined) {
424
+ return input;
425
+ }
426
+ try {
427
+ const parsed = Number(input);
428
+ if (Number.isNaN(parsed)) {
429
+ if (options?.warnFunction) {
430
+ const warningMessage = `Failed to parse "${String(input)}" as number`;
431
+ callSafely(() => options.warnFunction(warningMessage));
432
+ }
433
+ return typeof options?.defaultValue === "number"
434
+ ? options.defaultValue
435
+ : input;
436
+ }
437
+ return parsed;
438
+ }
439
+ catch (error) {
440
+ if (options?.warnFunction) {
441
+ let errorMessage = "";
442
+ if (error instanceof Error) {
443
+ errorMessage = error.message;
444
+ }
445
+ const warningMessage = `Error parsing "${String(input)}" as number${errorMessage ? "; " + errorMessage : ""}`;
446
+ callSafely(() => options.warnFunction(warningMessage));
447
+ }
448
+ return typeof options?.defaultValue === "number"
449
+ ? options.defaultValue
450
+ : input;
451
+ }
452
+ }
453
+
454
+ //
455
+ //
456
+ // Constants
457
+ //
458
+ const MAX_RETRIES_ABSOLUTE_LIMIT = 72;
459
+ const MAX_RETRIES_DEFAULT_LIMIT = 6;
460
+ // Retry policy constants
461
+ const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
462
+ const MAX_RETRY_DELAY_MS = 32000; // 32 seconds
463
+ const RETRY_BACKOFF_FACTOR = 2; // Exponential backoff multiplier
464
+ const RETRYABLE_ERRORS = [
465
+ APIConnectionError,
466
+ APIConnectionTimeoutError,
467
+ InternalServerError,
468
+ ];
469
+ const NOT_RETRYABLE_ERRORS = [
470
+ APIUserAbortError,
471
+ AuthenticationError,
472
+ BadRequestError,
473
+ ConflictError,
474
+ NotFoundError,
475
+ PermissionDeniedError,
476
+ RateLimitError,
477
+ UnprocessableEntityError,
478
+ ];
479
+ const ERROR = {
480
+ BAD_FUNCTION_CALL: "Bad Function Call",
481
+ };
482
+ //
483
+ //
484
+ // Helpers
485
+ //
486
+ /**
487
+ * Creates the request options for the OpenAI API call
488
+ *
489
+ * @param input - The formatted input messages
490
+ * @param options - The LLM operation options
491
+ * @returns The request options for the OpenAI API
492
+ */
493
+ function createRequestOptions(input, options = {}) {
494
+ const requestOptions = {
495
+ model: options?.model || PROVIDER.OPENAI.MODEL.DEFAULT,
496
+ input,
497
+ };
498
+ // Add user if provided
499
+ if (options?.user) {
500
+ requestOptions.user = options.user;
501
+ }
502
+ // Add any provider-specific options
503
+ if (options?.providerOptions) {
504
+ Object.assign(requestOptions, options.providerOptions);
505
+ }
506
+ // Handle instructions or system message
507
+ if (options?.instructions) {
508
+ // Apply placeholders to instructions if data is provided and placeholders.instructions is undefined or true
509
+ requestOptions.instructions =
510
+ options.data &&
511
+ (options.placeholders?.instructions === undefined ||
512
+ options.placeholders?.instructions)
513
+ ? placeholders(options.instructions, options.data)
514
+ : options.instructions;
515
+ }
516
+ else if (options?.system) {
517
+ // Check for illegal system option, use it as instructions, and log a warning
518
+ log.warn("[operate] Use 'instructions' instead of 'system'.");
519
+ // Apply placeholders to system if data is provided and placeholders.instructions is undefined or true
520
+ requestOptions.instructions =
521
+ options.data &&
522
+ (options.placeholders?.instructions === undefined ||
523
+ options.placeholders?.instructions)
524
+ ? placeholders(options.system, options.data)
525
+ : options.system;
526
+ }
527
+ // Handle structured output format
528
+ if (options?.format) {
529
+ // Check if format is a JsonObject with type "json_schema"
530
+ if (typeof options.format === "object" &&
531
+ options.format !== null &&
532
+ !Array.isArray(options.format) &&
533
+ options.format.type === "json_schema") {
534
+ // Direct pass-through for JsonObject with type "json_schema"
535
+ requestOptions.text = {
536
+ format: options.format,
537
+ };
538
+ }
539
+ else {
540
+ // Convert NaturalSchema to Zod schema if needed
541
+ const zodSchema = options.format instanceof z.ZodType
542
+ ? options.format
543
+ : naturalZodSchema(options.format);
544
+ const responseFormat = zodResponseFormat(zodSchema, "response");
545
+ // Set up structured output format in the format expected by the test
546
+ requestOptions.text = {
547
+ format: {
548
+ name: responseFormat.json_schema.name,
549
+ schema: responseFormat.json_schema.schema,
550
+ strict: responseFormat.json_schema.strict,
551
+ type: responseFormat.type,
552
+ },
553
+ };
554
+ }
555
+ }
556
+ // Create toolkit and add tools if provided
557
+ if (options.tools?.length) {
558
+ const explain = options?.explain ?? false;
559
+ const toolkit = new Toolkit(options.tools, { explain });
560
+ requestOptions.tools = toolkit.tools;
561
+ }
562
+ return requestOptions;
563
+ }
564
+ //
565
+ //
566
+ // Main
567
+ //
568
+ async function operate(input, options = {}, context = {
569
+ client: new OpenAI(),
570
+ }) {
571
+ //
572
+ //
573
+ // Setup
574
+ //
575
+ const openai = context.client;
576
+ if (!context.maxRetries) {
577
+ context.maxRetries = MAX_RETRIES_DEFAULT_LIMIT;
578
+ }
579
+ let retryCount = 0;
580
+ let retryDelay = INITIAL_RETRY_DELAY_MS;
581
+ const maxRetries = Math.min(context.maxRetries, MAX_RETRIES_ABSOLUTE_LIMIT);
582
+ const returnResponse = {
583
+ history: [],
584
+ output: [],
585
+ responses: [],
586
+ status: LlmResponseStatus.InProgress,
587
+ usage: {
588
+ input: 0,
589
+ output: 0,
590
+ reasoning: 0,
591
+ total: 0,
592
+ },
593
+ };
594
+ // Convert string input to array format with placeholders if needed
595
+ let currentInput = formatOperateInput(input);
596
+ if (options?.data &&
597
+ (options.placeholders?.input === undefined || options.placeholders?.input)) {
598
+ currentInput = formatOperateInput(input, {
599
+ data: options?.data,
600
+ });
601
+ }
602
+ // Determine max turns from options
603
+ const maxTurns = maxTurnsFromOptions(options);
604
+ const enableMultipleTurns = maxTurns > 1;
605
+ let currentTurn = 0;
606
+ // If history is provided, merge it with currentInput
607
+ if (options.history) {
608
+ currentInput = [...options.history, ...currentInput];
609
+ }
610
+ // Initialize history with currentInput
611
+ returnResponse.history = [...currentInput];
612
+ // Build request options outside the retry loop
613
+ const requestOptions = createRequestOptions(currentInput, options);
614
+ // OpenAI Multi-turn Loop
615
+ while (currentTurn < maxTurns) {
616
+ currentTurn++;
617
+ retryCount = 0;
618
+ retryDelay = INITIAL_RETRY_DELAY_MS;
619
+ // OpenAI Retry Loop
620
+ while (true) {
621
+ try {
622
+ // Log appropriate message based on turn number
623
+ if (currentTurn > 1) {
624
+ log.trace(`[operate] Calling OpenAI Responses API - ${currentTurn}`);
625
+ }
626
+ else {
627
+ log.trace("[operate] Calling OpenAI Responses API");
628
+ }
629
+ // Use type assertion to handle the OpenAI SDK response type
630
+ const currentResponse = (await openai.responses.create(
631
+ // @ts-expect-error error claims missing non-required id, status
632
+ requestOptions));
633
+ if (retryCount > 0) {
634
+ log.debug(`OpenAI API call succeeded after ${retryCount} retries`);
635
+ }
636
+ // Add the response to the responses array
637
+ returnResponse.responses.push(currentResponse);
638
+ // Accumulate token usage from the current response
639
+ if (currentResponse.usage) {
640
+ returnResponse.usage.input += currentResponse.usage.input_tokens || 0;
641
+ returnResponse.usage.output +=
642
+ currentResponse.usage.output_tokens || 0;
643
+ returnResponse.usage.total += currentResponse.usage.total_tokens || 0;
644
+ if (currentResponse.usage.output_tokens_details?.reasoning_tokens) {
645
+ returnResponse.usage.reasoning =
646
+ (returnResponse.usage.reasoning || 0) +
647
+ currentResponse.usage.output_tokens_details.reasoning_tokens;
648
+ }
649
+ }
650
+ // Check if we need to process function calls for multi-turn conversations
651
+ let hasFunctionCall = false;
652
+ try {
653
+ if (currentResponse.output && Array.isArray(currentResponse.output)) {
654
+ // New OpenAI API format with output array
655
+ for (const output of currentResponse.output) {
656
+ returnResponse.output.push(output);
657
+ returnResponse.history.push(output);
658
+ if (output.type === LlmMessageType.FunctionCall) {
659
+ hasFunctionCall = true;
660
+ let toolkit;
661
+ const explain = options?.explain ?? false;
662
+ // Initialize toolkit if tools are provided for multi-turn function calling
663
+ if (options.tools?.length) {
664
+ toolkit = new Toolkit(options.tools, { explain });
665
+ }
666
+ if (toolkit && enableMultipleTurns) {
667
+ try {
668
+ // Call the tool and ensure the result is resolved if it's a Promise
669
+ log.trace(`[operate] Calling tool - ${output.name}`);
670
+ returnResponse.content = `${LlmMessageType.FunctionCall}:${output.name}${output.arguments}#${output.call_id}`;
671
+ const result = await toolkit.call({
672
+ name: output.name,
673
+ arguments: output.arguments,
674
+ });
675
+ // Add model's function call and result
676
+ if (Array.isArray(currentInput)) {
677
+ currentInput.push(output);
678
+ // Add function call result
679
+ const functionCallOutput = {
680
+ call_id: output.call_id,
681
+ output: JSON.stringify(result),
682
+ type: LlmMessageType.FunctionCallOutput,
683
+ };
684
+ currentInput.push(functionCallOutput);
685
+ returnResponse.output.push(functionCallOutput);
686
+ returnResponse.history.push(functionCallOutput);
687
+ returnResponse.content = `${LlmMessageType.FunctionCallOutput}:${functionCallOutput.output}#${functionCallOutput.call_id}`;
688
+ }
689
+ }
690
+ catch (error) {
691
+ // TODO: but I do need to tell the model that something went wrong, right?
692
+ const jaypieError = new BadGatewayError();
693
+ const detail = [
694
+ `Error executing function call ${output.name}.`,
695
+ error.message,
696
+ ].join("\n");
697
+ returnResponse.error = {
698
+ detail,
699
+ status: jaypieError.status,
700
+ title: ERROR.BAD_FUNCTION_CALL,
701
+ };
702
+ log.error(`Error executing function call ${output.name}`);
703
+ log.var({ error });
704
+ // We don't add error messages to allResponses here as we want to keep the original response objects
705
+ }
706
+ }
707
+ else if (!toolkit) {
708
+ log.warn("Model requested function call but no toolkit available");
709
+ }
710
+ }
711
+ if (output.type === LlmMessageType.Message) {
712
+ if (output.content?.[0] &&
713
+ output.content[0].type === LlmMessageType.OutputText) {
714
+ returnResponse.content = output.content[0].text;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ catch (error) {
721
+ // If there's an error processing the response, log it but don't fail
722
+ // This helps with test mocks that might not have the expected structure
723
+ log.warn("Error processing response for function calls");
724
+ log.var({ error });
725
+ }
726
+ // If there's no function call or we can't take another turn, exit the loop
727
+ if (!hasFunctionCall || !enableMultipleTurns) {
728
+ returnResponse.status = LlmResponseStatus.Completed;
729
+ return returnResponse;
730
+ }
731
+ // If we've reached the maximum number of turns, exit the loop
732
+ if (currentTurn >= maxTurns) {
733
+ const error = new TooManyRequestsError();
734
+ const detail = `Model requested function call but exceeded ${maxTurns} turns`;
735
+ log.warn(detail);
736
+ returnResponse.status = LlmResponseStatus.Incomplete;
737
+ returnResponse.error = {
738
+ detail,
739
+ status: error.status,
740
+ title: error.title,
741
+ };
742
+ return returnResponse;
743
+ }
744
+ // Continue to next turn
745
+ break;
746
+ }
747
+ catch (error) {
748
+ // Check if we've reached the maximum number of retries
749
+ if (retryCount >= maxRetries) {
750
+ log.error(`OpenAI API call failed after ${maxRetries} retries`);
751
+ log.var({ error });
752
+ throw new BadGatewayError();
753
+ }
754
+ // Check if the error is not retryable
755
+ let isNotRetryable = false;
756
+ for (const notRetryableError of NOT_RETRYABLE_ERRORS) {
757
+ if (error instanceof notRetryableError) {
758
+ isNotRetryable = true;
759
+ break;
760
+ }
761
+ }
762
+ if (isNotRetryable) {
763
+ log.error("OpenAI API call failed with non-retryable error");
764
+ log.var({ error });
765
+ throw new BadGatewayError();
766
+ }
767
+ // Warn if this error is not in our known retryable errors
768
+ let isUnknownError = true;
769
+ for (const retryableError of RETRYABLE_ERRORS) {
770
+ if (error instanceof retryableError) {
771
+ isUnknownError = false;
772
+ break;
773
+ }
774
+ }
775
+ if (isUnknownError) {
776
+ log.warn("OpenAI API returned unknown error");
777
+ log.var({ error });
778
+ }
779
+ // Log the error and retry
780
+ log.warn(`OpenAI API call failed. Retrying in ${retryDelay}ms...`);
781
+ // Wait before retrying
782
+ await sleep(retryDelay);
783
+ // Increase retry count and delay for next attempt (exponential backoff)
784
+ retryCount++;
785
+ retryDelay = Math.min(retryDelay * RETRY_BACKOFF_FACTOR, MAX_RETRY_DELAY_MS);
786
+ }
787
+ }
788
+ }
789
+ // * All possible paths should return a response; getting here is an error
790
+ // The main loop is `currentTurn < maxTurns` and `currentTurn >= maxTurns` within the loop returns
791
+ log.warn("This should never happen");
792
+ returnResponse.status = LlmResponseStatus.Incomplete;
793
+ // Always return the full LlmOperateResponse object for consistency
794
+ return returnResponse;
795
+ }
796
+
797
+ // Logger
798
+ const getLogger = () => log$1.lib({ lib: JAYPIE.LIB.LLM });
799
+ // Client initialization
800
+ async function initializeClient({ apiKey, } = {}) {
801
+ const logger = getLogger();
802
+ const resolvedApiKey = apiKey || (await getEnvSecret("OPENAI_API_KEY"));
803
+ if (!resolvedApiKey) {
804
+ throw new ConfigurationError$1("The application could not resolve the requested keys");
805
+ }
806
+ const client = new OpenAI({ apiKey: resolvedApiKey });
807
+ logger.trace("Initialized OpenAI client");
808
+ return client;
809
+ }
810
+ function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
811
+ const content = placeholders$1?.system === false
812
+ ? systemPrompt
813
+ : placeholders(systemPrompt, data);
814
+ return {
815
+ role: "developer",
816
+ content,
817
+ };
818
+ }
819
+ function formatUserMessage(message, { data, placeholders: placeholders$1 } = {}) {
820
+ const content = placeholders$1?.message === false
821
+ ? message
822
+ : placeholders(message, data);
823
+ return {
824
+ role: "user",
825
+ content,
826
+ };
827
+ }
828
+ function prepareMessages(message, { system, data, placeholders } = {}) {
829
+ const logger = getLogger();
830
+ const messages = [];
831
+ if (system) {
832
+ const systemMessage = formatSystemMessage(system, { data, placeholders });
833
+ messages.push(systemMessage);
834
+ logger.trace(`System message: ${systemMessage.content?.length} characters`);
835
+ }
836
+ const userMessage = formatUserMessage(message, { data, placeholders });
837
+ messages.push(userMessage);
838
+ logger.trace(`User message: ${userMessage.content?.length} characters`);
839
+ return messages;
840
+ }
841
+ // Completion requests
842
+ async function createStructuredCompletion(client, { messages, responseSchema, model, }) {
843
+ const logger = getLogger();
844
+ logger.trace("Using structured output");
845
+ const zodSchema = responseSchema instanceof z.ZodType
846
+ ? responseSchema
847
+ : naturalZodSchema(responseSchema);
848
+ const completion = await client.beta.chat.completions.parse({
849
+ messages,
850
+ model,
851
+ response_format: zodResponseFormat(zodSchema, "response"),
852
+ });
853
+ logger.var({ assistantReply: completion.choices[0].message.parsed });
854
+ return completion.choices[0].message.parsed;
855
+ }
856
+ async function createTextCompletion(client, { messages, model, }) {
857
+ const logger = getLogger();
858
+ logger.trace("Using text output (unstructured)");
859
+ const completion = await client.chat.completions.create({
860
+ messages,
861
+ model,
862
+ });
863
+ logger.trace(`Assistant reply: ${completion.choices[0]?.message?.content?.length} characters`);
864
+ return completion.choices[0]?.message?.content || "";
865
+ }
866
+
99
867
  class OpenAiProvider {
100
868
  constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
869
+ this.log = getLogger();
870
+ this.conversationHistory = [];
101
871
  this.model = model;
102
872
  this.apiKey = apiKey;
103
- this.log = log.lib({ lib: JAYPIE.LIB.LLM });
104
873
  }
105
874
  async getClient() {
106
875
  if (this._client) {
107
876
  return this._client;
108
877
  }
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");
878
+ this._client = await initializeClient({ apiKey: this.apiKey });
115
879
  return this._client;
116
880
  }
117
881
  async send(message, options) {
118
882
  const client = await this.getClient();
119
- const messages = [];
120
- if (options?.system) {
121
- const content = options?.placeholders?.system === false
122
- ? options.system
123
- : placeholders(options.system, options?.data);
124
- const systemMessage = {
125
- role: "developer",
126
- content,
127
- };
128
- messages.push(systemMessage);
129
- this.log.trace(`System message: ${content?.length} characters`);
130
- }
131
- const formattedMessage = options?.placeholders?.message === false
132
- ? message
133
- : placeholders(message, options?.data);
134
- const userMessage = {
135
- role: "user",
136
- content: formattedMessage,
137
- };
138
- messages.push(userMessage);
139
- this.log.trace(`User message: ${formattedMessage?.length} characters`);
883
+ const messages = prepareMessages(message, options || {});
884
+ const modelToUse = options?.model || this.model;
140
885
  if (options?.response) {
141
- this.log.trace("Using structured output");
142
- const zodSchema = options.response instanceof z.ZodType
143
- ? options.response
144
- : naturalZodSchema(options.response);
145
- const completion = await client.beta.chat.completions.parse({
886
+ return createStructuredCompletion(client, {
146
887
  messages,
147
- model: options?.model || this.model,
148
- response_format: zodResponseFormat(zodSchema, "response"),
888
+ responseSchema: options.response,
889
+ model: modelToUse,
149
890
  });
150
- this.log.var({ assistantReply: completion.choices[0].message.parsed });
151
- return completion.choices[0].message.parsed;
152
891
  }
153
- this.log.trace("Using text output (unstructured)");
154
- const completion = await client.chat.completions.create({
892
+ return createTextCompletion(client, {
155
893
  messages,
156
- model: options?.model || this.model,
894
+ model: modelToUse,
157
895
  });
158
- this.log.trace(`Assistant reply: ${completion.choices[0]?.message?.content?.length} characters`);
159
- return completion.choices[0]?.message?.content || "";
896
+ }
897
+ async operate(input, options = {}) {
898
+ const client = await this.getClient();
899
+ options.model = options?.model || this.model;
900
+ // TODO: Create a merged history including both the tracked history and any explicitly provided history
901
+ // Call operate with the updated options
902
+ const response = await operate(input, options, { client });
903
+ // TODO: Update conversation history with the input and response
904
+ return response;
905
+ }
906
+ /**
907
+ * Updates the conversation history with the latest input and response
908
+ * @param input The formatted input messages
909
+ * @param response The response from the model
910
+ */
911
+ updateConversationHistory(input, response) {
912
+ // Add the input to history
913
+ this.conversationHistory.push(...input);
914
+ // Add the response to history if it exists and has content
915
+ if (response && response.length > 0) {
916
+ // Extract the last response item and add it to history
917
+ const lastResponse = response[response.length - 1];
918
+ if (lastResponse) {
919
+ this.conversationHistory.push(lastResponse);
920
+ }
921
+ }
160
922
  }
161
923
  }
162
924
 
163
925
  class AnthropicProvider {
164
- constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT) {
926
+ constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
165
927
  this.model = model;
928
+ this.apiKey = apiKey;
166
929
  }
167
930
  async send(message) {
168
931
  // TODO: Implement Anthropic API call
@@ -171,16 +934,20 @@ class AnthropicProvider {
171
934
  }
172
935
 
173
936
  class Llm {
174
- constructor(providerName = DEFAULT.PROVIDER.NAME) {
937
+ constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
175
938
  this._provider = providerName;
176
- this._llm = this.createProvider(providerName);
939
+ this._options = options;
940
+ this._llm = this.createProvider(providerName, options);
177
941
  }
178
- createProvider(providerName) {
942
+ createProvider(providerName, options = {}) {
943
+ const { apiKey, model } = options;
179
944
  switch (providerName) {
180
945
  case PROVIDER.OPENAI.NAME:
181
- return new OpenAiProvider();
946
+ return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
947
+ apiKey,
948
+ });
182
949
  case PROVIDER.ANTHROPIC.NAME:
183
- return new AnthropicProvider();
950
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
184
951
  default:
185
952
  throw new Error(`Unsupported provider: ${providerName}`);
186
953
  }
@@ -188,12 +955,254 @@ class Llm {
188
955
  async send(message, options) {
189
956
  return this._llm.send(message, options);
190
957
  }
958
+ async operate(message, options = {}) {
959
+ if (!this._llm.operate) {
960
+ throw new NotImplementedError(`Provider ${this._provider} does not support operate method`);
961
+ }
962
+ return this._llm.operate(message, options);
963
+ }
191
964
  static async send(message, options) {
192
- const { llm, ...messageOptions } = options || {};
193
- const instance = new Llm(llm);
965
+ const { llm, apiKey, model, ...messageOptions } = options || {};
966
+ const instance = new Llm(llm, { apiKey, model });
194
967
  return instance.send(message, messageOptions);
195
968
  }
969
+ static async operate(message, options) {
970
+ const { llm, apiKey, model, ...operateOptions } = options || {};
971
+ const instance = new Llm(llm, { apiKey, model });
972
+ return instance.operate(message, operateOptions);
973
+ }
196
974
  }
197
975
 
198
- export { constants as LLM, Llm };
976
+ const random = {
977
+ description: "Generate a random number with optional distribution, precision, range, and seeding",
978
+ name: "random",
979
+ parameters: {
980
+ type: "object",
981
+ properties: {
982
+ min: {
983
+ type: "number",
984
+ description: "Minimum value (inclusive). Default: 0. When used with mean/stddev, acts as a clamp on the normal distribution",
985
+ },
986
+ max: {
987
+ type: "number",
988
+ description: "Maximum value (inclusive). Default: 1. When used with mean/stddev, acts as a clamp on the normal distribution",
989
+ },
990
+ mean: {
991
+ type: "number",
992
+ description: "Mean value for normal distribution. When set with stddev, uses normal distribution (clamped by min/max if provided)",
993
+ },
994
+ stddev: {
995
+ type: "number",
996
+ description: "Standard deviation for normal distribution. When set with mean, uses normal distribution (clamped by min/max if provided)",
997
+ },
998
+ integer: {
999
+ type: "boolean",
1000
+ description: "Whether to return an integer value. Default: false",
1001
+ },
1002
+ seed: {
1003
+ type: "string",
1004
+ description: "Seed string for consistent random generation. Default: undefined (uses default RNG)",
1005
+ },
1006
+ precision: {
1007
+ type: "number",
1008
+ description: "Number of decimal places for the result. Default: undefined (full precision)",
1009
+ },
1010
+ currency: {
1011
+ type: "boolean",
1012
+ description: "Whether to format as currency (2 decimal places). Default: false",
1013
+ },
1014
+ },
1015
+ required: [],
1016
+ },
1017
+ type: "function",
1018
+ call: (options) => {
1019
+ const rng = random$1();
1020
+ return rng(options);
1021
+ },
1022
+ };
1023
+
1024
+ const roll = {
1025
+ description: "Roll one or more dice with a specified number of sides",
1026
+ name: "roll",
1027
+ parameters: {
1028
+ type: "object",
1029
+ properties: {
1030
+ number: {
1031
+ type: "number",
1032
+ description: "Number of dice to roll. Default: 1",
1033
+ },
1034
+ sides: {
1035
+ type: "number",
1036
+ description: "Number of sides on each die. Default: 6",
1037
+ },
1038
+ },
1039
+ required: ["number", "sides"],
1040
+ },
1041
+ type: "function",
1042
+ call: ({ number = 1, sides = 6 } = {}) => {
1043
+ const rng = random$1();
1044
+ const rolls = [];
1045
+ let total = 0;
1046
+ const parsedNumber = tryParseNumber(number, {
1047
+ defaultValue: 1,
1048
+ warnFunction: log.warn,
1049
+ });
1050
+ const parsedSides = tryParseNumber(sides, {
1051
+ defaultValue: 6,
1052
+ warnFunction: log.warn,
1053
+ });
1054
+ for (let i = 0; i < parsedNumber; i++) {
1055
+ const rollValue = rng({ min: 1, max: parsedSides, integer: true });
1056
+ rolls.push(rollValue);
1057
+ total += rollValue;
1058
+ }
1059
+ return { rolls, total };
1060
+ },
1061
+ };
1062
+
1063
+ const time = {
1064
+ description: "Returns the provided date as an ISO UTC string or the current time if no date provided.",
1065
+ name: "time",
1066
+ parameters: {
1067
+ type: "object",
1068
+ properties: {
1069
+ date: {
1070
+ type: "string",
1071
+ description: "Date string to convert to ISO UTC format. Default: current time",
1072
+ },
1073
+ },
1074
+ required: [],
1075
+ },
1076
+ type: "function",
1077
+ call: ({ date } = {}) => {
1078
+ if (typeof date === "number" || typeof date === "string") {
1079
+ const parsedDate = new Date(date);
1080
+ if (isNaN(parsedDate.getTime())) {
1081
+ throw new Error(`Invalid date format: ${date}`);
1082
+ }
1083
+ return parsedDate.toISOString();
1084
+ }
1085
+ return new Date().toISOString();
1086
+ },
1087
+ };
1088
+
1089
+ const weather = {
1090
+ description: "Get current weather and forecast data for a specific location",
1091
+ name: "weather",
1092
+ parameters: {
1093
+ type: "object",
1094
+ properties: {
1095
+ latitude: {
1096
+ type: "number",
1097
+ description: "Latitude of the location. Default: 42.051554533384866 (Evanston, IL)",
1098
+ },
1099
+ longitude: {
1100
+ type: "number",
1101
+ description: "Longitude of the location. Default: -87.6759911441785 (Evanston, IL)",
1102
+ },
1103
+ timezone: {
1104
+ type: "string",
1105
+ description: "Timezone for the location. Default: America/Chicago",
1106
+ },
1107
+ past_days: {
1108
+ type: "number",
1109
+ description: "Number of past days to include in the forecast. Default: 1",
1110
+ },
1111
+ forecast_days: {
1112
+ type: "number",
1113
+ description: "Number of forecast days to include. Default: 1",
1114
+ },
1115
+ },
1116
+ required: [],
1117
+ },
1118
+ type: "function",
1119
+ call: async ({ latitude = 42.051554533384866, longitude = -87.6759911441785, timezone = "America/Chicago", past_days = 1, forecast_days = 1, } = {}) => {
1120
+ try {
1121
+ const params = {
1122
+ latitude,
1123
+ longitude,
1124
+ hourly: [
1125
+ "temperature_2m",
1126
+ "precipitation_probability",
1127
+ "precipitation",
1128
+ ],
1129
+ current: [
1130
+ "temperature_2m",
1131
+ "is_day",
1132
+ "showers",
1133
+ "cloud_cover",
1134
+ "wind_speed_10m",
1135
+ "relative_humidity_2m",
1136
+ "precipitation",
1137
+ "snowfall",
1138
+ "rain",
1139
+ "apparent_temperature",
1140
+ ],
1141
+ timezone,
1142
+ past_days,
1143
+ forecast_days,
1144
+ wind_speed_unit: "mph",
1145
+ temperature_unit: "fahrenheit",
1146
+ precipitation_unit: "inch",
1147
+ };
1148
+ const url = "https://api.open-meteo.com/v1/forecast";
1149
+ const responses = await fetchWeatherApi(url, params);
1150
+ // Helper function to form time ranges
1151
+ const range = (start, stop, step) => Array.from({ length: (stop - start) / step }, (_, i) => start + i * step);
1152
+ // Process first location
1153
+ const response = responses[0];
1154
+ // Attributes for timezone and location
1155
+ const utcOffsetSeconds = response.utcOffsetSeconds();
1156
+ const timezoneAbbreviation = response.timezoneAbbreviation();
1157
+ const current = response.current();
1158
+ const hourly = response.hourly();
1159
+ // Create weather data object
1160
+ const weatherData = {
1161
+ location: {
1162
+ latitude: response.latitude(),
1163
+ longitude: response.longitude(),
1164
+ timezone: response.timezone(),
1165
+ timezoneAbbreviation,
1166
+ utcOffsetSeconds,
1167
+ },
1168
+ current: {
1169
+ time: new Date((Number(current.time()) + utcOffsetSeconds) * 1000).toISOString(),
1170
+ temperature2m: current.variables(0).value(),
1171
+ isDay: current.variables(1).value(),
1172
+ showers: current.variables(2).value(),
1173
+ cloudCover: current.variables(3).value(),
1174
+ windSpeed10m: current.variables(4).value(),
1175
+ relativeHumidity2m: current.variables(5).value(),
1176
+ precipitation: current.variables(6).value(),
1177
+ snowfall: current.variables(7).value(),
1178
+ rain: current.variables(8).value(),
1179
+ apparentTemperature: current.variables(9).value(),
1180
+ },
1181
+ hourly: {
1182
+ time: range(Number(hourly.time()), Number(hourly.timeEnd()), hourly.interval()).map((t) => new Date((t + utcOffsetSeconds) * 1000).toISOString()),
1183
+ temperature2m: Array.from(hourly.variables(0).valuesArray()),
1184
+ precipitationProbability: Array.from(hourly.variables(1).valuesArray()),
1185
+ precipitation: Array.from(hourly.variables(2).valuesArray()),
1186
+ },
1187
+ };
1188
+ return weatherData;
1189
+ }
1190
+ catch (error) {
1191
+ if (error instanceof Error) {
1192
+ throw new Error(`Weather API error: ${error.message}`);
1193
+ }
1194
+ throw new Error("Unknown error occurred while fetching weather data");
1195
+ }
1196
+ },
1197
+ };
1198
+
1199
+ const toolkit = {
1200
+ random,
1201
+ roll,
1202
+ time,
1203
+ weather,
1204
+ };
1205
+ const tools = Object.values(toolkit);
1206
+
1207
+ export { constants as LLM, Llm, toolkit, tools };
199
1208
  //# sourceMappingURL=index.js.map