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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +8 -0
  3. data/CHANGELOG.md +14 -2
  4. data/README.md +2 -0
  5. data/Rakefile +2 -0
  6. data/lib/ruboty/ai_agent/actions/add_mcp.rb +7 -2
  7. data/lib/ruboty/ai_agent/actions/base.rb +11 -1
  8. data/lib/ruboty/ai_agent/actions/chat.rb +15 -6
  9. data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +1 -1
  10. data/lib/ruboty/ai_agent/actions/list_mcp.rb +50 -3
  11. data/lib/ruboty/ai_agent/agent.rb +3 -3
  12. data/lib/ruboty/ai_agent/commands/base.rb +12 -7
  13. data/lib/ruboty/ai_agent/commands/prompt_command.rb +3 -4
  14. data/lib/ruboty/ai_agent/commands.rb +6 -16
  15. data/lib/ruboty/ai_agent/database/query_methods.rb +1 -1
  16. data/lib/ruboty/ai_agent/llm/openai.rb +1 -1
  17. data/lib/ruboty/ai_agent/mcp_clients.rb +2 -15
  18. data/lib/ruboty/ai_agent/request.rb +17 -0
  19. data/lib/ruboty/ai_agent/tool.rb +11 -3
  20. data/lib/ruboty/ai_agent/tool_definitions/base.rb +84 -0
  21. data/lib/ruboty/ai_agent/tool_definitions/think.rb +41 -0
  22. data/lib/ruboty/ai_agent/tool_definitions.rb +19 -0
  23. data/lib/ruboty/ai_agent/user_mcp_client.rb +3 -2
  24. data/lib/ruboty/ai_agent/version.rb +1 -1
  25. data/lib/ruboty/ai_agent.rb +2 -0
  26. data/lib/ruboty/handlers/ai_agent.rb +1 -1
  27. data/sig/generated/ruboty/ai_agent/actions/base.rbs +4 -0
  28. data/sig/generated/ruboty/ai_agent/actions/chat.rbs +4 -0
  29. data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +11 -0
  30. data/sig/generated/ruboty/ai_agent/agent.rbs +1 -1
  31. data/sig/generated/ruboty/ai_agent/commands/base.rbs +6 -5
  32. data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +2 -3
  33. data/sig/generated/ruboty/ai_agent/commands.rbs +2 -3
  34. data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +0 -5
  35. data/sig/generated/ruboty/ai_agent/request.rbs +23 -0
  36. data/sig/generated/ruboty/ai_agent/tool.rbs +9 -3
  37. data/sig/generated/ruboty/ai_agent/tool_definitions/base.rbs +52 -0
  38. data/sig/generated/ruboty/ai_agent/tool_definitions/think.rbs +17 -0
  39. data/sig/generated/ruboty/ai_agent/tool_definitions.rbs +12 -0
  40. data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +4 -2
  41. data/sig/generated-by-scripts/memorized_ivars.rbs +10 -0
  42. metadata +9 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ee903a1f588ede1c0b6e68bea298457b3ee2fbe826494f036f9c6482288475a
4
- data.tar.gz: 9acf203be78f86b2eeaf1f6810606a4a5b0f0a05709aba6c52b7194a589ed533
3
+ metadata.gz: 4358837ce9305dcb0965a67c957b75e8320aad2988647a9e6ae751a98e7b7009
4
+ data.tar.gz: 3b1ad1c43bc443c5d86b1ea80bd8d9a45263056bc91ade861058e600493cb1b7
5
5
  SHA512:
6
- metadata.gz: a59e8c688b63d055d59389a95b95a6a5637bac86d917e35a4bd90b7260bc2028b2defd00c6e7bd3a5b26d5cfdb22536de5e4a1428b4e597c29ae573898021922
7
- data.tar.gz: 1daed254a4b194c16b3b9dda2180c59e21cb5597d7c965e20905c302523b1f3a9f2a2829528e7746370fa567de487389c5846b392167c7331d8e00422a45a73e
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
- # Changelog
1
+ ## Unreleased
2
+ ## 0.3.0
2
3
 
3
- All notable changes to this project will be documented in this file.
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
@@ -17,6 +17,8 @@ Steep::RakeTask.new do |t|
17
17
  t.watch.verbose
18
18
  end
19
19
 
20
+ Bump.changelog = true
21
+
20
22
  task default: %i[rubocop steep spec]
21
23
  task autocorrect: %i[rubocop:autocorrect rbs steep spec]
22
24
 
@@ -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.split(/\s+(?=-)/)
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(message.from || 'default')
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:, message:, chat_thread:)
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(message:, chat_thread:)
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 = McpClients.new(user.mcp_clients).available_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
- mcp_configurations = (user.mcp_configurations.all || {}).map do |name, mcp_configuration|
10
- "#{name}: #{mcp_configuration.to_h.except(:record_type).to_json}"
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
- message.reply(mcp_configurations.empty? ? 'No memories found.' : mcp_configurations)
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 :message #: Ruboty::Message
10
- attr_reader :chat_thread #: Ruboty::AiAgent::ChatThread
9
+ attr_reader :request #: Request
11
10
 
12
- # @rbs message: Ruboty::Message
13
- # @rbs chat_thread: Ruboty::AiAgent::ChatThread
14
- def initialize(message:, chat_thread:)
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 message: Ruboty::Message
14
- # @rbs chat_thread: Ruboty::AiAgent::ChatThread
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(message:, chat_thread:)
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 message: Ruboty::Message
15
- # @rbs chat_thread: ChatThread
14
+ # @rbs request: Request
16
15
  # @rbs return: Array[Commands::BuiltinBase]
17
- def self.builtins(message:, chat_thread:)
16
+ def self.builtins(request:)
18
17
  [
19
- Commands::Clear.new(
20
- message:,
21
- chat_thread:
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
@@ -22,7 +22,7 @@ module Ruboty
22
22
  when Integer
23
23
  k
24
24
  when NilClass
25
- nil
25
+ ''
26
26
  else
27
27
  raise ArgumentError, "Invalid key type: #{k.class}"
28
28
  end
@@ -158,7 +158,7 @@ module Ruboty
158
158
  tool_call #: OpenAI::Models::Chat::ChatCompletionMessageFunctionToolCall
159
159
  .function.arguments
160
160
 
161
- JSON.parse(arguments, { symbolize_names: true })
161
+ JSON.parse(arguments)
162
162
  end
163
163
 
164
164
  Response.new(
@@ -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: tool_def['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
@@ -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 :on_call #: (^(Hash[String, untyped]) -> String )?
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 &on_call: ? (Hash[String, untyped]) -> String
16
- def initialize(name:, title:, description:, input_schema:, &on_call) #: void
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruboty
4
4
  module AiAgent
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -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')
@@ -28,6 +28,10 @@ module Ruboty
28
28
  # @rbs %a{memorized}
29
29
  %a{memorized}
30
30
  def chat_thread: () -> Ruboty::AiAgent::ChatThread
31
+
32
+ private
33
+
34
+ def thread_id: () -> String
31
35
  end
32
36
  end
33
37
  end
@@ -12,6 +12,10 @@ module Ruboty
12
12
 
13
13
  def body_param: () -> String
14
14
 
15
+ # @rbs %a{memorized}
16
+ %a{memorized}
17
+ def request: () -> Request
18
+
15
19
  private
16
20
 
17
21
  # @rbs body: String
@@ -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 message: Ruboty::Message
9
+ attr_reader request: Request
10
10
 
11
- attr_reader chat_thread: Ruboty::AiAgent::ChatThread
11
+ # @rbs request: Request
12
+ def initialize: (request: Request) -> untyped
12
13
 
13
- # @rbs message: Ruboty::Message
14
- # @rbs chat_thread: Ruboty::AiAgent::ChatThread
15
- def initialize: (message: Ruboty::Message, chat_thread: Ruboty::AiAgent::ChatThread) -> untyped
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 message: Ruboty::Message
12
- # @rbs chat_thread: Ruboty::AiAgent::ChatThread
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 message: Ruboty::Message
8
- # @rbs chat_thread: ChatThread
7
+ # @rbs request: Request
9
8
  # @rbs return: Array[Commands::BuiltinBase]
10
- def self.builtins: (message: Ruboty::Message, chat_thread: ChatThread) -> Array[Commands::BuiltinBase]
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 on_call: (^(Hash[String, untyped]) -> String)?
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 &on_call: ? (Hash[String, untyped]) -> String
22
- def initialize: (name: String, title: String, description: String, input_schema: Hash[untyped, untyped]?) ?{ (Hash[String, untyped]) -> String } -> void
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
@@ -13,6 +13,16 @@ module Ruboty
13
13
  end
14
14
  end
15
15
 
16
+ module Ruboty
17
+ module AiAgent
18
+ module Actions
19
+ class Chat
20
+ @request: Request
21
+ end
22
+ end
23
+ end
24
+ end
25
+
16
26
  module Ruboty
17
27
  module AiAgent
18
28
  class ChatThread
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.2.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