ollama_chat 0.0.61 → 0.0.63

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +78 -0
  3. data/Rakefile +1 -0
  4. data/lib/ollama_chat/chat.rb +46 -16
  5. data/lib/ollama_chat/env_config.rb +18 -0
  6. data/lib/ollama_chat/follow_chat.rb +41 -2
  7. data/lib/ollama_chat/message_list.rb +3 -1
  8. data/lib/ollama_chat/ollama_chat_config/default_config.yml +38 -1
  9. data/lib/ollama_chat/parsing.rb +4 -0
  10. data/lib/ollama_chat/tool_calling.rb +26 -1
  11. data/lib/ollama_chat/tools/browse.rb +86 -0
  12. data/lib/ollama_chat/tools/concern.rb +59 -0
  13. data/lib/ollama_chat/tools/directory_structure.rb +14 -78
  14. data/lib/ollama_chat/tools/endoflife.rb +0 -98
  15. data/lib/ollama_chat/tools/{grep.rb → execute_grep.rb} +28 -30
  16. data/lib/ollama_chat/tools/file_context.rb +38 -41
  17. data/lib/ollama_chat/tools/gem_path_lookup.rb +96 -0
  18. data/lib/ollama_chat/tools/{weather.rb → get_current_weather.rb} +13 -21
  19. data/lib/ollama_chat/tools/{cve.rb → get_cve.rb} +11 -38
  20. data/lib/ollama_chat/tools/get_endoflife.rb +74 -0
  21. data/lib/ollama_chat/tools/{location.rb → get_location.rb} +9 -21
  22. data/lib/ollama_chat/tools/import_url.rb +81 -0
  23. data/lib/ollama_chat/tools/read_file.rb +43 -0
  24. data/lib/ollama_chat/tools/run_tests.rb +84 -0
  25. data/lib/ollama_chat/tools/search_web.rb +75 -0
  26. data/lib/ollama_chat/tools/vim_open_file.rb +93 -0
  27. data/lib/ollama_chat/tools/write_file.rb +91 -0
  28. data/lib/ollama_chat/tools.rb +30 -15
  29. data/lib/ollama_chat/utils/analyze_directory.rb +64 -0
  30. data/lib/ollama_chat/utils/path_validator.rb +55 -0
  31. data/lib/ollama_chat/utils.rb +2 -0
  32. data/lib/ollama_chat/version.rb +1 -1
  33. data/lib/ollama_chat/vim.rb +84 -10
  34. data/lib/ollama_chat.rb +20 -2
  35. data/ollama_chat.gemspec +6 -5
  36. data/spec/ollama_chat/message_list_spec.rb +1 -0
  37. data/spec/ollama_chat/tools/browse_spec.rb +131 -0
  38. data/spec/ollama_chat/tools/directory_structure_spec.rb +12 -7
  39. data/spec/ollama_chat/tools/execute_grep_spec.rb +211 -0
  40. data/spec/ollama_chat/tools/{file_content_spec.rb → file_context_spec.rb} +4 -7
  41. data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +82 -0
  42. data/spec/ollama_chat/tools/{weather_spec.rb → get_current_weather_spec.rb} +32 -3
  43. data/spec/ollama_chat/tools/{cve_spec.rb → get_cve_spec.rb} +11 -9
  44. data/spec/ollama_chat/tools/{endoflife_spec.rb → get_endoflife_spec.rb} +12 -10
  45. data/spec/ollama_chat/tools/{location_spec.rb → get_location_spec.rb} +6 -6
  46. data/spec/ollama_chat/tools/import_url_spec.rb +97 -0
  47. data/spec/ollama_chat/tools/read_file_spec.rb +85 -0
  48. data/spec/ollama_chat/tools/run_tests_spec.rb +113 -0
  49. data/spec/ollama_chat/tools/search_web_spec.rb +104 -0
  50. data/spec/ollama_chat/tools/vim_open_file_spec.rb +86 -0
  51. data/spec/ollama_chat/tools/write_file_spec.rb +136 -0
  52. data/spec/ollama_chat/utils/analyze_directory_spec.rb +91 -0
  53. data/spec/spec_helper.rb +44 -7
  54. metadata +77 -21
  55. data/spec/ollama_chat/tools/grep_spec.rb +0 -136
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9c81f6b541a81aff2a9988c4b00512724805592857d64a395689e4a3cb32e7c
4
- data.tar.gz: 440a433901396bf91ac0872128c353b88bb4da55995e5acdaff15f88c0bb4d94
3
+ metadata.gz: 5d207c29d7838f0cb8bb885dfc93038d2c246eb63d829d0af2e0cdef4c404d3f
4
+ data.tar.gz: 3b70df18f2d302506614a56556ec0f98d2e814a3558a7bc141b73cc0716c5158
5
5
  SHA512:
6
- metadata.gz: 483ea8c92759e79ce445df26226e47e53383089e5724dfa1af72be23ce8b90a0c7899fef859fbbf31699edeb033e52caf9fff8be3ca876d31575fd13938b8d72
7
- data.tar.gz: efa523df1b2c2659e791677ec6243e441f01d70be634472bcadcc10ce28e036faed2b05c3d06e879f9d50c41c18ed3b18125b7fab043e86c94f46193037d220e
6
+ metadata.gz: 9b2647b7371490991e879f6f48682a7e7e2a2c3d7ab0fafd41fc7275cd204f9bb288b0ca0e8b11d5be9b0c23b54c93d141e418f0495ce4782de2b0165df1a703
7
+ data.tar.gz: 83fa7ff6da22352d5d5af335e98975d423641ab3969e51f311068fd0912f4fb3494c22058c1a66e892cf1d11574cc705d9dc3c714ef50123ad555ca67011311b
data/CHANGES.md CHANGED
@@ -1,5 +1,83 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-02-09 v0.0.63
4
+
5
+ - Added `OllamaChat::Utils::PathValidator` module with `assert_valid_path`
6
+ helper and `OllamaChat::InvalidPathError` exception
7
+ - Refactored `FileContext`, `ReadFile`, and `WriteFile` tools to use new path
8
+ validation logic
9
+ - Simplified `OllamaChat::Tools::FileContext` to use glob pattern only,
10
+ removing the `path` parameter
11
+ - Added `ignore_case` flag to `execute_grep` tool with dynamic command
12
+ construction using `eval_template`
13
+ - Renamed tool classes and files to more descriptive names:
14
+ - `browser.rb` → `browse.rb` (class `Browser` → `Browse`)
15
+ - `grep.rb` → `execute_grep.rb` (class `Grep` → `ExecuteGrep`)
16
+ - `weather.rb` → `get_current_weather.rb` (class `Weather` → `GetCurrentWeather`)
17
+ - `cve.rb` → `get_cve.rb` (class `CVE` → `GetCVE`)
18
+ - `endoflife.rb` → `get_endoflife.rb` (class `EndOfLife` → `GetEndoflife`)
19
+ - `location.rb` → `get_location.rb` (class `Location` → `GetLocation`)
20
+ - `file_reader.rb` → `read_file.rb` (class `FileReader` → `ReadFile`)
21
+ - `file_writer.rb` → `write_file.rb` (class `FileWriter` → `WriteFile`)
22
+ - Updated `follow_chat.rb` to use `require_confirmation?` instead of `confirm?`
23
+ when checking tool confirmation
24
+ - Added scheme whitelist to `ImportURL` tool with `schemes: [http, https]`
25
+ configuration
26
+ - Introduced `read_file` tool with path validation and error handling
27
+ - Added `run_tests` tool for executing RSpec or Test-Unit test suites with
28
+ configurable runner and coverage reporting
29
+ - Added `vim_open_file` tool for remote Vim file opening with line range
30
+ support
31
+ - Enhanced `OllamaChat::Vim` class with file opening and line/range selection
32
+ capabilities
33
+ - Added `GemPathLookup` tool to find gem installation paths using Bundler
34
+ - Added `valid_json?` method to `OllamaChat::Tools::Concern` for consistent
35
+ JSON validation
36
+ - Implemented `ImportURL` tool for fetching web content
37
+
38
+ ## 2026-02-07 v0.0.62
39
+
40
+ **Tool Execution**
41
+ - All tools now return structured JSON errors (`error` + `message`).
42
+ - Confirmation prompts (`confirm?`) added to `OllamaChat::FollowChat`.
43
+ - `infobar` displays a busy indicator and status messages during tool runs.
44
+ - Tool methods accept `config:` and `chat:` keyword arguments.
45
+
46
+ **Tool Registration**
47
+ - Centralized logic via `OllamaChat::Tools::Concern` to prevent duplicate
48
+ registrations.
49
+
50
+ **File Context Tool (`file_context`)**
51
+ - Supports an exact `path:` argument in addition to `directory:` + `pattern:`.
52
+ - Uses `blank?` for argument validation.
53
+ - YARD documentation added.
54
+
55
+ **Directory Structure Tool (`directory_structure`)**
56
+ - Delegates to `OllamaChat::Utils::AnalyzeDirectory.generate_structure`.
57
+ - Excludes hidden files, symlinks, and the `pkg` directory by default.
58
+ - `exclude` option configurable in `default_config.yml`.
59
+
60
+ **Utility Module**
61
+ - New `OllamaChat::Utils::AnalyzeDirectory` containing the `generate_structure`
62
+ method.
63
+
64
+ **Error Handling**
65
+ - `CVE`, `EndOfLife`, `Grep`, and `Weather` tools now catch all exceptions and return structured JSON errors.
66
+
67
+ **Testing**
68
+ - Added comprehensive specs for `AnalyzeDirectory` (traversal, exclusions,
69
+ error handling).
70
+ - Tests for exact `path` usage in `file_context` with conflict detection.
71
+ - Updated `test_files` list in the gemspec.
72
+
73
+ **Configuration**
74
+ - `directory_structure` accepts an `exclude` option via `default_config.yml`.
75
+ - Tool signatures updated to accept `config:` and `chat:`.
76
+
77
+ **Gem Specification**
78
+ - Updated `test_files`, `extra_rdoc_files`, and `files` arrays to include new
79
+ utilities, tests, and documentation.
80
+
3
81
  ## 2026-02-06 v0.0.61
4
82
 
5
83
  ### New Features
data/Rakefile CHANGED
@@ -59,6 +59,7 @@ GemHadar do
59
59
  dependency 'csv', '~> 3.0'
60
60
  dependency 'const_conf', '~> 0.3'
61
61
  dependency 'context_spook', '~> 1.5'
62
+ dependency 'infobar', '>= 0.13.1'
62
63
  dependency 'rubyzip', '~> 3.0'
63
64
  development_dependency 'all_images', '~> 0.6'
64
65
  development_dependency 'rspec', '~> 3.2'
@@ -95,27 +95,21 @@ class OllamaChat::Chat
95
95
  connect_ollama
96
96
  @model = choose_model(@opts[?m], config.model.name)
97
97
  @model_options = Ollama::Options[config.model.options]
98
- model_system = pull_model_unless_present(@model, @model_options)
99
- embedding_enabled.set(config.embedding.enabled && !@opts[?E])
98
+ @model_system = pull_model_unless_present(@model, @model_options)
100
99
  if @opts[?c]
101
100
  messages.load_conversation(@opts[?c])
102
101
  else
103
- default = config.system_prompts.default? || model_system
104
- if @opts[?s] =~ /\A\?/
105
- change_system_prompt(default, system: @opts[?s])
106
- else
107
- system = OllamaChat::Utils::FileArgument.get_file_argument(@opts[?s], default:)
108
- system.present? and messages.set_system_prompt(system)
109
- end
102
+ setup_system_prompt
110
103
  end
104
+ embedding_enabled.set(config.embedding.enabled && !@opts[?E])
111
105
  @documents = setup_documents
112
106
  @cache = setup_cache
113
107
  @images = []
114
108
  @kramdown_ansi_styles = configure_kramdown_ansi_styles
109
+ @enabled_tools = default_enabled_tools
110
+ @tool_call_results = {}
115
111
  init_chat_history
116
112
  @opts[?S] and init_server_socket
117
- @enabled_tools = config.tools.to_h.map { |n, v| n.to_s if v[:default] }.compact
118
- @tool_call_results = {}
119
113
  rescue ComplexConfig::AttributeMissing, ComplexConfig::ConfigurationSyntaxError => e
120
114
  fix_config(e)
121
115
  end
@@ -208,6 +202,25 @@ class OllamaChat::Chat
208
202
  interact_with_user
209
203
  end
210
204
 
205
+ # The vim method creates and returns a new Vim instance for interacting with
206
+ # a Vim server.
207
+ #
208
+ # This method initializes a Vim client that can be used to insert text into
209
+ # Vim buffers or open files in a running Vim server. It derives the server
210
+ # name from the provided argument or uses a default server name based on the
211
+ # current working directory.
212
+ #
213
+ # @param server_name [ String, nil ] the name of the Vim server to connect to
214
+ # If nil or empty, a default server name is derived from the current
215
+ # working directory
216
+ #
217
+ # @return [ OllamaChat::Vim ] a new Vim instance configured with the
218
+ # specified server name
219
+ def vim(server_name = nil)
220
+ clientserver = config.vim?&.clientserver
221
+ OllamaChat::Vim.new(server_name, clientserver:)
222
+ end
223
+
211
224
  private
212
225
 
213
226
  # Handles user input commands and processes chat interactions.
@@ -248,7 +261,7 @@ class OllamaChat::Chat
248
261
  messages.list_conversation(n)
249
262
  :next
250
263
  when %r(^/last(?:\s+(\d*))?$)
251
- n = $1.to_i if $1
264
+ n = $1.to_i.clamp(1..)
252
265
  messages.show_last(n)
253
266
  :next
254
267
  when %r(^/clear(?:\s+(messages|links|history|tags|all))?$)
@@ -370,8 +383,7 @@ class OllamaChat::Chat
370
383
  :next
371
384
  when %r(^/vim(?:\s+(.+))?$)
372
385
  if message = messages.last
373
- clientserver = config.vim?&.clientserver
374
- OllamaChat::Vim.new($1, clientserver:).insert message.content
386
+ vim($1).insert message.content
375
387
  else
376
388
  STDERR.puts "Warning: No message found to insert into Vim"
377
389
  end
@@ -702,8 +714,6 @@ class OllamaChat::Chat
702
714
  save_history
703
715
  end
704
716
 
705
- private
706
-
707
717
  # The base_url method returns the Ollama server URL from command-line options
708
718
  # or environment configuration.
709
719
  #
@@ -738,6 +748,26 @@ class OllamaChat::Chat
738
748
  @ollama
739
749
  end
740
750
 
751
+ # Sets up the system prompt for the chat session.
752
+ #
753
+ # This method determines whether to use a default system prompt or a custom
754
+ # one specified via command-line options. If a custom system prompt is
755
+ # provided with a regex selector (starting with ?), it invokes the
756
+ # change_system_prompt method to handle the selection. Otherwise, it
757
+ # retrieves the system prompt from a file or uses the default value, then
758
+ # sets it in the message history.
759
+ #
760
+ # @return [ void ] this method returns nil after setting up the system prompt
761
+ def setup_system_prompt
762
+ default = config.system_prompts.default? || @model_system
763
+ if @opts[?s] =~ /\A\?/
764
+ change_system_prompt(default, system: @opts[?s])
765
+ else
766
+ system = OllamaChat::Utils::FileArgument.get_file_argument(@opts[?s], default:)
767
+ system.present? and messages.set_system_prompt(system)
768
+ end
769
+ end
770
+
741
771
  # The setup_documents method initializes the document processing pipeline by
742
772
  # configuring the embedding model and database connection.
743
773
  # It then loads specified documents into the system and returns the
@@ -51,6 +51,14 @@ module OllamaChat
51
51
  end
52
52
  end
53
53
 
54
+ BROWSER = set do
55
+ description 'Browser to use'
56
+
57
+ default do
58
+ %w[ open xdg-open ].find { `which #{_1}` }.full?(:chomp)
59
+ end
60
+ end
61
+
54
62
  DIFF_TOOL = set do
55
63
  description 'Diff tool to apply changes with'
56
64
 
@@ -128,6 +136,16 @@ module OllamaChat
128
136
  description 'File to save the chat history in'
129
137
  default XDG_CACHE_HOME + 'history.json'
130
138
  end
139
+
140
+ module TOOLS
141
+ description 'Tool specific configuration settings'
142
+
143
+ RUN_TESTS_TEST_RUNNER = set do
144
+ description 'Configured test runner for run_tests tool function'
145
+ default 'rspec'
146
+ required true
147
+ end
148
+ end
131
149
  end
132
150
  end
133
151
  end
@@ -102,8 +102,47 @@ class OllamaChat::FollowChat
102
102
 
103
103
  response.message.tool_calls.each do |tool_call|
104
104
  name = tool_call.function.name
105
- @chat.tool_call_results[name] = OllamaChat::Tools.registered[name].
106
- execute(tool_call, chat: @chat, config: @chat.config)
105
+ unless @chat.config.tools.attribute_set?(name)
106
+ STDERR.printf("Unconfigured tool named %s ignored => Skip.\n", name)
107
+ next
108
+ end
109
+ unless OllamaChat::Tools.registered?(name)
110
+ STDERR.printf("Unregistered tool named %s ignored => Skip.\n", name)
111
+ next
112
+ end
113
+ STDOUT.puts
114
+ confirmed = true
115
+ args = JSON.pretty_generate(tool_call.function.arguments)
116
+ if @chat.config.tools[name].require_confirmation?
117
+ prompt = "I want to execute tool %s\n%s\nConfirm? (y/n) " % [
118
+ bold { name },
119
+ italic { args },
120
+ ]
121
+ confirmed = @chat.ask?(prompt:) =~ /\Ay/i
122
+ else
123
+ STDOUT.puts "Executing tool %s\n%s" % [
124
+ bold { name },
125
+ italic { args },
126
+ ]
127
+ end
128
+ result = nil
129
+ if confirmed
130
+ STDOUT.printf(
131
+ "\n%s Execution of tool %s confirmed.\n\n", ?✅, bold { name }
132
+ )
133
+ result = OllamaChat::Tools.registered[name].
134
+ execute(tool_call, chat: @chat, config: @chat.config)
135
+ else
136
+ result = JSON(
137
+ message: 'User denied confirmation!',
138
+ resolve: 'You **MUST** ask the user for instructions on how to proceed!!!',
139
+ )
140
+ STDOUT.printf(
141
+ "\n%s Execution of tool %s denied by user.\n", ?🚫, bold { name }
142
+ )
143
+ sleep 1
144
+ end
145
+ @chat.tool_call_results[name] = result
107
146
  end
108
147
  end
109
148
 
@@ -113,7 +113,9 @@ class OllamaChat::MessageList
113
113
  end
114
114
  @messages =
115
115
  File.open(filename, 'r') do |output|
116
- JSON(output.read).map { Ollama::Message.from_hash(_1) }
116
+ JSON(output.read).map {
117
+ Ollama::Message.from_hash(_1 | { 'content' => nil })
118
+ }
117
119
  end
118
120
  self
119
121
  end
@@ -98,8 +98,45 @@ tools:
98
98
  default: true
99
99
  file_context:
100
100
  default: false
101
+ require_confirmation: true
102
+ allowed:
103
+ - ./spec
101
104
  directory_structure:
102
105
  default: false
106
+ require_confirmation: true
107
+ exclude:
108
+ - corpus
109
+ - pkg
103
110
  execute_grep:
104
111
  default: true
105
- cmd: 'grep -m %{max_results} -r %{pattern} %{path}'
112
+ cmd: |
113
+ grep #{'-i' if ignore_case} -m #{max_results} -r #{pattern} #{path}
114
+ browse:
115
+ default: false
116
+ write_file:
117
+ default: false
118
+ require_confirmation: true
119
+ allowed:
120
+ - ./tmp
121
+ read_file:
122
+ default: false
123
+ require_confirmation: false
124
+ allowed:
125
+ - ./tmp
126
+ - ./lib
127
+ - ./spec
128
+ search_web:
129
+ default: true
130
+ import_url:
131
+ default: true
132
+ require_confirmation: true
133
+ schemes:
134
+ - http
135
+ - https
136
+ gem_path_lookup:
137
+ default: true
138
+ vim_open_file:
139
+ default: true
140
+ run_tests:
141
+ require_confirmation: true
142
+ default: true
@@ -219,6 +219,10 @@ module OllamaChat::Parsing
219
219
  tags = Documentrix::Utils::Tags.new valid_tag: /\A#*([\w\]\[]+)/
220
220
  contents = [ content ]
221
221
  content.scan(CONTENT_REGEXP).each { |url, tag, file_url, quoted_file, file|
222
+ if file && File.directory?(file)
223
+ contents << OllamaChat::Utils::AnalyzeDirectory.generate_structure(file)
224
+ next
225
+ end
222
226
  check_exist = false
223
227
  case
224
228
  when tag
@@ -13,6 +13,27 @@ module OllamaChat::ToolCalling
13
13
  @enabled_tools.map { OllamaChat::Tools.registered[it]&.to_hash }.compact
14
14
  end
15
15
 
16
+ # The default_enabled_tools method returns an array of tool names that are
17
+ # configured as default tools.
18
+ #
19
+ # This method iterates through the configured tools and collects those that
20
+ # are registered and have the default flag set to true. Unregistered tools
21
+ # are skipped with a warning message.
22
+ #
23
+ # @return [Array<String>] a sorted array of tool names that are configured as
24
+ # default tools
25
+ def default_enabled_tools
26
+ result = []
27
+ config.tools.each { |n, v|
28
+ if OllamaChat::Tools.registered?(n)
29
+ result << n.to_s if v.default
30
+ else
31
+ STDERR.puts "Skipping configuration for unregistered tool %s" % bold { n }
32
+ end
33
+ }
34
+ result
35
+ end
36
+
16
37
  # The configured_tools method returns an array of tool names configured for
17
38
  # the chat session.
18
39
  #
@@ -39,7 +60,11 @@ module OllamaChat::ToolCalling
39
60
  def list_tools
40
61
  configured_tools.each do |tool|
41
62
  enabled = @enabled_tools.member?(tool) ? ?✓ : ?☐
42
- printf "%s %s\n", enabled, (enabled ? bold { tool } : tool)
63
+ require_confirmation = config.tools[tool].require_confirmation? ? ?? : ?☐
64
+ printf(
65
+ "%s %s %s\n",
66
+ enabled, require_confirmation, (enabled ? bold { tool } : tool)
67
+ )
43
68
  end
44
69
  end
45
70
 
@@ -0,0 +1,86 @@
1
+ require "shellwords"
2
+
3
+ # A tool for opening URLs/files in the user's default browser application.
4
+ #
5
+ # This tool enables the chat client to open web URLs or local files in the
6
+ # user's default browser, allowing users to view content directly in their
7
+ # browser environment. It integrates with the Ollama tool calling system to
8
+ # provide
9
+ # seamless web browsing capabilities during chat interactions.
10
+ class OllamaChat::Tools::Browse
11
+ include OllamaChat::Tools::Concern
12
+
13
+ def self.register_name = 'browse'
14
+
15
+ # Creates and returns a tool definition for opening URLs/files in the browser.
16
+ #
17
+ # This method constructs the function signature that describes what the tool
18
+ # does, its parameters, and required fields. The tool expects a URL parameter
19
+ # to be provided.
20
+ #
21
+ # @return [Ollama::Tool] a tool definition for opening URLs/files in the browser
22
+ def tool
23
+ Tool.new(
24
+ type: 'function',
25
+ function: Tool::Function.new(
26
+ name:,
27
+ description: <<~EOT,
28
+ Open a URL or file in the user\'s default browser application so they
29
+ can view the content directly
30
+ EOT
31
+ parameters: Tool::Function::Parameters.new(
32
+ type: 'object',
33
+ properties: {
34
+ url: Tool::Function::Parameters::Property.new(
35
+ type: 'string',
36
+ description: <<~EOT,
37
+ The URL or file to open in the user\'s browser for them to view
38
+ directly
39
+ EOT
40
+ ),
41
+ },
42
+ required: %w[url]
43
+ )
44
+ )
45
+ )
46
+ end
47
+
48
+ # Executes the browser opening operation.
49
+ #
50
+ # This method opens the specified URL or file in the user's default browser
51
+ # application. It handles the system call and returns the result status.
52
+ #
53
+ # @param tool_call [Ollama::Tool::Call] the tool call object containing function details
54
+ # @param opts [Hash] additional options
55
+ # @return [String] the execution result as JSON string
56
+ # @raise [StandardError] if there's an issue with opening the URL/file
57
+ def execute(tool_call, **opts)
58
+ url = tool_call.function.arguments.url
59
+ result = browse_url(url)
60
+ {
61
+ success: result.success?,
62
+ exitstatus: result.exitstatus,
63
+ message: 'opening URL/file',
64
+ url: ,
65
+ }.to_json
66
+ rescue => e
67
+ { error: e.class, message: e.message }.to_json
68
+ end
69
+
70
+ private
71
+
72
+ # Opens a URL or file in the system browser.
73
+ #
74
+ # This method uses the system's default browser to open the provided URL or file.
75
+ # It respects the BROWSER environment variable if set, otherwise defaults to "open".
76
+ #
77
+ # @param url [String] the URL or file path to open
78
+ # @return [Process::Status] the process status of the system call
79
+ def browse_url(url)
80
+ browser = OllamaChat::EnvConfig::BROWSER? || "open"
81
+ system %{#{browser} #{Shellwords.escape(url)}}
82
+ $?
83
+ end
84
+
85
+ self
86
+ end.register
@@ -0,0 +1,59 @@
1
+ # A module that provides common functionality for OllamaChat tools.
2
+ #
3
+ # This module serves as a base class for all tool implementations in the
4
+ # OllamaChat application, providing shared behavior and methods that tools can
5
+ # inherit from. It includes delegation to the tool name and registration
6
+ # functionality to integrate tools into the chat system's tool registry.
7
+ module OllamaChat::Tools::Concern
8
+ extend Tins::Concern
9
+
10
+ included do
11
+ include Ollama
12
+
13
+ implement :tool, :submodule
14
+ end
15
+
16
+ class_methods do
17
+ # The register method registers the tool with the tools registry.
18
+ # @return [ OllamaChat::Tools ] the current instance after registration
19
+ def register
20
+ OllamaChat::Tools.register(self)
21
+ end
22
+
23
+ # The register_name attribute accessor provides read and write access to
24
+ # the register name of the tool.
25
+ #
26
+ # @return [ String ] the register name of the tool
27
+ attr_accessor :register_name
28
+ end
29
+
30
+ # The name method returns the registered name of the tool.
31
+ #
32
+ # @return [String] the registered name of the tool instance
33
+ def name
34
+ self.class.register_name
35
+ end
36
+
37
+ # The valid_json? method returns a proc that validates JSON data from a
38
+ # temporary file.
39
+ #
40
+ # @return [Proc] a proc that takes a temporary file and returns its JSON
41
+ # content or raises an error
42
+ def valid_json?
43
+ -> tmp {
44
+ if data = tmp.read.full?
45
+ JSON.parse(data)
46
+ return data
47
+ else
48
+ raise JSON::ParserError, 'require JSON data'
49
+ end
50
+ }
51
+ end
52
+
53
+ # The to_hash method converts the tool to a hash representation.
54
+ #
55
+ # @return [ Hash ] a hash representation of the tool
56
+ def to_hash
57
+ tool.to_hash
58
+ end
59
+ end
@@ -7,19 +7,10 @@
7
7
  # The tool supports traversing directories and returns a structured
8
8
  # representation of the file system hierarchy.
9
9
  class OllamaChat::Tools::DirectoryStructure
10
- include Ollama
10
+ include OllamaChat::Tools::Concern
11
+ include OllamaChat::Utils::AnalyzeDirectory
11
12
 
12
- # Initializes a new directory_structure tool instance.
13
- #
14
- # @return [OllamaChat::Tools::DirectoryStructure] a new directory_structure tool instance
15
- def initialize
16
- @name = 'directory_structure'
17
- end
18
-
19
- # Returns the name of the tool.
20
- #
21
- # @return [String] the name of the tool ('directory_structure')
22
- attr_reader :name
13
+ def self.register_name = 'directory_structure'
23
14
 
24
15
  # Creates and returns a tool definition for retrieving directory structure.
25
16
  #
@@ -51,77 +42,22 @@ class OllamaChat::Tools::DirectoryStructure
51
42
  # Executes the directory structure retrieval operation.
52
43
  #
53
44
  # This method traverses the directory structure starting from the specified
54
- # path and returns a structured representation of
55
- # the file system hierarchy.
45
+ # path and returns a structured representation of the file system hierarchy.
46
+ #
47
+ # @param tool_call [Ollama::Tool::Call] the tool call object containing
48
+ # function details
56
49
  #
57
- # @param tool_call [Ollama::Tool::Call] the tool call object containing function details
58
50
  # @param opts [Hash] additional options
59
51
  # @return [String] the directory structure as a JSON string
60
- # @raise [StandardError] if there's an issue with directory traversal or JSON serialization
52
+ # @raise [StandardError] if there's an issue with directory traversal or JSON
53
+ # serialization
61
54
  def execute(tool_call, **opts)
62
- path = Pathname.new(tool_call.function.arguments.path || '.')
55
+ config = opts[:config]
56
+ path = Pathname.new(tool_call.function.arguments.path || '.')
63
57
 
64
- structure = generate_structure(path)
58
+ structure = generate_structure(path, exclude: config.tools.directory_structure.exclude?)
65
59
  structure.to_json
66
60
  end
67
61
 
68
- # Converts the tool to a hash representation.
69
- #
70
- # This method provides a standardized way to serialize the tool definition
71
- # for use in tool calling systems.
72
- #
73
- # @return [Hash] a hash representation of the tool
74
- def to_hash
75
- tool.to_hash
76
- end
77
-
78
- private
79
-
80
- # Generates a directory structure representation with files and
81
- # subdirectories.
82
- #
83
- # @param path [String] the path to start generating the structure from,
84
- # defaults to current directory
85
- #
86
- # @return [Array<Hash>, Hash] an array of hashes representing files and
87
- # directories, or a hash with error information if an exception occurs
88
- # @return [Array] an empty array if the path is invalid or has no children
89
- # @return [Hash] a hash with error details if an exception is raised during processing
90
- #
91
- # @example Generate structure for current directory
92
- # generate_structure
93
- #
94
- # @example Generate structure for a specific path
95
- # generate_structure('/path/to/directory')
96
- #
97
- # @note Hidden files and directories (starting with '.') are skipped
98
- # @note Symbolic links are skipped
99
- # @note The method uses recursive calls to traverse subdirectories
100
- # @note If an error occurs during traversal, it returns a hash with error details
101
- def generate_structure(path = ?.)
102
- path = Pathname.new(path).expand_path
103
- entries = []
104
- path.children.sort.each do |child|
105
- # Skip hidden files/directories
106
- next if child.basename.to_s.start_with?('.')
107
- # Skip symlinks
108
- next if child.symlink?
109
-
110
- if child.directory?
111
- entries << {
112
- type: 'directory',
113
- name: child.basename.to_s,
114
- children: generate_structure(child)
115
- }
116
- elsif child.file?
117
- entries << {
118
- type: 'file',
119
- name: child.basename.to_s
120
- }
121
- end
122
- end
123
- entries
124
- rescue => e
125
- { error: e.class, message: e.message }
126
- end
127
- end
62
+ self
63
+ end.register