klaude-code 1.6.0__py3-none-any.whl → 1.7.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.
- klaude_code/cli/list_model.py +55 -4
- klaude_code/cli/session_cmd.py +3 -2
- klaude_code/config/assets/builtin_config.yaml +37 -2
- klaude_code/config/builtin_config.py +1 -0
- klaude_code/config/config.py +14 -0
- klaude_code/config/thinking.py +14 -0
- klaude_code/llm/anthropic/client.py +127 -114
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/registry.py +10 -5
- klaude_code/protocol/llm_param.py +9 -0
- klaude_code/session/session.py +3 -1
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.0.dist-info}/METADATA +33 -5
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.0.dist-info}/RECORD +19 -14
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.6.0.dist-info → klaude_code-1.7.0.dist-info}/entry_points.txt +0 -0
klaude_code/cli/list_model.py
CHANGED
|
@@ -6,7 +6,7 @@ from rich.table import Table
|
|
|
6
6
|
from rich.text import Text
|
|
7
7
|
|
|
8
8
|
from klaude_code.config import Config
|
|
9
|
-
from klaude_code.config.config import ModelConfig, ProviderConfig
|
|
9
|
+
from klaude_code.config.config import ModelConfig, ProviderConfig, parse_env_var_syntax
|
|
10
10
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
11
11
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
12
12
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
@@ -94,6 +94,29 @@ def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
|
94
94
|
return Text("N/A")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def format_env_var_display(value: str | None) -> Text:
|
|
98
|
+
"""Format environment variable display with warning if not set."""
|
|
99
|
+
env_var, resolved = parse_env_var_syntax(value)
|
|
100
|
+
|
|
101
|
+
if env_var:
|
|
102
|
+
# Using ${ENV_VAR} syntax
|
|
103
|
+
if resolved:
|
|
104
|
+
return Text.assemble(
|
|
105
|
+
(f"${{{env_var}}} = ", "dim"),
|
|
106
|
+
(mask_api_key(resolved), ""),
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
return Text.assemble(
|
|
110
|
+
(f"${{{env_var}}} ", ""),
|
|
111
|
+
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
112
|
+
)
|
|
113
|
+
elif value:
|
|
114
|
+
# Plain value
|
|
115
|
+
return Text(mask_api_key(value))
|
|
116
|
+
else:
|
|
117
|
+
return Text("N/A")
|
|
118
|
+
|
|
119
|
+
|
|
97
120
|
def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
98
121
|
"""Get display elements for model parameters."""
|
|
99
122
|
params: list[Text] = []
|
|
@@ -162,15 +185,43 @@ def display_models_and_providers(config: Config):
|
|
|
162
185
|
format_api_key_display(provider),
|
|
163
186
|
)
|
|
164
187
|
|
|
188
|
+
# AWS Bedrock parameters
|
|
189
|
+
if provider.protocol == LLMClientProtocol.BEDROCK:
|
|
190
|
+
if provider.aws_access_key:
|
|
191
|
+
provider_info.add_row(
|
|
192
|
+
Text("AWS Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
193
|
+
format_env_var_display(provider.aws_access_key),
|
|
194
|
+
)
|
|
195
|
+
if provider.aws_secret_key:
|
|
196
|
+
provider_info.add_row(
|
|
197
|
+
Text("AWS Secret:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
198
|
+
format_env_var_display(provider.aws_secret_key),
|
|
199
|
+
)
|
|
200
|
+
if provider.aws_region:
|
|
201
|
+
provider_info.add_row(
|
|
202
|
+
Text("AWS Region:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
203
|
+
format_env_var_display(provider.aws_region),
|
|
204
|
+
)
|
|
205
|
+
if provider.aws_session_token:
|
|
206
|
+
provider_info.add_row(
|
|
207
|
+
Text("AWS Token:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
208
|
+
format_env_var_display(provider.aws_session_token),
|
|
209
|
+
)
|
|
210
|
+
if provider.aws_profile:
|
|
211
|
+
provider_info.add_row(
|
|
212
|
+
Text("AWS Profile:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
213
|
+
format_env_var_display(provider.aws_profile),
|
|
214
|
+
)
|
|
215
|
+
|
|
165
216
|
# Check if provider has valid API key
|
|
166
217
|
provider_available = not provider.is_api_key_missing()
|
|
167
218
|
|
|
168
219
|
# Models table for this provider
|
|
169
220
|
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
170
221
|
models_table.add_column(width=2, no_wrap=True) # Status
|
|
171
|
-
models_table.add_column(overflow="fold", ratio=
|
|
172
|
-
models_table.add_column(overflow="fold", ratio=
|
|
173
|
-
models_table.add_column(overflow="fold", ratio=
|
|
222
|
+
models_table.add_column(overflow="fold", ratio=2) # Name
|
|
223
|
+
models_table.add_column(overflow="fold", ratio=3) # Model
|
|
224
|
+
models_table.add_column(overflow="fold", ratio=4) # Params
|
|
174
225
|
|
|
175
226
|
# Add header
|
|
176
227
|
models_table.add_row(
|
klaude_code/cli/session_cmd.py
CHANGED
|
@@ -22,8 +22,9 @@ def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) ->
|
|
|
22
22
|
log(f"Sessions to delete ({len(sessions)}):")
|
|
23
23
|
for s in sessions:
|
|
24
24
|
msg_count_display = "N/A" if s.messages_count == -1 else str(s.messages_count)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
first_msg_text = s.user_messages[0] if s.user_messages else ""
|
|
26
|
+
first_msg = first_msg_text.strip().replace("\n", " ")[:50]
|
|
27
|
+
if len(first_msg_text) > 50:
|
|
27
28
|
first_msg += "..."
|
|
28
29
|
log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
|
|
29
30
|
|
|
@@ -7,7 +7,7 @@ provider_list:
|
|
|
7
7
|
protocol: anthropic
|
|
8
8
|
api_key: ${ANTHROPIC_API_KEY}
|
|
9
9
|
model_list:
|
|
10
|
-
- model_name: sonnet
|
|
10
|
+
- model_name: sonnet@ant
|
|
11
11
|
model_params:
|
|
12
12
|
model: claude-sonnet-4-5-20250929
|
|
13
13
|
context_limit: 200000
|
|
@@ -18,7 +18,7 @@ provider_list:
|
|
|
18
18
|
output: 15.0
|
|
19
19
|
cache_read: 0.3
|
|
20
20
|
cache_write: 3.75
|
|
21
|
-
- model_name: opus
|
|
21
|
+
- model_name: opus@ant
|
|
22
22
|
model_params:
|
|
23
23
|
model: claude-opus-4-5-20251101
|
|
24
24
|
context_limit: 200000
|
|
@@ -194,6 +194,41 @@ provider_list:
|
|
|
194
194
|
output: 1.74
|
|
195
195
|
cache_read: 0.04
|
|
196
196
|
|
|
197
|
+
- provider_name: google
|
|
198
|
+
protocol: google
|
|
199
|
+
api_key: ${GOOGLE_API_KEY}
|
|
200
|
+
model_list:
|
|
201
|
+
- model_name: gemini-pro@google
|
|
202
|
+
model_params:
|
|
203
|
+
model: gemini-3-pro-preview
|
|
204
|
+
context_limit: 1048576
|
|
205
|
+
cost:
|
|
206
|
+
input: 2.0
|
|
207
|
+
output: 12.0
|
|
208
|
+
cache_read: 0.2
|
|
209
|
+
- model_name: gemini-flash@google
|
|
210
|
+
model_params:
|
|
211
|
+
model: gemini-3-flash-preview
|
|
212
|
+
context_limit: 1048576
|
|
213
|
+
cost:
|
|
214
|
+
input: 0.5
|
|
215
|
+
output: 3.0
|
|
216
|
+
cache_read: 0.05
|
|
217
|
+
- provider_name: bedrock
|
|
218
|
+
protocol: bedrock
|
|
219
|
+
aws_access_key: ${AWS_ACCESS_KEY_ID}
|
|
220
|
+
aws_secret_key: ${AWS_SECRET_ACCESS_KEY}
|
|
221
|
+
aws_region: ${AWS_REGION}
|
|
222
|
+
model_list:
|
|
223
|
+
- model_name: sonnet@bedrock
|
|
224
|
+
model_params:
|
|
225
|
+
model: us.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|
226
|
+
context_limit: 200000
|
|
227
|
+
cost:
|
|
228
|
+
input: 3.0
|
|
229
|
+
output: 15.0
|
|
230
|
+
cache_read: 0.3
|
|
231
|
+
cache_write: 3.75
|
|
197
232
|
- provider_name: deepseek
|
|
198
233
|
protocol: anthropic
|
|
199
234
|
api_key: ${DEEPSEEK_API_KEY}
|
klaude_code/config/config.py
CHANGED
|
@@ -77,6 +77,7 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
|
77
77
|
"""Check if the API key is missing (either not set or env var not found).
|
|
78
78
|
|
|
79
79
|
For codex protocol, checks OAuth login status instead of API key.
|
|
80
|
+
For bedrock protocol, checks AWS credentials instead of API key.
|
|
80
81
|
"""
|
|
81
82
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
82
83
|
|
|
@@ -89,6 +90,19 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
|
89
90
|
# Consider available if logged in and token not expired
|
|
90
91
|
return state is None or state.is_expired()
|
|
91
92
|
|
|
93
|
+
if self.protocol == LLMClientProtocol.BEDROCK:
|
|
94
|
+
# Bedrock uses AWS credentials, not API key. Region is always required.
|
|
95
|
+
_, resolved_profile = parse_env_var_syntax(self.aws_profile)
|
|
96
|
+
_, resolved_region = parse_env_var_syntax(self.aws_region)
|
|
97
|
+
|
|
98
|
+
# When using profile, we still need region to initialize the client.
|
|
99
|
+
if resolved_profile:
|
|
100
|
+
return resolved_region is None
|
|
101
|
+
|
|
102
|
+
_, resolved_access_key = parse_env_var_syntax(self.aws_access_key)
|
|
103
|
+
_, resolved_secret_key = parse_env_var_syntax(self.aws_secret_key)
|
|
104
|
+
return resolved_region is None or resolved_access_key is None or resolved_secret_key is None
|
|
105
|
+
|
|
92
106
|
return self.get_resolved_api_key() is None
|
|
93
107
|
|
|
94
108
|
|
klaude_code/config/thinking.py
CHANGED
|
@@ -121,6 +121,13 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
|
121
121
|
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
122
122
|
return "not set"
|
|
123
123
|
|
|
124
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
125
|
+
if thinking.type == "disabled":
|
|
126
|
+
return "off"
|
|
127
|
+
if thinking.type == "enabled":
|
|
128
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
129
|
+
return "not set"
|
|
130
|
+
|
|
124
131
|
return "unknown protocol"
|
|
125
132
|
|
|
126
133
|
|
|
@@ -230,6 +237,13 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
|
|
|
230
237
|
current_value=_get_current_budget_value(thinking),
|
|
231
238
|
)
|
|
232
239
|
|
|
240
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
241
|
+
return ThinkingPickerData(
|
|
242
|
+
options=_build_budget_options(),
|
|
243
|
+
message="Select thinking level:",
|
|
244
|
+
current_value=_get_current_budget_value(thinking),
|
|
245
|
+
)
|
|
246
|
+
|
|
233
247
|
return None
|
|
234
248
|
|
|
235
249
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from collections.abc import AsyncGenerator
|
|
4
|
-
from typing import override
|
|
4
|
+
from typing import Any, override
|
|
5
5
|
|
|
6
6
|
import anthropic
|
|
7
7
|
import httpx
|
|
@@ -58,6 +58,130 @@ def build_payload(param: llm_param.LLMCallParameter) -> MessageCreateParamsStrea
|
|
|
58
58
|
return payload
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
async def parse_anthropic_stream(
|
|
62
|
+
stream: Any,
|
|
63
|
+
param: llm_param.LLMCallParameter,
|
|
64
|
+
metadata_tracker: MetadataTracker,
|
|
65
|
+
) -> AsyncGenerator[model.ConversationItem]:
|
|
66
|
+
"""Parse Anthropic beta messages stream and yield conversation items.
|
|
67
|
+
|
|
68
|
+
This function is shared between AnthropicClient and BedrockClient.
|
|
69
|
+
"""
|
|
70
|
+
accumulated_thinking: list[str] = []
|
|
71
|
+
accumulated_content: list[str] = []
|
|
72
|
+
response_id: str | None = None
|
|
73
|
+
|
|
74
|
+
current_tool_name: str | None = None
|
|
75
|
+
current_tool_call_id: str | None = None
|
|
76
|
+
current_tool_inputs: list[str] | None = None
|
|
77
|
+
|
|
78
|
+
input_token = 0
|
|
79
|
+
cached_token = 0
|
|
80
|
+
|
|
81
|
+
async for event in await stream:
|
|
82
|
+
log_debug(
|
|
83
|
+
f"[{event.type}]",
|
|
84
|
+
event.model_dump_json(exclude_none=True),
|
|
85
|
+
style="blue",
|
|
86
|
+
debug_type=DebugType.LLM_STREAM,
|
|
87
|
+
)
|
|
88
|
+
match event:
|
|
89
|
+
case BetaRawMessageStartEvent() as event:
|
|
90
|
+
response_id = event.message.id
|
|
91
|
+
cached_token = event.message.usage.cache_read_input_tokens or 0
|
|
92
|
+
input_token = event.message.usage.input_tokens
|
|
93
|
+
yield model.StartItem(response_id=response_id)
|
|
94
|
+
case BetaRawContentBlockDeltaEvent() as event:
|
|
95
|
+
match event.delta:
|
|
96
|
+
case BetaThinkingDelta() as delta:
|
|
97
|
+
if delta.thinking:
|
|
98
|
+
metadata_tracker.record_token()
|
|
99
|
+
accumulated_thinking.append(delta.thinking)
|
|
100
|
+
yield model.ReasoningTextDelta(
|
|
101
|
+
content=delta.thinking,
|
|
102
|
+
response_id=response_id,
|
|
103
|
+
)
|
|
104
|
+
case BetaSignatureDelta() as delta:
|
|
105
|
+
yield model.ReasoningEncryptedItem(
|
|
106
|
+
encrypted_content=delta.signature,
|
|
107
|
+
response_id=response_id,
|
|
108
|
+
model=str(param.model),
|
|
109
|
+
)
|
|
110
|
+
case BetaTextDelta() as delta:
|
|
111
|
+
if delta.text:
|
|
112
|
+
metadata_tracker.record_token()
|
|
113
|
+
accumulated_content.append(delta.text)
|
|
114
|
+
yield model.AssistantMessageDelta(
|
|
115
|
+
content=delta.text,
|
|
116
|
+
response_id=response_id,
|
|
117
|
+
)
|
|
118
|
+
case BetaInputJSONDelta() as delta:
|
|
119
|
+
if current_tool_inputs is not None:
|
|
120
|
+
if delta.partial_json:
|
|
121
|
+
metadata_tracker.record_token()
|
|
122
|
+
current_tool_inputs.append(delta.partial_json)
|
|
123
|
+
case _:
|
|
124
|
+
pass
|
|
125
|
+
case BetaRawContentBlockStartEvent() as event:
|
|
126
|
+
match event.content_block:
|
|
127
|
+
case BetaToolUseBlock() as block:
|
|
128
|
+
metadata_tracker.record_token()
|
|
129
|
+
yield model.ToolCallStartItem(
|
|
130
|
+
response_id=response_id,
|
|
131
|
+
call_id=block.id,
|
|
132
|
+
name=block.name,
|
|
133
|
+
)
|
|
134
|
+
current_tool_name = block.name
|
|
135
|
+
current_tool_call_id = block.id
|
|
136
|
+
current_tool_inputs = []
|
|
137
|
+
case _:
|
|
138
|
+
pass
|
|
139
|
+
case BetaRawContentBlockStopEvent():
|
|
140
|
+
if len(accumulated_thinking) > 0:
|
|
141
|
+
metadata_tracker.record_token()
|
|
142
|
+
full_thinking = "".join(accumulated_thinking)
|
|
143
|
+
yield model.ReasoningTextItem(
|
|
144
|
+
content=full_thinking,
|
|
145
|
+
response_id=response_id,
|
|
146
|
+
model=str(param.model),
|
|
147
|
+
)
|
|
148
|
+
accumulated_thinking.clear()
|
|
149
|
+
if len(accumulated_content) > 0:
|
|
150
|
+
metadata_tracker.record_token()
|
|
151
|
+
yield model.AssistantMessageItem(
|
|
152
|
+
content="".join(accumulated_content),
|
|
153
|
+
response_id=response_id,
|
|
154
|
+
)
|
|
155
|
+
accumulated_content.clear()
|
|
156
|
+
if current_tool_name and current_tool_call_id:
|
|
157
|
+
metadata_tracker.record_token()
|
|
158
|
+
yield model.ToolCallItem(
|
|
159
|
+
name=current_tool_name,
|
|
160
|
+
call_id=current_tool_call_id,
|
|
161
|
+
arguments="".join(current_tool_inputs) if current_tool_inputs else "",
|
|
162
|
+
response_id=response_id,
|
|
163
|
+
)
|
|
164
|
+
current_tool_name = None
|
|
165
|
+
current_tool_call_id = None
|
|
166
|
+
current_tool_inputs = None
|
|
167
|
+
case BetaRawMessageDeltaEvent() as event:
|
|
168
|
+
metadata_tracker.set_usage(
|
|
169
|
+
model.Usage(
|
|
170
|
+
input_tokens=input_token + cached_token,
|
|
171
|
+
output_tokens=event.usage.output_tokens,
|
|
172
|
+
cached_tokens=cached_token,
|
|
173
|
+
context_size=input_token + cached_token + event.usage.output_tokens,
|
|
174
|
+
context_limit=param.context_limit,
|
|
175
|
+
max_tokens=param.max_tokens,
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
metadata_tracker.set_model_name(str(param.model))
|
|
179
|
+
metadata_tracker.set_response_id(response_id)
|
|
180
|
+
yield metadata_tracker.finalize()
|
|
181
|
+
case _:
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
|
|
61
185
|
@register(llm_param.LLMClientProtocol.ANTHROPIC)
|
|
62
186
|
class AnthropicClient(LLMClientABC):
|
|
63
187
|
def __init__(self, config: llm_param.LLMConfigParameter):
|
|
@@ -102,119 +226,8 @@ class AnthropicClient(LLMClientABC):
|
|
|
102
226
|
extra_headers={"extra": json.dumps({"session_id": param.session_id}, sort_keys=True)},
|
|
103
227
|
)
|
|
104
228
|
|
|
105
|
-
accumulated_thinking: list[str] = []
|
|
106
|
-
accumulated_content: list[str] = []
|
|
107
|
-
response_id: str | None = None
|
|
108
|
-
|
|
109
|
-
current_tool_name: str | None = None
|
|
110
|
-
current_tool_call_id: str | None = None
|
|
111
|
-
current_tool_inputs: list[str] | None = None
|
|
112
|
-
|
|
113
|
-
input_token = 0
|
|
114
|
-
cached_token = 0
|
|
115
|
-
|
|
116
229
|
try:
|
|
117
|
-
async for
|
|
118
|
-
|
|
119
|
-
f"[{event.type}]",
|
|
120
|
-
event.model_dump_json(exclude_none=True),
|
|
121
|
-
style="blue",
|
|
122
|
-
debug_type=DebugType.LLM_STREAM,
|
|
123
|
-
)
|
|
124
|
-
match event:
|
|
125
|
-
case BetaRawMessageStartEvent() as event:
|
|
126
|
-
response_id = event.message.id
|
|
127
|
-
cached_token = event.message.usage.cache_read_input_tokens or 0
|
|
128
|
-
input_token = event.message.usage.input_tokens
|
|
129
|
-
yield model.StartItem(response_id=response_id)
|
|
130
|
-
case BetaRawContentBlockDeltaEvent() as event:
|
|
131
|
-
match event.delta:
|
|
132
|
-
case BetaThinkingDelta() as delta:
|
|
133
|
-
if delta.thinking:
|
|
134
|
-
metadata_tracker.record_token()
|
|
135
|
-
accumulated_thinking.append(delta.thinking)
|
|
136
|
-
yield model.ReasoningTextDelta(
|
|
137
|
-
content=delta.thinking,
|
|
138
|
-
response_id=response_id,
|
|
139
|
-
)
|
|
140
|
-
case BetaSignatureDelta() as delta:
|
|
141
|
-
yield model.ReasoningEncryptedItem(
|
|
142
|
-
encrypted_content=delta.signature,
|
|
143
|
-
response_id=response_id,
|
|
144
|
-
model=str(param.model),
|
|
145
|
-
)
|
|
146
|
-
case BetaTextDelta() as delta:
|
|
147
|
-
if delta.text:
|
|
148
|
-
metadata_tracker.record_token()
|
|
149
|
-
accumulated_content.append(delta.text)
|
|
150
|
-
yield model.AssistantMessageDelta(
|
|
151
|
-
content=delta.text,
|
|
152
|
-
response_id=response_id,
|
|
153
|
-
)
|
|
154
|
-
case BetaInputJSONDelta() as delta:
|
|
155
|
-
if current_tool_inputs is not None:
|
|
156
|
-
if delta.partial_json:
|
|
157
|
-
metadata_tracker.record_token()
|
|
158
|
-
current_tool_inputs.append(delta.partial_json)
|
|
159
|
-
case _:
|
|
160
|
-
pass
|
|
161
|
-
case BetaRawContentBlockStartEvent() as event:
|
|
162
|
-
match event.content_block:
|
|
163
|
-
case BetaToolUseBlock() as block:
|
|
164
|
-
metadata_tracker.record_token()
|
|
165
|
-
yield model.ToolCallStartItem(
|
|
166
|
-
response_id=response_id,
|
|
167
|
-
call_id=block.id,
|
|
168
|
-
name=block.name,
|
|
169
|
-
)
|
|
170
|
-
current_tool_name = block.name
|
|
171
|
-
current_tool_call_id = block.id
|
|
172
|
-
current_tool_inputs = []
|
|
173
|
-
case _:
|
|
174
|
-
pass
|
|
175
|
-
case BetaRawContentBlockStopEvent() as event:
|
|
176
|
-
if len(accumulated_thinking) > 0:
|
|
177
|
-
metadata_tracker.record_token()
|
|
178
|
-
full_thinking = "".join(accumulated_thinking)
|
|
179
|
-
yield model.ReasoningTextItem(
|
|
180
|
-
content=full_thinking,
|
|
181
|
-
response_id=response_id,
|
|
182
|
-
model=str(param.model),
|
|
183
|
-
)
|
|
184
|
-
accumulated_thinking.clear()
|
|
185
|
-
if len(accumulated_content) > 0:
|
|
186
|
-
metadata_tracker.record_token()
|
|
187
|
-
yield model.AssistantMessageItem(
|
|
188
|
-
content="".join(accumulated_content),
|
|
189
|
-
response_id=response_id,
|
|
190
|
-
)
|
|
191
|
-
accumulated_content.clear()
|
|
192
|
-
if current_tool_name and current_tool_call_id:
|
|
193
|
-
metadata_tracker.record_token()
|
|
194
|
-
yield model.ToolCallItem(
|
|
195
|
-
name=current_tool_name,
|
|
196
|
-
call_id=current_tool_call_id,
|
|
197
|
-
arguments="".join(current_tool_inputs) if current_tool_inputs else "",
|
|
198
|
-
response_id=response_id,
|
|
199
|
-
)
|
|
200
|
-
current_tool_name = None
|
|
201
|
-
current_tool_call_id = None
|
|
202
|
-
current_tool_inputs = None
|
|
203
|
-
case BetaRawMessageDeltaEvent() as event:
|
|
204
|
-
metadata_tracker.set_usage(
|
|
205
|
-
model.Usage(
|
|
206
|
-
input_tokens=input_token + cached_token,
|
|
207
|
-
output_tokens=event.usage.output_tokens,
|
|
208
|
-
cached_tokens=cached_token,
|
|
209
|
-
context_size=input_token + cached_token + event.usage.output_tokens,
|
|
210
|
-
context_limit=param.context_limit,
|
|
211
|
-
max_tokens=param.max_tokens,
|
|
212
|
-
)
|
|
213
|
-
)
|
|
214
|
-
metadata_tracker.set_model_name(str(param.model))
|
|
215
|
-
metadata_tracker.set_response_id(response_id)
|
|
216
|
-
yield metadata_tracker.finalize()
|
|
217
|
-
case _:
|
|
218
|
-
pass
|
|
230
|
+
async for item in parse_anthropic_stream(stream, param, metadata_tracker):
|
|
231
|
+
yield item
|
|
219
232
|
except (APIError, httpx.HTTPError) as e:
|
|
220
233
|
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""AWS Bedrock LLM client using Anthropic SDK."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from typing import override
|
|
6
|
+
|
|
7
|
+
import anthropic
|
|
8
|
+
import httpx
|
|
9
|
+
from anthropic import APIError
|
|
10
|
+
|
|
11
|
+
from klaude_code.llm.anthropic.client import build_payload, parse_anthropic_stream
|
|
12
|
+
from klaude_code.llm.client import LLMClientABC
|
|
13
|
+
from klaude_code.llm.input_common import apply_config_defaults
|
|
14
|
+
from klaude_code.llm.registry import register
|
|
15
|
+
from klaude_code.llm.usage import MetadataTracker
|
|
16
|
+
from klaude_code.protocol import llm_param, model
|
|
17
|
+
from klaude_code.trace import DebugType, log_debug
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@register(llm_param.LLMClientProtocol.BEDROCK)
|
|
21
|
+
class BedrockClient(LLMClientABC):
|
|
22
|
+
"""LLM client for AWS Bedrock using Anthropic SDK."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: llm_param.LLMConfigParameter):
|
|
25
|
+
super().__init__(config)
|
|
26
|
+
self.client = anthropic.AsyncAnthropicBedrock(
|
|
27
|
+
aws_access_key=config.aws_access_key,
|
|
28
|
+
aws_secret_key=config.aws_secret_key,
|
|
29
|
+
aws_region=config.aws_region,
|
|
30
|
+
aws_session_token=config.aws_session_token,
|
|
31
|
+
aws_profile=config.aws_profile,
|
|
32
|
+
timeout=httpx.Timeout(300.0, connect=15.0, read=285.0),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
@override
|
|
37
|
+
def create(cls, config: llm_param.LLMConfigParameter) -> "LLMClientABC":
|
|
38
|
+
return cls(config)
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[model.ConversationItem]:
|
|
42
|
+
param = apply_config_defaults(param, self.get_llm_config())
|
|
43
|
+
|
|
44
|
+
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
45
|
+
|
|
46
|
+
payload = build_payload(param)
|
|
47
|
+
|
|
48
|
+
log_debug(
|
|
49
|
+
json.dumps(payload, ensure_ascii=False, default=str),
|
|
50
|
+
style="yellow",
|
|
51
|
+
debug_type=DebugType.LLM_PAYLOAD,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
stream = self.client.beta.messages.create(**payload)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
async for item in parse_anthropic_stream(stream, param, metadata_tracker):
|
|
58
|
+
yield item
|
|
59
|
+
except (APIError, httpx.HTTPError) as e:
|
|
60
|
+
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# pyright: reportUnknownMemberType=false
|
|
2
|
+
# pyright: reportUnknownVariableType=false
|
|
3
|
+
# pyright: reportUnknownArgumentType=false
|
|
4
|
+
# pyright: reportAttributeAccessIssue=false
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
8
|
+
from typing import Any, cast, override
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from google.genai import Client
|
|
13
|
+
from google.genai.errors import APIError, ClientError, ServerError
|
|
14
|
+
from google.genai.types import (
|
|
15
|
+
FunctionCallingConfig,
|
|
16
|
+
FunctionCallingConfigMode,
|
|
17
|
+
GenerateContentConfig,
|
|
18
|
+
HttpOptions,
|
|
19
|
+
ThinkingConfig,
|
|
20
|
+
ToolConfig,
|
|
21
|
+
UsageMetadata,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from klaude_code.llm.client import LLMClientABC
|
|
25
|
+
from klaude_code.llm.google.input import convert_history_to_contents, convert_tool_schema
|
|
26
|
+
from klaude_code.llm.input_common import apply_config_defaults
|
|
27
|
+
from klaude_code.llm.registry import register
|
|
28
|
+
from klaude_code.llm.usage import MetadataTracker
|
|
29
|
+
from klaude_code.protocol import llm_param, model
|
|
30
|
+
from klaude_code.trace import DebugType, log_debug
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_config(param: llm_param.LLMCallParameter) -> GenerateContentConfig:
|
|
34
|
+
tool_list = convert_tool_schema(param.tools)
|
|
35
|
+
tool_config: ToolConfig | None = None
|
|
36
|
+
|
|
37
|
+
if tool_list:
|
|
38
|
+
tool_config = ToolConfig(
|
|
39
|
+
function_calling_config=FunctionCallingConfig(
|
|
40
|
+
mode=FunctionCallingConfigMode.AUTO,
|
|
41
|
+
# Gemini streams tool args; keep this enabled to maximize fidelity.
|
|
42
|
+
stream_function_call_arguments=True,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
thinking_config: ThinkingConfig | None = None
|
|
47
|
+
if param.thinking and param.thinking.type == "enabled":
|
|
48
|
+
thinking_config = ThinkingConfig(
|
|
49
|
+
include_thoughts=True,
|
|
50
|
+
thinking_budget=param.thinking.budget_tokens,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return GenerateContentConfig(
|
|
54
|
+
system_instruction=param.system,
|
|
55
|
+
temperature=param.temperature,
|
|
56
|
+
max_output_tokens=param.max_tokens,
|
|
57
|
+
tools=tool_list or None,
|
|
58
|
+
tool_config=tool_config,
|
|
59
|
+
thinking_config=thinking_config,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _usage_from_metadata(
|
|
64
|
+
usage: UsageMetadata | None,
|
|
65
|
+
*,
|
|
66
|
+
context_limit: int | None,
|
|
67
|
+
max_tokens: int | None,
|
|
68
|
+
) -> model.Usage | None:
|
|
69
|
+
if usage is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
cached = usage.cached_content_token_count or 0
|
|
73
|
+
prompt = usage.prompt_token_count or 0
|
|
74
|
+
response = usage.response_token_count or 0
|
|
75
|
+
thoughts = usage.thoughts_token_count or 0
|
|
76
|
+
|
|
77
|
+
total = usage.total_token_count
|
|
78
|
+
if total is None:
|
|
79
|
+
total = prompt + cached + response + thoughts
|
|
80
|
+
|
|
81
|
+
return model.Usage(
|
|
82
|
+
input_tokens=prompt + cached,
|
|
83
|
+
cached_tokens=cached,
|
|
84
|
+
output_tokens=response + thoughts,
|
|
85
|
+
reasoning_tokens=thoughts,
|
|
86
|
+
context_size=total,
|
|
87
|
+
context_limit=context_limit,
|
|
88
|
+
max_tokens=max_tokens,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _partial_arg_value(partial: Any) -> Any:
|
|
93
|
+
if getattr(partial, "string_value", None) is not None:
|
|
94
|
+
return partial.string_value
|
|
95
|
+
if getattr(partial, "number_value", None) is not None:
|
|
96
|
+
return partial.number_value
|
|
97
|
+
if getattr(partial, "bool_value", None) is not None:
|
|
98
|
+
return partial.bool_value
|
|
99
|
+
if getattr(partial, "null_value", None) is not None:
|
|
100
|
+
return None
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _merge_partial_args(dst: dict[str, Any], partial_args: list[Any] | None) -> None:
|
|
105
|
+
if not partial_args:
|
|
106
|
+
return
|
|
107
|
+
for partial in partial_args:
|
|
108
|
+
json_path = getattr(partial, "json_path", None)
|
|
109
|
+
if not isinstance(json_path, str) or not json_path.startswith("$."):
|
|
110
|
+
continue
|
|
111
|
+
key = json_path[2:]
|
|
112
|
+
if not key or any(ch in key for ch in "[]"):
|
|
113
|
+
continue
|
|
114
|
+
dst[key] = _partial_arg_value(partial)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def parse_google_stream(
|
|
118
|
+
stream: AsyncIterator[Any],
|
|
119
|
+
param: llm_param.LLMCallParameter,
|
|
120
|
+
metadata_tracker: MetadataTracker,
|
|
121
|
+
) -> AsyncGenerator[model.ConversationItem]:
|
|
122
|
+
response_id: str | None = None
|
|
123
|
+
started = False
|
|
124
|
+
|
|
125
|
+
accumulated_text: list[str] = []
|
|
126
|
+
accumulated_thoughts: list[str] = []
|
|
127
|
+
thought_signature: str | None = None
|
|
128
|
+
|
|
129
|
+
# Track tool calls where args arrive as partial updates.
|
|
130
|
+
partial_args_by_call: dict[str, dict[str, Any]] = {}
|
|
131
|
+
started_tool_calls: dict[str, str] = {} # call_id -> name
|
|
132
|
+
started_tool_items: set[str] = set()
|
|
133
|
+
emitted_tool_items: set[str] = set()
|
|
134
|
+
|
|
135
|
+
last_usage_metadata: UsageMetadata | None = None
|
|
136
|
+
|
|
137
|
+
async for chunk in stream:
|
|
138
|
+
log_debug(
|
|
139
|
+
chunk.model_dump_json(exclude_none=True),
|
|
140
|
+
style="blue",
|
|
141
|
+
debug_type=DebugType.LLM_STREAM,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if response_id is None:
|
|
145
|
+
response_id = getattr(chunk, "response_id", None) or uuid4().hex
|
|
146
|
+
assert response_id is not None
|
|
147
|
+
if not started:
|
|
148
|
+
started = True
|
|
149
|
+
yield model.StartItem(response_id=response_id)
|
|
150
|
+
|
|
151
|
+
if getattr(chunk, "usage_metadata", None) is not None:
|
|
152
|
+
last_usage_metadata = chunk.usage_metadata
|
|
153
|
+
|
|
154
|
+
candidates = getattr(chunk, "candidates", None) or []
|
|
155
|
+
candidate0 = candidates[0] if candidates else None
|
|
156
|
+
content = getattr(candidate0, "content", None) if candidate0 else None
|
|
157
|
+
parts = getattr(content, "parts", None) if content else None
|
|
158
|
+
if not parts:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
for part in parts:
|
|
162
|
+
if getattr(part, "text", None) is not None:
|
|
163
|
+
metadata_tracker.record_token()
|
|
164
|
+
text = part.text
|
|
165
|
+
if getattr(part, "thought", False) is True:
|
|
166
|
+
accumulated_thoughts.append(text)
|
|
167
|
+
if getattr(part, "thought_signature", None):
|
|
168
|
+
thought_signature = part.thought_signature
|
|
169
|
+
yield model.ReasoningTextDelta(content=text, response_id=response_id)
|
|
170
|
+
else:
|
|
171
|
+
accumulated_text.append(text)
|
|
172
|
+
yield model.AssistantMessageDelta(content=text, response_id=response_id)
|
|
173
|
+
|
|
174
|
+
function_call = getattr(part, "function_call", None)
|
|
175
|
+
if function_call is None:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
metadata_tracker.record_token()
|
|
179
|
+
call_id = getattr(function_call, "id", None) or uuid4().hex
|
|
180
|
+
name = getattr(function_call, "name", None) or ""
|
|
181
|
+
started_tool_calls.setdefault(call_id, name)
|
|
182
|
+
|
|
183
|
+
if call_id not in started_tool_items:
|
|
184
|
+
started_tool_items.add(call_id)
|
|
185
|
+
yield model.ToolCallStartItem(response_id=response_id, call_id=call_id, name=name)
|
|
186
|
+
|
|
187
|
+
args_obj = getattr(function_call, "args", None)
|
|
188
|
+
if args_obj is not None:
|
|
189
|
+
emitted_tool_items.add(call_id)
|
|
190
|
+
yield model.ToolCallItem(
|
|
191
|
+
response_id=response_id,
|
|
192
|
+
call_id=call_id,
|
|
193
|
+
name=name,
|
|
194
|
+
arguments=json.dumps(args_obj, ensure_ascii=False),
|
|
195
|
+
)
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
partial_args = getattr(function_call, "partial_args", None)
|
|
199
|
+
if partial_args is not None:
|
|
200
|
+
acc = partial_args_by_call.setdefault(call_id, {})
|
|
201
|
+
_merge_partial_args(acc, partial_args)
|
|
202
|
+
|
|
203
|
+
will_continue = getattr(function_call, "will_continue", None)
|
|
204
|
+
if will_continue is False and call_id in partial_args_by_call and call_id not in emitted_tool_items:
|
|
205
|
+
emitted_tool_items.add(call_id)
|
|
206
|
+
yield model.ToolCallItem(
|
|
207
|
+
response_id=response_id,
|
|
208
|
+
call_id=call_id,
|
|
209
|
+
name=name,
|
|
210
|
+
arguments=json.dumps(partial_args_by_call[call_id], ensure_ascii=False),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Flush any pending tool calls that never produced args.
|
|
214
|
+
for call_id, name in started_tool_calls.items():
|
|
215
|
+
if call_id in emitted_tool_items:
|
|
216
|
+
continue
|
|
217
|
+
args = partial_args_by_call.get(call_id, {})
|
|
218
|
+
emitted_tool_items.add(call_id)
|
|
219
|
+
yield model.ToolCallItem(
|
|
220
|
+
response_id=response_id,
|
|
221
|
+
call_id=call_id,
|
|
222
|
+
name=name,
|
|
223
|
+
arguments=json.dumps(args, ensure_ascii=False),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if accumulated_thoughts:
|
|
227
|
+
metadata_tracker.record_token()
|
|
228
|
+
yield model.ReasoningTextItem(
|
|
229
|
+
content="".join(accumulated_thoughts),
|
|
230
|
+
response_id=response_id,
|
|
231
|
+
model=str(param.model),
|
|
232
|
+
)
|
|
233
|
+
if thought_signature:
|
|
234
|
+
yield model.ReasoningEncryptedItem(
|
|
235
|
+
encrypted_content=thought_signature,
|
|
236
|
+
response_id=response_id,
|
|
237
|
+
model=str(param.model),
|
|
238
|
+
format="google_thought_signature",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if accumulated_text:
|
|
242
|
+
metadata_tracker.record_token()
|
|
243
|
+
yield model.AssistantMessageItem(content="".join(accumulated_text), response_id=response_id)
|
|
244
|
+
|
|
245
|
+
usage = _usage_from_metadata(last_usage_metadata, context_limit=param.context_limit, max_tokens=param.max_tokens)
|
|
246
|
+
if usage is not None:
|
|
247
|
+
metadata_tracker.set_usage(usage)
|
|
248
|
+
metadata_tracker.set_model_name(str(param.model))
|
|
249
|
+
metadata_tracker.set_response_id(response_id)
|
|
250
|
+
yield metadata_tracker.finalize()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@register(llm_param.LLMClientProtocol.GOOGLE)
|
|
254
|
+
class GoogleClient(LLMClientABC):
|
|
255
|
+
def __init__(self, config: llm_param.LLMConfigParameter):
|
|
256
|
+
super().__init__(config)
|
|
257
|
+
http_options: HttpOptions | None = None
|
|
258
|
+
if config.base_url:
|
|
259
|
+
# If base_url already contains version path, don't append api_version.
|
|
260
|
+
http_options = HttpOptions(base_url=str(config.base_url), api_version="")
|
|
261
|
+
|
|
262
|
+
self.client = Client(
|
|
263
|
+
api_key=config.api_key,
|
|
264
|
+
http_options=http_options,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
@override
|
|
269
|
+
def create(cls, config: llm_param.LLMConfigParameter) -> "LLMClientABC":
|
|
270
|
+
return cls(config)
|
|
271
|
+
|
|
272
|
+
@override
|
|
273
|
+
async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[model.ConversationItem]:
|
|
274
|
+
param = apply_config_defaults(param, self.get_llm_config())
|
|
275
|
+
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
276
|
+
|
|
277
|
+
contents = convert_history_to_contents(param.input, model_name=str(param.model))
|
|
278
|
+
config = _build_config(param)
|
|
279
|
+
|
|
280
|
+
log_debug(
|
|
281
|
+
json.dumps(
|
|
282
|
+
{
|
|
283
|
+
"model": str(param.model),
|
|
284
|
+
"contents": [c.model_dump(exclude_none=True) for c in contents],
|
|
285
|
+
"config": config.model_dump(exclude_none=True),
|
|
286
|
+
},
|
|
287
|
+
ensure_ascii=False,
|
|
288
|
+
),
|
|
289
|
+
style="yellow",
|
|
290
|
+
debug_type=DebugType.LLM_PAYLOAD,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
stream = await self.client.aio.models.generate_content_stream(
|
|
295
|
+
model=str(param.model),
|
|
296
|
+
contents=cast(Any, contents),
|
|
297
|
+
config=config,
|
|
298
|
+
)
|
|
299
|
+
except (APIError, ClientError, ServerError, httpx.HTTPError) as e:
|
|
300
|
+
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
301
|
+
yield metadata_tracker.finalize()
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
async for item in parse_google_stream(stream, param=param, metadata_tracker=metadata_tracker):
|
|
306
|
+
yield item
|
|
307
|
+
except (APIError, ClientError, ServerError, httpx.HTTPError) as e:
|
|
308
|
+
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
309
|
+
yield metadata_tracker.finalize()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# pyright: reportReturnType=false
|
|
2
|
+
# pyright: reportArgumentType=false
|
|
3
|
+
# pyright: reportUnknownMemberType=false
|
|
4
|
+
# pyright: reportAttributeAccessIssue=false
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from base64 import b64decode
|
|
8
|
+
from binascii import Error as BinasciiError
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from google.genai import types
|
|
12
|
+
|
|
13
|
+
from klaude_code.llm.input_common import AssistantGroup, ToolGroup, UserGroup, merge_reminder_text, parse_message_groups
|
|
14
|
+
from klaude_code.protocol import llm_param, model
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _data_url_to_blob(url: str) -> types.Blob:
|
|
18
|
+
header_and_media = url.split(",", 1)
|
|
19
|
+
if len(header_and_media) != 2:
|
|
20
|
+
raise ValueError("Invalid data URL for image: missing comma separator")
|
|
21
|
+
header, base64_data = header_and_media
|
|
22
|
+
if not header.startswith("data:"):
|
|
23
|
+
raise ValueError("Invalid data URL for image: missing data: prefix")
|
|
24
|
+
if ";base64" not in header:
|
|
25
|
+
raise ValueError("Invalid data URL for image: missing base64 marker")
|
|
26
|
+
|
|
27
|
+
media_type = header[5:].split(";", 1)[0]
|
|
28
|
+
base64_payload = base64_data.strip()
|
|
29
|
+
if base64_payload == "":
|
|
30
|
+
raise ValueError("Inline image data is empty")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
decoded = b64decode(base64_payload, validate=True)
|
|
34
|
+
except (BinasciiError, ValueError) as exc:
|
|
35
|
+
raise ValueError("Inline image data is not valid base64") from exc
|
|
36
|
+
|
|
37
|
+
return types.Blob(data=decoded, mime_type=media_type)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _image_part_to_part(image: model.ImageURLPart) -> types.Part:
|
|
41
|
+
url = image.image_url.url
|
|
42
|
+
if url.startswith("data:"):
|
|
43
|
+
return types.Part(inline_data=_data_url_to_blob(url))
|
|
44
|
+
# Best-effort: Gemini supports file URIs, and may accept public HTTPS URLs.
|
|
45
|
+
return types.Part(file_data=types.FileData(file_uri=url))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _user_group_to_content(group: UserGroup) -> types.Content:
|
|
49
|
+
parts: list[types.Part] = []
|
|
50
|
+
for text in group.text_parts:
|
|
51
|
+
parts.append(types.Part(text=text + "\n"))
|
|
52
|
+
for image in group.images:
|
|
53
|
+
parts.append(_image_part_to_part(image))
|
|
54
|
+
if not parts:
|
|
55
|
+
parts.append(types.Part(text=""))
|
|
56
|
+
return types.Content(role="user", parts=parts)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tool_groups_to_content(groups: list[ToolGroup], model_name: str | None) -> list[types.Content]:
|
|
60
|
+
supports_multimodal_function_response = bool(model_name and "gemini-3" in model_name.lower())
|
|
61
|
+
|
|
62
|
+
response_parts: list[types.Part] = []
|
|
63
|
+
extra_image_contents: list[types.Content] = []
|
|
64
|
+
|
|
65
|
+
for group in groups:
|
|
66
|
+
merged_text = merge_reminder_text(
|
|
67
|
+
group.tool_result.output or "<system-reminder>Tool ran without output or errors</system-reminder>",
|
|
68
|
+
group.reminder_texts,
|
|
69
|
+
)
|
|
70
|
+
has_text = merged_text.strip() != ""
|
|
71
|
+
|
|
72
|
+
images = list(group.tool_result.images or []) + list(group.reminder_images)
|
|
73
|
+
image_parts: list[types.Part] = []
|
|
74
|
+
for image in images:
|
|
75
|
+
try:
|
|
76
|
+
image_parts.append(_image_part_to_part(image))
|
|
77
|
+
except ValueError:
|
|
78
|
+
# Skip invalid data URLs
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
has_images = len(image_parts) > 0
|
|
82
|
+
response_value = merged_text if has_text else "(see attached image)" if has_images else ""
|
|
83
|
+
response_payload = (
|
|
84
|
+
{"error": response_value} if group.tool_result.status == "error" else {"output": response_value}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
function_response = types.FunctionResponse(
|
|
88
|
+
id=group.tool_result.call_id,
|
|
89
|
+
name=group.tool_result.tool_name or "",
|
|
90
|
+
response=response_payload,
|
|
91
|
+
parts=image_parts if (has_images and supports_multimodal_function_response) else None,
|
|
92
|
+
)
|
|
93
|
+
response_parts.append(types.Part(function_response=function_response))
|
|
94
|
+
|
|
95
|
+
if has_images and not supports_multimodal_function_response:
|
|
96
|
+
extra_image_contents.append(
|
|
97
|
+
types.Content(role="user", parts=[types.Part(text="Tool result image:"), *image_parts])
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
contents: list[types.Content] = []
|
|
101
|
+
if response_parts:
|
|
102
|
+
contents.append(types.Content(role="user", parts=response_parts))
|
|
103
|
+
contents.extend(extra_image_contents)
|
|
104
|
+
return contents
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _assistant_group_to_content(group: AssistantGroup, model_name: str | None) -> types.Content | None:
|
|
108
|
+
parts: list[types.Part] = []
|
|
109
|
+
|
|
110
|
+
degraded_thinking_texts: list[str] = []
|
|
111
|
+
pending_thought_text: str | None = None
|
|
112
|
+
pending_thought_signature: str | None = None
|
|
113
|
+
|
|
114
|
+
for item in group.reasoning_items:
|
|
115
|
+
match item:
|
|
116
|
+
case model.ReasoningTextItem():
|
|
117
|
+
if not item.content:
|
|
118
|
+
continue
|
|
119
|
+
if model_name is not None and item.model is not None and item.model != model_name:
|
|
120
|
+
degraded_thinking_texts.append(item.content)
|
|
121
|
+
else:
|
|
122
|
+
pending_thought_text = item.content
|
|
123
|
+
case model.ReasoningEncryptedItem():
|
|
124
|
+
if not (
|
|
125
|
+
model_name is not None
|
|
126
|
+
and item.model == model_name
|
|
127
|
+
and item.encrypted_content
|
|
128
|
+
and (item.format or "").startswith("google")
|
|
129
|
+
and pending_thought_text
|
|
130
|
+
):
|
|
131
|
+
continue
|
|
132
|
+
pending_thought_signature = item.encrypted_content
|
|
133
|
+
parts.append(
|
|
134
|
+
types.Part(
|
|
135
|
+
text=pending_thought_text,
|
|
136
|
+
thought=True,
|
|
137
|
+
thought_signature=pending_thought_signature,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
pending_thought_text = None
|
|
141
|
+
pending_thought_signature = None
|
|
142
|
+
|
|
143
|
+
if pending_thought_text:
|
|
144
|
+
parts.append(
|
|
145
|
+
types.Part(
|
|
146
|
+
text=pending_thought_text,
|
|
147
|
+
thought=True,
|
|
148
|
+
thought_signature=pending_thought_signature,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if degraded_thinking_texts:
|
|
153
|
+
parts.insert(0, types.Part(text="<thinking>\n" + "\n".join(degraded_thinking_texts) + "\n</thinking>"))
|
|
154
|
+
|
|
155
|
+
if group.text_content:
|
|
156
|
+
parts.append(types.Part(text=group.text_content))
|
|
157
|
+
|
|
158
|
+
for tc in group.tool_calls:
|
|
159
|
+
args: dict[str, Any]
|
|
160
|
+
if tc.arguments:
|
|
161
|
+
try:
|
|
162
|
+
args = json.loads(tc.arguments)
|
|
163
|
+
except json.JSONDecodeError:
|
|
164
|
+
args = {"_raw": tc.arguments}
|
|
165
|
+
else:
|
|
166
|
+
args = {}
|
|
167
|
+
parts.append(types.Part(function_call=types.FunctionCall(id=tc.call_id, name=tc.name, args=args)))
|
|
168
|
+
|
|
169
|
+
if not parts:
|
|
170
|
+
return None
|
|
171
|
+
return types.Content(role="model", parts=parts)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def convert_history_to_contents(
|
|
175
|
+
history: list[model.ConversationItem],
|
|
176
|
+
model_name: str | None,
|
|
177
|
+
) -> list[types.Content]:
|
|
178
|
+
contents: list[types.Content] = []
|
|
179
|
+
pending_tool_groups: list[ToolGroup] = []
|
|
180
|
+
|
|
181
|
+
def flush_tool_groups() -> None:
|
|
182
|
+
nonlocal pending_tool_groups
|
|
183
|
+
if pending_tool_groups:
|
|
184
|
+
contents.extend(_tool_groups_to_content(pending_tool_groups, model_name=model_name))
|
|
185
|
+
pending_tool_groups = []
|
|
186
|
+
|
|
187
|
+
for group in parse_message_groups(history):
|
|
188
|
+
match group:
|
|
189
|
+
case UserGroup():
|
|
190
|
+
flush_tool_groups()
|
|
191
|
+
contents.append(_user_group_to_content(group))
|
|
192
|
+
case ToolGroup():
|
|
193
|
+
pending_tool_groups.append(group)
|
|
194
|
+
case AssistantGroup():
|
|
195
|
+
flush_tool_groups()
|
|
196
|
+
content = _assistant_group_to_content(group, model_name=model_name)
|
|
197
|
+
if content is not None:
|
|
198
|
+
contents.append(content)
|
|
199
|
+
|
|
200
|
+
flush_tool_groups()
|
|
201
|
+
return contents
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def convert_tool_schema(tools: list[llm_param.ToolSchema] | None) -> list[types.Tool]:
|
|
205
|
+
if tools is None or len(tools) == 0:
|
|
206
|
+
return []
|
|
207
|
+
declarations = [
|
|
208
|
+
types.FunctionDeclaration(
|
|
209
|
+
name=tool.name,
|
|
210
|
+
description=tool.description,
|
|
211
|
+
parameters_json_schema=tool.parameters,
|
|
212
|
+
)
|
|
213
|
+
for tool in tools
|
|
214
|
+
]
|
|
215
|
+
return [types.Tool(function_declarations=declarations)]
|
klaude_code/llm/registry.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import importlib
|
|
1
2
|
from collections.abc import Callable
|
|
2
3
|
from typing import TYPE_CHECKING, TypeVar
|
|
3
4
|
|
|
@@ -21,15 +22,19 @@ def _load_protocol(protocol: llm_param.LLMClientProtocol) -> None:
|
|
|
21
22
|
|
|
22
23
|
# Import only the needed module to trigger @register decorator
|
|
23
24
|
if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
24
|
-
|
|
25
|
+
importlib.import_module("klaude_code.llm.anthropic")
|
|
26
|
+
elif protocol == llm_param.LLMClientProtocol.BEDROCK:
|
|
27
|
+
importlib.import_module("klaude_code.llm.bedrock")
|
|
25
28
|
elif protocol == llm_param.LLMClientProtocol.CODEX:
|
|
26
|
-
|
|
29
|
+
importlib.import_module("klaude_code.llm.codex")
|
|
27
30
|
elif protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
28
|
-
|
|
31
|
+
importlib.import_module("klaude_code.llm.openai_compatible")
|
|
29
32
|
elif protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
30
|
-
|
|
33
|
+
importlib.import_module("klaude_code.llm.openrouter")
|
|
31
34
|
elif protocol == llm_param.LLMClientProtocol.RESPONSES:
|
|
32
|
-
|
|
35
|
+
importlib.import_module("klaude_code.llm.responses")
|
|
36
|
+
elif protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
37
|
+
importlib.import_module("klaude_code.llm.google")
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
def register(name: llm_param.LLMClientProtocol) -> Callable[[_T], _T]:
|
|
@@ -12,7 +12,9 @@ class LLMClientProtocol(Enum):
|
|
|
12
12
|
RESPONSES = "responses"
|
|
13
13
|
OPENROUTER = "openrouter"
|
|
14
14
|
ANTHROPIC = "anthropic"
|
|
15
|
+
BEDROCK = "bedrock"
|
|
15
16
|
CODEX = "codex"
|
|
17
|
+
GOOGLE = "google"
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class ToolSchema(BaseModel):
|
|
@@ -91,8 +93,15 @@ class LLMConfigProviderParameter(BaseModel):
|
|
|
91
93
|
protocol: LLMClientProtocol
|
|
92
94
|
base_url: str | None = None
|
|
93
95
|
api_key: str | None = None
|
|
96
|
+
# Azure OpenAI
|
|
94
97
|
is_azure: bool = False
|
|
95
98
|
azure_api_version: str | None = None
|
|
99
|
+
# AWS Bedrock configuration
|
|
100
|
+
aws_access_key: str | None = None
|
|
101
|
+
aws_secret_key: str | None = None
|
|
102
|
+
aws_region: str | None = None
|
|
103
|
+
aws_session_token: str | None = None
|
|
104
|
+
aws_profile: str | None = None
|
|
96
105
|
|
|
97
106
|
|
|
98
107
|
class LLMConfigModelParameter(BaseModel):
|
klaude_code/session/session.py
CHANGED
|
@@ -218,7 +218,9 @@ class Session(BaseModel):
|
|
|
218
218
|
forked.file_tracker = {k: v.model_copy(deep=True) for k, v in self.file_tracker.items()}
|
|
219
219
|
forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
|
|
220
220
|
|
|
221
|
-
history_to_copy =
|
|
221
|
+
history_to_copy = (
|
|
222
|
+
self.conversation_history[:until_index] if until_index is not None else self.conversation_history
|
|
223
|
+
)
|
|
222
224
|
items = [it.model_copy(deep=True) for it in history_to_copy]
|
|
223
225
|
if items:
|
|
224
226
|
forked.append_history(items)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
7
7
|
Requires-Dist: ddgs>=9.9.3
|
|
8
8
|
Requires-Dist: diff-match-patch>=20241021
|
|
9
|
+
Requires-Dist: google-genai>=1.56.0
|
|
9
10
|
Requires-Dist: markdown-it-py>=4.0.0
|
|
10
11
|
Requires-Dist: openai>=1.102.0
|
|
11
12
|
Requires-Dist: pillow>=12.0.0
|
|
@@ -34,6 +35,10 @@ Minimal code agent CLI.
|
|
|
34
35
|
- **Output truncation**: Large outputs saved to file system with snapshot links
|
|
35
36
|
- **Skills**: Built-in + user + project Agent Skills (with implicit invocation by Skill tool or explicit invocation by typing `$`)
|
|
36
37
|
- **Sessions**: Resumable with `--continue`
|
|
38
|
+
- **Cost tracking**: Automatic API cost calculation and display (USD/CNY)
|
|
39
|
+
- **Version update check**: Background PyPI version check with upgrade prompts
|
|
40
|
+
- **Terminal title**: Shows current directory and model name
|
|
41
|
+
- **Mermaid diagrams**: Interactive local HTML viewer with zoom, pan, and SVG export
|
|
37
42
|
- **Extras**: Slash commands, sub-agents, image paste, terminal notifications, auto-theming
|
|
38
43
|
|
|
39
44
|
## Installation
|
|
@@ -77,6 +82,7 @@ klaude [--model <name>] [--select-model]
|
|
|
77
82
|
- `--select-model`/`-s`: Open the interactive model selector at startup (shows all models unless `--model` is also provided).
|
|
78
83
|
- `--continue`/`-c`: Resume the most recent session.
|
|
79
84
|
- `--resume`/`-r`: Select a session to resume for this project.
|
|
85
|
+
- `--resume-by-id <id>`: Resume a session by its ID directly.
|
|
80
86
|
- `--vanilla`: Minimal mode with only basic tools (Bash, Read, Edit) and no system prompts.
|
|
81
87
|
|
|
82
88
|
**Model selection behavior:**
|
|
@@ -251,12 +257,18 @@ klaude session clean-all
|
|
|
251
257
|
|
|
252
258
|
Inside the interactive session (`klaude`), use these commands to streamline your workflow:
|
|
253
259
|
|
|
254
|
-
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
255
|
-
- `/export` - Export last assistant message to a temp Markdown file.
|
|
256
|
-
- `/init` - Bootstrap a new project structure or module.
|
|
257
260
|
- `/model` - Switch the active LLM during the session.
|
|
261
|
+
- `/thinking` - Configure model thinking/reasoning level.
|
|
258
262
|
- `/clear` - Clear the current conversation context.
|
|
259
|
-
- `/
|
|
263
|
+
- `/status` - Show session usage statistics (cost, tokens, model breakdown).
|
|
264
|
+
- `/resume` - Select and resume a previous session.
|
|
265
|
+
- `/fork-session` - Fork current session to a new session ID (supports interactive fork point selection).
|
|
266
|
+
- `/export` - Export last assistant message to a temp Markdown file.
|
|
267
|
+
- `/export-online` - Export and deploy session to surge.sh as a static webpage.
|
|
268
|
+
- `/debug [filters]` - Toggle debug mode and configure debug filters.
|
|
269
|
+
- `/init` - Bootstrap a new project structure or module.
|
|
270
|
+
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
271
|
+
- `/terminal-setup` - Configure terminal for Shift+Enter support.
|
|
260
272
|
- `/help` - List all available commands.
|
|
261
273
|
|
|
262
274
|
|
|
@@ -267,6 +279,8 @@ Inside the interactive session (`klaude`), use these commands to streamline your
|
|
|
267
279
|
| `Enter` | Submit input |
|
|
268
280
|
| `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
|
|
269
281
|
| `Ctrl+J` | Insert newline |
|
|
282
|
+
| `Ctrl+L` | Open model picker overlay |
|
|
283
|
+
| `Ctrl+T` | Open thinking level picker overlay |
|
|
270
284
|
| `Ctrl+V` | Paste image from clipboard |
|
|
271
285
|
| `Left/Right` | Move cursor (wraps across lines) |
|
|
272
286
|
| `Backspace` | Delete character or selected text |
|
|
@@ -290,4 +304,18 @@ echo "generate quicksort in python" | klaude exec --model gpt-5.1
|
|
|
290
304
|
|
|
291
305
|
# Partial/ambiguous name opens the interactive selector (filtered)
|
|
292
306
|
echo "generate quicksort in python" | klaude exec --model gpt
|
|
307
|
+
|
|
308
|
+
# Stream all events as JSON lines (for programmatic processing)
|
|
309
|
+
klaude exec "what is 2+2?" --stream-json
|
|
293
310
|
```
|
|
311
|
+
|
|
312
|
+
### Sub-Agents
|
|
313
|
+
|
|
314
|
+
The main agent can spawn specialized sub-agents for specific tasks:
|
|
315
|
+
|
|
316
|
+
| Sub-Agent | Purpose |
|
|
317
|
+
|-----------|---------|
|
|
318
|
+
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
319
|
+
| **Task** | Handle complex multi-step tasks autonomously |
|
|
320
|
+
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
321
|
+
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|
|
@@ -9,11 +9,11 @@ klaude_code/cli/__init__.py,sha256=YzlAoWAr5rx5oe6B_4zPxRFS4QaZauuy1AFwampP5fg,4
|
|
|
9
9
|
klaude_code/cli/auth_cmd.py,sha256=UWMHjn9xZp2o8OZc-x8y9MnkZgRWOkFXk05iKJYcySE,2561
|
|
10
10
|
klaude_code/cli/config_cmd.py,sha256=hlvslLNgdRHkokq1Pnam0XOdR3jqO3K0vNLqtWnPa6Q,3261
|
|
11
11
|
klaude_code/cli/debug.py,sha256=cPQ7cgATcJTyBIboleW_Q4Pa_t-tGG6x-Hj3woeeuHE,2669
|
|
12
|
-
klaude_code/cli/list_model.py,sha256=
|
|
12
|
+
klaude_code/cli/list_model.py,sha256=3SLURZXH_WgX-vGWIt52NuRm2D14-jcONtiS5GDM2xA,11248
|
|
13
13
|
klaude_code/cli/main.py,sha256=uNZl0RjeLRITbfHerma4_kq2f0hF166dFZqAHLBu580,13236
|
|
14
14
|
klaude_code/cli/runtime.py,sha256=6CtsQa8UcC9ppnNm2AvsF3yxgncyEYwpIIX0bb-3NN0,19826
|
|
15
15
|
klaude_code/cli/self_update.py,sha256=iGuj0i869Zi0M70W52-VVLxZp90ISr30fQpZkHGMK2o,8059
|
|
16
|
-
klaude_code/cli/session_cmd.py,sha256=
|
|
16
|
+
klaude_code/cli/session_cmd.py,sha256=9C30dzXCbPobminqenCjYvEuzZBS5zWXg3JpuhxT_OQ,3199
|
|
17
17
|
klaude_code/command/__init__.py,sha256=IK2jz2SFMLVIcVzD5evKk3zWv6u1CjgCgfJXzWdvDlk,3470
|
|
18
18
|
klaude_code/command/clear_cmd.py,sha256=3Ru6pFmOwru06XTLTuEGNUhKgy3COOaNe22Dk0TpGrQ,719
|
|
19
19
|
klaude_code/command/command_abc.py,sha256=wZl_azY6Dpd4OvjtkSEPI3ilXaygLIVkO7NCgNlrofQ,2536
|
|
@@ -36,11 +36,11 @@ klaude_code/command/terminal_setup_cmd.py,sha256=SivM1gX_anGY_8DCQNFZ5VblFqt4sVg
|
|
|
36
36
|
klaude_code/command/thinking_cmd.py,sha256=NPejWmx6HDxoWzAJVLEENCr3Wi6sQSbT8A8LRh1-2Nk,3059
|
|
37
37
|
klaude_code/config/__init__.py,sha256=Qe1BeMekBfO2-Zd30x33lB70hdM1QQZGrp4DbWSQ-II,353
|
|
38
38
|
klaude_code/config/assets/__init__.py,sha256=uMUfmXT3I-gYiI-HVr1DrE60mx5cY1o8V7SYuGqOmvY,32
|
|
39
|
-
klaude_code/config/assets/builtin_config.yaml,sha256=
|
|
40
|
-
klaude_code/config/builtin_config.py,sha256=
|
|
41
|
-
klaude_code/config/config.py,sha256=
|
|
39
|
+
klaude_code/config/assets/builtin_config.yaml,sha256=9kQZOEd5PmNZhhQWUnrCENTfHNTBhRIJUfZ444X3rmY,6491
|
|
40
|
+
klaude_code/config/builtin_config.py,sha256=LkHr7Ml-6ir6rObn9hUj5-wa-fgfJsc4T2_NdRa1ax0,1135
|
|
41
|
+
klaude_code/config/config.py,sha256=Nxnwcu8SvOweX6YC6ueVgEEdClLAij1G1v6yyFeiXEc,17114
|
|
42
42
|
klaude_code/config/select_model.py,sha256=PPbQ-BAJkwXPINBcCSPAlZjiXm4rEtg2y0hPnZE8Bnc,5183
|
|
43
|
-
klaude_code/config/thinking.py,sha256=
|
|
43
|
+
klaude_code/config/thinking.py,sha256=X-vywa36ggO_2z4iVhss1mAVEPAwAbcj1s68F0-B0G4,9223
|
|
44
44
|
klaude_code/const.py,sha256=Xc6UKku2sGQE05mvPNCpBbKK205vJrS9CaNAeKvu1AA,4612
|
|
45
45
|
klaude_code/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
klaude_code/core/agent.py,sha256=bWm-UFX_0-KAy5j_YHH8X8o3MJT4-40Ni2EaDP2SL5k,5819
|
|
@@ -107,11 +107,16 @@ klaude_code/core/tool/web/web_search_tool.py,sha256=9-dzzMXOdTA_QsdnwHw251R0VelO
|
|
|
107
107
|
klaude_code/core/turn.py,sha256=PvtVV5GLAvYYAsl3RJNDnIvX1Yp4Va8whr0TR8x-9PI,12706
|
|
108
108
|
klaude_code/llm/__init__.py,sha256=b4AsqnrMIs0a5qR_ti6rZcHwFzAReTwOW96EqozEoSo,287
|
|
109
109
|
klaude_code/llm/anthropic/__init__.py,sha256=PWETvaeNAAX3ue0ww1uRUIxTJG0RpWiutkn7MlwKxBs,67
|
|
110
|
-
klaude_code/llm/anthropic/client.py,sha256=
|
|
110
|
+
klaude_code/llm/anthropic/client.py,sha256=32MdRXm605OUXWrOy3MHAaFrUBih0gchETwNbjuoLmg,10248
|
|
111
111
|
klaude_code/llm/anthropic/input.py,sha256=nyDX3uFK0GVduSiLlBEgBjAl70e0pgIZSF3PbbbuW78,8585
|
|
112
|
+
klaude_code/llm/bedrock/__init__.py,sha256=UmXPBXMmigAJ7euIh59iivSeUdrYJwum0RYU7okkkPM,86
|
|
113
|
+
klaude_code/llm/bedrock/client.py,sha256=U-vWTEwrwpTKMGNPEINEOHtkCwpLv6n3fBDSF4By0mU,2135
|
|
112
114
|
klaude_code/llm/client.py,sha256=FbFnzLUAKM61goiYNdKi8-D4rBfu_ksaxjJtmJn0w_4,960
|
|
113
115
|
klaude_code/llm/codex/__init__.py,sha256=8vN2j2ezWB_UVpfqQ8ooStsBeLL5SY4SUMXOXdWiMaI,132
|
|
114
116
|
klaude_code/llm/codex/client.py,sha256=0BAOiLAdk2PxBEYuC_TGOs4_h6yfNZr1YWuf1lzkBxM,5329
|
|
117
|
+
klaude_code/llm/google/__init__.py,sha256=tQtf_mh_mC3E4S9XAsnhS2JZXGRnYUsBKF0jpXZTvM0,61
|
|
118
|
+
klaude_code/llm/google/client.py,sha256=VqEkPqypzrTyBU_u3yfnMcBlfhZISQi8tejertqICKs,11383
|
|
119
|
+
klaude_code/llm/google/input.py,sha256=AJkrqtTyP80tE80HK5hMaPsHMJK2oF5F3h25cKeELeQ,7930
|
|
115
120
|
klaude_code/llm/input_common.py,sha256=NxiYlhGRFntiLiKm5sKLCF0xGYW6DwcyvIhj6KAZoeU,8533
|
|
116
121
|
klaude_code/llm/openai_compatible/__init__.py,sha256=ACGpnki7k53mMcCl591aw99pm9jZOZk0ghr7atOfNps,81
|
|
117
122
|
klaude_code/llm/openai_compatible/client.py,sha256=sMzHxaDZ4CRDgwSr1pZPqpG6YbHJA-Zk0cFyC_r-ihA,4396
|
|
@@ -122,7 +127,7 @@ klaude_code/llm/openrouter/__init__.py,sha256=_As8lHjwj6vapQhLorZttTpukk5ZiCdhFd
|
|
|
122
127
|
klaude_code/llm/openrouter/client.py,sha256=zUkH7wkiYUJMGS_8iaVwdhzUnry7WLw4Q1IDQtncmK0,4864
|
|
123
128
|
klaude_code/llm/openrouter/input.py,sha256=aHVJCejkwzWaTM_EBbgmzKWyZfttAws2Y7QDW_NCnZk,5671
|
|
124
129
|
klaude_code/llm/openrouter/reasoning.py,sha256=d6RU6twuGfdf0mXGQoxSNRzFaSa3GJFV8Eve5GzDXfU,4472
|
|
125
|
-
klaude_code/llm/registry.py,sha256=
|
|
130
|
+
klaude_code/llm/registry.py,sha256=ezfUcPld-j_KbC-DBuFeJpqLYTOL54DvGlfJpAslEL8,2089
|
|
126
131
|
klaude_code/llm/responses/__init__.py,sha256=WsiyvnNiIytaYcaAqNiB8GI-5zcpjjeODPbMlteeFjA,67
|
|
127
132
|
klaude_code/llm/responses/client.py,sha256=XEsVehevQJ0WFbEVxIkI-su7VwIcaeq0P9eSrIRcGug,10184
|
|
128
133
|
klaude_code/llm/responses/input.py,sha256=qr61LmQJdcb_f-ofrAz06WpK_k4PEcI36XsyuZAXbKk,6805
|
|
@@ -130,7 +135,7 @@ klaude_code/llm/usage.py,sha256=ohQ6EBsWXZj6B4aJ4lDPqfhXRyd0LUAM1nXEJ_elD7A,4207
|
|
|
130
135
|
klaude_code/protocol/__init__.py,sha256=aGUgzhYqvhuT3Mk2vj7lrHGriH4h9TSbqV1RsRFAZjQ,194
|
|
131
136
|
klaude_code/protocol/commands.py,sha256=4tFt98CD_KvS9C-XEaHLN-S-QFsbDxQb_kGKnPkQlrk,958
|
|
132
137
|
klaude_code/protocol/events.py,sha256=KUMf1rLNdHQO9cZiQ9Pa1VsKkP1PTMbUkp18bu_jGy8,3935
|
|
133
|
-
klaude_code/protocol/llm_param.py,sha256=
|
|
138
|
+
klaude_code/protocol/llm_param.py,sha256=O5sn3-KJnhS_0FOxDDvAFJ2Sx7ZYdJ74ugnwu-gHZG8,4538
|
|
134
139
|
klaude_code/protocol/model.py,sha256=zz1DeSkpUWDT-OZBlypaGWA4z78TSeefA-Tj8mJMHp4,14257
|
|
135
140
|
klaude_code/protocol/op.py,sha256=--RllgP6Upacb6cqHd26RSwrvqZg4w6GhcebmV8gKJo,5763
|
|
136
141
|
klaude_code/protocol/op_handler.py,sha256=hSxEVPEkk0vRPRsOyJdEn3sa87c_m17wzFGKjaMqa4o,1929
|
|
@@ -144,7 +149,7 @@ klaude_code/session/__init__.py,sha256=4sw81uQvEd3YUOOjamKk1KqGmxeb4Ic9T1Tee5zzt
|
|
|
144
149
|
klaude_code/session/codec.py,sha256=ummbqT7t6uHHXtaS9lOkyhi1h0YpMk7SNSms8DyGAHU,2015
|
|
145
150
|
klaude_code/session/export.py,sha256=dj-IRUNtXL8uONDj9bsEXcEHKyeHY7lIcXv80yP88h4,31022
|
|
146
151
|
klaude_code/session/selector.py,sha256=FpKpGs06fM-LdV-yVUqEY-FJsFn2OtGK-0paXjsZVTg,2770
|
|
147
|
-
klaude_code/session/session.py,sha256=
|
|
152
|
+
klaude_code/session/session.py,sha256=VvGMxu5gwFasLlaT9h5x1gBFpuIfXDZJKC1qNwKU8tQ,17342
|
|
148
153
|
klaude_code/session/store.py,sha256=-e-lInCB3N1nFLlet7bipkmPk1PXmGthuMxv5z3hg5o,6953
|
|
149
154
|
klaude_code/session/templates/export_session.html,sha256=bA27AkcC7DQRoWmcMBeaR8WOx1z76hezEDf0aYH-0HQ,119780
|
|
150
155
|
klaude_code/session/templates/mermaid_viewer.html,sha256=lOkETxlctX1C1WJtS1wFw6KhNQmemxwJZFpXDSjlMOk,27842
|
|
@@ -207,7 +212,7 @@ klaude_code/ui/terminal/progress_bar.py,sha256=MDnhPbqCnN4GDgLOlxxOEVZPDwVC_XL2N
|
|
|
207
212
|
klaude_code/ui/terminal/selector.py,sha256=NblhWxUp0AW2OyepG4DHNy4yKE947Oi0OiqlafvBCEE,22144
|
|
208
213
|
klaude_code/ui/utils/__init__.py,sha256=YEsCLjbCPaPza-UXTPUMTJTrc9BmNBUP5CbFWlshyOQ,15
|
|
209
214
|
klaude_code/ui/utils/common.py,sha256=tqHqwgLtAyP805kwRFyoAL4EgMutcNb3Y-GAXJ4IeuM,2263
|
|
210
|
-
klaude_code-1.
|
|
211
|
-
klaude_code-1.
|
|
212
|
-
klaude_code-1.
|
|
213
|
-
klaude_code-1.
|
|
215
|
+
klaude_code-1.7.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
216
|
+
klaude_code-1.7.0.dist-info/entry_points.txt,sha256=kkXIXedaTOtjXPr2rVjRVVXZYlFUcBHELaqmyVlWUFA,92
|
|
217
|
+
klaude_code-1.7.0.dist-info/METADATA,sha256=AscMVASGUlpBW2pN_633mhuS6Tz9ExAdGZpC97m_6WM,10675
|
|
218
|
+
klaude_code-1.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|