llm-fns 1.0.18 → 1.0.20

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.
@@ -14,12 +14,7 @@ export interface CreateFetcherDependencies {
14
14
  prefix?: string;
15
15
  /** Time-to-live for cache entries, in milliseconds. */
16
16
  ttl?: number;
17
- /** Request timeout in milliseconds. If not provided, no timeout is applied.**Restoring Corrected File**
18
-
19
- I'm now generating the corrected version of `src/createCachedFetcher.ts`. The primary fix is removing the extraneous text from the `set` method signature within the `CacheLike` interface. I've ensured the syntax is correct, and I'm confident the test run should now pass. After this is output, I plan to assess its integration within the wider project.
20
-
21
-
22
- */
17
+ /** Request timeout in milliseconds. If not provided, no timeout is applied. */
23
18
  timeout?: number;
24
19
  /** User-Agent string for requests. */
25
20
  userAgent?: string;
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- // src/createCachedFetcher.ts
3
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
4
  };
@@ -7,20 +6,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
6
  exports.CachedResponse = void 0;
8
7
  exports.createCachedFetcher = createCachedFetcher;
9
8
  const crypto_1 = __importDefault(require("crypto"));
10
- // A custom Response class to correctly handle the `.url` property on cache HITs.
11
- // This is an implementation detail and doesn't need to be exported.
12
9
  class CachedResponse extends Response {
13
10
  #finalUrl;
14
11
  constructor(body, init, finalUrl) {
15
12
  super(body, init);
16
13
  this.#finalUrl = finalUrl;
17
14
  }
18
- // Override the read-only `url` property
19
15
  get url() {
20
16
  return this.#finalUrl;
21
17
  }
22
18
  }
23
19
  exports.CachedResponse = CachedResponse;
20
+ /**
21
+ * Creates a deterministic hash of headers for cache key generation.
22
+ * Headers are sorted alphabetically to ensure consistency.
23
+ */
24
+ function hashHeaders(headers) {
25
+ if (!headers)
26
+ return '';
27
+ let headerEntries;
28
+ if (headers instanceof Headers) {
29
+ headerEntries = Array.from(headers.entries());
30
+ }
31
+ else if (Array.isArray(headers)) {
32
+ headerEntries = headers;
33
+ }
34
+ else {
35
+ headerEntries = Object.entries(headers);
36
+ }
37
+ if (headerEntries.length === 0)
38
+ return '';
39
+ // Sort alphabetically by key for deterministic ordering
40
+ headerEntries.sort((a, b) => a[0].localeCompare(b[0]));
41
+ const headerString = headerEntries
42
+ .map(([key, value]) => `${key}:${value}`)
43
+ .join('|');
44
+ return crypto_1.default.createHash('md5').update(headerString).digest('hex');
45
+ }
24
46
  /**
25
47
  * Factory function that creates a `fetch` replacement with a caching layer.
26
48
  * @param deps - Dependencies including the cache instance, prefix, TTL, and timeout.
@@ -30,8 +52,6 @@ function createCachedFetcher(deps) {
30
52
  const { cache, prefix = 'http-cache', ttl, timeout, userAgent, fetch: customFetch, shouldCache } = deps;
31
53
  const fetchImpl = customFetch ?? fetch;
32
54
  const fetchWithTimeout = async (url, options) => {
33
- // Correctly merge headers using Headers API to handle various input formats (plain object, Headers instance, array)
34
- // and avoid issues with spreading Headers objects which can lead to lost headers or Symbol errors.
35
55
  const headers = new Headers(options?.headers);
36
56
  if (userAgent) {
37
57
  headers.set('User-Agent', userAgent);
@@ -70,10 +90,7 @@ function createCachedFetcher(deps) {
70
90
  clearTimeout(timeoutId);
71
91
  }
72
92
  };
73
- // This is the actual fetcher implementation, returned by the factory.
74
- // It "closes over" the dependencies provided to the factory.
75
93
  return async (url, options) => {
76
- // Determine the request method. Default to GET for fetch.
77
94
  let method = 'GET';
78
95
  if (options?.method) {
79
96
  method = options.method;
@@ -87,7 +104,7 @@ function createCachedFetcher(deps) {
87
104
  return fetchWithTimeout(url, options);
88
105
  }
89
106
  let cacheKey = `${prefix}:${urlString}`;
90
- // If POST (or others with body), append hash of body to cache key
107
+ // Hash body for POST requests
91
108
  if (method.toUpperCase() === 'POST' && options?.body) {
92
109
  let bodyStr = '';
93
110
  if (typeof options.body === 'string') {
@@ -97,7 +114,6 @@ function createCachedFetcher(deps) {
97
114
  bodyStr = options.body.toString();
98
115
  }
99
116
  else {
100
- // Fallback for other types, though mostly we expect string/JSON here
101
117
  try {
102
118
  bodyStr = JSON.stringify(options.body);
103
119
  }
@@ -105,13 +121,17 @@ function createCachedFetcher(deps) {
105
121
  bodyStr = 'unserializable';
106
122
  }
107
123
  }
108
- const hash = crypto_1.default.createHash('md5').update(bodyStr).digest('hex');
109
- cacheKey += `:${hash}`;
124
+ const bodyHash = crypto_1.default.createHash('md5').update(bodyStr).digest('hex');
125
+ cacheKey += `:body:${bodyHash}`;
126
+ }
127
+ // Hash all request headers into cache key
128
+ const headersHash = hashHeaders(options?.headers);
129
+ if (headersHash) {
130
+ cacheKey += `:headers:${headersHash}`;
110
131
  }
111
132
  // 1. Check the cache
112
133
  const cachedItem = await cache.get(cacheKey);
113
134
  if (cachedItem) {
114
- // Decode the base64 body back into a Buffer.
115
135
  const body = Buffer.from(cachedItem.bodyBase64, 'base64');
116
136
  return new CachedResponse(body, {
117
137
  status: cachedItem.status,
@@ -135,7 +155,6 @@ function createCachedFetcher(deps) {
135
155
  }
136
156
  }
137
157
  else {
138
- // Default behavior: check for .error in JSON responses
139
158
  const contentType = response.headers.get('content-type');
140
159
  if (contentType && contentType.includes('application/json')) {
141
160
  const checkClone = response.clone();
@@ -154,7 +173,6 @@ function createCachedFetcher(deps) {
154
173
  if (isCacheable) {
155
174
  const responseClone = response.clone();
156
175
  const bodyBuffer = await responseClone.arrayBuffer();
157
- // Convert ArrayBuffer to a base64 string for safe JSON serialization.
158
176
  const bodyBase64 = Buffer.from(bodyBuffer).toString('base64');
159
177
  const headers = Object.fromEntries(response.headers.entries());
160
178
  const itemToCache = {
@@ -1,6 +1,13 @@
1
1
  import OpenAI from 'openai';
2
- import { PromptFunction, LlmPromptOptions } from "./createLlmClient.js";
3
- export type JsonSchemaLlmClientOptions = Omit<LlmPromptOptions, 'messages' | 'response_format'> & {
2
+ import { PromptFunction, LlmCommonOptions } from "./createLlmClient.js";
3
+ export declare class SchemaValidationError extends Error {
4
+ constructor(message: string, options?: ErrorOptions);
5
+ }
6
+ /**
7
+ * Options for JSON schema prompt functions.
8
+ * Extends common options with JSON-specific settings.
9
+ */
10
+ export interface JsonSchemaLlmClientOptions extends LlmCommonOptions {
4
11
  maxRetries?: number;
5
12
  /**
6
13
  * If true, passes `response_format: { type: 'json_object' }` to the model.
@@ -22,7 +29,7 @@ export type JsonSchemaLlmClientOptions = Omit<LlmPromptOptions, 'messages' | 're
22
29
  * If not provided, an AJV-based validator will be used.
23
30
  */
24
31
  validator?: (data: any) => any;
25
- };
32
+ }
26
33
  export interface CreateJsonSchemaLlmClientParams {
27
34
  prompt: PromptFunction;
28
35
  fallbackPrompt?: PromptFunction;
@@ -3,13 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaValidationError = void 0;
6
7
  exports.createJsonSchemaLlmClient = createJsonSchemaLlmClient;
7
8
  const ajv_1 = __importDefault(require("ajv"));
8
9
  const createLlmRetryClient_js_1 = require("./createLlmRetryClient.js");
10
+ class SchemaValidationError extends Error {
11
+ constructor(message, options) {
12
+ super(message, options);
13
+ this.name = 'SchemaValidationError';
14
+ }
15
+ }
16
+ exports.SchemaValidationError = SchemaValidationError;
9
17
  function createJsonSchemaLlmClient(params) {
10
18
  const { prompt, fallbackPrompt, disableJsonFixer = false } = params;
11
19
  const llmRetryClient = (0, createLlmRetryClient_js_1.createLlmRetryClient)({ prompt, fallbackPrompt });
12
- const ajv = new ajv_1.default({ strict: false }); // Initialize AJV
20
+ const ajv = new ajv_1.default({ strict: false });
13
21
  async function _tryToFixJson(brokenResponse, schemaJsonString, errorDetails, options) {
14
22
  const fixupPrompt = `
15
23
  An attempt to generate a JSON object resulted in the following output, which is either not valid JSON or does not conform to the required schema.
@@ -37,7 +45,7 @@ ${brokenResponse}
37
45
  const response_format = useResponseFormat
38
46
  ? { type: 'json_object' }
39
47
  : undefined;
40
- const { maxRetries, useResponseFormat: _useResponseFormat, ...restOptions } = options || {};
48
+ const { maxRetries, useResponseFormat: _useResponseFormat, beforeValidation, validator, ...restOptions } = options || {};
41
49
  const completion = await prompt({
42
50
  messages,
43
51
  response_format,
@@ -51,7 +59,6 @@ ${brokenResponse}
51
59
  }
52
60
  async function _parseOrFixJson(llmResponseString, schemaJsonString, options) {
53
61
  let jsonDataToParse = llmResponseString.trim();
54
- // Robust handling for responses wrapped in markdown code blocks
55
62
  const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/;
56
63
  const match = codeBlockRegex.exec(jsonDataToParse);
57
64
  if (match && match[1]) {
@@ -64,10 +71,14 @@ ${brokenResponse}
64
71
  return JSON.parse(jsonDataToParse);
65
72
  }
66
73
  catch (parseError) {
74
+ // Only attempt to fix SyntaxErrors (JSON parsing errors).
75
+ // Other errors (like runtime errors) should bubble up.
76
+ if (!(parseError instanceof SyntaxError)) {
77
+ throw parseError;
78
+ }
67
79
  if (disableJsonFixer) {
68
- throw parseError; // re-throw original error
80
+ throw parseError;
69
81
  }
70
- // Attempt a one-time fix before failing.
71
82
  const errorDetails = `JSON Parse Error: ${parseError.message}`;
72
83
  const fixedResponse = await _tryToFixJson(jsonDataToParse, schemaJsonString, errorDetails, options);
73
84
  if (fixedResponse) {
@@ -75,11 +86,10 @@ ${brokenResponse}
75
86
  return JSON.parse(fixedResponse);
76
87
  }
77
88
  catch (e) {
78
- // Fix-up failed, throw original error.
79
89
  throw parseError;
80
90
  }
81
91
  }
82
- throw parseError; // if no fixed response
92
+ throw parseError;
83
93
  }
84
94
  }
85
95
  async function _validateOrFix(jsonData, validator, schemaJsonString, options) {
@@ -90,10 +100,14 @@ ${brokenResponse}
90
100
  return validator(jsonData);
91
101
  }
92
102
  catch (validationError) {
103
+ // Only attempt to fix known validation errors (SchemaValidationError).
104
+ // Arbitrary errors thrown by custom validators (e.g. "Database Error") should bubble up.
105
+ if (!(validationError instanceof SchemaValidationError)) {
106
+ throw validationError;
107
+ }
93
108
  if (disableJsonFixer) {
94
109
  throw validationError;
95
110
  }
96
- // Attempt a one-time fix for schema validation errors.
97
111
  const errorDetails = `Schema Validation Error: ${validationError.message}`;
98
112
  const fixedResponse = await _tryToFixJson(JSON.stringify(jsonData, null, 2), schemaJsonString, errorDetails, options);
99
113
  if (fixedResponse) {
@@ -105,11 +119,10 @@ ${brokenResponse}
105
119
  return validator(fixedJsonData);
106
120
  }
107
121
  catch (e) {
108
- // Fix-up failed, throw original validation error
109
122
  throw validationError;
110
123
  }
111
124
  }
112
- throw validationError; // if no fixed response
125
+ throw validationError;
113
126
  }
114
127
  }
115
128
  function _getJsonPromptConfig(messages, schema, options) {
@@ -120,12 +133,9 @@ Do NOT include any other text, explanations, or markdown formatting (like \`\`\`
120
133
 
121
134
  JSON schema:
122
135
  ${schemaJsonString}`;
123
- // Clone messages to avoid mutating the input
124
136
  const finalMessages = [...messages];
125
- // Find the first system message to append instructions to
126
137
  const systemMessageIndex = finalMessages.findIndex(m => m.role === 'system');
127
138
  if (systemMessageIndex !== -1) {
128
- // Append to existing system message
129
139
  const existingContent = finalMessages[systemMessageIndex].content;
130
140
  finalMessages[systemMessageIndex] = {
131
141
  ...finalMessages[systemMessageIndex],
@@ -133,7 +143,6 @@ ${schemaJsonString}`;
133
143
  };
134
144
  }
135
145
  else {
136
- // Prepend new system message
137
146
  finalMessages.unshift({
138
147
  role: 'system',
139
148
  content: commonPromptFooter
@@ -146,14 +155,13 @@ ${schemaJsonString}`;
146
155
  return { finalMessages, schemaJsonString, response_format };
147
156
  }
148
157
  async function promptJson(messages, schema, options) {
149
- // Default validator using AJV
150
158
  const defaultValidator = (data) => {
151
159
  try {
152
160
  const validate = ajv.compile(schema);
153
161
  const valid = validate(data);
154
162
  if (!valid) {
155
- const errors = validate.errors?.map(e => `${e.instancePath} ${e.message}`).join(', ');
156
- throw new Error(`AJV Validation Error: ${errors}`);
163
+ const errors = (validate.errors || []).map(e => `${e.instancePath} ${e.message}`).join(', ');
164
+ throw new SchemaValidationError(`AJV Validation Error: ${errors}`);
157
165
  }
158
166
  return data;
159
167
  }
@@ -169,29 +177,40 @@ ${schemaJsonString}`;
169
177
  jsonData = await _parseOrFixJson(llmResponseString, schemaJsonString, options);
170
178
  }
171
179
  catch (parseError) {
172
- const errorMessage = `Your previous response resulted in an error.
180
+ // Only wrap SyntaxErrors (JSON parse errors) for retry.
181
+ if (parseError instanceof SyntaxError) {
182
+ const errorMessage = `Your previous response resulted in an error.
173
183
  Error Type: JSON_PARSE_ERROR
174
184
  Error Details: ${parseError.message}
175
185
  The response provided was not valid JSON. Please correct it.`;
176
- throw new createLlmRetryClient_js_1.LlmRetryError(errorMessage, 'JSON_PARSE_ERROR', undefined, llmResponseString);
186
+ throw new createLlmRetryClient_js_1.LlmRetryError(errorMessage, 'JSON_PARSE_ERROR', undefined, llmResponseString);
187
+ }
188
+ // Rethrow other errors (e.g. fatal errors, runtime errors)
189
+ throw parseError;
177
190
  }
178
191
  try {
179
192
  const validatedData = await _validateOrFix(jsonData, validator, schemaJsonString, options);
180
193
  return validatedData;
181
194
  }
182
195
  catch (validationError) {
183
- // We assume the validator throws an error with a meaningful message
184
- const rawResponseForError = JSON.stringify(jsonData, null, 2);
185
- const errorDetails = validationError.message;
186
- const errorMessage = `Your previous response resulted in an error.
196
+ // Only wrap known validation errors for retry.
197
+ if (validationError instanceof SchemaValidationError) {
198
+ const rawResponseForError = JSON.stringify(jsonData, null, 2);
199
+ const errorDetails = validationError.message;
200
+ const errorMessage = `Your previous response resulted in an error.
187
201
  Error Type: SCHEMA_VALIDATION_ERROR
188
202
  Error Details: ${errorDetails}
189
203
  The response was valid JSON but did not conform to the required schema. Please review the errors and the schema to provide a corrected response.`;
190
- throw new createLlmRetryClient_js_1.LlmRetryError(errorMessage, 'CUSTOM_ERROR', validationError, rawResponseForError);
204
+ throw new createLlmRetryClient_js_1.LlmRetryError(errorMessage, 'CUSTOM_ERROR', validationError, rawResponseForError);
205
+ }
206
+ // Rethrow other errors
207
+ throw validationError;
191
208
  }
192
209
  };
210
+ const { maxRetries, useResponseFormat: _useResponseFormat, beforeValidation, validator: _validator, ...restOptions } = options || {};
193
211
  const retryOptions = {
194
- ...options,
212
+ ...restOptions,
213
+ maxRetries,
195
214
  messages: finalMessages,
196
215
  response_format,
197
216
  validate: processResponse
@@ -1,5 +1,9 @@
1
1
  import OpenAI from "openai";
2
2
  import type PQueue from 'p-queue';
3
+ export declare class LlmFatalError extends Error {
4
+ readonly cause?: any | undefined;
5
+ constructor(message: string, cause?: any | undefined);
6
+ }
3
7
  export declare function countChars(message: OpenAI.Chat.Completions.ChatCompletionMessageParam): number;
4
8
  export declare function truncateSingleMessage(message: OpenAI.Chat.Completions.ChatCompletionMessageParam, charLimit: number): OpenAI.Chat.Completions.ChatCompletionMessageParam;
5
9
  export declare function truncateMessages(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], limit: number): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
@@ -21,12 +25,24 @@ export type OpenRouterResponseFormat = {
21
25
  };
22
26
  };
23
27
  /**
24
- * Options for the individual "prompt" function calls.
25
- * These can override defaults or add call-specific parameters.
26
- * 'messages' is a required property, inherited from OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming.
28
+ * Request-level options passed to the OpenAI SDK.
29
+ * These are separate from the body parameters.
27
30
  */
28
- export interface LlmPromptOptions extends Omit<OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming, 'model' | 'response_format' | 'modalities' | 'messages'> {
29
- messages: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[];
31
+ export interface LlmRequestOptions {
32
+ headers?: Record<string, string>;
33
+ signal?: AbortSignal;
34
+ timeout?: number;
35
+ }
36
+ /**
37
+ * Merges two LlmRequestOptions objects.
38
+ * Headers are merged (override wins on conflict), other properties are replaced.
39
+ */
40
+ export declare function mergeRequestOptions(base?: LlmRequestOptions, override?: LlmRequestOptions): LlmRequestOptions | undefined;
41
+ /**
42
+ * Common options shared by all prompt functions.
43
+ * Does NOT include messages - those are handled separately.
44
+ */
45
+ export interface LlmCommonOptions {
30
46
  model?: ModelConfig;
31
47
  retries?: number;
32
48
  /** @deprecated Use `reasoning` object instead. */
@@ -35,6 +51,31 @@ export interface LlmPromptOptions extends Omit<OpenAI.Chat.Completions.ChatCompl
35
51
  image_config?: {
36
52
  aspect_ratio?: string;
37
53
  };
54
+ requestOptions?: LlmRequestOptions;
55
+ temperature?: number;
56
+ max_tokens?: number;
57
+ top_p?: number;
58
+ frequency_penalty?: number;
59
+ presence_penalty?: number;
60
+ stop?: string | string[];
61
+ reasoning_effort?: 'low' | 'medium' | 'high';
62
+ seed?: number;
63
+ user?: string;
64
+ tools?: OpenAI.Chat.Completions.ChatCompletionTool[];
65
+ tool_choice?: OpenAI.Chat.Completions.ChatCompletionToolChoiceOption;
66
+ }
67
+ /**
68
+ * Options for the individual "prompt" function calls.
69
+ * Allows messages as string or array for convenience.
70
+ */
71
+ export interface LlmPromptOptions extends LlmCommonOptions {
72
+ messages: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[];
73
+ }
74
+ /**
75
+ * Internal normalized params - messages is always an array.
76
+ */
77
+ export interface LlmPromptParams extends LlmCommonOptions {
78
+ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
38
79
  }
39
80
  /**
40
81
  * Options required to create an instance of the LlmClient.
@@ -45,8 +86,13 @@ export interface CreateLlmClientParams {
45
86
  defaultModel: ModelConfig;
46
87
  maxConversationChars?: number;
47
88
  queue?: PQueue;
89
+ defaultRequestOptions?: LlmRequestOptions;
48
90
  }
49
- export declare function normalizeOptions(arg1: string | LlmPromptOptions, arg2?: Omit<LlmPromptOptions, 'messages'>): LlmPromptOptions;
91
+ /**
92
+ * Normalizes input arguments to LlmPromptParams.
93
+ * Handles string shorthand and messages-as-string.
94
+ */
95
+ export declare function normalizeOptions(arg1: string | LlmPromptOptions, arg2?: LlmCommonOptions): LlmPromptParams;
50
96
  /**
51
97
  * Factory function that creates a GPT "prompt" function.
52
98
  * @param params - The core dependencies (API key, base URL, default model).
@@ -54,15 +100,15 @@ export declare function normalizeOptions(arg1: string | LlmPromptOptions, arg2?:
54
100
  */
55
101
  export declare function createLlmClient(params: CreateLlmClientParams): {
56
102
  prompt: {
57
- (content: string, options?: Omit<LlmPromptOptions, "messages">): Promise<OpenAI.Chat.Completions.ChatCompletion>;
103
+ (content: string, options?: LlmCommonOptions): Promise<OpenAI.Chat.Completions.ChatCompletion>;
58
104
  (options: LlmPromptOptions): Promise<OpenAI.Chat.Completions.ChatCompletion>;
59
105
  };
60
106
  promptText: {
61
- (content: string, options?: Omit<LlmPromptOptions, "messages">): Promise<string>;
107
+ (content: string, options?: LlmCommonOptions): Promise<string>;
62
108
  (options: LlmPromptOptions): Promise<string>;
63
109
  };
64
110
  promptImage: {
65
- (content: string, options?: Omit<LlmPromptOptions, "messages">): Promise<Buffer>;
111
+ (content: string, options?: LlmCommonOptions): Promise<Buffer>;
66
112
  (options: LlmPromptOptions): Promise<Buffer>;
67
113
  };
68
114
  };
@@ -1,11 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LlmFatalError = void 0;
3
4
  exports.countChars = countChars;
4
5
  exports.truncateSingleMessage = truncateSingleMessage;
5
6
  exports.truncateMessages = truncateMessages;
7
+ exports.mergeRequestOptions = mergeRequestOptions;
6
8
  exports.normalizeOptions = normalizeOptions;
7
9
  exports.createLlmClient = createLlmClient;
8
10
  const retryUtils_js_1 = require("./retryUtils.js");
11
+ class LlmFatalError extends Error {
12
+ cause;
13
+ constructor(message, cause) {
14
+ super(message);
15
+ this.cause = cause;
16
+ this.name = 'LlmFatalError';
17
+ this.cause = cause;
18
+ }
19
+ }
20
+ exports.LlmFatalError = LlmFatalError;
9
21
  function countChars(message) {
10
22
  if (!message.content)
11
23
  return 0;
@@ -49,14 +61,12 @@ function truncateSingleMessage(message, charLimit) {
49
61
  return messageCopy;
50
62
  }
51
63
  if (Array.isArray(messageCopy.content)) {
52
- // Complex case: multipart message.
53
- // Strategy: consolidate text, remove images if needed, then truncate text.
54
64
  const textParts = messageCopy.content.filter((p) => p.type === 'text');
55
65
  const imageParts = messageCopy.content.filter((p) => p.type === 'image_url');
56
66
  let combinedText = textParts.map((p) => p.text).join('\n');
57
67
  let keptImages = [...imageParts];
58
68
  while (combinedText.length + (keptImages.length * 2500) > charLimit && keptImages.length > 0) {
59
- keptImages.pop(); // remove images from the end
69
+ keptImages.pop();
60
70
  }
61
71
  const imageChars = keptImages.length * 2500;
62
72
  const textCharLimit = charLimit - imageChars;
@@ -89,7 +99,6 @@ function truncateMessages(messages, limit) {
89
99
  }
90
100
  const mutableOtherMessages = JSON.parse(JSON.stringify(otherMessages));
91
101
  let excessChars = totalChars - limit;
92
- // Truncate messages starting from the second one.
93
102
  for (let i = 1; i < mutableOtherMessages.length; i++) {
94
103
  if (excessChars <= 0)
95
104
  break;
@@ -100,7 +109,6 @@ function truncateMessages(messages, limit) {
100
109
  mutableOtherMessages[i] = truncateSingleMessage(message, newCharCount);
101
110
  excessChars -= charsToCut;
102
111
  }
103
- // If still over limit, truncate the first message.
104
112
  if (excessChars > 0) {
105
113
  const firstMessage = mutableOtherMessages[0];
106
114
  const firstMessageChars = countChars(firstMessage);
@@ -108,7 +116,6 @@ function truncateMessages(messages, limit) {
108
116
  const newCharCount = firstMessageChars - charsToCut;
109
117
  mutableOtherMessages[0] = truncateSingleMessage(firstMessage, newCharCount);
110
118
  }
111
- // Filter out empty messages (char count is 0)
112
119
  const finalMessages = mutableOtherMessages.filter(msg => countChars(msg) > 0);
113
120
  return systemMessage ? [systemMessage, ...finalMessages] : finalMessages;
114
121
  }
@@ -135,7 +142,6 @@ function concatMessageText(messages) {
135
142
  }
136
143
  function getPromptSummary(messages) {
137
144
  const fullText = concatMessageText(messages);
138
- // Replace multiple whitespace chars with a single space and trim.
139
145
  const cleanedText = fullText.replace(/\s+/g, ' ').trim();
140
146
  if (cleanedText.length <= 50) {
141
147
  return cleanedText;
@@ -149,6 +155,30 @@ function getPromptSummary(messages) {
149
155
  const middle = cleanedText.substring(midStart, midEnd);
150
156
  return `${start}...${middle}...${end}`;
151
157
  }
158
+ /**
159
+ * Merges two LlmRequestOptions objects.
160
+ * Headers are merged (override wins on conflict), other properties are replaced.
161
+ */
162
+ function mergeRequestOptions(base, override) {
163
+ if (!base && !override)
164
+ return undefined;
165
+ if (!base)
166
+ return override;
167
+ if (!override)
168
+ return base;
169
+ return {
170
+ ...base,
171
+ ...override,
172
+ headers: {
173
+ ...base.headers,
174
+ ...override.headers
175
+ }
176
+ };
177
+ }
178
+ /**
179
+ * Normalizes input arguments to LlmPromptParams.
180
+ * Handles string shorthand and messages-as-string.
181
+ */
152
182
  function normalizeOptions(arg1, arg2) {
153
183
  if (typeof arg1 === 'string') {
154
184
  return {
@@ -171,14 +201,12 @@ function normalizeOptions(arg1, arg2) {
171
201
  * @returns An async function `prompt` ready to make OpenAI calls.
172
202
  */
173
203
  function createLlmClient(params) {
174
- const { openai, defaultModel: factoryDefaultModel, maxConversationChars, queue } = params;
175
- const getCompletionParams = (options) => {
176
- const { model: callSpecificModel, messages, reasoning_effort, retries, ...restApiOptions } = options;
177
- // Ensure messages is an array (it should be if normalized, but for safety/types)
178
- const messagesArray = typeof messages === 'string'
179
- ? [{ role: 'user', content: messages }]
204
+ const { openai, defaultModel: factoryDefaultModel, maxConversationChars, queue, defaultRequestOptions } = params;
205
+ const getCompletionParams = (promptParams) => {
206
+ const { model: callSpecificModel, messages, retries, requestOptions, ...restApiOptions } = promptParams;
207
+ const finalMessages = maxConversationChars
208
+ ? truncateMessages(messages, maxConversationChars)
180
209
  : messages;
181
- const finalMessages = maxConversationChars ? truncateMessages(messagesArray, maxConversationChars) : messagesArray;
182
210
  const baseConfig = typeof factoryDefaultModel === 'object' && factoryDefaultModel !== null
183
211
  ? factoryDefaultModel
184
212
  : (typeof factoryDefaultModel === 'string' ? { model: factoryDefaultModel } : {});
@@ -196,15 +224,24 @@ function createLlmClient(params) {
196
224
  messages: finalMessages,
197
225
  ...restApiOptions,
198
226
  };
199
- return { completionParams, modelToUse, finalMessages, retries };
227
+ const mergedRequestOptions = mergeRequestOptions(defaultRequestOptions, requestOptions);
228
+ return { completionParams, modelToUse, finalMessages, retries, requestOptions: mergedRequestOptions };
200
229
  };
201
230
  async function prompt(arg1, arg2) {
202
- const options = normalizeOptions(arg1, arg2);
203
- const { completionParams, finalMessages, retries } = getCompletionParams(options);
231
+ const promptParams = normalizeOptions(arg1, arg2);
232
+ const { completionParams, finalMessages, retries, requestOptions } = getCompletionParams(promptParams);
204
233
  const promptSummary = getPromptSummary(finalMessages);
205
234
  const apiCall = async () => {
206
235
  const task = () => (0, retryUtils_js_1.executeWithRetry)(async () => {
207
- return openai.chat.completions.create(completionParams);
236
+ try {
237
+ return await openai.chat.completions.create(completionParams, requestOptions);
238
+ }
239
+ catch (error) {
240
+ if (error?.status === 400 || error?.status === 401 || error?.status === 403) {
241
+ throw new LlmFatalError(error.message || 'Fatal API Error', error);
242
+ }
243
+ throw error;
244
+ }
208
245
  }, async (completion) => {
209
246
  if (completion.error) {
210
247
  return {
@@ -213,8 +250,9 @@ function createLlmClient(params) {
213
250
  }
214
251
  return { isValid: true, data: completion };
215
252
  }, retries ?? 3, undefined, (error) => {
216
- // Do not retry if the API key is invalid (401) or if the error code explicitly states it.
217
- if (error?.status === 401 || error?.code === 'invalid_api_key') {
253
+ if (error instanceof LlmFatalError)
254
+ return false;
255
+ if (error?.status === 400 || error?.status === 401 || error?.status === 403 || error?.code === 'invalid_api_key') {
218
256
  return false;
219
257
  }
220
258
  return true;
@@ -225,8 +263,8 @@ function createLlmClient(params) {
225
263
  return apiCall();
226
264
  }
227
265
  async function promptText(arg1, arg2) {
228
- const options = normalizeOptions(arg1, arg2);
229
- const response = await prompt(options);
266
+ const promptParams = normalizeOptions(arg1, arg2);
267
+ const response = await prompt(promptParams);
230
268
  const content = response.choices[0]?.message?.content;
231
269
  if (content === null || content === undefined) {
232
270
  throw new Error("LLM returned no text content.");
@@ -234,8 +272,8 @@ function createLlmClient(params) {
234
272
  return content;
235
273
  }
236
274
  async function promptImage(arg1, arg2) {
237
- const options = normalizeOptions(arg1, arg2);
238
- const response = await prompt(options);
275
+ const promptParams = normalizeOptions(arg1, arg2);
276
+ const response = await prompt(promptParams);
239
277
  const message = response.choices[0]?.message;
240
278
  if (message.images && Array.isArray(message.images) && message.images.length > 0) {
241
279
  const imageUrl = message.images[0].image_url.url;
@@ -37,4 +37,76 @@ const createLlmClient_js_1 = require("./createLlmClient.js");
37
37
  temperature: 0.7
38
38
  });
39
39
  });
40
+ (0, vitest_1.it)('should include requestOptions when provided', () => {
41
+ const result = (0, createLlmClient_js_1.normalizeOptions)('Hello world', {
42
+ temperature: 0.5,
43
+ requestOptions: {
44
+ headers: { 'X-Custom': 'value' },
45
+ timeout: 5000
46
+ }
47
+ });
48
+ (0, vitest_1.expect)(result).toEqual({
49
+ messages: [{ role: 'user', content: 'Hello world' }],
50
+ temperature: 0.5,
51
+ requestOptions: {
52
+ headers: { 'X-Custom': 'value' },
53
+ timeout: 5000
54
+ }
55
+ });
56
+ });
57
+ });
58
+ (0, vitest_1.describe)('mergeRequestOptions', () => {
59
+ (0, vitest_1.it)('should return undefined when both are undefined', () => {
60
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(undefined, undefined);
61
+ (0, vitest_1.expect)(result).toBeUndefined();
62
+ });
63
+ (0, vitest_1.it)('should return override when base is undefined', () => {
64
+ const override = { timeout: 5000 };
65
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(undefined, override);
66
+ (0, vitest_1.expect)(result).toBe(override);
67
+ });
68
+ (0, vitest_1.it)('should return base when override is undefined', () => {
69
+ const base = { timeout: 5000 };
70
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(base, undefined);
71
+ (0, vitest_1.expect)(result).toBe(base);
72
+ });
73
+ (0, vitest_1.it)('should merge headers from both', () => {
74
+ const base = {
75
+ headers: { 'X-Base': 'base-value' },
76
+ timeout: 5000
77
+ };
78
+ const override = {
79
+ headers: { 'X-Override': 'override-value' }
80
+ };
81
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(base, override);
82
+ (0, vitest_1.expect)(result).toEqual({
83
+ headers: {
84
+ 'X-Base': 'base-value',
85
+ 'X-Override': 'override-value'
86
+ },
87
+ timeout: 5000
88
+ });
89
+ });
90
+ (0, vitest_1.it)('should override scalar properties', () => {
91
+ const base = { timeout: 5000 };
92
+ const override = { timeout: 10000 };
93
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(base, override);
94
+ (0, vitest_1.expect)(result).toEqual({ timeout: 10000, headers: {} });
95
+ });
96
+ (0, vitest_1.it)('should override conflicting headers', () => {
97
+ const base = {
98
+ headers: { 'X-Shared': 'base-value', 'X-Base': 'base' }
99
+ };
100
+ const override = {
101
+ headers: { 'X-Shared': 'override-value', 'X-Override': 'override' }
102
+ };
103
+ const result = (0, createLlmClient_js_1.mergeRequestOptions)(base, override);
104
+ (0, vitest_1.expect)(result).toEqual({
105
+ headers: {
106
+ 'X-Shared': 'override-value',
107
+ 'X-Base': 'base',
108
+ 'X-Override': 'override'
109
+ }
110
+ });
111
+ });
40
112
  });
@@ -1,5 +1,5 @@
1
1
  import OpenAI from 'openai';
2
- import { PromptFunction, LlmPromptOptions } from "./createLlmClient.js";
2
+ import { PromptFunction, LlmCommonOptions, LlmPromptOptions } from "./createLlmClient.js";
3
3
  export declare class LlmRetryError extends Error {
4
4
  readonly message: string;
5
5
  readonly type: 'JSON_PARSE_ERROR' | 'CUSTOM_ERROR';
@@ -16,33 +16,38 @@ export declare class LlmRetryAttemptError extends Error {
16
16
  readonly mode: 'main' | 'fallback';
17
17
  readonly conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
18
18
  readonly attemptNumber: number;
19
- constructor(message: string, mode: 'main' | 'fallback', conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[], attemptNumber: number, options?: ErrorOptions);
19
+ readonly error: Error;
20
+ constructor(message: string, mode: 'main' | 'fallback', conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[], attemptNumber: number, error: Error, options?: ErrorOptions);
20
21
  }
21
22
  export interface LlmRetryResponseInfo {
22
23
  mode: 'main' | 'fallback';
23
24
  conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
24
25
  attemptNumber: number;
25
26
  }
26
- export type LlmRetryOptions<T = any> = LlmPromptOptions & {
27
+ /**
28
+ * Options for retry prompt functions.
29
+ * Extends common options with retry-specific settings.
30
+ */
31
+ export interface LlmRetryOptions<T = any> extends LlmCommonOptions {
27
32
  maxRetries?: number;
28
33
  validate?: (response: any, info: LlmRetryResponseInfo) => Promise<T>;
29
- };
34
+ }
30
35
  export interface CreateLlmRetryClientParams {
31
36
  prompt: PromptFunction;
32
37
  fallbackPrompt?: PromptFunction;
33
38
  }
34
39
  export declare function createLlmRetryClient(params: CreateLlmRetryClientParams): {
35
40
  promptRetry: {
36
- <T = OpenAI.Chat.Completions.ChatCompletion>(content: string, options?: Omit<LlmRetryOptions<T>, "messages">): Promise<T>;
37
- <T = OpenAI.Chat.Completions.ChatCompletion>(options: LlmRetryOptions<T>): Promise<T>;
41
+ <T = OpenAI.Chat.Completions.ChatCompletion>(content: string, options?: LlmRetryOptions<T>): Promise<T>;
42
+ <T = OpenAI.Chat.Completions.ChatCompletion>(options: LlmPromptOptions & LlmRetryOptions<T>): Promise<T>;
38
43
  };
39
44
  promptTextRetry: {
40
- <T = string>(content: string, options?: Omit<LlmRetryOptions<T>, "messages">): Promise<T>;
41
- <T = string>(options: LlmRetryOptions<T>): Promise<T>;
45
+ <T = string>(content: string, options?: LlmRetryOptions<T>): Promise<T>;
46
+ <T = string>(options: LlmPromptOptions & LlmRetryOptions<T>): Promise<T>;
42
47
  };
43
48
  promptImageRetry: {
44
- <T = Buffer<ArrayBufferLike>>(content: string, options?: Omit<LlmRetryOptions<T>, "messages">): Promise<T>;
45
- <T = Buffer<ArrayBufferLike>>(options: LlmRetryOptions<T>): Promise<T>;
49
+ <T = Buffer<ArrayBufferLike>>(content: string, options?: LlmRetryOptions<T>): Promise<T>;
50
+ <T = Buffer<ArrayBufferLike>>(options: LlmPromptOptions & LlmRetryOptions<T>): Promise<T>;
46
51
  };
47
52
  };
48
53
  export type LlmRetryClient = ReturnType<typeof createLlmRetryClient>;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LlmRetryAttemptError = exports.LlmRetryExhaustedError = exports.LlmRetryError = void 0;
4
4
  exports.createLlmRetryClient = createLlmRetryClient;
5
5
  const createLlmClient_js_1 = require("./createLlmClient.js");
6
- // Custom error for the querier to handle, allowing retries with structured feedback.
7
6
  class LlmRetryError extends Error {
8
7
  message;
9
8
  type;
@@ -28,33 +27,39 @@ class LlmRetryExhaustedError extends Error {
28
27
  }
29
28
  }
30
29
  exports.LlmRetryExhaustedError = LlmRetryExhaustedError;
31
- // This error is thrown by LlmRetryClient for each failed attempt.
32
- // It wraps the underlying error (from API call or validation) and adds context.
33
30
  class LlmRetryAttemptError extends Error {
34
31
  message;
35
32
  mode;
36
33
  conversation;
37
34
  attemptNumber;
38
- constructor(message, mode, conversation, attemptNumber, options) {
35
+ error;
36
+ constructor(message, mode, conversation, attemptNumber, error, options) {
39
37
  super(message, options);
40
38
  this.message = message;
41
39
  this.mode = mode;
42
40
  this.conversation = conversation;
43
41
  this.attemptNumber = attemptNumber;
42
+ this.error = error;
44
43
  this.name = 'LlmRetryAttemptError';
45
44
  }
46
45
  }
47
46
  exports.LlmRetryAttemptError = LlmRetryAttemptError;
47
+ function normalizeRetryOptions(arg1, arg2) {
48
+ const baseParams = (0, createLlmClient_js_1.normalizeOptions)(arg1, arg2);
49
+ return {
50
+ ...baseParams,
51
+ ...arg2,
52
+ messages: baseParams.messages
53
+ };
54
+ }
48
55
  function constructLlmMessages(initialMessages, attemptNumber, previousError) {
49
56
  if (attemptNumber === 0) {
50
- // First attempt
51
57
  return initialMessages;
52
58
  }
53
59
  if (!previousError) {
54
- // Should not happen for attempt > 0, but as a safeguard...
55
60
  throw new Error("Invariant violation: previousError is missing for a retry attempt.");
56
61
  }
57
- const cause = previousError.cause;
62
+ const cause = previousError.error;
58
63
  if (!(cause instanceof LlmRetryError)) {
59
64
  throw Error('cause must be an instanceof LlmRetryError');
60
65
  }
@@ -64,10 +69,8 @@ function constructLlmMessages(initialMessages, attemptNumber, previousError) {
64
69
  }
65
70
  function createLlmRetryClient(params) {
66
71
  const { prompt, fallbackPrompt } = params;
67
- async function runPromptLoop(options, responseType) {
68
- const { maxRetries = 3, validate, messages, ...restOptions } = options;
69
- // Ensure messages is an array (normalizeOptions ensures this but types might be loose)
70
- const initialMessages = messages;
72
+ async function runPromptLoop(retryParams, responseType) {
73
+ const { maxRetries = 3, validate, messages: initialMessages, ...restOptions } = retryParams;
71
74
  let lastError;
72
75
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
73
76
  const useFallback = !!fallbackPrompt && attempt > 0;
@@ -111,7 +114,6 @@ function createLlmRetryClient(params) {
111
114
  throw new LlmRetryError("LLM returned no image.", 'CUSTOM_ERROR', undefined, JSON.stringify(completion));
112
115
  }
113
116
  }
114
- // Construct conversation history for success or potential error reporting
115
117
  const finalConversation = [...currentMessages];
116
118
  if (assistantMessage) {
117
119
  finalConversation.push(assistantMessage);
@@ -128,21 +130,18 @@ function createLlmRetryClient(params) {
128
130
  return dataToProcess;
129
131
  }
130
132
  catch (error) {
133
+ if (error instanceof createLlmClient_js_1.LlmFatalError) {
134
+ const fatalAttemptError = new LlmRetryAttemptError(`Fatal error on attempt ${attempt + 1}: ${error.message}`, mode, currentMessages, attempt, error, { cause: lastError });
135
+ throw new LlmRetryExhaustedError(`Operation failed with fatal error on attempt ${attempt + 1}.`, { cause: fatalAttemptError });
136
+ }
131
137
  if (error instanceof LlmRetryError) {
132
- // This is a recoverable error, so we'll create a detailed attempt error and continue the loop.
133
138
  const conversationForError = [...currentMessages];
134
- // If the error contains the raw response (e.g. the invalid text), add it to history
135
- // so the LLM knows what it generated previously.
136
139
  if (error.rawResponse) {
137
140
  conversationForError.push({ role: 'assistant', content: error.rawResponse });
138
141
  }
139
- else if (responseType === 'raw' && error.details) {
140
- // For raw mode, if we have details, maybe we can infer something, but usually rawResponse is key.
141
- }
142
- lastError = new LlmRetryAttemptError(`Attempt ${attempt + 1} failed.`, mode, conversationForError, attempt, { cause: error });
142
+ lastError = new LlmRetryAttemptError(`Attempt ${attempt + 1} failed: ${error.message}`, mode, conversationForError, attempt, error, { cause: lastError });
143
143
  }
144
144
  else {
145
- // This is a non-recoverable error (e.g., network, API key), so we re-throw it immediately.
146
145
  throw error;
147
146
  }
148
147
  }
@@ -150,16 +149,16 @@ function createLlmRetryClient(params) {
150
149
  throw new LlmRetryExhaustedError(`Operation failed after ${maxRetries + 1} attempts.`, { cause: lastError });
151
150
  }
152
151
  async function promptRetry(arg1, arg2) {
153
- const options = (0, createLlmClient_js_1.normalizeOptions)(arg1, arg2);
154
- return runPromptLoop(options, 'raw');
152
+ const retryParams = normalizeRetryOptions(arg1, arg2);
153
+ return runPromptLoop(retryParams, 'raw');
155
154
  }
156
155
  async function promptTextRetry(arg1, arg2) {
157
- const options = (0, createLlmClient_js_1.normalizeOptions)(arg1, arg2);
158
- return runPromptLoop(options, 'text');
156
+ const retryParams = normalizeRetryOptions(arg1, arg2);
157
+ return runPromptLoop(retryParams, 'text');
159
158
  }
160
159
  async function promptImageRetry(arg1, arg2) {
161
- const options = (0, createLlmClient_js_1.normalizeOptions)(arg1, arg2);
162
- return runPromptLoop(options, 'image');
160
+ const retryParams = normalizeRetryOptions(arg1, arg2);
161
+ return runPromptLoop(retryParams, 'image');
163
162
  }
164
163
  return { promptRetry, promptTextRetry, promptImageRetry };
165
164
  }
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.normalizeZodArgs = normalizeZodArgs;
37
37
  exports.createZodLlmClient = createZodLlmClient;
38
38
  const z = __importStar(require("zod"));
39
+ const createJsonSchemaLlmClient_js_1 = require("./createJsonSchemaLlmClient.js");
39
40
  function isZodSchema(obj) {
40
41
  return (typeof obj === 'object' &&
41
42
  obj !== null &&
@@ -94,7 +95,15 @@ function createZodLlmClient(params) {
94
95
  unrepresentable: 'any'
95
96
  });
96
97
  const zodValidator = (data) => {
97
- return dataExtractionSchema.parse(data);
98
+ try {
99
+ return dataExtractionSchema.parse(data);
100
+ }
101
+ catch (error) {
102
+ if (error instanceof z.ZodError) {
103
+ throw new createJsonSchemaLlmClient_js_1.SchemaValidationError(error.toString(), { cause: error });
104
+ }
105
+ throw error;
106
+ }
98
107
  };
99
108
  const result = await jsonSchemaClient.promptJson(messages, schema, {
100
109
  ...options,
@@ -10,27 +10,27 @@ export declare function createLlm(params: CreateLlmFactoryParams): {
10
10
  };
11
11
  promptJson: <T>(messages: import("openai/resources/index.js").ChatCompletionMessageParam[], schema: Record<string, any>, options?: import("./createJsonSchemaLlmClient.js").JsonSchemaLlmClientOptions) => Promise<T>;
12
12
  promptRetry: {
13
- <T = import("openai/resources/index.js").ChatCompletion>(content: string, options?: Omit<import("./createLlmRetryClient.js").LlmRetryOptions<T>, "messages">): Promise<T>;
14
- <T = import("openai/resources/index.js").ChatCompletion>(options: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
13
+ <T = import("openai/resources/index.js").ChatCompletion>(content: string, options?: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
14
+ <T = import("openai/resources/index.js").ChatCompletion>(options: import("./createLlmClient.js").LlmPromptOptions & import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
15
15
  };
16
16
  promptTextRetry: {
17
- <T = string>(content: string, options?: Omit<import("./createLlmRetryClient.js").LlmRetryOptions<T>, "messages">): Promise<T>;
18
- <T = string>(options: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
17
+ <T = string>(content: string, options?: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
18
+ <T = string>(options: import("./createLlmClient.js").LlmPromptOptions & import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
19
19
  };
20
20
  promptImageRetry: {
21
- <T = Buffer<ArrayBufferLike>>(content: string, options?: Omit<import("./createLlmRetryClient.js").LlmRetryOptions<T>, "messages">): Promise<T>;
22
- <T = Buffer<ArrayBufferLike>>(options: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
21
+ <T = Buffer<ArrayBufferLike>>(content: string, options?: import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
22
+ <T = Buffer<ArrayBufferLike>>(options: import("./createLlmClient.js").LlmPromptOptions & import("./createLlmRetryClient.js").LlmRetryOptions<T>): Promise<T>;
23
23
  };
24
24
  prompt: {
25
- (content: string, options?: Omit<import("./createLlmClient.js").LlmPromptOptions, "messages">): Promise<import("openai/resources/index.js").ChatCompletion>;
25
+ (content: string, options?: import("./createLlmClient.js").LlmCommonOptions): Promise<import("openai/resources/index.js").ChatCompletion>;
26
26
  (options: import("./createLlmClient.js").LlmPromptOptions): Promise<import("openai/resources/index.js").ChatCompletion>;
27
27
  };
28
28
  promptText: {
29
- (content: string, options?: Omit<import("./createLlmClient.js").LlmPromptOptions, "messages">): Promise<string>;
29
+ (content: string, options?: import("./createLlmClient.js").LlmCommonOptions): Promise<string>;
30
30
  (options: import("./createLlmClient.js").LlmPromptOptions): Promise<string>;
31
31
  };
32
32
  promptImage: {
33
- (content: string, options?: Omit<import("./createLlmClient.js").LlmPromptOptions, "messages">): Promise<Buffer>;
33
+ (content: string, options?: import("./createLlmClient.js").LlmCommonOptions): Promise<Buffer>;
34
34
  (options: import("./createLlmClient.js").LlmPromptOptions): Promise<Buffer>;
35
35
  };
36
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-fns",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "ajv": "^8.17.1",
15
15
  "openai": "^6.9.1",
16
16
  "undici": "^7.16.0",
17
- "zod": "^4.1.13"
17
+ "zod": "^4.2.1"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@keyv/sqlite": "^4.0.6",
package/readme.md CHANGED
@@ -27,6 +27,7 @@ const llm = createLlm({
27
27
  // cache: Cache instance (cache-manager)
28
28
  // queue: PQueue instance for concurrency control
29
29
  // maxConversationChars: number (auto-truncation)
30
+ // defaultRequestOptions: { headers, timeout, signal }
30
31
  });
31
32
  ```
32
33
 
@@ -214,8 +215,14 @@ const res = await llm.prompt({
214
215
 
215
216
  // Library Extensions
216
217
  model: "gpt-4o", // Override default model for this call
217
- ttl: 5000, // Cache this specific call for 5s (in ms)
218
218
  retries: 5, // Retry network errors 5 times
219
+
220
+ // Request-level options (headers, timeout, abort signal)
221
+ requestOptions: {
222
+ headers: { 'X-Cache-Salt': 'v2' }, // Affects cache key
223
+ timeout: 60000,
224
+ signal: abortController.signal
225
+ }
219
226
  });
220
227
  ```
221
228
 
@@ -299,7 +306,6 @@ const gameState = await llm.promptZod(
299
306
  model: "google/gemini-flash-1.5",
300
307
  disableJsonFixer: true, // Turn off the automatic JSON repair agent
301
308
  maxRetries: 0, // Fail immediately on error
302
- ttl: 60000 // Cache result
303
309
  }
304
310
  );
305
311
  ```