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.
Files changed (189) hide show
  1. pi_agent/__init__.py +127 -0
  2. pi_agent/agent.py +617 -0
  3. pi_agent/agent_loop.py +820 -0
  4. pi_agent/proxy.py +269 -0
  5. pi_agent/py.typed +0 -0
  6. pi_agent/stream_fn.py +27 -0
  7. pi_agent/tools.py +327 -0
  8. pi_agent/types.py +394 -0
  9. pi_ai/__init__.py +263 -0
  10. pi_ai/abort.py +315 -0
  11. pi_ai/api/__init__.py +1 -0
  12. pi_ai/api/anthropic_messages.py +1782 -0
  13. pi_ai/api/azure_openai_config.py +138 -0
  14. pi_ai/api/azure_openai_responses.py +245 -0
  15. pi_ai/api/bedrock_converse_stream.py +1598 -0
  16. pi_ai/api/classifier_shared.py +211 -0
  17. pi_ai/api/cloudflare.py +36 -0
  18. pi_ai/api/cloudflare_workers_ai_system_one.py +80 -0
  19. pi_ai/api/constrained_sampling.py +279 -0
  20. pi_ai/api/github_copilot_headers.py +30 -0
  21. pi_ai/api/google_generative_ai.py +138 -0
  22. pi_ai/api/google_shared.py +927 -0
  23. pi_ai/api/google_vertex.py +234 -0
  24. pi_ai/api/lazy.py +156 -0
  25. pi_ai/api/llama_cpp_classify.py +449 -0
  26. pi_ai/api/mistral_conversations.py +756 -0
  27. pi_ai/api/openai_codex_responses.py +1545 -0
  28. pi_ai/api/openai_completions.py +1594 -0
  29. pi_ai/api/openai_decisions.py +210 -0
  30. pi_ai/api/openai_prompt_cache.py +19 -0
  31. pi_ai/api/openai_responses.py +418 -0
  32. pi_ai/api/openai_responses_shared.py +840 -0
  33. pi_ai/api/openai_sdk.py +204 -0
  34. pi_ai/api/openrouter_images.py +153 -0
  35. pi_ai/api/pi_messages.py +449 -0
  36. pi_ai/api/registry.py +103 -0
  37. pi_ai/api/simple_options.py +119 -0
  38. pi_ai/api/system_one_shared.py +148 -0
  39. pi_ai/api/transform_messages.py +211 -0
  40. pi_ai/api/typesafe_system_one.py +43 -0
  41. pi_ai/auth/__init__.py +1 -0
  42. pi_ai/auth/context.py +47 -0
  43. pi_ai/auth/credential_store.py +208 -0
  44. pi_ai/auth/helpers.py +123 -0
  45. pi_ai/auth/oauth/__init__.py +5 -0
  46. pi_ai/auth/oauth/_common.py +454 -0
  47. pi_ai/auth/oauth/anthropic.py +283 -0
  48. pi_ai/auth/oauth/callback_server.py +339 -0
  49. pi_ai/auth/oauth/device_code.py +127 -0
  50. pi_ai/auth/oauth/github_copilot.py +508 -0
  51. pi_ai/auth/oauth/kimi_coding.py +283 -0
  52. pi_ai/auth/oauth/load.py +64 -0
  53. pi_ai/auth/oauth/meta.py +238 -0
  54. pi_ai/auth/oauth/openai_chatgpt.py +321 -0
  55. pi_ai/auth/oauth/openai_codex.py +391 -0
  56. pi_ai/auth/oauth/openrouter.py +173 -0
  57. pi_ai/auth/oauth/pkce.py +23 -0
  58. pi_ai/auth/oauth/radius.py +311 -0
  59. pi_ai/auth/oauth/xai.py +220 -0
  60. pi_ai/auth/resolve.py +190 -0
  61. pi_ai/auth/types.py +292 -0
  62. pi_ai/cli.py +265 -0
  63. pi_ai/compat.py +651 -0
  64. pi_ai/env_api_keys.py +137 -0
  65. pi_ai/event_stream.py +329 -0
  66. pi_ai/models.py +1191 -0
  67. pi_ai/models_store.py +121 -0
  68. pi_ai/oauth.py +90 -0
  69. pi_ai/providers/__init__.py +1 -0
  70. pi_ai/providers/all.py +154 -0
  71. pi_ai/providers/amazon_bedrock.py +116 -0
  72. pi_ai/providers/ant_ling.py +23 -0
  73. pi_ai/providers/anthropic.py +105 -0
  74. pi_ai/providers/azure.py +103 -0
  75. pi_ai/providers/baseten.py +23 -0
  76. pi_ai/providers/catalog.py +167 -0
  77. pi_ai/providers/cerebras.py +23 -0
  78. pi_ai/providers/cloudflare_ai_gateway.py +30 -0
  79. pi_ai/providers/cloudflare_auth.py +112 -0
  80. pi_ai/providers/cloudflare_stream.py +84 -0
  81. pi_ai/providers/cloudflare_workers_ai.py +26 -0
  82. pi_ai/providers/data/amazon-bedrock.json +1 -0
  83. pi_ai/providers/data/ant-ling.json +1 -0
  84. pi_ai/providers/data/anthropic.json +1 -0
  85. pi_ai/providers/data/azure.json +1 -0
  86. pi_ai/providers/data/baseten.json +1 -0
  87. pi_ai/providers/data/cerebras.json +1 -0
  88. pi_ai/providers/data/cloudflare-ai-gateway.json +1 -0
  89. pi_ai/providers/data/cloudflare-workers-ai.json +1 -0
  90. pi_ai/providers/data/deepseek.json +1 -0
  91. pi_ai/providers/data/fireworks.json +1 -0
  92. pi_ai/providers/data/github-copilot.json +1 -0
  93. pi_ai/providers/data/google-vertex.json +1 -0
  94. pi_ai/providers/data/google.json +1 -0
  95. pi_ai/providers/data/groq.json +1 -0
  96. pi_ai/providers/data/huggingface.json +1 -0
  97. pi_ai/providers/data/kimi-coding.json +1 -0
  98. pi_ai/providers/data/manifest.json +1 -0
  99. pi_ai/providers/data/meta.json +1 -0
  100. pi_ai/providers/data/minimax-cn.json +1 -0
  101. pi_ai/providers/data/minimax.json +1 -0
  102. pi_ai/providers/data/mistral.json +1 -0
  103. pi_ai/providers/data/moonshotai-cn.json +1 -0
  104. pi_ai/providers/data/moonshotai.json +1 -0
  105. pi_ai/providers/data/nvidia.json +1 -0
  106. pi_ai/providers/data/openai-codex.json +1 -0
  107. pi_ai/providers/data/openai.json +1 -0
  108. pi_ai/providers/data/opencode-go.json +1 -0
  109. pi_ai/providers/data/opencode.json +1 -0
  110. pi_ai/providers/data/openrouter.json +1 -0
  111. pi_ai/providers/data/qwen-token-plan-cn.json +1 -0
  112. pi_ai/providers/data/qwen-token-plan-individual.json +1 -0
  113. pi_ai/providers/data/qwen-token-plan.json +1 -0
  114. pi_ai/providers/data/radius.json +1 -0
  115. pi_ai/providers/data/together.json +1 -0
  116. pi_ai/providers/data/typesafe.json +1 -0
  117. pi_ai/providers/data/vercel-ai-gateway.json +1 -0
  118. pi_ai/providers/data/xai.json +1 -0
  119. pi_ai/providers/data/xiaomi-token-plan-ams.json +1 -0
  120. pi_ai/providers/data/xiaomi-token-plan-cn.json +1 -0
  121. pi_ai/providers/data/xiaomi-token-plan-sgp.json +1 -0
  122. pi_ai/providers/data/xiaomi.json +1 -0
  123. pi_ai/providers/data/zai-coding-cn.json +1 -0
  124. pi_ai/providers/data/zai.json +1 -0
  125. pi_ai/providers/deepseek.py +23 -0
  126. pi_ai/providers/faux.py +680 -0
  127. pi_ai/providers/fireworks.py +26 -0
  128. pi_ai/providers/github_copilot.py +46 -0
  129. pi_ai/providers/google.py +23 -0
  130. pi_ai/providers/google_vertex.py +131 -0
  131. pi_ai/providers/groq.py +23 -0
  132. pi_ai/providers/huggingface.py +23 -0
  133. pi_ai/providers/kimi_coding.py +32 -0
  134. pi_ai/providers/meta.py +32 -0
  135. pi_ai/providers/minimax.py +23 -0
  136. pi_ai/providers/minimax_cn.py +23 -0
  137. pi_ai/providers/mistral.py +23 -0
  138. pi_ai/providers/moonshotai.py +23 -0
  139. pi_ai/providers/moonshotai_cn.py +23 -0
  140. pi_ai/providers/nvidia.py +23 -0
  141. pi_ai/providers/openai.py +44 -0
  142. pi_ai/providers/openai_codex.py +26 -0
  143. pi_ai/providers/opencode.py +36 -0
  144. pi_ai/providers/opencode_go.py +33 -0
  145. pi_ai/providers/opencode_headers.py +60 -0
  146. pi_ai/providers/openrouter.py +145 -0
  147. pi_ai/providers/openrouter_api.py +122 -0
  148. pi_ai/providers/qwen_token_plan.py +23 -0
  149. pi_ai/providers/qwen_token_plan_cn.py +23 -0
  150. pi_ai/providers/qwen_token_plan_individual.py +23 -0
  151. pi_ai/providers/radius.py +146 -0
  152. pi_ai/providers/radius_config.py +136 -0
  153. pi_ai/providers/together.py +23 -0
  154. pi_ai/providers/typesafe.py +22 -0
  155. pi_ai/providers/vercel_ai_gateway.py +25 -0
  156. pi_ai/providers/xai.py +32 -0
  157. pi_ai/providers/xiaomi.py +23 -0
  158. pi_ai/providers/xiaomi_token_plan_ams.py +23 -0
  159. pi_ai/providers/xiaomi_token_plan_cn.py +23 -0
  160. pi_ai/providers/xiaomi_token_plan_sgp.py +23 -0
  161. pi_ai/providers/zai.py +23 -0
  162. pi_ai/providers/zai_coding_cn.py +23 -0
  163. pi_ai/py.typed +0 -0
  164. pi_ai/serialization.py +477 -0
  165. pi_ai/session_resources.py +33 -0
  166. pi_ai/types.py +1003 -0
  167. pi_ai/utils/__init__.py +1 -0
  168. pi_ai/utils/callbacks.py +15 -0
  169. pi_ai/utils/diagnostics.py +70 -0
  170. pi_ai/utils/error_body.py +89 -0
  171. pi_ai/utils/estimate.py +125 -0
  172. pi_ai/utils/fastjson.py +63 -0
  173. pi_ai/utils/http.py +409 -0
  174. pi_ai/utils/json_parse.py +424 -0
  175. pi_ai/utils/misc.py +193 -0
  176. pi_ai/utils/model_operations.py +73 -0
  177. pi_ai/utils/models_error.py +35 -0
  178. pi_ai/utils/oauth_page.py +121 -0
  179. pi_ai/utils/overflow.py +87 -0
  180. pi_ai/utils/retry.py +193 -0
  181. pi_ai/utils/schema.py +283 -0
  182. pi_ai/utils/text.py +65 -0
  183. pi_ai/utils/transcript.py +271 -0
  184. pi_ai/utils/validation.py +813 -0
  185. pi_ai_python-1.3.0.dist-info/METADATA +167 -0
  186. pi_ai_python-1.3.0.dist-info/RECORD +189 -0
  187. pi_ai_python-1.3.0.dist-info/WHEEL +4 -0
  188. pi_ai_python-1.3.0.dist-info/entry_points.txt +2 -0
  189. 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
+ )