superqode 0.1.5__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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,815 @@
1
+ """
2
+ Thinking Display Widget - Model Reasoning Visualization.
3
+
4
+ Shows the model's thinking process in an elegant, expandable display:
5
+ - Streaming thinking text with typing effect
6
+ - Collapsible sections
7
+ - Thought categorization (planning, analyzing, deciding)
8
+ - Visual timeline of thoughts
9
+ - Support for extended thinking (Claude-style)
10
+
11
+ Provides transparency into how the model reasons about problems.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime
19
+ from enum import Enum
20
+ from typing import Any, Callable, Dict, List, Optional
21
+
22
+ from rich.console import RenderableType, Group
23
+ from rich.panel import Panel
24
+ from rich.text import Text
25
+ from rich.box import ROUNDED
26
+ from textual.reactive import reactive
27
+ from textual.widgets import Static
28
+ from textual.containers import Container, Vertical
29
+ from textual.timer import Timer
30
+ from textual import events
31
+
32
+
33
+ class ThoughtType(Enum):
34
+ """Type of thought/reasoning."""
35
+
36
+ PLANNING = "planning"
37
+ ANALYZING = "analyzing"
38
+ DECIDING = "deciding"
39
+ SEARCHING = "searching"
40
+ READING = "reading"
41
+ WRITING = "writing"
42
+ DEBUGGING = "debugging"
43
+ REFLECTING = "reflecting"
44
+ EXECUTING = "executing"
45
+ VERIFYING = "verifying"
46
+ TESTING = "testing"
47
+ REFACTORING = "refactoring"
48
+ GENERAL = "general"
49
+
50
+
51
+ # Thought styling - colorful icons for visual clarity
52
+ THOUGHT_STYLES = {
53
+ ThoughtType.PLANNING: {"icon": "📋", "color": "#3b82f6", "label": "Planning"},
54
+ ThoughtType.ANALYZING: {"icon": "🔬", "color": "#8b5cf6", "label": "Analyzing"},
55
+ ThoughtType.DECIDING: {"icon": "🤔", "color": "#f59e0b", "label": "Deciding"},
56
+ ThoughtType.SEARCHING: {"icon": "🔍", "color": "#06b6d4", "label": "Searching"},
57
+ ThoughtType.READING: {"icon": "📖", "color": "#14b8a6", "label": "Reading"},
58
+ ThoughtType.WRITING: {"icon": "✏️", "color": "#22c55e", "label": "Writing"},
59
+ ThoughtType.DEBUGGING: {"icon": "🐛", "color": "#ef4444", "label": "Debugging"},
60
+ ThoughtType.REFLECTING: {"icon": "💭", "color": "#ec4899", "label": "Reflecting"},
61
+ ThoughtType.EXECUTING: {"icon": "⚡", "color": "#f97316", "label": "Executing"},
62
+ ThoughtType.VERIFYING: {"icon": "✅", "color": "#10b981", "label": "Verifying"},
63
+ ThoughtType.TESTING: {"icon": "🧪", "color": "#6366f1", "label": "Testing"},
64
+ ThoughtType.REFACTORING: {"icon": "🔧", "color": "#a855f7", "label": "Refactoring"},
65
+ ThoughtType.GENERAL: {"icon": "💡", "color": "#a1a1aa", "label": "Thinking"},
66
+ }
67
+
68
+
69
+ @dataclass
70
+ class ThoughtChunk:
71
+ """A chunk of thinking text."""
72
+
73
+ text: str
74
+ thought_type: ThoughtType = ThoughtType.GENERAL
75
+ timestamp: datetime = field(default_factory=datetime.now)
76
+ is_streaming: bool = False
77
+
78
+
79
+ def classify_thought(text: str) -> ThoughtType:
80
+ """
81
+ Classify a thought based on its content.
82
+
83
+ Uses keyword matching to determine the type of reasoning/activity
84
+ the agent is performing. Order matters - more specific matches first.
85
+ """
86
+ text_lower = text.lower()
87
+
88
+ # Testing - check first as it's specific
89
+ if any(
90
+ w in text_lower
91
+ for w in ["test", "pytest", "unittest", "assertion", "expect", "should pass", "should fail"]
92
+ ):
93
+ return ThoughtType.TESTING
94
+
95
+ # Verifying - checking if something works
96
+ if any(
97
+ w in text_lower
98
+ for w in ["verify", "confirm", "validate", "check if", "ensure", "make sure", "works"]
99
+ ):
100
+ return ThoughtType.VERIFYING
101
+
102
+ # Executing - running commands
103
+ if any(
104
+ w in text_lower
105
+ for w in ["run", "execute", "running", "executing", "shell", "command", "npm", "pip"]
106
+ ):
107
+ return ThoughtType.EXECUTING
108
+
109
+ # Refactoring - restructuring code
110
+ if any(
111
+ w in text_lower
112
+ for w in ["refactor", "restructure", "reorganize", "clean up", "simplify", "extract"]
113
+ ):
114
+ return ThoughtType.REFACTORING
115
+
116
+ # Debugging - fixing issues
117
+ if any(
118
+ w in text_lower
119
+ for w in ["debug", "error", "fix", "issue", "problem", "bug", "traceback", "exception"]
120
+ ):
121
+ return ThoughtType.DEBUGGING
122
+
123
+ # Planning - strategizing approach
124
+ if any(
125
+ w in text_lower
126
+ for w in ["plan", "step", "approach", "strategy", "first", "then", "next", "let me", "i'll"]
127
+ ):
128
+ return ThoughtType.PLANNING
129
+
130
+ # Analyzing - understanding code/problem
131
+ if any(
132
+ w in text_lower
133
+ for w in ["analyze", "understand", "examine", "look at", "check", "inspect", "review"]
134
+ ):
135
+ return ThoughtType.ANALYZING
136
+
137
+ # Deciding - making choices
138
+ if any(
139
+ w in text_lower
140
+ for w in ["decide", "choose", "option", "should i", "best way", "which", "either", "or"]
141
+ ):
142
+ return ThoughtType.DECIDING
143
+
144
+ # Searching - finding files/code
145
+ if any(
146
+ w in text_lower
147
+ for w in ["search", "find", "look for", "grep", "locate", "where is", "looking for"]
148
+ ):
149
+ return ThoughtType.SEARCHING
150
+
151
+ # Reading - examining content
152
+ if any(
153
+ w in text_lower
154
+ for w in ["read", "reading", "content", "see what", "open", "view", "contents of"]
155
+ ):
156
+ return ThoughtType.READING
157
+
158
+ # Writing - creating/modifying code
159
+ if any(
160
+ w in text_lower
161
+ for w in ["write", "create", "add", "implement", "modify", "update", "change", "edit"]
162
+ ):
163
+ return ThoughtType.WRITING
164
+
165
+ # Reflecting - meta-thinking
166
+ if any(
167
+ w in text_lower
168
+ for w in ["think", "consider", "hmm", "wait", "actually", "interesting", "notice"]
169
+ ):
170
+ return ThoughtType.REFLECTING
171
+
172
+ return ThoughtType.GENERAL
173
+
174
+
175
+ class ThinkingBubble(Static):
176
+ """Single thinking bubble widget."""
177
+
178
+ DEFAULT_CSS = """
179
+ ThinkingBubble {
180
+ height: auto;
181
+ margin: 0 0 0 2;
182
+ padding: 0;
183
+ }
184
+ """
185
+
186
+ def __init__(self, chunk: ThoughtChunk, **kwargs):
187
+ super().__init__(**kwargs)
188
+ self.chunk = chunk
189
+
190
+ def render(self) -> Text:
191
+ style = THOUGHT_STYLES.get(self.chunk.thought_type, THOUGHT_STYLES[ThoughtType.GENERAL])
192
+
193
+ result = Text()
194
+ result.append(f"{style['icon']} ", style=style["color"])
195
+
196
+ # Truncate long thoughts
197
+ text = self.chunk.text
198
+ if len(text) > 150:
199
+ text = text[:147] + "..."
200
+
201
+ result.append(text, style=f"italic #a1a1aa")
202
+
203
+ # Streaming indicator
204
+ if self.chunk.is_streaming:
205
+ result.append(" ●", style="bold #fbbf24")
206
+
207
+ return result
208
+
209
+
210
+ class ThinkingPanel(Container):
211
+ """
212
+ Panel displaying model thinking/reasoning.
213
+
214
+ Features:
215
+ - Collapsible display
216
+ - Streaming support
217
+ - Thought categorization
218
+ - Summary when collapsed
219
+ """
220
+
221
+ DEFAULT_CSS = """
222
+ ThinkingPanel {
223
+ height: auto;
224
+ max-height: 30%;
225
+ border: solid #27272a;
226
+ border-left: tall #ec4899;
227
+ background: #0d0d0d;
228
+ padding: 0 1;
229
+ margin: 0 0 1 0;
230
+ }
231
+
232
+ ThinkingPanel.collapsed {
233
+ max-height: 3;
234
+ }
235
+
236
+ ThinkingPanel .thinking-header {
237
+ height: 1;
238
+ margin-bottom: 1;
239
+ }
240
+
241
+ ThinkingPanel .thinking-content {
242
+ height: auto;
243
+ max-height: 20;
244
+ overflow-y: auto;
245
+ }
246
+ """
247
+
248
+ collapsed: reactive[bool] = reactive(False)
249
+ is_streaming: reactive[bool] = reactive(False)
250
+
251
+ def __init__(self, **kwargs):
252
+ super().__init__(**kwargs)
253
+ self._chunks: List[ThoughtChunk] = []
254
+ self._current_text = ""
255
+ self._timer: Optional[Timer] = None
256
+
257
+ def on_mount(self) -> None:
258
+ """Start animation timer."""
259
+ self._timer = self.set_interval(0.5, self._tick)
260
+
261
+ def _tick(self) -> None:
262
+ """Animation tick."""
263
+ if self.is_streaming:
264
+ self._update_header()
265
+
266
+ def toggle(self) -> None:
267
+ """Toggle collapsed state."""
268
+ self.collapsed = not self.collapsed
269
+ self.set_class(self.collapsed, "collapsed")
270
+ self._update_display()
271
+
272
+ def on_click(self, event: events.Click) -> None:
273
+ """Toggle on click."""
274
+ self.toggle()
275
+
276
+ def start_streaming(self) -> None:
277
+ """Start a new streaming thought."""
278
+ self.is_streaming = True
279
+ self._current_text = ""
280
+ self._update_header()
281
+
282
+ def append_chunk(self, text: str) -> None:
283
+ """Append text to current streaming thought."""
284
+ self._current_text += text
285
+ self._update_display()
286
+
287
+ def complete_thought(self) -> None:
288
+ """Complete the current streaming thought."""
289
+ if self._current_text:
290
+ thought_type = classify_thought(self._current_text)
291
+ chunk = ThoughtChunk(
292
+ text=self._current_text.strip(),
293
+ thought_type=thought_type,
294
+ is_streaming=False,
295
+ )
296
+ self._chunks.append(chunk)
297
+
298
+ self._current_text = ""
299
+ self.is_streaming = False
300
+ self._update_display()
301
+
302
+ def add_thought(self, text: str) -> None:
303
+ """Add a complete thought."""
304
+ thought_type = classify_thought(text)
305
+ chunk = ThoughtChunk(
306
+ text=text.strip(),
307
+ thought_type=thought_type,
308
+ is_streaming=False,
309
+ )
310
+ self._chunks.append(chunk)
311
+ self._update_display()
312
+
313
+ def _update_header(self) -> None:
314
+ """Update the header."""
315
+ try:
316
+ header = self.query_one(".thinking-header", Static)
317
+ except Exception:
318
+ return
319
+
320
+ text = Text()
321
+ text.append("💭 ", style="bold #ec4899")
322
+ text.append("Thinking", style="bold #e4e4e7")
323
+
324
+ if self.is_streaming:
325
+ text.append(" ● ", style="bold #fbbf24")
326
+ text.append("Streaming...", style="italic #fbbf24")
327
+ elif self._chunks:
328
+ text.append(f" ({len(self._chunks)} thoughts)", style="#6b7280")
329
+
330
+ # Collapse indicator
331
+ icon = "▶" if self.collapsed else "▼"
332
+ text.append(f" {icon}", style="#52525b")
333
+
334
+ header.update(text)
335
+
336
+ def _update_display(self) -> None:
337
+ """Update the display."""
338
+ self._update_header()
339
+
340
+ if self.collapsed:
341
+ return
342
+
343
+ try:
344
+ content = self.query_one(".thinking-content", Container)
345
+ except Exception:
346
+ return
347
+
348
+ # Clear existing bubbles
349
+ content.remove_children()
350
+
351
+ # Add thought bubbles
352
+ visible_chunks = self._chunks[-10:] # Show last 10
353
+ for chunk in visible_chunks:
354
+ bubble = ThinkingBubble(chunk)
355
+ content.mount(bubble)
356
+
357
+ # Add current streaming chunk
358
+ if self._current_text:
359
+ streaming_chunk = ThoughtChunk(
360
+ text=self._current_text,
361
+ thought_type=classify_thought(self._current_text),
362
+ is_streaming=True,
363
+ )
364
+ content.mount(ThinkingBubble(streaming_chunk))
365
+
366
+ def clear(self) -> None:
367
+ """Clear all thoughts."""
368
+ self._chunks.clear()
369
+ self._current_text = ""
370
+ self.is_streaming = False
371
+
372
+ try:
373
+ content = self.query_one(".thinking-content", Container)
374
+ content.remove_children()
375
+ except Exception:
376
+ pass
377
+
378
+ self._update_header()
379
+
380
+ def compose(self):
381
+ """Compose the panel."""
382
+ yield Static("", classes="thinking-header")
383
+ with Container(classes="thinking-content"):
384
+ pass
385
+
386
+
387
+ class ExtendedThinkingPanel(Container):
388
+ """
389
+ Extended thinking display for Claude-style extended thinking.
390
+
391
+ Shows the full thinking trace with:
392
+ - Collapsible major sections
393
+ - Search through thoughts
394
+ - Copy thinking text
395
+ """
396
+
397
+ DEFAULT_CSS = """
398
+ ExtendedThinkingPanel {
399
+ height: auto;
400
+ max-height: 50%;
401
+ border: double #a855f7;
402
+ background: #0d0d0d;
403
+ padding: 1;
404
+ margin: 0 0 1 0;
405
+ }
406
+
407
+ ExtendedThinkingPanel .extended-header {
408
+ height: 2;
409
+ border-bottom: solid #27272a;
410
+ margin-bottom: 1;
411
+ }
412
+
413
+ ExtendedThinkingPanel .extended-content {
414
+ height: auto;
415
+ max-height: 40;
416
+ overflow-y: auto;
417
+ }
418
+
419
+ ExtendedThinkingPanel .extended-footer {
420
+ height: 1;
421
+ border-top: solid #27272a;
422
+ margin-top: 1;
423
+ }
424
+ """
425
+
426
+ collapsed: reactive[bool] = reactive(False)
427
+
428
+ def __init__(self, **kwargs):
429
+ super().__init__(**kwargs)
430
+ self._thinking_text = ""
431
+ self._token_count = 0
432
+
433
+ def set_thinking(self, text: str, token_count: int = 0) -> None:
434
+ """Set the extended thinking text."""
435
+ self._thinking_text = text
436
+ self._token_count = token_count
437
+ self._update_display()
438
+
439
+ def append_thinking(self, text: str) -> None:
440
+ """Append to thinking text."""
441
+ self._thinking_text += text
442
+ self._update_display()
443
+
444
+ def _update_display(self) -> None:
445
+ """Update the display."""
446
+ try:
447
+ header = self.query_one(".extended-header", Static)
448
+ content = self.query_one(".extended-content", Static)
449
+ footer = self.query_one(".extended-footer", Static)
450
+ except Exception:
451
+ return
452
+
453
+ # Header
454
+ header_text = Text()
455
+ header_text.append("🧠 ", style="bold #a855f7")
456
+ header_text.append("Extended Thinking", style="bold #e4e4e7")
457
+
458
+ if self._token_count:
459
+ header_text.append(f" ({self._token_count} tokens)", style="#6b7280")
460
+
461
+ header.update(header_text)
462
+
463
+ # Content
464
+ if self.collapsed:
465
+ content_text = Text()
466
+ lines = self._thinking_text.splitlines()
467
+ if lines:
468
+ preview = lines[0][:80] + "..." if len(lines[0]) > 80 else lines[0]
469
+ content_text.append(preview, style="italic #a1a1aa")
470
+ if len(lines) > 1:
471
+ content_text.append(f"\n... ({len(lines)} lines)", style="#52525b")
472
+ content.update(content_text)
473
+ else:
474
+ content.update(Text(self._thinking_text, style="#a1a1aa"))
475
+
476
+ # Footer
477
+ footer_text = Text()
478
+ word_count = len(self._thinking_text.split())
479
+ footer_text.append(f"📊 {word_count} words", style="#6b7280")
480
+ footer_text.append(" │ ", style="#27272a")
481
+ footer_text.append("[Space] Toggle", style="#52525b")
482
+ footer.update(footer_text)
483
+
484
+ def toggle(self) -> None:
485
+ """Toggle collapsed state."""
486
+ self.collapsed = not self.collapsed
487
+ self._update_display()
488
+
489
+ def on_key(self, event: events.Key) -> None:
490
+ """Handle key events."""
491
+ if event.key == "space":
492
+ self.toggle()
493
+ event.prevent_default()
494
+
495
+ def clear(self) -> None:
496
+ """Clear thinking."""
497
+ self._thinking_text = ""
498
+ self._token_count = 0
499
+ self._update_display()
500
+
501
+ def compose(self):
502
+ """Compose the panel."""
503
+ yield Static("", classes="extended-header")
504
+ yield Static("", classes="extended-content")
505
+ yield Static("", classes="extended-footer")
506
+
507
+
508
+ class ThinkingIndicator(Static):
509
+ """Compact thinking indicator for status bar."""
510
+
511
+ DEFAULT_CSS = """
512
+ ThinkingIndicator {
513
+ width: auto;
514
+ height: 1;
515
+ padding: 0 1;
516
+ }
517
+ """
518
+
519
+ thinking: reactive[bool] = reactive(False)
520
+
521
+ def __init__(self, **kwargs):
522
+ super().__init__(**kwargs)
523
+ self._frame = 0
524
+ self._thought_count = 0
525
+
526
+ def set_thinking(self, is_thinking: bool) -> None:
527
+ """Set thinking state."""
528
+ self.thinking = is_thinking
529
+ self.refresh()
530
+
531
+ def set_count(self, count: int) -> None:
532
+ """Set thought count."""
533
+ self._thought_count = count
534
+ self.refresh()
535
+
536
+ def animate(self) -> None:
537
+ """Advance animation frame."""
538
+ self._frame += 1
539
+ if self.thinking:
540
+ self.refresh()
541
+
542
+ def render(self) -> Text:
543
+ text = Text()
544
+
545
+ if self.thinking:
546
+ # Animated thinking indicator
547
+ frames = ["💭", "💬", "💭", "💬"]
548
+ icon = frames[self._frame % len(frames)]
549
+ text.append(f"{icon} ", style="bold #ec4899")
550
+ text.append("Thinking...", style="italic #ec4899")
551
+ elif self._thought_count > 0:
552
+ text.append("💭 ", style="#6b7280")
553
+ text.append(f"{self._thought_count}", style="#a1a1aa")
554
+ else:
555
+ text.append("💭 -", style="#52525b")
556
+
557
+ return text
558
+
559
+
560
+ class ThinkingSource(Enum):
561
+ """Source of thinking/reasoning content."""
562
+
563
+ ACP = "acp" # ACP agent_thought_chunk
564
+ BYOK = "byok" # BYOK StreamChunk.thinking_content
565
+ LOCAL = "local" # Local models (through BYOK gateway)
566
+ OPEN_RESPONSES = "openresponses" # Open Responses reasoning.delta
567
+
568
+
569
+ @dataclass
570
+ class ThinkingStats:
571
+ """Statistics for thinking/reasoning."""
572
+
573
+ source: ThinkingSource
574
+ token_count: int = 0
575
+ thought_count: int = 0
576
+ duration_ms: float = 0.0
577
+
578
+
579
+ class UnifiedThinkingManager:
580
+ """
581
+ Routes thinking from any source to ThinkingPanel.
582
+
583
+ Provides a unified interface for handling thinking/reasoning content
584
+ from multiple connection modes:
585
+ - ACP: agent_thought_chunk events
586
+ - BYOK: StreamChunk.thinking_content
587
+ - Local: Route through BYOK gateway
588
+ - OpenResponses: response.reasoning.delta events
589
+
590
+ Usage:
591
+ panel = ThinkingPanel()
592
+ manager = UnifiedThinkingManager(panel)
593
+
594
+ # In ACP mode
595
+ await manager.handle_acp_thought("Planning approach...")
596
+
597
+ # In BYOK mode
598
+ await manager.handle_byok_chunk(chunk)
599
+
600
+ # In Open Responses mode
601
+ await manager.handle_openresponses_event(event)
602
+ """
603
+
604
+ def __init__(
605
+ self,
606
+ panel: ThinkingPanel,
607
+ extended_panel: Optional[ExtendedThinkingPanel] = None,
608
+ indicator: Optional[ThinkingIndicator] = None,
609
+ ):
610
+ self.panel = panel
611
+ self.extended_panel = extended_panel
612
+ self.indicator = indicator
613
+ self._current_source: Optional[ThinkingSource] = None
614
+ self._stats = ThinkingStats(source=ThinkingSource.BYOK)
615
+ self._start_time: Optional[float] = None
616
+ self._is_streaming = False
617
+
618
+ def start_session(self, source: ThinkingSource) -> None:
619
+ """Start a new thinking session."""
620
+ self._current_source = source
621
+ self._stats = ThinkingStats(source=source)
622
+ self._start_time = (
623
+ asyncio.get_event_loop().time() if asyncio.get_event_loop().is_running() else None
624
+ )
625
+ self._is_streaming = False
626
+ self.panel.clear()
627
+ if self.extended_panel:
628
+ self.extended_panel.clear()
629
+
630
+ def end_session(self) -> ThinkingStats:
631
+ """End the current thinking session and return stats."""
632
+ if self._start_time:
633
+ try:
634
+ loop = asyncio.get_event_loop()
635
+ if loop.is_running():
636
+ self._stats.duration_ms = (loop.time() - self._start_time) * 1000
637
+ except RuntimeError:
638
+ pass
639
+
640
+ if self._is_streaming:
641
+ self.panel.complete_thought()
642
+ self._is_streaming = False
643
+
644
+ if self.indicator:
645
+ self.indicator.set_thinking(False)
646
+ self.indicator.set_count(self._stats.thought_count)
647
+
648
+ return self._stats
649
+
650
+ async def handle_acp_thought(self, text: str) -> None:
651
+ """
652
+ Handle ACP agent_thought_chunk.
653
+
654
+ ACP sends complete thought chunks, not streaming deltas.
655
+ """
656
+ if not text:
657
+ return
658
+
659
+ self._current_source = ThinkingSource.ACP
660
+ self._stats.thought_count += 1
661
+
662
+ # ACP thoughts are complete - add directly
663
+ self.panel.add_thought(text)
664
+
665
+ if self.indicator:
666
+ self.indicator.set_thinking(True)
667
+
668
+ async def handle_byok_chunk(self, chunk: Any) -> None:
669
+ """
670
+ Handle BYOK StreamChunk.thinking_content.
671
+
672
+ BYOK sends streaming deltas that need to be accumulated.
673
+
674
+ Args:
675
+ chunk: StreamChunk with optional thinking_content field
676
+ """
677
+ thinking_content = getattr(chunk, "thinking_content", None)
678
+ if not thinking_content:
679
+ return
680
+
681
+ self._current_source = ThinkingSource.BYOK
682
+
683
+ # Start streaming if not already
684
+ if not self._is_streaming:
685
+ self.panel.start_streaming()
686
+ self._is_streaming = True
687
+ if self.indicator:
688
+ self.indicator.set_thinking(True)
689
+
690
+ # Append chunk to current streaming thought
691
+ self.panel.append_chunk(thinking_content)
692
+
693
+ # Also update extended panel if available
694
+ if self.extended_panel:
695
+ self.extended_panel.append_thinking(thinking_content)
696
+
697
+ async def handle_byok_response(self, response: Any) -> None:
698
+ """
699
+ Handle complete BYOK GatewayResponse.thinking_content.
700
+
701
+ Called after non-streaming completion with full thinking.
702
+
703
+ Args:
704
+ response: GatewayResponse with optional thinking_content field
705
+ """
706
+ thinking_content = getattr(response, "thinking_content", None)
707
+ thinking_tokens = getattr(response, "thinking_tokens", None)
708
+
709
+ if not thinking_content:
710
+ return
711
+
712
+ self._current_source = ThinkingSource.BYOK
713
+ self._stats.thought_count += 1
714
+
715
+ if thinking_tokens:
716
+ self._stats.token_count = thinking_tokens
717
+
718
+ # Add complete thought
719
+ self.panel.add_thought(thinking_content)
720
+
721
+ # Update extended panel with full content
722
+ if self.extended_panel:
723
+ self.extended_panel.set_thinking(thinking_content, thinking_tokens or 0)
724
+
725
+ if self.indicator:
726
+ self.indicator.set_count(self._stats.thought_count)
727
+
728
+ async def handle_openresponses_event(self, event: Dict[str, Any]) -> None:
729
+ """
730
+ Handle Open Responses streaming event.
731
+
732
+ Open Responses sends multiple event types:
733
+ - response.reasoning.delta: Reasoning text delta
734
+ - response.reasoning.done: Reasoning complete
735
+ - response.output_text.delta: Regular output delta
736
+
737
+ Args:
738
+ event: Open Responses streaming event dict
739
+ """
740
+ event_type = event.get("type", "")
741
+
742
+ if event_type == "response.reasoning.delta":
743
+ delta = event.get("delta", "")
744
+ if delta:
745
+ self._current_source = ThinkingSource.OPEN_RESPONSES
746
+
747
+ if not self._is_streaming:
748
+ self.panel.start_streaming()
749
+ self._is_streaming = True
750
+ if self.indicator:
751
+ self.indicator.set_thinking(True)
752
+
753
+ self.panel.append_chunk(delta)
754
+
755
+ if self.extended_panel:
756
+ self.extended_panel.append_thinking(delta)
757
+
758
+ elif event_type == "response.reasoning.done":
759
+ # Reasoning complete - finalize
760
+ if self._is_streaming:
761
+ self.panel.complete_thought()
762
+ self._is_streaming = False
763
+ self._stats.thought_count += 1
764
+
765
+ # Extract token count if available
766
+ usage = event.get("usage", {})
767
+ reasoning_tokens = usage.get("reasoning_tokens", 0)
768
+ if reasoning_tokens:
769
+ self._stats.token_count = reasoning_tokens
770
+
771
+ if self.indicator:
772
+ self.indicator.set_thinking(False)
773
+ self.indicator.set_count(self._stats.thought_count)
774
+
775
+ async def handle_local_thought(self, text: str) -> None:
776
+ """
777
+ Handle Local model thinking (routed through BYOK).
778
+
779
+ Local models may provide thinking through various mechanisms
780
+ depending on the model and server.
781
+ """
782
+ if not text:
783
+ return
784
+
785
+ self._current_source = ThinkingSource.LOCAL
786
+ self._stats.thought_count += 1
787
+
788
+ self.panel.add_thought(text)
789
+
790
+ if self.indicator:
791
+ self.indicator.set_thinking(True)
792
+
793
+ def complete_streaming(self) -> None:
794
+ """Complete any in-progress streaming thought."""
795
+ if self._is_streaming:
796
+ self.panel.complete_thought()
797
+ self._is_streaming = False
798
+ self._stats.thought_count += 1
799
+
800
+ if self.indicator:
801
+ self.indicator.set_thinking(False)
802
+
803
+ def get_stats(self) -> ThinkingStats:
804
+ """Get current thinking statistics."""
805
+ return self._stats
806
+
807
+ @property
808
+ def current_source(self) -> Optional[ThinkingSource]:
809
+ """Get the current thinking source."""
810
+ return self._current_source
811
+
812
+ @property
813
+ def is_streaming(self) -> bool:
814
+ """Check if currently streaming thinking content."""
815
+ return self._is_streaming