call-ai 0.7.0-dev-preview-5 → 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 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> | AsyncGenerator<string, string | undefined, unknown>;
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 callAIStreaming(prompt, options);
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 = callAIStreaming(prompt, streamingOptions);
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 || response.status !== 400 || isRetry) {
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
- // Check if the error message indicates an invalid model
80
- if (errorData.error &&
81
- errorData.error.message &&
82
- errorData.error.message.toLowerCase().includes("not a valid model")) {
83
- console.warn(`Model ${model} not valid, retrying with ${FALLBACK_MODEL}`);
84
- return { isInvalidModel: true };
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
+ });
85
131
  }
86
- return { isInvalidModel: false, errorData };
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}`);
157
+ }
158
+ return { isInvalidModel, errorData };
87
159
  }
88
160
  catch (parseError) {
89
- // If we can't parse the response as JSON, continue with original error
90
- console.error("Failed to parse error response:", parseError);
91
- return { isInvalidModel: false };
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,30 +366,11 @@ async function extractClaudeResponse(response) {
278
366
  }
279
367
  }
280
368
  /**
281
- * Internal implementation for streaming API calls
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* callAIStreaming(prompt, options = {}, isRetry = false) {
372
+ async function* createStreamingGenerator(response, options, schemaStrategy, model) {
284
373
  try {
285
- const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
286
- const response = await fetch(endpoint, requestOptions);
287
- if (!response.ok) {
288
- const { isInvalidModel } = await checkForInvalidModelError(response, model, isRetry, options.skipRetry);
289
- if (isInvalidModel) {
290
- // Retry with fallback model
291
- return yield* callAIStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
292
- }
293
- const errorText = await response.text();
294
- console.error(`API Error: ${response.status} ${response.statusText}`, errorText);
295
- // Create a detailed error with status information
296
- const errorMessage = `API returned error ${response.status}: ${response.statusText}`;
297
- const error = new Error(errorMessage);
298
- // Add extra properties for more context
299
- error.status = response.status;
300
- error.statusText = response.statusText;
301
- error.details = errorText;
302
- // Throw immediately - we want this to propagate correctly in all environments
303
- throw error;
304
- }
305
374
  // Handle streaming response
306
375
  if (!response.body) {
307
376
  throw new Error("Response body is undefined - API endpoint may not support streaming");
package/dist/types.d.ts CHANGED
@@ -64,6 +64,10 @@ export interface SchemaStrategy {
64
64
  processResponse: ModelStrategy["processResponse"];
65
65
  shouldForceStream: boolean;
66
66
  }
67
+ /**
68
+ * Return type for streaming API calls
69
+ */
70
+ export type StreamResponse = AsyncGenerator<string, string, unknown>;
67
71
  export interface CallAIOptions {
68
72
  /**
69
73
  * API key for authentication
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "call-ai",
3
- "version": "0.7.0-dev-preview-5",
3
+ "version": "0.7.0-dev-preview-6",
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",