openclacky 1.0.0.beta.1 → 1.0.0.beta.3

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: b0938f51d788566d1c708a0054a3b45565f0f3b2ea7cd68f9e18897247c745cd
4
- data.tar.gz: 96bef1e4b6333fcee57e26fb330f12d7d5b9f79f470a34f35483ab5f51571e64
3
+ metadata.gz: b145934170a510f46e3263c3fbce94acf618cdd416c93e17a7758984361c02b7
4
+ data.tar.gz: 618fdf4917ce68514e0332ad44c522afa061a21884d6e95511f564da985f8433
5
5
  SHA512:
6
- metadata.gz: 59ba5927fef6187d4d862f210282190b589838e4950b0a5bf0e37731e6cd772bba0d4bfd483d6ff14f979eabf7a4dc09abacb2c60f98df7762af6b4191dc67cf
7
- data.tar.gz: 52063a91b64281d4dcf3e6bd8e3f4d87cc63cfed70169c87cfacf55e4f6c1cb16f2d938a9f554f564829e879755573d3eb8fb3988260285d11087dfa0058473d
6
+ metadata.gz: 50a63fc4087f97c9a3c3242e2e379da95de6e73cdac3bb75ab11ad67bc03eb151afaf47e3a229bd769a4790f57e3b116309c0908807f35618ae64222feb30575
7
+ data.tar.gz: f492569e101a0b6af312c65cffa244b29a5c47d79201129214e66ac62db7181c29678d64c9ddec88e6dc61525cf2442b0f22647e8dcdb430e0bbc87ca9f1a370
@@ -3,7 +3,9 @@
3
3
  name: gem-release
4
4
  description: >-
5
5
  Automates the complete process of releasing a new version of the openclacky Ruby
6
- gem
6
+ gem. Supports both stable releases (auto-increment) and pre-release versions
7
+ (user-specified, e.g., 1.0.0.beta.1). Handles version bumping, testing, building,
8
+ RubyGems publishing, GitHub Releases, and OSS CDN mirroring.
7
9
  disable-model-invocation: false
8
10
  user-invocable: true
9
11
  ---
@@ -21,6 +23,7 @@ This skill handles the entire gem release workflow from version bumping to publi
21
23
  To use this skill, simply say:
22
24
  - "Release a new version"
23
25
  - "Publish a new gem version"
26
+ - "Release version 1.0.0.beta.1" (pre-release with explicit version)
24
27
  - Use the command: `/gem-release`
25
28
 
26
29
  ## Process Steps
@@ -31,10 +34,30 @@ To use this skill, simply say:
31
34
  - Ensure the repository is in a clean state
32
35
 
33
36
  ### 2. Version Management
37
+
38
+ **Stable releases (default):**
34
39
  - Read current version from `lib/clacky/version.rb`
35
40
  - Increment version number (typically patch version: x.y.z → x.y.z+1)
36
41
  - Update the VERSION constant in the version file
37
42
 
43
+ **Pre-release versions (when user specifies a version like `1.0.0.beta.1`):**
44
+ - Accept the user-provided version string directly — do NOT auto-increment
45
+ - The version must follow semver pre-release format: `X.Y.Z-<identifier>` or `X.Y.Z.<identifier>` (e.g., `1.0.0.beta.1`, `2.0.0-alpha`, `1.5.0-rc1`)
46
+ - Before proceeding, warn the user about pre-release caveats (see Pre-Release Caveats below)
47
+
48
+ ### 2a. Pre-Release Caveats
49
+
50
+ When releasing a pre-release version, inform the user of these known behaviors in the Clacky ecosystem:
51
+
52
+ | Concern | Behavior | Impact |
53
+ |---------|----------|--------|
54
+ | **Version check notification** | RubyGems API returns the highest version number, including prereleases. `Gem::Version("0.9.38") < Gem::Version("1.0.0.beta.1")` → `true`. | ✅ The upgrade dot WILL appear in the Web UI for most users. |
55
+ | **`gem update` (official source)** | `gem update openclacky --no-document` does NOT install prereleases without `--pre`. | ❌ Users on official RubyGems source who click "Upgrade" will see the notification but the upgrade will silently do nothing. |
56
+ | **OSS CDN upgrade (mirror users)** | `upgrade_via_oss_cdn` downloads the exact `.gem` from `latest.txt` on OSS. | ⚠️ If you update `latest.txt` to point to the prerelease, mirror users WILL get the beta. |
57
+ | **OSS `latest.txt`** | Stable users fetching `latest.txt` for fresh installs would get the beta. | ⚠️ By default, do NOT update `latest.txt` for pre-releases. Only update if this is intentional (e.g., a release candidate for broad testing). |
58
+
59
+ **Action**: Ask the user whether to update `latest.txt` on OSS before proceeding. For internal testing, the answer is usually "no".
60
+
38
61
  ### 3. Quality Assurance
39
62
  - Run the full test suite with `bundle exec rspec`
40
63
  - Ensure all 167+ tests pass
@@ -93,15 +116,26 @@ To use this skill, simply say:
93
116
 
94
117
  4. **Create GitHub Release and Upload gem**
95
118
 
96
- Extract the release notes for this version from CHANGELOG.md, then create a GitHub Release with the .gem file attached:
119
+ Extract the release notes for this version from CHANGELOG.md, then create a GitHub Release with the .gem file attached.
120
+
121
+ **For stable releases:**
97
122
  ```bash
98
123
  gh release create v{version} \
99
124
  --title "v{version}" \
100
- --notes-file /tmp/release_notes.md \
125
+ --notes-file /tmp/release_notes_{version}.md \
101
126
  --latest \
102
127
  openclacky-{version}.gem
103
128
  ```
104
129
 
130
+ **For pre-release versions (e.g., `1.0.0.beta.1`):** use `--prerelease` instead of `--latest`:
131
+ ```bash
132
+ gh release create v{version} \
133
+ --title "v{version}" \
134
+ --notes-file /tmp/release_notes_{version}.md \
135
+ --prerelease \
136
+ openclacky-{version}.gem
137
+ ```
138
+
105
139
  Steps:
106
140
  - Parse the CHANGELOG.md section for `[{version}]`
107
141
  - Write it to a temp file (e.g., `/tmp/release_notes_{version}.md`) to avoid shell escaping issues
@@ -112,22 +146,28 @@ To use this skill, simply say:
112
146
 
113
147
  5. **Sync to Tencent Cloud OSS (CN mirror)**
114
148
 
115
- After GitHub Release is created, upload the .gem file and update `latest.txt` on OSS so Chinese users can install without hitting GitHub directly:
149
+ After GitHub Release is created, upload the .gem file to OSS so Chinese users can install without hitting GitHub directly.
116
150
 
117
151
  ```bash
118
- # Upload .gem file
152
+ # Upload .gem file (always do this for any release)
119
153
  coscli cp openclacky-{version}.gem cos://clackyai-1258723534/openclacky/openclacky-{version}.gem
154
+ ```
120
155
 
121
- # Update latest.txt
156
+ **For stable releases only** — update `latest.txt` so fresh installs and mirror users pick up the new version:
157
+ ```bash
122
158
  echo "{version}" > /tmp/latest.txt
123
159
  coscli cp /tmp/latest.txt cos://clackyai-1258723534/openclacky/latest.txt
124
160
 
125
161
  # Verify
126
162
  curl -fsSL https://oss.1024code.com/openclacky/latest.txt
127
163
  ```
128
-
129
164
  Expected output of verify: `{version}`
130
165
 
166
+ **For pre-release versions** — do NOT update `latest.txt` unless the user explicitly requested it. Updating `latest.txt` to a prerelease would cause:
167
+ - Mirror users clicking "Upgrade" to get the beta via `upgrade_via_oss_cdn`
168
+ - Fresh installs via the install script to get the beta
169
+ - Only skip this if the user explicitly wants broad beta distribution
170
+
131
171
  > **Prerequisite**: `coscli` installed at `/usr/local/bin/coscli` and configured at `~/.cos.yaml`
132
172
 
133
173
  6. **Sync scripts/ to OSS**
@@ -325,22 +365,34 @@ git tag vX.Y.Z
325
365
  git push origin main
326
366
  git push origin --tags
327
367
 
328
- # Create GitHub Release with .gem asset (requires gh CLI)
329
- # 1. Extract release notes from CHANGELOG.md for this version
330
- # 2. Write to temp file to avoid shell escaping issues
331
- # 3. Create the release and attach .gem file
368
+ # ── GitHub Release ──────────────────────────────────────────────────────
369
+
370
+ # Stable release:
332
371
  gh release create vX.Y.Z \
333
372
  --title "vX.Y.Z" \
334
373
  --notes-file /tmp/release_notes_X.Y.Z.md \
335
374
  --latest \
336
375
  openclacky-X.Y.Z.gem
337
376
 
338
- # Sync to Tencent Cloud OSS (CN mirror)
377
+ # Pre-release (use --prerelease instead of --latest):
378
+ gh release create vX.Y.Z-beta.1 \
379
+ --title "vX.Y.Z-beta.1" \
380
+ --notes-file /tmp/release_notes_X.Y.Z-beta.1.md \
381
+ --prerelease \
382
+ openclacky-X.Y.Z.beta.1.gem
383
+
384
+ # ── OSS CDN (CN mirror) ─────────────────────────────────────────────────
385
+
386
+ # Always upload the .gem file:
339
387
  coscli cp openclacky-X.Y.Z.gem cos://clackyai-1258723534/openclacky/openclacky-X.Y.Z.gem
388
+
389
+ # Stable releases ONLY — update latest.txt:
340
390
  echo "X.Y.Z" > /tmp/latest.txt
341
391
  coscli cp /tmp/latest.txt cos://clackyai-1258723534/openclacky/latest.txt
342
392
  curl -fsSL https://oss.1024code.com/openclacky/latest.txt # verify
343
393
 
394
+ # Pre-releases — skip latest.txt update unless user explicitly requests it
395
+
344
396
  # Sync scripts/ to OSS (build from templates first)
345
397
  bash scripts/build/build.sh
346
398
  for script in scripts/*; do
@@ -365,8 +417,10 @@ curl -fsSL https://oss.1024code.com/clacky-ai/openclacky/main/scripts/install.sh
365
417
  - Git repository updated with version tag
366
418
  - CHANGELOG.md updated with release notes
367
419
  - GitHub Release created with .gem file attached at https://github.com/clacky-ai/openclacky/releases
420
+ - Use `--latest` for stable releases, `--prerelease` for pre-releases
368
421
  - .gem file uploaded to OSS: https://oss.1024code.com/openclacky/openclacky-{version}.gem
369
- - latest.txt updated on OSS: https://oss.1024code.com/openclacky/latest.txt returns the new version
422
+ - For stable releases: `latest.txt` updated on OSS: https://oss.1024code.com/openclacky/latest.txt returns the new version
423
+ - For pre-releases: `latest.txt` NOT updated (unless user explicitly opts in)
370
424
  - No build or deployment errors
371
425
  - User-facing release summary presented at the end
372
426
 
data/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.0.beta.3] - 2026-04-28
11
+
12
+ ### Added
13
+ - **Gemini 2.5 Pro support.** The new `gemini2.5-pro` model is now available as a selectable option, giving you access to Google's latest flagship model.
14
+ - **File attachments now support Markdown, plain text, and `.tar.gz` archives.** When you attach `.md`, `.txt`, or `.tar.gz` files to a session, the agent can read and reason over their contents directly.
15
+ - **Image type auto-detection.** Image files are now correctly identified by their binary content (magic bytes), not just their file extension — preventing misclassified images from causing upload or vision errors.
16
+
17
+ ### Improved
18
+ - **Settings page fully revamped.** The Web UI Settings panel now saves configuration correctly and exposes a richer set of options for managing providers, models, and API keys.
19
+ - **Skills no longer have a 50-item cap.** The skill loader previously limited the results list to 50 entries; that cap has been removed so all available skills show up.
20
+ - **Cost tracking no longer requires a hard-coded price list.** Model pricing is now resolved dynamically, so new models show real cost figures instead of falling back to a default.
21
+
22
+ ### Fixed
23
+ - **Terminal tool no longer crashes on non-UTF-8 output.** Commands that emit binary or non-UTF-8 bytes (e.g. compiled output, legacy scripts) no longer raise an encoding error in the terminal tool.
24
+
25
+ ## [1.0.0.beta.2] - 2026-04-27
26
+
27
+ ### Added
28
+ - **New session creation supports model & working-directory options.** The Web UI "new session" dialog now lets you pick the model and starting directory up front, instead of having to adjust them after the session opens.
29
+
30
+ ### Fixed
31
+ - **System prompt now refreshes when you switch models.** Previously the system prompt captured at session start stuck around even after `/model` or `/provider` switches, which could leave model-specific instructions out of sync. The agent now re-injects the correct system prompt on every model change.
32
+ - **Port 7070 properly released when the terminal tool exits.** A lingering listener on port 7070 could block subsequent runs; the terminal tool now cleans it up on shutdown.
33
+ - **Windows installer uses `[IO.Path]::GetTempPath()` for the temp directory** (#58) — more reliable than `$env:TEMP` on systems where the env var is unset or points to a non-ASCII path.
34
+
10
35
  ## [1.0.0.beta.1] - 2026-04-26
11
36
 
12
37
  ### Added
@@ -24,15 +24,22 @@ module Clacky
24
24
  cost = result[:cost]
25
25
  pricing_source = result[:source]
26
26
 
27
- @total_cost += cost
28
- iteration_cost = cost
29
- # Map pricing source to cost source: :price or :default
30
- @cost_source = pricing_source
31
- @task_cost_source = pricing_source
27
+ # Only accumulate cost when the model has known pricing.
28
+ # Unknown models return nil — display N/A, don't add to total.
29
+ if cost
30
+ @total_cost += cost
31
+ iteration_cost = cost
32
+ @cost_source = pricing_source
33
+ @task_cost_source = pricing_source
34
+ end
32
35
 
33
36
  if @config.verbose
34
- source_label = pricing_source == :price ? "model pricing" : "default pricing"
35
- @ui&.log("Calculated cost for #{@config.model_name} using #{source_label}: $#{cost.round(6)}", level: :debug)
37
+ if cost
38
+ source_label = pricing_source == :price ? "model pricing" : "default pricing"
39
+ @ui&.log("Calculated cost for #{@config.model_name} using #{source_label}: $#{cost.round(6)}", level: :debug)
40
+ else
41
+ @ui&.log("No pricing data available for #{@config.model_name} — cost is unknown", level: :debug)
42
+ end
36
43
  @ui&.log("Usage breakdown: prompt=#{usage[:prompt_tokens]}, completion=#{usage[:completion_tokens]}, cache_write=#{usage[:cache_creation_input_tokens] || 0}, cache_read=#{usage[:cache_read_input_tokens] || 0}", level: :debug)
37
44
  end
38
45
  end
@@ -18,6 +18,10 @@ module Clacky
18
18
  @working_dir = session_data[:working_dir]
19
19
  @created_at = session_data[:created_at]
20
20
  @total_tasks = session_data.dig(:stats, :total_tasks) || 0
21
+ # Restore cost_source so frontend knows if cost is reliable
22
+ cost_src = session_data.dig(:stats, :cost_source)
23
+ @cost_source = (cost_src && cost_src.to_sym) || :estimated
24
+ @task_cost_source = :estimated
21
25
  # Restore source; fall back to :manual for sessions saved before this field existed
22
26
  @source = (session_data[:source] || "manual").to_sym
23
27
 
@@ -65,6 +69,7 @@ module Clacky
65
69
  total_tasks: @total_tasks,
66
70
  total_iterations: @iterations,
67
71
  total_cost_usd: @total_cost.round(4),
72
+ cost_source: @cost_source.to_s,
68
73
  duration_seconds: @start_time ? (Time.now - @start_time).round(2) : 0,
69
74
  last_status: status.to_s,
70
75
  cache_stats: @cache_stats,
data/lib/clacky/agent.rb CHANGED
@@ -477,6 +477,7 @@ module Clacky
477
477
  @ui&.show_complete(
478
478
  iterations: result[:iterations],
479
479
  cost: result[:total_cost_usd],
480
+ cost_source: result[:cost_source],
480
481
  duration: result[:duration_seconds],
481
482
  cache_stats: result[:cache_stats],
482
483
  awaiting_user_feedback: awaiting_user_feedback
@@ -1371,7 +1372,19 @@ module Clacky
1371
1372
  # Core method to inject session context (date, model, OS, paths).
1372
1373
  # Called by inject_session_context_if_needed (with date check)
1373
1374
  # and by switch_model (without date check, to force update).
1375
+ #
1376
+ # IMPORTANT: Skip injection when the system prompt hasn't been built yet.
1377
+ # Otherwise, appending a user message to an empty history makes
1378
+ # @history.empty? false, which causes run() to skip building the
1379
+ # system prompt entirely (see run()'s "first run" guard).
1380
+ # The injection will happen naturally in run() via
1381
+ # inject_session_context_if_needed after the system prompt is in place.
1374
1382
  private def inject_session_context
1383
+ # Don't inject context before system prompt exists — defer to
1384
+ # inject_session_context_if_needed which runs inside run()
1385
+ # after the system prompt has been built.
1386
+ return unless @history.has_system_prompt?
1387
+
1375
1388
  today = Time.now.strftime("%Y-%m-%d")
1376
1389
  os = Clacky::Utils::EnvironmentDetector.os_type
1377
1390
  desktop = Clacky::Utils::EnvironmentDetector.desktop_path
@@ -190,6 +190,10 @@ module Clacky
190
190
  auto_create_threshold: 12,
191
191
  reflection_mode: "llm_analysis"
192
192
  }
193
+ # Deep-symbolize keys — YAML-loaded hashes come with string keys,
194
+ # but the rest of the codebase accesses with symbols.
195
+ @skill_evolution = @skill_evolution.transform_keys(&:to_sym)
196
+ @skill_evolution.transform_values! { |v| v.is_a?(Hash) ? v.transform_keys(&:to_sym) : v }
193
197
 
194
198
  # Per-session virtual model overlay.
195
199
  # When set, #current_model returns a *merged* hash (the resolved @models
@@ -211,6 +215,13 @@ module Clacky
211
215
  data = nil
212
216
  end
213
217
 
218
+ # Extract settings from hash-format config (new format).
219
+ # Old flat-array configs have no settings section — all defaults.
220
+ loaded_settings = {}
221
+ if data.is_a?(Hash) && data["settings"].is_a?(Hash)
222
+ loaded_settings = data["settings"]
223
+ end
224
+
214
225
  # Parse models from config
215
226
  models = parse_models(data)
216
227
 
@@ -278,7 +289,21 @@ module Clacky
278
289
  default_index = models.find_index { |m| m["type"] == "default" } || 0
279
290
  default_id = models[default_index] && models[default_index]["id"]
280
291
 
281
- new(models: models, current_model_index: default_index, current_model_id: default_id)
292
+ # Build constructor args from loaded settings (new hash-format config)
293
+ # plus the parsed models. Only pass settings that have explicit values;
294
+ # omitted keys get their default from AgentConfig#initialize.
295
+ constructor_args = {
296
+ models: models,
297
+ current_model_index: default_index,
298
+ current_model_id: default_id
299
+ }
300
+ CONFIG_SETTINGS_KEYS.each do |key|
301
+ if loaded_settings.key?(key)
302
+ constructor_args[key.to_sym] = loaded_settings[key]
303
+ end
304
+ end
305
+
306
+ new(**constructor_args)
282
307
  end
283
308
 
284
309
  # Auto-injection of provider-preset lite models into @models has been
@@ -339,11 +364,27 @@ module Clacky
339
364
  # config.yml remains backward compatible with users on older versions.
340
365
  RUNTIME_ONLY_FIELDS = %w[id auto_injected].freeze
341
366
 
367
+ # Settings keys that are persisted to config.yml.
368
+ # These map directly to AgentConfig accessors.
369
+ CONFIG_SETTINGS_KEYS = %w[
370
+ enable_compression enable_prompt_caching memory_update_enabled
371
+ skill_evolution
372
+ ].freeze
373
+
374
+ # Serialize the current agent configuration to YAML.
375
+ # Outputs a hash with "settings" and "models" keys (new format).
376
+ # Backward compatibility: old flat-array format is still readable by .load.
342
377
  def to_yaml
343
- persistable = @models.reject { |m| m["auto_injected"] }.map do |m|
378
+ persistable_models = @models.reject { |m| m["auto_injected"] }.map do |m|
344
379
  m.reject { |k, _| RUNTIME_ONLY_FIELDS.include?(k) }
345
380
  end
346
- YAML.dump(persistable)
381
+ settings = {
382
+ "enable_compression" => @enable_compression,
383
+ "enable_prompt_caching" => @enable_prompt_caching,
384
+ "memory_update_enabled" => @memory_update_enabled,
385
+ "skill_evolution" => @skill_evolution
386
+ }
387
+ YAML.dump("settings" => settings, "models" => persistable_models)
347
388
  end
348
389
 
349
390
  # Check if any model is configured
data/lib/clacky/cli.rb CHANGED
@@ -905,6 +905,14 @@ module Clacky
905
905
  option :port, type: :numeric, default: 7070, desc: "Listen port (default: 7070)"
906
906
  option :brand_test, type: :boolean, default: false,
907
907
  desc: "Enable brand test mode: mock license activation without calling remote API"
908
+ option :no_compression, type: :boolean, default: false,
909
+ desc: "Disable message compression (saves tokens but may lose context)"
910
+ option :no_memory, type: :boolean, default: false,
911
+ desc: "Disable automatic memory updates"
912
+ option :no_caching, type: :boolean, default: false,
913
+ desc: "Disable prompt caching"
914
+ option :no_skill_evolution, type: :boolean, default: false,
915
+ desc: "Disable automatic skill evolution"
908
916
  def server
909
917
  # ── Security gate ──────────────────────────────────────────────────────
910
918
  # Binding to 0.0.0.0 exposes the server to the public network.
@@ -948,6 +956,15 @@ module Clacky
948
956
  agent_config = Clacky::AgentConfig.load
949
957
  agent_config.permission_mode = :confirm_all
950
958
 
959
+ # Apply CLI overrides to agent config (--no-compression etc.)
960
+ # These override whatever is stored in config.yml.
961
+ agent_config.enable_compression = false if options[:no_compression]
962
+ agent_config.memory_update_enabled = false if options[:no_memory]
963
+ agent_config.enable_prompt_caching = false if options[:no_caching]
964
+ if options[:no_skill_evolution]
965
+ agent_config.skill_evolution[:enabled] = false
966
+ end
967
+
951
968
  client_factory = lambda do
952
969
  Clacky::Client.new(
953
970
  agent_config.api_key,
@@ -987,6 +1004,10 @@ module Clacky
987
1004
 
988
1005
  extra_flags = []
989
1006
  extra_flags << "--brand-test" if options[:brand_test]
1007
+ extra_flags << "--no-compression" if options[:no_compression]
1008
+ extra_flags << "--no-memory" if options[:no_memory]
1009
+ extra_flags << "--no-caching" if options[:no_caching]
1010
+ extra_flags << "--no-skill-evolution" if options[:no_skill_evolution]
990
1011
 
991
1012
  Clacky::Logger.console = true
992
1013
 
@@ -78,7 +78,7 @@ module Clacky
78
78
  emit("token_usage", **token_data)
79
79
  end
80
80
 
81
- def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
81
+ def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
82
82
  data = { iterations: iterations, cost: cost }
83
83
  data[:duration] = duration if duration
84
84
  data[:cache_stats] = cache_stats if cache_stats
@@ -19,14 +19,26 @@ module Clacky
19
19
  # Detect if the request should use the Bedrock Converse API.
20
20
  # Matches any of:
21
21
  # - API key with "ABSK" prefix (native AWS Bedrock)
22
- # - API key with "clacky-" prefix (Clacky workspace key, proxied via Bedrock Converse)
23
22
  # - Model ID with "abs-" prefix (Clacky AI proxy that speaks Bedrock Converse)
23
+ #
24
+ # A bare "clacky-" key is NOT enough: that same workspace key is also
25
+ # used for dsk-*, or-*, and other OpenAI-compatible aliases served by
26
+ # the same Clacky proxy on a different endpoint. The *model prefix* is
27
+ # the source of truth for which upstream format the proxy expects:
28
+ #
29
+ # abs-* → Bedrock Converse (POST /model/{id}/converse)
30
+ # dsk-* → OpenAI-compatible (POST /chat/completions)
31
+ # or-* → OpenAI-compatible (POST /chat/completions)
32
+ # other → depends on base_url + explicit anthropic_format flag
33
+ #
34
+ # Historically this method also returned true for any "clacky-" key,
35
+ # which forced non-abs aliases into the Bedrock endpoint and produced
36
+ # `unknown model "..."` errors. Keep the explicit-prefix rule: if you
37
+ # add a new OpenAI-compatible alias family on the Clacky proxy, it
38
+ # will route correctly without touching this file.
24
39
  def self.bedrock_api_key?(api_key, model)
25
- # dsk- prefixed models use the OpenAI-compatible /chat/completions endpoint
26
- # on the same Clacky proxy, not the Bedrock Converse /model/{model}/converse path.
27
- return false if model.to_s.start_with?("dsk-")
28
-
29
- api_key.to_s.start_with?("ABSK", "clacky-") || model.to_s.start_with?("abs-")
40
+ return true if api_key.to_s.start_with?("ABSK")
41
+ model.to_s.start_with?("abs-")
30
42
  end
31
43
 
32
44
  module_function
@@ -100,6 +100,14 @@ module Clacky
100
100
  # Business queries
101
101
  # ─────────────────────────────────────────────
102
102
 
103
+ # True when a system prompt message is present in the history.
104
+ # Used by inject_session_context to avoid injecting context messages
105
+ # before the system prompt has been built (which would cause the
106
+ # guard in run() to skip building it altogether).
107
+ def has_system_prompt?
108
+ @messages.any? { |m| m[:role] == "system" }
109
+ end
110
+
103
111
  # True when the last assistant message has tool_calls but no
104
112
  # tool_result has been appended yet (would cause a 400 from the API).
105
113
  def pending_tool_calls?
@@ -97,7 +97,7 @@ module Clacky
97
97
  puts_line("[shell] #{command}")
98
98
  end
99
99
 
100
- def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
100
+ def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
101
101
  parts = ["[done] iterations=#{iterations}", "cost=$#{cost.round(4)}"]
102
102
  parts << "duration=#{duration.round(1)}s" if duration
103
103
  puts_line(parts.join(" "))
@@ -22,7 +22,7 @@ module Clacky
22
22
  "name" => "OpenClacky",
23
23
  "base_url" => "https://api.openclacky.com",
24
24
  "api" => "bedrock",
25
- "default_model" => "abs-claude-sonnet-4-5",
25
+ "default_model" => "abs-claude-sonnet-4-6",
26
26
  "models" => [
27
27
  "abs-claude-opus-4-7",
28
28
  "abs-claude-opus-4-6",
@@ -30,12 +30,15 @@ module Clacky
30
30
  "abs-claude-sonnet-4-5",
31
31
  "abs-claude-haiku-4-5",
32
32
  "dsk-deepseek-v4-pro",
33
- "dsk-deepseek-v4-flash"
33
+ "dsk-deepseek-v4-flash",
34
+ "or-gemini-3-1-pro"
34
35
  ],
35
36
  # Provider-level default: the Claude family served here is vision-capable.
36
37
  "capabilities" => { "vision" => true }.freeze,
37
38
  # Model-level overrides: DeepSeek models routed through this provider
38
39
  # are text-only; images uploaded for them must be downgraded to disk refs.
40
+ # Gemini 3.1 Pro keeps the provider-default vision=true (it accepts
41
+ # image/audio/video input natively via OpenRouter).
39
42
  "model_capabilities" => {
40
43
  "dsk-deepseek-v4-pro" => { "vision" => false }.freeze,
41
44
  "dsk-deepseek-v4-flash" => { "vision" => false }.freeze
@@ -46,6 +49,10 @@ module Clacky
46
49
  # weak models (haiku / v4-flash) ARE the lite tier themselves, so
47
50
  # they're intentionally not listed here — no injection happens when
48
51
  # the default model is already lite-class.
52
+ #
53
+ # or-gemini-3-1-pro is intentionally absent: Gemini has no lite
54
+ # sibling wired up (yet) on this provider; subagents using the
55
+ # Gemini default will just reuse it for lite work until we add one.
49
56
  "lite_models" => {
50
57
  "abs-claude-opus-4-7" => "abs-claude-haiku-4-5",
51
58
  "abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
@@ -86,8 +93,6 @@ module Clacky
86
93
  "models" => [
87
94
  "deepseek-v4-flash",
88
95
  "deepseek-v4-pro",
89
- "deepseek-chat",
90
- "deepseek-reasoner"
91
96
  ],
92
97
  # DeepSeek V4 API does not accept image inputs — text-only across all models.
93
98
  "capabilities" => { "vision" => false }.freeze,
@@ -137,9 +142,11 @@ module Clacky
137
142
  "abs-claude-sonnet-4-5",
138
143
  "abs-claude-haiku-4-5",
139
144
  "dsk-deepseek-v4-pro",
140
- "dsk-deepseek-v4-flash"
145
+ "dsk-deepseek-v4-flash",
146
+ "or-gemini-3-1-pro"
141
147
  ],
142
- # Same lineup as openclacky — Claude is vision, DeepSeek is text-only.
148
+ # Same lineup as openclacky — Claude is vision, DeepSeek is text-only,
149
+ # Gemini inherits the provider-default vision=true.
143
150
  "capabilities" => { "vision" => true }.freeze,
144
151
  "model_capabilities" => {
145
152
  "dsk-deepseek-v4-pro" => { "vision" => false }.freeze,
@@ -104,10 +104,14 @@ module Clacky
104
104
  # Suppress
105
105
  end
106
106
 
107
- def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
107
+ def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
108
108
  flush_buffer
109
109
  parts = ["Done", "#{iterations} step#{"s" if iterations != 1}"]
110
- parts << "$#{cost.round(4)}" if cost && cost > 0
110
+ # Only show cost when pricing source is known (model matched pricing table).
111
+ # Unknown models return nil — skip to avoid misleading numbers.
112
+ if cost && cost > 0 && cost_source
113
+ parts << "$#{cost.round(4)}"
114
+ end
111
115
  parts << "#{duration.round(1)}s" if duration
112
116
  send_text(parts.join(" · "))
113
117
  end