pi-python-core 0.10.0__py3-none-any.whl → 0.10.1__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_python/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.10.0"
1
+ __version__ = "0.10.1"
@@ -21,6 +21,7 @@ import hashlib
21
21
  import importlib
22
22
  import importlib.machinery
23
23
  import importlib.util
24
+ import inspect
24
25
  import json
25
26
  import math
26
27
  import os
@@ -49,6 +50,7 @@ from ..tools import Tool, ToolContext, ToolResult, invoke
49
50
  from .._mcp_interaction import MCPCallbacks
50
51
  from ._compose import (
51
52
  HOOK_NAMES,
53
+ HOOK_PARAMETERS,
52
54
  PluginFailure,
53
55
  compose_hooks,
54
56
  isolated_listener,
@@ -286,7 +288,9 @@ class PluginAPI:
286
288
  raise ConfigurationError(f"Unknown hook {hook!r}; hooks are {', '.join(HOOK_NAMES)}")
287
289
 
288
290
  def register(function: F) -> F:
289
- _require_callable(function, f"the {hook} handler of plugin {self.name!r}")
291
+ _require_callable(
292
+ function, f"the {hook} handler of plugin {self.name!r}", HOOK_PARAMETERS[hook]
293
+ )
290
294
  self._handlers.append((hook, function))
291
295
  return function
292
296
 
@@ -294,7 +298,7 @@ class PluginAPI:
294
298
 
295
299
  def subscribe(self, listener: EventListener) -> None:
296
300
  """Receive the events of agents built from this set; failures are reported, not raised."""
297
- _require_callable(listener, f"an event listener of plugin {self.name!r}")
301
+ _require_callable(listener, f"an event listener of plugin {self.name!r}", ("event",))
298
302
  self._listeners.append(listener)
299
303
 
300
304
  def _path(self, path: str | os.PathLike[str]) -> Path:
@@ -1087,9 +1091,21 @@ def _redact(text: str, secrets: set[str] | None) -> str:
1087
1091
  return text
1088
1092
 
1089
1093
 
1090
- def _require_callable(value: Any, what: str) -> None:
1094
+ def _require_callable(value: Any, what: str, arguments: tuple[str, ...] = ()) -> None:
1095
+ """Reject a callback now if it cannot take the positional `arguments` it will be called
1096
+ with; otherwise each call would fail and, for a hook, only be logged and skipped."""
1091
1097
  if not callable(value):
1092
1098
  raise ConfigurationError(f"{what} must be callable, not {type(value).__name__}")
1099
+ try:
1100
+ # Not following __wrapped__: a decorator's own wrapper is what gets called.
1101
+ signature = inspect.signature(value, follow_wrapped=False)
1102
+ except (TypeError, ValueError):
1103
+ return # no signature to inspect, as for some builtins
1104
+ try:
1105
+ signature.bind(*arguments)
1106
+ except TypeError as exc:
1107
+ expected = f"the arguments ({', '.join(arguments)})" if arguments else "no arguments"
1108
+ raise ConfigurationError(f"{what} must accept {expected}: {exc}") from None
1093
1109
 
1094
1110
 
1095
1111
  def _looks_like_path(text: str) -> bool:
@@ -34,6 +34,21 @@ from ..hooks import Hooks, RunContext, TurnUpdate
34
34
  from ..tools import ToolResult, ToolResultUpdate, apply_result_update, invoke
35
35
 
36
36
  HOOK_NAMES = tuple(f.name for f in fields(Hooks))
37
+ # The positional arguments the composed hooks below pass to each handler.
38
+ HOOK_PARAMETERS: dict[str, tuple[str, ...]] = {
39
+ "before_tool_call": ("call", "args", "context"),
40
+ "after_tool_call": ("call", "result", "context"),
41
+ "transform_context": ("messages", "cancel"),
42
+ "convert_to_llm": ("messages",),
43
+ "prepare_request": ("context", "cancel"),
44
+ "prepare_next_turn": ("context", "cancel"),
45
+ "finish_turn": ("context", "cancel"),
46
+ "get_api_key": ("provider",),
47
+ "on_payload": ("payload",),
48
+ "on_response": ("response",),
49
+ "on_provider_stream_event": ("event",),
50
+ }
51
+ assert set(HOOK_PARAMETERS) == set(HOOK_NAMES)
37
52
  logger = logging.getLogger("pi_python.plugins")
38
53
 
39
54
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pi-python-core
3
- Version: 0.10.0
3
+ Version: 0.10.1
4
4
  Summary: An embeddable asyncio agent core with opt-in Claude and OpenAI providers
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -99,6 +99,7 @@ To try it offline first: `python examples/quickstart.py`.
99
99
  | Use MCP tools | `async with connect_stdio(...) as tools`, or `connect_http(url)` for a remote server | [mcp_tools](examples/mcp_tools.py) |
100
100
  | Let one agent call another | Wrap the sub-agent as a tool; cancellation propagates down. Plugins can also define subagents in Markdown | [subagent](examples/subagent.py) |
101
101
  | Package and share capabilities | A plugin bundles tools, instructions, hooks, skills, prompt templates, subagents and MCP servers; load it by name or path with `load_plugins` and build the agent with `plugins.agent(...)` | [plugin_demo](examples/plugin_demo.py) |
102
+ | Remember across conversations | Not built in, as in Pi's core; an example plugin saves facts to a file and puts them in the system prompt, ready to copy and adapt | [memory_demo](examples/memory_demo.py) |
102
103
  | Intervene mid-run | `steer` injects guidance, `follow_up` queues the next task, `abort` cancels at any time (callable from any thread) | |
103
104
  | Context full, service errors | `is_context_overflow` and `is_retryable_error` tell you why, `continue_run()` retries, `transform_context` compacts | [recovery](examples/recovery.py) |
104
105
  | Save and restore conversations | `encode_messages` / `decode_messages`; your application decides where to store them | [save_restore](examples/save_restore.py) |
@@ -3,7 +3,7 @@ pi_python/_mcp_host.py,sha256=CXio-tiEeUCUfdl65tatwSrzIOYdgecdS00sO6tNy5k,8630
3
3
  pi_python/_mcp_interaction.py,sha256=jPL-C8wLjBDV9tOdvGVFfXOl8-1FQIDJLq-MojlzXaM,43851
4
4
  pi_python/_mcp_process.py,sha256=4szi5gMEP_0GUGOcV-oLPUuVQAUSb0ToAaiM979zHME,6324
5
5
  pi_python/_mcp_sampling.py,sha256=BcWCrPgjsRC-GNx65fIB-JPq2WzWnoBK2w9iTnyPUww,12148
6
- pi_python/_version.py,sha256=v4zmKjsKOPZbp6BrWoz7iK4ST0sdZdUh9bQSJmluZ5o,23
6
+ pi_python/_version.py,sha256=v7Gyp89umFzDtY45tTjCdXqZnQ2RN01AibdYNxEvxYo,23
7
7
  pi_python/agent.py,sha256=I4P4waCdkcURDd022H4XgBbHNhpa7LrI5Iwb5ncxhGU,16021
8
8
  pi_python/cancellation.py,sha256=DoQVdX7DcQdVLkl4OvhcpMhKZJEW0Kscl2-xxF6i6Y0,608
9
9
  pi_python/errors.py,sha256=65lT0nYfzqxRfUlFG-Y9vgYxfDHEuFiBfgqmFnX9_O8,675
@@ -31,8 +31,8 @@ pi_python/testing.py,sha256=XfTSOkEv0CenqrBILI_T205jOhyitW53YQZ9yiMXir4,1046
31
31
  pi_python/tools.py,sha256=zBrYVoPOgrVHqvrL5sIhMXCf501QjyuDNdmR-tV6Ads,23274
32
32
  pi_python/transcript.py,sha256=g6Tg8eiyMCAK43iLpcBv220MHBq8kO7Ty-6OYLCzw2A,6685
33
33
  pi_python/data/models.json,sha256=oa_LGv7vaRrS-l7_Z7iu8f7WTyoJJLPhUQyptUz7OmE,77549
34
- pi_python/plugins/__init__.py,sha256=OnQoKk63SJ_t8I3yldTn0Zw12tGqzD2K-LDqM3JdAfY,55330
35
- pi_python/plugins/_compose.py,sha256=x1yZZtoXWqTugMWcNeFFitWSeFnRH3WI0XiQdH7oJxA,10767
34
+ pi_python/plugins/__init__.py,sha256=FeYHYxgaOjKuq3QBBv1ezOyfORi5Fl2SdnImN_O0IrE,56156
35
+ pi_python/plugins/_compose.py,sha256=vmIh901G6D298uQBn_IhiKkczIBymsqCPWo7SxXUrTM,11411
36
36
  pi_python/plugins/_frontmatter.py,sha256=_i5QeDcPlz9n8ou9W7b20TNWMVoeYThI1f4j68ZbuCk,16127
37
37
  pi_python/plugins/_resources.py,sha256=uK5TSyJc1H6aptJqKw5FI83Gyp2MghxcUvgxOmWQu08,13531
38
38
  pi_python/plugins/_subagents.py,sha256=HhUYK4z7gdtX2_DSh3vFfxyJCKo2zz0zSOjRE1j8CmY,10252
@@ -43,8 +43,8 @@ pi_python/providers/completions.py,sha256=vcqoLfJ-9quMREEEAJcHYs3Oq-A0Sz8Q1NV1QL
43
43
  pi_python/providers/oauth.py,sha256=ZY8j7P-I-R6hZ8sInkGao957KWNGsqfvMF0F-YzlSgU,22786
44
44
  pi_python/providers/openai.py,sha256=p0WHDnojAYb7mUX8EbComwEU_XskcYoMhMIR07QlSfc,30196
45
45
  pi_python/providers/transport.py,sha256=B1mGKlVEqYGVK1MlcA9opwRhnuUYGuaYuTslNC3O8xs,26772
46
- pi_python_core-0.10.0.dist-info/METADATA,sha256=Ntw_teJVikFWKlCZtjeUPxCZMfq0poW7Z1E2caoFpJ8,8731
47
- pi_python_core-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
- pi_python_core-0.10.0.dist-info/licenses/LICENSE,sha256=BFf1vOw7OyEWBd-10aSQQv1jjzaGpBD-CZwkolrxPEg,1069
49
- pi_python_core-0.10.0.dist-info/licenses/NOTICE,sha256=fJna16fm1ECHN7zVZhtyNmjLw8KGxGp8mL9blISF1K0,633
50
- pi_python_core-0.10.0.dist-info/RECORD,,
46
+ pi_python_core-0.10.1.dist-info/METADATA,sha256=AncCL6Sz-4xE1Kx9_iHB2TmHcOtq8jJjBFHxAGExpxs,8939
47
+ pi_python_core-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
+ pi_python_core-0.10.1.dist-info/licenses/LICENSE,sha256=BFf1vOw7OyEWBd-10aSQQv1jjzaGpBD-CZwkolrxPEg,1069
49
+ pi_python_core-0.10.1.dist-info/licenses/NOTICE,sha256=fJna16fm1ECHN7zVZhtyNmjLw8KGxGp8mL9blISF1K0,633
50
+ pi_python_core-0.10.1.dist-info/RECORD,,