idside 0.1.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idside/__about__.py +5 -0
- idside/__init__.py +158 -0
- idside/_destination.py +165 -0
- idside/_guard.py +573 -0
- idside/_jcs.py +130 -0
- idside/_log.py +163 -0
- idside/_routing.py +280 -0
- idside/_transport.py +831 -0
- idside/adapters/__init__.py +110 -0
- idside/adapters/_call_options.py +859 -0
- idside/adapters/base.py +462 -0
- idside/adapters/gemini.py +89 -0
- idside/consent-options.json +207 -0
- idside/consent.py +1580 -0
- idside/exceptions.py +481 -0
- idside/options.py +297 -0
- idside/providers/__init__.py +252 -0
- idside/providers/anthropic.json +108 -0
- idside/providers/deepseek.json +108 -0
- idside/providers/gemini.json +130 -0
- idside/providers/mistral.json +195 -0
- idside/providers/openai.json +217 -0
- idside/py.typed +0 -0
- idside/verify.py +776 -0
- idside-0.1.0.dev0.dist-info/METADATA +473 -0
- idside-0.1.0.dev0.dist-info/RECORD +29 -0
- idside-0.1.0.dev0.dist-info/WHEEL +4 -0
- idside-0.1.0.dev0.dist-info/licenses/LICENSE +350 -0
- idside-0.1.0.dev0.dist-info/licenses/NOTICE +29 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Patents: https://sdk.idside.eu/patents
|
|
2
|
+
# This software may be covered by the patents and applications listed there.
|
|
3
|
+
|
|
4
|
+
"""Provider adapters: the descriptor-driven engine + auto-detection registry.
|
|
5
|
+
|
|
6
|
+
Standard providers are described entirely by a JSON descriptor (see
|
|
7
|
+
:mod:`idside.providers`). Exotic providers can register a ``ProviderAdapter``
|
|
8
|
+
subclass in ``_OVERRIDES`` to customize the mechanics.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
from urllib.parse import urlsplit
|
|
15
|
+
|
|
16
|
+
from idside.adapters.base import ProviderAdapter
|
|
17
|
+
from idside.adapters.gemini import GeminiAdapter
|
|
18
|
+
from idside.exceptions import UnsupportedProviderError
|
|
19
|
+
from idside.providers import ProviderDescriptor, load_descriptors
|
|
20
|
+
|
|
21
|
+
__all__ = ["GeminiAdapter", "ProviderAdapter", "get_adapter_for", "register_adapter"]
|
|
22
|
+
|
|
23
|
+
#: Per-provider engine overrides for exotic APIs (default: the generic engine).
|
|
24
|
+
#: google-genai takes no free-form kwargs, so Gemini ships a built-in override.
|
|
25
|
+
_OVERRIDES: dict[str, type[ProviderAdapter]] = {"gemini": GeminiAdapter}
|
|
26
|
+
|
|
27
|
+
#: Maps a client's module (or module prefix, e.g. "google.genai") to the
|
|
28
|
+
#: descriptors that use it. A LIST, not one descriptor: OpenAI-compatible
|
|
29
|
+
#: providers (DeepSeek, and Groq/xAI/Together when they get descriptors) are all
|
|
30
|
+
#: reached through the `openai` client, so a module can legitimately name
|
|
31
|
+
#: several. Prefixes let namespace packages match without capturing the whole
|
|
32
|
+
#: namespace ("google.genai" matches google.genai.client, not google.*).
|
|
33
|
+
_BY_MODULE: dict[str, list[ProviderDescriptor]] = {}
|
|
34
|
+
for _descriptor in load_descriptors().values():
|
|
35
|
+
_BY_MODULE.setdefault(_descriptor.client_module, []).append(_descriptor)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _base_url_host(client: Any) -> str | None:
|
|
39
|
+
"""The host a client is pointed at, or None if it does not say.
|
|
40
|
+
|
|
41
|
+
``openai.OpenAI`` exposes ``base_url`` as an ``httpx.URL``; other clients
|
|
42
|
+
expose a string, or nothing at all. Everything here is best-effort and
|
|
43
|
+
failure-tolerant, because this only ever *narrows* a choice.
|
|
44
|
+
"""
|
|
45
|
+
raw = getattr(client, "base_url", None)
|
|
46
|
+
if raw is None:
|
|
47
|
+
return None
|
|
48
|
+
try:
|
|
49
|
+
host = urlsplit(str(raw)).hostname
|
|
50
|
+
except ValueError: # a malformed URL is simply not a match
|
|
51
|
+
return None
|
|
52
|
+
return host.lower() if host else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _resolve(client: Any) -> ProviderDescriptor | None:
|
|
56
|
+
"""Pick the descriptor for ``client``: by module, then by base URL.
|
|
57
|
+
|
|
58
|
+
The second step is not a nicety. An `OpenAI` instance pointed at
|
|
59
|
+
api.deepseek.com is an `OpenAI` instance, so the class name says nothing
|
|
60
|
+
about who receives the request — and the provider name is what tells the
|
|
61
|
+
cloud which registry rules to apply. Answering "openai" for a DeepSeek call
|
|
62
|
+
would apply OpenAI's parameters to a server that never agreed to them.
|
|
63
|
+
"""
|
|
64
|
+
parts = type(client).__module__.split(".")
|
|
65
|
+
candidates = next(
|
|
66
|
+
(
|
|
67
|
+
found
|
|
68
|
+
for end in range(len(parts), 0, -1)
|
|
69
|
+
if (found := _BY_MODULE.get(".".join(parts[:end]))) is not None
|
|
70
|
+
),
|
|
71
|
+
None,
|
|
72
|
+
)
|
|
73
|
+
if not candidates:
|
|
74
|
+
return None
|
|
75
|
+
if len(candidates) == 1:
|
|
76
|
+
return candidates[0]
|
|
77
|
+
|
|
78
|
+
host = _base_url_host(client)
|
|
79
|
+
if host is not None:
|
|
80
|
+
for descriptor in candidates:
|
|
81
|
+
if host in descriptor.client_base_url_hosts:
|
|
82
|
+
return descriptor
|
|
83
|
+
|
|
84
|
+
# No host matched: fall back to the descriptor that claims no host in
|
|
85
|
+
# particular — the module's default (openai itself). If several do, the
|
|
86
|
+
# descriptors are ambiguous and guessing would be worse than refusing.
|
|
87
|
+
defaults = [d for d in candidates if not d.client_base_url_hosts]
|
|
88
|
+
return defaults[0] if len(defaults) == 1 else None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_adapter_for(client: Any) -> ProviderAdapter:
|
|
92
|
+
"""Return the adapter matching ``client``'s provider, or raise."""
|
|
93
|
+
descriptor = _resolve(client)
|
|
94
|
+
if descriptor is None:
|
|
95
|
+
raise UnsupportedProviderError(
|
|
96
|
+
f"No ID Side adapter for client of type "
|
|
97
|
+
f"{type(client).__module__}.{type(client).__qualname__}."
|
|
98
|
+
)
|
|
99
|
+
adapter_cls = _OVERRIDES.get(descriptor.name, ProviderAdapter)
|
|
100
|
+
return adapter_cls(descriptor)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def register_adapter(name: str, adapter_cls: type[ProviderAdapter]) -> None:
|
|
104
|
+
"""Register a ``ProviderAdapter`` subclass to override the generic engine.
|
|
105
|
+
|
|
106
|
+
Use this for exotic providers whose API does not fit the declarative model
|
|
107
|
+
(e.g. SDKs without per-request header support). ``name`` must match a
|
|
108
|
+
descriptor's ``name``.
|
|
109
|
+
"""
|
|
110
|
+
_OVERRIDES[name] = adapter_cls
|