python-codex 0.3.3__py3-none-any.whl → 0.4.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.
- pycodex/__init__.py +3 -27
- pycodex/bootstrap.py +5 -45
- pycodex/cli.py +1 -14
- pycodex/context.py +1 -1
- pycodex/events.py +1 -1
- pycodex/model.py +2 -2
- pycodex/portable/__init__.py +45 -0
- pycodex/{portable.py → portable/client.py} +4 -7
- pycodex/{portable_server.py → portable/server.py} +3 -3
- pycodex/protocol.py +1 -1
- pycodex/runtime.py +2 -2
- pycodex/tools/base_tool.py +1 -1
- pycodex/tools/close_agent_tool.py +1 -1
- pycodex/tools/code_mode_manager.py +1 -1
- pycodex/tools/request_permissions_tool.py +1 -1
- pycodex/tools/resume_agent_tool.py +1 -1
- pycodex/{runtime_services.py → tools/runtime_services.py} +5 -5
- pycodex/tools/send_input_tool.py +1 -1
- pycodex/tools/spawn_agent_tool.py +1 -1
- pycodex/tools/unified_exec_manager.py +1 -1
- pycodex/tools/update_plan_tool.py +1 -1
- pycodex/tools/view_image_tool.py +5 -0
- pycodex/tools/wait_agent_tool.py +1 -1
- pycodex/utils/__init__.py +0 -4
- pycodex/utils/_aes_gcm/LICENSE +56 -0
- pycodex/utils/_aes_gcm/README.md +61 -0
- pycodex/utils/_aes_gcm/__init__.py +1 -0
- pycodex/utils/_aes_gcm/aes.py +175 -0
- pycodex/utils/_aes_gcm/gcm.py +73 -0
- pycodex/utils/_aes_gcm/source-sha256.json +4 -0
- pycodex/utils/async_bridge.py +3 -4
- pycodex/{compat.py → utils/compat.py} +2 -0
- pycodex/utils/crypto.py +35 -0
- pycodex/utils/debug.py +24 -0
- pycodex/{doctor.py → utils/doctor.py} +3 -3
- pycodex/utils/image_utils.py +3 -2
- pycodex/{model_metadata.py → utils/model_metadata.py} +2 -2
- pycodex/utils/session_persist.py +28 -0
- {python_codex-0.3.3.dist-info → python_codex-0.4.1.dist-info}/METADATA +124 -42
- python_codex-0.4.1.dist-info/RECORD +90 -0
- {python_codex-0.3.3.dist-info → python_codex-0.4.1.dist-info}/entry_points.txt +0 -1
- responses_server/app.py +28 -15
- pycodex/feishu_card.py +0 -793
- pycodex/feishu_link.py +0 -440
- pycodex/tools/ipython_tool.py +0 -142
- python_codex-0.3.3.dist-info/RECORD +0 -98
- workspace_server/__init__.py +0 -39
- workspace_server/__main__.py +0 -4
- workspace_server/app.py +0 -1906
- workspace_server/content_views.LICENSE.txt +0 -470
- workspace_server/content_views.css +0 -1
- workspace_server/content_views.mjs +0 -25
- workspace_server/editor.css +0 -1
- workspace_server/editor.mjs +0 -1260
- workspace_server/owned_process.py +0 -27
- workspace_server/workspace.html +0 -2267
- workspace_server/workspace_views.py +0 -533
- workspace_server/workspaces.html +0 -550
- workspace_server/workspaces.py +0 -632
- {python_codex-0.3.3.dist-info → python_codex-0.4.1.dist-info}/WHEEL +0 -0
- {python_codex-0.3.3.dist-info → python_codex-0.4.1.dist-info}/licenses/LICENSE +0 -0
pycodex/feishu_link.py
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import threading
|
|
3
|
-
import time
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
from .events import SessionStateEvent
|
|
7
|
-
from .feishu_card import PycodexCard
|
|
8
|
-
from .runtime import AgentRuntime
|
|
9
|
-
|
|
10
|
-
CARD_UPDATE_FLUSH_INTERVAL_SECONDS = 0.5
|
|
11
|
-
_LISTENER = None
|
|
12
|
-
_LISTENER_LOCK = threading.Lock()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class PycodexRuntimeLink:
|
|
16
|
-
def __init__(
|
|
17
|
-
self,
|
|
18
|
-
runtime: "AgentRuntime",
|
|
19
|
-
target: str,
|
|
20
|
-
loop=None,
|
|
21
|
-
card: "typing.Union[PycodexCard, None]" = None,
|
|
22
|
-
) -> None:
|
|
23
|
-
self.runtime = runtime
|
|
24
|
-
self.target = target
|
|
25
|
-
self.loop = loop
|
|
26
|
-
self.card = card or PycodexCard.from_env()
|
|
27
|
-
self.session_key = None
|
|
28
|
-
self.message_id = None
|
|
29
|
-
self._listener = None
|
|
30
|
-
self._frontend_id = None
|
|
31
|
-
self._detached = False
|
|
32
|
-
self._update_thread = None
|
|
33
|
-
self._update_stop = threading.Event()
|
|
34
|
-
self._update_lock = threading.Lock()
|
|
35
|
-
self._update_pending = False
|
|
36
|
-
|
|
37
|
-
async def start_async(self) -> "PycodexRuntimeLink":
|
|
38
|
-
self.loop = asyncio.get_event_loop()
|
|
39
|
-
self.card.apply_event(SessionStateEvent("attach", self.runtime.snapshot()))
|
|
40
|
-
try:
|
|
41
|
-
await self.loop.run_in_executor(None, self.start)
|
|
42
|
-
except Exception:
|
|
43
|
-
await self.loop.run_in_executor(None, self.stop)
|
|
44
|
-
raise
|
|
45
|
-
self._frontend_id = self.runtime.attach(self._handle_runtime_event)
|
|
46
|
-
return self
|
|
47
|
-
|
|
48
|
-
def start(self) -> "PycodexRuntimeLink":
|
|
49
|
-
if self.loop is None:
|
|
50
|
-
raise RuntimeError("PycodexRuntimeLink.start_async() is required")
|
|
51
|
-
self.card.send(self.target)
|
|
52
|
-
self.session_key = self.card.session_key
|
|
53
|
-
self.message_id = self.card.message_id
|
|
54
|
-
self._listener = _feishu_listener(self.card)
|
|
55
|
-
self._listener.register(self)
|
|
56
|
-
self._listener.start()
|
|
57
|
-
self._start_update_thread()
|
|
58
|
-
return self
|
|
59
|
-
|
|
60
|
-
def stop(self) -> None:
|
|
61
|
-
self._detach_frontend()
|
|
62
|
-
if self._listener is not None:
|
|
63
|
-
_release_feishu_listener(self._listener, self)
|
|
64
|
-
self._listener = None
|
|
65
|
-
self._stop_update_thread()
|
|
66
|
-
|
|
67
|
-
def detach(self) -> None:
|
|
68
|
-
self._detached = True
|
|
69
|
-
self._detach_frontend()
|
|
70
|
-
if self._listener is not None:
|
|
71
|
-
_release_feishu_listener(self._listener, self)
|
|
72
|
-
self._listener = None
|
|
73
|
-
self.card.detach()
|
|
74
|
-
self._safe_update_card()
|
|
75
|
-
self._stop_update_thread()
|
|
76
|
-
|
|
77
|
-
def _detach_frontend(self) -> None:
|
|
78
|
-
if self._frontend_id is not None:
|
|
79
|
-
self.runtime.detach(self._frontend_id)
|
|
80
|
-
self._frontend_id = None
|
|
81
|
-
|
|
82
|
-
def _handle_runtime_event(self, event) -> None:
|
|
83
|
-
if self._detached:
|
|
84
|
-
return
|
|
85
|
-
self.card.apply_event(event)
|
|
86
|
-
self._update_card()
|
|
87
|
-
|
|
88
|
-
async def _submit(
|
|
89
|
-
self, action: "typing.Dict[str, object]"
|
|
90
|
-
) -> "typing.Dict[str, object]":
|
|
91
|
-
if self._detached:
|
|
92
|
-
return {"toast": {"type": "warning", "content": "pycodex is detached."}}
|
|
93
|
-
if action.get("action") == "history":
|
|
94
|
-
try:
|
|
95
|
-
self.card.show_history(action.get("history_index"))
|
|
96
|
-
except ValueError as exc:
|
|
97
|
-
return {"toast": {"type": "warning", "content": str(exc)}}
|
|
98
|
-
self._update_card()
|
|
99
|
-
return {}
|
|
100
|
-
if action.get("action") != "send":
|
|
101
|
-
return {
|
|
102
|
-
"toast": {
|
|
103
|
-
"type": "info",
|
|
104
|
-
"content": "Unsupported action: {0}".format(action.get("action")),
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
prompt = str(action.get("prompt") or "").strip()
|
|
108
|
-
future = asyncio.run_coroutine_threadsafe(
|
|
109
|
-
self.runtime.submit_input(
|
|
110
|
-
prompt, sender=str(action.get("sender") or "feishu")
|
|
111
|
-
),
|
|
112
|
-
self.loop,
|
|
113
|
-
)
|
|
114
|
-
try:
|
|
115
|
-
await asyncio.wrap_future(future)
|
|
116
|
-
except (ValueError, RuntimeError) as exc:
|
|
117
|
-
return {"toast": {"type": "warning", "content": str(exc)}}
|
|
118
|
-
return {"toast": {"type": "info", "content": "Input submitted."}}
|
|
119
|
-
|
|
120
|
-
def _update_card(self) -> None:
|
|
121
|
-
with self._update_lock:
|
|
122
|
-
self._update_pending = True
|
|
123
|
-
|
|
124
|
-
def _safe_update_card(self) -> None:
|
|
125
|
-
try:
|
|
126
|
-
self.card.update()
|
|
127
|
-
except Exception as e:
|
|
128
|
-
print(e)
|
|
129
|
-
|
|
130
|
-
def _start_update_thread(self) -> None:
|
|
131
|
-
if self._update_thread is not None and self._update_thread.is_alive():
|
|
132
|
-
return
|
|
133
|
-
self._update_stop.clear()
|
|
134
|
-
self._update_thread = threading.Thread(
|
|
135
|
-
target=self._run_update_loop,
|
|
136
|
-
name="pycodex-feishu-card",
|
|
137
|
-
)
|
|
138
|
-
self._update_thread.daemon = True
|
|
139
|
-
self._update_thread.start()
|
|
140
|
-
|
|
141
|
-
def _stop_update_thread(self) -> None:
|
|
142
|
-
self._update_stop.set()
|
|
143
|
-
thread = self._update_thread
|
|
144
|
-
if thread is not None and thread.is_alive():
|
|
145
|
-
thread.join(timeout=2.0)
|
|
146
|
-
self._update_thread = None
|
|
147
|
-
|
|
148
|
-
def _run_update_loop(self) -> None:
|
|
149
|
-
while not self._update_stop.wait(CARD_UPDATE_FLUSH_INTERVAL_SECONDS):
|
|
150
|
-
with self._update_lock:
|
|
151
|
-
if not self._update_pending:
|
|
152
|
-
continue
|
|
153
|
-
self._update_pending = False
|
|
154
|
-
self._safe_update_card()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def _feishu_listener(card: PycodexCard) -> "_FeishuCardActionListener":
|
|
158
|
-
global _LISTENER
|
|
159
|
-
with _LISTENER_LOCK:
|
|
160
|
-
if _LISTENER is None:
|
|
161
|
-
_LISTENER = _FeishuCardActionListener(card)
|
|
162
|
-
else:
|
|
163
|
-
_LISTENER.assert_compatible(card)
|
|
164
|
-
return _LISTENER
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def _release_feishu_listener(
|
|
168
|
-
listener: "_FeishuCardActionListener",
|
|
169
|
-
link: PycodexRuntimeLink,
|
|
170
|
-
) -> None:
|
|
171
|
-
global _LISTENER
|
|
172
|
-
with _LISTENER_LOCK:
|
|
173
|
-
listener.unregister(link)
|
|
174
|
-
if listener.empty():
|
|
175
|
-
listener.stop()
|
|
176
|
-
if _LISTENER is listener:
|
|
177
|
-
_LISTENER = None
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class _FeishuCardActionListener:
|
|
181
|
-
def __init__(self, card: PycodexCard) -> None:
|
|
182
|
-
self.app_id = card.app_id
|
|
183
|
-
self.app_secret = card.app_secret
|
|
184
|
-
self.domain = card.domain
|
|
185
|
-
self.encrypt_key = card.encrypt_key
|
|
186
|
-
self.verification_token = card.verification_token
|
|
187
|
-
self._links = {}
|
|
188
|
-
self._links_lock = threading.Lock()
|
|
189
|
-
self._async_thread = _AsyncLoopThread()
|
|
190
|
-
self._thread = None
|
|
191
|
-
self._client = None
|
|
192
|
-
self._client_loop = None
|
|
193
|
-
self._stop_requested = threading.Event()
|
|
194
|
-
|
|
195
|
-
def assert_compatible(self, card: PycodexCard) -> None:
|
|
196
|
-
if self.app_id != card.app_id or self.domain != card.domain:
|
|
197
|
-
raise RuntimeError("Feishu listener is already running for another app")
|
|
198
|
-
|
|
199
|
-
def start(self) -> None:
|
|
200
|
-
if not self.app_id or not self.app_secret:
|
|
201
|
-
raise RuntimeError("FEISHU_APP_ID and FEISHU_APP_SECRET are required")
|
|
202
|
-
if self._thread is not None and self._thread.is_alive():
|
|
203
|
-
return
|
|
204
|
-
self._stop_requested.clear()
|
|
205
|
-
self._async_thread.start()
|
|
206
|
-
self._thread = threading.Thread(
|
|
207
|
-
target=self._listen,
|
|
208
|
-
name="pycodex-feishu-link",
|
|
209
|
-
)
|
|
210
|
-
self._thread.daemon = True
|
|
211
|
-
self._thread.start()
|
|
212
|
-
|
|
213
|
-
def register(self, link: PycodexRuntimeLink) -> None:
|
|
214
|
-
message_id = str(getattr(link, "message_id", "") or "").strip()
|
|
215
|
-
if not message_id:
|
|
216
|
-
raise RuntimeError("Feishu linked card message_id is required")
|
|
217
|
-
with self._links_lock:
|
|
218
|
-
self._links[message_id] = link
|
|
219
|
-
|
|
220
|
-
def unregister(self, link: PycodexRuntimeLink) -> None:
|
|
221
|
-
message_id = str(getattr(link, "message_id", "") or "").strip()
|
|
222
|
-
with self._links_lock:
|
|
223
|
-
if message_id and self._links.get(message_id) is link:
|
|
224
|
-
del self._links[message_id]
|
|
225
|
-
return
|
|
226
|
-
stale_ids = [
|
|
227
|
-
registered_id
|
|
228
|
-
for registered_id, registered_link in self._links.items()
|
|
229
|
-
if registered_link is link
|
|
230
|
-
]
|
|
231
|
-
for registered_id in stale_ids:
|
|
232
|
-
del self._links[registered_id]
|
|
233
|
-
|
|
234
|
-
def empty(self) -> bool:
|
|
235
|
-
with self._links_lock:
|
|
236
|
-
return not self._links
|
|
237
|
-
|
|
238
|
-
def stop(self) -> None:
|
|
239
|
-
self._stop_requested.set()
|
|
240
|
-
client = self._client
|
|
241
|
-
loop = self._client_loop
|
|
242
|
-
if loop is not None:
|
|
243
|
-
_stop_lark_client(client, loop)
|
|
244
|
-
thread = self._thread
|
|
245
|
-
if thread is not None and thread.is_alive():
|
|
246
|
-
thread.join(timeout=2.0)
|
|
247
|
-
self._thread = None
|
|
248
|
-
self._async_thread.stop()
|
|
249
|
-
|
|
250
|
-
def _listen(self) -> None:
|
|
251
|
-
try:
|
|
252
|
-
import lark_oapi as lark
|
|
253
|
-
import lark_oapi.ws as lark_ws
|
|
254
|
-
from lark_oapi.event.callback.model.p2_card_action_trigger import (
|
|
255
|
-
P2CardActionTriggerResponse,
|
|
256
|
-
)
|
|
257
|
-
except ImportError as exc:
|
|
258
|
-
raise RuntimeError(
|
|
259
|
-
"lark-oapi is required for Feishu long-connection mode"
|
|
260
|
-
) from exc
|
|
261
|
-
|
|
262
|
-
def on_card_action(event):
|
|
263
|
-
return P2CardActionTriggerResponse(self._handle_card_action(event))
|
|
264
|
-
|
|
265
|
-
handler = (
|
|
266
|
-
lark.EventDispatcherHandler.builder(
|
|
267
|
-
self.encrypt_key or "",
|
|
268
|
-
self.verification_token or "",
|
|
269
|
-
lark.LogLevel.INFO,
|
|
270
|
-
)
|
|
271
|
-
.register_p2_card_action_trigger(on_card_action)
|
|
272
|
-
.build()
|
|
273
|
-
)
|
|
274
|
-
import lark_oapi.ws.client as lark_ws_client
|
|
275
|
-
|
|
276
|
-
client = None
|
|
277
|
-
loop = asyncio.new_event_loop()
|
|
278
|
-
self._client_loop = loop
|
|
279
|
-
asyncio.set_event_loop(loop)
|
|
280
|
-
lark_ws_client.loop = loop
|
|
281
|
-
try:
|
|
282
|
-
if self._stop_requested.is_set():
|
|
283
|
-
return
|
|
284
|
-
client = lark_ws.Client(
|
|
285
|
-
self.app_id,
|
|
286
|
-
self.app_secret,
|
|
287
|
-
event_handler=handler,
|
|
288
|
-
domain=self.domain,
|
|
289
|
-
)
|
|
290
|
-
self._client = client
|
|
291
|
-
if self._stop_requested.is_set():
|
|
292
|
-
return
|
|
293
|
-
client.start()
|
|
294
|
-
except RuntimeError as exc:
|
|
295
|
-
stopped = "Event loop stopped before Future completed" in str(exc)
|
|
296
|
-
if not self._stop_requested.is_set() or not stopped:
|
|
297
|
-
raise
|
|
298
|
-
finally:
|
|
299
|
-
if self._client is client:
|
|
300
|
-
self._client = None
|
|
301
|
-
if self._client_loop is loop:
|
|
302
|
-
self._client_loop = None
|
|
303
|
-
_close_loop(loop)
|
|
304
|
-
asyncio.set_event_loop(None)
|
|
305
|
-
|
|
306
|
-
def _handle_card_action(self, event) -> "typing.Dict[str, object]":
|
|
307
|
-
try:
|
|
308
|
-
message_id = _event_message_id(event)
|
|
309
|
-
link = self._resolve_link(message_id)
|
|
310
|
-
if link is None or link._detached:
|
|
311
|
-
return {
|
|
312
|
-
"toast": {
|
|
313
|
-
"type": "warning",
|
|
314
|
-
"content": "pycodex is detached.",
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
action = link.card.parse_action(event)
|
|
318
|
-
return self._async_thread.run(link._submit(action))
|
|
319
|
-
except Exception as exc:
|
|
320
|
-
return {
|
|
321
|
-
"toast": {
|
|
322
|
-
"type": "error",
|
|
323
|
-
"content": "pycodex error: {0}".format(exc),
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
def _resolve_link(self, message_id):
|
|
328
|
-
message_id = str(message_id or "").strip()
|
|
329
|
-
with self._links_lock:
|
|
330
|
-
if message_id:
|
|
331
|
-
return self._links.get(message_id)
|
|
332
|
-
if len(self._links) == 1:
|
|
333
|
-
return next(iter(self._links.values()))
|
|
334
|
-
return None
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def _event_message_id(event) -> "typing.Union[str, None]":
|
|
338
|
-
event_data = getattr(event, "event", None)
|
|
339
|
-
context = getattr(event_data, "context", None)
|
|
340
|
-
for name in ("open_message_id", "message_id"):
|
|
341
|
-
value = str(getattr(context, name, None) or "").strip()
|
|
342
|
-
if value:
|
|
343
|
-
return value
|
|
344
|
-
return None
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
def _stop_lark_client(client, loop) -> None:
|
|
348
|
-
if loop.is_closed():
|
|
349
|
-
return
|
|
350
|
-
disconnect = getattr(client, "_disconnect", None) if client is not None else None
|
|
351
|
-
if loop.is_running():
|
|
352
|
-
future = asyncio.run_coroutine_threadsafe(
|
|
353
|
-
_disconnect_then_stop(disconnect, loop),
|
|
354
|
-
loop,
|
|
355
|
-
)
|
|
356
|
-
try:
|
|
357
|
-
future.result(timeout=2.0)
|
|
358
|
-
except Exception as exc:
|
|
359
|
-
future.cancel()
|
|
360
|
-
print(exc)
|
|
361
|
-
loop.call_soon_threadsafe(loop.stop)
|
|
362
|
-
return
|
|
363
|
-
if callable(disconnect):
|
|
364
|
-
try:
|
|
365
|
-
loop.run_until_complete(disconnect())
|
|
366
|
-
except Exception as exc:
|
|
367
|
-
print(exc)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
async def _disconnect_then_stop(disconnect, loop) -> None:
|
|
371
|
-
try:
|
|
372
|
-
if callable(disconnect):
|
|
373
|
-
await disconnect()
|
|
374
|
-
except Exception as exc:
|
|
375
|
-
print(exc)
|
|
376
|
-
finally:
|
|
377
|
-
loop.stop()
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def _close_loop(loop) -> None:
|
|
381
|
-
if loop.is_closed():
|
|
382
|
-
return
|
|
383
|
-
tasks = list(_all_loop_tasks(loop))
|
|
384
|
-
for task in tasks:
|
|
385
|
-
task.cancel()
|
|
386
|
-
if tasks:
|
|
387
|
-
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
|
388
|
-
shutdown_asyncgens = getattr(loop, "shutdown_asyncgens", None)
|
|
389
|
-
if callable(shutdown_asyncgens):
|
|
390
|
-
loop.run_until_complete(shutdown_asyncgens())
|
|
391
|
-
loop.close()
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def _all_loop_tasks(loop):
|
|
395
|
-
all_tasks = getattr(asyncio, "all_tasks", None)
|
|
396
|
-
if callable(all_tasks):
|
|
397
|
-
return all_tasks(loop)
|
|
398
|
-
return asyncio.Task.all_tasks(loop)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
class _AsyncLoopThread:
|
|
402
|
-
def __init__(self) -> None:
|
|
403
|
-
self.loop = None
|
|
404
|
-
self.thread = None
|
|
405
|
-
self._started = threading.Event()
|
|
406
|
-
|
|
407
|
-
def start(self) -> None:
|
|
408
|
-
if self.loop is not None and self.loop.is_running():
|
|
409
|
-
return
|
|
410
|
-
self.loop = asyncio.new_event_loop()
|
|
411
|
-
self._started.clear()
|
|
412
|
-
self.thread = threading.Thread(
|
|
413
|
-
target=self._run_loop,
|
|
414
|
-
name="pycodex-feishu-asyncio",
|
|
415
|
-
)
|
|
416
|
-
self.thread.daemon = True
|
|
417
|
-
self.thread.start()
|
|
418
|
-
self._started.wait()
|
|
419
|
-
|
|
420
|
-
def run(self, coro):
|
|
421
|
-
if self.loop is None or not self.loop.is_running():
|
|
422
|
-
raise RuntimeError("Feishu asyncio loop is not running")
|
|
423
|
-
return asyncio.run_coroutine_threadsafe(coro, self.loop).result()
|
|
424
|
-
|
|
425
|
-
def stop(self) -> None:
|
|
426
|
-
loop = self.loop
|
|
427
|
-
if loop is None:
|
|
428
|
-
return
|
|
429
|
-
if loop.is_running():
|
|
430
|
-
loop.call_soon_threadsafe(loop.stop)
|
|
431
|
-
if self.thread is not None:
|
|
432
|
-
self.thread.join()
|
|
433
|
-
loop.close()
|
|
434
|
-
self.loop = None
|
|
435
|
-
self.thread = None
|
|
436
|
-
|
|
437
|
-
def _run_loop(self) -> None:
|
|
438
|
-
asyncio.set_event_loop(self.loop)
|
|
439
|
-
self._started.set()
|
|
440
|
-
self.loop.run_forever()
|
pycodex/tools/ipython_tool.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"""Optional tool that executes code in the current IPython kernel."""
|
|
2
|
-
|
|
3
|
-
from ..events import ToolCompletedEvent
|
|
4
|
-
from ..utils.event_helpers import colorize_tool_message
|
|
5
|
-
from .base_tool import BaseTool, ToolContext
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class IPythonTool(BaseTool):
|
|
9
|
-
name = "ipython"
|
|
10
|
-
description = (
|
|
11
|
-
"Execute Python code in the current IPython kernel namespace. Use this "
|
|
12
|
-
"only when running inside IPython and live notebook/session variables "
|
|
13
|
-
"need to be inspected."
|
|
14
|
-
)
|
|
15
|
-
input_schema = {
|
|
16
|
-
"type": "object",
|
|
17
|
-
"properties": {
|
|
18
|
-
"code": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"description": "Python code to execute in the current IPython session.",
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
"required": ["code"],
|
|
24
|
-
"additionalProperties": False,
|
|
25
|
-
}
|
|
26
|
-
supports_parallel = False
|
|
27
|
-
|
|
28
|
-
def __init__(self, shell, name="ipython"):
|
|
29
|
-
self._shell = shell
|
|
30
|
-
self.name = name
|
|
31
|
-
|
|
32
|
-
async def run(self, context: "ToolContext", args):
|
|
33
|
-
del context
|
|
34
|
-
if not isinstance(args, dict):
|
|
35
|
-
return {"error": "arguments must be an object"}
|
|
36
|
-
|
|
37
|
-
code = args.get("code", "")
|
|
38
|
-
if not isinstance(code, str) or not code.strip():
|
|
39
|
-
return {"error": "`code` must be a non-empty string"}
|
|
40
|
-
|
|
41
|
-
from IPython.display import Code, display
|
|
42
|
-
from IPython.utils.capture import capture_output
|
|
43
|
-
|
|
44
|
-
display(Code(code, language="python"))
|
|
45
|
-
with capture_output() as captured:
|
|
46
|
-
result = self._shell.run_cell(code, store_history=False)
|
|
47
|
-
captured.show()
|
|
48
|
-
|
|
49
|
-
output = {
|
|
50
|
-
"stdout": captured.stdout,
|
|
51
|
-
"stderr": captured.stderr,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
display_outputs = []
|
|
55
|
-
for item in captured.outputs:
|
|
56
|
-
data = getattr(item, "data", None)
|
|
57
|
-
if isinstance(data, dict):
|
|
58
|
-
display_outputs.append(
|
|
59
|
-
data.get("text/plain") or data.get("text/html") or repr(data)
|
|
60
|
-
)
|
|
61
|
-
else:
|
|
62
|
-
display_outputs.append(repr(item))
|
|
63
|
-
if display_outputs:
|
|
64
|
-
output["display"] = display_outputs
|
|
65
|
-
|
|
66
|
-
if getattr(result, "result", None) is not None:
|
|
67
|
-
output["result"] = repr(result.result)
|
|
68
|
-
|
|
69
|
-
error = result.error_before_exec or result.error_in_exec
|
|
70
|
-
if error is not None:
|
|
71
|
-
output["error"] = f"{type(error).__name__}: {error}"
|
|
72
|
-
|
|
73
|
-
return output
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _install_agent_shortcut(shell):
|
|
77
|
-
existing = shell.user_ns.get("_pycodex_agent_shortcut_transform")
|
|
78
|
-
if existing in shell.input_transformers_cleanup:
|
|
79
|
-
return existing
|
|
80
|
-
|
|
81
|
-
def transform(lines):
|
|
82
|
-
if len(lines) != 1:
|
|
83
|
-
return lines
|
|
84
|
-
|
|
85
|
-
line = lines[0]
|
|
86
|
-
stripped = line.lstrip()
|
|
87
|
-
indent = line[: len(line) - len(stripped)]
|
|
88
|
-
if not stripped.startswith("@"):
|
|
89
|
-
return lines
|
|
90
|
-
|
|
91
|
-
body = stripped[1:].rstrip("\n")
|
|
92
|
-
parts = body.split(None, 1)
|
|
93
|
-
if not parts:
|
|
94
|
-
return lines
|
|
95
|
-
|
|
96
|
-
agent_name = parts[0]
|
|
97
|
-
if not agent_name.isidentifier():
|
|
98
|
-
return lines
|
|
99
|
-
|
|
100
|
-
agent = shell.user_ns.get(agent_name)
|
|
101
|
-
if agent is None or not callable(getattr(agent, "run_turn", None)):
|
|
102
|
-
return lines
|
|
103
|
-
|
|
104
|
-
prompt = "" if len(parts) == 1 else parts[1]
|
|
105
|
-
if not prompt.strip():
|
|
106
|
-
return [f"{indent}print('Usage: @{agent_name} <prompt>')\n"]
|
|
107
|
-
|
|
108
|
-
return [f"{indent}print({agent_name}.ask({prompt!r}).output_text)\n"]
|
|
109
|
-
|
|
110
|
-
shell.input_transformers_cleanup.append(transform)
|
|
111
|
-
shell.user_ns["_pycodex_agent_shortcut_transform"] = transform
|
|
112
|
-
return transform
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def attach_ipython_event_printer(agent, color=True):
|
|
116
|
-
def handle_event(event):
|
|
117
|
-
if isinstance(event, ToolCompletedEvent):
|
|
118
|
-
tool_name = event.call.name
|
|
119
|
-
message = event.visualize()
|
|
120
|
-
for line in message.splitlines() or [""]:
|
|
121
|
-
print(colorize_tool_message(line, color, tool_name), flush=True)
|
|
122
|
-
|
|
123
|
-
agent.event_handler = handle_event
|
|
124
|
-
return handle_event
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def attach_ipython_tool(
|
|
128
|
-
agent, name="current_ipython", shortcut=True, print_tool_events=True, color=True
|
|
129
|
-
):
|
|
130
|
-
from IPython import get_ipython
|
|
131
|
-
|
|
132
|
-
shell = get_ipython()
|
|
133
|
-
if shell is None:
|
|
134
|
-
raise RuntimeError("not running inside IPython")
|
|
135
|
-
|
|
136
|
-
tool = IPythonTool(shell, name=name)
|
|
137
|
-
agent.tool_registry.register(tool)
|
|
138
|
-
if shortcut:
|
|
139
|
-
_install_agent_shortcut(shell)
|
|
140
|
-
if print_tool_events:
|
|
141
|
-
attach_ipython_event_printer(agent, color=color)
|
|
142
|
-
return tool
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
pycodex/__init__.py,sha256=4BBGUXImsNBdSQjp2KtshkjQK9BMz5gK93DzpWokkWY,3235
|
|
2
|
-
pycodex/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
-
pycodex/agent.py,sha256=zCIG2DV0oQntLXHFj8FVRkgBp5TCC0YJ7sz0lni9J1s,21177
|
|
4
|
-
pycodex/bootstrap.py,sha256=DSVx8u94tHRYbUa_e9zdKPqozOv_CrfctbTKZ4JG064,13765
|
|
5
|
-
pycodex/cli.py,sha256=m5IJhnodNqsfMTIzdwbuaHBl2r-N8x1MxYSpe_hlHiM,12997
|
|
6
|
-
pycodex/compat.py,sha256=yoQQWMiAQ6ytZv3DAMIbp66ImA0kyY4G5eKHrtwmj6c,3490
|
|
7
|
-
pycodex/context.py,sha256=nkq2Y0coZNpWs5IJj7uVVT65fDqh58Z9c07y_Wy0f4Q,26604
|
|
8
|
-
pycodex/doctor.py,sha256=9p624b1zloXbhyMRi1-Of1YtnYeoF4zHxfGFhfujDS4,10620
|
|
9
|
-
pycodex/events.py,sha256=MZljtqra6-Hnt9Emjbn_wgp5cGaODx1w3CbmIKd5htI,29692
|
|
10
|
-
pycodex/feishu_card.py,sha256=Gclo4TI3KTfdT5NDLiv58CRg6hH3sUCRXuSrHgXgYjI,27767
|
|
11
|
-
pycodex/feishu_link.py,sha256=yUbEvO-8g-Bxo8_cF9s6vLZguz3NA6q3qt2A9Q09fsE,14523
|
|
12
|
-
pycodex/model.py,sha256=HeHeaYEC06idh_66mM4uzyc7IZc_AS789ajByvOqbr8,44571
|
|
13
|
-
pycodex/model_metadata.py,sha256=RMYhvgkm2v1nhCslSiDXPAwrRAx8FPz9QJqv8QTbWlc,1332
|
|
14
|
-
pycodex/portable.py,sha256=rfOpVnBZ01RS0Kyjo_w2FNKHY2t-lUcHVtWhPsbp1-k,16158
|
|
15
|
-
pycodex/portable_server.py,sha256=A4IY3xHtBQLRxdHQ4O5zdaVGjqENaLvypsSkLGZvCZ0,7498
|
|
16
|
-
pycodex/protocol.py,sha256=dRcTVKTaE7Eaq_jiSYdCzBMebIo3gnL7wB1x-NJqY5E,12027
|
|
17
|
-
pycodex/runtime.py,sha256=bRlqrmXqK6kPndAG96QoGALKicbWmfuXgo2jDvbQzdE,23806
|
|
18
|
-
pycodex/runtime_services.py,sha256=4jDCMlXHwgbJZPJkWzVEPCRHPxrD0mpCBbDInVToGG8,15033
|
|
19
|
-
pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
|
|
20
|
-
pycodex/prompts/models.json,sha256=u-bS5xtI7ZHbrG_nbzrt-xmp5RtQT6YL8PHbd_4XZB4,397650
|
|
21
|
-
pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
|
|
22
|
-
pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
|
|
23
|
-
pycodex/prompts/permissions/approval_policy/on_request.md,sha256=hVQalzh0FAdkKzw5u-N4H7-LtC9ijVDlYsh3OKsZKzo,3661
|
|
24
|
-
pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md,sha256=mOinishp1k-wlPsaEuIOMn5GoVm_dAIsWIuEMmv2r7o,1725
|
|
25
|
-
pycodex/prompts/permissions/approval_policy/unless_trusted.md,sha256=XHpi1Lfx1iIXFbbQ_ho_kGstA3JN-RLho291HM30UNw,247
|
|
26
|
-
pycodex/prompts/permissions/sandbox_mode/danger_full_access.md,sha256=nZ7YHacBd3cAHKRZc9XClOOOnXJPXPh0WFBueh5C2D0,197
|
|
27
|
-
pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3euS-3uv_v97catIsnhxlSQSIM,173
|
|
28
|
-
pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
|
|
29
|
-
pycodex/tools/__init__.py,sha256=UYwmsX_Ckw_YaYQ6TYhUU6w5pxi2qzZoTrq6BAgNErM,1857
|
|
30
|
-
pycodex/tools/agent_tool_schemas.py,sha256=8cL9ml4So0H6IIeK0ZzjAo9bk3VeprDOSYPpIamOqEU,2048
|
|
31
|
-
pycodex/tools/apply_patch_tool.py,sha256=t8XFCGZ7q-s9C1Ty6qkReBRk-QD3ONvcKXLAz9djKRY,13956
|
|
32
|
-
pycodex/tools/base_tool.py,sha256=kNNzPWijtdl4C_GunyR5G1qvsr7KQtnFJ2RiRHHnAYo,6478
|
|
33
|
-
pycodex/tools/clock_tool.py,sha256=2_RYbBEw4V9_f1L-qZl3hhJ6XNPM_gBDmOlCh1cCAjo,6575
|
|
34
|
-
pycodex/tools/close_agent_tool.py,sha256=zQtvd5iGW6YCPFiec8zc2OdGtgNIxoA4p2ZTuCoJOaI,2031
|
|
35
|
-
pycodex/tools/code_mode_manager.py,sha256=VFVn7S_qUHCOHW952CMeGxTIE-7CPOx2uuXJZgBml8o,19160
|
|
36
|
-
pycodex/tools/exec_command_tool.py,sha256=iqRiKaaX-AjG6LjATAMOakHIP0gpCWCj2o8af_8eAiQ,4464
|
|
37
|
-
pycodex/tools/exec_runtime.js,sha256=DR1uocKailTqNWAcJNFJuQgFFMSUzTpT_uQsRaneg2s,3643
|
|
38
|
-
pycodex/tools/exec_tool.py,sha256=tuC21rlhP8CPSYml4XnSDSRV0UPGkQ6aHj0DLIoOOvM,2274
|
|
39
|
-
pycodex/tools/grep_files_tool.py,sha256=Ysw68VKI9OEvUH5haYx8FWuthkHykc1wskbuzjj8gTk,5293
|
|
40
|
-
pycodex/tools/ipython_tool.py,sha256=SKVYpJEwlYjOQZQWbpqvrA-3K9aJTlIG9KM50p5JYUM,4496
|
|
41
|
-
pycodex/tools/list_dir_tool.py,sha256=_U6ouRcVbGHT5mChVyUsiMgDGrFK7EFMFaspPLAHtH8,5337
|
|
42
|
-
pycodex/tools/read_file_tool.py,sha256=YK__uhIVldBjAQega5S_ZuZU5jgP_kLOwr-YhjAmRo4,9735
|
|
43
|
-
pycodex/tools/request_permissions_tool.py,sha256=n8eg29SgRbx1DUxxLbAztnc5i-0j2N2WnDupspjwWbE,3979
|
|
44
|
-
pycodex/tools/request_user_input_tool.py,sha256=_7siHF2p3KiNDFwsTqKKIUw2kPCXMzLaR12PkZ1wdIQ,4050
|
|
45
|
-
pycodex/tools/resume_agent_tool.py,sha256=D_OcWQbIbsWf-2NrRgDab4hB6BUbay0XidfPInhg88Y,1614
|
|
46
|
-
pycodex/tools/send_input_tool.py,sha256=J91LLz4RpvHmBWti0XZgI9xWipWwD8-1hQIdC8-Jd5E,3762
|
|
47
|
-
pycodex/tools/shell_command_tool.py,sha256=US63I5xiV5zfO1Oox2yHUg7F6XoKzmyxtohfaQIsmrw,4216
|
|
48
|
-
pycodex/tools/shell_tool.py,sha256=lqFH2XosfBg7yQKhpB7i4vLGz9sNQtEZihD0rjsmBCQ,4068
|
|
49
|
-
pycodex/tools/spawn_agent_tool.py,sha256=n_WZi1IX4Waj-GQ-hR1BVlk3zzw_ssvem7zcj3tYI38,6209
|
|
50
|
-
pycodex/tools/unified_exec_manager.py,sha256=sO1H3uoXyZ6DiyF98-eidQ77mS-P5gSWyPqM3L50yTQ,14538
|
|
51
|
-
pycodex/tools/update_plan_tool.py,sha256=8zyANrQgj8u2LBJ2yz_0Cg5jP3S6ibp6eASxUupX9C8,3199
|
|
52
|
-
pycodex/tools/view_image_tool.py,sha256=UZRkBEwuCkqxOw7XBwacdgPZAtbnD6uSHTdm-6amUo4,3902
|
|
53
|
-
pycodex/tools/wait_agent_tool.py,sha256=2OWfb9FCGu5SjbRp33dVEJWG3apMtQgczvQR1V_rm6I,3829
|
|
54
|
-
pycodex/tools/wait_tool.py,sha256=0_OO_DvMsBhG3oDKZePTOjZTBq1lmWntXAzqIqvebdA,3206
|
|
55
|
-
pycodex/tools/web_search_tool.py,sha256=3ao1IvJw7MwcP35UKLbqrM-nlm5Z-UUFVdEzkJYOV1o,988
|
|
56
|
-
pycodex/tools/write_stdin_tool.py,sha256=jvBDchLLmoDITd7g-ZVeEVXHRhWKkmnjM5laL-53FOM,3609
|
|
57
|
-
pycodex/utils/__init__.py,sha256=lbf-jbo6JS7t1wfFkQqLHlG4zZVqXYGkOIj2cvtrjx8,591
|
|
58
|
-
pycodex/utils/async_bridge.py,sha256=d21Pjim-nsQbSG5pJddd0WaQ03CzA3w3TINWDmmjWbg,1815
|
|
59
|
-
pycodex/utils/compactor.py,sha256=0tlszw1n54aMEGH6sL1U1dEgFSU5ioYL2NI5uSvHSiM,6119
|
|
60
|
-
pycodex/utils/debug.py,sha256=BdXmuAQVWVB1BONKRDlkMpgXpkt4M1jhvEAq5n2AumI,296
|
|
61
|
-
pycodex/utils/dotenv.py,sha256=h7ZgvqpEswetqjhtwfiaHyokAHwNKPakizTAHPHz5Ds,1865
|
|
62
|
-
pycodex/utils/event_helpers.py,sha256=pnxL0-nBC7cnOheHPCIfkHqJjMbkJg3C348_EzAVhFA,6342
|
|
63
|
-
pycodex/utils/get_env.py,sha256=L4S8h84tkgxZKvrxY7ezTgePbXepG-yIigUTKeY6uuE,6094
|
|
64
|
-
pycodex/utils/image_utils.py,sha256=1kXhtTqL4xtazpEABHhtvJBUJlceo8hSzg6ukmf_4FA,2407
|
|
65
|
-
pycodex/utils/random_ids.py,sha256=jLTxZZ0MBbRdQOFDFWGVRjWzIo6FKSQ5ADXbQSLPp1s,379
|
|
66
|
-
pycodex/utils/session_persist.py,sha256=v9KhmjUB2yCg55c85i8ZU1BWfc-WEMDPmpT0Rv4K8a8,22489
|
|
67
|
-
pycodex/utils/truncation.py,sha256=5D6vAorT8EgXgjd7ASwtraAyIZdlCR-w5jKZqJxDzho,5446
|
|
68
|
-
responses_server/__init__.py,sha256=sx9C88YWqmehASZy24FUMqT14A36doM_rj0MIZtQ9iQ,377
|
|
69
|
-
responses_server/__main__.py,sha256=876xZTqhPeDBD2GKKr8pKX33Af8PhFTosAI8bYQsFgQ,61
|
|
70
|
-
responses_server/app.py,sha256=9uhglqUaRXhotYDkGFemnETopgx9Moi2wHOE0pDCwtU,7755
|
|
71
|
-
responses_server/config.py,sha256=sCJYGisPHW9Uup26-R9Zv0o9W_kRVt-ETc2MFCVjG90,2795
|
|
72
|
-
responses_server/messages_api.py,sha256=ialAYTMmN0y-uxQB3Y51ukq-kvkSVO84WVLXaqLsKw0,18775
|
|
73
|
-
responses_server/payload_processors.py,sha256=dqPwqHD-FhyiIwjzFEMf2wCtQDxkRyerZzbdqnH1aDE,3561
|
|
74
|
-
responses_server/server.py,sha256=k4DbsOYkaHHcy38uF9u_QXahJC1cplKsTu-MdC3DCIs,2286
|
|
75
|
-
responses_server/session_store.py,sha256=nwGxQL6nUVKnDVeDHz8IzqNQ7dWk8Ybf0URekOHdxTo,1133
|
|
76
|
-
responses_server/stream_router.py,sha256=TQX2-8uOGad3TBJnRu9SOtDaHBHjYwE-WERYz6rcuo8,46274
|
|
77
|
-
responses_server/trajectory_dump.py,sha256=5_Wy6tslszrqx3j1JeP_boyTbzIicKy8No84PzMnmHk,5111
|
|
78
|
-
responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
|
|
79
|
-
responses_server/tools/custom_adapter.py,sha256=SnIiNUdomff3APtTriURAC9YCeEdhCDjzGMEYusfUdk,8340
|
|
80
|
-
responses_server/tools/web_search.py,sha256=pnKH9JyTScRehWpZS3HpXRvjWsQvC0QZqxjEOCT4ndg,8936
|
|
81
|
-
workspace_server/__init__.py,sha256=PRM4ONODb6hxjBUHkBD4TprE_pkTCIlT9sfftK5ic6M,866
|
|
82
|
-
workspace_server/__main__.py,sha256=876xZTqhPeDBD2GKKr8pKX33Af8PhFTosAI8bYQsFgQ,61
|
|
83
|
-
workspace_server/app.py,sha256=aoo5vq5AciEHe3r5gcgt2B349uuWLY6fbFYbOQ2CRAE,67164
|
|
84
|
-
workspace_server/content_views.LICENSE.txt,sha256=PUOceZvrZI9c2LeLVh6wJNv5wwALKoJ8jOOfq-05WmQ,64183
|
|
85
|
-
workspace_server/content_views.css,sha256=n1yPrOZbzNS0phv2JGwaYbK8t81MI2kKe9rc3sY2TRI,1662
|
|
86
|
-
workspace_server/content_views.mjs,sha256=8sbYqAgwYrAE9SMd3bO6jv6DlHwWru1gboID8h2oTLE,16241
|
|
87
|
-
workspace_server/editor.css,sha256=4qBe2ZLqloAFQ2YNyJYvsJelvTHIIQCxpfg4EVTubk8,346450
|
|
88
|
-
workspace_server/editor.mjs,sha256=oZpBsmnA56VIogpbg_NDg8cs4bIQZkuUCtXPJnVLook,4110455
|
|
89
|
-
workspace_server/owned_process.py,sha256=j1cJ89Nm7gS91Zj62kYKabFbb9qhbU7-q1YDsWcVcn8,895
|
|
90
|
-
workspace_server/workspace.html,sha256=r1cBihjnyt8cxjI9xGqTVBs3zboMcoYHemgvwNMpN2A,74728
|
|
91
|
-
workspace_server/workspace_views.py,sha256=acV-SS3Uw-Qy5AI_KV16AowB1R3cbJN4Okk6CDbpPzw,22172
|
|
92
|
-
workspace_server/workspaces.html,sha256=72WEextEEUZeAYCmZ5UcWbg1m7voFezxgL6j48koRuU,15794
|
|
93
|
-
workspace_server/workspaces.py,sha256=hMvGyZtuFshGV3l56AMhGJN0vDOvvVj8iv9-VAJJHhw,22524
|
|
94
|
-
python_codex-0.3.3.dist-info/METADATA,sha256=ucgUraJ7rlCeUstF-FlAE8qiURNuqF-qOw6xKMbYF7M,42715
|
|
95
|
-
python_codex-0.3.3.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
|
|
96
|
-
python_codex-0.3.3.dist-info/entry_points.txt,sha256=vkV2UWCtEKvQNMJuPNjt8HyBKiwp83JyqBatrBNGDp8,80
|
|
97
|
-
python_codex-0.3.3.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
|
|
98
|
-
python_codex-0.3.3.dist-info/RECORD,,
|