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/abort.py ADDED
@@ -0,0 +1,315 @@
1
+ """Cancellation primitives modeled on the web ``AbortController``/``AbortSignal`` pair.
2
+
3
+ pi passes an ``AbortSignal`` through every layer (``options.signal``). The Python
4
+ port keeps that contract so the same cancellation semantics apply: aborting a
5
+ request ends its stream with ``stop_reason == "aborted"`` instead of raising.
6
+
7
+ Inside async code, :func:`abort_scope` turns an abort into prompt task
8
+ cancellation (any pending ``await`` such as a network read is interrupted) and
9
+ re-raises it as :class:`AbortError`, the same way :func:`asyncio.timeout` turns a
10
+ deadline into :class:`TimeoutError`.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import sys
17
+ import threading
18
+ from collections.abc import Callable, Iterable
19
+ from typing import Any
20
+
21
+ __all__ = [
22
+ "AbortError",
23
+ "AbortController",
24
+ "AbortSignal",
25
+ "abort_scope",
26
+ "operation_signal",
27
+ "race_with_abort_signal",
28
+ "sleep",
29
+ ]
30
+
31
+
32
+ class AbortError(Exception):
33
+ """Raised when an operation is cancelled through an :class:`AbortSignal`."""
34
+
35
+ def __init__(self, reason: Any = None) -> None:
36
+ if isinstance(reason, BaseException):
37
+ message = str(reason) or "The operation was aborted"
38
+ elif reason is None:
39
+ message = "The operation was aborted"
40
+ else:
41
+ message = str(reason)
42
+ super().__init__(message)
43
+ self.reason = reason
44
+
45
+
46
+ _Listener = Callable[[], Any]
47
+
48
+
49
+ class AbortSignal:
50
+ """Read side of an :class:`AbortController`.
51
+
52
+ Listeners run synchronously when the signal aborts. ``abort()`` may be called
53
+ from any thread; listeners that touch an event loop should hand work to it with
54
+ ``loop.call_soon_threadsafe`` (the helpers in this module already do).
55
+ """
56
+
57
+ __slots__ = ("__weakref__", "_aborted", "_listeners", "_lock", "_reason")
58
+
59
+ def __init__(self) -> None:
60
+ self._aborted = False
61
+ self._reason: Any = None
62
+ self._listeners: list[_Listener] | None = None
63
+ self._lock = threading.Lock()
64
+
65
+ @property
66
+ def aborted(self) -> bool:
67
+ return self._aborted
68
+
69
+ @property
70
+ def reason(self) -> Any:
71
+ return self._reason
72
+
73
+ def throw_if_aborted(self) -> None:
74
+ if self._aborted:
75
+ raise self._reason if isinstance(self._reason, AbortError) else AbortError(self._reason)
76
+
77
+ def add_listener(self, listener: _Listener) -> Callable[[], None]:
78
+ """Register ``listener`` to run on abort. Returns a function that removes it.
79
+
80
+ If the signal is already aborted the listener runs immediately.
81
+ """
82
+ with self._lock:
83
+ if not self._aborted:
84
+ if self._listeners is None:
85
+ self._listeners = []
86
+ self._listeners.append(listener)
87
+ return lambda: self._remove(listener)
88
+ listener()
89
+ return _noop
90
+
91
+ def _remove(self, listener: _Listener) -> None:
92
+ with self._lock:
93
+ if self._listeners:
94
+ try:
95
+ self._listeners.remove(listener)
96
+ except ValueError:
97
+ pass
98
+
99
+ def _abort(self, reason: Any) -> None:
100
+ with self._lock:
101
+ if self._aborted:
102
+ return
103
+ self._aborted = True
104
+ self._reason = AbortError(reason) if reason is None or not isinstance(reason, BaseException) else reason
105
+ listeners, self._listeners = self._listeners, None
106
+ for listener in listeners or ():
107
+ try:
108
+ listener()
109
+ except Exception: # listeners must not break abort propagation
110
+ pass
111
+
112
+ async def wait(self) -> Any:
113
+ """Wait until the signal aborts and return its reason."""
114
+ if self._aborted:
115
+ return self._reason
116
+ loop = asyncio.get_running_loop()
117
+ future: asyncio.Future[None] = loop.create_future()
118
+
119
+ remove = self.add_listener(lambda: _resolve_in_loop(loop, future))
120
+ try:
121
+ await future
122
+ finally:
123
+ remove()
124
+ return self._reason
125
+
126
+ @staticmethod
127
+ def abort(reason: Any = None) -> AbortSignal:
128
+ """An already aborted signal."""
129
+ controller = AbortController()
130
+ controller.abort(reason)
131
+ return controller.signal
132
+
133
+ @staticmethod
134
+ def timeout(seconds: float) -> AbortSignal:
135
+ """A signal that aborts after ``seconds`` (requires a running event loop)."""
136
+ controller = AbortController()
137
+ loop = asyncio.get_running_loop()
138
+ loop.call_later(seconds, controller.abort, TimeoutError("The operation timed out"))
139
+ return controller.signal
140
+
141
+ @staticmethod
142
+ def any(signals: Iterable[AbortSignal | None]) -> AbortSignal:
143
+ """A signal that aborts when any of ``signals`` aborts."""
144
+ active = [signal for signal in signals if signal is not None]
145
+ controller = AbortController()
146
+ for signal in active:
147
+ if signal.aborted:
148
+ controller.abort(signal.reason)
149
+ return controller.signal
150
+ for signal in active:
151
+ signal.add_listener(lambda signal=signal: controller.abort(signal.reason))
152
+ return controller.signal
153
+
154
+ def __repr__(self) -> str:
155
+ return f"AbortSignal(aborted={self._aborted})"
156
+
157
+
158
+ def _noop() -> None:
159
+ return None
160
+
161
+
162
+ def _resolve_future(future: asyncio.Future[Any]) -> None:
163
+ if not future.done():
164
+ future.set_result(None)
165
+
166
+
167
+ class AbortController:
168
+ """Write side: ``controller.abort()`` aborts ``controller.signal``."""
169
+
170
+ __slots__ = ("signal",)
171
+
172
+ def __init__(self) -> None:
173
+ self.signal = AbortSignal()
174
+
175
+ def abort(self, reason: Any = None) -> None:
176
+ self.signal._abort(reason)
177
+
178
+
179
+ def operation_signal(signal: AbortSignal | None) -> AbortSignal:
180
+ """Return ``signal`` or a fresh never-aborting signal for APIs whose signal is optional."""
181
+ return signal if signal is not None else AbortSignal()
182
+
183
+
184
+ class abort_scope:
185
+ """Cancel the current task when ``signal`` aborts, raising :class:`AbortError`.
186
+
187
+ ::
188
+
189
+ async with abort_scope(options.get("signal")):
190
+ response = await client.send(request, stream=True)
191
+ async for line in response.aiter_lines():
192
+ ...
193
+
194
+ A ``None`` signal makes the scope a no-op. Cancellation that does not come from
195
+ the signal propagates unchanged.
196
+ """
197
+
198
+ __slots__ = ("_cancelling", "_exited", "_fired", "_loop", "_remove", "_signal", "_task")
199
+
200
+ def __init__(self, signal: AbortSignal | None) -> None:
201
+ self._signal = signal
202
+ self._task: asyncio.Task[Any] | None = None
203
+ self._loop: asyncio.AbstractEventLoop | None = None
204
+ self._remove: Callable[[], None] | None = None
205
+ self._fired = False
206
+ self._exited = False
207
+ self._cancelling = 0
208
+
209
+ async def __aenter__(self) -> abort_scope:
210
+ signal = self._signal
211
+ if signal is None:
212
+ return self
213
+ signal.throw_if_aborted()
214
+ self._task = asyncio.current_task()
215
+ self._loop = asyncio.get_running_loop()
216
+ if self._task is not None:
217
+ self._cancelling = self._task.cancelling()
218
+ self._remove = signal.add_listener(self._on_abort)
219
+ return self
220
+
221
+ def _on_abort(self) -> None:
222
+ loop = self._loop
223
+ if loop is None or loop.is_closed():
224
+ return
225
+ try:
226
+ running = asyncio.get_running_loop()
227
+ except RuntimeError:
228
+ running = None
229
+ if running is loop:
230
+ self._cancel()
231
+ else:
232
+ loop.call_soon_threadsafe(self._cancel)
233
+
234
+ def _cancel(self) -> None:
235
+ task = self._task
236
+ if task is not None and not task.done() and not self._fired and not self._exited:
237
+ self._fired = True
238
+ task.cancel()
239
+
240
+ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> bool | None:
241
+ self._exited = True
242
+ if self._remove is not None:
243
+ self._remove()
244
+ self._remove = None
245
+ if self._fired and self._task is not None:
246
+ if self._task.uncancel() <= self._cancelling:
247
+ if exc_type is asyncio.CancelledError:
248
+ assert self._signal is not None
249
+ raise AbortError(self._signal.reason) from exc
250
+ if sys.version_info < (3, 13):
251
+ # The body finished before the cancellation was delivered. Python 3.13's
252
+ # uncancel() drops that pending cancellation; older versions would raise it
253
+ # at the caller's next await, so take it here.
254
+ try:
255
+ await asyncio.sleep(0)
256
+ except asyncio.CancelledError:
257
+ pass
258
+ return None
259
+
260
+
261
+ async def race_with_abort_signal(awaitable: Any, signal: AbortSignal | None) -> Any:
262
+ """Await ``awaitable`` but stop waiting (raising :class:`AbortError`) when ``signal`` aborts.
263
+
264
+ Unlike :class:`abort_scope`, the abandoned operation keeps running in its own task
265
+ and its eventual exception is consumed, matching pi's ``raceWithAbortSignal``.
266
+ """
267
+ if signal is None:
268
+ return await awaitable
269
+ task = asyncio.ensure_future(awaitable)
270
+ if signal.aborted:
271
+ task.add_done_callback(_consume_task_result)
272
+ signal.throw_if_aborted()
273
+ loop = asyncio.get_running_loop()
274
+ abort_future: asyncio.Future[None] = loop.create_future()
275
+ remove = signal.add_listener(lambda: _resolve_in_loop(loop, abort_future))
276
+ try:
277
+ await asyncio.wait({task, abort_future}, return_when=asyncio.FIRST_COMPLETED)
278
+ except asyncio.CancelledError:
279
+ task.cancel()
280
+ raise
281
+ finally:
282
+ remove()
283
+ if not abort_future.done():
284
+ abort_future.cancel()
285
+ # An abort that fired while the operation ran wins, even if the operation also finished
286
+ # in the same loop iteration (JavaScript abort listeners run synchronously).
287
+ if signal.aborted:
288
+ task.add_done_callback(_consume_task_result)
289
+ signal.throw_if_aborted()
290
+ return task.result()
291
+
292
+
293
+ def _resolve_in_loop(loop: asyncio.AbstractEventLoop, future: asyncio.Future[Any]) -> None:
294
+ try:
295
+ running = asyncio.get_running_loop()
296
+ except RuntimeError:
297
+ running = None
298
+ if running is loop:
299
+ _resolve_future(future)
300
+ elif not loop.is_closed():
301
+ loop.call_soon_threadsafe(_resolve_future, future)
302
+
303
+
304
+ def _consume_task_result(task: asyncio.Future[Any]) -> None:
305
+ if not task.cancelled():
306
+ task.exception()
307
+
308
+
309
+ async def sleep(seconds: float, signal: AbortSignal | None = None) -> None:
310
+ """``asyncio.sleep`` that raises :class:`AbortError` as soon as ``signal`` aborts."""
311
+ if signal is None:
312
+ await asyncio.sleep(seconds)
313
+ return
314
+ async with abort_scope(signal):
315
+ await asyncio.sleep(max(0.0, seconds))
pi_ai/api/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Provider API (wire protocol) implementations. Each module exposes ``stream`` and ``stream_simple``."""