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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. tau/__init__.py +0 -0
  2. tau/agent/__init__.py +11 -0
  3. tau/agent/prompt/__init__.py +10 -0
  4. tau/agent/prompt/builder.py +302 -0
  5. tau/agent/prompt/types.py +33 -0
  6. tau/agent/service.py +369 -0
  7. tau/agent/types.py +61 -0
  8. tau/auth/manager.py +247 -0
  9. tau/auth/storage.py +82 -0
  10. tau/auth/types.py +41 -0
  11. tau/builtins/__init__.py +4 -0
  12. tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
  13. tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
  14. tau/builtins/commands/__init__.py +41 -0
  15. tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  16. tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
  17. tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
  18. tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
  19. tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
  20. tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
  21. tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
  22. tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
  23. tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
  24. tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
  25. tau/builtins/commands/clear.py +16 -0
  26. tau/builtins/commands/compact.py +28 -0
  27. tau/builtins/commands/reload.py +27 -0
  28. tau/builtins/commands/session.py +19 -0
  29. tau/builtins/extensions/footer/__init__.py +76 -0
  30. tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
  31. tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
  32. tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
  33. tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
  34. tau/builtins/extensions/footer/git.py +26 -0
  35. tau/builtins/extensions/footer/model.py +69 -0
  36. tau/builtins/extensions/footer/utils.py +44 -0
  37. tau/builtins/extensions/header/__init__.py +18 -0
  38. tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
  39. tau/builtins/models/__init__.py +0 -0
  40. tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
  41. tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
  42. tau/builtins/models/audio.py +43 -0
  43. tau/builtins/models/image.py +43 -0
  44. tau/builtins/models/text.py +482 -0
  45. tau/builtins/models/video.py +40 -0
  46. tau/builtins/prompts/commit.md +7 -0
  47. tau/builtins/prompts/docs.md +7 -0
  48. tau/builtins/prompts/explain.md +7 -0
  49. tau/builtins/prompts/fix.md +7 -0
  50. tau/builtins/prompts/refactor.md +7 -0
  51. tau/builtins/prompts/review.md +7 -0
  52. tau/builtins/prompts/test.md +7 -0
  53. tau/builtins/providers/__init__.py +0 -0
  54. tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  55. tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
  56. tau/builtins/providers/audio.py +10 -0
  57. tau/builtins/providers/image.py +9 -0
  58. tau/builtins/providers/text.py +33 -0
  59. tau/builtins/providers/video.py +6 -0
  60. tau/builtins/skills/code-review/SKILL.md +4 -0
  61. tau/builtins/skills/debug/SKILL.md +4 -0
  62. tau/builtins/skills/git-commit/SKILL.md +4 -0
  63. tau/builtins/themes/dark.yaml +1 -0
  64. tau/builtins/themes/light.yaml +46 -0
  65. tau/builtins/tools/__init__.py +73 -0
  66. tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  67. tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  68. tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
  69. tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
  70. tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
  71. tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
  72. tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
  73. tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
  74. tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
  75. tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
  76. tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
  77. tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
  78. tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
  79. tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
  80. tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
  81. tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
  82. tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
  83. tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
  84. tau/builtins/tools/edit.py +215 -0
  85. tau/builtins/tools/glob.py +112 -0
  86. tau/builtins/tools/grep.py +146 -0
  87. tau/builtins/tools/ls.py +135 -0
  88. tau/builtins/tools/read.py +122 -0
  89. tau/builtins/tools/terminal.py +150 -0
  90. tau/builtins/tools/write.py +105 -0
  91. tau/commands/__init__.py +10 -0
  92. tau/commands/registry.py +71 -0
  93. tau/commands/types.py +33 -0
  94. tau/console/__init__.py +0 -0
  95. tau/console/cli.py +266 -0
  96. tau/console/commands/__init__.py +0 -0
  97. tau/console/commands/auth.py +193 -0
  98. tau/console/commands/packages.py +104 -0
  99. tau/console/commands/update.py +76 -0
  100. tau/core/__init__.py +0 -0
  101. tau/core/registry.py +102 -0
  102. tau/engine/__init__.py +47 -0
  103. tau/engine/service.py +768 -0
  104. tau/engine/types.py +163 -0
  105. tau/extensions/__init__.py +28 -0
  106. tau/extensions/api.py +928 -0
  107. tau/extensions/context.py +462 -0
  108. tau/extensions/events.py +70 -0
  109. tau/extensions/loader.py +386 -0
  110. tau/extensions/runtime.py +184 -0
  111. tau/extensions/settings.py +137 -0
  112. tau/hooks/__init__.py +112 -0
  113. tau/hooks/engine.py +237 -0
  114. tau/hooks/inference.py +21 -0
  115. tau/hooks/runtime.py +126 -0
  116. tau/hooks/service.py +121 -0
  117. tau/hooks/session.py +117 -0
  118. tau/hooks/tui.py +61 -0
  119. tau/hooks/types.py +72 -0
  120. tau/inference/__init__.py +180 -0
  121. tau/inference/api/__init__.py +0 -0
  122. tau/inference/api/audio/__init__.py +0 -0
  123. tau/inference/api/audio/base.py +29 -0
  124. tau/inference/api/audio/builtins.py +15 -0
  125. tau/inference/api/audio/elevenlabs_audio.py +183 -0
  126. tau/inference/api/audio/gemini_audio.py +95 -0
  127. tau/inference/api/audio/openai_audio.py +159 -0
  128. tau/inference/api/audio/registry.py +15 -0
  129. tau/inference/api/audio/sarvam_audio.py +163 -0
  130. tau/inference/api/audio/service.py +103 -0
  131. tau/inference/api/audio/utils.py +47 -0
  132. tau/inference/api/image/__init__.py +0 -0
  133. tau/inference/api/image/base.py +17 -0
  134. tau/inference/api/image/builtins.py +8 -0
  135. tau/inference/api/image/gemini_image.py +77 -0
  136. tau/inference/api/image/openai_image.py +103 -0
  137. tau/inference/api/image/openrouter.py +144 -0
  138. tau/inference/api/image/registry.py +15 -0
  139. tau/inference/api/image/service.py +71 -0
  140. tau/inference/api/registry.py +82 -0
  141. tau/inference/api/text/__init__.py +0 -0
  142. tau/inference/api/text/anthropic_claude_code.py +222 -0
  143. tau/inference/api/text/anthropic_messages.py +196 -0
  144. tau/inference/api/text/base.py +40 -0
  145. tau/inference/api/text/builtins.py +19 -0
  146. tau/inference/api/text/gemini_generate.py +234 -0
  147. tau/inference/api/text/github_copilot_chat.py +172 -0
  148. tau/inference/api/text/google_antigravity.py +522 -0
  149. tau/inference/api/text/mistral_chat.py +284 -0
  150. tau/inference/api/text/ollama_chat.py +200 -0
  151. tau/inference/api/text/openai_codex_responses.py +497 -0
  152. tau/inference/api/text/openai_completions.py +227 -0
  153. tau/inference/api/text/openai_responses.py +235 -0
  154. tau/inference/api/text/registry.py +50 -0
  155. tau/inference/api/text/service.py +297 -0
  156. tau/inference/api/text/types.py +7 -0
  157. tau/inference/api/text/utils.py +228 -0
  158. tau/inference/api/video/__init__.py +0 -0
  159. tau/inference/api/video/base.py +26 -0
  160. tau/inference/api/video/builtins.py +7 -0
  161. tau/inference/api/video/fal_video.py +119 -0
  162. tau/inference/api/video/openrouter_video.py +142 -0
  163. tau/inference/api/video/registry.py +15 -0
  164. tau/inference/api/video/service.py +72 -0
  165. tau/inference/model/__init__.py +0 -0
  166. tau/inference/model/registry.py +102 -0
  167. tau/inference/model/types.py +65 -0
  168. tau/inference/provider/__init__.py +0 -0
  169. tau/inference/provider/oauth/__init__.py +35 -0
  170. tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
  171. tau/inference/provider/oauth/github_copilot.py +333 -0
  172. tau/inference/provider/oauth/google_antigravity.py +258 -0
  173. tau/inference/provider/oauth/openai_codex.py +309 -0
  174. tau/inference/provider/oauth/pkce.py +14 -0
  175. tau/inference/provider/oauth/types.py +46 -0
  176. tau/inference/provider/oauth/utils.py +154 -0
  177. tau/inference/provider/registry.py +141 -0
  178. tau/inference/provider/types.py +114 -0
  179. tau/inference/types.py +549 -0
  180. tau/inference/utils.py +219 -0
  181. tau/message/__init__.py +0 -0
  182. tau/message/types.py +482 -0
  183. tau/message/utils.py +178 -0
  184. tau/packages/__init__.py +11 -0
  185. tau/packages/manager.py +190 -0
  186. tau/packages/types.py +20 -0
  187. tau/packages/utils.py +67 -0
  188. tau/prompts/expand.py +58 -0
  189. tau/prompts/loader.py +69 -0
  190. tau/prompts/registry.py +45 -0
  191. tau/prompts/types.py +24 -0
  192. tau/rpc/__init__.py +8 -0
  193. tau/rpc/mode.py +783 -0
  194. tau/rpc/types.py +252 -0
  195. tau/runtime/service.py +759 -0
  196. tau/runtime/types.py +303 -0
  197. tau/session/branch_summarization.py +312 -0
  198. tau/session/compaction.py +646 -0
  199. tau/session/manager.py +652 -0
  200. tau/session/types.py +188 -0
  201. tau/session/utils.py +233 -0
  202. tau/settings/manager.py +1077 -0
  203. tau/settings/paths.py +150 -0
  204. tau/settings/storage.py +63 -0
  205. tau/settings/types.py +173 -0
  206. tau/settings/utils.py +25 -0
  207. tau/skills/loader.py +91 -0
  208. tau/skills/registry.py +70 -0
  209. tau/skills/types.py +25 -0
  210. tau/themes/loader.py +238 -0
  211. tau/themes/registry.py +108 -0
  212. tau/themes/types.py +19 -0
  213. tau/tool/__init__.py +3 -0
  214. tau/tool/registry.py +117 -0
  215. tau/tool/render.py +21 -0
  216. tau/tool/types.py +244 -0
  217. tau/trust/__init__.py +13 -0
  218. tau/trust/manager.py +80 -0
  219. tau/trust/types.py +14 -0
  220. tau/trust/utils.py +72 -0
  221. tau/tui/__init__.py +54 -0
  222. tau/tui/agent_hooks.py +346 -0
  223. tau/tui/ansi.py +330 -0
  224. tau/tui/app.py +540 -0
  225. tau/tui/autocomplete.py +33 -0
  226. tau/tui/capabilities.py +119 -0
  227. tau/tui/commands/__init__.py +3 -0
  228. tau/tui/commands/appearance.py +498 -0
  229. tau/tui/commands/auth.py +232 -0
  230. tau/tui/commands/context.py +38 -0
  231. tau/tui/commands/misc.py +82 -0
  232. tau/tui/commands/model.py +118 -0
  233. tau/tui/commands/session.py +464 -0
  234. tau/tui/component.py +268 -0
  235. tau/tui/components/__init__.py +0 -0
  236. tau/tui/components/autocomplete_manager.py +267 -0
  237. tau/tui/components/autocomplete_picker.py +143 -0
  238. tau/tui/components/box.py +90 -0
  239. tau/tui/components/command_palette.py +144 -0
  240. tau/tui/components/dynamic_border.py +19 -0
  241. tau/tui/components/file_picker.py +233 -0
  242. tau/tui/components/image.py +181 -0
  243. tau/tui/components/inline_selector.py +71 -0
  244. tau/tui/components/layout.py +1194 -0
  245. tau/tui/components/message_list.py +692 -0
  246. tau/tui/components/modal.py +97 -0
  247. tau/tui/components/model_palette.py +204 -0
  248. tau/tui/components/picker_overlay.py +174 -0
  249. tau/tui/components/prompt_overlay.py +236 -0
  250. tau/tui/components/resume_modal.py +372 -0
  251. tau/tui/components/select_list.py +222 -0
  252. tau/tui/components/settings_modal.py +274 -0
  253. tau/tui/components/settings_schema.py +203 -0
  254. tau/tui/components/spinner.py +119 -0
  255. tau/tui/components/text_input.py +396 -0
  256. tau/tui/components/text_prompt.py +82 -0
  257. tau/tui/components/tree_select_list.py +580 -0
  258. tau/tui/components/trust_screen.py +97 -0
  259. tau/tui/diff.py +114 -0
  260. tau/tui/fuzzy.py +99 -0
  261. tau/tui/input.py +496 -0
  262. tau/tui/input_handler.py +716 -0
  263. tau/tui/keybindings.py +87 -0
  264. tau/tui/markdown.py +286 -0
  265. tau/tui/message_renderers.py +31 -0
  266. tau/tui/overlay.py +326 -0
  267. tau/tui/renderer.py +378 -0
  268. tau/tui/terminal.py +499 -0
  269. tau/tui/theme.py +148 -0
  270. tau/tui/tui.py +544 -0
  271. tau/tui/ui_context.py +768 -0
  272. tau/tui/utils.py +20 -0
  273. tau/utils/__init__.py +0 -0
  274. tau/utils/http_proxy.py +221 -0
  275. tau/utils/image_processing.py +172 -0
  276. tau/utils/secrets.py +59 -0
  277. tau/utils/version_check.py +60 -0
  278. tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
  279. tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
  280. tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
  281. tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  282. tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
  283. tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,222 @@
1
+ from __future__ import annotations
2
+ import json
3
+ from tau.inference.api.text.utils import parse_tool_args, anthropic_messages_to_list, anthropic_output_config, anthropic_apply_message_cache
4
+ from collections.abc import AsyncGenerator, AsyncIterator
5
+ from typing import Any
6
+ from anthropic import AsyncAnthropic
7
+ from tau.inference.api.text.base import BaseLLMAPI as BaseAPI
8
+ from tau.inference.model.types import Model
9
+ from tau.inference.types import (
10
+ LLMContext, LLMEvent, LLMOptions, StopReason, ThinkingBudgets, ThinkingLevel,
11
+ StartEvent, EndEvent, ErrorEvent,
12
+ TextStartEvent, TextDeltaEvent, TextEndEvent,
13
+ ThinkingStartEvent, ThinkingDeltaEvent, ThinkingEndEvent,
14
+ ToolCallStartEvent, ToolCallDeltaEvent, ToolCallEndEvent,
15
+ )
16
+ from tau.message.types import (
17
+ SystemMessage, UserMessage, AssistantMessage, ToolMessage,
18
+ TextContent, ImageContent, ThinkingContent, ToolCallContent, ToolResultContent,
19
+ )
20
+ from typing import Optional, TYPE_CHECKING
21
+ if TYPE_CHECKING:
22
+ from tau.tool.types import Tool
23
+
24
+ _STOP_REASON: dict[str, StopReason] = {
25
+ "end_turn": StopReason.Stop,
26
+ "max_tokens": StopReason.Length,
27
+ "tool_use": StopReason.ToolCalls,
28
+ "stop_sequence": StopReason.Stop,
29
+ }
30
+
31
+ _DEFAULT_MAX_TOKENS = 8096
32
+
33
+
34
+ _OAUTH_HEADERS = {
35
+ "anthropic-beta": "oauth-2025-04-20",
36
+ "x-app": "cli",
37
+ "User-Agent": "claude-cli/2.1.122 (external, sdk-cli)",
38
+ }
39
+
40
+
41
+ class AnthropicClaudeCodeAPI(BaseAPI):
42
+ """Anthropic Messages API using OAuth token auth (Claude Pro/Max).
43
+
44
+ Sends the token via X-Api-Key (not Authorization: Bearer) with the
45
+ required OAuth beta headers, which is what Anthropic's API enforces
46
+ for Claude Max / Pro OAuth tokens.
47
+ """
48
+
49
+ def __init__(self, options: LLMOptions) -> None:
50
+ """Initialise the AsyncAnthropic client with OAuth headers merged from options."""
51
+ super().__init__(options)
52
+ merged_headers = {**_OAUTH_HEADERS, **(options.headers or {})}
53
+ self._client = AsyncAnthropic(
54
+ auth_token=options.api_key, # Bearer auth for OAuth tokens
55
+ base_url=options.base_url,
56
+ default_headers=merged_headers,
57
+ max_retries=options.max_retries,
58
+ timeout=options.timeout.total_seconds(),
59
+ )
60
+ self._current_api_key = options.api_key
61
+
62
+ def _build_params(
63
+ self,
64
+ model: Model,
65
+ system: str | None,
66
+ messages: list[dict[str, Any]],
67
+ tools: Optional[list[Tool]] = None,
68
+ ) -> dict[str, Any]:
69
+ """Assemble the Anthropic API request payload, including thinking and tool configs."""
70
+ params: dict[str, Any] = {
71
+ "model": model.id,
72
+ "messages": anthropic_apply_message_cache(messages),
73
+ "max_tokens": self.options.max_tokens or _DEFAULT_MAX_TOKENS,
74
+ "temperature": self.options.temperature,
75
+ }
76
+ if system:
77
+ params["system"] = [{"type": "text", "text": system, "cache_control": {"type": "ephemeral"}}]
78
+ if self.options.thinking_level is not None and self.options.thinking_level != ThinkingLevel.Off:
79
+ budgets = self.options.thinking_budgets or ThinkingBudgets()
80
+ params["thinking"] = {"type": "enabled", "budget_tokens": budgets.get(self.options.thinking_level)}
81
+
82
+ if tools:
83
+ tool_defs = [
84
+ {
85
+ "name": tool.name,
86
+ "description": tool.description,
87
+ "input_schema": tool.schema.model_json_schema(),
88
+ }
89
+ for tool in tools
90
+ ]
91
+ # Cache the last tool definition to reduce repeated prompt-token charges.
92
+ tool_defs[-1]["cache_control"] = {"type": "ephemeral"}
93
+ params["tools"] = tool_defs
94
+ return params
95
+
96
+ def _sync_client(self) -> None:
97
+ """Rebuild client if the api_key (OAuth token) has been refreshed."""
98
+ if self.options.api_key != self._current_api_key:
99
+ self._current_api_key = self.options.api_key
100
+ merged_headers = {**_OAUTH_HEADERS, **(self.options.headers or {})}
101
+ self._client = AsyncAnthropic(
102
+ auth_token=self.options.api_key,
103
+ base_url=self.options.base_url,
104
+ default_headers=merged_headers,
105
+ max_retries=self.options.max_retries,
106
+ timeout=self.options.timeout.total_seconds(),
107
+ )
108
+
109
+ async def stream(self, context: LLMContext, model: Model) -> AsyncGenerator[LLMEvent, None]: # type: ignore[override]
110
+ """Stream LLMEvents from the Anthropic Messages API using an OAuth token."""
111
+ self._sync_client()
112
+ system, anthropic_messages = anthropic_messages_to_list(context.messages)
113
+ if context.system_prompt:
114
+ system = context.system_prompt
115
+ params = self._build_params(model, system, anthropic_messages, tools=context.tools or None)
116
+ output_config = anthropic_output_config(context.response_format)
117
+ if output_config is not None:
118
+ params["output_config"] = output_config
119
+
120
+ if self.options.on_payload:
121
+ modified = self.options.on_payload(params)
122
+ if modified is not None:
123
+ params = modified
124
+
125
+ # Per-block accumulation buffers keyed by content block index.
126
+ block_types: dict[int, str] = {}
127
+ tool_ids: dict[int, str] = {}
128
+ tool_names: dict[int, str] = {}
129
+ text_bufs: dict[int, str] = {}
130
+ thinking_bufs: dict[int, str] = {}
131
+ tool_bufs: dict[int, str] = {}
132
+ _input_tokens = 0
133
+ _output_tokens = 0
134
+ _cache_read_tokens = 0
135
+ _cache_write_tokens = 0
136
+
137
+ yield StartEvent()
138
+
139
+ async with self._client.messages.stream(**params) as stream:
140
+ async for event in stream:
141
+ if self._cancelled():
142
+ yield ErrorEvent(reason=StopReason.Abort, error="Cancelled")
143
+ return
144
+ etype = event.type
145
+
146
+ if etype == "content_block_start":
147
+ idx = getattr(event, "index", 0)
148
+ block = getattr(event, "content_block", None)
149
+ if block is None:
150
+ continue
151
+ btype_start = getattr(block, "type", "")
152
+ block_types[idx] = btype_start
153
+ if btype_start == "text":
154
+ text_bufs[idx] = ""
155
+ yield TextStartEvent(text=TextContent(content=""))
156
+ elif btype_start == "thinking":
157
+ thinking_bufs[idx] = ""
158
+ yield ThinkingStartEvent(thinking=None)
159
+ elif btype_start == "tool_use":
160
+ tool_ids[idx] = getattr(block, "id", "")
161
+ tool_names[idx] = getattr(block, "name", "")
162
+ tool_bufs[idx] = ""
163
+ yield ToolCallStartEvent(tool_call=ToolCallContent(id=tool_ids[idx], name=tool_names[idx]))
164
+
165
+ elif etype == "content_block_delta":
166
+ idx = getattr(event, "index", 0)
167
+ delta = getattr(event, "delta", None)
168
+ if delta is None:
169
+ continue
170
+ dtype = getattr(delta, "type", "")
171
+ if dtype == "text_delta":
172
+ text = getattr(delta, "text", "")
173
+ text_bufs[idx] = text_bufs.get(idx, "") + text
174
+ yield TextDeltaEvent(text=TextContent(content=text))
175
+ elif dtype == "thinking_delta":
176
+ thinking = getattr(delta, "thinking", "")
177
+ thinking_bufs[idx] = thinking_bufs.get(idx, "") + thinking
178
+ yield ThinkingDeltaEvent(thinking=ThinkingContent(content=thinking))
179
+ elif dtype == "input_json_delta":
180
+ partial = getattr(delta, "partial_json", "")
181
+ tool_bufs[idx] = tool_bufs.get(idx, "") + partial
182
+ yield ToolCallDeltaEvent(tool_call=ToolCallContent(id=tool_ids.get(idx, "")))
183
+
184
+ elif etype == "content_block_stop":
185
+ idx = getattr(event, "index", 0)
186
+ btype = block_types.get(idx, "")
187
+ if btype == "text":
188
+ yield TextEndEvent(text=TextContent(content=text_bufs.get(idx, "")))
189
+ elif btype == "thinking":
190
+ yield ThinkingEndEvent(thinking=ThinkingContent(content=thinking_bufs.get(idx, "")))
191
+ elif btype == "tool_use":
192
+ args_str = tool_bufs.get(idx, "").strip()
193
+ args = parse_tool_args(args_str)
194
+
195
+ yield ToolCallEndEvent(tool_call=ToolCallContent(
196
+ id=tool_ids.get(idx, ""),
197
+ name=tool_names.get(idx, ""),
198
+ args=args
199
+ ))
200
+
201
+ elif etype == "message_start":
202
+ u = getattr(getattr(event, 'message', None), 'usage', None)
203
+ if u:
204
+ _input_tokens = getattr(u, 'input_tokens', 0) or 0
205
+ _cache_read_tokens = getattr(u, 'cache_read_input_tokens', 0) or 0
206
+ _cache_write_tokens = getattr(u, 'cache_creation_input_tokens', 0) or 0
207
+
208
+ elif etype == "message_delta":
209
+ u = getattr(event, 'usage', None)
210
+ if u:
211
+ _output_tokens = getattr(u, 'output_tokens', 0) or 0
212
+ stop_reason = _STOP_REASON.get(getattr(getattr(event, 'delta', None), 'stop_reason', None) or "", StopReason.Stop)
213
+ yield EndEvent(
214
+ reason=stop_reason,
215
+ input_tokens=_input_tokens,
216
+ output_tokens=_output_tokens,
217
+ cache_read_tokens=_cache_read_tokens,
218
+ cache_write_tokens=_cache_write_tokens,
219
+ )
220
+
221
+ elif etype == "error":
222
+ yield ErrorEvent(reason=StopReason.Abort, error=str(event))
@@ -0,0 +1,196 @@
1
+ from __future__ import annotations
2
+ import json
3
+ from tau.inference.api.text.utils import parse_tool_args, anthropic_messages_to_list, anthropic_output_config, anthropic_apply_message_cache
4
+ from collections.abc import AsyncGenerator, AsyncIterator
5
+ from typing import Any
6
+ from anthropic import AsyncAnthropic
7
+ from tau.inference.api.text.base import BaseLLMAPI as BaseAPI
8
+ from tau.inference.model.types import Model
9
+ from tau.inference.types import (
10
+ LLMContext, LLMEvent, LLMOptions, StopReason, ThinkingBudgets, ThinkingLevel,
11
+ StartEvent, EndEvent, ErrorEvent,
12
+ TextStartEvent, TextDeltaEvent, TextEndEvent,
13
+ ThinkingStartEvent, ThinkingDeltaEvent, ThinkingEndEvent,
14
+ ToolCallStartEvent, ToolCallDeltaEvent, ToolCallEndEvent,
15
+ )
16
+ from tau.message.types import (
17
+ SystemMessage, UserMessage, AssistantMessage, ToolMessage,
18
+ TextContent, ImageContent, ThinkingContent, ToolCallContent, ToolResultContent,
19
+ )
20
+ from typing import Optional, TYPE_CHECKING
21
+ if TYPE_CHECKING:
22
+ from tau.tool.types import Tool
23
+
24
+ _STOP_REASON: dict[str, StopReason] = {
25
+ "end_turn": StopReason.Stop,
26
+ "max_tokens": StopReason.Length,
27
+ "tool_use": StopReason.ToolCalls,
28
+ "stop_sequence": StopReason.Stop,
29
+ }
30
+
31
+ _DEFAULT_MAX_TOKENS = 8096
32
+
33
+
34
+ class AnthropicMessagesAPI(BaseAPI):
35
+ """Streaming LLM API adapter for Anthropic Messages API (API-key auth)."""
36
+
37
+ def __init__(self, options: LLMOptions) -> None:
38
+ """Initialise the AsyncAnthropic client with the supplied options."""
39
+ super().__init__(options)
40
+ self._client = AsyncAnthropic(
41
+ api_key=options.api_key,
42
+ base_url=options.base_url,
43
+ default_headers=options.headers,
44
+ max_retries=options.max_retries,
45
+ timeout=options.timeout.total_seconds(),
46
+ )
47
+
48
+ def _build_params(
49
+ self,
50
+ model: Model,
51
+ system: str | None,
52
+ messages: list[dict[str, Any]],
53
+ tools: Optional[list[Tool]] = None,
54
+ ) -> dict[str, Any]:
55
+ """Assemble the Anthropic API request payload, including thinking and tool configs."""
56
+ params: dict[str, Any] = {
57
+ "model": model.id,
58
+ "messages": anthropic_apply_message_cache(messages),
59
+ "max_tokens": self.options.max_tokens or _DEFAULT_MAX_TOKENS,
60
+ "temperature": self.options.temperature,
61
+ }
62
+ if system:
63
+ params["system"] = [{"type": "text", "text": system, "cache_control": {"type": "ephemeral"}}]
64
+ if self.options.thinking_level is not None and self.options.thinking_level != ThinkingLevel.Off:
65
+ budgets = self.options.thinking_budgets or ThinkingBudgets()
66
+ params["thinking"] = {"type": "enabled", "budget_tokens": budgets.get(self.options.thinking_level)}
67
+
68
+ if tools:
69
+ tool_defs = [
70
+ {
71
+ "name": tool.name,
72
+ "description": tool.description,
73
+ "input_schema": tool.schema.model_json_schema(),
74
+ }
75
+ for tool in tools
76
+ ]
77
+ # Cache the last tool definition to reduce repeated prompt-token charges.
78
+ tool_defs[-1]["cache_control"] = {"type": "ephemeral"}
79
+ params["tools"] = tool_defs
80
+
81
+ return params
82
+
83
+ async def stream(self, context: LLMContext, model: Model) -> AsyncGenerator[LLMEvent, None]: # type: ignore[override]
84
+ """Stream LLMEvents from the Anthropic Messages API."""
85
+ system, anthropic_messages = anthropic_messages_to_list(context.messages)
86
+ if context.system_prompt:
87
+ system = context.system_prompt
88
+ params = self._build_params(model, system, anthropic_messages, tools=context.tools or None)
89
+ output_config = anthropic_output_config(context.response_format)
90
+ if output_config is not None:
91
+ params["output_config"] = output_config
92
+
93
+ if self.options.on_payload:
94
+ modified = self.options.on_payload(params)
95
+ if modified is not None:
96
+ params = modified
97
+
98
+ # Per-block accumulation buffers keyed by content block index.
99
+ block_types: dict[int, str] = {}
100
+ tool_ids: dict[int, str] = {}
101
+ tool_names: dict[int, str] = {}
102
+ text_bufs: dict[int, str] = {}
103
+ thinking_bufs: dict[int, str] = {}
104
+ tool_bufs: dict[int, str] = {}
105
+ _input_tokens = 0
106
+ _output_tokens = 0
107
+ _cache_read_tokens = 0
108
+ _cache_write_tokens = 0
109
+
110
+ yield StartEvent()
111
+
112
+ async with self._client.messages.stream(**params) as stream:
113
+ async for event in stream:
114
+ if self._cancelled():
115
+ yield ErrorEvent(reason=StopReason.Abort, error="Cancelled")
116
+ return
117
+ etype = event.type
118
+
119
+ if etype == "content_block_start":
120
+ idx = getattr(event, "index", 0)
121
+ block = getattr(event, "content_block", None)
122
+ if block is None:
123
+ continue
124
+ btype_start = getattr(block, "type", "")
125
+ block_types[idx] = btype_start
126
+ if btype_start == "text":
127
+ text_bufs[idx] = ""
128
+ yield TextStartEvent(text=TextContent(content=""))
129
+ elif btype_start == "thinking":
130
+ thinking_bufs[idx] = ""
131
+ yield ThinkingStartEvent(thinking=None)
132
+ elif btype_start == "tool_use":
133
+ tool_ids[idx] = getattr(block, "id", "")
134
+ tool_names[idx] = getattr(block, "name", "")
135
+ tool_bufs[idx] = ""
136
+ yield ToolCallStartEvent(tool_call=ToolCallContent(id=tool_ids[idx], name=tool_names[idx]))
137
+
138
+ elif etype == "content_block_delta":
139
+ idx = getattr(event, "index", 0)
140
+ delta = getattr(event, "delta", None)
141
+ if delta is None:
142
+ continue
143
+ dtype = getattr(delta, "type", "")
144
+ if dtype == "text_delta":
145
+ text = getattr(delta, "text", "")
146
+ text_bufs[idx] = text_bufs.get(idx, "") + text
147
+ yield TextDeltaEvent(text=TextContent(content=text))
148
+ elif dtype == "thinking_delta":
149
+ thinking = getattr(delta, "thinking", "")
150
+ thinking_bufs[idx] = thinking_bufs.get(idx, "") + thinking
151
+ yield ThinkingDeltaEvent(thinking=ThinkingContent(content=thinking))
152
+ elif dtype == "input_json_delta":
153
+ partial = getattr(delta, "partial_json", "")
154
+ tool_bufs[idx] = tool_bufs.get(idx, "") + partial
155
+ yield ToolCallDeltaEvent(tool_call=ToolCallContent(id=tool_ids.get(idx, "")))
156
+
157
+ elif etype == "content_block_stop":
158
+ idx = getattr(event, "index", 0)
159
+ btype = block_types.get(idx, "")
160
+ if btype == "text":
161
+ yield TextEndEvent(text=TextContent(content=text_bufs.get(idx, "")))
162
+ elif btype == "thinking":
163
+ yield ThinkingEndEvent(thinking=ThinkingContent(content=thinking_bufs.get(idx, "")))
164
+ elif btype == "tool_use":
165
+ args_str = tool_bufs.get(idx, "").strip()
166
+ args = parse_tool_args(args_str)
167
+
168
+ yield ToolCallEndEvent(tool_call=ToolCallContent(
169
+ id=tool_ids.get(idx, ""),
170
+ name=tool_names.get(idx, ""),
171
+ args=args
172
+ )
173
+ )
174
+
175
+ elif etype == "message_start":
176
+ u = getattr(getattr(event, 'message', None), 'usage', None)
177
+ if u:
178
+ _input_tokens = getattr(u, 'input_tokens', 0) or 0
179
+ _cache_read_tokens = getattr(u, 'cache_read_input_tokens', 0) or 0
180
+ _cache_write_tokens = getattr(u, 'cache_creation_input_tokens', 0) or 0
181
+
182
+ elif etype == "message_delta":
183
+ u = getattr(event, 'usage', None)
184
+ if u:
185
+ _output_tokens = getattr(u, 'output_tokens', 0) or 0
186
+ stop_reason = _STOP_REASON.get(getattr(getattr(event, 'delta', None), 'stop_reason', None) or "", StopReason.Stop)
187
+ yield EndEvent(
188
+ reason=stop_reason,
189
+ input_tokens=_input_tokens,
190
+ output_tokens=_output_tokens,
191
+ cache_read_tokens=_cache_read_tokens,
192
+ cache_write_tokens=_cache_write_tokens,
193
+ )
194
+
195
+ elif etype == "error":
196
+ yield ErrorEvent(reason=StopReason.Abort, error=str(event))
@@ -0,0 +1,40 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import AsyncGenerator, AsyncIterator
3
+ from tau.inference.model.types import Model
4
+ from tau.inference.types import LLMContext, LLMEvent, LLMOptions, Transport
5
+ from tau.message.types import LLMMessage
6
+
7
+
8
+ class BaseLLMAPI(ABC):
9
+ """Abstract base for all provider-specific streaming LLM API implementations."""
10
+
11
+ SUPPORTED_TRANSPORTS: tuple[Transport, ...] = (Transport.HTTP,)
12
+
13
+ def __init__(self, options: LLMOptions) -> None:
14
+ """Validate transport compatibility and store options."""
15
+ if options.transport not in self.SUPPORTED_TRANSPORTS:
16
+ raise ValueError(
17
+ f"{self.__class__.__name__} does not support transport '{options.transport.value}'. "
18
+ f"Supported: {[t.value for t in self.SUPPORTED_TRANSPORTS]}"
19
+ )
20
+ self.options = options
21
+
22
+ def _cancelled(self) -> bool:
23
+ """Return True if the caller has set the abort signal."""
24
+ return self.options.signal is not None and self.options.signal.is_set()
25
+
26
+ @abstractmethod
27
+ async def stream(self, context: LLMContext, model: Model) -> AsyncGenerator[LLMEvent, None]:
28
+ """Yield LLMEvent objects as the provider streams its response."""
29
+ # The unreachable `yield` marks this as an async-generator signature so
30
+ # callers (and type-checkers) treat stream() as returning an
31
+ # AsyncGenerator — which supports aclose() — rather than a coroutine.
32
+ raise NotImplementedError
33
+ yield # type: ignore[unreachable] # pragma: no cover
34
+
35
+ async def invoke(self, context: LLMContext, model: Model) -> list[LLMEvent]:
36
+ """Collect all stream events into a list and return them."""
37
+ events: list[LLMEvent] = []
38
+ async for event in self.stream(context, model=model):
39
+ events.append(event)
40
+ return events
@@ -0,0 +1,19 @@
1
+ # Lazy registry entries: each provider API is referenced by a "module:ClassName"
2
+ # path so its SDK (anthropic, openai, google.genai, mistralai, ...) is only
3
+ # imported when that provider is actually used. Loading every SDK at startup
4
+ # previously cost ~1.5s on cold start.
5
+ LLM_APIS: list[tuple[str, str]] = [
6
+ ("openai_responses", "tau.inference.api.text.openai_responses:OpenAIResponsesAPI"),
7
+ ("openai_completions", "tau.inference.api.text.openai_completions:OpenAICompletionsAPI"),
8
+ ("openai_codex_responses", "tau.inference.api.text.openai_codex_responses:OpenAICodexResponsesAPI"),
9
+ ("anthropic_messages", "tau.inference.api.text.anthropic_messages:AnthropicMessagesAPI"),
10
+ ("anthropic_claude_code", "tau.inference.api.text.anthropic_claude_code:AnthropicClaudeCodeAPI"),
11
+ ("github_copilot_chat", "tau.inference.api.text.github_copilot_chat:GitHubCopilotChatAPI"),
12
+ ("gemini_generate", "tau.inference.api.text.gemini_generate:GeminiGenerateAPI"),
13
+ ("mistral_chat", "tau.inference.api.text.mistral_chat:MistralChatAPI"),
14
+ ("ollama_chat", "tau.inference.api.text.ollama_chat:OllamaChatAPI"),
15
+ ("google_antigravity", "tau.inference.api.text.google_antigravity:GoogleAntigravityAPI"),
16
+ ]
17
+
18
+ # Backward-compat alias
19
+ APIS = LLM_APIS