glchat-plugin 0.4.3__py3-none-any.whl → 0.4.5__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.
- glchat_plugin/pipeline/pipeline_handler.py +133 -13
- glchat_plugin/pipeline/pipeline_plugin.py +16 -0
- glchat_plugin/pipeline/tool_processor.py +105 -0
- {glchat_plugin-0.4.3.dist-info → glchat_plugin-0.4.5.dist-info}/METADATA +1 -1
- {glchat_plugin-0.4.3.dist-info → glchat_plugin-0.4.5.dist-info}/RECORD +7 -6
- {glchat_plugin-0.4.3.dist-info → glchat_plugin-0.4.5.dist-info}/WHEEL +0 -0
- {glchat_plugin-0.4.3.dist-info → glchat_plugin-0.4.5.dist-info}/top_level.txt +0 -0
|
@@ -7,11 +7,13 @@ Authors:
|
|
|
7
7
|
Hermes Vincentius Gani (hermes.v.gani@gdplabs.id)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import inspect
|
|
10
11
|
import traceback
|
|
11
12
|
from typing import Any, Type
|
|
12
13
|
|
|
13
14
|
from bosa_core import Plugin
|
|
14
15
|
from bosa_core.plugin.handler import PluginHandler
|
|
16
|
+
from gllm_core.schema import Tool
|
|
15
17
|
from gllm_core.utils import LoggerManager
|
|
16
18
|
from gllm_inference.catalog import LMRequestProcessorCatalog, PromptBuilderCatalog
|
|
17
19
|
from gllm_pipeline.pipeline.pipeline import Pipeline
|
|
@@ -19,6 +21,7 @@ from pydantic import BaseModel, ConfigDict
|
|
|
19
21
|
|
|
20
22
|
from glchat_plugin.config.app_config import AppConfig
|
|
21
23
|
from glchat_plugin.config.constant import DEFAULT_ORGANIZATION_ID
|
|
24
|
+
from glchat_plugin.pipeline.tool_processor import ToolProcessor
|
|
22
25
|
from glchat_plugin.storage.base_chat_history_storage import BaseChatHistoryStorage
|
|
23
26
|
|
|
24
27
|
|
|
@@ -64,6 +67,73 @@ class ChatbotPresetMapping(BaseModel):
|
|
|
64
67
|
chatbot_preset_map: dict[str, PipelinePresetConfig]
|
|
65
68
|
|
|
66
69
|
|
|
70
|
+
class PipelineBundle:
|
|
71
|
+
"""Pipeline bundle.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
pipeline (Pipeline): The pipeline.
|
|
75
|
+
tools (list[ToolProcessor]): The tools.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
pipeline: Pipeline
|
|
79
|
+
tools: dict[str, ToolProcessor]
|
|
80
|
+
|
|
81
|
+
def __init__(self, pipeline: Pipeline, tools: list[ToolProcessor]):
|
|
82
|
+
"""Initialize the pipeline bundle.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
pipeline (Pipeline): The pipeline.
|
|
86
|
+
tools (list[ToolProcessor]): The tools.
|
|
87
|
+
"""
|
|
88
|
+
self.pipeline = pipeline
|
|
89
|
+
self.tools = {tool.name: tool for tool in tools}
|
|
90
|
+
|
|
91
|
+
async def invoke(self, **kwargs: Any) -> dict[str, Any]:
|
|
92
|
+
"""Invoke the pipeline bundle.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
kwargs (Any): The keyword arguments to pass to the pipeline.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
dict[str, Any]: The result of the pipeline invocation.
|
|
99
|
+
"""
|
|
100
|
+
return await self.pipeline.invoke(**kwargs)
|
|
101
|
+
|
|
102
|
+
async def invoke_as_tool(
|
|
103
|
+
self,
|
|
104
|
+
tool_name: str,
|
|
105
|
+
pipeline_config: dict[str, Any],
|
|
106
|
+
inputs: dict[str, Any],
|
|
107
|
+
config: dict[str, Any],
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> Any:
|
|
110
|
+
"""Invoke the pipeline bundle as a tool.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
tool_name (str): The name of the tool.
|
|
114
|
+
pipeline_config (dict[str, Any]): The pipeline configuration.
|
|
115
|
+
inputs (dict[str, Any]): The inputs to the tool.
|
|
116
|
+
config (dict[str, Any]): The configuration of the tool.
|
|
117
|
+
kwargs (Any): The keyword arguments to pass to the tool.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Any: The result of the tool invocation.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ValueError: If the tool is not found in the pipeline bundle.
|
|
124
|
+
"""
|
|
125
|
+
tool = self.tools.get(tool_name)
|
|
126
|
+
if not tool:
|
|
127
|
+
raise ValueError(f"Tool `{tool_name}` not found in pipeline bundle")
|
|
128
|
+
|
|
129
|
+
return await tool.process(
|
|
130
|
+
pipeline_config=pipeline_config,
|
|
131
|
+
inputs=inputs,
|
|
132
|
+
config=config,
|
|
133
|
+
**kwargs,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
67
137
|
logger = LoggerManager().get_logger(__name__)
|
|
68
138
|
|
|
69
139
|
|
|
@@ -98,7 +168,7 @@ class PipelineHandler(PluginHandler):
|
|
|
98
168
|
_chatbot_configs: dict[tuple[str, str], ChatbotConfig] = {}
|
|
99
169
|
_builders: dict[tuple[str, str], Plugin] = {}
|
|
100
170
|
_plugins: dict[tuple[str, str], Plugin] = {}
|
|
101
|
-
_pipeline_cache: dict[tuple[str, str, str],
|
|
171
|
+
_pipeline_cache: dict[tuple[str, str, str], PipelineBundle] = {}
|
|
102
172
|
_chatbot_pipeline_keys: dict[tuple[str, str], set[tuple[str, str, str]]] = {}
|
|
103
173
|
_pipeline_build_errors: dict[tuple[str, str, str], str] = {}
|
|
104
174
|
|
|
@@ -203,8 +273,13 @@ class PipelineHandler(PluginHandler):
|
|
|
203
273
|
organization_id (str): The organization ID.
|
|
204
274
|
"""
|
|
205
275
|
chatbot_organization_id = (chatbot_id, organization_id)
|
|
206
|
-
|
|
207
|
-
|
|
276
|
+
|
|
277
|
+
prompt_builder_catalogs = instance._chatbot_configs[chatbot_organization_id].prompt_builder_catalogs
|
|
278
|
+
plugin.prompt_builder_catalogs = prompt_builder_catalogs
|
|
279
|
+
|
|
280
|
+
lmrp_catalogs = instance._chatbot_configs[chatbot_organization_id].lmrp_catalogs
|
|
281
|
+
plugin.lmrp_catalogs = lmrp_catalogs
|
|
282
|
+
|
|
208
283
|
instance._builders[chatbot_organization_id] = plugin
|
|
209
284
|
|
|
210
285
|
for pipeline_type in INTERNAL_PIPELINES:
|
|
@@ -212,15 +287,19 @@ class PipelineHandler(PluginHandler):
|
|
|
212
287
|
internal_plugin = instance._plugins.get(pipeline_type_organization_id)
|
|
213
288
|
if internal_plugin:
|
|
214
289
|
try:
|
|
215
|
-
internal_plugin.prompt_builder_catalogs =
|
|
216
|
-
|
|
217
|
-
].prompt_builder_catalogs
|
|
218
|
-
internal_plugin.lmrp_catalogs = instance._chatbot_configs[chatbot_organization_id].lmrp_catalogs
|
|
290
|
+
internal_plugin.prompt_builder_catalogs = prompt_builder_catalogs
|
|
291
|
+
internal_plugin.lmrp_catalogs = lmrp_catalogs
|
|
219
292
|
pipeline_config = instance._chatbot_configs[chatbot_organization_id].pipeline_config.copy()
|
|
220
|
-
pipeline = await
|
|
293
|
+
pipeline = await cls._call_build(
|
|
294
|
+
internal_plugin,
|
|
295
|
+
pipeline_config,
|
|
296
|
+
prompt_builder_catalogs,
|
|
297
|
+
lmrp_catalogs,
|
|
298
|
+
)
|
|
299
|
+
pipeline_tools = await internal_plugin.build_tools(pipeline_config)
|
|
221
300
|
pipeline_key = (chatbot_id, f"__{pipeline_type}__", organization_id)
|
|
222
301
|
instance._chatbot_pipeline_keys.setdefault(chatbot_organization_id, set()).add(pipeline_key)
|
|
223
|
-
instance._pipeline_cache[pipeline_key] = pipeline
|
|
302
|
+
instance._pipeline_cache[pipeline_key] = PipelineBundle(pipeline, pipeline_tools)
|
|
224
303
|
|
|
225
304
|
# Clear any previous error for this internal pipeline if build succeeded
|
|
226
305
|
instance._pipeline_build_errors.pop(pipeline_key, None)
|
|
@@ -251,10 +330,16 @@ class PipelineHandler(PluginHandler):
|
|
|
251
330
|
if credentials:
|
|
252
331
|
pipeline_config["api_key"] = credentials
|
|
253
332
|
|
|
254
|
-
pipeline = await
|
|
333
|
+
pipeline = await cls._call_build(
|
|
334
|
+
plugin,
|
|
335
|
+
pipeline_config,
|
|
336
|
+
prompt_builder_catalogs,
|
|
337
|
+
lmrp_catalogs,
|
|
338
|
+
)
|
|
339
|
+
pipeline_tools = await plugin.build_tools(pipeline_config)
|
|
255
340
|
pipeline_key = (chatbot_id, str(model_id), organization_id)
|
|
256
341
|
instance._chatbot_pipeline_keys.setdefault(chatbot_organization_id, set()).add(pipeline_key)
|
|
257
|
-
instance._pipeline_cache[pipeline_key] = pipeline
|
|
342
|
+
instance._pipeline_cache[pipeline_key] = PipelineBundle(pipeline, pipeline_tools)
|
|
258
343
|
|
|
259
344
|
# Clear any previous error for this pipeline if build succeeded
|
|
260
345
|
instance._pipeline_build_errors.pop(pipeline_key, None)
|
|
@@ -266,6 +351,41 @@ class PipelineHandler(PluginHandler):
|
|
|
266
351
|
# Store the error message for later retrieval
|
|
267
352
|
instance._store_pipeline_build_error(chatbot_id, str(model_id), organization_id, error_message)
|
|
268
353
|
|
|
354
|
+
@classmethod
|
|
355
|
+
async def _call_build(
|
|
356
|
+
cls,
|
|
357
|
+
plugin: Plugin,
|
|
358
|
+
pipeline_config: dict[str, Any],
|
|
359
|
+
prompt_builder_catalogs: dict[str, PromptBuilderCatalog] | None,
|
|
360
|
+
lmrp_catalogs: dict[str, LMRequestProcessorCatalog] | None,
|
|
361
|
+
) -> Pipeline:
|
|
362
|
+
"""Call plugin.build in a backward-compatible way.
|
|
363
|
+
|
|
364
|
+
If the plugin.build signature declares prompt_builder_catalogs or lmrp_catalogs,
|
|
365
|
+
they will be passed as keyword arguments. Otherwise, only pipeline_config is passed.
|
|
366
|
+
"""
|
|
367
|
+
try:
|
|
368
|
+
sig = inspect.signature(plugin.build)
|
|
369
|
+
except (TypeError, ValueError):
|
|
370
|
+
# Fallback: call with just pipeline_config
|
|
371
|
+
return await plugin.build(pipeline_config)
|
|
372
|
+
|
|
373
|
+
params = list(sig.parameters.values())
|
|
374
|
+
# Skip 'self' if present
|
|
375
|
+
if params and params[0].name == "self":
|
|
376
|
+
params = params[1:]
|
|
377
|
+
|
|
378
|
+
param_names = {p.name for p in params}
|
|
379
|
+
|
|
380
|
+
if "prompt_builder_catalogs" in param_names or "lmrp_catalogs" in param_names:
|
|
381
|
+
return await plugin.build(
|
|
382
|
+
pipeline_config,
|
|
383
|
+
prompt_builder_catalogs=prompt_builder_catalogs,
|
|
384
|
+
lmrp_catalogs=lmrp_catalogs,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return await plugin.build(pipeline_config)
|
|
388
|
+
|
|
269
389
|
def get_pipeline_builder(self, chatbot_id: str, organization_id: str = DEFAULT_ORGANIZATION_ID) -> Plugin:
|
|
270
390
|
"""Get a pipeline builder instance for the given chatbot.
|
|
271
391
|
|
|
@@ -394,7 +514,7 @@ class PipelineHandler(PluginHandler):
|
|
|
394
514
|
|
|
395
515
|
async def aget_pipeline(
|
|
396
516
|
self, chatbot_id: str, model_id: str, organization_id: str = DEFAULT_ORGANIZATION_ID
|
|
397
|
-
) ->
|
|
517
|
+
) -> PipelineBundle:
|
|
398
518
|
"""Get a pipeline instance for the given chatbot and model ID (async version).
|
|
399
519
|
|
|
400
520
|
Args:
|
|
@@ -403,7 +523,7 @@ class PipelineHandler(PluginHandler):
|
|
|
403
523
|
organization_id (str): The organization ID.
|
|
404
524
|
|
|
405
525
|
Returns:
|
|
406
|
-
|
|
526
|
+
PipelineBundle: The pipeline bundle instance.
|
|
407
527
|
|
|
408
528
|
Raises:
|
|
409
529
|
ValueError: If the chatbot ID is invalid.
|
|
@@ -11,11 +11,13 @@ from abc import ABC, abstractmethod
|
|
|
11
11
|
from typing import Any, Generic, Type, TypeVar
|
|
12
12
|
|
|
13
13
|
from bosa_core.plugin.plugin import Plugin
|
|
14
|
+
from gllm_core.schema import Tool
|
|
14
15
|
from gllm_inference.catalog.catalog import BaseCatalog
|
|
15
16
|
from gllm_pipeline.pipeline.pipeline import Pipeline
|
|
16
17
|
|
|
17
18
|
from glchat_plugin.config.constant import DEFAULT_ORGANIZATION_ID
|
|
18
19
|
from glchat_plugin.pipeline.pipeline_handler import PipelineHandler
|
|
20
|
+
from glchat_plugin.pipeline.tool_processor import ToolProcessor
|
|
19
21
|
|
|
20
22
|
PipelineState = TypeVar("PipelineState")
|
|
21
23
|
PipelinePresetConfig = TypeVar("PipelinePresetConfig", bound="BasePipelinePresetConfig")
|
|
@@ -131,3 +133,17 @@ class PipelineBuilderPlugin(Plugin, Generic[PipelineState, PipelinePresetConfig]
|
|
|
131
133
|
if self.preset_config_class:
|
|
132
134
|
return self.preset_config_class().model_dump()
|
|
133
135
|
return {}
|
|
136
|
+
|
|
137
|
+
async def build_tools(
|
|
138
|
+
self,
|
|
139
|
+
pipeline_config: dict[str, Any],
|
|
140
|
+
) -> list[ToolProcessor]:
|
|
141
|
+
"""Build a pipeline instance.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
pipeline_config (dict[str, Any]): Pipeline configuration including model name and other settings.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
list[ToolProcessor]: Built tool processors.
|
|
148
|
+
"""
|
|
149
|
+
return []
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Tool processor.
|
|
2
|
+
|
|
3
|
+
This module provides a base class for tool processors.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Surya Mahadi (surya.mahadi@gdplabs.id)
|
|
7
|
+
|
|
8
|
+
References:
|
|
9
|
+
None
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from gllm_core.schema import Tool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ToolProcessor(ABC):
|
|
19
|
+
"""Tool processor.
|
|
20
|
+
|
|
21
|
+
This class is responsible for processing tools.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
tool (Tool): The tool.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, tool: Tool):
|
|
28
|
+
"""Initialize the tool processor.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
tool (Tool): The tool.
|
|
32
|
+
"""
|
|
33
|
+
self.tool = tool
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def name(self) -> str:
|
|
37
|
+
"""Get the name of the tool.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str: The name of the tool.
|
|
41
|
+
"""
|
|
42
|
+
return self.tool.name
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def preprocess(
|
|
46
|
+
self,
|
|
47
|
+
pipeline_config: dict[str, Any],
|
|
48
|
+
inputs: dict[str, Any],
|
|
49
|
+
config: dict[str, Any],
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
52
|
+
"""Process a tool.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
pipeline_config (dict[str, Any]): The pipeline configuration.
|
|
56
|
+
inputs (dict[str, Any]): The inputs to the tool.
|
|
57
|
+
config (dict[str, Any]): The configuration of the tool.
|
|
58
|
+
kwargs (Any): The keyword arguments to pass to the tool.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
tuple[dict[str, Any], dict[str, Any]]: The inputs and configuration for the tool invocation.
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def postprocess(self, result: Any) -> dict[str, Any]:
|
|
67
|
+
"""Postprocess a tool for json friendly output.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
result (Any): The result of the tool invocation.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
dict[str, Any]: The result of the tool invocation.
|
|
74
|
+
"""
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
|
|
77
|
+
async def process(
|
|
78
|
+
self,
|
|
79
|
+
pipeline_config: dict[str, Any],
|
|
80
|
+
inputs: dict[str, Any],
|
|
81
|
+
config: dict[str, Any],
|
|
82
|
+
**kwargs: Any,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Process a tool.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
pipeline_config (dict[str, Any]): The pipeline configuration.
|
|
88
|
+
inputs (dict[str, Any]): The inputs to the tool.
|
|
89
|
+
config (dict[str, Any]): The configuration of the tool.
|
|
90
|
+
kwargs (Any): The keyword arguments to pass to the tool.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Any: The result of the tool invocation.
|
|
94
|
+
"""
|
|
95
|
+
tool_input, config = await self.preprocess(
|
|
96
|
+
pipeline_config=pipeline_config,
|
|
97
|
+
inputs=inputs,
|
|
98
|
+
config=config,
|
|
99
|
+
**kwargs,
|
|
100
|
+
)
|
|
101
|
+
result = await self.tool.invoke(
|
|
102
|
+
input=tool_input,
|
|
103
|
+
context=config,
|
|
104
|
+
)
|
|
105
|
+
return await self.postprocess(result)
|
|
@@ -8,8 +8,9 @@ glchat_plugin/handler/__init__.py,sha256=H5DJaAfwwtRsvMcOaEzHfGMQk25H7la0E7uPfks
|
|
|
8
8
|
glchat_plugin/handler/base_post_login_handler.py,sha256=48xSbe_LwTCjRY-lCuzWXqbnEr1ql8bAhQih1Xeh8f8,2835
|
|
9
9
|
glchat_plugin/pipeline/__init__.py,sha256=Sk-NfIGyA9VKIg0Bt5OHatNUYyWVPh9i5xhE5DFAfbo,41
|
|
10
10
|
glchat_plugin/pipeline/base_pipeline_preset_config.py,sha256=lbMH8y_HU3LrqtMYXLzQ2906ZkMXARKY5vBuIGvRVdA,2969
|
|
11
|
-
glchat_plugin/pipeline/pipeline_handler.py,sha256=
|
|
12
|
-
glchat_plugin/pipeline/pipeline_plugin.py,sha256=
|
|
11
|
+
glchat_plugin/pipeline/pipeline_handler.py,sha256=sBD9Ohkd6GIIiTxgRAm81HuQJIzcNmV7KjE3hHQiftw,38108
|
|
12
|
+
glchat_plugin/pipeline/pipeline_plugin.py,sha256=fDQWJkEMgbbY6bK81gHjzp3fLZZN0a0Pv6_DS4wSKL0,5040
|
|
13
|
+
glchat_plugin/pipeline/tool_processor.py,sha256=pc-uHoBqOSXjLM8R_Wgws_z5ehPtaSeb9Bhk9aeLDus,2731
|
|
13
14
|
glchat_plugin/service/__init__.py,sha256=9T4qzyYL052qLqva5el1F575OTRNaaf9tb9UvW-leTc,47
|
|
14
15
|
glchat_plugin/service/base_rate_limiter_service.py,sha256=tgKwdr4EqnGo5iDRVJPnlg8W9q0hiUzfeewAtdW4IjU,1232
|
|
15
16
|
glchat_plugin/service/base_tenant_service.py,sha256=CB-jJWXi87nTcjO1XhPoSgNMf2YA_LfXoHr30ye0VtI,734
|
|
@@ -19,7 +20,7 @@ glchat_plugin/storage/base_anonymizer_storage.py,sha256=oFwovWrsjM7v1YjeN-4p-M3O
|
|
|
19
20
|
glchat_plugin/storage/base_chat_history_storage.py,sha256=YIGM8zv7s5BQ8O7W1LfaLyQW4SF9Bt3aolowsoDaQw8,11257
|
|
20
21
|
glchat_plugin/tools/__init__.py,sha256=OFotHbgQ8mZEbdlvlv5aVMdxfubPvkVWAcTwhIPdIqQ,542
|
|
21
22
|
glchat_plugin/tools/decorators.py,sha256=AvQBV18wzXWdC483RSSmpfh92zsqTyp8SzDLIkreIGU,3925
|
|
22
|
-
glchat_plugin-0.4.
|
|
23
|
-
glchat_plugin-0.4.
|
|
24
|
-
glchat_plugin-0.4.
|
|
25
|
-
glchat_plugin-0.4.
|
|
23
|
+
glchat_plugin-0.4.5.dist-info/METADATA,sha256=8lU2TR_qmgA8oM25F_Kq7g0v5YAKiGJuLUsFvKO2P1k,2063
|
|
24
|
+
glchat_plugin-0.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
glchat_plugin-0.4.5.dist-info/top_level.txt,sha256=fzKSXmct5dY4CAKku4-mkdHX-QPAyQVvo8vpQj8qizY,14
|
|
26
|
+
glchat_plugin-0.4.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|