Class: RubyCoded::Chat::State

Inherits:
Object
  • Object
show all
Includes:
LoginFlow, MessageAssistant, MessageTokenTracking, Messages, ModelSelection, PlanTracking, Scrollable, TokenCost, ToolConfirmation
Defined in:
lib/ruby_coded/chat/state.rb,
lib/ruby_coded/chat/state/messages.rb,
lib/ruby_coded/chat/state/login_flow.rb,
lib/ruby_coded/chat/state/scrollable.rb,
lib/ruby_coded/chat/state/token_cost.rb,
lib/ruby_coded/chat/state/plan_tracking.rb,
lib/ruby_coded/chat/state/model_selection.rb,
lib/ruby_coded/chat/state/login_flow_steps.rb,
lib/ruby_coded/chat/state/message_assistant.rb,
lib/ruby_coded/chat/state/tool_confirmation.rb,
lib/ruby_coded/chat/state/message_token_tracking.rb

Overview

This class is used to manage the state of the chat

Defined Under Namespace

Modules: LoginFlow, LoginFlowSteps, MessageAssistant, MessageTokenTracking, Messages, ModelSelection, PlanTracking, Scrollable, TokenCost, ToolConfirmation

Constant Summary collapse

MIN_RENDER_INTERVAL =
0.05

Constants included from TokenCost

TokenCost::CACHE_CREATION_INPUT_MULTIPLIER, TokenCost::UNPRICED_DEFAULTS

Constants included from MessageTokenTracking

MessageTokenTracking::TOKEN_KEYS

Constants included from Messages

Messages::ZERO_TOKEN_USAGE

Instance Attribute Summary collapse

Attributes included from LoginFlow

#login_auth_method, #login_error, #login_items, #login_key_buffer, #login_key_cursor, #login_oauth_result, #login_provider, #login_select_index, #login_step

Attributes included from PlanTracking

#clarification_custom_input, #clarification_index, #clarification_input_mode, #clarification_options, #clarification_question

Attributes included from ToolConfirmation

#tool_cv

Attributes included from Messages

#message_generation

Instance Method Summary collapse

Methods included from LoginFlow

#append_to_login_key, #delete_last_login_key_char, #enter_login_flow!, #exit_login_flow!, #init_login_flow, #login_active?, #login_advance_to_api_key!, #login_advance_to_auth_method!, #login_advance_to_oauth!, #login_clear_oauth_result!, #login_provider_module, #login_select_down, #login_select_up, #login_selected_item, #login_set_error!, #login_set_oauth_result!

Methods included from TokenCost

#init_token_cost, #session_cost_breakdown, #total_session_cost

Methods included from PlanTracking

#activate_plan_mode!, #append_to_clarification_input, #clarification_down, #clarification_up, #clear_plan!, #current_plan, #deactivate_plan_mode!, #delete_last_clarification_char, #enter_plan_clarification!, #exit_plan_clarification!, #has_unsaved_plan?, #init_plan_tracking, #mark_plan_saved!, #plan_clarification?, #plan_mode_active?, #plan_saved?, #selected_clarification_option, #toggle_clarification_input_mode!, #update_current_plan!

Methods included from ToolConfirmation

#auto_approve_tools?, #awaiting_tool_confirmation?, #clear_tool_confirmation!, #disable_auto_approve!, #enable_auto_approve!, #init_tool_confirmation, #pending_tool_args, #pending_tool_name, #request_tool_confirmation!, #resolve_tool_confirmation!, #tool_confirmation_response, #tool_confirmation_response=

Methods included from Scrollable

#scroll_down, #scroll_to_bottom, #scroll_to_top, #scroll_up, #update_scroll_metrics

Methods included from MessageTokenTracking

#token_usage_by_model, #total_input_tokens, #total_output_tokens, #total_thinking_tokens, #update_last_message_tokens

Methods included from MessageAssistant

#ensure_last_is_assistant!, #fail_last_assistant, #last_assistant_empty?, #reset_last_assistant_content, #streaming_append

Methods included from Messages

#add_message, #append_to_last_message, #build_message, #clear_messages!, #init_messages, #messages_snapshot

Methods included from ModelSelection

#append_to_model_filter, #delete_last_filter_char, #enter_model_select!, #exit_model_select!, #filtered_model_list, #model_select?, #model_select_down, #model_select_show_all?, #model_select_up, #selected_model

Constructor Details

#initialize(model:) ⇒ State

Returns a new instance of State.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/ruby_coded/chat/state.rb', line 34

def initialize(model:)
  @model = model
  # String.new: literals like "" are frozen under frozen_string_literal
  @input_buffer = String.new
  @cursor_position = 0
  @input_scroll_offset = 0
  @messages = []
  @streaming = false
  @should_quit = false
  @agentic_mode = false
  @codex_mode = false
  @mutex = Mutex.new
  @dirty = true
  @last_render_at = 0.0
  @scroll_offset = 0
  @total_lines = 0
  @visible_height = 0
  @mode = :chat
  @model_list = []
  @model_select_index = 0
  @model_select_filter = String.new
  @model_select_show_all = false
  init_messages
  init_tool_confirmation
  init_plan_tracking
  init_token_cost
  
  init_plugin_state
end

Instance Attribute Details

#codex_modeObject

Returns the value of attribute codex_mode.



30
31
32
# File 'lib/ruby_coded/chat/state.rb', line 30

def codex_mode
  @codex_mode
end

#cursor_positionObject (readonly)

Returns the value of attribute cursor_position.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def cursor_position
  @cursor_position
end

#input_bufferObject (readonly)

Returns the value of attribute input_buffer.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def input_buffer
  @input_buffer
end

#input_scroll_offsetObject (readonly)

Returns the value of attribute input_scroll_offset.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def input_scroll_offset
  @input_scroll_offset
end

#messagesObject (readonly)

Returns the value of attribute messages.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def messages
  @messages
end

#modeObject (readonly)

Returns the value of attribute mode.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def mode
  @mode
end

#modelObject

Returns the value of attribute model.



30
31
32
# File 'lib/ruby_coded/chat/state.rb', line 30

def model
  @model
end

#model_listObject (readonly)

Returns the value of attribute model_list.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def model_list
  @model_list
end

#model_select_filterObject (readonly)

Returns the value of attribute model_select_filter.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def model_select_filter
  @model_select_filter
end

#model_select_indexObject (readonly)

Returns the value of attribute model_select_index.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def model_select_index
  @model_select_index
end

#mutexObject (readonly)

Returns the value of attribute mutex.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def mutex
  @mutex
end

#scroll_offsetObject (readonly)

Returns the value of attribute scroll_offset.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def scroll_offset
  @scroll_offset
end

#should_quitObject

Returns the value of attribute should_quit.



30
31
32
# File 'lib/ruby_coded/chat/state.rb', line 30

def should_quit
  @should_quit
end

#streamingObject

Returns the value of attribute streaming.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def streaming
  @streaming
end

#tui_suspend_reasonObject (readonly)

Returns the value of attribute tui_suspend_reason.



27
28
29
# File 'lib/ruby_coded/chat/state.rb', line 27

def tui_suspend_reason
  @tui_suspend_reason
end

Instance Method Details

#agentic_mode=(value) ⇒ Object



77
78
79
80
# File 'lib/ruby_coded/chat/state.rb', line 77

def agentic_mode=(value)
  @agentic_mode = value
  mark_dirty!
end

#agentic_mode?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/ruby_coded/chat/state.rb', line 73

def agentic_mode?
  @agentic_mode
end

#append_to_input(text) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/ruby_coded/chat/state.rb', line 128

def append_to_input(text)
  @input_buffer.insert(@cursor_position, text)
  @cursor_position += text.length
  update_input_scroll_offset
  mark_dirty!
  reset_command_completion_index if respond_to?(:reset_command_completion_index, true)
end

#clear_input!Object



178
179
180
181
182
183
# File 'lib/ruby_coded/chat/state.rb', line 178

def clear_input!
  @input_buffer.clear
  @cursor_position = 0
  @input_scroll_offset = 0
  mark_dirty!
end

#clear_tui_suspend!Object



207
208
209
210
# File 'lib/ruby_coded/chat/state.rb', line 207

def clear_tui_suspend!
  @tui_suspend_reason = nil
  @tui_suspend_metadata = nil
end

#consume_input!Object



185
186
187
188
189
190
191
# File 'lib/ruby_coded/chat/state.rb', line 185

def consume_input!
  input = @input_buffer.dup
  @input_buffer.clear
  @cursor_position = 0
  @input_scroll_offset = 0
  input
end

#delete_last_charObject



136
137
138
139
140
141
142
143
144
# File 'lib/ruby_coded/chat/state.rb', line 136

def delete_last_char
  return if @cursor_position <= 0

  @input_buffer.slice!(@cursor_position - 1)
  @cursor_position -= 1
  update_input_scroll_offset
  mark_dirty!
  reset_command_completion_index if respond_to?(:reset_command_completion_index, true)
end

#dirty?Boolean

Returns:

  • (Boolean)


86
87
88
89
90
91
92
93
94
# File 'lib/ruby_coded/chat/state.rb', line 86

def dirty?
  @mutex.synchronize do
    return false unless @dirty
    return true unless @streaming

    now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    (now - @last_render_at) >= MIN_RENDER_INTERVAL
  end
end

#mark_clean!Object



96
97
98
99
100
101
# File 'lib/ruby_coded/chat/state.rb', line 96

def mark_clean!
  @mutex.synchronize do
    @dirty = false
    @last_render_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  end
end

#mark_dirty!Object



103
104
105
# File 'lib/ruby_coded/chat/state.rb', line 103

def mark_dirty!
  @mutex.synchronize { @dirty = true }
end

#move_cursor_leftObject



146
147
148
149
150
151
152
# File 'lib/ruby_coded/chat/state.rb', line 146

def move_cursor_left
  return if @cursor_position <= 0

  @cursor_position -= 1
  update_input_scroll_offset
  mark_dirty!
end

#move_cursor_rightObject



154
155
156
157
158
159
160
# File 'lib/ruby_coded/chat/state.rb', line 154

def move_cursor_right
  return if @cursor_position >= @input_buffer.length

  @cursor_position += 1
  update_input_scroll_offset
  mark_dirty!
end

#move_cursor_to_endObject



170
171
172
173
174
175
176
# File 'lib/ruby_coded/chat/state.rb', line 170

def move_cursor_to_end
  return if @cursor_position == @input_buffer.length

  @cursor_position = @input_buffer.length
  update_input_scroll_offset
  mark_dirty!
end

#move_cursor_to_startObject



162
163
164
165
166
167
168
# File 'lib/ruby_coded/chat/state.rb', line 162

def move_cursor_to_start
  return if @cursor_position == 0

  @cursor_position = 0
  update_input_scroll_offset
  mark_dirty!
end

#request_tui_suspend!(reason, **metadata) ⇒ Object



193
194
195
196
197
# File 'lib/ruby_coded/chat/state.rb', line 193

def request_tui_suspend!(reason, **)
  @tui_suspend_reason = reason
  @tui_suspend_metadata = 
  mark_dirty!
end

#should_quit?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/ruby_coded/chat/state.rb', line 82

def should_quit?
  @should_quit
end

#streaming?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/ruby_coded/chat/state.rb', line 69

def streaming?
  @streaming
end

#tui_suspend_metadataObject



203
204
205
# File 'lib/ruby_coded/chat/state.rb', line 203

def 
  @tui_suspend_metadata || {}
end

#tui_suspend_requested?Boolean

Returns:

  • (Boolean)


199
200
201
# File 'lib/ruby_coded/chat/state.rb', line 199

def tui_suspend_requested?
  !@tui_suspend_reason.nil?
end

#update_input_scroll_offsetObject

Updates the horizontal scroll offset of the input area so the cursor is always visible. Call this after every cursor / buffer change. visible_width is set by the renderer each frame via update_input_visible_width.



111
112
113
114
115
116
117
118
119
120
# File 'lib/ruby_coded/chat/state.rb', line 111

def update_input_scroll_offset
  visible = @input_visible_width || 0
  return if visible <= 0

  if @cursor_position < @input_scroll_offset
    @input_scroll_offset = @cursor_position
  elsif @cursor_position >= @input_scroll_offset + visible
    @input_scroll_offset = @cursor_position - visible + 1
  end
end

#update_input_visible_width(width) ⇒ Object

Called by the renderer so the state knows how many characters fit on screen (inner width minus the prompt prefix).



124
125
126
# File 'lib/ruby_coded/chat/state.rb', line 124

def update_input_visible_width(width)
  @input_visible_width = width
end