agent-phonon-sdk 0.2.4__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.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-phonon-sdk
3
+ Version: 0.2.4
4
+ Summary: Server SDK for agent-phonon — orchestrate AI agents across multiple devices.
5
+ Author: agent-phonon contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hackerphysics/agent-phonon
8
+ Project-URL: Repository, https://github.com/hackerphysics/agent-phonon
9
+ Project-URL: Issues, https://github.com/hackerphysics/agent-phonon/issues
10
+ Keywords: ai,agents,orchestration,llm,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: websockets>=12.0
18
+
19
+ # agent-phonon — Python Server SDK
20
+
21
+ 让任何 Python 项目「一键成为 agent-phonon 服务端」:编排**多台设备**上的 AI agent(Claude Code / Codex / OpenCode / OpenClaw / Hermes)。
22
+
23
+ ## 安装
24
+
25
+ ```bash
26
+ pip install agent-phonon # 或 pip install -e sdk-python/
27
+ ```
28
+
29
+ ## 用法
30
+
31
+ ```python
32
+ import asyncio
33
+ from agent_phonon import PhononServer
34
+
35
+ async def main():
36
+ server = PhononServer(port=8080, authenticate=verify_device)
37
+
38
+ @server.on_device
39
+ async def handle(device): # 每台设备拨入时
40
+ agents = await device.discover() # 列设备上的 agent
41
+ proj = await device.project_create("my-proj")
42
+ session = await device.create_session(
43
+ project=proj["project"]["projectId"],
44
+ agent="openclaw:main", model="claude-opus-4.8",
45
+ )
46
+ # HITL:危险操作裁决
47
+ device.set_hook_decider(
48
+ lambda hook, s: "abort" if "rm -rf" in str(hook.get("payload", {})) else "continue"
49
+ )
50
+ await session.send("帮我重构这个函数")
51
+ async for event in session.stream(): # 流式输出
52
+ if event.get("type") == "message":
53
+ print(event["text"], end="")
54
+
55
+ await server.listen()
56
+ await asyncio.Future() # 长跑
57
+
58
+ async def verify_device(device_id, device_key):
59
+ return "tenant-1" if device_key == "secret" else None # None = 拒绝
60
+
61
+ asyncio.run(main())
62
+ ```
63
+
64
+ ## 核心抽象
65
+
66
+ - **`PhononServer`** — ws 监听,管理多设备;`authenticate(device_id, device_key) -> tenant_id | None`;`on_device` 回调;`list_devices()` / `get_device()`
67
+ - **`PhononDevice`** — `discover()` / `create_session()` / `list_sessions()` + `project_*` / `skill_*` 封装;`set_hook_decider()`(HITL)/ `set_unsolicited_handler()`(自发输出)
68
+ - **`PhononSession`** — `send()` + `async for event in session.stream()`(流式);`inject/interrupt/switch_model/compress/status/terminate`;自动 stream.ack
69
+
70
+ ## 多设备
71
+
72
+ 一个 `PhononServer` 同时连多个 phonon 设备,各设备 tenant 隔离、互不干扰——这是 phonon 作为「个人设备编排中心」的核心。
73
+
74
+ ## 协议
75
+
76
+ JSON-RPC 2.0 over WebSocket。与 TS SDK(`@agent-phonon/server-sdk`)完全协议兼容——Python 服务端能指挥 TS phonon,反之亦然。
@@ -0,0 +1,58 @@
1
+ # agent-phonon — Python Server SDK
2
+
3
+ 让任何 Python 项目「一键成为 agent-phonon 服务端」:编排**多台设备**上的 AI agent(Claude Code / Codex / OpenCode / OpenClaw / Hermes)。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install agent-phonon # 或 pip install -e sdk-python/
9
+ ```
10
+
11
+ ## 用法
12
+
13
+ ```python
14
+ import asyncio
15
+ from agent_phonon import PhononServer
16
+
17
+ async def main():
18
+ server = PhononServer(port=8080, authenticate=verify_device)
19
+
20
+ @server.on_device
21
+ async def handle(device): # 每台设备拨入时
22
+ agents = await device.discover() # 列设备上的 agent
23
+ proj = await device.project_create("my-proj")
24
+ session = await device.create_session(
25
+ project=proj["project"]["projectId"],
26
+ agent="openclaw:main", model="claude-opus-4.8",
27
+ )
28
+ # HITL:危险操作裁决
29
+ device.set_hook_decider(
30
+ lambda hook, s: "abort" if "rm -rf" in str(hook.get("payload", {})) else "continue"
31
+ )
32
+ await session.send("帮我重构这个函数")
33
+ async for event in session.stream(): # 流式输出
34
+ if event.get("type") == "message":
35
+ print(event["text"], end="")
36
+
37
+ await server.listen()
38
+ await asyncio.Future() # 长跑
39
+
40
+ async def verify_device(device_id, device_key):
41
+ return "tenant-1" if device_key == "secret" else None # None = 拒绝
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ## 核心抽象
47
+
48
+ - **`PhononServer`** — ws 监听,管理多设备;`authenticate(device_id, device_key) -> tenant_id | None`;`on_device` 回调;`list_devices()` / `get_device()`
49
+ - **`PhononDevice`** — `discover()` / `create_session()` / `list_sessions()` + `project_*` / `skill_*` 封装;`set_hook_decider()`(HITL)/ `set_unsolicited_handler()`(自发输出)
50
+ - **`PhononSession`** — `send()` + `async for event in session.stream()`(流式);`inject/interrupt/switch_model/compress/status/terminate`;自动 stream.ack
51
+
52
+ ## 多设备
53
+
54
+ 一个 `PhononServer` 同时连多个 phonon 设备,各设备 tenant 隔离、互不干扰——这是 phonon 作为「个人设备编排中心」的核心。
55
+
56
+ ## 协议
57
+
58
+ JSON-RPC 2.0 over WebSocket。与 TS SDK(`@agent-phonon/server-sdk`)完全协议兼容——Python 服务端能指挥 TS phonon,反之亦然。
@@ -0,0 +1,6 @@
1
+ """agent-phonon Server SDK — orchestrate AI agents across multiple devices."""
2
+ from .server import PhononServer, PhononDevice, PhononSession
3
+ from .rpc import RpcPeer, RpcError
4
+
5
+ __all__ = ["PhononServer", "PhononDevice", "PhononSession", "RpcPeer", "RpcError"]
6
+ __version__ = "0.2.4"
@@ -0,0 +1,75 @@
1
+ """双向 JSON-RPC 2.0 peer(asyncio)。每个 device 连接一个。"""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import json
6
+ import uuid
7
+ from typing import Any, Awaitable, Callable
8
+
9
+ RpcHandler = Callable[[str, Any], Awaitable[Any]]
10
+
11
+
12
+ class RpcPeer:
13
+ def __init__(self, send: Callable[[str], Awaitable[None]], handler: RpcHandler) -> None:
14
+ self._send = send
15
+ self._handler = handler
16
+ self._pending: dict[Any, asyncio.Future] = {}
17
+ self._next_id = 1
18
+
19
+ async def request(self, method: str, params: Any, timeout: float = 600.0) -> Any:
20
+ rid = self._next_id
21
+ self._next_id += 1
22
+ fut: asyncio.Future = asyncio.get_event_loop().create_future()
23
+ self._pending[rid] = fut
24
+ await self._send(json.dumps({"jsonrpc": "2.0", "id": rid, "method": method, "params": params}))
25
+ try:
26
+ return await asyncio.wait_for(fut, timeout)
27
+ finally:
28
+ self._pending.pop(rid, None)
29
+
30
+ async def notify(self, method: str, params: Any) -> None:
31
+ await self._send(json.dumps({"jsonrpc": "2.0", "method": method, "params": params}))
32
+
33
+ async def handle(self, data: str) -> None:
34
+ try:
35
+ msg = json.loads(data)
36
+ except json.JSONDecodeError:
37
+ return
38
+ # 响应
39
+ if ("result" in msg or "error" in msg) and "id" in msg:
40
+ fut = self._pending.get(msg["id"])
41
+ if fut and not fut.done():
42
+ if "error" in msg and msg["error"]:
43
+ fut.set_exception(RpcError(msg["error"]))
44
+ else:
45
+ fut.set_result(msg.get("result"))
46
+ return
47
+ # 请求 / 通知
48
+ method = msg.get("method")
49
+ if isinstance(method, str):
50
+ is_notification = "id" not in msg or msg.get("id") is None
51
+ try:
52
+ result = await self._handler(method, msg.get("params"))
53
+ if not is_notification:
54
+ await self._send(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": result if result is not None else None}))
55
+ except Exception as e: # noqa: BLE001
56
+ if not is_notification:
57
+ await self._send(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32000, "message": str(e)}}))
58
+
59
+ def reject_all(self, reason: str) -> None:
60
+ for fut in self._pending.values():
61
+ if not fut.done():
62
+ fut.set_exception(ConnectionError(reason))
63
+ self._pending.clear()
64
+
65
+
66
+ class RpcError(Exception):
67
+ def __init__(self, error: Any) -> None:
68
+ self.error = error
69
+ msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
70
+ self.app_code = (error.get("data") or {}).get("appCode") if isinstance(error, dict) else None
71
+ super().__init__(msg)
72
+
73
+
74
+ def new_id() -> str:
75
+ return str(uuid.uuid4())
@@ -0,0 +1,241 @@
1
+ """agent-phonon Server SDK(Python)。
2
+
3
+ 让任何 Python 项目「一键成为 phonon 服务端」:导入 SDK → 配鉴权 → 监听 device →
4
+ 用干净接口(discover / create_session / send / stream / on_hook)编排多台设备上的 agent。
5
+ 协议帧/握手/ack/HITL 路由全由 SDK 处理。支持多设备。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from typing import Any, AsyncIterator, Awaitable, Callable, Optional
11
+
12
+ import websockets
13
+
14
+ from .rpc import RpcPeer, new_id
15
+
16
+ # 鉴权回调:返回 tenantId 字符串表示通过,返回 None 拒绝。
17
+ Authenticate = Callable[[str, Optional[str]], Awaitable[Optional[str]]]
18
+ # HITL 裁决:返回 "continue"/"abort"/"inject"/"modify" 或 (action, reason)。
19
+ HookDecider = Callable[[dict, Optional["PhononSession"]], Awaitable[Any]]
20
+
21
+
22
+ class PhononSession:
23
+ """一个会话。流式输出用 async iterator。"""
24
+
25
+ def __init__(self, device: "PhononDevice", session_id: str) -> None:
26
+ self.session_id = session_id
27
+ self.device = device
28
+ self._queue: asyncio.Queue = asyncio.Queue()
29
+ self._last_seq = -1
30
+
31
+ async def send(self, text: str, **opts: Any) -> dict:
32
+ return await self.device.call("session.send", {"sessionId": self.session_id, "input": text, **opts})
33
+
34
+ async def stream(self) -> AsyncIterator[dict]:
35
+ """异步迭代本会话的流式事件,直到 final。"""
36
+ while True:
37
+ ev = await self._queue.get()
38
+ yield ev
39
+ if ev.get("final"):
40
+ break
41
+
42
+ async def inject(self, context: list[dict]) -> Any:
43
+ return await self.device.call("session.inject", {"sessionId": self.session_id, "context": context})
44
+
45
+ async def interrupt(self, reason: str | None = None) -> Any:
46
+ return await self.device.call("session.interrupt", {"sessionId": self.session_id, "reason": reason})
47
+
48
+ async def switch_model(self, model: str) -> Any:
49
+ return await self.device.call("session.switchModel", {"sessionId": self.session_id, "model": model})
50
+
51
+ async def compress(self, mode: str = "native") -> Any:
52
+ return await self.device.call("session.compress", {"sessionId": self.session_id, "mode": mode})
53
+
54
+ async def status(self) -> dict:
55
+ return await self.device.call("session.status", {"sessionId": self.session_id})
56
+
57
+ async def terminate(self) -> Any:
58
+ return await self.device.call("session.terminate", {"sessionId": self.session_id})
59
+
60
+ def _on_stream(self, ev: dict) -> None:
61
+ seq = ev.get("seq", -1)
62
+ if seq > self._last_seq:
63
+ self._last_seq = seq
64
+ self._queue.put_nowait(ev)
65
+
66
+
67
+ class PhononDevice:
68
+ """一台连入的设备。"""
69
+
70
+ def __init__(self, device_id: str, tenant_id: str, peer: RpcPeer) -> None:
71
+ self.device_id = device_id
72
+ self.tenant_id = tenant_id
73
+ self._peer = peer
74
+ self._sessions: dict[str, PhononSession] = {}
75
+ self._hook_decider: Optional[HookDecider] = None
76
+ self._unsolicited: Optional[Callable[[dict], None]] = None
77
+
78
+ async def call(self, method: str, params: Any) -> Any:
79
+ return await self._peer.request(method, params)
80
+
81
+ async def discover(self) -> list[dict]:
82
+ r = await self._peer.request("discovery.list", {})
83
+ return r.get("agents", [])
84
+
85
+ async def create_session(self, project: str, agent: str, model: str, **opts: Any) -> PhononSession:
86
+ r = await self._peer.request("session.create", {"project": project, "agent": agent, "model": model, "verbosity": "messages", **opts})
87
+ s = PhononSession(self, r["sessionId"])
88
+ self._sessions[r["sessionId"]] = s
89
+ return s
90
+
91
+ async def list_sessions(self, **filt: Any) -> dict:
92
+ return await self._peer.request("session.list", filt)
93
+
94
+ async def info(self) -> dict:
95
+ """设备 OS/机器信息,用于服务端做任务调度决策。"""
96
+ return await self._peer.request("device.info", {})
97
+
98
+ async def resources(self) -> dict:
99
+ """设备资源快照:CPU/内存/磁盘/进程/GPU best-effort。"""
100
+ return await self._peer.request("device.resources", {})
101
+
102
+ # ---- project / file / skill 便捷封装 ----
103
+ async def project_create(self, name: str, git: bool = True, **opts: Any) -> dict:
104
+ return await self._peer.request("project.create", {"name": name, "git": git, **opts})
105
+
106
+ async def project_list(self) -> dict:
107
+ return await self._peer.request("project.list", {})
108
+
109
+ async def project_remove(self, project_id: str, **opts: Any) -> Any:
110
+ return await self._peer.request("project.remove", {"projectId": project_id, **opts})
111
+
112
+ async def env_set(self, scope: str, name: str, value: str, **opts: Any) -> dict:
113
+ return await self._peer.request("env.set", {"scope": scope, "name": name, "value": value, **opts})
114
+
115
+ async def env_list(self, **opts: Any) -> dict:
116
+ return await self._peer.request("env.list", opts)
117
+
118
+ async def env_delete(self, scope: str, name: str, **opts: Any) -> dict:
119
+ return await self._peer.request("env.delete", {"scope": scope, "name": name, **opts})
120
+
121
+ async def file_read(self, project_id: str, path: str, **opts: Any) -> dict:
122
+ return await self._peer.request("file.read", {"projectId": project_id, "path": path, **opts})
123
+
124
+ async def file_write(self, project_id: str, path: str, data: str, **opts: Any) -> dict:
125
+ return await self._peer.request("file.write", {"projectId": project_id, "path": path, "data": data, **opts})
126
+
127
+ async def file_list(self, project_id: str, path: str = ".", **opts: Any) -> dict:
128
+ return await self._peer.request("file.list", {"projectId": project_id, "path": path, **opts})
129
+
130
+ async def file_stat(self, project_id: str, path: str, **opts: Any) -> dict:
131
+ return await self._peer.request("file.stat", {"projectId": project_id, "path": path, **opts})
132
+
133
+ async def file_mkdir(self, project_id: str, path: str, **opts: Any) -> dict:
134
+ return await self._peer.request("file.mkdir", {"projectId": project_id, "path": path, **opts})
135
+
136
+ async def skill_install(self, agent: str, name: str, scope: str, source: dict, project_id: str | None = None) -> Any:
137
+ return await self._peer.request("skill.install", {"agent": agent, "name": name, "scope": scope, "projectId": project_id, "source": source})
138
+
139
+ async def skill_list(self, **filt: Any) -> dict:
140
+ return await self._peer.request("skill.list", filt)
141
+
142
+ def set_hook_decider(self, fn: HookDecider) -> None:
143
+ self._hook_decider = fn
144
+
145
+ def set_unsolicited_handler(self, fn: Callable[[dict], None]) -> None:
146
+ self._unsolicited = fn
147
+
148
+ async def _handle_inbound(self, method: str, params: Any) -> Any:
149
+ if method == "stream.event":
150
+ ev = params or {}
151
+ s = self._sessions.get(ev.get("sessionId"))
152
+ if s is not None:
153
+ s._on_stream(ev)
154
+ await self._peer.notify("stream.ack", {"sessionId": ev.get("sessionId"), "lastSeq": s._last_seq})
155
+ elif ev.get("origin") == "unsolicited" and self._unsolicited:
156
+ self._unsolicited(ev)
157
+ return None
158
+ if method == "hook.fired":
159
+ s = self._sessions.get((params or {}).get("sessionId"))
160
+ if not self._hook_decider:
161
+ return {"applied": True}
162
+ d = await self._hook_decider(params, s)
163
+ if isinstance(d, tuple):
164
+ return {"action": d[0], "reason": d[1]}
165
+ return {"action": d}
166
+ return None
167
+
168
+ def _on_close(self) -> None:
169
+ self._peer.reject_all("device disconnected")
170
+
171
+
172
+ class PhononServer:
173
+ """监听 ws,管理多设备。"""
174
+
175
+ def __init__(self, host: str = "127.0.0.1", port: int = 0, authenticate: Optional[Authenticate] = None) -> None:
176
+ self._host = host
177
+ self._port = port
178
+ self._authenticate = authenticate
179
+ self._devices: dict[str, PhononDevice] = {}
180
+ self._on_device: Optional[Callable[[PhononDevice], Awaitable[None]]] = None
181
+ self._server: Any = None
182
+ self.port = port
183
+
184
+ def on_device(self, fn: Callable[[PhononDevice], Awaitable[None]]) -> Callable:
185
+ """装饰器/setter:设备拨入时回调。"""
186
+ self._on_device = fn
187
+ return fn
188
+
189
+ def list_devices(self) -> list[PhononDevice]:
190
+ return list(self._devices.values())
191
+
192
+ def get_device(self, device_id: str) -> Optional[PhononDevice]:
193
+ return self._devices.get(device_id)
194
+
195
+ async def listen(self) -> int:
196
+ self._server = await websockets.serve(self._on_connection, self._host, self._port)
197
+ sock = list(self._server.sockets)[0]
198
+ self.port = sock.getsockname()[1]
199
+ return self.port
200
+
201
+ async def _on_connection(self, ws: Any) -> None:
202
+ device: Optional[PhononDevice] = None
203
+
204
+ async def send(data: str) -> None:
205
+ await ws.send(data)
206
+
207
+ async def handler(method: str, params: Any) -> Any:
208
+ nonlocal device
209
+ if method == "connect.hello":
210
+ p = params or {}
211
+ device_id = p.get("deviceId")
212
+ device_key = (p.get("auth") or {}).get("deviceKey")
213
+ if self._authenticate:
214
+ tenant = await self._authenticate(device_id, device_key)
215
+ else:
216
+ tenant = f"tenant-{device_id}"
217
+ if tenant is None:
218
+ raise PermissionError("unauthorized")
219
+ device = PhononDevice(device_id, tenant, peer)
220
+ self._devices[device_id] = device
221
+ if self._on_device:
222
+ asyncio.create_task(self._on_device(device))
223
+ import datetime
224
+ return {"protocolVersion": "0.1.0", "tenantId": tenant, "features": [], "at": datetime.datetime.now(datetime.timezone.utc).isoformat()}
225
+ if device is None:
226
+ raise RuntimeError("not connected")
227
+ return await device._handle_inbound(method, params)
228
+
229
+ peer = RpcPeer(send, handler)
230
+ try:
231
+ async for raw in ws:
232
+ await peer.handle(raw if isinstance(raw, str) else raw.decode())
233
+ finally:
234
+ if device is not None:
235
+ self._devices.pop(device.device_id, None)
236
+ device._on_close()
237
+
238
+ async def close(self) -> None:
239
+ if self._server:
240
+ self._server.close()
241
+ await self._server.wait_closed()
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-phonon-sdk
3
+ Version: 0.2.4
4
+ Summary: Server SDK for agent-phonon — orchestrate AI agents across multiple devices.
5
+ Author: agent-phonon contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hackerphysics/agent-phonon
8
+ Project-URL: Repository, https://github.com/hackerphysics/agent-phonon
9
+ Project-URL: Issues, https://github.com/hackerphysics/agent-phonon/issues
10
+ Keywords: ai,agents,orchestration,llm,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: websockets>=12.0
18
+
19
+ # agent-phonon — Python Server SDK
20
+
21
+ 让任何 Python 项目「一键成为 agent-phonon 服务端」:编排**多台设备**上的 AI agent(Claude Code / Codex / OpenCode / OpenClaw / Hermes)。
22
+
23
+ ## 安装
24
+
25
+ ```bash
26
+ pip install agent-phonon # 或 pip install -e sdk-python/
27
+ ```
28
+
29
+ ## 用法
30
+
31
+ ```python
32
+ import asyncio
33
+ from agent_phonon import PhononServer
34
+
35
+ async def main():
36
+ server = PhononServer(port=8080, authenticate=verify_device)
37
+
38
+ @server.on_device
39
+ async def handle(device): # 每台设备拨入时
40
+ agents = await device.discover() # 列设备上的 agent
41
+ proj = await device.project_create("my-proj")
42
+ session = await device.create_session(
43
+ project=proj["project"]["projectId"],
44
+ agent="openclaw:main", model="claude-opus-4.8",
45
+ )
46
+ # HITL:危险操作裁决
47
+ device.set_hook_decider(
48
+ lambda hook, s: "abort" if "rm -rf" in str(hook.get("payload", {})) else "continue"
49
+ )
50
+ await session.send("帮我重构这个函数")
51
+ async for event in session.stream(): # 流式输出
52
+ if event.get("type") == "message":
53
+ print(event["text"], end="")
54
+
55
+ await server.listen()
56
+ await asyncio.Future() # 长跑
57
+
58
+ async def verify_device(device_id, device_key):
59
+ return "tenant-1" if device_key == "secret" else None # None = 拒绝
60
+
61
+ asyncio.run(main())
62
+ ```
63
+
64
+ ## 核心抽象
65
+
66
+ - **`PhononServer`** — ws 监听,管理多设备;`authenticate(device_id, device_key) -> tenant_id | None`;`on_device` 回调;`list_devices()` / `get_device()`
67
+ - **`PhononDevice`** — `discover()` / `create_session()` / `list_sessions()` + `project_*` / `skill_*` 封装;`set_hook_decider()`(HITL)/ `set_unsolicited_handler()`(自发输出)
68
+ - **`PhononSession`** — `send()` + `async for event in session.stream()`(流式);`inject/interrupt/switch_model/compress/status/terminate`;自动 stream.ack
69
+
70
+ ## 多设备
71
+
72
+ 一个 `PhononServer` 同时连多个 phonon 设备,各设备 tenant 隔离、互不干扰——这是 phonon 作为「个人设备编排中心」的核心。
73
+
74
+ ## 协议
75
+
76
+ JSON-RPC 2.0 over WebSocket。与 TS SDK(`@agent-phonon/server-sdk`)完全协议兼容——Python 服务端能指挥 TS phonon,反之亦然。
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ agent_phonon/__init__.py
4
+ agent_phonon/rpc.py
5
+ agent_phonon/server.py
6
+ agent_phonon_sdk.egg-info/PKG-INFO
7
+ agent_phonon_sdk.egg-info/SOURCES.txt
8
+ agent_phonon_sdk.egg-info/dependency_links.txt
9
+ agent_phonon_sdk.egg-info/requires.txt
10
+ agent_phonon_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ websockets>=12.0
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-phonon-sdk"
7
+ version = "0.2.4"
8
+ description = "Server SDK for agent-phonon — orchestrate AI agents across multiple devices."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = ["websockets>=12.0"]
12
+ license = "MIT"
13
+ authors = [{ name = "agent-phonon contributors" }]
14
+ keywords = ["ai", "agents", "orchestration", "llm", "sdk"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Topic :: Software Development :: Libraries",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/hackerphysics/agent-phonon"
24
+ Repository = "https://github.com/hackerphysics/agent-phonon"
25
+ Issues = "https://github.com/hackerphysics/agent-phonon/issues"
26
+
27
+ [tool.setuptools.packages.find]
28
+ include = ["agent_phonon*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+