nc1709 1.15.4__py3-none-any.whl → 1.18.8__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.
- nc1709/__init__.py +1 -1
- nc1709/agent/core.py +172 -19
- nc1709/agent/permissions.py +2 -2
- nc1709/agent/tools/bash_tool.py +295 -8
- nc1709/cli.py +435 -19
- nc1709/cli_ui.py +137 -52
- nc1709/conversation_logger.py +416 -0
- nc1709/llm_adapter.py +62 -4
- nc1709/plugins/agents/database_agent.py +695 -0
- nc1709/plugins/agents/django_agent.py +11 -4
- nc1709/plugins/agents/docker_agent.py +11 -4
- nc1709/plugins/agents/fastapi_agent.py +11 -4
- nc1709/plugins/agents/git_agent.py +11 -4
- nc1709/plugins/agents/nextjs_agent.py +11 -4
- nc1709/plugins/agents/ollama_agent.py +574 -0
- nc1709/plugins/agents/test_agent.py +702 -0
- nc1709/prompts/unified_prompt.py +156 -14
- nc1709/requirements_tracker.py +526 -0
- nc1709/thinking_messages.py +337 -0
- nc1709/version_check.py +6 -2
- nc1709/web/server.py +63 -3
- nc1709/web/templates/index.html +819 -140
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/METADATA +10 -7
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/RECORD +28 -22
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/WHEEL +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/entry_points.txt +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/licenses/LICENSE +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic Thinking Messages
|
|
3
|
+
Engaging, varied status messages to keep users informed during processing.
|
|
4
|
+
"""
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from typing import List, Optional, Tuple
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ThinkingPhase(Enum):
|
|
12
|
+
"""Different phases of processing"""
|
|
13
|
+
INITIAL = "initial"
|
|
14
|
+
ANALYZING = "analyzing"
|
|
15
|
+
PLANNING = "planning"
|
|
16
|
+
RESEARCHING = "researching"
|
|
17
|
+
CODING = "coding"
|
|
18
|
+
REVIEWING = "reviewing"
|
|
19
|
+
FINALIZING = "finalizing"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Dynamic thinking messages organized by phase
|
|
23
|
+
THINKING_MESSAGES = {
|
|
24
|
+
ThinkingPhase.INITIAL: [
|
|
25
|
+
"Let me think about this...",
|
|
26
|
+
"Hmm, interesting question...",
|
|
27
|
+
"Give me a moment to process this...",
|
|
28
|
+
"Let me understand what you need...",
|
|
29
|
+
"Processing your request...",
|
|
30
|
+
"Analyzing your input...",
|
|
31
|
+
"Let me work on this...",
|
|
32
|
+
"I'm on it...",
|
|
33
|
+
"Just a second, thinking...",
|
|
34
|
+
"Let me see what we can do here...",
|
|
35
|
+
],
|
|
36
|
+
ThinkingPhase.ANALYZING: [
|
|
37
|
+
"Analyzing the codebase...",
|
|
38
|
+
"Looking at the structure...",
|
|
39
|
+
"Understanding the context...",
|
|
40
|
+
"Examining the code...",
|
|
41
|
+
"Digging deeper into this...",
|
|
42
|
+
"Studying the patterns...",
|
|
43
|
+
"Mapping out the architecture...",
|
|
44
|
+
"Tracing the dependencies...",
|
|
45
|
+
"Checking the relationships...",
|
|
46
|
+
"Scanning for relevant code...",
|
|
47
|
+
],
|
|
48
|
+
ThinkingPhase.PLANNING: [
|
|
49
|
+
"Planning my approach...",
|
|
50
|
+
"Figuring out the best way...",
|
|
51
|
+
"Strategizing the solution...",
|
|
52
|
+
"Breaking this down...",
|
|
53
|
+
"Mapping out the steps...",
|
|
54
|
+
"Designing the approach...",
|
|
55
|
+
"Working out the details...",
|
|
56
|
+
"Thinking through the logic...",
|
|
57
|
+
"Considering the options...",
|
|
58
|
+
"Evaluating different approaches...",
|
|
59
|
+
],
|
|
60
|
+
ThinkingPhase.RESEARCHING: [
|
|
61
|
+
"Searching for information...",
|
|
62
|
+
"Looking up references...",
|
|
63
|
+
"Finding relevant examples...",
|
|
64
|
+
"Checking documentation...",
|
|
65
|
+
"Gathering context...",
|
|
66
|
+
"Exploring possibilities...",
|
|
67
|
+
"Investigating the best practices...",
|
|
68
|
+
"Reviewing similar patterns...",
|
|
69
|
+
"Looking for the right approach...",
|
|
70
|
+
"Consulting my knowledge...",
|
|
71
|
+
],
|
|
72
|
+
ThinkingPhase.CODING: [
|
|
73
|
+
"Writing the code...",
|
|
74
|
+
"Crafting the solution...",
|
|
75
|
+
"Building this out...",
|
|
76
|
+
"Implementing the changes...",
|
|
77
|
+
"Putting it together...",
|
|
78
|
+
"Coding away...",
|
|
79
|
+
"Making it happen...",
|
|
80
|
+
"Bringing this to life...",
|
|
81
|
+
"Creating the implementation...",
|
|
82
|
+
"Working on the code...",
|
|
83
|
+
],
|
|
84
|
+
ThinkingPhase.REVIEWING: [
|
|
85
|
+
"Double-checking my work...",
|
|
86
|
+
"Reviewing the solution...",
|
|
87
|
+
"Making sure everything's right...",
|
|
88
|
+
"Validating the approach...",
|
|
89
|
+
"Checking for issues...",
|
|
90
|
+
"Looking over the code...",
|
|
91
|
+
"Ensuring quality...",
|
|
92
|
+
"Verifying the logic...",
|
|
93
|
+
"Testing my assumptions...",
|
|
94
|
+
"Polishing the solution...",
|
|
95
|
+
],
|
|
96
|
+
ThinkingPhase.FINALIZING: [
|
|
97
|
+
"Almost there...",
|
|
98
|
+
"Finishing up...",
|
|
99
|
+
"Wrapping things up...",
|
|
100
|
+
"Just about done...",
|
|
101
|
+
"Final touches...",
|
|
102
|
+
"Putting on the finishing touches...",
|
|
103
|
+
"One moment more...",
|
|
104
|
+
"Just finalizing...",
|
|
105
|
+
"Nearly there...",
|
|
106
|
+
"Preparing the response...",
|
|
107
|
+
],
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Engaging idle messages when waiting longer
|
|
111
|
+
LONG_WAIT_MESSAGES = [
|
|
112
|
+
"Still working on it, thanks for your patience...",
|
|
113
|
+
"This is taking a bit longer, but I'm making progress...",
|
|
114
|
+
"Complex task, but I'm getting there...",
|
|
115
|
+
"Bear with me, almost got it...",
|
|
116
|
+
"Working through some tricky parts...",
|
|
117
|
+
"Thorough analysis takes a moment...",
|
|
118
|
+
"Good things take time...",
|
|
119
|
+
"Deep diving into this one...",
|
|
120
|
+
"Making sure I get it right...",
|
|
121
|
+
"Quality takes a moment...",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
# Personal/friendly messages for casual interaction
|
|
125
|
+
CASUAL_MESSAGES = [
|
|
126
|
+
"Let me see...",
|
|
127
|
+
"Okay, working on it...",
|
|
128
|
+
"Sure thing, give me a sec...",
|
|
129
|
+
"On it!",
|
|
130
|
+
"Let me figure this out...",
|
|
131
|
+
"Alright, let's do this...",
|
|
132
|
+
"Got it, processing...",
|
|
133
|
+
"Looking into it...",
|
|
134
|
+
"Here we go...",
|
|
135
|
+
"Let me help with that...",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# Tool action messages - what to show when specific tools are being used
|
|
139
|
+
TOOL_MESSAGES = {
|
|
140
|
+
"Read": [
|
|
141
|
+
"Reading {target}...",
|
|
142
|
+
"Looking at {target}...",
|
|
143
|
+
"Examining {target}...",
|
|
144
|
+
"Opening {target}...",
|
|
145
|
+
"Checking {target}...",
|
|
146
|
+
],
|
|
147
|
+
"Write": [
|
|
148
|
+
"Writing to {target}...",
|
|
149
|
+
"Creating {target}...",
|
|
150
|
+
"Saving {target}...",
|
|
151
|
+
"Updating {target}...",
|
|
152
|
+
],
|
|
153
|
+
"Edit": [
|
|
154
|
+
"Editing {target}...",
|
|
155
|
+
"Modifying {target}...",
|
|
156
|
+
"Updating {target}...",
|
|
157
|
+
"Changing {target}...",
|
|
158
|
+
],
|
|
159
|
+
"Bash": [
|
|
160
|
+
"Running command...",
|
|
161
|
+
"Executing...",
|
|
162
|
+
"Processing command...",
|
|
163
|
+
"Working on it...",
|
|
164
|
+
],
|
|
165
|
+
"Grep": [
|
|
166
|
+
"Searching for patterns...",
|
|
167
|
+
"Looking for matches...",
|
|
168
|
+
"Scanning files...",
|
|
169
|
+
"Finding occurrences...",
|
|
170
|
+
],
|
|
171
|
+
"Glob": [
|
|
172
|
+
"Finding files...",
|
|
173
|
+
"Searching for files...",
|
|
174
|
+
"Locating files...",
|
|
175
|
+
"Scanning directory...",
|
|
176
|
+
],
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ThinkingMessageManager:
|
|
181
|
+
"""
|
|
182
|
+
Manages dynamic thinking messages to keep users engaged.
|
|
183
|
+
|
|
184
|
+
Tracks time elapsed and switches messages to maintain variety
|
|
185
|
+
and show progress even during long processing.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def __init__(self):
|
|
189
|
+
self.start_time: Optional[float] = None
|
|
190
|
+
self.current_phase: ThinkingPhase = ThinkingPhase.INITIAL
|
|
191
|
+
self.last_message: str = ""
|
|
192
|
+
self.last_message_time: float = 0
|
|
193
|
+
self.message_count: int = 0
|
|
194
|
+
self.used_messages: set = set()
|
|
195
|
+
|
|
196
|
+
def start(self) -> None:
|
|
197
|
+
"""Start a new thinking session"""
|
|
198
|
+
self.start_time = time.time()
|
|
199
|
+
self.message_count = 0
|
|
200
|
+
self.used_messages.clear()
|
|
201
|
+
self.current_phase = ThinkingPhase.INITIAL
|
|
202
|
+
|
|
203
|
+
def reset(self) -> None:
|
|
204
|
+
"""Reset the manager for a new request"""
|
|
205
|
+
self.start()
|
|
206
|
+
|
|
207
|
+
def set_phase(self, phase: ThinkingPhase) -> None:
|
|
208
|
+
"""Set the current processing phase"""
|
|
209
|
+
self.current_phase = phase
|
|
210
|
+
|
|
211
|
+
def get_elapsed(self) -> float:
|
|
212
|
+
"""Get elapsed time in seconds"""
|
|
213
|
+
if self.start_time is None:
|
|
214
|
+
return 0
|
|
215
|
+
return time.time() - self.start_time
|
|
216
|
+
|
|
217
|
+
def should_update(self, interval: float = 3.0) -> bool:
|
|
218
|
+
"""Check if it's time for a new message"""
|
|
219
|
+
now = time.time()
|
|
220
|
+
if now - self.last_message_time >= interval:
|
|
221
|
+
return True
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def get_message(self, phase: Optional[ThinkingPhase] = None, casual: bool = False) -> str:
|
|
225
|
+
"""
|
|
226
|
+
Get a thinking message for the current state.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
phase: Optional phase override
|
|
230
|
+
casual: Use more casual/friendly messages
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
A contextually appropriate thinking message
|
|
234
|
+
"""
|
|
235
|
+
phase = phase or self.current_phase
|
|
236
|
+
elapsed = self.get_elapsed()
|
|
237
|
+
|
|
238
|
+
# After 10+ seconds, add some long-wait messages
|
|
239
|
+
if elapsed > 10 and random.random() < 0.3:
|
|
240
|
+
messages = LONG_WAIT_MESSAGES
|
|
241
|
+
# Use casual messages for variety
|
|
242
|
+
elif casual or (random.random() < 0.2 and self.message_count > 0):
|
|
243
|
+
messages = CASUAL_MESSAGES
|
|
244
|
+
else:
|
|
245
|
+
messages = THINKING_MESSAGES.get(phase, THINKING_MESSAGES[ThinkingPhase.INITIAL])
|
|
246
|
+
|
|
247
|
+
# Try to get an unused message for variety
|
|
248
|
+
available = [m for m in messages if m not in self.used_messages]
|
|
249
|
+
if not available:
|
|
250
|
+
# Reset if we've used all messages
|
|
251
|
+
self.used_messages.clear()
|
|
252
|
+
available = messages
|
|
253
|
+
|
|
254
|
+
message = random.choice(available)
|
|
255
|
+
self.used_messages.add(message)
|
|
256
|
+
self.last_message = message
|
|
257
|
+
self.last_message_time = time.time()
|
|
258
|
+
self.message_count += 1
|
|
259
|
+
|
|
260
|
+
return message
|
|
261
|
+
|
|
262
|
+
def get_tool_message(self, tool_name: str, target: str = "") -> str:
|
|
263
|
+
"""
|
|
264
|
+
Get a message for a specific tool action.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
tool_name: Name of the tool being used
|
|
268
|
+
target: Target of the action (file, command, etc.)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Formatted tool action message
|
|
272
|
+
"""
|
|
273
|
+
templates = TOOL_MESSAGES.get(tool_name, ["Working..."])
|
|
274
|
+
template = random.choice(templates)
|
|
275
|
+
|
|
276
|
+
if "{target}" in template and target:
|
|
277
|
+
# Truncate long targets
|
|
278
|
+
if len(target) > 40:
|
|
279
|
+
target = "..." + target[-37:]
|
|
280
|
+
return template.format(target=target)
|
|
281
|
+
return template
|
|
282
|
+
|
|
283
|
+
def get_progress_message(self) -> str:
|
|
284
|
+
"""
|
|
285
|
+
Get a progress-aware message based on elapsed time.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Message appropriate for current progress
|
|
289
|
+
"""
|
|
290
|
+
elapsed = self.get_elapsed()
|
|
291
|
+
|
|
292
|
+
if elapsed < 2:
|
|
293
|
+
return self.get_message(ThinkingPhase.INITIAL)
|
|
294
|
+
elif elapsed < 5:
|
|
295
|
+
return self.get_message(ThinkingPhase.ANALYZING)
|
|
296
|
+
elif elapsed < 10:
|
|
297
|
+
return self.get_message(ThinkingPhase.PLANNING)
|
|
298
|
+
elif elapsed < 20:
|
|
299
|
+
return self.get_message(ThinkingPhase.CODING)
|
|
300
|
+
elif elapsed < 30:
|
|
301
|
+
return self.get_message(ThinkingPhase.REVIEWING)
|
|
302
|
+
else:
|
|
303
|
+
return self.get_message(ThinkingPhase.FINALIZING)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Global instance for convenience
|
|
307
|
+
_manager = ThinkingMessageManager()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def start_thinking() -> None:
|
|
311
|
+
"""Start a new thinking session"""
|
|
312
|
+
_manager.start()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def get_thinking_message(phase: Optional[ThinkingPhase] = None, casual: bool = False) -> str:
|
|
316
|
+
"""Get a dynamic thinking message"""
|
|
317
|
+
return _manager.get_message(phase, casual)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def get_tool_message(tool_name: str, target: str = "") -> str:
|
|
321
|
+
"""Get a tool-specific message"""
|
|
322
|
+
return _manager.get_tool_message(tool_name, target)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_progress_message() -> str:
|
|
326
|
+
"""Get a progress-aware message"""
|
|
327
|
+
return _manager.get_progress_message()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def set_phase(phase: ThinkingPhase) -> None:
|
|
331
|
+
"""Set the current thinking phase"""
|
|
332
|
+
_manager.set_phase(phase)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def should_update_message(interval: float = 3.0) -> bool:
|
|
336
|
+
"""Check if we should update the thinking message"""
|
|
337
|
+
return _manager.should_update(interval)
|
nc1709/version_check.py
CHANGED
|
@@ -15,7 +15,7 @@ from packaging import version
|
|
|
15
15
|
# Cache settings
|
|
16
16
|
CACHE_DIR = Path.home() / ".nc1709"
|
|
17
17
|
CACHE_FILE = CACHE_DIR / "version_cache.json"
|
|
18
|
-
CACHE_TTL =
|
|
18
|
+
CACHE_TTL = 3600 # Check once per hour (reduced from 24h for faster update notifications)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def get_current_version() -> str:
|
|
@@ -86,8 +86,12 @@ def check_for_update(force: bool = False) -> Tuple[bool, Optional[str], Optional
|
|
|
86
86
|
if not force:
|
|
87
87
|
cache = load_cache()
|
|
88
88
|
cache_time = cache.get("timestamp", 0)
|
|
89
|
+
cached_current = cache.get("current_version", "")
|
|
89
90
|
|
|
90
|
-
if
|
|
91
|
+
# Invalidate cache if installed version changed (user upgraded/downgraded)
|
|
92
|
+
if cached_current != current:
|
|
93
|
+
force = True
|
|
94
|
+
elif time.time() - cache_time < CACHE_TTL:
|
|
91
95
|
# Use cached result
|
|
92
96
|
latest = cache.get("latest_version")
|
|
93
97
|
if latest:
|
nc1709/web/server.py
CHANGED
|
@@ -9,12 +9,22 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Dict, Any, Optional, List
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
|
|
12
|
-
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Header, Depends
|
|
12
|
+
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Header, Depends, Request
|
|
13
13
|
from fastapi.staticfiles import StaticFiles
|
|
14
14
|
from fastapi.responses import HTMLResponse, FileResponse
|
|
15
15
|
from fastapi.middleware.cors import CORSMiddleware
|
|
16
16
|
from pydantic import BaseModel
|
|
17
17
|
|
|
18
|
+
# Import conversation logger
|
|
19
|
+
try:
|
|
20
|
+
from ..conversation_logger import ConversationLogger
|
|
21
|
+
HAS_CONVERSATION_LOGGER = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
HAS_CONVERSATION_LOGGER = False
|
|
24
|
+
|
|
25
|
+
# Session loggers by client IP
|
|
26
|
+
_session_loggers: Dict[str, ConversationLogger] = {}
|
|
27
|
+
|
|
18
28
|
# Get the directory where this file is located
|
|
19
29
|
WEB_DIR = Path(__file__).parent
|
|
20
30
|
STATIC_DIR = WEB_DIR / "static"
|
|
@@ -212,11 +222,12 @@ def create_app() -> FastAPI:
|
|
|
212
222
|
@app.get("/api/status")
|
|
213
223
|
async def get_status():
|
|
214
224
|
"""Get system status"""
|
|
225
|
+
from .. import __version__
|
|
215
226
|
config = get_config()
|
|
216
227
|
|
|
217
228
|
return {
|
|
218
229
|
"status": "ok",
|
|
219
|
-
"version":
|
|
230
|
+
"version": __version__,
|
|
220
231
|
"project": str(Path.cwd()),
|
|
221
232
|
"memory_enabled": config.get("memory.enabled", False),
|
|
222
233
|
"timestamp": datetime.now().isoformat()
|
|
@@ -543,11 +554,32 @@ def create_app() -> FastAPI:
|
|
|
543
554
|
@app.get("/api/remote/status")
|
|
544
555
|
async def remote_status(authorized: bool = Depends(verify_api_key)):
|
|
545
556
|
"""Check remote API status and available models"""
|
|
557
|
+
from .. import __version__
|
|
546
558
|
config = get_config()
|
|
559
|
+
|
|
560
|
+
# Count available tools (tools that client can execute locally)
|
|
561
|
+
tools_count = 17 # Default count
|
|
562
|
+
try:
|
|
563
|
+
from ..agent.tools.base import ToolRegistry
|
|
564
|
+
from ..agent.tools.file_tools import register_file_tools
|
|
565
|
+
from ..agent.tools.search_tools import register_search_tools
|
|
566
|
+
from ..agent.tools.bash_tool import register_bash_tools
|
|
567
|
+
from ..agent.tools.web_tools import register_web_tools
|
|
568
|
+
|
|
569
|
+
registry = ToolRegistry()
|
|
570
|
+
register_file_tools(registry)
|
|
571
|
+
register_search_tools(registry)
|
|
572
|
+
register_bash_tools(registry)
|
|
573
|
+
register_web_tools(registry)
|
|
574
|
+
tools_count = len(registry.list_names())
|
|
575
|
+
except ImportError:
|
|
576
|
+
pass
|
|
577
|
+
|
|
547
578
|
return {
|
|
548
579
|
"status": "ok",
|
|
549
580
|
"server": "nc1709",
|
|
550
|
-
"version":
|
|
581
|
+
"version": __version__,
|
|
582
|
+
"tools_count": tools_count,
|
|
551
583
|
"models": config.get("models", {}),
|
|
552
584
|
"ollama_url": config.get("ollama.base_url", "http://localhost:11434"),
|
|
553
585
|
"auth_required": bool(config.get("remote.api_key")),
|
|
@@ -760,6 +792,7 @@ def create_app() -> FastAPI:
|
|
|
760
792
|
@app.post("/api/remote/agent")
|
|
761
793
|
async def remote_agent_chat(
|
|
762
794
|
request: AgentChatRequest,
|
|
795
|
+
http_request: Request,
|
|
763
796
|
authorized: bool = Depends(verify_api_key)
|
|
764
797
|
):
|
|
765
798
|
"""
|
|
@@ -772,6 +805,26 @@ def create_app() -> FastAPI:
|
|
|
772
805
|
4. Client parses tool calls and executes them LOCALLY
|
|
773
806
|
5. Client sends tool results back for next iteration
|
|
774
807
|
"""
|
|
808
|
+
# Get client IP for logging
|
|
809
|
+
client_ip = http_request.client.host if http_request.client else "unknown"
|
|
810
|
+
user_agent = http_request.headers.get("user-agent", "unknown")
|
|
811
|
+
|
|
812
|
+
# Get or create logger for this client
|
|
813
|
+
if HAS_CONVERSATION_LOGGER:
|
|
814
|
+
if client_ip not in _session_loggers:
|
|
815
|
+
_session_loggers[client_ip] = ConversationLogger(
|
|
816
|
+
ip_address=client_ip,
|
|
817
|
+
user_agent=user_agent,
|
|
818
|
+
mode="server"
|
|
819
|
+
)
|
|
820
|
+
logger = _session_loggers[client_ip]
|
|
821
|
+
|
|
822
|
+
# Log the last user message if present
|
|
823
|
+
if request.messages:
|
|
824
|
+
last_msg = request.messages[-1]
|
|
825
|
+
if last_msg.get("role") == "user":
|
|
826
|
+
logger.log_user_message(last_msg.get("content", ""), {"cwd": request.cwd})
|
|
827
|
+
|
|
775
828
|
try:
|
|
776
829
|
from ..llm_adapter import LLMAdapter
|
|
777
830
|
from ..prompts.unified_prompt import get_unified_prompt
|
|
@@ -788,12 +841,19 @@ def create_app() -> FastAPI:
|
|
|
788
841
|
# Get LLM response (just thinking, no execution)
|
|
789
842
|
response = llm.chat(messages)
|
|
790
843
|
|
|
844
|
+
# Log assistant response
|
|
845
|
+
if HAS_CONVERSATION_LOGGER and client_ip in _session_loggers:
|
|
846
|
+
_session_loggers[client_ip].log_assistant_message(response[:2000])
|
|
847
|
+
|
|
791
848
|
return {
|
|
792
849
|
"response": response,
|
|
793
850
|
"timestamp": datetime.now().isoformat(),
|
|
794
851
|
"type": "agent_response"
|
|
795
852
|
}
|
|
796
853
|
except Exception as e:
|
|
854
|
+
# Log error
|
|
855
|
+
if HAS_CONVERSATION_LOGGER and client_ip in _session_loggers:
|
|
856
|
+
_session_loggers[client_ip].log_error(str(e))
|
|
797
857
|
raise HTTPException(status_code=500, detail=str(e))
|
|
798
858
|
|
|
799
859
|
# =========================================================================
|