rubycode 0.1.2 → 0.1.3
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/.rubocop.yml +4 -0
- data/README.md +33 -4
- data/config/locales/en.yml +87 -0
- data/config/system_prompt.md +54 -0
- data/config/tools/done.json +2 -2
- data/config/tools/update.json +25 -0
- data/config/tools/write.json +21 -0
- data/docs/images/demo.png +0 -0
- data/lib/rubycode/adapters/ollama.rb +76 -3
- data/lib/rubycode/agent_loop.rb +41 -16
- data/lib/rubycode/client/approval_handler.rb +70 -0
- data/lib/rubycode/client/display_formatter.rb +32 -12
- data/lib/rubycode/client/response_handler.rb +20 -12
- data/lib/rubycode/client.rb +25 -36
- data/lib/rubycode/configuration.rb +8 -1
- data/lib/rubycode/database.rb +50 -0
- data/lib/rubycode/errors.rb +12 -0
- data/lib/rubycode/models/base.rb +68 -0
- data/lib/rubycode/models/memory.rb +57 -0
- data/lib/rubycode/models.rb +4 -0
- data/lib/rubycode/tools/base.rb +1 -10
- data/lib/rubycode/tools/bash.rb +10 -7
- data/lib/rubycode/tools/read.rb +3 -0
- data/lib/rubycode/tools/update.rb +80 -0
- data/lib/rubycode/tools/write.rb +57 -0
- data/lib/rubycode/tools.rb +4 -0
- data/lib/rubycode/version.rb +1 -1
- data/lib/rubycode/views/agent_loop/adapter_error.rb +14 -0
- data/lib/rubycode/views/agent_loop/iteration_footer.rb +17 -0
- data/lib/rubycode/views/agent_loop/iteration_header.rb +24 -0
- data/lib/rubycode/views/agent_loop/response_received.rb +17 -0
- data/lib/rubycode/views/agent_loop/retry_status.rb +14 -0
- data/lib/rubycode/views/agent_loop/thinking_status.rb +17 -0
- data/lib/rubycode/views/agent_loop/tool_error.rb +14 -0
- data/lib/rubycode/views/agent_loop.rb +8 -0
- data/lib/rubycode/views/bash_approval.rb +28 -0
- data/lib/rubycode/views/cli/configuration_table.rb +28 -0
- data/lib/rubycode/views/cli/error_display.rb +19 -0
- data/lib/rubycode/views/cli/error_message.rb +17 -0
- data/lib/rubycode/views/cli/exit_message.rb +17 -0
- data/lib/rubycode/views/cli/interrupt_message.rb +17 -0
- data/lib/rubycode/views/cli/memory_cleared_message.rb +17 -0
- data/lib/rubycode/views/cli/ready_message.rb +17 -0
- data/lib/rubycode/views/cli/response_box.rb +29 -0
- data/lib/rubycode/views/cli.rb +11 -0
- data/lib/rubycode/views/formatter/debug_tool_info.rb +17 -0
- data/lib/rubycode/views/formatter/info_message.rb +17 -0
- data/lib/rubycode/views/formatter/minimal_tool_info.rb +26 -0
- data/lib/rubycode/views/formatter/tool_result.rb +20 -0
- data/lib/rubycode/views/formatter.rb +7 -0
- data/lib/rubycode/views/response_handler/agent_finished.rb +31 -0
- data/lib/rubycode/views/response_handler/complete_message.rb +31 -0
- data/lib/rubycode/views/response_handler/max_iterations.rb +29 -0
- data/lib/rubycode/views/response_handler/max_tool_calls.rb +29 -0
- data/lib/rubycode/views/response_handler/tool_injection_warning.rb +17 -0
- data/lib/rubycode/views/response_handler.rb +8 -0
- data/lib/rubycode/views/skip_notification.rb +15 -0
- data/lib/rubycode/views/update_approval.rb +36 -0
- data/lib/rubycode/views/welcome.rb +27 -0
- data/lib/rubycode/views/write_approval.rb +42 -0
- data/lib/rubycode/views.rb +12 -0
- data/lib/rubycode.rb +9 -1
- data/rubycode_cli.rb +41 -51
- metadata +220 -5
- data/lib/rubycode/history.rb +0 -39
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Tools
|
|
7
|
+
# Tool for creating new files
|
|
8
|
+
class Write < Base
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def perform(params)
|
|
12
|
+
file_path = params["file_path"]
|
|
13
|
+
content = params["content"]
|
|
14
|
+
full_path = resolve_path(file_path)
|
|
15
|
+
|
|
16
|
+
validate_file_does_not_exist(file_path, full_path)
|
|
17
|
+
request_approval(file_path, content)
|
|
18
|
+
create_file(full_path, content)
|
|
19
|
+
build_result(file_path, content)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate_file_does_not_exist(file_path, full_path)
|
|
23
|
+
return unless File.exist?(full_path)
|
|
24
|
+
|
|
25
|
+
raise ToolError, I18n.t("rubycode.errors.file_exists", path: file_path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def request_approval(file_path, content)
|
|
29
|
+
approval_handler = context[:approval_handler]
|
|
30
|
+
return if approval_handler.request_write_approval(file_path, content)
|
|
31
|
+
|
|
32
|
+
raise ToolError, I18n.t("rubycode.errors.user_cancelled_write")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create_file(full_path, content)
|
|
36
|
+
dir = File.dirname(full_path)
|
|
37
|
+
FileUtils.mkdir_p(dir)
|
|
38
|
+
File.write(full_path, content)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def build_result(file_path, content)
|
|
42
|
+
ToolResult.new(
|
|
43
|
+
content: "Created '#{file_path}' (#{content.lines.count} lines)",
|
|
44
|
+
metadata: {
|
|
45
|
+
file_path: file_path,
|
|
46
|
+
lines: content.lines.count,
|
|
47
|
+
bytes: content.bytesize
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resolve_path(file_path)
|
|
53
|
+
File.absolute_path?(file_path) ? file_path : File.join(root_path, file_path)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/rubycode/tools.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative "tools/base"
|
|
|
4
4
|
require_relative "tools/bash"
|
|
5
5
|
require_relative "tools/read"
|
|
6
6
|
require_relative "tools/search"
|
|
7
|
+
require_relative "tools/write"
|
|
8
|
+
require_relative "tools/update"
|
|
7
9
|
require_relative "tools/done"
|
|
8
10
|
|
|
9
11
|
module RubyCode
|
|
@@ -14,6 +16,8 @@ module RubyCode
|
|
|
14
16
|
Bash,
|
|
15
17
|
Read,
|
|
16
18
|
Search,
|
|
19
|
+
Write,
|
|
20
|
+
Update,
|
|
17
21
|
Done
|
|
18
22
|
].freeze
|
|
19
23
|
|
data/lib/rubycode/version.rb
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module AgentLoop
|
|
8
|
+
# Builds iteration footer
|
|
9
|
+
class IterationFooter
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
pastel.cyan("└────────────────────────────────────────────────")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module AgentLoop
|
|
8
|
+
# Builds iteration header with tool list
|
|
9
|
+
class IterationHeader
|
|
10
|
+
def self.build(iteration:, tool_calls:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
|
|
13
|
+
header = pastel.cyan("┌─ Iteration #{iteration} ─────────────────────────")
|
|
14
|
+
tool_list = tool_calls.each_with_index.map do |tool_call, idx|
|
|
15
|
+
tool_name = tool_call.dig("function", "name")
|
|
16
|
+
" #{pastel.dim("#{idx + 1}.")} #{tool_name}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
[header, *tool_list].join("\n")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module AgentLoop
|
|
8
|
+
# Builds response received status
|
|
9
|
+
class ResponseReceived
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"#{pastel.green("✓")} Response received"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCode
|
|
4
|
+
module Views
|
|
5
|
+
module AgentLoop
|
|
6
|
+
# Builds retry status message
|
|
7
|
+
class RetryStatus
|
|
8
|
+
def self.build(attempt:, max_retries:, delay:, error:)
|
|
9
|
+
"⚠ Request failed: #{error}. Retrying in #{delay.round(1)}s... (attempt #{attempt}/#{max_retries})"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module AgentLoop
|
|
8
|
+
# Builds thinking status indicator
|
|
9
|
+
class ThinkingStatus
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"#{pastel.dim("→")} Thinking..."
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Agent loop view components
|
|
4
|
+
require_relative "agent_loop/thinking_status"
|
|
5
|
+
require_relative "agent_loop/response_received"
|
|
6
|
+
require_relative "agent_loop/iteration_header"
|
|
7
|
+
require_relative "agent_loop/iteration_footer"
|
|
8
|
+
require_relative "agent_loop/tool_error"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
# Builds bash command approval prompt display
|
|
8
|
+
class BashApproval
|
|
9
|
+
def self.build(command:, base_command:, safe_commands:)
|
|
10
|
+
pastel = Pastel.new
|
|
11
|
+
|
|
12
|
+
[
|
|
13
|
+
"",
|
|
14
|
+
pastel.red("━" * 80),
|
|
15
|
+
pastel.bold.red("⚠ WARNING: Non-Whitelisted Command"),
|
|
16
|
+
"#{pastel.cyan("Command:")} #{command}",
|
|
17
|
+
"#{pastel.cyan("Base command:")} #{base_command}",
|
|
18
|
+
pastel.red("─" * 80),
|
|
19
|
+
pastel.yellow("This command is not in the safe whitelist:"),
|
|
20
|
+
pastel.dim("Safe commands: #{safe_commands.join(", ")}"),
|
|
21
|
+
"",
|
|
22
|
+
pastel.yellow("⚠ Only approve if you trust this command will not cause harm"),
|
|
23
|
+
pastel.red("━" * 80)
|
|
24
|
+
].join("\n")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
|
|
6
|
+
module RubyCode
|
|
7
|
+
module Views
|
|
8
|
+
module Cli
|
|
9
|
+
# Builds configuration table display
|
|
10
|
+
class ConfigurationTable
|
|
11
|
+
def self.build(directory:, model:, debug_mode:)
|
|
12
|
+
pastel = Pastel.new
|
|
13
|
+
|
|
14
|
+
table = TTY::Table.new(
|
|
15
|
+
header: [pastel.bold("Setting"), pastel.bold("Value")],
|
|
16
|
+
rows: [
|
|
17
|
+
["Directory", directory],
|
|
18
|
+
["Model", model],
|
|
19
|
+
["Debug Mode", debug_mode ? pastel.green("ON") : pastel.dim("OFF")]
|
|
20
|
+
]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
"\n#{table.render(:unicode, padding: [0, 1])}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds error display with backtrace
|
|
9
|
+
class ErrorDisplay
|
|
10
|
+
def self.build(error:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
backtrace_preview = error.backtrace.first(3).join("\n")
|
|
13
|
+
|
|
14
|
+
"\n#{pastel.red("[ERROR]")} #{error.message}\n#{pastel.dim(backtrace_preview)}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds error message display
|
|
9
|
+
class ErrorMessage
|
|
10
|
+
def self.build(message:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"\n#{pastel.red("[ERROR]")} #{message}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds goodbye/exit message
|
|
9
|
+
class ExitMessage
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"\n#{pastel.green(I18n.t("rubycode.cli.exit"))}\n"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds interrupt notification message
|
|
9
|
+
class InterruptMessage
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"\n#{pastel.yellow("[INTERRUPTED]")} Type 'exit' to quit or continue chatting."
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds memory cleared confirmation message
|
|
9
|
+
class MemoryClearedMessage
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"#{pastel.yellow("✓")} #{I18n.t("rubycode.cli.memory_cleared")}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Cli
|
|
8
|
+
# Builds ready status message
|
|
9
|
+
class ReadyMessage
|
|
10
|
+
def self.build
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"\n#{pastel.green("✓")} #{pastel.bold("Ready!")} #{I18n.t("rubycode.cli.ready")}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
require "tty-markdown"
|
|
5
|
+
|
|
6
|
+
module RubyCode
|
|
7
|
+
module Views
|
|
8
|
+
module Cli
|
|
9
|
+
# Builds response box with markdown rendering
|
|
10
|
+
class ResponseBox
|
|
11
|
+
def self.build(response:)
|
|
12
|
+
pastel = Pastel.new
|
|
13
|
+
|
|
14
|
+
header = "\n#{pastel.magenta("╔═══ Agent Response ═══")}"
|
|
15
|
+
footer = pastel.magenta("╚══════════════════════")
|
|
16
|
+
|
|
17
|
+
# Try to parse as markdown, fallback to plain
|
|
18
|
+
content = begin
|
|
19
|
+
TTY::Markdown.parse(response, width: 80)
|
|
20
|
+
rescue StandardError
|
|
21
|
+
response
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
"#{header}\n#{content}\n#{footer}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CLI view components
|
|
4
|
+
require_relative "cli/error_message"
|
|
5
|
+
require_relative "cli/configuration_table"
|
|
6
|
+
require_relative "cli/ready_message"
|
|
7
|
+
require_relative "cli/exit_message"
|
|
8
|
+
require_relative "cli/memory_cleared_message"
|
|
9
|
+
require_relative "cli/response_box"
|
|
10
|
+
require_relative "cli/interrupt_message"
|
|
11
|
+
require_relative "cli/error_display"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Formatter
|
|
8
|
+
# Builds debug mode tool information display
|
|
9
|
+
class DebugToolInfo
|
|
10
|
+
def self.build(tool_name:, arguments:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
"\n#{pastel.yellow("[TOOL]")} #{tool_name}\n #{pastel.dim("Args:")} #{arguments.inspect}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Formatter
|
|
8
|
+
# Builds info message display
|
|
9
|
+
class InfoMessage
|
|
10
|
+
def self.build(message:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
" #{pastel.dim("ℹ #{message}")}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
|
|
6
|
+
module RubyCode
|
|
7
|
+
module Views
|
|
8
|
+
module Formatter
|
|
9
|
+
# Builds minimal tool info table display
|
|
10
|
+
class MinimalToolInfo
|
|
11
|
+
def self.build(label:, value:)
|
|
12
|
+
pastel = Pastel.new
|
|
13
|
+
|
|
14
|
+
table = TTY::Table.new(rows: [
|
|
15
|
+
[
|
|
16
|
+
pastel.cyan(label),
|
|
17
|
+
value
|
|
18
|
+
]
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
" #{table.render(:basic, padding: [0, 1])}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module Formatter
|
|
8
|
+
# Builds tool result summary display
|
|
9
|
+
class ToolResult
|
|
10
|
+
def self.build(result:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
first_line = result.lines.first&.strip || "(empty)"
|
|
13
|
+
suffix = result.lines.count > 1 ? "... (#{result.lines.count} lines)" : ""
|
|
14
|
+
|
|
15
|
+
" #{pastel.green("✓")} #{pastel.dim("Result:")} #{first_line}#{suffix}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module ResponseHandler
|
|
8
|
+
# Builds agent finished success box
|
|
9
|
+
class AgentFinished
|
|
10
|
+
def self.build(iteration:, total_tool_calls:)
|
|
11
|
+
success_box = TTY::Box.frame(
|
|
12
|
+
title: { top_left: " #{I18n.t("rubycode.response_handler.agent_finished.title")} " },
|
|
13
|
+
border: :light,
|
|
14
|
+
padding: 1,
|
|
15
|
+
style: {
|
|
16
|
+
fg: :green,
|
|
17
|
+
border: {
|
|
18
|
+
fg: :green
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
) do
|
|
22
|
+
I18n.t("rubycode.response_handler.agent_finished.message",
|
|
23
|
+
iterations: iteration,
|
|
24
|
+
tool_calls: total_tool_calls)
|
|
25
|
+
end
|
|
26
|
+
"\n#{success_box}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module ResponseHandler
|
|
8
|
+
# Builds task completion success box
|
|
9
|
+
class CompleteMessage
|
|
10
|
+
def self.build(iteration:, total_tool_calls:)
|
|
11
|
+
success_box = TTY::Box.frame(
|
|
12
|
+
title: { top_left: " #{I18n.t("rubycode.response_handler.complete.title")} " },
|
|
13
|
+
border: :light,
|
|
14
|
+
padding: 1,
|
|
15
|
+
style: {
|
|
16
|
+
fg: :green,
|
|
17
|
+
border: {
|
|
18
|
+
fg: :green
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
) do
|
|
22
|
+
I18n.t("rubycode.response_handler.complete.message",
|
|
23
|
+
iterations: iteration,
|
|
24
|
+
tool_calls: total_tool_calls)
|
|
25
|
+
end
|
|
26
|
+
"\n#{success_box}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module ResponseHandler
|
|
8
|
+
# Builds max iterations warning box
|
|
9
|
+
class MaxIterations
|
|
10
|
+
def self.build(max_iterations:)
|
|
11
|
+
error_box = TTY::Box.frame(
|
|
12
|
+
title: { top_left: " #{I18n.t("rubycode.response_handler.max_iterations.title")} " },
|
|
13
|
+
border: :thick,
|
|
14
|
+
padding: 1,
|
|
15
|
+
style: {
|
|
16
|
+
fg: :yellow,
|
|
17
|
+
border: {
|
|
18
|
+
fg: :yellow
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
) do
|
|
22
|
+
I18n.t("rubycode.response_handler.max_iterations.message", max: max_iterations)
|
|
23
|
+
end
|
|
24
|
+
"\n#{error_box}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-box"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module ResponseHandler
|
|
8
|
+
# Builds max tool calls warning box
|
|
9
|
+
class MaxToolCalls
|
|
10
|
+
def self.build(max_tool_calls:)
|
|
11
|
+
error_box = TTY::Box.frame(
|
|
12
|
+
title: { top_left: " ⚠ WARNING " },
|
|
13
|
+
border: :thick,
|
|
14
|
+
padding: 1,
|
|
15
|
+
style: {
|
|
16
|
+
fg: :yellow,
|
|
17
|
+
border: {
|
|
18
|
+
fg: :yellow
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
) do
|
|
22
|
+
"Reached maximum tool calls (#{max_tool_calls})\nStopping to prevent excessive operations."
|
|
23
|
+
end
|
|
24
|
+
"\n#{error_box}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module RubyCode
|
|
6
|
+
module Views
|
|
7
|
+
module ResponseHandler
|
|
8
|
+
# Builds tool injection warning message
|
|
9
|
+
class ToolInjectionWarning
|
|
10
|
+
def self.build(iteration:)
|
|
11
|
+
pastel = Pastel.new
|
|
12
|
+
" #{pastel.yellow("[WARNING]")} No tool calls - injecting reminder (iteration #{iteration})"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Response handler view components
|
|
4
|
+
require_relative "response_handler/max_iterations"
|
|
5
|
+
require_relative "response_handler/max_tool_calls"
|
|
6
|
+
require_relative "response_handler/complete_message"
|
|
7
|
+
require_relative "response_handler/agent_finished"
|
|
8
|
+
require_relative "response_handler/tool_injection_warning"
|