11import { CODEX_BASE_URL } from "../auth/codex/constants.js" ;
2+ import { isCodexProviderName } from "../config/codex-providers.js" ;
23import { lookupModelPricing , type PricingCache } from "./pricing-fetcher.js" ;
34
45// Free-model naming conventions: OpenRouter appends ":free", some gateways use
@@ -30,17 +31,21 @@ export function isCodingPlanBaseURL(baseURL: string | undefined): boolean {
3031// not apply there, so dollar estimates must be suppressed. Matched against
3132// the canonical Codex base (origin + path prefix) so api.openai.com stays
3233// metered and a bare chatgpt.com host does not hide costs.
33- const CHATGPT_SUBSCRIPTION_FALLBACK = / c h a t g p t \. c o m \/ b a c k e n d - a p i ( \/ | $ ) / i;
34+ const CODEX_BASE = new URL ( CODEX_BASE_URL ) ;
35+ const CODEX_ORIGIN = CODEX_BASE . origin ;
36+ const CODEX_PATH = CODEX_BASE . pathname . replace ( / \/ $ / , "" ) . toLowerCase ( ) ;
37+ // Host-anchored: a scheme or start of string must precede chatgpt.com so
38+ // notchatgpt.com/backend-api never matches. Query/hash after the path still
39+ // count. Unparseable noise that merely contains the substring does not.
40+ const CHATGPT_SUBSCRIPTION_FALLBACK = / (?: ^ | \/ \/ ) c h a t g p t \. c o m \/ b a c k e n d - a p i (?: \/ | $ | \? | # ) / i;
3441
3542export function isChatGPTSubscriptionBaseURL ( baseURL : string | undefined ) : boolean {
3643 if ( baseURL === undefined ) return false ;
3744 try {
3845 const url = new URL ( baseURL ) ;
39- const codex = new URL ( CODEX_BASE_URL ) ;
40- if ( url . origin !== codex . origin ) return false ;
41- const basePath = codex . pathname . replace ( / \/ $ / , "" ) ;
42- const path = url . pathname . replace ( / \/ $ / , "" ) || "/" ;
43- return path === basePath || path . startsWith ( `${ basePath } /` ) ;
46+ if ( url . origin !== CODEX_ORIGIN ) return false ;
47+ const path = url . pathname . replace ( / \/ $ / , "" ) . toLowerCase ( ) || "/" ;
48+ return path === CODEX_PATH || path . startsWith ( `${ CODEX_PATH } /` ) ;
4449 } catch {
4550 return CHATGPT_SUBSCRIPTION_FALLBACK . test ( baseURL ) ;
4651 }
@@ -54,6 +59,10 @@ export function isFreeModelByPricing(cache: PricingCache | null, modelId: string
5459
5560export interface CostVisibilityInput {
5661 baseURL ?: string | undefined ;
62+ // Live /model identity. When set, it wins over a stale launch baseURL for
63+ // the ChatGPT-subscription hide: Codex names hide even on api.openai.com,
64+ // non-Codex names show even on CODEX_BASE_URL. Undefined falls back to URL.
65+ providerName ?: string | undefined ;
5766 modelId : string ;
5867 providerFree ?: boolean | undefined ;
5968 pricingCache : PricingCache | null ;
@@ -62,15 +71,22 @@ export interface CostVisibilityInput {
6271export type CostHiddenReason =
6372 "provider-free" | "coding-plan" | "chatgpt-subscription" | "free-model" | "zero-priced" ;
6473
74+ function isChatGPTSubscriptionSession ( input : CostVisibilityInput ) : boolean {
75+ if ( input . providerName !== undefined ) {
76+ return isCodexProviderName ( input . providerName ) ;
77+ }
78+ return isChatGPTSubscriptionBaseURL ( input . baseURL ) ;
79+ }
80+
6581// Non-null when the dollar cost should be suppressed: a manual provider
66- // override, a coding-plan endpoint, a ChatGPT/Codex subscription endpoint, a
67- // free-named model, or a model the pricing registry reports as zero-cost. The
68- // reason is carried to the display so / cost can say which condition hid the
69- // figure.
82+ // override, a coding-plan endpoint, a ChatGPT/Codex subscription (live
83+ // provider identity, else Codex URL), a free-named model, or a model the
84+ // pricing registry reports as zero- cost. The reason is carried to the
85+ // display so /cost can say which condition hid the figure.
7086export function costHiddenReason ( input : CostVisibilityInput ) : CostHiddenReason | null {
7187 if ( input . providerFree === true ) return "provider-free" ;
7288 if ( isCodingPlanBaseURL ( input . baseURL ) ) return "coding-plan" ;
73- if ( isChatGPTSubscriptionBaseURL ( input . baseURL ) ) return "chatgpt-subscription" ;
89+ if ( isChatGPTSubscriptionSession ( input ) ) return "chatgpt-subscription" ;
7490 if ( isFreeModelId ( input . modelId ) ) return "free-model" ;
7591 return isFreeModelByPricing ( input . pricingCache , input . modelId ) ? "zero-priced" : null ;
7692}
0 commit comments