pi-ai-python 1.3.0__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.
- pi_agent/__init__.py +127 -0
- pi_agent/agent.py +617 -0
- pi_agent/agent_loop.py +820 -0
- pi_agent/proxy.py +269 -0
- pi_agent/py.typed +0 -0
- pi_agent/stream_fn.py +27 -0
- pi_agent/tools.py +327 -0
- pi_agent/types.py +394 -0
- pi_ai/__init__.py +263 -0
- pi_ai/abort.py +315 -0
- pi_ai/api/__init__.py +1 -0
- pi_ai/api/anthropic_messages.py +1782 -0
- pi_ai/api/azure_openai_config.py +138 -0
- pi_ai/api/azure_openai_responses.py +245 -0
- pi_ai/api/bedrock_converse_stream.py +1598 -0
- pi_ai/api/classifier_shared.py +211 -0
- pi_ai/api/cloudflare.py +36 -0
- pi_ai/api/cloudflare_workers_ai_system_one.py +80 -0
- pi_ai/api/constrained_sampling.py +279 -0
- pi_ai/api/github_copilot_headers.py +30 -0
- pi_ai/api/google_generative_ai.py +138 -0
- pi_ai/api/google_shared.py +927 -0
- pi_ai/api/google_vertex.py +234 -0
- pi_ai/api/lazy.py +156 -0
- pi_ai/api/llama_cpp_classify.py +449 -0
- pi_ai/api/mistral_conversations.py +756 -0
- pi_ai/api/openai_codex_responses.py +1545 -0
- pi_ai/api/openai_completions.py +1594 -0
- pi_ai/api/openai_decisions.py +210 -0
- pi_ai/api/openai_prompt_cache.py +19 -0
- pi_ai/api/openai_responses.py +418 -0
- pi_ai/api/openai_responses_shared.py +840 -0
- pi_ai/api/openai_sdk.py +204 -0
- pi_ai/api/openrouter_images.py +153 -0
- pi_ai/api/pi_messages.py +449 -0
- pi_ai/api/registry.py +103 -0
- pi_ai/api/simple_options.py +119 -0
- pi_ai/api/system_one_shared.py +148 -0
- pi_ai/api/transform_messages.py +211 -0
- pi_ai/api/typesafe_system_one.py +43 -0
- pi_ai/auth/__init__.py +1 -0
- pi_ai/auth/context.py +47 -0
- pi_ai/auth/credential_store.py +208 -0
- pi_ai/auth/helpers.py +123 -0
- pi_ai/auth/oauth/__init__.py +5 -0
- pi_ai/auth/oauth/_common.py +454 -0
- pi_ai/auth/oauth/anthropic.py +283 -0
- pi_ai/auth/oauth/callback_server.py +339 -0
- pi_ai/auth/oauth/device_code.py +127 -0
- pi_ai/auth/oauth/github_copilot.py +508 -0
- pi_ai/auth/oauth/kimi_coding.py +283 -0
- pi_ai/auth/oauth/load.py +64 -0
- pi_ai/auth/oauth/meta.py +238 -0
- pi_ai/auth/oauth/openai_chatgpt.py +321 -0
- pi_ai/auth/oauth/openai_codex.py +391 -0
- pi_ai/auth/oauth/openrouter.py +173 -0
- pi_ai/auth/oauth/pkce.py +23 -0
- pi_ai/auth/oauth/radius.py +311 -0
- pi_ai/auth/oauth/xai.py +220 -0
- pi_ai/auth/resolve.py +190 -0
- pi_ai/auth/types.py +292 -0
- pi_ai/cli.py +265 -0
- pi_ai/compat.py +651 -0
- pi_ai/env_api_keys.py +137 -0
- pi_ai/event_stream.py +329 -0
- pi_ai/models.py +1191 -0
- pi_ai/models_store.py +121 -0
- pi_ai/oauth.py +90 -0
- pi_ai/providers/__init__.py +1 -0
- pi_ai/providers/all.py +154 -0
- pi_ai/providers/amazon_bedrock.py +116 -0
- pi_ai/providers/ant_ling.py +23 -0
- pi_ai/providers/anthropic.py +105 -0
- pi_ai/providers/azure.py +103 -0
- pi_ai/providers/baseten.py +23 -0
- pi_ai/providers/catalog.py +167 -0
- pi_ai/providers/cerebras.py +23 -0
- pi_ai/providers/cloudflare_ai_gateway.py +30 -0
- pi_ai/providers/cloudflare_auth.py +112 -0
- pi_ai/providers/cloudflare_stream.py +84 -0
- pi_ai/providers/cloudflare_workers_ai.py +26 -0
- pi_ai/providers/data/amazon-bedrock.json +1 -0
- pi_ai/providers/data/ant-ling.json +1 -0
- pi_ai/providers/data/anthropic.json +1 -0
- pi_ai/providers/data/azure.json +1 -0
- pi_ai/providers/data/baseten.json +1 -0
- pi_ai/providers/data/cerebras.json +1 -0
- pi_ai/providers/data/cloudflare-ai-gateway.json +1 -0
- pi_ai/providers/data/cloudflare-workers-ai.json +1 -0
- pi_ai/providers/data/deepseek.json +1 -0
- pi_ai/providers/data/fireworks.json +1 -0
- pi_ai/providers/data/github-copilot.json +1 -0
- pi_ai/providers/data/google-vertex.json +1 -0
- pi_ai/providers/data/google.json +1 -0
- pi_ai/providers/data/groq.json +1 -0
- pi_ai/providers/data/huggingface.json +1 -0
- pi_ai/providers/data/kimi-coding.json +1 -0
- pi_ai/providers/data/manifest.json +1 -0
- pi_ai/providers/data/meta.json +1 -0
- pi_ai/providers/data/minimax-cn.json +1 -0
- pi_ai/providers/data/minimax.json +1 -0
- pi_ai/providers/data/mistral.json +1 -0
- pi_ai/providers/data/moonshotai-cn.json +1 -0
- pi_ai/providers/data/moonshotai.json +1 -0
- pi_ai/providers/data/nvidia.json +1 -0
- pi_ai/providers/data/openai-codex.json +1 -0
- pi_ai/providers/data/openai.json +1 -0
- pi_ai/providers/data/opencode-go.json +1 -0
- pi_ai/providers/data/opencode.json +1 -0
- pi_ai/providers/data/openrouter.json +1 -0
- pi_ai/providers/data/qwen-token-plan-cn.json +1 -0
- pi_ai/providers/data/qwen-token-plan-individual.json +1 -0
- pi_ai/providers/data/qwen-token-plan.json +1 -0
- pi_ai/providers/data/radius.json +1 -0
- pi_ai/providers/data/together.json +1 -0
- pi_ai/providers/data/typesafe.json +1 -0
- pi_ai/providers/data/vercel-ai-gateway.json +1 -0
- pi_ai/providers/data/xai.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-ams.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-cn.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-sgp.json +1 -0
- pi_ai/providers/data/xiaomi.json +1 -0
- pi_ai/providers/data/zai-coding-cn.json +1 -0
- pi_ai/providers/data/zai.json +1 -0
- pi_ai/providers/deepseek.py +23 -0
- pi_ai/providers/faux.py +680 -0
- pi_ai/providers/fireworks.py +26 -0
- pi_ai/providers/github_copilot.py +46 -0
- pi_ai/providers/google.py +23 -0
- pi_ai/providers/google_vertex.py +131 -0
- pi_ai/providers/groq.py +23 -0
- pi_ai/providers/huggingface.py +23 -0
- pi_ai/providers/kimi_coding.py +32 -0
- pi_ai/providers/meta.py +32 -0
- pi_ai/providers/minimax.py +23 -0
- pi_ai/providers/minimax_cn.py +23 -0
- pi_ai/providers/mistral.py +23 -0
- pi_ai/providers/moonshotai.py +23 -0
- pi_ai/providers/moonshotai_cn.py +23 -0
- pi_ai/providers/nvidia.py +23 -0
- pi_ai/providers/openai.py +44 -0
- pi_ai/providers/openai_codex.py +26 -0
- pi_ai/providers/opencode.py +36 -0
- pi_ai/providers/opencode_go.py +33 -0
- pi_ai/providers/opencode_headers.py +60 -0
- pi_ai/providers/openrouter.py +145 -0
- pi_ai/providers/openrouter_api.py +122 -0
- pi_ai/providers/qwen_token_plan.py +23 -0
- pi_ai/providers/qwen_token_plan_cn.py +23 -0
- pi_ai/providers/qwen_token_plan_individual.py +23 -0
- pi_ai/providers/radius.py +146 -0
- pi_ai/providers/radius_config.py +136 -0
- pi_ai/providers/together.py +23 -0
- pi_ai/providers/typesafe.py +22 -0
- pi_ai/providers/vercel_ai_gateway.py +25 -0
- pi_ai/providers/xai.py +32 -0
- pi_ai/providers/xiaomi.py +23 -0
- pi_ai/providers/xiaomi_token_plan_ams.py +23 -0
- pi_ai/providers/xiaomi_token_plan_cn.py +23 -0
- pi_ai/providers/xiaomi_token_plan_sgp.py +23 -0
- pi_ai/providers/zai.py +23 -0
- pi_ai/providers/zai_coding_cn.py +23 -0
- pi_ai/py.typed +0 -0
- pi_ai/serialization.py +477 -0
- pi_ai/session_resources.py +33 -0
- pi_ai/types.py +1003 -0
- pi_ai/utils/__init__.py +1 -0
- pi_ai/utils/callbacks.py +15 -0
- pi_ai/utils/diagnostics.py +70 -0
- pi_ai/utils/error_body.py +89 -0
- pi_ai/utils/estimate.py +125 -0
- pi_ai/utils/fastjson.py +63 -0
- pi_ai/utils/http.py +409 -0
- pi_ai/utils/json_parse.py +424 -0
- pi_ai/utils/misc.py +193 -0
- pi_ai/utils/model_operations.py +73 -0
- pi_ai/utils/models_error.py +35 -0
- pi_ai/utils/oauth_page.py +121 -0
- pi_ai/utils/overflow.py +87 -0
- pi_ai/utils/retry.py +193 -0
- pi_ai/utils/schema.py +283 -0
- pi_ai/utils/text.py +65 -0
- pi_ai/utils/transcript.py +271 -0
- pi_ai/utils/validation.py +813 -0
- pi_ai_python-1.3.0.dist-info/METADATA +167 -0
- pi_ai_python-1.3.0.dist-info/RECORD +189 -0
- pi_ai_python-1.3.0.dist-info/WHEEL +4 -0
- pi_ai_python-1.3.0.dist-info/entry_points.txt +2 -0
- pi_ai_python-1.3.0.dist-info/licenses/LICENSE +22 -0
pi_ai/models_store.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Persistent storage for dynamic model catalogs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import copy
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Protocol
|
|
13
|
+
|
|
14
|
+
from .abort import AbortSignal
|
|
15
|
+
|
|
16
|
+
__all__ = ["ModelsStoreEntry", "ModelsStore", "InMemoryModelsStore", "FileModelsStore"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class ModelsStoreEntry:
|
|
21
|
+
#: Persisted models of every type.
|
|
22
|
+
models: list[Any] = field(default_factory=list)
|
|
23
|
+
#: Unix timestamp (ms) from the remote catalog's Last-Modified header.
|
|
24
|
+
last_modified: int | None = None
|
|
25
|
+
#: Unix timestamp (ms) of the last completed remote check.
|
|
26
|
+
checked_at: int | None = None
|
|
27
|
+
#: Opaque validator from the remote catalog's ETag header, echoed back as If-None-Match.
|
|
28
|
+
etag: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ModelsStore(Protocol):
|
|
32
|
+
"""Persistent model catalogs keyed by provider id."""
|
|
33
|
+
|
|
34
|
+
async def read(self, provider_id: str, *, signal: AbortSignal | None = None) -> ModelsStoreEntry | None: ...
|
|
35
|
+
|
|
36
|
+
async def write(self, provider_id: str, entry: ModelsStoreEntry, *, signal: AbortSignal | None = None) -> None: ...
|
|
37
|
+
|
|
38
|
+
async def delete(self, provider_id: str, *, signal: AbortSignal | None = None) -> None: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InMemoryModelsStore:
|
|
42
|
+
"""Default in-process store. Entries are deep-copied on read and write."""
|
|
43
|
+
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
self._entries: dict[str, ModelsStoreEntry] = {}
|
|
46
|
+
|
|
47
|
+
async def read(self, provider_id: str, *, signal: AbortSignal | None = None) -> ModelsStoreEntry | None:
|
|
48
|
+
if signal is not None:
|
|
49
|
+
signal.throw_if_aborted()
|
|
50
|
+
entry = self._entries.get(provider_id)
|
|
51
|
+
return copy.deepcopy(entry) if entry is not None else None
|
|
52
|
+
|
|
53
|
+
async def write(self, provider_id: str, entry: ModelsStoreEntry, *, signal: AbortSignal | None = None) -> None:
|
|
54
|
+
if signal is not None:
|
|
55
|
+
signal.throw_if_aborted()
|
|
56
|
+
self._entries[provider_id] = copy.deepcopy(entry)
|
|
57
|
+
|
|
58
|
+
async def delete(self, provider_id: str, *, signal: AbortSignal | None = None) -> None:
|
|
59
|
+
if signal is not None:
|
|
60
|
+
signal.throw_if_aborted()
|
|
61
|
+
self._entries.pop(provider_id, None)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class FileModelsStore:
|
|
65
|
+
"""JSON-file store (``{"<provider>": {"models": [...], "checkedAt": ...}}``) in pi's format."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, path: str | os.PathLike[str]) -> None:
|
|
68
|
+
self.path = Path(path).expanduser()
|
|
69
|
+
self._lock = asyncio.Lock()
|
|
70
|
+
|
|
71
|
+
def _load(self) -> dict[str, Any]:
|
|
72
|
+
try:
|
|
73
|
+
return json.loads(self.path.read_text("utf-8"))
|
|
74
|
+
except FileNotFoundError:
|
|
75
|
+
return {}
|
|
76
|
+
|
|
77
|
+
def _save(self, data: dict[str, Any]) -> None:
|
|
78
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
fd, tmp = tempfile.mkstemp(dir=self.path.parent, prefix=".models-", suffix=".json")
|
|
80
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
81
|
+
json.dump(data, handle, ensure_ascii=False, indent=2)
|
|
82
|
+
os.replace(tmp, self.path)
|
|
83
|
+
|
|
84
|
+
async def read(self, provider_id: str, *, signal: AbortSignal | None = None) -> ModelsStoreEntry | None:
|
|
85
|
+
from .serialization import model_from_dict
|
|
86
|
+
|
|
87
|
+
if signal is not None:
|
|
88
|
+
signal.throw_if_aborted()
|
|
89
|
+
raw = (await asyncio.to_thread(self._load)).get(provider_id)
|
|
90
|
+
if not isinstance(raw, dict):
|
|
91
|
+
return None
|
|
92
|
+
models = []
|
|
93
|
+
for item in raw.get("models", []):
|
|
94
|
+
try:
|
|
95
|
+
models.append(model_from_dict(item))
|
|
96
|
+
except ValueError:
|
|
97
|
+
continue # unknown model types from newer versions
|
|
98
|
+
return ModelsStoreEntry(
|
|
99
|
+
models=models,
|
|
100
|
+
last_modified=raw.get("lastModified"),
|
|
101
|
+
checked_at=raw.get("checkedAt"),
|
|
102
|
+
etag=raw.get("etag"),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def write(self, provider_id: str, entry: ModelsStoreEntry, *, signal: AbortSignal | None = None) -> None:
|
|
106
|
+
from .serialization import to_json_value
|
|
107
|
+
|
|
108
|
+
if signal is not None:
|
|
109
|
+
signal.throw_if_aborted()
|
|
110
|
+
async with self._lock:
|
|
111
|
+
data = await asyncio.to_thread(self._load)
|
|
112
|
+
data[provider_id] = to_json_value(entry)
|
|
113
|
+
await asyncio.to_thread(self._save, data)
|
|
114
|
+
|
|
115
|
+
async def delete(self, provider_id: str, *, signal: AbortSignal | None = None) -> None:
|
|
116
|
+
if signal is not None:
|
|
117
|
+
signal.throw_if_aborted()
|
|
118
|
+
async with self._lock:
|
|
119
|
+
data = await asyncio.to_thread(self._load)
|
|
120
|
+
if data.pop(provider_id, None) is not None:
|
|
121
|
+
await asyncio.to_thread(self._save, data)
|
pi_ai/oauth.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Type-only compatibility entry point for coding-agent extension OAuth declarations.
|
|
2
|
+
|
|
3
|
+
These legacy callback shapes are kept so extensions written against the older
|
|
4
|
+
``OAuthLoginCallbacks`` surface keep type-checking. Built-in flows use
|
|
5
|
+
:class:`pi_ai.OAuthAuth` with :class:`pi_ai.ProviderAuthInteraction` instead, and
|
|
6
|
+
this module never imports them.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Awaitable, Callable
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import TYPE_CHECKING, Protocol
|
|
14
|
+
|
|
15
|
+
from .auth.oauth._common import OAuthError
|
|
16
|
+
from .auth.types import OAuthCredentials
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .abort import AbortSignal
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"OAuthError",
|
|
23
|
+
"OAuthAuthInfo",
|
|
24
|
+
"OAuthCredentials",
|
|
25
|
+
"OAuthDeviceCodeInfo",
|
|
26
|
+
"OAuthLoginCallbacks",
|
|
27
|
+
"OAuthPrompt",
|
|
28
|
+
"OAuthSelectOption",
|
|
29
|
+
"OAuthSelectPrompt",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True)
|
|
34
|
+
class OAuthPrompt:
|
|
35
|
+
"""Legacy extension OAuth prompt."""
|
|
36
|
+
|
|
37
|
+
message: str
|
|
38
|
+
placeholder: str | None = None
|
|
39
|
+
allow_empty: bool | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class OAuthAuthInfo:
|
|
44
|
+
"""Legacy extension OAuth authorization link."""
|
|
45
|
+
|
|
46
|
+
url: str
|
|
47
|
+
instructions: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(slots=True)
|
|
51
|
+
class OAuthDeviceCodeInfo:
|
|
52
|
+
"""Legacy extension OAuth device-code notification."""
|
|
53
|
+
|
|
54
|
+
user_code: str
|
|
55
|
+
verification_uri: str
|
|
56
|
+
interval_seconds: float | None = None
|
|
57
|
+
expires_in_seconds: float | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(slots=True)
|
|
61
|
+
class OAuthSelectOption:
|
|
62
|
+
id: str
|
|
63
|
+
label: str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(slots=True)
|
|
67
|
+
class OAuthSelectPrompt:
|
|
68
|
+
message: str
|
|
69
|
+
options: list[OAuthSelectOption] = field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OAuthLoginCallbacks(Protocol):
|
|
73
|
+
"""Callback surface retained only for coding-agent extension compatibility.
|
|
74
|
+
|
|
75
|
+
``on_progress`` and ``on_manual_code_input`` are optional (``None`` when unsupported).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
#: Optional progress message sink.
|
|
79
|
+
on_progress: Callable[[str], None] | None
|
|
80
|
+
#: Optional manual code entry that races the browser callback.
|
|
81
|
+
on_manual_code_input: Callable[[], Awaitable[str]] | None
|
|
82
|
+
signal: AbortSignal | None
|
|
83
|
+
|
|
84
|
+
def on_auth(self, info: OAuthAuthInfo) -> None: ...
|
|
85
|
+
|
|
86
|
+
def on_device_code(self, info: OAuthDeviceCodeInfo) -> None: ...
|
|
87
|
+
|
|
88
|
+
async def on_prompt(self, prompt: OAuthPrompt) -> str: ...
|
|
89
|
+
|
|
90
|
+
async def on_select(self, prompt: OAuthSelectPrompt) -> str | None: ...
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provider factories. Import ``pi_ai.providers.all`` for every built-in provider."""
|
pi_ai/providers/all.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Every built-in provider, plus static reads of the generated catalog.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
from pi_ai.providers.all import builtin_models
|
|
6
|
+
|
|
7
|
+
models = builtin_models()
|
|
8
|
+
model = models.get_model("anthropic", "claude-sonnet-4-5")
|
|
9
|
+
|
|
10
|
+
Building providers is cheap: catalogs load on first access to a provider's models
|
|
11
|
+
and API implementations on the first request.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import TypeAlias
|
|
17
|
+
|
|
18
|
+
from ..auth.types import AuthContext, CredentialStore
|
|
19
|
+
from ..models import Models, Provider, create_models
|
|
20
|
+
from ..models_store import ModelsStore
|
|
21
|
+
from .amazon_bedrock import amazon_bedrock_provider
|
|
22
|
+
from .ant_ling import ant_ling_provider
|
|
23
|
+
from .anthropic import anthropic_provider
|
|
24
|
+
from .azure import azure_provider
|
|
25
|
+
from .baseten import baseten_provider
|
|
26
|
+
from .catalog import (
|
|
27
|
+
BUILTIN_PROVIDERS,
|
|
28
|
+
get_all_builtin_models,
|
|
29
|
+
get_builtin_classifier_model,
|
|
30
|
+
get_builtin_classifier_models,
|
|
31
|
+
get_builtin_image_model,
|
|
32
|
+
get_builtin_image_models,
|
|
33
|
+
get_builtin_model,
|
|
34
|
+
get_builtin_model_data_generated_at,
|
|
35
|
+
get_builtin_models,
|
|
36
|
+
get_builtin_providers,
|
|
37
|
+
)
|
|
38
|
+
from .cerebras import cerebras_provider
|
|
39
|
+
from .cloudflare_ai_gateway import cloudflare_ai_gateway_provider
|
|
40
|
+
from .cloudflare_workers_ai import cloudflare_workers_ai_provider
|
|
41
|
+
from .deepseek import deepseek_provider
|
|
42
|
+
from .fireworks import fireworks_provider
|
|
43
|
+
from .github_copilot import github_copilot_provider
|
|
44
|
+
from .google import google_provider
|
|
45
|
+
from .google_vertex import google_vertex_provider
|
|
46
|
+
from .groq import groq_provider
|
|
47
|
+
from .huggingface import huggingface_provider
|
|
48
|
+
from .kimi_coding import kimi_coding_provider
|
|
49
|
+
from .meta import meta_provider
|
|
50
|
+
from .minimax import minimax_provider
|
|
51
|
+
from .minimax_cn import minimax_cn_provider
|
|
52
|
+
from .mistral import mistral_provider
|
|
53
|
+
from .moonshotai import moonshotai_provider
|
|
54
|
+
from .moonshotai_cn import moonshotai_cn_provider
|
|
55
|
+
from .nvidia import nvidia_provider
|
|
56
|
+
from .openai import openai_provider
|
|
57
|
+
from .openai_codex import openai_codex_provider
|
|
58
|
+
from .opencode import opencode_provider
|
|
59
|
+
from .opencode_go import opencode_go_provider
|
|
60
|
+
from .openrouter import openrouter_provider
|
|
61
|
+
from .qwen_token_plan import qwen_token_plan_provider
|
|
62
|
+
from .qwen_token_plan_cn import qwen_token_plan_cn_provider
|
|
63
|
+
from .qwen_token_plan_individual import qwen_token_plan_individual_provider
|
|
64
|
+
from .radius import radius_provider
|
|
65
|
+
from .together import together_provider
|
|
66
|
+
from .typesafe import typesafe_provider
|
|
67
|
+
from .vercel_ai_gateway import vercel_ai_gateway_provider
|
|
68
|
+
from .xai import xai_provider
|
|
69
|
+
from .xiaomi import xiaomi_provider
|
|
70
|
+
from .xiaomi_token_plan_ams import xiaomi_token_plan_ams_provider
|
|
71
|
+
from .xiaomi_token_plan_cn import xiaomi_token_plan_cn_provider
|
|
72
|
+
from .xiaomi_token_plan_sgp import xiaomi_token_plan_sgp_provider
|
|
73
|
+
from .zai import zai_provider
|
|
74
|
+
from .zai_coding_cn import zai_coding_cn_provider
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"BuiltinProvider",
|
|
78
|
+
"BUILTIN_PROVIDERS",
|
|
79
|
+
"builtin_providers",
|
|
80
|
+
"builtin_models",
|
|
81
|
+
"radius_provider",
|
|
82
|
+
"get_builtin_model",
|
|
83
|
+
"get_builtin_image_model",
|
|
84
|
+
"get_builtin_classifier_model",
|
|
85
|
+
"get_builtin_providers",
|
|
86
|
+
"get_builtin_model_data_generated_at",
|
|
87
|
+
"get_builtin_models",
|
|
88
|
+
"get_builtin_image_models",
|
|
89
|
+
"get_builtin_classifier_models",
|
|
90
|
+
"get_all_builtin_models",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
#: Providers present in the generated catalog (see :data:`BUILTIN_PROVIDERS`).
|
|
94
|
+
BuiltinProvider: TypeAlias = str
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def builtin_providers() -> list[Provider]:
|
|
98
|
+
"""All built-in providers, freshly constructed."""
|
|
99
|
+
return [
|
|
100
|
+
amazon_bedrock_provider(),
|
|
101
|
+
ant_ling_provider(),
|
|
102
|
+
anthropic_provider(),
|
|
103
|
+
azure_provider(),
|
|
104
|
+
baseten_provider(),
|
|
105
|
+
cerebras_provider(),
|
|
106
|
+
cloudflare_ai_gateway_provider(),
|
|
107
|
+
cloudflare_workers_ai_provider(),
|
|
108
|
+
deepseek_provider(),
|
|
109
|
+
fireworks_provider(),
|
|
110
|
+
github_copilot_provider(),
|
|
111
|
+
google_provider(),
|
|
112
|
+
google_vertex_provider(),
|
|
113
|
+
groq_provider(),
|
|
114
|
+
huggingface_provider(),
|
|
115
|
+
kimi_coding_provider(),
|
|
116
|
+
meta_provider(),
|
|
117
|
+
minimax_provider(),
|
|
118
|
+
minimax_cn_provider(),
|
|
119
|
+
mistral_provider(),
|
|
120
|
+
moonshotai_provider(),
|
|
121
|
+
moonshotai_cn_provider(),
|
|
122
|
+
nvidia_provider(),
|
|
123
|
+
openai_provider(),
|
|
124
|
+
openai_codex_provider(),
|
|
125
|
+
opencode_provider(),
|
|
126
|
+
opencode_go_provider(),
|
|
127
|
+
openrouter_provider(),
|
|
128
|
+
qwen_token_plan_provider(),
|
|
129
|
+
qwen_token_plan_cn_provider(),
|
|
130
|
+
qwen_token_plan_individual_provider(),
|
|
131
|
+
radius_provider(),
|
|
132
|
+
together_provider(),
|
|
133
|
+
typesafe_provider(),
|
|
134
|
+
vercel_ai_gateway_provider(),
|
|
135
|
+
xai_provider(),
|
|
136
|
+
xiaomi_provider(),
|
|
137
|
+
xiaomi_token_plan_ams_provider(),
|
|
138
|
+
xiaomi_token_plan_cn_provider(),
|
|
139
|
+
xiaomi_token_plan_sgp_provider(),
|
|
140
|
+
zai_provider(),
|
|
141
|
+
zai_coding_cn_provider(),
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def builtin_models(
|
|
146
|
+
credentials: CredentialStore | None = None,
|
|
147
|
+
models_store: ModelsStore | None = None,
|
|
148
|
+
auth_context: AuthContext | None = None,
|
|
149
|
+
) -> Models:
|
|
150
|
+
"""A :class:`~pi_ai.Models` collection with every built-in provider registered."""
|
|
151
|
+
models = create_models(credentials=credentials, models_store=models_store, auth_context=auth_context)
|
|
152
|
+
for provider in builtin_providers():
|
|
153
|
+
models.set_provider(provider)
|
|
154
|
+
return models
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Amazon Bedrock provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..abort import AbortSignal
|
|
6
|
+
from ..api.registry import bedrock_converse_stream_api
|
|
7
|
+
from ..auth.types import (
|
|
8
|
+
ApiKeyAuth,
|
|
9
|
+
AuthContext,
|
|
10
|
+
AuthEvent,
|
|
11
|
+
AuthInfoLink,
|
|
12
|
+
AuthPrompt,
|
|
13
|
+
AuthResult,
|
|
14
|
+
AuthSelectOption,
|
|
15
|
+
Credential,
|
|
16
|
+
ModelAuth,
|
|
17
|
+
ProviderAuth,
|
|
18
|
+
ProviderAuthInteraction,
|
|
19
|
+
)
|
|
20
|
+
from ..models import Provider, create_provider
|
|
21
|
+
from .catalog import catalog_models
|
|
22
|
+
|
|
23
|
+
__all__ = ["amazon_bedrock_provider", "bedrock_auth"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def _login(interaction: ProviderAuthInteraction) -> Credential:
|
|
27
|
+
interaction.signal.throw_if_aborted()
|
|
28
|
+
method = await interaction.prompt(
|
|
29
|
+
AuthPrompt(
|
|
30
|
+
type="select",
|
|
31
|
+
message="Select Amazon Bedrock authentication method:",
|
|
32
|
+
options=[
|
|
33
|
+
AuthSelectOption(id="bearer-token", label="Bearer token"),
|
|
34
|
+
AuthSelectOption(id="aws-profile", label="AWS profile"),
|
|
35
|
+
AuthSelectOption(id="credential-chain", label="Existing AWS credential chain"),
|
|
36
|
+
],
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
interaction.signal.throw_if_aborted()
|
|
40
|
+
if method == "bearer-token":
|
|
41
|
+
return {
|
|
42
|
+
"type": "api_key",
|
|
43
|
+
"key": await interaction.prompt(AuthPrompt(type="secret", message="Enter Amazon Bedrock bearer token")),
|
|
44
|
+
}
|
|
45
|
+
interaction.notify(
|
|
46
|
+
AuthEvent(
|
|
47
|
+
type="info",
|
|
48
|
+
message="Amazon Bedrock supports AWS profiles, IAM credentials, and role-based credentials.",
|
|
49
|
+
links=[
|
|
50
|
+
AuthInfoLink(
|
|
51
|
+
label="AWS credential provider chain",
|
|
52
|
+
url="https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html",
|
|
53
|
+
)
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
if method == "aws-profile":
|
|
58
|
+
return {
|
|
59
|
+
"type": "api_key",
|
|
60
|
+
"env": {"AWS_PROFILE": await interaction.prompt(AuthPrompt(type="text", message="Enter AWS profile name"))},
|
|
61
|
+
}
|
|
62
|
+
if method != "credential-chain":
|
|
63
|
+
raise ValueError(f"Unknown Amazon Bedrock auth method: {method}")
|
|
64
|
+
await interaction.prompt(AuthPrompt(type="text", message="Configure AWS credentials, then press Enter to continue"))
|
|
65
|
+
return {"type": "api_key"}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def _resolve(*, ctx: AuthContext, credential: Credential | None, signal: AbortSignal) -> AuthResult | None:
|
|
69
|
+
async def env(name: str) -> str | None:
|
|
70
|
+
signal.throw_if_aborted()
|
|
71
|
+
value = await ctx.env(name)
|
|
72
|
+
signal.throw_if_aborted()
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
if credential and credential.get("key"):
|
|
76
|
+
return AuthResult(ModelAuth(api_key=credential["key"]), env=credential.get("env"), source="stored credential")
|
|
77
|
+
if await env("AWS_BEARER_TOKEN_BEDROCK"):
|
|
78
|
+
return AuthResult(ModelAuth(), source="AWS_BEARER_TOKEN_BEDROCK")
|
|
79
|
+
credential_env = credential.get("env") if credential else None
|
|
80
|
+
stored_profile = credential_env.get("AWS_PROFILE") if credential_env else None
|
|
81
|
+
profile = stored_profile if stored_profile is not None else await env("AWS_PROFILE")
|
|
82
|
+
if profile:
|
|
83
|
+
return AuthResult(
|
|
84
|
+
ModelAuth(),
|
|
85
|
+
env=credential_env,
|
|
86
|
+
source="stored credential" if stored_profile else "AWS_PROFILE",
|
|
87
|
+
)
|
|
88
|
+
if (await env("AWS_ACCESS_KEY_ID")) and (await env("AWS_SECRET_ACCESS_KEY")):
|
|
89
|
+
return AuthResult(ModelAuth(), source="AWS access keys")
|
|
90
|
+
if await env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"):
|
|
91
|
+
return AuthResult(ModelAuth(), source="ECS task role")
|
|
92
|
+
if await env("AWS_CONTAINER_CREDENTIALS_FULL_URI"):
|
|
93
|
+
return AuthResult(ModelAuth(), source="ECS task role")
|
|
94
|
+
if await env("AWS_WEB_IDENTITY_TOKEN_FILE"):
|
|
95
|
+
return AuthResult(ModelAuth(), source="web identity token")
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def bedrock_auth() -> ApiKeyAuth:
|
|
100
|
+
"""Bedrock accepts a bearer token or the AWS default credential chain.
|
|
101
|
+
|
|
102
|
+
The login flow can store a token/profile choice; ``resolve`` also detects ambient
|
|
103
|
+
AWS credentials without copying them into pi's credential store.
|
|
104
|
+
"""
|
|
105
|
+
return ApiKeyAuth("AWS credentials or bearer token", _resolve, login=_login)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def amazon_bedrock_provider() -> Provider:
|
|
109
|
+
"""Amazon Bedrock (Converse Stream API) with bearer-token or AWS credential-chain auth."""
|
|
110
|
+
return create_provider(
|
|
111
|
+
id="amazon-bedrock",
|
|
112
|
+
name="Amazon Bedrock",
|
|
113
|
+
auth=ProviderAuth(api_key=bedrock_auth()),
|
|
114
|
+
models=lambda: catalog_models("amazon-bedrock"),
|
|
115
|
+
api=bedrock_converse_stream_api(),
|
|
116
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Ant Ling provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..api.registry import openai_completions_api
|
|
6
|
+
from ..auth.helpers import env_api_key_auth
|
|
7
|
+
from ..auth.types import ProviderAuth
|
|
8
|
+
from ..models import Provider, create_provider
|
|
9
|
+
from .catalog import catalog_models
|
|
10
|
+
|
|
11
|
+
__all__ = ["ant_ling_provider"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ant_ling_provider() -> Provider:
|
|
15
|
+
"""Ant Ling (OpenAI-compatible Chat Completions), authenticated with ``ANT_LING_API_KEY``."""
|
|
16
|
+
return create_provider(
|
|
17
|
+
id="ant-ling",
|
|
18
|
+
name="Ant Ling",
|
|
19
|
+
base_url="https://api.ant-ling.com/v1",
|
|
20
|
+
auth=ProviderAuth(api_key=env_api_key_auth("Ant Ling API key", ["ANT_LING_API_KEY"])),
|
|
21
|
+
models=lambda: catalog_models("ant-ling"),
|
|
22
|
+
api=openai_completions_api(),
|
|
23
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Anthropic provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..abort import AbortSignal
|
|
6
|
+
from ..api.registry import anthropic_messages_api
|
|
7
|
+
from ..auth.helpers import lazy_oauth
|
|
8
|
+
from ..auth.oauth.load import load_anthropic_oauth
|
|
9
|
+
from ..auth.types import (
|
|
10
|
+
ApiKeyAuth,
|
|
11
|
+
AuthContext,
|
|
12
|
+
AuthPrompt,
|
|
13
|
+
AuthResult,
|
|
14
|
+
Credential,
|
|
15
|
+
ModelAuth,
|
|
16
|
+
ProviderAuth,
|
|
17
|
+
ProviderAuthInteraction,
|
|
18
|
+
)
|
|
19
|
+
from ..env_api_keys import (
|
|
20
|
+
ANTHROPIC_API_KEY_ENV,
|
|
21
|
+
ANTHROPIC_AUTH_TOKEN_ENV,
|
|
22
|
+
ANTHROPIC_FEDERATION_RULE_ID_ENV,
|
|
23
|
+
ANTHROPIC_IDENTITY_TOKEN_FILE_ENV,
|
|
24
|
+
ANTHROPIC_OAUTH_TOKEN_ENV,
|
|
25
|
+
ANTHROPIC_ORGANIZATION_ID_ENV,
|
|
26
|
+
ANTHROPIC_SERVICE_ACCOUNT_ID_ENV,
|
|
27
|
+
ANTHROPIC_WORKSPACE_ID_ENV,
|
|
28
|
+
)
|
|
29
|
+
from ..models import Provider, create_provider
|
|
30
|
+
from .catalog import catalog_models
|
|
31
|
+
|
|
32
|
+
__all__ = ["anthropic_provider", "anthropic_api_key_auth"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def anthropic_api_key_auth() -> ApiKeyAuth:
|
|
36
|
+
"""Anthropic API-key auth.
|
|
37
|
+
|
|
38
|
+
Order: stored key, ``ANTHROPIC_AUTH_TOKEN`` (sent as ``Authorization: Bearer``),
|
|
39
|
+
``ANTHROPIC_OAUTH_TOKEN``, ``ANTHROPIC_API_KEY``, then workload identity federation.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
async def login(interaction: ProviderAuthInteraction) -> Credential:
|
|
43
|
+
interaction.signal.throw_if_aborted()
|
|
44
|
+
key = await interaction.prompt(AuthPrompt(type="secret", message="Enter Anthropic API key"))
|
|
45
|
+
interaction.signal.throw_if_aborted()
|
|
46
|
+
return {"type": "api_key", "key": key}
|
|
47
|
+
|
|
48
|
+
async def resolve(*, ctx: AuthContext, credential: Credential | None, signal: AbortSignal) -> AuthResult | None:
|
|
49
|
+
signal.throw_if_aborted()
|
|
50
|
+
if credential and credential.get("key"):
|
|
51
|
+
return AuthResult(
|
|
52
|
+
ModelAuth(api_key=credential["key"]), env=credential.get("env"), source="stored credential"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
auth_token = await ctx.env(ANTHROPIC_AUTH_TOKEN_ENV)
|
|
56
|
+
signal.throw_if_aborted()
|
|
57
|
+
if auth_token:
|
|
58
|
+
return AuthResult(
|
|
59
|
+
ModelAuth(headers={"Authorization": f"Bearer {auth_token}"}), source=ANTHROPIC_AUTH_TOKEN_ENV
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
for env_var in (ANTHROPIC_OAUTH_TOKEN_ENV, ANTHROPIC_API_KEY_ENV):
|
|
63
|
+
api_key = await ctx.env(env_var)
|
|
64
|
+
signal.throw_if_aborted()
|
|
65
|
+
if api_key:
|
|
66
|
+
return AuthResult(ModelAuth(api_key=api_key), source=env_var)
|
|
67
|
+
|
|
68
|
+
# Workload identity federation: the API implementation exchanges the identity
|
|
69
|
+
# token for a short-lived access token and refreshes it itself. Last in
|
|
70
|
+
# line so keys and ANTHROPIC_AUTH_TOKEN keep winning, as in the SDK. The
|
|
71
|
+
# ids are provider config rather than auth, so they travel in `env`.
|
|
72
|
+
federation: dict[str, str] = {}
|
|
73
|
+
for env_var in (
|
|
74
|
+
ANTHROPIC_FEDERATION_RULE_ID_ENV,
|
|
75
|
+
ANTHROPIC_ORGANIZATION_ID_ENV,
|
|
76
|
+
ANTHROPIC_IDENTITY_TOKEN_FILE_ENV,
|
|
77
|
+
):
|
|
78
|
+
value = await ctx.env(env_var)
|
|
79
|
+
signal.throw_if_aborted()
|
|
80
|
+
if not value:
|
|
81
|
+
return None
|
|
82
|
+
federation[env_var] = value
|
|
83
|
+
for env_var in (ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, ANTHROPIC_WORKSPACE_ID_ENV):
|
|
84
|
+
value = await ctx.env(env_var)
|
|
85
|
+
signal.throw_if_aborted()
|
|
86
|
+
if value:
|
|
87
|
+
federation[env_var] = value
|
|
88
|
+
return AuthResult(ModelAuth(), env=federation, source="workload identity federation")
|
|
89
|
+
|
|
90
|
+
return ApiKeyAuth("Anthropic API key", resolve, login=login)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def anthropic_provider() -> Provider:
|
|
94
|
+
"""Anthropic (Messages API): API key, ``ANTHROPIC_AUTH_TOKEN``, federation, or Claude Pro/Max OAuth."""
|
|
95
|
+
return create_provider(
|
|
96
|
+
id="anthropic",
|
|
97
|
+
name="Anthropic",
|
|
98
|
+
base_url="https://api.anthropic.com",
|
|
99
|
+
auth=ProviderAuth(
|
|
100
|
+
api_key=anthropic_api_key_auth(),
|
|
101
|
+
oauth=lazy_oauth("Anthropic (Claude Pro/Max)", load_anthropic_oauth, is_subscription=True),
|
|
102
|
+
),
|
|
103
|
+
models=lambda: catalog_models("anthropic"),
|
|
104
|
+
api=anthropic_messages_api(),
|
|
105
|
+
)
|