diff --git a/src/shared/key-management/openai/checker.ts b/src/shared/key-management/openai/checker.ts index 93d6c5e..1bf353d 100644 --- a/src/shared/key-management/openai/checker.ts +++ b/src/shared/key-management/openai/checker.ts @@ -334,11 +334,9 @@ export class OpenAIKeyChecker extends KeyCheckerBase { } /** - * Tests whether the key has access to the gpt-image-1 model by making a small test request. - * Returns true if the key has access, false otherwise. - * - * Ensures that the key can actually generate images by verifying the API response - * contains the expected data structure for successful image generation. + * Tests whether the key has access to the gpt-image-1 model by making a test request. + * Returns true if the API accepts the request without specific access errors. + * Does not wait for actual image generation to complete. */ public async testGptImageAccess(key: OpenAIKey): Promise { this.log.info({ key: key.hash }, "Testing gpt-image-1 access"); @@ -346,85 +344,96 @@ export class OpenAIKeyChecker extends KeyCheckerBase { try { const payload = { model: "gpt-image-1", - prompt: "A cute baby sea otter", + prompt: "A simple test image", n: 1, size: "auto", quality: "low", }; - // Make a real request to test access + // Make a minimal request just to check access const response = await axios.post( POST_IMAGE_GENERATIONS_URL, payload, { headers: OpenAIKeyChecker.getHeaders(key), - validateStatus: (status) => status >= 200 && status < 500, // Accept any non-server error response - timeout: 200000, // 200 second timeout to allow for image generation - signal: AbortSignal.timeout(200000) + validateStatus: (status) => true, // Accept any status code to inspect errors + timeout: 5000, // 5 second timeout + signal: AbortSignal.timeout(5000) } ); - // Status 200 means success, but we need to verify the response actually contains image data - if (response.status === 200) { - // Check if the response contains the expected data structure - if (response.data && - response.data.data && - Array.isArray(response.data.data) && - response.data.data.length > 0 && - response.data.data[0].url) { - this.log.info( - { key: key.hash, status: response.status }, - "Verified gpt-image-1 access: Successfully generated an image" - ); - return true; - } else { - this.log.warn( - { key: key.hash, status: response.status, data: response.data }, - "Response status is 200 but received unexpected response format. Image may not have been generated." - ); - return false; - } + // If we get a 200, 400, or 500 response, consider the key valid + if (response.status === 200 || response.status === 400 || response.status === 500) { + this.log.info( + { key: key.hash, status: response.status }, + `Verified gpt-image-1 access with status code ${response.status}` + ); + return true; } - // All other status codes indicate issues + // Check for specific error responses that indicate no access const data = response.data as any; + const errorMessage = data?.error?.message || ''; + + // Explicitly check for organization verification errors + if (response.status === 403 && errorMessage.includes("organization must be verified")) { + this.log.warn( + { key: key.hash, status: response.status, error: errorMessage }, + "Key does not have access to gpt-image-1: organization verification required" + ); + return false; + } + + // Only 401/403/404 responses indicate the key doesn't have model access + // (we're now considering 400 as valid) + if (response.status === 401 || response.status === 403 || response.status === 404) { + this.log.warn( + { key: key.hash, status: response.status, error: errorMessage }, + `Key does not have access to gpt-image-1: received ${response.status} response` + ); + return false; + } + + // For other status codes, log the issue but assume no access this.log.warn( - { key: key.hash, status: response.status, error: data?.error?.message || 'Unknown error' }, + { key: key.hash, status: response.status, error: errorMessage }, "Unexpected response when testing gpt-image-1 access, assuming no access" ); return false; } catch (error) { + // Handle network errors or request failures if (error instanceof AxiosError && error.response) { - // Check for forbidden/unauthorized responses const status = error.response.status; const data = error.response.data as any; const errorMessage = data?.error?.message || 'Unknown error'; - // Organization verification errors (403 forbidden) - if (status === 403) { + // Check for specific error messages related to access + if (errorMessage.includes("organization must be verified") || + errorMessage.includes("does not have access") || + errorMessage.includes("not available")) { this.log.warn( - { key: key.hash, error: errorMessage }, - "Key does not have access to gpt-image-1: received 403 Forbidden response" + { key: key.hash, status, error: errorMessage }, + "Key does not have access to gpt-image-1 based on error message" ); return false; } - // Explicitly handle all status codes that indicate the key doesn't have access - if (status === 401 || status === 404 || status === 400) { - this.log.warn( + // Status 400 or 500 indicates the API processed the request but had validation issues + // This means the key is authorized to use the model + if (status === 400 || status === 500) { + this.log.info( { key: key.hash, status, error: errorMessage }, - `Key does not have access to gpt-image-1: received ${status} response` + `Verified gpt-image-1 access with error response ${status}` ); - return false; + return true; } - // Rate limit errors - we no longer assume these mean the key has access - // since the rate limit could be happening before model validation - if (status === 429) { + // 401/403/404 responses indicate the key doesn't have proper access + if (status === 401 || status === 403 || status === 404) { this.log.warn( { key: key.hash, status, error: errorMessage }, - "Cannot verify gpt-image-1 access: key is rate limited. Assuming no access for safety." + `Key does not have access to gpt-image-1: received ${status} error response` ); return false; } @@ -433,7 +442,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase { // For all other errors, assume no access this.log.error( { key: key.hash, error: error.message }, - "Unexpected error testing gpt-image-1 access. Assuming no access for safety." + "Error testing gpt-image-1 access. Assuming no access for safety." ); return false; }