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
|
@@ -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/async_bridge.py
CHANGED
|
@@ -5,10 +5,9 @@ import inspect
|
|
|
5
5
|
def run_async(awaitable):
|
|
6
6
|
"""Run an awaitable from synchronous code and return its real result.
|
|
7
7
|
|
|
8
|
-
This helper
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
failure instead of swallowing exceptions.
|
|
8
|
+
This helper lets synchronous callers use async agent APIs without
|
|
9
|
+
managing the event loop themselves. It returns the underlying result
|
|
10
|
+
directly and propagates exceptions.
|
|
12
11
|
"""
|
|
13
12
|
try:
|
|
14
13
|
running_loop = asyncio.get_running_loop()
|
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)
|
pycodex/utils/session_persist.py
CHANGED
|
@@ -3,6 +3,7 @@ import mmap
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
5
|
import typing
|
|
6
|
+
from collections import deque
|
|
6
7
|
from contextlib import closing
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from pathlib import Path
|
|
@@ -323,6 +324,33 @@ def conversation_history_to_turns(
|
|
|
323
324
|
return tuple(turns)
|
|
324
325
|
|
|
325
326
|
|
|
327
|
+
def load_display_history(
|
|
328
|
+
rollout_path: "typing.Union[str, Path]",
|
|
329
|
+
limit: "int" = 80,
|
|
330
|
+
) -> "typing.Tuple[typing.Union[typing.Tuple[str, str], None], ...]":
|
|
331
|
+
"""Read visible turns across checkpoints; None marks a compaction."""
|
|
332
|
+
path = Path(rollout_path).expanduser().resolve()
|
|
333
|
+
turns = deque(maxlen=limit)
|
|
334
|
+
for entry in _iter_rollout_entries(path):
|
|
335
|
+
item_type = entry.get("type")
|
|
336
|
+
payload = entry.get("payload")
|
|
337
|
+
if not isinstance(payload, dict):
|
|
338
|
+
continue
|
|
339
|
+
if item_type == "compacted":
|
|
340
|
+
turns.append(None)
|
|
341
|
+
elif item_type == "event_msg" and payload.get("type") == "user_message":
|
|
342
|
+
turns.append((str(payload.get("message", "")), ""))
|
|
343
|
+
elif (
|
|
344
|
+
item_type == "response_item"
|
|
345
|
+
and payload.get("type") == "message"
|
|
346
|
+
and payload.get("role") == "assistant"
|
|
347
|
+
):
|
|
348
|
+
if not turns or turns[-1] is None:
|
|
349
|
+
turns.append(("", ""))
|
|
350
|
+
turns[-1] = (turns[-1][0], _extract_response_message_text(payload))
|
|
351
|
+
return tuple(turns)
|
|
352
|
+
|
|
353
|
+
|
|
326
354
|
def _drop_unmatched_tool_calls(
|
|
327
355
|
history: "typing.List[ConversationItem]",
|
|
328
356
|
) -> "typing.List[ConversationItem]":
|