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
tau/__init__.py
ADDED
|
File without changes
|
tau/agent/__init__.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
from datetime import date
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from tau.agent.prompt.types import PromptOptions
|
|
10
|
+
from tau.settings.paths import (
|
|
11
|
+
get_system_prompt_path,
|
|
12
|
+
get_append_system_prompt_path,
|
|
13
|
+
get_docs_dir,
|
|
14
|
+
get_readme_path,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_project_context_file(cwd: Path) -> tuple[str, Path] | None:
|
|
19
|
+
"""Load AGENTS.md or CLAUDE.md from project directory.
|
|
20
|
+
|
|
21
|
+
Returns (content, path) tuple if found, None otherwise.
|
|
22
|
+
Looks for AGENTS.md first, then CLAUDE.md (case-insensitive).
|
|
23
|
+
"""
|
|
24
|
+
candidates = ["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]
|
|
25
|
+
for filename in candidates:
|
|
26
|
+
path = cwd / filename
|
|
27
|
+
if path.is_file():
|
|
28
|
+
try:
|
|
29
|
+
content = path.read_text(encoding="utf-8").strip()
|
|
30
|
+
return (content, path) if content else None
|
|
31
|
+
except OSError:
|
|
32
|
+
pass
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
_DEFAULT_IDENTITY = """\
|
|
36
|
+
You are a coding agent. You help users understand, write, and modify code and files.
|
|
37
|
+
|
|
38
|
+
You have strong software engineering skills. You think carefully before making changes,
|
|
39
|
+
and follow the existing style and conventions of the project.
|
|
40
|
+
If a task is ambiguous, ask a clarifying question before proceeding.\
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_GIT_STATUS_MAX_LINES = 30
|
|
45
|
+
_GIT_LOG_COUNT = 5
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _detect_os() -> str:
|
|
49
|
+
"""Return a human-readable OS name and version."""
|
|
50
|
+
system = platform.system()
|
|
51
|
+
if system == "Darwin":
|
|
52
|
+
return f"macOS {platform.mac_ver()[0] or platform.release()}"
|
|
53
|
+
if system == "Linux":
|
|
54
|
+
return f"Linux {platform.release()}"
|
|
55
|
+
if system == "Windows":
|
|
56
|
+
return f"Windows {platform.release()}"
|
|
57
|
+
return f"{system} {platform.release()}".strip()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _detect_shell() -> str:
|
|
61
|
+
"""Detect the user's shell from $SHELL, falling back to common shells on PATH."""
|
|
62
|
+
shell = os.environ.get("SHELL", "")
|
|
63
|
+
if shell:
|
|
64
|
+
return Path(shell).name
|
|
65
|
+
for candidate in ("bash", "zsh", "fish", "sh"):
|
|
66
|
+
if shutil.which(candidate):
|
|
67
|
+
return candidate
|
|
68
|
+
return "unknown"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _git_status(cwd: Path) -> str:
|
|
72
|
+
"""Return a git-state snapshot for the system prompt, or "" if not a repo.
|
|
73
|
+
|
|
74
|
+
Best-effort: any GitPython error (not a repo, no commits, git binary missing)
|
|
75
|
+
results in an empty string rather than raising. The ``status`` listing is
|
|
76
|
+
truncated to keep large/dirty working trees from bloating the prompt.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
from git import Repo
|
|
80
|
+
from git.exc import GitError, InvalidGitRepositoryError, NoSuchPathError
|
|
81
|
+
except ImportError:
|
|
82
|
+
return ""
|
|
83
|
+
|
|
84
|
+
repo = None
|
|
85
|
+
try:
|
|
86
|
+
repo = Repo(cwd, search_parent_directories=True)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
branch = repo.active_branch.name
|
|
90
|
+
except TypeError:
|
|
91
|
+
# Detached HEAD — no active branch.
|
|
92
|
+
branch = f"(detached at {repo.head.commit.hexsha[:8]})"
|
|
93
|
+
|
|
94
|
+
remote = next((r.url for r in repo.remotes if r.name == "origin"), None)
|
|
95
|
+
if remote is None and repo.remotes:
|
|
96
|
+
remote = repo.remotes[0].url
|
|
97
|
+
|
|
98
|
+
status = repo.git.status("--porcelain")
|
|
99
|
+
if status:
|
|
100
|
+
lines = status.splitlines()
|
|
101
|
+
shown = lines[:_GIT_STATUS_MAX_LINES]
|
|
102
|
+
extra = len(lines) - len(shown)
|
|
103
|
+
status_block = "\n".join(shown)
|
|
104
|
+
if extra > 0:
|
|
105
|
+
status_block += f"\n… and {extra} more changed file(s)"
|
|
106
|
+
else:
|
|
107
|
+
status_block = "(clean)"
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
log = repo.git.log(f"-{_GIT_LOG_COUNT}", "--oneline", "--no-color")
|
|
111
|
+
except GitError:
|
|
112
|
+
log = "" # repo with no commits yet
|
|
113
|
+
except (InvalidGitRepositoryError, NoSuchPathError):
|
|
114
|
+
return ""
|
|
115
|
+
except GitError:
|
|
116
|
+
return ""
|
|
117
|
+
finally:
|
|
118
|
+
if repo is not None:
|
|
119
|
+
repo.close()
|
|
120
|
+
|
|
121
|
+
parts = [
|
|
122
|
+
"\n\ngitStatus: snapshot taken at session start; it is not updated during the "
|
|
123
|
+
"conversation. Re-run git yourself before relying on it.",
|
|
124
|
+
f"Current branch: {branch}",
|
|
125
|
+
]
|
|
126
|
+
if remote:
|
|
127
|
+
parts.append(f"Remote (origin): {remote}")
|
|
128
|
+
parts.append(f"Status:\n{status_block}")
|
|
129
|
+
if log:
|
|
130
|
+
parts.append(f"Recent commits:\n{log}")
|
|
131
|
+
return "\n".join(parts)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class PromptBuilder:
|
|
135
|
+
"""
|
|
136
|
+
Assembles the system prompt from layered sources.
|
|
137
|
+
|
|
138
|
+
Layers in order:
|
|
139
|
+
Identity — SYSTEM.md if present, else built-in identity; --system bypasses entirely
|
|
140
|
+
Tools section — auto-generated from tool list (descriptions + guidelines)
|
|
141
|
+
Tau docs — tau documentation and examples
|
|
142
|
+
Project Instructions — AGENTS.md or CLAUDE.md from project (if present)
|
|
143
|
+
Skills section — available skills
|
|
144
|
+
APPEND_SYSTEM.md — verbatim append (user additions)
|
|
145
|
+
Footer — cwd, date
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, options: PromptOptions) -> None:
|
|
149
|
+
self._opts = options
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
# Public
|
|
153
|
+
# ------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def build(self) -> str:
|
|
156
|
+
"""Build the complete system prompt."""
|
|
157
|
+
identity = self._identity()
|
|
158
|
+
tools = self._tools_section()
|
|
159
|
+
docs = self._docs_section()
|
|
160
|
+
project_context = self._project_context_section()
|
|
161
|
+
skills = self._skills_section()
|
|
162
|
+
append = self._append()
|
|
163
|
+
git = self._git_section()
|
|
164
|
+
footer = self._footer()
|
|
165
|
+
return identity + tools + docs + project_context + skills + append + git + footer
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
# Layers
|
|
169
|
+
# ------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
def _identity(self) -> str:
|
|
172
|
+
if self._opts.custom_prompt:
|
|
173
|
+
return self._opts.custom_prompt
|
|
174
|
+
|
|
175
|
+
system_md = self._read_path(get_system_prompt_path(self._opts.cwd))
|
|
176
|
+
if system_md:
|
|
177
|
+
return system_md
|
|
178
|
+
|
|
179
|
+
return _DEFAULT_IDENTITY
|
|
180
|
+
|
|
181
|
+
def _tools_section(self) -> str:
|
|
182
|
+
tools = self._opts.tools
|
|
183
|
+
if not tools:
|
|
184
|
+
return ""
|
|
185
|
+
lines: list[str] = []
|
|
186
|
+
guidelines: list[str] = []
|
|
187
|
+
for t in sorted(tools, key=lambda t: t.name):
|
|
188
|
+
desc = t.description.splitlines()[0].strip().rstrip(".")
|
|
189
|
+
snippet = getattr(t, "prompt_snippet", None)
|
|
190
|
+
if snippet:
|
|
191
|
+
desc = f"{desc}. {snippet.strip()}"
|
|
192
|
+
lines.append(f"- **{t.name}** — {desc}")
|
|
193
|
+
guideline = getattr(t, "prompt_guidelines", None)
|
|
194
|
+
if guideline:
|
|
195
|
+
guidelines.append(f"- **{t.name}**: {guideline.strip()}")
|
|
196
|
+
section = "\n\n# Available Tools\n\n" + "\n".join(lines)
|
|
197
|
+
if guidelines:
|
|
198
|
+
section += "\n\n## Tool Guidelines\n\n" + "\n".join(guidelines)
|
|
199
|
+
return section
|
|
200
|
+
|
|
201
|
+
def _project_context_section(self) -> str:
|
|
202
|
+
"""Include project-specific context from AGENTS.md or CLAUDE.md if present.
|
|
203
|
+
|
|
204
|
+
Skipped if:
|
|
205
|
+
- disable_context_files is True (--no-context-files flag)
|
|
206
|
+
- project is not trusted (--no-approve flag or trust store)
|
|
207
|
+
"""
|
|
208
|
+
if self._opts.disable_context_files:
|
|
209
|
+
return ""
|
|
210
|
+
if self._opts.project_trusted is False:
|
|
211
|
+
return ""
|
|
212
|
+
context = load_project_context_file(self._opts.cwd)
|
|
213
|
+
if not context:
|
|
214
|
+
return ""
|
|
215
|
+
content, path = context
|
|
216
|
+
return (
|
|
217
|
+
f"\n\n# Project Instructions\n\n"
|
|
218
|
+
f"Project-specific guidelines and rules (from {path.name}):\n\n"
|
|
219
|
+
f"{content}\n\n"
|
|
220
|
+
"Follow project-specific instructions before general Tau guidelines."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _docs_section(self) -> str:
|
|
224
|
+
readme = get_readme_path()
|
|
225
|
+
docs = get_docs_dir()
|
|
226
|
+
return (
|
|
227
|
+
"\n\nTau documentation (read only when the user asks about Tau itself, its"
|
|
228
|
+
" settings, extensions, themes, skills, tools, sessions, or keybindings):\n"
|
|
229
|
+
f"- README: {readme}\n"
|
|
230
|
+
f"- Docs directory: {docs}\n"
|
|
231
|
+
"- When asked about: settings (docs/settings.md), tools (docs/tools.md),"
|
|
232
|
+
" extensions (docs/extensions.md), themes (docs/themes.md),"
|
|
233
|
+
" skills (docs/skills.md), keybindings (docs/keybindings.md),"
|
|
234
|
+
" sessions (docs/sessions.md), usage (docs/usage.md),"
|
|
235
|
+
" Python API (docs/python-api.md), inference providers (docs/inference-providers.md)\n"
|
|
236
|
+
"- Resolve all doc paths under the Docs directory above, not the current working directory\n"
|
|
237
|
+
"- Read .md files completely and follow cross-references before answering"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _skills_section(self) -> str:
|
|
241
|
+
from tau.skills.registry import skill_registry
|
|
242
|
+
block = skill_registry.format_for_system_prompt(self._opts.skills)
|
|
243
|
+
return f"\n\n{block}" if block else ""
|
|
244
|
+
|
|
245
|
+
def _append(self) -> str:
|
|
246
|
+
parts: list[str] = []
|
|
247
|
+
|
|
248
|
+
if self._opts.append_prompt:
|
|
249
|
+
parts.append(self._opts.append_prompt)
|
|
250
|
+
else:
|
|
251
|
+
append_md = self._read_path(get_append_system_prompt_path(self._opts.cwd))
|
|
252
|
+
if append_md:
|
|
253
|
+
parts.append(append_md)
|
|
254
|
+
|
|
255
|
+
for extra in self._opts.extra_appends:
|
|
256
|
+
stripped = extra.strip()
|
|
257
|
+
if stripped:
|
|
258
|
+
parts.append(stripped)
|
|
259
|
+
|
|
260
|
+
return ("\n\n" + "\n\n".join(parts)) if parts else ""
|
|
261
|
+
|
|
262
|
+
def _git_section(self) -> str:
|
|
263
|
+
"""Include a snapshot of git state when cwd is inside a repo.
|
|
264
|
+
|
|
265
|
+
Skipped if the project is not trusted (--no-approve flag or trust store),
|
|
266
|
+
mirroring the project-context gating. Returns "" when not a git repo or
|
|
267
|
+
if anything goes wrong reading it — git info is best-effort context, never
|
|
268
|
+
a hard failure.
|
|
269
|
+
"""
|
|
270
|
+
if self._opts.project_trusted is False:
|
|
271
|
+
return ""
|
|
272
|
+
return _git_status(self._opts.cwd)
|
|
273
|
+
|
|
274
|
+
def _footer(self) -> str:
|
|
275
|
+
cwd = str(self._opts.cwd).replace("\\", "/")
|
|
276
|
+
today = date.today().isoformat()
|
|
277
|
+
return (
|
|
278
|
+
"\n\n# Environment\n"
|
|
279
|
+
f"Current working directory: {cwd}\n"
|
|
280
|
+
f"OS: {_detect_os()}\n"
|
|
281
|
+
f"Architecture: {platform.machine()}\n"
|
|
282
|
+
f"Shell: {_detect_shell()}\n"
|
|
283
|
+
f"Date: {today}"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# ------------------------------------------------------------------
|
|
287
|
+
# Helpers
|
|
288
|
+
# ------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
def _read_path(self, path: Path) -> str | None:
|
|
291
|
+
if path.is_file():
|
|
292
|
+
try:
|
|
293
|
+
content = path.read_text(encoding="utf-8").strip()
|
|
294
|
+
return content if content else None
|
|
295
|
+
except OSError:
|
|
296
|
+
return None
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def build_prompt(options: PromptOptions) -> str:
|
|
301
|
+
"""Build a system prompt from the given options."""
|
|
302
|
+
return PromptBuilder(options).build()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from tau.tool.types import Tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PromptOptions(BaseModel):
|
|
10
|
+
"""Options for constructing the system prompt."""
|
|
11
|
+
model_config = {'arbitrary_types_allowed': True}
|
|
12
|
+
|
|
13
|
+
cwd: Path
|
|
14
|
+
tools: list[Tool] = Field(default_factory=list)
|
|
15
|
+
|
|
16
|
+
# Identity override — loaded from SYSTEM.md if present, else default coding agent identity.
|
|
17
|
+
custom_prompt: str | None = None
|
|
18
|
+
|
|
19
|
+
# Appended verbatim after all other sections (APPEND_SYSTEM.md).
|
|
20
|
+
append_prompt: str | None = None
|
|
21
|
+
|
|
22
|
+
# Extra strings appended after append_prompt (used by extensions).
|
|
23
|
+
extra_appends: list[str] = Field(default_factory=list)
|
|
24
|
+
|
|
25
|
+
# Skills to list in the system prompt as <available_skills>.
|
|
26
|
+
skills: list[Any] = Field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
# Disable auto-discovery of AGENTS.md and CLAUDE.md from project directory.
|
|
29
|
+
disable_context_files: bool = Field(default=False)
|
|
30
|
+
|
|
31
|
+
# Whether the project directory is trusted (for loading extensions, settings, context files).
|
|
32
|
+
# None = auto-detect from trust store.
|
|
33
|
+
project_trusted: bool | None = Field(default=None)
|