python-codex 0.3.3__py3-none-any.whl → 0.4.0__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 +1 -1
- 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/{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
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/METADATA +120 -42
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/RECORD +42 -50
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.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
- 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.0.dist-info}/WHEEL +0 -0
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2014 Richard Moore
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""AES encryption subset of pyaes 1.6.1; see README.md for changes."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AES256:
|
|
27
|
+
"""AES-256 block encryption, used for both directions of GCM."""
|
|
28
|
+
|
|
29
|
+
S = (
|
|
30
|
+
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
|
|
31
|
+
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
|
32
|
+
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
|
|
33
|
+
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
|
34
|
+
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
|
|
35
|
+
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
|
36
|
+
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
|
|
37
|
+
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
|
38
|
+
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
|
|
39
|
+
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
|
40
|
+
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
|
|
41
|
+
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
|
42
|
+
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
|
|
43
|
+
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
|
44
|
+
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
|
|
45
|
+
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
|
46
|
+
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
|
|
47
|
+
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
|
48
|
+
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
|
|
49
|
+
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
|
50
|
+
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
|
|
51
|
+
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
|
52
|
+
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
|
|
53
|
+
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
|
54
|
+
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
|
|
55
|
+
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
|
56
|
+
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
|
|
57
|
+
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
|
58
|
+
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
|
|
59
|
+
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
|
60
|
+
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
|
|
61
|
+
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
T1 = (
|
|
65
|
+
0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
|
|
66
|
+
0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
|
|
67
|
+
0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b,
|
|
68
|
+
0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
|
|
69
|
+
0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f,
|
|
70
|
+
0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
|
|
71
|
+
0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5,
|
|
72
|
+
0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
|
|
73
|
+
0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb,
|
|
74
|
+
0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
|
|
75
|
+
0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed,
|
|
76
|
+
0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
|
|
77
|
+
0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594,
|
|
78
|
+
0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
|
|
79
|
+
0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504,
|
|
80
|
+
0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
|
|
81
|
+
0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739,
|
|
82
|
+
0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
|
|
83
|
+
0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883,
|
|
84
|
+
0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
|
|
85
|
+
0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4,
|
|
86
|
+
0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
|
|
87
|
+
0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0,
|
|
88
|
+
0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
|
|
89
|
+
0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651,
|
|
90
|
+
0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
|
|
91
|
+
0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12,
|
|
92
|
+
0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
|
|
93
|
+
0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7,
|
|
94
|
+
0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
|
|
95
|
+
0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8,
|
|
96
|
+
0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# The other encryption tables are byte rotations of T1.
|
|
100
|
+
T2 = tuple((value >> 8) | ((value & 0xff) << 24) for value in T1)
|
|
101
|
+
T3 = tuple((value >> 16) | ((value & 0xffff) << 16) for value in T1)
|
|
102
|
+
T4 = tuple((value >> 24) | ((value & 0xffffff) << 8) for value in T1)
|
|
103
|
+
rcon = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40)
|
|
104
|
+
|
|
105
|
+
def __init__(self, key):
|
|
106
|
+
if len(key) != 32:
|
|
107
|
+
raise ValueError("AES-256 requires a 32-byte key")
|
|
108
|
+
|
|
109
|
+
self._Ke = [[0] * 4 for _ in range(15)]
|
|
110
|
+
tk = [int.from_bytes(key[i:i + 4], "big") for i in range(0, 32, 4)]
|
|
111
|
+
for i in range(8):
|
|
112
|
+
self._Ke[i // 4][i % 4] = tk[i]
|
|
113
|
+
|
|
114
|
+
# AES-256 key expansion, FIPS 197 section 5.2.
|
|
115
|
+
rconpointer = 0
|
|
116
|
+
t = 8
|
|
117
|
+
while t < 60:
|
|
118
|
+
tt = tk[7]
|
|
119
|
+
tk[0] ^= (
|
|
120
|
+
(self.S[(tt >> 16) & 0xff] << 24)
|
|
121
|
+
^ (self.S[(tt >> 8) & 0xff] << 16)
|
|
122
|
+
^ (self.S[tt & 0xff] << 8)
|
|
123
|
+
^ self.S[(tt >> 24) & 0xff]
|
|
124
|
+
^ (self.rcon[rconpointer] << 24)
|
|
125
|
+
)
|
|
126
|
+
rconpointer += 1
|
|
127
|
+
for i in range(1, 4):
|
|
128
|
+
tk[i] ^= tk[i - 1]
|
|
129
|
+
tt = tk[3]
|
|
130
|
+
tk[4] ^= (
|
|
131
|
+
self.S[tt & 0xff]
|
|
132
|
+
^ (self.S[(tt >> 8) & 0xff] << 8)
|
|
133
|
+
^ (self.S[(tt >> 16) & 0xff] << 16)
|
|
134
|
+
^ (self.S[(tt >> 24) & 0xff] << 24)
|
|
135
|
+
)
|
|
136
|
+
for i in range(5, 8):
|
|
137
|
+
tk[i] ^= tk[i - 1]
|
|
138
|
+
|
|
139
|
+
j = 0
|
|
140
|
+
while j < 8 and t < 60:
|
|
141
|
+
self._Ke[t // 4][t % 4] = tk[j]
|
|
142
|
+
j += 1
|
|
143
|
+
t += 1
|
|
144
|
+
|
|
145
|
+
def encrypt(self, plaintext):
|
|
146
|
+
"""Encrypt one 16-byte block without padding."""
|
|
147
|
+
if len(plaintext) != 16:
|
|
148
|
+
raise ValueError("AES requires a 16-byte block")
|
|
149
|
+
|
|
150
|
+
keys = self._Ke
|
|
151
|
+
sbox, t1, t2, t3, t4 = self.S, self.T1, self.T2, self.T3, self.T4
|
|
152
|
+
t = [
|
|
153
|
+
int.from_bytes(plaintext[4 * i:4 * i + 4], "big") ^ keys[0][i]
|
|
154
|
+
for i in range(4)
|
|
155
|
+
]
|
|
156
|
+
a = [0] * 4
|
|
157
|
+
for r in range(1, 14):
|
|
158
|
+
for i in range(4):
|
|
159
|
+
a[i] = (
|
|
160
|
+
t1[(t[i] >> 24) & 0xff]
|
|
161
|
+
^ t2[(t[(i + 1) % 4] >> 16) & 0xff]
|
|
162
|
+
^ t3[(t[(i + 2) % 4] >> 8) & 0xff]
|
|
163
|
+
^ t4[t[(i + 3) % 4] & 0xff]
|
|
164
|
+
^ keys[r][i]
|
|
165
|
+
)
|
|
166
|
+
t = a[:]
|
|
167
|
+
|
|
168
|
+
result = []
|
|
169
|
+
for i in range(4):
|
|
170
|
+
tt = keys[14][i]
|
|
171
|
+
result.append((sbox[(t[i] >> 24) & 0xff] ^ (tt >> 24)) & 0xff)
|
|
172
|
+
result.append((sbox[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff)
|
|
173
|
+
result.append((sbox[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff)
|
|
174
|
+
result.append((sbox[t[(i + 3) % 4] & 0xff] ^ tt) & 0xff)
|
|
175
|
+
return bytes(result)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2008, The Chromium Authors. All rights reserved.
|
|
2
|
+
# Adapted from tlslite-ng 0.8.2, tlslite/utils/aesgcm.py (author: Google).
|
|
3
|
+
# GCM derived there from Go's crypto/cipher implementation.
|
|
4
|
+
# See LICENSE for the BSD license and README.md for local changes.
|
|
5
|
+
|
|
6
|
+
"""GCM for 12-byte nonces, 16-byte tags and empty associated data."""
|
|
7
|
+
|
|
8
|
+
from .aes import AES256
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AESGCM:
|
|
12
|
+
# Reduction by 1+x+x^2+x^7 for each four-bit polynomial.
|
|
13
|
+
_reduction = (
|
|
14
|
+
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
|
|
15
|
+
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def __init__(self, key):
|
|
19
|
+
self._aes = AES256(key)
|
|
20
|
+
h = int.from_bytes(self._aes.encrypt(b"\0" * 16), "big")
|
|
21
|
+
self._product_table = [0] * 16
|
|
22
|
+
self._product_table[8] = h
|
|
23
|
+
for i in range(2, 16, 2):
|
|
24
|
+
self._product_table[self._reverse_bits(i)] = self._shift(
|
|
25
|
+
self._product_table[self._reverse_bits(i // 2)]
|
|
26
|
+
)
|
|
27
|
+
self._product_table[self._reverse_bits(i + 1)] = (
|
|
28
|
+
self._product_table[self._reverse_bits(i)] ^ h
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def crypt(self, nonce, data):
|
|
32
|
+
"""Apply GCM's counter stream to plaintext or authenticated ciphertext."""
|
|
33
|
+
output = bytearray(len(data))
|
|
34
|
+
for offset in range(0, len(data), 16):
|
|
35
|
+
counter = nonce + (2 + offset // 16).to_bytes(4, "big")
|
|
36
|
+
mask = self._aes.encrypt(counter)
|
|
37
|
+
for i, value in enumerate(data[offset:offset + 16]):
|
|
38
|
+
output[offset + i] = value ^ mask[i]
|
|
39
|
+
return bytes(output)
|
|
40
|
+
|
|
41
|
+
def tag(self, nonce, ciphertext):
|
|
42
|
+
"""Authenticate ciphertext with empty associated data."""
|
|
43
|
+
y = 0
|
|
44
|
+
for offset in range(0, len(ciphertext), 16):
|
|
45
|
+
block = ciphertext[offset:offset + 16].ljust(16, b"\0")
|
|
46
|
+
y = self._multiply(y ^ int.from_bytes(block, "big"))
|
|
47
|
+
y = self._multiply(y ^ (len(ciphertext) << 3))
|
|
48
|
+
mask = self._aes.encrypt(nonce + b"\0\0\0\1")
|
|
49
|
+
return (y ^ int.from_bytes(mask, "big")).to_bytes(16, "big")
|
|
50
|
+
|
|
51
|
+
def _multiply(self, y):
|
|
52
|
+
"""Multiply by the GCM key in GF(2^128), four bits at a time."""
|
|
53
|
+
result = 0
|
|
54
|
+
for _ in range(0, 128, 4):
|
|
55
|
+
high = result & 0xf
|
|
56
|
+
result >>= 4
|
|
57
|
+
result ^= self._reduction[high] << 112
|
|
58
|
+
result ^= self._product_table[y & 0xf]
|
|
59
|
+
y >>= 4
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _reverse_bits(value):
|
|
64
|
+
value = ((value << 2) & 0xc) | ((value >> 2) & 0x3)
|
|
65
|
+
return ((value << 1) & 0xa) | ((value >> 1) & 0x5)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _shift(value):
|
|
69
|
+
high = value & 1
|
|
70
|
+
value >>= 1
|
|
71
|
+
if high:
|
|
72
|
+
value ^= 0xe1 << 120
|
|
73
|
+
return value
|
pycodex/utils/crypto.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""AES-256-GCM using a bundled pure Python implementation."""
|
|
2
|
+
|
|
3
|
+
from hmac import compare_digest
|
|
4
|
+
|
|
5
|
+
from ._aes_gcm.gcm import AESGCM
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _validate(key, nonce, data):
|
|
9
|
+
if len(key) != 32:
|
|
10
|
+
raise ValueError("AES-256-GCM requires a 32-byte key")
|
|
11
|
+
if len(nonce) != 12:
|
|
12
|
+
raise ValueError("AES-GCM requires a 12-byte nonce")
|
|
13
|
+
if len(data) > (1 << 36) - 32:
|
|
14
|
+
raise ValueError("AES-GCM input exceeds the maximum message length")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def aes_gcm_encrypt(key: "bytes", nonce: "bytes", plaintext: "bytes"):
|
|
18
|
+
"""Return ciphertext and a 16-byte authentication tag."""
|
|
19
|
+
_validate(key, nonce, plaintext)
|
|
20
|
+
cipher = AESGCM(key)
|
|
21
|
+
ciphertext = cipher.crypt(nonce, plaintext)
|
|
22
|
+
return ciphertext, cipher.tag(nonce, ciphertext)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def aes_gcm_decrypt(
|
|
26
|
+
key: "bytes", nonce: "bytes", ciphertext: "bytes", tag: "bytes"
|
|
27
|
+
) -> "bytes":
|
|
28
|
+
"""Verify the authentication tag before decrypting any ciphertext."""
|
|
29
|
+
_validate(key, nonce, ciphertext)
|
|
30
|
+
if len(tag) != 16:
|
|
31
|
+
raise ValueError("AES-GCM requires a 16-byte authentication tag")
|
|
32
|
+
cipher = AESGCM(key)
|
|
33
|
+
if not compare_digest(tag, cipher.tag(nonce, ciphertext)):
|
|
34
|
+
raise ValueError("AES-GCM authentication failed")
|
|
35
|
+
return cipher.crypt(nonce, ciphertext)
|
pycodex/utils/debug.py
CHANGED
|
@@ -10,3 +10,27 @@ def get_debug_dir() -> "typing.Union[Path, None]":
|
|
|
10
10
|
path = Path(value).expanduser()
|
|
11
11
|
path.mkdir(parents=True, exist_ok=True)
|
|
12
12
|
return path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def debug(stop: "bool" = False):
|
|
16
|
+
import socket
|
|
17
|
+
|
|
18
|
+
import debugpy
|
|
19
|
+
|
|
20
|
+
if debugpy.is_client_connected():
|
|
21
|
+
return
|
|
22
|
+
try:
|
|
23
|
+
host = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
|
|
24
|
+
port = 5000
|
|
25
|
+
|
|
26
|
+
debugpy.listen((host, port), in_process_debug_adapter=False)
|
|
27
|
+
print("Waiting for debugger...\a")
|
|
28
|
+
print(f"ip: {host} port:{port}", flush=True)
|
|
29
|
+
debugpy.wait_for_client()
|
|
30
|
+
print("Connected.")
|
|
31
|
+
if stop:
|
|
32
|
+
debugpy.breakpoint()
|
|
33
|
+
except Exception as e:
|
|
34
|
+
import traceback
|
|
35
|
+
|
|
36
|
+
print("\n".join(traceback.format_exception(e)))
|
|
@@ -10,9 +10,9 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
import requests
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from .
|
|
13
|
+
from ..model import ResponsesModelClient, ResponsesProviderConfig
|
|
14
|
+
from ..protocol import AssistantMessage, Prompt, UserMessage
|
|
15
|
+
from .dotenv import DOTENV_FILENAME, load_codex_dotenv
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
pycodex/utils/image_utils.py
CHANGED
|
@@ -8,6 +8,7 @@ Expected behavior:
|
|
|
8
8
|
attached to a model request, matching upstream `PromptImageMode::ResizeToFit`.
|
|
9
9
|
- Keep the original bytes when the caller asks for `original` detail or the
|
|
10
10
|
image already fits.
|
|
11
|
+
- Import Pillow only when resizing is requested.
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
import base64
|
|
@@ -16,8 +17,6 @@ import mimetypes
|
|
|
16
17
|
import typing
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
|
|
19
|
-
from PIL import Image
|
|
20
|
-
|
|
21
20
|
MAX_DIMENSION = 2048
|
|
22
21
|
|
|
23
22
|
_PRESERVABLE_MIME_TYPES = ("image/png", "image/jpeg", "image/webp")
|
|
@@ -48,6 +47,8 @@ def _resize_to_fit(
|
|
|
48
47
|
mime_type: "str",
|
|
49
48
|
image_bytes: "bytes",
|
|
50
49
|
) -> "typing.Tuple[str, bytes]":
|
|
50
|
+
from PIL import Image
|
|
51
|
+
|
|
51
52
|
with Image.open(io.BytesIO(image_bytes)) as image:
|
|
52
53
|
width, height = image.size
|
|
53
54
|
if width <= MAX_DIMENSION and height <= MAX_DIMENSION:
|
|
@@ -6,9 +6,9 @@ import typing
|
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from ..protocol import JSONDict
|
|
10
10
|
|
|
11
|
-
DEFAULT_MODELS_PATH = Path(__file__).resolve().
|
|
11
|
+
DEFAULT_MODELS_PATH = Path(__file__).resolve().parents[1] / "prompts" / "models.json"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@lru_cache(maxsize=1)
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-codex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A minimal Python extraction of Codex's main agent loop
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.6.2
|
|
7
7
|
Requires-Dist: dataclasses>=0.8; python_version < '3.7'
|
|
8
|
-
Requires-Dist: fastapi<0.84,>=0.83.0; python_version < '3.7'
|
|
9
|
-
Requires-Dist: fastapi>=0.83.0; python_version >= '3.7'
|
|
10
8
|
Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
|
|
11
9
|
Requires-Dist: loguru>=0.7.3
|
|
12
|
-
Requires-Dist: pillow>=8.4.0; python_version < '3.7'
|
|
13
|
-
Requires-Dist: pillow>=9.0.0; python_version >= '3.7'
|
|
14
10
|
Requires-Dist: prompt-toolkit>=3.0.36
|
|
15
|
-
Requires-Dist: pycryptodomex>=3.20
|
|
16
11
|
Requires-Dist: pyyaml>=6.0
|
|
17
12
|
Requires-Dist: requests>=2.27.1
|
|
13
|
+
Requires-Dist: starlette<0.20,>=0.19.1; python_version < '3.7'
|
|
14
|
+
Requires-Dist: starlette>=0.19.1; python_version >= '3.7'
|
|
18
15
|
Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
|
|
19
16
|
Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
|
|
20
17
|
Requires-Dist: uvicorn<0.17,>=0.16.0; python_version < '3.7'
|
|
@@ -25,6 +22,8 @@ Description-Content-Type: text/markdown
|
|
|
25
22
|
|
|
26
23
|
English README. Chinese version: `README_ZH.md`
|
|
27
24
|
|
|
25
|
+
0.4.0 core/workspace package split and dependency cleanup: [release notes](docs/RELEASE_0.4.0.md).
|
|
26
|
+
|
|
28
27
|
0.3.3 workspace viewers and reasoning stream fix: [release notes](docs/RELEASE_0.3.3.md).
|
|
29
28
|
|
|
30
29
|
0.3.2 startup fixes: [release notes](docs/RELEASE_0.3.2.md).
|
|
@@ -33,15 +32,33 @@ English README. Chinese version: `README_ZH.md`
|
|
|
33
32
|
|
|
34
33
|
PyPI distributions:
|
|
35
34
|
|
|
36
|
-
-
|
|
37
|
-
- Workspace
|
|
35
|
+
- Core package: `python-codex`
|
|
36
|
+
- Workspace and integrations: `pycodex-ws`
|
|
38
37
|
|
|
39
38
|
The import path remains `pycodex`; the CLI commands are `pycodex` and
|
|
40
39
|
`pycodex-ws`.
|
|
41
40
|
|
|
42
|
-
`
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
`python-codex` contains the Agent, runtime, CLI, standard tools, Responses
|
|
42
|
+
compatibility server and portable storage support. `pycodex-ws` depends on the
|
|
43
|
+
exact matching core version and contains the browser workspace, viewer assets,
|
|
44
|
+
Feishu and IPython integrations, including their dependencies. It owns the
|
|
45
|
+
`workspace_server` and `pycodex_ws` modules and the `pycodex-ws` command.
|
|
46
|
+
|
|
47
|
+
Core does not require Pillow. Without it, `view_image` sends the original image
|
|
48
|
+
bytes with `detail="original"`, including when `high` is requested. With Pillow
|
|
49
|
+
installed, the default path retains resizing to a maximum dimension of 2048.
|
|
50
|
+
`pycodex-ws` installs Pillow for image preview validation.
|
|
51
|
+
|
|
52
|
+
Portable encryption uses a bundled pure Python AES-GCM subset of pyaes and
|
|
53
|
+
tlslite-ng, under MIT and BSD licenses. Core does not require a crypto package
|
|
54
|
+
or compiler and ships a `py3-none-any` wheel. Existing `PCX1` bundles and call
|
|
55
|
+
tokens remain compatible; large bundles take longer to encrypt and decrypt.
|
|
56
|
+
See the [source notes](pycodex/utils/_aes_gcm/README.md).
|
|
57
|
+
|
|
58
|
+
The Responses compatibility server and workspace HTTP server use Starlette
|
|
59
|
+
and Uvicorn directly. Neither package requires FastAPI, Pydantic or
|
|
60
|
+
`pydantic-core`. Python 3.6 uses Starlette 0.19.x; newer interpreters resolve
|
|
61
|
+
a compatible Starlette release through its Python version metadata.
|
|
45
62
|
|
|
46
63
|
This repository extracts the core Codex agent loop from upstream Codex
|
|
47
64
|
(`https://github.com/openai/codex`) into a deliberately small Python version,
|
|
@@ -61,16 +78,17 @@ Relevant Rust reference points:
|
|
|
61
78
|
|
|
62
79
|
## Quick Start
|
|
63
80
|
|
|
64
|
-
Install the
|
|
81
|
+
Install the core CLI, or install the workspace package to include all integrations:
|
|
65
82
|
|
|
66
83
|
```bash
|
|
67
84
|
python3 -m pip install python-codex
|
|
68
85
|
python3 -m pip install pycodex-ws
|
|
69
86
|
```
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
The core supports Python 3.6.2 or newer. The workspace package requires Python
|
|
89
|
+
3.7 or newer because of its Feishu SDK dependency. Commands are installed into
|
|
90
|
+
that interpreter's scripts directory. On a machine with multiple Python
|
|
91
|
+
installations, select the same interpreter explicitly for startup:
|
|
74
92
|
|
|
75
93
|
```bash
|
|
76
94
|
python3 -m pycodex
|
|
@@ -81,10 +99,14 @@ If `pycodex` is missing from `PATH` or resolves to an older installation, use th
|
|
|
81
99
|
module entry points. `python3 -m pip --version` shows which Python environment
|
|
82
100
|
received the installation.
|
|
83
101
|
|
|
102
|
+
For an existing Web installation, use `python3 -m pip install --upgrade pycodex-ws`
|
|
103
|
+
to upgrade the workspace and its matching core together. Installing only
|
|
104
|
+
`python-codex` provides the CLI and core API.
|
|
105
|
+
|
|
84
106
|
For development from this repository, install dependencies first:
|
|
85
107
|
|
|
86
108
|
```bash
|
|
87
|
-
uv sync
|
|
109
|
+
uv sync --dev
|
|
88
110
|
```
|
|
89
111
|
|
|
90
112
|
Try the real entry points:
|
|
@@ -94,8 +116,22 @@ uv run pycodex "Reply with exactly OK."
|
|
|
94
116
|
uv run pycodex
|
|
95
117
|
```
|
|
96
118
|
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
Tests are split by package. `uv run --dev pytest` runs both suites;
|
|
120
|
+
`uv run --dev pytest packages/pycodex-ws/tests` runs workspace and integration
|
|
121
|
+
tests. To check core without installing workspace or its SDKs, use a separate
|
|
122
|
+
environment:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv sync --group test
|
|
126
|
+
UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv run --group test pytest tests
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [test environments and coverage](tests/TESTS.md). Core tests support Python
|
|
130
|
+
3.6.2+; workspace tests require Python 3.7+.
|
|
131
|
+
|
|
132
|
+
A VS Code frontend is available in [`packages/pycodex-ws/extensions/vscode`](packages/pycodex-ws/extensions/vscode/README.md).
|
|
133
|
+
It belongs to the workspace frontend and is packaged separately as a VSIX.
|
|
134
|
+
It reuses an existing `pycodex-ws>=0.4.0` installation. On first use, missing
|
|
99
135
|
or older backends are installed/upgraded with pip in the selected Python
|
|
100
136
|
environment on the workspace host; compatible versions need no network check.
|
|
101
137
|
It starts a dedicated `pycodex-ws` workspace on the project's host and opens a
|
|
@@ -143,19 +179,35 @@ nailing the core tool-augmented reasoning loop first.
|
|
|
143
179
|
|
|
144
180
|
## Layout
|
|
145
181
|
|
|
182
|
+
The top level of `pycodex/` contains the core components and package entry points.
|
|
183
|
+
Supporting services live in `tools/`, `portable/` and `utils/`.
|
|
184
|
+
|
|
185
|
+
- `pycodex/agent.py`: inner turn loop
|
|
186
|
+
- `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
|
|
187
|
+
- `pycodex/model.py`: model client protocol and Responses API adapter
|
|
188
|
+
- `pycodex/context.py`: configuration, prompt assembly and instruction loading
|
|
146
189
|
- `pycodex/protocol.py`: minimal conversation item / prompt protocol
|
|
147
190
|
- `pycodex/events.py`: typed events, plain-text views and stateful presentation
|
|
148
|
-
- `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
|
|
149
|
-
- `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
|
|
150
|
-
- `pycodex/model.py`: model client protocol and Responses API adapter
|
|
151
|
-
- `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
|
|
152
191
|
- `pycodex/bootstrap.py`: frontend-independent model, tools, Agent and session assembly
|
|
192
|
+
- `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
|
|
153
193
|
- `pycodex/tools/base_tool.py`: `BaseTool`, `ToolRegistry`, `ToolContext`
|
|
154
|
-
- `pycodex/tools
|
|
155
|
-
- `pycodex/
|
|
156
|
-
- `pycodex/
|
|
157
|
-
- `
|
|
158
|
-
- `
|
|
194
|
+
- `pycodex/tools/runtime_services.py`: plan, sub-agent, user-input and permissions services
|
|
195
|
+
- `pycodex/tools/`: concrete tools and their execution managers
|
|
196
|
+
- `pycodex/portable/`: Codex home storage client and server, both included in core
|
|
197
|
+
- `pycodex/utils/compat.py`: supported-Python compatibility helpers
|
|
198
|
+
- `pycodex/utils/doctor.py`: configuration and connectivity diagnostics
|
|
199
|
+
- `pycodex/utils/model_metadata.py`: vendored model metadata lookup
|
|
200
|
+
- `pycodex/utils/debug.py`: debug paths and the `pycodex.debug()` implementation
|
|
201
|
+
- `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
|
|
202
|
+
- `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
|
|
203
|
+
- `pycodex/prompts/`: vendored prompts and model metadata
|
|
204
|
+
- `packages/pycodex-ws/workspace_server/`: Web board, API and file/diff viewers
|
|
205
|
+
- `packages/pycodex-ws/pycodex_ws/`: Feishu, IPython and workspace assembly
|
|
206
|
+
- `packages/pycodex-ws/extensions/vscode/`: workspace's VS Code frontend, built separately as a VSIX
|
|
207
|
+
- `tests/`: core tests
|
|
208
|
+
- `packages/pycodex-ws/tests/`: workspace and cross-frontend integration tests
|
|
209
|
+
|
|
210
|
+
See [package layout and import paths](docs/RUNTIME.md#package-layout).
|
|
159
211
|
|
|
160
212
|
## Current Alignment Status
|
|
161
213
|
|
|
@@ -266,7 +318,7 @@ Current behavior:
|
|
|
266
318
|
- with an argv prompt or piped stdin, run a single turn
|
|
267
319
|
- `pycodex-ws` starts the standalone browser workspace manager and serves each
|
|
268
320
|
workspace with a board pane and a pycodex session pane
|
|
269
|
-
- the left pane is the board, containing
|
|
321
|
+
- the left pane is the board, containing an optional live HTML Canvas tab and closable read-only file/diff tabs that
|
|
270
322
|
scroll together in one toolbar; a mouse wheel scrolls overflowing tab strips
|
|
271
323
|
horizontally.
|
|
272
324
|
Click Markdown file links such as `[source](src/app.py:42)` or
|
|
@@ -285,6 +337,14 @@ Current behavior:
|
|
|
285
337
|
hover titles retain the complete paths and revisions. Text,
|
|
286
338
|
Markdown, HTML, common raster images and text diffs are supported. Monaco
|
|
287
339
|
provides syntax highlighting, find, folding and character-level diffs.
|
|
340
|
+
Right-click code and choose **Insert into chat**, or select text and use the
|
|
341
|
+
toolbar button, to insert context at the current chat draft's cursor.
|
|
342
|
+
Context appears as a separate block among your words. Click it to edit or
|
|
343
|
+
remove it; Backspace and Delete also remove a block as one element.
|
|
344
|
+
Context identifies the full view label, including both revisions of a diff.
|
|
345
|
+
Selections include only the selected text and range; otherwise it records the
|
|
346
|
+
cursor location. Diff context also identifies the selected side.
|
|
347
|
+
The draft stays editable until you send it.
|
|
288
348
|
Its code and styles load only when a code or diff view opens, so Canvas,
|
|
289
349
|
Markdown and HTML previews do not load the editor. Viewer assets use gzip
|
|
290
350
|
when supported and revalidate cached content with ETags.
|
|
@@ -353,28 +413,37 @@ Current behavior:
|
|
|
353
413
|
non-persisted follow-up session and submits the file contents as the next
|
|
354
414
|
user instruction; this is intended for side-effect follow-ups such as
|
|
355
415
|
Feishu notifications
|
|
356
|
-
- `/link <feishu-email|open_id|chat_id>`
|
|
357
|
-
|
|
416
|
+
- `/link <feishu-email|open_id|chat_id>` in a workspace session attaches it
|
|
417
|
+
to a Feishu card; multiple sessions in the same workspace process share
|
|
358
418
|
one Feishu long-connection listener and route card actions by message id
|
|
359
419
|
- Feishu user OAuth stores its refresh token only in
|
|
360
420
|
`~/.codex/.feishu_refresh_token` (mode `0600`). Run
|
|
361
|
-
`
|
|
362
|
-
|
|
421
|
+
`python3 -m pycodex_ws.feishu_oauth` after installing `pycodex-ws` to authorize.
|
|
422
|
+
From this repository, use `env -u VIRTUAL_ENV uv run --dev python -m pycodex_ws.feishu_oauth`.
|
|
423
|
+
App credentials can stay in `~/.codex/.env`. Each refresh reads
|
|
363
424
|
the latest token from the dedicated file and saves its replacement there.
|
|
364
425
|
`FEISHU_REFRESH_TOKEN` in `.env` or the environment is no longer used and
|
|
365
426
|
can be removed. Refresh is triggered by API calls, with no idle keepalive.
|
|
366
427
|
- `pycodex-ws --workspace-config workspaces.json` serves workspace boards from
|
|
367
428
|
one process. The JSON file can be either a list or
|
|
368
|
-
`{"workspaces": [...]}`; each entry uses `
|
|
369
|
-
display/URL `id
|
|
370
|
-
`
|
|
371
|
-
|
|
429
|
+
`{"workspaces": [...]}`; each entry uses `state_path` and `work_dir`, with optional
|
|
430
|
+
display/URL `id` and `enable_board` (default `true`), and is exposed at `/w/<id>/`.
|
|
431
|
+
`state_path` is a filename prefix: `/data/demo` uses
|
|
432
|
+
`/data/demo.pycodex-ws.json` for the session index,
|
|
433
|
+
`/data/demo.pycodex-views.json` for file views and `/data/demo.html` for Canvas.
|
|
434
|
+
Suffixes are appended, so prefixes containing dots remain distinct.
|
|
435
|
+
The resolved `state_path` is the workspace identity; duplicates are rejected
|
|
436
|
+
regardless of name or Canvas setting. Relative `state_path` and `work_dir` paths
|
|
372
437
|
resolve from the config file directory. The root path `/` shows a workspaces
|
|
373
|
-
management page with
|
|
374
|
-
`
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
438
|
+
management page with create/delete controls; omitted names become `workspace-1`,
|
|
439
|
+
`workspace-2`, etc. Omitted `state_path` when creating a workspace generates a
|
|
440
|
+
writable `/tmp/pcws-*` prefix. `enable_board: false` hides Canvas and disables its
|
|
441
|
+
routes, polling and context injection; sessions and file views still persist.
|
|
442
|
+
Existing `board: "demo.html"` and `board: false` configurations load with their
|
|
443
|
+
original state paths and are saved in the new format. See the
|
|
444
|
+
[workspace configuration example](packages/pycodex-ws/README.md).
|
|
445
|
+
Add/delete actions and later session-state saves refresh the JSON file.
|
|
446
|
+
Board HTML can reference local
|
|
378
447
|
images beside the board (including nested paths) with relative URLs; only
|
|
379
448
|
`image/*` files contained by the board directory are served.
|
|
380
449
|
Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
|
|
@@ -466,6 +535,13 @@ pycodex doctor --json
|
|
|
466
535
|
`Portable Mode` is the quickest way to bring your usual `pycodex` setup into a
|
|
467
536
|
fresh machine, container, or debug image.
|
|
468
537
|
|
|
538
|
+
The storage client and server are both included in `python-codex`. To start a
|
|
539
|
+
local storage service, run this in a separate terminal:
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
python -m pycodex.portable.server --root .tmp/pycodex_storage --host 127.0.0.1 --port 5577
|
|
543
|
+
```
|
|
544
|
+
|
|
469
545
|
Use it like this:
|
|
470
546
|
|
|
471
547
|
```bash
|
|
@@ -659,7 +735,9 @@ CLI, Web and Feishu all use `AgentRuntime.submit_input`: not just `/model`
|
|
|
659
735
|
and `/resume`, but every session command, steer/queue admission, interactive
|
|
660
736
|
questions and permissions, state notifications and shutdown. Frontends only
|
|
661
737
|
adapt input and render events. Web no longer imports or runs the CLI shell.
|
|
662
|
-
IPython is the deliberate exception: `ipython_agent()`
|
|
738
|
+
IPython is the deliberate exception: `pycodex_ws.ipython.ipython_agent()` returns
|
|
739
|
+
a bare Agent. Workspace assembly uses `pycodex_ws.build_runtime()` to add
|
|
740
|
+
`/link` and `/unlink`; `pycodex.bootstrap.build_runtime()` provides core commands.
|
|
663
741
|
|
|
664
742
|
```python
|
|
665
743
|
from pycodex.bootstrap import build_agent, build_model, build_runtime
|