ruboty-ai_agent 0.1.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/.rubocop.yml +19 -0
- data/AGENTS.md +8 -0
- data/CHANGELOG.md +14 -2
- data/Gemfile +10 -3
- data/README.md +2 -0
- data/Rakefile +4 -0
- data/Steepfile +1 -0
- data/lib/ruboty/ai_agent/actions/add_ai_command.rb +16 -1
- 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 +56 -19
- data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +20 -4
- data/lib/ruboty/ai_agent/actions/list_mcp.rb +50 -3
- data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +7 -1
- data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +1 -1
- data/lib/ruboty/ai_agent/agent.rb +3 -3
- data/lib/ruboty/ai_agent/chat_message.rb +7 -2
- data/lib/ruboty/ai_agent/chat_thread_messages.rb +40 -0
- data/lib/ruboty/ai_agent/commands/base.rb +16 -14
- data/lib/ruboty/ai_agent/commands/builtin_base.rb +39 -0
- data/lib/ruboty/ai_agent/commands/clear.rb +2 -14
- data/lib/ruboty/ai_agent/commands/compact.rb +4 -47
- data/lib/ruboty/ai_agent/commands/prompt_command.rb +60 -0
- data/lib/ruboty/ai_agent/commands/usage.rb +2 -14
- data/lib/ruboty/ai_agent/commands.rb +9 -17
- data/lib/ruboty/ai_agent/database/query_methods.rb +31 -6
- data/lib/ruboty/ai_agent/database.rb +2 -1
- data/lib/ruboty/ai_agent/http_mcp_client.rb +5 -2
- data/lib/ruboty/ai_agent/llm/openai.rb +6 -6
- data/lib/ruboty/ai_agent/mcp_clients.rb +5 -12
- data/lib/ruboty/ai_agent/mcp_configuration.rb +3 -2
- data/lib/ruboty/ai_agent/prompt_command_definition.rb +17 -0
- data/lib/ruboty/ai_agent/record_set.rb +9 -5
- data/lib/ruboty/ai_agent/recordable.rb +11 -9
- data/lib/ruboty/ai_agent/request.rb +17 -0
- data/lib/ruboty/ai_agent/token_usage.rb +10 -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.rb +5 -0
- data/lib/ruboty/ai_agent/user_mcp_caches.rb +2 -2
- data/lib/ruboty/ai_agent/user_mcp_client.rb +5 -4
- data/lib/ruboty/ai_agent/user_prompt_command_definitions.rb +17 -0
- data/lib/ruboty/ai_agent/version.rb +1 -1
- data/lib/ruboty/ai_agent.rb +13 -0
- data/lib/ruboty/handlers/ai_agent.rb +28 -16
- data/rbs_collection.yaml +1 -0
- data/ruboty-ai_agent.gemspec +2 -6
- data/script/clean-orphaned-rbs.rb +105 -0
- data/script/generate-concern-rbs.rb +5 -5
- data/script/generate-data-rbs.rb +3 -5
- data/script/generate-memorized-ivar-rbs.rb +6 -11
- data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/base.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/chat.rbs +10 -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/chat_message.rbs +5 -2
- data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/commands/base.rbs +8 -19
- data/sig/generated/ruboty/ai_agent/commands/builtin_base.rbs +40 -0
- data/sig/generated/ruboty/ai_agent/commands/clear.rbs +2 -10
- data/sig/generated/ruboty/ai_agent/commands/compact.rbs +2 -20
- data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +26 -0
- data/sig/generated/ruboty/ai_agent/commands/usage.rbs +2 -10
- data/sig/generated/ruboty/ai_agent/commands.rbs +3 -4
- data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +18 -12
- data/sig/generated/ruboty/ai_agent/database.rbs +3 -1
- data/sig/generated/ruboty/ai_agent/llm/openai.rbs +3 -3
- data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +0 -5
- data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +4 -2
- data/sig/generated/ruboty/ai_agent/prompt_command_definition.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/record_set.rbs +11 -9
- data/sig/generated/ruboty/ai_agent/recordable.rbs +6 -6
- data/sig/generated/ruboty/ai_agent/request.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/token_usage.rbs +4 -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.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +4 -2
- data/sig/generated/ruboty/ai_agent/user_prompt_command_definitions.rbs +12 -0
- data/sig/generated/ruboty/handlers/ai_agent.rbs +12 -12
- data/sig/generated-by-scripts/concerns.rbs +5 -0
- data/sig/generated-by-scripts/memorized_ivars.rbs +19 -0
- metadata +19 -57
@@ -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
|
data/lib/ruboty/ai_agent/user.rb
CHANGED
@@ -47,6 +47,11 @@ module Ruboty
|
|
47
47
|
def mcp_tools_caches #: UserMcpToolsCaches
|
48
48
|
@mcp_tools_caches ||= UserMcpToolsCaches.new(database: database, user_id: id)
|
49
49
|
end
|
50
|
+
|
51
|
+
# @rbs %a{memorized}
|
52
|
+
def prompt_command_definitions #: UserPromptCommandDefinitions
|
53
|
+
@prompt_command_definitions ||= UserPromptCommandDefinitions.new(database: database, user_id: id)
|
54
|
+
end
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
@@ -57,12 +57,12 @@ module Ruboty
|
|
57
57
|
|
58
58
|
# @rbs override
|
59
59
|
def all
|
60
|
-
super
|
60
|
+
super.reject { |(_key, cache)| cache.expired? }
|
61
61
|
end
|
62
62
|
|
63
63
|
# @rbs override
|
64
64
|
def fetch(key)
|
65
|
-
cache = super
|
65
|
+
cache = super
|
66
66
|
|
67
67
|
if cache&.expired?
|
68
68
|
remove(key)
|
@@ -26,8 +26,8 @@ module Ruboty
|
|
26
26
|
# @rbs arguments: Hash[String, untyped]
|
27
27
|
# @rbs &block: ? (Hash[String, untyped]) -> void
|
28
28
|
# @rbs return: untyped
|
29
|
-
def call_tool(name, arguments = {}, &
|
30
|
-
mcp_client.call_tool(name, arguments, &
|
29
|
+
def call_tool(name, arguments = {}, &)
|
30
|
+
mcp_client.call_tool(name, arguments, &)
|
31
31
|
end
|
32
32
|
|
33
33
|
# @rbs return: untyped
|
@@ -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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
# User-defined commands record set
|
6
|
+
class UserPromptCommandDefinitions < UserAssociations #[PromptCommandDefinition]
|
7
|
+
self.association_key = :user_prompt_command_definitions
|
8
|
+
|
9
|
+
# @rbs command: PromptCommandDefinition
|
10
|
+
# @rbs return: PromptCommandDefinition
|
11
|
+
def add(command)
|
12
|
+
store(command, key: command.name)
|
13
|
+
command
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ruboty/ai_agent.rb
CHANGED
@@ -25,10 +25,13 @@ module Ruboty
|
|
25
25
|
autoload :McpClient, 'ruboty/ai_agent/mcp_client'
|
26
26
|
autoload :McpClients, 'ruboty/ai_agent/mcp_clients'
|
27
27
|
autoload :McpConfiguration, 'ruboty/ai_agent/mcp_configuration'
|
28
|
+
autoload :PromptCommandDefinition, 'ruboty/ai_agent/prompt_command_definition'
|
28
29
|
autoload :Recordable, 'ruboty/ai_agent/recordable'
|
29
30
|
autoload :RecordSet, 'ruboty/ai_agent/record_set'
|
31
|
+
autoload :Request, 'ruboty/ai_agent/request'
|
30
32
|
autoload :TokenUsage, 'ruboty/ai_agent/token_usage'
|
31
33
|
autoload :Tool, 'ruboty/ai_agent/tool'
|
34
|
+
autoload :ToolDefinitions, 'ruboty/ai_agent/tool_definitions'
|
32
35
|
autoload :User, 'ruboty/ai_agent/user'
|
33
36
|
autoload :UserAiMemories, 'ruboty/ai_agent/user_ai_memories'
|
34
37
|
autoload :UserAssociations, 'ruboty/ai_agent/user_associations'
|
@@ -36,5 +39,15 @@ module Ruboty
|
|
36
39
|
autoload :UserMcpClient, 'ruboty/ai_agent/user_mcp_client'
|
37
40
|
autoload :UserMcpConfigurations, 'ruboty/ai_agent/user_mcp_configurations'
|
38
41
|
autoload :UserMcpToolsCaches, 'ruboty/ai_agent/user_mcp_tools_caches'
|
42
|
+
autoload :UserPromptCommandDefinitions, 'ruboty/ai_agent/user_prompt_command_definitions'
|
43
|
+
|
44
|
+
# Ensure all recordables are loaded
|
45
|
+
[
|
46
|
+
CachedValue,
|
47
|
+
ChatMessage,
|
48
|
+
McpConfiguration,
|
49
|
+
PromptCommandDefinition,
|
50
|
+
TokenUsage
|
51
|
+
]
|
39
52
|
end
|
40
53
|
end
|
@@ -18,66 +18,78 @@ 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
|
-
on(/set
|
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')
|
25
25
|
|
26
26
|
on(/add ai memory "(?<prompt>.+)"/, name: 'add_ai_memory', description: 'Add a new AI memory')
|
27
27
|
on(/remove ai memory (?<index>\d+)/, name: 'remove_ai_memory', description: 'Remove the specified AI memory')
|
28
28
|
on(/list ai memor(?:y|ies)/, name: 'list_mcp', description: "List AI's memories")
|
29
29
|
|
30
|
-
on(%r{add ai command /(?<name>\S+)\s+
|
31
|
-
|
30
|
+
on(%r{add ai command /(?<name>\S+)\s+(?<prompt>.+)}, name: 'add_ai_command',
|
31
|
+
description: 'Add a new AI command')
|
32
32
|
on(%r{remove ai command /(?<name>\S+)}, name: 'remove_ai_command', description: 'Remove the specified AI command')
|
33
33
|
on(/list ai commands?/, name: 'list_ai_commands', description: 'List AI commands')
|
34
34
|
|
35
|
-
def chat(message)
|
35
|
+
def chat(message) #: true
|
36
36
|
Ruboty::AiAgent::Actions::Chat.call(message)
|
37
|
+
true
|
37
38
|
end
|
38
39
|
|
39
|
-
def add_mcp(message)
|
40
|
+
def add_mcp(message) #: true
|
40
41
|
Ruboty::AiAgent::Actions::AddMcp.call(message)
|
42
|
+
true
|
41
43
|
end
|
42
44
|
|
43
|
-
def remove_mcp(message)
|
45
|
+
def remove_mcp(message) #: true
|
44
46
|
Ruboty::AiAgent::Actions::RemoveMcp.call(message)
|
47
|
+
true
|
45
48
|
end
|
46
49
|
|
47
|
-
def list_mcp(message)
|
50
|
+
def list_mcp(message) #: true
|
48
51
|
Ruboty::AiAgent::Actions::ListMcp.call(message)
|
52
|
+
true
|
49
53
|
end
|
50
54
|
|
51
|
-
def set_system_prompt(message)
|
55
|
+
def set_system_prompt(message) #: true
|
52
56
|
Ruboty::AiAgent::Actions::SetSystemPrompt.call(message)
|
57
|
+
true
|
53
58
|
end
|
54
59
|
|
55
|
-
def show_system_prompt(message)
|
60
|
+
def show_system_prompt(message) #: true
|
56
61
|
Ruboty::AiAgent::Actions::ShowSystemPrompt.call(message)
|
62
|
+
true
|
57
63
|
end
|
58
64
|
|
59
|
-
def add_ai_memory(message)
|
65
|
+
def add_ai_memory(message) #: true
|
60
66
|
Ruboty::AiAgent::Actions::AddAiMemory.call(message)
|
67
|
+
true
|
61
68
|
end
|
62
69
|
|
63
|
-
def remove_ai_memory(message)
|
70
|
+
def remove_ai_memory(message) #: true
|
64
71
|
Ruboty::AiAgent::Actions::RemoveAiMemory.call(message)
|
72
|
+
true
|
65
73
|
end
|
66
74
|
|
67
|
-
def list_ai_memories(message)
|
75
|
+
def list_ai_memories(message) #: true
|
68
76
|
Ruboty::AiAgent::Actions::ListAiMemories.call(message)
|
77
|
+
true
|
69
78
|
end
|
70
79
|
|
71
|
-
def add_ai_command(message)
|
80
|
+
def add_ai_command(message) #: true
|
72
81
|
Ruboty::AiAgent::Actions::AddAiCommand.call(message)
|
82
|
+
true
|
73
83
|
end
|
74
84
|
|
75
|
-
def remove_ai_command(message)
|
85
|
+
def remove_ai_command(message) #: true
|
76
86
|
Ruboty::AiAgent::Actions::RemoveAiCommand.call(message)
|
87
|
+
true
|
77
88
|
end
|
78
89
|
|
79
|
-
def list_ai_commands(message)
|
90
|
+
def list_ai_commands(message) #: true
|
80
91
|
Ruboty::AiAgent::Actions::ListAiCommands.call(message)
|
92
|
+
true
|
81
93
|
end
|
82
94
|
end
|
83
95
|
end
|
data/rbs_collection.yaml
CHANGED
data/ruboty-ai_agent.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.metadata['changelog_uri'] = 'https://github.com/tomoasleep/ruboty-ai_agent/blob/main/CHANGELOG.md'
|
27
27
|
else
|
28
28
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
29
|
-
|
29
|
+
'public gem pushes.'
|
30
30
|
end
|
31
31
|
|
32
32
|
# Specify which files should be added to the gem when it is released.
|
@@ -38,12 +38,8 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
39
39
|
spec.require_paths = ['lib']
|
40
40
|
|
41
|
-
spec.add_development_dependency 'bundler', '~> 2'
|
42
|
-
spec.add_development_dependency 'rake', '~> 13'
|
43
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
|
-
spec.add_development_dependency 'webmock', '~> 3.0'
|
45
|
-
|
46
41
|
spec.add_dependency 'event_stream_parser', '~> 1.0'
|
47
42
|
spec.add_dependency 'openai', '~> 0.22.0'
|
48
43
|
spec.add_dependency 'ruboty', '~> 1.3'
|
44
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
49
45
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Clean orphaned RBS files from sig/generated
|
5
|
+
#
|
6
|
+
# This script removes RBS files from sig/generated directory that don't have
|
7
|
+
# corresponding Ruby files in the project.
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'pathname'
|
11
|
+
|
12
|
+
# Remove orphaned RBS files by `rbs-inline`
|
13
|
+
class OrphanedRbsCleaner
|
14
|
+
def initialize(project_root: Dir.pwd)
|
15
|
+
@project_root = Pathname.new(project_root)
|
16
|
+
@sig_generated_dir = @project_root / 'sig' / 'generated'
|
17
|
+
@removed_files = []
|
18
|
+
@kept_files = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(dry_run: false)
|
22
|
+
return unless @sig_generated_dir.exist?
|
23
|
+
|
24
|
+
puts "Scanning for orphaned RBS files in #{@sig_generated_dir}..."
|
25
|
+
|
26
|
+
rbs_files = find_rbs_files
|
27
|
+
puts "Found #{rbs_files.size} RBS files to check"
|
28
|
+
|
29
|
+
rbs_files.each do |rbs_file|
|
30
|
+
corresponding_ruby_file = find_corresponding_ruby_file(rbs_file)
|
31
|
+
|
32
|
+
if corresponding_ruby_file&.exist?
|
33
|
+
@kept_files << rbs_file
|
34
|
+
else
|
35
|
+
@removed_files << rbs_file
|
36
|
+
if dry_run
|
37
|
+
puts "Would remove: #{rbs_file.relative_path_from(@project_root)}"
|
38
|
+
else
|
39
|
+
puts "Removing: #{rbs_file.relative_path_from(@project_root)}"
|
40
|
+
rbs_file.delete
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
cleanup_empty_directories unless dry_run
|
46
|
+
|
47
|
+
print_summary
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def find_rbs_files
|
53
|
+
@sig_generated_dir.glob('**/*.rbs').sort
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_corresponding_ruby_file(rbs_file)
|
57
|
+
# Convert sig/generated/path/to/file.rbs to possible Ruby file locations
|
58
|
+
relative_path = rbs_file.relative_path_from(@sig_generated_dir)
|
59
|
+
ruby_file_name = relative_path.sub_ext('.rb')
|
60
|
+
|
61
|
+
# Check common Ruby source directories
|
62
|
+
possible_locations = [
|
63
|
+
@project_root / 'lib' / ruby_file_name
|
64
|
+
]
|
65
|
+
|
66
|
+
possible_locations.find(&:exist?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def cleanup_empty_directories
|
70
|
+
# Remove empty directories in sig/generated
|
71
|
+
@sig_generated_dir.find do |path|
|
72
|
+
next unless path.directory?
|
73
|
+
next if path == @sig_generated_dir
|
74
|
+
|
75
|
+
begin
|
76
|
+
path.rmdir if path.empty?
|
77
|
+
rescue Errno::ENOTEMPTY
|
78
|
+
# Directory is not empty, skip
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_summary
|
84
|
+
puts "\nSummary:"
|
85
|
+
puts " Kept files: #{@kept_files.size}"
|
86
|
+
puts " Removed files: #{@removed_files.size}"
|
87
|
+
|
88
|
+
return unless @removed_files.any?
|
89
|
+
|
90
|
+
puts "\nRemoved files:"
|
91
|
+
@removed_files.each do |file|
|
92
|
+
puts " - #{file.relative_path_from(@project_root)}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Script execution
|
98
|
+
if __FILE__ == $PROGRAM_NAME
|
99
|
+
dry_run = ARGV.include?('--dry-run')
|
100
|
+
|
101
|
+
puts 'Running in dry-run mode (no files will be deleted)' if dry_run
|
102
|
+
|
103
|
+
cleaner = OrphanedRbsCleaner.new
|
104
|
+
cleaner.run(dry_run: dry_run)
|
105
|
+
end
|
@@ -303,7 +303,7 @@ class ConcernRbsGenerator
|
|
303
303
|
|
304
304
|
# Open namespaces
|
305
305
|
namespace_parts.each do |part|
|
306
|
-
content << (' ' * indent_level) + "module #{part}"
|
306
|
+
content << ((' ' * indent_level) + "module #{part}")
|
307
307
|
indent_level += 1
|
308
308
|
end
|
309
309
|
|
@@ -315,17 +315,17 @@ class ConcernRbsGenerator
|
|
315
315
|
# Add type parameters if present
|
316
316
|
if includer[:type_params] && !includer[:type_params].empty?
|
317
317
|
type_params_str = "[#{includer[:type_params].join(', ')}]"
|
318
|
-
content << (' ' * indent_level) + "#{keyword} #{simple_name}#{type_params_str}"
|
318
|
+
content << ((' ' * indent_level) + "#{keyword} #{simple_name}#{type_params_str}")
|
319
319
|
else
|
320
|
-
content << (' ' * indent_level) + "#{keyword} #{simple_name}"
|
320
|
+
content << ((' ' * indent_level) + "#{keyword} #{simple_name}")
|
321
321
|
end
|
322
322
|
|
323
323
|
concern = includer[:concern]
|
324
324
|
concern_simple_name = concern[:name].sub(/^::/, '').split('::').last
|
325
325
|
|
326
|
-
content << (' ' * (indent_level + 1)) + "extend #{concern_simple_name}::ClassMethods" if concern[:has_class_methods]
|
326
|
+
content << ((' ' * (indent_level + 1)) + "extend #{concern_simple_name}::ClassMethods") if concern[:has_class_methods]
|
327
327
|
|
328
|
-
content << (' ' * (indent_level + 1)) + "prepend #{concern_simple_name}::PrependMethods" if concern[:has_prepend_methods]
|
328
|
+
content << ((' ' * (indent_level + 1)) + "prepend #{concern_simple_name}::PrependMethods") if concern[:has_prepend_methods]
|
329
329
|
|
330
330
|
content << "#{' ' * indent_level}end"
|
331
331
|
content << ''
|
data/script/generate-data-rbs.rb
CHANGED
@@ -31,7 +31,7 @@ class Processor
|
|
31
31
|
puts "Scanning Ruby files in #{@lib_path}..."
|
32
32
|
|
33
33
|
@lib_path.glob('**/*.rb').map do |file|
|
34
|
-
puts "Processing #{file}..."
|
34
|
+
puts "Processing #{file}..." if ENV['DEBUG']
|
35
35
|
content = file.read
|
36
36
|
|
37
37
|
parsed = Prism.parse(content)
|
@@ -127,9 +127,7 @@ class DataClassRbsGenerator
|
|
127
127
|
|
128
128
|
def extract_constant_name(constant_path)
|
129
129
|
case constant_path
|
130
|
-
when Prism::ConstantReadNode
|
131
|
-
constant_path.name.to_s
|
132
|
-
when Prism::ConstantPathNode
|
130
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
133
131
|
constant_path.name.to_s
|
134
132
|
end
|
135
133
|
end
|
@@ -242,7 +240,7 @@ class DataClassRbsGenerator
|
|
242
240
|
end
|
243
241
|
|
244
242
|
def indent(string, level:)
|
245
|
-
' ' * level + string
|
243
|
+
(' ' * level) + string
|
246
244
|
end
|
247
245
|
end
|
248
246
|
end
|
@@ -101,15 +101,15 @@ class MemorizedIvarRbsGenerator
|
|
101
101
|
|
102
102
|
# Open namespaces
|
103
103
|
parts[0...-1].each do |part|
|
104
|
-
content << (' ' * indent_level) + "module #{part}"
|
104
|
+
content << ((' ' * indent_level) + "module #{part}")
|
105
105
|
indent_level += 1
|
106
106
|
end
|
107
107
|
|
108
108
|
# Class declaration with instance variables
|
109
|
-
content << (' ' * indent_level) + "class #{parts.last}"
|
109
|
+
content << ((' ' * indent_level) + "class #{parts.last}")
|
110
110
|
|
111
111
|
methods.each do |method|
|
112
|
-
content << (' ' * (indent_level + 1)) + "@#{method.ivar_name}: #{method.return_type}"
|
112
|
+
content << ((' ' * (indent_level + 1)) + "@#{method.ivar_name}: #{method.return_type}")
|
113
113
|
end
|
114
114
|
|
115
115
|
content << "#{' ' * indent_level}end"
|
@@ -188,9 +188,7 @@ class MemorizedIvarRbsGenerator
|
|
188
188
|
|
189
189
|
def extract_constant_name(constant_path)
|
190
190
|
case constant_path
|
191
|
-
when Prism::ConstantReadNode
|
192
|
-
constant_path.name.to_s
|
193
|
-
when Prism::ConstantPathNode
|
191
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
194
192
|
constant_path.name.to_s
|
195
193
|
end
|
196
194
|
end
|
@@ -248,11 +246,8 @@ class MemorizedIvarRbsGenerator
|
|
248
246
|
return result if result
|
249
247
|
end
|
250
248
|
nil
|
251
|
-
when Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode
|
252
|
-
# This handles @ivar ||= ...
|
253
|
-
node.name.to_s.delete('@')
|
254
|
-
when Prism::InstanceVariableWriteNode
|
255
|
-
# This handles @ivar = ...
|
249
|
+
when Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableWriteNode
|
250
|
+
# This handles @ivar ||= ... or @ivar = ...
|
256
251
|
node.name.to_s.delete('@')
|
257
252
|
else
|
258
253
|
# Recursively search in child nodes
|
@@ -11,6 +11,16 @@ module Ruboty
|
|
11
11
|
def call: ...
|
12
12
|
|
13
13
|
def body_param: () -> String
|
14
|
+
|
15
|
+
# @rbs %a{memorized}
|
16
|
+
%a{memorized}
|
17
|
+
def request: () -> Request
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @rbs body: String
|
22
|
+
# @rbs return: void
|
23
|
+
def complete_chat: (String body) -> void
|
14
24
|
end
|
15
25
|
end
|
16
26
|
end
|
@@ -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
|