public name. * * Keys are FRAGMENTS, matched case-insensitively, because a map keyed on exact strings starts * returning the generic label the moment a vendor ships a point release: ids carry dates * (claude-sonnet-4-6-20260514) and sometimes a vendor prefix (z-ai/glm-5.2). Longest match wins, * so 'gpt-4o-mini-tts' beats 'gpt-4o-mini' beats 'gpt-4o'. See public_name(). */ const MANAGED = array( // Text. Two tiers, because the routed backend is what makes the allowance go far and the // premium path is what it falls back to for the hardest work. 'glm-' => 'CS-Text-1', 'claude-haiku' => 'CS-Text-1', 'claude-sonnet' => 'CS-Text-Pro', 'claude-opus' => 'CS-Text-Pro', 'gpt-4o-mini' => 'CS-Text-1', 'gpt-4o' => 'CS-Text-Pro', // Voice. 'kokoro' => 'CS-Voice-1', 'zonos' => 'CS-Voice-1', 'gpt-4o-mini-tts' => 'CS-Voice-Pro', 'tts-1' => 'CS-Voice-Pro', // Images. 'gpt-image' => 'CS-Image-1', 'dall-e' => 'CS-Image-1', ); /** Shown when a managed model is not in the map: never the raw id. */ const FALLBACK = 'CloudScale managed model'; /** * The public name for a model served by CloudScale. * * @param string $model_id Raw provider model id. * @return string Public name, never the raw id. */ public static function public_name( string $model_id ): string { $id = strtolower( trim( $model_id ) ); if ( '' === $id ) { return self::FALLBACK; } // Ids arrive in two shapes depending on the route: bare (kokoro, gpt-image-2) and // vendor-qualified (z-ai/glm-5.2, anthropic/claude-sonnet-4-6). Prefix-matching alone // missed every qualified one and quietly returned the generic label, which leaks nothing, // but does throw away the tier the reader needed. So the vendor segment is stripped first, // and a contains-match is the last resort. $bare = strrpos( $id, '/' ) !== false ? substr( $id, strrpos( $id, '/' ) + 1 ) : $id; $best = ''; $found = self::FALLBACK; foreach ( self::MANAGED as $needle => $name ) { // Longest match wins, so 'gpt-4o-mini-tts' beats 'gpt-4o-mini' and 'gpt-4o'. if ( strlen( $needle ) <= strlen( $best ) ) { continue; } if ( 0 === strpos( $bare, $needle ) || 0 === strpos( $id, $needle ) || false !== strpos( $id, $needle ) ) { $best = $needle; $found = $name; } } return $found; } /** * The name to show for a model, deciding by WHO PAYS rather than by the id. * * @param string $model_id Raw provider model id. * @param bool $managed True when CloudScale served it; false when the site's own key did. * @return string */ public static function display_name( string $model_id, bool $managed ): string { if ( ! $managed ) { // The customer's own model, on the customer's own key: say what it is. return $model_id; } return self::public_name( $model_id ); } /** * Every public name in use, for glossaries and tests. * * @return array */ public static function public_names(): array { $names = array_values( self::MANAGED ); $names[] = self::FALLBACK; return array_values( array_unique( $names ) ); } /** * What a public name means, in one line, for a help page or tooltip. * * Capability and trade-off only. No vendor, no id, that is the point of the scheme, and no * promise about which engine sits underneath, because that is chosen per job and changes. * * @return array */ public static function glossary(): array { return array( 'CS-Text-1' => 'Everyday text: audits, summaries, metadata. Fast, and the reason a month of requests costs what it does.', 'CS-Text-Pro' => 'The premium text tier, used for the hardest reasoning and as the fallback when CS-Text-1 is not a good fit.', 'CS-Voice-1' => 'Standard narration. Natural-sounding, and cheap enough to narrate a whole archive.', 'CS-Voice-Pro' => 'Premium narration, used when a prompt asks for delivery a standard voice cannot follow.', 'CS-Image-1' => 'Featured-image generation.', ); } } }