ollama_chat 0.0.134 → 0.0.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +121 -0
- data/Rakefile +1 -1
- data/lib/ollama_chat/chat.rb +14 -2
- data/lib/ollama_chat/commands.rb +14 -11
- data/lib/ollama_chat/database/models/session.rb +2 -2
- data/lib/ollama_chat/database/{session_locking.rb → row_locking.rb} +11 -16
- data/lib/ollama_chat/database.rb +1 -1
- data/lib/ollama_chat/follow_chat.rb +1 -135
- data/lib/ollama_chat/information.rb +59 -1
- data/lib/ollama_chat/model_handling/model_cache.rb +106 -0
- data/lib/ollama_chat/model_handling.rb +37 -25
- data/lib/ollama_chat/oc.rb +8 -0
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +2 -1
- data/lib/ollama_chat/ollama_chat_config/fastfetch-profile.jsonc +13 -0
- data/lib/ollama_chat/personae_management.rb +20 -2
- data/lib/ollama_chat/prompt_management.rb +1 -1
- data/lib/ollama_chat/rag_handling.rb +2 -2
- data/lib/ollama_chat/session_management.rb +33 -26
- data/lib/ollama_chat/source_fetching.rb +1 -1
- data/lib/ollama_chat/tool_call_processor.rb +176 -0
- data/lib/ollama_chat/tool_calling.rb +0 -16
- data/lib/ollama_chat/tools/execute_shell.rb +72 -32
- data/lib/ollama_chat/version.rb +7 -2
- data/lib/ollama_chat.rb +1 -0
- data/ollama_chat.gemspec +7 -7
- data/spec/ollama_chat/commands_spec.rb +10 -2
- data/spec/ollama_chat/follow_chat_spec.rb +2 -1
- data/spec/ollama_chat/information_spec.rb +1 -1
- data/spec/ollama_chat/kramdown_ansi_spec.rb +1 -1
- data/spec/ollama_chat/model_handling/model_cache_spec.rb +181 -0
- data/spec/ollama_chat/personae_management_spec.rb +75 -0
- data/spec/ollama_chat/session_management_spec.rb +31 -6
- data/spec/ollama_chat/tool_call_processor_spec.rb +51 -0
- data/spec/ollama_chat/tool_calling_spec.rb +0 -26
- data/spec/ollama_chat/tools/execute_shell_spec.rb +68 -0
- metadata +18 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e22822d0b02f1f0dad3fe7e9b2902fc727a6dc2d13ffcd192da3fda3980fdefc
|
|
4
|
+
data.tar.gz: 2bb58b895a48bceefb16600def20f1ea8bb16deeeabd4a307f7fea27749a9139
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41e74251f4318137993e985f97beba5626b9d200b460d827ce04bddfb6dc316df6dae2d78024dd1c7d4486f3b7f4f7febd4e38d666fdf1e5f00f0e3e246b7c70
|
|
7
|
+
data.tar.gz: 1cd96acf7bca275337aaaad85c41d65c2bd75101ccfa1f0924d72eda254f4fffe04c019c9c2ee12285df6ea2e91da96cf702753059c0a7e7d7aa4bde8c13b0f4
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,126 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-21 v0.0.136
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Added a `machine` key to the static runtime information, injecting a compact
|
|
8
|
+
host description into the system prompt via a new `machine_profile` method in
|
|
9
|
+
`information.rb`.
|
|
10
|
+
- Introduced a new `OC::FASTFETCH` configuration option to resolve the
|
|
11
|
+
`fastfetch` executable path.
|
|
12
|
+
- Added a new `fastfetch-profile.jsonc` configuration file to define the
|
|
13
|
+
modules used for the machine profile.
|
|
14
|
+
- Added `always-allow` and `direct` modes to the `execute_shell` tool, allowing
|
|
15
|
+
users to remember command approvals for the session.
|
|
16
|
+
- Extended the `execute_shell` confirmation prompt to include `a` (always allow
|
|
17
|
+
with pager) and `d` (always allow direct to `STDOUT`) options.
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
|
|
21
|
+
- Updated the `kramdown_ansi` table separator spec to use pipe-bounded syntax
|
|
22
|
+
(`| Name | Age |`) to align with `kramdown-ansi` **0.6** requirements,
|
|
23
|
+
preventing literal dashes from leaking into the output.
|
|
24
|
+
- Renamed the `/last` command option from `-p` to `-d` for consistency with the
|
|
25
|
+
`execute_shell` direct mode flag.
|
|
26
|
+
|
|
27
|
+
### Improvements
|
|
28
|
+
|
|
29
|
+
- Refactored `execute_shell` output display to use a shared `write_output`
|
|
30
|
+
helper for both pager and direct output paths.
|
|
31
|
+
- Updated the `execute_shell` tool description to document the new
|
|
32
|
+
`always-allow` and `direct` options.
|
|
33
|
+
|
|
34
|
+
### Build & CI
|
|
35
|
+
|
|
36
|
+
- Updated the `kramdown-ansi` dependency constraint from **0.5** to **0.6** in
|
|
37
|
+
`Rakefile` and `ollama_chat.gemspec`.
|
|
38
|
+
- Added `lib/ollama_chat/ollama_chat_config/fastfetch-profile.jsonc` to the
|
|
39
|
+
gemspec files list.
|
|
40
|
+
- Added `git config --global --add safe.directory /work` to the CI test script
|
|
41
|
+
in `.all_images.yml`.
|
|
42
|
+
|
|
43
|
+
### Testing
|
|
44
|
+
|
|
45
|
+
- Added four new specs for `execute_shell` covering `always-allow` and `direct`
|
|
46
|
+
mode behaviors, including repeat execution and command differentiation.
|
|
47
|
+
- Updated `commands_spec.rb` to reflect the rename of the `/last` command
|
|
48
|
+
option from `-p` to `-d`.
|
|
49
|
+
- Added a `before` block in `execute_shell_spec.rb` to clear the
|
|
50
|
+
`always_allowed` hash and prevent cross-spec contamination.
|
|
51
|
+
|
|
52
|
+
## 2026-10-19 v0.0.135
|
|
53
|
+
|
|
54
|
+
### Features
|
|
55
|
+
|
|
56
|
+
- Introduced `OllamaChat::ModelHandling::ModelCache` to manage model metadata
|
|
57
|
+
and sizes with two-tier storage (names/sizes and full metadata), supporting
|
|
58
|
+
lazy registration and per-client isolation.
|
|
59
|
+
- Added `-r` flag to the `/model change` command to flush the model cache
|
|
60
|
+
before re-listing models.
|
|
61
|
+
- Added `OllamaChat::ToolCallProcessor` to encapsulate the tool call lifecycle,
|
|
62
|
+
including execution, result storage, and draining of results for the chat
|
|
63
|
+
loop.
|
|
64
|
+
- Enabled searching sessions by ID in the session chooser by including the
|
|
65
|
+
session ID in the search value.
|
|
66
|
+
|
|
67
|
+
### Fixes
|
|
68
|
+
|
|
69
|
+
- Rescued `Ollama::Errors::BadRequestError` in `model_present?` to prevent
|
|
70
|
+
unhandled exceptions for model names rejected by the server with a 400
|
|
71
|
+
status.
|
|
72
|
+
- Fixed persona leakage in new sessions by using `config.persona&.default_name`
|
|
73
|
+
instead of the current session's persona in `Session.with_defaults`.
|
|
74
|
+
- Synchronized session triggers with the active persona via `sync_trigger` in
|
|
75
|
+
`PersonaeManagement`, ensuring triggers are rekeyed, cleared, or seeded
|
|
76
|
+
correctly on persona changes.
|
|
77
|
+
|
|
78
|
+
### Improvements
|
|
79
|
+
|
|
80
|
+
- Refactored `model_present?` to use the new `ModelCache` instead of an inline
|
|
81
|
+
hash, improving cache management and reducing repeated API calls.
|
|
82
|
+
- Updated `choose_model` to skip the `all_models` API call if the requested
|
|
83
|
+
model is already confirmed present via `model_present?`.
|
|
84
|
+
- Renamed the prompt context for report templates from `session` to `report` in
|
|
85
|
+
`default_config.yml` and `SessionManagement` to better reflect its purpose.
|
|
86
|
+
- Renamed `OllamaChat::Database::SessionLocking` to
|
|
87
|
+
`OllamaChat::Database::RowLocking` to reflect its generic row-level locking
|
|
88
|
+
behavior, removing the `chat:` keyword argument from `lock?` and moving
|
|
89
|
+
feedback emission to the caller.
|
|
90
|
+
- Replaced `Kramdown::ANSI::Width.truncate` and `Kramdown::ANSI::Width.wrap`
|
|
91
|
+
with bare `truncate` and `wrap` in multiple modules where ANSI sequences are
|
|
92
|
+
not present at measurement time.
|
|
93
|
+
- Truncated session names in the `/sessions` list to fit terminal width,
|
|
94
|
+
preventing long names from wrapping or pushing metadata off-screen.
|
|
95
|
+
- Decoupled `model_with_size` from the `Ollama::Tag` shape, now accepting model
|
|
96
|
+
name and size directly.
|
|
97
|
+
|
|
98
|
+
### Build & CI
|
|
99
|
+
|
|
100
|
+
- Updated `gem_hadar` development dependency to **2.20.1**.
|
|
101
|
+
- Regenerated gem metadata to include new files (`tool_call_processor.rb`,
|
|
102
|
+
`model_cache.rb`, `row_locking.rb`) and updated file lists.
|
|
103
|
+
|
|
104
|
+
### Testing
|
|
105
|
+
|
|
106
|
+
- Added `spec/ollama_chat/model_handling/model_cache_spec.rb` with 18 examples
|
|
107
|
+
covering cache operations, lazy registration, per-instance isolation, and
|
|
108
|
+
flushing.
|
|
109
|
+
- Added `spec/ollama_chat/personae_management_spec.rb` with 5 examples covering
|
|
110
|
+
`sync_trigger` state transitions.
|
|
111
|
+
- Added `spec/ollama_chat/tool_call_processor_spec.rb` covering `drain`,
|
|
112
|
+
`clear!`, and result storage.
|
|
113
|
+
- Updated `spec/ollama_chat/commands_spec.rb` to test the `-r` flag for `/model
|
|
114
|
+
change`.
|
|
115
|
+
- Updated `spec/ollama_chat/session_management_spec.rb` to verify persona and
|
|
116
|
+
trigger behavior in new and duplicated sessions, and adjusted lock
|
|
117
|
+
expectations.
|
|
118
|
+
- Updated `spec/ollama_chat/information_spec.rb` to reflect the correct persona
|
|
119
|
+
display after initialization.
|
|
120
|
+
- Removed `#handle_tool_call_results?` tests from
|
|
121
|
+
`spec/ollama_chat/tool_calling_spec.rb` as the method was moved to
|
|
122
|
+
`ToolCallProcessor`.
|
|
123
|
+
|
|
3
124
|
## 2026-10-17 v0.0.134
|
|
4
125
|
|
|
5
126
|
* Removed the local dependency on the `ollama-ruby` gem from the `Gemfile`.
|
data/Rakefile
CHANGED
|
@@ -47,7 +47,7 @@ GemHadar do
|
|
|
47
47
|
dependency 'redis', '~> 5.0'
|
|
48
48
|
dependency 'mime-types', '~> 3.0'
|
|
49
49
|
dependency 'reverse_markdown', '~> 3.0'
|
|
50
|
-
dependency 'kramdown-ansi', '~> 0.
|
|
50
|
+
dependency 'kramdown-ansi', '~> 0.6'
|
|
51
51
|
dependency 'complex_config', '~> 0.22', '>= 0.22.2'
|
|
52
52
|
dependency 'tins', '~> 1.61'
|
|
53
53
|
dependency 'search_ui', '>= 0.2'
|
data/lib/ollama_chat/chat.rb
CHANGED
|
@@ -116,6 +116,7 @@ class OllamaChat::Chat
|
|
|
116
116
|
setup_switches
|
|
117
117
|
setup_session
|
|
118
118
|
setup_state_selectors(config)
|
|
119
|
+
@model_cache = ModelCache.new
|
|
119
120
|
connect_ollama
|
|
120
121
|
connect_embedding_ollama
|
|
121
122
|
@documents = setup_documents
|
|
@@ -123,7 +124,7 @@ class OllamaChat::Chat
|
|
|
123
124
|
@cache = setup_cache
|
|
124
125
|
@images = []
|
|
125
126
|
@kramdown_ansi_styles = configure_kramdown_ansi_styles
|
|
126
|
-
@
|
|
127
|
+
@tool_call_processor = OllamaChat::ToolCallProcessor.new(chat: self)
|
|
127
128
|
setup_personae_directory
|
|
128
129
|
@opts[?S] and init_server_socket
|
|
129
130
|
rescue ComplexConfig::AttributeMissing, ComplexConfig::ConfigurationSyntaxError => e
|
|
@@ -166,6 +167,11 @@ class OllamaChat::Chat
|
|
|
166
167
|
# @return [OllamaChat::RedisCache, nil] the cache instance
|
|
167
168
|
attr_reader :cache
|
|
168
169
|
|
|
170
|
+
# The model_cache method provides read-only access to the model cache.
|
|
171
|
+
#
|
|
172
|
+
# @return [ ModelCache ] the model cache instance
|
|
173
|
+
attr_reader :model_cache
|
|
174
|
+
|
|
169
175
|
# Returns the voice handler class based on the configuration.
|
|
170
176
|
#
|
|
171
177
|
# This method resolves the voice handler class specified in the config.
|
|
@@ -341,6 +347,12 @@ class OllamaChat::Chat
|
|
|
341
347
|
"#<#{self.class}: #{base_url.to_s}>"
|
|
342
348
|
end
|
|
343
349
|
|
|
350
|
+
# Returns the tool call processor instance.
|
|
351
|
+
#
|
|
352
|
+
# @return [OllamaChat::ToolCallProcessor] the processor that handles
|
|
353
|
+
# tool execution and result storage
|
|
354
|
+
attr_reader :tool_call_processor
|
|
355
|
+
|
|
344
356
|
private
|
|
345
357
|
|
|
346
358
|
# Wrapper for ollama.chat to provide consistent logging of all arguments.
|
|
@@ -462,7 +474,7 @@ class OllamaChat::Chat
|
|
|
462
474
|
type = :terminal_input
|
|
463
475
|
input_prompt = bold { color(172) { message_type(@images) + " user" } } + bold { "> " }
|
|
464
476
|
begin
|
|
465
|
-
tools_were_called =
|
|
477
|
+
tools_were_called = tool_call_processor.drain { |index, tool_name, content|
|
|
466
478
|
messages << OllamaChat::Message.new(
|
|
467
479
|
role: 'user', # XXX this should be 'tool' but it doesn't currently seem to work in Ollama API
|
|
468
480
|
sender_name: tool_name,
|
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -285,15 +285,15 @@ module OllamaChat::Commands
|
|
|
285
285
|
|
|
286
286
|
command(
|
|
287
287
|
name: :last,
|
|
288
|
-
regexp: %r(^/last((?:\s+(?:-[
|
|
289
|
-
options: '[-
|
|
288
|
+
regexp: %r(^/last((?:\s+(?:-[dtsv]))*)(?:\s+(\d*))?$),
|
|
289
|
+
options: '[-d|-t|-s|n=1]',
|
|
290
290
|
help: <<~EOT
|
|
291
291
|
🔍 Show or speak last message(s)
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
Options: -d (direct), -t (show thinking),
|
|
293
|
+
-s (hide thinking) -v (voice output)
|
|
294
294
|
EOT
|
|
295
295
|
) do |opts,number|
|
|
296
|
-
opts = go_command('
|
|
296
|
+
opts = go_command('dtsv', opts.to_s)
|
|
297
297
|
n = number.to_i.clamp(1..)
|
|
298
298
|
if opts[?v]
|
|
299
299
|
messages.speak_last(n)
|
|
@@ -306,7 +306,7 @@ module OllamaChat::Commands
|
|
|
306
306
|
else
|
|
307
307
|
self.think_loud.on?
|
|
308
308
|
end
|
|
309
|
-
messages.show_last(n, think_loud:, pager: !opts[?
|
|
309
|
+
messages.show_last(n, think_loud:, pager: !opts[?d])
|
|
310
310
|
end
|
|
311
311
|
:next
|
|
312
312
|
end
|
|
@@ -552,12 +552,12 @@ module OllamaChat::Commands
|
|
|
552
552
|
|
|
553
553
|
command(
|
|
554
554
|
name: :model,
|
|
555
|
-
regexp: %r(^/model(?:\s+(change|options(?: copy| delete| export| import)?|options from session|options to session))?((?:\s+(?:-m|-p\s+[\S]+))*)$),
|
|
555
|
+
regexp: %r(^/model(?:\s+(change|options(?: copy| delete| export| import)?|options from session|options to session))?((?:\s+(?:-m|-r|-p\s+[\S]+))*)$),
|
|
556
556
|
complete: [ 'model', %w[ change options options\ copy options\ delete options\ export options\ import options\ from\ session options\ to\ session ] ],
|
|
557
|
-
options: '[-m|-p pattern]',
|
|
557
|
+
options: '[-m|-r|-p pattern]',
|
|
558
558
|
help: <<~EOT
|
|
559
559
|
🤖 Manage AI models & profiles:
|
|
560
|
-
- change: Switch active model
|
|
560
|
+
- change: Switch active model (-r to refresh)
|
|
561
561
|
- options: Edit saved profile config
|
|
562
562
|
- options copy: Copy profile from another model
|
|
563
563
|
- options delete: Delete a saved profile
|
|
@@ -565,11 +565,14 @@ module OllamaChat::Commands
|
|
|
565
565
|
- options import: Selectively import profiles from JSON
|
|
566
566
|
- options from session: Save live → Saved
|
|
567
567
|
- options to session: Apply Saved → Live
|
|
568
|
-
|
|
569
|
-
|
|
568
|
+
-m interactively choose a model
|
|
569
|
+
-r refresh model list (flush cache)
|
|
570
|
+
-p PATTERN narrow file search for import
|
|
570
571
|
EOT
|
|
571
572
|
) do |subcommand, opts|
|
|
572
573
|
if subcommand == 'change'
|
|
574
|
+
opts = go_command('mr', opts)
|
|
575
|
+
@model_cache.flush if opts[?r]
|
|
573
576
|
begin
|
|
574
577
|
model = choose_model('', @model)
|
|
575
578
|
profile = choose_profile_for_model(model) || 'default'
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# the JSON serialization of complex attributes such as `tools_default_enabled`
|
|
6
6
|
# and `model_options`.
|
|
7
7
|
class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
|
|
8
|
-
include OllamaChat::Database::
|
|
8
|
+
include OllamaChat::Database::RowLocking
|
|
9
9
|
include OllamaChat::Database::Duplicatable
|
|
10
10
|
|
|
11
11
|
plugin :timestamps, update_on_create: true
|
|
@@ -200,7 +200,7 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
|
|
|
200
200
|
name: ,
|
|
201
201
|
current_model: ,
|
|
202
202
|
current_collection: chat.initial_collection,
|
|
203
|
-
default_persona_name: chat.
|
|
203
|
+
default_persona_name: chat.config.persona&.default_name,
|
|
204
204
|
current_system_prompt: chat.initial_system_prompt,
|
|
205
205
|
tools_enabled: chat.config.tools.enabled,
|
|
206
206
|
tools_default_enabled: ,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# The
|
|
1
|
+
# The RowLocking module provides a mechanism for exclusive row access,
|
|
2
2
|
# similar to the swap-file locking used by Vim.
|
|
3
3
|
#
|
|
4
4
|
# It prevents multiple concurrent instances of the application from modifying
|
|
5
|
-
# the same
|
|
5
|
+
# the same database row, thereby avoiding race conditions and state collision.
|
|
6
6
|
# The lock is based on the Process ID (PID) and is validated by checking if
|
|
7
7
|
# the owning process is still active on the system.
|
|
8
8
|
#
|
|
9
|
-
# This module is intended to be mixed into
|
|
10
|
-
#
|
|
11
|
-
module OllamaChat::Database::
|
|
12
|
-
# Checks if the
|
|
9
|
+
# This module is intended to be mixed into any Sequel model that has a
|
|
10
|
+
# `locked_by_pid` column.
|
|
11
|
+
module OllamaChat::Database::RowLocking
|
|
12
|
+
# Checks if the row is currently locked by an active process.
|
|
13
13
|
#
|
|
14
14
|
# @return [Integer, nil] the PID of the locking process if active, or nil if unlocked/stale.
|
|
15
15
|
def locked?
|
|
@@ -23,17 +23,12 @@ module OllamaChat::Database::SessionLocking
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Attempts to acquire a lock on the
|
|
26
|
+
# Attempts to acquire a lock on the row.
|
|
27
27
|
#
|
|
28
|
-
# @param chat [OllamaChat] the chat instance that receives feedback
|
|
29
28
|
# @return [Boolean] true if the lock was successfully acquired, false if already locked.
|
|
30
|
-
def lock?
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
false
|
|
34
|
-
else
|
|
35
|
-
lock
|
|
36
|
-
end
|
|
29
|
+
def lock?
|
|
30
|
+
return false if locked?
|
|
31
|
+
!!lock
|
|
37
32
|
end
|
|
38
33
|
|
|
39
34
|
# Sets the lock for the current process.
|
|
@@ -43,7 +38,7 @@ module OllamaChat::Database::SessionLocking
|
|
|
43
38
|
update(locked_by_pid: $$)
|
|
44
39
|
end
|
|
45
40
|
|
|
46
|
-
# Releases the lock on the
|
|
41
|
+
# Releases the lock on the row.
|
|
47
42
|
#
|
|
48
43
|
# @return [Boolean] true if the update was successful.
|
|
49
44
|
def unlock
|
data/lib/ollama_chat/database.rb
CHANGED
|
@@ -89,147 +89,13 @@ class OllamaChat::FollowChat
|
|
|
89
89
|
|
|
90
90
|
output_eval_stats(response)
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
chat.tool_call_processor.handle(response)
|
|
93
93
|
|
|
94
94
|
self
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
private
|
|
98
98
|
|
|
99
|
-
# The handle_tool_calls method processes tool calls from a response and
|
|
100
|
-
# executes them.
|
|
101
|
-
#
|
|
102
|
-
# This method checks if the response contains tool calls, and if so, iterates
|
|
103
|
-
# through each tool call to execute the corresponding tool from the
|
|
104
|
-
# registered tools. The results of the tool execution are stored in the
|
|
105
|
-
# chat's tool_call_results hash using the tool name as the key.
|
|
106
|
-
#
|
|
107
|
-
# @param response [Object] the response object containing tool calls to
|
|
108
|
-
# process
|
|
109
|
-
def handle_tool_calls(response)
|
|
110
|
-
return unless response.message.ask_and_send(:tool_calls)
|
|
111
|
-
|
|
112
|
-
tools_used = {}
|
|
113
|
-
|
|
114
|
-
response.message.tool_calls.each do |tool_call|
|
|
115
|
-
name = tool_call.function.name
|
|
116
|
-
if %r(.*[/:](?<new_name>.+)) =~ name
|
|
117
|
-
msg = "Received namespaced tool call for #{name}, correcting to #{new_name}"
|
|
118
|
-
chat.log(:warn, msg, data: { tool: name, new_name: })
|
|
119
|
-
name = new_name
|
|
120
|
-
end
|
|
121
|
-
unless chat.tool_configured?(name)
|
|
122
|
-
msg = "Error: Unconfigured tool named %s ignored => Skip.\n" % name
|
|
123
|
-
chat.tool_call_results[name] << msg
|
|
124
|
-
chat.log(:error, msg, data: { tool: name, reason: :unconfigured })
|
|
125
|
-
next
|
|
126
|
-
end
|
|
127
|
-
unless chat.tool_registered?(name)
|
|
128
|
-
msg = "Error: Unregistered tool named %s ignored => Skip.\n" % name
|
|
129
|
-
chat.tool_call_results[name] << msg
|
|
130
|
-
chat.log(:error, msg, data: { tool: name, reason: :unregistered })
|
|
131
|
-
next
|
|
132
|
-
end
|
|
133
|
-
unless chat.tool_enabled?(name)
|
|
134
|
-
msg = "Error: Disabled tool named %s ignored => Skip.\n" % name
|
|
135
|
-
chat.tool_call_results[name] << msg
|
|
136
|
-
chat.log(:error, msg, data: { tool: name, reason: :disabled })
|
|
137
|
-
next
|
|
138
|
-
end
|
|
139
|
-
feedback(?\n, output:)
|
|
140
|
-
confirmed = :implicit
|
|
141
|
-
resolve = nil
|
|
142
|
-
function = JSON.pretty_generate(tool_call.function)
|
|
143
|
-
chat.log(:info, "Tool call received", data: { tool: name, function: })
|
|
144
|
-
if chat.tool_function(name).require_confirmation?
|
|
145
|
-
feedback(
|
|
146
|
-
("I want to execute tool %s\n%s\n" % [ bold { name }, italic { function }, ]),
|
|
147
|
-
type: :alarm
|
|
148
|
-
)
|
|
149
|
-
prompt = '❓ Allow ✅[y]es / ⛔️[n]o / 📝[i]nstruct? '
|
|
150
|
-
answer = chat.confirm?(prompt:)&.to_s&.downcase
|
|
151
|
-
resolve = 'You **MUST** ask the user for instructions on how to proceed!!!'
|
|
152
|
-
case answer
|
|
153
|
-
when 'y'
|
|
154
|
-
confirmed = :explicit
|
|
155
|
-
when 'n'
|
|
156
|
-
confirmed = :denied
|
|
157
|
-
else
|
|
158
|
-
confirmed = :denied
|
|
159
|
-
instr = chat.ask?(prompt: '📝 Instructions: ')&.strip
|
|
160
|
-
resolve = instr.full? || resolve
|
|
161
|
-
end
|
|
162
|
-
else
|
|
163
|
-
feedback(
|
|
164
|
-
"Executing tool %s\n%s" % [ bold { name }, italic { function }, ]
|
|
165
|
-
)
|
|
166
|
-
end
|
|
167
|
-
start = Time.now
|
|
168
|
-
result = nil
|
|
169
|
-
case confirmed
|
|
170
|
-
when :denied
|
|
171
|
-
result = JSON(message: 'User denied confirmation!', resolve:)
|
|
172
|
-
feedback(?\n)
|
|
173
|
-
feedback(
|
|
174
|
-
("Execution of tool %s denied by user.\n\n" % [ bold { name } ]),
|
|
175
|
-
type: :denied
|
|
176
|
-
)
|
|
177
|
-
chat.log(:warn, "Tool execution denied", data: { tool: name, reason: :denied })
|
|
178
|
-
else
|
|
179
|
-
symbol = confirmed == :implicit ? '☑️ ' : '✅'
|
|
180
|
-
feedback(
|
|
181
|
-
("\n%s Execution of tool %s confirmed.\n\n" % [ symbol, bold { name } ])
|
|
182
|
-
)
|
|
183
|
-
result = OllamaChat::Tools.registered_tool(name, chat:).execute(tool_call)
|
|
184
|
-
chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
chat.tool_call_results[name] << result
|
|
188
|
-
data = nil
|
|
189
|
-
message = begin
|
|
190
|
-
data = JSON.parse(result)
|
|
191
|
-
chat.log(:info, "Tool result", data: { result: data })
|
|
192
|
-
data.to_hash['message'] if data.respond_to?(:to_hash)
|
|
193
|
-
rescue
|
|
194
|
-
chat.log(:info, "Tool result", data: { result: })
|
|
195
|
-
nil
|
|
196
|
-
end
|
|
197
|
-
warn =
|
|
198
|
-
begin
|
|
199
|
-
!!data.ask_and_send(:[], 'error') || data.ask_and_send(:[], 'success') == false
|
|
200
|
-
rescue
|
|
201
|
-
false
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
es = OllamaChat::TokenEstimator.estimate(result.to_s)
|
|
205
|
-
|
|
206
|
-
tools_used[name] = {
|
|
207
|
-
message:,
|
|
208
|
-
warn: ,
|
|
209
|
-
size: es.bytes_formatted,
|
|
210
|
-
tokens: es.tokens_formatted,
|
|
211
|
-
duration: Tins::Duration.new(Time.now - start).to_s,
|
|
212
|
-
}
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
if tools_used.full?
|
|
216
|
-
infobar.reset
|
|
217
|
-
tools_used.each do |name, info|
|
|
218
|
-
feedback_message =
|
|
219
|
-
if info[:message]
|
|
220
|
-
"\n%s %s\n\n" % [ info[:warn] ? '⚠️' : '💡', info[:message] ]
|
|
221
|
-
end
|
|
222
|
-
feedback(<<~EOT.strip)
|
|
223
|
-
🔧 Tool functions #{name} returned result (#{info[:size]}/#{info[:tokens]} in #{info[:duration]}).
|
|
224
|
-
#{feedback_message}
|
|
225
|
-
EOT
|
|
226
|
-
feedback(?\n)
|
|
227
|
-
timeout = chat.tool_function(name).result_display_timeout?
|
|
228
|
-
chat.confirm?(prompt: '⏎ Press any key to continue (%s). ', timeout:)
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
99
|
# The truncate_for_terminal method processes text to fit within a specified
|
|
234
100
|
# number of lines.
|
|
235
101
|
#
|
|
@@ -74,7 +74,7 @@ module OllamaChat::Information
|
|
|
74
74
|
def collection_stats(output: STDOUT)
|
|
75
75
|
col = database_collection?(collection)
|
|
76
76
|
length = (Tins::Terminal.cols - 10).clamp(0..)
|
|
77
|
-
wrapped_tags =
|
|
77
|
+
wrapped_tags =
|
|
78
78
|
wrap(@documents.tags.to_a.join(', '), length:).
|
|
79
79
|
gsub(/(?<!\A)^/, ' ' * 4)
|
|
80
80
|
output.puts <<~EOT
|
|
@@ -341,6 +341,7 @@ module OllamaChat::Information
|
|
|
341
341
|
current_directory: Pathname.pwd.expand_path.to_path,
|
|
342
342
|
languages: config.languages * ', ',
|
|
343
343
|
location: location.on?.full? { location_description } || 'n/a',
|
|
344
|
+
machine: machine_profile || 'n/a',
|
|
344
345
|
user: ,
|
|
345
346
|
}
|
|
346
347
|
end
|
|
@@ -474,6 +475,63 @@ module OllamaChat::Information
|
|
|
474
475
|
|
|
475
476
|
private
|
|
476
477
|
|
|
478
|
+
# Builds and caches a compact, single-line description of the host
|
|
479
|
+
# machine, derived from a `fastfetch` run against the bundled profile
|
|
480
|
+
# (+OC::FASTFETCH+). The profile requests a focused set of modules
|
|
481
|
+
# (os, kernel, cpu, gpu, de, wm, terminal, memory, editor, packages,
|
|
482
|
+
# chassis, board, bios) so the result stays small enough to inject into
|
|
483
|
+
# the system prompt at zero per-turn cost.
|
|
484
|
+
#
|
|
485
|
+
# Gracefully degrades to `'n/a'` when +fastfetch+ is not installed or
|
|
486
|
+
# the config is missing, so a missing optional dependency never breaks
|
|
487
|
+
# session startup.
|
|
488
|
+
#
|
|
489
|
+
# @return [String] e.g.
|
|
490
|
+
# `C64 1541 (c64) · 6502@1MHz m64 · MOS Technology 6502 ·
|
|
491
|
+
# NVIDIA GeForce RTX 5090 (32GB) · VIC-II (no DE) · Home Computer ·
|
|
492
|
+
# PET2001N-Board · MOS 3.0 UEFI 4000 · 64 KiB · vim 1.0 · 1 pkgs`
|
|
493
|
+
def machine_profile
|
|
494
|
+
return @machine_profile if defined?(@machine_profile)
|
|
495
|
+
@machine_profile = nil
|
|
496
|
+
unless fastfetch = OC::FASTFETCH?
|
|
497
|
+
log(:warn, 'Fastfetch executable not found, skipping machine profile!')
|
|
498
|
+
return
|
|
499
|
+
end
|
|
500
|
+
config = Shellwords.escape(File.join(__dir__, 'ollama_chat_config', 'fastfetch-profile.jsonc'))
|
|
501
|
+
out = `#{fastfetch} -c #{config} --format json 2>/dev/null`.full? or return
|
|
502
|
+
mods = JSON.parse(out, object_class: JSON::GenericObject)
|
|
503
|
+
@machine_profile =
|
|
504
|
+
mods.map do |m|
|
|
505
|
+
r = m.result or next
|
|
506
|
+
case m.type
|
|
507
|
+
when 'OS'
|
|
508
|
+
"%s %s (%s)" % [ r.name, r.versionID, r.idLike ]
|
|
509
|
+
when 'Kernel'
|
|
510
|
+
"%s %s" % [ r.release, r.architecture ]
|
|
511
|
+
when 'CPU'
|
|
512
|
+
r.cpu
|
|
513
|
+
when 'GPU'
|
|
514
|
+
(r.find { _1.type == 'Discrete' } || r.first)&.name
|
|
515
|
+
when 'WM'
|
|
516
|
+
suffix = mods.any? { _1.type == 'DE' && _1.result } ? '' : ' (no DE)'
|
|
517
|
+
"%s/%s%s" % [ r.processName, r.protocolName, suffix ]
|
|
518
|
+
when 'Memory'
|
|
519
|
+
format_bytes(r.total)
|
|
520
|
+
when 'Editor'
|
|
521
|
+
"%s %s" % [ r.exe, r.version ]
|
|
522
|
+
when 'Packages'
|
|
523
|
+
"%s pkgs" % r.all
|
|
524
|
+
when 'Chassis'
|
|
525
|
+
r.type
|
|
526
|
+
when 'Board'
|
|
527
|
+
r.name
|
|
528
|
+
when 'BIOS'
|
|
529
|
+
"%s %s" % [ r.type, r.version ]
|
|
530
|
+
end
|
|
531
|
+
end.compact * ' · '
|
|
532
|
+
rescue JSON::ParserError
|
|
533
|
+
end
|
|
534
|
+
|
|
477
535
|
# Lazily builds and caches the {ContextUsage} bound to this chat,
|
|
478
536
|
# which owns the ANSI/emoji presentation logic for context usage.
|
|
479
537
|
#
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module OllamaChat::ModelHandling
|
|
2
|
+
# Caches model metadata for one or more Ollama instances.
|
|
3
|
+
#
|
|
4
|
+
# This class provides a simple in-memory cache to store and retrieve
|
|
5
|
+
# model metadata, reducing the need for repeated `ollama show` calls.
|
|
6
|
+
# It supports multiple Ollama instances by using the instance's
|
|
7
|
+
# object ID as part of the cache key.
|
|
8
|
+
class ModelCache
|
|
9
|
+
# Initializes a new model cache.
|
|
10
|
+
def initialize
|
|
11
|
+
@models = Hash.new { |h, k| h[k] = {} }
|
|
12
|
+
@sizes = Hash.new { |h, k| h[k] = {} }
|
|
13
|
+
@registered = Hash.new(false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Caches model metadata (and optionally size) for a given Ollama
|
|
17
|
+
# instance and model name.
|
|
18
|
+
#
|
|
19
|
+
# Existing metadata is never overwritten (`||=`); only missing
|
|
20
|
+
# entries are filled in. Size, when provided, overwrites unconditionally.
|
|
21
|
+
#
|
|
22
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
23
|
+
# @param name [String, Symbol] the model name
|
|
24
|
+
# @param value [ModelMetadata, nil] the metadata to cache (defaults to nil)
|
|
25
|
+
# @param size [Integer, nil] the model size in bytes (optional)
|
|
26
|
+
# @return [ModelMetadata, nil] the cached metadata
|
|
27
|
+
def cache(ollama, name, value = nil, size: nil)
|
|
28
|
+
name = name.to_s
|
|
29
|
+
@models[ollama.__id__][name] ||= value
|
|
30
|
+
size and @sizes[ollama.__id__][name] = size
|
|
31
|
+
@models[ollama.__id__][name]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the cached `ModelMetadata` for a given model, if it has
|
|
35
|
+
# been fetched. Note: this is a *metadata* predicate, not an
|
|
36
|
+
# existence check. A model can be registered (via `models`) and
|
|
37
|
+
# have a size, yet still return `nil` here until metadata is
|
|
38
|
+
# explicitly cached (e.g. by `model_present?`).
|
|
39
|
+
#
|
|
40
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
41
|
+
# @param name [String, Symbol] the model name
|
|
42
|
+
# @return [ModelMetadata, nil] the metadata if fetched, nil otherwise
|
|
43
|
+
def cached?(ollama, name)
|
|
44
|
+
name = name.to_s
|
|
45
|
+
@models[ollama.__id__][name]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns the cached size in bytes for a given model.
|
|
49
|
+
#
|
|
50
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
51
|
+
# @param name [String, Symbol] the model name
|
|
52
|
+
# @return [Integer] the size in bytes, 0 if not cached
|
|
53
|
+
def model_size(ollama, name)
|
|
54
|
+
@sizes[ollama.__id__][name].to_i
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns a list of cached model names for the given Ollama instance.
|
|
58
|
+
#
|
|
59
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
60
|
+
# @return [Array<String>] the cached model names
|
|
61
|
+
def models(ollama)
|
|
62
|
+
registered?(ollama) or register_all(ollama)
|
|
63
|
+
@models[ollama.__id__].keys
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Flushes cached metadata and sizes for one or all Ollama instances.
|
|
67
|
+
#
|
|
68
|
+
# @param ollama [Ollama::Client, Integer, nil] an Ollama client instance,
|
|
69
|
+
# a raw object ID, or `nil` to flush all instances (default: nil)
|
|
70
|
+
# @return [true]
|
|
71
|
+
def flush(ollama = nil)
|
|
72
|
+
if ollama
|
|
73
|
+
ollama_id = ollama.ask_and_send(:to_int) || ollama.__id__
|
|
74
|
+
@models[ollama_id].clear
|
|
75
|
+
@sizes[ollama_id].clear
|
|
76
|
+
@registered[ollama_id] = false
|
|
77
|
+
else
|
|
78
|
+
@models.each_key { |ollama_id| flush(ollama_id) }
|
|
79
|
+
end
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# Checks whether model registration has been completed for the instance.
|
|
86
|
+
#
|
|
87
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def registered?(ollama)
|
|
90
|
+
@registered[ollama.__id__]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Registers multiple model names as cached for the given Ollama instance.
|
|
94
|
+
#
|
|
95
|
+
# @param ollama [Ollama::Client] the Ollama client instance
|
|
96
|
+
#
|
|
97
|
+
# @return [ ModelCache ] this cache instance
|
|
98
|
+
def register_all(ollama)
|
|
99
|
+
ollama.tags.models.each do |model|
|
|
100
|
+
cache(ollama, model.name, size: model.size)
|
|
101
|
+
end
|
|
102
|
+
@registered[ollama.__id__] = true
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|