klaude-code 1.2.23__py3-none-any.whl → 1.2.24__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.
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from collections.abc import AsyncGenerator
3
- from typing import override
3
+ from typing import Any, override
4
4
 
5
5
  import httpx
6
6
  import openai
@@ -9,13 +9,13 @@ from openai.types.chat.completion_create_params import CompletionCreateParamsStr
9
9
  from klaude_code.llm.client import LLMClientABC
10
10
  from klaude_code.llm.input_common import apply_config_defaults
11
11
  from klaude_code.llm.openai_compatible.input import convert_tool_schema
12
- from klaude_code.llm.openai_compatible.stream_processor import StreamStateManager
12
+ from klaude_code.llm.openai_compatible.stream import parse_chat_completions_stream
13
13
  from klaude_code.llm.openrouter.input import convert_history_to_input, is_claude_model
14
- from klaude_code.llm.openrouter.reasoning_handler import ReasoningDetail, ReasoningStreamHandler
14
+ from klaude_code.llm.openrouter.reasoning import ReasoningStreamHandler
15
15
  from klaude_code.llm.registry import register
16
- from klaude_code.llm.usage import MetadataTracker, convert_usage
16
+ from klaude_code.llm.usage import MetadataTracker
17
17
  from klaude_code.protocol import llm_param, model
18
- from klaude_code.trace import DebugType, is_debug_enabled, log, log_debug
18
+ from klaude_code.trace import DebugType, is_debug_enabled, log_debug
19
19
 
20
20
 
21
21
  def build_payload(
@@ -96,114 +96,34 @@ class OpenRouterClient(LLMClientABC):
96
96
  debug_type=DebugType.LLM_PAYLOAD,
97
97
  )
98
98
 
99
- stream = self.client.chat.completions.create(
100
- **payload,
101
- extra_body=extra_body,
102
- extra_headers=extra_headers,
103
- )
99
+ try:
100
+ stream = await self.client.chat.completions.create(
101
+ **payload,
102
+ extra_body=extra_body,
103
+ extra_headers=extra_headers,
104
+ )
105
+ except (openai.OpenAIError, httpx.HTTPError) as e:
106
+ yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
107
+ yield metadata_tracker.finalize()
108
+ return
104
109
 
105
110
  reasoning_handler = ReasoningStreamHandler(
106
111
  param_model=str(param.model),
107
112
  response_id=None,
108
113
  )
109
114
 
110
- state = StreamStateManager(
111
- param_model=str(param.model),
112
- reasoning_flusher=reasoning_handler.flush,
113
- )
114
-
115
- try:
116
- async for event in await stream:
117
- log_debug(
118
- event.model_dump_json(exclude_none=True),
119
- style="blue",
120
- debug_type=DebugType.LLM_STREAM,
121
- )
122
-
123
- if not state.response_id and event.id:
124
- state.set_response_id(event.id)
125
- reasoning_handler.set_response_id(event.id)
126
- yield model.StartItem(response_id=event.id)
127
- if event.usage is not None:
128
- metadata_tracker.set_usage(convert_usage(event.usage, param.context_limit, param.max_tokens))
129
- if event.model:
130
- metadata_tracker.set_model_name(event.model)
131
- if provider := getattr(event, "provider", None):
132
- metadata_tracker.set_provider(str(provider))
133
- if len(event.choices) == 0:
134
- continue
135
- delta = event.choices[0].delta
136
-
137
- # Reasoning
138
- if reasoning_details := getattr(delta, "reasoning_details", None):
139
- for item in reasoning_details:
140
- try:
141
- reasoning_detail = ReasoningDetail.model_validate(item)
142
- if reasoning_detail.text or reasoning_detail.summary:
143
- metadata_tracker.record_token()
144
- state.stage = "reasoning"
145
- # Yield delta immediately for streaming
146
- if reasoning_detail.text:
147
- yield model.ReasoningTextDelta(
148
- content=reasoning_detail.text,
149
- response_id=state.response_id,
150
- )
151
- if reasoning_detail.summary:
152
- yield model.ReasoningTextDelta(
153
- content=reasoning_detail.summary,
154
- response_id=state.response_id,
155
- )
156
- # Keep existing handler logic for final items
157
- for conversation_item in reasoning_handler.on_detail(reasoning_detail):
158
- yield conversation_item
159
- except Exception as e:
160
- log("reasoning_details error", str(e), style="red")
161
-
162
- # Assistant
163
- if delta.content and (
164
- state.stage == "assistant" or delta.content.strip()
165
- ): # Process all content in assistant stage, filter empty content in reasoning stage
166
- metadata_tracker.record_token()
167
- if state.stage == "reasoning":
168
- for item in state.flush_reasoning():
169
- yield item
170
- state.stage = "assistant"
171
- state.accumulated_content.append(delta.content)
172
- yield model.AssistantMessageDelta(
173
- content=delta.content,
174
- response_id=state.response_id,
175
- )
176
-
177
- # Tool
178
- if delta.tool_calls and len(delta.tool_calls) > 0:
179
- metadata_tracker.record_token()
180
- if state.stage == "reasoning":
181
- for item in state.flush_reasoning():
182
- yield item
183
- elif state.stage == "assistant":
184
- for item in state.flush_assistant():
185
- yield item
186
- state.stage = "tool"
187
- # Emit ToolCallStartItem for new tool calls
188
- for tc in delta.tool_calls:
189
- if tc.index not in state.emitted_tool_start_indices and tc.function and tc.function.name:
190
- state.emitted_tool_start_indices.add(tc.index)
191
- yield model.ToolCallStartItem(
192
- response_id=state.response_id,
193
- call_id=tc.id or "",
194
- name=tc.function.name,
195
- )
196
- state.accumulated_tool_calls.add(delta.tool_calls)
197
-
198
- except (openai.OpenAIError, httpx.HTTPError) as e:
199
- yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
200
-
201
- # Finalize
202
- flushed_items = state.flush_all()
203
- if flushed_items:
204
- metadata_tracker.record_token()
205
- for item in flushed_items:
115
+ def on_event(event: Any) -> None:
116
+ log_debug(
117
+ event.model_dump_json(exclude_none=True),
118
+ style="blue",
119
+ debug_type=DebugType.LLM_STREAM,
120
+ )
121
+
122
+ async for item in parse_chat_completions_stream(
123
+ stream,
124
+ param=param,
125
+ metadata_tracker=metadata_tracker,
126
+ reasoning_handler=reasoning_handler,
127
+ on_event=on_event,
128
+ ):
206
129
  yield item
207
-
208
- metadata_tracker.set_response_id(state.response_id)
209
- yield metadata_tracker.finalize()
@@ -1,6 +1,8 @@
1
1
  from pydantic import BaseModel
2
2
 
3
+ from klaude_code.llm.openai_compatible.stream import ReasoningDeltaResult, ReasoningHandlerABC
3
4
  from klaude_code.protocol import model
5
+ from klaude_code.trace import log
4
6
 
5
7
 
6
8
  class ReasoningDetail(BaseModel):
@@ -16,8 +18,8 @@ class ReasoningDetail(BaseModel):
16
18
  signature: str | None = None # Claude's signature
17
19
 
18
20
 
19
- class ReasoningStreamHandler:
20
- """Accumulates reasoning text and flushes on encrypted content or finalize."""
21
+ class ReasoningStreamHandler(ReasoningHandlerABC):
22
+ """Accumulates OpenRouter reasoning details and emits ordered outputs."""
21
23
 
22
24
  def __init__(
23
25
  self,
@@ -34,6 +36,26 @@ class ReasoningStreamHandler:
34
36
  """Update the response identifier used for emitted items."""
35
37
  self._response_id = response_id
36
38
 
39
+ def on_delta(self, delta: object) -> ReasoningDeltaResult:
40
+ """Parse OpenRouter's reasoning_details and return ordered stream outputs."""
41
+ reasoning_details = getattr(delta, "reasoning_details", None)
42
+ if not reasoning_details:
43
+ return ReasoningDeltaResult(handled=False, outputs=[])
44
+
45
+ outputs: list[str | model.ConversationItem] = []
46
+ for item in reasoning_details:
47
+ try:
48
+ reasoning_detail = ReasoningDetail.model_validate(item)
49
+ if reasoning_detail.text:
50
+ outputs.append(reasoning_detail.text)
51
+ if reasoning_detail.summary:
52
+ outputs.append(reasoning_detail.summary)
53
+ outputs.extend(self.on_detail(reasoning_detail))
54
+ except Exception as e:
55
+ log("reasoning_details error", str(e), style="red")
56
+
57
+ return ReasoningDeltaResult(handled=True, outputs=outputs)
58
+
37
59
  def on_detail(self, detail: ReasoningDetail) -> list[model.ConversationItem]:
38
60
  """Process a single reasoning detail and return streamable items."""
39
61
  items: list[model.ConversationItem] = []
@@ -138,6 +138,12 @@ class TruncationUIExtra(BaseModel):
138
138
  truncated_length: int
139
139
 
140
140
 
141
+ class MarkdownDocUIExtra(BaseModel):
142
+ type: Literal["markdown_doc"] = "markdown_doc"
143
+ file_path: str
144
+ content: str
145
+
146
+
141
147
  class SessionStatusUIExtra(BaseModel):
142
148
  type: Literal["session_status"] = "session_status"
143
149
  usage: "Usage"
@@ -146,7 +152,13 @@ class SessionStatusUIExtra(BaseModel):
146
152
 
147
153
 
148
154
  ToolResultUIExtra = Annotated[
149
- DiffUIExtra | TodoListUIExtra | SessionIdUIExtra | MermaidLinkUIExtra | TruncationUIExtra | SessionStatusUIExtra,
155
+ DiffUIExtra
156
+ | TodoListUIExtra
157
+ | SessionIdUIExtra
158
+ | MermaidLinkUIExtra
159
+ | TruncationUIExtra
160
+ | MarkdownDocUIExtra
161
+ | SessionStatusUIExtra,
150
162
  Field(discriminator="type"),
151
163
  ]
152
164
 
@@ -20,12 +20,10 @@ class StageManager:
20
20
  *,
21
21
  finish_assistant: Callable[[], Awaitable[None]],
22
22
  finish_thinking: Callable[[], Awaitable[None]],
23
- on_enter_thinking: Callable[[], None],
24
23
  ):
25
24
  self._stage = Stage.WAITING
26
25
  self._finish_assistant = finish_assistant
27
26
  self._finish_thinking = finish_thinking
28
- self._on_enter_thinking = on_enter_thinking
29
27
 
30
28
  @property
31
29
  def current_stage(self) -> Stage:
@@ -41,7 +39,6 @@ class StageManager:
41
39
  if self._stage == Stage.THINKING:
42
40
  return
43
41
  await self.transition_to(Stage.THINKING)
44
- self._on_enter_thinking()
45
42
 
46
43
  async def finish_assistant(self) -> None:
47
44
  if self._stage != Stage.ASSISTANT:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
+ from rich.cells import cell_len
5
6
  from rich.rule import Rule
6
7
  from rich.text import Text
7
8
 
@@ -10,13 +11,48 @@ from klaude_code.protocol import events
10
11
  from klaude_code.ui.core.stage_manager import Stage, StageManager
11
12
  from klaude_code.ui.modes.repl.renderer import REPLRenderer
12
13
  from klaude_code.ui.renderers.assistant import ASSISTANT_MESSAGE_MARK
13
- from klaude_code.ui.renderers.thinking import normalize_thinking_content
14
+ from klaude_code.ui.renderers.thinking import THINKING_MESSAGE_MARK, normalize_thinking_content
14
15
  from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
15
16
  from klaude_code.ui.rich.theme import ThemeKey
16
17
  from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier
17
18
  from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
18
19
 
19
20
 
21
+ def extract_last_bold_header(text: str) -> str | None:
22
+ """Extract the latest complete bold header ("**...**") from text.
23
+
24
+ We treat a bold segment as a "header" only if it appears at the beginning
25
+ of a line (ignoring leading whitespace). This avoids picking up incidental
26
+ emphasis inside paragraphs.
27
+
28
+ Returns None if no complete bold segment is available yet.
29
+ """
30
+
31
+ last: str | None = None
32
+ i = 0
33
+ while True:
34
+ start = text.find("**", i)
35
+ if start < 0:
36
+ break
37
+
38
+ line_start = text.rfind("\n", 0, start) + 1
39
+ if text[line_start:start].strip():
40
+ i = start + 2
41
+ continue
42
+
43
+ end = text.find("**", start + 2)
44
+ if end < 0:
45
+ break
46
+
47
+ inner = " ".join(text[start + 2 : end].split())
48
+ if inner and "\n" not in inner:
49
+ last = inner
50
+
51
+ i = end + 2
52
+
53
+ return last
54
+
55
+
20
56
  @dataclass
21
57
  class ActiveStream:
22
58
  """Active streaming state containing buffer and markdown renderer.
@@ -117,7 +153,7 @@ class ActivityState:
117
153
  for name, count in self._tool_calls.items():
118
154
  if not first:
119
155
  activity_text.append(", ")
120
- activity_text.append(Text(name, style=ThemeKey.SPINNER_STATUS_TEXT_BOLD))
156
+ activity_text.append(Text(name, style=ThemeKey.STATUS_TEXT_BOLD))
121
157
  if count > 1:
122
158
  activity_text.append(f" x {count}")
123
159
  first = False
@@ -130,8 +166,9 @@ class ActivityState:
130
166
  class SpinnerStatusState:
131
167
  """Multi-layer spinner status state management.
132
168
 
133
- Composed of two independent layers:
134
- - base_status: Set by TodoChange, persistent within a turn
169
+ Layers:
170
+ - todo_status: Set by TodoChange (preferred when present)
171
+ - reasoning_status: Derived from Thinking/ThinkingDelta bold headers
135
172
  - activity: Current activity (composing or tool_calls), mutually exclusive
136
173
  - context_percent: Context usage percentage, updated during task execution
137
174
 
@@ -142,25 +179,31 @@ class SpinnerStatusState:
142
179
  - Context percent is appended at the end if available
143
180
  """
144
181
 
145
- DEFAULT_STATUS = "Thinking …"
146
-
147
182
  def __init__(self) -> None:
148
- self._base_status: str | None = None
183
+ self._todo_status: str | None = None
184
+ self._reasoning_status: str | None = None
149
185
  self._activity = ActivityState()
150
186
  self._context_percent: float | None = None
151
187
 
152
188
  def reset(self) -> None:
153
189
  """Reset all layers."""
154
- self._base_status = None
190
+ self._todo_status = None
191
+ self._reasoning_status = None
155
192
  self._activity.reset()
156
193
  self._context_percent = None
157
194
 
158
- def set_base_status(self, status: str | None) -> None:
195
+ def set_todo_status(self, status: str | None) -> None:
159
196
  """Set base status from TodoChange."""
160
- self._base_status = status
197
+ self._todo_status = status
198
+
199
+ def set_reasoning_status(self, status: str | None) -> None:
200
+ """Set reasoning-derived base status from ThinkingDelta bold headers."""
201
+ self._reasoning_status = status
161
202
 
162
203
  def set_composing(self, composing: bool) -> None:
163
204
  """Set composing state when assistant is streaming."""
205
+ if composing:
206
+ self._reasoning_status = None
164
207
  self._activity.set_composing(composing)
165
208
 
166
209
  def add_tool_call(self, tool_name: str) -> None:
@@ -187,8 +230,10 @@ class SpinnerStatusState:
187
230
  """Get current spinner status as rich Text (without context)."""
188
231
  activity_text = self._activity.get_activity_text()
189
232
 
190
- if self._base_status:
191
- result = Text(self._base_status)
233
+ base_status = self._todo_status or self._reasoning_status
234
+
235
+ if base_status:
236
+ result = Text(base_status, style=ThemeKey.STATUS_TEXT_BOLD)
192
237
  if activity_text:
193
238
  result.append(" | ")
194
239
  result.append_text(activity_text)
@@ -196,7 +241,7 @@ class SpinnerStatusState:
196
241
  activity_text.append(" …")
197
242
  result = activity_text
198
243
  else:
199
- result = Text(self.DEFAULT_STATUS)
244
+ result = Text(const.STATUS_DEFAULT_TEXT, style=ThemeKey.STATUS_TEXT)
200
245
 
201
246
  return result
202
247
 
@@ -220,7 +265,6 @@ class DisplayEventHandler:
220
265
  self.stage_manager = StageManager(
221
266
  finish_assistant=self._finish_assistant_stream,
222
267
  finish_thinking=self._finish_thinking_stream,
223
- on_enter_thinking=self._print_thinking_prefix,
224
268
  )
225
269
 
226
270
  async def consume_event(self, event: events.Event) -> None:
@@ -311,6 +355,10 @@ class DisplayEventHandler:
311
355
  await self._finish_thinking_stream()
312
356
  else:
313
357
  # Non-streaming path (history replay or models without delta support)
358
+ reasoning_status = extract_last_bold_header(normalize_thinking_content(event.content))
359
+ if reasoning_status:
360
+ self.spinner_status.set_reasoning_status(reasoning_status)
361
+ self._update_spinner()
314
362
  await self.stage_manager.enter_thinking_stage()
315
363
  self.renderer.display_thinking(event.content)
316
364
 
@@ -329,6 +377,8 @@ class DisplayEventHandler:
329
377
  theme=self.renderer.themes.thinking_markdown_theme,
330
378
  console=self.renderer.console,
331
379
  spinner=self.renderer.spinner_renderable(),
380
+ mark=THINKING_MESSAGE_MARK,
381
+ mark_style=ThemeKey.THINKING,
332
382
  left_margin=const.MARKDOWN_LEFT_MARGIN,
333
383
  markdown_class=ThinkingMarkdown,
334
384
  )
@@ -337,6 +387,11 @@ class DisplayEventHandler:
337
387
 
338
388
  self.thinking_stream.append(event.content)
339
389
 
390
+ reasoning_status = extract_last_bold_header(normalize_thinking_content(self.thinking_stream.buffer))
391
+ if reasoning_status:
392
+ self.spinner_status.set_reasoning_status(reasoning_status)
393
+ self._update_spinner()
394
+
340
395
  if first_delta and self.thinking_stream.mdstream is not None:
341
396
  self.thinking_stream.mdstream.update(normalize_thinking_content(self.thinking_stream.buffer))
342
397
 
@@ -415,7 +470,7 @@ class DisplayEventHandler:
415
470
 
416
471
  def _on_todo_change(self, event: events.TodoChangeEvent) -> None:
417
472
  active_form_status_text = self._extract_active_form_text(event)
418
- self.spinner_status.set_base_status(active_form_status_text if active_form_status_text else None)
473
+ self.spinner_status.set_todo_status(active_form_status_text if active_form_status_text else None)
419
474
  # Clear tool calls when todo changes, as the tool execution has advanced
420
475
  self.spinner_status.clear_for_new_turn()
421
476
  self._update_spinner()
@@ -469,14 +524,14 @@ class DisplayEventHandler:
469
524
  mdstream.update(self.assistant_stream.buffer, final=True)
470
525
  self.assistant_stream.finish()
471
526
 
472
- def _print_thinking_prefix(self) -> None:
473
- self.renderer.display_thinking_prefix()
474
-
475
527
  def _update_spinner(self) -> None:
476
528
  """Update spinner text from current status state."""
529
+ status_text = self.spinner_status.get_status()
530
+ context_text = self.spinner_status.get_context_text()
531
+ status_text = self._truncate_spinner_status_text(status_text, right_text=context_text)
477
532
  self.renderer.spinner_update(
478
- self.spinner_status.get_status(),
479
- self.spinner_status.get_context_text(),
533
+ status_text,
534
+ context_text,
480
535
  )
481
536
 
482
537
  async def _flush_assistant_buffer(self, state: StreamState) -> None:
@@ -534,35 +589,28 @@ class DisplayEventHandler:
534
589
  status_text = todo.active_form
535
590
  if len(todo.content) > 0:
536
591
  status_text = todo.content
537
- status_text = status_text.replace("\n", "")
538
- max_length = self._calculate_base_status_max_length()
539
- return self._truncate_status_text(status_text, max_length=max_length)
540
-
541
- def _calculate_base_status_max_length(self) -> int:
542
- """Calculate max length for base_status based on terminal width.
543
-
544
- Reserve space for:
545
- - Spinner glyph + space + context text: 2 chars + context text length 10 chars
546
- - " | " separator: 3 chars (only if activity text present)
547
- - Activity text: actual length (only if present)
548
- - Status hint text (esc to interrupt)
592
+ return status_text.replace("\n", " ").strip()
593
+
594
+ def _truncate_spinner_status_text(self, status_text: Text, *, right_text: Text | None) -> Text:
595
+ """Truncate spinner status to a single line based on terminal width.
596
+
597
+ Rich wraps based on terminal cell width (CJK chars count as 2). Use
598
+ cell-aware truncation to prevent the status from wrapping into two lines.
549
599
  """
600
+
550
601
  terminal_width = self.renderer.console.size.width
551
602
 
552
- # Base reserved space: spinner + context + status hint
553
- reserved_space = 12 + len(const.STATUS_HINT_TEXT)
603
+ # BreathingSpinner renders as a 2-column Table.grid(padding=1):
604
+ # 1 cell for glyph + 1 cell of padding between columns (collapsed).
605
+ spinner_prefix_cells = 2
554
606
 
555
- # Add space for activity text if present
556
- activity_text = self.spinner_status.get_activity_text()
557
- if activity_text:
558
- # " | " separator + actual activity text length
559
- reserved_space += 3 + len(activity_text.plain)
607
+ hint_cells = cell_len(const.STATUS_HINT_TEXT)
608
+ right_cells = cell_len(right_text.plain) if right_text is not None else 0
560
609
 
561
- max_length = max(10, terminal_width - reserved_space)
562
- return max_length
610
+ max_main_cells = terminal_width - spinner_prefix_cells - hint_cells - right_cells - 1
611
+ # rich.text.Text.truncate behaves unexpectedly for 0; clamp to at least 1.
612
+ max_main_cells = max(1, max_main_cells)
563
613
 
564
- def _truncate_status_text(self, text: str, max_length: int) -> str:
565
- if len(text) <= max_length:
566
- return text
567
- truncated = text[:max_length]
568
- return truncated + "…"
614
+ truncated = status_text.copy()
615
+ truncated.truncate(max_main_cells, overflow="ellipsis", pad=False)
616
+ return truncated
@@ -33,7 +33,9 @@ class REPLStatusSnapshot(NamedTuple):
33
33
  update_message: str | None = None
34
34
 
35
35
 
36
- COMPLETION_SELECTED = "#5869f7"
36
+ COMPLETION_SELECTED_DARK_BG = "#8b9bff"
37
+ COMPLETION_SELECTED_LIGHT_BG = "#5869f7"
38
+ COMPLETION_SELECTED_UNKNOWN_BG = "#7080f0"
37
39
  COMPLETION_MENU = "ansibrightblack"
38
40
  INPUT_PROMPT_STYLE = "ansimagenta bold"
39
41
  PLACEHOLDER_TEXT_STYLE_DARK_BG = "fg:#5a5a5a italic"
@@ -66,6 +68,14 @@ class PromptToolkitInput(InputProviderABC):
66
68
  at_token_pattern=AT_TOKEN_PATTERN,
67
69
  )
68
70
 
71
+ # Select completion selected color based on terminal background
72
+ if self._is_light_terminal_background is True:
73
+ completion_selected = COMPLETION_SELECTED_LIGHT_BG
74
+ elif self._is_light_terminal_background is False:
75
+ completion_selected = COMPLETION_SELECTED_DARK_BG
76
+ else:
77
+ completion_selected = COMPLETION_SELECTED_UNKNOWN_BG
78
+
69
79
  self._session: PromptSession[str] = PromptSession(
70
80
  [(INPUT_PROMPT_STYLE, prompt)],
71
81
  history=FileHistory(str(history_path)),
@@ -86,8 +96,8 @@ class PromptToolkitInput(InputProviderABC):
86
96
  "scrollbar.button": "bg:default",
87
97
  "completion-menu.completion": f"bg:default fg:{COMPLETION_MENU}",
88
98
  "completion-menu.meta.completion": f"bg:default fg:{COMPLETION_MENU}",
89
- "completion-menu.completion.current": f"noreverse bg:default fg:{COMPLETION_SELECTED} bold",
90
- "completion-menu.meta.completion.current": f"bg:default fg:{COMPLETION_SELECTED} bold",
99
+ "completion-menu.completion.current": f"noreverse bg:default fg:{completion_selected} bold",
100
+ "completion-menu.meta.completion.current": f"bg:default fg:{completion_selected} bold",
91
101
  }
92
102
  ),
93
103
  )