daita-agents 0.1.0__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.

Potentially problematic release.


This version of daita-agents might be problematic. Click here for more details.

Files changed (69) hide show
  1. daita/__init__.py +208 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +722 -0
  4. daita/agents/substrate.py +895 -0
  5. daita/cli/__init__.py +145 -0
  6. daita/cli/__main__.py +7 -0
  7. daita/cli/ascii_art.py +44 -0
  8. daita/cli/core/__init__.py +0 -0
  9. daita/cli/core/create.py +254 -0
  10. daita/cli/core/deploy.py +473 -0
  11. daita/cli/core/deployments.py +309 -0
  12. daita/cli/core/import_detector.py +219 -0
  13. daita/cli/core/init.py +382 -0
  14. daita/cli/core/logs.py +239 -0
  15. daita/cli/core/managed_deploy.py +709 -0
  16. daita/cli/core/run.py +648 -0
  17. daita/cli/core/status.py +421 -0
  18. daita/cli/core/test.py +239 -0
  19. daita/cli/core/webhooks.py +172 -0
  20. daita/cli/main.py +588 -0
  21. daita/cli/utils.py +541 -0
  22. daita/config/__init__.py +62 -0
  23. daita/config/base.py +159 -0
  24. daita/config/settings.py +184 -0
  25. daita/core/__init__.py +262 -0
  26. daita/core/decision_tracing.py +701 -0
  27. daita/core/exceptions.py +480 -0
  28. daita/core/focus.py +251 -0
  29. daita/core/interfaces.py +76 -0
  30. daita/core/plugin_tracing.py +550 -0
  31. daita/core/relay.py +695 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +444 -0
  34. daita/core/tools.py +402 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1084 -0
  37. daita/display/__init__.py +1 -0
  38. daita/display/console.py +160 -0
  39. daita/execution/__init__.py +58 -0
  40. daita/execution/client.py +856 -0
  41. daita/execution/exceptions.py +92 -0
  42. daita/execution/models.py +317 -0
  43. daita/llm/__init__.py +60 -0
  44. daita/llm/anthropic.py +166 -0
  45. daita/llm/base.py +373 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +152 -0
  48. daita/llm/grok.py +114 -0
  49. daita/llm/mock.py +135 -0
  50. daita/llm/openai.py +109 -0
  51. daita/plugins/__init__.py +141 -0
  52. daita/plugins/base.py +37 -0
  53. daita/plugins/base_db.py +167 -0
  54. daita/plugins/elasticsearch.py +844 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +510 -0
  57. daita/plugins/mysql.py +351 -0
  58. daita/plugins/postgresql.py +331 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +529 -0
  61. daita/plugins/s3.py +761 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.1.0.dist-info/METADATA +350 -0
  65. daita_agents-0.1.0.dist-info/RECORD +69 -0
  66. daita_agents-0.1.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
  69. daita_agents-0.1.0.dist-info/top_level.txt +1 -0
daita/plugins/mysql.py ADDED
@@ -0,0 +1,351 @@
1
+ """
2
+ MySQL plugin for Daita Agents.
3
+
4
+ Simple MySQL connection and querying - no over-engineering.
5
+ """
6
+ import logging
7
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
8
+ from .base_db import BaseDatabasePlugin
9
+
10
+ if TYPE_CHECKING:
11
+ from ..core.tools import AgentTool
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class MySQLPlugin(BaseDatabasePlugin):
16
+ """
17
+ MySQL plugin for agents with standardized connection management.
18
+
19
+ Inherits common database functionality from BaseDatabasePlugin.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ host: str = "localhost",
25
+ port: int = 3306,
26
+ database: str = "",
27
+ username: str = "",
28
+ password: str = "",
29
+ connection_string: Optional[str] = None,
30
+ **kwargs
31
+ ):
32
+ """
33
+ Initialize MySQL connection.
34
+
35
+ Args:
36
+ host: Database host
37
+ port: Database port
38
+ database: Database name
39
+ username: Username
40
+ password: Password
41
+ connection_string: Full connection string (overrides individual params)
42
+ **kwargs: Additional aiomysql parameters
43
+ """
44
+ if connection_string:
45
+ self.connection_string = connection_string
46
+ else:
47
+ self.connection_string = f"mysql://{username}:{password}@{host}:{port}/{database}"
48
+
49
+ # Store connection parameters for aiomysql
50
+ self.host = host
51
+ self.port = port
52
+ self.user = username
53
+ self.password = password
54
+ self.db = database
55
+
56
+ self.pool_config = {
57
+ 'minsize': kwargs.get('min_size', 1),
58
+ 'maxsize': kwargs.get('max_size', 10),
59
+ 'charset': kwargs.get('charset', 'utf8mb4'),
60
+ 'autocommit': kwargs.get('autocommit', True),
61
+ }
62
+
63
+ # Initialize base class with all config
64
+ super().__init__(
65
+ host=host, port=port, database=database,
66
+ username=username, connection_string=connection_string,
67
+ **kwargs
68
+ )
69
+
70
+ logger.debug(f"MySQL plugin configured for {host}:{port}/{database}")
71
+
72
+ async def connect(self):
73
+ """Connect to MySQL database."""
74
+ if self._pool is not None:
75
+ return # Already connected
76
+
77
+ try:
78
+ import aiomysql
79
+ self._pool = await aiomysql.create_pool(
80
+ host=self.host,
81
+ port=self.port,
82
+ user=self.user,
83
+ password=self.password,
84
+ db=self.db,
85
+ **self.pool_config
86
+ )
87
+ logger.info("Connected to MySQL")
88
+ except ImportError:
89
+ self._handle_connection_error(
90
+ ImportError("aiomysql not installed. Run: pip install aiomysql"),
91
+ "connection"
92
+ )
93
+ except Exception as e:
94
+ self._handle_connection_error(e, "connection")
95
+
96
+ async def disconnect(self):
97
+ """Disconnect from the database."""
98
+ if self._pool:
99
+ self._pool.close()
100
+ await self._pool.wait_closed()
101
+ self._pool = None
102
+ logger.info("Disconnected from MySQL")
103
+
104
+ async def query(self, sql: str, params: Optional[List] = None) -> List[Dict[str, Any]]:
105
+ """
106
+ Run a SELECT query and return results.
107
+
108
+ Args:
109
+ sql: SQL query with %s placeholders
110
+ params: List of parameters for the query
111
+
112
+ Returns:
113
+ List of rows as dictionaries
114
+
115
+ Example:
116
+ results = await db.query("SELECT * FROM users WHERE age > %s", [25])
117
+ """
118
+ # Only auto-connect if pool is None - allows manual mocking
119
+ if self._pool is None:
120
+ await self.connect()
121
+
122
+ async with self._pool.acquire() as conn:
123
+ async with conn.cursor() as cursor:
124
+ if params:
125
+ await cursor.execute(sql, params)
126
+ else:
127
+ await cursor.execute(sql)
128
+
129
+ rows = await cursor.fetchall()
130
+ columns = [desc[0] for desc in cursor.description]
131
+
132
+ return [dict(zip(columns, row)) for row in rows]
133
+
134
+ async def execute(self, sql: str, params: Optional[List] = None) -> int:
135
+ """
136
+ Execute INSERT/UPDATE/DELETE and return affected rows.
137
+
138
+ Args:
139
+ sql: SQL statement
140
+ params: List of parameters
141
+
142
+ Returns:
143
+ Number of affected rows
144
+ """
145
+ # Only auto-connect if pool is None - allows manual mocking
146
+ if self._pool is None:
147
+ await self.connect()
148
+
149
+ async with self._pool.acquire() as conn:
150
+ async with conn.cursor() as cursor:
151
+ if params:
152
+ await cursor.execute(sql, params)
153
+ else:
154
+ await cursor.execute(sql)
155
+
156
+ return cursor.rowcount
157
+
158
+ async def insert_many(self, table: str, data: List[Dict[str, Any]]) -> int:
159
+ """
160
+ Bulk insert data into a table.
161
+
162
+ Args:
163
+ table: Table name
164
+ data: List of dictionaries to insert
165
+
166
+ Returns:
167
+ Number of rows inserted
168
+ """
169
+ if not data:
170
+ return 0
171
+
172
+ # Only auto-connect if pool is None - allows manual mocking
173
+ if self._pool is None:
174
+ await self.connect()
175
+
176
+ # Get columns from first row
177
+ columns = list(data[0].keys())
178
+ placeholders = ', '.join(['%s'] * len(columns))
179
+
180
+ sql = f"INSERT INTO {table} (`{'`, `'.join(columns)}`) VALUES ({placeholders})"
181
+
182
+ # Convert to list of tuples for executemany
183
+ rows = [tuple(row[col] for col in columns) for row in data]
184
+
185
+ async with self._pool.acquire() as conn:
186
+ async with conn.cursor() as cursor:
187
+ await cursor.executemany(sql, rows)
188
+ return cursor.rowcount
189
+
190
+ async def tables(self) -> List[str]:
191
+ """List all tables in the database."""
192
+ sql = """
193
+ SELECT TABLE_NAME as table_name
194
+ FROM INFORMATION_SCHEMA.TABLES
195
+ WHERE TABLE_TYPE = 'BASE TABLE'
196
+ AND TABLE_SCHEMA = DATABASE()
197
+ ORDER BY TABLE_NAME
198
+ """
199
+ results = await self.query(sql)
200
+ return [row['table_name'] for row in results]
201
+
202
+ async def describe(self, table: str) -> List[Dict[str, Any]]:
203
+ """Get table schema information."""
204
+ sql = """
205
+ SELECT
206
+ COLUMN_NAME as column_name,
207
+ DATA_TYPE as data_type,
208
+ IS_NULLABLE as is_nullable,
209
+ COLUMN_DEFAULT as column_default,
210
+ COLUMN_TYPE as column_type
211
+ FROM INFORMATION_SCHEMA.COLUMNS
212
+ WHERE TABLE_NAME = %s
213
+ AND TABLE_SCHEMA = DATABASE()
214
+ ORDER BY ORDINAL_POSITION
215
+ """
216
+ return await self.query(sql, [table])
217
+
218
+ def get_tools(self) -> List['AgentTool']:
219
+ """
220
+ Expose MySQL operations as agent tools.
221
+
222
+ Returns:
223
+ List of AgentTool instances for database operations
224
+ """
225
+ from ..core.tools import AgentTool
226
+
227
+ return [
228
+ AgentTool(
229
+ name="query_database",
230
+ description="Execute a SQL SELECT query on the MySQL database and return results as a list of dictionaries",
231
+ parameters={
232
+ "sql": {
233
+ "type": "string",
234
+ "description": "SQL SELECT query with %s placeholders for parameters",
235
+ "required": True
236
+ },
237
+ "params": {
238
+ "type": "array",
239
+ "description": "Optional list of parameter values for query placeholders",
240
+ "items": {"type": "string"},
241
+ "required": False
242
+ }
243
+ },
244
+ handler=self._tool_query,
245
+ category="database",
246
+ source="plugin",
247
+ plugin_name="MySQL",
248
+ timeout_seconds=60
249
+ ),
250
+ AgentTool(
251
+ name="list_tables",
252
+ description="List all tables in the MySQL database",
253
+ parameters={},
254
+ handler=self._tool_list_tables,
255
+ category="database",
256
+ source="plugin",
257
+ plugin_name="MySQL",
258
+ timeout_seconds=30
259
+ ),
260
+ AgentTool(
261
+ name="get_table_schema",
262
+ description="Get column information (name, data type, nullable) for a specific table in MySQL",
263
+ parameters={
264
+ "table_name": {
265
+ "type": "string",
266
+ "description": "Name of the table to inspect",
267
+ "required": True
268
+ }
269
+ },
270
+ handler=self._tool_get_schema,
271
+ category="database",
272
+ source="plugin",
273
+ plugin_name="MySQL",
274
+ timeout_seconds=30
275
+ ),
276
+ AgentTool(
277
+ name="execute_sql",
278
+ description="Execute an INSERT, UPDATE, or DELETE SQL statement on MySQL. Returns the number of affected rows.",
279
+ parameters={
280
+ "sql": {
281
+ "type": "string",
282
+ "description": "SQL statement to execute (INSERT, UPDATE, or DELETE)",
283
+ "required": True
284
+ },
285
+ "params": {
286
+ "type": "array",
287
+ "description": "Optional list of parameter values for statement placeholders",
288
+ "items": {"type": "string"},
289
+ "required": False
290
+ }
291
+ },
292
+ handler=self._tool_execute,
293
+ category="database",
294
+ source="plugin",
295
+ plugin_name="MySQL",
296
+ timeout_seconds=60
297
+ )
298
+ ]
299
+
300
+ async def _tool_query(self, args: Dict[str, Any]) -> Dict[str, Any]:
301
+ """Tool handler for query_database"""
302
+ sql = args.get("sql")
303
+ params = args.get("params")
304
+
305
+ results = await self.query(sql, params)
306
+
307
+ return {
308
+ "success": True,
309
+ "rows": results,
310
+ "row_count": len(results)
311
+ }
312
+
313
+ async def _tool_list_tables(self, args: Dict[str, Any]) -> Dict[str, Any]:
314
+ """Tool handler for list_tables"""
315
+ tables = await self.tables()
316
+
317
+ return {
318
+ "success": True,
319
+ "tables": tables,
320
+ "count": len(tables)
321
+ }
322
+
323
+ async def _tool_get_schema(self, args: Dict[str, Any]) -> Dict[str, Any]:
324
+ """Tool handler for get_table_schema"""
325
+ table_name = args.get("table_name")
326
+
327
+ columns = await self.describe(table_name)
328
+
329
+ return {
330
+ "success": True,
331
+ "table": table_name,
332
+ "columns": columns,
333
+ "column_count": len(columns)
334
+ }
335
+
336
+ async def _tool_execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
337
+ """Tool handler for execute_sql"""
338
+ sql = args.get("sql")
339
+ params = args.get("params")
340
+
341
+ affected_rows = await self.execute(sql, params)
342
+
343
+ return {
344
+ "success": True,
345
+ "affected_rows": affected_rows
346
+ }
347
+
348
+
349
+ def mysql(**kwargs) -> MySQLPlugin:
350
+ """Create MySQL plugin with simplified interface."""
351
+ return MySQLPlugin(**kwargs)
@@ -0,0 +1,331 @@
1
+ """
2
+ PostgreSQL plugin for Daita Agents.
3
+
4
+ Simple database connection and querying - no over-engineering.
5
+ """
6
+ import logging
7
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
8
+ from .base_db import BaseDatabasePlugin
9
+
10
+ if TYPE_CHECKING:
11
+ from ..core.tools import AgentTool
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class PostgreSQLPlugin(BaseDatabasePlugin):
16
+ """
17
+ PostgreSQL plugin for agents with standardized connection management.
18
+
19
+ Inherits common database functionality from BaseDatabasePlugin.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ host: str = "localhost",
25
+ port: int = 5432,
26
+ database: str = "",
27
+ username: str = "",
28
+ user: Optional[str] = None, # Add this
29
+ password: str = "",
30
+ connection_string: Optional[str] = None,
31
+ **kwargs
32
+ ):
33
+ """
34
+ Initialize PostgreSQL connection.
35
+
36
+ Args:
37
+ host: Database host
38
+ port: Database port
39
+ database: Database name
40
+ username: Username
41
+ user: Username (alias for username)
42
+ password: Password
43
+ connection_string: Full connection string (overrides individual params)
44
+ **kwargs: Additional asyncpg parameters
45
+ """
46
+ # Use 'user' parameter as alias for 'username' if provided
47
+ effective_username = user if user is not None else username
48
+
49
+ # Build connection string
50
+ if connection_string:
51
+ self.connection_string = connection_string
52
+ else:
53
+ self.connection_string = f"postgresql://{effective_username}:{password}@{host}:{port}/{database}"
54
+
55
+ # PostgreSQL-specific pool configuration
56
+ self.pool_config = {
57
+ 'min_size': kwargs.get('min_size', 1),
58
+ 'max_size': kwargs.get('max_size', 10),
59
+ 'command_timeout': kwargs.get('command_timeout', 60),
60
+ 'statement_cache_size': kwargs.get('statement_cache_size', 0), # Set to 0 for pgbouncer compatibility
61
+ }
62
+
63
+ # Initialize base class with all config
64
+ super().__init__(
65
+ host=host, port=port, database=database,
66
+ username=effective_username, connection_string=connection_string,
67
+ **kwargs
68
+ )
69
+
70
+ logger.debug(f"PostgreSQL plugin configured for {host}:{port}/{database}")
71
+
72
+ async def connect(self):
73
+ """Connect to PostgreSQL database."""
74
+ if self._pool is not None:
75
+ return # Already connected
76
+
77
+ try:
78
+ import asyncpg
79
+ self._pool = await asyncpg.create_pool(
80
+ self.connection_string,
81
+ **self.pool_config
82
+ )
83
+ logger.info("Connected to PostgreSQL")
84
+ except ImportError:
85
+ self._handle_connection_error(
86
+ ImportError("asyncpg not installed. Run: pip install asyncpg"),
87
+ "connection"
88
+ )
89
+ except Exception as e:
90
+ self._handle_connection_error(e, "connection")
91
+
92
+ async def disconnect(self):
93
+ """Disconnect from PostgreSQL database."""
94
+ if self._pool:
95
+ await self._pool.close()
96
+ self._pool = None
97
+ logger.info("Disconnected from PostgreSQL")
98
+
99
+ async def query(self, sql: str, params: Optional[List] = None) -> List[Dict[str, Any]]:
100
+ """
101
+ Run a SELECT query and return results.
102
+
103
+ Args:
104
+ sql: SQL query with $1, $2, etc. placeholders
105
+ params: List of parameters for the query
106
+
107
+ Returns:
108
+ List of rows as dictionaries
109
+
110
+ Example:
111
+ results = await db.query("SELECT * FROM users WHERE age > $1", [25])
112
+ """
113
+ # Only auto-connect if pool is None - allows manual mocking
114
+ if self._pool is None:
115
+ await self.connect()
116
+
117
+ async with self._pool.acquire() as conn:
118
+ if params:
119
+ rows = await conn.fetch(sql, *params)
120
+ else:
121
+ rows = await conn.fetch(sql)
122
+
123
+ return [dict(row) for row in rows]
124
+
125
+ async def execute(self, sql: str, params: Optional[List] = None) -> int:
126
+ """
127
+ Execute INSERT/UPDATE/DELETE and return affected rows.
128
+
129
+ Args:
130
+ sql: SQL statement
131
+ params: List of parameters
132
+
133
+ Returns:
134
+ Number of affected rows
135
+ """
136
+ # Only auto-connect if pool is None - allows manual mocking
137
+ if self._pool is None:
138
+ await self.connect()
139
+
140
+ async with self._pool.acquire() as conn:
141
+ if params:
142
+ result = await conn.execute(sql, *params)
143
+ else:
144
+ result = await conn.execute(sql)
145
+
146
+ # Extract number from result like "INSERT 0 5"
147
+ return int(result.split()[-1]) if result else 0
148
+
149
+ async def insert_many(self, table: str, data: List[Dict[str, Any]]) -> int:
150
+ """
151
+ Bulk insert data into a table.
152
+
153
+ Args:
154
+ table: Table name
155
+ data: List of dictionaries to insert
156
+
157
+ Returns:
158
+ Number of rows inserted
159
+ """
160
+ if not data:
161
+ return 0
162
+
163
+ # Only auto-connect if pool is None - allows manual mocking
164
+ if self._pool is None:
165
+ await self.connect()
166
+
167
+ # Get columns from first row
168
+ columns = list(data[0].keys())
169
+ placeholders = ', '.join([f'${i+1}' for i in range(len(columns))])
170
+
171
+ sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders})"
172
+
173
+ # Convert to list of tuples for executemany
174
+ rows = [[row[col] for col in columns] for row in data]
175
+
176
+ async with self._pool.acquire() as conn:
177
+ await conn.executemany(sql, rows)
178
+
179
+ return len(data)
180
+
181
+ async def tables(self) -> List[str]:
182
+ """List all tables in the database."""
183
+ sql = """
184
+ SELECT table_name
185
+ FROM information_schema.tables
186
+ WHERE table_schema = 'public'
187
+ ORDER BY table_name
188
+ """
189
+ results = await self.query(sql)
190
+ return [row['table_name'] for row in results]
191
+
192
+ def get_tools(self) -> List['AgentTool']:
193
+ """
194
+ Expose PostgreSQL operations as agent tools.
195
+
196
+ Returns:
197
+ List of AgentTool instances for database operations
198
+ """
199
+ from ..core.tools import AgentTool
200
+
201
+ return [
202
+ AgentTool(
203
+ name="query_database",
204
+ description="Execute a SQL SELECT query on the PostgreSQL database and return results as a list of dictionaries",
205
+ parameters={
206
+ "sql": {
207
+ "type": "string",
208
+ "description": "SQL SELECT query with $1, $2, etc. placeholders for parameters",
209
+ "required": True
210
+ },
211
+ "params": {
212
+ "type": "array",
213
+ "description": "Optional list of parameter values for query placeholders",
214
+ "items": {"type": "string"},
215
+ "required": False
216
+ }
217
+ },
218
+ handler=self._tool_query,
219
+ category="database",
220
+ source="plugin",
221
+ plugin_name="PostgreSQL",
222
+ timeout_seconds=60
223
+ ),
224
+ AgentTool(
225
+ name="list_tables",
226
+ description="List all tables in the PostgreSQL database",
227
+ parameters={},
228
+ handler=self._tool_list_tables,
229
+ category="database",
230
+ source="plugin",
231
+ plugin_name="PostgreSQL",
232
+ timeout_seconds=30
233
+ ),
234
+ AgentTool(
235
+ name="get_table_schema",
236
+ description="Get column information (name, data type, nullable) for a specific table in PostgreSQL",
237
+ parameters={
238
+ "table_name": {
239
+ "type": "string",
240
+ "description": "Name of the table to inspect",
241
+ "required": True
242
+ }
243
+ },
244
+ handler=self._tool_get_schema,
245
+ category="database",
246
+ source="plugin",
247
+ plugin_name="PostgreSQL",
248
+ timeout_seconds=30
249
+ ),
250
+ AgentTool(
251
+ name="execute_sql",
252
+ description="Execute an INSERT, UPDATE, or DELETE SQL statement on PostgreSQL. Returns the number of affected rows.",
253
+ parameters={
254
+ "sql": {
255
+ "type": "string",
256
+ "description": "SQL statement to execute (INSERT, UPDATE, or DELETE)",
257
+ "required": True
258
+ },
259
+ "params": {
260
+ "type": "array",
261
+ "description": "Optional list of parameter values for statement placeholders",
262
+ "items": {"type": "string"},
263
+ "required": False
264
+ }
265
+ },
266
+ handler=self._tool_execute,
267
+ category="database",
268
+ source="plugin",
269
+ plugin_name="PostgreSQL",
270
+ timeout_seconds=60
271
+ )
272
+ ]
273
+
274
+ async def _tool_query(self, args: Dict[str, Any]) -> Dict[str, Any]:
275
+ """Tool handler for query_database"""
276
+ sql = args.get("sql")
277
+ params = args.get("params")
278
+
279
+ results = await self.query(sql, params)
280
+
281
+ return {
282
+ "success": True,
283
+ "rows": results,
284
+ "row_count": len(results)
285
+ }
286
+
287
+ async def _tool_list_tables(self, args: Dict[str, Any]) -> Dict[str, Any]:
288
+ """Tool handler for list_tables"""
289
+ tables = await self.tables()
290
+
291
+ return {
292
+ "success": True,
293
+ "tables": tables,
294
+ "count": len(tables)
295
+ }
296
+
297
+ async def _tool_get_schema(self, args: Dict[str, Any]) -> Dict[str, Any]:
298
+ """Tool handler for get_table_schema"""
299
+ table_name = args.get("table_name")
300
+
301
+ schema_query = """
302
+ SELECT column_name, data_type, is_nullable
303
+ FROM information_schema.columns
304
+ WHERE table_name = $1
305
+ ORDER BY ordinal_position
306
+ """
307
+
308
+ columns = await self.query(schema_query, [table_name])
309
+
310
+ return {
311
+ "success": True,
312
+ "table": table_name,
313
+ "columns": columns,
314
+ "column_count": len(columns)
315
+ }
316
+
317
+ async def _tool_execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
318
+ """Tool handler for execute_sql"""
319
+ sql = args.get("sql")
320
+ params = args.get("params")
321
+
322
+ affected_rows = await self.execute(sql, params)
323
+
324
+ return {
325
+ "success": True,
326
+ "affected_rows": affected_rows
327
+ }
328
+
329
+ def postgresql(**kwargs) -> PostgreSQLPlugin:
330
+ """Create PostgreSQL plugin with simplified interface."""
331
+ return PostgreSQLPlugin(**kwargs)