klaude-code 2.3.0__py3-none-any.whl → 2.4.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 +3 -3
- klaude_code/cli/main.py +2 -2
- klaude_code/config/assets/builtin_config.yaml +165 -307
- klaude_code/config/config.py +17 -17
- klaude_code/config/{select_model.py → model_matcher.py} +7 -7
- klaude_code/config/sub_agent_model_helper.py +1 -1
- klaude_code/config/thinking.py +2 -2
- klaude_code/core/executor.py +59 -52
- klaude_code/core/tool/file/diff_builder.py +25 -18
- klaude_code/llm/anthropic/client.py +5 -5
- klaude_code/llm/client.py +1 -1
- klaude_code/llm/codex/client.py +2 -2
- klaude_code/llm/google/client.py +6 -6
- klaude_code/llm/input_common.py +2 -2
- klaude_code/llm/openai_compatible/client.py +3 -3
- klaude_code/llm/openai_compatible/stream.py +1 -1
- klaude_code/llm/openrouter/client.py +4 -4
- klaude_code/llm/openrouter/input.py +1 -3
- klaude_code/llm/responses/client.py +5 -5
- klaude_code/protocol/events/__init__.py +7 -1
- klaude_code/protocol/events/chat.py +10 -0
- klaude_code/protocol/llm_param.py +1 -1
- klaude_code/protocol/model.py +0 -26
- klaude_code/protocol/op.py +0 -5
- klaude_code/session/session.py +4 -2
- klaude_code/tui/command/clear_cmd.py +0 -1
- klaude_code/tui/command/command_abc.py +6 -4
- klaude_code/tui/command/copy_cmd.py +10 -10
- klaude_code/tui/command/debug_cmd.py +11 -10
- klaude_code/tui/command/export_online_cmd.py +18 -23
- klaude_code/tui/command/fork_session_cmd.py +39 -43
- klaude_code/tui/command/model_cmd.py +5 -7
- klaude_code/tui/command/{model_select.py → model_picker.py} +3 -5
- klaude_code/tui/command/refresh_cmd.py +0 -1
- klaude_code/tui/command/registry.py +15 -21
- klaude_code/tui/command/resume_cmd.py +10 -16
- klaude_code/tui/command/status_cmd.py +8 -12
- klaude_code/tui/command/sub_agent_model_cmd.py +11 -16
- klaude_code/tui/command/terminal_setup_cmd.py +8 -11
- klaude_code/tui/command/thinking_cmd.py +4 -6
- klaude_code/tui/commands.py +5 -0
- klaude_code/tui/components/command_output.py +96 -0
- klaude_code/tui/components/developer.py +3 -110
- klaude_code/tui/components/welcome.py +2 -2
- klaude_code/tui/input/prompt_toolkit.py +6 -8
- klaude_code/tui/machine.py +5 -0
- klaude_code/tui/renderer.py +5 -5
- klaude_code/tui/runner.py +0 -6
- klaude_code/tui/terminal/selector.py +4 -4
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +53 -52
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -51,7 +51,7 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
51
51
|
# Only show models from providers with valid API keys
|
|
52
52
|
models: list[ModelEntry] = sorted(
|
|
53
53
|
config.iter_model_entries(only_available=True),
|
|
54
|
-
key=lambda m: (m.
|
|
54
|
+
key=lambda m: (m.provider.lower(), m.model_name.lower()),
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
if not models:
|
|
@@ -86,13 +86,13 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
86
86
|
return ModelMatchResult(matched_model=None, filtered_models=exact_base_matches, filter_hint=filter_hint)
|
|
87
87
|
|
|
88
88
|
preferred_lower = preferred.lower()
|
|
89
|
-
# Case-insensitive exact match (selector/model_name/
|
|
89
|
+
# Case-insensitive exact match (selector/model_name/model_id)
|
|
90
90
|
exact_ci_matches = [
|
|
91
91
|
m
|
|
92
92
|
for m in models
|
|
93
93
|
if preferred_lower == m.selector.lower()
|
|
94
94
|
or preferred_lower == m.model_name.lower()
|
|
95
|
-
or preferred_lower == (m.
|
|
95
|
+
or preferred_lower == (m.model_id or "").lower()
|
|
96
96
|
]
|
|
97
97
|
if len(exact_ci_matches) == 1:
|
|
98
98
|
return ModelMatchResult(
|
|
@@ -110,7 +110,7 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
110
110
|
for m in models
|
|
111
111
|
if preferred_norm == _normalize_model_key(m.selector)
|
|
112
112
|
or preferred_norm == _normalize_model_key(m.model_name)
|
|
113
|
-
or preferred_norm == _normalize_model_key(m.
|
|
113
|
+
or preferred_norm == _normalize_model_key(m.model_id or "")
|
|
114
114
|
]
|
|
115
115
|
if len(normalized_matches) == 1:
|
|
116
116
|
return ModelMatchResult(
|
|
@@ -125,7 +125,7 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
125
125
|
for m in models
|
|
126
126
|
if preferred_norm in _normalize_model_key(m.selector)
|
|
127
127
|
or preferred_norm in _normalize_model_key(m.model_name)
|
|
128
|
-
or preferred_norm in _normalize_model_key(m.
|
|
128
|
+
or preferred_norm in _normalize_model_key(m.model_id or "")
|
|
129
129
|
]
|
|
130
130
|
if len(normalized_matches) == 1:
|
|
131
131
|
return ModelMatchResult(
|
|
@@ -134,14 +134,14 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
134
134
|
filter_hint=None,
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
# Partial match (case-insensitive) on model_name or
|
|
137
|
+
# Partial match (case-insensitive) on model_name or model_id.
|
|
138
138
|
# If normalized matching found candidates (even if multiple), prefer those as the filter set.
|
|
139
139
|
matches = normalized_matches or [
|
|
140
140
|
m
|
|
141
141
|
for m in models
|
|
142
142
|
if preferred_lower in m.selector.lower()
|
|
143
143
|
or preferred_lower in m.model_name.lower()
|
|
144
|
-
or preferred_lower in (m.
|
|
144
|
+
or preferred_lower in (m.model_id or "").lower()
|
|
145
145
|
]
|
|
146
146
|
if len(matches) == 1:
|
|
147
147
|
return ModelMatchResult(matched_model=matches[0].selector, filtered_models=models, filter_hint=None)
|
|
@@ -183,7 +183,7 @@ class SubAgentModelHelper:
|
|
|
183
183
|
all_models = self._config.iter_model_entries(only_available=True)
|
|
184
184
|
|
|
185
185
|
if profile.availability_requirement == AVAILABILITY_IMAGE_MODEL:
|
|
186
|
-
return [m for m in all_models if m.
|
|
186
|
+
return [m for m in all_models if m.modalities and "image" in m.modalities]
|
|
187
187
|
|
|
188
188
|
return all_models
|
|
189
189
|
|
klaude_code/config/thinking.py
CHANGED
|
@@ -104,7 +104,7 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
|
104
104
|
return "not set"
|
|
105
105
|
|
|
106
106
|
if protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
107
|
-
if is_openrouter_model_with_reasoning_effort(config.
|
|
107
|
+
if is_openrouter_model_with_reasoning_effort(config.model_id):
|
|
108
108
|
if thinking.reasoning_effort:
|
|
109
109
|
return f"reasoning_effort={thinking.reasoning_effort}"
|
|
110
110
|
else:
|
|
@@ -198,7 +198,7 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
|
|
|
198
198
|
ThinkingPickerData with options and current value, or None if protocol doesn't support thinking.
|
|
199
199
|
"""
|
|
200
200
|
protocol = config.protocol
|
|
201
|
-
model_name = config.
|
|
201
|
+
model_name = config.model_id
|
|
202
202
|
thinking = config.thinking
|
|
203
203
|
|
|
204
204
|
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX_OAUTH):
|
klaude_code/core/executor.py
CHANGED
|
@@ -162,27 +162,16 @@ class AgentRuntime:
|
|
|
162
162
|
|
|
163
163
|
async def run_agent(self, operation: op.RunAgentOperation) -> None:
|
|
164
164
|
agent = await self.ensure_agent(operation.session_id)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
images=operation.input.images,
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if operation.persist_user_input:
|
|
176
|
-
agent.session.append_history(
|
|
177
|
-
[
|
|
178
|
-
message.UserMessage(
|
|
179
|
-
parts=message.parts_from_text_and_images(
|
|
180
|
-
operation.input.text,
|
|
181
|
-
operation.input.images,
|
|
182
|
-
)
|
|
165
|
+
agent.session.append_history(
|
|
166
|
+
[
|
|
167
|
+
message.UserMessage(
|
|
168
|
+
parts=message.parts_from_text_and_images(
|
|
169
|
+
operation.input.text,
|
|
170
|
+
operation.input.images,
|
|
183
171
|
)
|
|
184
|
-
|
|
185
|
-
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
)
|
|
186
175
|
|
|
187
176
|
existing_active = self._task_manager.get(operation.id)
|
|
188
177
|
if existing_active is not None and not existing_active.task.done():
|
|
@@ -201,11 +190,13 @@ class AgentRuntime:
|
|
|
201
190
|
new_session.model_thinking = agent.session.model_thinking
|
|
202
191
|
agent.session = new_session
|
|
203
192
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
await self._emit_event(
|
|
194
|
+
events.CommandOutputEvent(
|
|
195
|
+
session_id=agent.session.id,
|
|
196
|
+
command_name=commands.CommandName.CLEAR,
|
|
197
|
+
content="started new conversation",
|
|
198
|
+
)
|
|
207
199
|
)
|
|
208
|
-
await self._emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
209
200
|
await self._emit_event(
|
|
210
201
|
events.WelcomeEvent(
|
|
211
202
|
session_id=agent.session.id,
|
|
@@ -460,12 +451,13 @@ class ExecutorContext:
|
|
|
460
451
|
|
|
461
452
|
if operation.emit_switch_message:
|
|
462
453
|
default_note = " (saved as default)" if operation.save_as_default else ""
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
454
|
+
await self.emit_event(
|
|
455
|
+
events.CommandOutputEvent(
|
|
456
|
+
session_id=agent.session.id,
|
|
457
|
+
command_name=commands.CommandName.MODEL,
|
|
458
|
+
content=f"Switched to: {llm_config.model_id}{default_note}",
|
|
459
|
+
)
|
|
466
460
|
)
|
|
467
|
-
agent.session.append_history([developer_item])
|
|
468
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
469
461
|
|
|
470
462
|
if self._on_model_change is not None:
|
|
471
463
|
self._on_model_change(llm_client_name)
|
|
@@ -510,12 +502,13 @@ class ExecutorContext:
|
|
|
510
502
|
new_status = _format_thinking_for_display(operation.thinking)
|
|
511
503
|
|
|
512
504
|
if operation.emit_switch_message:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
505
|
+
await self.emit_event(
|
|
506
|
+
events.CommandOutputEvent(
|
|
507
|
+
session_id=agent.session.id,
|
|
508
|
+
command_name=commands.CommandName.THINKING,
|
|
509
|
+
content=f"Thinking changed: {current} -> {new_status}",
|
|
510
|
+
)
|
|
516
511
|
)
|
|
517
|
-
agent.session.append_history([developer_item])
|
|
518
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
519
512
|
|
|
520
513
|
if operation.emit_welcome_event:
|
|
521
514
|
await self.emit_event(
|
|
@@ -572,12 +565,13 @@ class ExecutorContext:
|
|
|
572
565
|
await config.save()
|
|
573
566
|
|
|
574
567
|
saved_note = " (saved in ~/.klaude/klaude-config.yaml)" if operation.save_as_default else ""
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
568
|
+
await self.emit_event(
|
|
569
|
+
events.CommandOutputEvent(
|
|
570
|
+
session_id=agent.session.id,
|
|
571
|
+
command_name=commands.CommandName.SUB_AGENT_MODEL,
|
|
572
|
+
content=f"{sub_agent_type} model: {display_model}{saved_note}",
|
|
573
|
+
)
|
|
578
574
|
)
|
|
579
|
-
agent.session.append_history([developer_item])
|
|
580
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
581
575
|
|
|
582
576
|
async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
|
|
583
577
|
await self._agent_runtime.clear_session(operation.session_id)
|
|
@@ -593,21 +587,24 @@ class ExecutorContext:
|
|
|
593
587
|
await asyncio.to_thread(output_path.parent.mkdir, parents=True, exist_ok=True)
|
|
594
588
|
await asyncio.to_thread(output_path.write_text, html_doc, "utf-8")
|
|
595
589
|
await asyncio.to_thread(self._open_file, output_path)
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
590
|
+
await self.emit_event(
|
|
591
|
+
events.CommandOutputEvent(
|
|
592
|
+
session_id=agent.session.id,
|
|
593
|
+
command_name=commands.CommandName.EXPORT,
|
|
594
|
+
content=f"Session exported and opened: {output_path}",
|
|
595
|
+
)
|
|
599
596
|
)
|
|
600
|
-
agent.session.append_history([developer_item])
|
|
601
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
602
597
|
except Exception as exc: # pragma: no cover
|
|
603
598
|
import traceback
|
|
604
599
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
600
|
+
await self.emit_event(
|
|
601
|
+
events.CommandOutputEvent(
|
|
602
|
+
session_id=agent.session.id,
|
|
603
|
+
command_name=commands.CommandName.EXPORT,
|
|
604
|
+
content=f"Failed to export session: {exc}\n{traceback.format_exc()}",
|
|
605
|
+
is_error=True,
|
|
606
|
+
)
|
|
608
607
|
)
|
|
609
|
-
agent.session.append_history([developer_item])
|
|
610
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
611
608
|
|
|
612
609
|
def _resolve_export_output_path(self, raw: str | None, session: Session) -> Path:
|
|
613
610
|
trimmed = (raw or "").strip()
|
|
@@ -701,12 +698,15 @@ class Executor:
|
|
|
701
698
|
Unique submission ID for tracking
|
|
702
699
|
"""
|
|
703
700
|
|
|
704
|
-
|
|
705
|
-
|
|
701
|
+
if operation.id in self._completion_events:
|
|
702
|
+
raise RuntimeError(f"Submission already registered: {operation.id}")
|
|
706
703
|
|
|
707
|
-
# Create completion event
|
|
704
|
+
# Create completion event before queueing to avoid races.
|
|
708
705
|
self._completion_events[operation.id] = asyncio.Event()
|
|
709
706
|
|
|
707
|
+
submission = op.Submission(id=operation.id, operation=operation)
|
|
708
|
+
await self.submission_queue.put(submission)
|
|
709
|
+
|
|
710
710
|
log_debug(
|
|
711
711
|
f"Submitted operation {operation.type} with ID {operation.id}",
|
|
712
712
|
style="blue",
|
|
@@ -786,9 +786,16 @@ class Executor:
|
|
|
786
786
|
if tasks_to_await:
|
|
787
787
|
await asyncio.gather(*tasks_to_await, return_exceptions=True)
|
|
788
788
|
|
|
789
|
+
if self._background_tasks:
|
|
790
|
+
await asyncio.gather(*self._background_tasks, return_exceptions=True)
|
|
791
|
+
self._background_tasks.clear()
|
|
792
|
+
|
|
789
793
|
# Clear the active task manager
|
|
790
794
|
self.context.task_manager.clear()
|
|
791
795
|
|
|
796
|
+
for event in self._completion_events.values():
|
|
797
|
+
event.set()
|
|
798
|
+
|
|
792
799
|
# Send EndOperation to wake up the start() loop
|
|
793
800
|
try:
|
|
794
801
|
end_operation = op.EndOperation()
|
|
@@ -54,24 +54,31 @@ def _build_file_diff(before: str, after: str, *, file_path: str) -> model.DiffFi
|
|
|
54
54
|
elif tag == "replace":
|
|
55
55
|
old_block = before_lines[i1:i2]
|
|
56
56
|
new_block = after_lines[j1:j2]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
57
|
+
|
|
58
|
+
# Emit replacement blocks in unified-diff style: all removals first, then all additions.
|
|
59
|
+
# This matches VSCode's readability (--- then +++), while keeping per-line char spans.
|
|
60
|
+
remove_block: list[list[model.DiffSpan]] = []
|
|
61
|
+
add_block: list[list[model.DiffSpan]] = []
|
|
62
|
+
|
|
63
|
+
paired_len = min(len(old_block), len(new_block))
|
|
64
|
+
for idx in range(paired_len):
|
|
65
|
+
remove_spans, add_spans = _diff_line_spans(old_block[idx], new_block[idx])
|
|
66
|
+
remove_block.append(remove_spans)
|
|
67
|
+
add_block.append(add_spans)
|
|
68
|
+
|
|
69
|
+
for old_line in old_block[paired_len:]:
|
|
70
|
+
remove_block.append([model.DiffSpan(op="equal", text=old_line)])
|
|
71
|
+
for new_line in new_block[paired_len:]:
|
|
72
|
+
add_block.append([model.DiffSpan(op="equal", text=new_line)])
|
|
73
|
+
|
|
74
|
+
for spans in remove_block:
|
|
75
|
+
lines.append(_remove_line(spans))
|
|
76
|
+
stats_remove += 1
|
|
77
|
+
|
|
78
|
+
for spans in add_block:
|
|
79
|
+
lines.append(_add_line(spans, new_line_no))
|
|
80
|
+
stats_add += 1
|
|
81
|
+
new_line_no += 1
|
|
75
82
|
|
|
76
83
|
return model.DiffFileDiff(
|
|
77
84
|
file_path=file_path,
|
|
@@ -65,7 +65,7 @@ def build_payload(
|
|
|
65
65
|
param: LLM call parameters.
|
|
66
66
|
extra_betas: Additional beta flags to prepend to the betas list.
|
|
67
67
|
"""
|
|
68
|
-
messages = convert_history_to_input(param.input, param.
|
|
68
|
+
messages = convert_history_to_input(param.input, param.model_id)
|
|
69
69
|
tools = convert_tool_schema(param.tools)
|
|
70
70
|
system_messages = [msg for msg in param.input if isinstance(msg, message.SystemMessage)]
|
|
71
71
|
system = convert_system_to_input(param.system, system_messages)
|
|
@@ -89,7 +89,7 @@ def build_payload(
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
payload: MessageCreateParamsStreaming = {
|
|
92
|
-
"model": str(param.
|
|
92
|
+
"model": str(param.model_id),
|
|
93
93
|
"tool_choice": tool_choice,
|
|
94
94
|
"stream": True,
|
|
95
95
|
"max_tokens": param.max_tokens or DEFAULT_MAX_TOKENS,
|
|
@@ -186,12 +186,12 @@ async def parse_anthropic_stream(
|
|
|
186
186
|
if accumulated_thinking:
|
|
187
187
|
metadata_tracker.record_token()
|
|
188
188
|
full_thinking = "".join(accumulated_thinking)
|
|
189
|
-
parts.append(message.ThinkingTextPart(text=full_thinking, model_id=str(param.
|
|
189
|
+
parts.append(message.ThinkingTextPart(text=full_thinking, model_id=str(param.model_id)))
|
|
190
190
|
if pending_signature:
|
|
191
191
|
parts.append(
|
|
192
192
|
message.ThinkingSignaturePart(
|
|
193
193
|
signature=pending_signature,
|
|
194
|
-
model_id=str(param.
|
|
194
|
+
model_id=str(param.model_id),
|
|
195
195
|
format="anthropic",
|
|
196
196
|
)
|
|
197
197
|
)
|
|
@@ -224,7 +224,7 @@ async def parse_anthropic_stream(
|
|
|
224
224
|
max_tokens=param.max_tokens,
|
|
225
225
|
)
|
|
226
226
|
)
|
|
227
|
-
metadata_tracker.set_model_name(str(param.
|
|
227
|
+
metadata_tracker.set_model_name(str(param.model_id))
|
|
228
228
|
metadata_tracker.set_response_id(response_id)
|
|
229
229
|
raw_stop_reason = getattr(event, "stop_reason", None)
|
|
230
230
|
if isinstance(raw_stop_reason, str):
|
klaude_code/llm/client.py
CHANGED
klaude_code/llm/codex/client.py
CHANGED
|
@@ -31,13 +31,13 @@ from klaude_code.protocol import llm_param, message
|
|
|
31
31
|
|
|
32
32
|
def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStreaming:
|
|
33
33
|
"""Build Codex API request parameters."""
|
|
34
|
-
inputs = convert_history_to_input(param.input, param.
|
|
34
|
+
inputs = convert_history_to_input(param.input, param.model_id)
|
|
35
35
|
tools = convert_tool_schema(param.tools)
|
|
36
36
|
|
|
37
37
|
session_id = param.session_id or ""
|
|
38
38
|
|
|
39
39
|
payload: ResponseCreateParamsStreaming = {
|
|
40
|
-
"model": str(param.
|
|
40
|
+
"model": str(param.model_id),
|
|
41
41
|
"tool_choice": "auto",
|
|
42
42
|
"parallel_tool_calls": True,
|
|
43
43
|
"include": [
|
klaude_code/llm/google/client.py
CHANGED
|
@@ -163,7 +163,7 @@ async def parse_google_stream(
|
|
|
163
163
|
assistant_parts.append(
|
|
164
164
|
message.ThinkingTextPart(
|
|
165
165
|
text="".join(accumulated_thoughts),
|
|
166
|
-
model_id=str(param.
|
|
166
|
+
model_id=str(param.model_id),
|
|
167
167
|
)
|
|
168
168
|
)
|
|
169
169
|
accumulated_thoughts.clear()
|
|
@@ -171,7 +171,7 @@ async def parse_google_stream(
|
|
|
171
171
|
assistant_parts.append(
|
|
172
172
|
message.ThinkingSignaturePart(
|
|
173
173
|
signature=thought_signature,
|
|
174
|
-
model_id=str(param.
|
|
174
|
+
model_id=str(param.model_id),
|
|
175
175
|
format="google_thought_signature",
|
|
176
176
|
)
|
|
177
177
|
)
|
|
@@ -301,7 +301,7 @@ async def parse_google_stream(
|
|
|
301
301
|
usage = _usage_from_metadata(last_usage_metadata, context_limit=param.context_limit, max_tokens=param.max_tokens)
|
|
302
302
|
if usage is not None:
|
|
303
303
|
metadata_tracker.set_usage(usage)
|
|
304
|
-
metadata_tracker.set_model_name(str(param.
|
|
304
|
+
metadata_tracker.set_model_name(str(param.model_id))
|
|
305
305
|
metadata_tracker.set_response_id(response_id)
|
|
306
306
|
metadata = metadata_tracker.finalize()
|
|
307
307
|
yield message.AssistantMessage(
|
|
@@ -336,13 +336,13 @@ class GoogleClient(LLMClientABC):
|
|
|
336
336
|
param = apply_config_defaults(param, self.get_llm_config())
|
|
337
337
|
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
338
338
|
|
|
339
|
-
contents = convert_history_to_contents(param.input, model_name=str(param.
|
|
339
|
+
contents = convert_history_to_contents(param.input, model_name=str(param.model_id))
|
|
340
340
|
config = _build_config(param)
|
|
341
341
|
|
|
342
342
|
log_debug(
|
|
343
343
|
json.dumps(
|
|
344
344
|
{
|
|
345
|
-
"model": str(param.
|
|
345
|
+
"model": str(param.model_id),
|
|
346
346
|
"contents": [c.model_dump(exclude_none=True) for c in contents],
|
|
347
347
|
"config": config.model_dump(exclude_none=True),
|
|
348
348
|
},
|
|
@@ -354,7 +354,7 @@ class GoogleClient(LLMClientABC):
|
|
|
354
354
|
|
|
355
355
|
try:
|
|
356
356
|
stream = await self.client.aio.models.generate_content_stream(
|
|
357
|
-
model=str(param.
|
|
357
|
+
model=str(param.model_id),
|
|
358
358
|
contents=cast(Any, contents),
|
|
359
359
|
config=config,
|
|
360
360
|
)
|
klaude_code/llm/input_common.py
CHANGED
|
@@ -165,8 +165,8 @@ def split_thinking_parts(
|
|
|
165
165
|
|
|
166
166
|
def apply_config_defaults(param: "LLMCallParameter", config: "LLMConfigParameter") -> "LLMCallParameter":
|
|
167
167
|
"""Apply config defaults to LLM call parameters."""
|
|
168
|
-
if param.
|
|
169
|
-
param.
|
|
168
|
+
if param.model_id is None:
|
|
169
|
+
param.model_id = config.model_id
|
|
170
170
|
if param.temperature is None:
|
|
171
171
|
param.temperature = config.temperature
|
|
172
172
|
if param.max_tokens is None:
|
|
@@ -19,7 +19,7 @@ from klaude_code.protocol import llm_param, message
|
|
|
19
19
|
|
|
20
20
|
def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreateParamsStreaming, dict[str, object]]:
|
|
21
21
|
"""Build OpenAI API request parameters."""
|
|
22
|
-
messages = convert_history_to_input(param.input, param.system, param.
|
|
22
|
+
messages = convert_history_to_input(param.input, param.system, param.model_id)
|
|
23
23
|
tools = convert_tool_schema(param.tools)
|
|
24
24
|
|
|
25
25
|
extra_body: dict[str, object] = {}
|
|
@@ -31,7 +31,7 @@ def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreatePa
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
payload: CompletionCreateParamsStreaming = {
|
|
34
|
-
"model": str(param.
|
|
34
|
+
"model": str(param.model_id),
|
|
35
35
|
"tool_choice": "auto",
|
|
36
36
|
"parallel_tool_calls": True,
|
|
37
37
|
"stream": True,
|
|
@@ -108,7 +108,7 @@ class OpenAICompatibleClient(LLMClientABC):
|
|
|
108
108
|
return
|
|
109
109
|
|
|
110
110
|
reasoning_handler = DefaultReasoningHandler(
|
|
111
|
-
param_model=str(param.
|
|
111
|
+
param_model=str(param.model_id),
|
|
112
112
|
response_id=None,
|
|
113
113
|
)
|
|
114
114
|
|
|
@@ -30,7 +30,7 @@ def build_payload(
|
|
|
30
30
|
param: llm_param.LLMCallParameter,
|
|
31
31
|
) -> tuple[CompletionCreateParamsStreaming, dict[str, object], dict[str, str]]:
|
|
32
32
|
"""Build OpenRouter API request parameters."""
|
|
33
|
-
messages = convert_history_to_input(param.input, param.system, param.
|
|
33
|
+
messages = convert_history_to_input(param.input, param.system, param.model_id)
|
|
34
34
|
tools = convert_tool_schema(param.tools)
|
|
35
35
|
|
|
36
36
|
extra_body: dict[str, object] = {
|
|
@@ -66,13 +66,13 @@ def build_payload(
|
|
|
66
66
|
if param.provider_routing:
|
|
67
67
|
extra_body["provider"] = param.provider_routing.model_dump(exclude_none=True)
|
|
68
68
|
|
|
69
|
-
if is_claude_model(param.
|
|
69
|
+
if is_claude_model(param.model_id):
|
|
70
70
|
extra_headers["x-anthropic-beta"] = (
|
|
71
71
|
f"{ANTHROPIC_BETA_FINE_GRAINED_TOOL_STREAMING},{ANTHROPIC_BETA_INTERLEAVED_THINKING}"
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
payload: CompletionCreateParamsStreaming = {
|
|
75
|
-
"model": str(param.
|
|
75
|
+
"model": str(param.model_id),
|
|
76
76
|
"tool_choice": "auto",
|
|
77
77
|
"parallel_tool_calls": True,
|
|
78
78
|
"stream": True,
|
|
@@ -133,7 +133,7 @@ class OpenRouterClient(LLMClientABC):
|
|
|
133
133
|
return
|
|
134
134
|
|
|
135
135
|
reasoning_handler = ReasoningStreamHandler(
|
|
136
|
-
param_model=str(param.
|
|
136
|
+
param_model=str(param.model_id),
|
|
137
137
|
response_id=None,
|
|
138
138
|
)
|
|
139
139
|
|
|
@@ -115,9 +115,7 @@ def convert_history_to_input(
|
|
|
115
115
|
)
|
|
116
116
|
]
|
|
117
117
|
if system and use_cache_control
|
|
118
|
-
else (
|
|
119
|
-
[cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system})] if system else []
|
|
120
|
-
)
|
|
118
|
+
else ([cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system})] if system else [])
|
|
121
119
|
)
|
|
122
120
|
|
|
123
121
|
for msg, attachment in attach_developer_messages(history):
|
|
@@ -24,11 +24,11 @@ if TYPE_CHECKING:
|
|
|
24
24
|
|
|
25
25
|
def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStreaming:
|
|
26
26
|
"""Build OpenAI Responses API request parameters."""
|
|
27
|
-
inputs = convert_history_to_input(param.input, param.
|
|
27
|
+
inputs = convert_history_to_input(param.input, param.model_id)
|
|
28
28
|
tools = convert_tool_schema(param.tools)
|
|
29
29
|
|
|
30
30
|
payload: ResponseCreateParamsStreaming = {
|
|
31
|
-
"model": str(param.
|
|
31
|
+
"model": str(param.model_id),
|
|
32
32
|
"tool_choice": "auto",
|
|
33
33
|
"parallel_tool_calls": True,
|
|
34
34
|
"include": [
|
|
@@ -77,7 +77,7 @@ async def parse_responses_stream(
|
|
|
77
77
|
assistant_parts.append(
|
|
78
78
|
message.ThinkingTextPart(
|
|
79
79
|
text="".join(accumulated_thinking),
|
|
80
|
-
model_id=str(param.
|
|
80
|
+
model_id=str(param.model_id),
|
|
81
81
|
)
|
|
82
82
|
)
|
|
83
83
|
accumulated_thinking.clear()
|
|
@@ -85,7 +85,7 @@ async def parse_responses_stream(
|
|
|
85
85
|
assistant_parts.append(
|
|
86
86
|
message.ThinkingSignaturePart(
|
|
87
87
|
signature=pending_signature,
|
|
88
|
-
model_id=str(param.
|
|
88
|
+
model_id=str(param.model_id),
|
|
89
89
|
format="openai_reasoning",
|
|
90
90
|
)
|
|
91
91
|
)
|
|
@@ -197,7 +197,7 @@ async def parse_responses_stream(
|
|
|
197
197
|
max_tokens=param.max_tokens,
|
|
198
198
|
)
|
|
199
199
|
)
|
|
200
|
-
metadata_tracker.set_model_name(str(param.
|
|
200
|
+
metadata_tracker.set_model_name(str(param.model_id))
|
|
201
201
|
metadata_tracker.set_response_id(response_id)
|
|
202
202
|
stop_reason = map_stop_reason(event.response.status, error_reason)
|
|
203
203
|
if event.response.status != "completed":
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from klaude_code.protocol.events.base import Event, ResponseEvent
|
|
4
|
-
from klaude_code.protocol.events.chat import
|
|
4
|
+
from klaude_code.protocol.events.chat import (
|
|
5
|
+
CommandOutputEvent,
|
|
6
|
+
DeveloperMessageEvent,
|
|
7
|
+
TodoChangeEvent,
|
|
8
|
+
UserMessageEvent,
|
|
9
|
+
)
|
|
5
10
|
from klaude_code.protocol.events.lifecycle import TaskFinishEvent, TaskStartEvent, TurnEndEvent, TurnStartEvent
|
|
6
11
|
from klaude_code.protocol.events.metadata import TaskMetadataEvent, UsageEvent
|
|
7
12
|
from klaude_code.protocol.events.streaming import (
|
|
@@ -30,6 +35,7 @@ __all__ = [
|
|
|
30
35
|
"AssistantTextDeltaEvent",
|
|
31
36
|
"AssistantTextEndEvent",
|
|
32
37
|
"AssistantTextStartEvent",
|
|
38
|
+
"CommandOutputEvent",
|
|
33
39
|
"DeveloperMessageEvent",
|
|
34
40
|
"EndEvent",
|
|
35
41
|
"ErrorEvent",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from klaude_code.protocol import message, model
|
|
4
|
+
from klaude_code.protocol.commands import CommandName
|
|
4
5
|
|
|
5
6
|
from .base import Event
|
|
6
7
|
|
|
@@ -18,3 +19,12 @@ class DeveloperMessageEvent(Event):
|
|
|
18
19
|
|
|
19
20
|
class TodoChangeEvent(Event):
|
|
20
21
|
todos: list[model.TodoItem]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CommandOutputEvent(Event):
|
|
25
|
+
"""Event for command output display. Not persisted to session history."""
|
|
26
|
+
|
|
27
|
+
command_name: CommandName | str
|
|
28
|
+
content: str = ""
|
|
29
|
+
ui_extra: model.ToolResultUIExtra | None = None
|
|
30
|
+
is_error: bool = False
|
|
@@ -119,7 +119,7 @@ class LLMConfigProviderParameter(BaseModel):
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
class LLMConfigModelParameter(BaseModel):
|
|
122
|
-
|
|
122
|
+
model_id: str | None = None
|
|
123
123
|
temperature: float | None = None
|
|
124
124
|
max_tokens: int | None = None
|
|
125
125
|
context_limit: int | None = None
|