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.
- package/dist/api.js +111 -68
- 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(
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
81
|
+
const hasJsonError = contentType.includes("application/json");
|
|
75
82
|
if (hasHttpError || hasJsonError) {
|
|
76
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
149
|
-
errorBody.
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 !==
|
|
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 ===
|
|
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 ===
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ===
|
|
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 =
|
|
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-
|
|
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",
|