pyb-agent-sdk 0.1.0__tar.gz

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 (29) hide show
  1. pyb_agent_sdk-0.1.0/PKG-INFO +23 -0
  2. pyb_agent_sdk-0.1.0/README.md +10 -0
  3. pyb_agent_sdk-0.1.0/pyproject.toml +25 -0
  4. pyb_agent_sdk-0.1.0/setup.cfg +4 -0
  5. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/__init__.py +87 -0
  6. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/agent_compaction.py +81 -0
  7. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/agent_hooks.py +107 -0
  8. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/agent_mcp.py +144 -0
  9. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/agent_session.py +536 -0
  10. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/client.py +285 -0
  11. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/permissions.py +216 -0
  12. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/protocol.py +219 -0
  13. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/protocol_session_backend.py +254 -0
  14. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/runtime_events.py +56 -0
  15. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk/transport.py +284 -0
  16. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk.egg-info/PKG-INFO +23 -0
  17. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk.egg-info/SOURCES.txt +27 -0
  18. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk.egg-info/dependency_links.txt +1 -0
  19. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk.egg-info/requires.txt +6 -0
  20. pyb_agent_sdk-0.1.0/src/pyb_agent_sdk.egg-info/top_level.txt +1 -0
  21. pyb_agent_sdk-0.1.0/tests/test_agent_session_contract.py +124 -0
  22. pyb_agent_sdk-0.1.0/tests/test_agent_session_p3_p5_guards.py +98 -0
  23. pyb_agent_sdk-0.1.0/tests/test_agent_session_p4_bridges.py +456 -0
  24. pyb_agent_sdk-0.1.0/tests/test_integration_serve.py +60 -0
  25. pyb_agent_sdk-0.1.0/tests/test_package_entry_contract.py +34 -0
  26. pyb_agent_sdk-0.1.0/tests/test_permission_updated_input.py +100 -0
  27. pyb_agent_sdk-0.1.0/tests/test_protocol_contract.py +142 -0
  28. pyb_agent_sdk-0.1.0/tests/test_protocol_session_backend.py +124 -0
  29. pyb_agent_sdk-0.1.0/tests/test_transport_client.py +410 -0
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyb-agent-sdk
3
+ Version: 0.1.0
4
+ Summary: Protocol-first Python skeleton for PYB Agent SDK
5
+ Author: PYB-XC
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: websockets>=12.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
13
+
14
+ # PYB Agent SDK Python
15
+
16
+ Protocol-first Python skeleton for the PYB Agent SDK.
17
+
18
+ Current scope:
19
+
20
+ - `AgentSession` main entry skeleton
21
+ - `ProtocolSessionBackend`
22
+ - runtime event projection
23
+ - protocol honesty boundaries for unimplemented bridges
@@ -0,0 +1,10 @@
1
+ # PYB Agent SDK Python
2
+
3
+ Protocol-first Python skeleton for the PYB Agent SDK.
4
+
5
+ Current scope:
6
+
7
+ - `AgentSession` main entry skeleton
8
+ - `ProtocolSessionBackend`
9
+ - runtime event projection
10
+ - protocol honesty boundaries for unimplemented bridges
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyb-agent-sdk"
7
+ version = "0.1.0"
8
+ description = "Protocol-first Python skeleton for PYB Agent SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ authors = [{ name = "PYB-XC" }]
12
+ dependencies = ["httpx>=0.27", "websockets>=12.0"]
13
+
14
+ [project.optional-dependencies]
15
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.24"]
16
+
17
+ [tool.pytest.ini_options]
18
+ asyncio_mode = "auto"
19
+ testpaths = ["tests"]
20
+
21
+ [tool.setuptools]
22
+ package-dir = {"" = "src"}
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,87 @@
1
+ from .agent_compaction import (
2
+ AgentCompactionAdapter,
3
+ AgentCompactionEvent,
4
+ AgentCompactionState,
5
+ create_agent_compaction_adapter,
6
+ )
7
+ from .agent_hooks import (
8
+ AGENT_HOOK_EVENT_NAMES,
9
+ AGENT_HOOK_TYPES,
10
+ AgentHookAdapter,
11
+ AgentSubagentLifecycleEvent,
12
+ create_agent_hook_adapter,
13
+ )
14
+ from .agent_mcp import (
15
+ AgentMcpAdapter,
16
+ AgentMcpCapabilityState,
17
+ AgentMcpEvent,
18
+ create_agent_mcp_adapter,
19
+ create_default_mcp_adapter_from_pyb_client,
20
+ )
21
+ from .agent_session import (
22
+ AgentSession,
23
+ AgentSessionInput,
24
+ CreateAgentSessionOptions,
25
+ QueryOnceResult,
26
+ create_agent_session,
27
+ query_once,
28
+ )
29
+ from .client import ClientOptions, PybAgentClient, SessionLostError
30
+ from .permissions import OnPermissionLost, PermissionManager, PermissionRequest
31
+ from .protocol import (
32
+ ProtocolError,
33
+ ServerMessage,
34
+ decode_envelope,
35
+ decode_server_message,
36
+ encode_client_message,
37
+ )
38
+ from .protocol_session_backend import (
39
+ DefaultProtocolSessionClient,
40
+ DefaultSSEClient,
41
+ ProtocolSessionBackend,
42
+ create_protocol_session_backend,
43
+ )
44
+ from .runtime_events import RuntimeEvent, RuntimeEventDomain, project_legacy_event_to_runtime_event
45
+ from .transport import HybridTransport, TransportConfig
46
+
47
+ __all__ = [
48
+ "AgentCompactionState",
49
+ "AgentCompactionAdapter",
50
+ "AgentCompactionEvent",
51
+ "AGENT_HOOK_EVENT_NAMES",
52
+ "AGENT_HOOK_TYPES",
53
+ "AgentHookAdapter",
54
+ "AgentMcpAdapter",
55
+ "AgentMcpCapabilityState",
56
+ "AgentMcpEvent",
57
+ "AgentSession",
58
+ "AgentSessionInput",
59
+ "AgentSubagentLifecycleEvent",
60
+ "ClientOptions",
61
+ "CreateAgentSessionOptions",
62
+ "DefaultProtocolSessionClient",
63
+ "DefaultSSEClient",
64
+ "ProtocolSessionBackend",
65
+ "ProtocolError",
66
+ "PermissionManager",
67
+ "PermissionRequest",
68
+ "PybAgentClient",
69
+ "SessionLostError",
70
+ "OnPermissionLost",
71
+ "HybridTransport",
72
+ "TransportConfig",
73
+ "QueryOnceResult",
74
+ "RuntimeEvent",
75
+ "RuntimeEventDomain",
76
+ "create_agent_session",
77
+ "create_agent_compaction_adapter",
78
+ "create_agent_hook_adapter",
79
+ "create_agent_mcp_adapter",
80
+ "create_default_mcp_adapter_from_pyb_client",
81
+ "create_protocol_session_backend",
82
+ "decode_envelope",
83
+ "decode_server_message",
84
+ "encode_client_message",
85
+ "project_legacy_event_to_runtime_event",
86
+ "query_once",
87
+ ]
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable, Protocol
5
+
6
+
7
+ @dataclass(slots=True)
8
+ class AgentCompactionState:
9
+ mode: str | None = None
10
+ trigger_strategy: str | None = None
11
+ summary_present: bool = False
12
+ has_restore_seeds: bool = False
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class AgentCompactionEvent:
17
+ type: str
18
+ session_id: str
19
+ timestamp: float
20
+ data: dict[str, object]
21
+
22
+
23
+ class AgentCompactionAdapter(Protocol):
24
+ def get_state(self) -> object: ...
25
+
26
+ def get_summary(self) -> object: ...
27
+
28
+ async def compact(self, input_data: dict[str, object] | None = None) -> object: ...
29
+
30
+ def subscribe(
31
+ self,
32
+ listener: Callable[[AgentCompactionEvent], None],
33
+ ) -> Callable[[], None]: ...
34
+
35
+
36
+ class _AgentCompactionAdapter:
37
+ def __init__(
38
+ self,
39
+ *,
40
+ get_state: Callable[[], object],
41
+ get_summary: Callable[[], object],
42
+ compact: Callable[[dict[str, object] | None], object],
43
+ subscribe: Callable[[Callable[[AgentCompactionEvent], None]], Callable[[], None]],
44
+ ) -> None:
45
+ self._get_state = get_state
46
+ self._get_summary = get_summary
47
+ self._compact = compact
48
+ self._subscribe = subscribe
49
+
50
+ def get_state(self) -> object:
51
+ return self._get_state()
52
+
53
+ def get_summary(self) -> object:
54
+ return self._get_summary()
55
+
56
+ async def compact(self, input_data: dict[str, object] | None = None) -> object:
57
+ result = self._compact(input_data)
58
+ if hasattr(result, "__await__"):
59
+ return await result # type: ignore[misc]
60
+ return result
61
+
62
+ def subscribe(
63
+ self,
64
+ listener: Callable[[AgentCompactionEvent], None],
65
+ ) -> Callable[[], None]:
66
+ return self._subscribe(listener)
67
+
68
+
69
+ def create_agent_compaction_adapter(
70
+ *,
71
+ get_state: Callable[[], object],
72
+ get_summary: Callable[[], object],
73
+ compact: Callable[[dict[str, object] | None], object],
74
+ subscribe: Callable[[Callable[[AgentCompactionEvent], None]], Callable[[], None]],
75
+ ) -> AgentCompactionAdapter:
76
+ return _AgentCompactionAdapter(
77
+ get_state=get_state,
78
+ get_summary=get_summary,
79
+ compact=compact,
80
+ subscribe=subscribe,
81
+ )
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable, Literal, Protocol, Sequence
5
+
6
+ AGENT_HOOK_EVENT_NAMES = {
7
+ "PreToolUse",
8
+ "PostToolUse",
9
+ "PostToolUseFailure",
10
+ "PermissionRequest",
11
+ "PermissionDenied",
12
+ "SessionStart",
13
+ "SessionEnd",
14
+ "UserPromptSubmit",
15
+ "Stop",
16
+ "PreCompact",
17
+ "PostCompact",
18
+ "Elicitation",
19
+ "ElicitationResult",
20
+ "Notification",
21
+ "SubagentStart",
22
+ "SubagentStop",
23
+ "ConfigChange",
24
+ }
25
+ AGENT_HOOK_TYPES = {"command", "prompt"}
26
+
27
+
28
+ AgentHookType = Literal["command", "prompt"]
29
+
30
+
31
+ @dataclass(slots=True)
32
+ class AgentSubagentLifecycleEvent:
33
+ type: str
34
+ session_id: str
35
+ timestamp: float
36
+ data: dict[str, object]
37
+
38
+
39
+ class AgentHookAdapter(Protocol):
40
+ def register_hooks(self, hooks: Sequence[dict[str, object]]) -> None: ...
41
+
42
+ def get_runtime_state(self) -> object: ...
43
+
44
+ def subscribe_subagent_lifecycle(
45
+ self,
46
+ listener: Callable[[AgentSubagentLifecycleEvent], None],
47
+ ) -> Callable[[], None]: ...
48
+
49
+
50
+ class _AgentHookAdapter:
51
+ def __init__(
52
+ self,
53
+ *,
54
+ register_hooks: Callable[[Sequence[dict[str, object]]], None] | None = None,
55
+ get_runtime_state: Callable[[], object] | None = None,
56
+ subscribe_subagent_lifecycle: Callable[
57
+ [Callable[[AgentSubagentLifecycleEvent], None]], Callable[[], None]
58
+ ]
59
+ | None = None,
60
+ ) -> None:
61
+ self._register_hooks = register_hooks
62
+ self._get_runtime_state = get_runtime_state
63
+ self._subscribe_subagent_lifecycle = subscribe_subagent_lifecycle
64
+
65
+ def register_hooks(self, hooks: Sequence[dict[str, object]]) -> None:
66
+ if self._register_hooks is None:
67
+ return
68
+ self._register_hooks(hooks)
69
+
70
+ def get_runtime_state(self) -> object:
71
+ if self._get_runtime_state is None:
72
+ return None
73
+ return self._get_runtime_state()
74
+
75
+ def subscribe_subagent_lifecycle(
76
+ self,
77
+ listener: Callable[[AgentSubagentLifecycleEvent], None],
78
+ ) -> Callable[[], None]:
79
+ if self._subscribe_subagent_lifecycle is None:
80
+ return lambda: None
81
+ return self._subscribe_subagent_lifecycle(listener)
82
+
83
+
84
+ def create_agent_hook_adapter(
85
+ *,
86
+ register_hooks: Callable[[Sequence[dict[str, object]]], None] | None = None,
87
+ get_runtime_state: Callable[[], object] | None = None,
88
+ subscribe_subagent_lifecycle: Callable[
89
+ [Callable[[AgentSubagentLifecycleEvent], None]], Callable[[], None]
90
+ ]
91
+ | None = None,
92
+ ) -> AgentHookAdapter:
93
+ return _AgentHookAdapter(
94
+ register_hooks=register_hooks,
95
+ get_runtime_state=get_runtime_state,
96
+ subscribe_subagent_lifecycle=subscribe_subagent_lifecycle,
97
+ )
98
+
99
+
100
+ def validate_hook_descriptors(hooks: Sequence[dict[str, object]]) -> None:
101
+ for hook in hooks:
102
+ hook_type = hook.get("type")
103
+ hook_event = hook.get("event")
104
+ if hook_type not in AGENT_HOOK_TYPES:
105
+ raise RuntimeError("AGENT_SESSION_HOOK_TYPE_UNSUPPORTED")
106
+ if hook_event not in AGENT_HOOK_EVENT_NAMES:
107
+ raise RuntimeError("AGENT_SESSION_HOOK_EVENT_UNSUPPORTED")
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Callable, Protocol
5
+
6
+
7
+ @dataclass(slots=True)
8
+ class AgentMcpCapabilityState:
9
+ servers: list[dict[str, object]] = field(default_factory=list)
10
+ capabilities: list[dict[str, object]] = field(default_factory=list)
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class AgentMcpEvent:
15
+ type: str
16
+ session_id: str
17
+ timestamp: float
18
+ data: dict[str, object]
19
+
20
+
21
+ class AgentMcpAdapter(Protocol):
22
+ async def list_servers(self) -> object: ...
23
+
24
+ async def connect_server(self, server_id: str) -> object: ...
25
+
26
+ async def disconnect_server(self, server_id: str) -> object: ...
27
+
28
+ async def reconnect_server(self, server_id: str) -> object: ...
29
+
30
+ def get_capability_state(self) -> object: ...
31
+
32
+ def get_semantic_state(self) -> object: ...
33
+
34
+ def get_deep_runtime_state(self) -> object: ...
35
+
36
+ def subscribe(
37
+ self,
38
+ listener: Callable[[AgentMcpEvent], None],
39
+ ) -> Callable[[], None]: ...
40
+
41
+
42
+ class _AgentMcpAdapter:
43
+ def __init__(
44
+ self,
45
+ *,
46
+ list_servers: Callable[[], object],
47
+ connect_server: Callable[[str], object],
48
+ disconnect_server: Callable[[str], object],
49
+ reconnect_server: Callable[[str], object] | None = None,
50
+ get_capability_state: Callable[[], object] | None = None,
51
+ get_semantic_state: Callable[[], object] | None = None,
52
+ get_deep_runtime_state: Callable[[], object] | None = None,
53
+ subscribe: Callable[[Callable[[AgentMcpEvent], None]], Callable[[], None]] | None = None,
54
+ ) -> None:
55
+ self._list_servers = list_servers
56
+ self._connect_server = connect_server
57
+ self._disconnect_server = disconnect_server
58
+ self._reconnect_server = reconnect_server
59
+ self._get_capability_state = get_capability_state
60
+ self._get_semantic_state = get_semantic_state
61
+ self._get_deep_runtime_state = get_deep_runtime_state
62
+ self._subscribe = subscribe
63
+
64
+ async def list_servers(self) -> object:
65
+ return await _maybe_await(self._list_servers())
66
+
67
+ async def connect_server(self, server_id: str) -> object:
68
+ return await _maybe_await(self._connect_server(server_id))
69
+
70
+ async def disconnect_server(self, server_id: str) -> object:
71
+ return await _maybe_await(self._disconnect_server(server_id))
72
+
73
+ async def reconnect_server(self, server_id: str) -> object:
74
+ if self._reconnect_server is None:
75
+ raise RuntimeError("AGENT_SESSION_MCP_RECONNECT_UNAVAILABLE")
76
+ return await _maybe_await(self._reconnect_server(server_id))
77
+
78
+ def get_capability_state(self) -> object:
79
+ if self._get_capability_state is None:
80
+ return None
81
+ return self._get_capability_state()
82
+
83
+ def get_semantic_state(self) -> object:
84
+ if self._get_semantic_state is None:
85
+ return None
86
+ return self._get_semantic_state()
87
+
88
+ def get_deep_runtime_state(self) -> object:
89
+ if self._get_deep_runtime_state is None:
90
+ return None
91
+ return self._get_deep_runtime_state()
92
+
93
+ def subscribe(
94
+ self,
95
+ listener: Callable[[AgentMcpEvent], None],
96
+ ) -> Callable[[], None]:
97
+ if self._subscribe is None:
98
+ return lambda: None
99
+ return self._subscribe(listener)
100
+
101
+
102
+ def create_agent_mcp_adapter(
103
+ *,
104
+ list_servers: Callable[[], object],
105
+ connect_server: Callable[[str], object],
106
+ disconnect_server: Callable[[str], object],
107
+ reconnect_server: Callable[[str], object] | None = None,
108
+ get_capability_state: Callable[[], object] | None = None,
109
+ get_semantic_state: Callable[[], object] | None = None,
110
+ get_deep_runtime_state: Callable[[], object] | None = None,
111
+ subscribe: Callable[[Callable[[AgentMcpEvent], None]], Callable[[], None]] | None = None,
112
+ ) -> AgentMcpAdapter:
113
+ return _AgentMcpAdapter(
114
+ list_servers=list_servers,
115
+ connect_server=connect_server,
116
+ disconnect_server=disconnect_server,
117
+ reconnect_server=reconnect_server,
118
+ get_capability_state=get_capability_state,
119
+ get_semantic_state=get_semantic_state,
120
+ get_deep_runtime_state=get_deep_runtime_state,
121
+ subscribe=subscribe,
122
+ )
123
+
124
+
125
+ def create_default_mcp_adapter_from_pyb_client(pyb_client: object | None) -> AgentMcpAdapter | None:
126
+ mcp = getattr(pyb_client, "mcp", None) if pyb_client is not None else None
127
+ if mcp is None:
128
+ return None
129
+ if not all(hasattr(mcp, name) for name in ("list", "connect", "disconnect")):
130
+ return None
131
+ return create_agent_mcp_adapter(
132
+ list_servers=lambda: mcp.list(),
133
+ connect_server=lambda server_id: mcp.connect(server_id),
134
+ disconnect_server=lambda server_id: mcp.disconnect(server_id),
135
+ reconnect_server=(lambda server_id: mcp.reconnect(server_id))
136
+ if hasattr(mcp, "reconnect")
137
+ else None,
138
+ )
139
+
140
+
141
+ async def _maybe_await(value: object) -> object:
142
+ if hasattr(value, "__await__"):
143
+ return await value # type: ignore[misc]
144
+ return value