call-ai 0.7.0-dev-preview-13 → 0.7.0-dev-preview-14

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 (2) hide show
  1. package/dist/api.js +111 -68
  2. package/package.json +2 -2
package/dist/api.js CHANGED
@@ -4,7 +4,7 @@ exports.callAI = callAI;
4
4
  const strategies_1 = require("./strategies");
5
5
  // Import package version for debugging
6
6
  // eslint-disable-next-line @typescript-eslint/no-var-requires
7
- const PACKAGE_VERSION = require('../package.json').version;
7
+ const PACKAGE_VERSION = require("../package.json").version;
8
8
  // Default fallback model when the primary model fails or is unavailable
9
9
  const FALLBACK_MODEL = "openrouter/auto";
10
10
  /**
@@ -33,47 +33,56 @@ function callAI(prompt, options = {}) {
33
33
  // Do setup and validation before returning the generator
34
34
  const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
35
35
  // Make the fetch request and handle errors before creating the generator
36
- console.log(`[callAI:${PACKAGE_VERSION}] Making fetch request to: ${endpoint}`);
37
- console.log(`[callAI:${PACKAGE_VERSION}] With model: ${model}`);
38
- console.log(`[callAI:${PACKAGE_VERSION}] Request headers:`, JSON.stringify(requestOptions.headers));
36
+ if (options.debug) {
37
+ console.log(`[callAI:${PACKAGE_VERSION}] Making fetch request to: ${endpoint}`);
38
+ console.log(`[callAI:${PACKAGE_VERSION}] With model: ${model}`);
39
+ console.log(`[callAI:${PACKAGE_VERSION}] Request headers:`, JSON.stringify(requestOptions.headers));
40
+ }
39
41
  let response;
40
42
  try {
41
43
  response = await fetch(endpoint, requestOptions);
42
- console.log(`[callAI:${PACKAGE_VERSION}] Fetch completed with status:`, response.status, response.statusText);
43
- // Log all headers
44
- console.log(`[callAI:${PACKAGE_VERSION}] Response headers:`);
45
- response.headers.forEach((value, name) => {
46
- console.log(`[callAI:${PACKAGE_VERSION}] ${name}: ${value}`);
47
- });
48
- // Clone response for diagnostic purposes only
49
- const diagnosticResponse = response.clone();
50
- try {
51
- // Try to get the response as text for debugging
52
- const responseText = await diagnosticResponse.text();
53
- console.log(`[callAI:${PACKAGE_VERSION}] First 500 chars of response body:`, responseText.substring(0, 500) + (responseText.length > 500 ? '...' : ''));
54
- }
55
- catch (e) {
56
- console.log(`[callAI:${PACKAGE_VERSION}] Could not read response body for diagnostics:`, e);
44
+ if (options.debug) {
45
+ console.log(`[callAI:${PACKAGE_VERSION}] Fetch completed with status:`, response.status, response.statusText);
46
+ // Log all headers
47
+ console.log(`[callAI:${PACKAGE_VERSION}] Response headers:`);
48
+ response.headers.forEach((value, name) => {
49
+ console.log(`[callAI:${PACKAGE_VERSION}] ${name}: ${value}`);
50
+ });
51
+ // Clone response for diagnostic purposes only
52
+ const diagnosticResponse = response.clone();
53
+ try {
54
+ // Try to get the response as text for debugging
55
+ const responseText = await diagnosticResponse.text();
56
+ console.log(`[callAI:${PACKAGE_VERSION}] First 500 chars of response body:`, responseText.substring(0, 500) +
57
+ (responseText.length > 500 ? "..." : ""));
58
+ }
59
+ catch (e) {
60
+ console.log(`[callAI:${PACKAGE_VERSION}] Could not read response body for diagnostics:`, e);
61
+ }
57
62
  }
58
63
  }
59
64
  catch (fetchError) {
60
65
  console.error(`[callAI:${PACKAGE_VERSION}] Network error during fetch:`, fetchError);
61
66
  throw fetchError; // Re-throw network errors
62
67
  }
63
- // Explicitly check for HTTP error status and log extensively
64
- console.log(`[callAI:${PACKAGE_VERSION}] Response.ok =`, response.ok);
65
- console.log(`[callAI:${PACKAGE_VERSION}] Response.status =`, response.status);
66
- console.log(`[callAI:${PACKAGE_VERSION}] Response.statusText =`, response.statusText);
67
- console.log(`[callAI:${PACKAGE_VERSION}] Response.type =`, response.type);
68
- // Double check for content-type to see if there's a mismatch in error response handling
69
- const contentType = response.headers.get('content-type') || '';
70
- console.log(`[callAI:${PACKAGE_VERSION}] Content-Type =`, contentType);
68
+ // Explicitly check for HTTP error status and log extensively if debug is enabled
69
+ // Safe access to headers in case of mock environments
70
+ const contentType = response?.headers?.get?.("content-type") || "";
71
+ if (options.debug) {
72
+ console.log(`[callAI:${PACKAGE_VERSION}] Response.ok =`, response.ok);
73
+ console.log(`[callAI:${PACKAGE_VERSION}] Response.status =`, response.status);
74
+ console.log(`[callAI:${PACKAGE_VERSION}] Response.statusText =`, response.statusText);
75
+ console.log(`[callAI:${PACKAGE_VERSION}] Response.type =`, response.type);
76
+ console.log(`[callAI:${PACKAGE_VERSION}] Content-Type =`, contentType);
77
+ }
71
78
  // Browser-compatible error handling - must check BOTH status code AND content-type
72
79
  // Some browsers will report status 200 for SSE streams even when server returns 400
73
80
  const hasHttpError = !response.ok || response.status >= 400;
74
- const hasJsonError = contentType.includes('application/json');
81
+ const hasJsonError = contentType.includes("application/json");
75
82
  if (hasHttpError || hasJsonError) {
76
- console.log(`[callAI:${PACKAGE_VERSION}] ⚠️ Error detected - HTTP Status: ${response.status}, Content-Type: ${contentType}`);
83
+ if (options.debug) {
84
+ console.log(`[callAI:${PACKAGE_VERSION}] ⚠️ Error detected - HTTP Status: ${response.status}, Content-Type: ${contentType}`);
85
+ }
77
86
  // Handle the error with fallback model if appropriate
78
87
  if (!options.skipRetry) {
79
88
  const clonedResponse = response.clone();
@@ -87,7 +96,10 @@ function callAI(prompt, options = {}) {
87
96
  console.log(`[callAI:${PACKAGE_VERSION}] Retrying with fallback model: ${FALLBACK_MODEL}`);
88
97
  }
89
98
  // Retry with fallback model
90
- return await callAI(prompt, { ...options, model: FALLBACK_MODEL });
99
+ return (await callAI(prompt, {
100
+ ...options,
101
+ model: FALLBACK_MODEL,
102
+ }));
91
103
  }
92
104
  }
93
105
  catch (modelCheckError) {
@@ -99,19 +111,25 @@ function callAI(prompt, options = {}) {
99
111
  try {
100
112
  // Try to get error details from the response body
101
113
  const errorBody = await response.text();
102
- console.log(`[callAI:${PACKAGE_VERSION}] Error body:`, errorBody);
114
+ if (options.debug) {
115
+ console.log(`[callAI:${PACKAGE_VERSION}] Error body:`, errorBody);
116
+ }
103
117
  try {
104
118
  // Try to parse JSON error
105
119
  const errorJson = JSON.parse(errorBody);
106
- console.log(`[callAI:${PACKAGE_VERSION}] Parsed error:`, errorJson);
120
+ if (options.debug) {
121
+ console.log(`[callAI:${PACKAGE_VERSION}] Parsed error:`, errorJson);
122
+ }
107
123
  // Extract message from OpenRouter error format
108
- let errorMessage = '';
124
+ let errorMessage = "";
109
125
  // Handle common error formats
110
- if (errorJson.error && typeof errorJson.error === 'object' && errorJson.error.message) {
126
+ if (errorJson.error &&
127
+ typeof errorJson.error === "object" &&
128
+ errorJson.error.message) {
111
129
  // OpenRouter/OpenAI format: { error: { message: "..." } }
112
130
  errorMessage = errorJson.error.message;
113
131
  }
114
- else if (errorJson.error && typeof errorJson.error === 'string') {
132
+ else if (errorJson.error && typeof errorJson.error === "string") {
115
133
  // Simple error format: { error: "..." }
116
134
  errorMessage = errorJson.error;
117
135
  }
@@ -127,7 +145,9 @@ function callAI(prompt, options = {}) {
127
145
  if (!errorMessage.includes(response.status.toString())) {
128
146
  errorMessage = `${errorMessage} (Status: ${response.status})`;
129
147
  }
130
- console.log(`[callAI:${PACKAGE_VERSION}] Extracted error message:`, errorMessage);
148
+ if (options.debug) {
149
+ console.log(`[callAI:${PACKAGE_VERSION}] Extracted error message:`, errorMessage);
150
+ }
131
151
  // Create error with standard format
132
152
  const error = new Error(errorMessage);
133
153
  // Add useful metadata
@@ -139,15 +159,18 @@ function callAI(prompt, options = {}) {
139
159
  }
140
160
  catch (jsonError) {
141
161
  // If JSON parsing fails, extract a useful message from the raw error body
142
- console.log(`[callAI:${PACKAGE_VERSION}] JSON parse error:`, jsonError);
162
+ if (options.debug) {
163
+ console.log(`[callAI:${PACKAGE_VERSION}] JSON parse error:`, jsonError);
164
+ }
143
165
  // Try to extract a useful message even from non-JSON text
144
- let errorMessage = '';
166
+ let errorMessage = "";
145
167
  // Check if it's a plain text error message
146
168
  if (errorBody && errorBody.trim().length > 0) {
147
169
  // Limit length for readability
148
- errorMessage = errorBody.length > 100 ?
149
- errorBody.substring(0, 100) + '...' :
150
- errorBody;
170
+ errorMessage =
171
+ errorBody.length > 100
172
+ ? errorBody.substring(0, 100) + "..."
173
+ : errorBody;
151
174
  }
152
175
  else {
153
176
  errorMessage = `API error: ${response.status} ${response.statusText}`;
@@ -156,7 +179,9 @@ function callAI(prompt, options = {}) {
156
179
  if (!errorMessage.includes(response.status.toString())) {
157
180
  errorMessage = `${errorMessage} (Status: ${response.status})`;
158
181
  }
159
- console.log(`[callAI:${PACKAGE_VERSION}] Extracted text error message:`, errorMessage);
182
+ if (options.debug) {
183
+ console.log(`[callAI:${PACKAGE_VERSION}] Extracted text error message:`, errorMessage);
184
+ }
160
185
  const error = new Error(errorMessage);
161
186
  error.status = response.status;
162
187
  error.statusText = response.statusText;
@@ -179,11 +204,13 @@ function callAI(prompt, options = {}) {
179
204
  }
180
205
  }
181
206
  // Only if response is OK, create and return the streaming generator
182
- console.log(`[callAI:${PACKAGE_VERSION}] Response OK, creating streaming generator`);
207
+ if (options.debug) {
208
+ console.log(`[callAI:${PACKAGE_VERSION}] Response OK, creating streaming generator`);
209
+ }
183
210
  return createStreamingGenerator(response, options, schemaStrategy, model);
184
211
  })();
185
212
  // For backward compatibility with v0.6.x where users didn't await the result
186
- if (process.env.NODE_ENV !== 'production') {
213
+ if (process.env.NODE_ENV !== "production") {
187
214
  console.warn(`[callAI:${PACKAGE_VERSION}] WARNING: Using callAI with streaming without await is deprecated. ` +
188
215
  `Please use 'const generator = await callAI(...)' instead of 'const generator = callAI(...)'. ` +
189
216
  `This backward compatibility will be removed in a future version.`);
@@ -204,7 +231,7 @@ async function bufferStreamingResults(prompt, options) {
204
231
  };
205
232
  try {
206
233
  // Get streaming generator
207
- const generator = await callAI(prompt, streamingOptions);
234
+ const generator = (await callAI(prompt, streamingOptions));
208
235
  // Buffer all chunks
209
236
  let finalResult = "";
210
237
  let chunkCount = 0;
@@ -230,7 +257,10 @@ function createBackwardCompatStreamingProxy(promise) {
230
257
  return new Proxy({}, {
231
258
  get(target, prop) {
232
259
  // First check if it's an AsyncGenerator method (needed for for-await)
233
- if (prop === 'next' || prop === 'throw' || prop === 'return' || prop === Symbol.asyncIterator) {
260
+ if (prop === "next" ||
261
+ prop === "throw" ||
262
+ prop === "return" ||
263
+ prop === Symbol.asyncIterator) {
234
264
  // Create wrapper functions that await the Promise first
235
265
  if (prop === Symbol.asyncIterator) {
236
266
  return function () {
@@ -245,7 +275,7 @@ function createBackwardCompatStreamingProxy(promise) {
245
275
  // Turn Promise rejection into iterator result with error thrown
246
276
  return Promise.reject(error);
247
277
  }
248
- }
278
+ },
249
279
  };
250
280
  };
251
281
  }
@@ -256,11 +286,11 @@ function createBackwardCompatStreamingProxy(promise) {
256
286
  };
257
287
  }
258
288
  // Then check if it's a Promise method
259
- if (prop === 'then' || prop === 'catch' || prop === 'finally') {
289
+ if (prop === "then" || prop === "catch" || prop === "finally") {
260
290
  return promise[prop].bind(promise);
261
291
  }
262
292
  return undefined;
263
- }
293
+ },
264
294
  });
265
295
  }
266
296
  /**
@@ -293,7 +323,7 @@ async function checkForInvalidModelError(response, model, isRetry, skipRetry = f
293
323
  console.log(`[callAI:${PACKAGE_VERSION}] Checking for invalid model error:`, {
294
324
  model,
295
325
  statusCode: response.status,
296
- errorData
326
+ errorData,
297
327
  });
298
328
  }
299
329
  // Common patterns for invalid model errors across different providers
@@ -304,10 +334,10 @@ async function checkForInvalidModelError(response, model, isRetry, skipRetry = f
304
334
  "unknown model",
305
335
  "no provider was found",
306
336
  "fake-model", // For our test case
307
- "does-not-exist" // For our test case
337
+ "does-not-exist", // For our test case
308
338
  ];
309
339
  // Check if error message contains any of our patterns
310
- let errorMessage = '';
340
+ let errorMessage = "";
311
341
  if (errorData.error && errorData.error.message) {
312
342
  errorMessage = errorData.error.message.toLowerCase();
313
343
  }
@@ -318,7 +348,7 @@ async function checkForInvalidModelError(response, model, isRetry, skipRetry = f
318
348
  errorMessage = JSON.stringify(errorData).toLowerCase();
319
349
  }
320
350
  // Test the error message against each pattern
321
- const isInvalidModel = invalidModelPatterns.some(pattern => errorMessage.includes(pattern.toLowerCase()));
351
+ const isInvalidModel = invalidModelPatterns.some((pattern) => errorMessage.includes(pattern.toLowerCase()));
322
352
  if (isInvalidModel && debugEnabled) {
323
353
  console.warn(`[callAI:${PACKAGE_VERSION}] Model ${model} not valid, will retry with ${FALLBACK_MODEL}`);
324
354
  }
@@ -541,10 +571,12 @@ async function extractClaudeResponse(response) {
541
571
  * return a 200 OK initially but then deliver error information in the stream.
542
572
  */
543
573
  async function* createStreamingGenerator(response, options, schemaStrategy, model) {
544
- console.log(`[callAI:${PACKAGE_VERSION}] Starting streaming generator with model: ${model}`);
545
- console.log(`[callAI:${PACKAGE_VERSION}] Response status:`, response.status);
546
- console.log(`[callAI:${PACKAGE_VERSION}] Response type:`, response.type);
547
- console.log(`[callAI:${PACKAGE_VERSION}] Response Content-Type:`, response.headers.get('content-type'));
574
+ if (options.debug) {
575
+ console.log(`[callAI:${PACKAGE_VERSION}] Starting streaming generator with model: ${model}`);
576
+ console.log(`[callAI:${PACKAGE_VERSION}] Response status:`, response.status);
577
+ console.log(`[callAI:${PACKAGE_VERSION}] Response type:`, response.type);
578
+ console.log(`[callAI:${PACKAGE_VERSION}] Response Content-Type:`, response.headers.get("content-type"));
579
+ }
548
580
  try {
549
581
  // Handle streaming response
550
582
  if (!response.body) {
@@ -558,8 +590,8 @@ async function* createStreamingGenerator(response, options, schemaStrategy, mode
558
590
  while (true) {
559
591
  const { done, value } = await reader.read();
560
592
  if (done) {
561
- console.log(`[callAI:${PACKAGE_VERSION}] Stream done=true after ${chunkCount} chunks`);
562
593
  if (options.debug) {
594
+ console.log(`[callAI:${PACKAGE_VERSION}] Stream done=true after ${chunkCount} chunks`);
563
595
  console.log(`[callAI-streaming:complete v${PACKAGE_VERSION}] Stream finished after ${chunkCount} chunks`);
564
596
  }
565
597
  break;
@@ -567,15 +599,23 @@ async function* createStreamingGenerator(response, options, schemaStrategy, mode
567
599
  // Increment chunk counter before processing
568
600
  chunkCount++;
569
601
  const chunk = decoder.decode(value);
570
- console.log(`[callAI:${PACKAGE_VERSION}] Raw chunk #${chunkCount} (${chunk.length} bytes):`, chunk.length > 200 ? chunk.substring(0, 200) + '...' : chunk);
602
+ if (options.debug) {
603
+ console.log(`[callAI:${PACKAGE_VERSION}] Raw chunk #${chunkCount} (${chunk.length} bytes):`, chunk.length > 200 ? chunk.substring(0, 200) + "..." : chunk);
604
+ }
571
605
  const lines = chunk.split("\n").filter((line) => line.trim() !== "");
572
- console.log(`[callAI:${PACKAGE_VERSION}] Chunk #${chunkCount} contains ${lines.length} non-empty lines`);
606
+ if (options.debug) {
607
+ console.log(`[callAI:${PACKAGE_VERSION}] Chunk #${chunkCount} contains ${lines.length} non-empty lines`);
608
+ }
573
609
  for (const line of lines) {
574
- console.log(`[callAI:${PACKAGE_VERSION}] Processing line:`, line.length > 100 ? line.substring(0, 100) + '...' : line);
610
+ if (options.debug) {
611
+ console.log(`[callAI:${PACKAGE_VERSION}] Processing line:`, line.length > 100 ? line.substring(0, 100) + "..." : line);
612
+ }
575
613
  if (line.startsWith("data: ")) {
576
614
  let data = line.slice(6);
577
615
  if (data === "[DONE]") {
578
- console.log(`[callAI:${PACKAGE_VERSION}] Received [DONE] marker`);
616
+ if (options.debug) {
617
+ console.log(`[callAI:${PACKAGE_VERSION}] Received [DONE] marker`);
618
+ }
579
619
  break;
580
620
  }
581
621
  if (options.debug) {
@@ -592,14 +632,16 @@ async function* createStreamingGenerator(response, options, schemaStrategy, mode
592
632
  console.log(`[callAI:${PACKAGE_VERSION}] Empty JSON line after data: prefix`);
593
633
  continue;
594
634
  }
595
- console.log(`[callAI:${PACKAGE_VERSION}] JSON line (first 100 chars):`, jsonLine.length > 100 ? jsonLine.substring(0, 100) + '...' : jsonLine);
635
+ console.log(`[callAI:${PACKAGE_VERSION}] JSON line (first 100 chars):`, jsonLine.length > 100
636
+ ? jsonLine.substring(0, 100) + "..."
637
+ : jsonLine);
596
638
  // Parse the JSON chunk
597
639
  let json;
598
640
  try {
599
641
  json = JSON.parse(jsonLine);
600
- console.log(`[callAI:${PACKAGE_VERSION}] Parsed JSON:`, JSON.stringify(json).length > 100 ?
601
- JSON.stringify(json).substring(0, 100) + '...' :
602
- JSON.stringify(json));
642
+ console.log(`[callAI:${PACKAGE_VERSION}] Parsed JSON:`, JSON.stringify(json).length > 100
643
+ ? JSON.stringify(json).substring(0, 100) + "..."
644
+ : JSON.stringify(json));
603
645
  }
604
646
  catch (parseError) {
605
647
  console.error(`[callAI:${PACKAGE_VERSION}] JSON parse error:`, parseError);
@@ -608,7 +650,7 @@ async function* createStreamingGenerator(response, options, schemaStrategy, mode
608
650
  }
609
651
  // Enhanced error detection - check for BOTH error and json.error
610
652
  // Some APIs return 200 OK but then deliver errors in the stream
611
- if (json.error || (typeof json === 'object' && 'error' in json)) {
653
+ if (json.error || (typeof json === "object" && "error" in json)) {
612
654
  console.error(`[callAI:${PACKAGE_VERSION}] Detected error in streaming response:`, json);
613
655
  // Create a detailed error object similar to our HTTP error handling
614
656
  const errorMessage = json.error?.message ||
@@ -617,7 +659,8 @@ async function* createStreamingGenerator(response, options, schemaStrategy, mode
617
659
  const detailedError = new Error(`API streaming error: ${errorMessage}`);
618
660
  // Add error metadata
619
661
  detailedError.status = json.error?.status || 400;
620
- detailedError.statusText = json.error?.type || 'Bad Request';
662
+ detailedError.statusText =
663
+ json.error?.type || "Bad Request";
621
664
  detailedError.details = JSON.stringify(json.error || json);
622
665
  console.error(`[callAI:${PACKAGE_VERSION}] Throwing stream error:`, detailedError);
623
666
  throw detailedError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "call-ai",
3
- "version": "0.7.0-dev-preview-13",
3
+ "version": "0.7.0-dev-preview-14",
4
4
  "description": "Lightweight library for making AI API calls with streaming support",
5
5
  "main": "dist/index.js",
6
6
  "browser": "dist/index.js",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc",
46
- "test": "jest --testPathIgnorePatterns=\".*\\.integration\\.test\\.ts$\"",
46
+ "test": "jest --testPathIgnorePatterns=\".*\\.integration\\.(no-await\\.)?test\\.ts$\"",
47
47
  "test:integration": "jest --testMatch=\"**/*\\.integration\\.test\\.ts\"",
48
48
  "test:all": "jest",
49
49
  "typecheck": "tsc --noEmit",