aient 1.1.3__py3-none-any.whl → 1.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aient/core/models.py CHANGED
@@ -42,13 +42,6 @@ class ContentItem(BaseModel):
42
42
  text: Optional[str] = None
43
43
  image_url: Optional[ImageUrl] = None
44
44
 
45
- class Message(BaseModel):
46
- role: str
47
- name: Optional[str] = None
48
- arguments: Optional[str] = None
49
- content: Optional[Union[str, List[ContentItem]]] = None
50
- tool_calls: Optional[List[ToolCall]] = None
51
-
52
45
  class Message(BaseModel):
53
46
  role: str
54
47
  name: Optional[str] = None
aient/core/request.py CHANGED
@@ -20,6 +20,8 @@ from .utils import (
20
20
  get_image_message,
21
21
  )
22
22
 
23
+ gemini_max_token_65k_models = ["gemini-2.5-pro", "gemini-2.0-pro", "gemini-2.0-flash-thinking", "gemini-2.5-flash"]
24
+
23
25
  async def get_gemini_payload(request, engine, provider, api_key=None):
24
26
  import re
25
27
 
@@ -102,8 +104,7 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
102
104
  content[0]["text"] = re.sub(r"_+", "_", content[0]["text"])
103
105
  systemInstruction = {"parts": content}
104
106
 
105
- off_models = ["gemini-2.0-flash", "gemini-2.5-flash", "gemini-1.5", "gemini-2.5-pro"]
106
- if any(off_model in original_model for off_model in off_models):
107
+ if any(off_model in original_model for off_model in gemini_max_token_65k_models):
107
108
  safety_settings = "OFF"
108
109
  else:
109
110
  safety_settings = "BLOCK_NONE"
@@ -196,16 +197,17 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
196
197
  elif field == "temperature":
197
198
  generation_config["temperature"] = value
198
199
  elif field == "max_tokens":
200
+ if value > 65536:
201
+ value = 65536
199
202
  generation_config["maxOutputTokens"] = value
200
203
  elif field == "top_p":
201
204
  generation_config["topP"] = value
202
205
  else:
203
206
  payload[field] = value
204
207
 
205
- max_token_65k_models = ["gemini-2.5-pro", "gemini-2.0-pro", "gemini-2.0-flash-thinking", "gemini-2.5-flash"]
206
208
  payload["generationConfig"] = generation_config
207
209
  if "maxOutputTokens" not in generation_config:
208
- if any(pro_model in original_model for pro_model in max_token_65k_models):
210
+ if any(pro_model in original_model for pro_model in gemini_max_token_65k_models):
209
211
  payload["generationConfig"]["maxOutputTokens"] = 65536
210
212
  else:
211
213
  payload["generationConfig"]["maxOutputTokens"] = 8192
@@ -226,10 +228,7 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
226
228
 
227
229
  # 检测search标签
228
230
  if request.model.endswith("-search"):
229
- if "tools" not in payload:
230
- payload["tools"] = [{"googleSearch": {}}]
231
- else:
232
- payload["tools"].append({"googleSearch": {}})
231
+ payload["tools"] = [{"googleSearch": {}}]
233
232
 
234
233
  return url, headers, payload
235
234
 
@@ -390,27 +389,35 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
390
389
  elif msg.role == "system":
391
390
  systemInstruction = {"parts": content}
392
391
 
392
+ if any(off_model in original_model for off_model in gemini_max_token_65k_models):
393
+ safety_settings = "OFF"
394
+ else:
395
+ safety_settings = "BLOCK_NONE"
393
396
 
394
397
  payload = {
395
398
  "contents": messages,
396
- # "safetySettings": [
397
- # {
398
- # "category": "HARM_CATEGORY_HARASSMENT",
399
- # "threshold": "BLOCK_NONE"
400
- # },
401
- # {
402
- # "category": "HARM_CATEGORY_HATE_SPEECH",
403
- # "threshold": "BLOCK_NONE"
404
- # },
405
- # {
406
- # "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
407
- # "threshold": "BLOCK_NONE"
408
- # },
409
- # {
410
- # "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
411
- # "threshold": "BLOCK_NONE"
412
- # }
413
- # ]
399
+ "safetySettings": [
400
+ {
401
+ "category": "HARM_CATEGORY_HARASSMENT",
402
+ "threshold": safety_settings
403
+ },
404
+ {
405
+ "category": "HARM_CATEGORY_HATE_SPEECH",
406
+ "threshold": safety_settings
407
+ },
408
+ {
409
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
410
+ "threshold": safety_settings
411
+ },
412
+ {
413
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
414
+ "threshold": safety_settings
415
+ },
416
+ {
417
+ "category": "HARM_CATEGORY_CIVIC_INTEGRITY",
418
+ "threshold": "BLOCK_NONE"
419
+ },
420
+ ]
414
421
  }
415
422
  if systemInstruction:
416
423
  payload["system_instruction"] = systemInstruction
@@ -447,15 +454,19 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
447
454
  elif field == "temperature":
448
455
  generation_config["temperature"] = value
449
456
  elif field == "max_tokens":
457
+ if value > 65535:
458
+ value = 65535
450
459
  generation_config["max_output_tokens"] = value
451
460
  elif field == "top_p":
452
461
  generation_config["top_p"] = value
453
462
  else:
454
463
  payload[field] = value
455
464
 
456
- if generation_config:
457
- payload["generationConfig"] = generation_config
458
- if "max_output_tokens" not in generation_config:
465
+ payload["generationConfig"] = generation_config
466
+ if "max_output_tokens" not in generation_config:
467
+ if any(pro_model in original_model for pro_model in gemini_max_token_65k_models):
468
+ payload["generationConfig"]["max_output_tokens"] = 65535
469
+ else:
459
470
  payload["generationConfig"]["max_output_tokens"] = 8192
460
471
 
461
472
  if request.model.endswith("-search"):
aient/core/response.py CHANGED
@@ -67,9 +67,9 @@ async def fetch_gemini_response_stream(client, url, headers, payload, model):
67
67
  if (line and '"parts": [' in line or parts_json != "") and is_finish == False:
68
68
  parts_json += line
69
69
  if parts_json != "" and line and '],' == line.strip():
70
- parts_json = "{" + parts_json.strip().replace(', "groundingMetadata": {}', "").rstrip(",} ]}").lstrip("{") + "}]}"
70
+ tmp_parts_json = "{" + parts_json.split("} ] },")[0].strip().rstrip("}], ").replace("\n", "\\n").lstrip("{") + "}]}"
71
71
  try:
72
- json_data = json.loads(parts_json)
72
+ json_data = json.loads(tmp_parts_json)
73
73
 
74
74
  content = safe_get(json_data, "parts", 0, "text", default="")
75
75
 
aient/core/utils.py CHANGED
@@ -1,12 +1,10 @@
1
1
  import re
2
2
  import io
3
- import os
4
3
  import ast
5
4
  import json
6
5
  import httpx
7
6
  import base64
8
7
  import asyncio
9
- import urllib.parse
10
8
  from time import time
11
9
  from PIL import Image
12
10
  from fastapi import HTTPException
@@ -17,8 +15,11 @@ from .log_config import logger
17
15
 
18
16
  def get_model_dict(provider):
19
17
  model_dict = {}
18
+ if "model" not in provider:
19
+ logger.error(f"Error: model is not set in provider: {provider}")
20
+ return model_dict
20
21
  for model in provider['model']:
21
- if type(model) == str:
22
+ if isinstance(model, str):
22
23
  model_dict[model] = model
23
24
  if isinstance(model, dict):
24
25
  model_dict.update({str(new): old for old, new in model.items()})
@@ -68,7 +69,9 @@ def get_engine(provider, endpoint=None, original_model=""):
68
69
  parsed_url.path.endswith("/v1") or \
69
70
  (parsed_url.netloc == 'generativelanguage.googleapis.com' and "openai/chat/completions" not in parsed_url.path):
70
71
  engine = "gemini"
71
- elif parsed_url.netloc.rstrip('/').endswith('aiplatform.googleapis.com') or (parsed_url.netloc.rstrip('/').endswith('gateway.ai.cloudflare.com') and "google-vertex-ai" in parsed_url.path):
72
+ elif parsed_url.netloc.rstrip('/').endswith('aiplatform.googleapis.com') or \
73
+ (parsed_url.netloc.rstrip('/').endswith('gateway.ai.cloudflare.com') and "google-vertex-ai" in parsed_url.path) or \
74
+ "aiplatform.googleapis.com" in parsed_url.path:
72
75
  engine = "vertex"
73
76
  elif parsed_url.netloc.rstrip('/').endswith('azure.com'):
74
77
  engine = "azure"
@@ -3,6 +3,14 @@ from .registry import register_tool
3
3
 
4
4
  import re
5
5
  import html
6
+ import os
7
+ import select
8
+
9
+ # 检查是否在 Unix-like 系统上 (pty 模块主要用于 Unix)
10
+ IS_UNIX = hasattr(os, 'fork')
11
+
12
+ if IS_UNIX:
13
+ import pty
6
14
 
7
15
  def unescape_html(input_string: str) -> str:
8
16
  """
@@ -28,18 +36,18 @@ def get_python_executable(command: str) -> str:
28
36
  executable = cmd_parts[0]
29
37
  args_str = cmd_parts[1] if len(cmd_parts) > 1 else ""
30
38
 
31
- # 检查是否是 python 可执行文件 (如 python, python3, pythonX.Y)
32
39
  is_python_exe = False
33
40
  if executable == "python" or re.match(r"^python[23]?(\.\d+)?$", executable):
34
41
  is_python_exe = True
35
42
 
36
43
  if is_python_exe:
37
- # 检查参数中是否已经有 -u 选项
38
44
  args_list = args_str.split()
39
45
  has_u_option = "-u" in args_list
40
46
  if not has_u_option:
41
47
  if args_str:
42
48
  command = f"{executable} -u {args_str}"
49
+ else:
50
+ command = f"{executable} -u" # 如果没有其他参数,也添加 -u
43
51
  return command
44
52
 
45
53
  # 执行命令
@@ -56,60 +64,95 @@ def excute_command(command):
56
64
  命令执行的最终状态和收集到的输出/错误信息
57
65
  """
58
66
  try:
59
- command = unescape_html(command) # 保留 HTML 解码
60
-
67
+ command = unescape_html(command)
61
68
  command = get_python_executable(command)
62
69
 
63
-
64
- # 使用 Popen 以便实时处理输出
65
- # bufsize=1 表示行缓冲, universal_newlines=True 与 text=True 效果类似,用于文本模式
66
- process = subprocess.Popen(
67
- command,
68
- shell=True,
69
- stdout=subprocess.PIPE,
70
- stderr=subprocess.PIPE,
71
- text=True,
72
- bufsize=1,
73
- universal_newlines=True
74
- )
75
-
76
- stdout_lines = []
77
-
78
- # 实时打印 stdout
79
- # print(f"--- 开始执行命令: {command} ---")
80
- if process.stdout:
81
- for line in iter(process.stdout.readline, ''):
82
- # 对 pip install 命令的输出进行过滤,去除进度条相关的行
83
- if "pip install" in command and '━━' in line:
84
- continue
85
- print(line, end='', flush=True) # 实时打印到控制台,并刷新缓冲区
86
- stdout_lines.append(line) # 收集行以供后续返回
87
- process.stdout.close()
88
- # print(f"\n--- 命令实时输出结束 ---")
89
-
90
- # 等待命令完成
91
- process.wait()
92
-
93
- # 获取 stderr (命令完成后一次性读取)
70
+ output_lines = []
71
+
72
+ if IS_UNIX:
73
+ # Unix-like 系统上使用 pty 以支持 tqdm 等库的 ANSI 转义序列
74
+ master_fd, slave_fd = pty.openpty()
75
+
76
+ process = subprocess.Popen(
77
+ command,
78
+ shell=True,
79
+ stdin=subprocess.PIPE, # 提供一个 stdin,即使未使用
80
+ stdout=slave_fd,
81
+ stderr=slave_fd, # 将 stdout 和 stderr 合并到 pty
82
+ close_fds=True, # 在子进程中关闭除 stdin/stdout/stderr 之外的文件描述符
83
+ # bufsize=1, # 移除此行:pty 通常处理字节,且 bufsize=1 会导致 stdin 的二进制模式警告
84
+ # universal_newlines=True # pty 通常处理字节,解码在读取端进行
85
+ )
86
+ os.close(slave_fd) # 在父进程中关闭 slave
87
+
88
+ # print(f"--- 开始执行命令 (PTY): {command} ---")
89
+ while True:
90
+ try:
91
+ # 使用 select 进行非阻塞读取
92
+ r, _, _ = select.select([master_fd], [], [], 0.1) # 0.1 秒超时
93
+ if r:
94
+ data_bytes = os.read(master_fd, 1024)
95
+ if not data_bytes: # EOF
96
+ break
97
+ # 尝试解码,如果失败则使用 repr 显示原始字节
98
+ try:
99
+ data_str = data_bytes.decode(errors='replace')
100
+ except UnicodeDecodeError:
101
+ data_str = repr(data_bytes) + " (decode error)\n"
102
+
103
+ print(data_str, end='', flush=True)
104
+ output_lines.append(data_str)
105
+ # 检查进程是否已结束,避免在进程已退出后 select 仍然阻塞
106
+ if process.poll() is not None and not r:
107
+ break
108
+ except OSError: # 当 PTY 关闭时可能会发生
109
+ break
110
+ # print(f"\n--- 命令实时输出结束 (PTY) ---")
111
+ os.close(master_fd)
112
+ else:
113
+ # 在非 Unix 系统上,回退到原始的 subprocess.PIPE 行为
114
+ # tqdm 进度条可能不会像在终端中那样动态更新
115
+ process = subprocess.Popen(
116
+ command,
117
+ shell=True,
118
+ stdout=subprocess.PIPE,
119
+ stderr=subprocess.PIPE,
120
+ text=True,
121
+ bufsize=1,
122
+ universal_newlines=True
123
+ )
124
+ # print(f"--- 开始执行命令 (PIPE): {command} ---")
125
+ if process.stdout:
126
+ for line in iter(process.stdout.readline, ''):
127
+ print(line, end='', flush=True)
128
+ if "pip install" in command and '━━' in line:
129
+ continue
130
+ output_lines.append(line)
131
+ process.stdout.close()
132
+ # print(f"\n--- 命令实时输出结束 (PIPE) ---")
133
+
134
+ process.wait() # 等待命令完成
135
+
136
+ # 在非 PTY 模式下,stderr 需要单独读取
94
137
  stderr_output = ""
95
- if process.stderr:
138
+ if not IS_UNIX and process.stderr:
96
139
  stderr_output = process.stderr.read()
97
140
  process.stderr.close()
98
141
 
99
- # 组合最终的 stdout 日志 (已经过 pip install 过滤)
100
- final_stdout_log = "".join(stdout_lines)
142
+ final_output_log = "".join(output_lines)
101
143
 
102
144
  if process.returncode == 0:
103
- return f"执行命令成功:\n{final_stdout_log}"
145
+ return f"执行命令成功:\n{final_output_log}"
104
146
  else:
105
- return f"执行命令失败 (退出码 {process.returncode}):\n错误: {stderr_output}\n输出: {final_stdout_log}"
147
+ # 如果是 PTY 模式,stderr 已经包含在 final_output_log 中
148
+ if IS_UNIX:
149
+ return f"执行命令失败 (退出码 {process.returncode}):\n输出/错误:\n{final_output_log}"
150
+ else:
151
+ return f"执行命令失败 (退出码 {process.returncode}):\n错误: {stderr_output}\n输出: {final_output_log}"
106
152
 
107
153
  except FileNotFoundError:
108
- # 当 shell=True 时,命令未找到通常由 shell 处理,并返回非零退出码。
109
- # 此处捕获 FileNotFoundError 主要用于 Popen 自身无法启动命令的场景 (例如 shell 本身未找到)。
110
154
  return f"执行命令失败: 命令或程序未找到 ({command})"
111
155
  except Exception as e:
112
- # 其他未知异常
113
156
  return f"执行命令时发生异常: {e}"
114
157
 
115
158
  if __name__ == "__main__":
@@ -125,12 +168,25 @@ if __name__ == "__main__":
125
168
  # time.sleep(1)
126
169
  # print('\\n-------TQDM 任务完成.')
127
170
  # """
171
+
172
+ # tqdm_script = """
173
+ # import time
174
+ # from tqdm import tqdm
175
+
176
+ # for i in tqdm(range(10)):
177
+ # time.sleep(1)
178
+ # """
128
179
  # processed_tqdm_script = tqdm_script.replace('"', '\\"')
129
- # tqdm_command = f"python -u -u -c \"{processed_tqdm_script}\""
180
+ # tqdm_command = f"python -c \"{processed_tqdm_script}\""
130
181
  # # print(f"执行: {tqdm_command}")
131
182
  # print(excute_command(tqdm_command))
132
183
 
133
184
 
185
+ tqdm_command = f"pip install requests"
186
+ # print(f"执行: {tqdm_command}")
187
+ print(excute_command(tqdm_command))
188
+
189
+
134
190
  # long_running_command_unix = "echo '开始长时间任务...' && for i in 1 2 3; do echo \"正在处理步骤 $i/3...\"; sleep 1; done && echo '长时间任务完成!'"
135
191
  # print(f"执行: {long_running_command_unix}")
136
192
  # print(excute_command(long_running_command_unix))
@@ -148,5 +204,5 @@ if __name__ == "__main__":
148
204
  # print(f"执行: {python_long_task_command}")
149
205
  # print(excute_command(python_long_task_command))
150
206
 
151
- print(get_python_executable("python -c 'print(123)'"))
207
+ # print(get_python_executable("python -c 'print(123)'"))
152
208
  # python -m beswarm.aient.src.aient.plugins.excute_command
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.3
3
+ Version: 1.1.4
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -3,10 +3,10 @@ aient/core/.git,sha256=lrAcW1SxzRBUcUiuKL5tS9ykDmmTXxyLP3YYU-Y-Q-I,45
3
3
  aient/core/.gitignore,sha256=5JRRlYYsqt_yt6iFvvzhbqh2FTUQMqwo6WwIuFzlGR8,13
4
4
  aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
5
5
  aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
6
- aient/core/models.py,sha256=_1wYZg_n9kb2A3C8xCboyqleH2iHc9scwOvtx9DPeok,7582
7
- aient/core/request.py,sha256=8ghtI3gU7QMiw1voINWZ-PMsHu4IYjV6ncO6a9Qe9sI,61551
8
- aient/core/response.py,sha256=rJnt4tOyJXF2pT9_4VdU7aG3Ac-k2MiCAp9nANVyFqI,31505
9
- aient/core/utils.py,sha256=CAFqWzICaKVysH9GLHBcp-VeOShisLjWGhEsh6-beWo,26365
6
+ aient/core/models.py,sha256=kF-HLi1I2k_G5r153ZHuiGH8_NmpTlFMfK0_myB28YQ,7366
7
+ aient/core/request.py,sha256=Sa2muc4buCkSNPQITOvXwQt0CGA_A39TpIoeVqdqwFQ,61929
8
+ aient/core/response.py,sha256=BNHLazjfQT8mVg7LnPLzlX429aQM3S03pumPbOpczCI,31518
9
+ aient/core/utils.py,sha256=n3dyaApN4rrSduI8cjZbeD0mv8_O5LPTTbwRkj1_v4w,26540
10
10
  aient/core/test/test_base_api.py,sha256=pWnycRJbuPSXKKU9AQjWrMAX1wiLC_014Qc9hh5C2Pw,524
11
11
  aient/core/test/test_geminimask.py,sha256=HFX8jDbNg_FjjgPNxfYaR-0-roUrOO-ND-FVsuxSoiw,13254
12
12
  aient/core/test/test_image.py,sha256=_T4peNGdXKBHHxyQNx12u-NTyFE8TlYI6NvvagsG2LE,319
@@ -23,7 +23,7 @@ aient/models/vertex.py,sha256=qVD5l1Q538xXUPulxG4nmDjXE1VoV4yuAkTCpIeJVw0,16795
23
23
  aient/plugins/__init__.py,sha256=p3KO6Aa3Lupos4i2SjzLQw1hzQTigOAfEHngsldrsyk,986
24
24
  aient/plugins/arXiv.py,sha256=yHjb6PS3GUWazpOYRMKMzghKJlxnZ5TX8z9F6UtUVow,1461
25
25
  aient/plugins/config.py,sha256=Vp6CG9ocdC_FAlCMEGtKj45xamir76DFxdJVvURNtog,6539
26
- aient/plugins/excute_command.py,sha256=u-JOZ21dDcDx1j3O0KVIHAsa6MNuOxHFBdV3iCnTih0,5413
26
+ aient/plugins/excute_command.py,sha256=dwm9DEbXDYbSCbAYOagn0qoGGv8Ak2qAI3mbOVAa9KY,7664
27
27
  aient/plugins/get_time.py,sha256=Ih5XIW5SDAIhrZ9W4Qe5Hs1k4ieKPUc_LAd6ySNyqZk,654
28
28
  aient/plugins/image.py,sha256=ZElCIaZznE06TN9xW3DrSukS7U3A5_cjk1Jge4NzPxw,2072
29
29
  aient/plugins/list_directory.py,sha256=5ubm-mfrj-tanGSDp4M_Tmb6vQb3dx2-XVfQ2yL2G8A,1394
@@ -37,8 +37,8 @@ aient/prompt/agent.py,sha256=6f5ZB66Rb8y0iQScHMRhvXZ1qMM3YsKpCBPCTAAw2rg,24917
37
37
  aient/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  aient/utils/prompt.py,sha256=UcSzKkFE4-h_1b6NofI6xgk3GoleqALRKY8VBaXLjmI,11311
39
39
  aient/utils/scripts.py,sha256=NXmTxcZqHoRv3S13isLsv7kvqktXnA5ej7uMsxCJUe0,26656
40
- aient-1.1.3.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
41
- aient-1.1.3.dist-info/METADATA,sha256=-GSnBw0pcE6HoEPyh1I9WGKrTp7-WnWQfcuSDaq5mxE,4967
42
- aient-1.1.3.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
43
- aient-1.1.3.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
44
- aient-1.1.3.dist-info/RECORD,,
40
+ aient-1.1.4.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
41
+ aient-1.1.4.dist-info/METADATA,sha256=L1TDoq_HxvzaIMWdSVDxdOzj6ex8W-R3CNc1KoSXWO8,4967
42
+ aient-1.1.4.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
43
+ aient-1.1.4.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
44
+ aient-1.1.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5