call-ai 0.8.3 → 0.8.5-dev-preview

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.
@@ -0,0 +1,12 @@
1
+ declare function handleApiError(error: any, context: string, debug?: boolean, options?: {
2
+ apiKey?: string;
3
+ endpoint?: string;
4
+ skipRefresh?: boolean;
5
+ refreshToken?: string;
6
+ updateRefreshToken?: (currentToken: string) => Promise<string>;
7
+ }): Promise<void>;
8
+ declare function checkForInvalidModelError(response: Response, model: string, debug?: boolean): Promise<{
9
+ isInvalidModel: boolean;
10
+ errorData?: any;
11
+ }>;
12
+ export { handleApiError, checkForInvalidModelError };
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleApiError = handleApiError;
4
+ exports.checkForInvalidModelError = checkForInvalidModelError;
5
+ /**
6
+ * Error handling utilities for call-ai
7
+ */
8
+ const key_management_1 = require("./key-management");
9
+ // Standardized API error handler
10
+ // @param error The error object
11
+ // @param context Context description for error messages
12
+ // @param debug Whether to log debug information
13
+ // @param options Options for error handling including key refresh control
14
+ async function handleApiError(error, context, debug = key_management_1.globalDebug, options = {}) {
15
+ // Extract error details
16
+ const errorMessage = error?.message || String(error);
17
+ const status = error?.status ||
18
+ error?.statusCode ||
19
+ error?.response?.status ||
20
+ (errorMessage.match(/status: (\d+)/i)?.[1] &&
21
+ parseInt(errorMessage.match(/status: (\d+)/i)[1]));
22
+ if (debug) {
23
+ console.error(`[callAI:error] ${context} error:`, {
24
+ message: errorMessage,
25
+ status,
26
+ name: error?.name,
27
+ cause: error?.cause,
28
+ });
29
+ }
30
+ // Don't attempt API key refresh if explicitly skipped
31
+ if (options.skipRefresh) {
32
+ throw error;
33
+ }
34
+ // Determine if this error suggests we need a new API key
35
+ const needsNewKey = (0, key_management_1.isNewKeyError)(error, debug);
36
+ // If the error suggests an API key issue, try to refresh the key
37
+ if (needsNewKey) {
38
+ if (debug) {
39
+ console.log(`[callAI:key-refresh] Error suggests API key issue, attempting refresh...`);
40
+ }
41
+ try {
42
+ // Use provided key/endpoint/refreshToken or fallback to global configuration
43
+ const currentKey = options.apiKey || key_management_1.keyStore.current;
44
+ const endpoint = options.endpoint || key_management_1.keyStore.refreshEndpoint;
45
+ let refreshToken = options.refreshToken || key_management_1.keyStore.refreshToken;
46
+ // First attempt to refresh the API key
47
+ try {
48
+ const { apiKey, topup } = await (0, key_management_1.refreshApiKey)(currentKey, endpoint, refreshToken, debug);
49
+ // Update the key in the store (if not already set by refreshApiKey)
50
+ if (key_management_1.keyStore.current !== apiKey) {
51
+ key_management_1.keyStore.current = apiKey;
52
+ }
53
+ if (debug) {
54
+ console.log(`[callAI:key-refresh] ${topup ? "Topped up" : "Refreshed"} API key successfully`);
55
+ }
56
+ // Return without throwing since we've successfully recovered
57
+ return;
58
+ }
59
+ catch (initialRefreshError) {
60
+ // If there's an updateRefreshToken callback and the error was due to token issue
61
+ if (options.updateRefreshToken && refreshToken) {
62
+ if (debug) {
63
+ console.log(`[callAI:key-refresh] Initial refresh failed, attempting to update refresh token`);
64
+ }
65
+ try {
66
+ // Get a new refresh token using the callback
67
+ const newRefreshToken = await options.updateRefreshToken(refreshToken);
68
+ if (newRefreshToken && newRefreshToken !== refreshToken) {
69
+ if (debug) {
70
+ console.log(`[callAI:key-refresh] Got new refresh token, retrying key refresh`);
71
+ }
72
+ // Update the stored refresh token
73
+ key_management_1.keyStore.refreshToken = newRefreshToken;
74
+ // Try again with the new token
75
+ const { apiKey, topup } = await (0, key_management_1.refreshApiKey)(currentKey, endpoint, newRefreshToken, debug);
76
+ // Update the key in the store
77
+ if (key_management_1.keyStore.current !== apiKey) {
78
+ key_management_1.keyStore.current = apiKey;
79
+ }
80
+ if (debug) {
81
+ console.log(`[callAI:key-refresh] ${topup ? "Topped up" : "Refreshed"} API key successfully with new refresh token`);
82
+ }
83
+ // Return without throwing since we've successfully recovered
84
+ return;
85
+ }
86
+ else {
87
+ if (debug) {
88
+ console.log(`[callAI:key-refresh] No new refresh token provided or same token returned, cannot retry`);
89
+ }
90
+ // Continue to error handling
91
+ throw initialRefreshError;
92
+ }
93
+ }
94
+ catch (tokenUpdateError) {
95
+ if (debug) {
96
+ console.error(`[callAI:key-refresh] Failed to update refresh token:`, tokenUpdateError);
97
+ }
98
+ // Continue to error handling with the original refresh error
99
+ throw initialRefreshError;
100
+ }
101
+ }
102
+ else {
103
+ // No updateRefreshToken callback or no token, rethrow the initial error
104
+ throw initialRefreshError;
105
+ }
106
+ }
107
+ }
108
+ catch (refreshError) {
109
+ // Log refresh failure but throw the original error
110
+ if (debug) {
111
+ console.error(`[callAI:key-refresh] API key refresh failed:`, refreshError);
112
+ }
113
+ // Create a more detailed error from the original one
114
+ const detailedError = new Error(`${errorMessage} (Key refresh failed: ${refreshError instanceof Error ? refreshError.message : String(refreshError)})`);
115
+ // Preserve error metadata from the original error
116
+ detailedError.originalError = error;
117
+ detailedError.refreshError = refreshError;
118
+ detailedError.status = status || 401;
119
+ throw detailedError;
120
+ }
121
+ }
122
+ // For non-key errors, create a detailed error object
123
+ const detailedError = new Error(`${context}: ${errorMessage}`);
124
+ detailedError.originalError = error;
125
+ detailedError.status = status || 500;
126
+ detailedError.errorType = error?.name || "Error";
127
+ throw detailedError;
128
+ }
129
+ // Helper to check if an error indicates invalid model and handle fallback
130
+ async function checkForInvalidModelError(response, model, debug = key_management_1.globalDebug) {
131
+ // Only check 4xx errors (which could indicate invalid model)
132
+ if (response.status < 400 || response.status >= 500) {
133
+ return { isInvalidModel: false };
134
+ }
135
+ // Clone the response so we can still use the original later if needed
136
+ const responseClone = response.clone();
137
+ // Try to parse the response as JSON
138
+ let errorData;
139
+ try {
140
+ errorData = await responseClone.json();
141
+ }
142
+ catch (e) {
143
+ // If it's not JSON, get the text
144
+ try {
145
+ const text = await responseClone.text();
146
+ errorData = { error: text };
147
+ }
148
+ catch (textError) {
149
+ errorData = { error: `Error ${response.status}: ${response.statusText}` };
150
+ }
151
+ }
152
+ // Check if the error indicates an invalid model
153
+ const isInvalidModelError =
154
+ // Status checks
155
+ response.status === 404 ||
156
+ response.status === 400 ||
157
+ // Response content checks
158
+ (errorData &&
159
+ ((typeof errorData.error === "string" &&
160
+ (errorData.error.toLowerCase().includes("model") ||
161
+ errorData.error.toLowerCase().includes("engine") ||
162
+ errorData.error.toLowerCase().includes("not found") ||
163
+ errorData.error.toLowerCase().includes("invalid") ||
164
+ errorData.error.toLowerCase().includes("unavailable"))) ||
165
+ (errorData.error?.message &&
166
+ typeof errorData.error.message === "string" &&
167
+ (errorData.error.message.toLowerCase().includes("model") ||
168
+ errorData.error.message.toLowerCase().includes("engine") ||
169
+ errorData.error.message.toLowerCase().includes("not found") ||
170
+ errorData.error.message.toLowerCase().includes("invalid") ||
171
+ errorData.error.message.toLowerCase().includes("unavailable")))));
172
+ if (debug && isInvalidModelError) {
173
+ console.log(`[callAI:model-fallback] Detected invalid model error for "${model}":`, errorData);
174
+ }
175
+ return { isInvalidModel: isInvalidModelError, errorData };
176
+ }
package/dist/image.js CHANGED
@@ -19,18 +19,21 @@ async function imageGen(prompt, options = {}) {
19
19
  // Get custom origin if set
20
20
  const customOrigin = options.imgUrl ||
21
21
  (typeof window !== "undefined" ? window.CALLAI_IMG_URL : null) ||
22
- (typeof process !== "undefined" && process.env ? process.env.CALLAI_IMG_URL : null);
22
+ (typeof process !== "undefined" && process.env
23
+ ? process.env.CALLAI_IMG_URL
24
+ : null);
23
25
  try {
24
26
  // Handle image generation
25
27
  if (!options.images || options.images.length === 0) {
26
28
  // Simple image generation with text prompt
27
29
  // Use custom origin or document.location.origin
28
- const origin = customOrigin || (typeof document !== 'undefined' ? document.location.origin : '');
30
+ const origin = customOrigin ||
31
+ (typeof document !== "undefined" ? document.location.origin : "");
29
32
  const generateEndpoint = `${origin}/api/openai-image/generate`;
30
33
  const response = await fetch(generateEndpoint, {
31
34
  method: "POST",
32
35
  headers: {
33
- "Authorization": `Bearer ${apiKey}`,
36
+ Authorization: `Bearer ${apiKey}`,
34
37
  "Content-Type": "application/json",
35
38
  },
36
39
  body: JSON.stringify({
@@ -65,12 +68,13 @@ async function imageGen(prompt, options = {}) {
65
68
  if (options.style)
66
69
  formData.append("style", options.style);
67
70
  // Use custom origin or document.location.origin
68
- const origin = customOrigin || (typeof document !== 'undefined' ? document.location.origin : '');
71
+ const origin = customOrigin ||
72
+ (typeof document !== "undefined" ? document.location.origin : "");
69
73
  const editEndpoint = `${origin}/api/openai-image/edit`;
70
74
  const response = await fetch(editEndpoint, {
71
75
  method: "POST",
72
76
  headers: {
73
- "Authorization": `Bearer ${apiKey}`,
77
+ Authorization: `Bearer ${apiKey}`,
74
78
  },
75
79
  body: formData,
76
80
  });
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * call-ai: A lightweight library for making AI API calls
3
3
  */
4
4
  export * from "./types";
5
- export { callAI } from "./api";
5
+ export { callAI, getMeta } from "./api";
6
6
  export { imageGen } from "./image";
7
7
  export * from "./strategies";
8
8
  export * from "./utils";
package/dist/index.js CHANGED
@@ -17,12 +17,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
17
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.imageGen = exports.callAI = void 0;
20
+ exports.imageGen = exports.getMeta = exports.callAI = void 0;
21
21
  // Export public types
22
22
  __exportStar(require("./types"), exports);
23
- // Export API function
23
+ // Export API functions
24
24
  var api_1 = require("./api");
25
25
  Object.defineProperty(exports, "callAI", { enumerable: true, get: function () { return api_1.callAI; } });
26
+ Object.defineProperty(exports, "getMeta", { enumerable: true, get: function () { return api_1.getMeta; } });
26
27
  // Export image generation function
27
28
  var image_1 = require("./image");
28
29
  Object.defineProperty(exports, "imageGen", { enumerable: true, get: function () { return image_1.imageGen; } });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Key management functionality for call-ai
3
+ */
4
+ declare const keyStore: {
5
+ current: string | null;
6
+ refreshEndpoint: string | null;
7
+ refreshToken: string | null;
8
+ isRefreshing: boolean;
9
+ lastRefreshAttempt: number;
10
+ metadata: Record<string, any>;
11
+ };
12
+ declare let globalDebug: boolean;
13
+ /**
14
+ * Initialize key store with environment variables
15
+ */
16
+ declare function initKeyStore(): void;
17
+ /**
18
+ * Check if an error indicates we need a new API key
19
+ * @param error The error to check
20
+ * @param debug Whether to log debug information
21
+ * @returns True if the error suggests we need a new key
22
+ */
23
+ declare function isNewKeyError(error: any, debug?: boolean): boolean;
24
+ /**
25
+ * Refreshes the API key by calling the specified endpoint
26
+ * @param currentKey The current API key (may be null for initial key request)
27
+ * @param endpoint The endpoint to call for key refresh
28
+ * @param refreshToken Authentication token for the refresh endpoint
29
+ * @returns Object containing the API key and topup flag
30
+ */
31
+ declare function refreshApiKey(currentKey: string | null, endpoint: string | null, refreshToken: string | null, debug?: boolean): Promise<{
32
+ apiKey: string;
33
+ topup: boolean;
34
+ }>;
35
+ /**
36
+ * Helper function to extract hash from key (implementation depends on how you store metadata)
37
+ */
38
+ declare function getHashFromKey(key: string): string | null;
39
+ /**
40
+ * Helper function to store key metadata for future reference
41
+ */
42
+ declare function storeKeyMetadata(data: any): void;
43
+ export { keyStore, globalDebug, initKeyStore, isNewKeyError, refreshApiKey, getHashFromKey, storeKeyMetadata, };
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ /**
3
+ * Key management functionality for call-ai
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.globalDebug = exports.keyStore = void 0;
7
+ exports.initKeyStore = initKeyStore;
8
+ exports.isNewKeyError = isNewKeyError;
9
+ exports.refreshApiKey = refreshApiKey;
10
+ exports.getHashFromKey = getHashFromKey;
11
+ exports.storeKeyMetadata = storeKeyMetadata;
12
+ // Internal key store to keep track of the latest key
13
+ const keyStore = {
14
+ // Default key from environment or config
15
+ current: null,
16
+ // The refresh endpoint URL - defaults to vibecode.garden
17
+ refreshEndpoint: "https://vibecode.garden",
18
+ // Authentication token for refresh endpoint - defaults to use-vibes
19
+ refreshToken: "use-vibes",
20
+ // Flag to prevent concurrent refresh attempts
21
+ isRefreshing: false,
22
+ // Timestamp of last refresh attempt (to prevent too frequent refreshes)
23
+ lastRefreshAttempt: 0,
24
+ // Storage for key metadata (useful for future top-up implementation)
25
+ metadata: {},
26
+ };
27
+ exports.keyStore = keyStore;
28
+ // Global debug flag
29
+ let globalDebug = false;
30
+ exports.globalDebug = globalDebug;
31
+ /**
32
+ * Initialize key store with environment variables
33
+ */
34
+ function initKeyStore() {
35
+ // Initialize with environment variables if available
36
+ if (typeof process !== "undefined" && process.env) {
37
+ if (process.env.CALLAI_API_KEY) {
38
+ keyStore.current = process.env.CALLAI_API_KEY;
39
+ }
40
+ // Support both CALLAI_REFRESH_ENDPOINT and CALLAI_REKEY_ENDPOINT for backward compatibility
41
+ if (process.env.CALLAI_REFRESH_ENDPOINT) {
42
+ keyStore.refreshEndpoint = process.env.CALLAI_REFRESH_ENDPOINT;
43
+ }
44
+ else if (process.env.CALLAI_REKEY_ENDPOINT) {
45
+ keyStore.refreshEndpoint = process.env.CALLAI_REKEY_ENDPOINT;
46
+ }
47
+ else {
48
+ // Default to vibecode.garden if not specified
49
+ keyStore.refreshEndpoint = "https://vibecode.garden";
50
+ }
51
+ // Support both CALL_AI_REFRESH_TOKEN and CALL_AI_KEY_TOKEN for backward compatibility
52
+ if (process.env.CALL_AI_REFRESH_TOKEN) {
53
+ keyStore.refreshToken = process.env.CALL_AI_REFRESH_TOKEN;
54
+ }
55
+ else if (process.env.CALL_AI_KEY_TOKEN) {
56
+ keyStore.refreshToken = process.env.CALL_AI_KEY_TOKEN;
57
+ }
58
+ else {
59
+ // Default to use-vibes if not specified - this is the default token for vibecode.garden
60
+ keyStore.refreshToken = "use-vibes";
61
+ }
62
+ // Check for CALLAI_DEBUG environment variable (any truthy value works)
63
+ if (process.env.CALLAI_DEBUG) {
64
+ // Set the global debug flag
65
+ exports.globalDebug = globalDebug = true;
66
+ }
67
+ }
68
+ // Initialize from window globals if in browser context
69
+ else if (typeof window !== "undefined") {
70
+ // Use window.CALLAI_API_KEY or window.callAI.API_KEY if available
71
+ if (window.CALLAI_API_KEY) {
72
+ keyStore.current = window.CALLAI_API_KEY;
73
+ }
74
+ else if (window.callAI?.API_KEY) {
75
+ keyStore.current = window.callAI.API_KEY;
76
+ }
77
+ // Check for debug flag in browser environment
78
+ if (window.CALLAI_DEBUG) {
79
+ exports.globalDebug = globalDebug = true;
80
+ }
81
+ keyStore.refreshEndpoint =
82
+ window.CALLAI_REFRESH_ENDPOINT || keyStore.refreshEndpoint;
83
+ keyStore.refreshToken =
84
+ window.CALL_AI_REFRESH_TOKEN || keyStore.refreshToken;
85
+ }
86
+ }
87
+ // Initialize on module load
88
+ initKeyStore();
89
+ /**
90
+ * Check if an error indicates we need a new API key
91
+ * @param error The error to check
92
+ * @param debug Whether to log debug information
93
+ * @returns True if the error suggests we need a new key
94
+ */
95
+ function isNewKeyError(error, debug = false) {
96
+ // Extract status from error object or message text
97
+ let status = error?.status || error?.statusCode || error?.response?.status;
98
+ const errorMessage = String(error || "").toLowerCase();
99
+ // Extract status code from error message if not found in the object properties
100
+ // Handle messages like "HTTP error! Status: 403" common in fetch errors
101
+ if (!status && errorMessage.includes("status:")) {
102
+ const statusMatch = errorMessage.match(/status:\\s*(\\d+)/i);
103
+ if (statusMatch && statusMatch[1]) {
104
+ status = parseInt(statusMatch[1], 10);
105
+ }
106
+ }
107
+ const is4xx = status >= 400 && status < 500;
108
+ // Check for various error types that indicate key issues
109
+ const isAuthError = status === 401 ||
110
+ status === 403 ||
111
+ errorMessage.includes("unauthorized") ||
112
+ errorMessage.includes("forbidden") ||
113
+ errorMessage.includes("authentication") ||
114
+ errorMessage.includes("api key") ||
115
+ errorMessage.includes("apikey") ||
116
+ errorMessage.includes("auth");
117
+ // More specific message checks, especially for common API providers
118
+ const isInvalidKeyError = errorMessage.includes("invalid api key") ||
119
+ errorMessage.includes("invalid key") ||
120
+ errorMessage.includes("incorrect api key") ||
121
+ errorMessage.includes("incorrect key") ||
122
+ errorMessage.includes("authentication failed") ||
123
+ errorMessage.includes("not authorized");
124
+ // Check for OpenAI specific error patterns
125
+ const isOpenAIKeyError = errorMessage.includes("openai") &&
126
+ (errorMessage.includes("api key") ||
127
+ errorMessage.includes("authentication"));
128
+ // Check for rate limit errors which might indicate a key top-up is needed
129
+ const isRateLimitError = status === 429 ||
130
+ errorMessage.includes("rate limit") ||
131
+ errorMessage.includes("too many requests") ||
132
+ errorMessage.includes("quota") ||
133
+ errorMessage.includes("exceed");
134
+ // Check for billing or payment errors
135
+ const isBillingError = errorMessage.includes("billing") ||
136
+ errorMessage.includes("payment") ||
137
+ errorMessage.includes("subscription") ||
138
+ errorMessage.includes("account");
139
+ // Simple heuristic: if it's a 4xx error with any key-related terms, likely needs key refresh
140
+ const needsNewKey = is4xx &&
141
+ (isAuthError ||
142
+ isInvalidKeyError ||
143
+ isOpenAIKeyError ||
144
+ isRateLimitError ||
145
+ isBillingError);
146
+ if (debug && needsNewKey) {
147
+ console.log(`[callAI:key-refresh] Detected error requiring key refresh: ${errorMessage}`);
148
+ }
149
+ return needsNewKey;
150
+ }
151
+ /**
152
+ * Refreshes the API key by calling the specified endpoint
153
+ * @param currentKey The current API key (may be null for initial key request)
154
+ * @param endpoint The endpoint to call for key refresh
155
+ * @param refreshToken Authentication token for the refresh endpoint
156
+ * @returns Object containing the API key and topup flag
157
+ */
158
+ async function refreshApiKey(currentKey, endpoint, refreshToken, debug = globalDebug) {
159
+ // Ensure we have an endpoint and refreshToken
160
+ if (!endpoint) {
161
+ throw new Error("No API key refresh endpoint specified");
162
+ }
163
+ if (!refreshToken) {
164
+ throw new Error("No API key refresh token specified");
165
+ }
166
+ // Check if we're already in the process of refreshing (to prevent parallel refreshes)
167
+ if (keyStore.isRefreshing) {
168
+ if (debug) {
169
+ console.log("API key refresh already in progress, waiting...");
170
+ }
171
+ // Wait for refresh to complete (simple polling)
172
+ return new Promise((resolve) => {
173
+ const checkInterval = setInterval(() => {
174
+ if (!keyStore.isRefreshing && keyStore.current) {
175
+ clearInterval(checkInterval);
176
+ resolve({ apiKey: keyStore.current, topup: false });
177
+ }
178
+ }, 100);
179
+ });
180
+ }
181
+ // Rate limit key refresh to prevent overloading the service
182
+ const now = Date.now();
183
+ const timeSinceLastRefresh = now - keyStore.lastRefreshAttempt;
184
+ const minRefreshInterval = 2000; // 2 seconds minimum interval between refreshes
185
+ if (timeSinceLastRefresh < minRefreshInterval) {
186
+ if (debug) {
187
+ console.log(`Rate limiting key refresh, last attempt was ${timeSinceLastRefresh}ms ago`);
188
+ }
189
+ // If we've refreshed too recently, wait a bit
190
+ await new Promise((resolve) => setTimeout(resolve, minRefreshInterval - timeSinceLastRefresh));
191
+ }
192
+ // Set refreshing flag and update last attempt timestamp
193
+ keyStore.isRefreshing = true;
194
+ keyStore.lastRefreshAttempt = Date.now();
195
+ // Process API paths
196
+ let apiPath = "/api/keys";
197
+ // Normalize endpoint URL to remove any trailing slashes
198
+ const baseUrl = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
199
+ // Construct the full URL
200
+ const url = `${baseUrl}${apiPath}`;
201
+ if (debug) {
202
+ console.log(`Refreshing API key from: ${url}`);
203
+ }
204
+ try {
205
+ // Request payload
206
+ const requestPayload = {
207
+ key: currentKey,
208
+ hash: currentKey ? getHashFromKey(currentKey) : null,
209
+ name: "call-ai-client", // Add the required name field
210
+ };
211
+ if (debug) {
212
+ console.log(`[callAI:key-refresh] Request URL: ${url}`);
213
+ console.log(`[callAI:key-refresh] Request headers:`, {
214
+ "Content-Type": "application/json",
215
+ Authorization: `Bearer ${refreshToken}`,
216
+ });
217
+ console.log(`[callAI:key-refresh] Request payload:`, requestPayload);
218
+ }
219
+ // Make the request
220
+ const response = await fetch(url, {
221
+ method: "POST",
222
+ headers: {
223
+ "Content-Type": "application/json",
224
+ Authorization: `Bearer ${refreshToken}`,
225
+ },
226
+ body: JSON.stringify(requestPayload),
227
+ });
228
+ if (debug) {
229
+ console.log(`[callAI:key-refresh] Response status: ${response.status} ${response.statusText}`);
230
+ console.log(`[callAI:key-refresh] Response headers:`, Object.fromEntries([...response.headers.entries()]));
231
+ }
232
+ if (!response.ok) {
233
+ // Try to get the response body for more details
234
+ const errorText = await response.text();
235
+ if (debug) {
236
+ console.log(`[callAI:key-refresh] Error response body: ${errorText}`);
237
+ }
238
+ throw new Error(`API key refresh failed: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
239
+ }
240
+ // Parse the response
241
+ const data = await response.json();
242
+ // Log the complete response structure for debugging
243
+ if (debug) {
244
+ console.log(`[callAI:key-refresh] Full response structure:`, JSON.stringify(data, null, 2));
245
+ }
246
+ // Handle different API response formats
247
+ let newKey;
248
+ // Check if response has the new nested format with data.key.key
249
+ if (data.key && typeof data.key === "object" && data.key.key) {
250
+ newKey = data.key.key;
251
+ }
252
+ // Check for old format where data.key is the string key directly
253
+ else if (data.key && typeof data.key === "string") {
254
+ newKey = data.key;
255
+ }
256
+ // Handle error case
257
+ else {
258
+ throw new Error("Invalid response from key refresh endpoint: missing or malformed key");
259
+ }
260
+ if (debug) {
261
+ console.log(`API key refreshed successfully: ${newKey.substring(0, 10)}...`);
262
+ }
263
+ // Store metadata for potential future use (like top-up)
264
+ if (data.metadata ||
265
+ (data.key && typeof data.key === "object" && data.key.metadata)) {
266
+ const metadata = data.metadata || data.key.metadata;
267
+ storeKeyMetadata(metadata);
268
+ }
269
+ // Update the key store with the string value
270
+ keyStore.current = newKey;
271
+ // Determine if this was a top-up (using existing key) or new key
272
+ // For the new API response format, hash is in data.key.hash
273
+ const hashValue = data.hash || (data.key && typeof data.key === "object" && data.key.hash);
274
+ const isTopup = currentKey && hashValue && hashValue === getHashFromKey(currentKey);
275
+ // Reset refreshing flag
276
+ keyStore.isRefreshing = false;
277
+ return {
278
+ apiKey: newKey, // Return the string key, not the object
279
+ topup: isTopup,
280
+ };
281
+ }
282
+ catch (error) {
283
+ // Reset refreshing flag
284
+ keyStore.isRefreshing = false;
285
+ throw error;
286
+ }
287
+ }
288
+ /**
289
+ * Helper function to extract hash from key (implementation depends on how you store metadata)
290
+ */
291
+ function getHashFromKey(key) {
292
+ if (!key)
293
+ return null;
294
+ // Simple implementation: just look up in our metadata store
295
+ const metaKey = Object.keys(keyStore.metadata).find((k) => k === key);
296
+ return metaKey ? keyStore.metadata[metaKey].hash || null : null;
297
+ }
298
+ /**
299
+ * Helper function to store key metadata for future reference
300
+ */
301
+ function storeKeyMetadata(data) {
302
+ if (!data || !data.key)
303
+ return;
304
+ // Store metadata with the key as the dictionary key
305
+ keyStore.metadata[data.key] = {
306
+ hash: data.hash || null,
307
+ created: data.created || Date.now(),
308
+ expires: data.expires || null,
309
+ remaining: data.remaining || null,
310
+ limit: data.limit || null,
311
+ };
312
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Non-streaming API call implementation for call-ai
3
+ */
4
+ import { CallAIOptions, Message, SchemaStrategy } from "./types";
5
+ declare const PACKAGE_VERSION: any;
6
+ declare const FALLBACK_MODEL = "openrouter/auto";
7
+ declare function callAINonStreaming(prompt: string | Message[], options?: CallAIOptions, isRetry?: boolean): Promise<string>;
8
+ declare function extractContent(result: any, schemaStrategy: SchemaStrategy): any;
9
+ declare function extractClaudeResponse(response: Response): Promise<any>;
10
+ export { callAINonStreaming, extractContent, extractClaudeResponse, PACKAGE_VERSION, FALLBACK_MODEL, };