Discovery — swarph_mesh.discovery¶
Cost discovery substrate: AIMLAPI catalog, Gemini Cloud Billing, Anthropic static pricing, OpenAI / xAI / DeepSeek cost reconciliation, model normalization, retirement registry.
Tier 1 stability. list_models, is_model_supported, get_model_info, fetch_*_pricing, fetch_*_cost_buckets, reconcile_*_cost signatures are part of the contract.
Tier 2 stability. normalize_*_id helpers, _RETIREMENT_REGISTRY keys (consumers may iterate).
Tier 3 stability. _ANTHROPIC_PRICING static table contents.
Model discovery — swarph_mesh.discovery (v0.6.0 architectural promotion).
Per drop’s DM #720 direction: replaces the static PRICING dict’s implicit “is this model_id real” check with a runtime catalog query. Closes the silent-mis-attribute trap from drop’s #728 obs (a) at the substrate layer.
Architecture (commander direction 2026-05-09): AIMLAPI as primary discovery, per-provider as fallback.
Primary: GET https://api.aimlapi.com/models — public, no auth,
~608 entries with structured info (id, developer, contextLength,
maxTokens, type, aliases, tags). 24h TTL cache at module scope.
Fallback: per-provider /v1/models endpoints when AIMLAPI is
unreachable. Each provider requires its own API key:
OpenAI:
client.models.list()(requires OPENAI_API_KEY)DeepSeek: same shape via
base_url(requires DEEPSEEK_API_KEY)xAI: same shape via
base_url(requires XAI_API_KEY)
- Anthropic:
GET /v1/models(requires ANTHROPIC_API_KEY — notethis is METERED auth, not the subscription path)
- Google:
models.list()viagoogle-generativeaiSDK(requires GEMINI_API_KEY)
Fallback fires per-provider; if one provider’s key is missing, that provider’s models simply don’t appear in the fallback list.
PRICING stays in adapter-local tables (the AIMLAPI /models
response does NOT include pricing — only the HTML pricing page does,
and we won’t HTML-scrape). The discovery module exposes ModelInfo
records WITHOUT pricing; callers join against the local PRICING dict
for cost information. Drift detection is a future v0.6.x stretch.
- class swarph_mesh.discovery.ModelInfo(id, developer, context_length=None, max_tokens=None, name=None, description=None, type=None, aliases=<factory>, tags=<factory>, source='aimlapi')[source]¶
Bases:
objectOne row from the discovery catalog. Provider-agnostic shape; pricing intentionally absent (kept in adapter-local PRICING dicts since AIMLAPI doesn’t expose pricing via API).
- Parameters:
- swarph_mesh.discovery.invalidate_catalog()[source]¶
Force the next
list_modelscall to re-fetch from AIMLAPI.- Return type:
None
- swarph_mesh.discovery.list_models(*, provider=None, ttl_seconds=86400)[source]¶
Return models known to the catalog.
provideris the swarph-mesh adapter name (“openai”, “deepseek”, “claude”, “gemini”, “grok”) which maps to AIMLAPI’s developer field via_PROVIDER_TO_DEVELOPER. PassNonefor the full catalog.Cache TTL: 24h by default. Pass
ttl_seconds=0to force a fresh fetch (e.g., after publishing a new model and wanting to verify it appears).
- swarph_mesh.discovery.is_model_supported(model_id, *, ttl_seconds=86400)[source]¶
Fast existence check. Used by adapters to validate model_id before dispatching to the underlying SDK — closes the silent-mis- attribute trap (drop’s #728 obs (a)) at the substrate layer.
Matches against
idANDaliases(AIMLAPI catalogs both canonical IDs likeclaude-opus-4-7and aliased forms likeopenai/gpt-3.5-turbo).
- swarph_mesh.discovery.get_model_info(model_id, *, ttl_seconds=86400)[source]¶
Single-model lookup. Returns
Nonewhen not in catalog.
- swarph_mesh.discovery.normalize_xai_id(model_id)[source]¶
Strip xAI prefix (
x-ai/) +-beta+ dated build suffixes (-DD-DD). Seeswarph_mesh.adapters.grok._normalize_xai_idfor adapter-local copy with the same semantics.
- swarph_mesh.discovery.normalize_deepseek_id(model_id)[source]¶
Strip DeepSeek prefix (
deepseek/) + version suffixes (-v3.1,-v3.2-terminus). Seeswarph_mesh.adapters.deepseek._normalize_deepseek_idfor adapter-local copy.
- swarph_mesh.discovery.normalize_model_id(provider, model_id)[source]¶
Provider-aware normalizer. Dispatches to the right per-provider helper. Provider names match the adapter
namefield ("openai","grok", etc.).
- swarph_mesh.discovery.is_retired(provider, model_id, *, today=None)[source]¶
Return True if the model is past its retirement date as of
today(default: actual today UTC). Returns False for unregistered models (not retired) and fordeprecatedsentinel entries (still routable, just deprecated).
- swarph_mesh.discovery.retirement_date(provider, model_id)[source]¶
Return the retirement-date string for a provider/model_id, OR None if not in the registry.
- class swarph_mesh.discovery.ProviderPricing(provider, model_hint, sku_id, sku_description, input_per_mtok=None, output_per_mtok=None, tier_threshold_tokens=0, usage_unit=None, source='google-cloud-billing', verified_at=None)[source]¶
Bases:
objectOne row of pricing data for a provider+SKU combo.
Tiered models (e.g., Gemini Pro at >128K context) surface as multiple
ProviderPricingrecords — one per tier band — withtier_threshold_tokensdistinguishing them. Callers wanting “the price at N tokens” pick the highest-threshold record wheretier_threshold_tokens <= N.- Parameters:
- swarph_mesh.discovery.invalidate_pricing(provider=None)[source]¶
Clear pricing cache.
provider=Noneclears all.- Parameters:
provider (str | None)
- Return type:
None
- swarph_mesh.discovery.fetch_gemini_pricing(*, api_key=None, ttl_seconds=86400)[source]¶
Hit Cloud Billing Catalog API, return parsed
ProviderPricingrecords for all Gemini SKUs.Auth:
api_keyparameter, OR$GOOGLE_CLOUD_BILLING_API_KEYenv, OR$GOOGLE_CLOUD_API_KEYenv. Note: this is a separate API key from the Generative AIGEMINI_API_KEYused by the chat adapter — Cloud Billing requires Cloud Console project keys with billing-API scope enabled. Operators provisioning need:gcloud services enable cloudbilling.googleapis.com # then create an API key in Cloud Console with Cloud Billing # API restriction; export as GOOGLE_CLOUD_BILLING_API_KEY
Returns empty list on auth failure (treat as “billing-API key not configured” — adapter PRICING tables stay authoritative).
Pagination: Google returns up to 5000 SKUs per page; service
241C-273D-49C8typically has < 500 entries so a single page suffices. We still page defensively for forward-compat.- Parameters:
- Return type:
- swarph_mesh.discovery.pricing_for_anthropic_model(model_id)[source]¶
Return a
ProviderPricingrecord for an Anthropic model.Source:
_ANTHROPIC_PRICINGstatic table (manually verified against claude.com/pricing on_ANTHROPIC_PRICING_VERIFIED_AT).Returns
Nonefor unknown model_ids — callers should fall back to the adapter-local PRICING table (which mirrors a subset of this table for the modern model lineup).The
ProviderPricingshape carries all five Anthropic price dimensions in custom attributes;input_per_mtokis the base input rate,output_per_mtokis the output rate. Cache writes + hits are exposed viaraw_pricingfor callers that care.- Parameters:
model_id (str)
- Return type:
ProviderPricing | None
- swarph_mesh.discovery.list_anthropic_pricing()[source]¶
Return
ProviderPricingrecords for every model in the static Anthropic table. Used by drift-detection cron + audit surfaces.- Return type:
- class swarph_mesh.discovery.DeepSeekBalance(total_balance, granted_balance, topped_up_balance, currency='USD', is_available=True, fetched_at=None, raw=None)[source]¶
Bases:
objectSnapshot of a DeepSeek account’s credit balance.
Unlike OpenAI’s
CostBucketand xAI’sXAICostBucket, DeepSeek’s API exposes only current balance — not historical per-day usage. Usefetched_atto track snapshots over time; diff successive balances to approximate windowed spend.- Parameters:
- swarph_mesh.discovery.fetch_deepseek_balance(*, api_key=None, timeout=10.0)[source]¶
Hit
GET /user/balanceon DeepSeek for current credit state.api_keyresolves from arg →$DEEPSEEK_API_KEYenv →/home/ubuntu/deepseek/.envlegacy fallback (matchesDeepSeekAdapter._resolve_api_keyshape). The Anthropic-protocol endpoint at/anthropic/v1/messagesis documented in the adapter module docstring but not used by this balance primitive.Returns
Noneif no key is configured OR the call fails.Note: DeepSeek can have multiple
balance_infosentries (different currencies). We return the USD entry; callers needing other currencies can inspectraw.- Parameters:
- Return type:
DeepSeekBalance | None
- class swarph_mesh.discovery.XAICostBucket(start_time, end_time, total_usd=0.0, line_items=<factory>, raw_bucket=None)[source]¶
Bases:
objectOne time-bucket of xAI usage data per
/v1/billing/teams/.../usage.xAI’s response shape exposes
line_itemsper bucket, each with a model description (e.g., “Chat grok-4-0709”) and aggregated usage. We extract sum-style cost data intototal_usdwhen available.- Parameters:
- swarph_mesh.discovery.fetch_xai_cost_buckets(*, start_time, end_time, management_key=None, team_id=None, time_unit='TIME_UNIT_DAY', timezone='Etc/GMT', timeout=15.0)[source]¶
Hit xAI’s
POST /v1/billing/teams/{team_id}/usagefor a date range.start_time/end_timeaccept either ISO 8601 (e.g.,"2026-05-01T00:00:00Z") or xAI’s native format ("2026-05-01 00:00:00"). ISO inputs are converted internally; timezone is supplied via the separatetimezoneparameter (default"Etc/GMT").time_unitis one of xAI’s enum strings:TIME_UNIT_DAY,TIME_UNIT_HOUR,TIME_UNIT_MONTH,TIME_UNIT_CALENDAR_WEEK,TIME_UNIT_QUARTER_HOUR,TIME_UNIT_MINUTE,TIME_UNIT_SECOND,TIME_UNIT_NONE.management_keyresolves from arg →$XAI_MANAGEMENT_KEYenv.team_idresolves from arg →$XAI_TEAM_IDenv (obtained from xAI console — not discoverable via API).PRIVILEGE BOUNDARY: management keys can manage API keys, view billing, configure spending limits. swarph-mesh does NOT persist the key to disk — env-only.
Returns
[]if either credential is missing OR the call fails (4xx/5xx/network).
- swarph_mesh.discovery.reconcile_xai_cost(*, start_time, end_time, swarph_attributed_usd=None, management_key=None, team_id=None)[source]¶
xAI parallel of
reconcile_openai_cost(). Compares xAI’s actual billed costs against swarph-mesh’s attribution.jsonl total and returns drift report.Returns same shape as
reconcile_openai_costbut withxai_actual_usdinstead ofopenai_actual_usd.
- class swarph_mesh.discovery.CostBucket(start_time, end_time, total_usd, currency='usd', line_item_breakdown=<factory>, project_breakdown=<factory>, api_key_breakdown=<factory>, raw_results=<factory>)[source]¶
Bases:
objectOne day’s worth of OpenAI cost data per
/v1/organization/costs.The endpoint returns up to 7 buckets by default (configurable via
limit). Each bucket aggregates spend across the bucket window. Optional groupings (line_item / project_id / api_key_id) populate the breakdown dicts.- Parameters:
- swarph_mesh.discovery.fetch_openai_cost_buckets(*, start_time, end_time=None, admin_key=None, group_by=None, limit=7, timeout=15.0)[source]¶
Hit OpenAI’s
/v1/organization/costsfor a date range.start_time/end_timeare Unix-seconds timestamps; bucket width is 1d (the only supported value as of 2026-05).group_byaccepts any subset of["line_item", "project_id", "api_key_id"]. Default is no grouping (just total per bucket).admin_keyresolves from arg →$OPENAI_ADMIN_KEYenv. If no key is found, returns[](treat as “reconciliation not configured” — discovery.pricing primitives stay authoritative).PRIVILEGE BOUNDARY: admin keys can mint more admin keys, delete keys, and manage org settings. swarph-mesh does NOT persist this key to disk — operators paste at daemon boot if reconciliation is wanted, and the env-var lives in the process for that session only. Treat this function as a privileged read; do NOT call it inside hot loops or chat paths.
Returns
list[CostBucket]in chronological order (oldest first).
- swarph_mesh.discovery.reconcile_openai_cost(*, start_time, end_time=None, swarph_attributed_usd=None, admin_key=None)[source]¶
Fetch OpenAI’s actual costs for a window + return a drift report against swarph-mesh’s attribution.jsonl-recorded total.
Returns dict:
{ "openai_actual_usd": float, # what OpenAI billed "swarph_attributed_usd": float | None, # what we recorded "drift_usd": float | None, # actual - attributed "drift_pct": float | None, # (drift / attributed) * 100 "buckets": list[CostBucket], # per-day breakdown "window_start": int, # unix seconds "window_end": int | None, }
swarph_attributed_usd=Noneskips the comparison (just returns actual costs). Caller is responsible for summing the relevant attribution.jsonl rows for the same window.
- swarph_mesh.discovery.pricing_for_gemini_model(model_hint, *, direction='output', tier_threshold_tokens=0, api_key=None, ttl_seconds=86400)[source]¶
Return USD-per-Mtok for a Gemini model+direction+tier combination.
model_hintis matched as a substring against SKU description’s model fragment (e.g.,"1.5 Pro"matches"Gemini 1.5 Pro").directionis"input"or"output".tier_threshold_tokensis 0 for base, 128000 for >128K.Returns
Nonewhen no matching SKU is found OR billing-API key is unavailable. Callers fall back to the adapter-local PRICING table (swarph_mesh.adapters.gemini.PRICING).