call-ai 0.6.0 → 0.6.2

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 CHANGED
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.callAI = callAI;
4
4
  const strategies_1 = require("./strategies");
5
+ // Default fallback model when the primary model fails or is unavailable
6
+ const FALLBACK_MODEL = "openrouter/auto";
5
7
  /**
6
8
  * Make an AI API call with the given options
7
9
  * @param prompt User prompt as string or an array of message objects
@@ -60,6 +62,33 @@ function handleApiError(error, context) {
60
62
  message: `Sorry, I couldn't process that request: ${String(error)}`,
61
63
  });
62
64
  }
65
+ /**
66
+ * Helper to check if an error indicates invalid model and handle fallback
67
+ */
68
+ async function checkForInvalidModelError(response, model, isRetry, skipRetry = false) {
69
+ // Skip retry immediately if skipRetry is true
70
+ if (skipRetry || response.status !== 400 || isRetry) {
71
+ return { isInvalidModel: false };
72
+ }
73
+ // Clone the response so we can read the body
74
+ const clonedResponse = response.clone();
75
+ try {
76
+ const errorData = await clonedResponse.json();
77
+ // Check if the error message indicates an invalid model
78
+ if (errorData.error &&
79
+ errorData.error.message &&
80
+ errorData.error.message.toLowerCase().includes("not a valid model")) {
81
+ console.warn(`Model ${model} not valid, retrying with ${FALLBACK_MODEL}`);
82
+ return { isInvalidModel: true };
83
+ }
84
+ return { isInvalidModel: false, errorData };
85
+ }
86
+ catch (parseError) {
87
+ // If we can't parse the response as JSON, continue with original error
88
+ console.error("Failed to parse error response:", parseError);
89
+ return { isInvalidModel: false };
90
+ }
91
+ }
63
92
  /**
64
93
  * Prepare request parameters common to both streaming and non-streaming calls
65
94
  */
@@ -110,6 +139,8 @@ function prepareRequestParams(prompt, options) {
110
139
  method: "POST",
111
140
  headers: {
112
141
  Authorization: `Bearer ${apiKey}`,
142
+ "HTTP-Referer": "https://vibes.diy",
143
+ "X-Title": "Vibes",
113
144
  "Content-Type": "application/json",
114
145
  },
115
146
  body: JSON.stringify(requestParams),
@@ -119,10 +150,19 @@ function prepareRequestParams(prompt, options) {
119
150
  /**
120
151
  * Internal implementation for non-streaming API calls
121
152
  */
122
- async function callAINonStreaming(prompt, options = {}) {
153
+ async function callAINonStreaming(prompt, options = {}, isRetry = false) {
123
154
  try {
124
155
  const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, options);
125
156
  const response = await fetch(endpoint, requestOptions);
157
+ // Handle HTTP errors, with potential fallback for invalid model
158
+ if (!response.ok) {
159
+ const { isInvalidModel } = await checkForInvalidModelError(response, model, isRetry, options.skipRetry);
160
+ if (isInvalidModel) {
161
+ // Retry with fallback model
162
+ return callAINonStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
163
+ }
164
+ throw new Error(`HTTP error! Status: ${response.status}`);
165
+ }
126
166
  let result;
127
167
  // For Claude, use text() instead of json() to avoid potential hanging
128
168
  if (/claude/i.test(model)) {
@@ -139,6 +179,14 @@ async function callAINonStreaming(prompt, options = {}) {
139
179
  // Handle error responses
140
180
  if (result.error) {
141
181
  console.error("API returned an error:", result.error);
182
+ // If it's a model error and not already a retry, try with fallback
183
+ if (!isRetry &&
184
+ !options.skipRetry &&
185
+ result.error.message &&
186
+ result.error.message.toLowerCase().includes("not a valid model")) {
187
+ console.warn(`Model ${model} error, retrying with ${FALLBACK_MODEL}`);
188
+ return callAINonStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
189
+ }
142
190
  return JSON.stringify({
143
191
  error: result.error,
144
192
  message: result.error.message || "API returned an error",
@@ -220,11 +268,16 @@ async function extractClaudeResponse(response) {
220
268
  /**
221
269
  * Internal implementation for streaming API calls
222
270
  */
223
- async function* callAIStreaming(prompt, options = {}) {
271
+ async function* callAIStreaming(prompt, options = {}, isRetry = false) {
224
272
  try {
225
273
  const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
226
274
  const response = await fetch(endpoint, requestOptions);
227
275
  if (!response.ok) {
276
+ const { isInvalidModel } = await checkForInvalidModelError(response, model, isRetry, options.skipRetry);
277
+ if (isInvalidModel) {
278
+ // Retry with fallback model
279
+ return yield* callAIStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
280
+ }
228
281
  const errorText = await response.text();
229
282
  console.error(`API Error: ${response.status} ${response.statusText}`, errorText);
230
283
  throw new Error(`API returned error ${response.status}: ${response.statusText}`);
package/dist/types.d.ts CHANGED
@@ -90,6 +90,11 @@ export interface CallAIOptions {
90
90
  * Used for multimodal models that can generate images
91
91
  */
92
92
  modalities?: string[];
93
+ /**
94
+ * Whether to skip retry with fallback model when model errors occur
95
+ * Useful in testing and cases where retries should be suppressed
96
+ */
97
+ skipRetry?: boolean;
93
98
  /**
94
99
  * Any additional options to pass to the API
95
100
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "call-ai",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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",
@@ -47,6 +47,7 @@
47
47
  "test:integration": "jest --testMatch=\"**/test/integration.test.ts\" --testPathIgnorePatterns=''",
48
48
  "typecheck": "tsc --noEmit",
49
49
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
50
- "coverage": "jest --coverage"
50
+ "coverage": "jest --coverage",
51
+ "check": "npm run typecheck && npm run format && npm run test"
51
52
  }
52
53
  }