Coverage for src / osiris_cli / letta_formatter.py: 0%

148 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-27 17:41 +0200

1""" 

2Letta Visual Standard Formatter 

3 

4Implements the exact visual style preferred by the user: 

5- Action-Result indentation with ● and ⎿ glyphs 

6- Thinking blocks with ✻ symbol 

7- Persistent bottom status bar 

8- No walls of text - always summarize 

9""" 

10 

11import os 

12import sys 

13from typing import Optional, List, Dict, Any 

14from datetime import datetime 

15from pathlib import Path 

16 

17from rich.console import Console 

18from rich.text import Text 

19from rich.panel import Panel 

20from rich.layout import Layout 

21from rich.live import Live 

22from rich import box 

23 

24from .logger import get_logger 

25from .config import settings 

26 

27logger = get_logger() 

28console = Console() 

29 

30 

31class LettaGlyphs: 

32 """Official glyphs for Letta Visual Standard""" 

33 ACTION = "●" # Circle for actions/tool calls 

34 RESULT = "⎿" # Branch for results 

35 THINKING = "✻" # Asterisk for thinking/reasoning 

36 SUCCESS = "✓" # Checkmark for success 

37 ERROR = "✗" # X for errors 

38 WARNING = "⚠" # Warning triangle 

39 ARROW = "→" # Arrow for flow 

40 BULLET = "•" # Bullet for lists 

41 

42 

43class LettaFormatter: 

44 """ 

45 Letta Visual Standard Formatter. 

46  

47 Formats all CLI output according to Letta Code aesthetic: 

48 - Clean action-result trees 

49 - Summarized outputs (no walls of text) 

50 - Thinking blocks 

51 - Persistent status bar 

52 """ 

53 

54 def __init__(self): 

55 self.status_mode = "standard" 

56 self.status_version = "v4.0.0" 

57 self.status_context = "0 files" 

58 self.live: Optional[Live] = None 

59 self.messages: List[str] = [] 

60 self.indent_level = 0 

61 

62 def format_action( 

63 self, 

64 tool_name: str, 

65 args: Dict[str, Any], 

66 indent: int = 0 

67 ) -> str: 

68 """ 

69 Format tool action in Letta style. 

70  

71 Example: 

72 ● db_connect(connection_id="mydb", db_type="postgresql") 

73 """ 

74 # Format arguments 

75 arg_parts = [] 

76 for key, value in args.items(): 

77 if isinstance(value, str): 

78 arg_parts.append(f'{key}="{value}"') 

79 else: 

80 arg_parts.append(f'{key}={value}') 

81 

82 args_str = ", ".join(arg_parts) if arg_parts else "" 

83 

84 indent_str = " " * indent 

85 return f"{indent_str}{LettaGlyphs.ACTION} {tool_name}({args_str})" 

86 

87 def format_result( 

88 self, 

89 summary: str, 

90 indent: int = 1, 

91 success: bool = True 

92 ) -> str: 

93 """ 

94 Format tool result in Letta style. 

95  

96 Example: 

97 ⎿ Successfully connected to postgresql database 

98 """ 

99 indent_str = " " * indent 

100 status_glyph = LettaGlyphs.SUCCESS if success else LettaGlyphs.ERROR 

101 return f"{indent_str}{LettaGlyphs.RESULT} {summary} {status_glyph}" 

102 

103 def format_thinking(self, reasoning: str) -> str: 

104 """ 

105 Format thinking block in Letta style. 

106  

107 Example: 

108 ✻ Thinking... 

109 I need to connect to the database first, then query the users table. 

110 """ 

111 lines = reasoning.strip().split('\n') 

112 formatted = [f"{LettaGlyphs.THINKING} Thinking..."] 

113 for line in lines: 

114 formatted.append(f" {line}") 

115 return '\n'.join(formatted) 

116 

117 def format_status_bar(self) -> str: 

118 """ 

119 Format persistent bottom status bar. 

120  

121 Example: 

122 >> yolo (allow all) mode | Osiris v4.0.0 | 3 files loaded 

123 """ 

124 mode_display = f"{self.status_mode}" 

125 if settings.yolo_mode: 

126 mode_display += " (allow all)" 

127 

128 return f">> {mode_display} mode | Osiris {self.status_version} | {self.status_context}" 

129 

130 def summarize_file_content( 

131 self, 

132 file_path: str, 

133 content: str, 

134 max_lines: int = 3 

135 ) -> str: 

136 """ 

137 Summarize file content instead of dumping it. 

138  

139 Example: 

140 ⎿ Read 500 lines from config.py 

141 """ 

142 lines = content.split('\n') 

143 line_count = len(lines) 

144 size_kb = len(content) / 1024 

145 

146 return f"Read {line_count} lines from {Path(file_path).name} ({size_kb:.1f}KB)" 

147 

148 def summarize_query_results( 

149 self, 

150 result: Dict[str, Any] 

151 ) -> str: 

152 """ 

153 Summarize database query results. 

154  

155 Example: 

156 ⎿ Found 150 rows in users table ✓ 

157 """ 

158 if "rows" in result: 

159 row_count = len(result["rows"]) 

160 columns = result.get("columns", []) 

161 return f"Found {row_count} rows with {len(columns)} columns" 

162 elif "documents" in result: 

163 doc_count = len(result["documents"]) 

164 return f"Found {doc_count} documents" 

165 elif "rows_affected" in result: 

166 affected = result["rows_affected"] 

167 return f"Modified {affected} rows" 

168 else: 

169 return "Query completed successfully" 

170 

171 def summarize_schema( 

172 self, 

173 schema: Dict[str, Any] 

174 ) -> str: 

175 """ 

176 Summarize database schema. 

177  

178 Example: 

179 ⎿ Schema: 12 tables, 85 columns total ✓ 

180 """ 

181 table_count = schema.get("table_count", 0) 

182 

183 # Count total columns 

184 total_columns = 0 

185 tables = schema.get("tables", {}) 

186 for table_name, columns in tables.items(): 

187 total_columns += len(columns) 

188 

189 return f"Schema: {table_count} tables, {total_columns} columns total" 

190 

191 def print_action_result( 

192 self, 

193 tool_name: str, 

194 args: Dict[str, Any], 

195 result_summary: str, 

196 success: bool = True 

197 ): 

198 """ 

199 Print complete action-result pair. 

200  

201 Example: 

202 ● db_connect(connection_id="mydb", db_type="postgresql") 

203 ⎿ Successfully connected to postgresql database ✓ 

204 """ 

205 # Action line 

206 action_line = self.format_action(tool_name, args, indent=0) 

207 console.print(action_line, style="cyan") 

208 

209 # Result line 

210 result_line = self.format_result(result_summary, indent=1, success=success) 

211 result_style = "green" if success else "red" 

212 console.print(result_line, style=result_style) 

213 

214 # Add to message history 

215 self.messages.append(action_line) 

216 self.messages.append(result_line) 

217 

218 def print_thinking(self, reasoning: str): 

219 """ 

220 Print thinking block. 

221  

222 Example: 

223 ✻ Thinking... 

224 I need to query the database to get the user count. 

225 """ 

226 thinking_block = self.format_thinking(reasoning) 

227 console.print(thinking_block, style="dim cyan") 

228 

229 self.messages.append(thinking_block) 

230 

231 def print_error( 

232 self, 

233 tool_name: str, 

234 args: Dict[str, Any], 

235 error: str 

236 ): 

237 """ 

238 Print error in Letta style. 

239  

240 Example: 

241 ● db_connect(connection_id="bad", db_type="postgresql") 

242 ⎿ Connection failed: Invalid credentials ✗ 

243 """ 

244 self.print_action_result(tool_name, args, f"Error: {error}", success=False) 

245 

246 def update_status( 

247 self, 

248 mode: Optional[str] = None, 

249 version: Optional[str] = None, 

250 context: Optional[str] = None 

251 ): 

252 """Update status bar values""" 

253 if mode is not None: 

254 self.status_mode = mode 

255 if version is not None: 

256 self.status_version = version 

257 if context is not None: 

258 self.status_context = context 

259 

260 def render_with_status_bar(self): 

261 """ 

262 Render interface with persistent bottom status bar. 

263  

264 Layout: 

265 [Chat messages - scrollable] 

266 ──────────────────────────── 

267 >> mode | version | context 

268 """ 

269 # Get terminal size 

270 terminal_height = console.height 

271 

272 # Chat area (all but last 2 lines) 

273 chat_lines = self.messages[-(terminal_height - 3):] 

274 chat_content = '\n'.join(chat_lines) 

275 

276 # Status bar 

277 status_bar = self.format_status_bar() 

278 

279 # Create layout 

280 layout = Layout() 

281 layout.split_column( 

282 Layout(name="chat", ratio=1), 

283 Layout(name="status", size=1) 

284 ) 

285 

286 layout["chat"].update(Text(chat_content)) 

287 layout["status"].update(Panel( 

288 status_bar, 

289 style="bold blue", 

290 box=box.SIMPLE, 

291 padding=(0, 1) 

292 )) 

293 

294 return layout 

295 

296 def start_live(self): 

297 """Start live rendering with status bar""" 

298 self.live = Live( 

299 self.render_with_status_bar(), 

300 console=console, 

301 refresh_per_second=2 

302 ) 

303 self.live.start() 

304 

305 def stop_live(self): 

306 """Stop live rendering""" 

307 if self.live: 

308 self.live.stop() 

309 

310 def update_live(self): 

311 """Update live display""" 

312 if self.live: 

313 self.live.update(self.render_with_status_bar()) 

314 

315 

316# Global formatter instance 

317letta_formatter = LettaFormatter() 

318 

319 

320# Convenience functions 

321def action_result( 

322 tool_name: str, 

323 args: Dict[str, Any], 

324 result_summary: str, 

325 success: bool = True 

326): 

327 """Print action-result pair in Letta style""" 

328 letta_formatter.print_action_result(tool_name, args, result_summary, success) 

329 

330 

331def thinking(reasoning: str): 

332 """Print thinking block in Letta style""" 

333 letta_formatter.print_thinking(reasoning) 

334 

335 

336def update_status(mode: str = None, context: str = None): 

337 """Update status bar""" 

338 letta_formatter.update_status(mode=mode, context=context) 

339 

340 

341def summarize_file(file_path: str, content: str) -> str: 

342 """Summarize file instead of dumping content""" 

343 return letta_formatter.summarize_file_content(file_path, content) 

344 

345 

346def summarize_query(result: Dict[str, Any]) -> str: 

347 """Summarize query results""" 

348 return letta_formatter.summarize_query_results(result) 

349 

350 

351def summarize_schema(schema: Dict[str, Any]) -> str: 

352 """Summarize database schema""" 

353 return letta_formatter.summarize_schema(schema) 

354 

355 

356# Examples of usage 

357def example_database_operation(): 

358 """Example of database operation with Letta formatting""" 

359 

360 # Show thinking 

361 thinking("I need to connect to the database first, then query the users table to count how many users exist.") 

362 

363 # Connect action 

364 action_result( 

365 "db_connect", 

366 { 

367 "connection_id": "mydb", 

368 "db_type": "postgresql", 

369 "host": "localhost", 

370 "database": "users_db" 

371 }, 

372 "Successfully connected to postgresql database", 

373 success=True 

374 ) 

375 

376 # Query action 

377 action_result( 

378 "db_query", 

379 { 

380 "connection_id": "mydb", 

381 "query": "SELECT COUNT(*) FROM users" 

382 }, 

383 "Found 150 rows in users table", 

384 success=True 

385 ) 

386 

387 # Schema action 

388 action_result( 

389 "db_schema", 

390 {"connection_id": "mydb"}, 

391 "Schema: 12 tables, 85 columns total", 

392 success=True 

393 ) 

394 

395 

396def example_file_operation(): 

397 """Example of file operation with Letta formatting""" 

398 

399 thinking("I'll read the configuration file to understand the current settings.") 

400 

401 # Simulate reading a large file 

402 large_content = "import os\nfrom pathlib import Path\n" + ("# More code...\n" * 100) 

403 

404 action_result( 

405 "read_file", 

406 {"file_path": "config.py"}, 

407 summarize_file("config.py", large_content), 

408 success=True 

409 ) 

410 

411 

412def example_cloud_operation(): 

413 """Example of cloud operation with Letta formatting""" 

414 

415 thinking("I need to list the EC2 instances to see what's currently running.") 

416 

417 action_result( 

418 "aws_ec2_list", 

419 {"region": "us-east-1"}, 

420 "Found 3 instances: 2 running, 1 stopped", 

421 success=True 

422 ) 

423 

424 

425def example_error(): 

426 """Example of error handling with Letta formatting""" 

427 

428 thinking("Let me try to connect to the database.") 

429 

430 action_result( 

431 "db_connect", 

432 { 

433 "connection_id": "baddb", 

434 "db_type": "postgresql", 

435 "host": "invalid.example.com" 

436 }, 

437 "Connection failed: Connection refused", 

438 success=False 

439 )