ollama_chat 0.0.97 → 0.0.99

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83e62a371bea41c14f117fc546d3acf8e8a22d6c7f3be71a06f5dbb9f76a0201
4
- data.tar.gz: 1348fecdb529bd12784e3843fcee09228d422db9f82a06554e6cf1923d318971
3
+ metadata.gz: '0818076703ebd6e8d8babbe5898a6a0069863ce7cebf18658f70e8d4b5592c51'
4
+ data.tar.gz: 7e9f768d41bff3560c2d22ac20ace84da0b580b3b17f8f9d1ffd3522af5d8db1
5
5
  SHA512:
6
- metadata.gz: f53884d1050759f8d32731f872e3746f92fc3b9747ecfdb12015bafc39cd98dd1e3fc977c831f116a7b95ece70fd8fc2cbfc760803d06207a2cf49584138a6de
7
- data.tar.gz: 78144c20b98504852ce74ce33709b9a5592865d9fc75c49edc5cb23ce93a767275d5f090c38d77450be6e503b5b37cdbe895650b25f0e07f91cb118d8a7f59dc
6
+ metadata.gz: 4864950b3246106793b51ffe74e3ead6dad4683f1b9fcaf710bf7f672264c646dc90dacdbcef437725b725230b8d0b70938045729ca982c83845ebce41bb773d
7
+ data.tar.gz: 5b99426668efc34bbb89a17faf2dcf016c1a6ada4c69d44882ebb40bcb836907741f96b5fb66ee99ea75b78abbdd34069ddbc186d525acd015302bcea95e55a7
data/CHANGES.md CHANGED
@@ -1,5 +1,71 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-07-13 v0.0.99
4
+
5
+ ### Changed
6
+
7
+ - **Command Handling & UX**
8
+ - Updated regexes for `/system`, `/session`, `/prompt`, `/conversation`,
9
+ `/character`, `/input`, `/pipe`, and `/output` in
10
+ `lib/ollama_chat/commands.rb` to prevent positional arguments from
11
+ starting with a hyphen.
12
+ - Enhanced warning messages in `lib/ollama_chat/message_list.rb` to include
13
+ explicit suggestions for `/drop` and `/regenerate`.
14
+
15
+ - **LLM & Prompt Management**
16
+ - Updated the `generate` method in `lib/ollama_chat/chat.rb` to capture
17
+ output in `content` and implemented conditional logging ( `:warn` for
18
+ empty results, `:info` otherwise).
19
+ - Added `.full? or return` guard clauses to `edit_text('')` and
20
+ `generate(prompt: full_prompt)` within
21
+ `lib/ollama_chat/prompt_management.rb`.
22
+ - Overhauled the `suggest_coding` prompt in `default_config.yml` to adopt a
23
+ Senior Software Architect and Technical Auditor persona.
24
+ - Overhauled the `suggest_roleplaying` prompt in `default_config.yml` to
25
+ adopt a Narrative Consultant and Game Master's Assistant persona.
26
+
27
+ - **Tools**
28
+ - Modified `OllamaChat::Tools::CopyToClipboard` to make the `text`
29
+ parameter required.
30
+ - Updated `lib/ollama_chat/tools/copy_to_clipboard.rb` to raise
31
+ `OllamaChat::ToolFunctionArgumentError` if `text` is missing and
32
+ simplified the success message.
33
+
34
+ - **Refactoring & Internal Improvements**
35
+ - Adopted Ruby shorthand for keyword arguments in:
36
+ - `set_default_persona_name` and `load_personae`
37
+ (`lib/ollama_chat/personae_management.rb`).
38
+ - `choose_prompt` via the `default` parameter
39
+ (`lib/ollama_chat/prompt_management.rb`).
40
+ - `OllamaChat::Chat` instantiation using `argv` in specs.
41
+ - The `topic` parameter in `spec/ollama_chat/tools/execute_ri_spec.rb`.
42
+ - The `url` parameter in `spec/ollama_chat/tools/get_url_spec.rb`.
43
+
44
+ ## 2026-07-09 v0.0.98
45
+
46
+ ### Added
47
+
48
+ - Implemented `prefill_prompt` getter and setter in `lib/ollama_chat/chat.rb`
49
+ with accompanying YARD documentation.
50
+
51
+ ### Changed
52
+
53
+ - Updated `lib/ollama_chat/commands.rb` to persist the result of
54
+ `suggest_prompts` as `self.prefill_prompt`, allowing prefilled prompts to be
55
+ stored in history for reuse and modification.
56
+ - Refactored call sites in `lib/ollama_chat/chat.rb`,
57
+ `lib/ollama_chat/commands.rb`, and `lib/ollama_chat/personae_management.rb`
58
+ to use the `self.prefill_prompt` interface instead of the `@prefill_prompt`
59
+ instance variable.
60
+ - Wrapped the output of the `info` subcommand in `use_pager` within
61
+ `lib/ollama_chat/commands.rb`.
62
+ - Added a confirmation message to the `import` subcommand in
63
+ `lib/ollama_chat/commands.rb`.
64
+ - Refined the description of the `directory_structure` tool in
65
+ `lib/ollama_chat/tools/directory_structure.rb` to include warnings regarding
66
+ token usage and the exponential growth of nodes relative to the `max_depth`
67
+ parameter.
68
+
3
69
  ## 2026-07-06 v0.0.97
4
70
 
5
71
  ### New Features
@@ -255,7 +255,7 @@ class OllamaChat::Chat
255
255
  ),
256
256
  ]
257
257
 
258
- ollama.chat(
258
+ content = ollama.chat(
259
259
  model: @model,
260
260
  messages: ,
261
261
  options: model_options,
@@ -263,6 +263,20 @@ class OllamaChat::Chat
263
263
  think: false,
264
264
  tools:
265
265
  )&.message&.content.to_s
266
+
267
+ if content.empty?
268
+ log(
269
+ :warn,
270
+ "Sent #{prompt.inspect} to LLM for generation, received no content!"
271
+ )
272
+ else
273
+ log(
274
+ :info,
275
+ "Sent #{prompt.inspect} to LLM for generation, received #{content.inspect}"
276
+ )
277
+ end
278
+
279
+ content
266
280
  end
267
281
 
268
282
  private
@@ -302,6 +316,18 @@ class OllamaChat::Chat
302
316
  !!@parse_content
303
317
  end
304
318
 
319
+ # Returns whether there is a prompt waiting to be prefilled into the input buffer.
320
+ #
321
+ # @return [Boolean] true if a prefill prompt exists and is not empty, false otherwise
322
+ def prefill_prompt
323
+ @prefill_prompt.full?
324
+ end
325
+
326
+ # Sets the content to be prefilled into the next user input prompt.
327
+ #
328
+ # @param prefill_prompt [String, nil] The text to prefill, or nil to clear it
329
+ attr_writer :prefill_prompt
330
+
305
331
  # Handles user input commands and processes chat interactions.
306
332
  #
307
333
  # @param content [String] The input content to process
@@ -388,10 +414,10 @@ class OllamaChat::Chat
388
414
  tools_were_called and type = :tool_input
389
415
  unless tools_were_called
390
416
  content = enable_command_completion do
391
- if prefill_prompt = @prefill_prompt.full?
417
+ if prefill_prompt
392
418
  Reline.pre_input_hook = -> {
393
419
  Reline.insert_text prefill_prompt.gsub(/\n*\z/, '')
394
- @prefill_prompt = nil
420
+ self.prefill_prompt = nil
395
421
  }
396
422
  else
397
423
  Reline.pre_input_hook = nil
@@ -170,7 +170,7 @@ module OllamaChat::Commands
170
170
 
171
171
  command(
172
172
  name: :system,
173
- regexp: %r(^/system(?:\s+(change|info|edit|add|delete|list|duplicate|export|import|reset))?(?:\s+(\S+))?$),
173
+ regexp: %r(^/system(?:\s+(change|info|edit|add|delete|list|duplicate|export|import|reset))?(?:\s+([^-].*))?$),
174
174
  complete: [ 'system', %w[ change info edit add delete list duplicate export import reset ] ],
175
175
  optional: true,
176
176
  help: <<~EOT
@@ -285,7 +285,7 @@ module OllamaChat::Commands
285
285
 
286
286
  command(
287
287
  name: :session,
288
- regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|summarize|delete|model options change|model options))?((?:\s+-(?:[sf]|p\s*\w+))*)(?:\s+(.+))?$),
288
+ regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|summarize|delete|model options change|model options))?((?:\s+-(?:[sf]|p\s*\w+))*)(?:\s+([^-].*))?$),
289
289
  complete: [ 'session', %w[ change previous list new duplicate rename summarize delete model\ options\ change model\ options ] ],
290
290
  optional: true,
291
291
  options: "[-s|-f|-p profile]\n[name]",
@@ -490,7 +490,7 @@ module OllamaChat::Commands
490
490
 
491
491
  command(
492
492
  name: :prompt,
493
- regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|suggest|-e))?(\s+-e)?(?:\s+(\S+))?$),
493
+ regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|suggest|-e))?(\s+-e)?(?:\s+([^-].*))?$),
494
494
  complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset suggest ] ],
495
495
  optional: true,
496
496
  help: <<~EOT,
@@ -536,14 +536,16 @@ module OllamaChat::Commands
536
536
  end
537
537
  when 'suggest'
538
538
  opts = go_command('e', opts)
539
- prompt = suggest_prompts(edit: opts[?e]) and next prompt
539
+ if prompt = suggest_prompts(edit: opts[?e])
540
+ self.prefill_prompt = prompt
541
+ end
540
542
  when nil, '-e'
541
543
  if prompt = choose_prompt(prompt: 'Which template shall guide the next response? %s').full?(&:to_s)
542
544
  if subcommand
543
545
  prompt = edit_text(prompt)
544
546
  next prompt
545
547
  else
546
- @prefill_prompt = prompt
548
+ self.prefill_prompt = prompt
547
549
  end
548
550
  end
549
551
  end
@@ -562,7 +564,7 @@ module OllamaChat::Commands
562
564
 
563
565
  command(
564
566
  name: :conversation,
565
- regexp: %r(^/conversation\s+(save|load)((?:\s+-(?:[c]))*)\s+(.+)$),
567
+ regexp: %r(^/conversation\s+(save|load)((?:\s+-(?:[c]))*)\s+([^-].*)$),
566
568
  complete: [ 'conversation', %w[ save load ] ],
567
569
  options: '[-c]',
568
570
  help: <<~EOT
@@ -694,7 +696,7 @@ module OllamaChat::Commands
694
696
 
695
697
  command(
696
698
  name: :character,
697
- regexp: %r(^/character(?:\s+(info|load|import))(?:\s+(\S+))?$),
699
+ regexp: %r(^/character(?:\s+(info|load|import))(?:\s+([^-].+))?$),
698
700
  complete: [ 'character', %w[ info load import ] ],
699
701
  help: <<~EOT
700
702
  Display character info, load or import a character
@@ -734,13 +736,16 @@ module OllamaChat::Commands
734
736
  }
735
737
  case subcommand
736
738
  when 'info'
737
- puts json_to_yaml.(data)
739
+ use_pager do |output|
740
+ output.puts json_to_yaml.(data)
741
+ end
738
742
  :next
739
743
  when 'load'
740
744
  disable_content_parsing
741
745
  data
742
746
  when 'import'
743
- import_persona_from_json(data)
747
+ persona_name = import_persona_from_json(data)
748
+ STDOUT.puts "Imported character as persona %s." % persona_name.to_s.inspect
744
749
  :next
745
750
  end
746
751
  end
@@ -767,7 +772,7 @@ module OllamaChat::Commands
767
772
 
768
773
  command(
769
774
  name: :input,
770
- regexp: %r(^/input(?:\s+(path|context|embedding|summary)(?:\s*(?=\z))?)?((?:\s+-(?:[apre]|c\s*\w+|w\s*\d+|t\s*[-\w\.]+(?:,[-\w\.]+)*))*)(?:\s+(.+))?$),
775
+ regexp: %r(^/input(?:\s+(path|context|embedding|summary)(?:\s*(?=\z))?)?((?:\s+-(?:[apre]|c\s*\w+|w\s*\d+|t\s*[-\w\.]+(?:,[-\w\.]+)*))*)(?:\s+([^-].*))?$),
771
776
  optional: true,
772
777
  complete: [ 'input', %w[ path context embedding summary ] ],
773
778
  options: "[\n -w|-a|-p|-e|\n -c <collection>|\n -t <tags>\n]\n[arg…]",
@@ -886,7 +891,7 @@ module OllamaChat::Commands
886
891
 
887
892
  command(
888
893
  name: :pipe,
889
- regexp: %r(^/pipe(\s+-e)?\s+(.+)$),
894
+ regexp: %r(^/pipe(\s+-e)?\s+([^-].*)$),
890
895
  options: 'path',
891
896
  help: <<~EOT
892
897
  Pipe the last response into another command's stdin.
@@ -913,7 +918,7 @@ module OllamaChat::Commands
913
918
 
914
919
  command(
915
920
  name: :output,
916
- regexp: %r(^/output(\s+-e)?\s+(.+)$),
921
+ regexp: %r(^/output(\s+-e)?\s+([^-].*)$),
917
922
  options: '[-e] path',
918
923
  help: <<~EOT
919
924
  Save the last response to a file.
@@ -277,11 +277,11 @@ class OllamaChat::MessageList
277
277
  )
278
278
  msg = <<~EOT
279
279
 
280
- ⚠️ Last message is actually #{bold{'user message'}}, see:
280
+ ⚠️ Last message is actually a #{bold{'user message'}}, see:
281
281
 
282
282
  #{message_content}
283
283
 
284
- You might want to drop it.
284
+ You might want to /drop it or /regenerate it.
285
285
  EOT
286
286
  output.puts msg
287
287
  end
@@ -172,33 +172,165 @@ prompts:
172
172
 
173
173
  %{content}
174
174
  suggest_coding: |
175
- Analyze the conversation history above. Generate exactly 3 distinct,
176
- concise follow-up prompts that the user might want to ask next to further
177
- their goal.
175
+ ## Context
178
176
 
179
- Criteria:
180
- 1. One prompt should dive deeper into the current technical implementation.
181
- 2. One prompt should challenge a potential edge case or optimization.
182
- 3. One prompt should suggest a logical next step or a broader architectural question.
177
+ You are acting as a Senior Software Architect and Technical Auditor. Your goal
178
+ is to analyze the conversation history between a developer and an AI to
179
+ identify technical blind spots, architectural risks, and optimization
180
+ opportunities. You do not just summarize; you challenge the current
181
+ implementation to ensure it is production-ready, scalable, and maintainable.
183
182
 
184
- Output only the text of these suggestions, separated by an empty line,
185
- without any numbering, bullet points, or introductory text.
183
+ ## Task
184
+
185
+ Analyze the provided Conversation History. Generate exactly 8 distinct, concise
186
+ follow-up prompts that the user could ask next to further their goal from a
187
+ professional engineering perspective.
188
+
189
+ Each prompt must map strictly to one of these categories:
190
+
191
+ 1. **Technical Deep-Dive**: A tactical inquiry into the immediate
192
+ implementation details or logic flow.
193
+ 2. **Edge Case/Optimization**: A stress test identifying where the logic
194
+ breaks, fails silently, or degrades in performance.
195
+ 3. **Complexity & Scale**: An analysis of Big O, memory pressure, or
196
+ concurrency (e.g., "How does this behave under 100x load?").
197
+ 4. **Maintainability & Debt**: An evaluation of clean code principles and
198
+ whether the current approach introduces future technical debt.
199
+ 5. **Alternative Trade-offs**: A "Plan B" challenge suggesting a different
200
+ design pattern, library, or architectural approach.
201
+ 6. **Verification & Testing**: A request for a programmatic verification
202
+ strategy (e.g., boundary value analysis or mocking dependencies).
203
+ 7. **Knowledge Transfer & Documentation**: An inquiry about updating API
204
+ contracts, READMEs, or creating an Architectural Decision Record (ADR).
205
+ 8. **Strategic Next Step**: A high-level roadmap question connecting this
206
+ tactical win to the broader system architecture.
207
+
208
+ ## Constraints
209
+
210
+ - **Quantity**: Exactly 8 suggestions.
211
+ - **Tone**: Rigorous, auditing, and senior. Avoid passive phrasing ("Maybe you
212
+ could...") in favor of critical analysis ("This approach risks X; how do we
213
+ resolve it?").
214
+ - **Negative Constraints**:
215
+ - DO NOT use numbering or bullet points.
216
+ - DO NOT include introductory or concluding text.
217
+ - DO NOT suggest generic, superficial questions (e.g., "How does this
218
+ work?").
219
+ - **Conciseness**: Each prompt must be a single, punchy sentence.
220
+
221
+ ## Few-Shot Examples
222
+
223
+ **Input:** [User and AI just implemented a Ruby method to parse a large JSON
224
+ file into a database]
225
+
226
+ **Output:**
227
+
228
+ Can we refactor the parser to use a stream-based approach instead of loading
229
+ the entire JSON into memory?
230
+
231
+ How does this implementation handle malformed JSON or missing keys in the
232
+ middle of a 2GB file without crashing the process?
233
+
234
+ What is the time complexity of the current insert logic, and can we implement
235
+ bulk inserts to reduce database round trips?
236
+
237
+ Does the current naming convention for these helper methods align with our
238
+ project's style guide, or are we introducing cognitive load for future
239
+ maintainers?
240
+
241
+ Would using a dedicated library like Oj provide significant performance gains
242
+ over the standard JSON implementation here?
243
+
244
+ What specific boundary cases should be covered in the RSpec suite to ensure the
245
+ parser doesn't truncate data on trailing whitespace?
246
+
247
+ Which sections of the API documentation need to be updated to reflect this
248
+ change in the data ingestion pipeline?
249
+
250
+ Now that parsing is solved, how does this integrate into our overall background
251
+ job queue and retry logic?
252
+
253
+ ## Output Format
254
+
255
+ Output only the 8 suggestions as plain text, each separated by one empty line.
256
+ No other characters or formatting.
186
257
  suggest_roleplaying: |
187
- Analyze the conversation history and the current narrative state of this
188
- roleplay/story. Generate exactly 3 distinct, concise prompts that the
189
- user could use to respond as their character.
190
-
191
- Criteria:
192
- 1. One prompt should be an active choice or bold action that pushes the
193
- plot forward or creates tension.
194
- 2. One prompt should focus on character interaction, exploring emotional
195
- depth, a social reaction, or a subtle nuance in dialogue.
196
- 3. One prompt should introduce a sudden complication, a risky gamble, or an
197
- unexpected event. This prompt MUST explicitly include a suggested dice roll
198
- (e.g., "Roll 1d20") to determine the success or failure of the action.
199
-
200
- Output only the text of these suggestions, separated by an empty line,
201
- without any numbering, bullet points, or introductory text.
258
+ ## Context
259
+
260
+ You are an expert Narrative Consultant and Game Master's Assistant. Your
261
+ purpose is to analyze a roleplay conversation history and provide high-value,
262
+ diverse action prompts that help a player overcome writer's block while
263
+ deepening the story.
264
+
265
+ ## Task
266
+
267
+ Analyze the provided Conversation History and current narrative state. Generate
268
+ exactly 9 distinct, concise prompt suggestions that the user could use as their
269
+ character's next response.
270
+
271
+ Each suggestion must map to one of these specific categories:
272
+
273
+ 1. Plot Driver: A bold action or choice that advances the plot or increases
274
+ tension.
275
+ 2. Emotional Core: An interaction focusing on emotional depth, social nuance,
276
+ or psychological reaction.
277
+ 3. World Insight: Investigating an object or detail in the environment. Include
278
+ a suggested dice roll (e.g., "Roll 1d20") if there is an inherent risk of
279
+ failure or a need for high skill.
280
+ 4. Hidden Agenda: An action involving secrecy, deception, or private motives.
281
+ Include a suggested dice roll (e.g., "Roll 1d20") if the action risks
282
+ discovery.
283
+ 5. Social Catalyst: A prompt that engages all characters currently present in
284
+ the scene to force group interaction.
285
+ 6. High Stakes: A risky gamble or sudden complication. This MUST explicitly
286
+ include a suggested dice roll (e.g., "Roll 1d20").
287
+ 7. Character Quirk: A response rooted in the character's specific flaws,
288
+ habits, or obsessions.
289
+ 8. Meta-Pivot: An abrupt shift in scene direction via a revelation, surprising
290
+ admission, or sudden change of heart.
291
+ 9. The Curveball: An unexpected external event or realization that forces an
292
+ immediate reaction.
293
+
294
+ ## Constraints
295
+
296
+ - Generate exactly 9 suggestions.
297
+ - Each suggestion must be concise and punchy.
298
+ - Negative Constraints: Do not use numbering, bullet points, or
299
+ introductory/concluding text. Avoid generic clichés (e.g., "I can't believe
300
+ this" or "What do we do now?").
301
+ - Ensure the tone of the suggestions aligns with the established voice of the
302
+ characters in the history.
303
+
304
+ ## Variables
305
+
306
+ - Conversation History: The provided log of messages between players and AI.
307
+
308
+ ## Few-Shot Examples
309
+
310
+ Input: [Scene where a party is arguing in a rainy forest while being hunted]
311
+ Output:
312
+ Demand that everyone stop shouting and listen for the sound of footsteps in the brush.
313
+
314
+ Quietly admit to the party leader that you are terrified, leaning into their shoulder for support.
315
+
316
+ Examine the muddy tracks near the campfire to see if the hunters are human or something else. Roll 1d20.
317
+
318
+ Secretly slip the map out of the leader's pocket while they are distracted by the argument. Roll 1d20.
319
+
320
+ Raise your voice and challenge every single person present to state their true loyalty right now.
321
+
322
+ Attempt to leap across the ravine to flank the hunters, risking a fall into the rapids. Roll 1d20.
323
+
324
+ Obsessively polish your sword with a rag, ignoring the argument entirely until it is perfectly mirrored.
325
+
326
+ Suddenly reveal that you were actually sent here by the enemy as a double agent.
327
+
328
+ A sudden lightning strike hits a nearby tree, knocking everyone off balance and creating a deafening blast.
329
+
330
+ ## Output Format
331
+
332
+ Output only the 9 suggestions as plain text, each separated by one empty line.
333
+ No other text.
202
334
  system_prompts:
203
335
  default: <%= OC::OLLAMA::CHAT::SYSTEM || "%{persona}\n\n%{runtime_info}".inspect %>
204
336
  persona: |
@@ -73,7 +73,7 @@ module OllamaChat::PersonaeManagement
73
73
  def set_default_persona_name(persona_name)
74
74
  if persona_name.present? && persona_name != :none
75
75
  @default_persona_name = Pathname.new(persona_name).basename.sub_ext('').to_path
76
- @session.update(default_persona_name: default_persona_name)
76
+ @session.update(default_persona_name:)
77
77
  else
78
78
  @session.update(default_persona_name: nil)
79
79
  @default_persona_name = nil
@@ -269,7 +269,7 @@ module OllamaChat::PersonaeManagement
269
269
  persona = choose_persona(prompt: "Which persona's path do you need? %s") or return
270
270
  path = persona_name_to_pathname(persona).to_s
271
271
  perform_copy_to_clipboard(text: path, edit: false)
272
- no_prefill or @prefill_prompt = path
272
+ no_prefill or self.prefill_prompt = path
273
273
  path
274
274
  end
275
275
 
@@ -403,7 +403,7 @@ module OllamaChat::PersonaeManagement
403
403
  def load_personae
404
404
  chosen = Set[]
405
405
  choose_with_state do
406
- while persona = choose_persona(chosen: chosen, prompt: 'Who else should join the conversation? %s')
406
+ while persona = choose_persona(chosen:, prompt: 'Who else should join the conversation? %s')
407
407
  persona == :none and next
408
408
  chosen << persona
409
409
  end
@@ -31,7 +31,7 @@ module OllamaChat::PromptManagement
31
31
  # @return [OllamaChat::Database::Models::Prompt, nil] the selected prompt
32
32
  # model, or nil if the user chooses '[EXIT]' or cancels the selection.
33
33
  def choose_prompt(default: nil, prompt: 'Select a prompt template: %s')
34
- prompts = all_prompts(default: default)
34
+ prompts = all_prompts(default:)
35
35
  prompts.unshift('[EXIT]')
36
36
  case chosen = choose_entry(prompts, prompt:)
37
37
  when '[EXIT]', nil
@@ -246,7 +246,7 @@ module OllamaChat::PromptManagement
246
246
  instruction = nil
247
247
  if edit
248
248
  # Let the user write a suggestion instruction on the fly
249
- instruction = edit_text('')
249
+ instruction = edit_text('').full? or return
250
250
  else
251
251
  # Let the user pick a prompt template (e.g., suggest_coding, suggest_roleplaying)
252
252
  instruction = choose_prompt(prompt: 'Which suggestion strategy shall we employ? %s') or return
@@ -263,7 +263,7 @@ module OllamaChat::PromptManagement
263
263
  EOT
264
264
 
265
265
  # Execute a silent chat oneshot call (doesn't add to history)
266
- suggestions = generate(prompt: full_prompt)
266
+ suggestions = generate(prompt: full_prompt).full? or return
267
267
 
268
268
  # Pass the AI's suggestions through the editor for final refinement
269
269
  edit_text(suggestions)
@@ -1,11 +1,8 @@
1
- # A tool for copying the last assistant response to the clipboard.
1
+ # A tool for copying a text to the clipboard.
2
2
  #
3
3
  # The executable used for the copy operation is read from the configuration
4
4
  # under `copy.executable`. The default configuration ships with
5
5
  # `ctc` – a small CLI that copies stdin to the system clipboard.
6
- #
7
- # The tool has no parameters; it simply grabs the most recent response
8
- # from the chat instance and streams it to the executable.
9
6
  class OllamaChat::Tools::CopyToClipboard
10
7
  include OllamaChat::Tools::Concern
11
8
 
@@ -20,9 +17,8 @@ class OllamaChat::Tools::CopyToClipboard
20
17
  function: Tool::Function.new(
21
18
  name:,
22
19
  description: <<~EOT,
23
- Clipboard helper – Copies supplied string (or last assistant reply if
24
- omitted) into the OS clipboard, enabling quick pasting elsewhere. No
25
- output.
20
+ Clipboard helper – Copies supplied string into the OS clipboard,
21
+ enabling quick pasting elsewhere. No output.
26
22
  EOT
27
23
  parameters: Tool::Function::Parameters.new(
28
24
  type: 'object',
@@ -36,7 +32,7 @@ class OllamaChat::Tools::CopyToClipboard
36
32
  description: 'True if the copied text should be edited by the user, (default: false)'
37
33
  )
38
34
  },
39
- required: []
35
+ required: %w[ text ]
40
36
  )
41
37
  )
42
38
  )
@@ -51,17 +47,12 @@ class OllamaChat::Tools::CopyToClipboard
51
47
  def execute(tool_call, **opts)
52
48
  args = tool_call.function.arguments
53
49
  edit = !!args.edit
54
- text = args.text.full?
50
+ text = args.text.full? or raise OllamaChat::ToolFunctionArgumentError, 'no text given'
55
51
 
56
52
  chat = opts[:chat]
57
53
  chat.perform_copy_to_clipboard(text:, content: true, edit:)
58
54
 
59
- message =
60
- if text.nil?
61
- "The last response has been successfully copied to the system clipboard."
62
- else
63
- "The provided text has been successfully copied to the system clipboard."
64
- end
55
+ message = "The provided text has been successfully copied to the system clipboard."
65
56
 
66
57
  {
67
58
  success: true,
@@ -29,7 +29,9 @@ class OllamaChat::Tools::DirectoryStructure
29
29
  Tree viewer – Returns JSON describing files/folders under path up to
30
30
  max_depth (<= height of the tree), optionally only files ending with
31
31
  suffix, e. g. rb for ruby files. Handy for locating resources or
32
- presenting a project layout.
32
+ presenting a project layout. Limit the required tokens by using the
33
+ max_depth parameter if possible, because the number of nodes in a
34
+ tree can grow exponentially with its height.
33
35
  EOT
34
36
  parameters: Tool::Function::Parameters.new(
35
37
  type: 'object',
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.97'
3
+ VERSION = '0.0.99'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/ollama_chat.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama_chat 0.0.97 ruby lib
2
+ # stub: ollama_chat 0.0.99 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama_chat".freeze
6
- s.version = "0.0.97".freeze
6
+ s.version = "0.0.99".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -15,7 +15,7 @@ describe OllamaChat::Chat, protect_env: true do
15
15
  end
16
16
 
17
17
  let :chat do
18
- OllamaChat::Chat.new(argv: argv).expose
18
+ OllamaChat::Chat.new(argv:).expose
19
19
  end
20
20
 
21
21
  describe 'instantiation' do
@@ -4,7 +4,7 @@ describe OllamaChat::History do
4
4
  end
5
5
 
6
6
  let :chat do
7
- OllamaChat::Chat.new(argv: argv).expose
7
+ OllamaChat::Chat.new(argv:).expose
8
8
  end
9
9
 
10
10
  connect_to_ollama_server(instantiate: false)
@@ -17,7 +17,7 @@ describe OllamaChat::Tools::CopyToClipboard do
17
17
  expect(described_class.new.to_hash).to be_a Hash
18
18
  end
19
19
 
20
- it 'can be executed successfully without provided text' do
20
+ it 'raises an error if no text is provided' do
21
21
  tool_call = double(
22
22
  'ToolCall',
23
23
  function: double(
@@ -29,17 +29,13 @@ describe OllamaChat::Tools::CopyToClipboard do
29
29
  )
30
30
  )
31
31
 
32
- # Test that perform_copy_to_clipboard is called with content: true
33
- expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: false)
34
-
35
32
  result = described_class.new.execute(tool_call, chat:)
36
33
 
37
34
  # Should return valid JSON
38
35
  expect(result).to be_a(String)
39
36
  json = json_object(result)
40
- expect(json.error).to be_nil # No exception was raised
41
- expect(json.success).to be true
42
- expect(json.message).to eq 'The last response has been successfully copied to the system clipboard.'
37
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
38
+ expect(json.message).to eq 'no text given'
43
39
  end
44
40
 
45
41
  it 'can copy custom text to the clipboard' do
@@ -70,19 +66,20 @@ describe OllamaChat::Tools::CopyToClipboard do
70
66
  end
71
67
 
72
68
  it 'can be executed with edit option' do
69
+ text = 'Text to edit'
73
70
  tool_call = double(
74
71
  'ToolCall',
75
72
  function: double(
76
73
  name: 'copy_to_clipboard',
77
74
  arguments: double(
78
- text: nil,
75
+ text: ,
79
76
  edit: true
80
77
  )
81
78
  )
82
79
  )
83
80
 
84
81
  # Test that perform_copy_to_clipboard is called with edit: true
85
- expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: true)
82
+ expect(chat).to receive(:perform_copy_to_clipboard).with(text:, content: true, edit: true)
86
83
 
87
84
  result = described_class.new.execute(tool_call, chat:)
88
85
 
@@ -91,23 +88,24 @@ describe OllamaChat::Tools::CopyToClipboard do
91
88
  json = json_object(result)
92
89
  expect(json.error).to be_nil
93
90
  expect(json.success).to be true
94
- expect(json.message).to eq 'The last response has been successfully copied to the system clipboard.'
91
+ expect(json.message).to eq 'The provided text has been successfully copied to the system clipboard.'
95
92
  end
96
93
 
97
94
  it 'can handle execution errors gracefully' do
95
+ text = 'Error triggering text'
98
96
  tool_call = double(
99
97
  'ToolCall',
100
98
  function: double(
101
99
  name: 'copy_to_clipboard',
102
100
  arguments: double(
103
- text: nil,
101
+ text: ,
104
102
  edit: nil
105
103
  )
106
104
  )
107
105
  )
108
106
 
109
107
  # Test that perform_copy_to_clipboard raises an error
110
- expect(chat).to receive(:perform_copy_to_clipboard).with(text:nil, content: true, edit: false).
108
+ expect(chat).to receive(:perform_copy_to_clipboard).with(text:, content: true, edit: false).
111
109
  and_raise(OllamaChat::OllamaChatError, 'No response available to copy to the system clipboard.')
112
110
 
113
111
  result = described_class.new.execute(tool_call, chat:)
@@ -120,19 +118,20 @@ describe OllamaChat::Tools::CopyToClipboard do
120
118
  end
121
119
 
122
120
  it 'can handle execution exceptions gracefully' do
121
+ text = 'Exception triggering text'
123
122
  tool_call = double(
124
123
  'ToolCall',
125
124
  function: double(
126
125
  name: 'copy_to_clipboard',
127
126
  arguments: double(
128
- text: nil,
127
+ text: ,
129
128
  edit: nil
130
129
  )
131
130
  )
132
131
  )
133
132
 
134
133
  # Test that perform_copy_to_clipboard raises an exception
135
- expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: false).
134
+ expect(chat).to receive(:perform_copy_to_clipboard).with(text:, content: true, edit: false).
136
135
  and_raise(RuntimeError, 'some kind of exception')
137
136
 
138
137
  result = described_class.new.execute(tool_call, chat:)
@@ -31,7 +31,7 @@ describe OllamaChat::Tools::ExecuteRI do
31
31
  'ToolCall',
32
32
  function: double(
33
33
  name: 'execute_ri',
34
- arguments: double(topic: topic)
34
+ arguments: double(topic:)
35
35
  )
36
36
  )
37
37
 
@@ -104,7 +104,7 @@ describe OllamaChat::Tools::GetURL do
104
104
  end
105
105
 
106
106
  it 'handles the "ignoring" policy' do
107
- args = double(url: url, document_policy: 'ignoring')
107
+ args = double(url:, document_policy: 'ignoring')
108
108
  tool_call = double(function: double(arguments: args))
109
109
 
110
110
  result = described_class.new.execute(tool_call, chat:)
@@ -113,7 +113,7 @@ describe OllamaChat::Tools::GetURL do
113
113
  end
114
114
 
115
115
  it 'handles the "importing" policy' do
116
- args = double(url: url, document_policy: 'importing')
116
+ args = double(url:, document_policy: 'importing')
117
117
  tool_call = double(function: double(arguments: args))
118
118
  expect(chat).to receive(:import_source).with(source_io, URI.parse(url)).and_return('imported content')
119
119
 
@@ -123,7 +123,7 @@ describe OllamaChat::Tools::GetURL do
123
123
  end
124
124
 
125
125
  it 'handles the "embedding" policy' do
126
- args = double(url: url, document_policy: 'embedding')
126
+ args = double(url:, document_policy: 'embedding')
127
127
  tool_call = double(function: double(arguments: args))
128
128
  expect(chat).to receive(:embed_source).with(source_io, URI.parse(url)).and_return('embedded content')
129
129
 
@@ -133,7 +133,7 @@ describe OllamaChat::Tools::GetURL do
133
133
  end
134
134
 
135
135
  it 'handles the "summarizing" policy' do
136
- args = double(url: url, document_policy: 'summarizing')
136
+ args = double(url:, document_policy: 'summarizing')
137
137
  tool_call = double(function: double(arguments: args))
138
138
  expect(chat).to receive(:summarize_source).with(source_io, URI.parse(url)).and_return('summarized content')
139
139
 
@@ -143,7 +143,7 @@ describe OllamaChat::Tools::GetURL do
143
143
  end
144
144
 
145
145
  it 'handles an invalid policy' do
146
- args = double(url: url, document_policy: 'chaos_mode')
146
+ args = double(url:, document_policy: 'chaos_mode')
147
147
  tool_call = double(function: double(arguments: args))
148
148
 
149
149
  result = described_class.new.execute(tool_call, chat:)
@@ -160,7 +160,7 @@ describe OllamaChat::Tools::GetURL do
160
160
  allow(chat).to receive(:fetch_source).and_yield(source_io)
161
161
  expect(chat).to receive(:add_image).with(chat.images, source_io, URI.parse(url))
162
162
 
163
- args = double(url: url, document_policy: 'ignoring')
163
+ args = double(url:, document_policy: 'ignoring')
164
164
  tool_call = double(function: double(arguments: args))
165
165
 
166
166
  result = described_class.new.execute(tool_call, chat:)
@@ -172,7 +172,7 @@ describe OllamaChat::Tools::GetURL do
172
172
  source_io = double('SourceIO', content_type: double(media_type: 'video'))
173
173
  allow(chat).to receive(:fetch_source).and_yield(source_io)
174
174
 
175
- args = double(url: url, document_policy: 'ignoring')
175
+ args = double(url:, document_policy: 'ignoring')
176
176
  tool_call = double(function: double(arguments: args))
177
177
 
178
178
  result = described_class.new.execute(tool_call, chat:)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.97
4
+ version: 0.0.99
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank