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,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
|