ollama_chat 0.0.112 → 0.0.114
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 +92 -0
- data/README.md +7 -0
- data/bin/ollama_chat_log +1 -1
- data/bin/ollama_chat_send +3 -1
- data/lib/ollama_chat/chat.rb +30 -14
- data/lib/ollama_chat/clipboard.rb +1 -1
- data/lib/ollama_chat/commands.rb +25 -19
- data/lib/ollama_chat/compaction.rb +289 -0
- data/lib/ollama_chat/config_handling.rb +32 -0
- data/lib/ollama_chat/database/migrations/010_add_enabled_to_collections.rb +7 -0
- data/lib/ollama_chat/database/models/app_state.rb +48 -16
- data/lib/ollama_chat/database/models/session.rb +1 -2
- data/lib/ollama_chat/database.rb +4 -2
- data/lib/ollama_chat/information.rb +53 -6
- data/lib/ollama_chat/logging.rb +3 -2
- data/lib/ollama_chat/message.rb +22 -1
- data/lib/ollama_chat/message_format.rb +1 -2
- data/lib/ollama_chat/message_list.rb +223 -9
- data/lib/ollama_chat/model_handling.rb +1 -1
- data/lib/ollama_chat/oc.rb +17 -7
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +68 -4
- data/lib/ollama_chat/parsing.rb +2 -1
- data/lib/ollama_chat/prompt_handling.rb +1 -1
- data/lib/ollama_chat/prompt_management.rb +1 -0
- data/lib/ollama_chat/rag_handling.rb +20 -6
- data/lib/ollama_chat/session_management.rb +5 -3
- data/lib/ollama_chat/token_estimator/crude.rb +1 -1
- data/lib/ollama_chat/token_estimator.rb +1 -1
- data/lib/ollama_chat/tools/concern.rb +17 -0
- data/lib/ollama_chat/tools/directory_structure.rb +11 -7
- data/lib/ollama_chat/tools/eval_ruby.rb +16 -6
- data/lib/ollama_chat/tools/get_rfc.rb +2 -0
- data/lib/ollama_chat/tools/lookup_group.rb +100 -0
- data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -1
- data/lib/ollama_chat/tools/patch_file.rb +23 -5
- data/lib/ollama_chat/tools/read_file.rb +1 -1
- data/lib/ollama_chat/tools/resolve_tag.rb +1 -1
- data/lib/ollama_chat/tools/search_knowledge.rb +7 -1
- data/lib/ollama_chat/tools/write_file.rb +25 -6
- data/lib/ollama_chat/tools.rb +1 -0
- data/lib/ollama_chat/utils/analyze_directory.rb +5 -4
- data/lib/ollama_chat/utils/fetcher.rb +1 -1
- data/lib/ollama_chat/utils/log_rotation.rb +29 -0
- data/lib/ollama_chat/utils/tag_resolver.rb +3 -3
- data/lib/ollama_chat/utils.rb +1 -0
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +7 -0
- data/ollama_chat.gemspec +5 -5
- data/spec/ollama_chat/commands_spec.rb +29 -0
- data/spec/ollama_chat/compaction_spec.rb +381 -0
- data/spec/ollama_chat/config_handling_spec.rb +60 -0
- data/spec/ollama_chat/message_list_spec.rb +229 -0
- data/spec/ollama_chat/message_spec.rb +143 -0
- data/spec/ollama_chat/rag_handling_spec.rb +3 -0
- data/spec/ollama_chat/system_prompt_management_spec.rb +0 -2
- data/spec/ollama_chat/tools/browse_spec.rb +7 -0
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +15 -0
- data/spec/ollama_chat/tools/concern_spec.rb +77 -0
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -0
- data/spec/ollama_chat/tools/delete_file_spec.rb +8 -0
- data/spec/ollama_chat/tools/directory_structure_spec.rb +4 -0
- data/spec/ollama_chat/tools/execute_grep_spec.rb +13 -0
- data/spec/ollama_chat/tools/execute_ri_spec.rb +8 -0
- data/spec/ollama_chat/tools/file_context_spec.rb +5 -0
- data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +2 -0
- data/spec/ollama_chat/tools/generate_image_spec.rb +12 -0
- data/spec/ollama_chat/tools/generate_password_spec.rb +5 -0
- data/spec/ollama_chat/tools/get_cve_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_ghr_spec.rb +6 -0
- data/spec/ollama_chat/tools/get_location_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_time_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_url_spec.rb +7 -0
- data/spec/ollama_chat/tools/lookup_group_spec.rb +111 -0
- data/spec/ollama_chat/tools/move_file_spec.rb +10 -0
- data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +5 -0
- data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/patch_file_spec.rb +62 -0
- data/spec/ollama_chat/tools/read_file_spec.rb +7 -0
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +46 -0
- data/spec/ollama_chat/tools/roll_dice_spec.rb +11 -0
- data/spec/ollama_chat/tools/run_tests_spec.rb +8 -0
- data/spec/ollama_chat/tools/search_knowledge_spec.rb +2 -0
- data/spec/ollama_chat/tools/write_file_spec.rb +96 -0
- data/spec/ollama_chat/utils/analyze_directory_spec.rb +46 -7
- data/spec/ollama_chat/utils/log_rotation_spec.rb +71 -0
- data/spec/spec_helper.rb +2 -1
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5e93e610536ac396d18e929ef76b3dcecac7d3b9d88a35b8d6ff141e7eba8e4
|
|
4
|
+
data.tar.gz: a7792a3b397ebcc0d88552f0fa1417933f44ff00c6e0fad4eb62d567b5c98bfd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95a81969d48e98ff06623f887c33bd0acf94452639fabbf4eca30127ef4d300124f9e12d7bbb97a3d8d86263116d3c772e5d9c403d1e4f4cbdc2df241e1f719f
|
|
7
|
+
data.tar.gz: 92201492f3a4a084c845d11f7957b6af6ea8630e7f242158458cb1bad059bababb8699f0dc2f24204ddfde38bcd47e8491812e03d5f38128f179b443f858a9dc
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-09-01 v0.0.114
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* Context compaction pipeline:
|
|
8
|
+
* `OllamaChat::Compaction` module with `summarize_for_compaction`,
|
|
9
|
+
`compact_with_retry`, `compact_ratio_tokens`, `tool_summary_line`, and
|
|
10
|
+
`Result` value object.
|
|
11
|
+
* `CompactionError` exception.
|
|
12
|
+
* `MessageList` methods: `compact!`, `find_cut_point`, `find_summary`,
|
|
13
|
+
`compacted_messages`, `compacted_estimate_tokens`, and
|
|
14
|
+
`full_estimate_tokens`.
|
|
15
|
+
* `lookup_group` tool for retrieving pre-summary group content by UUID
|
|
16
|
+
suffix.
|
|
17
|
+
* `summary_template` base method in `Tools::Concern` and `message` field in
|
|
18
|
+
`eval_ruby` and `get_rfc` results.
|
|
19
|
+
* `compaction:` configuration block (`enabled`, `reserve`, `keep_recent`) and
|
|
20
|
+
`prompts.compaction` templates in `default_config.yml`.
|
|
21
|
+
* `conversation_length` in `Information` and dynamic runtime values.
|
|
22
|
+
* Per-message token estimation:
|
|
23
|
+
* `#token_estimate(strip_thinking:)` in `OllamaChat::MessageMixin`.
|
|
24
|
+
* `#total_tokens(strip_thinking:)` in `OllamaChat::MessageList`.
|
|
25
|
+
* Color-coded context gauge:
|
|
26
|
+
* `context_gauge` method in `information.rb` displaying context usage with
|
|
27
|
+
ANSI colors and emoji indicators.
|
|
28
|
+
* Configurable syntax checkers for file tools:
|
|
29
|
+
* `syntax_checkers:` block in `default_config.yml` for `ruby`, `javascript`,
|
|
30
|
+
`python`, and `shell`.
|
|
31
|
+
* `ConfigHandling#syntax_checker_for` and `ConfigHandling#run_syntax_check`
|
|
32
|
+
methods.
|
|
33
|
+
* Integration into `Tools::WriteFile#execute` and `Tools::PatchFile#execute`.
|
|
34
|
+
* Log rotation and restructured log configuration:
|
|
35
|
+
* `LOG` module (`CHAT`, `DATABASE`, `TAIL_LINES`) in `oc.rb`.
|
|
36
|
+
* `OllamaChat::Utils::LogRotation.truncate_tail` for in-place log truncation.
|
|
37
|
+
* `truncate_logs` call in `OllamaChat::Chat#initialize`.
|
|
38
|
+
* Database `Logger` level set to `WARN`.
|
|
39
|
+
* Enabled flag for RAG collections:
|
|
40
|
+
* Migration adding `enabled` boolean to `collections` table.
|
|
41
|
+
* Filtering of disabled collections in `information.rb` and
|
|
42
|
+
`search_knowledge`.
|
|
43
|
+
* Interactive toggle in `edit_collection` and `[DISABLED]` suffix in
|
|
44
|
+
`list_collections`.
|
|
45
|
+
* Monochrome flags:
|
|
46
|
+
* `-m` flag for `bin/ollama_chat_send` to strip ANSI codes from stdin.
|
|
47
|
+
* `-m` flag for `/input` command to strip ANSI codes from file content and
|
|
48
|
+
import results.
|
|
49
|
+
* Session synchronization:
|
|
50
|
+
* `session_sync` method extracted from `session_close` in
|
|
51
|
+
`session_management.rb`.
|
|
52
|
+
* `ensure session.lock` in `set_new_session`.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
* Increased `compaction.reserve.ratio` from **0.30** to **0.60** in
|
|
57
|
+
`default_config.yml`.
|
|
58
|
+
* Switched `Struct.new` to `Data.define` in `ModelMetadata`, `Estimate`, and
|
|
59
|
+
`TagResult`.
|
|
60
|
+
* Standardized user-facing output format to `tokens (bytes)` across
|
|
61
|
+
`read_file`, `write_file`, `session_management`, and `information`.
|
|
62
|
+
* Replaced `.size` with `.bytesize` for multi-byte-safe byte counts in
|
|
63
|
+
`token_estimator/crude.rb`, `fetcher.rb`, `paste_from_clipboard.rb`, and
|
|
64
|
+
`prompt_handling.rb`.
|
|
65
|
+
* Updated `/conversation compact` to delegate to `compact_with_retry`.
|
|
66
|
+
* Threaded `chat` into `TagResolver` for logging, fixing `NoMethodError` in
|
|
67
|
+
rescue blocks.
|
|
68
|
+
* Capped inline directory structure depth:
|
|
69
|
+
* Added `parsing.directory_structure_max_depth` (default **1**) to
|
|
70
|
+
`default_config.yml`.
|
|
71
|
+
* Shifted `max_depth` semantics: **1** = immediate children, **2** = children
|
|
72
|
+
+ grandchildren, `nil` = unlimited.
|
|
73
|
+
* Acknowledged prompt state after `/prompt sync` to suppress boot drift nag.
|
|
74
|
+
* Reordered `session_title` prompt for reliability.
|
|
75
|
+
* `/config diff` now calls `reload_config` after `diff_config`.
|
|
76
|
+
* Reordered `/config` subcommand alternatives to `edit diff reload`.
|
|
77
|
+
* Clarified `suffix` description in `directory_structure` as "file extension".
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
* `NoMethodError` in `TagResolver#resolve` rescue block.
|
|
82
|
+
* Unbounded tree expansion in directory structure parsing.
|
|
83
|
+
|
|
84
|
+
## 2026-08-25 v0.0.113
|
|
85
|
+
|
|
86
|
+
* Simplified hash syntax by replacing `text: text` with `text:` etc.
|
|
87
|
+
* Removed the `/conversation compact` stub, including its regexp,
|
|
88
|
+
completion list, help text, handler logic, and associated specs.
|
|
89
|
+
* Added specs for `/config edit`, `/config reload`, and `/config diff` in
|
|
90
|
+
`commands_spec.rb`, verifying the ordered sequence of `diff_config`
|
|
91
|
+
followed by the new `reload_config` for the diff command.
|
|
92
|
+
* Removed the unused `collection_descriptions: {}` entry from the
|
|
93
|
+
`embedding:` section in `default_config.yml`.
|
|
94
|
+
|
|
3
95
|
## 2026-08-25 v0.0.112
|
|
4
96
|
|
|
5
97
|
* Added a `diff` subcommand to the `/config` command, including updated
|
data/README.md
CHANGED
|
@@ -37,6 +37,9 @@ The following environment variables can be used to configure behavior:
|
|
|
37
37
|
- `OLLAMA_CHAT_HISTORY` - Chat history filename
|
|
38
38
|
- `OLLAMA_CHAT_USER` - Full name of the chat user
|
|
39
39
|
- `OLLAMA_CHAT_DEBUG` - Debug mode toggle (1 = enabled)
|
|
40
|
+
- `OLLAMA_CHAT_LOG_CHAT` - Chat log file path (default: `$XDG_STATE_HOME/chat.log`)
|
|
41
|
+
- `OLLAMA_CHAT_LOG_DATABASE` - Database log file path (default: `$XDG_STATE_HOME/database.log`)
|
|
42
|
+
- `OLLAMA_CHAT_LOG_TAIL_LINES` - Max lines to retain in log files (default: `10000`)
|
|
40
43
|
|
|
41
44
|
#### Tool Configuration
|
|
42
45
|
- `OLLAMA_CHAT_TOOLS_TEST_RUNNER` - Configured test runner for `run_tests` (default: `rspec`)
|
|
@@ -45,11 +48,14 @@ The following environment variables can be used to configure behavior:
|
|
|
45
48
|
- `OLLAMA_CHAT_TOOLS_JIRA_URL` - Base URL for Jira instance
|
|
46
49
|
- `OLLAMA_CHAT_TOOLS_JIRA_USER` - Username for Jira authentication
|
|
47
50
|
- `OLLAMA_CHAT_TOOLS_JIRA_API_TOKEN` - API token for Jira authentication
|
|
51
|
+
- `OLLAMA_CHAT_TOOLS_JIRA_TWG` - Path to the twg CLI binary
|
|
48
52
|
- `OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_URL` - Base URL for ComfyUI server
|
|
49
53
|
- `OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_WORKFLOW` - ComfyUI workflow as JSON string
|
|
50
54
|
- `OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_PROMPT_NODE_ID` - Prompt node ID
|
|
51
55
|
- `OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_FILENAME_PREFIX_NODE_ID` - Filename prefix node ID
|
|
52
56
|
- `OLLAMA_CHAT_TOOLS_PIRATEWEATHER_API_KEY` - Pirate Weather API key
|
|
57
|
+
- `OLLAMA_CHAT_TOOLS_GHR_URL` - Base URL for GHR API server
|
|
58
|
+
- `OLLAMA_CHAT_TOOLS_RUBY_EVAL_IMAGE_TEMPLATE` - Docker image template for `eval_ruby` (default: `ruby:%{version}-alpine`)
|
|
53
59
|
|
|
54
60
|
#### System & Infrastructure
|
|
55
61
|
- `XDG_CONFIG_HOME` - XDG Configuration home directory
|
|
@@ -59,6 +65,7 @@ The following environment variables can be used to configure behavior:
|
|
|
59
65
|
- `EDITOR` - Default text editor
|
|
60
66
|
- `BROWSER` - Default web browser
|
|
61
67
|
- `DIFF_TOOL` - Tool for diff operations (default: `vimdiff`)
|
|
68
|
+
- `DIFF_COMMAND` - Command to generate unified diffs (default: `diff -u --color=always`)
|
|
62
69
|
- `KRAMDOWN_ANSI_OLLAMA_CHAT_STYLES` - Custom ANSI styles for Markdown formatting
|
|
63
70
|
- `KRAMDOWN_ANSI_STYLES` - Fallback ANSI styles configuration
|
|
64
71
|
- `OLLAMA_REDIS_URL` - Redis connection URL for documents (documentrix)
|
data/bin/ollama_chat_log
CHANGED
data/bin/ollama_chat_send
CHANGED
|
@@ -5,7 +5,7 @@ include OllamaChat::FileEditing
|
|
|
5
5
|
require 'tins/go'
|
|
6
6
|
include Tins::GO
|
|
7
7
|
|
|
8
|
-
opts = go 'f:d:F:
|
|
8
|
+
opts = go 'f:d:F:rtemph', ARGV
|
|
9
9
|
|
|
10
10
|
# Displays the usage information for the ollama_chat_send command.
|
|
11
11
|
#
|
|
@@ -25,6 +25,7 @@ def usage(rc = 0)
|
|
|
25
25
|
-f CONFIG file to read
|
|
26
26
|
-d DIR the working directory to derive the socket file from
|
|
27
27
|
-e Edit content in editor before sending
|
|
28
|
+
-m Strip ANSI color codes from content before sending
|
|
28
29
|
-F FORMAT file extension for editor (e.g., rb, md)
|
|
29
30
|
-h Show this help message
|
|
30
31
|
|
|
@@ -42,6 +43,7 @@ begin
|
|
|
42
43
|
opts[?r] ? :socket_input_with_response : :socket_input
|
|
43
44
|
end
|
|
44
45
|
content = STDIN.read
|
|
46
|
+
content = Term::ANSIColor.uncolor(content) if opts[?m]
|
|
45
47
|
if opts[?e]
|
|
46
48
|
basename = %w[ text ]
|
|
47
49
|
basename << opts[?F]&.sub(/\A(?=[^.])/, ?.) || '.md'
|
data/lib/ollama_chat/chat.rb
CHANGED
|
@@ -69,6 +69,7 @@ class OllamaChat::Chat
|
|
|
69
69
|
include OllamaChat::PromptHandling
|
|
70
70
|
include OllamaChat::SystemPromptManagement
|
|
71
71
|
include OllamaChat::PromptManagement
|
|
72
|
+
include OllamaChat::Compaction
|
|
72
73
|
include OllamaChat::Utils::Chooser
|
|
73
74
|
include OllamaChat::Utils::ValueFormatter
|
|
74
75
|
include OllamaChat::Utils::UTF8Converter
|
|
@@ -106,10 +107,11 @@ class OllamaChat::Chat
|
|
|
106
107
|
@opts[?V] and exit version
|
|
107
108
|
@ollama_chat_config = OllamaChat::OllamaChatConfig.new(@opts[?f])
|
|
108
109
|
self.config = @ollama_chat_config.config
|
|
110
|
+
truncate_logs
|
|
109
111
|
@messages = OllamaChat::MessageList.new(self)
|
|
110
112
|
OllamaChat::Database.setup_models.each { _1.ask_and_send(:seed, self) }
|
|
111
|
-
setup_session
|
|
112
113
|
setup_switches
|
|
114
|
+
setup_session
|
|
113
115
|
setup_state_selectors(config)
|
|
114
116
|
connect_ollama
|
|
115
117
|
@documents = setup_documents
|
|
@@ -254,12 +256,14 @@ class OllamaChat::Chat
|
|
|
254
256
|
# and a user message, executing it as a one-shot chat interaction.
|
|
255
257
|
#
|
|
256
258
|
# @param system [String] the system prompt to guide the model's behavior
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
+
# (defaults to current raw_system_prompt)
|
|
259
260
|
# @param prompt [String] the user prompt to send to the model
|
|
261
|
+
# @param stream [Boolean] whether to stream the response (default: false)
|
|
262
|
+
# @param think [Boolean] enable thinking mode for hybrid-thinking models
|
|
263
|
+
# (default: false)
|
|
260
264
|
#
|
|
261
265
|
# @return [String] the content of the resulting response message
|
|
262
|
-
def generate(system: raw_system_prompt, prompt:)
|
|
266
|
+
def generate(system: raw_system_prompt, prompt:, stream: false, think: false)
|
|
263
267
|
messages = [
|
|
264
268
|
OllamaChat::Message.new(
|
|
265
269
|
role: 'system',
|
|
@@ -276,9 +280,8 @@ class OllamaChat::Chat
|
|
|
276
280
|
model: @model,
|
|
277
281
|
messages: ,
|
|
278
282
|
options: model_options,
|
|
279
|
-
stream:
|
|
280
|
-
think:
|
|
281
|
-
tools:
|
|
283
|
+
stream: ,
|
|
284
|
+
think:
|
|
282
285
|
)&.message&.content.to_s
|
|
283
286
|
|
|
284
287
|
if content.empty?
|
|
@@ -315,6 +318,18 @@ class OllamaChat::Chat
|
|
|
315
318
|
ollama.chat(**opts, &block)
|
|
316
319
|
end
|
|
317
320
|
|
|
321
|
+
# Truncates chat.log and database.log to the configured tail line count
|
|
322
|
+
# before any new logging begins for this session.
|
|
323
|
+
def truncate_logs
|
|
324
|
+
keep = OC::OLLAMA::CHAT::LOG::TAIL_LINES
|
|
325
|
+
OllamaChat::Utils::LogRotation.truncate_tail(
|
|
326
|
+
OC::OLLAMA::CHAT::LOG::CHAT, keep_lines: keep
|
|
327
|
+
)
|
|
328
|
+
OllamaChat::Utils::LogRotation.truncate_tail(
|
|
329
|
+
OC::OLLAMA::CHAT::LOG::DATABASE, keep_lines: keep
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
318
333
|
# @return [Module] The module containing the database models.
|
|
319
334
|
def models
|
|
320
335
|
OllamaChat::Database::Models
|
|
@@ -532,14 +547,15 @@ class OllamaChat::Chat
|
|
|
532
547
|
)
|
|
533
548
|
begin
|
|
534
549
|
retried = false
|
|
535
|
-
sent_messages = messages.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
550
|
+
sent_messages = messages.compacted_messages
|
|
551
|
+
sent_messages = sent_messages.map {
|
|
552
|
+
_1.dup.tap { |message|
|
|
553
|
+
message.sender_name = nil
|
|
554
|
+
message.group_uuid = nil
|
|
555
|
+
message.tool_calls = nil
|
|
556
|
+
message.thinking = nil if think_strip.on?
|
|
541
557
|
}
|
|
542
|
-
|
|
558
|
+
}
|
|
543
559
|
prepare_model(@model)
|
|
544
560
|
call_ollama_chat(
|
|
545
561
|
model: @model,
|
|
@@ -114,7 +114,7 @@ module OllamaChat::Clipboard
|
|
|
114
114
|
# @return [TrueClass] if the copying has been performed successfully.
|
|
115
115
|
def copy_to_clipboard(edit: false)
|
|
116
116
|
text = last_message_content(content: false)
|
|
117
|
-
perform_copy_to_clipboard(text
|
|
117
|
+
perform_copy_to_clipboard(text:, edit:)
|
|
118
118
|
log(:info, "Copied to clipboard", data: { bytes: format_bytes(text.bytesize) }) if text
|
|
119
119
|
STDOUT.puts "The last response has been successfully copied to the system clipboard."
|
|
120
120
|
true
|
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -62,8 +62,8 @@ module OllamaChat::Commands
|
|
|
62
62
|
|
|
63
63
|
command(
|
|
64
64
|
name: :config,
|
|
65
|
-
regexp: %r(^/config(?:\s+(edit|reload
|
|
66
|
-
complete: [ 'config', %w[ edit reload
|
|
65
|
+
regexp: %r(^/config(?:\s+(edit|diff|reload))?$),
|
|
66
|
+
complete: [ 'config', %w[ edit diff reload ] ],
|
|
67
67
|
optional: true,
|
|
68
68
|
help: <<~EOT
|
|
69
69
|
\u2699\uFE0F View, edit, diff, or reload configuration
|
|
@@ -72,10 +72,11 @@ module OllamaChat::Commands
|
|
|
72
72
|
case subcommand
|
|
73
73
|
when 'edit'
|
|
74
74
|
edit_config
|
|
75
|
-
when 'reload'
|
|
76
|
-
reload_config
|
|
77
75
|
when 'diff'
|
|
78
76
|
diff_config
|
|
77
|
+
reload_config
|
|
78
|
+
when 'reload'
|
|
79
|
+
reload_config
|
|
79
80
|
else
|
|
80
81
|
display_config
|
|
81
82
|
end
|
|
@@ -637,9 +638,7 @@ module OllamaChat::Commands
|
|
|
637
638
|
yes: /\Ay/i
|
|
638
639
|
)
|
|
639
640
|
then
|
|
640
|
-
|
|
641
|
-
session_sync
|
|
642
|
-
STDOUT.puts "Conversation compacted."
|
|
641
|
+
compact_with_retry
|
|
643
642
|
else
|
|
644
643
|
STDOUT.puts 'Cancelled.'
|
|
645
644
|
end
|
|
@@ -848,22 +847,23 @@ module OllamaChat::Commands
|
|
|
848
847
|
|
|
849
848
|
command(
|
|
850
849
|
name: :input,
|
|
851
|
-
regexp: %r(^/input(?:\s+(path|context|embedding|summary)(?:\s*(?=\z))?)?((?:\s+-(?:[
|
|
850
|
+
regexp: %r(^/input(?:\s+(path|context|embedding|summary)(?:\s*(?=\z))?)?((?:\s+-(?:[aprem]|c\s*#{OllamaChat::COLLECTION_NAME_REGEXP.source}|w\s*\d+|t\s*[-\w\.]+(?:,[-\w\.]+)*))*)(?:\s+([^-].*))?$),
|
|
852
851
|
optional: true,
|
|
853
852
|
complete: [ 'input', %w[ path context embedding summary ] ],
|
|
854
853
|
options: <<~EOT,
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
854
|
+
[
|
|
855
|
+
-w|-a|-p|-e|-m|
|
|
856
|
+
-c <collection>|
|
|
857
|
+
-t <tags>
|
|
858
|
+
]
|
|
860
859
|
[arg…]
|
|
861
860
|
EOT
|
|
862
861
|
help: <<~EOT
|
|
863
862
|
📥 Import content (read, summarize, embed, context)
|
|
864
863
|
Subcommands: path, context, embedding, summary
|
|
865
|
-
|
|
866
|
-
|
|
864
|
+
Options: -p (pattern), -w [words], -a (all),
|
|
865
|
+
-c [collection], -t [tags], -e (edit),
|
|
866
|
+
-m (monochrome, strip ANSI from file content)
|
|
867
867
|
EOT
|
|
868
868
|
) do |input_mode,opts,arg|
|
|
869
869
|
disable_content_parsing
|
|
@@ -919,19 +919,21 @@ module OllamaChat::Commands
|
|
|
919
919
|
end
|
|
920
920
|
end
|
|
921
921
|
when 'path'
|
|
922
|
-
opts = go_command('
|
|
922
|
+
opts = go_command('paem', opts)
|
|
923
923
|
if opts[?p]
|
|
924
924
|
all = opts.fetch(?a, false)
|
|
925
925
|
patterns = extract_patterns(arg)
|
|
926
926
|
read = -> pathname {
|
|
927
927
|
STDOUT.puts "Reading #{pathname.to_s.inspect}."
|
|
928
|
-
pathname.read
|
|
928
|
+
content = pathname.read
|
|
929
|
+
opts[?m] ? Term::ANSIColor.uncolor(content) : content
|
|
929
930
|
}
|
|
930
931
|
next provide_file_set_content(patterns, all:, &read) || :next
|
|
931
932
|
elsif arg
|
|
932
933
|
filename = Pathname.new(arg).expand_path
|
|
933
934
|
filename.file? or next :next
|
|
934
935
|
content = filename.read
|
|
936
|
+
content = Term::ANSIColor.uncolor(content) if opts[?m]
|
|
935
937
|
content = edit_text(content) if opts[?e]
|
|
936
938
|
content
|
|
937
939
|
else
|
|
@@ -939,14 +941,18 @@ module OllamaChat::Commands
|
|
|
939
941
|
next :next
|
|
940
942
|
end
|
|
941
943
|
else
|
|
942
|
-
opts = go_command('
|
|
944
|
+
opts = go_command('paem', opts)
|
|
943
945
|
if opts[?p]
|
|
944
946
|
all = opts.fetch(?a, false)
|
|
945
947
|
patterns = extract_patterns(arg)
|
|
946
|
-
next provide_file_set_content(patterns, all:, skip_blank: true)
|
|
948
|
+
next provide_file_set_content(patterns, all:, skip_blank: true) do |src|
|
|
949
|
+
content = import(src)
|
|
950
|
+
opts[?m] ? Term::ANSIColor.uncolor(content) : content
|
|
951
|
+
end || :next
|
|
947
952
|
elsif arg
|
|
948
953
|
source = arg
|
|
949
954
|
content = import(source) or next :next
|
|
955
|
+
content = Term::ANSIColor.uncolor(content) if opts[?m]
|
|
950
956
|
content = edit_text(content) if opts[?e]
|
|
951
957
|
content
|
|
952
958
|
else
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Provides compaction support for chat sessions. Mixed into `Chat`.
|
|
2
|
+
#
|
|
3
|
+
# Supplies configuration resolvers that derive concrete token budgets
|
|
4
|
+
# from the `compaction:` config block (ratios of `num_ctx` with absolute
|
|
5
|
+
# floors), the LLM-based summarization pipeline
|
|
6
|
+
# (+summarize_for_compaction+), and the +tool_summary_line+ resolver
|
|
7
|
+
# for per-tool summary templates.
|
|
8
|
+
#
|
|
9
|
+
# @see OllamaChat::OllamaChatConfig::DefaultConfig for the `compaction:`
|
|
10
|
+
# block structure (`reserve`, `keep_recent`, `summary`).
|
|
11
|
+
module OllamaChat::Compaction
|
|
12
|
+
class << self
|
|
13
|
+
# Generates a one-line summary for a tool call result.
|
|
14
|
+
#
|
|
15
|
+
# Looks up the registered tool class and delegates to its
|
|
16
|
+
# `summary_template(result:)` class method. Falls back to a generic
|
|
17
|
+
# sentence if the tool is not registered or the template raises.
|
|
18
|
+
#
|
|
19
|
+
# @param tool_name [String] the registered name, e.g. `'read_file'`.
|
|
20
|
+
# @param result [String] the raw JSON result string from `execute`.
|
|
21
|
+
# @return [String] a short natural-language description of the call.
|
|
22
|
+
def tool_summary_line(tool_name, result)
|
|
23
|
+
default_summary = "was called."
|
|
24
|
+
klass = OllamaChat::Tools.registered[tool_name.to_s]&.class
|
|
25
|
+
klass&.summary_template(result:) || default_summary
|
|
26
|
+
rescue StandardError
|
|
27
|
+
default_summary
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Value object carrying the outcome of a successful +compact!+ call.
|
|
32
|
+
#
|
|
33
|
+
# @!attribute [r] context_before
|
|
34
|
+
# @return [String] formatted context usage before compaction
|
|
35
|
+
# @!attribute [r] context_after
|
|
36
|
+
# @return [String] formatted context usage after compaction
|
|
37
|
+
# @!attribute [r] candidates
|
|
38
|
+
# @return [Integer] number of messages that were summarized
|
|
39
|
+
# @!attribute [r] candidate_size
|
|
40
|
+
# @return [String] formatted size of the candidate block
|
|
41
|
+
# @!attribute [r] summary_size
|
|
42
|
+
# @return [String] formatted size of the generated summary
|
|
43
|
+
# @!attribute [r] stored_total
|
|
44
|
+
# @return [String] formatted full conversation length
|
|
45
|
+
Result = Data.define(
|
|
46
|
+
:context_before, :context_after,
|
|
47
|
+
:candidates, :candidate_size,
|
|
48
|
+
:summary_size, :stored_total
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Generates a context summary for the given messages via LLM.
|
|
52
|
+
#
|
|
53
|
+
# Serializes non-system messages into a structured prompt, calls the
|
|
54
|
+
# model via +generate+, and assembles the final summary with
|
|
55
|
+
# deterministic tool-call entries stored out-of-band.
|
|
56
|
+
#
|
|
57
|
+
# @param messages [Array<OllamaChat::Message>] the messages to summarize.
|
|
58
|
+
# @param previous_summary [OllamaChat::Message, nil] the previous summary
|
|
59
|
+
# message for iterative re-compaction.
|
|
60
|
+
# @return [Array(String, Array<Hash>)] the assembled content and the
|
|
61
|
+
# merged tool-call entries array.
|
|
62
|
+
def summarize_for_compaction(messages:, previous_summary: nil)
|
|
63
|
+
groups = serialize_groups(messages)
|
|
64
|
+
tool_entries = build_tool_entries(messages)
|
|
65
|
+
|
|
66
|
+
es = OllamaChat::TokenEstimator.estimate(groups)
|
|
67
|
+
log(:info, "Compaction: #{messages.size} messages, " \
|
|
68
|
+
"#{es.tokens_formatted} (#{es.bytes_formatted}) groups")
|
|
69
|
+
|
|
70
|
+
narrative_prev = ''
|
|
71
|
+
old_tool_calls = []
|
|
72
|
+
if previous_summary
|
|
73
|
+
narrative_prev = extract_narrative(previous_summary.content)
|
|
74
|
+
old_tool_calls = extract_old_tool_calls(previous_summary)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
narrative = call_summarizer(groups:, previous_summary: narrative_prev)
|
|
78
|
+
assemble_summary(narrative, tool_entries, old_tool_calls)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Resolves the effective token budget for a concern given a context size.
|
|
82
|
+
#
|
|
83
|
+
# Computes `max(ratio * tokens, min_tokens).floor`, so the floor acts as
|
|
84
|
+
# a guaranteed minimum even for very small context windows.
|
|
85
|
+
#
|
|
86
|
+
# @param name [Symbol] the concern name, e.g. `:reserve`, `:keep_recent`,
|
|
87
|
+
# or `:summary`.
|
|
88
|
+
# @param tokens [Integer] the context window size (`num_ctx`) to derive
|
|
89
|
+
# the budget from.
|
|
90
|
+
# @return [Integer] the resolved token budget.
|
|
91
|
+
def compact_ratio_tokens(name, tokens)
|
|
92
|
+
[
|
|
93
|
+
compact_ratio(name).to_f * tokens,
|
|
94
|
+
compact_min_tokens(name).to_f,
|
|
95
|
+
].max.floor
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Attempts compaction, prompting the user to retry on failure.
|
|
99
|
+
#
|
|
100
|
+
# +compact!+ is idempotent on failure: the LLM call (and thus any
|
|
101
|
+
# +CompactionError+) happens before the message list is mutated, so a
|
|
102
|
+
# retry always starts from a clean state.
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true if compaction succeeded, false if the user
|
|
105
|
+
# declined to retry.
|
|
106
|
+
def compact_with_retry
|
|
107
|
+
result = messages.compact!
|
|
108
|
+
session_sync
|
|
109
|
+
if result
|
|
110
|
+
report_compaction(result)
|
|
111
|
+
else
|
|
112
|
+
STDOUT.puts('Nothing to compact.')
|
|
113
|
+
end
|
|
114
|
+
return true
|
|
115
|
+
rescue OllamaChat::CompactionError => e
|
|
116
|
+
STDERR.puts "⚠️ Compaction failed: #{e.message}"
|
|
117
|
+
log(:error, "Compaction failed", data: {
|
|
118
|
+
model: @model,
|
|
119
|
+
ctx: current_context_length,
|
|
120
|
+
size: messages.size,
|
|
121
|
+
})
|
|
122
|
+
retry if confirm?(
|
|
123
|
+
prompt: '🔔 Retry compaction? (y/n) ',
|
|
124
|
+
yes: /\Ay/i
|
|
125
|
+
)
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# Serializes non-system messages into a readable block for the prompt.
|
|
132
|
+
#
|
|
133
|
+
# @param messages [Array<OllamaChat::Message>] the messages to serialize.
|
|
134
|
+
# @return [String] a formatted block of message lines.
|
|
135
|
+
def serialize_groups(messages)
|
|
136
|
+
messages
|
|
137
|
+
.reject { _1.role == 'system' }
|
|
138
|
+
.map { |m| format_summary_line(m) }
|
|
139
|
+
.join("\n")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Formats a single message as a summary line.
|
|
143
|
+
#
|
|
144
|
+
# @param msg [OllamaChat::Message] the message to format.
|
|
145
|
+
# @return [String] a single line describing the message.
|
|
146
|
+
def format_summary_line(msg)
|
|
147
|
+
tool_name = msg.tool_name.full?
|
|
148
|
+
parts = +"[#{msg.role}]"
|
|
149
|
+
parts << " (#{tool_name})" if tool_name
|
|
150
|
+
parts << " (g:#{msg.group_uuid.to_s[-8..]})" if msg.group_uuid
|
|
151
|
+
if tool_name
|
|
152
|
+
line = OllamaChat::Compaction.tool_summary_line(tool_name, msg.content.to_s)
|
|
153
|
+
parts << " #{line}"
|
|
154
|
+
elsif msg.content.present?
|
|
155
|
+
parts << " #{msg.content.to_s.strip}"
|
|
156
|
+
end
|
|
157
|
+
parts
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Builds the deterministic tool-call entries from tool result messages.
|
|
161
|
+
#
|
|
162
|
+
# @param messages [Array<OllamaChat::Message>] the messages to scan.
|
|
163
|
+
# @return [Array<Hash>] tool-call entry hashes, empty if no tool results.
|
|
164
|
+
def build_tool_entries(messages)
|
|
165
|
+
messages.filter_map do |msg|
|
|
166
|
+
next unless tool_name = msg.tool_name.full?
|
|
167
|
+
next if tool_name == 'runtime_information'
|
|
168
|
+
uuid = msg.group_uuid.to_s[-8..]
|
|
169
|
+
summary = OllamaChat::Compaction.tool_summary_line(
|
|
170
|
+
tool_name, msg.content.to_s
|
|
171
|
+
)
|
|
172
|
+
time = msg.group_time&.strftime('%Y-%m-%d %H:%M')
|
|
173
|
+
{
|
|
174
|
+
'type' => 'tool',
|
|
175
|
+
'name' => tool_name,
|
|
176
|
+
'summary' => summary,
|
|
177
|
+
'uuid' => uuid,
|
|
178
|
+
'time' => time,
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Calls the LLM to generate the narrative portion of the summary.
|
|
184
|
+
#
|
|
185
|
+
# @param groups [String] the serialized groups block.
|
|
186
|
+
# @param previous_summary [String, nil] the prior summary for iteration.
|
|
187
|
+
# @return [String] the LLM-generated narrative text.
|
|
188
|
+
def call_summarizer(groups:, previous_summary:)
|
|
189
|
+
previous = previous_summary.to_s
|
|
190
|
+
|
|
191
|
+
prompt = prompt(:summarize, context: 'compaction').to_s % {
|
|
192
|
+
previous:, groups:
|
|
193
|
+
}
|
|
194
|
+
system = prompt(:system, context: 'compaction').to_s
|
|
195
|
+
|
|
196
|
+
es = OllamaChat::TokenEstimator.estimate(prompt)
|
|
197
|
+
log(:info, "Compaction: sending prompt " \
|
|
198
|
+
"#{es.tokens_formatted} (#{es.bytes_formatted}) to #{@model}")
|
|
199
|
+
|
|
200
|
+
response = Infobar.busy(
|
|
201
|
+
label: 'Compacting context…',
|
|
202
|
+
frames: :braille7,
|
|
203
|
+
output: STDOUT,
|
|
204
|
+
) do
|
|
205
|
+
generate(system:, prompt:, think: true).strip
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if response.empty?
|
|
209
|
+
log(:error, 'Compaction: LLM returned empty response')
|
|
210
|
+
raise OllamaChat::CompactionError,
|
|
211
|
+
'Summarization failed: model returned empty response'
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
res_es = OllamaChat::TokenEstimator.estimate(response)
|
|
215
|
+
log(:info, "Compaction: got response " \
|
|
216
|
+
"#{res_es.tokens_formatted} (#{res_es.bytes_formatted})")
|
|
217
|
+
|
|
218
|
+
response
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Assembles the final summary content and merged tool entries.
|
|
222
|
+
#
|
|
223
|
+
# @param narrative [String] the LLM-generated narrative.
|
|
224
|
+
# @param tool_entries [Array<Hash>] new tool-call entries from this pass.
|
|
225
|
+
# @param old_tool_entries [Array<Hash>] entries carried over from a
|
|
226
|
+
# prior compaction.
|
|
227
|
+
# @return [Array(String, Array<Hash>)] the content string and the
|
|
228
|
+
# merged entries array.
|
|
229
|
+
def assemble_summary(narrative, tool_entries, old_tool_entries = [])
|
|
230
|
+
all_tools = old_tool_entries + tool_entries
|
|
231
|
+
tool_section = all_tools.map(&:to_json) * ?\n
|
|
232
|
+
|
|
233
|
+
template = prompt(:assemble, context: 'compaction').to_s
|
|
234
|
+
content = template % { narrative:, tool_section: }
|
|
235
|
+
|
|
236
|
+
[content, all_tools]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Extracts the narrative portion from a summary message's content.
|
|
240
|
+
#
|
|
241
|
+
# @param content [String, nil] the summary content.
|
|
242
|
+
# @return [String] the narrative text.
|
|
243
|
+
def extract_narrative(content)
|
|
244
|
+
content.to_s.sub(/\ntool_calls:\n.*\z/m, '').strip
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Returns the tool-call entries from a previous summary message.
|
|
248
|
+
#
|
|
249
|
+
# @param summary_msg [OllamaChat::Message] the previous summary message.
|
|
250
|
+
# @return [Array<Hash>] the tool-call entries, empty if none.
|
|
251
|
+
def extract_old_tool_calls(summary_msg)
|
|
252
|
+
summary_msg.tool_calls || []
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Returns the absolute minimum token floor for a given concern.
|
|
256
|
+
#
|
|
257
|
+
# @param name [Symbol] the concern name, e.g. `:reserve`, `:keep_recent`,
|
|
258
|
+
# or `:summary`.
|
|
259
|
+
# @return [Integer] the configured `min_tokens` floor.
|
|
260
|
+
def compact_min_tokens(name)
|
|
261
|
+
config.compaction.attribute_get!(name).min_tokens
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Returns the fractional ratio (portion of `num_ctx`) for a given concern.
|
|
265
|
+
#
|
|
266
|
+
# @param name [Symbol] the concern name, e.g. `:reserve`, `:keep_recent`,
|
|
267
|
+
# or `:summary`.
|
|
268
|
+
# @return [Float] the configured `ratio` value.
|
|
269
|
+
def compact_ratio(name)
|
|
270
|
+
config.compaction.attribute_get!(name).ratio
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Displays a compact compaction report to +STDOUT+ after the
|
|
274
|
+
# +Infobar.busy+ spinner has finished.
|
|
275
|
+
#
|
|
276
|
+
# Replaces the former bare "Conversation compacted." line with
|
|
277
|
+
# before/after context metrics, candidate volume, and summary size.
|
|
278
|
+
#
|
|
279
|
+
# @param result [Result] the compaction result from +compact!+.
|
|
280
|
+
def report_compaction(result)
|
|
281
|
+
STDOUT.puts(<<~EOT)
|
|
282
|
+
✅ Conversation compacted.
|
|
283
|
+
Summarized: #{result.candidates} messages (#{result.candidate_size})
|
|
284
|
+
Summary: #{result.summary_size}
|
|
285
|
+
Context: #{result.context_before} → #{result.context_after}
|
|
286
|
+
Stored total: #{result.stored_total}
|
|
287
|
+
EOT
|
|
288
|
+
end
|
|
289
|
+
end
|