proper streaming filter
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { KeyCheckerBase } from "../key-checker-base";
|
||||
import type { OpenAIKey, OpenAIKeyProvider } from "./provider";
|
||||
import type { OpenAIKey, OpenAIKeyProvider, OpenAIKeyUpdate } from "./provider";
|
||||
import { OpenAIModelFamily, getOpenAIModelFamily } from "../../models";
|
||||
import { getAxiosInstance } from "../../network";
|
||||
|
||||
@@ -51,29 +51,38 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
this.testLiveness(key),
|
||||
this.maybeCreateOrganizationClones(key),
|
||||
]);
|
||||
const updates = {
|
||||
const updates: OpenAIKeyUpdate = {
|
||||
modelFamilies: provisionedModels,
|
||||
isTrial: livenessTest.rateLimit <= 250,
|
||||
};
|
||||
|
||||
// If the model list claims to include gpt-image-1, verify organization status with streaming test
|
||||
if (provisionedModels.includes("gpt-image") || provisionedModels.includes("o3")) {
|
||||
try {
|
||||
const isVerifiedOrg = await this.testVerifiedOrg(key);
|
||||
if (!isVerifiedOrg) {
|
||||
// Only remove gpt-image from unverified orgs - they can still use o3, just not stream it
|
||||
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
||||
updates.modelFamilies = updatedFamilies;
|
||||
this.log.warn({ key: key.hash }, "Key's organization is not verified. Removing gpt-image-1 from available models.");
|
||||
} else {
|
||||
this.log.info({ key: key.hash }, "Verified organization status for key. Can use gpt-image-1 and o3 streaming.");
|
||||
}
|
||||
} catch (error) {
|
||||
// If test fails, assume no access to be safe, but only for gpt-image
|
||||
// Test organization verification status for all keys
|
||||
// This is needed for GPT-5, o1, o3, and gpt-image-1 streaming restrictions
|
||||
try {
|
||||
const isVerifiedOrg = await this.testVerifiedOrg(key);
|
||||
// Always set the organizationVerified field for all keys
|
||||
updates.organizationVerified = isVerifiedOrg;
|
||||
|
||||
// Only remove gpt-image from unverified orgs if they have it
|
||||
if (!isVerifiedOrg && provisionedModels.includes("gpt-image")) {
|
||||
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
||||
updates.modelFamilies = updatedFamilies;
|
||||
this.log.error({ key: key.hash, error }, "Error testing organization verification status. Removing gpt-image-1 from available models.");
|
||||
this.log.warn({ key: key.hash }, "Key's organization is not verified. Removing gpt-image-1 from available models.");
|
||||
}
|
||||
|
||||
if (isVerifiedOrg) {
|
||||
this.log.info({ key: key.hash }, "Verified organization status for key. Can use streaming for GPT-5, o1, o3, and gpt-image-1.");
|
||||
} else {
|
||||
this.log.warn({ key: key.hash }, "Key's organization is not verified. Streaming restricted for GPT-5, o1, o3, and gpt-image-1.");
|
||||
}
|
||||
} catch (error) {
|
||||
// If test fails, assume no access to be safe
|
||||
updates.organizationVerified = false;
|
||||
if (provisionedModels.includes("gpt-image")) {
|
||||
const updatedFamilies = provisionedModels.filter(family => family !== "gpt-image");
|
||||
updates.modelFamilies = updatedFamilies;
|
||||
}
|
||||
this.log.error({ key: key.hash, error }, "Error testing organization verification status. Assuming not verified for safety.");
|
||||
}
|
||||
|
||||
this.updateKey(key.hash, updates);
|
||||
@@ -334,17 +343,17 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the key's organization is verified by attempting to stream from the o3 model.
|
||||
* Only verified organizations can stream from o3, so this is a reliable test for both
|
||||
* o3 streaming and gpt-image-1 access (which also requires verified organization status).
|
||||
* Tests whether the key's organization is verified by attempting to stream from the gpt-5-mini model.
|
||||
* Only verified organizations can stream from GPT-5 models, so this is a reliable test for both
|
||||
* GPT-5 streaming and gpt-image-1 access (which also requires verified organization status).
|
||||
* Returns true if the organization is verified.
|
||||
*/
|
||||
public async testVerifiedOrg(key: OpenAIKey): Promise<boolean> {
|
||||
this.log.info({ key: key.hash }, "Testing organization verification status via o3 streaming");
|
||||
this.log.info({ key: key.hash }, "Testing organization verification status via gpt-5-mini streaming");
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
model: "o3",
|
||||
model: "gpt-5-nano",
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
max_completion_tokens: 1,
|
||||
stream: true
|
||||
@@ -366,7 +375,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
if (response.status === 200) {
|
||||
this.log.info(
|
||||
{ key: key.hash, status: response.status },
|
||||
`Organization is verified. Streaming o3 request succeeded with status code ${response.status}`
|
||||
`Organization is verified. Streaming gpt-5-mini request succeeded with status code ${response.status}`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -379,7 +388,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
if (errorMessage.includes("organization must be verified")) {
|
||||
this.log.warn(
|
||||
{ key: key.hash, status: response.status, error: errorMessage },
|
||||
"Organization is not verified: verification required for streaming o3"
|
||||
"Organization is not verified: verification required for streaming gpt-5-mini"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -391,7 +400,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
||||
this.log.warn(
|
||||
{ key: key.hash, status: response.status, error: errorMessage },
|
||||
"Organization is not verified: cannot stream with o3"
|
||||
"Organization is not verified: cannot stream with gpt-5-mini"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -433,7 +442,7 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
||||
if (errorMessage.includes("stream") && errorMessage.includes("unsupported_value")) {
|
||||
this.log.warn(
|
||||
{ key: key.hash, status, error: errorMessage },
|
||||
"Organization is not verified: cannot stream with o3"
|
||||
"Organization is not verified: cannot stream with gpt-5-mini"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface OpenAIKey extends Key {
|
||||
organizationId?: string;
|
||||
/** Whether this is a free trial key. These are prioritized over paid keys if they can fulfill the request. */
|
||||
isTrial: boolean;
|
||||
/** Whether the organization associated with this key is verified. Verified organizations can use streaming for GPT-5 models and gpt-image-1. */
|
||||
organizationVerified?: boolean;
|
||||
/** Set when key check returns a non-transient 429. */
|
||||
isOverQuota: boolean;
|
||||
/**
|
||||
@@ -146,7 +148,9 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
||||
|
||||
// GPT-5 models (gpt-5, gpt-5-mini, gpt-5-nano) require verified keys for streaming
|
||||
const isGpt5Model = /^gpt-5(-mini|-nano)?(-\d{4}-\d{2}-\d{2})?$/.test(model) || model === "gpt-5-chat-latest";
|
||||
const isGpt5StreamingRequest = isGpt5Model && streaming;
|
||||
const isO1Model = /^o1(-mini|-preview)?(-\d{4}-\d{2}-\d{2})?$/.test(model);
|
||||
const isO3Model = /^o3(-mini)?(-\d{4}-\d{2}-\d{2})?$/.test(model);
|
||||
const requiresVerifiedStreaming = (isGpt5Model || isO1Model || isO3Model) && streaming;
|
||||
|
||||
// First, filter keys based on basic criteria
|
||||
let availableKeys = this.keys.filter(
|
||||
@@ -174,9 +178,8 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
||||
});
|
||||
|
||||
// Filter to only include keys from verified organizations
|
||||
// A key is from a verified organization if our verification test didn't remove gpt-image
|
||||
// This is the critical filter that ensures only verified org keys are used
|
||||
const verifiedKeys = availableKeys.filter(key => key.modelFamilies.includes("gpt-image"));
|
||||
// Use the organizationVerified field which is set by the key checker
|
||||
const verifiedKeys = availableKeys.filter(key => key.organizationVerified === true);
|
||||
|
||||
if (verifiedKeys.length > 0) {
|
||||
this.log.info(
|
||||
@@ -192,28 +195,28 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
||||
}
|
||||
}
|
||||
|
||||
// For GPT-5 streaming requests, we need to use only verified keys
|
||||
// GPT-5 models (gpt-5, gpt-5-mini, gpt-5-nano) require verified organizations for streaming
|
||||
if (isGpt5StreamingRequest) {
|
||||
// For streaming requests with models that require verified organizations
|
||||
// GPT-5, o1, and o3 models require verified organizations for streaming
|
||||
if (requiresVerifiedStreaming) {
|
||||
this.log.debug(
|
||||
{ model, keyCount: availableKeys.length, streaming },
|
||||
"Filtering keys for GPT-5 streaming request to ensure verified organization status"
|
||||
"Filtering keys for streaming request to ensure verified organization status"
|
||||
);
|
||||
|
||||
// Filter to only include keys from verified organizations
|
||||
// We piggyback on the existing verification logic: verified keys still have gpt-image access
|
||||
const verifiedKeys = availableKeys.filter(key => key.modelFamilies.includes("gpt-image"));
|
||||
// Use the organizationVerified field which is set by the key checker
|
||||
const verifiedKeys = availableKeys.filter(key => key.organizationVerified === true);
|
||||
|
||||
if (verifiedKeys.length > 0) {
|
||||
this.log.info(
|
||||
{ model, totalKeys: availableKeys.length, verifiedKeys: verifiedKeys.length, streaming },
|
||||
"Using only verified organization keys for GPT-5 streaming request"
|
||||
"Using only verified organization keys for streaming request"
|
||||
);
|
||||
availableKeys = verifiedKeys;
|
||||
} else {
|
||||
this.log.warn(
|
||||
{ model, totalKeys: availableKeys.length, streaming },
|
||||
"No verified organization keys available for GPT-5 streaming request"
|
||||
"No verified organization keys available for streaming request"
|
||||
);
|
||||
// Set availableKeys to empty array to trigger the error below
|
||||
availableKeys = [];
|
||||
@@ -221,10 +224,9 @@ export class OpenAIKeyProvider implements KeyProvider<OpenAIKey> {
|
||||
}
|
||||
|
||||
if (availableKeys.length === 0) {
|
||||
// Provide specific error message for GPT-5 streaming requests
|
||||
if (isGpt5StreamingRequest) {
|
||||
if (requiresVerifiedStreaming) {
|
||||
throw new PaymentRequiredError(
|
||||
`No verified OpenAI keys available for streaming ${model}. GPT-5 models require verified organization keys for streaming. Please disable streaming.`
|
||||
"No verified OpenAI keys available for streaming GPT-5, o1, or o3 models. Only verified organizations can stream these models. Please disable streaming or contact support to verify your organization."
|
||||
);
|
||||
}
|
||||
throw new PaymentRequiredError(
|
||||
|
||||
Reference in New Issue
Block a user