dbagent-cli 0.9.7__tar.gz → 0.9.9__tar.gz
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.
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/PKG-INFO +1 -1
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/__init__.py +1 -1
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/generator.py +4 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/pipeline.py +14 -1
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/cli.py +45 -7
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/ground_truth.py +5 -3
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/ui/chat_screen.py +8 -1
- dbagent_cli-0.9.9/dbagent/ui/live_streamer.py +117 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/PKG-INFO +1 -1
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/SOURCES.txt +1 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/pyproject.toml +1 -1
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_cli.py +19 -2
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/README.md +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/confidence.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/conversation.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/doctor.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/error_classifier.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/recommender.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/agent/validator.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/config.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/connectors/base.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/connectors/factory.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/connectors/mongo.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/connectors/relational.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/__init__.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/cache.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/changeset.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/cost.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/recovery.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/sandbox.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/execution/streaming.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/anthropic_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/auto_setup.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/base.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/deepseek_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/factory.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/gemini_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/groq_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/mistral_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/mock_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/ollama_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/openai_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/llm/openrouter_provider.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/cache.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/formatter.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/graph.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/models.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/retriever.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/schema/selector.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/__init__.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/audit.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/environment.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/masking.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/modes.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/security/rbac.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/semantic/__init__.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/semantic/dictionary.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/semantic/knowledge.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/semantic/templates.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/sql/__init__.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/sql/optimizer.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/sql/validator.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/ui/console.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/ui/grid_window.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/ui/viewer.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent/ui/visualizer.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/dependency_links.txt +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/entry_points.txt +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/requires.txt +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/dbagent_cli.egg-info/top_level.txt +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/setup.cfg +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_cache.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_connectors.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_direct_sql.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_doctor_and_viz.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_generator.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_graph.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_knowledge.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_providers.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_recovery.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_safety.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_sandbox.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_schema.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_security.py +0 -0
- {dbagent_cli-0.9.7 → dbagent_cli-0.9.9}/tests/test_semantic.py +0 -0
|
@@ -247,6 +247,7 @@ class ScriptGenerator:
|
|
|
247
247
|
user_prompt: str,
|
|
248
248
|
conversation_history: Optional[str] = None,
|
|
249
249
|
model: Optional[str] = None,
|
|
250
|
+
stream_callback: Optional[Callable[[str], None]] = None,
|
|
250
251
|
) -> str:
|
|
251
252
|
"""Generate a natural conversational response to the user's message with full session memory."""
|
|
252
253
|
table_names = [t.name for t in schema.tables]
|
|
@@ -268,6 +269,7 @@ class ScriptGenerator:
|
|
|
268
269
|
prompt=full_prompt,
|
|
269
270
|
system_prompt=system_prompt,
|
|
270
271
|
model=model,
|
|
272
|
+
stream_callback=stream_callback,
|
|
271
273
|
)
|
|
272
274
|
|
|
273
275
|
def generate_analysis_response(
|
|
@@ -276,6 +278,7 @@ class ScriptGenerator:
|
|
|
276
278
|
user_prompt: str,
|
|
277
279
|
conversation_history: Optional[str] = None,
|
|
278
280
|
model: Optional[str] = None,
|
|
281
|
+
stream_callback: Optional[Callable[[str], None]] = None,
|
|
279
282
|
) -> str:
|
|
280
283
|
"""Generate a comprehensive architectural analysis report with ASCII diagrams, theory, and SQL queries."""
|
|
281
284
|
# 1. Filter relevant tables including foreign keys and junction tables
|
|
@@ -301,6 +304,7 @@ class ScriptGenerator:
|
|
|
301
304
|
prompt=full_prompt,
|
|
302
305
|
system_prompt=system_prompt,
|
|
303
306
|
model=model,
|
|
307
|
+
stream_callback=stream_callback,
|
|
304
308
|
)
|
|
305
309
|
|
|
306
310
|
# --- Full generation (for `generate` command) ---
|
|
@@ -56,6 +56,7 @@ class PipelineResult:
|
|
|
56
56
|
# Conversational chat response
|
|
57
57
|
is_chat: bool = False
|
|
58
58
|
is_direct_sql: bool = False
|
|
59
|
+
was_streamed: bool = False
|
|
59
60
|
message: str = ""
|
|
60
61
|
# Query classification
|
|
61
62
|
query_type: str = "unknown" # read, write, ddl, chat, unknown
|
|
@@ -192,7 +193,13 @@ class QueryPipeline:
|
|
|
192
193
|
retriever = ContextRetriever(targeted)
|
|
193
194
|
return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=12)
|
|
194
195
|
|
|
195
|
-
def run(
|
|
196
|
+
def run(
|
|
197
|
+
self,
|
|
198
|
+
user_prompt: str,
|
|
199
|
+
force: bool = False,
|
|
200
|
+
bypass_cache: bool = False,
|
|
201
|
+
stream_callback: Optional[Callable[[str], None]] = None,
|
|
202
|
+
) -> PipelineResult:
|
|
196
203
|
"""
|
|
197
204
|
Executes end-to-end pipeline: Prompt -> Security/Semantics -> SQL -> Safe Exec -> Results.
|
|
198
205
|
"""
|
|
@@ -217,7 +224,10 @@ class QueryPipeline:
|
|
|
217
224
|
user_prompt=user_prompt,
|
|
218
225
|
conversation_history=conv_history,
|
|
219
226
|
model=self.model,
|
|
227
|
+
stream_callback=stream_callback,
|
|
220
228
|
)
|
|
229
|
+
if stream_callback:
|
|
230
|
+
result.was_streamed = True
|
|
221
231
|
result.is_chat = True
|
|
222
232
|
result.was_executed = True
|
|
223
233
|
result.query_type = "chat"
|
|
@@ -273,7 +283,10 @@ class QueryPipeline:
|
|
|
273
283
|
user_prompt=analysis_prompt,
|
|
274
284
|
conversation_history=conv_history,
|
|
275
285
|
model=self.model,
|
|
286
|
+
stream_callback=stream_callback,
|
|
276
287
|
)
|
|
288
|
+
if stream_callback:
|
|
289
|
+
result.was_streamed = True
|
|
277
290
|
result.ground_truth = SchemaGroundTruthValidator.validate_response(result.message, sub_schema)
|
|
278
291
|
result.is_chat = True
|
|
279
292
|
result.was_executed = True
|
|
@@ -339,7 +339,10 @@ def _display_pipeline_result(result: PipelineResult, show_sql: bool = True) -> N
|
|
|
339
339
|
|
|
340
340
|
if result.is_chat and result.message:
|
|
341
341
|
console.print("")
|
|
342
|
-
|
|
342
|
+
if not result.was_streamed:
|
|
343
|
+
console.print(Markdown(result.message))
|
|
344
|
+
if result.ground_truth:
|
|
345
|
+
console.print(result.ground_truth.format_summary())
|
|
343
346
|
if result.execution_time_ms > 0:
|
|
344
347
|
print_timing(result.execution_time_ms)
|
|
345
348
|
console.print("")
|
|
@@ -527,8 +530,26 @@ def ask_command(
|
|
|
527
530
|
choice_callback=_choice_callback,
|
|
528
531
|
)
|
|
529
532
|
|
|
530
|
-
|
|
531
|
-
|
|
533
|
+
from dbagent.ui.live_streamer import LiveMarkdownStreamer
|
|
534
|
+
streamer = LiveMarkdownStreamer(console)
|
|
535
|
+
spinner = DynamicSpinner.for_prompt(prompt, model_name=model or getattr(llm, "default_model", None), connector=connector)
|
|
536
|
+
stream_started = [False]
|
|
537
|
+
|
|
538
|
+
def _query_stream_callback(chunk: str) -> None:
|
|
539
|
+
if not stream_started[0]:
|
|
540
|
+
stream_started[0] = True
|
|
541
|
+
spinner.pause()
|
|
542
|
+
timing_str = f"⚡ {spinner.elapsed_ms / 1000:.2f}s" if spinner.elapsed_ms > 0 else ""
|
|
543
|
+
active_model = model or getattr(llm, "default_model", None)
|
|
544
|
+
model_display = f" ({active_model})" if active_model else ""
|
|
545
|
+
console.print(f"\n[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
|
|
546
|
+
streamer.feed(chunk)
|
|
547
|
+
|
|
548
|
+
with spinner:
|
|
549
|
+
result = pipeline.run(user_prompt=prompt, force=force, bypass_cache=no_cache, stream_callback=_query_stream_callback)
|
|
550
|
+
|
|
551
|
+
if stream_started[0]:
|
|
552
|
+
streamer.finalize()
|
|
532
553
|
|
|
533
554
|
_display_pipeline_result(result, show_sql=True)
|
|
534
555
|
connector.close()
|
|
@@ -1094,14 +1115,31 @@ def chat_command(
|
|
|
1094
1115
|
console.print("[bold cyan]Goodbye![/bold cyan]")
|
|
1095
1116
|
break
|
|
1096
1117
|
|
|
1097
|
-
# --- Natural language query via pipeline ---
|
|
1098
|
-
# Update pipeline settings in-place (pipeline was created once before the loop)
|
|
1118
|
+
# --- Natural language query via pipeline with live streaming ---
|
|
1099
1119
|
pipeline.model = model
|
|
1100
1120
|
pipeline.mode = current_mode
|
|
1101
1121
|
pipeline.auto_execute = auto_execute
|
|
1102
1122
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1123
|
+
from dbagent.ui.live_streamer import LiveMarkdownStreamer
|
|
1124
|
+
streamer = LiveMarkdownStreamer(console)
|
|
1125
|
+
spinner = DynamicSpinner.for_prompt(user_input, model_name=model or getattr(llm, "default_model", None), connector=connector)
|
|
1126
|
+
stream_started = [False]
|
|
1127
|
+
|
|
1128
|
+
def _live_stream_callback(chunk: str) -> None:
|
|
1129
|
+
if not stream_started[0]:
|
|
1130
|
+
stream_started[0] = True
|
|
1131
|
+
spinner.pause()
|
|
1132
|
+
timing_str = f"⚡ {spinner.elapsed_ms / 1000:.2f}s" if spinner.elapsed_ms > 0 else ""
|
|
1133
|
+
active_model = model or getattr(llm, "default_model", None)
|
|
1134
|
+
model_display = f" ({active_model})" if active_model else ""
|
|
1135
|
+
console.print(f"\n[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
|
|
1136
|
+
streamer.feed(chunk)
|
|
1137
|
+
|
|
1138
|
+
with spinner:
|
|
1139
|
+
result = pipeline.run(user_prompt=user_input, stream_callback=_live_stream_callback)
|
|
1140
|
+
|
|
1141
|
+
if stream_started[0]:
|
|
1142
|
+
streamer.finalize()
|
|
1105
1143
|
|
|
1106
1144
|
render_assistant_turn(
|
|
1107
1145
|
result,
|
|
@@ -85,17 +85,19 @@ class SchemaGroundTruthValidator:
|
|
|
85
85
|
if fk_desc not in result.verified_fks:
|
|
86
86
|
result.verified_fks.append(fk_desc)
|
|
87
87
|
|
|
88
|
-
# 2. Extract and validate SQL queries against physical schema
|
|
89
|
-
sql_blocks = re.findall(r'```(?:sql)
|
|
88
|
+
# 2. Extract and validate only actual SQL queries against physical schema
|
|
89
|
+
sql_blocks = re.findall(r'```(?:sql)\s*([\s\S]*?)\s*```', response_text, re.IGNORECASE)
|
|
90
90
|
result.sql_queries_checked = len(sql_blocks)
|
|
91
91
|
sql_referenced_tables = set()
|
|
92
|
+
sql_ignore_keywords = {"via", "lateral", "on", "using", "where", "select", "table", "as", "and", "or", "values"}
|
|
92
93
|
|
|
93
94
|
for block in sql_blocks:
|
|
94
95
|
# Extract FROM and JOIN table references
|
|
95
96
|
matches = re.findall(r'\b(?:FROM|JOIN)\s+([a-zA-Z0-9_]+)', block, re.IGNORECASE)
|
|
96
97
|
for m in matches:
|
|
97
98
|
m_clean = m.strip().lower()
|
|
98
|
-
|
|
99
|
+
if m_clean not in sql_ignore_keywords:
|
|
100
|
+
sql_referenced_tables.add(m_clean)
|
|
99
101
|
|
|
100
102
|
for sql_tbl in sql_referenced_tables:
|
|
101
103
|
if sql_tbl in schema_table_map:
|
|
@@ -114,8 +114,15 @@ def render_assistant_turn(
|
|
|
114
114
|
model_display = f" ({ai_model})" if ai_model else ""
|
|
115
115
|
agent_title = f"[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]"
|
|
116
116
|
|
|
117
|
-
# Conversational Chat Response
|
|
117
|
+
# Conversational Chat / Architectural Analysis Response
|
|
118
118
|
if result.is_chat and result.message:
|
|
119
|
+
if result.was_streamed:
|
|
120
|
+
console.print("")
|
|
121
|
+
if result.ground_truth:
|
|
122
|
+
console.print(result.ground_truth.format_summary())
|
|
123
|
+
console.print("")
|
|
124
|
+
return
|
|
125
|
+
|
|
119
126
|
console.print(f"\n{agent_title}")
|
|
120
127
|
console.print(Markdown(result.message))
|
|
121
128
|
if result.ground_truth:
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real-Time Line-Buffered Rich Markdown Streamer for DB-Agent CLI.
|
|
3
|
+
Renders markdown headers, formatted diagram panels, syntax-highlighted SQL blocks,
|
|
4
|
+
and sleek bulleted lists dynamically as tokens stream in from LLM providers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.syntax import Syntax
|
|
13
|
+
from rich.rule import Rule
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LiveMarkdownStreamer:
|
|
17
|
+
"""
|
|
18
|
+
Line-buffered dynamic terminal renderer for streaming LLM responses.
|
|
19
|
+
Renders syntax-highlighted code blocks, bordered ASCII diagrams,
|
|
20
|
+
styled markdown headers, and clean bullet lists live as tokens arrive.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, console: Optional[Console] = None):
|
|
24
|
+
self.console = console or Console()
|
|
25
|
+
self.buffer = ""
|
|
26
|
+
self.in_code_block = False
|
|
27
|
+
self.code_lang = ""
|
|
28
|
+
self.code_lines: List[str] = []
|
|
29
|
+
self._has_started = False
|
|
30
|
+
|
|
31
|
+
def feed(self, chunk: str) -> None:
|
|
32
|
+
"""Feed a token chunk from the LLM stream."""
|
|
33
|
+
self._has_started = True
|
|
34
|
+
self.buffer += chunk
|
|
35
|
+
while "\n" in self.buffer:
|
|
36
|
+
line, self.buffer = self.buffer.split("\n", 1)
|
|
37
|
+
self._process_line(line)
|
|
38
|
+
|
|
39
|
+
def _process_line(self, line: str) -> None:
|
|
40
|
+
line_strip = line.strip()
|
|
41
|
+
|
|
42
|
+
# Code block fence (```sql, ```text, ```)
|
|
43
|
+
if line_strip.startswith("```"):
|
|
44
|
+
if self.in_code_block:
|
|
45
|
+
# Close code block
|
|
46
|
+
self.in_code_block = False
|
|
47
|
+
code_text = "\n".join(self.code_lines)
|
|
48
|
+
lang = self.code_lang or "sql"
|
|
49
|
+
if lang in ["sql", "postgres", "postgresql", "mysql", "sqlite", "tsql"]:
|
|
50
|
+
syntax = Syntax(code_text, "sql", theme="monokai", line_numbers=False)
|
|
51
|
+
self.console.print(Panel(syntax, title="[dim]SQL Statement[/dim]", title_align="left", border_style="dim", padding=(0, 1)))
|
|
52
|
+
else:
|
|
53
|
+
syntax = Syntax(code_text, "text", theme="monokai", line_numbers=False)
|
|
54
|
+
self.console.print(Panel(syntax, title="[dim]Architecture Flow Diagram[/dim]", title_align="left", border_style="cyan", padding=(0, 1)))
|
|
55
|
+
self.code_lines = []
|
|
56
|
+
self.code_lang = ""
|
|
57
|
+
else:
|
|
58
|
+
# Open code block
|
|
59
|
+
self.in_code_block = True
|
|
60
|
+
self.code_lang = line_strip[3:].strip().lower()
|
|
61
|
+
self.code_lines = []
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if self.in_code_block:
|
|
65
|
+
self.code_lines.append(line)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Markdown Section Headers (#, ##, ###)
|
|
69
|
+
if line_strip.startswith("#"):
|
|
70
|
+
header_text = line_strip.lstrip("#").strip()
|
|
71
|
+
self.console.print("")
|
|
72
|
+
self.console.print(f"[bold cyan]{header_text}[/bold cyan]")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# Markdown Horizontal Rules (---, ***, ___)
|
|
76
|
+
if line_strip in ["---", "***", "___"]:
|
|
77
|
+
self.console.print(Rule(style="dim cyan"))
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# Markdown Bullet Points (- , * , • )
|
|
81
|
+
if line_strip.startswith(("- ", "* ", "• ")):
|
|
82
|
+
content = line_strip[2:].strip()
|
|
83
|
+
styled = re.sub(r'\*\*(.*?)\*\*', r'[bold cyan]\1[/bold cyan]', content)
|
|
84
|
+
styled = re.sub(r'`(.*?)`', r'[bold white]\1[/bold white]', styled)
|
|
85
|
+
self.console.print(f" [dim]•[/dim] {styled}")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Numbered List items (1. , 2. )
|
|
89
|
+
num_match = re.match(r'^(\d+[\.\)])\s+(.*)', line_strip)
|
|
90
|
+
if num_match:
|
|
91
|
+
num_prefix, content = num_match.groups()
|
|
92
|
+
styled = re.sub(r'\*\*(.*?)\*\*', r'[bold cyan]\1[/bold cyan]', content)
|
|
93
|
+
styled = re.sub(r'`(.*?)`', r'[bold white]\1[/bold white]', styled)
|
|
94
|
+
self.console.print(f" [bold yellow]{num_prefix}[/bold yellow] {styled}")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Blank Line
|
|
98
|
+
if not line_strip:
|
|
99
|
+
self.console.print("")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Regular Markdown paragraph text with bold and inline code styling
|
|
103
|
+
styled_line = re.sub(r'\*\*(.*?)\*\*', r'[bold cyan]\1[/bold cyan]', line)
|
|
104
|
+
styled_line = re.sub(r'`(.*?)`', r'[bold white]\1[/bold white]', styled_line)
|
|
105
|
+
self.console.print(styled_line)
|
|
106
|
+
|
|
107
|
+
def finalize(self) -> None:
|
|
108
|
+
"""Flush remaining buffer and close any dangling open blocks."""
|
|
109
|
+
if self.buffer.strip():
|
|
110
|
+
self._process_line(self.buffer)
|
|
111
|
+
if self.in_code_block and self.code_lines:
|
|
112
|
+
code_text = "\n".join(self.code_lines)
|
|
113
|
+
syntax = Syntax(code_text, self.code_lang or "sql", theme="monokai", line_numbers=False)
|
|
114
|
+
self.console.print(Panel(syntax, border_style="dim", padding=(0, 1)))
|
|
115
|
+
self.buffer = ""
|
|
116
|
+
self.code_lines = []
|
|
117
|
+
self.in_code_block = False
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbagent-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.9"
|
|
8
8
|
description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -481,6 +481,25 @@ def test_cli_changes_and_audit_commands():
|
|
|
481
481
|
assert res_audit.exit_code == 0
|
|
482
482
|
|
|
483
483
|
|
|
484
|
+
def test_pipeline_streaming_callback(sample_db: str):
|
|
485
|
+
from dbagent.connectors.factory import create_connector
|
|
486
|
+
from dbagent.llm.mock_provider import MockProvider
|
|
487
|
+
from dbagent.agent.pipeline import QueryPipeline
|
|
488
|
+
|
|
489
|
+
connector = create_connector(sample_db)
|
|
490
|
+
llm = MockProvider()
|
|
491
|
+
pipeline = QueryPipeline(connector=connector, llm=llm)
|
|
492
|
+
|
|
493
|
+
chunks = []
|
|
494
|
+
def callback(token: str):
|
|
495
|
+
chunks.append(token)
|
|
496
|
+
|
|
497
|
+
result = pipeline.run("hello assistant", stream_callback=callback)
|
|
498
|
+
assert result.was_streamed is True
|
|
499
|
+
assert result.is_chat is True
|
|
500
|
+
connector.close()
|
|
501
|
+
|
|
502
|
+
|
|
484
503
|
def test_cli_recovery_commands():
|
|
485
504
|
res_list = runner.invoke(app, ["recovery", "list"])
|
|
486
505
|
assert res_list.exit_code == 0
|
|
@@ -491,5 +510,3 @@ def test_cli_recovery_commands():
|
|
|
491
510
|
|
|
492
511
|
res_cleanup = runner.invoke(app, ["recovery", "cleanup"])
|
|
493
512
|
assert res_cleanup.exit_code == 0
|
|
494
|
-
|
|
495
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|