versionhq 1.1.8__py3-none-any.whl → 1.1.8.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.
- versionhq/__init__.py +1 -1
- versionhq/task/model.py +4 -3
- versionhq/team/model.py +112 -51
- versionhq/team/team_planner.py +58 -20
- versionhq/tool/model.py +0 -1
- {versionhq-1.1.8.dist-info → versionhq-1.1.8.1.dist-info}/METADATA +1 -1
- {versionhq-1.1.8.dist-info → versionhq-1.1.8.1.dist-info}/RECORD +10 -10
- {versionhq-1.1.8.dist-info → versionhq-1.1.8.1.dist-info}/LICENSE +0 -0
- {versionhq-1.1.8.dist-info → versionhq-1.1.8.1.dist-info}/WHEEL +0 -0
- {versionhq-1.1.8.dist-info → versionhq-1.1.8.1.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
versionhq/task/model.py
CHANGED
@@ -399,7 +399,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
399
399
|
"""
|
400
400
|
Run the core execution logic of the task.
|
401
401
|
To speed up the process, when the format is not expected to return, we will skip the conversion process.
|
402
|
-
When the task is allowed to delegate to another agent, we will select a responsible one in order of
|
402
|
+
When the task is allowed to delegate to another agent, we will select a responsible one in order of manager > peer_agent > anoymous agent.
|
403
403
|
"""
|
404
404
|
from versionhq.agent.model import Agent
|
405
405
|
from versionhq.team.model import Team
|
@@ -410,8 +410,9 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
410
410
|
agent_to_delegate = None
|
411
411
|
|
412
412
|
if hasattr(agent, "team") and isinstance(agent.team, Team):
|
413
|
-
if agent.team.
|
414
|
-
|
413
|
+
if agent.team.managers:
|
414
|
+
idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.task is None]
|
415
|
+
agent_to_delegate = idling_manager_agents[0] if idling_manager_agents else agent.team.managers[0]
|
415
416
|
else:
|
416
417
|
peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
|
417
418
|
if len(peers) > 0:
|
versionhq/team/model.py
CHANGED
@@ -113,6 +113,9 @@ class TeamMember(ABC, BaseModel):
|
|
113
113
|
is_manager: bool = Field(default=False)
|
114
114
|
task: Optional[Task] = Field(default=None)
|
115
115
|
|
116
|
+
def update(self, task: Task):
|
117
|
+
self.task = task
|
118
|
+
|
116
119
|
|
117
120
|
class Team(BaseModel):
|
118
121
|
"""
|
@@ -165,34 +168,47 @@ class Team(BaseModel):
|
|
165
168
|
|
166
169
|
|
167
170
|
@property
|
168
|
-
def
|
169
|
-
|
170
|
-
return
|
171
|
+
def managers(self) -> List[TeamMember] | None:
|
172
|
+
managers = [member for member in self.members if member.is_manager == True]
|
173
|
+
return managers if len(managers) > 0 else None
|
171
174
|
|
172
175
|
|
173
176
|
@property
|
174
|
-
def
|
177
|
+
def manager_tasks(self) -> List[Task] | None:
|
175
178
|
"""
|
176
|
-
|
177
|
-
The task is set as second priority following to the team tasks.
|
179
|
+
Tasks (incl. team tasks) handled by managers in the team.
|
178
180
|
"""
|
179
|
-
|
180
|
-
|
181
|
+
if self.managers:
|
182
|
+
tasks = [manager.task for manager in self.managers if manager.task is not None]
|
183
|
+
return tasks if len(tasks) > 0 else None
|
184
|
+
|
185
|
+
return None
|
181
186
|
|
182
187
|
|
183
188
|
@property
|
184
189
|
def tasks(self):
|
185
190
|
"""
|
186
191
|
Return all the tasks that the team needs to handle in order of priority:
|
187
|
-
1. team tasks,
|
192
|
+
1. team tasks, -> assigned to the member
|
188
193
|
2. manager_task,
|
189
194
|
3. members' tasks
|
190
195
|
"""
|
191
|
-
|
192
|
-
|
196
|
+
|
197
|
+
team_tasks = self.team_tasks
|
198
|
+
manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
|
199
|
+
member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
|
200
|
+
|
201
|
+
return team_tasks + manager_tasks + member_tasks
|
202
|
+
|
203
|
+
|
204
|
+
@property
|
205
|
+
def member_tasks_without_agent(self) -> List[Task] | None:
|
206
|
+
if self.members:
|
207
|
+
return [member.task for member in self.members if member.agent is None]
|
208
|
+
|
209
|
+
return None
|
193
210
|
|
194
211
|
|
195
|
-
# validators
|
196
212
|
@field_validator("id", mode="before")
|
197
213
|
@classmethod
|
198
214
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
@@ -201,6 +217,21 @@ class Team(BaseModel):
|
|
201
217
|
raise PydanticCustomError("may_not_set_field", "The 'id' field cannot be set by the user.", {})
|
202
218
|
|
203
219
|
|
220
|
+
@model_validator(mode="after")
|
221
|
+
def validate_tasks(self):
|
222
|
+
"""
|
223
|
+
Validates if the model recognize all tasks that the team needs to handle.
|
224
|
+
"""
|
225
|
+
|
226
|
+
if self.tasks:
|
227
|
+
if all(task in self.tasks for task in self.team_tasks) == False:
|
228
|
+
raise PydanticCustomError("task_validation_error", "`team_tasks` needs to be recognized in the task.", {})
|
229
|
+
|
230
|
+
if len(self.tasks) != len(self.team_tasks) + len([member for member in self.members if member.task is not None]):
|
231
|
+
raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
|
232
|
+
return self
|
233
|
+
|
234
|
+
|
204
235
|
@model_validator(mode="after")
|
205
236
|
def check_manager_llm(self):
|
206
237
|
"""
|
@@ -208,37 +239,29 @@ class Team(BaseModel):
|
|
208
239
|
"""
|
209
240
|
|
210
241
|
if self.process == TaskHandlingProcess.hierarchical:
|
211
|
-
if self.
|
242
|
+
if self.managers is None:
|
212
243
|
raise PydanticCustomError(
|
213
|
-
"
|
214
|
-
"Attribute `manager_llm` or `
|
244
|
+
"missing_manager_llm_or_manager",
|
245
|
+
"Attribute `manager_llm` or `manager` is required when using hierarchical process.",
|
215
246
|
{},
|
216
247
|
)
|
217
248
|
|
218
|
-
if (self.
|
219
|
-
|
220
|
-
|
221
|
-
raise PydanticCustomError(
|
222
|
-
"manager_agent_in_agents",
|
223
|
-
"Manager agent should not be included in agents list.",
|
224
|
-
{},
|
225
|
-
)
|
249
|
+
if self.managers and (self.manager_tasks is None or self.team_tasks is None):
|
250
|
+
raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
|
251
|
+
|
226
252
|
return self
|
227
253
|
|
228
254
|
|
229
255
|
@model_validator(mode="after")
|
230
256
|
def validate_tasks(self):
|
231
257
|
"""
|
232
|
-
|
258
|
+
Sequential task processing without any team tasks require a task-agent pairing.
|
233
259
|
"""
|
234
|
-
|
260
|
+
|
261
|
+
if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
|
235
262
|
for member in self.members:
|
236
263
|
if member.task is None:
|
237
|
-
raise PydanticCustomError(
|
238
|
-
"missing_agent_in_task",
|
239
|
-
f"Sequential process error: Agent is missing in the task with the following description: {member.task.description}",
|
240
|
-
{},
|
241
|
-
)
|
264
|
+
raise PydanticCustomError("missing_agent_in_task", f"Sequential process error: Agent is missing the task", {})
|
242
265
|
return self
|
243
266
|
|
244
267
|
@model_validator(mode="after")
|
@@ -249,7 +272,9 @@ class Team(BaseModel):
|
|
249
272
|
|
250
273
|
async_task_count = 0
|
251
274
|
for task in reversed(self.tasks):
|
252
|
-
if task
|
275
|
+
if not task:
|
276
|
+
break
|
277
|
+
elif task.async_execution:
|
253
278
|
async_task_count += 1
|
254
279
|
else:
|
255
280
|
break
|
@@ -262,21 +287,54 @@ class Team(BaseModel):
|
|
262
287
|
)
|
263
288
|
return self
|
264
289
|
|
290
|
+
|
265
291
|
def _get_responsible_agent(self, task: Task) -> Agent:
|
266
|
-
|
267
|
-
|
292
|
+
if task is None:
|
293
|
+
return None
|
294
|
+
else:
|
295
|
+
res = [member.agent for member in self.members if member.task and member.task.id == task.id]
|
296
|
+
return None if len(res) == 0 else res[0]
|
297
|
+
|
298
|
+
|
299
|
+
def _handle_team_planning(self) -> None:
|
300
|
+
"""
|
301
|
+
Form a team considering agents and tasks given, and update `self.members` field:
|
302
|
+
1. Idling managers to take the team tasks.
|
303
|
+
2. Idling members to take the remaining tasks starting from the team tasks to member tasks.
|
304
|
+
3. Create agents to handle the rest tasks.
|
305
|
+
"""
|
268
306
|
|
269
|
-
# setup team planner
|
270
|
-
def _handle_team_planning(self):
|
271
307
|
team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
|
272
|
-
|
308
|
+
idling_managers: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is True]
|
309
|
+
idling_members: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is False]
|
310
|
+
unassigned_tasks: List[Task] = self.member_tasks_without_agent
|
311
|
+
new_team_members: List[TeamMember] = []
|
312
|
+
|
313
|
+
|
314
|
+
if self.team_tasks:
|
315
|
+
candidates = idling_managers + idling_members
|
316
|
+
if candidates:
|
317
|
+
i = 0
|
318
|
+
while i < len(candidates):
|
319
|
+
if self.team_tasks[i]:
|
320
|
+
candidates[i].task = self.team_tasks[i]
|
321
|
+
i += 1
|
322
|
+
|
323
|
+
if len(self.team_tasks) > i:
|
324
|
+
for item in self.team_tasks[i:]:
|
325
|
+
if item not in unassigned_tasks:
|
326
|
+
unassigned_tasks = [item, ] + unassigned_tasks
|
273
327
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
328
|
+
else:
|
329
|
+
for item in self.team_tasks:
|
330
|
+
if item not in unassigned_tasks:
|
331
|
+
unassigned_tasks = [item, ] + unassigned_tasks
|
332
|
+
|
333
|
+
if unassigned_tasks:
|
334
|
+
new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
|
335
|
+
|
336
|
+
if new_team_members:
|
337
|
+
self.members += new_team_members
|
280
338
|
|
281
339
|
|
282
340
|
# task execution
|
@@ -333,9 +391,11 @@ class Team(BaseModel):
|
|
333
391
|
token_sum = agent._token_process.get_summary()
|
334
392
|
total_usage_metrics.add_usage_metrics(token_sum)
|
335
393
|
|
336
|
-
if self.
|
337
|
-
|
338
|
-
|
394
|
+
if self.managers:
|
395
|
+
for manager in self.managers:
|
396
|
+
if hasattr(manager.agent, "_token_process"):
|
397
|
+
token_sum = manager.agent._token_process.get_summary()
|
398
|
+
total_usage_metrics.add_usage_metrics(token_sum)
|
339
399
|
|
340
400
|
self.usage_metrics = total_usage_metrics
|
341
401
|
return total_usage_metrics
|
@@ -366,7 +426,7 @@ class Team(BaseModel):
|
|
366
426
|
|
367
427
|
responsible_agent = self._get_responsible_agent(task)
|
368
428
|
if responsible_agent is None:
|
369
|
-
|
429
|
+
self._handle_team_planning()
|
370
430
|
|
371
431
|
if isinstance(task, ConditionalTask):
|
372
432
|
skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
@@ -377,13 +437,13 @@ class Team(BaseModel):
|
|
377
437
|
# self._log_task_start(task, responsible_agent)
|
378
438
|
|
379
439
|
if task.async_execution:
|
380
|
-
context = create_raw_outputs(tasks=[task, ],task_outputs=([last_sync_output,] if last_sync_output else []))
|
440
|
+
context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
|
381
441
|
future = task.execute_async(agent=responsible_agent, context=context, tools=responsible_agent.tools)
|
382
442
|
futures.append((task, future, task_index))
|
383
443
|
else:
|
384
444
|
context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
|
385
445
|
task_output = task.execute_sync(agent=responsible_agent, context=context, tools=responsible_agent.tools)
|
386
|
-
if responsible_agent
|
446
|
+
if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
387
447
|
lead_task_output = task_output
|
388
448
|
|
389
449
|
task_outputs.append(task_output)
|
@@ -400,15 +460,16 @@ class Team(BaseModel):
|
|
400
460
|
def kickoff(self, kwargs_before: Optional[Dict[str, str]] = None, kwargs_after: Optional[Dict[str, Any]] = None) -> TeamOutput:
|
401
461
|
"""
|
402
462
|
Kickoff the team:
|
403
|
-
0.
|
463
|
+
0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planning_llm.
|
404
464
|
1. Address `before_kickoff_callbacks` if any.
|
405
|
-
2. Handle team members' tasks in accordance with the
|
465
|
+
2. Handle team members' tasks in accordance with the process.
|
406
466
|
3. Address `after_kickoff_callbacks` if any.
|
407
467
|
"""
|
408
468
|
|
409
469
|
metrics: List[UsageMetrics] = []
|
410
470
|
|
411
|
-
|
471
|
+
|
472
|
+
if self.team_tasks or self.member_tasks_without_agent:
|
412
473
|
self._handle_team_planning()
|
413
474
|
|
414
475
|
if kwargs_before is not None:
|
versionhq/team/team_planner.py
CHANGED
@@ -3,37 +3,80 @@ from dotenv import load_dotenv
|
|
3
3
|
from typing import Any, List, Optional
|
4
4
|
from pydantic import BaseModel, Field
|
5
5
|
|
6
|
-
from versionhq.agent.model import Agent
|
7
|
-
from versionhq.task.model import Task, ResponseField
|
8
|
-
|
9
6
|
load_dotenv(override=True)
|
10
7
|
|
11
8
|
|
12
9
|
class TeamPlanner:
|
13
10
|
"""
|
14
|
-
|
11
|
+
Assign agents to multiple tasks.
|
15
12
|
"""
|
16
13
|
|
14
|
+
from versionhq.task.model import Task, ResponseField, TaskOutput
|
15
|
+
from versionhq.agent.model import Agent
|
16
|
+
|
17
|
+
|
17
18
|
def __init__(self, tasks: List[Task], planner_llm: Optional[Any] = None):
|
18
19
|
self.tasks = tasks
|
19
|
-
self.planner_llm = (
|
20
|
-
|
20
|
+
self.planner_llm = planner_llm if planner_llm else os.environ.get("LITELLM_MODEL_NAME")
|
21
|
+
|
22
|
+
|
23
|
+
def _handle_assign_agents(self, unassigned_tasks: List[Task]) -> List[Any]:
|
24
|
+
"""
|
25
|
+
Build an agent and assign it a task, then return a list of TeamMember connecting the agent created and the task given.
|
26
|
+
"""
|
27
|
+
|
28
|
+
from versionhq.agent.model import Agent
|
29
|
+
from versionhq.task.model import Task, ResponseField
|
30
|
+
from versionhq.team.model import TeamMember
|
31
|
+
|
32
|
+
new_member_list: List[TeamMember] = []
|
33
|
+
agent_creator = Agent(
|
34
|
+
role="agent_creator",
|
35
|
+
goal="build an ai agent that can competitively handle the task given",
|
36
|
+
llm=self.planner_llm,
|
21
37
|
)
|
22
38
|
|
23
|
-
|
39
|
+
for unassgined_task in unassigned_tasks:
|
40
|
+
task = Task(
|
41
|
+
description=f"""
|
42
|
+
Based on the following task summary, draft a AI agent's role and goal in concise manner.
|
43
|
+
Task summary: {unassgined_task.summary}
|
44
|
+
""",
|
45
|
+
expected_output_json=True,
|
46
|
+
output_field_list=[
|
47
|
+
ResponseField(title="goal", type=str, required=True),
|
48
|
+
ResponseField(title="role", type=str, required=True),
|
49
|
+
],
|
50
|
+
)
|
51
|
+
res = task.execute_sync(agent=agent_creator)
|
52
|
+
agent = Agent(
|
53
|
+
role=res.json_dict["role"] if "role" in res.json_dict else res.raw,
|
54
|
+
goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
|
55
|
+
)
|
56
|
+
if agent.id:
|
57
|
+
team_member = TeamMember(agent=agent, task=unassgined_task, is_manager=False)
|
58
|
+
new_member_list.append(team_member)
|
59
|
+
|
60
|
+
return new_member_list
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
def _handle_task_planning(self, context: Optional[str] = None, tools: Optional[str] = None) -> TaskOutput:
|
24
65
|
"""
|
25
66
|
Handles the team planning by creating detailed step-by-step plans for each task.
|
26
67
|
"""
|
27
68
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
69
|
+
from versionhq.agent.model import Agent
|
70
|
+
from versionhq.task.model import Task, ResponseField
|
71
|
+
|
72
|
+
team_planner = Agent(
|
73
|
+
role="team planner",
|
74
|
+
goal="Plan extremely detailed, step-by-step plan based on the tasks and tools available to each agent so that they can perform the tasks in an exemplary manner and assign a task to each agent.",
|
32
75
|
llm=self.planner_llm,
|
33
76
|
)
|
34
77
|
|
35
78
|
task_summary_list = [task.summary for task in self.tasks]
|
36
|
-
|
79
|
+
task = Task(
|
37
80
|
description=f"""
|
38
81
|
Based on the following task summaries, create the most descriptive plan that the team can execute most efficiently. Take all the task summaries - task's description and tools available - into consideration. Your answer only contains a dictionary.
|
39
82
|
|
@@ -42,14 +85,9 @@ class TeamPlanner:
|
|
42
85
|
expected_output_json=False,
|
43
86
|
expected_output_pydantic=True,
|
44
87
|
output_field_list=[
|
45
|
-
ResponseField(title=
|
88
|
+
ResponseField(title="task", type=str, required=True)
|
46
89
|
for task in self.tasks
|
47
90
|
],
|
48
91
|
)
|
49
|
-
|
50
|
-
|
51
|
-
if isinstance(task_output.pydantic, BaseModel):
|
52
|
-
return task_output.pydantic
|
53
|
-
|
54
|
-
else:
|
55
|
-
return None
|
92
|
+
output = task.execute_sync(agent=team_planner, context=context, tools=tools)
|
93
|
+
return output
|
versionhq/tool/model.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
1
|
+
versionhq/__init__.py,sha256=vDnsppEwUlpBHeAcl8jCKzt8SaGylbfIND1PjgjY2s8,871
|
2
2
|
versionhq/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
versionhq/_utils/cache_handler.py,sha256=zDQKzIn7vp-M2-uepHFxgJstjfftZS5mzXKL_-4uVvI,370
|
4
4
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
@@ -27,16 +27,16 @@ versionhq/storage/task_output_storage.py,sha256=xoBJHeqUyQt6iJoR1WQTghP-fyxXL66q
|
|
27
27
|
versionhq/task/__init__.py,sha256=g4mCATnn1mUXxsfQ5p6IpPawr8O421wVIT8kMKEcxQw,180
|
28
28
|
versionhq/task/formatter.py,sha256=N8Kmk9vtrMtBdgJ8J7RmlKNMdZWSmV8O1bDexmCWgU0,643
|
29
29
|
versionhq/task/log_handler.py,sha256=KJRrcNZgFSKhlNzvtYFnvtp6xukaF1s7ifX9u4zWrN8,1683
|
30
|
-
versionhq/task/model.py,sha256=
|
30
|
+
versionhq/task/model.py,sha256=F6yd8oPJ6cjH0PcGZru7eska7Qu6lEtMDyjgn-ZnAfE,19394
|
31
31
|
versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
versionhq/team/model.py,sha256=
|
33
|
-
versionhq/team/team_planner.py,sha256=
|
32
|
+
versionhq/team/model.py,sha256=Liq0PUHncZAJyjjmqOL5mmt5E0Z0dBfhTmtRjUZQWiE,20369
|
33
|
+
versionhq/team/team_planner.py,sha256=uzX2yed7A7gNSs6qH5jIq2zXMVF5BwQQ4HPATsB9DSQ,3675
|
34
34
|
versionhq/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
35
|
versionhq/tool/decorator.py,sha256=Y-j4jkoujD5LUvpe8uf3p5Zagk2XVaRKC9rkIE-2geo,1189
|
36
|
-
versionhq/tool/model.py,sha256=
|
36
|
+
versionhq/tool/model.py,sha256=GjhQdlAyl2MGu4iiiFW7LqBWaaXhdlRQz2OGHhmr9FM,6828
|
37
37
|
versionhq/tool/tool_handler.py,sha256=esUqGp8HoREesai8fmh2klAf04Sjpsacmb03C7F6sNQ,1541
|
38
|
-
versionhq-1.1.8.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
|
39
|
-
versionhq-1.1.8.dist-info/METADATA,sha256=
|
40
|
-
versionhq-1.1.8.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
41
|
-
versionhq-1.1.8.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
42
|
-
versionhq-1.1.8.dist-info/RECORD,,
|
38
|
+
versionhq-1.1.8.1.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
|
39
|
+
versionhq-1.1.8.1.dist-info/METADATA,sha256=7_hZDC_4H87AwyP_TtICbEv0XB_9DtUNI-80xjqqSBU,15919
|
40
|
+
versionhq-1.1.8.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
41
|
+
versionhq-1.1.8.1.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
42
|
+
versionhq-1.1.8.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|