tunacode-cli 0.1.21__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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (174) hide show
  1. tunacode/__init__.py +0 -0
  2. tunacode/cli/textual_repl.tcss +283 -0
  3. tunacode/configuration/__init__.py +1 -0
  4. tunacode/configuration/defaults.py +45 -0
  5. tunacode/configuration/models.py +147 -0
  6. tunacode/configuration/models_registry.json +1 -0
  7. tunacode/configuration/pricing.py +74 -0
  8. tunacode/configuration/settings.py +35 -0
  9. tunacode/constants.py +227 -0
  10. tunacode/core/__init__.py +6 -0
  11. tunacode/core/agents/__init__.py +39 -0
  12. tunacode/core/agents/agent_components/__init__.py +48 -0
  13. tunacode/core/agents/agent_components/agent_config.py +441 -0
  14. tunacode/core/agents/agent_components/agent_helpers.py +290 -0
  15. tunacode/core/agents/agent_components/message_handler.py +99 -0
  16. tunacode/core/agents/agent_components/node_processor.py +477 -0
  17. tunacode/core/agents/agent_components/response_state.py +129 -0
  18. tunacode/core/agents/agent_components/result_wrapper.py +51 -0
  19. tunacode/core/agents/agent_components/state_transition.py +112 -0
  20. tunacode/core/agents/agent_components/streaming.py +271 -0
  21. tunacode/core/agents/agent_components/task_completion.py +40 -0
  22. tunacode/core/agents/agent_components/tool_buffer.py +44 -0
  23. tunacode/core/agents/agent_components/tool_executor.py +101 -0
  24. tunacode/core/agents/agent_components/truncation_checker.py +37 -0
  25. tunacode/core/agents/delegation_tools.py +109 -0
  26. tunacode/core/agents/main.py +545 -0
  27. tunacode/core/agents/prompts.py +66 -0
  28. tunacode/core/agents/research_agent.py +231 -0
  29. tunacode/core/compaction.py +218 -0
  30. tunacode/core/prompting/__init__.py +27 -0
  31. tunacode/core/prompting/loader.py +66 -0
  32. tunacode/core/prompting/prompting_engine.py +98 -0
  33. tunacode/core/prompting/sections.py +50 -0
  34. tunacode/core/prompting/templates.py +69 -0
  35. tunacode/core/state.py +409 -0
  36. tunacode/exceptions.py +313 -0
  37. tunacode/indexing/__init__.py +5 -0
  38. tunacode/indexing/code_index.py +432 -0
  39. tunacode/indexing/constants.py +86 -0
  40. tunacode/lsp/__init__.py +112 -0
  41. tunacode/lsp/client.py +351 -0
  42. tunacode/lsp/diagnostics.py +19 -0
  43. tunacode/lsp/servers.py +101 -0
  44. tunacode/prompts/default_prompt.md +952 -0
  45. tunacode/prompts/research/sections/agent_role.xml +5 -0
  46. tunacode/prompts/research/sections/constraints.xml +14 -0
  47. tunacode/prompts/research/sections/output_format.xml +57 -0
  48. tunacode/prompts/research/sections/tool_use.xml +23 -0
  49. tunacode/prompts/sections/advanced_patterns.xml +255 -0
  50. tunacode/prompts/sections/agent_role.xml +8 -0
  51. tunacode/prompts/sections/completion.xml +10 -0
  52. tunacode/prompts/sections/critical_rules.xml +37 -0
  53. tunacode/prompts/sections/examples.xml +220 -0
  54. tunacode/prompts/sections/output_style.xml +94 -0
  55. tunacode/prompts/sections/parallel_exec.xml +105 -0
  56. tunacode/prompts/sections/search_pattern.xml +100 -0
  57. tunacode/prompts/sections/system_info.xml +6 -0
  58. tunacode/prompts/sections/tool_use.xml +84 -0
  59. tunacode/prompts/sections/user_instructions.xml +3 -0
  60. tunacode/py.typed +0 -0
  61. tunacode/templates/__init__.py +5 -0
  62. tunacode/templates/loader.py +15 -0
  63. tunacode/tools/__init__.py +10 -0
  64. tunacode/tools/authorization/__init__.py +29 -0
  65. tunacode/tools/authorization/context.py +32 -0
  66. tunacode/tools/authorization/factory.py +20 -0
  67. tunacode/tools/authorization/handler.py +58 -0
  68. tunacode/tools/authorization/notifier.py +35 -0
  69. tunacode/tools/authorization/policy.py +19 -0
  70. tunacode/tools/authorization/requests.py +119 -0
  71. tunacode/tools/authorization/rules.py +72 -0
  72. tunacode/tools/bash.py +222 -0
  73. tunacode/tools/decorators.py +213 -0
  74. tunacode/tools/glob.py +353 -0
  75. tunacode/tools/grep.py +468 -0
  76. tunacode/tools/grep_components/__init__.py +9 -0
  77. tunacode/tools/grep_components/file_filter.py +93 -0
  78. tunacode/tools/grep_components/pattern_matcher.py +158 -0
  79. tunacode/tools/grep_components/result_formatter.py +87 -0
  80. tunacode/tools/grep_components/search_result.py +34 -0
  81. tunacode/tools/list_dir.py +205 -0
  82. tunacode/tools/prompts/bash_prompt.xml +10 -0
  83. tunacode/tools/prompts/glob_prompt.xml +7 -0
  84. tunacode/tools/prompts/grep_prompt.xml +10 -0
  85. tunacode/tools/prompts/list_dir_prompt.xml +7 -0
  86. tunacode/tools/prompts/read_file_prompt.xml +9 -0
  87. tunacode/tools/prompts/todoclear_prompt.xml +12 -0
  88. tunacode/tools/prompts/todoread_prompt.xml +16 -0
  89. tunacode/tools/prompts/todowrite_prompt.xml +28 -0
  90. tunacode/tools/prompts/update_file_prompt.xml +9 -0
  91. tunacode/tools/prompts/web_fetch_prompt.xml +11 -0
  92. tunacode/tools/prompts/write_file_prompt.xml +7 -0
  93. tunacode/tools/react.py +111 -0
  94. tunacode/tools/read_file.py +68 -0
  95. tunacode/tools/todo.py +222 -0
  96. tunacode/tools/update_file.py +62 -0
  97. tunacode/tools/utils/__init__.py +1 -0
  98. tunacode/tools/utils/ripgrep.py +311 -0
  99. tunacode/tools/utils/text_match.py +352 -0
  100. tunacode/tools/web_fetch.py +245 -0
  101. tunacode/tools/write_file.py +34 -0
  102. tunacode/tools/xml_helper.py +34 -0
  103. tunacode/types/__init__.py +166 -0
  104. tunacode/types/base.py +94 -0
  105. tunacode/types/callbacks.py +53 -0
  106. tunacode/types/dataclasses.py +121 -0
  107. tunacode/types/pydantic_ai.py +31 -0
  108. tunacode/types/state.py +122 -0
  109. tunacode/ui/__init__.py +6 -0
  110. tunacode/ui/app.py +542 -0
  111. tunacode/ui/commands/__init__.py +430 -0
  112. tunacode/ui/components/__init__.py +1 -0
  113. tunacode/ui/headless/__init__.py +5 -0
  114. tunacode/ui/headless/output.py +72 -0
  115. tunacode/ui/main.py +252 -0
  116. tunacode/ui/renderers/__init__.py +41 -0
  117. tunacode/ui/renderers/errors.py +197 -0
  118. tunacode/ui/renderers/panels.py +550 -0
  119. tunacode/ui/renderers/search.py +314 -0
  120. tunacode/ui/renderers/tools/__init__.py +21 -0
  121. tunacode/ui/renderers/tools/bash.py +247 -0
  122. tunacode/ui/renderers/tools/diagnostics.py +186 -0
  123. tunacode/ui/renderers/tools/glob.py +226 -0
  124. tunacode/ui/renderers/tools/grep.py +228 -0
  125. tunacode/ui/renderers/tools/list_dir.py +198 -0
  126. tunacode/ui/renderers/tools/read_file.py +226 -0
  127. tunacode/ui/renderers/tools/research.py +294 -0
  128. tunacode/ui/renderers/tools/update_file.py +237 -0
  129. tunacode/ui/renderers/tools/web_fetch.py +182 -0
  130. tunacode/ui/repl_support.py +226 -0
  131. tunacode/ui/screens/__init__.py +16 -0
  132. tunacode/ui/screens/model_picker.py +303 -0
  133. tunacode/ui/screens/session_picker.py +181 -0
  134. tunacode/ui/screens/setup.py +218 -0
  135. tunacode/ui/screens/theme_picker.py +90 -0
  136. tunacode/ui/screens/update_confirm.py +69 -0
  137. tunacode/ui/shell_runner.py +129 -0
  138. tunacode/ui/styles/layout.tcss +98 -0
  139. tunacode/ui/styles/modals.tcss +38 -0
  140. tunacode/ui/styles/panels.tcss +81 -0
  141. tunacode/ui/styles/theme-nextstep.tcss +303 -0
  142. tunacode/ui/styles/widgets.tcss +33 -0
  143. tunacode/ui/styles.py +18 -0
  144. tunacode/ui/widgets/__init__.py +23 -0
  145. tunacode/ui/widgets/command_autocomplete.py +62 -0
  146. tunacode/ui/widgets/editor.py +402 -0
  147. tunacode/ui/widgets/file_autocomplete.py +47 -0
  148. tunacode/ui/widgets/messages.py +46 -0
  149. tunacode/ui/widgets/resource_bar.py +182 -0
  150. tunacode/ui/widgets/status_bar.py +98 -0
  151. tunacode/utils/__init__.py +0 -0
  152. tunacode/utils/config/__init__.py +13 -0
  153. tunacode/utils/config/user_configuration.py +91 -0
  154. tunacode/utils/messaging/__init__.py +10 -0
  155. tunacode/utils/messaging/message_utils.py +34 -0
  156. tunacode/utils/messaging/token_counter.py +77 -0
  157. tunacode/utils/parsing/__init__.py +13 -0
  158. tunacode/utils/parsing/command_parser.py +55 -0
  159. tunacode/utils/parsing/json_utils.py +188 -0
  160. tunacode/utils/parsing/retry.py +146 -0
  161. tunacode/utils/parsing/tool_parser.py +267 -0
  162. tunacode/utils/security/__init__.py +15 -0
  163. tunacode/utils/security/command.py +106 -0
  164. tunacode/utils/system/__init__.py +25 -0
  165. tunacode/utils/system/gitignore.py +155 -0
  166. tunacode/utils/system/paths.py +190 -0
  167. tunacode/utils/ui/__init__.py +9 -0
  168. tunacode/utils/ui/file_filter.py +135 -0
  169. tunacode/utils/ui/helpers.py +24 -0
  170. tunacode_cli-0.1.21.dist-info/METADATA +170 -0
  171. tunacode_cli-0.1.21.dist-info/RECORD +174 -0
  172. tunacode_cli-0.1.21.dist-info/WHEEL +4 -0
  173. tunacode_cli-0.1.21.dist-info/entry_points.txt +2 -0
  174. tunacode_cli-0.1.21.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,245 @@
1
+ """
2
+ Web fetch tool for TunaCode - HTTP GET requests with HTML-to-text conversion.
3
+
4
+ This tool provides web content fetching with:
5
+ - HTTP GET requests with configurable timeout
6
+ - HTML-to-text conversion for readable output
7
+ - URL security validation (blocks localhost, private IPs, file://)
8
+ - Content size limiting (5MB max)
9
+
10
+ CLAUDE_ANCHOR[web-fetch-module]: HTTP GET with HTML-to-text conversion
11
+ """
12
+
13
+ import ipaddress
14
+ import re
15
+ from urllib.parse import urlparse
16
+
17
+ import html2text
18
+ import httpx
19
+ from pydantic_ai.exceptions import ModelRetry
20
+
21
+ from tunacode.tools.decorators import base_tool
22
+
23
+ # Constants
24
+ MAX_CONTENT_SIZE = 5 * 1024 * 1024 # 5MB
25
+ MAX_OUTPUT_SIZE = 100 * 1024 # 100KB for output truncation
26
+ DEFAULT_TIMEOUT = 60 # seconds
27
+ USER_AGENT = "TunaCode/1.0 (https://tunacode.xyz)"
28
+
29
+ # Private IP ranges to block
30
+ PRIVATE_IP_PATTERNS = [
31
+ re.compile(r"^127\."), # 127.x.x.x
32
+ re.compile(r"^10\."), # 10.x.x.x
33
+ re.compile(r"^172\.(1[6-9]|2[0-9]|3[01])\."), # 172.16-31.x.x
34
+ re.compile(r"^192\.168\."), # 192.168.x.x
35
+ re.compile(r"^0\."), # 0.x.x.x
36
+ re.compile(r"^169\.254\."), # Link-local
37
+ re.compile(r"^::1$"), # IPv6 localhost
38
+ re.compile(r"^fe80:"), # IPv6 link-local
39
+ re.compile(r"^fc00:"), # IPv6 unique local
40
+ re.compile(r"^fd00:"), # IPv6 unique local
41
+ ]
42
+
43
+ # Blocked hostnames
44
+ BLOCKED_HOSTNAMES = frozenset(
45
+ [
46
+ "localhost",
47
+ "localhost.localdomain",
48
+ "local",
49
+ "0.0.0.0", # nosec B104 - this is a blocklist, not a bind address
50
+ "127.0.0.1",
51
+ "::1",
52
+ ]
53
+ )
54
+
55
+
56
+ def _is_private_ip(ip_str: str) -> bool:
57
+ """Check if an IP address is private/reserved."""
58
+ for pattern in PRIVATE_IP_PATTERNS:
59
+ if pattern.match(ip_str):
60
+ return True
61
+
62
+ try:
63
+ ip = ipaddress.ip_address(ip_str)
64
+ return ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local
65
+ except ValueError:
66
+ return False
67
+
68
+
69
+ def _validate_url(url: str) -> str:
70
+ """Validate URL for security.
71
+
72
+ Args:
73
+ url: URL to validate
74
+
75
+ Returns:
76
+ Validated URL
77
+
78
+ Raises:
79
+ ModelRetry: If URL is invalid or blocked
80
+ """
81
+ if not url or not url.strip():
82
+ raise ModelRetry("URL cannot be empty.")
83
+
84
+ url = url.strip()
85
+
86
+ try:
87
+ parsed = urlparse(url)
88
+ except Exception as err:
89
+ raise ModelRetry(f"Invalid URL format: {url}") from err
90
+
91
+ # Check scheme
92
+ if parsed.scheme not in ("http", "https"):
93
+ raise ModelRetry(
94
+ f"Invalid URL scheme '{parsed.scheme}'. Only http:// and https:// are allowed."
95
+ )
96
+
97
+ # Check hostname presence
98
+ if not parsed.hostname:
99
+ raise ModelRetry(f"URL missing hostname: {url}")
100
+
101
+ hostname = parsed.hostname.lower()
102
+
103
+ # Block known localhost hostnames
104
+ if hostname in BLOCKED_HOSTNAMES:
105
+ raise ModelRetry(f"Blocked URL: {url}. Cannot fetch from localhost or local addresses.")
106
+
107
+ # Check if hostname is an IP address and validate
108
+ if _is_private_ip(hostname):
109
+ raise ModelRetry(f"Blocked URL: {url}. Cannot fetch from private or reserved IP addresses.")
110
+
111
+ return url
112
+
113
+
114
+ def _convert_html_to_text(html_content: str) -> str:
115
+ """Convert HTML to readable plain text.
116
+
117
+ Args:
118
+ html_content: Raw HTML content
119
+
120
+ Returns:
121
+ Plain text extracted from HTML
122
+ """
123
+ converter = html2text.HTML2Text()
124
+ converter.ignore_links = False
125
+ converter.ignore_images = True
126
+ converter.ignore_emphasis = False
127
+ converter.body_width = 80
128
+ converter.unicode_snob = True
129
+ converter.skip_internal_links = True
130
+
131
+ return converter.handle(html_content)
132
+
133
+
134
+ def _truncate_output(content: str, max_size: int = MAX_OUTPUT_SIZE) -> str:
135
+ """Truncate content if it exceeds max size.
136
+
137
+ Args:
138
+ content: Content to truncate
139
+ max_size: Maximum size in bytes
140
+
141
+ Returns:
142
+ Truncated content with indicator if truncated
143
+ """
144
+ if len(content.encode("utf-8")) <= max_size:
145
+ return content
146
+
147
+ # Truncate to approximate character count
148
+ truncated = content[: max_size // 2]
149
+ return truncated + "\n\n... [Content truncated due to size] ..."
150
+
151
+
152
+ @base_tool
153
+ async def web_fetch(
154
+ url: str,
155
+ timeout: int = DEFAULT_TIMEOUT,
156
+ ) -> str:
157
+ """Fetch web content from a URL and return as readable text.
158
+
159
+ Args:
160
+ url: The URL to fetch (http:// or https://)
161
+ timeout: Request timeout in seconds (default: 60)
162
+
163
+ Returns:
164
+ Readable text content from the URL
165
+ """
166
+ # Validate URL security
167
+ validated_url = _validate_url(url)
168
+
169
+ # Clamp timeout to reasonable bounds
170
+ timeout = max(5, min(timeout, 120))
171
+
172
+ try:
173
+ async with httpx.AsyncClient(
174
+ timeout=httpx.Timeout(timeout),
175
+ follow_redirects=True,
176
+ max_redirects=5,
177
+ headers={"User-Agent": USER_AGENT},
178
+ ) as client:
179
+ # First, do a HEAD request to check content size
180
+ try:
181
+ head_response = await client.head(validated_url)
182
+ content_length = head_response.headers.get("content-length")
183
+ if content_length and int(content_length) > MAX_CONTENT_SIZE:
184
+ raise ModelRetry(
185
+ f"Content too large ({int(content_length) // 1024 // 1024}MB). "
186
+ f"Maximum allowed is {MAX_CONTENT_SIZE // 1024 // 1024}MB."
187
+ )
188
+ except httpx.HTTPError:
189
+ # HEAD failed, proceed with GET and stream check
190
+ pass
191
+
192
+ # Fetch the actual content
193
+ response = await client.get(validated_url)
194
+ response.raise_for_status()
195
+
196
+ # Check final URL after redirects for security
197
+ final_url = str(response.url)
198
+ if final_url != validated_url:
199
+ _validate_url(final_url)
200
+
201
+ # Check content size
202
+ content = response.content
203
+ if len(content) > MAX_CONTENT_SIZE:
204
+ raise ModelRetry(
205
+ f"Content too large ({len(content) // 1024 // 1024}MB). "
206
+ f"Maximum allowed is {MAX_CONTENT_SIZE // 1024 // 1024}MB."
207
+ )
208
+
209
+ # Decode content
210
+ try:
211
+ text_content = content.decode("utf-8")
212
+ except UnicodeDecodeError:
213
+ text_content = content.decode("latin-1", errors="replace")
214
+
215
+ # Convert HTML to text if content is HTML
216
+ content_type = response.headers.get("content-type", "").lower()
217
+ if "text/html" in content_type or "<html" in text_content[:1000].lower():
218
+ text_content = _convert_html_to_text(text_content)
219
+
220
+ # Truncate if too large
221
+ text_content = _truncate_output(text_content)
222
+
223
+ return text_content
224
+
225
+ except httpx.TimeoutException as err:
226
+ msg = f"Request timed out after {timeout} seconds. Try again or use a shorter timeout."
227
+ raise ModelRetry(msg) from err
228
+ except httpx.TooManyRedirects as err:
229
+ msg = f"Too many redirects while fetching {url}. The URL may be invalid."
230
+ raise ModelRetry(msg) from err
231
+ except httpx.HTTPStatusError as err:
232
+ status = err.response.status_code
233
+ if status == 404:
234
+ raise ModelRetry(f"Page not found (404): {url}. Check the URL.") from err
235
+ if status == 403:
236
+ msg = f"Access forbidden (403): {url}. The page may require authentication."
237
+ raise ModelRetry(msg) from err
238
+ if status == 429:
239
+ raise ModelRetry(f"Rate limited (429): {url}. Try again later.") from err
240
+ if status >= 500:
241
+ msg = f"Server error ({status}): {url}. The server may be down."
242
+ raise ModelRetry(msg) from err
243
+ raise ModelRetry(f"HTTP error {status} fetching {url}") from err
244
+ except httpx.RequestError as err:
245
+ raise ModelRetry(f"Failed to connect to {url}: {err}") from err
@@ -0,0 +1,34 @@
1
+ """File writing tool for agent operations."""
2
+
3
+ import os
4
+
5
+ from pydantic_ai.exceptions import ModelRetry
6
+
7
+ from tunacode.tools.decorators import file_tool
8
+
9
+
10
+ @file_tool(writes=True)
11
+ async def write_file(filepath: str, content: str) -> str:
12
+ """Write content to a new file. Fails if the file already exists.
13
+
14
+ Args:
15
+ filepath: The absolute path to the file to write.
16
+ content: The content to write to the file.
17
+
18
+ Returns:
19
+ A message indicating success.
20
+ """
21
+ if os.path.exists(filepath):
22
+ raise ModelRetry(
23
+ f"File '{filepath}' already exists. "
24
+ "Use the `update_file` tool to modify it, or choose a different filepath."
25
+ )
26
+
27
+ dirpath = os.path.dirname(filepath)
28
+ if dirpath and not os.path.exists(dirpath):
29
+ os.makedirs(dirpath, exist_ok=True)
30
+
31
+ with open(filepath, "w", encoding="utf-8") as f:
32
+ f.write(content)
33
+
34
+ return f"Successfully wrote to new file: {filepath}"
@@ -0,0 +1,34 @@
1
+ """Helper module for loading prompts and schemas from XML files."""
2
+
3
+ from functools import lru_cache
4
+ from pathlib import Path
5
+
6
+ from defusedxml.ElementTree import ParseError
7
+ from defusedxml.ElementTree import parse as xml_parse
8
+
9
+
10
+ @lru_cache(maxsize=32)
11
+ def load_prompt_from_xml(tool_name: str) -> str | None:
12
+ """Load and return the base prompt from XML file.
13
+
14
+ Args:
15
+ tool_name: Name of the tool (e.g., 'grep', 'glob')
16
+
17
+ Returns:
18
+ str: The loaded prompt from XML or None if not found
19
+ """
20
+ prompt_file = Path(__file__).parent / "prompts" / f"{tool_name}_prompt.xml"
21
+ if not prompt_file.exists():
22
+ return None
23
+
24
+ try:
25
+ tree = xml_parse(prompt_file)
26
+ except ParseError:
27
+ return None
28
+
29
+ root = tree.getroot()
30
+ description = root.find("description")
31
+ if description is None or description.text is None:
32
+ return None
33
+
34
+ return description.text.strip()
@@ -0,0 +1,166 @@
1
+ """Centralized type definitions for TunaCode CLI.
2
+
3
+ This package contains all type aliases, protocols, and type definitions
4
+ used throughout the TunaCode codebase.
5
+
6
+ All types are re-exported from this module for backward compatibility.
7
+ """
8
+
9
+ from collections.abc import Awaitable, Callable
10
+ from typing import Any
11
+
12
+ # Base types
13
+ from tunacode.types.base import (
14
+ AgentConfig,
15
+ AgentName,
16
+ CommandArgs,
17
+ CommandResult,
18
+ ConfigFile,
19
+ ConfigPath,
20
+ CostAmount,
21
+ DeviceId,
22
+ DiffHunk,
23
+ DiffLine,
24
+ EnvConfig,
25
+ ErrorContext,
26
+ ErrorMessage,
27
+ FileContent,
28
+ FileDiff,
29
+ FileEncoding,
30
+ FilePath,
31
+ FileSize,
32
+ InputSessions,
33
+ LineNumber,
34
+ ModelName,
35
+ OriginalError,
36
+ SessionId,
37
+ TokenCount,
38
+ ToolArgs,
39
+ ToolCallId,
40
+ ToolName,
41
+ ToolResult,
42
+ UpdateOperation,
43
+ UserConfig,
44
+ ValidationResult,
45
+ Validator,
46
+ )
47
+
48
+ # Callback types
49
+ from tunacode.types.callbacks import (
50
+ AsyncFunc,
51
+ AsyncToolFunc,
52
+ AsyncVoidFunc,
53
+ ToolCallback,
54
+ ToolProgress,
55
+ ToolProgressCallback,
56
+ ToolStartCallback,
57
+ UICallback,
58
+ UIInputCallback,
59
+ )
60
+
61
+ # Dataclasses
62
+ from tunacode.types.dataclasses import (
63
+ AgentState,
64
+ CommandContext,
65
+ CostBreakdown,
66
+ FallbackResponse,
67
+ ModelConfig,
68
+ ModelPricing,
69
+ ModelRegistry,
70
+ ResponseState,
71
+ TokenUsage,
72
+ ToolConfirmationRequest,
73
+ ToolConfirmationResponse,
74
+ )
75
+
76
+ # Pydantic-AI wrappers
77
+ from tunacode.types.pydantic_ai import (
78
+ AgentResponse,
79
+ AgentRun,
80
+ MessageHistory,
81
+ MessagePart,
82
+ ModelRequest,
83
+ ModelResponse,
84
+ PydanticAgent,
85
+ )
86
+
87
+ # State protocol
88
+ from tunacode.types.state import (
89
+ SessionStateProtocol,
90
+ StateManager,
91
+ StateManagerProtocol,
92
+ )
93
+
94
+ # Backward compatibility: ProcessRequestCallback needs StateManager
95
+ ProcessRequestCallback = Callable[[str, StateManager, bool], Awaitable[Any]]
96
+
97
+ __all__ = [
98
+ # Base types
99
+ "AgentConfig",
100
+ "AgentName",
101
+ "CommandArgs",
102
+ "CommandResult",
103
+ "ConfigFile",
104
+ "ConfigPath",
105
+ "CostAmount",
106
+ "DeviceId",
107
+ "DiffHunk",
108
+ "DiffLine",
109
+ "EnvConfig",
110
+ "ErrorContext",
111
+ "ErrorMessage",
112
+ "FileContent",
113
+ "FileDiff",
114
+ "FileEncoding",
115
+ "FilePath",
116
+ "FileSize",
117
+ "InputSessions",
118
+ "LineNumber",
119
+ "ModelName",
120
+ "OriginalError",
121
+ "SessionId",
122
+ "TokenCount",
123
+ "ToolArgs",
124
+ "ToolCallId",
125
+ "ToolName",
126
+ "ToolResult",
127
+ "UpdateOperation",
128
+ "UserConfig",
129
+ "ValidationResult",
130
+ "Validator",
131
+ # Pydantic-AI
132
+ "AgentResponse",
133
+ "AgentRun",
134
+ "MessageHistory",
135
+ "MessagePart",
136
+ "ModelRequest",
137
+ "ModelResponse",
138
+ "PydanticAgent",
139
+ # Callbacks
140
+ "AsyncFunc",
141
+ "AsyncToolFunc",
142
+ "AsyncVoidFunc",
143
+ "ProcessRequestCallback",
144
+ "ToolCallback",
145
+ "ToolProgress",
146
+ "ToolProgressCallback",
147
+ "ToolStartCallback",
148
+ "UICallback",
149
+ "UIInputCallback",
150
+ # State
151
+ "SessionStateProtocol",
152
+ "StateManager",
153
+ "StateManagerProtocol",
154
+ # Dataclasses
155
+ "AgentState",
156
+ "CommandContext",
157
+ "CostBreakdown",
158
+ "FallbackResponse",
159
+ "ModelConfig",
160
+ "ModelPricing",
161
+ "ModelRegistry",
162
+ "ResponseState",
163
+ "TokenUsage",
164
+ "ToolConfirmationRequest",
165
+ "ToolConfirmationResponse",
166
+ ]
tunacode/types/base.py ADDED
@@ -0,0 +1,94 @@
1
+ """Base type aliases for TunaCode CLI.
2
+
3
+ Contains fundamental type definitions that have no external dependencies
4
+ beyond Python stdlib.
5
+ """
6
+
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ # Identity types - string wrappers for semantic clarity
12
+ ModelName = str
13
+ ToolName = str
14
+ SessionId = str
15
+ DeviceId = str
16
+ AgentName = str
17
+ ToolCallId = str
18
+
19
+ # File system types
20
+ FilePath = str | Path
21
+ FileContent = str
22
+ FileEncoding = str
23
+ FileDiff = tuple[str, str]
24
+ FileSize = int
25
+ LineNumber = int
26
+ ConfigPath = Path
27
+ ConfigFile = Path
28
+
29
+ # Configuration types
30
+ UserConfig = dict[str, Any]
31
+ EnvConfig = dict[str, str]
32
+ InputSessions = dict[str, Any]
33
+ AgentConfig = dict[str, Any]
34
+
35
+ # Tool types
36
+ ToolArgs = dict[str, Any]
37
+ ToolResult = str
38
+
39
+ # Error handling types
40
+ ErrorContext = dict[str, Any]
41
+ OriginalError = Exception | None
42
+ ErrorMessage = str
43
+
44
+ # Diff types
45
+ UpdateOperation = dict[str, Any]
46
+ DiffLine = str
47
+ DiffHunk = list[DiffLine]
48
+
49
+ # Validation types
50
+ ValidationResult = bool | str
51
+ Validator = Callable[[Any], ValidationResult]
52
+
53
+ # Token/Cost types
54
+ TokenCount = int
55
+ CostAmount = float
56
+
57
+ # Command types
58
+ CommandArgs = list[str]
59
+ CommandResult = Any | None
60
+
61
+ __all__ = [
62
+ "AgentConfig",
63
+ "AgentName",
64
+ "CommandArgs",
65
+ "CommandResult",
66
+ "ConfigFile",
67
+ "ConfigPath",
68
+ "CostAmount",
69
+ "DeviceId",
70
+ "DiffHunk",
71
+ "DiffLine",
72
+ "EnvConfig",
73
+ "ErrorContext",
74
+ "ErrorMessage",
75
+ "FileContent",
76
+ "FileDiff",
77
+ "FileEncoding",
78
+ "FilePath",
79
+ "FileSize",
80
+ "InputSessions",
81
+ "LineNumber",
82
+ "ModelName",
83
+ "OriginalError",
84
+ "SessionId",
85
+ "TokenCount",
86
+ "ToolArgs",
87
+ "ToolCallId",
88
+ "ToolName",
89
+ "ToolResult",
90
+ "UpdateOperation",
91
+ "UserConfig",
92
+ "ValidationResult",
93
+ "Validator",
94
+ ]
@@ -0,0 +1,53 @@
1
+ """Callback type definitions for TunaCode CLI.
2
+
3
+ Contains callback signatures and the ToolProgress dataclass for
4
+ structured progress reporting.
5
+ """
6
+
7
+ from collections.abc import Awaitable, Callable
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class ToolProgress:
14
+ """Structured progress information for subagent tool execution.
15
+
16
+ Attributes:
17
+ subagent: Name of the subagent (e.g., "research")
18
+ operation: Description of current operation (e.g., "grep pattern...")
19
+ current: Current operation count (1-indexed)
20
+ total: Total expected operations (0 if unknown)
21
+ """
22
+
23
+ subagent: str
24
+ operation: str
25
+ current: int
26
+ total: int
27
+
28
+
29
+ # Tool callbacks
30
+ ToolCallback = Callable[[Any, Any], Awaitable[None]]
31
+ ToolStartCallback = Callable[[str], None]
32
+ ToolProgressCallback = Callable[[ToolProgress], None]
33
+
34
+ # UI callbacks
35
+ UICallback = Callable[[str], Awaitable[None]]
36
+ UIInputCallback = Callable[[str, str], Awaitable[str]]
37
+
38
+ # Async function types
39
+ AsyncFunc = Callable[..., Awaitable[Any]]
40
+ AsyncToolFunc = Callable[..., Awaitable[str]]
41
+ AsyncVoidFunc = Callable[..., Awaitable[None]]
42
+
43
+ __all__ = [
44
+ "AsyncFunc",
45
+ "AsyncToolFunc",
46
+ "AsyncVoidFunc",
47
+ "ToolCallback",
48
+ "ToolProgress",
49
+ "ToolProgressCallback",
50
+ "ToolStartCallback",
51
+ "UICallback",
52
+ "UIInputCallback",
53
+ ]