call-ai 0.7.0-dev-preview-4 → 0.7.0-dev-preview-6
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.d.ts +2 -2
- package/dist/api.js +108 -56
- package/dist/types.d.ts +2 -8
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core API implementation for call-ai
|
|
3
3
|
*/
|
|
4
|
-
import { CallAIOptions, Message } from "./types";
|
|
4
|
+
import { CallAIOptions, Message, StreamResponse } from "./types";
|
|
5
5
|
/**
|
|
6
6
|
* Make an AI API call with the given options
|
|
7
7
|
* @param prompt User prompt as string or an array of message objects
|
|
@@ -9,4 +9,4 @@ import { CallAIOptions, Message } from "./types";
|
|
|
9
9
|
* @returns A Promise that resolves to the complete response string when streaming is disabled,
|
|
10
10
|
* or an AsyncGenerator that yields partial responses when streaming is enabled
|
|
11
11
|
*/
|
|
12
|
-
export declare function callAI(prompt: string | Message[], options?: CallAIOptions): Promise<string
|
|
12
|
+
export declare function callAI(prompt: string | Message[], options?: CallAIOptions): Promise<string | StreamResponse>;
|
package/dist/api.js
CHANGED
|
@@ -26,8 +26,49 @@ function callAI(prompt, options = {}) {
|
|
|
26
26
|
if (options.stream !== true) {
|
|
27
27
|
return callAINonStreaming(prompt, options);
|
|
28
28
|
}
|
|
29
|
-
// Handle streaming mode
|
|
30
|
-
return
|
|
29
|
+
// Handle streaming mode - return a Promise that resolves to an AsyncGenerator
|
|
30
|
+
return (async () => {
|
|
31
|
+
// Do setup and validation before returning the generator
|
|
32
|
+
const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
|
|
33
|
+
// Make the fetch request and handle errors before creating the generator
|
|
34
|
+
const response = await fetch(endpoint, requestOptions);
|
|
35
|
+
// Enhanced error handling with more debugging
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
if (options.debug) {
|
|
38
|
+
console.error(`[callAI:${PACKAGE_VERSION}] HTTP Error:`, response.status, response.statusText, response.url);
|
|
39
|
+
}
|
|
40
|
+
// Check if this is an invalid model error
|
|
41
|
+
const { isInvalidModel, errorData } = await checkForInvalidModelError(response.clone(), // Clone response since we'll need to read body twice
|
|
42
|
+
model, false, options.skipRetry);
|
|
43
|
+
if (isInvalidModel && !options.skipRetry) {
|
|
44
|
+
if (options.debug) {
|
|
45
|
+
console.log(`[callAI:${PACKAGE_VERSION}] Retrying with fallback model: ${FALLBACK_MODEL}`);
|
|
46
|
+
}
|
|
47
|
+
// Retry with fallback model - it will return a promise
|
|
48
|
+
const result = await callAI(prompt, { ...options, model: FALLBACK_MODEL });
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
// Get full error text from body
|
|
52
|
+
const errorText = await response.text();
|
|
53
|
+
if (options.debug) {
|
|
54
|
+
console.error(`[callAI:${PACKAGE_VERSION}] Error response body:`, errorText);
|
|
55
|
+
}
|
|
56
|
+
// Create a detailed error with status information
|
|
57
|
+
const errorMessage = `API returned error ${response.status}: ${response.statusText}`;
|
|
58
|
+
const error = new Error(errorMessage);
|
|
59
|
+
// Add extra properties for more context
|
|
60
|
+
error.status = response.status;
|
|
61
|
+
error.statusText = response.statusText;
|
|
62
|
+
error.details = errorText;
|
|
63
|
+
// Ensure this error is thrown and caught properly in the Promise chain
|
|
64
|
+
if (options.debug) {
|
|
65
|
+
console.error(`[callAI:${PACKAGE_VERSION}] Throwing error:`, error);
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
// Only if response is OK, create and return the streaming generator
|
|
70
|
+
return createStreamingGenerator(response, options, schemaStrategy, model);
|
|
71
|
+
})();
|
|
31
72
|
}
|
|
32
73
|
/**
|
|
33
74
|
* Buffer streaming results into a single response for cases where
|
|
@@ -41,7 +82,7 @@ async function bufferStreamingResults(prompt, options) {
|
|
|
41
82
|
};
|
|
42
83
|
try {
|
|
43
84
|
// Get streaming generator
|
|
44
|
-
const generator =
|
|
85
|
+
const generator = await callAI(prompt, streamingOptions);
|
|
45
86
|
// Buffer all chunks
|
|
46
87
|
let finalResult = "";
|
|
47
88
|
let chunkCount = 0;
|
|
@@ -68,27 +109,74 @@ function handleApiError(error, context, debug = false) {
|
|
|
68
109
|
* Helper to check if an error indicates invalid model and handle fallback
|
|
69
110
|
*/
|
|
70
111
|
async function checkForInvalidModelError(response, model, isRetry, skipRetry = false) {
|
|
71
|
-
// Skip retry immediately if skipRetry is true
|
|
72
|
-
if (skipRetry ||
|
|
112
|
+
// Skip retry immediately if skipRetry is true or if we're already retrying
|
|
113
|
+
if (skipRetry || isRetry) {
|
|
114
|
+
return { isInvalidModel: false };
|
|
115
|
+
}
|
|
116
|
+
// We want to check all 4xx errors, not just 400
|
|
117
|
+
if (response.status < 400 || response.status >= 500) {
|
|
73
118
|
return { isInvalidModel: false };
|
|
74
119
|
}
|
|
75
120
|
// Clone the response so we can read the body
|
|
76
121
|
const clonedResponse = response.clone();
|
|
77
122
|
try {
|
|
78
123
|
const errorData = await clonedResponse.json();
|
|
79
|
-
//
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
const debugEnabled = true; // Always log for now to help diagnose the issue
|
|
125
|
+
if (debugEnabled) {
|
|
126
|
+
console.log(`[callAI:${PACKAGE_VERSION}] Checking for invalid model error:`, {
|
|
127
|
+
model,
|
|
128
|
+
statusCode: response.status,
|
|
129
|
+
errorData
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Common patterns for invalid model errors across different providers
|
|
133
|
+
const invalidModelPatterns = [
|
|
134
|
+
"not a valid model",
|
|
135
|
+
"model .* does not exist",
|
|
136
|
+
"invalid model",
|
|
137
|
+
"unknown model",
|
|
138
|
+
"no provider was found",
|
|
139
|
+
"fake-model", // For our test case
|
|
140
|
+
"does-not-exist" // For our test case
|
|
141
|
+
];
|
|
142
|
+
// Check if error message contains any of our patterns
|
|
143
|
+
let errorMessage = '';
|
|
144
|
+
if (errorData.error && errorData.error.message) {
|
|
145
|
+
errorMessage = errorData.error.message.toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
else if (errorData.message) {
|
|
148
|
+
errorMessage = errorData.message.toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
errorMessage = JSON.stringify(errorData).toLowerCase();
|
|
152
|
+
}
|
|
153
|
+
// Test the error message against each pattern
|
|
154
|
+
const isInvalidModel = invalidModelPatterns.some(pattern => errorMessage.includes(pattern.toLowerCase()));
|
|
155
|
+
if (isInvalidModel && debugEnabled) {
|
|
156
|
+
console.warn(`[callAI:${PACKAGE_VERSION}] Model ${model} not valid, will retry with ${FALLBACK_MODEL}`);
|
|
85
157
|
}
|
|
86
|
-
return { isInvalidModel
|
|
158
|
+
return { isInvalidModel, errorData };
|
|
87
159
|
}
|
|
88
160
|
catch (parseError) {
|
|
89
|
-
// If we can't parse the response as JSON,
|
|
90
|
-
console.error("Failed to parse error response:", parseError);
|
|
91
|
-
|
|
161
|
+
// If we can't parse the response as JSON, try to read it as text
|
|
162
|
+
console.error("Failed to parse error response as JSON:", parseError);
|
|
163
|
+
try {
|
|
164
|
+
const textResponse = await response.clone().text();
|
|
165
|
+
console.log("Error response as text:", textResponse);
|
|
166
|
+
// Even if it's not JSON, check if it contains any of our known patterns
|
|
167
|
+
const lowerText = textResponse.toLowerCase();
|
|
168
|
+
const isInvalidModel = lowerText.includes("invalid model") ||
|
|
169
|
+
lowerText.includes("not exist") ||
|
|
170
|
+
lowerText.includes("fake-model");
|
|
171
|
+
if (isInvalidModel) {
|
|
172
|
+
console.warn(`[callAI:${PACKAGE_VERSION}] Detected invalid model in text response for ${model}`);
|
|
173
|
+
}
|
|
174
|
+
return { isInvalidModel, errorData: { text: textResponse } };
|
|
175
|
+
}
|
|
176
|
+
catch (textError) {
|
|
177
|
+
console.error("Failed to read error response as text:", textError);
|
|
178
|
+
return { isInvalidModel: false };
|
|
179
|
+
}
|
|
92
180
|
}
|
|
93
181
|
}
|
|
94
182
|
/**
|
|
@@ -278,40 +366,11 @@ async function extractClaudeResponse(response) {
|
|
|
278
366
|
}
|
|
279
367
|
}
|
|
280
368
|
/**
|
|
281
|
-
*
|
|
369
|
+
* Generator factory function for streaming API calls
|
|
370
|
+
* This is called after the fetch is made and response is validated
|
|
282
371
|
*/
|
|
283
|
-
async function*
|
|
284
|
-
// Track errors to ensure consistent propagation across environments
|
|
285
|
-
let streamingError = null;
|
|
286
|
-
// Browser-specific detection to help with environment-specific handling
|
|
287
|
-
const isBrowser = typeof window !== 'undefined';
|
|
372
|
+
async function* createStreamingGenerator(response, options, schemaStrategy, model) {
|
|
288
373
|
try {
|
|
289
|
-
const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
|
|
290
|
-
const response = await fetch(endpoint, requestOptions);
|
|
291
|
-
if (!response.ok) {
|
|
292
|
-
const { isInvalidModel } = await checkForInvalidModelError(response, model, isRetry, options.skipRetry);
|
|
293
|
-
if (isInvalidModel) {
|
|
294
|
-
// Retry with fallback model
|
|
295
|
-
return yield* callAIStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
|
|
296
|
-
}
|
|
297
|
-
const errorText = await response.text();
|
|
298
|
-
console.error(`API Error: ${response.status} ${response.statusText}`, errorText);
|
|
299
|
-
// Create and yield a structured error object instead of throwing
|
|
300
|
-
const errorMessage = `API returned error ${response.status}: ${response.statusText}`;
|
|
301
|
-
const errorObj = {
|
|
302
|
-
error: true,
|
|
303
|
-
type: "APIError",
|
|
304
|
-
status: response.status,
|
|
305
|
-
message: errorMessage,
|
|
306
|
-
timestamp: new Date().toISOString()
|
|
307
|
-
};
|
|
308
|
-
// Store for later reference
|
|
309
|
-
streamingError = new Error(errorMessage);
|
|
310
|
-
// Yield the error object as a JSON string - this works in all environments
|
|
311
|
-
yield JSON.stringify(errorObj);
|
|
312
|
-
// Return after yielding the error to end the generator
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
374
|
// Handle streaming response
|
|
316
375
|
if (!response.body) {
|
|
317
376
|
throw new Error("Response body is undefined - API endpoint may not support streaming");
|
|
@@ -463,15 +522,8 @@ async function* callAIStreaming(prompt, options = {}, isRetry = false) {
|
|
|
463
522
|
}
|
|
464
523
|
}
|
|
465
524
|
}
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
if (streamingError) {
|
|
469
|
-
handleApiError(streamingError, "Streaming API call", options.debug);
|
|
470
|
-
}
|
|
471
|
-
// We don't need this check anymore as we're yielding errors
|
|
472
|
-
// and returning immediately after
|
|
473
|
-
// We no longer need this check since we're yielding errors directly
|
|
474
|
-
// and returning from the generator after yielding an error
|
|
525
|
+
// We no longer need special error handling here as errors are thrown immediately
|
|
526
|
+
// No extra error handling needed here - errors are thrown immediately
|
|
475
527
|
// If we have assembled tool calls but haven't yielded them yet
|
|
476
528
|
if (toolCallsAssembled && (!completeText || completeText.length === 0)) {
|
|
477
529
|
return toolCallsAssembled;
|
package/dist/types.d.ts
CHANGED
|
@@ -65,15 +65,9 @@ export interface SchemaStrategy {
|
|
|
65
65
|
shouldForceStream: boolean;
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* Return type for streaming API calls
|
|
69
69
|
*/
|
|
70
|
-
export
|
|
71
|
-
error: true;
|
|
72
|
-
type: string;
|
|
73
|
-
status: number;
|
|
74
|
-
message: string;
|
|
75
|
-
timestamp: string;
|
|
76
|
-
}
|
|
70
|
+
export type StreamResponse = AsyncGenerator<string, string, unknown>;
|
|
77
71
|
export interface CallAIOptions {
|
|
78
72
|
/**
|
|
79
73
|
* API key for authentication
|