ruboty-ai_agent 0.2.0 → 0.3.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/AGENTS.md +8 -0
- data/CHANGELOG.md +14 -2
- data/README.md +2 -0
- data/Rakefile +2 -0
- data/lib/ruboty/ai_agent/actions/add_mcp.rb +7 -2
- data/lib/ruboty/ai_agent/actions/base.rb +11 -1
- data/lib/ruboty/ai_agent/actions/chat.rb +15 -6
- data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +1 -1
- data/lib/ruboty/ai_agent/actions/list_mcp.rb +50 -3
- data/lib/ruboty/ai_agent/agent.rb +3 -3
- data/lib/ruboty/ai_agent/commands/base.rb +12 -7
- data/lib/ruboty/ai_agent/commands/prompt_command.rb +3 -4
- data/lib/ruboty/ai_agent/commands.rb +6 -16
- data/lib/ruboty/ai_agent/database/query_methods.rb +1 -1
- data/lib/ruboty/ai_agent/llm/openai.rb +1 -1
- data/lib/ruboty/ai_agent/mcp_clients.rb +2 -15
- data/lib/ruboty/ai_agent/request.rb +17 -0
- data/lib/ruboty/ai_agent/tool.rb +11 -3
- data/lib/ruboty/ai_agent/tool_definitions/base.rb +84 -0
- data/lib/ruboty/ai_agent/tool_definitions/think.rb +41 -0
- data/lib/ruboty/ai_agent/tool_definitions.rb +19 -0
- data/lib/ruboty/ai_agent/user_mcp_client.rb +3 -2
- data/lib/ruboty/ai_agent/version.rb +1 -1
- data/lib/ruboty/ai_agent.rb +2 -0
- data/lib/ruboty/handlers/ai_agent.rb +1 -1
- data/sig/generated/ruboty/ai_agent/actions/base.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/chat.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +11 -0
- data/sig/generated/ruboty/ai_agent/agent.rbs +1 -1
- data/sig/generated/ruboty/ai_agent/commands/base.rbs +6 -5
- data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +2 -3
- data/sig/generated/ruboty/ai_agent/commands.rbs +2 -3
- data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +0 -5
- data/sig/generated/ruboty/ai_agent/request.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/tool.rbs +9 -3
- data/sig/generated/ruboty/ai_agent/tool_definitions/base.rbs +52 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions/think.rbs +17 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions.rbs +12 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +4 -2
- data/sig/generated-by-scripts/memorized_ivars.rbs +10 -0
- metadata +9 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4358837ce9305dcb0965a67c957b75e8320aad2988647a9e6ae751a98e7b7009
|
4
|
+
data.tar.gz: 3b1ad1c43bc443c5d86b1ea80bd8d9a45263056bc91ade861058e600493cb1b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb0b30fad316d595d3e088d9262b46b32af67467c23b28494a7214f83093bb2a939953005c19b84e907eae81c4d1ba00a2dd84ca96e8cc85fdffe016d687e036
|
7
|
+
data.tar.gz: 586e425b1c2f806d663e861d18fa57579649b6d29135576a1677f825f637fc017d5cb910e6b1edc1bc2e1a7d5c8eb4e40d6c7795c170e69f418434b84750c0f8
|
data/AGENTS.md
CHANGED
@@ -20,3 +20,11 @@
|
|
20
20
|
## PR instructions
|
21
21
|
|
22
22
|
- Always run `rake autocorrect` before committing.
|
23
|
+
|
24
|
+
## CHANGELOG instructions
|
25
|
+
|
26
|
+
- When you make changes, please update `CHANGELOG.md` accordingly.
|
27
|
+
- Add to the `Unreleased` section at the top.
|
28
|
+
- Follow the format used in previous entries.
|
29
|
+
- Use English for the changelog entries
|
30
|
+
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
-
|
1
|
+
## Unreleased
|
2
|
+
## 0.3.0
|
2
3
|
|
3
|
-
|
4
|
+
- Add think tool.
|
5
|
+
- Different agent threads are prepared for different slack threads.
|
6
|
+
|
7
|
+
## 0.2.0
|
8
|
+
|
9
|
+
- Support for user defined commends.
|
10
|
+
- Agent uses user defined system prompts and memories.
|
11
|
+
- Support automatic prompt compaction.
|
12
|
+
|
13
|
+
## 0.1.0
|
14
|
+
|
15
|
+
- Initial release with basic features and functionality.
|
data/README.md
CHANGED
@@ -57,9 +57,11 @@ MCP (Model Context Protocol):
|
|
57
57
|
- `add mcp <NAME> <OPTIONS> <URL>` — Add an MCP server
|
58
58
|
- Example (HTTP transport with auth header):
|
59
59
|
- `add mcp search --transport http --header 'Authorization: Bearer xxx' https://example.com/mcp`
|
60
|
+
- `add mcp search --transport http --bearer-token xxx https://example.com/mcp`
|
60
61
|
- Options:
|
61
62
|
- `--transport http|sse` (currently only `http` implemented; `sse` is not yet implemented)
|
62
63
|
- `--header 'Key: Value'` (repeatable)
|
64
|
+
- `--bearer-token <TOKEN>` (shorthand for `--header 'Authorization: Bearer <TOKEN>'`)
|
63
65
|
- `remove mcp <NAME>` — Remove an MCP server
|
64
66
|
- `list mcp` / `list mcps` — List configured MCP servers
|
65
67
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
+
require 'shellwords'
|
4
5
|
|
5
6
|
module Ruboty
|
6
7
|
module AiAgent
|
@@ -58,11 +59,11 @@ module Ruboty
|
|
58
59
|
args: []
|
59
60
|
} #: config
|
60
61
|
|
61
|
-
args = config_param.
|
62
|
+
args = config_param.shellsplit
|
62
63
|
|
63
64
|
parser = OptionParser.new do |opts|
|
64
65
|
opts.on('--transport TYPE', %w[http sse], 'Transport type (http or sse)') do |t|
|
65
|
-
options[:transport] = t
|
66
|
+
options[:transport] = t.to_sym
|
66
67
|
end
|
67
68
|
|
68
69
|
opts.on('--header VALUE', 'Add a header (can be specified multiple times)') do |h|
|
@@ -74,6 +75,10 @@ module Ruboty
|
|
74
75
|
|
75
76
|
options[:headers][key] = value
|
76
77
|
end
|
78
|
+
|
79
|
+
opts.on('--bearer-token TOKEN', 'Set Authorization Bearer token (shorthand for --header "Authorization: Bearer TOKEN")') do |token|
|
80
|
+
options[:headers]['Authorization'] = "Bearer #{token}"
|
81
|
+
end
|
77
82
|
end
|
78
83
|
|
79
84
|
options[:args] = parser.parse(args)
|
@@ -35,7 +35,17 @@ module Ruboty
|
|
35
35
|
|
36
36
|
# @rbs %a{memorized}
|
37
37
|
def chat_thread #: Ruboty::AiAgent::ChatThread
|
38
|
-
@chat_thread ||= database.chat_thread(
|
38
|
+
@chat_thread ||= database.chat_thread(thread_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def thread_id #: String
|
44
|
+
if message.respond_to?(:original) && message.original&.dig(:thread_ts)
|
45
|
+
message.original[:thread_ts]
|
46
|
+
else
|
47
|
+
message.from || 'default'
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Ruboty
|
4
6
|
module AiAgent
|
5
7
|
module Actions
|
@@ -10,14 +12,14 @@ module Ruboty
|
|
10
12
|
# @rbs override
|
11
13
|
def call
|
12
14
|
user.prompt_command_definitions.all_values.each do |definition|
|
13
|
-
command = Commands::PromptCommand.new(definition:,
|
15
|
+
command = Commands::PromptCommand.new(definition:, request:)
|
14
16
|
if command.match?(body_param)
|
15
17
|
new_prompt = command.call(commandline: body_param)
|
16
18
|
return complete_chat(new_prompt)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
builtin_commands = Commands.builtins(
|
22
|
+
builtin_commands = Commands.builtins(request:)
|
21
23
|
builtin_commands.each do |command|
|
22
24
|
return command.call if command.match?(body_param)
|
23
25
|
end
|
@@ -29,6 +31,11 @@ module Ruboty
|
|
29
31
|
message[:body]
|
30
32
|
end
|
31
33
|
|
34
|
+
# @rbs %a{memorized}
|
35
|
+
def request #: Request
|
36
|
+
@request ||= Request.new(message:, chat_thread:)
|
37
|
+
end
|
38
|
+
|
32
39
|
private
|
33
40
|
|
34
41
|
# @rbs body: String
|
@@ -56,7 +63,10 @@ module Ruboty
|
|
56
63
|
messages += chat_thread.messages.all_values
|
57
64
|
|
58
65
|
llm = LLM::OpenAI.new
|
59
|
-
tools =
|
66
|
+
tools = [
|
67
|
+
*McpClients.new(user.mcp_clients).available_tools,
|
68
|
+
*ToolDefinitions.builtins(request:).map(&:to_tool)
|
69
|
+
]
|
60
70
|
|
61
71
|
agent = Agent.new(
|
62
72
|
llm:,
|
@@ -72,11 +82,10 @@ module Ruboty
|
|
72
82
|
|
73
83
|
chat_thread.messages.compact(llm:) if chat_thread.messages.over_auto_compact_threshold?
|
74
84
|
when :tool_call
|
75
|
-
message.reply("Calling tool #{event[:tool].name} with arguments #{event[:tool_arguments]}"
|
76
|
-
streaming: true)
|
85
|
+
message.reply("Calling tool #{event[:tool].name} with arguments #{event[:tool_arguments]&.to_json}") unless event[:tool].silent?
|
77
86
|
when :tool_response
|
78
87
|
chat_thread.messages << event[:message]
|
79
|
-
message.reply("Tool response: #{event[:tool_response].slice(0..100)}")
|
88
|
+
message.reply("Tool response: #{event[:tool_response].slice(0..100)}") unless event[:tool].silent?
|
80
89
|
end
|
81
90
|
end
|
82
91
|
rescue StandardError => e
|
@@ -7,7 +7,7 @@ module Ruboty
|
|
7
7
|
class ListAiCommands < Base
|
8
8
|
# @rbs override
|
9
9
|
def call
|
10
|
-
builtin_commands = Commands.builtins(message:, chat_thread:)
|
10
|
+
builtin_commands = Commands.builtins(request: Request.new(message:, chat_thread:))
|
11
11
|
|
12
12
|
builtin_list = builtin_commands.flat_map(&:matchers)
|
13
13
|
.map { |matcher| "#{matcher.pattern.inspect} - #{matcher.description}" }
|
@@ -6,11 +6,58 @@ module Ruboty
|
|
6
6
|
# ListMcp action for Ruboty::AiAgent
|
7
7
|
class ListMcp < Base
|
8
8
|
def call
|
9
|
-
|
10
|
-
|
9
|
+
clients = user.mcp_clients
|
10
|
+
|
11
|
+
if clients.empty?
|
12
|
+
message.reply('No MCP servers found.')
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
show_headers = !message[:with_headers].nil?
|
17
|
+
output = clients.map do |mcp_client|
|
18
|
+
format_mcp_client(mcp_client, show_headers: show_headers)
|
19
|
+
end.join("\n\n")
|
20
|
+
|
21
|
+
message.reply(output)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @rbs client: UserMcpClient
|
27
|
+
# @rbs show_headers: bool
|
28
|
+
# @rbs return: String
|
29
|
+
def format_mcp_client(client, show_headers: false)
|
30
|
+
configuration = client.configuration
|
31
|
+
|
32
|
+
tools_info = format_tools(client)
|
33
|
+
mcp_info = <<~TEXT
|
34
|
+
#{configuration.name}:
|
35
|
+
Transport: #{configuration.transport}
|
36
|
+
URL: #{configuration.url}
|
37
|
+
TEXT
|
38
|
+
|
39
|
+
mcp_info += " Headers: #{configuration.headers.to_json}\n" if show_headers
|
40
|
+
|
41
|
+
"#{mcp_info}#{tools_info}".chomp
|
42
|
+
end
|
43
|
+
|
44
|
+
# @rbs client: UserMcpClient
|
45
|
+
# @rbs return: String
|
46
|
+
def format_tools(client)
|
47
|
+
tools = client.list_tools
|
48
|
+
return '' if tools.empty?
|
49
|
+
|
50
|
+
tools_output = tools.map do |tool|
|
51
|
+
tool_name = tool['name'] || 'unnamed'
|
52
|
+
description = tool['description'] || 'No description'
|
53
|
+
truncated_description = description.length > 100 ? "#{description[0, 100]}..." : description
|
54
|
+
" - #{tool_name}: #{truncated_description}"
|
11
55
|
end.join("\n")
|
12
56
|
|
13
|
-
|
57
|
+
" Tools:\n#{tools_output}\n"
|
58
|
+
rescue HttpMcpClient::Error => e
|
59
|
+
warn "Failed to list tools for MCP client: #{e.message}"
|
60
|
+
''
|
14
61
|
end
|
15
62
|
end
|
16
63
|
end
|
@@ -41,7 +41,7 @@ module Ruboty
|
|
41
41
|
tool_arguments: response.tool_arguments,
|
42
42
|
tool_response:
|
43
43
|
)
|
44
|
-
on_tool_response(tool_response:, message: tool_response_message, &)
|
44
|
+
on_tool_response(tool: response.tool, tool_response:, message: tool_response_message, &)
|
45
45
|
messages << tool_response_message
|
46
46
|
else
|
47
47
|
messages << response.message
|
@@ -59,8 +59,8 @@ module Ruboty
|
|
59
59
|
callback&.call({ type: :tool_call, tool:, tool_arguments: })
|
60
60
|
end
|
61
61
|
|
62
|
-
def on_tool_response(tool_response:, message:, &callback)
|
63
|
-
callback&.call({ type: :tool_response, tool_response:, message: })
|
62
|
+
def on_tool_response(tool:, tool_response:, message:, &callback)
|
63
|
+
callback&.call({ type: :tool_response, tool:, tool_response:, message: })
|
64
64
|
end
|
65
65
|
|
66
66
|
def on_response(response, &callback)
|
@@ -6,18 +6,23 @@ module Ruboty
|
|
6
6
|
# Base class for commands.
|
7
7
|
# @abstract
|
8
8
|
class Base
|
9
|
-
attr_reader :
|
10
|
-
attr_reader :chat_thread #: Ruboty::AiAgent::ChatThread
|
9
|
+
attr_reader :request #: Request
|
11
10
|
|
12
|
-
# @rbs
|
13
|
-
|
14
|
-
|
15
|
-
@message = message
|
16
|
-
@chat_thread = chat_thread
|
11
|
+
# @rbs request: Request
|
12
|
+
def initialize(request:)
|
13
|
+
@request = request
|
17
14
|
|
18
15
|
super()
|
19
16
|
end
|
20
17
|
|
18
|
+
def message #: Ruboty::Message
|
19
|
+
request.message
|
20
|
+
end
|
21
|
+
|
22
|
+
def chat_thread #: Ruboty::AiAgent::ChatThread
|
23
|
+
request.chat_thread
|
24
|
+
end
|
25
|
+
|
21
26
|
# @rbs *args: untyped
|
22
27
|
# @rbs return: untyped
|
23
28
|
def call(*args)
|
@@ -10,12 +10,11 @@ module Ruboty
|
|
10
10
|
attr_reader :definition #: Ruboty::AiAgent::PromptCommandDefinition
|
11
11
|
|
12
12
|
# @rbs definition: Ruboty::AiAgent::PromptCommandDefinition
|
13
|
-
# @rbs
|
14
|
-
|
15
|
-
def initialize(definition:, message:, chat_thread:)
|
13
|
+
# @rbs request: Ruboty::AiAgent::Request
|
14
|
+
def initialize(definition:, request:)
|
16
15
|
@definition = definition
|
17
16
|
|
18
|
-
super(
|
17
|
+
super(request:)
|
19
18
|
end
|
20
19
|
|
21
20
|
# @rbs commandline: String
|
@@ -11,24 +11,14 @@ module Ruboty
|
|
11
11
|
autoload :Usage, 'ruboty/ai_agent/commands/usage'
|
12
12
|
autoload :PromptCommand, 'ruboty/ai_agent/commands/prompt_command'
|
13
13
|
|
14
|
-
# @rbs
|
15
|
-
# @rbs chat_thread: ChatThread
|
14
|
+
# @rbs request: Request
|
16
15
|
# @rbs return: Array[Commands::BuiltinBase]
|
17
|
-
def self.builtins(
|
16
|
+
def self.builtins(request:)
|
18
17
|
[
|
19
|
-
Commands::Clear
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
Commands::Compact.new(
|
24
|
-
message:,
|
25
|
-
chat_thread:
|
26
|
-
),
|
27
|
-
Commands::Usage.new(
|
28
|
-
message:,
|
29
|
-
chat_thread:
|
30
|
-
)
|
31
|
-
]
|
18
|
+
Commands::Clear,
|
19
|
+
Commands::Compact,
|
20
|
+
Commands::Usage
|
21
|
+
].map { |cmd_class| cmd_class.new(request: request) }
|
32
22
|
end
|
33
23
|
end
|
34
24
|
end
|
@@ -16,8 +16,9 @@ module Ruboty
|
|
16
16
|
clients.flat_map do |client|
|
17
17
|
tool_defs = client.list_tools
|
18
18
|
tool_defs.map do |tool_def|
|
19
|
+
tool_name = "mcp_#{client.mcp_name}__#{tool_def['name']}"
|
19
20
|
Tool.new(
|
20
|
-
name:
|
21
|
+
name: tool_name,
|
21
22
|
title: tool_def['title'] || '',
|
22
23
|
description: tool_def['description'] || '',
|
23
24
|
input_schema: tool_def['inputSchema']
|
@@ -31,20 +32,6 @@ module Ruboty
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
# @rbs function_name: String
|
35
|
-
# @rbs arguments: Hash[String, untyped]
|
36
|
-
# @rbs return: untyped
|
37
|
-
def execute_tool(function_name, arguments)
|
38
|
-
clients.each do |mcp_client|
|
39
|
-
tools = mcp_client.list_tools
|
40
|
-
return mcp_client.call_tool(function_name, arguments) if tools.any? { |t| t['name'] == function_name }
|
41
|
-
end
|
42
|
-
nil
|
43
|
-
rescue HttpMcpClient::Error => e
|
44
|
-
warn "Failed to execute tool '#{function_name}': #{e.message}"
|
45
|
-
nil
|
46
|
-
end
|
47
|
-
|
48
35
|
# @rbs return: bool
|
49
36
|
def any?
|
50
37
|
@clients.any?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
Request = Data.define(
|
6
|
+
:message, #: Ruboty::Message
|
7
|
+
:chat_thread #: Ruboty::AiAgent::ChatThread
|
8
|
+
)
|
9
|
+
|
10
|
+
# Request for chat action from user.
|
11
|
+
class Request
|
12
|
+
def message_body #: String
|
13
|
+
message[:body]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ruboty/ai_agent/tool.rb
CHANGED
@@ -6,21 +6,29 @@ module Ruboty
|
|
6
6
|
class Tool
|
7
7
|
attr_reader :name, :title, :description #: String
|
8
8
|
attr_reader :input_schema #: Hash[untyped, untyped]?
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :silent #: boolish
|
10
|
+
attr_reader :on_call #: (^(Hash[String, untyped]) -> String? )?
|
10
11
|
|
11
12
|
# @rbs name: String
|
12
13
|
# @rbs title: String
|
13
14
|
# @rbs description: String
|
14
15
|
# @rbs input_schema: Hash[untyped, untyped]?
|
15
|
-
# @rbs
|
16
|
-
|
16
|
+
# @rbs ?silent: boolish?
|
17
|
+
# @rbs &on_call: ? (Hash[String, untyped]) -> String?
|
18
|
+
def initialize(name:, title:, description:, input_schema:, silent: false, &on_call) #: void
|
17
19
|
@name = name
|
18
20
|
@title = title
|
19
21
|
@description = description
|
20
22
|
@input_schema = input_schema
|
23
|
+
@silent = silent
|
21
24
|
@on_call = on_call
|
22
25
|
end
|
23
26
|
|
27
|
+
# Returns true if the tool should be called silently (without notifying the user).
|
28
|
+
def silent? #: boolish
|
29
|
+
silent
|
30
|
+
end
|
31
|
+
|
24
32
|
def call(params) #: String?
|
25
33
|
on_call&.call(params)
|
26
34
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module ToolDefinitions
|
6
|
+
# Base class for tool definitions
|
7
|
+
# @abstract
|
8
|
+
class Base
|
9
|
+
# @rbs!
|
10
|
+
# type input_schema = Hash[String | Symbol, untyped]
|
11
|
+
|
12
|
+
# @rbs!
|
13
|
+
# def self.tool_name: () -> String?
|
14
|
+
# def self.tool_name=: (String) -> String
|
15
|
+
# def self.tool_title: () -> String?
|
16
|
+
# def self.tool_title=: (String) -> String
|
17
|
+
# def self.tool_description: () -> String?
|
18
|
+
# def self.tool_description=: (String) -> String
|
19
|
+
# def self.tool_input_schema: () -> input_schema?
|
20
|
+
# def self.tool_input_schema=: (input_schema) -> input_schema
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# @rbs skip
|
24
|
+
attr_accessor :tool_name
|
25
|
+
|
26
|
+
# @rbs skip
|
27
|
+
attr_accessor :tool_title
|
28
|
+
|
29
|
+
# @rbs skip
|
30
|
+
attr_accessor :tool_description
|
31
|
+
|
32
|
+
# @rbs skip
|
33
|
+
attr_accessor :tool_input_schema
|
34
|
+
end
|
35
|
+
|
36
|
+
def tool_name #: String
|
37
|
+
self.class.tool_name || (raise NotImplementedError, "Subclasses must define 'tool_name'")
|
38
|
+
end
|
39
|
+
|
40
|
+
def tool_title #: String
|
41
|
+
self.class.tool_title || ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def tool_description #: String
|
45
|
+
self.class.tool_description || ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def tool_input_schema #: input_schema
|
49
|
+
self.class.tool_input_schema || {} #: input_schema
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return true if you want the tool not to produce call logs in the chat.
|
53
|
+
def silent? #: boolish
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :request #: Request
|
58
|
+
|
59
|
+
# @rbs request: Request
|
60
|
+
def initialize(request:)
|
61
|
+
@request = request
|
62
|
+
end
|
63
|
+
|
64
|
+
# @abstract
|
65
|
+
# @rbs arguments: Hash[String, untyped]
|
66
|
+
# @rbs return: String?
|
67
|
+
def call(arguments)
|
68
|
+
raise NotImplementedError, "Subclasses must implement the 'call' method"
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_tool #: Tool
|
72
|
+
Tool.new(
|
73
|
+
name: tool_name,
|
74
|
+
title: tool_title,
|
75
|
+
description: tool_description,
|
76
|
+
input_schema: tool_input_schema,
|
77
|
+
silent: silent?,
|
78
|
+
&method(:call) #: ^ (Hash[String, untyped]) -> String?
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module ToolDefinitions
|
6
|
+
# Tool
|
7
|
+
class Think < Base
|
8
|
+
self.tool_name = 'think'
|
9
|
+
self.tool_title = 'Think'
|
10
|
+
|
11
|
+
self.tool_description = <<~TEXT
|
12
|
+
Use this tool to think abount something. It will not obtain new information or make any changes, but just log the thought.
|
13
|
+
Use it when completx reasoing or brainstorming is needed.
|
14
|
+
TEXT
|
15
|
+
|
16
|
+
self.tool_input_schema = {
|
17
|
+
type: 'object',
|
18
|
+
properties: {
|
19
|
+
thought: { type: 'string', description: 'Your thought.' }
|
20
|
+
},
|
21
|
+
required: ['thought']
|
22
|
+
}
|
23
|
+
|
24
|
+
# @rbs arguments: Hash[String, untyped]
|
25
|
+
# @rbs return: String?
|
26
|
+
def call(arguments)
|
27
|
+
thought = arguments['thought']
|
28
|
+
|
29
|
+
request.message.reply("Thought:\n#{thought}")
|
30
|
+
|
31
|
+
thought
|
32
|
+
end
|
33
|
+
|
34
|
+
# @rbs override
|
35
|
+
def silent?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
# Tool Definitions for AI Agent
|
6
|
+
module ToolDefinitions
|
7
|
+
autoload :Base, 'ruboty/ai_agent/tool_definitions/base'
|
8
|
+
autoload :Think, 'ruboty/ai_agent/tool_definitions/think'
|
9
|
+
|
10
|
+
# @rbs request: Request
|
11
|
+
# @rbs return: Array[Base]
|
12
|
+
def self.builtins(request:)
|
13
|
+
[
|
14
|
+
Think
|
15
|
+
].map { |tool_def| tool_def.new(request:) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -68,14 +68,15 @@ module Ruboty
|
|
68
68
|
mcp_client.cleanup_session
|
69
69
|
end
|
70
70
|
|
71
|
-
private
|
72
|
-
|
73
71
|
# @rbs return: McpConfiguration
|
74
72
|
def configuration
|
75
73
|
user.mcp_configurations.all_values.find { |config| config.name == mcp_name } ||
|
76
74
|
raise("MCP configuration not found: #{mcp_name}")
|
77
75
|
end
|
78
76
|
|
77
|
+
private
|
78
|
+
|
79
|
+
# @rbs %a{memorized}
|
79
80
|
# @rbs return: HttpMcpClient
|
80
81
|
def mcp_client
|
81
82
|
@mcp_client ||= case configuration.transport
|
data/lib/ruboty/ai_agent.rb
CHANGED
@@ -28,8 +28,10 @@ module Ruboty
|
|
28
28
|
autoload :PromptCommandDefinition, 'ruboty/ai_agent/prompt_command_definition'
|
29
29
|
autoload :Recordable, 'ruboty/ai_agent/recordable'
|
30
30
|
autoload :RecordSet, 'ruboty/ai_agent/record_set'
|
31
|
+
autoload :Request, 'ruboty/ai_agent/request'
|
31
32
|
autoload :TokenUsage, 'ruboty/ai_agent/token_usage'
|
32
33
|
autoload :Tool, 'ruboty/ai_agent/tool'
|
34
|
+
autoload :ToolDefinitions, 'ruboty/ai_agent/tool_definitions'
|
33
35
|
autoload :User, 'ruboty/ai_agent/user'
|
34
36
|
autoload :UserAiMemories, 'ruboty/ai_agent/user_ai_memories'
|
35
37
|
autoload :UserAssociations, 'ruboty/ai_agent/user_associations'
|
@@ -18,7 +18,7 @@ module Ruboty
|
|
18
18
|
|
19
19
|
on(/add mcp (?<name>\S+)\s+(?<config>.+)\z/, name: 'add_mcp', description: 'Add a new MCP server')
|
20
20
|
on(/remove mcp (?<name>\S+)/, name: 'remove_mcp', description: 'Remove the specified MCP server')
|
21
|
-
on(/list mcps?/, name: 'list_mcp', description: 'List configured MCP servers')
|
21
|
+
on(/list mcps?(?<with_headers>\s+with\s+headers)?/, name: 'list_mcp', description: 'List configured MCP servers')
|
22
22
|
|
23
23
|
on(/set (?:(?<scope>user|global) )?system prompt "(?<prompt>.+?)"/, name: 'set_system_prompt', description: 'Set system prompt')
|
24
24
|
on(/show system prompt/, name: 'show_system_prompt', description: 'Show system prompt')
|
@@ -6,6 +6,17 @@ module Ruboty
|
|
6
6
|
# ListMcp action for Ruboty::AiAgent
|
7
7
|
class ListMcp < Base
|
8
8
|
def call: () -> untyped
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# @rbs client: UserMcpClient
|
13
|
+
# @rbs show_headers: bool
|
14
|
+
# @rbs return: String
|
15
|
+
def format_mcp_client: (UserMcpClient client, ?show_headers: bool) -> String
|
16
|
+
|
17
|
+
# @rbs client: UserMcpClient
|
18
|
+
# @rbs return: String
|
19
|
+
def format_tools: (UserMcpClient client) -> String
|
9
20
|
end
|
10
21
|
end
|
11
22
|
end
|
@@ -21,7 +21,7 @@ module Ruboty
|
|
21
21
|
|
22
22
|
def on_tool_call: (tool: untyped, tool_arguments: untyped) ?{ (?) -> untyped } -> untyped
|
23
23
|
|
24
|
-
def on_tool_response: (tool_response: untyped, message: untyped) ?{ (?) -> untyped } -> untyped
|
24
|
+
def on_tool_response: (tool: untyped, tool_response: untyped, message: untyped) ?{ (?) -> untyped } -> untyped
|
25
25
|
|
26
26
|
def on_response: (untyped response) ?{ (?) -> untyped } -> untyped
|
27
27
|
end
|
@@ -6,13 +6,14 @@ module Ruboty
|
|
6
6
|
# Base class for commands.
|
7
7
|
# @abstract
|
8
8
|
class Base
|
9
|
-
attr_reader
|
9
|
+
attr_reader request: Request
|
10
10
|
|
11
|
-
|
11
|
+
# @rbs request: Request
|
12
|
+
def initialize: (request: Request) -> untyped
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
def
|
14
|
+
def message: () -> Ruboty::Message
|
15
|
+
|
16
|
+
def chat_thread: () -> Ruboty::AiAgent::ChatThread
|
16
17
|
|
17
18
|
# @rbs *args: untyped
|
18
19
|
# @rbs return: untyped
|
@@ -8,9 +8,8 @@ module Ruboty
|
|
8
8
|
attr_reader definition: Ruboty::AiAgent::PromptCommandDefinition
|
9
9
|
|
10
10
|
# @rbs definition: Ruboty::AiAgent::PromptCommandDefinition
|
11
|
-
# @rbs
|
12
|
-
|
13
|
-
def initialize: (definition: Ruboty::AiAgent::PromptCommandDefinition, message: Ruboty::Message, chat_thread: Ruboty::AiAgent::ChatThread) -> untyped
|
11
|
+
# @rbs request: Ruboty::AiAgent::Request
|
12
|
+
def initialize: (definition: Ruboty::AiAgent::PromptCommandDefinition, request: Ruboty::AiAgent::Request) -> untyped
|
14
13
|
|
15
14
|
# @rbs commandline: String
|
16
15
|
# @rbs return: boolish
|
@@ -4,10 +4,9 @@ module Ruboty
|
|
4
4
|
module AiAgent
|
5
5
|
# Interaction commands (a.k.a. Slash commands, Prompts, etc)
|
6
6
|
module Commands
|
7
|
-
# @rbs
|
8
|
-
# @rbs chat_thread: ChatThread
|
7
|
+
# @rbs request: Request
|
9
8
|
# @rbs return: Array[Commands::BuiltinBase]
|
10
|
-
def self.builtins: (
|
9
|
+
def self.builtins: (request: Request) -> Array[Commands::BuiltinBase]
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
@@ -12,11 +12,6 @@ module Ruboty
|
|
12
12
|
# @rbs return: Array[Tool]
|
13
13
|
def available_tools: () -> Array[Tool]
|
14
14
|
|
15
|
-
# @rbs function_name: String
|
16
|
-
# @rbs arguments: Hash[String, untyped]
|
17
|
-
# @rbs return: untyped
|
18
|
-
def execute_tool: (String function_name, Hash[String, untyped] arguments) -> untyped
|
19
|
-
|
20
15
|
# @rbs return: bool
|
21
16
|
def any?: () -> bool
|
22
17
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/request.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
class Request < Data
|
6
|
+
attr_reader message(): Ruboty::Message
|
7
|
+
|
8
|
+
attr_reader chat_thread(): Ruboty::AiAgent::ChatThread
|
9
|
+
|
10
|
+
def self.new: (Ruboty::Message message, Ruboty::AiAgent::ChatThread chat_thread) -> instance
|
11
|
+
| (message: Ruboty::Message, chat_thread: Ruboty::AiAgent::ChatThread) -> instance
|
12
|
+
|
13
|
+
def self.members: () -> [ :message, :chat_thread ]
|
14
|
+
|
15
|
+
def members: () -> [ :message, :chat_thread ]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Request for chat action from user.
|
19
|
+
class Request
|
20
|
+
def message_body: () -> String
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -12,14 +12,20 @@ module Ruboty
|
|
12
12
|
|
13
13
|
attr_reader input_schema: Hash[untyped, untyped]?
|
14
14
|
|
15
|
-
attr_reader
|
15
|
+
attr_reader silent: boolish
|
16
|
+
|
17
|
+
attr_reader on_call: (^(Hash[String, untyped]) -> String?)?
|
16
18
|
|
17
19
|
# @rbs name: String
|
18
20
|
# @rbs title: String
|
19
21
|
# @rbs description: String
|
20
22
|
# @rbs input_schema: Hash[untyped, untyped]?
|
21
|
-
# @rbs
|
22
|
-
|
23
|
+
# @rbs ?silent: boolish?
|
24
|
+
# @rbs &on_call: ? (Hash[String, untyped]) -> String?
|
25
|
+
def initialize: (name: String, title: String, description: String, input_schema: Hash[untyped, untyped]?, ?silent: untyped) ?{ (Hash[String, untyped]) -> String? } -> void
|
26
|
+
|
27
|
+
# Returns true if the tool should be called silently (without notifying the user).
|
28
|
+
def silent?: () -> boolish
|
23
29
|
|
24
30
|
def call: (untyped params) -> String?
|
25
31
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/tool_definitions/base.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module ToolDefinitions
|
6
|
+
# Base class for tool definitions
|
7
|
+
# @abstract
|
8
|
+
class Base
|
9
|
+
type input_schema = Hash[String | Symbol, untyped]
|
10
|
+
|
11
|
+
def self.tool_name: () -> String?
|
12
|
+
|
13
|
+
def self.tool_name=: (String) -> String
|
14
|
+
|
15
|
+
def self.tool_title: () -> String?
|
16
|
+
|
17
|
+
def self.tool_title=: (String) -> String
|
18
|
+
|
19
|
+
def self.tool_description: () -> String?
|
20
|
+
|
21
|
+
def self.tool_description=: (String) -> String
|
22
|
+
|
23
|
+
def self.tool_input_schema: () -> input_schema?
|
24
|
+
|
25
|
+
def self.tool_input_schema=: (input_schema) -> input_schema
|
26
|
+
|
27
|
+
def tool_name: () -> String
|
28
|
+
|
29
|
+
def tool_title: () -> String
|
30
|
+
|
31
|
+
def tool_description: () -> String
|
32
|
+
|
33
|
+
def tool_input_schema: () -> input_schema
|
34
|
+
|
35
|
+
# Return true if you want the tool not to produce call logs in the chat.
|
36
|
+
def silent?: () -> boolish
|
37
|
+
|
38
|
+
attr_reader request: Request
|
39
|
+
|
40
|
+
# @rbs request: Request
|
41
|
+
def initialize: (request: Request) -> untyped
|
42
|
+
|
43
|
+
# @abstract
|
44
|
+
# @rbs arguments: Hash[String, untyped]
|
45
|
+
# @rbs return: String?
|
46
|
+
def call: (Hash[String, untyped] arguments) -> String?
|
47
|
+
|
48
|
+
def to_tool: () -> Tool
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/tool_definitions/think.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module ToolDefinitions
|
6
|
+
# Tool
|
7
|
+
class Think < Base
|
8
|
+
# @rbs arguments: Hash[String, untyped]
|
9
|
+
# @rbs return: String?
|
10
|
+
def call: (Hash[String, untyped] arguments) -> String?
|
11
|
+
|
12
|
+
# @rbs override
|
13
|
+
def silent?: ...
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/tool_definitions.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
# Tool Definitions for AI Agent
|
6
|
+
module ToolDefinitions
|
7
|
+
# @rbs request: Request
|
8
|
+
# @rbs return: Array[Base]
|
9
|
+
def self.builtins: (request: Request) -> Array[Base]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -46,12 +46,14 @@ module Ruboty
|
|
46
46
|
# @rbs return: untyped
|
47
47
|
def cleanup_session: () -> untyped
|
48
48
|
|
49
|
-
private
|
50
|
-
|
51
49
|
# @rbs return: McpConfiguration
|
52
50
|
def configuration: () -> McpConfiguration
|
53
51
|
|
52
|
+
private
|
53
|
+
|
54
|
+
# @rbs %a{memorized}
|
54
55
|
# @rbs return: HttpMcpClient
|
56
|
+
%a{memorized}
|
55
57
|
def mcp_client: () -> HttpMcpClient
|
56
58
|
end
|
57
59
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruboty-ai_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomoya Chiba
|
@@ -113,8 +113,12 @@ files:
|
|
113
113
|
- lib/ruboty/ai_agent/prompt_command_definition.rb
|
114
114
|
- lib/ruboty/ai_agent/record_set.rb
|
115
115
|
- lib/ruboty/ai_agent/recordable.rb
|
116
|
+
- lib/ruboty/ai_agent/request.rb
|
116
117
|
- lib/ruboty/ai_agent/token_usage.rb
|
117
118
|
- lib/ruboty/ai_agent/tool.rb
|
119
|
+
- lib/ruboty/ai_agent/tool_definitions.rb
|
120
|
+
- lib/ruboty/ai_agent/tool_definitions/base.rb
|
121
|
+
- lib/ruboty/ai_agent/tool_definitions/think.rb
|
118
122
|
- lib/ruboty/ai_agent/user.rb
|
119
123
|
- lib/ruboty/ai_agent/user_ai_memories.rb
|
120
124
|
- lib/ruboty/ai_agent/user_associations.rb
|
@@ -177,8 +181,12 @@ files:
|
|
177
181
|
- sig/generated/ruboty/ai_agent/prompt_command_definition.rbs
|
178
182
|
- sig/generated/ruboty/ai_agent/record_set.rbs
|
179
183
|
- sig/generated/ruboty/ai_agent/recordable.rbs
|
184
|
+
- sig/generated/ruboty/ai_agent/request.rbs
|
180
185
|
- sig/generated/ruboty/ai_agent/token_usage.rbs
|
181
186
|
- sig/generated/ruboty/ai_agent/tool.rbs
|
187
|
+
- sig/generated/ruboty/ai_agent/tool_definitions.rbs
|
188
|
+
- sig/generated/ruboty/ai_agent/tool_definitions/base.rbs
|
189
|
+
- sig/generated/ruboty/ai_agent/tool_definitions/think.rbs
|
182
190
|
- sig/generated/ruboty/ai_agent/user.rbs
|
183
191
|
- sig/generated/ruboty/ai_agent/user_ai_memories.rbs
|
184
192
|
- sig/generated/ruboty/ai_agent/user_associations.rbs
|