kward 0.67.1 → 0.68.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 +20 -0
- data/Gemfile.lock +2 -2
- data/README.md +5 -5
- data/doc/authentication.md +24 -1
- data/doc/configuration.md +9 -2
- data/doc/extensibility.md +1 -1
- data/doc/getting-started.md +4 -6
- data/doc/plugins.md +0 -2
- data/doc/releasing.md +7 -8
- data/doc/rpc.md +6 -6
- data/doc/usage.md +5 -2
- data/doc/web-search.md +2 -2
- data/kward.gemspec +4 -0
- data/lib/kward/agent.rb +29 -2
- data/lib/kward/ansi.rb +3 -0
- data/lib/kward/auth/anthropic_oauth.rb +291 -0
- data/lib/kward/auth/file.rb +2 -0
- data/lib/kward/auth/github_oauth.rb +3 -0
- data/lib/kward/auth/openai_oauth.rb +4 -0
- data/lib/kward/auth/openrouter_api_key.rb +2 -0
- data/lib/kward/cancellation.rb +3 -0
- data/lib/kward/cli/auth_commands.rb +82 -0
- data/lib/kward/cli/commands.rb +222 -0
- data/lib/kward/cli/compaction.rb +25 -0
- data/lib/kward/cli/doctor.rb +121 -0
- data/lib/kward/cli/interactive_turn.rb +225 -0
- data/lib/kward/cli/memory_commands.rb +133 -0
- data/lib/kward/cli/plugins.rb +112 -0
- data/lib/kward/cli/prompt_interface.rb +132 -0
- data/lib/kward/cli/rendering.rb +389 -0
- data/lib/kward/cli/runtime_helpers.rb +159 -0
- data/lib/kward/cli/sessions.rb +376 -0
- data/lib/kward/cli/settings.rb +663 -0
- data/lib/kward/cli/slash_commands.rb +112 -0
- data/lib/kward/cli/stats.rb +64 -0
- data/lib/kward/cli/tool_summaries.rb +153 -0
- data/lib/kward/cli.rb +38 -2790
- data/lib/kward/cli_transcript_formatter.rb +4 -7
- data/lib/kward/clipboard.rb +1 -0
- data/lib/kward/compaction/file_operation_tracker.rb +3 -0
- data/lib/kward/compactor.rb +29 -7
- data/lib/kward/config_files.rb +33 -24
- data/lib/kward/conversation.rb +70 -5
- data/lib/kward/events.rb +2 -0
- data/lib/kward/export_path.rb +2 -0
- data/lib/kward/image_attachments.rb +2 -0
- data/lib/kward/markdown_transcript.rb +2 -0
- data/lib/kward/memory/manager.rb +13 -0
- data/lib/kward/message_access.rb +23 -2
- data/lib/kward/message_text.rb +45 -0
- data/lib/kward/model/chat_invocation.rb +2 -0
- data/lib/kward/model/client.rb +295 -77
- data/lib/kward/model/context_overflow.rb +2 -0
- data/lib/kward/model/context_usage.rb +3 -0
- data/lib/kward/model/model_info.rb +143 -4
- data/lib/kward/model/payloads.rb +166 -13
- data/lib/kward/model/retry_message.rb +2 -0
- data/lib/kward/model/stream_parser.rb +129 -0
- data/lib/kward/pan/server.rb +3 -1
- data/lib/kward/plugin_registry.rb +12 -0
- data/lib/kward/private_file.rb +2 -0
- data/lib/kward/prompt_interface/banner.rb +3 -0
- data/lib/kward/prompt_interface/composer_controller.rb +262 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +172 -0
- data/lib/kward/prompt_interface/composer_state.rb +221 -0
- data/lib/kward/prompt_interface/key_handler.rb +365 -0
- data/lib/kward/prompt_interface/layout.rb +31 -0
- data/lib/kward/prompt_interface/overlay_renderer.rb +111 -0
- data/lib/kward/prompt_interface/prompt_renderer.rb +91 -0
- data/lib/kward/prompt_interface/question_prompt.rb +328 -0
- data/lib/kward/prompt_interface/runtime_state.rb +59 -0
- data/lib/kward/prompt_interface/screen.rb +186 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +242 -0
- data/lib/kward/prompt_interface/slash_overlay.rb +102 -0
- data/lib/kward/prompt_interface/stream_state.rb +65 -0
- data/lib/kward/prompt_interface/transcript_buffer.rb +85 -0
- data/lib/kward/prompt_interface/transcript_renderer.rb +142 -0
- data/lib/kward/prompt_interface.rb +69 -1832
- data/lib/kward/prompts/commands.rb +2 -0
- data/lib/kward/prompts/templates.rb +3 -0
- data/lib/kward/prompts.rb +2 -0
- data/lib/kward/question_contract.rb +66 -0
- data/lib/kward/resources/avatar_kward_logo.rb +2 -0
- data/lib/kward/resources/pixel_logo.rb +2 -0
- data/lib/kward/rpc/attachment_normalizer.rb +60 -0
- data/lib/kward/rpc/auth_manager.rb +65 -11
- data/lib/kward/rpc/config_manager.rb +11 -0
- data/lib/kward/rpc/prompt_bridge.rb +5 -26
- data/lib/kward/rpc/redactor.rb +3 -0
- data/lib/kward/rpc/runtime_payloads.rb +4 -1
- data/lib/kward/rpc/server.rb +36 -9
- data/lib/kward/rpc/session_manager.rb +121 -345
- data/lib/kward/rpc/session_metrics.rb +68 -0
- data/lib/kward/rpc/session_tree.rb +48 -0
- data/lib/kward/rpc/session_tree_rows.rb +208 -0
- data/lib/kward/rpc/tool_event_normalizer.rb +3 -0
- data/lib/kward/rpc/tool_metadata.rb +3 -0
- data/lib/kward/rpc/transcript_normalizer.rb +3 -0
- data/lib/kward/rpc/transport.rb +3 -0
- data/lib/kward/session_diff.rb +2 -0
- data/lib/kward/session_store.rb +114 -24
- data/lib/kward/session_trash.rb +1 -0
- data/lib/kward/session_tree_renderer.rb +8 -41
- data/lib/kward/session_tree_tool_display.rb +56 -0
- data/lib/kward/skills/registry.rb +3 -0
- data/lib/kward/starter_pack_installer.rb +1 -0
- data/lib/kward/steering.rb +2 -0
- data/lib/kward/telemetry/logger.rb +3 -0
- data/lib/kward/telemetry/stats.rb +3 -0
- data/lib/kward/tools/ask_user_question.rb +20 -32
- data/lib/kward/tools/base.rb +8 -0
- data/lib/kward/tools/code_search.rb +5 -0
- data/lib/kward/tools/edit_file.rb +5 -0
- data/lib/kward/tools/list_directory.rb +5 -0
- data/lib/kward/tools/read_file.rb +5 -0
- data/lib/kward/tools/read_skill.rb +5 -0
- data/lib/kward/tools/registry.rb +33 -2
- data/lib/kward/tools/run_shell_command.rb +5 -0
- data/lib/kward/tools/search/code.rb +7 -0
- data/lib/kward/tools/search/web.rb +17 -14
- data/lib/kward/tools/tool_call.rb +25 -5
- data/lib/kward/tools/web_search.rb +7 -1
- data/lib/kward/tools/write_file.rb +5 -0
- data/lib/kward/transcript_export.rb +2 -0
- data/lib/kward/version.rb +2 -1
- data/lib/kward/workspace.rb +45 -5
- metadata +43 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Command-line frontend that coordinates terminal interaction, sessions, tools, and model turns.
|
|
4
|
+
class CLI
|
|
5
|
+
# Interactive slash-command parsing and dispatch helpers.
|
|
6
|
+
module SlashCommands
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_local_slash_command(command, agent, session_store)
|
|
10
|
+
name, argument = parse_slash_command(command)
|
|
11
|
+
case name
|
|
12
|
+
when "status"
|
|
13
|
+
run_busy_local_command_and_requeue { print_status }
|
|
14
|
+
[true, nil]
|
|
15
|
+
when "stats"
|
|
16
|
+
run_busy_local_command_and_requeue { print_stats(argument) }
|
|
17
|
+
[true, nil]
|
|
18
|
+
when "memory"
|
|
19
|
+
activity = memory_summarize_command?(argument) ? "summarizing" : "loading"
|
|
20
|
+
run_busy_local_command_and_requeue(activity: activity) { handle_memory_command(argument, agent) }
|
|
21
|
+
[true, nil]
|
|
22
|
+
when "redraw"
|
|
23
|
+
run_busy_local_command_and_requeue { @prompt.redraw if @prompt.respond_to?(:redraw) }
|
|
24
|
+
[true, nil]
|
|
25
|
+
when "settings"
|
|
26
|
+
configure_settings(agent.conversation)
|
|
27
|
+
[true, nil]
|
|
28
|
+
when "login"
|
|
29
|
+
login_interactively
|
|
30
|
+
[true, nil]
|
|
31
|
+
when "model"
|
|
32
|
+
models = run_busy_local_command_and_requeue { normalized_available_models }
|
|
33
|
+
configure_model(agent.conversation, models: models)
|
|
34
|
+
[true, nil]
|
|
35
|
+
when "openrouter/catalog"
|
|
36
|
+
run_busy_local_command_and_requeue { print_openrouter_catalog }
|
|
37
|
+
[true, nil]
|
|
38
|
+
when "reasoning"
|
|
39
|
+
configure_reasoning(agent.conversation)
|
|
40
|
+
[true, nil]
|
|
41
|
+
when "reload"
|
|
42
|
+
run_busy_local_command_and_requeue { reload_plugins(agent.conversation) }
|
|
43
|
+
[true, nil]
|
|
44
|
+
when "new"
|
|
45
|
+
[true, run_busy_local_command_and_requeue { start_new_session(session_store) }]
|
|
46
|
+
when "resume"
|
|
47
|
+
[true, run_busy_local_command_and_requeue do
|
|
48
|
+
path = argument.to_s.strip
|
|
49
|
+
path = select_session_path(session_store) if session_store && path.empty?
|
|
50
|
+
resume_session(session_store, path)
|
|
51
|
+
end]
|
|
52
|
+
when "name"
|
|
53
|
+
run_busy_local_command_and_requeue { rename_session(argument) }
|
|
54
|
+
[true, nil]
|
|
55
|
+
when "clone"
|
|
56
|
+
[true, run_busy_local_command_and_requeue { clone_session(session_store, agent) }]
|
|
57
|
+
when "tree"
|
|
58
|
+
[true, run_busy_local_command_and_requeue { navigate_session_tree(session_store) }]
|
|
59
|
+
when "copy"
|
|
60
|
+
run_busy_local_command_and_requeue { copy_session_text(agent.conversation, argument) }
|
|
61
|
+
[true, nil]
|
|
62
|
+
when "export"
|
|
63
|
+
run_busy_local_command_and_requeue { export_session(agent.conversation, argument) }
|
|
64
|
+
[true, nil]
|
|
65
|
+
when "compact"
|
|
66
|
+
run_busy_local_command_and_requeue(activity: "compacting") { compact_context(agent, argument) }
|
|
67
|
+
[true, nil]
|
|
68
|
+
else
|
|
69
|
+
return run_plugin_command(name, argument, agent) if plugin_command_for(name)
|
|
70
|
+
|
|
71
|
+
[false, nil]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_slash_command(command)
|
|
76
|
+
PromptCommands.parse(command) || [nil, ""]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Writes the status output for the terminal CLI flow.
|
|
80
|
+
def print_status
|
|
81
|
+
lines = [STATUS_MESSAGE]
|
|
82
|
+
lines << ""
|
|
83
|
+
lines << auto_compaction_status_line
|
|
84
|
+
if @active_session
|
|
85
|
+
lines << "Session: #{@active_session.name || @active_session.id}"
|
|
86
|
+
lines << "File: #{@active_session.path}"
|
|
87
|
+
end
|
|
88
|
+
lines.compact!
|
|
89
|
+
@prompt.say("\n#{colored(assistant_output_prompt, :green, :bold)} #{lines.join("\n")}\n")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def auto_compaction_status_line
|
|
93
|
+
settings = Kward::Compaction::Settings.from_config
|
|
94
|
+
return "Auto-compaction: disabled" unless settings.enabled
|
|
95
|
+
|
|
96
|
+
context_window = composer_context_window
|
|
97
|
+
return "Auto-compaction: enabled, unknown context window" unless context_window.to_i.positive?
|
|
98
|
+
|
|
99
|
+
reserve_tokens = Kward::Compactor.auto_compaction_reserve_tokens(
|
|
100
|
+
context_window: context_window,
|
|
101
|
+
configured_reserve_tokens: settings.reserve_tokens
|
|
102
|
+
)
|
|
103
|
+
percent = ((reserve_tokens.to_f / context_window.to_i) * 100).round(1)
|
|
104
|
+
"Auto-compaction reserve: #{reserve_tokens} tokens (#{percent}% of #{context_window})"
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
warn "Auto-compaction status unavailable: #{e.message}"
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Command-line frontend that coordinates terminal interaction, sessions, tools, and model turns.
|
|
4
|
+
class CLI
|
|
5
|
+
# Token statistics command helpers mixed into the CLI frontend.
|
|
6
|
+
module Stats
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def export_token_stats(arguments)
|
|
10
|
+
options = parse_token_stats_options(arguments)
|
|
11
|
+
csv = TelemetryStats.new.token_usage_csv(options[:range], bucket: options[:bucket])
|
|
12
|
+
if options[:output]
|
|
13
|
+
File.write(options[:output], csv)
|
|
14
|
+
else
|
|
15
|
+
$stdout.write(csv)
|
|
16
|
+
end
|
|
17
|
+
rescue ArgumentError => e
|
|
18
|
+
warn e.message
|
|
19
|
+
warn "Usage: kward stats tokens [range] [--bucket second|minute|hour|day|week|month|year] [--output path]"
|
|
20
|
+
exit 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_token_stats_options(arguments)
|
|
24
|
+
remaining = []
|
|
25
|
+
bucket = nil
|
|
26
|
+
output = nil
|
|
27
|
+
index = 0
|
|
28
|
+
while index < arguments.length
|
|
29
|
+
argument = arguments[index]
|
|
30
|
+
case argument
|
|
31
|
+
when "--bucket"
|
|
32
|
+
index += 1
|
|
33
|
+
raise ArgumentError, "Missing value for --bucket" if index >= arguments.length
|
|
34
|
+
|
|
35
|
+
bucket = arguments[index]
|
|
36
|
+
when /\A--bucket=(.+)\z/
|
|
37
|
+
bucket = Regexp.last_match(1)
|
|
38
|
+
when "--output"
|
|
39
|
+
index += 1
|
|
40
|
+
raise ArgumentError, "Missing value for --output" if index >= arguments.length
|
|
41
|
+
|
|
42
|
+
output = arguments[index]
|
|
43
|
+
when /\A--output=(.+)\z/
|
|
44
|
+
output = Regexp.last_match(1)
|
|
45
|
+
else
|
|
46
|
+
remaining << argument
|
|
47
|
+
end
|
|
48
|
+
index += 1
|
|
49
|
+
end
|
|
50
|
+
{ range: remaining.join(" "), bucket: bucket, output: output }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Writes the stats output for the terminal CLI flow.
|
|
54
|
+
def print_stats(argument)
|
|
55
|
+
result = TelemetryStats.new.collect(argument)
|
|
56
|
+
@prompt.say("\n#{colored(assistant_output_prompt, :green, :bold)} #{TelemetryStats.format(result)}\n")
|
|
57
|
+
rescue ArgumentError => e
|
|
58
|
+
message = e.message == TelemetryStats::USAGE ? e.message : "#{e.message}\n#{TelemetryStats::USAGE}"
|
|
59
|
+
@prompt.say("\n#{message}\n")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Command-line frontend that coordinates terminal interaction, sessions, tools, and model turns.
|
|
4
|
+
class CLI
|
|
5
|
+
# Compact tool-output summaries for terminal display and restored transcripts.
|
|
6
|
+
module ToolSummaries
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def tool_result_summary(tool_call, content)
|
|
10
|
+
name = tool_call_name(tool_call)
|
|
11
|
+
args = tool_call_args(tool_call)
|
|
12
|
+
text = content.to_s
|
|
13
|
+
return error_tool_summary(name, args, text) if text.start_with?("Error:", "Declined:")
|
|
14
|
+
|
|
15
|
+
case name
|
|
16
|
+
when "read_file"
|
|
17
|
+
read_file_summary(args, text)
|
|
18
|
+
when "write_file", "edit_file"
|
|
19
|
+
file_change_summary(name, args, text)
|
|
20
|
+
when "run_shell_command"
|
|
21
|
+
shell_command_summary(args, text)
|
|
22
|
+
when "web_search"
|
|
23
|
+
web_search_summary(args, text)
|
|
24
|
+
else
|
|
25
|
+
generic_tool_summary(name, text)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def limit_tool_output_lines(content, line_limit)
|
|
30
|
+
lines = content.to_s.lines
|
|
31
|
+
return content.to_s if lines.length <= line_limit
|
|
32
|
+
|
|
33
|
+
kept_lines = lines.first(line_limit - 1).join
|
|
34
|
+
omitted_lines = lines.length - (line_limit - 1)
|
|
35
|
+
suffix = omitted_lines == 1 ? "line" : "lines"
|
|
36
|
+
notice = "...[truncated #{omitted_lines} #{suffix}]"
|
|
37
|
+
kept_lines.end_with?("\n") || kept_lines.empty? ? "#{kept_lines}#{notice}" : "#{kept_lines}\n#{notice}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_file_summary(args, content)
|
|
41
|
+
path = args["path"] || args[:path] || "(unknown path)"
|
|
42
|
+
"read_file: #{path}\n#{content.lines.count} lines, #{content.bytesize} bytes"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def file_change_summary(name, args, content)
|
|
46
|
+
path = args["path"] || args[:path] || path_from_tool_result(content) || "(unknown path)"
|
|
47
|
+
concise = content.lines.first.to_s.strip
|
|
48
|
+
concise = "completed" if concise.empty?
|
|
49
|
+
"#{name}: #{path}\n#{concise}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def shell_command_summary(args, content)
|
|
53
|
+
command = args["command"] || args[:command] || ""
|
|
54
|
+
lines = ["run_shell_command: #{command}".strip]
|
|
55
|
+
lines << "Exit status: #{shell_exit_status(content) || "unknown"}"
|
|
56
|
+
stdout = shell_section(content, "STDOUT")
|
|
57
|
+
stderr = shell_section(content, "STDERR")
|
|
58
|
+
lines << compact_stream_summary("stdout", stdout) unless stdout.empty?
|
|
59
|
+
lines << compact_stream_summary("stderr", stderr) unless stderr.empty?
|
|
60
|
+
lines.join("\n")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def web_search_summary(args, content)
|
|
64
|
+
queries = Array(args["queries"] || args[:queries]).map(&:to_s)
|
|
65
|
+
queries = web_search_queries_from_content(content) if queries.empty?
|
|
66
|
+
counts = web_search_result_counts(content)
|
|
67
|
+
lines = ["web_search"]
|
|
68
|
+
queries.each do |query|
|
|
69
|
+
lines << "#{query}: #{counts.fetch(query, 0)} result(s)"
|
|
70
|
+
end
|
|
71
|
+
lines << "#{web_search_total_count(content)} result(s)" if queries.empty?
|
|
72
|
+
lines.join("\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def error_tool_summary(name, args, content)
|
|
76
|
+
path = args["path"] || args[:path]
|
|
77
|
+
command = args["command"] || args[:command]
|
|
78
|
+
context = path || command
|
|
79
|
+
[name, context, content.lines.first.to_s.strip].compact.reject(&:empty?).join("\n")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def generic_tool_summary(name, content)
|
|
83
|
+
text = content.to_s
|
|
84
|
+
return "#{name}: #{text}" if text.length <= RESTORED_TOOL_OUTPUT_LIMIT
|
|
85
|
+
|
|
86
|
+
"#{name}: #{text[0, RESTORED_TOOL_OUTPUT_LIMIT]}\n...[truncated #{text.length - RESTORED_TOOL_OUTPUT_LIMIT} bytes]"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def compact_stream_summary(label, text)
|
|
90
|
+
summary = text.strip
|
|
91
|
+
summary = summary[0, 500] + "\n...[truncated #{summary.length - 500} chars]" if summary.length > 500
|
|
92
|
+
"#{label} (#{text.bytesize} bytes):#{summary.empty? ? "" : "\n#{summary}"}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def shell_exit_status(content)
|
|
96
|
+
content.match(/^Exit status: ([^\n]+)/)&.[](1)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def shell_section(content, name)
|
|
100
|
+
match = content.match(/^#{Regexp.escape(name)}:\n(.*?)(?=\nSTD(?:OUT|ERR):\n|\z)/m)
|
|
101
|
+
match ? match[1] : ""
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def web_search_queries_from_content(content)
|
|
105
|
+
content.scan(/^## Query: (.+)$/).flatten
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def web_search_result_counts(content)
|
|
109
|
+
counts = {}
|
|
110
|
+
current_query = nil
|
|
111
|
+
content.each_line do |line|
|
|
112
|
+
if (match = line.match(/^## Query: (.+)$/))
|
|
113
|
+
current_query = match[1]
|
|
114
|
+
counts[current_query] ||= 0
|
|
115
|
+
elsif current_query && line.match?(/^\d+\. /)
|
|
116
|
+
counts[current_query] += 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
counts
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def web_search_total_count(content)
|
|
123
|
+
content.each_line.count { |line| line.match?(/^\d+\. /) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def path_from_tool_result(content)
|
|
127
|
+
content.match(/\b(?:to|file|Edited)\s+([^:\n]+?)(?:\s|:|\z)/)&.[](1)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def tool_call_name(tool_call)
|
|
131
|
+
ToolCall.name(tool_call) || "unknown_tool"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def tool_call_args(tool_call)
|
|
135
|
+
ToolCall.arguments(tool_call)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def tool_command(tool_call)
|
|
139
|
+
name = tool_call_name(tool_call)
|
|
140
|
+
args = tool_call_args(tool_call)
|
|
141
|
+
|
|
142
|
+
if name == "run_shell_command"
|
|
143
|
+
args["command"] || args[:command] || ""
|
|
144
|
+
elsif args.empty?
|
|
145
|
+
name.to_s
|
|
146
|
+
else
|
|
147
|
+
"#{name} #{JSON.dump(args)}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|