Source code for axio_transport_openai.custom

"""Custom OpenAI-compatible provider transport.

Each custom provider is a separate :class:`OpenAICompatibleTransport` instance with its
own ``base_url``, ``api_key``, and ``models``.  Instances are created by the TUI hub
screen and registered dynamically in the transport registry.

Configuration is persisted to ``~/.local/share/axio/openai-custom.json``:

.. code-block:: json

    [
      {
        "name": "localai",
        "base_url": "http://localhost:8080/v1",
        "api_key": "",
        "models": [
          {
            "id": "llama3.2",
            "context_window": 131072,
            "max_output_tokens": 4096,
            "capabilities": ["text", "tool_use"],
            "input_cost": 0.0,
            "output_cost": 0.0
          }
        ]
      }
    ]
"""

from __future__ import annotations

import dataclasses
from dataclasses import dataclass, field
from typing import Any, Literal, Self

import aiohttp
from axio.models import ModelRegistry

from axio_transport_openai import OpenAITransport


[docs] @dataclass class OpenAICompatibleTransport(OpenAITransport): """OpenAI-compatible transport for a single user-defined provider. Instances are created by :class:`~axio_transport_openai.tui.custom.CustomHubScreen` with ``name``, ``base_url``, ``api_key``, and ``models`` populated from the JSON config. Supports JSON round-trip via :meth:`to_dict` / :meth:`from_dict`. """ # These point at servers that implement /v1/chat/completions and not /v1/responses. api: Literal["responses", "chat"] = field(default="chat", kw_only=True) base_url: str = "" # override OpenAITransport default models: ModelRegistry = field(default_factory=ModelRegistry) # empty default async def fetch_models(self) -> None: pass # models passed in at construction
[docs] @classmethod def from_dict(cls, data: dict[str, Any], *, session: aiohttp.ClientSession | None = None) -> Self: """As :meth:`OpenAITransport.from_dict`, but ``base_url``/``api_key`` come verbatim from ``data``. The base implementation reads a value saved empty as empty, and falls back to the ``OPENAI_BASE_URL``/``OPENAI_API_KEY`` env vars only where the key is absent altogether. That is right for the built-in OpenAI provider, whose settings dict is written by hand and omits what it wants the default for. A custom provider's config is a full round-trip of :meth:`to_dict`, so an absent key means the same thing as an empty one: this server takes no credential. Reading it through the environment would point a local server at an unrelated real endpoint. """ obj = super().from_dict(data, session=session) return dataclasses.replace(obj, base_url=str(data.get("base_url", "")), api_key=str(data.get("api_key", "")))