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
tau/auth/storage.py ADDED
@@ -0,0 +1,82 @@
1
+ from abc import ABC, abstractmethod
2
+ from tau.auth.types import LockResult
3
+ from filelock import FileLock
4
+ from typing import Callable, Awaitable
5
+ from pathlib import Path
6
+
7
+
8
+ class AuthStorage(ABC):
9
+ """Abstract storage backend for auth credentials."""
10
+
11
+ @abstractmethod
12
+ def with_lock(self, fn: Callable[[str | None], LockResult]) -> LockResult:
13
+ """Execute fn with exclusive access to storage."""
14
+ pass
15
+
16
+ @abstractmethod
17
+ async def with_lock_async(self, fn: Callable[[str | None], Awaitable[LockResult]]) -> LockResult:
18
+ """Execute async fn with exclusive access to storage."""
19
+ pass
20
+
21
+
22
+ class FileAuthStorage(AuthStorage):
23
+ """File-based storage backend with locking."""
24
+
25
+ def __init__(self, store_path: Path):
26
+ """Initialize file storage at the given path."""
27
+ self.store_path = store_path
28
+ self.lock_path = store_path.with_suffix(".lock")
29
+ self._ensure_parent_dir()
30
+ self._ensure_file_exists()
31
+
32
+ def _ensure_parent_dir(self) -> None:
33
+ """Create parent directory if it doesn't exist."""
34
+ self.store_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
35
+
36
+ def _ensure_file_exists(self) -> None:
37
+ """Create storage file if it doesn't exist."""
38
+ if not self.store_path.exists():
39
+ self.store_path.write_text("{}", encoding="utf-8")
40
+ self.store_path.chmod(0o600)
41
+
42
+ def with_lock(self, fn: Callable[[str | None], LockResult]) -> LockResult:
43
+ """Execute fn with exclusive access to storage."""
44
+ with FileLock(self.lock_path):
45
+ current = self.store_path.read_text(encoding="utf-8") if self.store_path.exists() else None
46
+ result = fn(current)
47
+ if result.next is not None:
48
+ self.store_path.write_text(result.next, encoding="utf-8")
49
+ self.store_path.chmod(0o600)
50
+ return result
51
+
52
+ async def with_lock_async(self, fn: Callable[[str | None], Awaitable[LockResult]]) -> LockResult:
53
+ """Execute async fn with exclusive access to storage."""
54
+ with FileLock(self.lock_path):
55
+ current = self.store_path.read_text(encoding="utf-8") if self.store_path.exists() else None
56
+ result = await fn(current)
57
+ if result.next is not None:
58
+ self.store_path.write_text(result.next, encoding="utf-8")
59
+ self.store_path.chmod(0o600)
60
+ return result
61
+
62
+
63
+ class InMemoryAuthStorage(AuthStorage):
64
+ """In-memory storage backend for testing."""
65
+
66
+ def __init__(self):
67
+ """Initialize empty in-memory storage."""
68
+ self._value: str | None = None
69
+
70
+ def with_lock(self, fn: Callable[[str | None], LockResult]) -> LockResult:
71
+ """Execute fn with exclusive access to memory storage."""
72
+ result = fn(self._value)
73
+ if result.next is not None:
74
+ self._value = result.next
75
+ return result
76
+
77
+ async def with_lock_async(self, fn: Callable[[str | None], Awaitable[LockResult]]) -> LockResult:
78
+ """Execute async fn with exclusive access to memory storage."""
79
+ result = await fn(self._value)
80
+ if result.next is not None:
81
+ self._value = result.next
82
+ return result
tau/auth/types.py ADDED
@@ -0,0 +1,41 @@
1
+ from tau.inference.types import AuthType
2
+ from dataclasses import dataclass, field
3
+ from typing import Generic, TypeVar, Optional, Literal
4
+
5
+
6
+ @dataclass
7
+ class OAuthCredential:
8
+ """OAuth 2.0 credential with access and refresh tokens."""
9
+ type: AuthType = field(default_factory=lambda: AuthType.OAuth, init=False)
10
+ access: str = ""
11
+ refresh: str = ""
12
+ expires: int = 0 # Unix timestamp in milliseconds
13
+ extra: dict[str, str] = field(default_factory=dict)
14
+
15
+
16
+ @dataclass
17
+ class APICredential:
18
+ """API key credential."""
19
+ type: AuthType = field(default_factory=lambda: AuthType.ApiKey, init=False)
20
+ key: str = ""
21
+
22
+
23
+ AuthCredential = OAuthCredential | APICredential
24
+
25
+
26
+ @dataclass
27
+ class AuthStatus:
28
+ """Authentication status and source information."""
29
+ configured: bool
30
+ source: Optional[Literal["stored", "runtime", "env"]] = None
31
+ label: Optional[str] = None
32
+
33
+
34
+ T = TypeVar('T')
35
+
36
+
37
+ @dataclass
38
+ class LockResult(Generic[T]):
39
+ """Result of a locked operation with next continuation."""
40
+ result: T
41
+ next: str | None = None
@@ -0,0 +1,4 @@
1
+ # tau.builtins — concrete built-in implementations.
2
+ #
3
+ # Tools: from tau.builtins.tools import TOOLS, TerminalTool, …
4
+ # Themes: from tau.themes.registry import theme_registry
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from tau.commands.types import CommandInfo
4
+ from tau.builtins.commands.session import cmd_new, cmd_fork
5
+ from tau.builtins.commands.reload import cmd_reload
6
+ from tau.builtins.commands.compact import cmd_compact
7
+ from tau.builtins.commands.clear import cmd_clear
8
+
9
+
10
+ def get_builtin_commands() -> list[CommandInfo]:
11
+ """Get the list of builtin slash commands."""
12
+ return [
13
+ CommandInfo(
14
+ name="new",
15
+ description="Start a fresh session.",
16
+ call=cmd_new,
17
+ ),
18
+ CommandInfo(
19
+ name="fork",
20
+ description="Branch the session tree at a given entry ID.",
21
+ call=cmd_fork,
22
+ argument_hint="<entry_id>",
23
+ required_arg_names=["entry_id"],
24
+ ),
25
+ CommandInfo(
26
+ name="reload",
27
+ description="Reload extensions, themes, and prompt appends.",
28
+ call=cmd_reload,
29
+ ),
30
+ CommandInfo(
31
+ name="compact",
32
+ description="Summarise and compact the current session context.",
33
+ call=cmd_compact,
34
+ argument_hint="<custom_instruction>",
35
+ ),
36
+ CommandInfo(
37
+ name="clear",
38
+ description="Clear the message list.",
39
+ call=cmd_clear,
40
+ ),
41
+ ]
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from tau.commands.registry import CommandRegistry
7
+
8
+
9
+ async def cmd_clear(reg: CommandRegistry, _args: list[str]) -> None:
10
+ """Clear the message list."""
11
+ if reg.runtime is None:
12
+ return
13
+ from tau.extensions.context import ExtensionContext
14
+ ctx = ExtensionContext.from_runtime(reg.runtime)
15
+ if ctx.ui is not None:
16
+ ctx.ui.clear_messages()
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from tau.commands.registry import CommandRegistry
7
+
8
+
9
+ async def cmd_compact(reg: CommandRegistry, args: list[str]) -> None:
10
+ """Manually compact the current session context."""
11
+ if reg.runtime is None or reg.runtime.agent is None:
12
+ return
13
+
14
+ sm = reg.runtime.settings_manager
15
+ if sm is not None and not sm.is_compaction_enabled():
16
+ from tau.extensions.context import ExtensionContext
17
+ ctx = ExtensionContext.from_runtime(reg.runtime)
18
+ if ctx.ui is not None:
19
+ ctx.ui.notify("Compaction is disabled. Enable it in /settings → Compaction.")
20
+ return
21
+
22
+ custom_instructions = " ".join(args).strip() or None
23
+ did_compact = await reg.runtime.agent.compact(custom_instructions=custom_instructions)
24
+ if not did_compact:
25
+ from tau.extensions.context import ExtensionContext
26
+ ctx = ExtensionContext.from_runtime(reg.runtime)
27
+ if ctx.ui is not None:
28
+ ctx.ui.notify("Nothing to compact — conversation is too short to summarize.")
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from tau.commands.registry import CommandRegistry
7
+
8
+
9
+ async def cmd_reload(reg: CommandRegistry, _args: list[str]) -> None:
10
+ """Reload extensions, themes, and prompt appends."""
11
+ if reg.runtime is None:
12
+ return
13
+ result = await reg.runtime.reload_extensions()
14
+
15
+ from tau.extensions.context import ExtensionContext
16
+ ctx = ExtensionContext.from_runtime(reg.runtime)
17
+ if ctx.ui is None:
18
+ return
19
+
20
+ n = len(result.extensions)
21
+ ext_word = "extension" if n == 1 else "extensions"
22
+ if result.errors:
23
+ e = len(result.errors)
24
+ err_word = "error" if e == 1 else "errors"
25
+ ctx.ui.notify(f"Reloaded {n} {ext_word} with {e} {err_word}.")
26
+ else:
27
+ ctx.ui.notify(f"Reloaded {n} {ext_word}.")
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from tau.commands.registry import CommandRegistry
7
+
8
+
9
+ async def cmd_new(reg: CommandRegistry, _args: list[str]) -> None:
10
+ """Start a new session."""
11
+ if reg.runtime is not None:
12
+ await reg.runtime.new_session()
13
+
14
+
15
+ async def cmd_fork(reg: CommandRegistry, args: list[str]) -> None:
16
+ """Fork the session at a given entry ID."""
17
+ if reg.runtime is None:
18
+ return
19
+ await reg.runtime.fork_session(args[0])
@@ -0,0 +1,76 @@
1
+ """Built-in footer status extension — git branch left, model/context right."""
2
+ from __future__ import annotations
3
+
4
+ from .git import GitBadge
5
+ from .model import ModelBadge
6
+
7
+
8
+ def register(tau: object) -> None:
9
+ from tau.tui.component import Row
10
+
11
+ git_badge = GitBadge()
12
+ model_badge = ModelBadge()
13
+ row = Row([(git_badge, "left"), (model_badge, "right")]) # type: ignore[arg-type]
14
+
15
+ def _request_render(ctx: object) -> None:
16
+ layout = getattr(ctx, "_layout", None)
17
+ if layout is not None:
18
+ layout._tui.request_render()
19
+
20
+ @tau.on("tui_ready")
21
+ def on_ready(event, ctx):
22
+ ctx._layout.footer.add_child(row)
23
+ git_badge.update(str(ctx.cwd))
24
+ model_badge.update_from_ctx(ctx)
25
+ _request_render(ctx)
26
+
27
+ @tau.on("session_start")
28
+ def on_session_start(event, ctx):
29
+ if ctx.has_ui:
30
+ git_badge.update(str(ctx.cwd))
31
+ model_badge.update_from_ctx(ctx)
32
+ _request_render(ctx)
33
+
34
+ @tau.on("model_select")
35
+ def on_model_select(event, ctx):
36
+ if not ctx.has_ui:
37
+ return
38
+ model = getattr(event, "model", None)
39
+ if model is not None:
40
+ model_badge.set_model(
41
+ getattr(model, "id", "") or "",
42
+ getattr(model, "provider", "") or "",
43
+ bool(getattr(model, "thinking", False)),
44
+ )
45
+ # The new model usually has a different context window, so the usage %
46
+ # changes even though the token count didn't — refresh it immediately
47
+ # instead of waiting for the next turn.
48
+ model_badge.update_context_from_ctx(ctx)
49
+ _request_render(ctx)
50
+
51
+ @tau.on("thinking_level_select")
52
+ def on_thinking_level_select(event, ctx):
53
+ if not ctx.has_ui:
54
+ return
55
+ model_badge.set_thinking_level(getattr(event, "level", None))
56
+ _request_render(ctx)
57
+
58
+ @tau.on("settled")
59
+ def on_settled(event, ctx):
60
+ if ctx.has_ui:
61
+ git_badge.update(str(ctx.cwd))
62
+ model_badge.update_context_from_ctx(ctx)
63
+ _request_render(ctx)
64
+
65
+ @tau.on("message_end")
66
+ def on_message_end(event, ctx):
67
+ if ctx.has_ui:
68
+ git_badge.update(str(ctx.cwd))
69
+ model_badge.update_context_from_ctx(ctx)
70
+ _request_render(ctx)
71
+
72
+ @tau.on("compaction_end")
73
+ def on_compaction_end(event, ctx):
74
+ if ctx.has_ui:
75
+ model_badge.update_context_from_ctx(ctx)
76
+ _request_render(ctx)
@@ -0,0 +1,26 @@
1
+ """Git branch badge component."""
2
+ from __future__ import annotations
3
+
4
+ from .utils import read_branch, shorten_home
5
+
6
+
7
+ class GitBadge:
8
+ """Renders ``~/path (branch)`` for the footer Row left slot."""
9
+
10
+ def __init__(self) -> None:
11
+ self._text = ""
12
+
13
+ def update(self, cwd: str) -> None:
14
+ branch = read_branch(cwd)
15
+ display = shorten_home(cwd)
16
+ self._text = f"{display} ({branch})" if branch else display
17
+
18
+ def render(self, width: int) -> list[str]: # noqa: ARG002
19
+ from tau.tui.ansi import DIM, RESET
20
+ return [DIM + self._text + RESET]
21
+
22
+ def handle_input(self, event: object) -> bool: # noqa: ARG002
23
+ return False
24
+
25
+ def invalidate(self) -> None:
26
+ pass
@@ -0,0 +1,69 @@
1
+ """Model + context-usage badge component."""
2
+ from __future__ import annotations
3
+
4
+
5
+ class ModelBadge:
6
+ """Renders ``(provider) model ∙ Level|context%`` for the footer Row right slot.
7
+
8
+ The ``∙ Level`` segment only appears when the active model supports
9
+ extended thinking and a thinking level is set.
10
+ """
11
+
12
+ def __init__(self) -> None:
13
+ self._provider = ""
14
+ self._model = ""
15
+ self._tokens = 0
16
+ self._context_window = 0
17
+ self._thinking = False
18
+ self._thinking_level = ""
19
+
20
+ def set_model(self, model_id: str, provider: str, thinking: bool = False) -> None:
21
+ self._model = model_id
22
+ self._provider = provider
23
+ self._thinking = thinking
24
+
25
+ def set_thinking_level(self, level: object) -> None:
26
+ self._thinking_level = str(getattr(level, "value", level) or "")
27
+
28
+ def set_context(self, tokens: int, context_window: int) -> None:
29
+ self._tokens = tokens
30
+ self._context_window = context_window
31
+
32
+ def update_from_ctx(self, ctx: object) -> None:
33
+ self.set_model(
34
+ getattr(ctx, "model_id", "") or "",
35
+ getattr(ctx, "provider_id", "") or "",
36
+ bool(getattr(ctx, "model_thinking", False)),
37
+ )
38
+ settings = getattr(ctx, "settings", None)
39
+ if settings is not None:
40
+ self.set_thinking_level(settings.get_thinking_level())
41
+ self.update_context_from_ctx(ctx)
42
+
43
+ def update_context_from_ctx(self, ctx: object) -> None:
44
+ usage = getattr(ctx, "get_context_usage", lambda: None)()
45
+ if usage is not None:
46
+ tokens = usage.get("tokens") or 0
47
+ window = usage.get("context_window") or 0
48
+ self.set_context(tokens, window)
49
+
50
+ def render(self, width: int) -> list[str]: # noqa: ARG002
51
+ from tau.tui.ansi import DIM, RESET
52
+ if not self._provider and not self._model:
53
+ return []
54
+
55
+ left = f"({self._provider}) {self._model}" if self._provider else self._model
56
+ if self._thinking and self._thinking_level and self._thinking_level != "off":
57
+ left += f" ∙ {self._thinking_level.title()}"
58
+
59
+ if self._context_window > 0 and self._tokens > 0:
60
+ pct = self._tokens / self._context_window * 100
61
+ label = f"{pct:.1f}%" if pct < 1 else f"{int(round(pct))}%"
62
+ return [DIM + f"{left}|{label}" + RESET]
63
+ return [DIM + left + RESET]
64
+
65
+ def handle_input(self, event: object) -> bool: # noqa: ARG002
66
+ return False
67
+
68
+ def invalidate(self) -> None:
69
+ pass
@@ -0,0 +1,44 @@
1
+ """Shared helpers for the status extension."""
2
+ from __future__ import annotations
3
+
4
+
5
+ def read_branch(cwd: object) -> str:
6
+ """Return the current git branch name for the given directory, or ''."""
7
+ import os
8
+ from pathlib import Path
9
+ try:
10
+ path = Path(str(cwd))
11
+ for candidate in [path, *path.parents]:
12
+ git = candidate / ".git"
13
+ if git.is_dir():
14
+ head = git / "HEAD"
15
+ elif git.is_file():
16
+ content = git.read_text(encoding="utf-8").strip()
17
+ if not content.startswith("gitdir: "):
18
+ continue
19
+ gitdir = os.path.normpath(
20
+ os.path.join(str(candidate), content[8:].strip())
21
+ )
22
+ head = Path(gitdir) / "HEAD"
23
+ else:
24
+ continue
25
+ if not head.is_file():
26
+ continue
27
+ text = head.read_text(encoding="utf-8").strip()
28
+ if text.startswith("ref: refs/heads/"):
29
+ return text[len("ref: refs/heads/"):]
30
+ return text[:7]
31
+ return ""
32
+ except OSError:
33
+ return ""
34
+
35
+
36
+ def shorten_home(path: str) -> str:
37
+ """Replace the home directory prefix with ``~``."""
38
+ import os
39
+ home = os.path.expanduser("~")
40
+ if path == home:
41
+ return "~"
42
+ if path.startswith(home + os.sep):
43
+ return "~" + path[len(home):]
44
+ return path
@@ -0,0 +1,18 @@
1
+ """Built-in header extension — shows app name and version above the message list."""
2
+ from __future__ import annotations
3
+
4
+
5
+ def register(tau: object) -> None:
6
+ from tau.settings.paths import get_app_name, get_app_version
7
+ from tau.tui.ansi import BOLD, CYAN, DIM, RESET
8
+ from tau.tui.component import StaticComponent
9
+
10
+ def _build() -> StaticComponent:
11
+ name = BOLD + CYAN + get_app_name() + RESET
12
+ version = DIM + f"v{get_app_version()}" + RESET
13
+ return StaticComponent([f"{name} {version}"])
14
+
15
+ @tau.on("tui_ready")
16
+ def on_ready(event, ctx):
17
+ if ctx.has_ui:
18
+ ctx.ui.set_header(_build())
File without changes
@@ -0,0 +1,43 @@
1
+ from tau.inference.model.types import Cost, Model, Modality
2
+
3
+ _TEXT = [Modality.Text]
4
+ _AUDIO = [Modality.Audio]
5
+
6
+ _OPENAI_VOICES = ["alloy", "ash", "ballad", "coral", "echo", "fable", "nova", "onyx", "sage", "shimmer", "verse"]
7
+ _GROQ_VOICES = ["autumn", "diana", "hannah", "austin", "daniel", "troy"]
8
+
9
+ models = [
10
+ # OpenAI TTS
11
+ Model(id="tts-1", name="TTS-1", provider="openai", cost=Cost(input=15.0), input=_TEXT, output=_AUDIO, api="openai-audio", voices=_OPENAI_VOICES),
12
+ Model(id="tts-1-hd", name="TTS-1 HD", provider="openai", cost=Cost(input=30.0), input=_TEXT, output=_AUDIO, api="openai-audio", voices=_OPENAI_VOICES),
13
+ Model(id="gpt-4o-mini-tts", name="GPT-4o Mini TTS", provider="openai", cost=Cost(input=0.60), input=_TEXT, output=_AUDIO, api="openai-audio", voices=_OPENAI_VOICES),
14
+ # OpenAI STT
15
+ Model(id="whisper-1", name="Whisper 1", provider="openai", cost=Cost(input=0.006), input=_AUDIO, output=_TEXT, api="openai-audio"),
16
+ Model(id="gpt-4o-transcribe", name="GPT-4o Transcribe", provider="openai", cost=Cost(input=2.5), input=_AUDIO, output=_TEXT, api="openai-audio"),
17
+ Model(id="gpt-4o-mini-transcribe", name="GPT-4o Mini Transcribe", provider="openai", cost=Cost(input=0.003), input=_AUDIO, output=_TEXT, api="openai-audio"),
18
+ # Google Gemini TTS
19
+ Model(id="gemini-2.5-flash-preview-tts", name="Gemini 2.5 Flash TTS", provider="google", cost=Cost(input=0.50, output=10.0), input=_TEXT, output=_AUDIO, api="gemini-audio"),
20
+ Model(id="gemini-2.5-pro-preview-tts", name="Gemini 2.5 Pro TTS", provider="google", cost=Cost(input=2.00, output=16.0), input=_TEXT, output=_AUDIO, api="gemini-audio"),
21
+ Model(id="gemini-3.1-flash-tts-preview", name="Gemini 3.1 Flash TTS", provider="google", cost=Cost(input=0.10, output=1.00), input=_TEXT, output=_AUDIO, api="gemini-audio"),
22
+ # OpenRouter speech models
23
+ Model(id="openai/gpt-audio", name="GPT Audio", provider="openrouter", cost=Cost(input=2.50, output=10.00), input=_TEXT, output=_AUDIO, api="openai-audio"),
24
+ Model(id="openai/gpt-audio-mini", name="GPT Audio Mini", provider="openrouter", cost=Cost(input=0.60, output=2.40), input=_TEXT, output=_AUDIO, api="openai-audio"),
25
+ Model(id="openai/gpt-4o-audio-preview", name="GPT-4o Audio", provider="openrouter", cost=Cost(input=2.50, output=10.00), input=_TEXT, output=_AUDIO, api="openai-audio"),
26
+ Model(id="google/lyria-3-pro-preview", name="Lyria 3 Pro Preview", provider="openrouter", cost=Cost(), input=_TEXT, output=_AUDIO, api="openai-audio"),
27
+ Model(id="google/lyria-3-clip-preview", name="Lyria 3 Clip Preview", provider="openrouter", cost=Cost(), input=_TEXT, output=_AUDIO, api="openai-audio"),
28
+ # Sarvam AI
29
+ Model(id="bulbul:v3", name="Bulbul v3", provider="sarvam", cost=Cost(), input=_TEXT, output=_AUDIO, api="sarvam-audio"),
30
+ Model(id="saarika:v2.5", name="Saarika v2.5", provider="sarvam", cost=Cost(), input=_AUDIO, output=_TEXT, api="sarvam-audio"),
31
+ Model(id="saaras:v3", name="Saaras v3", provider="sarvam", cost=Cost(), input=_AUDIO, output=_TEXT, api="sarvam-audio"),
32
+ # ElevenLabs
33
+ Model(id="eleven_multilingual_v2", name="Eleven Multilingual v2", provider="elevenlabs", cost=Cost(input=0.30), input=_TEXT, output=_AUDIO, api="elevenlabs-audio"),
34
+ Model(id="eleven_flash_v2_5", name="Eleven Flash v2.5", provider="elevenlabs", cost=Cost(input=0.08), input=_TEXT, output=_AUDIO, api="elevenlabs-audio"),
35
+ Model(id="eleven_turbo_v2_5", name="Eleven Turbo v2.5", provider="elevenlabs", cost=Cost(input=0.15), input=_TEXT, output=_AUDIO, api="elevenlabs-audio"),
36
+ Model(id="scribe_v1", name="Scribe v1", provider="elevenlabs", cost=Cost(input=0.40), input=_AUDIO, output=_TEXT, api="elevenlabs-audio"),
37
+ Model(id="scribe_v2", name="Scribe v2", provider="elevenlabs", cost=Cost(input=0.40), input=_AUDIO, output=_TEXT, api="elevenlabs-audio"),
38
+ # Groq TTS
39
+ Model(id="canopylabs/orpheus-v1-english", name="Orpheus v1 English", provider="groq", cost=Cost(), input=_TEXT, output=_AUDIO, api="openai-audio", voices=_GROQ_VOICES, tts_format="wav"),
40
+ # Groq STT
41
+ Model(id="whisper-large-v3", name="Whisper Large v3", provider="groq", cost=Cost(input=0.111), input=_AUDIO, output=_TEXT, api="openai-audio"),
42
+ Model(id="whisper-large-v3-turbo", name="Whisper Large v3 Turbo", provider="groq", cost=Cost(input=0.04), input=_AUDIO, output=_TEXT, api="openai-audio"),
43
+ ]
@@ -0,0 +1,43 @@
1
+ from tau.inference.model.types import Cost, Model, Modality
2
+
3
+ _TEXT = [Modality.Text]
4
+ _IMAGE = [Modality.Image]
5
+ _TEXT_IMAGE = [Modality.Text, Modality.Image]
6
+ _TEXT_IMAGE_OUT = [Modality.Text, Modality.Image]
7
+
8
+ models = [
9
+ # OpenAI DALL-E
10
+ Model(id="dall-e-3", name="DALL-E 3", provider="openai", cost=Cost(input=40.0), input=_TEXT, output=_IMAGE, api="openai-image"),
11
+ Model(id="dall-e-2", name="DALL-E 2", provider="openai", cost=Cost(input=20.0), input=_TEXT, output=_IMAGE, api="openai-image"),
12
+ # Together AI
13
+ Model(id="black-forest-labs/FLUX.1-schnell-Free", name="FLUX.1 Schnell Free", provider="together", cost=Cost(), input=_TEXT, output=_IMAGE, api="openai-image"),
14
+ Model(id="black-forest-labs/FLUX.1-schnell", name="FLUX.1 Schnell", provider="together", cost=Cost(input=0.053), input=_TEXT, output=_IMAGE, api="openai-image"),
15
+ Model(id="black-forest-labs/FLUX.1-dev", name="FLUX.1 Dev", provider="together", cost=Cost(input=0.35), input=_TEXT, output=_IMAGE, api="openai-image"),
16
+ Model(id="black-forest-labs/FLUX.1.1-pro", name="FLUX.1.1 Pro", provider="together", cost=Cost(input=0.40), input=_TEXT, output=_IMAGE, api="openai-image"),
17
+ # Fireworks AI
18
+ Model(id="accounts/fireworks/models/flux-1-schnell-fp8", name="FLUX.1 Schnell FP8", provider="fireworks", cost=Cost(input=0.053), input=_TEXT, output=_IMAGE, api="openai-image"),
19
+ Model(id="accounts/fireworks/models/flux-1-dev-fp8", name="FLUX.1 Dev FP8", provider="fireworks", cost=Cost(input=0.35), input=_TEXT, output=_IMAGE, api="openai-image"),
20
+ # Black Forest Labs FLUX via OpenRouter
21
+ Model(id="black-forest-labs/flux-2-flex", name="FLUX.2 Flex", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
22
+ Model(id="black-forest-labs/flux-2-klein", name="FLUX.2 Klein 4B", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
23
+ Model(id="black-forest-labs/flux-2-max", name="FLUX.2 Max", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
24
+ Model(id="black-forest-labs/flux-2-pro", name="FLUX.2 Pro", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
25
+ # Google Imagen (native gemini-image API)
26
+ Model(id="imagen-3.0-generate-002", name="Imagen 3", provider="google", cost=Cost(input=0.04), input=_TEXT, output=_IMAGE, api="gemini-image"),
27
+ Model(id="imagen-3.0-fast-generate-001", name="Imagen 3 Fast", provider="google", cost=Cost(input=0.02), input=_TEXT, output=_IMAGE, api="gemini-image"),
28
+ # Google Gemini Image via OpenRouter
29
+ Model(id="google/gemini-2.5-flash-image-generation", name="Gemini 2.5 Flash Image", provider="openrouter", cost=Cost(input=0.30, output=2.50), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
30
+ Model(id="google/gemini-3-pro-image-generation-preview", name="Gemini 3 Pro Image Preview", provider="openrouter", cost=Cost(input=2.00, output=12.00), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
31
+ Model(id="google/gemini-3.1-flash-image-generation-preview", name="Gemini 3.1 Flash Image Preview", provider="openrouter", cost=Cost(input=0.50, output=3.00), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
32
+ # OpenAI GPT Image via OpenRouter
33
+ Model(id="openai/gpt-5-image", name="GPT-5 Image", provider="openrouter", cost=Cost(input=10.00, output=10.00), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
34
+ Model(id="openai/gpt-5-image-mini", name="GPT-5 Image Mini", provider="openrouter", cost=Cost(input=2.50, output=2.00), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
35
+ Model(id="openai/gpt-5.4-image-2", name="GPT-5.4 Image 2", provider="openrouter", cost=Cost(input=8.00, output=15.00), input=_TEXT_IMAGE, output=_TEXT_IMAGE_OUT),
36
+ # ByteDance
37
+ Model(id="bytedance/seedream-4.5", name="Seedream 4.5", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
38
+ # Sourceful Riverflow via OpenRouter
39
+ Model(id="sourceful/riverflow-v2", name="Riverflow V2", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
40
+ Model(id="sourceful/riverflow-v2-turbo", name="Riverflow V2 Turbo", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
41
+ Model(id="sourceful/riverflow-v2-max", name="Riverflow V2 Max", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
42
+ Model(id="sourceful/riverflow-v2-pro", name="Riverflow V2 Pro", provider="openrouter", cost=Cost(), input=_TEXT_IMAGE, output=_IMAGE),
43
+ ]