chibi-bot 1.6.0b0__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.
- chibi/__init__.py +0 -0
- chibi/__main__.py +343 -0
- chibi/cli.py +90 -0
- chibi/config/__init__.py +6 -0
- chibi/config/app.py +123 -0
- chibi/config/gpt.py +108 -0
- chibi/config/logging.py +15 -0
- chibi/config/telegram.py +43 -0
- chibi/config_generator.py +233 -0
- chibi/constants.py +362 -0
- chibi/exceptions.py +58 -0
- chibi/models.py +496 -0
- chibi/schemas/__init__.py +0 -0
- chibi/schemas/anthropic.py +20 -0
- chibi/schemas/app.py +54 -0
- chibi/schemas/cloudflare.py +65 -0
- chibi/schemas/mistralai.py +56 -0
- chibi/schemas/suno.py +83 -0
- chibi/service.py +135 -0
- chibi/services/bot.py +276 -0
- chibi/services/lock_manager.py +20 -0
- chibi/services/mcp/manager.py +242 -0
- chibi/services/metrics.py +54 -0
- chibi/services/providers/__init__.py +16 -0
- chibi/services/providers/alibaba.py +79 -0
- chibi/services/providers/anthropic.py +40 -0
- chibi/services/providers/cloudflare.py +98 -0
- chibi/services/providers/constants/suno.py +2 -0
- chibi/services/providers/customopenai.py +11 -0
- chibi/services/providers/deepseek.py +15 -0
- chibi/services/providers/eleven_labs.py +85 -0
- chibi/services/providers/gemini_native.py +489 -0
- chibi/services/providers/grok.py +40 -0
- chibi/services/providers/minimax.py +96 -0
- chibi/services/providers/mistralai_native.py +312 -0
- chibi/services/providers/moonshotai.py +20 -0
- chibi/services/providers/openai.py +74 -0
- chibi/services/providers/provider.py +892 -0
- chibi/services/providers/suno.py +130 -0
- chibi/services/providers/tools/__init__.py +23 -0
- chibi/services/providers/tools/cmd.py +132 -0
- chibi/services/providers/tools/common.py +127 -0
- chibi/services/providers/tools/constants.py +78 -0
- chibi/services/providers/tools/exceptions.py +1 -0
- chibi/services/providers/tools/file_editor.py +875 -0
- chibi/services/providers/tools/mcp_management.py +274 -0
- chibi/services/providers/tools/mcp_simple.py +72 -0
- chibi/services/providers/tools/media.py +451 -0
- chibi/services/providers/tools/memory.py +252 -0
- chibi/services/providers/tools/schemas.py +10 -0
- chibi/services/providers/tools/send.py +435 -0
- chibi/services/providers/tools/tool.py +163 -0
- chibi/services/providers/tools/utils.py +146 -0
- chibi/services/providers/tools/web.py +261 -0
- chibi/services/providers/utils.py +182 -0
- chibi/services/task_manager.py +93 -0
- chibi/services/user.py +269 -0
- chibi/storage/abstract.py +54 -0
- chibi/storage/database.py +86 -0
- chibi/storage/dynamodb.py +257 -0
- chibi/storage/local.py +70 -0
- chibi/storage/redis.py +91 -0
- chibi/utils/__init__.py +0 -0
- chibi/utils/app.py +249 -0
- chibi/utils/telegram.py +521 -0
- chibi_bot-1.6.0b0.dist-info/LICENSE +21 -0
- chibi_bot-1.6.0b0.dist-info/METADATA +340 -0
- chibi_bot-1.6.0b0.dist-info/RECORD +70 -0
- chibi_bot-1.6.0b0.dist-info/WHEEL +4 -0
- chibi_bot-1.6.0b0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Callable, Coroutine, cast
|
|
3
|
+
|
|
4
|
+
from dotenv import dotenv_values
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from mcp import ClientSession
|
|
7
|
+
from mcp.types import CallToolResult
|
|
8
|
+
from openai.types.chat import ChatCompletionToolParam
|
|
9
|
+
from openai.types.shared_params import FunctionDefinition
|
|
10
|
+
|
|
11
|
+
from chibi.config import application_settings
|
|
12
|
+
from chibi.services.mcp.manager import MCPManager
|
|
13
|
+
from chibi.services.providers.tools.tool import ChibiTool, RegisteredChibiTools
|
|
14
|
+
from chibi.services.providers.tools.utils import AdditionalOptions
|
|
15
|
+
from chibi.services.providers.utils import escape_and_truncate
|
|
16
|
+
|
|
17
|
+
JsonNode = dict[str, Any] | list[Any] | str | int | float | bool | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _clean_schema(schema: JsonNode) -> JsonNode:
|
|
21
|
+
"""Recursively cleans the schema for LLM provider compatibility.
|
|
22
|
+
|
|
23
|
+
Removes 'additionalProperties', '$schema', and other meta-fields that cause validation errors.
|
|
24
|
+
"""
|
|
25
|
+
if isinstance(schema, dict):
|
|
26
|
+
# Fields to exclude from the schema
|
|
27
|
+
exclude = {"additionalProperties", "$schema"}
|
|
28
|
+
# Also clean nested objects in 'properties' and 'items'
|
|
29
|
+
cleaned = {k: _clean_schema(v) for k, v in schema.items() if k not in exclude}
|
|
30
|
+
return cleaned
|
|
31
|
+
if isinstance(schema, list):
|
|
32
|
+
return [_clean_schema(i) for i in schema]
|
|
33
|
+
return schema
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_wrapper(
|
|
37
|
+
server_name: str, original_tool_name: str, chibi_tool_name: str
|
|
38
|
+
) -> Callable[..., Coroutine[Any, Any, dict[str, Any]]]:
|
|
39
|
+
async def wrapper(**kwargs_inner: Any) -> dict[str, Any]:
|
|
40
|
+
tool_args = {k: v for k, v in kwargs_inner.items() if k not in list(AdditionalOptions.__annotations__.keys())}
|
|
41
|
+
|
|
42
|
+
logger.log(
|
|
43
|
+
"TOOL",
|
|
44
|
+
(
|
|
45
|
+
f"[{kwargs_inner.get('model', 'Unknown model')}] "
|
|
46
|
+
f"Calling MCP tool '{chibi_tool_name}' with args: {escape_and_truncate(tool_args)}"
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
res: CallToolResult = await MCPManager.call_tool(
|
|
50
|
+
server_name=server_name, tool_name=original_tool_name, arguments=tool_args
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if len(res.content) == 1:
|
|
54
|
+
output = res.content[0].model_dump()
|
|
55
|
+
else:
|
|
56
|
+
output = {"result": [content.model_dump() for content in res.content]}
|
|
57
|
+
|
|
58
|
+
logger.log(
|
|
59
|
+
"TOOL",
|
|
60
|
+
(
|
|
61
|
+
f"[{kwargs_inner.get('model', 'Unknown model')}] "
|
|
62
|
+
f"MCP tool '{chibi_tool_name}' returned: {escape_and_truncate(output)}",
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
return output
|
|
66
|
+
|
|
67
|
+
return wrapper
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def register_tools_from_mcp_session(
|
|
71
|
+
mcp_session: ClientSession, server_name: str, transport: str
|
|
72
|
+
) -> dict[str, str]:
|
|
73
|
+
try:
|
|
74
|
+
tools_result = await mcp_session.list_tools()
|
|
75
|
+
|
|
76
|
+
mcp_tools = []
|
|
77
|
+
for tool in tools_result.tools:
|
|
78
|
+
chibi_tool_name = f"mcp_{server_name}_{transport}_{tool.name}"
|
|
79
|
+
class_attributes = {
|
|
80
|
+
"register": True,
|
|
81
|
+
"definition": ChatCompletionToolParam(
|
|
82
|
+
type="function",
|
|
83
|
+
function=FunctionDefinition(
|
|
84
|
+
name=chibi_tool_name,
|
|
85
|
+
description=f"MCP Server: {server_name}. {tool.description or ''}",
|
|
86
|
+
parameters=cast(dict[str, object], _clean_schema(tool.inputSchema)),
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
"name": chibi_tool_name,
|
|
90
|
+
"function": staticmethod(create_wrapper(server_name, tool.name, chibi_tool_name)),
|
|
91
|
+
}
|
|
92
|
+
type(f"MCP{server_name.capitalize()}{transport.capitalize()}Tool", (ChibiTool,), class_attributes)
|
|
93
|
+
mcp_tools.append(chibi_tool_name)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Failed to register tools for MCP server '{server_name}': {e}")
|
|
96
|
+
await MCPManager.disconnect(server_name)
|
|
97
|
+
return {"status": "error", "message": f"Failed to register tools: {e}"}
|
|
98
|
+
MCPManager.associate_tools_with_server(server_name=server_name, tool_names=mcp_tools)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"status": "success",
|
|
102
|
+
"message": (
|
|
103
|
+
f"Successfully connected to MCP server '{server_name}'. "
|
|
104
|
+
f"Registered {len(mcp_tools)} tools: {', '.join(mcp_tools)}"
|
|
105
|
+
),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class InitializeStdioMCPServer(ChibiTool):
|
|
110
|
+
register = application_settings.enable_mcp_stdio
|
|
111
|
+
name = "initialize_stdio_mcp_server"
|
|
112
|
+
definition = ChatCompletionToolParam(
|
|
113
|
+
type="function",
|
|
114
|
+
function=FunctionDefinition(
|
|
115
|
+
name="initialize_stdio_mcp_server",
|
|
116
|
+
description="Connect to an MCP server via stdio and register its tools dynamically.",
|
|
117
|
+
parameters={
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"server_name": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Unique name for this server session (e.g., 'sqlite', 'github').",
|
|
123
|
+
},
|
|
124
|
+
"command": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Command to start the MCP server",
|
|
127
|
+
},
|
|
128
|
+
"args": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {"type": "string"},
|
|
131
|
+
"description": "Arguments for the command",
|
|
132
|
+
},
|
|
133
|
+
"env": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"description": "Optional environment variables",
|
|
136
|
+
},
|
|
137
|
+
"secret_envs": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": {"type": "string"},
|
|
140
|
+
"description": (
|
|
141
|
+
"A list of environment variables that need to be passed to the mcp server "
|
|
142
|
+
"without direct access to their contents."
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
"env_file_path": {"type": "string", "description": "Absolute path to the env-file."},
|
|
146
|
+
},
|
|
147
|
+
"required": ["server_name", "command", "args"],
|
|
148
|
+
},
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
async def function(
|
|
154
|
+
cls,
|
|
155
|
+
server_name: str,
|
|
156
|
+
command: str,
|
|
157
|
+
args: list[str],
|
|
158
|
+
env: dict[str, str] | None = None,
|
|
159
|
+
secret_envs: list[str] | None = None,
|
|
160
|
+
env_file_path: str | None = None,
|
|
161
|
+
**kwargs: Any,
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Connect to an MCP server via stdio and register its tools dynamically.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
server_name: Unique name for the session.
|
|
167
|
+
command: Command to start the server.
|
|
168
|
+
args: Command arguments.
|
|
169
|
+
env: Optional environment variables.
|
|
170
|
+
secret_envs: Optional list of environment variable names.
|
|
171
|
+
env_file_path: Optional path to the env file.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
dict[str, Any]: Success or error message.
|
|
175
|
+
"""
|
|
176
|
+
if secret_envs:
|
|
177
|
+
env = env or {}
|
|
178
|
+
for secret_env in secret_envs:
|
|
179
|
+
env[secret_env] = os.environ[secret_env]
|
|
180
|
+
|
|
181
|
+
if env_file_path:
|
|
182
|
+
if not os.path.exists(env_file_path):
|
|
183
|
+
raise ValueError(f"The file {env_file_path} does not exist.")
|
|
184
|
+
env = env or {}
|
|
185
|
+
envs_from_file = dotenv_values(env_file_path)
|
|
186
|
+
envs_with_values_from_file = {k: v for k, v in envs_from_file.items() if v}
|
|
187
|
+
if envs_with_values_from_file:
|
|
188
|
+
env.update(envs_with_values_from_file)
|
|
189
|
+
|
|
190
|
+
session = await MCPManager.connect_stdio(name=server_name, command=command, args=args, env=env)
|
|
191
|
+
return await register_tools_from_mcp_session(mcp_session=session, server_name=server_name, transport="stdio")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class InitializeSseMCPServer(ChibiTool):
|
|
195
|
+
register = application_settings.enable_mcp_sse
|
|
196
|
+
name = "initialize_sse_mcp_server"
|
|
197
|
+
definition = ChatCompletionToolParam(
|
|
198
|
+
type="function",
|
|
199
|
+
function=FunctionDefinition(
|
|
200
|
+
name="initialize_sse_mcp_server",
|
|
201
|
+
description="Connect to an MCP server via SSE and register its tools dynamically.",
|
|
202
|
+
parameters={
|
|
203
|
+
"type": "object",
|
|
204
|
+
"properties": {
|
|
205
|
+
"server_name": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "Unique name for this server session (e.g., 'sqlite', 'github').",
|
|
208
|
+
},
|
|
209
|
+
"url": {"type": "string", "description": "SSE endpoint URL (required for sse)."},
|
|
210
|
+
},
|
|
211
|
+
"required": ["server_name", "url"],
|
|
212
|
+
},
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
async def function(
|
|
218
|
+
cls,
|
|
219
|
+
server_name: str,
|
|
220
|
+
url: str,
|
|
221
|
+
**kwargs: Any,
|
|
222
|
+
) -> dict[str, Any]:
|
|
223
|
+
"""Executes the tool.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
server_name: Unique name for the session.
|
|
227
|
+
url: SSE endpoint URL (sse).
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
dict[str, Any]: Success or error message.
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
session = await MCPManager.connect_sse(server_name, url)
|
|
234
|
+
except Exception:
|
|
235
|
+
raise
|
|
236
|
+
return await register_tools_from_mcp_session(mcp_session=session, server_name=server_name, transport="sse")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class DeinitializeMCPServer(ChibiTool):
|
|
240
|
+
"""Disconnects from an MCP server and removes its tools."""
|
|
241
|
+
|
|
242
|
+
register = application_settings.enable_mcp_stdio or application_settings.enable_mcp_sse
|
|
243
|
+
name = "deinitialize_mcp_server"
|
|
244
|
+
definition = ChatCompletionToolParam(
|
|
245
|
+
type="function",
|
|
246
|
+
function=FunctionDefinition(
|
|
247
|
+
name="deinitialize_mcp_server",
|
|
248
|
+
description="Disconnect from an MCP server and unregister its tools.",
|
|
249
|
+
parameters={
|
|
250
|
+
"type": "object",
|
|
251
|
+
"properties": {"server_name": {"type": "string", "description": "Name of the server to disconnect."}},
|
|
252
|
+
"required": ["server_name"],
|
|
253
|
+
},
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
async def function(cls, server_name: str, **kwargs: Any) -> dict[str, Any]:
|
|
259
|
+
"""Executes the tool.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
server_name: Name of the server to disconnect.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
dict[str, Any]: Success or error message.
|
|
266
|
+
"""
|
|
267
|
+
tools_to_deregister = await MCPManager.disconnect(server_name)
|
|
268
|
+
if tools_to_deregister:
|
|
269
|
+
RegisteredChibiTools.deregister_tools(tools_to_deregister)
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
"status": "success",
|
|
273
|
+
"message": f"Disconnected from MCP server '{server_name}' and removed its tools.",
|
|
274
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Any, Unpack
|
|
2
|
+
|
|
3
|
+
from mcp import ClientSession, StdioServerParameters
|
|
4
|
+
from mcp.client.stdio import stdio_client
|
|
5
|
+
|
|
6
|
+
from chibi.services.providers.tools.tool import ChibiTool
|
|
7
|
+
from chibi.services.providers.tools.utils import AdditionalOptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class McpEchoTool(ChibiTool):
|
|
11
|
+
"""Test/demo only"""
|
|
12
|
+
|
|
13
|
+
register = False
|
|
14
|
+
name = "mcp_echo"
|
|
15
|
+
definition = {
|
|
16
|
+
"type": "function",
|
|
17
|
+
"function": {
|
|
18
|
+
"name": "mcp_echo",
|
|
19
|
+
"description": "Send a message to a local MCP echo server and get the response.",
|
|
20
|
+
"parameters": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {"message": {"type": "string", "description": "The message to echo back"}},
|
|
23
|
+
"required": ["message"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
async def function(cls, message: str, **kwargs: Unpack[AdditionalOptions]) -> dict[str, Any]:
|
|
30
|
+
server_script = """
|
|
31
|
+
import asyncio
|
|
32
|
+
import sys
|
|
33
|
+
from mcp.server.fastmcp import FastMCP
|
|
34
|
+
|
|
35
|
+
mcp = FastMCP("Echo")
|
|
36
|
+
|
|
37
|
+
@mcp.tool()
|
|
38
|
+
def echo(message: str) -> str:
|
|
39
|
+
return f"Echo from MCP: {message}"
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
mcp.run(transport='stdio')
|
|
43
|
+
"""
|
|
44
|
+
import os
|
|
45
|
+
import tempfile
|
|
46
|
+
|
|
47
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
48
|
+
f.write(server_script)
|
|
49
|
+
server_path = f.name
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
server_params = StdioServerParameters(
|
|
53
|
+
command="python",
|
|
54
|
+
args=[server_path],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async with stdio_client(server_params) as (read, write):
|
|
58
|
+
async with ClientSession(read, write) as session:
|
|
59
|
+
await session.initialize()
|
|
60
|
+
result = await session.call_tool("echo", arguments={"message": message})
|
|
61
|
+
|
|
62
|
+
if result.content and len(result.content) > 0:
|
|
63
|
+
content_item = result.content[0]
|
|
64
|
+
if content_item.type == "text":
|
|
65
|
+
return {"response": content_item.text}
|
|
66
|
+
return {"response": "No content returned"}
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return {"error": f"MCP Error: {str(e)}"}
|
|
70
|
+
finally:
|
|
71
|
+
if os.path.exists(server_path):
|
|
72
|
+
os.unlink(server_path)
|