tau-coding-agent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. tau/__init__.py +0 -0
  2. tau/agent/__init__.py +11 -0
  3. tau/agent/prompt/__init__.py +10 -0
  4. tau/agent/prompt/builder.py +302 -0
  5. tau/agent/prompt/types.py +33 -0
  6. tau/agent/service.py +369 -0
  7. tau/agent/types.py +61 -0
  8. tau/auth/manager.py +247 -0
  9. tau/auth/storage.py +82 -0
  10. tau/auth/types.py +41 -0
  11. tau/builtins/__init__.py +4 -0
  12. tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
  13. tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
  14. tau/builtins/commands/__init__.py +41 -0
  15. tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  16. tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
  17. tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
  18. tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
  19. tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
  20. tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
  21. tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
  22. tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
  23. tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
  24. tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
  25. tau/builtins/commands/clear.py +16 -0
  26. tau/builtins/commands/compact.py +28 -0
  27. tau/builtins/commands/reload.py +27 -0
  28. tau/builtins/commands/session.py +19 -0
  29. tau/builtins/extensions/footer/__init__.py +76 -0
  30. tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
  31. tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
  32. tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
  33. tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
  34. tau/builtins/extensions/footer/git.py +26 -0
  35. tau/builtins/extensions/footer/model.py +69 -0
  36. tau/builtins/extensions/footer/utils.py +44 -0
  37. tau/builtins/extensions/header/__init__.py +18 -0
  38. tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
  39. tau/builtins/models/__init__.py +0 -0
  40. tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
  41. tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
  42. tau/builtins/models/audio.py +43 -0
  43. tau/builtins/models/image.py +43 -0
  44. tau/builtins/models/text.py +482 -0
  45. tau/builtins/models/video.py +40 -0
  46. tau/builtins/prompts/commit.md +7 -0
  47. tau/builtins/prompts/docs.md +7 -0
  48. tau/builtins/prompts/explain.md +7 -0
  49. tau/builtins/prompts/fix.md +7 -0
  50. tau/builtins/prompts/refactor.md +7 -0
  51. tau/builtins/prompts/review.md +7 -0
  52. tau/builtins/prompts/test.md +7 -0
  53. tau/builtins/providers/__init__.py +0 -0
  54. tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  55. tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
  56. tau/builtins/providers/audio.py +10 -0
  57. tau/builtins/providers/image.py +9 -0
  58. tau/builtins/providers/text.py +33 -0
  59. tau/builtins/providers/video.py +6 -0
  60. tau/builtins/skills/code-review/SKILL.md +4 -0
  61. tau/builtins/skills/debug/SKILL.md +4 -0
  62. tau/builtins/skills/git-commit/SKILL.md +4 -0
  63. tau/builtins/themes/dark.yaml +1 -0
  64. tau/builtins/themes/light.yaml +46 -0
  65. tau/builtins/tools/__init__.py +73 -0
  66. tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  67. tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  68. tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
  69. tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
  70. tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
  71. tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
  72. tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
  73. tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
  74. tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
  75. tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
  76. tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
  77. tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
  78. tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
  79. tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
  80. tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
  81. tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
  82. tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
  83. tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
  84. tau/builtins/tools/edit.py +215 -0
  85. tau/builtins/tools/glob.py +112 -0
  86. tau/builtins/tools/grep.py +146 -0
  87. tau/builtins/tools/ls.py +135 -0
  88. tau/builtins/tools/read.py +122 -0
  89. tau/builtins/tools/terminal.py +150 -0
  90. tau/builtins/tools/write.py +105 -0
  91. tau/commands/__init__.py +10 -0
  92. tau/commands/registry.py +71 -0
  93. tau/commands/types.py +33 -0
  94. tau/console/__init__.py +0 -0
  95. tau/console/cli.py +266 -0
  96. tau/console/commands/__init__.py +0 -0
  97. tau/console/commands/auth.py +193 -0
  98. tau/console/commands/packages.py +104 -0
  99. tau/console/commands/update.py +76 -0
  100. tau/core/__init__.py +0 -0
  101. tau/core/registry.py +102 -0
  102. tau/engine/__init__.py +47 -0
  103. tau/engine/service.py +768 -0
  104. tau/engine/types.py +163 -0
  105. tau/extensions/__init__.py +28 -0
  106. tau/extensions/api.py +928 -0
  107. tau/extensions/context.py +462 -0
  108. tau/extensions/events.py +70 -0
  109. tau/extensions/loader.py +386 -0
  110. tau/extensions/runtime.py +184 -0
  111. tau/extensions/settings.py +137 -0
  112. tau/hooks/__init__.py +112 -0
  113. tau/hooks/engine.py +237 -0
  114. tau/hooks/inference.py +21 -0
  115. tau/hooks/runtime.py +126 -0
  116. tau/hooks/service.py +121 -0
  117. tau/hooks/session.py +117 -0
  118. tau/hooks/tui.py +61 -0
  119. tau/hooks/types.py +72 -0
  120. tau/inference/__init__.py +180 -0
  121. tau/inference/api/__init__.py +0 -0
  122. tau/inference/api/audio/__init__.py +0 -0
  123. tau/inference/api/audio/base.py +29 -0
  124. tau/inference/api/audio/builtins.py +15 -0
  125. tau/inference/api/audio/elevenlabs_audio.py +183 -0
  126. tau/inference/api/audio/gemini_audio.py +95 -0
  127. tau/inference/api/audio/openai_audio.py +159 -0
  128. tau/inference/api/audio/registry.py +15 -0
  129. tau/inference/api/audio/sarvam_audio.py +163 -0
  130. tau/inference/api/audio/service.py +103 -0
  131. tau/inference/api/audio/utils.py +47 -0
  132. tau/inference/api/image/__init__.py +0 -0
  133. tau/inference/api/image/base.py +17 -0
  134. tau/inference/api/image/builtins.py +8 -0
  135. tau/inference/api/image/gemini_image.py +77 -0
  136. tau/inference/api/image/openai_image.py +103 -0
  137. tau/inference/api/image/openrouter.py +144 -0
  138. tau/inference/api/image/registry.py +15 -0
  139. tau/inference/api/image/service.py +71 -0
  140. tau/inference/api/registry.py +82 -0
  141. tau/inference/api/text/__init__.py +0 -0
  142. tau/inference/api/text/anthropic_claude_code.py +222 -0
  143. tau/inference/api/text/anthropic_messages.py +196 -0
  144. tau/inference/api/text/base.py +40 -0
  145. tau/inference/api/text/builtins.py +19 -0
  146. tau/inference/api/text/gemini_generate.py +234 -0
  147. tau/inference/api/text/github_copilot_chat.py +172 -0
  148. tau/inference/api/text/google_antigravity.py +522 -0
  149. tau/inference/api/text/mistral_chat.py +284 -0
  150. tau/inference/api/text/ollama_chat.py +200 -0
  151. tau/inference/api/text/openai_codex_responses.py +497 -0
  152. tau/inference/api/text/openai_completions.py +227 -0
  153. tau/inference/api/text/openai_responses.py +235 -0
  154. tau/inference/api/text/registry.py +50 -0
  155. tau/inference/api/text/service.py +297 -0
  156. tau/inference/api/text/types.py +7 -0
  157. tau/inference/api/text/utils.py +228 -0
  158. tau/inference/api/video/__init__.py +0 -0
  159. tau/inference/api/video/base.py +26 -0
  160. tau/inference/api/video/builtins.py +7 -0
  161. tau/inference/api/video/fal_video.py +119 -0
  162. tau/inference/api/video/openrouter_video.py +142 -0
  163. tau/inference/api/video/registry.py +15 -0
  164. tau/inference/api/video/service.py +72 -0
  165. tau/inference/model/__init__.py +0 -0
  166. tau/inference/model/registry.py +102 -0
  167. tau/inference/model/types.py +65 -0
  168. tau/inference/provider/__init__.py +0 -0
  169. tau/inference/provider/oauth/__init__.py +35 -0
  170. tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
  171. tau/inference/provider/oauth/github_copilot.py +333 -0
  172. tau/inference/provider/oauth/google_antigravity.py +258 -0
  173. tau/inference/provider/oauth/openai_codex.py +309 -0
  174. tau/inference/provider/oauth/pkce.py +14 -0
  175. tau/inference/provider/oauth/types.py +46 -0
  176. tau/inference/provider/oauth/utils.py +154 -0
  177. tau/inference/provider/registry.py +141 -0
  178. tau/inference/provider/types.py +114 -0
  179. tau/inference/types.py +549 -0
  180. tau/inference/utils.py +219 -0
  181. tau/message/__init__.py +0 -0
  182. tau/message/types.py +482 -0
  183. tau/message/utils.py +178 -0
  184. tau/packages/__init__.py +11 -0
  185. tau/packages/manager.py +190 -0
  186. tau/packages/types.py +20 -0
  187. tau/packages/utils.py +67 -0
  188. tau/prompts/expand.py +58 -0
  189. tau/prompts/loader.py +69 -0
  190. tau/prompts/registry.py +45 -0
  191. tau/prompts/types.py +24 -0
  192. tau/rpc/__init__.py +8 -0
  193. tau/rpc/mode.py +783 -0
  194. tau/rpc/types.py +252 -0
  195. tau/runtime/service.py +759 -0
  196. tau/runtime/types.py +303 -0
  197. tau/session/branch_summarization.py +312 -0
  198. tau/session/compaction.py +646 -0
  199. tau/session/manager.py +652 -0
  200. tau/session/types.py +188 -0
  201. tau/session/utils.py +233 -0
  202. tau/settings/manager.py +1077 -0
  203. tau/settings/paths.py +150 -0
  204. tau/settings/storage.py +63 -0
  205. tau/settings/types.py +173 -0
  206. tau/settings/utils.py +25 -0
  207. tau/skills/loader.py +91 -0
  208. tau/skills/registry.py +70 -0
  209. tau/skills/types.py +25 -0
  210. tau/themes/loader.py +238 -0
  211. tau/themes/registry.py +108 -0
  212. tau/themes/types.py +19 -0
  213. tau/tool/__init__.py +3 -0
  214. tau/tool/registry.py +117 -0
  215. tau/tool/render.py +21 -0
  216. tau/tool/types.py +244 -0
  217. tau/trust/__init__.py +13 -0
  218. tau/trust/manager.py +80 -0
  219. tau/trust/types.py +14 -0
  220. tau/trust/utils.py +72 -0
  221. tau/tui/__init__.py +54 -0
  222. tau/tui/agent_hooks.py +346 -0
  223. tau/tui/ansi.py +330 -0
  224. tau/tui/app.py +540 -0
  225. tau/tui/autocomplete.py +33 -0
  226. tau/tui/capabilities.py +119 -0
  227. tau/tui/commands/__init__.py +3 -0
  228. tau/tui/commands/appearance.py +498 -0
  229. tau/tui/commands/auth.py +232 -0
  230. tau/tui/commands/context.py +38 -0
  231. tau/tui/commands/misc.py +82 -0
  232. tau/tui/commands/model.py +118 -0
  233. tau/tui/commands/session.py +464 -0
  234. tau/tui/component.py +268 -0
  235. tau/tui/components/__init__.py +0 -0
  236. tau/tui/components/autocomplete_manager.py +267 -0
  237. tau/tui/components/autocomplete_picker.py +143 -0
  238. tau/tui/components/box.py +90 -0
  239. tau/tui/components/command_palette.py +144 -0
  240. tau/tui/components/dynamic_border.py +19 -0
  241. tau/tui/components/file_picker.py +233 -0
  242. tau/tui/components/image.py +181 -0
  243. tau/tui/components/inline_selector.py +71 -0
  244. tau/tui/components/layout.py +1194 -0
  245. tau/tui/components/message_list.py +692 -0
  246. tau/tui/components/modal.py +97 -0
  247. tau/tui/components/model_palette.py +204 -0
  248. tau/tui/components/picker_overlay.py +174 -0
  249. tau/tui/components/prompt_overlay.py +236 -0
  250. tau/tui/components/resume_modal.py +372 -0
  251. tau/tui/components/select_list.py +222 -0
  252. tau/tui/components/settings_modal.py +274 -0
  253. tau/tui/components/settings_schema.py +203 -0
  254. tau/tui/components/spinner.py +119 -0
  255. tau/tui/components/text_input.py +396 -0
  256. tau/tui/components/text_prompt.py +82 -0
  257. tau/tui/components/tree_select_list.py +580 -0
  258. tau/tui/components/trust_screen.py +97 -0
  259. tau/tui/diff.py +114 -0
  260. tau/tui/fuzzy.py +99 -0
  261. tau/tui/input.py +496 -0
  262. tau/tui/input_handler.py +716 -0
  263. tau/tui/keybindings.py +87 -0
  264. tau/tui/markdown.py +286 -0
  265. tau/tui/message_renderers.py +31 -0
  266. tau/tui/overlay.py +326 -0
  267. tau/tui/renderer.py +378 -0
  268. tau/tui/terminal.py +499 -0
  269. tau/tui/theme.py +148 -0
  270. tau/tui/tui.py +544 -0
  271. tau/tui/ui_context.py +768 -0
  272. tau/tui/utils.py +20 -0
  273. tau/utils/__init__.py +0 -0
  274. tau/utils/http_proxy.py +221 -0
  275. tau/utils/image_processing.py +172 -0
  276. tau/utils/secrets.py +59 -0
  277. tau/utils/version_check.py +60 -0
  278. tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
  279. tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
  280. tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
  281. tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  282. tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
  283. tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,498 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from tau.tui.commands.context import CommandContext
6
+
7
+
8
+ def _headers_to_str(headers: dict | None) -> str:
9
+ if not headers:
10
+ return ""
11
+ return json.dumps(headers)
12
+
13
+
14
+ def open_theme_selector(ctx: CommandContext) -> None:
15
+ """Open the theme selector modal with live preview."""
16
+ from tau.themes.registry import theme_registry
17
+
18
+ names = theme_registry.list()
19
+ sm = ctx.runtime.settings_manager
20
+ original = (sm.get_theme() if sm is not None else None) or "default"
21
+
22
+ def preview(name: str) -> None:
23
+ """Preview the selected theme."""
24
+ try:
25
+ ctx.layout.set_theme(theme_registry.get(name))
26
+ except ValueError:
27
+ pass
28
+
29
+ def commit(name: str) -> None:
30
+ """Apply the selected theme."""
31
+ try:
32
+ theme = theme_registry.get(name)
33
+ except ValueError:
34
+ return
35
+ ctx.layout.set_theme(theme)
36
+ if sm is not None:
37
+ sm.set_theme(name)
38
+ ctx.notify(f"Theme set to {name}")
39
+
40
+ def cancel() -> None:
41
+ """Revert to the original theme."""
42
+ try:
43
+ ctx.layout.set_theme(theme_registry.get(original))
44
+ except ValueError:
45
+ pass
46
+ ctx.notify(f"Kept theme {original}")
47
+
48
+ ctx.layout.open_theme_selector(names, original, preview, commit, cancel)
49
+
50
+
51
+
52
+ def open_settings_panel(ctx: CommandContext) -> None:
53
+ """Open the interactive settings modal."""
54
+ from tau.tui.components.settings_modal import SettingItem, SettingsModal
55
+ from tau.inference.types import Transport, ThinkingLevel
56
+ from tau.engine.types import SteeringMode, FollowupMode
57
+ from tau.themes.registry import theme_registry
58
+
59
+ sm = ctx.runtime.settings_manager
60
+ if sm is None:
61
+ ctx.notify("Settings unavailable.")
62
+ return
63
+
64
+ items: list[SettingItem] = [
65
+ SettingItem(
66
+ id="quiet_startup",
67
+ label="Quiet startup",
68
+ description="Suppress verbose output at startup",
69
+ current_value="true" if sm.get_quiet_startup() else "false",
70
+ values=["false", "true"],
71
+ ),
72
+ SettingItem(
73
+ id="show_thinking",
74
+ label="Show thinking",
75
+ description="Display extended reasoning blocks in responses",
76
+ current_value="true" if sm.get_show_thinking() else "false",
77
+ values=["true", "false"],
78
+ ),
79
+ SettingItem(
80
+ id="show_tool_calls",
81
+ label="Show tool calls",
82
+ description="Display tool call and result blocks",
83
+ current_value="true" if sm.get_show_tool_calls() else "false",
84
+ values=["true", "false"],
85
+ ),
86
+ SettingItem(
87
+ id="show_images",
88
+ label="Show images",
89
+ description="Render inline images using terminal graphics (Kitty/iTerm2); disable to show text placeholders",
90
+ current_value="true" if sm.get_show_images() else "false",
91
+ values=["true", "false"],
92
+ ),
93
+ SettingItem(
94
+ id="image_auto_resize",
95
+ label="Auto-resize images",
96
+ description="Resize large images to 2000×2000 before sending",
97
+ current_value="true" if sm.get_image_auto_resize() else "false",
98
+ values=["true", "false"],
99
+ ),
100
+ SettingItem(
101
+ id="image_block",
102
+ label="Block images",
103
+ description="Prevent images from being sent to providers",
104
+ current_value="true" if sm.get_image_block_images() else "false",
105
+ values=["true", "false"],
106
+ ),
107
+ SettingItem(
108
+ id="steering_mode",
109
+ label="Steering mode",
110
+ description="How queued steering messages are delivered while the agent streams",
111
+ current_value=sm.get_steering_mode().value,
112
+ values=[m.value for m in SteeringMode],
113
+ ),
114
+ SettingItem(
115
+ id="follow_up_mode",
116
+ label="Follow-up mode",
117
+ description="How queued follow-up messages are delivered after the agent stops",
118
+ current_value=sm.get_follow_up_mode().value,
119
+ values=[m.value for m in FollowupMode],
120
+ ),
121
+ SettingItem(
122
+ id="transport",
123
+ label="Transport",
124
+ description="Wire protocol used to reach provider endpoints",
125
+ current_value=sm.get_transport().value,
126
+ values=[t.value for t in Transport],
127
+ ),
128
+ SettingItem(
129
+ id="thinking_level",
130
+ label="Thinking level",
131
+ description="Default reasoning depth for thinking-capable models",
132
+ current_value=getattr(sm.get_thinking_level(), 'value', None) or ThinkingLevel.Off.value,
133
+ submenu_items=[lv.value for lv in ThinkingLevel],
134
+ submenu_title="Thinking Level",
135
+ ),
136
+ SettingItem(
137
+ id="theme",
138
+ label="Theme",
139
+ description="Color theme for the interface",
140
+ current_value=sm.get_theme() or "default",
141
+ submenu_items=theme_registry.list(),
142
+ submenu_title="Theme",
143
+ ),
144
+ SettingItem(
145
+ id="proxy",
146
+ label="Proxy",
147
+ description="HTTP proxy URL, exclusions, and custom headers",
148
+ current_value=sm.get_proxy_url() or "(none)",
149
+ submenu_title="Proxy Settings",
150
+ submenu_settings=[
151
+ SettingItem(
152
+ id="proxy_url",
153
+ label="URL",
154
+ description="Proxy URL for HTTP and HTTPS requests (overrides HTTP_PROXY env var)",
155
+ current_value=sm.get_proxy_url() or "",
156
+ text_input=True,
157
+ ),
158
+ SettingItem(
159
+ id="proxy_no_proxy",
160
+ label="No-proxy hosts",
161
+ description="Comma-separated hostnames to exclude from proxying (overrides NO_PROXY env var)",
162
+ current_value=sm.get_no_proxy() or "",
163
+ text_input=True,
164
+ ),
165
+ SettingItem(
166
+ id="proxy_headers",
167
+ label="Headers (JSON)",
168
+ description='Custom proxy headers as JSON object, e.g. {"Authorization": "Bearer token"}',
169
+ current_value=_headers_to_str(sm.get_proxy_headers()),
170
+ text_input=True,
171
+ ),
172
+ ],
173
+ ),
174
+ SettingItem(
175
+ id="retry",
176
+ label="Retry",
177
+ description="Automatic retry behaviour for failed API requests",
178
+ current_value="on" if sm.is_retry_enabled() else "off",
179
+ submenu_title="Retry Settings",
180
+ submenu_settings=[
181
+ SettingItem(
182
+ id="retry_enabled",
183
+ label="Enabled",
184
+ description="Automatically retry failed API requests",
185
+ current_value="true" if sm.is_retry_enabled() else "false",
186
+ values=["false", "true"],
187
+ ),
188
+ SettingItem(
189
+ id="retry_max_retries",
190
+ label="Max attempts",
191
+ description="Maximum number of automatic retry attempts (default: 3)",
192
+ current_value=str(sm.get_retry_max_retries()),
193
+ text_input=True,
194
+ ),
195
+ SettingItem(
196
+ id="retry_base_delay_ms",
197
+ label="Base delay (ms)",
198
+ description="Base delay between retries in milliseconds (default: 1000)",
199
+ current_value=str(sm.get_retry_base_delay_ms()),
200
+ text_input=True,
201
+ ),
202
+ ],
203
+ ),
204
+ SettingItem(
205
+ id="compaction",
206
+ label="Compaction",
207
+ description="Automatic context compaction when approaching the token limit",
208
+ current_value="on" if sm.is_compaction_enabled() else "off",
209
+ submenu_title="Compaction Settings",
210
+ submenu_settings=[
211
+ SettingItem(
212
+ id="compaction_enabled",
213
+ label="Enabled",
214
+ description="Automatically compact context when approaching the token limit",
215
+ current_value="true" if sm.is_compaction_enabled() else "false",
216
+ values=["true", "false"],
217
+ ),
218
+ SettingItem(
219
+ id="compaction_reserve_tokens",
220
+ label="Reserve tokens",
221
+ description="Tokens reserved for LLM response during compaction (default: 16384)",
222
+ current_value=str(sm.get_compaction_reserve_tokens()),
223
+ text_input=True,
224
+ ),
225
+ SettingItem(
226
+ id="compaction_keep_recent_tokens",
227
+ label="Keep recent tokens",
228
+ description="Recent tokens to keep verbatim during compaction (default: 20000)",
229
+ current_value=str(sm.get_compaction_keep_recent_tokens()),
230
+ text_input=True,
231
+ ),
232
+ ],
233
+ ),
234
+ SettingItem(
235
+ id="branch_summary",
236
+ label="Branch summary",
237
+ description="Settings for branch summarization behaviour",
238
+ current_value="on" if sm.is_branch_summary_enabled() else "off",
239
+ submenu_title="Branch Summary Settings",
240
+ submenu_settings=[
241
+ SettingItem(
242
+ id="branch_summary_enabled",
243
+ label="Enabled",
244
+ description="Enable branch summarization when switching branches",
245
+ current_value="true" if sm.is_branch_summary_enabled() else "false",
246
+ values=["true", "false"],
247
+ ),
248
+ SettingItem(
249
+ id="branch_summary_skip_prompt",
250
+ label="Skip prompt",
251
+ description="Always skip the 'Summarize branch?' confirmation (only applies when enabled)",
252
+ current_value="true" if sm.get_branch_summary_skip_prompt() else "false",
253
+ values=["false", "true"],
254
+ ),
255
+ SettingItem(
256
+ id="branch_summary_reserve_tokens",
257
+ label="Reserve tokens",
258
+ description="Tokens to reserve when summarizing a branch (default: 16384)",
259
+ current_value=str(sm.get_branch_summary_reserve_tokens()),
260
+ text_input=True,
261
+ ),
262
+ ],
263
+ ),
264
+ SettingItem(
265
+ id="terminal",
266
+ label="Terminal",
267
+ description="Shell and execution settings for the terminal tool",
268
+ current_value="→",
269
+ submenu_title="Terminal Settings",
270
+ submenu_settings=[
271
+ SettingItem(
272
+ id="terminal_shell_path",
273
+ label="Shell path",
274
+ description="Shell binary to use (default: system shell)",
275
+ current_value=sm.get_shell_path() or "",
276
+ text_input=True,
277
+ ),
278
+ SettingItem(
279
+ id="terminal_shell_command_prefix",
280
+ label="Shell command prefix",
281
+ description="Lines prepended inside the shell before each command",
282
+ current_value=sm.get_shell_command_prefix() or "",
283
+ text_input=True,
284
+ ),
285
+ ],
286
+ ),
287
+ SettingItem(
288
+ id="project_trust",
289
+ label="Project trust",
290
+ description="Whether to load project config, extensions, and context files from .tau/ directories",
291
+ current_value=sm.get_project_trust(),
292
+ values=["ask", "always", "never"],
293
+ ),
294
+ SettingItem(
295
+ id="double_escape_action",
296
+ label="Double-Escape action",
297
+ description="Action when Escape is pressed twice on an empty editor",
298
+ current_value=sm.get_double_escape_action(),
299
+ values=["fork", "tree", "none"],
300
+ ),
301
+ SettingItem(
302
+ id="tree_filter_mode",
303
+ label="Tree filter mode",
304
+ description="Default message filter in the /tree view",
305
+ current_value=sm.get_tree_filter_mode(),
306
+ submenu_items=["default", "no-tools", "user-only", "labeled-only", "all"],
307
+ submenu_title="Tree Filter Mode",
308
+ ),
309
+ SettingItem(
310
+ id="show_hardware_cursor",
311
+ label="Hardware cursor",
312
+ description="Show terminal cursor while positioning (useful for IME input)",
313
+ current_value="true" if sm.get_show_hardware_cursor() else "false",
314
+ values=["false", "true"],
315
+ ),
316
+ SettingItem(
317
+ id="http_idle_timeout_ms",
318
+ label="HTTP idle timeout (ms)",
319
+ description="Idle timeout for LLM HTTP streams in milliseconds (default: 60000)",
320
+ current_value=str(sm.get_http_idle_timeout_ms()),
321
+ text_input=True,
322
+ ),
323
+ SettingItem(
324
+ id="picker_max_visible",
325
+ label="Picker max visible",
326
+ description="Maximum number of items visible in list pickers (default: 8)",
327
+ current_value=str(sm.get_picker_max_visible()),
328
+ text_input=True,
329
+ ),
330
+ SettingItem(
331
+ id="autocomplete_max_visible",
332
+ label="Autocomplete max visible",
333
+ description="Maximum number of autocomplete suggestions shown (default: 5)",
334
+ current_value=str(sm.get_autocomplete_max_visible()),
335
+ text_input=True,
336
+ ),
337
+ SettingItem(
338
+ id="editor_padding_x",
339
+ label="Editor padding X",
340
+ description="Horizontal padding for the input editor in characters (default: 0)",
341
+ current_value=str(sm.get_editor_padding_x()),
342
+ text_input=True,
343
+ ),
344
+ ]
345
+
346
+ def _ext_on_change(reg, row_id):
347
+ """Wrap an extension's on_change so toggling its summary field refreshes
348
+ the parent row's on/off value live (otherwise it would only update on
349
+ the next /settings open)."""
350
+ base = reg.on_change
351
+ summary_key = reg.summary_key
352
+
353
+ def _wrapped(key: str, value: str) -> None:
354
+ base(key, value)
355
+ if summary_key and key == summary_key:
356
+ _update_parent(row_id, "on" if str(value).lower() in ("on", "true") else "off")
357
+
358
+ return _wrapped
359
+
360
+ # Append sub-panels from loaded extensions that called register_settings()
361
+ ext_runtime = ctx.runtime.extension_runtime
362
+ if ext_runtime is not None:
363
+ for ext in ext_runtime._extensions:
364
+ for reg in ext.settings_registrations:
365
+ row_id = f"_ext_{id(reg)}"
366
+ items.append(SettingItem(
367
+ id=row_id,
368
+ label=reg.title,
369
+ description=f"Settings for extension: {ext.path}",
370
+ current_value=reg.summary or "→",
371
+ submenu_title=reg.title,
372
+ submenu_settings=reg.items,
373
+ submenu_on_change=_ext_on_change(reg, row_id),
374
+ ))
375
+
376
+ sm.begin_batch()
377
+
378
+ def _update_parent(parent_id: str, new_value: str) -> None:
379
+ item = next((i for i in items if i.id == parent_id), None)
380
+ if item is not None:
381
+ item.current_value = new_value
382
+
383
+ def on_change(item_id: str, value: str) -> None:
384
+ if item_id == "quiet_startup":
385
+ sm.set_quiet_startup(value == "true")
386
+ elif item_id == "show_thinking":
387
+ sm.set_show_thinking(value == "true")
388
+ elif item_id == "show_tool_calls":
389
+ sm.set_show_tool_calls(value == "true")
390
+ elif item_id == "show_images":
391
+ v = value == "true"
392
+ sm.set_show_images(v)
393
+ t = ctx.layout.messages._theme
394
+ t.show_images = v
395
+ ctx.layout.messages.set_theme(t)
396
+ ctx.tui.request_render()
397
+ elif item_id == "image_auto_resize":
398
+ sm.set_image_auto_resize(value == "true")
399
+ elif item_id == "image_block":
400
+ sm.set_image_block_images(value == "true")
401
+ elif item_id == "steering_mode":
402
+ sm.set_steering_mode(SteeringMode(value))
403
+ elif item_id == "follow_up_mode":
404
+ sm.set_follow_up_mode(FollowupMode(value))
405
+ elif item_id == "transport":
406
+ sm.set_transport(Transport(value))
407
+ elif item_id == "thinking_level":
408
+ sm.set_thinking_level(ThinkingLevel(value))
409
+ elif item_id == "theme":
410
+ try:
411
+ from tau.themes.registry import theme_registry as _tr
412
+ ctx.layout.set_theme(_tr.get(value))
413
+ sm.set_theme(value)
414
+ except ValueError:
415
+ pass
416
+ elif item_id == "proxy_url":
417
+ sm.set_proxy_url(value or None)
418
+ elif item_id == "proxy_no_proxy":
419
+ sm.set_no_proxy(value or None)
420
+ elif item_id == "proxy_headers":
421
+ if not value.strip():
422
+ sm.set_proxy_headers(None)
423
+ else:
424
+ try:
425
+ parsed = json.loads(value)
426
+ if not isinstance(parsed, dict):
427
+ ctx.notify("Proxy headers must be a JSON object")
428
+ return
429
+ if any(not isinstance(k, str) or not isinstance(v, str) for k, v in parsed.items()):
430
+ ctx.notify("Proxy header keys and values must be strings")
431
+ return
432
+ sm.set_proxy_headers(parsed)
433
+ except json.JSONDecodeError as e:
434
+ ctx.notify(f"Invalid JSON: {e.msg}")
435
+ return
436
+ elif item_id == "project_trust":
437
+ sm.set_project_trust(value)
438
+ elif item_id == "double_escape_action":
439
+ sm.set_double_escape_action(value)
440
+ elif item_id == "tree_filter_mode":
441
+ sm.set_tree_filter_mode(value)
442
+ elif item_id == "show_hardware_cursor":
443
+ sm.set_show_hardware_cursor(value == "true")
444
+ elif item_id == "retry_enabled":
445
+ sm.set_retry_enabled(value == "true")
446
+ _update_parent("retry", "on" if value == "true" else "off")
447
+ elif item_id == "compaction_enabled":
448
+ sm.set_compaction_enabled(value == "true")
449
+ _update_parent("compaction", "on" if value == "true" else "off")
450
+ elif item_id == "branch_summary_enabled":
451
+ sm.set_branch_summary_enabled(value == "true")
452
+ _update_parent("branch_summary", "on" if value == "true" else "off")
453
+ elif item_id == "branch_summary_skip_prompt":
454
+ sm.set_branch_summary_skip_prompt(value == "true")
455
+ elif item_id in (
456
+ "http_idle_timeout_ms", "picker_max_visible", "autocomplete_max_visible",
457
+ "editor_padding_x", "retry_max_retries", "retry_base_delay_ms",
458
+ "compaction_reserve_tokens", "compaction_keep_recent_tokens",
459
+ "branch_summary_reserve_tokens",
460
+ ):
461
+ try:
462
+ n = int(value)
463
+ except ValueError:
464
+ ctx.notify(f"Invalid number: {value!r}")
465
+ return
466
+ if item_id == "http_idle_timeout_ms":
467
+ sm.set_http_idle_timeout_ms(n)
468
+ elif item_id == "picker_max_visible":
469
+ sm.set_picker_max_visible(n)
470
+ elif item_id == "autocomplete_max_visible":
471
+ sm.set_autocomplete_max_visible(n)
472
+ elif item_id == "editor_padding_x":
473
+ sm.set_editor_padding_x(n)
474
+ elif item_id == "retry_max_retries":
475
+ sm.set_retry_max_retries(n)
476
+ elif item_id == "retry_base_delay_ms":
477
+ sm.set_retry_base_delay_ms(n)
478
+ elif item_id == "compaction_reserve_tokens":
479
+ sm.set_compaction_reserve_tokens(n)
480
+ elif item_id == "compaction_keep_recent_tokens":
481
+ sm.set_compaction_keep_recent_tokens(n)
482
+ elif item_id == "branch_summary_reserve_tokens":
483
+ sm.set_branch_summary_reserve_tokens(n)
484
+ elif item_id == "terminal_shell_path":
485
+ sm.set_shell_path(value or None)
486
+ elif item_id == "terminal_shell_command_prefix":
487
+ sm.set_shell_command_prefix(value or None)
488
+
489
+ def on_close() -> None:
490
+ sm.save_batch() # commits the batch and re-merges the live settings view
491
+ ctx.notify("Settings saved.")
492
+ # Rebuild the palette now that settings are committed — feature-gated
493
+ # commands (e.g. /compact when compaction is off) appear/disappear here.
494
+ if ctx.on_palette_refresh is not None:
495
+ ctx.on_palette_refresh()
496
+
497
+ modal = SettingsModal(items, on_change=on_change)
498
+ ctx.layout.open_settings_selector(modal, on_cancel=on_close)