ollama_chat 0.0.113 → 0.0.115

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +120 -0
  3. data/README.md +7 -0
  4. data/bin/ollama_chat_log +1 -1
  5. data/bin/ollama_chat_send +3 -1
  6. data/lib/ollama_chat/chat.rb +30 -14
  7. data/lib/ollama_chat/commands.rb +36 -18
  8. data/lib/ollama_chat/compaction.rb +289 -0
  9. data/lib/ollama_chat/config_handling.rb +32 -0
  10. data/lib/ollama_chat/database/migrations/010_add_enabled_to_collections.rb +7 -0
  11. data/lib/ollama_chat/database/models/app_state.rb +48 -16
  12. data/lib/ollama_chat/database/models/session.rb +1 -2
  13. data/lib/ollama_chat/database.rb +4 -2
  14. data/lib/ollama_chat/information.rb +53 -6
  15. data/lib/ollama_chat/logging.rb +3 -2
  16. data/lib/ollama_chat/message.rb +22 -1
  17. data/lib/ollama_chat/message_format.rb +1 -2
  18. data/lib/ollama_chat/message_list.rb +223 -9
  19. data/lib/ollama_chat/model_handling.rb +1 -1
  20. data/lib/ollama_chat/oc.rb +17 -7
  21. data/lib/ollama_chat/ollama_chat_config/default_config.yml +72 -3
  22. data/lib/ollama_chat/parsing.rb +2 -1
  23. data/lib/ollama_chat/prompt_handling.rb +1 -1
  24. data/lib/ollama_chat/prompt_management.rb +1 -0
  25. data/lib/ollama_chat/rag_handling.rb +19 -5
  26. data/lib/ollama_chat/session_management.rb +5 -3
  27. data/lib/ollama_chat/token_estimator/crude.rb +1 -1
  28. data/lib/ollama_chat/token_estimator.rb +1 -1
  29. data/lib/ollama_chat/tools/concern.rb +17 -0
  30. data/lib/ollama_chat/tools/directory_structure.rb +11 -7
  31. data/lib/ollama_chat/tools/eval_ruby.rb +16 -6
  32. data/lib/ollama_chat/tools/execute_shell.rb +158 -0
  33. data/lib/ollama_chat/tools/get_rfc.rb +2 -0
  34. data/lib/ollama_chat/tools/lookup_group.rb +100 -0
  35. data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -1
  36. data/lib/ollama_chat/tools/patch_file.rb +23 -5
  37. data/lib/ollama_chat/tools/read_file.rb +1 -1
  38. data/lib/ollama_chat/tools/resolve_tag.rb +1 -1
  39. data/lib/ollama_chat/tools/search_knowledge.rb +7 -1
  40. data/lib/ollama_chat/tools/write_file.rb +25 -6
  41. data/lib/ollama_chat/tools.rb +2 -0
  42. data/lib/ollama_chat/utils/analyze_directory.rb +5 -4
  43. data/lib/ollama_chat/utils/fetcher.rb +1 -1
  44. data/lib/ollama_chat/utils/log_rotation.rb +29 -0
  45. data/lib/ollama_chat/utils/strip_ansi.rb +22 -0
  46. data/lib/ollama_chat/utils/tag_resolver.rb +3 -3
  47. data/lib/ollama_chat/utils.rb +2 -0
  48. data/lib/ollama_chat/version.rb +1 -1
  49. data/lib/ollama_chat.rb +7 -0
  50. data/ollama_chat.gemspec +5 -5
  51. data/spec/ollama_chat/commands_spec.rb +25 -0
  52. data/spec/ollama_chat/compaction_spec.rb +381 -0
  53. data/spec/ollama_chat/config_handling_spec.rb +60 -0
  54. data/spec/ollama_chat/message_list_spec.rb +229 -0
  55. data/spec/ollama_chat/message_spec.rb +143 -0
  56. data/spec/ollama_chat/rag_handling_spec.rb +3 -0
  57. data/spec/ollama_chat/system_prompt_management_spec.rb +0 -2
  58. data/spec/ollama_chat/tools/browse_spec.rb +7 -0
  59. data/spec/ollama_chat/tools/compute_bmi_spec.rb +15 -0
  60. data/spec/ollama_chat/tools/concern_spec.rb +77 -0
  61. data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -0
  62. data/spec/ollama_chat/tools/delete_file_spec.rb +8 -0
  63. data/spec/ollama_chat/tools/directory_structure_spec.rb +4 -0
  64. data/spec/ollama_chat/tools/execute_grep_spec.rb +13 -0
  65. data/spec/ollama_chat/tools/execute_ri_spec.rb +8 -0
  66. data/spec/ollama_chat/tools/execute_shell_spec.rb +232 -0
  67. data/spec/ollama_chat/tools/file_context_spec.rb +5 -0
  68. data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +2 -0
  69. data/spec/ollama_chat/tools/generate_image_spec.rb +12 -0
  70. data/spec/ollama_chat/tools/generate_password_spec.rb +5 -0
  71. data/spec/ollama_chat/tools/get_cve_spec.rb +4 -0
  72. data/spec/ollama_chat/tools/get_endoflife_spec.rb +4 -0
  73. data/spec/ollama_chat/tools/get_ghr_spec.rb +6 -0
  74. data/spec/ollama_chat/tools/get_location_spec.rb +4 -0
  75. data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -0
  76. data/spec/ollama_chat/tools/get_time_spec.rb +2 -0
  77. data/spec/ollama_chat/tools/get_url_spec.rb +7 -0
  78. data/spec/ollama_chat/tools/lookup_group_spec.rb +111 -0
  79. data/spec/ollama_chat/tools/move_file_spec.rb +10 -0
  80. data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -0
  81. data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +5 -0
  82. data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -0
  83. data/spec/ollama_chat/tools/patch_file_spec.rb +62 -0
  84. data/spec/ollama_chat/tools/read_file_spec.rb +10 -3
  85. data/spec/ollama_chat/tools/resolve_tag_spec.rb +46 -0
  86. data/spec/ollama_chat/tools/roll_dice_spec.rb +11 -0
  87. data/spec/ollama_chat/tools/run_tests_spec.rb +8 -0
  88. data/spec/ollama_chat/tools/search_knowledge_spec.rb +2 -0
  89. data/spec/ollama_chat/tools/write_file_spec.rb +98 -2
  90. data/spec/ollama_chat/utils/analyze_directory_spec.rb +46 -7
  91. data/spec/ollama_chat/utils/log_rotation_spec.rb +71 -0
  92. data/spec/spec_helper.rb +2 -1
  93. metadata +25 -1
@@ -48,6 +48,9 @@ describe OllamaChat::Tools::ExecuteGrep do
48
48
  expect(json.cmd).to include('spec/assets')
49
49
  expect(json.result).to include('Hello World!')
50
50
  expect(json.message).to match(/Found some matches for "Hello World" in ".*spec\/assets"\./)
51
+ expect(described_class.summary_template(result:)).to match(
52
+ /Found some matches for "Hello World" in ".*spec\/assets"\./
53
+ )
51
54
  end
52
55
 
53
56
  it 'can be executed successfully with max_results parameter' do
@@ -80,6 +83,9 @@ describe OllamaChat::Tools::ExecuteGrep do
80
83
  expect(json.cmd).to include(' -m 5 ')
81
84
  expect(json.result).to match(/blub class blob/)
82
85
  expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
86
+ expect(described_class.summary_template(result:)).to match(
87
+ /Found some matches for "class" in ".*spec\/assets"\./
88
+ )
83
89
  end
84
90
 
85
91
  it 'can be executed successfully with max_results and ignore_case parameter' do
@@ -113,6 +119,9 @@ describe OllamaChat::Tools::ExecuteGrep do
113
119
  expect(json.cmd).to include(' -i ')
114
120
  expect(json.result).to match(/blub class blob/)
115
121
  expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
122
+ expect(described_class.summary_template(result:)).to match(
123
+ /Found some matches for "class" in ".*spec\/assets"\./
124
+ )
116
125
  end
117
126
 
118
127
  it 'can handle execution errors gracefully' do
@@ -145,6 +154,9 @@ describe OllamaChat::Tools::ExecuteGrep do
145
154
  expect(json.cmd).to include('grep')
146
155
  expect(json.result).to eq ''
147
156
  expect(json.message).to match(/No matches found for "nonexistent_pattern" in ".*spec\/assets"\./)
157
+ expect(described_class.summary_template(result:)).to match(
158
+ /No matches found for "nonexistent_pattern" in ".*spec\/assets"\./
159
+ )
148
160
  end
149
161
 
150
162
  it 'can handle non-existent paths gracefully' do
@@ -205,6 +217,7 @@ describe OllamaChat::Tools::ExecuteGrep do
205
217
  json = json_object(result)
206
218
  expect(json.error).to eq 'RuntimeError'
207
219
  expect(json.message).to eq 'my error'
220
+ expect(described_class.summary_template(result:)).to eq 'my error'
208
221
  end
209
222
 
210
223
  context 'when searching in spec/assets directory' do
@@ -42,6 +42,8 @@ describe OllamaChat::Tools::ExecuteRI do
42
42
  expect(json.cmd).to include('ri')
43
43
  expect(json.cmd).to include(topic)
44
44
  expect(json.result).to eq 'Documentation text for Tins::Full#full?'
45
+ expect(described_class.summary_template(result:)).to eq \
46
+ 'was called.'
45
47
  end
46
48
  end
47
49
 
@@ -59,6 +61,9 @@ describe OllamaChat::Tools::ExecuteRI do
59
61
 
60
62
  json = json_object(result)
61
63
  expect(json.error).to eq('OllamaChat::ToolFunctionArgumentError')
64
+ expect(json.message).to eq 'require a topic of ri'
65
+ expect(described_class.summary_template(result:)).to eq \
66
+ 'require a topic of ri'
62
67
  end
63
68
  end
64
69
 
@@ -82,6 +87,9 @@ describe OllamaChat::Tools::ExecuteRI do
82
87
  json = json_object(result)
83
88
  expect(json.error).to eq('RuntimeError')
84
89
  expect(json.message).to include('my error')
90
+ expect(described_class.summary_template(result:)).to include(
91
+ 'my error',
92
+ )
85
93
  end
86
94
  end
87
95
  end
@@ -0,0 +1,232 @@
1
+ describe OllamaChat::Tools::ExecuteShell do
2
+ let :chat do
3
+ OllamaChat::Chat.new(argv: chat_default_config)
4
+ end
5
+
6
+ connect_to_ollama_server
7
+
8
+ let :tool do
9
+ described_class.new
10
+ end
11
+
12
+ before do
13
+ allow(OllamaChat).to receive(:test_mode?).and_return(false)
14
+ allow(chat).to receive(:log)
15
+ allow(OC).to receive(:EDITOR?).and_return('vim')
16
+ end
17
+
18
+ def tool_call(command, monochrome: nil)
19
+ double('ToolCall', function: double(
20
+ name: 'execute_shell',
21
+ arguments: double('Args', command:, monochrome:)
22
+ ))
23
+ end
24
+
25
+ def stub_success(stdout: 'ok', stderr: '', exit_code: 0)
26
+ allow(chat).to receive(:edit_text).and_return('ls')
27
+ allow(chat).to receive(:confirm?).and_return('y')
28
+ allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
29
+ allow(Open3).to receive(:capture3)
30
+ .and_return([stdout, stderr, double('s', exitstatus: exit_code)])
31
+ end
32
+
33
+ describe 'identity' do
34
+ it 'has the expected name' do
35
+ expect(tool.name).to eq 'execute_shell'
36
+ end
37
+
38
+ it 'provides a Tool instance' do
39
+ expect(tool.tool).to be_a(Ollama::Tool)
40
+ end
41
+
42
+ it 'can be converted to hash' do
43
+ expect(tool.to_hash).to be_a(Hash)
44
+ end
45
+ end
46
+
47
+ describe '#execute' do
48
+ it 'runs the command after editor review and confirmation' do
49
+ expect(chat).to receive(:edit_text)
50
+ .with('ls -l', hash_including(basename: %w[cmd .sh]))
51
+ .and_return('ls -l')
52
+ expect(chat).to receive(:confirm?).and_return('y')
53
+ allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
54
+ expect(Open3).to receive(:capture3)
55
+ .with('sh', '-c', 'ls -l')
56
+ .and_return(['total 0', '', double('s', exitstatus: 0)])
57
+
58
+ result = tool.execute(tool_call('ls -l'), chat:)
59
+ json = json_object(result)
60
+
61
+ expect(json.error).to be_nil
62
+ expect(json.exit_code).to eq 0
63
+ expect(json.stdout).to eq 'total 0'
64
+ expect(json.stderr).to eq ''
65
+ expect(json.command).to eq 'ls -l'
66
+ expect(described_class.summary_template(result:)).to include('ls -l')
67
+ end
68
+
69
+ it 'executes the command as edited in the editor' do
70
+ allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
71
+ expect(chat).to receive(:edit_text)
72
+ .with('ls', anything)
73
+ .and_return('ls -la /tmp')
74
+ allow(chat).to receive(:confirm?).and_return('y')
75
+ expect(Open3).to receive(:capture3)
76
+ .with('sh', '-c', 'ls -la /tmp')
77
+ .and_return(['x', '', double('s', exitstatus: 0)])
78
+
79
+ result = tool.execute(tool_call('ls'), chat:)
80
+
81
+ expect(json_object(result).command).to eq 'ls -la /tmp'
82
+ end
83
+
84
+ it 'returns cancelled when the user answers n' do
85
+ allow(chat).to receive(:edit_text).and_return('rm -rf .')
86
+ expect(chat).to receive(:confirm?).and_return('n')
87
+ expect(Open3).not_to receive(:capture3)
88
+
89
+ result = tool.execute(tool_call('rm -rf .'), chat:)
90
+ json = json_object(result)
91
+
92
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
93
+ expect(json.message).to include('cancelled')
94
+ expect(json.command).to eq 'rm -rf .'
95
+ end
96
+
97
+ it 'returns instruct when the user answers i' do
98
+ allow(chat).to receive(:edit_text).and_return('rm -rf .')
99
+ allow(chat).to receive(:confirm?).and_return('i')
100
+ expect(chat).to receive(:ask?)
101
+ .and_return('Actually use the build dir, not root')
102
+ expect(Open3).not_to receive(:capture3)
103
+
104
+ result = tool.execute(tool_call('rm -rf .'), chat:)
105
+ json = json_object(result)
106
+
107
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
108
+ expect(json.command).to eq 'rm -rf .'
109
+ expect(json.message).to include('build dir')
110
+ end
111
+
112
+ it 'returns cancelled when the editor is abandoned (nil)' do
113
+ expect(chat).to receive(:edit_text).and_return(nil)
114
+ expect(chat).not_to receive(:confirm?)
115
+ expect(Open3).not_to receive(:capture3)
116
+
117
+ result = tool.execute(tool_call('ls'), chat:)
118
+ json = json_object(result)
119
+
120
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
121
+ expect(json.message).to include('cancelled')
122
+ expect(json.command).to eq 'ls'
123
+ end
124
+
125
+ it 'returns cancelled when the editor is cleared to empty' do
126
+ expect(chat).to receive(:edit_text).and_return('')
127
+ expect(chat).not_to receive(:confirm?)
128
+ expect(Open3).not_to receive(:capture3)
129
+
130
+ result = tool.execute(tool_call('ls'), chat:)
131
+ json = json_object(result)
132
+
133
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
134
+ end
135
+
136
+ it 'skips editor and confirm in test mode' do
137
+ expect(OllamaChat).to receive(:test_mode?).and_return(true)
138
+ expect(chat).not_to receive(:edit_text)
139
+ expect(chat).not_to receive(:confirm?)
140
+ expect(Open3).to receive(:capture3)
141
+ .with('sh', '-c', 'ls')
142
+ .and_return(['ok', '', double('s', exitstatus: 0)])
143
+
144
+ result = tool.execute(tool_call('ls'), chat:)
145
+
146
+ expect(json_object(result).exit_code).to eq 0
147
+ end
148
+
149
+ it 'displays output via use_pager after execution' do
150
+ stub_success(stdout: "file1\nfile2", stderr: '')
151
+ pager_output = nil
152
+ expect(chat).to receive(:use_pager) do |&blk|
153
+ io = StringIO.new
154
+ blk.call(io)
155
+ pager_output = io.string
156
+ end
157
+
158
+ tool.execute(tool_call('ls'), chat:)
159
+
160
+ expect(pager_output).to include('file1')
161
+ expect(pager_output).to include('file2')
162
+ expect(pager_output).to include('exit: 0')
163
+ end
164
+
165
+ it 'strips ANSI from stdout when monochrome is true' do
166
+ stub_success(stdout: "\e[32mgreen\e[0m", stderr: '')
167
+
168
+ result = tool.execute(tool_call('ls', monochrome: true), chat:)
169
+
170
+ expect(json_object(result).stdout).to eq 'green'
171
+ end
172
+
173
+ it 'keeps ANSI when monochrome is false' do
174
+ stub_success(stdout: "\e[32mgreen\e[0m", stderr: '')
175
+
176
+ result = tool.execute(tool_call('ls', monochrome: false), chat:)
177
+
178
+ expect(json_object(result).stdout).to eq "\e[32mgreen\e[0m"
179
+ end
180
+
181
+ it 'treats a nil monochrome as true (default)' do
182
+ stub_success(stdout: "\e[31mred\e[0m", stderr: '')
183
+
184
+ result = tool.execute(tool_call('ls', monochrome: nil), chat:)
185
+
186
+ expect(json_object(result).stdout).to eq 'red'
187
+ end
188
+
189
+ it 'reports the exit code and stderr on failure' do
190
+ stub_success(stdout: '', stderr: 'nope', exit_code: 2)
191
+
192
+ result = tool.execute(tool_call('false'), chat:)
193
+ json = json_object(result)
194
+
195
+ expect(json.exit_code).to eq 2
196
+ expect(json.stderr).to eq 'nope'
197
+ end
198
+
199
+ it 'rejects a blank command' do
200
+ allow(chat).to receive(:edit_text)
201
+
202
+ result = tool.execute(tool_call(nil), chat:)
203
+ json = json_object(result)
204
+
205
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
206
+ end
207
+
208
+ it 'summary_template includes the command and exit code' do
209
+ result = {
210
+ stdout: 'x', stderr: '', exit_code: 0,
211
+ command: 'ls -l', message: 'ok',
212
+ }.to_json
213
+
214
+ summary = described_class.summary_template(result:)
215
+ expect(summary).to include('ls -l')
216
+ expect(summary).to include('exit 0')
217
+ end
218
+
219
+ it 'summary_template includes error message for cancelled' do
220
+ result = {
221
+ error: 'OllamaChat::ToolFunctionArgumentError',
222
+ command: 'rm -rf .',
223
+ message: 'Shell command cancelled by user: rm -rf .',
224
+ }.to_json
225
+
226
+ summary = described_class.summary_template(result:)
227
+ expect(summary).to include('cancelled')
228
+ expect(summary).to include('rm -rf .')
229
+ end
230
+
231
+ end
232
+ end
@@ -31,6 +31,8 @@ describe OllamaChat::Tools::FileContext do
31
31
  json = json_object(result)
32
32
  content_file = json.files[Pathname.pwd.join('spec/assets/example.rb').to_s].content
33
33
  expect(content_file).to include 'Hello World!'
34
+ expect(described_class.summary_template(result:))\
35
+ .to eq "was called."
34
36
  end
35
37
 
36
38
  it 'can handle execution errors gracefully' do
@@ -50,5 +52,8 @@ describe OllamaChat::Tools::FileContext do
50
52
 
51
53
  # Should still return a string (even if empty or minimal)
52
54
  expect(result).to be_a(String)
55
+ expect(described_class.summary_template(result:))\
56
+ .to eq "was called."
53
57
  end
58
+
54
59
  end
@@ -56,6 +56,8 @@ describe OllamaChat::Tools::GemPathLookup do
56
56
  json = json_object(result)
57
57
  expect(json.error).to eq 'RuntimeError'
58
58
  expect(json.message).to eq 'an error has happened'
59
+ expect(described_class.summary_template(result:))\
60
+ .to eq 'an error has happened'
59
61
  end
60
62
  end
61
63
  end
@@ -62,6 +62,8 @@ describe OllamaChat::Tools::GenerateImage do
62
62
  expect(json.status).to eq 'success'
63
63
  expect(json.url).to include('/api/view')
64
64
  expect(json.url).to include('filename=kitten_output.png')
65
+ expect(described_class.summary_template(result:)).to\
66
+ match(/Image successfully generated!/)
65
67
  end
66
68
 
67
69
  it 'returns an error when prompt is missing' do
@@ -72,6 +74,8 @@ describe OllamaChat::Tools::GenerateImage do
72
74
  json = json_object(result)
73
75
  expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
74
76
  expect(json.message).to include('require prompt argument')
77
+ expect(described_class.summary_template(result:)).to\
78
+ include('require prompt argument')
75
79
  end
76
80
 
77
81
  it 'returns an error when ComfyUI configuration is missing' do
@@ -83,6 +87,8 @@ describe OllamaChat::Tools::GenerateImage do
83
87
  json = json_object(result)
84
88
  expect(json.error).to eq 'OllamaChat::ConfigMissingError'
85
89
  expect(json.message).to include('Require env var OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_URL configuration')
90
+ expect(described_class.summary_template(result:)).to\
91
+ include('Failed to generate image:')
86
92
  end
87
93
 
88
94
  it 'returns a timeout error when polling fails' do
@@ -98,6 +104,8 @@ describe OllamaChat::Tools::GenerateImage do
98
104
  json = json_object(result)
99
105
  expect(json.error).to eq 'OllamaChat::OllamaChatError'
100
106
  expect(json.message).to include('took too long or failed')
107
+ expect(described_class.summary_template(result:)).to\
108
+ include('took too long or failed')
101
109
  end
102
110
 
103
111
  it 'handles API failures gracefully' do
@@ -112,6 +120,8 @@ describe OllamaChat::Tools::GenerateImage do
112
120
  json = json_object(result)
113
121
  expect(json.error).to eq 'OllamaChat::OllamaChatError'
114
122
  expect(json.message).to include('Failed to generate image: failed to trigger ComfyUI')
123
+ expect(described_class.summary_template(result:)).to\
124
+ include('Failed to generate image:')
115
125
  end
116
126
 
117
127
  it 'rescues generic exceptions' do
@@ -124,6 +134,8 @@ describe OllamaChat::Tools::GenerateImage do
124
134
  json = json_object(result)
125
135
  expect(json.error).to eq 'StandardError'
126
136
  expect(json.message).to include('Network crash')
137
+ expect(described_class.summary_template(result:)).to\
138
+ include('Failed to generate image:')
127
139
  end
128
140
  end
129
141
 
@@ -46,6 +46,9 @@ describe OllamaChat::Tools::GeneratePassword do
46
46
  expect(json.message).to eq(
47
47
  'Successfully generated a 16-character secure password using the default alphabet.'
48
48
  )
49
+ expect(described_class.summary_template(result:)).to eq(
50
+ 'Successfully generated a 16-character secure password using the default alphabet.'
51
+ )
49
52
  end
50
53
 
51
54
  it 'can be executed successfully with bits parameter' do
@@ -134,6 +137,8 @@ describe OllamaChat::Tools::GeneratePassword do
134
137
  json = json_object(result)
135
138
  expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
136
139
  expect(json.message).to eq 'require either bits or length of password'
140
+ expect(described_class.summary_template(result:))\
141
+ .to eq 'require either bits or length of password'
137
142
  end
138
143
 
139
144
  it 'can be converted t)o hash' do
@@ -44,6 +44,8 @@ describe OllamaChat::Tools::GetCVE do
44
44
  json = json_object(result)
45
45
  expect(json.id).to eq 'CVE-2023-12345'
46
46
  expect(json.description).to include('Test vulnerability description')
47
+ expect(described_class.summary_template(result:)).to eq \
48
+ 'was called.'
47
49
  end
48
50
 
49
51
  it 'can handle execution errors gracefully' do
@@ -67,5 +69,7 @@ describe OllamaChat::Tools::GetCVE do
67
69
  json = json_object(result)
68
70
  expect(json.error).to eq 'OllamaChat::HTTPError'
69
71
  expect(json.message).to eq 'request failed with status 404'
72
+ expect(described_class.summary_template(result:)).to eq \
73
+ 'request failed with status 404'
70
74
  end
71
75
  end
@@ -45,6 +45,8 @@ describe OllamaChat::Tools::GetEndoflife do
45
45
  expect(json.cycle).to eq '3.1'
46
46
  expect(json.releaseDate).to eq '2023-05-01'
47
47
  expect(json.eol).to eq '2026-05-01'
48
+ expect(described_class.summary_template(result:)).to eq \
49
+ 'was called.'
48
50
  end
49
51
 
50
52
  it 'can handle execution errors gracefully' do
@@ -68,5 +70,7 @@ describe OllamaChat::Tools::GetEndoflife do
68
70
  json = json_object(result)
69
71
  expect(json.error).to eq 'OllamaChat::HTTPError'
70
72
  expect(json.message).to eq 'request failed with status 404'
73
+ expect(described_class.summary_template(result:)).to eq \
74
+ 'request failed with status 404'
71
75
  end
72
76
  end
@@ -54,6 +54,8 @@ describe OllamaChat::Tools::GetGHR do
54
54
  expect(json.repo).to eq repo
55
55
  expect(json.releases).to be_a Array
56
56
  expect(json.releases.first.version).to eq '1.0'
57
+ expect(described_class.summary_template(result:)).to eq \
58
+ 'was called.'
57
59
  end
58
60
 
59
61
  it 'can be executed successfully for an overview' do
@@ -104,6 +106,8 @@ describe OllamaChat::Tools::GetGHR do
104
106
 
105
107
  expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
106
108
  expect(json.message).to include('Both user and repo must be provided')
109
+ expect(described_class.summary_template(result:)).to include \
110
+ 'Both user and repo must be provided'
107
111
  end
108
112
 
109
113
  it 'can handle execution errors gracefully' do
@@ -132,6 +136,8 @@ describe OllamaChat::Tools::GetGHR do
132
136
 
133
137
  expect(json.error).to eq 'OllamaChat::HTTPError'
134
138
  expect(json.message).to eq 'request failed with status 404'
139
+ expect(described_class.summary_template(result:)).to eq \
140
+ 'request failed with status 404'
135
141
  end
136
142
 
137
143
  it 'can be executed successfully for a specific repository with pagination' do
@@ -41,6 +41,8 @@ describe OllamaChat::Tools::GetLocation do
41
41
  expect(json.latitude).to be_within(0.0001).of(40.7128)
42
42
  expect(json.longitude).to be_within(0.0001).of(-74.0060)
43
43
  expect(json.units).to eq 'metric'
44
+ expect(described_class.summary_template(result:))\
45
+ .to eq 'was called.'
44
46
  end
45
47
 
46
48
  it 'can handle execution errors gracefully' do
@@ -71,6 +73,8 @@ describe OllamaChat::Tools::GetLocation do
71
73
  result = described_class.new.execute(tool_call, chat:)
72
74
  # Should still be valid JSON even if location_data is nil
73
75
  expect { JSON.parse(result) }.to_not raise_error
76
+ expect(described_class.summary_template(result:))\
77
+ .to eq 'was called.'
74
78
  end
75
79
  end
76
80
  end
@@ -44,6 +44,7 @@ describe OllamaChat::Tools::GetRFC do
44
44
  json = json_object(result)
45
45
  expect(json.rfc_id).to eq rfc_id
46
46
  expect(json.content).to include('RFC 1234')
47
+ expect(described_class.summary_template(result:)).to eq 'RFC 1234 was fetched.'
47
48
  end
48
49
 
49
50
  it 'can handle execution errors gracefully' do
@@ -67,5 +68,6 @@ describe OllamaChat::Tools::GetRFC do
67
68
  json = json_object(result)
68
69
  expect(json.error).to be_a String
69
70
  expect(json.message).to be_a String
71
+ expect(described_class.summary_template(result:)).to eq 'request failed with status 404'
70
72
  end
71
73
  end
@@ -36,6 +36,8 @@ describe OllamaChat::Tools::GetTime do
36
36
  )
37
37
  expect(json.weekday).to be_present
38
38
  expect(json.message).to eq 'Good morning! It is currently 11:11 on Friday.'
39
+ expect(described_class.summary_template(result:)).to eq \
40
+ 'Good morning! It is currently 11:11 on Friday.'
39
41
  ensure
40
42
  Time.dummy = nil
41
43
  end
@@ -17,6 +17,7 @@ describe OllamaChat::Tools::GetURL do
17
17
  expect(described_class.new.to_hash).to be_a Hash
18
18
  end
19
19
 
20
+
20
21
  context "with a valid URL" do
21
22
  it "imports content from the URL" do
22
23
  tool_call = double(
@@ -39,6 +40,8 @@ describe OllamaChat::Tools::GetURL do
39
40
  json = json_object(result)
40
41
  expect(json.url).to eq('https://www.example.com/foo')
41
42
  expect(json.message).to eq('Received requested URL successfully.')
43
+ expect(described_class.summary_template(result:)).to eq \
44
+ 'Received requested URL successfully.'
42
45
  end
43
46
  end
44
47
 
@@ -68,6 +71,8 @@ describe OllamaChat::Tools::GetURL do
68
71
  expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
69
72
  expect(json.message).to match(/scheme "file" not allowed/)
70
73
  expect(json.url).to eq url
74
+ expect(described_class.summary_template(result:)).
75
+ to match(/scheme "file" not allowed/)
71
76
  end
72
77
  end
73
78
 
@@ -93,6 +98,8 @@ describe OllamaChat::Tools::GetURL do
93
98
  json = json_object(result)
94
99
  expect(json.error).to eq 'RuntimeError'
95
100
  expect(json.message).to eq 'it somehow failed'
101
+ expect(described_class.summary_template(result:)).to eq \
102
+ 'it somehow failed'
96
103
  end
97
104
 
98
105
  context 'with different document policies' do
@@ -0,0 +1,111 @@
1
+ describe OllamaChat::Tools::LookupGroup do
2
+ let :chat do
3
+ OllamaChat::Chat.new(argv: chat_default_config)
4
+ end
5
+
6
+ connect_to_ollama_server
7
+
8
+ let :tool do
9
+ described_class.new
10
+ end
11
+
12
+ let :uuid do
13
+ '0194a3f2-8c4d-7e1a-9b3c-abcdef012345'
14
+ end
15
+
16
+ before do
17
+ chat.messages << OllamaChat::Message.new(
18
+ role: 'user', content: 'Read the config file', group_uuid: uuid
19
+ )
20
+ chat.messages << OllamaChat::Message.new(
21
+ role: 'assistant', content: 'Sure, let me read it.', group_uuid: uuid
22
+ )
23
+ chat.messages << OllamaChat::Message.new(
24
+ role: 'tool', content: '{"path":"/tmp/config.yml","content":"key: value"}',
25
+ tool_name: 'read_file', group_uuid: uuid
26
+ )
27
+ chat.messages << OllamaChat::Message.new(
28
+ role: 'assistant', content: 'Here is the config.', group_uuid: uuid
29
+ )
30
+ end
31
+
32
+ it 'has the expected name' do
33
+ expect(tool.name).to eq 'lookup_group'
34
+ end
35
+
36
+ it 'provides a Tool instance' do
37
+ expect(tool.tool).to be_a(Ollama::Tool)
38
+ end
39
+
40
+ it 'can be converted to hash' do
41
+ expect(tool.to_hash).to be_a(Hash)
42
+ end
43
+
44
+ it 'returns the full messages of a matching group' do
45
+ tool_call = double(
46
+ 'ToolCall',
47
+ function: double(
48
+ name: 'lookup_group',
49
+ arguments: double(group_uuid: uuid)
50
+ )
51
+ )
52
+
53
+ result = tool.execute(tool_call, chat:)
54
+ expect(result).to be_a(String)
55
+
56
+ json = json_object(result)
57
+ expect(json.group_uuid).to eq uuid
58
+ expect(json.messages.size).to eq 4
59
+ expect(json.message).to eq 'Retrieved 4 message(s) for group 0194a3f2-8c4d-7e1a-9b3c-abcdef012345.'
60
+ expect(json.messages[0]).to eq '[user] Read the config file'
61
+ expect(json.messages[1]).to eq '[assistant] Sure, let me read it.'
62
+ expect(json.messages[2]).to eq '[tool (read_file)] {"path":"/tmp/config.yml","content":"key: value"}'
63
+ expect(json.messages[3]).to eq '[assistant] Here is the config.'
64
+ end
65
+
66
+ it 'returns an error for a non-existent group' do
67
+ tool_call = double(
68
+ 'ToolCall',
69
+ function: double(
70
+ name: 'lookup_group',
71
+ arguments: double(group_uuid: 'ffffffff-0000-0000-0000-000000000000')
72
+ )
73
+ )
74
+
75
+ result = tool.execute(tool_call, chat:)
76
+ json = json_object(result)
77
+ expect(json.error).to eq 'OllamaChat::OllamaChatError'
78
+ expect(json.message).to include('Group not found')
79
+ expect(json.message).to include('ffffffff-0000-0000-0000-000000000000')
80
+ end
81
+
82
+ it 'returns an error when group_uuid is empty' do
83
+ tool_call = double(
84
+ 'ToolCall',
85
+ function: double(
86
+ name: 'lookup_group',
87
+ arguments: double(group_uuid: '')
88
+ )
89
+ )
90
+
91
+ result = tool.execute(tool_call, chat:)
92
+ json = json_object(result)
93
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
94
+ expect(json.message).to eq 'group_uuid required'
95
+ end
96
+
97
+ it 'matches on the suffix of the group_uuid' do
98
+ tool_call = double(
99
+ 'ToolCall',
100
+ function: double(
101
+ name: 'lookup_group',
102
+ arguments: double(group_uuid: 'abcdef012345')
103
+ )
104
+ )
105
+
106
+ result = tool.execute(tool_call, chat:)
107
+ json = json_object(result)
108
+ expect(json.group_uuid).to eq uuid
109
+ expect(json.message).to include('Retrieved 4 message(s)')
110
+ end
111
+ end