Skip to content

list_models() fails for ALL consumers when any model omits ModelBilling.multiplier (currently every named model) #1302

@jrob5756

Description

@jrob5756

Summary

CopilotClient.list_models() raises ValueError("Missing required field 'multiplier' in ModelBilling") against the current production backend (SDK 0.3.0). Because the SDK parses the entire response in a list-comprehension, a single malformed entry takes down list_models() for every consumer — and right now every named model in the response triggers it. Only auto (which has billing: None) is parseable.

Reproduction

import asyncio
from copilot import CopilotClient

async def main():
    c = CopilotClient()
    await c.start()
    try:
        await c.list_models()
    finally:
        await c.stop()

asyncio.run(main())
ValueError: Missing required field 'multiplier' in ModelBilling

Why it's broken

  1. ModelBilling.from_dict (client.py:496-507) treats multiplier as required:
    if multiplier is None:
        raise ValueError("Missing required field 'multiplier' in ModelBilling")
  2. list_models() (client.py:1811) parses the whole response eagerly:
    models = [ModelInfo.from_dict(model) for model in models_data]
    So any malformed entry fails the entire call. The result cache is only populated on success, so every retry from a fresh session fails identically.
  3. The current backend response contains a billing object on every named model but omits multiplier from all of them — observed today against SDK 0.3.0:
    18/19 models affected:
    claude-sonnet-4.6, claude-sonnet-4.5, claude-haiku-4.5,
    claude-opus-4.7, claude-opus-4.7-1m-internal, claude-opus-4.7-high,
    claude-opus-4.7-xhigh, claude-opus-4.6, claude-opus-4.6-1m,
    claude-opus-4.5, gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex,
    gpt-5.2-codex, gpt-5.2, gpt-5-mini, gpt-4.1
    
    (Only auto is parseable, because its billing is None and the parser short-circuits.)

Why this is doubly fragile

#1188 notes that multiplier is being deprecated as of June 1 (except for legacy annual-plan users). Making it required in the parser turns a planned backend cleanup into a hard breakage for every SDK consumer.

Suggested fixes (any one of these would resolve the consumer-side breakage)

  1. Make multiplier optional in ModelBilling.from_dict — return ModelBilling(multiplier=None) (or skip the billing block entirely) when the field is absent. This matches the deprecation direction in Please add token cost properties to Models/Billing #1188.
  2. Or, isolate per-entry failures in list_models(): wrap each ModelInfo.from_dict(model) in its own try/except, log a warning, and skip the malformed entry. This protects consumers from any future schema drift on individual models, not just this one field.
  3. (Ideally both — option 1 fixes the immediate symptom; option 2 is a durable hardening.)

Impact on consumers

We hit this in microsoft/conductor (workflow orchestration tool built on the Copilot SDK). Any workflow that configures a reasoning_effort validates the model's advertised capabilities by calling list_models() first, so every such workflow fails with an opaque Dialog turn failed: Missing required field 'multiplier' in ModelBilling after the retry loop exhausts. Consumers can defensively wrap list_models() in a broad except Exception (which is what we ended up doing in microsoft/conductor#193), but that loses all model metadata for the lifetime of the session and treats a recoverable parser issue as a total blackout.

Happy to send a PR if you'd like — option 1 is a 2-line change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions