proper streaming filter

This commit is contained in:
reanon
2025-08-07 23:22:34 +02:00
parent 20c9920199
commit e8c5d06cd7
2 changed files with 52 additions and 41 deletions
+35 -26
View File
@@ -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;
}
+17 -15
View File
@@ -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(