llm.rb 13.0.0 → 14.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +505 -14
- data/README.md +484 -50
- data/bin/llm.rb +148 -0
- data/data/anthropic.json +206 -263
- data/data/bedrock.json +2138 -1860
- data/data/deepinfra.json +1003 -624
- data/data/deepseek.json +38 -34
- data/data/google.json +1079 -371
- data/data/mistral.json +448 -368
- data/data/moonshot.json +384 -0
- data/data/openai.json +974 -1343
- data/data/xai.json +154 -126
- data/data/zai.json +191 -191
- data/lib/llm/agent.rb +123 -20
- data/lib/llm/context.rb +71 -88
- data/lib/llm/cost.rb +23 -17
- data/lib/llm/error.rb +0 -8
- data/lib/llm/function/array.rb +3 -3
- data/lib/llm/function/async/task.rb +2 -0
- data/lib/llm/function/fiber/task.rb +2 -0
- data/lib/llm/function/fork/task.rb +2 -0
- data/lib/llm/function/ractor/task.rb +2 -0
- data/lib/llm/function/sequential/group.rb +4 -1
- data/lib/llm/function/sequential/task.rb +1 -1
- data/lib/llm/function/task.rb +4 -0
- data/lib/llm/function/thread/task.rb +2 -0
- data/lib/llm/function.rb +33 -6
- data/lib/llm/guard/loop.rb +89 -0
- data/lib/llm/guard/null.rb +19 -0
- data/lib/llm/guard.rb +61 -0
- data/lib/llm/provider.rb +36 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
- data/lib/llm/providers/anthropic.rb +2 -9
- data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
- data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
- data/lib/llm/providers/bedrock.rb +1 -8
- data/lib/llm/providers/google/stream_parser.rb +1 -0
- data/lib/llm/providers/google.rb +1 -8
- data/lib/llm/providers/mistral.rb +1 -1
- data/lib/llm/providers/moonshot.rb +76 -0
- data/lib/llm/providers/ollama.rb +2 -9
- data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
- data/lib/llm/providers/openai/responses.rb +7 -9
- data/lib/llm/providers/openai/stream_parser.rb +1 -0
- data/lib/llm/providers/openai.rb +4 -11
- data/lib/llm/repl/bar.rb +4 -3
- data/lib/llm/repl/{transcript.rb → buffer.rb} +69 -29
- data/lib/llm/repl/color.rb +78 -0
- data/lib/llm/repl/command.rb +12 -5
- data/lib/llm/repl/commands/compact.rb +2 -2
- data/lib/llm/repl/commands/help.rb +3 -5
- data/lib/llm/repl/input/char.rb +46 -0
- data/lib/llm/repl/input/row.rb +39 -0
- data/lib/llm/repl/input.rb +251 -66
- data/lib/llm/repl/markdown/table.rb +11 -3
- data/lib/llm/repl/markdown.rb +34 -8
- data/lib/llm/repl/node.rb +37 -0
- data/lib/llm/repl/status.rb +42 -7
- data/lib/llm/repl/stream.rb +18 -6
- data/lib/llm/repl/walker.rb +3 -2
- data/lib/llm/repl/window.rb +54 -35
- data/lib/llm/repl.rb +74 -32
- data/lib/llm/skill.rb +20 -4
- data/lib/llm/stream.rb +8 -7
- data/lib/llm/tool.rb +29 -0
- data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
- data/lib/llm/tools/git.rb +3 -0
- data/lib/llm/tools/mkdir.rb +3 -0
- data/lib/llm/tools/rg.rb +3 -0
- data/lib/llm/tools/ruby.rb +46 -0
- data/lib/llm/tools/shell.rb +3 -0
- data/lib/llm/tracer/pretty_logger.rb +127 -0
- data/lib/llm/tracer.rb +1 -0
- data/lib/llm/transformer/null.rb +21 -0
- data/lib/llm/transformer.rb +55 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +12 -2
- data/llm.gemspec +9 -2
- data/resources/deepdive/advanced/cancellation.md +74 -0
- data/resources/deepdive/advanced/compaction.md +83 -0
- data/resources/deepdive/advanced/context.md +267 -0
- data/resources/deepdive/advanced/guard.md +371 -0
- data/resources/deepdive/advanced/tracer.md +180 -0
- data/resources/deepdive/advanced/transformer.md +67 -0
- data/resources/deepdive/advanced/transports.md +45 -0
- data/resources/deepdive/everything_else/audio.md +122 -0
- data/resources/deepdive/everything_else/cost.md +99 -0
- data/resources/deepdive/everything_else/images.md +89 -0
- data/resources/deepdive/everything_else/object.md +108 -0
- data/resources/deepdive/everything_else/ocr.md +48 -0
- data/resources/deepdive/fundamentals/agents.md +202 -0
- data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
- data/resources/deepdive/fundamentals/concurrency.md +104 -0
- data/resources/deepdive/fundamentals/database.md +449 -0
- data/resources/deepdive/fundamentals/embeddings.md +157 -0
- data/resources/deepdive/fundamentals/repl.md +87 -0
- data/resources/deepdive/fundamentals/schema.md +61 -0
- data/resources/deepdive/fundamentals/skills.md +106 -0
- data/resources/deepdive/fundamentals/stream.md +110 -0
- data/resources/deepdive/fundamentals/tools.md +265 -0
- data/resources/deepdive/protocols/a2a.md +106 -0
- data/resources/deepdive/protocols/mcp.md +111 -0
- data/resources/deepdive.md +58 -1792
- metadata +51 -7
- data/lib/llm/loop_guard.rb +0 -107
data/lib/llm/providers/openai.rb
CHANGED
|
@@ -146,17 +146,17 @@ module LLM
|
|
|
146
146
|
|
|
147
147
|
##
|
|
148
148
|
# Returns the default model for chat completions
|
|
149
|
-
# @see https://platform.openai.com/docs/models/gpt-5.
|
|
149
|
+
# @see https://platform.openai.com/docs/models/gpt-5.6-luna gpt-5.6-luna
|
|
150
150
|
# @return [String]
|
|
151
151
|
def default_model
|
|
152
|
-
"gpt-5.
|
|
152
|
+
"gpt-5.6-luna"
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
##
|
|
156
156
|
# @param [LLM::Function] fn
|
|
157
157
|
# @return [Hash]
|
|
158
158
|
def adapt_function(fn)
|
|
159
|
-
params = fn.params
|
|
159
|
+
params = fn.params.to_h
|
|
160
160
|
{
|
|
161
161
|
type: "function", name: fn.name,
|
|
162
162
|
function: {name: fn.name, description: fn.description, parameters: params}
|
|
@@ -230,20 +230,13 @@ module LLM
|
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
def build_complete_request(prompt, params, role)
|
|
233
|
-
messages =
|
|
233
|
+
messages = build_messages(prompt, params, role)
|
|
234
234
|
body = LLM.json.dump({messages: adapt(messages, mode: :complete).flatten}.merge!(params))
|
|
235
235
|
req = LLM::Transport::Request.post(completions_path, headers)
|
|
236
236
|
transport.set_body_stream(req, StringIO.new(body))
|
|
237
237
|
[req, messages]
|
|
238
238
|
end
|
|
239
239
|
|
|
240
|
-
def build_complete_messages(prompt, params, role)
|
|
241
|
-
if LLM::Prompt === prompt
|
|
242
|
-
[*(params.delete(:messages) || []), *prompt]
|
|
243
|
-
else
|
|
244
|
-
[*(params.delete(:messages) || []), Message.new(role, prompt)]
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
240
|
|
|
248
241
|
def extract_user_input(messages, fallback:)
|
|
249
242
|
message = messages.reverse.find(&:user?) || messages.last
|
data/lib/llm/repl/bar.rb
CHANGED
|
@@ -17,7 +17,8 @@ class LLM::Repl
|
|
|
17
17
|
FREE = " "
|
|
18
18
|
|
|
19
19
|
##
|
|
20
|
-
# @param [Integer] used
|
|
20
|
+
# @param [Integer, nil] used
|
|
21
|
+
# The used context, or nil when it is unknown (eg after compaction).
|
|
21
22
|
# @param [Integer] total
|
|
22
23
|
# @param [Integer] width
|
|
23
24
|
# @return [LLM::Repl::Bar]
|
|
@@ -36,11 +37,11 @@ class LLM::Repl
|
|
|
36
37
|
private
|
|
37
38
|
|
|
38
39
|
##
|
|
39
|
-
# @param [Integer] used
|
|
40
|
+
# @param [Integer, nil] used
|
|
40
41
|
# @param [Integer] total
|
|
41
42
|
# @return [[String, Integer]]
|
|
42
43
|
def remainder(used, total)
|
|
43
|
-
return ["???", width] if total <= 0
|
|
44
|
+
return ["???", width] if used.nil? || total <= 0
|
|
44
45
|
diff = total - used
|
|
45
46
|
return ["0%", 0] if diff <= 0
|
|
46
47
|
remaining = (diff.fdiv(total) * 100).round(2)
|
|
@@ -14,12 +14,14 @@ class LLM::Repl
|
|
|
14
14
|
# It also maintains a cursor that tracks the active row
|
|
15
15
|
# by its index number. The streaming path reuses a single
|
|
16
16
|
# row by overwriting its contents repeatedly.
|
|
17
|
-
class
|
|
18
|
-
WIDTH = 80
|
|
19
|
-
|
|
17
|
+
class Buffer
|
|
20
18
|
##
|
|
21
|
-
# @
|
|
22
|
-
|
|
19
|
+
# @param [LLM::Repl] repl
|
|
20
|
+
# An instance of {LLM::Repl LLM::Repl}.
|
|
21
|
+
# @return [LLM::Repl::Buffer]
|
|
22
|
+
def initialize(repl)
|
|
23
|
+
@repl = repl
|
|
24
|
+
@window = repl.window
|
|
23
25
|
@rows = [[]]
|
|
24
26
|
@cursor = nil
|
|
25
27
|
@snapshot = nil
|
|
@@ -27,37 +29,45 @@ class LLM::Repl
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
##
|
|
30
|
-
# @param [String] chars
|
|
32
|
+
# @param [String, Array] chars
|
|
31
33
|
# @param [Object] attrs
|
|
32
34
|
# @param [Symbol] method
|
|
33
35
|
# @return [void]
|
|
34
36
|
def write(chars, attrs = nil, method: :append)
|
|
35
|
-
|
|
37
|
+
case chars
|
|
38
|
+
when Array then chunks = chars
|
|
39
|
+
else chunks = [Node.new(chars.to_s, attrs)]
|
|
40
|
+
end
|
|
36
41
|
self.method(method).call(chunks)
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
##
|
|
40
|
-
#
|
|
41
|
-
# @param [String]
|
|
45
|
+
# @param [String] user
|
|
46
|
+
# @param [String, Array] content
|
|
42
47
|
# @param [Symbol] method
|
|
43
48
|
# @return [void]
|
|
44
|
-
def
|
|
45
|
-
chunks =
|
|
46
|
-
|
|
49
|
+
def write_message(user, content, method: :append)
|
|
50
|
+
chunks = [Node.new("#{user}:\n", Curses::A_BOLD | Color.blue)]
|
|
51
|
+
case content
|
|
52
|
+
when Array then chunks.concat(content)
|
|
53
|
+
else chunks.push(Node.new(content))
|
|
54
|
+
end
|
|
55
|
+
chunks.push(Node.new("\n\n"))
|
|
56
|
+
write(chunks, method:)
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
##
|
|
50
|
-
#
|
|
60
|
+
# Open the buffer.
|
|
51
61
|
# @return [void]
|
|
52
|
-
def
|
|
62
|
+
def open
|
|
53
63
|
@cursor = @rows.size - 1
|
|
54
64
|
@snapshot = @rows.map(&:dup)
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
##
|
|
58
|
-
#
|
|
68
|
+
# Close the buffer.
|
|
59
69
|
# @return [void]
|
|
60
|
-
def
|
|
70
|
+
def close
|
|
61
71
|
@cursor = nil
|
|
62
72
|
@snapshot = nil
|
|
63
73
|
end
|
|
@@ -81,6 +91,15 @@ class LLM::Repl
|
|
|
81
91
|
@offset = 0
|
|
82
92
|
end
|
|
83
93
|
|
|
94
|
+
##
|
|
95
|
+
# The width of the centered content area -
|
|
96
|
+
# with 20% on each side. The rest makes up
|
|
97
|
+
# the width of the buffer.
|
|
98
|
+
# @return [Integer]
|
|
99
|
+
def width
|
|
100
|
+
(Curses.cols * 0.6).floor
|
|
101
|
+
end
|
|
102
|
+
|
|
84
103
|
##
|
|
85
104
|
# @param [Integer] height
|
|
86
105
|
# @return [Array<String>]
|
|
@@ -93,9 +112,13 @@ class LLM::Repl
|
|
|
93
112
|
|
|
94
113
|
private
|
|
95
114
|
|
|
115
|
+
##
|
|
116
|
+
# @api private
|
|
117
|
+
attr_reader :repl, :window
|
|
118
|
+
|
|
96
119
|
##
|
|
97
120
|
# Appends a new row
|
|
98
|
-
# @param [Array<
|
|
121
|
+
# @param [Array<Node>] chunks
|
|
99
122
|
# One or more chunks.
|
|
100
123
|
# @return [void]
|
|
101
124
|
def append(chunks)
|
|
@@ -104,29 +127,46 @@ class LLM::Repl
|
|
|
104
127
|
|
|
105
128
|
##
|
|
106
129
|
# Replaces the content of the active row
|
|
107
|
-
# @param [Array<
|
|
130
|
+
# @param [Array<Node>] chunks
|
|
108
131
|
# One or more chunks.
|
|
109
132
|
# @return [void]
|
|
110
133
|
def replace(chunks)
|
|
111
134
|
@rows = @snapshot.map(&:dup)
|
|
112
|
-
|
|
135
|
+
chunks.each { wrap(_1, @rows) }
|
|
113
136
|
end
|
|
114
137
|
|
|
115
138
|
##
|
|
116
|
-
# Given a chunk this method wraps text
|
|
117
|
-
#
|
|
118
|
-
# the current
|
|
119
|
-
#
|
|
120
|
-
#
|
|
139
|
+
# Given a chunk this method wraps text to the buffer's
|
|
140
|
+
# width. The text is split into words; a word that does
|
|
141
|
+
# not fit on the current row moves to the next. Only a
|
|
142
|
+
# single word longer than the whole width is hard-broken,
|
|
143
|
+
# so text is never clipped by the window.
|
|
121
144
|
def wrap(chunk, rows)
|
|
122
145
|
attrs = chunk[:attrs]
|
|
123
|
-
chunk[:text].to_s.
|
|
124
|
-
if
|
|
125
|
-
rows << []
|
|
126
|
-
elsif char == " " and sum(rows.last) >= WIDTH
|
|
146
|
+
chunk[:text].to_s.scan(/\n|[^\s]+|\s+/) do |token|
|
|
147
|
+
if token == "\n"
|
|
127
148
|
rows << []
|
|
149
|
+
elsif token.match?(/\s/)
|
|
150
|
+
if sum(rows.last) + token.length <= width
|
|
151
|
+
rows.last << Node.new(token, attrs)
|
|
152
|
+
end
|
|
153
|
+
elsif sum(rows.last) + token.length > width
|
|
154
|
+
if sum(rows.last) == 0
|
|
155
|
+
##
|
|
156
|
+
# A single word wider than the row: break it
|
|
157
|
+
# every width characters.
|
|
158
|
+
token.scan(/.{1,#{width}}/).each do |piece|
|
|
159
|
+
rows.last << Node.new(piece, attrs)
|
|
160
|
+
if sum(rows.last) >= width
|
|
161
|
+
rows << []
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
else
|
|
165
|
+
rows << []
|
|
166
|
+
rows.last << Node.new(token, attrs)
|
|
167
|
+
end
|
|
128
168
|
else
|
|
129
|
-
rows.last <<
|
|
169
|
+
rows.last << Node.new(token, attrs)
|
|
130
170
|
end
|
|
131
171
|
end
|
|
132
172
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Color LLM::Repl::Color} module returns
|
|
6
|
+
# bitmasks that are understood as colors by the Curses
|
|
7
|
+
# library. They can be bitwise OR'ed with other attributes,
|
|
8
|
+
# such as Curses::A_BOLD.
|
|
9
|
+
module Color
|
|
10
|
+
##
|
|
11
|
+
# Enable color and initialize 8 color pairs.
|
|
12
|
+
# @return [void]
|
|
13
|
+
def self.enable
|
|
14
|
+
Curses.start_color
|
|
15
|
+
##
|
|
16
|
+
# The first argument is the color pairs ID, and
|
|
17
|
+
# it is mapped back to a bitmask via {Curses.color_pair}.
|
|
18
|
+
# The second argument is the foreground color, and
|
|
19
|
+
# the third argument is the background color.
|
|
20
|
+
Curses.init_pair(1, Curses::COLOR_BLACK , Curses::COLOR_WHITE)
|
|
21
|
+
Curses.init_pair(2, Curses::COLOR_BLUE , Curses::COLOR_BLACK)
|
|
22
|
+
Curses.init_pair(3, Curses::COLOR_CYAN , Curses::COLOR_BLACK)
|
|
23
|
+
Curses.init_pair(4, Curses::COLOR_GREEN , Curses::COLOR_BLACK)
|
|
24
|
+
Curses.init_pair(5, Curses::COLOR_MAGENTA , Curses::COLOR_BLACK)
|
|
25
|
+
Curses.init_pair(6, Curses::COLOR_RED , Curses::COLOR_BLACK)
|
|
26
|
+
Curses.init_pair(7, Curses::COLOR_WHITE , Curses::COLOR_BLACK)
|
|
27
|
+
Curses.init_pair(8, Curses::COLOR_YELLOW , Curses::COLOR_BLACK)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# @return [Integer]
|
|
32
|
+
def self.black
|
|
33
|
+
Curses.color_pair(1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @return [Integer]
|
|
38
|
+
def self.blue
|
|
39
|
+
Curses.color_pair(2)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @return [Integer]
|
|
44
|
+
def self.cyan
|
|
45
|
+
Curses.color_pair(3)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# @return [Integer]
|
|
50
|
+
def self.green
|
|
51
|
+
Curses.color_pair(4)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# @return [Integer]
|
|
56
|
+
def self.magneta
|
|
57
|
+
Curses.color_pair(5)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# @return [Integer]
|
|
62
|
+
def self.red
|
|
63
|
+
Curses.color_pair(6)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# @return [Integer]
|
|
68
|
+
def self.white
|
|
69
|
+
Curses.color_pair(7)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# @return [Integer]
|
|
74
|
+
def self.yellow
|
|
75
|
+
Curses.color_pair(8)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/llm/repl/command.rb
CHANGED
|
@@ -176,12 +176,19 @@ class LLM::Repl
|
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
##
|
|
179
|
-
# Write a string to the
|
|
180
|
-
# @param [String]
|
|
179
|
+
# Write a string to the buffer
|
|
180
|
+
# @param [String] content
|
|
181
|
+
# @return [void]
|
|
182
|
+
def write(content)
|
|
183
|
+
write_message "command(#{self.class.name})", content
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# @param [String] user
|
|
188
|
+
# @param [String] content
|
|
181
189
|
# @return [void]
|
|
182
|
-
def
|
|
183
|
-
@repl.
|
|
184
|
-
@repl.write(str)
|
|
190
|
+
def write_message(user, content)
|
|
191
|
+
@repl.write_message(user, content)
|
|
185
192
|
end
|
|
186
193
|
|
|
187
194
|
##
|
|
@@ -11,13 +11,11 @@ class LLM::Repl
|
|
|
11
11
|
# @return [void]
|
|
12
12
|
def call(name: nil)
|
|
13
13
|
if name.nil?
|
|
14
|
-
write(
|
|
14
|
+
write(self.class.help)
|
|
15
15
|
elsif command = LLM::Command.find_by(name:)
|
|
16
|
-
write(
|
|
16
|
+
write(command.help)
|
|
17
17
|
else
|
|
18
|
-
write "
|
|
19
|
-
"\nThat command doesn't exist." \
|
|
20
|
-
"\n\n"
|
|
18
|
+
write "no help for #{name} was found"
|
|
21
19
|
end
|
|
22
20
|
end
|
|
23
21
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl::Input
|
|
4
|
+
##
|
|
5
|
+
# A single editable character of the input.
|
|
6
|
+
class Char
|
|
7
|
+
##
|
|
8
|
+
# @param [String] char
|
|
9
|
+
# @return [LLM::Repl::Input::Char]
|
|
10
|
+
def initialize(char)
|
|
11
|
+
@char = char
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Returns true when both strings returned
|
|
16
|
+
# by the #{to_s} method are considered equal
|
|
17
|
+
# by {String#==}.
|
|
18
|
+
# @param [#to_s] other
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def ==(other)
|
|
21
|
+
to_s == other.to_s
|
|
22
|
+
end
|
|
23
|
+
alias_method :eql?, :==
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Returns a hash consistent with {#eql?},
|
|
27
|
+
# so equal chars behave as the same key in
|
|
28
|
+
# a Hash or Set.
|
|
29
|
+
# @return [Integer]
|
|
30
|
+
def hash
|
|
31
|
+
to_s.hash
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def empty?
|
|
37
|
+
@char == ""
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# @return [String]
|
|
42
|
+
def to_s
|
|
43
|
+
@char.dup
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl::Input
|
|
4
|
+
##
|
|
5
|
+
# One line of the input. `chars` holds the editable characters.
|
|
6
|
+
# `break_type` records how this line got started: an auto-wrap
|
|
7
|
+
# (`:space`) or a real newline (`:newline`). The first row has no
|
|
8
|
+
# break type. It decides how the input is flattened on {Input#take}.
|
|
9
|
+
class Row
|
|
10
|
+
##
|
|
11
|
+
# @return [Array<Input::Char>]
|
|
12
|
+
attr_reader :chars
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# @return [Symbol, nil]
|
|
16
|
+
# `:space` or `:newline`, or nil for the first row.
|
|
17
|
+
attr_accessor :break_type
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# @param [Symbol, nil] break_type
|
|
21
|
+
# How this line was started.
|
|
22
|
+
def initialize(break_type = nil)
|
|
23
|
+
@chars = []
|
|
24
|
+
@break_type = break_type
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# @return [String]
|
|
29
|
+
def to_s
|
|
30
|
+
chars.map(&:to_s).join
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def empty?
|
|
36
|
+
chars.empty?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|