klaude-code 1.8.0__py3-none-any.whl → 1.9.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/auth/base.py +101 -0
- klaude_code/auth/claude/__init__.py +6 -0
- klaude_code/auth/claude/exceptions.py +9 -0
- klaude_code/auth/claude/oauth.py +172 -0
- klaude_code/auth/claude/token_manager.py +26 -0
- klaude_code/auth/codex/token_manager.py +10 -50
- klaude_code/cli/auth_cmd.py +127 -46
- klaude_code/cli/config_cmd.py +4 -2
- klaude_code/cli/cost_cmd.py +14 -9
- klaude_code/cli/list_model.py +248 -200
- klaude_code/command/prompt-commit.md +73 -0
- klaude_code/config/assets/builtin_config.yaml +36 -3
- klaude_code/config/config.py +24 -5
- klaude_code/config/thinking.py +4 -4
- klaude_code/core/prompt.py +1 -1
- klaude_code/llm/anthropic/client.py +28 -3
- klaude_code/llm/claude/__init__.py +3 -0
- klaude_code/llm/claude/client.py +95 -0
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/registry.py +3 -1
- klaude_code/protocol/llm_param.py +2 -1
- klaude_code/protocol/sub_agent/__init__.py +1 -2
- klaude_code/session/session.py +4 -4
- klaude_code/ui/renderers/metadata.py +6 -26
- klaude_code/ui/rich/theme.py +6 -5
- klaude_code/ui/utils/common.py +46 -0
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/METADATA +25 -5
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/RECORD +30 -25
- klaude_code/command/prompt-jj-describe.md +0 -32
- klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
- klaude_code/protocol/sub_agent/oracle.py +0 -91
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/entry_points.txt +0 -0
klaude_code/config/config.py
CHANGED
|
@@ -81,14 +81,23 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
|
81
81
|
"""
|
|
82
82
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
83
83
|
|
|
84
|
-
if self.protocol == LLMClientProtocol.
|
|
84
|
+
if self.protocol == LLMClientProtocol.CODEX_OAUTH:
|
|
85
85
|
# Codex uses OAuth authentication, not API key
|
|
86
86
|
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
87
87
|
|
|
88
88
|
token_manager = CodexTokenManager()
|
|
89
89
|
state = token_manager.get_state()
|
|
90
|
-
# Consider available if logged in
|
|
91
|
-
return state is None
|
|
90
|
+
# Consider available if logged in. Token refresh happens on-demand.
|
|
91
|
+
return state is None
|
|
92
|
+
|
|
93
|
+
if self.protocol == LLMClientProtocol.CLAUDE_OAUTH:
|
|
94
|
+
# Claude uses OAuth authentication, not API key
|
|
95
|
+
from klaude_code.auth.claude.token_manager import ClaudeTokenManager
|
|
96
|
+
|
|
97
|
+
token_manager = ClaudeTokenManager()
|
|
98
|
+
state = token_manager.get_state()
|
|
99
|
+
# Consider available if logged in. Token refresh happens on-demand.
|
|
100
|
+
return state is None
|
|
92
101
|
|
|
93
102
|
if self.protocol == LLMClientProtocol.BEDROCK:
|
|
94
103
|
# Bedrock uses AWS credentials, not API key. Region is always required.
|
|
@@ -182,7 +191,17 @@ class Config(BaseModel):
|
|
|
182
191
|
for provider in self.provider_list:
|
|
183
192
|
# Resolve ${ENV_VAR} syntax for api_key
|
|
184
193
|
api_key = provider.get_resolved_api_key()
|
|
185
|
-
|
|
194
|
+
|
|
195
|
+
# Some protocols do not use API keys for authentication.
|
|
196
|
+
if (
|
|
197
|
+
provider.protocol
|
|
198
|
+
not in {
|
|
199
|
+
llm_param.LLMClientProtocol.CODEX_OAUTH,
|
|
200
|
+
llm_param.LLMClientProtocol.CLAUDE_OAUTH,
|
|
201
|
+
llm_param.LLMClientProtocol.BEDROCK,
|
|
202
|
+
}
|
|
203
|
+
and not api_key
|
|
204
|
+
):
|
|
186
205
|
continue
|
|
187
206
|
for model in provider.model_list:
|
|
188
207
|
if model.model_name == model_name:
|
|
@@ -243,7 +262,7 @@ def get_example_config() -> UserConfig:
|
|
|
243
262
|
"""Generate example config for user reference (will be commented out)."""
|
|
244
263
|
return UserConfig(
|
|
245
264
|
main_model="opus",
|
|
246
|
-
sub_agent_models={"explore": "haiku", "
|
|
265
|
+
sub_agent_models={"explore": "haiku", "webagent": "sonnet", "task": "sonnet"},
|
|
247
266
|
provider_list=[
|
|
248
267
|
UserProviderConfig(
|
|
249
268
|
provider_name="my-provider",
|
klaude_code/config/thinking.py
CHANGED
|
@@ -91,12 +91,12 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
|
91
91
|
|
|
92
92
|
protocol = config.protocol
|
|
93
93
|
|
|
94
|
-
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.
|
|
94
|
+
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX_OAUTH):
|
|
95
95
|
if thinking.reasoning_effort:
|
|
96
96
|
return f"reasoning_effort={thinking.reasoning_effort}"
|
|
97
97
|
return "not set"
|
|
98
98
|
|
|
99
|
-
if protocol
|
|
99
|
+
if protocol in (llm_param.LLMClientProtocol.ANTHROPIC, llm_param.LLMClientProtocol.CLAUDE_OAUTH):
|
|
100
100
|
if thinking.type == "disabled":
|
|
101
101
|
return "off"
|
|
102
102
|
if thinking.type == "enabled":
|
|
@@ -201,7 +201,7 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
|
|
|
201
201
|
model_name = config.model
|
|
202
202
|
thinking = config.thinking
|
|
203
203
|
|
|
204
|
-
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.
|
|
204
|
+
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX_OAUTH):
|
|
205
205
|
levels = get_levels_for_responses(model_name)
|
|
206
206
|
return ThinkingPickerData(
|
|
207
207
|
options=_build_effort_options(levels),
|
|
@@ -209,7 +209,7 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
|
|
|
209
209
|
current_value=_get_current_effort_value(thinking),
|
|
210
210
|
)
|
|
211
211
|
|
|
212
|
-
if protocol
|
|
212
|
+
if protocol in (llm_param.LLMClientProtocol.ANTHROPIC, llm_param.LLMClientProtocol.CLAUDE_OAUTH):
|
|
213
213
|
return ThinkingPickerData(
|
|
214
214
|
options=_build_budget_options(),
|
|
215
215
|
message="Select thinking level:",
|
klaude_code/core/prompt.py
CHANGED
|
@@ -101,7 +101,7 @@ def load_system_prompt(
|
|
|
101
101
|
file_key = _get_file_key(model_name, protocol)
|
|
102
102
|
base_prompt = _load_base_prompt(file_key)
|
|
103
103
|
|
|
104
|
-
if protocol == llm_param.LLMClientProtocol.
|
|
104
|
+
if protocol == llm_param.LLMClientProtocol.CODEX_OAUTH:
|
|
105
105
|
# Do not append environment info for Codex protocol
|
|
106
106
|
return base_prompt
|
|
107
107
|
|
|
@@ -6,6 +6,7 @@ from typing import Any, override
|
|
|
6
6
|
import anthropic
|
|
7
7
|
import httpx
|
|
8
8
|
from anthropic import APIError
|
|
9
|
+
from anthropic.types.beta import BetaTextBlockParam
|
|
9
10
|
from anthropic.types.beta.beta_input_json_delta import BetaInputJSONDelta
|
|
10
11
|
from anthropic.types.beta.beta_raw_content_block_delta_event import BetaRawContentBlockDeltaEvent
|
|
11
12
|
from anthropic.types.beta.beta_raw_content_block_start_event import BetaRawContentBlockStartEvent
|
|
@@ -27,13 +28,37 @@ from klaude_code.llm.usage import MetadataTracker
|
|
|
27
28
|
from klaude_code.protocol import llm_param, model
|
|
28
29
|
from klaude_code.trace import DebugType, log_debug
|
|
29
30
|
|
|
31
|
+
_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude."
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
def build_payload(
|
|
35
|
+
param: llm_param.LLMCallParameter,
|
|
36
|
+
*,
|
|
37
|
+
extra_betas: list[str] | None = None,
|
|
38
|
+
) -> MessageCreateParamsStreaming:
|
|
39
|
+
"""Build Anthropic API request parameters.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
param: LLM call parameters.
|
|
43
|
+
extra_betas: Additional beta flags to prepend to the betas list.
|
|
44
|
+
"""
|
|
33
45
|
messages = convert_history_to_input(param.input, param.model)
|
|
34
46
|
tools = convert_tool_schema(param.tools)
|
|
35
47
|
system = convert_system_to_input(param.system)
|
|
36
48
|
|
|
49
|
+
# Add identity block at the beginning of the system prompt
|
|
50
|
+
identity_block: BetaTextBlockParam = {
|
|
51
|
+
"type": "text",
|
|
52
|
+
"text": _IDENTITY,
|
|
53
|
+
"cache_control": {"type": "ephemeral"},
|
|
54
|
+
}
|
|
55
|
+
system = [identity_block, *system]
|
|
56
|
+
|
|
57
|
+
betas = ["interleaved-thinking-2025-05-14"]
|
|
58
|
+
if extra_betas:
|
|
59
|
+
# Prepend extra betas, avoiding duplicates
|
|
60
|
+
betas = [b for b in extra_betas if b not in betas] + betas
|
|
61
|
+
|
|
37
62
|
payload: MessageCreateParamsStreaming = {
|
|
38
63
|
"model": str(param.model),
|
|
39
64
|
"tool_choice": {
|
|
@@ -46,7 +71,7 @@ def build_payload(param: llm_param.LLMCallParameter) -> MessageCreateParamsStrea
|
|
|
46
71
|
"messages": messages,
|
|
47
72
|
"system": system,
|
|
48
73
|
"tools": tools,
|
|
49
|
-
"betas":
|
|
74
|
+
"betas": betas,
|
|
50
75
|
}
|
|
51
76
|
|
|
52
77
|
if param.thinking and param.thinking.type == "enabled":
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
import anthropic
|
|
6
|
+
import httpx
|
|
7
|
+
from anthropic import APIError
|
|
8
|
+
|
|
9
|
+
from klaude_code.auth.claude.exceptions import ClaudeNotLoggedInError
|
|
10
|
+
from klaude_code.auth.claude.oauth import ClaudeOAuth
|
|
11
|
+
from klaude_code.auth.claude.token_manager import ClaudeTokenManager
|
|
12
|
+
from klaude_code.llm.anthropic.client import build_payload, parse_anthropic_stream
|
|
13
|
+
from klaude_code.llm.client import LLMClientABC
|
|
14
|
+
from klaude_code.llm.input_common import apply_config_defaults
|
|
15
|
+
from klaude_code.llm.registry import register
|
|
16
|
+
from klaude_code.llm.usage import MetadataTracker
|
|
17
|
+
from klaude_code.protocol import llm_param, model
|
|
18
|
+
from klaude_code.trace import DebugType, log_debug
|
|
19
|
+
|
|
20
|
+
_CLAUDE_OAUTH_REQUIRED_BETAS: tuple[str, ...] = (
|
|
21
|
+
"oauth-2025-04-20",
|
|
22
|
+
"fine-grained-tool-streaming-2025-05-14",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@register(llm_param.LLMClientProtocol.CLAUDE_OAUTH)
|
|
27
|
+
class ClaudeClient(LLMClientABC):
|
|
28
|
+
"""Claude OAuth client using Anthropic messages API with Bearer auth token."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config: llm_param.LLMConfigParameter):
|
|
31
|
+
super().__init__(config)
|
|
32
|
+
|
|
33
|
+
if config.base_url:
|
|
34
|
+
raise ValueError("CLAUDE protocol does not support custom base_url")
|
|
35
|
+
|
|
36
|
+
self._token_manager = ClaudeTokenManager()
|
|
37
|
+
self._oauth = ClaudeOAuth(self._token_manager)
|
|
38
|
+
|
|
39
|
+
if not self._token_manager.is_logged_in():
|
|
40
|
+
raise ClaudeNotLoggedInError("Claude authentication required. Run 'klaude login claude' first.")
|
|
41
|
+
|
|
42
|
+
self.client = self._create_client()
|
|
43
|
+
|
|
44
|
+
def _create_client(self) -> anthropic.AsyncAnthropic:
|
|
45
|
+
token = self._oauth.ensure_valid_token()
|
|
46
|
+
return anthropic.AsyncAnthropic(
|
|
47
|
+
auth_token=token,
|
|
48
|
+
timeout=httpx.Timeout(300.0, connect=15.0, read=285.0),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def _ensure_valid_token(self) -> None:
|
|
52
|
+
state = self._token_manager.get_state()
|
|
53
|
+
if state is None:
|
|
54
|
+
raise ClaudeNotLoggedInError("Not logged in to Claude. Run 'klaude login claude' first.")
|
|
55
|
+
|
|
56
|
+
if state.is_expired():
|
|
57
|
+
self._oauth.refresh()
|
|
58
|
+
self.client = self._create_client()
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
@override
|
|
62
|
+
def create(cls, config: llm_param.LLMConfigParameter) -> "LLMClientABC":
|
|
63
|
+
return cls(config)
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[model.ConversationItem]:
|
|
67
|
+
self._ensure_valid_token()
|
|
68
|
+
param = apply_config_defaults(param, self.get_llm_config())
|
|
69
|
+
|
|
70
|
+
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
71
|
+
|
|
72
|
+
# Anthropic OAuth requires the oauth beta flag
|
|
73
|
+
extra_betas = list(_CLAUDE_OAUTH_REQUIRED_BETAS)
|
|
74
|
+
payload = build_payload(param, extra_betas=extra_betas)
|
|
75
|
+
|
|
76
|
+
# Keep the interleaved-thinking beta in sync with configured thinking.
|
|
77
|
+
if not (param.thinking and param.thinking.type == "enabled"):
|
|
78
|
+
payload["betas"] = [b for b in payload.get("betas", []) if b != "interleaved-thinking-2025-05-14"]
|
|
79
|
+
|
|
80
|
+
log_debug(
|
|
81
|
+
json.dumps(payload, ensure_ascii=False, default=str),
|
|
82
|
+
style="yellow",
|
|
83
|
+
debug_type=DebugType.LLM_PAYLOAD,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
stream = self.client.beta.messages.create(
|
|
87
|
+
**payload,
|
|
88
|
+
extra_headers={"extra": json.dumps({"session_id": param.session_id}, sort_keys=True)},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
async for item in parse_anthropic_stream(stream, param, metadata_tracker):
|
|
93
|
+
yield item
|
|
94
|
+
except (APIError, httpx.HTTPError) as e:
|
|
95
|
+
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
klaude_code/llm/codex/client.py
CHANGED
klaude_code/llm/registry.py
CHANGED
|
@@ -23,9 +23,11 @@ def _load_protocol(protocol: llm_param.LLMClientProtocol) -> None:
|
|
|
23
23
|
# Import only the needed module to trigger @register decorator
|
|
24
24
|
if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
25
25
|
importlib.import_module("klaude_code.llm.anthropic")
|
|
26
|
+
elif protocol == llm_param.LLMClientProtocol.CLAUDE_OAUTH:
|
|
27
|
+
importlib.import_module("klaude_code.llm.claude")
|
|
26
28
|
elif protocol == llm_param.LLMClientProtocol.BEDROCK:
|
|
27
29
|
importlib.import_module("klaude_code.llm.bedrock")
|
|
28
|
-
elif protocol == llm_param.LLMClientProtocol.
|
|
30
|
+
elif protocol == llm_param.LLMClientProtocol.CODEX_OAUTH:
|
|
29
31
|
importlib.import_module("klaude_code.llm.codex")
|
|
30
32
|
elif protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
31
33
|
importlib.import_module("klaude_code.llm.openai_compatible")
|
|
@@ -37,7 +37,7 @@ class SubAgentProfile:
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
# Identity - single name used for type, tool_name, config_key, and prompt_key
|
|
40
|
-
name: str # e.g., "Task", "
|
|
40
|
+
name: str # e.g., "Task", "Explore", "WebAgent"
|
|
41
41
|
|
|
42
42
|
# Tool schema
|
|
43
43
|
description: str # Tool description shown to the main agent
|
|
@@ -112,6 +112,5 @@ def sub_agent_tool_names(enabled_only: bool = False, model_name: str | None = No
|
|
|
112
112
|
|
|
113
113
|
# Import sub-agent modules to trigger registration
|
|
114
114
|
from klaude_code.protocol.sub_agent import explore as explore # noqa: E402
|
|
115
|
-
from klaude_code.protocol.sub_agent import oracle as oracle # noqa: E402
|
|
116
115
|
from klaude_code.protocol.sub_agent import task as task # noqa: E402
|
|
117
116
|
from klaude_code.protocol.sub_agent import web as web # noqa: E402
|
klaude_code/session/session.py
CHANGED
|
@@ -193,14 +193,14 @@ class Session(BaseModel):
|
|
|
193
193
|
self.conversation_history.extend(items)
|
|
194
194
|
self._invalidate_messages_count_cache()
|
|
195
195
|
|
|
196
|
-
new_user_messages = [
|
|
197
|
-
it.content for it in items if isinstance(it, model.UserMessageItem) and it.content
|
|
198
|
-
]
|
|
196
|
+
new_user_messages = [it.content for it in items if isinstance(it, model.UserMessageItem) and it.content]
|
|
199
197
|
if new_user_messages:
|
|
200
198
|
if self._user_messages_cache is None:
|
|
201
199
|
# Build from full history once to ensure correctness when resuming older sessions.
|
|
202
200
|
self._user_messages_cache = [
|
|
203
|
-
it.content
|
|
201
|
+
it.content
|
|
202
|
+
for it in self.conversation_history
|
|
203
|
+
if isinstance(it, model.UserMessageItem) and it.content
|
|
204
204
|
]
|
|
205
205
|
else:
|
|
206
206
|
self._user_messages_cache.extend(new_user_messages)
|
|
@@ -11,7 +11,7 @@ from klaude_code.protocol import events, model
|
|
|
11
11
|
from klaude_code.trace import is_debug_enabled
|
|
12
12
|
from klaude_code.ui.renderers.common import create_grid
|
|
13
13
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
14
|
-
from klaude_code.ui.utils.common import format_number
|
|
14
|
+
from klaude_code.ui.utils.common import format_model_params, format_number
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def _get_version() -> str:
|
|
@@ -184,38 +184,18 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
|
184
184
|
(e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
|
|
185
185
|
)
|
|
186
186
|
|
|
187
|
-
#
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if e.llm_config.thinking is not None:
|
|
191
|
-
if e.llm_config.thinking.reasoning_effort:
|
|
192
|
-
config_items.append(("reasoning-effort", e.llm_config.thinking.reasoning_effort))
|
|
193
|
-
if e.llm_config.thinking.reasoning_summary:
|
|
194
|
-
config_items.append(("reasoning-summary", e.llm_config.thinking.reasoning_summary))
|
|
195
|
-
if e.llm_config.thinking.budget_tokens:
|
|
196
|
-
config_items.append(("thinking-budget", str(e.llm_config.thinking.budget_tokens)))
|
|
197
|
-
|
|
198
|
-
if e.llm_config.verbosity:
|
|
199
|
-
config_items.append(("verbosity", str(e.llm_config.verbosity)))
|
|
200
|
-
|
|
201
|
-
if pr := e.llm_config.provider_routing:
|
|
202
|
-
if pr.sort:
|
|
203
|
-
config_items.append(("provider-sort", str(pr.sort)))
|
|
204
|
-
if pr.only:
|
|
205
|
-
config_items.append(("provider-only", ">".join(pr.only)))
|
|
206
|
-
if pr.order:
|
|
207
|
-
config_items.append(("provider-order", ">".join(pr.order)))
|
|
187
|
+
# Use format_model_params for consistent formatting
|
|
188
|
+
param_strings = format_model_params(e.llm_config)
|
|
208
189
|
|
|
209
190
|
# Render config items with tree-style prefixes
|
|
210
|
-
for i,
|
|
211
|
-
is_last = i == len(
|
|
191
|
+
for i, param_str in enumerate(param_strings):
|
|
192
|
+
is_last = i == len(param_strings) - 1
|
|
212
193
|
prefix = "└─ " if is_last else "├─ "
|
|
213
194
|
panel_content.append_text(
|
|
214
195
|
Text.assemble(
|
|
215
196
|
("\n", ThemeKey.WELCOME_INFO),
|
|
216
197
|
(prefix, ThemeKey.LINES),
|
|
217
|
-
(
|
|
218
|
-
(value, ThemeKey.WELCOME_INFO),
|
|
198
|
+
(param_str, ThemeKey.WELCOME_INFO),
|
|
219
199
|
)
|
|
220
200
|
)
|
|
221
201
|
|
klaude_code/ui/rich/theme.py
CHANGED
|
@@ -197,8 +197,9 @@ class ThemeKey(str, Enum):
|
|
|
197
197
|
CONFIG_STATUS_PRIMARY = "config.status.primary"
|
|
198
198
|
CONFIG_STATUS_ERROR = "config.status.error"
|
|
199
199
|
CONFIG_ITEM_NAME = "config.item.name"
|
|
200
|
+
CONFIG_MODEL_ID = "config.model.id"
|
|
200
201
|
CONFIG_PARAM_LABEL = "config.param.label"
|
|
201
|
-
|
|
202
|
+
|
|
202
203
|
|
|
203
204
|
def __str__(self) -> str:
|
|
204
205
|
return self.value
|
|
@@ -300,13 +301,13 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
300
301
|
ThemeKey.RESUME_FLAG.value: "bold reverse " + palette.green,
|
|
301
302
|
ThemeKey.RESUME_INFO.value: palette.green,
|
|
302
303
|
# CONFIGURATION DISPLAY
|
|
303
|
-
ThemeKey.CONFIG_TABLE_HEADER.value: palette.grey1,
|
|
304
|
+
ThemeKey.CONFIG_TABLE_HEADER.value: "bold " + palette.grey1,
|
|
304
305
|
ThemeKey.CONFIG_STATUS_OK.value: palette.green,
|
|
305
|
-
ThemeKey.CONFIG_STATUS_PRIMARY.value: palette.yellow,
|
|
306
|
+
ThemeKey.CONFIG_STATUS_PRIMARY.value: "bold " + palette.yellow,
|
|
306
307
|
ThemeKey.CONFIG_STATUS_ERROR.value: palette.red,
|
|
307
308
|
ThemeKey.CONFIG_ITEM_NAME.value: palette.cyan,
|
|
308
|
-
ThemeKey.
|
|
309
|
-
ThemeKey.
|
|
309
|
+
ThemeKey.CONFIG_MODEL_ID.value: palette.blue,
|
|
310
|
+
ThemeKey.CONFIG_PARAM_LABEL.value: "dim",
|
|
310
311
|
ThemeKey.CONFIG_PROVIDER.value: palette.cyan + " bold",
|
|
311
312
|
}
|
|
312
313
|
),
|
klaude_code/ui/utils/common.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import subprocess
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from klaude_code.protocol.llm_param import LLMConfigModelParameter, OpenRouterProviderRouting
|
|
4
8
|
|
|
5
9
|
LEADING_NEWLINES_REGEX = re.compile(r"^\n{2,}")
|
|
6
10
|
|
|
@@ -88,3 +92,45 @@ def show_path_with_tilde(path: Path | None = None):
|
|
|
88
92
|
return f"~/{relative_path}"
|
|
89
93
|
except ValueError:
|
|
90
94
|
return str(path)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def format_model_params(model_params: "LLMConfigModelParameter") -> list[str]:
|
|
98
|
+
"""Format model parameters in a concise style.
|
|
99
|
+
|
|
100
|
+
Returns a list of formatted parameter strings like:
|
|
101
|
+
- "reasoning medium"
|
|
102
|
+
- "thinking budget 10000"
|
|
103
|
+
- "verbosity 2"
|
|
104
|
+
- "provider-routing: {...}"
|
|
105
|
+
"""
|
|
106
|
+
parts: list[str] = []
|
|
107
|
+
|
|
108
|
+
if model_params.thinking:
|
|
109
|
+
if model_params.thinking.reasoning_effort:
|
|
110
|
+
parts.append(f"reasoning {model_params.thinking.reasoning_effort}")
|
|
111
|
+
if model_params.thinking.reasoning_summary:
|
|
112
|
+
parts.append(f"reason-summary {model_params.thinking.reasoning_summary}")
|
|
113
|
+
if model_params.thinking.budget_tokens:
|
|
114
|
+
parts.append(f"thinking budget {model_params.thinking.budget_tokens}")
|
|
115
|
+
|
|
116
|
+
if model_params.verbosity:
|
|
117
|
+
parts.append(f"verbosity {model_params.verbosity}")
|
|
118
|
+
|
|
119
|
+
if model_params.provider_routing:
|
|
120
|
+
parts.append(f"provider routing {_format_provider_routing(model_params.provider_routing)}")
|
|
121
|
+
|
|
122
|
+
return parts
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _format_provider_routing(pr: "OpenRouterProviderRouting") -> str:
|
|
126
|
+
"""Format provider routing settings concisely."""
|
|
127
|
+
items: list[str] = []
|
|
128
|
+
if pr.sort:
|
|
129
|
+
items.append(pr.sort)
|
|
130
|
+
if pr.only:
|
|
131
|
+
items.append(">".join(pr.only))
|
|
132
|
+
if pr.order:
|
|
133
|
+
items.append(">".join(pr.order))
|
|
134
|
+
if pr.ignore:
|
|
135
|
+
items.append(f"ignore {'>'.join(pr.ignore)}")
|
|
136
|
+
return " · ".join(items) if items else ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -120,11 +120,12 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
|
|
|
120
120
|
| Provider | Env Variable | Models |
|
|
121
121
|
|-------------|-----------------------|-------------------------------------------------------------------------------|
|
|
122
122
|
| anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
|
|
123
|
+
| claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
|
|
123
124
|
| openai | `OPENAI_API_KEY` | gpt-5.2 |
|
|
124
125
|
| openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
|
|
125
126
|
| deepseek | `DEEPSEEK_API_KEY` | deepseek |
|
|
126
127
|
| moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
|
|
127
|
-
| codex | N/A (OAuth) | gpt-5.2-codex
|
|
128
|
+
| codex | N/A (OAuth) | gpt-5.2-codex (requires ChatGPT Pro subscription) |
|
|
128
129
|
|
|
129
130
|
List all configured providers and models:
|
|
130
131
|
|
|
@@ -134,6 +135,26 @@ klaude list
|
|
|
134
135
|
|
|
135
136
|
Models from providers without a valid API key are shown as dimmed/unavailable.
|
|
136
137
|
|
|
138
|
+
#### OAuth Login
|
|
139
|
+
|
|
140
|
+
For subscription-based providers (Claude Pro/Max, ChatGPT Pro), use the login command:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Interactive provider selection
|
|
144
|
+
klaude login
|
|
145
|
+
|
|
146
|
+
# Or specify provider directly
|
|
147
|
+
klaude login claude # Claude Pro/Max subscription
|
|
148
|
+
klaude login codex # ChatGPT Pro subscription
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
To logout:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
klaude logout claude
|
|
155
|
+
klaude logout codex
|
|
156
|
+
```
|
|
157
|
+
|
|
137
158
|
#### Custom Configuration
|
|
138
159
|
|
|
139
160
|
User config file: `~/.klaude/klaude-config.yaml`
|
|
@@ -240,7 +261,6 @@ provider_list:
|
|
|
240
261
|
main_model: opus
|
|
241
262
|
|
|
242
263
|
sub_agent_models:
|
|
243
|
-
oracle: gpt-4.1
|
|
244
264
|
explore: sonnet
|
|
245
265
|
task: opus
|
|
246
266
|
webagent: sonnet
|
|
@@ -269,12 +289,13 @@ provider_list:
|
|
|
269
289
|
##### Supported Protocols
|
|
270
290
|
|
|
271
291
|
- `anthropic` - Anthropic Claude API
|
|
292
|
+
- `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
|
|
272
293
|
- `openai` - OpenAI-compatible API
|
|
273
294
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
274
295
|
- `openrouter` - OpenRouter API
|
|
275
296
|
- `google` - Google Gemini API
|
|
276
297
|
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
277
|
-
- `
|
|
298
|
+
- `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
|
|
278
299
|
|
|
279
300
|
List configured providers and models:
|
|
280
301
|
|
|
@@ -374,4 +395,3 @@ The main agent can spawn specialized sub-agents for specific tasks:
|
|
|
374
395
|
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
375
396
|
| **Task** | Handle complex multi-step tasks autonomously |
|
|
376
397
|
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
377
|
-
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|