77from __future__ import annotations
88
99import logging
10+ import re
1011import threading
1112from dataclasses import dataclass
1213from typing import Any
@@ -123,13 +124,28 @@ def cost_for_call(self, call: Any) -> float | None:
123124 return round (cost , 6 )
124125
125126
127+ _DATE_SUFFIX_RE = re .compile (r"-\d{8}$" )
128+ _PROVIDER_PREFIXES = ("anthropic" , "openai" , "google" , "meta" , "cohere" , "mistral" )
129+ _ANTHROPIC_FAMILIES = {"opus" , "sonnet" , "haiku" }
130+
131+
126132def _model_key_candidates (model_name : str ) -> list [str ]:
133+ """Lookup-side variants of a Plano-emitted model name.
134+
135+ Plano resolves names like ``claude-haiku-4-5-20251001``; the catalog stores
136+ them as ``anthropic-claude-haiku-4.5``. We strip the date suffix and the
137+ ``provider/`` prefix here; the catalog itself registers the dash/dot and
138+ family-order aliases at parse time (see :func:`_expand_aliases`).
139+ """
127140 base = model_name .strip ()
128141 out = [base ]
129142 if "/" in base :
130143 out .append (base .split ("/" , 1 )[1 ])
144+ for k in list (out ):
145+ stripped = _DATE_SUFFIX_RE .sub ("" , k )
146+ if stripped != k :
147+ out .append (stripped )
131148 out .extend ([v .lower () for v in list (out )])
132- # Dedup while preserving order.
133149 seen : set [str ] = set ()
134150 uniq = []
135151 for key in out :
@@ -139,6 +155,54 @@ def _model_key_candidates(model_name: str) -> list[str]:
139155 return uniq
140156
141157
158+ def _expand_aliases (model_id : str ) -> set [str ]:
159+ """Catalog-side variants of a DO model id.
160+
161+ DO publishes Anthropic models under ids like ``anthropic-claude-opus-4.7``
162+ or ``anthropic-claude-4.6-sonnet`` while Plano emits ``claude-opus-4-7`` /
163+ ``claude-sonnet-4-6``. Generate a set covering provider-prefix stripping,
164+ dash↔dot in version segments, and family↔version word order so a single
165+ catalog entry matches every name shape we'll see at lookup.
166+ """
167+ aliases : set [str ] = set ()
168+
169+ def add (name : str ) -> None :
170+ if not name :
171+ return
172+ aliases .add (name )
173+ aliases .add (name .lower ())
174+
175+ add (model_id )
176+
177+ base = model_id
178+ head , _ , rest = base .partition ("-" )
179+ if head .lower () in _PROVIDER_PREFIXES and rest :
180+ add (rest )
181+ base = rest
182+
183+ for key in list (aliases ):
184+ if "." in key :
185+ add (key .replace ("." , "-" ))
186+
187+ parts = base .split ("-" )
188+ if len (parts ) >= 3 and parts [0 ].lower () == "claude" :
189+ rest_parts = parts [1 :]
190+ for i , p in enumerate (rest_parts ):
191+ if p .lower () in _ANTHROPIC_FAMILIES :
192+ others = rest_parts [:i ] + rest_parts [i + 1 :]
193+ if not others :
194+ break
195+ family_last = "claude-" + "-" .join (others ) + "-" + p
196+ family_first = "claude-" + p + "-" + "-" .join (others )
197+ add (family_last )
198+ add (family_first )
199+ add (family_last .replace ("." , "-" ))
200+ add (family_first .replace ("." , "-" ))
201+ break
202+
203+ return aliases
204+
205+
142206def _parse_do_pricing (data : Any ) -> dict [str , ModelPrice ]:
143207 """Parse DO catalog response into a ModelPrice map keyed by model id.
144208
@@ -204,11 +268,13 @@ def _parse_do_pricing(data: Any) -> dict[str, ModelPrice]:
204268 # rates for promo/open-weight models.
205269 if input_rate == 0 and output_rate == 0 :
206270 continue
207- prices [ str ( model_id )] = ModelPrice (
271+ price = ModelPrice (
208272 input_per_token_usd = input_rate ,
209273 output_per_token_usd = output_rate ,
210274 cached_input_per_token_usd = cached_rate ,
211275 )
276+ for alias in _expand_aliases (str (model_id )):
277+ prices .setdefault (alias , price )
212278 return prices
213279
214280
0 commit comments