@jaypie/llm 1.1.25 → 1.1.27

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 (66) hide show
  1. package/dist/cjs/index.cjs +2128 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/{types → cjs/types}/LlmProvider.interface.d.ts +2 -2
  4. package/dist/esm/Llm.d.ts +23 -0
  5. package/dist/esm/constants.d.ts +100 -0
  6. package/dist/esm/index.d.ts +6 -0
  7. package/dist/{index.js → esm/index.js} +8 -6
  8. package/dist/esm/index.js.map +1 -0
  9. package/dist/esm/providers/anthropic/AnthropicProvider.class.d.ts +15 -0
  10. package/dist/esm/providers/anthropic/index.d.ts +2 -0
  11. package/dist/esm/providers/anthropic/operate.d.ts +16 -0
  12. package/dist/esm/providers/anthropic/types.d.ts +2 -0
  13. package/dist/esm/providers/anthropic/utils.d.ts +48 -0
  14. package/dist/esm/providers/openai/OpenAiProvider.class.d.ts +15 -0
  15. package/dist/esm/providers/openai/index.d.ts +1 -0
  16. package/dist/esm/providers/openai/operate.d.ts +26 -0
  17. package/dist/esm/providers/openai/types.d.ts +100 -0
  18. package/dist/esm/providers/openai/utils.d.ts +59 -0
  19. package/dist/esm/tools/Toolkit.class.d.ts +28 -0
  20. package/dist/esm/tools/index.d.ts +12 -0
  21. package/dist/esm/tools/random.d.ts +2 -0
  22. package/dist/esm/tools/roll.d.ts +2 -0
  23. package/dist/esm/tools/time.d.ts +2 -0
  24. package/dist/esm/tools/weather.d.ts +2 -0
  25. package/dist/esm/types/LlmProvider.interface.d.ts +198 -0
  26. package/dist/esm/types/LlmTool.interface.d.ts +11 -0
  27. package/dist/esm/util/determineModelProvider.d.ts +4 -0
  28. package/dist/esm/util/formatOperateInput.d.ts +24 -0
  29. package/dist/esm/util/formatOperateMessage.d.ts +24 -0
  30. package/dist/esm/util/index.d.ts +8 -0
  31. package/dist/esm/util/logger.d.ts +71 -0
  32. package/dist/esm/util/maxTurnsFromOptions.d.ts +10 -0
  33. package/dist/esm/util/naturalZodSchema.d.ts +3 -0
  34. package/dist/esm/util/random.d.ts +39 -0
  35. package/dist/esm/util/tryParseNumber.d.ts +22 -0
  36. package/package.json +8 -6
  37. package/dist/index.js.map +0 -1
  38. /package/dist/{Llm.d.ts → cjs/Llm.d.ts} +0 -0
  39. /package/dist/{constants.d.ts → cjs/constants.d.ts} +0 -0
  40. /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  41. /package/dist/{providers → cjs/providers}/anthropic/AnthropicProvider.class.d.ts +0 -0
  42. /package/dist/{providers → cjs/providers}/anthropic/index.d.ts +0 -0
  43. /package/dist/{providers → cjs/providers}/anthropic/operate.d.ts +0 -0
  44. /package/dist/{providers → cjs/providers}/anthropic/types.d.ts +0 -0
  45. /package/dist/{providers → cjs/providers}/anthropic/utils.d.ts +0 -0
  46. /package/dist/{providers → cjs/providers}/openai/OpenAiProvider.class.d.ts +0 -0
  47. /package/dist/{providers → cjs/providers}/openai/index.d.ts +0 -0
  48. /package/dist/{providers → cjs/providers}/openai/operate.d.ts +0 -0
  49. /package/dist/{providers → cjs/providers}/openai/types.d.ts +0 -0
  50. /package/dist/{providers → cjs/providers}/openai/utils.d.ts +0 -0
  51. /package/dist/{tools → cjs/tools}/Toolkit.class.d.ts +0 -0
  52. /package/dist/{tools → cjs/tools}/index.d.ts +0 -0
  53. /package/dist/{tools → cjs/tools}/random.d.ts +0 -0
  54. /package/dist/{tools → cjs/tools}/roll.d.ts +0 -0
  55. /package/dist/{tools → cjs/tools}/time.d.ts +0 -0
  56. /package/dist/{tools → cjs/tools}/weather.d.ts +0 -0
  57. /package/dist/{types → cjs/types}/LlmTool.interface.d.ts +0 -0
  58. /package/dist/{util → cjs/util}/determineModelProvider.d.ts +0 -0
  59. /package/dist/{util → cjs/util}/formatOperateInput.d.ts +0 -0
  60. /package/dist/{util → cjs/util}/formatOperateMessage.d.ts +0 -0
  61. /package/dist/{util → cjs/util}/index.d.ts +0 -0
  62. /package/dist/{util → cjs/util}/logger.d.ts +0 -0
  63. /package/dist/{util → cjs/util}/maxTurnsFromOptions.d.ts +0 -0
  64. /package/dist/{util → cjs/util}/naturalZodSchema.d.ts +0 -0
  65. /package/dist/{util → cjs/util}/random.d.ts +0 -0
  66. /package/dist/{util → cjs/util}/tryParseNumber.d.ts +0 -0
@@ -0,0 +1,2128 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@jaypie/errors');
4
+ var core = require('@jaypie/core');
5
+ var openai = require('openai');
6
+ var zod = require('openai/helpers/zod');
7
+ var v4 = require('zod/v4');
8
+ var RandomLib = require('random');
9
+ var aws = require('@jaypie/aws');
10
+ var Anthropic = require('@anthropic-ai/sdk');
11
+ var ZSchema = require('z-schema');
12
+ var openmeteo = require('openmeteo');
13
+
14
+ const PROVIDER = {
15
+ ANTHROPIC: {
16
+ // https://docs.anthropic.com/en/docs/about-claude/models/overview
17
+ MODEL: {
18
+ // Jaypie Aliases
19
+ DEFAULT: "claude-opus-4-1",
20
+ SMALL: "claude-sonnet-4-0",
21
+ TINY: "claude-3-5-haiku-latest",
22
+ LARGE: "claude-opus-4-1",
23
+ // Latests
24
+ CLAUDE_OPUS_4: "claude-opus-4-1",
25
+ CLAUDE_SONNET_4: "claude-sonnet-4-0",
26
+ CLAUDE_3_HAIKU: "claude-3-5-haiku-latest",
27
+ CLAUDE_3_OPUS: "claude-3-opus-latest",
28
+ CLAUDE_3_SONNET: "claude-3-7-sonnet-latest",
29
+ // Specifics
30
+ CLAUDE_OPUS_4_1: "claude-opus-4-1",
31
+ CLAUDE_OPUS_4_0: "claude-opus-4-0",
32
+ CLAUDE_SONNET_4_0: "claude-sonnet-4-0",
33
+ CLAUDE_3_7_SONNET: "claude-3-7-sonnet-latest ",
34
+ CLAUDE_3_5_SONNET: "claude-3-5-sonnet-latest",
35
+ CLAUDE_3_5_HAIKU: "claude-3-5-haiku-latest",
36
+ // _Note: Claude reversed the order of model name and version in 4_
37
+ // Backward compatibility
38
+ CLAUDE_HAIKU_3: "claude-3-5-haiku-latest",
39
+ CLAUDE_OPUS_3: "claude-3-opus-latest",
40
+ CLAUDE_SONNET_3: "claude-3-7-sonnet-latest",
41
+ },
42
+ MODEL_MATCH_WORDS: [
43
+ "anthropic",
44
+ "claude",
45
+ "haiku",
46
+ "opus",
47
+ "sonnet",
48
+ ],
49
+ NAME: "anthropic",
50
+ PROMPT: {
51
+ AI: "\n\nAssistant:",
52
+ HUMAN: "\n\nHuman:",
53
+ },
54
+ ROLE: {
55
+ ASSISTANT: "assistant",
56
+ SYSTEM: "system",
57
+ USER: "user",
58
+ },
59
+ MAX_TOKENS: {
60
+ DEFAULT: 4096,
61
+ },
62
+ TOOLS: {
63
+ SCHEMA_VERSION: "v2",
64
+ },
65
+ },
66
+ OPENAI: {
67
+ // https://platform.openai.com/docs/models
68
+ MODEL: {
69
+ // Jaypie Aliases
70
+ DEFAULT: "gpt-5",
71
+ SMALL: "gpt-5-mini",
72
+ LARGE: "gpt-5",
73
+ TINY: "gpt-5-nano",
74
+ // OpenAI Official
75
+ GPT_5: "gpt-5",
76
+ GPT_5_MINI: "gpt-5-mini",
77
+ GPT_5_NANO: "gpt-5-nano",
78
+ GPT_4_1: "gpt-4.1",
79
+ GPT_4_1_MINI: "gpt-4.1-mini",
80
+ GPT_4_1_NANO: "gpt-4.1-nano",
81
+ GPT_4: "gpt-4",
82
+ GPT_4_O_MINI: "gpt-4o-mini",
83
+ GPT_4_O: "gpt-4o",
84
+ GPT_4_5: "gpt-4.5-preview",
85
+ O1: "o1",
86
+ O1_MINI: "o1-mini",
87
+ O1_PRO: "o1-pro",
88
+ O3_MINI: "o3-mini",
89
+ O3_MINI_HIGH: "o3-mini-high",
90
+ O3: "o3",
91
+ O3_PRO: "o3-pro",
92
+ O4_MINI: "o4-mini",
93
+ },
94
+ MODEL_MATCH_WORDS: ["openai", "gpt", /^o\d/],
95
+ NAME: "openai",
96
+ },
97
+ };
98
+ // Last: Defaults
99
+ const DEFAULT = {
100
+ PROVIDER: PROVIDER.OPENAI,
101
+ };
102
+
103
+ var constants = /*#__PURE__*/Object.freeze({
104
+ __proto__: null,
105
+ DEFAULT: DEFAULT,
106
+ PROVIDER: PROVIDER
107
+ });
108
+
109
+ function determineModelProvider(input) {
110
+ if (!input) {
111
+ return {
112
+ model: DEFAULT.PROVIDER.MODEL.DEFAULT,
113
+ provider: DEFAULT.PROVIDER.NAME,
114
+ };
115
+ }
116
+ // Check if input is a provider name
117
+ if (input === PROVIDER.ANTHROPIC.NAME) {
118
+ return {
119
+ model: PROVIDER.ANTHROPIC.MODEL.DEFAULT,
120
+ provider: PROVIDER.ANTHROPIC.NAME,
121
+ };
122
+ }
123
+ if (input === PROVIDER.OPENAI.NAME) {
124
+ return {
125
+ model: PROVIDER.OPENAI.MODEL.DEFAULT,
126
+ provider: PROVIDER.OPENAI.NAME,
127
+ };
128
+ }
129
+ // Check if input matches an Anthropic model exactly
130
+ for (const [, modelValue] of Object.entries(PROVIDER.ANTHROPIC.MODEL)) {
131
+ if (input === modelValue) {
132
+ return {
133
+ model: input,
134
+ provider: PROVIDER.ANTHROPIC.NAME,
135
+ };
136
+ }
137
+ }
138
+ // Check if input matches an OpenAI model exactly
139
+ for (const [, modelValue] of Object.entries(PROVIDER.OPENAI.MODEL)) {
140
+ if (input === modelValue) {
141
+ return {
142
+ model: input,
143
+ provider: PROVIDER.OPENAI.NAME,
144
+ };
145
+ }
146
+ }
147
+ // Check Anthropic match words
148
+ const lowerInput = input.toLowerCase();
149
+ for (const matchWord of PROVIDER.ANTHROPIC.MODEL_MATCH_WORDS) {
150
+ if (lowerInput.includes(matchWord)) {
151
+ return {
152
+ model: input,
153
+ provider: PROVIDER.ANTHROPIC.NAME,
154
+ };
155
+ }
156
+ }
157
+ // Check OpenAI match words
158
+ for (const matchWord of PROVIDER.OPENAI.MODEL_MATCH_WORDS) {
159
+ if (typeof matchWord === "string") {
160
+ if (lowerInput.includes(matchWord)) {
161
+ return {
162
+ model: input,
163
+ provider: PROVIDER.OPENAI.NAME,
164
+ };
165
+ }
166
+ }
167
+ else if (matchWord instanceof RegExp) {
168
+ if (matchWord.test(input)) {
169
+ return {
170
+ model: input,
171
+ provider: PROVIDER.OPENAI.NAME,
172
+ };
173
+ }
174
+ }
175
+ }
176
+ // Default fallback if model not recognized
177
+ return {
178
+ model: input,
179
+ };
180
+ }
181
+
182
+ const DEFAULT_TOOL_TYPE = "function";
183
+ const log$1 = core.log.lib({ lib: core.JAYPIE.LIB.LLM });
184
+ function logToolMessage(message, context) {
185
+ log$1.trace.var({ [context.name]: message });
186
+ }
187
+ class Toolkit {
188
+ constructor(tools, options) {
189
+ this._tools = tools;
190
+ this._options = options || {};
191
+ this.explain = this._options.explain ? true : false;
192
+ this.log = this._options.log !== undefined ? this._options.log : true;
193
+ }
194
+ get tools() {
195
+ return this._tools.map((tool) => {
196
+ const toolCopy = { ...tool };
197
+ delete toolCopy.call;
198
+ delete toolCopy.message;
199
+ if (this.explain && toolCopy.parameters?.type === "object") {
200
+ if (toolCopy.parameters?.properties) {
201
+ if (!toolCopy.parameters.properties.__Explanation) {
202
+ toolCopy.parameters.properties.__Explanation = {
203
+ type: "string",
204
+ description: `Clearly state why the tool is being called and what larger question it helps answer. For example, "I am checking the location API because the user asked for nearby pizza and I need coordinates to proceed"`,
205
+ };
206
+ }
207
+ }
208
+ }
209
+ // Set default type if not provided
210
+ if (!toolCopy.type) {
211
+ toolCopy.type = DEFAULT_TOOL_TYPE;
212
+ }
213
+ return toolCopy;
214
+ });
215
+ }
216
+ async call({ name, arguments: args }) {
217
+ const tool = this._tools.find((t) => t.name === name);
218
+ if (!tool) {
219
+ throw new Error(`Tool '${name}' not found`);
220
+ }
221
+ let parsedArgs;
222
+ try {
223
+ parsedArgs = JSON.parse(args);
224
+ if (this.explain) {
225
+ delete parsedArgs.__Explanation;
226
+ }
227
+ }
228
+ catch {
229
+ parsedArgs = args;
230
+ }
231
+ if (this.log !== false) {
232
+ try {
233
+ const context = {
234
+ name,
235
+ args: parsedArgs,
236
+ };
237
+ let message;
238
+ if (this.explain) {
239
+ context.explanation = parsedArgs.__Explanation;
240
+ }
241
+ if (tool.message) {
242
+ if (typeof tool.message === "string") {
243
+ log$1.trace("[Toolkit] Tool provided string message");
244
+ message = tool.message;
245
+ }
246
+ else if (typeof tool.message === "function") {
247
+ log$1.trace("[Toolkit] Tool provided function message");
248
+ log$1.trace("[Toolkit] Resolving message result");
249
+ message = await core.resolveValue(tool.message(parsedArgs, { name }));
250
+ }
251
+ else {
252
+ log$1.warn("[Toolkit] Tool provided unknown message type");
253
+ message = String(tool.message);
254
+ }
255
+ }
256
+ else {
257
+ log$1.trace("[Toolkit] Log tool call with default message");
258
+ message = `${tool.name}:${JSON.stringify(parsedArgs)}`;
259
+ }
260
+ if (typeof this.log === "function") {
261
+ log$1.trace("[Toolkit] Log tool call with custom logger");
262
+ await core.resolveValue(this.log(message, context));
263
+ }
264
+ else {
265
+ log$1.trace("[Toolkit] Log tool call with default logger");
266
+ logToolMessage(message, context);
267
+ }
268
+ }
269
+ catch (error) {
270
+ log$1.error("[Toolkit] Caught error during logToolCall");
271
+ log$1.var({ error });
272
+ log$1.debug("[Toolkit] Continuing...");
273
+ }
274
+ }
275
+ return await core.resolveValue(tool.call(parsedArgs));
276
+ }
277
+ extend(tools, options = {}) {
278
+ for (const tool of tools) {
279
+ const existingIndex = this._tools.findIndex((t) => t.name === tool.name);
280
+ if (existingIndex !== -1) {
281
+ if (options.replace === false) {
282
+ continue;
283
+ }
284
+ if (options.warn !== false) {
285
+ if (typeof this.log === "function") {
286
+ this.log(`[Toolkit] Tool '${tool.name}' already exists, replacing with new tool`, { name: tool.name, args: {} });
287
+ }
288
+ else if (this.log) {
289
+ log$1.warn(`[Toolkit] Tool '${tool.name}' already exists, replacing with new tool`);
290
+ }
291
+ }
292
+ this._tools[existingIndex] = tool;
293
+ }
294
+ else {
295
+ this._tools.push(tool);
296
+ }
297
+ }
298
+ if (Object.prototype.hasOwnProperty.call(options, "log")) {
299
+ this.log = options.log;
300
+ }
301
+ if (Object.prototype.hasOwnProperty.call(options, "explain")) {
302
+ this.explain = options.explain;
303
+ }
304
+ return this;
305
+ }
306
+ }
307
+
308
+ // Enums
309
+ exports.LlmMessageRole = void 0;
310
+ (function (LlmMessageRole) {
311
+ LlmMessageRole["Assistant"] = "assistant";
312
+ LlmMessageRole["Developer"] = "developer";
313
+ LlmMessageRole["System"] = "system";
314
+ LlmMessageRole["User"] = "user";
315
+ })(exports.LlmMessageRole || (exports.LlmMessageRole = {}));
316
+ exports.LlmMessageType = void 0;
317
+ (function (LlmMessageType) {
318
+ LlmMessageType["FunctionCall"] = "function_call";
319
+ LlmMessageType["FunctionCallOutput"] = "function_call_output";
320
+ LlmMessageType["InputFile"] = "input_file";
321
+ LlmMessageType["InputImage"] = "input_image";
322
+ LlmMessageType["InputText"] = "input_text";
323
+ LlmMessageType["ItemReference"] = "item_reference";
324
+ LlmMessageType["Message"] = "message";
325
+ LlmMessageType["OutputText"] = "output_text";
326
+ LlmMessageType["Refusal"] = "refusal";
327
+ })(exports.LlmMessageType || (exports.LlmMessageType = {}));
328
+ var LlmResponseStatus;
329
+ (function (LlmResponseStatus) {
330
+ LlmResponseStatus["Completed"] = "completed";
331
+ LlmResponseStatus["Incomplete"] = "incomplete";
332
+ LlmResponseStatus["InProgress"] = "in_progress";
333
+ })(LlmResponseStatus || (LlmResponseStatus = {}));
334
+
335
+ /**
336
+ * Converts a string to a standardized LlmInputMessage
337
+ * @param input - String to format
338
+ * @param options - Optional configuration
339
+ * @param options.data - Data to use for placeholder substitution
340
+ * @param options.role - Role to use for the message (defaults to User)
341
+ * @returns LlmInputMessage
342
+ */
343
+ function formatOperateMessage(input, options) {
344
+ const content = options?.data ? core.placeholders(input, options.data) : input;
345
+ return {
346
+ content,
347
+ role: options?.role || exports.LlmMessageRole.User,
348
+ type: exports.LlmMessageType.Message,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Converts various input types to a standardized LlmHistory format
354
+ * @param input - String, LlmInputMessage, or LlmHistory to format
355
+ * @param options - Optional configuration
356
+ * @param options.data - Data to use for placeholder substitution
357
+ * @param options.role - Role to use when converting a string to LlmInputMessage (defaults to User)
358
+ * @returns Standardized LlmHistory array
359
+ */
360
+ function formatOperateInput(input, options) {
361
+ // If input is already LlmHistory, return it
362
+ if (Array.isArray(input)) {
363
+ return input;
364
+ }
365
+ // If input is a string, convert it to LlmInputMessage
366
+ if (typeof input === "string") {
367
+ return [formatOperateMessage(input, options)];
368
+ }
369
+ // If input is LlmInputMessage, apply placeholders if data is provided
370
+ if (options?.data && typeof input.content === "string") {
371
+ return [
372
+ formatOperateMessage(input.content, {
373
+ data: options.data,
374
+ role: input.role || options?.role,
375
+ }),
376
+ ];
377
+ }
378
+ // Otherwise, just wrap the input in an array
379
+ return [input];
380
+ }
381
+
382
+ const getLogger$2 = () => core.log.lib({ lib: core.JAYPIE.LIB.LLM });
383
+ const log = getLogger$2();
384
+
385
+ // Turn policy constants
386
+ const MAX_TURNS_ABSOLUTE_LIMIT = 72;
387
+ const MAX_TURNS_DEFAULT_LIMIT = 12;
388
+ /**
389
+ * Determines the maximum number of turns based on the provided options
390
+ *
391
+ * @param options - The LLM operate options
392
+ * @returns The maximum number of turns
393
+ */
394
+ function maxTurnsFromOptions(options) {
395
+ // Default to single turn (1) when turns are disabled
396
+ // Handle the turns parameter
397
+ if (options.turns === undefined) {
398
+ // Default to default limit when undefined
399
+ return MAX_TURNS_DEFAULT_LIMIT;
400
+ }
401
+ else if (options.turns === true) {
402
+ // Explicitly set to true
403
+ return MAX_TURNS_DEFAULT_LIMIT;
404
+ }
405
+ else if (typeof options.turns === "number") {
406
+ if (options.turns > 0) {
407
+ // Positive number - use that limit (capped at absolute limit)
408
+ return Math.min(options.turns, MAX_TURNS_ABSOLUTE_LIMIT);
409
+ }
410
+ else if (options.turns < 0) {
411
+ // Negative number - use default limit
412
+ return MAX_TURNS_DEFAULT_LIMIT;
413
+ }
414
+ // If turns is 0, return 1 (disabled)
415
+ return 1;
416
+ }
417
+ // All other values (false, null, etc.) will return 1 (disabled)
418
+ return 1;
419
+ }
420
+
421
+ function naturalZodSchema(definition) {
422
+ if (Array.isArray(definition)) {
423
+ if (definition.length === 0) {
424
+ // Handle empty array - accept any[]
425
+ return v4.z.array(v4.z.any());
426
+ }
427
+ else if (definition.length === 1) {
428
+ // Handle array types
429
+ const itemType = definition[0];
430
+ switch (itemType) {
431
+ case String:
432
+ return v4.z.array(v4.z.string());
433
+ case Number:
434
+ return v4.z.array(v4.z.number());
435
+ case Boolean:
436
+ return v4.z.array(v4.z.boolean());
437
+ default:
438
+ if (typeof itemType === "object") {
439
+ // Handle array of objects
440
+ return v4.z.array(naturalZodSchema(itemType));
441
+ }
442
+ // Handle enum arrays
443
+ return v4.z.enum(definition);
444
+ }
445
+ }
446
+ else {
447
+ // Handle enum arrays
448
+ return v4.z.enum(definition);
449
+ }
450
+ }
451
+ else if (definition && typeof definition === "object") {
452
+ if (Object.keys(definition).length === 0) {
453
+ // Handle empty object - accept any key-value pairs
454
+ return v4.z.record(v4.z.string(), v4.z.any());
455
+ }
456
+ else {
457
+ // Handle object with properties
458
+ const schemaShape = {};
459
+ for (const [key, value] of Object.entries(definition)) {
460
+ schemaShape[key] = naturalZodSchema(value);
461
+ }
462
+ return v4.z.object(schemaShape);
463
+ }
464
+ }
465
+ else {
466
+ switch (definition) {
467
+ case String:
468
+ return v4.z.string();
469
+ case Number:
470
+ return v4.z.number();
471
+ case Boolean:
472
+ return v4.z.boolean();
473
+ case Object:
474
+ return v4.z.record(v4.z.string(), v4.z.any());
475
+ case Array:
476
+ return v4.z.array(v4.z.any());
477
+ default:
478
+ throw new Error(`Unsupported type: ${definition}`);
479
+ }
480
+ }
481
+ }
482
+
483
+ //
484
+ // Constants
485
+ //
486
+ const DEFAULT_MIN = 0;
487
+ const DEFAULT_MAX = 1;
488
+ //
489
+ // Helper Functions
490
+ //
491
+ /**
492
+ * Clamps a number between optional minimum and maximum values
493
+ * @param num - The number to clamp
494
+ * @param min - Optional minimum value
495
+ * @param max - Optional maximum value
496
+ * @returns The clamped number
497
+ */
498
+ const clamp = (num, min, max) => {
499
+ let result = num;
500
+ if (typeof min === "number")
501
+ result = Math.max(result, min);
502
+ if (typeof max === "number")
503
+ result = Math.min(result, max);
504
+ return result;
505
+ };
506
+ /**
507
+ * Validates that min is not greater than max
508
+ * @param min - Optional minimum value
509
+ * @param max - Optional maximum value
510
+ * @throws {ConfigurationError} If min is greater than max
511
+ */
512
+ const validateBounds = (min, max) => {
513
+ if (typeof min === "number" && typeof max === "number" && min > max) {
514
+ throw new errors.ConfigurationError(`Invalid bounds: min (${min}) cannot be greater than max (${max})`);
515
+ }
516
+ };
517
+ //
518
+ // Main
519
+ //
520
+ /**
521
+ * Creates a random number generator with optional seeding
522
+ *
523
+ * @param defaultSeed - Seed string for the default RNG
524
+ * @returns A function that generates random numbers based on provided options
525
+ *
526
+ * @example
527
+ * const rng = random("default-seed");
528
+ *
529
+ * // Generate a random float between 0 and 1
530
+ * const basic = rng();
531
+ *
532
+ * // Generate an integer between 1 and 10
533
+ * const integer = rng({ min: 1, max: 10, integer: true });
534
+ *
535
+ * // Generate from normal distribution
536
+ * const normal = rng({ mean: 50, stddev: 10 });
537
+ *
538
+ * // Use consistent seeding
539
+ * const seeded = rng({ seed: "my-seed" });
540
+ */
541
+ function random$1(defaultSeed) {
542
+ // Initialize default seeded RNG
543
+ const defaultRng = RandomLib.clone(defaultSeed);
544
+ // Store per-seed RNGs
545
+ const seedMap = new Map();
546
+ const rngFn = ({ min, max: providedMax, mean, stddev, integer = false, start = 0, seed, precision, currency = false, } = {}) => {
547
+ // Select the appropriate RNG based on seed
548
+ const rng = seed
549
+ ? seedMap.get(seed) ||
550
+ (() => {
551
+ const newRng = RandomLib.clone(seed);
552
+ seedMap.set(seed, newRng);
553
+ return newRng;
554
+ })()
555
+ : defaultRng;
556
+ // If only min is set, set max to min*2, but keep track of whether max was provided
557
+ let max = providedMax;
558
+ if (typeof min === "number" && typeof max !== "number") {
559
+ max = min * 2;
560
+ }
561
+ validateBounds(min, max);
562
+ // Determine effective precision based on parameters
563
+ const getEffectivePrecision = () => {
564
+ if (integer)
565
+ return 0;
566
+ const precisions = [];
567
+ if (typeof precision === "number" && precision >= 0) {
568
+ precisions.push(precision);
569
+ }
570
+ if (currency) {
571
+ precisions.push(2);
572
+ }
573
+ // Return the lowest precision if any are set, undefined otherwise
574
+ return precisions.length > 0 ? Math.min(...precisions) : undefined;
575
+ };
576
+ // Helper function to apply precision
577
+ const applyPrecision = (value) => {
578
+ const effectivePrecision = getEffectivePrecision();
579
+ if (typeof effectivePrecision === "number") {
580
+ const factor = Math.pow(10, effectivePrecision);
581
+ return Math.round(value * factor) / factor;
582
+ }
583
+ return value;
584
+ };
585
+ // Use normal distribution if both mean and stddev are provided
586
+ if (typeof mean === "number" && typeof stddev === "number") {
587
+ const normalDist = rng.normal(mean, stddev);
588
+ const value = normalDist();
589
+ // Only clamp if min/max are defined
590
+ const clampedValue = clamp(value, min, providedMax);
591
+ // Switch to uniform distribution only if both bounds were explicitly provided and exceeded
592
+ if (typeof min === "number" &&
593
+ typeof providedMax === "number" &&
594
+ clampedValue !== value) {
595
+ const baseValue = integer ? rng.int(min, max) : rng.float(min, max);
596
+ return applyPrecision(start + baseValue);
597
+ }
598
+ return applyPrecision(start + (integer ? Math.round(clampedValue) : clampedValue));
599
+ }
600
+ // For uniform distribution, use defaults if min/max are undefined
601
+ const uniformMin = typeof min === "number" ? min : DEFAULT_MIN;
602
+ const uniformMax = typeof max === "number" ? max : DEFAULT_MAX;
603
+ const baseValue = integer
604
+ ? rng.int(uniformMin, uniformMax)
605
+ : rng.float(uniformMin, uniformMax);
606
+ return applyPrecision(start + baseValue);
607
+ };
608
+ return rngFn;
609
+ }
610
+
611
+ /**
612
+ * Helper function to safely call a function that might throw
613
+ * @param fn Function to call safely
614
+ */
615
+ function callSafely(fn) {
616
+ try {
617
+ fn();
618
+ }
619
+ catch {
620
+ // Silently catch any errors from the function
621
+ }
622
+ }
623
+ /**
624
+ * Attempts to parse a value as a number. Returns the original input if parsing fails or results in NaN.
625
+ * @param input - The value to attempt to parse as a number
626
+ * @param options - Optional configuration
627
+ * @param options.defaultValue - Default value to return if parsing fails or results in NaN
628
+ * @param options.warnFunction - Function to call with warning message if parsing fails or results in NaN
629
+ * @returns The parsed number, defaultValue (if specified and parsing fails), or the original input
630
+ */
631
+ function tryParseNumber(input, options) {
632
+ if (input === null || input === undefined) {
633
+ return input;
634
+ }
635
+ try {
636
+ const parsed = Number(input);
637
+ if (Number.isNaN(parsed)) {
638
+ if (options?.warnFunction) {
639
+ const warningMessage = `Failed to parse "${String(input)}" as number`;
640
+ callSafely(() => options.warnFunction(warningMessage));
641
+ }
642
+ return typeof options?.defaultValue === "number"
643
+ ? options.defaultValue
644
+ : input;
645
+ }
646
+ return parsed;
647
+ }
648
+ catch (error) {
649
+ if (options?.warnFunction) {
650
+ let errorMessage = "";
651
+ if (error instanceof Error) {
652
+ errorMessage = error.message;
653
+ }
654
+ const warningMessage = `Error parsing "${String(input)}" as number${errorMessage ? "; " + errorMessage : ""}`;
655
+ callSafely(() => options.warnFunction(warningMessage));
656
+ }
657
+ return typeof options?.defaultValue === "number"
658
+ ? options.defaultValue
659
+ : input;
660
+ }
661
+ }
662
+
663
+ //
664
+ //
665
+ // Constants
666
+ //
667
+ const MAX_RETRIES_ABSOLUTE_LIMIT = 72;
668
+ const MAX_RETRIES_DEFAULT_LIMIT = 6;
669
+ // Retry policy constants
670
+ const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
671
+ const MAX_RETRY_DELAY_MS = 32000; // 32 seconds
672
+ const RETRY_BACKOFF_FACTOR = 2; // Exponential backoff multiplier
673
+ const RETRYABLE_ERRORS = [
674
+ openai.APIConnectionError,
675
+ openai.APIConnectionTimeoutError,
676
+ openai.InternalServerError,
677
+ ];
678
+ const NOT_RETRYABLE_ERRORS = [
679
+ openai.APIUserAbortError,
680
+ openai.AuthenticationError,
681
+ openai.BadRequestError,
682
+ openai.ConflictError,
683
+ openai.NotFoundError,
684
+ openai.PermissionDeniedError,
685
+ openai.RateLimitError,
686
+ openai.UnprocessableEntityError,
687
+ ];
688
+ const ERROR = {
689
+ BAD_FUNCTION_CALL: "Bad Function Call",
690
+ };
691
+ //
692
+ //
693
+ // Helpers
694
+ //
695
+ /**
696
+ * Creates content string for function calls
697
+ */
698
+ function createFunctionCallContent(output) {
699
+ return `${exports.LlmMessageType.FunctionCall}:${output.name}${output.arguments}#${output.call_id}`;
700
+ }
701
+ /**
702
+ * Extracts content from OpenAI response output array
703
+ */
704
+ function extractContentFromResponse(currentResponse, options) {
705
+ if (!currentResponse.output || !Array.isArray(currentResponse.output)) {
706
+ return "";
707
+ }
708
+ for (const output of currentResponse.output) {
709
+ if (output.type === exports.LlmMessageType.Message) {
710
+ if (output.content?.[0] &&
711
+ output.content[0].type === exports.LlmMessageType.OutputText) {
712
+ const rawContent = output.content[0].text;
713
+ // If format is provided, try to parse the content as JSON
714
+ if (options?.format && typeof rawContent === "string") {
715
+ try {
716
+ const parsedContent = JSON.parse(rawContent);
717
+ return parsedContent;
718
+ }
719
+ catch (error) {
720
+ // If parsing fails, keep the original string content
721
+ log.debug("Failed to parse formatted response as JSON");
722
+ log.var({ error });
723
+ return rawContent;
724
+ }
725
+ }
726
+ return rawContent;
727
+ }
728
+ }
729
+ if (output.type === exports.LlmMessageType.FunctionCall) {
730
+ return createFunctionCallContent(output);
731
+ }
732
+ // Skip reasoning items when extracting content
733
+ if (output.type === "reasoning") {
734
+ continue;
735
+ }
736
+ }
737
+ return "";
738
+ }
739
+ /**
740
+ * Creates the request options for the OpenAI API call
741
+ *
742
+ * @param input - The formatted input messages
743
+ * @param options - The LLM operation options
744
+ * @returns The request options for the OpenAI API
745
+ */
746
+ function createRequestOptions(input, options = {}) {
747
+ const requestOptions = {
748
+ model: options?.model || PROVIDER.OPENAI.MODEL.DEFAULT,
749
+ input,
750
+ };
751
+ // Add user if provided
752
+ if (options?.user) {
753
+ requestOptions.user = options.user;
754
+ }
755
+ // Add any provider-specific options
756
+ if (options?.providerOptions) {
757
+ Object.assign(requestOptions, options.providerOptions);
758
+ }
759
+ // Handle instructions or system message
760
+ if (options?.instructions) {
761
+ // Apply placeholders to instructions if data is provided and placeholders.instructions is undefined or true
762
+ requestOptions.instructions =
763
+ options.data &&
764
+ (options.placeholders?.instructions === undefined ||
765
+ options.placeholders?.instructions)
766
+ ? core.placeholders(options.instructions, options.data)
767
+ : options.instructions;
768
+ }
769
+ // Handle developer message as system message
770
+ // @ts-expect-error Testing invalid option
771
+ if (options?.developer) {
772
+ log.warn("Developer message provided but not supported. Using as system message.");
773
+ // @ts-expect-error Testing invalid option
774
+ requestOptions.system = options.developer;
775
+ }
776
+ // Handle structured output format
777
+ if (options?.format) {
778
+ // Check if format is a JsonObject with type "json_schema"
779
+ if (typeof options.format === "object" &&
780
+ options.format !== null &&
781
+ !Array.isArray(options.format) &&
782
+ options.format.type === "json_schema") {
783
+ // Direct pass-through for JsonObject with type "json_schema"
784
+ requestOptions.text = {
785
+ format: options.format,
786
+ };
787
+ }
788
+ else {
789
+ // Convert NaturalSchema to Zod schema if needed
790
+ const zodSchema = options.format instanceof v4.z.ZodType
791
+ ? options.format
792
+ : naturalZodSchema(options.format);
793
+ const responseFormat = zod.zodResponseFormat(zodSchema, "response");
794
+ const jsonSchema = v4.z.toJSONSchema(zodSchema);
795
+ // Temporary hack because of OpenAI requires additional_properties to be false on all objects
796
+ const checks = [jsonSchema];
797
+ while (checks.length > 0) {
798
+ const current = checks[0];
799
+ if (current.type == "object") {
800
+ current.additionalProperties = false;
801
+ }
802
+ Object.keys(current).forEach((key) => {
803
+ if (typeof current[key] == "object" && current[key] !== null) {
804
+ checks.push(current[key]);
805
+ }
806
+ });
807
+ checks.shift();
808
+ }
809
+ // Set up structured output format in the format expected by the test
810
+ requestOptions.text = {
811
+ format: {
812
+ name: responseFormat.json_schema.name,
813
+ schema: jsonSchema, // Replace this with responseFormat.json_schema.schema once OpenAI supports Zod v4
814
+ strict: responseFormat.json_schema.strict,
815
+ type: responseFormat.type,
816
+ },
817
+ };
818
+ }
819
+ }
820
+ // Handle tools - either as LlmTool[] or Toolkit
821
+ if (options.tools) {
822
+ let toolkit;
823
+ if (options.tools instanceof Toolkit) {
824
+ // If toolkit is already provided, use it directly
825
+ toolkit = options.tools;
826
+ }
827
+ else if (Array.isArray(options.tools) && options.tools.length > 0) {
828
+ // If array of tools provided, create toolkit from them
829
+ const explain = options?.explain ?? false;
830
+ toolkit = new Toolkit(options.tools, { explain });
831
+ }
832
+ if (toolkit) {
833
+ requestOptions.tools = toolkit.tools;
834
+ }
835
+ }
836
+ return requestOptions;
837
+ }
838
+ //
839
+ //
840
+ // Main
841
+ //
842
+ async function operate$1(input, options = {}, context = {
843
+ client: new openai.OpenAI(),
844
+ }) {
845
+ //
846
+ //
847
+ // Setup
848
+ //
849
+ const openai = context.client;
850
+ if (!context.maxRetries) {
851
+ context.maxRetries = MAX_RETRIES_DEFAULT_LIMIT;
852
+ }
853
+ let retryCount = 0;
854
+ let retryDelay = INITIAL_RETRY_DELAY_MS;
855
+ const maxRetries = Math.min(context.maxRetries, MAX_RETRIES_ABSOLUTE_LIMIT);
856
+ const returnResponse = {
857
+ history: [],
858
+ model: options?.model || PROVIDER.OPENAI.MODEL.DEFAULT,
859
+ output: [],
860
+ provider: PROVIDER.OPENAI.NAME,
861
+ responses: [],
862
+ status: LlmResponseStatus.InProgress,
863
+ usage: [], // Initialize as empty array, will add entry for each response
864
+ };
865
+ // Convert string input to array format with placeholders if needed
866
+ let currentInput = formatOperateInput(input);
867
+ if (options?.data &&
868
+ (options.placeholders?.input === undefined || options.placeholders?.input)) {
869
+ currentInput = formatOperateInput(input, {
870
+ data: options?.data,
871
+ });
872
+ }
873
+ // If history is provided, merge it with currentInput
874
+ if (options.history) {
875
+ currentInput = [...options.history, ...currentInput];
876
+ }
877
+ // If system message is provided, add it to the beginning of the input
878
+ if (options?.system) {
879
+ const systemMessage = options.data && options.placeholders?.system !== false
880
+ ? core.placeholders(options.system, options.data)
881
+ : options.system;
882
+ // Create system message
883
+ const systemInputMessage = {
884
+ content: systemMessage,
885
+ role: exports.LlmMessageRole.System,
886
+ type: exports.LlmMessageType.Message,
887
+ };
888
+ // Check if history starts with an identical system message
889
+ const firstMessage = currentInput[0];
890
+ const isIdenticalSystemMessage = firstMessage?.type === exports.LlmMessageType.Message &&
891
+ firstMessage?.role === exports.LlmMessageRole.System &&
892
+ firstMessage?.content === systemMessage;
893
+ // Only prepend if not identical
894
+ if (!isIdenticalSystemMessage) {
895
+ // Remove any existing system message from the beginning
896
+ if (currentInput[0]?.type === exports.LlmMessageType.Message &&
897
+ currentInput[0]?.role === exports.LlmMessageRole.System) {
898
+ currentInput = currentInput.slice(1);
899
+ }
900
+ currentInput = [systemInputMessage, ...currentInput];
901
+ }
902
+ }
903
+ // Initialize history with currentInput
904
+ returnResponse.history = [...currentInput];
905
+ // Determine max turns from options
906
+ const maxTurns = maxTurnsFromOptions(options);
907
+ const enableMultipleTurns = maxTurns > 1;
908
+ let currentTurn = 0;
909
+ // Build request options outside the retry loop
910
+ const requestOptions = createRequestOptions(currentInput, options);
911
+ // OpenAI Multi-turn Loop
912
+ while (currentTurn < maxTurns) {
913
+ currentTurn++;
914
+ retryCount = 0;
915
+ retryDelay = INITIAL_RETRY_DELAY_MS;
916
+ // OpenAI Retry Loop
917
+ while (true) {
918
+ try {
919
+ // Log appropriate message based on turn number
920
+ if (currentTurn > 1) {
921
+ log.trace(`[operate] Calling OpenAI Responses API - ${currentTurn}`);
922
+ }
923
+ else {
924
+ log.trace("[operate] Calling OpenAI Responses API");
925
+ }
926
+ // Execute beforeEachModelRequest hook if defined
927
+ if (options.hooks?.beforeEachModelRequest) {
928
+ await core.resolveValue(options.hooks.beforeEachModelRequest({
929
+ input,
930
+ options,
931
+ providerRequest: requestOptions,
932
+ }));
933
+ }
934
+ // Use type assertion to handle the OpenAI SDK response type
935
+ log.trace.var({ "openai.responses.create": requestOptions });
936
+ const currentResponse = (await openai.responses.create(
937
+ // @ts-expect-error error claims missing non-required id, status
938
+ requestOptions));
939
+ if (retryCount > 0) {
940
+ log.debug(`OpenAI API call succeeded after ${retryCount} retries`);
941
+ }
942
+ // Add the response to the responses array
943
+ returnResponse.responses.push(currentResponse);
944
+ // Add a new usage entry for each response instead of accumulating
945
+ if (currentResponse.usage) {
946
+ // Create new usage item for this response
947
+ returnResponse.usage.push({
948
+ input: currentResponse.usage.input_tokens || 0,
949
+ output: currentResponse.usage.output_tokens || 0,
950
+ total: currentResponse.usage.total_tokens || 0,
951
+ reasoning: currentResponse.usage.output_tokens_details?.reasoning_tokens ||
952
+ 0,
953
+ provider: PROVIDER.OPENAI.NAME,
954
+ model: options?.model || PROVIDER.OPENAI.MODEL.DEFAULT,
955
+ });
956
+ }
957
+ // Execute afterEachModelResponse hook immediately after usage processing
958
+ if (options.hooks?.afterEachModelResponse) {
959
+ const extractedContent = extractContentFromResponse(currentResponse, options);
960
+ await core.resolveValue(options.hooks.afterEachModelResponse({
961
+ input,
962
+ options,
963
+ providerRequest: requestOptions,
964
+ providerResponse: currentResponse,
965
+ content: extractedContent || "",
966
+ usage: returnResponse.usage,
967
+ }));
968
+ }
969
+ // Check if we need to process function calls for multi-turn conversations
970
+ let hasFunctionCall = false;
971
+ const pendingReasoningItems = []; // Track reasoning items that need to be paired
972
+ try {
973
+ if (currentResponse.output && Array.isArray(currentResponse.output)) {
974
+ // New OpenAI API format with output array
975
+ for (const output of currentResponse.output) {
976
+ returnResponse.output.push(output);
977
+ returnResponse.history.push(output);
978
+ // Handle reasoning items (GPT-5)
979
+ if (output.type === "reasoning") {
980
+ // Store reasoning items to be added with their paired function calls
981
+ pendingReasoningItems.push(output);
982
+ continue;
983
+ }
984
+ if (output.type === exports.LlmMessageType.FunctionCall) {
985
+ hasFunctionCall = true;
986
+ let toolkit;
987
+ // Initialize toolkit for multi-turn function calling
988
+ if (options.tools) {
989
+ if (options.tools instanceof Toolkit) {
990
+ // If toolkit is already provided, use it directly
991
+ toolkit = options.tools;
992
+ }
993
+ else if (Array.isArray(options.tools) &&
994
+ options.tools.length > 0) {
995
+ // If array of tools provided, create toolkit from them
996
+ const explain = options?.explain ?? false;
997
+ toolkit = new Toolkit(options.tools, { explain });
998
+ }
999
+ }
1000
+ if (toolkit && enableMultipleTurns) {
1001
+ try {
1002
+ // Call the tool and ensure the result is resolved if it's a Promise
1003
+ log.trace(`[operate] Calling tool - ${output.name}`);
1004
+ // Content will be set by extractContentFromResponse call later
1005
+ // Execute beforeEachTool hook if defined
1006
+ if (options.hooks?.beforeEachTool) {
1007
+ await core.resolveValue(options.hooks.beforeEachTool({
1008
+ toolName: output.name,
1009
+ args: output.arguments,
1010
+ }));
1011
+ }
1012
+ let result;
1013
+ try {
1014
+ result = await toolkit.call({
1015
+ name: output.name,
1016
+ arguments: output.arguments,
1017
+ });
1018
+ // Execute afterEachTool hook if defined
1019
+ if (options.hooks?.afterEachTool) {
1020
+ await core.resolveValue(options.hooks.afterEachTool({
1021
+ result,
1022
+ toolName: output.name,
1023
+ args: output.arguments,
1024
+ }));
1025
+ }
1026
+ }
1027
+ catch (error) {
1028
+ // Execute onToolError hook if defined
1029
+ if (options.hooks?.onToolError) {
1030
+ await core.resolveValue(options.hooks.onToolError({
1031
+ error: error,
1032
+ toolName: output.name,
1033
+ args: output.arguments,
1034
+ }));
1035
+ }
1036
+ throw error;
1037
+ }
1038
+ // Add model's function call and result
1039
+ if (Array.isArray(currentInput)) {
1040
+ // Add any pending reasoning items before the function call
1041
+ for (const reasoningItem of pendingReasoningItems) {
1042
+ currentInput.push(reasoningItem);
1043
+ }
1044
+ // Clear the pending reasoning items after adding them
1045
+ pendingReasoningItems.length = 0;
1046
+ // Add the function call
1047
+ currentInput.push(output);
1048
+ // Add function call result
1049
+ const functionCallOutput = {
1050
+ call_id: output.call_id,
1051
+ output: JSON.stringify(result),
1052
+ type: exports.LlmMessageType.FunctionCallOutput,
1053
+ };
1054
+ currentInput.push(functionCallOutput);
1055
+ returnResponse.output.push(functionCallOutput);
1056
+ returnResponse.history.push(functionCallOutput);
1057
+ // Content will be set by extractContentFromResponse call later
1058
+ }
1059
+ }
1060
+ catch (error) {
1061
+ // TODO: but I do need to tell the model that something went wrong, right?
1062
+ const jaypieError = new errors.BadGatewayError();
1063
+ const detail = [
1064
+ `Error executing function call ${output.name}.`,
1065
+ error.message,
1066
+ ].join("\n");
1067
+ returnResponse.error = {
1068
+ detail,
1069
+ status: jaypieError.status,
1070
+ title: ERROR.BAD_FUNCTION_CALL,
1071
+ };
1072
+ log.error(`Error executing function call ${output.name}`);
1073
+ log.var({ error });
1074
+ // We don't add error messages to allResponses here as we want to keep the original response objects
1075
+ }
1076
+ }
1077
+ else if (!toolkit) {
1078
+ log.warn("Model requested function call but no toolkit available");
1079
+ }
1080
+ }
1081
+ // Content processing is now handled by extractContentFromResponse function
1082
+ }
1083
+ }
1084
+ }
1085
+ catch (error) {
1086
+ // If there's an error processing the response, log it but don't fail
1087
+ // This helps with test mocks that might not have the expected structure
1088
+ log.warn("Error processing response for function calls");
1089
+ log.var({ error });
1090
+ }
1091
+ // Set content using the shared extraction function
1092
+ returnResponse.content = extractContentFromResponse(currentResponse, options);
1093
+ // If there's no function call or we can't take another turn, exit the loop
1094
+ if (!hasFunctionCall || !enableMultipleTurns) {
1095
+ returnResponse.status = LlmResponseStatus.Completed;
1096
+ return returnResponse;
1097
+ }
1098
+ // If we've reached the maximum number of turns, exit the loop
1099
+ if (currentTurn >= maxTurns) {
1100
+ const error = new errors.TooManyRequestsError();
1101
+ const detail = `Model requested function call but exceeded ${maxTurns} turns`;
1102
+ log.warn(detail);
1103
+ returnResponse.status = LlmResponseStatus.Incomplete;
1104
+ returnResponse.error = {
1105
+ detail,
1106
+ status: error.status,
1107
+ title: error.title,
1108
+ };
1109
+ return returnResponse;
1110
+ }
1111
+ // Continue to next turn
1112
+ break;
1113
+ }
1114
+ catch (error) {
1115
+ // Check if we've reached the maximum number of retries
1116
+ if (retryCount >= maxRetries) {
1117
+ log.error(`OpenAI API call failed after ${maxRetries} retries`);
1118
+ log.var({ error });
1119
+ // Execute onUnrecoverableModelError hook if defined
1120
+ if (options.hooks?.onUnrecoverableModelError) {
1121
+ await core.resolveValue(options.hooks.onUnrecoverableModelError({
1122
+ input,
1123
+ options,
1124
+ providerRequest: requestOptions,
1125
+ error,
1126
+ }));
1127
+ }
1128
+ throw new errors.BadGatewayError();
1129
+ }
1130
+ // Check if the error is not retryable
1131
+ let isNotRetryable = false;
1132
+ for (const notRetryableError of NOT_RETRYABLE_ERRORS) {
1133
+ if (error instanceof notRetryableError) {
1134
+ isNotRetryable = true;
1135
+ break;
1136
+ }
1137
+ }
1138
+ if (isNotRetryable) {
1139
+ log.error("OpenAI API call failed with non-retryable error");
1140
+ log.var({ error });
1141
+ // Execute onUnrecoverableModelError hook if defined
1142
+ if (options.hooks?.onUnrecoverableModelError) {
1143
+ await core.resolveValue(options.hooks.onUnrecoverableModelError({
1144
+ input,
1145
+ options,
1146
+ providerRequest: requestOptions,
1147
+ error,
1148
+ }));
1149
+ }
1150
+ throw new errors.BadGatewayError();
1151
+ }
1152
+ // Warn if this error is not in our known retryable errors
1153
+ let isUnknownError = true;
1154
+ for (const retryableError of RETRYABLE_ERRORS) {
1155
+ if (error instanceof retryableError) {
1156
+ isUnknownError = false;
1157
+ break;
1158
+ }
1159
+ }
1160
+ if (isUnknownError) {
1161
+ log.warn("OpenAI API returned unknown error");
1162
+ log.var({ error });
1163
+ }
1164
+ // Log the error and retry
1165
+ log.warn(`OpenAI API call failed. Retrying in ${retryDelay}ms...`);
1166
+ // Execute onRetryableModelError hook if defined
1167
+ if (options.hooks?.onRetryableModelError) {
1168
+ await core.resolveValue(options.hooks.onRetryableModelError({
1169
+ input,
1170
+ options,
1171
+ providerRequest: requestOptions,
1172
+ error,
1173
+ }));
1174
+ }
1175
+ // Wait before retrying
1176
+ await core.sleep(retryDelay);
1177
+ // Increase retry count and delay for next attempt (exponential backoff)
1178
+ retryCount++;
1179
+ retryDelay = Math.min(retryDelay * RETRY_BACKOFF_FACTOR, MAX_RETRY_DELAY_MS);
1180
+ }
1181
+ }
1182
+ }
1183
+ // * All possible paths should return a response; getting here is an error
1184
+ // The main loop is `currentTurn < maxTurns` and `currentTurn >= maxTurns` within the loop returns
1185
+ log.warn("This should never happen");
1186
+ returnResponse.status = LlmResponseStatus.Incomplete;
1187
+ // Always return the full LlmOperateResponse object for consistency
1188
+ return returnResponse;
1189
+ }
1190
+
1191
+ // Logger
1192
+ const getLogger$1 = () => core.log.lib({ lib: core.JAYPIE.LIB.LLM });
1193
+ // Client initialization
1194
+ async function initializeClient$1({ apiKey, } = {}) {
1195
+ const logger = getLogger$1();
1196
+ const resolvedApiKey = apiKey || (await aws.getEnvSecret("OPENAI_API_KEY"));
1197
+ if (!resolvedApiKey) {
1198
+ throw new core.ConfigurationError("The application could not resolve the requested keys");
1199
+ }
1200
+ const client = new openai.OpenAI({ apiKey: resolvedApiKey });
1201
+ logger.trace("Initialized OpenAI client");
1202
+ return client;
1203
+ }
1204
+ function formatSystemMessage$1(systemPrompt, { data, placeholders } = {}) {
1205
+ const content = placeholders?.system === false
1206
+ ? systemPrompt
1207
+ : core.placeholders(systemPrompt, data);
1208
+ return {
1209
+ role: "developer",
1210
+ content,
1211
+ };
1212
+ }
1213
+ function formatUserMessage$1(message, { data, placeholders } = {}) {
1214
+ const content = placeholders?.message === false
1215
+ ? message
1216
+ : core.placeholders(message, data);
1217
+ return {
1218
+ role: "user",
1219
+ content,
1220
+ };
1221
+ }
1222
+ function prepareMessages$1(message, { system, data, placeholders } = {}) {
1223
+ const logger = getLogger$1();
1224
+ const messages = [];
1225
+ if (system) {
1226
+ const systemMessage = formatSystemMessage$1(system, { data, placeholders });
1227
+ messages.push(systemMessage);
1228
+ logger.trace(`System message: ${systemMessage.content?.length} characters`);
1229
+ }
1230
+ const userMessage = formatUserMessage$1(message, { data, placeholders });
1231
+ messages.push(userMessage);
1232
+ logger.trace(`User message: ${userMessage.content?.length} characters`);
1233
+ return messages;
1234
+ }
1235
+ // Completion requests
1236
+ async function createStructuredCompletion$1(client, { messages, responseSchema, model, }) {
1237
+ const logger = getLogger$1();
1238
+ logger.trace("Using structured output");
1239
+ const zodSchema = responseSchema instanceof v4.z.ZodType
1240
+ ? responseSchema
1241
+ : naturalZodSchema(responseSchema);
1242
+ const responseFormat = zod.zodResponseFormat(zodSchema, "response");
1243
+ const jsonSchema = v4.z.toJSONSchema(zodSchema);
1244
+ // Temporary hack because OpenAI requires additional_properties to be false on all objects
1245
+ const checks = [jsonSchema];
1246
+ while (checks.length > 0) {
1247
+ const current = checks[0];
1248
+ if (current.type == "object") {
1249
+ current.additionalProperties = false;
1250
+ }
1251
+ Object.keys(current).forEach((key) => {
1252
+ if (typeof current[key] == "object" && current[key] !== null) {
1253
+ checks.push(current[key]);
1254
+ }
1255
+ });
1256
+ checks.shift();
1257
+ }
1258
+ responseFormat.json_schema.schema = jsonSchema;
1259
+ const completion = await client.beta.chat.completions.parse({
1260
+ messages,
1261
+ model,
1262
+ response_format: responseFormat,
1263
+ });
1264
+ logger.var({ assistantReply: completion.choices[0].message.parsed });
1265
+ return completion.choices[0].message.parsed;
1266
+ }
1267
+ async function createTextCompletion$1(client, { messages, model, }) {
1268
+ const logger = getLogger$1();
1269
+ logger.trace("Using text output (unstructured)");
1270
+ const completion = await client.chat.completions.create({
1271
+ messages,
1272
+ model,
1273
+ });
1274
+ logger.trace(`Assistant reply: ${completion.choices[0]?.message?.content?.length} characters`);
1275
+ return completion.choices[0]?.message?.content || "";
1276
+ }
1277
+
1278
+ class OpenAiProvider {
1279
+ constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
1280
+ this.log = getLogger$1();
1281
+ this.conversationHistory = [];
1282
+ this.model = model;
1283
+ this.apiKey = apiKey;
1284
+ }
1285
+ async getClient() {
1286
+ if (this._client) {
1287
+ return this._client;
1288
+ }
1289
+ this._client = await initializeClient$1({ apiKey: this.apiKey });
1290
+ return this._client;
1291
+ }
1292
+ async send(message, options) {
1293
+ const client = await this.getClient();
1294
+ const messages = prepareMessages$1(message, options || {});
1295
+ const modelToUse = options?.model || this.model;
1296
+ if (options?.response) {
1297
+ return createStructuredCompletion$1(client, {
1298
+ messages,
1299
+ responseSchema: options.response,
1300
+ model: modelToUse,
1301
+ });
1302
+ }
1303
+ return createTextCompletion$1(client, {
1304
+ messages,
1305
+ model: modelToUse,
1306
+ });
1307
+ }
1308
+ async operate(input, options = {}) {
1309
+ const client = await this.getClient();
1310
+ options.model = options?.model || this.model;
1311
+ // Create a merged history including both the tracked history and any explicitly provided history
1312
+ const mergedOptions = { ...options };
1313
+ if (this.conversationHistory.length > 0) {
1314
+ // If options.history exists, merge with instance history, otherwise use instance history
1315
+ mergedOptions.history = options.history
1316
+ ? [...this.conversationHistory, ...options.history]
1317
+ : [...this.conversationHistory];
1318
+ }
1319
+ // Call operate with the updated options
1320
+ const response = await operate$1(input, mergedOptions, { client });
1321
+ // Update conversation history with the new history from the response
1322
+ if (response.history && response.history.length > 0) {
1323
+ this.conversationHistory = response.history;
1324
+ }
1325
+ return response;
1326
+ }
1327
+ }
1328
+
1329
+ // Handle placeholder logic
1330
+ // Convert string input to array format if needed
1331
+ // Apply placeholders to fields if data is provided and placeholders.* is undefined or true
1332
+ function handleInputAndPlaceholders(input, options) {
1333
+ let history = formatOperateInput(input);
1334
+ let llmInstructions;
1335
+ let systemPrompt;
1336
+ if (options?.data &&
1337
+ (options.placeholders?.input === undefined || options.placeholders?.input)) {
1338
+ history = formatOperateInput(input, {
1339
+ data: options?.data,
1340
+ });
1341
+ }
1342
+ if (options?.instructions) {
1343
+ llmInstructions =
1344
+ options.data && options.placeholders?.instructions !== false
1345
+ ? core.placeholders(options.instructions, options.data)
1346
+ : options.instructions;
1347
+ }
1348
+ if (options?.system) {
1349
+ systemPrompt =
1350
+ options.data && options.placeholders?.system !== false
1351
+ ? core.placeholders(options.system, options.data)
1352
+ : options.system;
1353
+ }
1354
+ return { history, systemPrompt, llmInstructions };
1355
+ }
1356
+ function updateUsage(usage, totalUsage) {
1357
+ totalUsage.input += usage.input_tokens;
1358
+ totalUsage.output += usage.output_tokens;
1359
+ totalUsage.total += usage.input_tokens + usage.output_tokens;
1360
+ }
1361
+ function handleMaxTurns(maxTurns, history, inputMessages, response, totalUsage) {
1362
+ const error = new errors.TooManyRequestsError();
1363
+ const detail = `Model requested function call but exceeded ${maxTurns} turns`;
1364
+ log.warn(detail);
1365
+ return {
1366
+ //model: model,
1367
+ //provider: PROVIDER.ANTHROPIC,
1368
+ error: {
1369
+ detail,
1370
+ status: error.status,
1371
+ title: error.title,
1372
+ },
1373
+ history,
1374
+ output: inputMessages.slice(-1),
1375
+ responses: response.content,
1376
+ status: LlmResponseStatus.Incomplete,
1377
+ usage: [totalUsage],
1378
+ };
1379
+ }
1380
+ function handleOutputSchema(format) {
1381
+ let schema;
1382
+ if (format) {
1383
+ // Check if format is a JsonObject with type "json_schema"
1384
+ if (typeof format === "object" &&
1385
+ format !== null &&
1386
+ !Array.isArray(format) &&
1387
+ format.type === "json_schema") {
1388
+ // Direct pass-through for JsonObject with type "json_schema"
1389
+ schema = structuredClone(format);
1390
+ schema.type = "object"; // Validator does not recognise "json_schema" as a type
1391
+ }
1392
+ else {
1393
+ // Convert NaturalSchema to JSON schema through Zod
1394
+ const zodSchema = format instanceof v4.z.ZodType
1395
+ ? format
1396
+ : naturalZodSchema(format);
1397
+ schema = v4.z.toJSONSchema(zodSchema);
1398
+ }
1399
+ if (schema.$schema) {
1400
+ delete schema.$schema; // Hack to fix issue with validator
1401
+ }
1402
+ return schema;
1403
+ }
1404
+ }
1405
+ // Register tools and process them to work with Anthropic
1406
+ function bundleTools(tools, explain, schema) {
1407
+ let toolkit;
1408
+ let processedTools = [];
1409
+ if (tools instanceof Toolkit) {
1410
+ toolkit = tools;
1411
+ }
1412
+ else if (Array.isArray(tools)) {
1413
+ toolkit = new Toolkit(tools, { explain });
1414
+ }
1415
+ if (toolkit) {
1416
+ toolkit.tools.forEach((tool) => {
1417
+ processedTools.push({
1418
+ ...tool,
1419
+ input_schema: {
1420
+ ...tool.parameters,
1421
+ type: "object",
1422
+ },
1423
+ type: "custom",
1424
+ });
1425
+ delete processedTools[processedTools.length - 1].parameters;
1426
+ });
1427
+ }
1428
+ if (schema) {
1429
+ processedTools.push({
1430
+ name: "structured_output",
1431
+ description: "Output a structured JSON object, " +
1432
+ "use this before your final response to give structured outputs to the user",
1433
+ input_schema: schema,
1434
+ type: "custom",
1435
+ });
1436
+ }
1437
+ return { processedTools, toolkit };
1438
+ }
1439
+ // Handles individual tool calls. Returns true for break, false for continue.
1440
+ async function callTool(inputMessages, response, hooks, toolkit) {
1441
+ inputMessages.push({
1442
+ role: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
1443
+ content: response.content,
1444
+ });
1445
+ // Get the tool use
1446
+ const toolUse = response.content[response.content.length - 1];
1447
+ // If the tool use is structured output (magic tool), break
1448
+ if (toolUse.name === "structured_output") {
1449
+ return true;
1450
+ }
1451
+ if (hooks?.beforeEachTool) {
1452
+ await hooks.beforeEachTool({
1453
+ toolName: toolUse.name,
1454
+ args: JSON.stringify(toolUse.input),
1455
+ });
1456
+ }
1457
+ let result;
1458
+ try {
1459
+ result = await toolkit?.call({
1460
+ name: toolUse.name,
1461
+ arguments: JSON.stringify(toolUse.input),
1462
+ });
1463
+ }
1464
+ catch (error) {
1465
+ if (hooks?.onToolError) {
1466
+ await hooks.onToolError({
1467
+ error: error,
1468
+ toolName: toolUse.name,
1469
+ args: JSON.stringify(toolUse.input),
1470
+ });
1471
+ }
1472
+ throw error;
1473
+ }
1474
+ if (hooks?.afterEachTool) {
1475
+ await hooks.afterEachTool({
1476
+ result,
1477
+ toolName: toolUse.name,
1478
+ args: JSON.stringify(toolUse.input),
1479
+ });
1480
+ }
1481
+ inputMessages.push({
1482
+ role: PROVIDER.ANTHROPIC.ROLE.USER,
1483
+ content: [
1484
+ {
1485
+ type: "tool_result",
1486
+ content: JSON.stringify(result),
1487
+ tool_use_id: toolUse.id,
1488
+ },
1489
+ ],
1490
+ });
1491
+ return false;
1492
+ }
1493
+ //
1494
+ //
1495
+ // Main
1496
+ //
1497
+ async function operate(input, options = {}, context = {
1498
+ client: new Anthropic.Anthropic(),
1499
+ }) {
1500
+ // Set model
1501
+ const model = options?.model || PROVIDER.ANTHROPIC.MODEL.DEFAULT;
1502
+ let schema = handleOutputSchema(options.format);
1503
+ let { processedTools, toolkit } = bundleTools(options.tools, options.explain, schema);
1504
+ let { history, systemPrompt, llmInstructions } = handleInputAndPlaceholders(input, options);
1505
+ // If history is provided, merge it with the input
1506
+ if (options.history) {
1507
+ history = [...options.history, ...history];
1508
+ }
1509
+ // Avoid Anthropic error by removing type property
1510
+ const inputMessages = structuredClone(history);
1511
+ inputMessages.forEach((message) => {
1512
+ delete message.type;
1513
+ });
1514
+ // Add instruction to the input message
1515
+ if (llmInstructions) {
1516
+ inputMessages[inputMessages.length - 1].content += "\n\n" + llmInstructions;
1517
+ }
1518
+ // Setup usage tracking
1519
+ let totalUsage = {
1520
+ input: 0,
1521
+ output: 0,
1522
+ reasoning: 0,
1523
+ total: 0,
1524
+ };
1525
+ // Determine max turns from options
1526
+ const maxTurns = maxTurnsFromOptions(options);
1527
+ const enableMultipleTurns = maxTurns > 1;
1528
+ let currentTurn = 0;
1529
+ let response;
1530
+ while (true) {
1531
+ // Loop for tool use
1532
+ response = await context.client.messages.create({
1533
+ model: model,
1534
+ system: systemPrompt,
1535
+ messages: inputMessages,
1536
+ max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
1537
+ stream: false,
1538
+ tools: processedTools,
1539
+ tool_choice: processedTools.length > 0
1540
+ ? { type: schema ? "any" : "auto" }
1541
+ : undefined,
1542
+ ...options?.providerOptions,
1543
+ });
1544
+ // Update usage
1545
+ updateUsage(response.usage, totalUsage);
1546
+ // If the response is not a tool use, break
1547
+ if (response.stop_reason !== "tool_use") {
1548
+ break;
1549
+ }
1550
+ const breakLoop = await callTool(inputMessages, response, options.hooks, toolkit);
1551
+ if (breakLoop) {
1552
+ break;
1553
+ }
1554
+ // Handle turn limit
1555
+ if (!enableMultipleTurns || currentTurn >= maxTurns) {
1556
+ return handleMaxTurns(maxTurns, history, inputMessages, response, totalUsage);
1557
+ }
1558
+ currentTurn++;
1559
+ }
1560
+ let jsonResult;
1561
+ if (schema) {
1562
+ const validator = new ZSchema({});
1563
+ jsonResult = response.content[response.content.length - 1].input;
1564
+ if (!validator.validate(jsonResult, schema)) {
1565
+ throw new Error("Model returned invalid JSON");
1566
+ }
1567
+ }
1568
+ history.push({
1569
+ content: schema
1570
+ ? JSON.stringify(jsonResult)
1571
+ : response.content[0].text,
1572
+ role: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
1573
+ type: exports.LlmMessageType.Message,
1574
+ });
1575
+ return {
1576
+ //model: model,
1577
+ //provider: PROVIDER.ANTHROPIC,
1578
+ content: schema
1579
+ ? jsonResult
1580
+ : response.content[0].text,
1581
+ responses: [response],
1582
+ output: history.slice(-1),
1583
+ history,
1584
+ status: LlmResponseStatus.Completed,
1585
+ usage: [totalUsage],
1586
+ };
1587
+ }
1588
+
1589
+ // Logger
1590
+ const getLogger = () => core.log.lib({ lib: core.JAYPIE.LIB.LLM });
1591
+ // Client initialization
1592
+ async function initializeClient({ apiKey, } = {}) {
1593
+ const logger = getLogger();
1594
+ const resolvedApiKey = apiKey || (await aws.getEnvSecret("ANTHROPIC_API_KEY"));
1595
+ if (!resolvedApiKey) {
1596
+ throw new core.ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
1597
+ }
1598
+ const client = new Anthropic({ apiKey: resolvedApiKey });
1599
+ logger.trace("Initialized Anthropic client");
1600
+ return client;
1601
+ }
1602
+ // Message formatting functions
1603
+ function formatSystemMessage(systemPrompt, { data, placeholders } = {}) {
1604
+ return placeholders?.system === false
1605
+ ? systemPrompt
1606
+ : core.placeholders(systemPrompt, data);
1607
+ }
1608
+ function formatUserMessage(message, { data, placeholders } = {}) {
1609
+ const content = placeholders?.message === false
1610
+ ? message
1611
+ : core.placeholders(message, data);
1612
+ return {
1613
+ role: PROVIDER.ANTHROPIC.ROLE.USER,
1614
+ content,
1615
+ };
1616
+ }
1617
+ function prepareMessages(message, { data, placeholders } = {}) {
1618
+ const logger = getLogger();
1619
+ const messages = [];
1620
+ // Add user message (necessary for all requests)
1621
+ const userMessage = formatUserMessage(message, { data, placeholders });
1622
+ messages.push(userMessage);
1623
+ logger.trace(`User message: ${userMessage.content.length} characters`);
1624
+ return messages;
1625
+ }
1626
+ // Basic text completion
1627
+ async function createTextCompletion(client, messages, model, systemMessage) {
1628
+ core.log.trace("Using text output (unstructured)");
1629
+ const params = {
1630
+ model,
1631
+ messages,
1632
+ max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
1633
+ };
1634
+ // Add system instruction if provided
1635
+ if (systemMessage) {
1636
+ params.system = systemMessage;
1637
+ core.log.trace(`System message: ${systemMessage.length} characters`);
1638
+ }
1639
+ const response = await client.messages.create(params);
1640
+ const firstContent = response.content[0];
1641
+ const text = firstContent && "text" in firstContent ? firstContent.text : "";
1642
+ core.log.trace(`Assistant reply: ${text.length} characters`);
1643
+ return text;
1644
+ }
1645
+ // Structured output completion
1646
+ async function createStructuredCompletion(client, messages, model, responseSchema, systemMessage) {
1647
+ core.log.trace("Using structured output");
1648
+ // Get the JSON schema for the response
1649
+ const schema = responseSchema instanceof v4.z.ZodType
1650
+ ? responseSchema
1651
+ : naturalZodSchema(responseSchema);
1652
+ // Set system message with JSON instructions
1653
+ const defaultSystemPrompt = "You will be responding with structured JSON data. " +
1654
+ "Format your entire response as a valid JSON object with the following structure: " +
1655
+ JSON.stringify(v4.z.toJSONSchema(schema));
1656
+ const systemPrompt = systemMessage || defaultSystemPrompt;
1657
+ try {
1658
+ // Use standard Anthropic API to get response
1659
+ const params = {
1660
+ model,
1661
+ messages,
1662
+ max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
1663
+ system: systemPrompt,
1664
+ };
1665
+ const response = await client.messages.create(params);
1666
+ // Extract text from response
1667
+ const firstContent = response.content[0];
1668
+ const responseText = firstContent && "text" in firstContent ? firstContent.text : "";
1669
+ // Find JSON in response
1670
+ const jsonMatch = responseText.match(/```json\s*([\s\S]*?)\s*```/) ||
1671
+ responseText.match(/\{[\s\S]*\}/);
1672
+ if (jsonMatch) {
1673
+ try {
1674
+ // Parse the JSON response
1675
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
1676
+ const result = JSON.parse(jsonStr);
1677
+ if (!schema.parse(result)) {
1678
+ throw new Error(`JSON response from Anthropic does not match schema: ${responseText}`);
1679
+ }
1680
+ core.log.trace("Received structured response", { result });
1681
+ return result;
1682
+ }
1683
+ catch {
1684
+ throw new Error(`Failed to parse JSON response from Anthropic: ${responseText}`);
1685
+ }
1686
+ }
1687
+ // If we can't extract JSON
1688
+ throw new Error("Failed to parse structured response from Anthropic");
1689
+ }
1690
+ catch (error) {
1691
+ core.log.error("Error creating structured completion", { error });
1692
+ throw error;
1693
+ }
1694
+ }
1695
+
1696
+ // Maps Jaypie roles to Anthropic roles
1697
+ ({
1698
+ [exports.LlmMessageRole.User]: PROVIDER.ANTHROPIC.ROLE.USER,
1699
+ [exports.LlmMessageRole.System]: PROVIDER.ANTHROPIC.ROLE.SYSTEM,
1700
+ [exports.LlmMessageRole.Assistant]: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
1701
+ [exports.LlmMessageRole.Developer]: PROVIDER.ANTHROPIC.ROLE.SYSTEM,
1702
+ });
1703
+
1704
+ // Main class implementation
1705
+ class AnthropicProvider {
1706
+ constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
1707
+ this.log = getLogger();
1708
+ this.conversationHistory = [];
1709
+ this.model = model;
1710
+ this.apiKey = apiKey;
1711
+ }
1712
+ async getClient() {
1713
+ if (this._client) {
1714
+ return this._client;
1715
+ }
1716
+ this._client = await initializeClient({ apiKey: this.apiKey });
1717
+ return this._client;
1718
+ }
1719
+ // Main send method
1720
+ async send(message, options) {
1721
+ const client = await this.getClient();
1722
+ const messages = prepareMessages(message, options || {});
1723
+ const modelToUse = options?.model || this.model;
1724
+ // Process system message if provided
1725
+ let systemMessage;
1726
+ if (options?.system) {
1727
+ systemMessage = formatSystemMessage(options.system, {
1728
+ data: options.data,
1729
+ placeholders: options.placeholders,
1730
+ });
1731
+ }
1732
+ if (options?.response) {
1733
+ return createStructuredCompletion(client, messages, modelToUse, options.response, systemMessage);
1734
+ }
1735
+ return createTextCompletion(client, messages, modelToUse, systemMessage);
1736
+ }
1737
+ async operate(input, options = {}) {
1738
+ const client = await this.getClient();
1739
+ options.model = options?.model || this.model;
1740
+ // Create a merged history including both the tracked history and any explicitly provided history
1741
+ const mergedOptions = { ...options };
1742
+ if (this.conversationHistory.length > 0) {
1743
+ // If options.history exists, merge with instance history, otherwise use instance history
1744
+ mergedOptions.history = options.history
1745
+ ? [...this.conversationHistory, ...options.history]
1746
+ : [...this.conversationHistory];
1747
+ }
1748
+ // Call operate with the updated options
1749
+ const response = await operate(input, mergedOptions, { client });
1750
+ // Update conversation history with the new history from the response
1751
+ if (response.history && response.history.length > 0) {
1752
+ this.conversationHistory = response.history;
1753
+ }
1754
+ return response;
1755
+ }
1756
+ }
1757
+
1758
+ class Llm {
1759
+ constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
1760
+ const { model } = options;
1761
+ let finalProvider = providerName;
1762
+ let finalModel = model;
1763
+ if (model) {
1764
+ const modelDetermined = determineModelProvider(model);
1765
+ finalModel = modelDetermined.model;
1766
+ if (modelDetermined.provider) {
1767
+ finalProvider = modelDetermined.provider;
1768
+ }
1769
+ }
1770
+ // Only determine provider from providerName if we don't have a provider from model
1771
+ if (!model || !determineModelProvider(model).provider) {
1772
+ const providerDetermined = determineModelProvider(providerName);
1773
+ if (!providerDetermined.provider) {
1774
+ throw new errors.ConfigurationError(`Unable to determine provider from: ${providerName}`);
1775
+ }
1776
+ finalProvider = providerDetermined.provider;
1777
+ }
1778
+ // Handle conflicts: if both providerName and model specify different providers
1779
+ if (model && providerName !== DEFAULT.PROVIDER.NAME) {
1780
+ const modelDetermined = determineModelProvider(model);
1781
+ const providerDetermined = determineModelProvider(providerName);
1782
+ if (modelDetermined.provider &&
1783
+ providerDetermined.provider &&
1784
+ modelDetermined.provider !== providerDetermined.provider) {
1785
+ // Model's provider conflicts with explicit provider, don't pass model
1786
+ finalModel = undefined;
1787
+ }
1788
+ }
1789
+ this._provider = finalProvider;
1790
+ this._options = { ...options, model: finalModel };
1791
+ this._llm = this.createProvider(finalProvider, this._options);
1792
+ }
1793
+ createProvider(providerName, options = {}) {
1794
+ const { apiKey, model } = options;
1795
+ switch (providerName) {
1796
+ case PROVIDER.OPENAI.NAME:
1797
+ return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
1798
+ apiKey,
1799
+ });
1800
+ case PROVIDER.ANTHROPIC.NAME:
1801
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
1802
+ default:
1803
+ throw new errors.ConfigurationError(`Unsupported provider: ${providerName}`);
1804
+ }
1805
+ }
1806
+ async send(message, options) {
1807
+ return this._llm.send(message, options);
1808
+ }
1809
+ async operate(input, options = {}) {
1810
+ if (!this._llm.operate) {
1811
+ throw new errors.NotImplementedError(`Provider ${this._provider} does not support operate method`);
1812
+ }
1813
+ return this._llm.operate(input, options);
1814
+ }
1815
+ static async send(message, options) {
1816
+ const { llm, apiKey, model, ...messageOptions } = options || {};
1817
+ const instance = new Llm(llm, { apiKey, model });
1818
+ return instance.send(message, messageOptions);
1819
+ }
1820
+ static async operate(input, options) {
1821
+ const { llm, apiKey, model, ...operateOptions } = options || {};
1822
+ let finalLlm = llm;
1823
+ let finalModel = model;
1824
+ if (!llm && model) {
1825
+ const determined = determineModelProvider(model);
1826
+ if (determined.provider) {
1827
+ finalLlm = determined.provider;
1828
+ }
1829
+ }
1830
+ else if (llm && model) {
1831
+ // When both llm and model are provided, check if they conflict
1832
+ const determined = determineModelProvider(model);
1833
+ if (determined.provider && determined.provider !== llm) {
1834
+ // Don't pass the conflicting model to the constructor
1835
+ finalModel = undefined;
1836
+ }
1837
+ }
1838
+ const instance = new Llm(finalLlm, { apiKey, model: finalModel });
1839
+ return instance.operate(input, operateOptions);
1840
+ }
1841
+ }
1842
+
1843
+ const random = {
1844
+ description: "Generate a random number with optional distribution, precision, range, and seeding",
1845
+ name: "random",
1846
+ parameters: {
1847
+ type: "object",
1848
+ properties: {
1849
+ min: {
1850
+ type: "number",
1851
+ description: "Minimum value (inclusive). Default: 0. When used with mean/stddev, acts as a clamp on the normal distribution",
1852
+ },
1853
+ max: {
1854
+ type: "number",
1855
+ description: "Maximum value (inclusive). Default: 1. When used with mean/stddev, acts as a clamp on the normal distribution",
1856
+ },
1857
+ mean: {
1858
+ type: "number",
1859
+ description: "Mean value for normal distribution. When set with stddev, uses normal distribution (clamped by min/max if provided)",
1860
+ },
1861
+ stddev: {
1862
+ type: "number",
1863
+ description: "Standard deviation for normal distribution. When set with mean, uses normal distribution (clamped by min/max if provided)",
1864
+ },
1865
+ integer: {
1866
+ type: "boolean",
1867
+ description: "Whether to return an integer value. Default: false",
1868
+ },
1869
+ seed: {
1870
+ type: "string",
1871
+ description: "Seed string for consistent random generation. Default: undefined (uses default RNG)",
1872
+ },
1873
+ precision: {
1874
+ type: "number",
1875
+ description: "Number of decimal places for the result. Default: undefined (full precision)",
1876
+ },
1877
+ currency: {
1878
+ type: "boolean",
1879
+ description: "Whether to format as currency (2 decimal places). Default: false",
1880
+ },
1881
+ },
1882
+ required: [],
1883
+ },
1884
+ type: "function",
1885
+ call: (options) => {
1886
+ const rng = random$1();
1887
+ return rng(options);
1888
+ },
1889
+ message: ({ min, max, mean, stddev, integer, seed, precision, currency, } = {}) => {
1890
+ let description = "Generating";
1891
+ if (seed) {
1892
+ description += ` seeded`;
1893
+ }
1894
+ else {
1895
+ description += " random";
1896
+ }
1897
+ if (integer) {
1898
+ description += " integer";
1899
+ }
1900
+ else if (currency) {
1901
+ description += " currency";
1902
+ }
1903
+ else if (precision) {
1904
+ description += ` with precision \`${precision}\``;
1905
+ }
1906
+ else {
1907
+ description += " number";
1908
+ }
1909
+ if (min && max) {
1910
+ description += ` between \`${min}\` and \`${max}\``;
1911
+ }
1912
+ if (mean && stddev) {
1913
+ description += ` with normal distribution around \`${mean}\` with standard deviation \`${stddev}\``;
1914
+ }
1915
+ return description;
1916
+ },
1917
+ };
1918
+
1919
+ const roll = {
1920
+ description: "Roll one or more dice with a specified number of sides",
1921
+ name: "roll",
1922
+ parameters: {
1923
+ type: "object",
1924
+ properties: {
1925
+ number: {
1926
+ type: "number",
1927
+ description: "Number of dice to roll. Default: 1",
1928
+ },
1929
+ sides: {
1930
+ type: "number",
1931
+ description: "Number of sides on each die. Default: 6",
1932
+ },
1933
+ },
1934
+ required: ["number", "sides"],
1935
+ },
1936
+ type: "function",
1937
+ call: ({ number = 1, sides = 6 } = {}) => {
1938
+ const rng = random$1();
1939
+ const rolls = [];
1940
+ let total = 0;
1941
+ const parsedNumber = tryParseNumber(number, {
1942
+ defaultValue: 1,
1943
+ warnFunction: log.warn,
1944
+ });
1945
+ const parsedSides = tryParseNumber(sides, {
1946
+ defaultValue: 6,
1947
+ warnFunction: log.warn,
1948
+ });
1949
+ for (let i = 0; i < parsedNumber; i++) {
1950
+ const rollValue = rng({ min: 1, max: parsedSides, integer: true });
1951
+ rolls.push(rollValue);
1952
+ total += rollValue;
1953
+ }
1954
+ return { rolls, total };
1955
+ },
1956
+ message: ({ number = 1, sides = 6 } = {}) => {
1957
+ return `Rolling ${number} ${sides}-sided dice`;
1958
+ },
1959
+ };
1960
+
1961
+ const time = {
1962
+ description: "Returns the provided date as an ISO UTC string or the current time if no date provided.",
1963
+ name: "time",
1964
+ parameters: {
1965
+ type: "object",
1966
+ properties: {
1967
+ date: {
1968
+ type: "string",
1969
+ description: "Date string to convert to ISO UTC format. Default: current time",
1970
+ },
1971
+ },
1972
+ required: [],
1973
+ },
1974
+ type: "function",
1975
+ call: ({ date } = {}) => {
1976
+ if (typeof date === "number" || typeof date === "string") {
1977
+ const parsedDate = new Date(date);
1978
+ if (isNaN(parsedDate.getTime())) {
1979
+ throw new Error(`Invalid date format: ${date}`);
1980
+ }
1981
+ return parsedDate.toISOString();
1982
+ }
1983
+ return new Date().toISOString();
1984
+ },
1985
+ message: ({ date } = {}) => {
1986
+ if (typeof date === "number" || typeof date === "string") {
1987
+ return `Converting date to ISO UTC format`;
1988
+ }
1989
+ return "Checking current time";
1990
+ },
1991
+ };
1992
+
1993
+ const weather = {
1994
+ description: "Get current weather and forecast data for a specific location",
1995
+ name: "weather",
1996
+ parameters: {
1997
+ type: "object",
1998
+ properties: {
1999
+ latitude: {
2000
+ type: "number",
2001
+ description: "Latitude of the location. Default: 42.051554533384866 (Evanston, IL)",
2002
+ },
2003
+ location: {
2004
+ type: "string",
2005
+ description: "Human-readable description of the location, not used for the API call. Default: Evanston, IL",
2006
+ },
2007
+ longitude: {
2008
+ type: "number",
2009
+ description: "Longitude of the location. Default: -87.6759911441785 (Evanston, IL)",
2010
+ },
2011
+ timezone: {
2012
+ type: "string",
2013
+ description: "Timezone for the results. Default: America/Chicago",
2014
+ },
2015
+ past_days: {
2016
+ type: "number",
2017
+ description: "Number of past days to include in the forecast. Default: 1",
2018
+ },
2019
+ forecast_days: {
2020
+ type: "number",
2021
+ description: "Number of forecast days to include. Default: 1",
2022
+ },
2023
+ },
2024
+ required: ["latitude", "location", "longitude"],
2025
+ },
2026
+ type: "function",
2027
+ call: async ({ latitude = 42.051554533384866, longitude = -87.6759911441785, timezone = "America/Chicago", past_days = 1, forecast_days = 1, } = {}) => {
2028
+ try {
2029
+ const params = {
2030
+ latitude,
2031
+ longitude,
2032
+ hourly: [
2033
+ "temperature_2m",
2034
+ "precipitation_probability",
2035
+ "precipitation",
2036
+ ],
2037
+ current: [
2038
+ "temperature_2m",
2039
+ "is_day",
2040
+ "showers",
2041
+ "cloud_cover",
2042
+ "wind_speed_10m",
2043
+ "relative_humidity_2m",
2044
+ "precipitation",
2045
+ "snowfall",
2046
+ "rain",
2047
+ "apparent_temperature",
2048
+ ],
2049
+ timezone,
2050
+ past_days,
2051
+ forecast_days,
2052
+ wind_speed_unit: "mph",
2053
+ temperature_unit: "fahrenheit",
2054
+ precipitation_unit: "inch",
2055
+ };
2056
+ const url = "https://api.open-meteo.com/v1/forecast";
2057
+ const responses = await openmeteo.fetchWeatherApi(url, params);
2058
+ // Helper function to form time ranges
2059
+ const range = (start, stop, step) => Array.from({ length: (stop - start) / step }, (_, i) => start + i * step);
2060
+ // Process first location
2061
+ const response = responses[0];
2062
+ // Attributes for timezone and location
2063
+ const utcOffsetSeconds = response.utcOffsetSeconds();
2064
+ const timezoneAbbreviation = response.timezoneAbbreviation();
2065
+ const current = response.current();
2066
+ const hourly = response.hourly();
2067
+ // Create weather data object
2068
+ const weatherData = {
2069
+ location: {
2070
+ latitude: response.latitude(),
2071
+ longitude: response.longitude(),
2072
+ timezone: response.timezone(),
2073
+ timezoneAbbreviation,
2074
+ utcOffsetSeconds,
2075
+ },
2076
+ current: {
2077
+ time: new Date((Number(current.time()) + utcOffsetSeconds) * 1000).toISOString(),
2078
+ temperature2m: current.variables(0).value(),
2079
+ isDay: current.variables(1).value(),
2080
+ showers: current.variables(2).value(),
2081
+ cloudCover: current.variables(3).value(),
2082
+ windSpeed10m: current.variables(4).value(),
2083
+ relativeHumidity2m: current.variables(5).value(),
2084
+ precipitation: current.variables(6).value(),
2085
+ snowfall: current.variables(7).value(),
2086
+ rain: current.variables(8).value(),
2087
+ apparentTemperature: current.variables(9).value(),
2088
+ },
2089
+ hourly: {
2090
+ time: range(Number(hourly.time()), Number(hourly.timeEnd()), hourly.interval()).map((t) => new Date((t + utcOffsetSeconds) * 1000).toISOString()),
2091
+ temperature2m: Array.from(hourly.variables(0).valuesArray()),
2092
+ precipitationProbability: Array.from(hourly.variables(1).valuesArray()),
2093
+ precipitation: Array.from(hourly.variables(2).valuesArray()),
2094
+ },
2095
+ };
2096
+ return weatherData;
2097
+ }
2098
+ catch (error) {
2099
+ if (error instanceof Error) {
2100
+ throw new Error(`Weather API error: ${error.message}`);
2101
+ }
2102
+ throw new Error("Unknown error occurred while fetching weather data");
2103
+ }
2104
+ },
2105
+ message: ({ latitude = 42.051554533384866, longitude = -87.6759911441785, location = "Evanston, IL", } = {}) => {
2106
+ return `Getting weather for ${location} (${latitude}, ${longitude})`;
2107
+ },
2108
+ };
2109
+
2110
+ const tools = [random, roll, time, weather];
2111
+ class JaypieToolkit extends Toolkit {
2112
+ constructor(tools, options) {
2113
+ super(tools, options);
2114
+ this.random = random;
2115
+ this.roll = roll;
2116
+ this.time = time;
2117
+ this.weather = weather;
2118
+ }
2119
+ }
2120
+ const toolkit = new JaypieToolkit(tools);
2121
+
2122
+ exports.JaypieToolkit = JaypieToolkit;
2123
+ exports.LLM = constants;
2124
+ exports.Llm = Llm;
2125
+ exports.Toolkit = Toolkit;
2126
+ exports.toolkit = toolkit;
2127
+ exports.tools = tools;
2128
+ //# sourceMappingURL=index.cjs.map