ollama_chat 0.0.62 → 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +35 -0
  3. data/lib/ollama_chat/chat.rb +46 -16
  4. data/lib/ollama_chat/env_config.rb +18 -0
  5. data/lib/ollama_chat/follow_chat.rb +31 -28
  6. data/lib/ollama_chat/message_list.rb +3 -1
  7. data/lib/ollama_chat/ollama_chat_config/default_config.yml +35 -3
  8. data/lib/ollama_chat/tool_calling.rb +26 -1
  9. data/lib/ollama_chat/tools/browse.rb +86 -0
  10. data/lib/ollama_chat/tools/concern.rb +16 -0
  11. data/lib/ollama_chat/tools/endoflife.rb +0 -74
  12. data/lib/ollama_chat/tools/{grep.rb → execute_grep.rb} +24 -4
  13. data/lib/ollama_chat/tools/file_context.rb +34 -21
  14. data/lib/ollama_chat/tools/gem_path_lookup.rb +96 -0
  15. data/lib/ollama_chat/tools/{weather.rb → get_current_weather.rb} +4 -2
  16. data/lib/ollama_chat/tools/{cve.rb → get_cve.rb} +5 -8
  17. data/lib/ollama_chat/tools/get_endoflife.rb +74 -0
  18. data/lib/ollama_chat/tools/{location.rb → get_location.rb} +1 -1
  19. data/lib/ollama_chat/tools/import_url.rb +81 -0
  20. data/lib/ollama_chat/tools/read_file.rb +43 -0
  21. data/lib/ollama_chat/tools/run_tests.rb +84 -0
  22. data/lib/ollama_chat/tools/search_web.rb +75 -0
  23. data/lib/ollama_chat/tools/vim_open_file.rb +93 -0
  24. data/lib/ollama_chat/tools/write_file.rb +91 -0
  25. data/lib/ollama_chat/tools.rb +25 -7
  26. data/lib/ollama_chat/utils/path_validator.rb +55 -0
  27. data/lib/ollama_chat/utils.rb +1 -0
  28. data/lib/ollama_chat/version.rb +1 -1
  29. data/lib/ollama_chat/vim.rb +84 -10
  30. data/lib/ollama_chat.rb +19 -2
  31. data/ollama_chat.gemspec +5 -5
  32. data/spec/ollama_chat/message_list_spec.rb +1 -0
  33. data/spec/ollama_chat/tools/browse_spec.rb +131 -0
  34. data/spec/ollama_chat/tools/directory_structure_spec.rb +3 -3
  35. data/spec/ollama_chat/tools/{grep_spec.rb → execute_grep_spec.rb} +72 -20
  36. data/spec/ollama_chat/tools/file_context_spec.rb +6 -94
  37. data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +82 -0
  38. data/spec/ollama_chat/tools/{weather_spec.rb → get_current_weather_spec.rb} +6 -6
  39. data/spec/ollama_chat/tools/{cve_spec.rb → get_cve_spec.rb} +10 -9
  40. data/spec/ollama_chat/tools/{endoflife_spec.rb → get_endoflife_spec.rb} +12 -10
  41. data/spec/ollama_chat/tools/{location_spec.rb → get_location_spec.rb} +5 -5
  42. data/spec/ollama_chat/tools/import_url_spec.rb +97 -0
  43. data/spec/ollama_chat/tools/read_file_spec.rb +85 -0
  44. data/spec/ollama_chat/tools/run_tests_spec.rb +113 -0
  45. data/spec/ollama_chat/tools/search_web_spec.rb +104 -0
  46. data/spec/ollama_chat/tools/vim_open_file_spec.rb +86 -0
  47. data/spec/ollama_chat/tools/write_file_spec.rb +136 -0
  48. data/spec/spec_helper.rb +44 -7
  49. metadata +55 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c3a369c576e25ace4f2d8eeac73b7fe180e80008fc287713f85a0db18d65873
4
- data.tar.gz: 4b141d5b9e14fa584b87d5b09e9d6a2c13d1d0f13da197e7b76f659c8fe696ab
3
+ metadata.gz: 5d207c29d7838f0cb8bb885dfc93038d2c246eb63d829d0af2e0cdef4c404d3f
4
+ data.tar.gz: 3b70df18f2d302506614a56556ec0f98d2e814a3558a7bc141b73cc0716c5158
5
5
  SHA512:
6
- metadata.gz: 7923dac4d8370ee08e5c6169cbcb7b3d5ad23c2fbfeb7cca43511bd371f47107e1dcd9fd804c6270d5f28943d8b38c0786d459d13bc74e969295e4833cf57a80
7
- data.tar.gz: ec11915e20d4f5013c7d1850bcc10ac04133dfcc12dd29c77176abd1fdfa8ff44db416e4adea6ee40862fc8c59a9dd2d67ee8e073f8493b1bf1b3f626c4a4bec
6
+ metadata.gz: 9b2647b7371490991e879f6f48682a7e7e2a2c3d7ab0fafd41fc7275cd204f9bb288b0ca0e8b11d5be9b0c23b54c93d141e418f0495ce4782de2b0165df1a703
7
+ data.tar.gz: 83fa7ff6da22352d5d5af335e98975d423641ab3969e51f311068fd0912f4fb3494c22058c1a66e892cf1d11574cc705d9dc3c714ef50123ad555ca67011311b
data/CHANGES.md CHANGED
@@ -1,5 +1,40 @@
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
+
3
38
  ## 2026-02-07 v0.0.62
4
39
 
5
40
  **Tool Execution**
@@ -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
@@ -103,43 +103,46 @@ class OllamaChat::FollowChat
103
103
  response.message.tool_calls.each do |tool_call|
104
104
  name = tool_call.function.name
105
105
  unless @chat.config.tools.attribute_set?(name)
106
- STDERR.printf("Unknown tool named %s ignored => Skip.\n", 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)
107
111
  next
108
112
  end
109
113
  STDOUT.puts
110
114
  confirmed = true
111
- if @chat.config.tools[name].confirm?
112
- prompt = "I want to execute tool %s(%s)\n\nConfirm? (y/n) " % [
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) " % [
113
118
  bold { name },
114
- italic { JSON(tool_call.function.arguments) },
119
+ italic { args },
115
120
  ]
116
121
  confirmed = @chat.ask?(prompt:) =~ /\Ay/i
122
+ else
123
+ STDOUT.puts "Executing tool %s\n%s" % [
124
+ bold { name },
125
+ italic { args },
126
+ ]
117
127
  end
118
- Infobar.busy(
119
- label: 'Executing tool %s' % name,
120
- frames: :braille7,
121
- output: STDOUT,
122
- ) do
123
- result = nil
124
- if confirmed
125
- infobar.printf(
126
- "%s Execution of tool %s(%s) confirmed.\n",
127
- ?✅,
128
- bold { name },
129
- italic { JSON(tool_call.function.arguments) }
130
- )
131
- result = OllamaChat::Tools.registered[name].
132
- execute(tool_call, chat: @chat, config: @chat.config)
133
- else
134
- result = JSON(
135
- message: 'User denied confirmation!',
136
- resolve: 'You **MUST** ask the user for instructions on how to proceed!!!',
137
- )
138
- end
139
- @chat.tool_call_results[name] = result
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
140
144
  end
141
- infobar.finish message: "Executed tool #{bold { name }} %te %s"
142
- infobar.newline
145
+ @chat.tool_call_results[name] = result
143
146
  end
144
147
  end
145
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,13 +98,45 @@ tools:
98
98
  default: true
99
99
  file_context:
100
100
  default: false
101
- confirm: true
101
+ require_confirmation: true
102
+ allowed:
103
+ - ./spec
102
104
  directory_structure:
103
105
  default: false
104
- confirm: true
106
+ require_confirmation: true
105
107
  exclude:
106
108
  - corpus
107
109
  - pkg
108
110
  execute_grep:
109
111
  default: true
110
- 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
@@ -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
@@ -34,6 +34,22 @@ module OllamaChat::Tools::Concern
34
34
  self.class.register_name
35
35
  end
36
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
+
37
53
  # The to_hash method converts the tool to a hash representation.
38
54
  #
39
55
  # @return [ Hash ] a hash representation of the tool
@@ -1,74 +0,0 @@
1
- # A tool for fetching endoflife.date product information.
2
- #
3
- # This tool allows the chat client to retrieve endoflife.date information
4
- # for software products by ID. It integrates with the Ollama tool calling
5
- # system to provide lifecycle and support information to the language model.
6
- class OllamaChat::Tools::EndOfLife
7
- include OllamaChat::Tools::Concern
8
-
9
- def self.register_name = 'get_endoflife'
10
-
11
- # Creates and returns a tool definition for getting endoflife information.
12
- #
13
- # This method constructs the function signature that describes what the tool
14
- # does, its parameters, and required fields. The tool expects a product name
15
- # parameter to be provided.
16
- #
17
- # @return [Ollama::Tool] a tool definition for retrieving endoflife information
18
- def tool
19
- Tool.new(
20
- type: 'function',
21
- function: Tool::Function.new(
22
- name:,
23
- description: 'Get the endoflife information for a product as JSON',
24
- parameters: Tool::Function::Parameters.new(
25
- type: 'object',
26
- properties: {
27
- product: Tool::Function::Parameters::Property.new(
28
- type: 'string',
29
- description: 'The product name to get endoflife information for'
30
- ),
31
- },
32
- required: %w[product]
33
- )
34
- )
35
- )
36
- end
37
-
38
- # Executes the endoflife lookup operation.
39
- #
40
- # This method fetches endoflife data from the endoflife.date API using the
41
- # provided product name. It handles the HTTP request, parses the JSON response,
42
- # and returns the structured data.
43
- #
44
- # @param tool_call [Ollama::Tool::Call] the tool call object containing function details
45
- # @param opts [Hash] additional options
46
- # @option opts [ComplexConfig::Settings] :config the configuration object
47
- # @return [Hash, String] the parsed endoflife data as a hash or an error message
48
- # @raise [StandardError] if there's an issue with the HTTP request or JSON parsing
49
- def execute(tool_call, **opts)
50
- config = opts[:config]
51
- product = tool_call.function.arguments.product
52
-
53
- # Construct the URL for the endoflife API
54
- url = config.tools.get_endoflife.url % { product: }
55
-
56
- # Fetch the data from endoflife.date API
57
- OllamaChat::Utils::Fetcher.get(
58
- url,
59
- headers: {
60
- 'Accept' => 'application/json',
61
- 'User-Agent' => OllamaChat::Chat.user_agent
62
- },
63
- debug: OllamaChat::EnvConfig::OLLAMA::CHAT::DEBUG
64
- ) do |tmp|
65
- # Parse the JSON response
66
- data = JSON.parse(tmp.read, object_class: JSON::GenericObject)
67
- return data
68
- end
69
- rescue => e
70
- "Failed to fetch endoflife data for #{product}: #{e.class}: #{e.message}"
71
- end
72
-
73
- self
74
- end.register
@@ -3,7 +3,7 @@
3
3
  # This tool allows the chat client to execute grep commands to search for
4
4
  # patterns in files. It integrates with the Ollama tool calling system to
5
5
  # provide file search capabilities within the language model's context.
6
- class OllamaChat::Tools::Grep
6
+ class OllamaChat::Tools::ExecuteGrep
7
7
  include OllamaChat::Tools::Concern
8
8
 
9
9
  def self.register_name = 'execute_grep'
@@ -34,6 +34,10 @@ class OllamaChat::Tools::Grep
34
34
  max_results: Tool::Function::Parameters::Property.new(
35
35
  type: 'integer',
36
36
  description: 'Maximum number of matches to return (optional)'
37
+ ),
38
+ ignore_case: Tool::Function::Parameters::Property.new(
39
+ type: 'boolean',
40
+ description: 'Matches ignore case if true, (default: false)'
37
41
  )
38
42
  },
39
43
  required: %w[pattern]
@@ -54,20 +58,36 @@ class OllamaChat::Tools::Grep
54
58
  # @param tool_call [OllamaChat::Tool::Call] The tool call with arguments
55
59
  # @param opts [Hash] Additional options
56
60
  # @option opts [Hash] :config Configuration options including tool settings
57
- # @return [Hash] The execution result with command and output
58
- # @raise [StandardError] If the command execution fails
61
+ # @return [String] The execution result with command and output as JSON string
59
62
  def execute(tool_call, **opts)
60
63
  config = opts[:config]
61
64
  args = tool_call.function.arguments
62
65
  pattern = Shellwords.escape(args.pattern)
63
66
  path = Shellwords.escape(Pathname.new(args.path || '.').expand_path)
64
67
  max_results = args.max_results || 100
65
- cmd = config.tools.execute_grep.cmd % { max_results:, path:, pattern: }
68
+ ignore_case = args.ignore_case || false
69
+ cmd = eval_template(config, pattern, path, max_results, ignore_case)
66
70
  result = OllamaChat::Utils::Fetcher.execute(cmd, &:read)
67
71
  { cmd:, result: }.to_json
68
72
  rescue => e
69
73
  { error: e.class, message: e.message }.to_json
70
74
  end
71
75
 
76
+ private
77
+
78
+ # Evaluates a template string using the provided configuration and
79
+ # parameters.
80
+ #
81
+ # @param config [Object] the configuration object containing tool settings
82
+ # @param pattern [String] the regex pattern to search for
83
+ # @param path [String] the file or directory path to search in
84
+ # @param max_results [Integer] the maximum number of matches to return
85
+ # @param ignore_case [Boolean] whether to ignore case when searching
86
+ #
87
+ # @return [String] the evaluated template string with substituted variables
88
+ def eval_template(config, pattern, path, max_results, ignore_case)
89
+ eval('"%s"' % config.tools.execute_grep.cmd.chomp)
90
+ end
91
+
72
92
  self
73
93
  end.register