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.
- tau/__init__.py +0 -0
- tau/agent/__init__.py +11 -0
- tau/agent/prompt/__init__.py +10 -0
- tau/agent/prompt/builder.py +302 -0
- tau/agent/prompt/types.py +33 -0
- tau/agent/service.py +369 -0
- tau/agent/types.py +61 -0
- tau/auth/manager.py +247 -0
- tau/auth/storage.py +82 -0
- tau/auth/types.py +41 -0
- tau/builtins/__init__.py +4 -0
- tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__init__.py +41 -0
- tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
- tau/builtins/commands/clear.py +16 -0
- tau/builtins/commands/compact.py +28 -0
- tau/builtins/commands/reload.py +27 -0
- tau/builtins/commands/session.py +19 -0
- tau/builtins/extensions/footer/__init__.py +76 -0
- tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/git.py +26 -0
- tau/builtins/extensions/footer/model.py +69 -0
- tau/builtins/extensions/footer/utils.py +44 -0
- tau/builtins/extensions/header/__init__.py +18 -0
- tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__init__.py +0 -0
- tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/models/audio.py +43 -0
- tau/builtins/models/image.py +43 -0
- tau/builtins/models/text.py +482 -0
- tau/builtins/models/video.py +40 -0
- tau/builtins/prompts/commit.md +7 -0
- tau/builtins/prompts/docs.md +7 -0
- tau/builtins/prompts/explain.md +7 -0
- tau/builtins/prompts/fix.md +7 -0
- tau/builtins/prompts/refactor.md +7 -0
- tau/builtins/prompts/review.md +7 -0
- tau/builtins/prompts/test.md +7 -0
- tau/builtins/providers/__init__.py +0 -0
- tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/providers/audio.py +10 -0
- tau/builtins/providers/image.py +9 -0
- tau/builtins/providers/text.py +33 -0
- tau/builtins/providers/video.py +6 -0
- tau/builtins/skills/code-review/SKILL.md +4 -0
- tau/builtins/skills/debug/SKILL.md +4 -0
- tau/builtins/skills/git-commit/SKILL.md +4 -0
- tau/builtins/themes/dark.yaml +1 -0
- tau/builtins/themes/light.yaml +46 -0
- tau/builtins/tools/__init__.py +73 -0
- tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
- tau/builtins/tools/edit.py +215 -0
- tau/builtins/tools/glob.py +112 -0
- tau/builtins/tools/grep.py +146 -0
- tau/builtins/tools/ls.py +135 -0
- tau/builtins/tools/read.py +122 -0
- tau/builtins/tools/terminal.py +150 -0
- tau/builtins/tools/write.py +105 -0
- tau/commands/__init__.py +10 -0
- tau/commands/registry.py +71 -0
- tau/commands/types.py +33 -0
- tau/console/__init__.py +0 -0
- tau/console/cli.py +266 -0
- tau/console/commands/__init__.py +0 -0
- tau/console/commands/auth.py +193 -0
- tau/console/commands/packages.py +104 -0
- tau/console/commands/update.py +76 -0
- tau/core/__init__.py +0 -0
- tau/core/registry.py +102 -0
- tau/engine/__init__.py +47 -0
- tau/engine/service.py +768 -0
- tau/engine/types.py +163 -0
- tau/extensions/__init__.py +28 -0
- tau/extensions/api.py +928 -0
- tau/extensions/context.py +462 -0
- tau/extensions/events.py +70 -0
- tau/extensions/loader.py +386 -0
- tau/extensions/runtime.py +184 -0
- tau/extensions/settings.py +137 -0
- tau/hooks/__init__.py +112 -0
- tau/hooks/engine.py +237 -0
- tau/hooks/inference.py +21 -0
- tau/hooks/runtime.py +126 -0
- tau/hooks/service.py +121 -0
- tau/hooks/session.py +117 -0
- tau/hooks/tui.py +61 -0
- tau/hooks/types.py +72 -0
- tau/inference/__init__.py +180 -0
- tau/inference/api/__init__.py +0 -0
- tau/inference/api/audio/__init__.py +0 -0
- tau/inference/api/audio/base.py +29 -0
- tau/inference/api/audio/builtins.py +15 -0
- tau/inference/api/audio/elevenlabs_audio.py +183 -0
- tau/inference/api/audio/gemini_audio.py +95 -0
- tau/inference/api/audio/openai_audio.py +159 -0
- tau/inference/api/audio/registry.py +15 -0
- tau/inference/api/audio/sarvam_audio.py +163 -0
- tau/inference/api/audio/service.py +103 -0
- tau/inference/api/audio/utils.py +47 -0
- tau/inference/api/image/__init__.py +0 -0
- tau/inference/api/image/base.py +17 -0
- tau/inference/api/image/builtins.py +8 -0
- tau/inference/api/image/gemini_image.py +77 -0
- tau/inference/api/image/openai_image.py +103 -0
- tau/inference/api/image/openrouter.py +144 -0
- tau/inference/api/image/registry.py +15 -0
- tau/inference/api/image/service.py +71 -0
- tau/inference/api/registry.py +82 -0
- tau/inference/api/text/__init__.py +0 -0
- tau/inference/api/text/anthropic_claude_code.py +222 -0
- tau/inference/api/text/anthropic_messages.py +196 -0
- tau/inference/api/text/base.py +40 -0
- tau/inference/api/text/builtins.py +19 -0
- tau/inference/api/text/gemini_generate.py +234 -0
- tau/inference/api/text/github_copilot_chat.py +172 -0
- tau/inference/api/text/google_antigravity.py +522 -0
- tau/inference/api/text/mistral_chat.py +284 -0
- tau/inference/api/text/ollama_chat.py +200 -0
- tau/inference/api/text/openai_codex_responses.py +497 -0
- tau/inference/api/text/openai_completions.py +227 -0
- tau/inference/api/text/openai_responses.py +235 -0
- tau/inference/api/text/registry.py +50 -0
- tau/inference/api/text/service.py +297 -0
- tau/inference/api/text/types.py +7 -0
- tau/inference/api/text/utils.py +228 -0
- tau/inference/api/video/__init__.py +0 -0
- tau/inference/api/video/base.py +26 -0
- tau/inference/api/video/builtins.py +7 -0
- tau/inference/api/video/fal_video.py +119 -0
- tau/inference/api/video/openrouter_video.py +142 -0
- tau/inference/api/video/registry.py +15 -0
- tau/inference/api/video/service.py +72 -0
- tau/inference/model/__init__.py +0 -0
- tau/inference/model/registry.py +102 -0
- tau/inference/model/types.py +65 -0
- tau/inference/provider/__init__.py +0 -0
- tau/inference/provider/oauth/__init__.py +35 -0
- tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
- tau/inference/provider/oauth/github_copilot.py +333 -0
- tau/inference/provider/oauth/google_antigravity.py +258 -0
- tau/inference/provider/oauth/openai_codex.py +309 -0
- tau/inference/provider/oauth/pkce.py +14 -0
- tau/inference/provider/oauth/types.py +46 -0
- tau/inference/provider/oauth/utils.py +154 -0
- tau/inference/provider/registry.py +141 -0
- tau/inference/provider/types.py +114 -0
- tau/inference/types.py +549 -0
- tau/inference/utils.py +219 -0
- tau/message/__init__.py +0 -0
- tau/message/types.py +482 -0
- tau/message/utils.py +178 -0
- tau/packages/__init__.py +11 -0
- tau/packages/manager.py +190 -0
- tau/packages/types.py +20 -0
- tau/packages/utils.py +67 -0
- tau/prompts/expand.py +58 -0
- tau/prompts/loader.py +69 -0
- tau/prompts/registry.py +45 -0
- tau/prompts/types.py +24 -0
- tau/rpc/__init__.py +8 -0
- tau/rpc/mode.py +783 -0
- tau/rpc/types.py +252 -0
- tau/runtime/service.py +759 -0
- tau/runtime/types.py +303 -0
- tau/session/branch_summarization.py +312 -0
- tau/session/compaction.py +646 -0
- tau/session/manager.py +652 -0
- tau/session/types.py +188 -0
- tau/session/utils.py +233 -0
- tau/settings/manager.py +1077 -0
- tau/settings/paths.py +150 -0
- tau/settings/storage.py +63 -0
- tau/settings/types.py +173 -0
- tau/settings/utils.py +25 -0
- tau/skills/loader.py +91 -0
- tau/skills/registry.py +70 -0
- tau/skills/types.py +25 -0
- tau/themes/loader.py +238 -0
- tau/themes/registry.py +108 -0
- tau/themes/types.py +19 -0
- tau/tool/__init__.py +3 -0
- tau/tool/registry.py +117 -0
- tau/tool/render.py +21 -0
- tau/tool/types.py +244 -0
- tau/trust/__init__.py +13 -0
- tau/trust/manager.py +80 -0
- tau/trust/types.py +14 -0
- tau/trust/utils.py +72 -0
- tau/tui/__init__.py +54 -0
- tau/tui/agent_hooks.py +346 -0
- tau/tui/ansi.py +330 -0
- tau/tui/app.py +540 -0
- tau/tui/autocomplete.py +33 -0
- tau/tui/capabilities.py +119 -0
- tau/tui/commands/__init__.py +3 -0
- tau/tui/commands/appearance.py +498 -0
- tau/tui/commands/auth.py +232 -0
- tau/tui/commands/context.py +38 -0
- tau/tui/commands/misc.py +82 -0
- tau/tui/commands/model.py +118 -0
- tau/tui/commands/session.py +464 -0
- tau/tui/component.py +268 -0
- tau/tui/components/__init__.py +0 -0
- tau/tui/components/autocomplete_manager.py +267 -0
- tau/tui/components/autocomplete_picker.py +143 -0
- tau/tui/components/box.py +90 -0
- tau/tui/components/command_palette.py +144 -0
- tau/tui/components/dynamic_border.py +19 -0
- tau/tui/components/file_picker.py +233 -0
- tau/tui/components/image.py +181 -0
- tau/tui/components/inline_selector.py +71 -0
- tau/tui/components/layout.py +1194 -0
- tau/tui/components/message_list.py +692 -0
- tau/tui/components/modal.py +97 -0
- tau/tui/components/model_palette.py +204 -0
- tau/tui/components/picker_overlay.py +174 -0
- tau/tui/components/prompt_overlay.py +236 -0
- tau/tui/components/resume_modal.py +372 -0
- tau/tui/components/select_list.py +222 -0
- tau/tui/components/settings_modal.py +274 -0
- tau/tui/components/settings_schema.py +203 -0
- tau/tui/components/spinner.py +119 -0
- tau/tui/components/text_input.py +396 -0
- tau/tui/components/text_prompt.py +82 -0
- tau/tui/components/tree_select_list.py +580 -0
- tau/tui/components/trust_screen.py +97 -0
- tau/tui/diff.py +114 -0
- tau/tui/fuzzy.py +99 -0
- tau/tui/input.py +496 -0
- tau/tui/input_handler.py +716 -0
- tau/tui/keybindings.py +87 -0
- tau/tui/markdown.py +286 -0
- tau/tui/message_renderers.py +31 -0
- tau/tui/overlay.py +326 -0
- tau/tui/renderer.py +378 -0
- tau/tui/terminal.py +499 -0
- tau/tui/theme.py +148 -0
- tau/tui/tui.py +544 -0
- tau/tui/ui_context.py +768 -0
- tau/tui/utils.py +20 -0
- tau/utils/__init__.py +0 -0
- tau/utils/http_proxy.py +221 -0
- tau/utils/image_processing.py +172 -0
- tau/utils/secrets.py +59 -0
- tau/utils/version_check.py +60 -0
- tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
- tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
- tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
- tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Awaitable, Callable, Literal
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from tau.runtime.service import Runtime
|
|
9
|
+
from tau.session.manager import SessionManager
|
|
10
|
+
from tau.session.types import SessionEntry
|
|
11
|
+
from tau.settings.manager import SettingsManager
|
|
12
|
+
from tau.tui.ui_context import UIContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Session method option dataclasses
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class NewSessionOptions:
|
|
21
|
+
"""Options for ExtensionContext.new_session()."""
|
|
22
|
+
parent_session: str | None = None
|
|
23
|
+
with_session: Callable[[ExtensionContext], Awaitable[None] | None] | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ForkOptions:
|
|
28
|
+
"""Options for ExtensionContext.fork()."""
|
|
29
|
+
position: Literal["before", "at"] = "at"
|
|
30
|
+
with_session: Callable[[ExtensionContext], Awaitable[None] | None] | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class NavigateTreeOptions:
|
|
35
|
+
"""Options for ExtensionContext.navigate_tree()."""
|
|
36
|
+
summarize: bool = False
|
|
37
|
+
custom_instructions: str | None = None
|
|
38
|
+
replace_instructions: bool = False
|
|
39
|
+
label: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class SwitchSessionOptions:
|
|
44
|
+
"""Options for ExtensionContext.switch_session()."""
|
|
45
|
+
with_session: Callable[[ExtensionContext], Awaitable[None] | None] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ExtensionContext:
|
|
49
|
+
"""
|
|
50
|
+
Runtime context passed to extension command handlers and event handlers.
|
|
51
|
+
|
|
52
|
+
Provides live session state rather than the static info available at
|
|
53
|
+
registration time (which lives on ExtensionAPI).
|
|
54
|
+
|
|
55
|
+
Handler signature:
|
|
56
|
+
async def my_cmd(ctx: ExtensionContext, args: list[str]) -> None:
|
|
57
|
+
print(ctx.cwd, ctx.model_id)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
cwd: Path,
|
|
63
|
+
settings: SettingsManager | None,
|
|
64
|
+
model_id: str,
|
|
65
|
+
provider_id: str,
|
|
66
|
+
session_manager: SessionManager | None = None,
|
|
67
|
+
layout: object | None = None,
|
|
68
|
+
runtime: Runtime | None = None,
|
|
69
|
+
model_thinking: bool = False,
|
|
70
|
+
) -> None:
|
|
71
|
+
self._cwd = cwd
|
|
72
|
+
self._settings = settings
|
|
73
|
+
self._model_id = model_id
|
|
74
|
+
self._provider_id = provider_id
|
|
75
|
+
self._session_manager = session_manager
|
|
76
|
+
self._layout = layout
|
|
77
|
+
self._runtime = runtime
|
|
78
|
+
self._model_thinking = model_thinking
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_runtime(cls, runtime: Runtime) -> ExtensionContext:
|
|
82
|
+
"""Construct a context snapshot from the live runtime."""
|
|
83
|
+
sm = runtime.session_manager
|
|
84
|
+
agent = runtime.agent
|
|
85
|
+
llm = agent._engine.llm if agent is not None else None
|
|
86
|
+
return cls(
|
|
87
|
+
cwd=sm.cwd if sm is not None else Path("."),
|
|
88
|
+
settings=runtime.settings_manager,
|
|
89
|
+
model_id=llm.model.id if llm is not None else "",
|
|
90
|
+
provider_id=llm.provider_id if llm is not None else "",
|
|
91
|
+
session_manager=sm,
|
|
92
|
+
layout=getattr(runtime, "_layout", None),
|
|
93
|
+
runtime=runtime,
|
|
94
|
+
model_thinking=bool(llm.model.thinking) if llm is not None else False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def cwd(self) -> Path:
|
|
99
|
+
"""Current working directory."""
|
|
100
|
+
return self._cwd
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def settings(self) -> SettingsManager | None:
|
|
104
|
+
"""Settings manager, or None if not available."""
|
|
105
|
+
return self._settings
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def model_id(self) -> str:
|
|
109
|
+
"""Active model identifier."""
|
|
110
|
+
return self._model_id
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def provider_id(self) -> str:
|
|
114
|
+
"""Active provider identifier."""
|
|
115
|
+
return self._provider_id
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def model_thinking(self) -> bool:
|
|
119
|
+
"""Whether the active model supports extended thinking."""
|
|
120
|
+
return self._model_thinking
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def mode(self) -> str:
|
|
124
|
+
"""Execution mode: ``'tui'`` when running inside an interactive TUI session, ``'headless'`` otherwise."""
|
|
125
|
+
return "tui" if self._layout is not None else "headless"
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def session_entries(self) -> list[SessionEntry]:
|
|
129
|
+
"""All entries in the session file across every branch (the full tree).
|
|
130
|
+
|
|
131
|
+
Use this when you need to inspect the complete history regardless of
|
|
132
|
+
which branch the user is currently on. Most extensions should prefer
|
|
133
|
+
``branch_entries`` instead.
|
|
134
|
+
"""
|
|
135
|
+
if self._session_manager is None:
|
|
136
|
+
return []
|
|
137
|
+
return self._session_manager.get_entries()
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def branch_entries(self) -> list[SessionEntry]:
|
|
141
|
+
"""Entries on the current branch only (root → current leaf, in order).
|
|
142
|
+
|
|
143
|
+
This is the correct view for restoring per-branch extension state — it
|
|
144
|
+
excludes entries that belong to abandoned branches, so you always see
|
|
145
|
+
the data that is actually in scope for the current conversation.
|
|
146
|
+
|
|
147
|
+
Example — restore saved state from the most recent custom entry::
|
|
148
|
+
|
|
149
|
+
for entry in reversed(ctx.branch_entries):
|
|
150
|
+
if isinstance(entry, CustomInfoEntry) and entry.custom_type == "my-ext":
|
|
151
|
+
restore(entry.data)
|
|
152
|
+
break
|
|
153
|
+
"""
|
|
154
|
+
if self._session_manager is None:
|
|
155
|
+
return []
|
|
156
|
+
return self._session_manager.get_branch()
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def ui(self) -> "UIContext | None":
|
|
160
|
+
"""TUI customization API. None when running outside a TUI session."""
|
|
161
|
+
if self._layout is None:
|
|
162
|
+
return None
|
|
163
|
+
from tau.tui.ui_context import UIContext
|
|
164
|
+
return UIContext(self._layout, settings=self._settings) # type: ignore[arg-type]
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def has_ui(self) -> bool:
|
|
168
|
+
"""True when dialog-capable UI is available (TUI mode)."""
|
|
169
|
+
return self._layout is not None
|
|
170
|
+
|
|
171
|
+
# ── Agent state ───────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
def is_idle(self) -> bool:
|
|
174
|
+
"""Return True when the agent is not currently streaming a response."""
|
|
175
|
+
if self._runtime is None:
|
|
176
|
+
return True
|
|
177
|
+
agent = getattr(self._runtime, "agent", None)
|
|
178
|
+
if agent is None:
|
|
179
|
+
return True
|
|
180
|
+
return not getattr(agent, "_running", False)
|
|
181
|
+
|
|
182
|
+
def abort(self) -> None:
|
|
183
|
+
"""Abort the current agent operation (no-op if idle)."""
|
|
184
|
+
if self._runtime is None:
|
|
185
|
+
return
|
|
186
|
+
agent = getattr(self._runtime, "agent", None)
|
|
187
|
+
if agent is None:
|
|
188
|
+
return
|
|
189
|
+
cancel_fn = getattr(agent, "cancel", None)
|
|
190
|
+
if callable(cancel_fn):
|
|
191
|
+
cancel_fn()
|
|
192
|
+
|
|
193
|
+
def shutdown(self) -> None:
|
|
194
|
+
"""Gracefully shut down tau and exit."""
|
|
195
|
+
import sys
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
|
|
198
|
+
def get_context_usage(self) -> dict | None:
|
|
199
|
+
"""Return current context usage info or None if unavailable.
|
|
200
|
+
|
|
201
|
+
Keys: ``tokens`` (int | None), ``context_window`` (int), ``percent`` (float | None).
|
|
202
|
+
"""
|
|
203
|
+
if self._runtime is None:
|
|
204
|
+
return None
|
|
205
|
+
agent = getattr(self._runtime, "agent", None)
|
|
206
|
+
if agent is None:
|
|
207
|
+
return None
|
|
208
|
+
usage = agent.get_context_usage()
|
|
209
|
+
if usage is None:
|
|
210
|
+
return None
|
|
211
|
+
tokens = getattr(usage, "tokens", None)
|
|
212
|
+
window = getattr(usage, "context_window", None) or 0
|
|
213
|
+
percent = getattr(usage, "percent", None)
|
|
214
|
+
return {"tokens": tokens, "context_window": window, "percent": percent}
|
|
215
|
+
|
|
216
|
+
def compact(self, custom_instructions: str | None = None) -> None:
|
|
217
|
+
"""Trigger context compaction without waiting for completion."""
|
|
218
|
+
import asyncio
|
|
219
|
+
if self._runtime is None:
|
|
220
|
+
return
|
|
221
|
+
agent = getattr(self._runtime, "agent", None)
|
|
222
|
+
if agent is None:
|
|
223
|
+
return
|
|
224
|
+
compact_fn = getattr(agent, "compact", None)
|
|
225
|
+
if callable(compact_fn):
|
|
226
|
+
import inspect
|
|
227
|
+
result = compact_fn(custom_instructions=custom_instructions)
|
|
228
|
+
if inspect.isawaitable(result):
|
|
229
|
+
asyncio.ensure_future(result) # type: ignore[arg-type]
|
|
230
|
+
|
|
231
|
+
def get_system_prompt(self) -> str:
|
|
232
|
+
"""Return the current effective system prompt."""
|
|
233
|
+
if self._runtime is None:
|
|
234
|
+
return ""
|
|
235
|
+
agent = getattr(self._runtime, "agent", None)
|
|
236
|
+
if agent is None:
|
|
237
|
+
return ""
|
|
238
|
+
return getattr(agent, "_system_prompt", "") or ""
|
|
239
|
+
|
|
240
|
+
# ── Session control (command context) ─────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
async def wait_for_idle(self) -> None:
|
|
243
|
+
"""Suspend until the agent finishes its current turn."""
|
|
244
|
+
if self._runtime is None:
|
|
245
|
+
return
|
|
246
|
+
agent = self._runtime.agent
|
|
247
|
+
if agent is None:
|
|
248
|
+
return
|
|
249
|
+
await agent._engine.state.idle_event.wait()
|
|
250
|
+
|
|
251
|
+
async def new_session(self, options: NewSessionOptions | None = None) -> dict:
|
|
252
|
+
"""Start a new session. Returns ``{"cancelled": bool}``.
|
|
253
|
+
|
|
254
|
+
``options.with_session(ctx)`` runs inside the new session before the UI
|
|
255
|
+
transitions — use it to inject initial messages via ``ctx.send_user_message``.
|
|
256
|
+
"""
|
|
257
|
+
if self._runtime is None:
|
|
258
|
+
return {"cancelled": True}
|
|
259
|
+
opts = options or NewSessionOptions()
|
|
260
|
+
await self._runtime.new_session(with_session=opts.with_session)
|
|
261
|
+
return {"cancelled": False}
|
|
262
|
+
|
|
263
|
+
async def fork(self, entry_id: str, options: ForkOptions | None = None) -> dict:
|
|
264
|
+
"""Fork from a specific entry. Returns ``{"cancelled": bool}``."""
|
|
265
|
+
if self._runtime is None:
|
|
266
|
+
return {"cancelled": True}
|
|
267
|
+
opts = options or ForkOptions()
|
|
268
|
+
await self._runtime.fork_session(
|
|
269
|
+
entry_id, position=opts.position, with_session=opts.with_session
|
|
270
|
+
)
|
|
271
|
+
return {"cancelled": False}
|
|
272
|
+
|
|
273
|
+
async def navigate_tree(
|
|
274
|
+
self,
|
|
275
|
+
target_id: str,
|
|
276
|
+
*,
|
|
277
|
+
summarize: bool = False,
|
|
278
|
+
custom_instructions: str | None = None,
|
|
279
|
+
options: NavigateTreeOptions | None = None,
|
|
280
|
+
) -> dict:
|
|
281
|
+
"""Navigate the session tree. Returns ``{"cancelled": bool}``."""
|
|
282
|
+
if self._runtime is None:
|
|
283
|
+
return {"cancelled": True}
|
|
284
|
+
opts = options or NavigateTreeOptions(
|
|
285
|
+
summarize=summarize, custom_instructions=custom_instructions
|
|
286
|
+
)
|
|
287
|
+
ok = await self._runtime.navigate_tree(
|
|
288
|
+
target_id,
|
|
289
|
+
summarize=opts.summarize,
|
|
290
|
+
custom_instructions=opts.custom_instructions,
|
|
291
|
+
replace_instructions=opts.replace_instructions,
|
|
292
|
+
label=opts.label,
|
|
293
|
+
)
|
|
294
|
+
return {"cancelled": not ok}
|
|
295
|
+
|
|
296
|
+
async def switch_session(
|
|
297
|
+
self,
|
|
298
|
+
session_path: str,
|
|
299
|
+
options: SwitchSessionOptions | None = None,
|
|
300
|
+
) -> dict:
|
|
301
|
+
"""Switch to a different session file. Returns ``{"cancelled": bool}``."""
|
|
302
|
+
if self._runtime is None:
|
|
303
|
+
return {"cancelled": True}
|
|
304
|
+
opts = options or SwitchSessionOptions()
|
|
305
|
+
await self._runtime.resume_session(Path(session_path), with_session=opts.with_session)
|
|
306
|
+
return {"cancelled": False}
|
|
307
|
+
|
|
308
|
+
async def send_message(self, content: str) -> None:
|
|
309
|
+
"""Append a plain text message to the current session as a user turn.
|
|
310
|
+
|
|
311
|
+
Useful inside ``with_session`` callbacks to seed a new session with
|
|
312
|
+
context before the agent processes its first real turn.
|
|
313
|
+
"""
|
|
314
|
+
if self._runtime is None:
|
|
315
|
+
return
|
|
316
|
+
agent = getattr(self._runtime, "agent", None)
|
|
317
|
+
if agent is None:
|
|
318
|
+
return
|
|
319
|
+
engine = getattr(agent, "_engine", None)
|
|
320
|
+
if engine is None:
|
|
321
|
+
return
|
|
322
|
+
from tau.message.types import UserMessage, TextContent
|
|
323
|
+
msg = UserMessage(contents=[TextContent(content=content)])
|
|
324
|
+
await engine.steer(msg)
|
|
325
|
+
|
|
326
|
+
async def send_user_message(
|
|
327
|
+
self,
|
|
328
|
+
content: str,
|
|
329
|
+
deliver_as: Literal["steer", "follow_up"] = "steer",
|
|
330
|
+
) -> None:
|
|
331
|
+
"""Inject a user message into the active engine queue.
|
|
332
|
+
|
|
333
|
+
``deliver_as='steer'`` inserts immediately (mid-turn interception).
|
|
334
|
+
``deliver_as='follow_up'`` queues for after the current turn completes.
|
|
335
|
+
"""
|
|
336
|
+
if self._runtime is None:
|
|
337
|
+
return
|
|
338
|
+
agent = getattr(self._runtime, "agent", None)
|
|
339
|
+
if agent is None:
|
|
340
|
+
return
|
|
341
|
+
engine = getattr(agent, "_engine", None)
|
|
342
|
+
if engine is None:
|
|
343
|
+
return
|
|
344
|
+
from tau.message.types import UserMessage, TextContent
|
|
345
|
+
msg = UserMessage(contents=[TextContent(content=content)])
|
|
346
|
+
if deliver_as == "follow_up":
|
|
347
|
+
await engine.follow_up(msg)
|
|
348
|
+
else:
|
|
349
|
+
await engine.steer(msg)
|
|
350
|
+
|
|
351
|
+
async def is_project_trusted(self) -> bool | None:
|
|
352
|
+
"""Return the trust status of the current project directory.
|
|
353
|
+
|
|
354
|
+
Checks in order:
|
|
355
|
+
1. The ``project_trust`` hook (extensions can override).
|
|
356
|
+
2. The settings manager's cached trust state (set at startup or by the prompt).
|
|
357
|
+
3. Returns ``None`` if undecided.
|
|
358
|
+
"""
|
|
359
|
+
if self._runtime is None:
|
|
360
|
+
return None
|
|
361
|
+
sm_session = getattr(self._runtime, "session_manager", None)
|
|
362
|
+
cwd = str(sm_session.cwd) if sm_session is not None else ""
|
|
363
|
+
|
|
364
|
+
from tau.hooks.types import ProjectTrustEvent, ProjectTrustResult
|
|
365
|
+
results = await self._runtime.hooks.emit(ProjectTrustEvent(project_dir=cwd))
|
|
366
|
+
for r in results:
|
|
367
|
+
if isinstance(r, ProjectTrustResult) and r.trusted is not None:
|
|
368
|
+
return r.trusted
|
|
369
|
+
|
|
370
|
+
# Fall back to the settings manager's runtime trust state
|
|
371
|
+
sm = getattr(self._runtime, "settings_manager", None)
|
|
372
|
+
if sm is not None:
|
|
373
|
+
return sm.is_project_trusted()
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
def set_project_trusted(self, trusted: bool, *, remember: bool = False) -> None:
|
|
377
|
+
"""Set the project's trust state for this session.
|
|
378
|
+
|
|
379
|
+
``remember=True`` persists the decision to ``~/.tau/trust.json``
|
|
380
|
+
so it survives future sessions.
|
|
381
|
+
"""
|
|
382
|
+
sm = getattr(self._runtime, "settings_manager", None) if self._runtime else None
|
|
383
|
+
if sm is None:
|
|
384
|
+
return
|
|
385
|
+
sm.set_project_trusted(trusted)
|
|
386
|
+
if remember:
|
|
387
|
+
from tau.trust.manager import trust_store
|
|
388
|
+
sm_session = getattr(self._runtime, "session_manager", None)
|
|
389
|
+
cwd = sm_session.cwd if sm_session is not None else None
|
|
390
|
+
if cwd is not None:
|
|
391
|
+
trust_store.set(str(cwd), trusted)
|
|
392
|
+
|
|
393
|
+
async def reload(self) -> None:
|
|
394
|
+
"""Reload extensions, skills, prompts, and settings."""
|
|
395
|
+
if self._runtime is None:
|
|
396
|
+
return
|
|
397
|
+
await self._runtime.reload_extensions()
|
|
398
|
+
|
|
399
|
+
# ── Context inspection helpers ─────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
def has_pending_messages(self) -> bool:
|
|
402
|
+
"""Return True if any steering or follow-up messages are queued.
|
|
403
|
+
|
|
404
|
+
Useful to avoid injecting a duplicate message when one is already
|
|
405
|
+
waiting to be processed by the engine.
|
|
406
|
+
"""
|
|
407
|
+
if self._runtime is None:
|
|
408
|
+
return False
|
|
409
|
+
agent = getattr(self._runtime, "agent", None)
|
|
410
|
+
if agent is None:
|
|
411
|
+
return False
|
|
412
|
+
has_fn = getattr(agent, "has_pending_messages", None)
|
|
413
|
+
if callable(has_fn):
|
|
414
|
+
return bool(has_fn())
|
|
415
|
+
return False
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def signal(self) -> "object | None":
|
|
419
|
+
"""The current abort signal (``asyncio.Event``) while the agent is streaming.
|
|
420
|
+
|
|
421
|
+
The event is *set* when the current operation has been aborted.
|
|
422
|
+
Returns ``None`` when the agent is idle or unavailable.
|
|
423
|
+
"""
|
|
424
|
+
if self._runtime is None:
|
|
425
|
+
return None
|
|
426
|
+
agent = getattr(self._runtime, "agent", None)
|
|
427
|
+
if agent is None:
|
|
428
|
+
return None
|
|
429
|
+
return getattr(agent, "_signal", None)
|
|
430
|
+
|
|
431
|
+
def get_system_prompt_options(self) -> dict:
|
|
432
|
+
"""Return metadata about how the active system prompt was assembled.
|
|
433
|
+
|
|
434
|
+
Keys:
|
|
435
|
+
- ``skills`` — list of skill names currently loaded
|
|
436
|
+
- ``prompts`` — list of prompt template names currently loaded
|
|
437
|
+
- ``tools`` — list of tool names registered with the engine
|
|
438
|
+
- ``system_prompt_length`` — character length of the built prompt
|
|
439
|
+
"""
|
|
440
|
+
from tau.skills.registry import skill_registry
|
|
441
|
+
from tau.prompts.registry import prompt_registry
|
|
442
|
+
|
|
443
|
+
skill_names = [s.name for s in skill_registry.list()]
|
|
444
|
+
prompt_names = [p.name for p in prompt_registry.list()]
|
|
445
|
+
|
|
446
|
+
tool_names: list[str] = []
|
|
447
|
+
if self._runtime is not None:
|
|
448
|
+
agent = getattr(self._runtime, "agent", None)
|
|
449
|
+
if agent is not None:
|
|
450
|
+
engine = getattr(agent, "_engine", None)
|
|
451
|
+
if engine is not None:
|
|
452
|
+
tools = getattr(engine, "tools", None) or []
|
|
453
|
+
tool_names = [t.name for t in tools]
|
|
454
|
+
|
|
455
|
+
prompt_len = len(self.get_system_prompt())
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
"skills": skill_names,
|
|
459
|
+
"prompts": prompt_names,
|
|
460
|
+
"tools": tool_names,
|
|
461
|
+
"system_prompt_length": prompt_len,
|
|
462
|
+
}
|
tau/extensions/events.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EventBus:
|
|
12
|
+
"""
|
|
13
|
+
Lightweight pub/sub bus for cross-extension communication.
|
|
14
|
+
|
|
15
|
+
Separate from the main hook bus — intended for extensions to send
|
|
16
|
+
signals to each other rather than to the core runtime.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
# Publisher
|
|
20
|
+
await tau.events.emit("my-ext:done", {"count": 42})
|
|
21
|
+
|
|
22
|
+
# Subscriber (in another extension or same one)
|
|
23
|
+
@tau.events.on("my-ext:done")
|
|
24
|
+
async def handler(data):
|
|
25
|
+
print(data["count"])
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
self._handlers: dict[str, list[Callable]] = defaultdict(list)
|
|
30
|
+
|
|
31
|
+
def on(self, event: str, handler: Callable | None = None) -> Any:
|
|
32
|
+
"""Subscribe to a named event.
|
|
33
|
+
|
|
34
|
+
Direct call:
|
|
35
|
+
tau.events.on("my-ext:done", handler)
|
|
36
|
+
|
|
37
|
+
Decorator:
|
|
38
|
+
@tau.events.on("my-ext:done")
|
|
39
|
+
async def handler(data): ...
|
|
40
|
+
"""
|
|
41
|
+
if handler is not None:
|
|
42
|
+
self._handlers[event].append(handler)
|
|
43
|
+
return lambda: self._handlers[event].remove(handler)
|
|
44
|
+
|
|
45
|
+
def decorator(fn: Callable) -> Callable:
|
|
46
|
+
"""Register the function as an event handler."""
|
|
47
|
+
self._handlers[event].append(fn)
|
|
48
|
+
return fn
|
|
49
|
+
|
|
50
|
+
return decorator
|
|
51
|
+
|
|
52
|
+
async def emit(self, event: str, data: Any = None) -> list[Any]:
|
|
53
|
+
"""Fire all handlers subscribed to *event*. Returns collected results."""
|
|
54
|
+
results: list[Any] = []
|
|
55
|
+
for handler in list(self._handlers.get(event, [])):
|
|
56
|
+
try:
|
|
57
|
+
result = handler(data)
|
|
58
|
+
if asyncio.iscoroutine(result):
|
|
59
|
+
result = await result
|
|
60
|
+
results.append(result)
|
|
61
|
+
except Exception:
|
|
62
|
+
logger.exception("EventBus handler %r raised on event %r", handler, event)
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
def clear(self, event: str | None = None) -> None:
|
|
66
|
+
"""Remove all handlers for one event, or all events if None."""
|
|
67
|
+
if event is None:
|
|
68
|
+
self._handlers.clear()
|
|
69
|
+
else:
|
|
70
|
+
self._handlers.pop(event, None)
|