agent-phonon-sdk 0.2.4__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.
agent_phonon/__init__.py
ADDED
|
@@ -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"
|
agent_phonon/rpc.py
ADDED
|
@@ -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())
|
agent_phonon/server.py
ADDED
|
@@ -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,7 @@
|
|
|
1
|
+
agent_phonon/__init__.py,sha256=4xcYD3zHqyExp9-0NHBdyec-LoNk3Hk2CkWHcsTKsiQ,284
|
|
2
|
+
agent_phonon/rpc.py,sha256=z-3M1RD-HBhYholVetMA1pDkbDOYaNXdrsu126odKIc,2896
|
|
3
|
+
agent_phonon/server.py,sha256=u_p8bBx2Su3dVZDVRSGAS4wbVmpO8UM7Mk-MFbDNPGA,10460
|
|
4
|
+
agent_phonon_sdk-0.2.4.dist-info/METADATA,sha256=uOQKT83HvqwWEv6ndNRSh7FcUY3KZB4GDSGrBFyVrK0,3085
|
|
5
|
+
agent_phonon_sdk-0.2.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
agent_phonon_sdk-0.2.4.dist-info/top_level.txt,sha256=ENHMEURuw8-nSaYIUAoni5TS754pWMm9FOLO3iU7WL0,13
|
|
7
|
+
agent_phonon_sdk-0.2.4.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_phonon
|