riffer-rig 0.13.0 → 0.14.0
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/CHANGELOG.md +17 -0
- data/README.md +19 -13
- data/lib/riffer/rig/cli/flags.rb +8 -1
- data/lib/riffer/rig/commands/reasoning.rb +46 -0
- data/lib/riffer/rig/env.rb +3 -1
- data/lib/riffer/rig/headless.rb +2 -0
- data/lib/riffer/rig/loader/options.rb +5 -1
- data/lib/riffer/rig/loader.rb +57 -3
- data/lib/riffer/rig/registrar.rb +3 -1
- data/lib/riffer/rig/runtime/snapshot.rb +7 -3
- data/lib/riffer/rig/runtime.rb +42 -10
- data/lib/riffer/rig/settings/document.rb +5 -5
- data/lib/riffer/rig/settings/file.rb +7 -0
- data/lib/riffer/rig/settings/model_options.rb +13 -33
- data/lib/riffer/rig/settings.rb +4 -1
- data/lib/riffer/rig/stores/entries.rb +3 -0
- data/lib/riffer/rig/stores/jsonl.rb +1 -0
- data/lib/riffer/rig/stores/reasoning_entry.rb +31 -0
- data/lib/riffer/rig/stores/recorder.rb +11 -4
- data/lib/riffer/rig/terminal/console.rb +13 -1
- data/lib/riffer/rig/terminal/count.rb +14 -0
- data/lib/riffer/rig/terminal/frame.rb +176 -51
- data/lib/riffer/rig/terminal/frame_prompter.rb +3 -1
- data/lib/riffer/rig/terminal/idle_ticker.rb +28 -0
- data/lib/riffer/rig/terminal/key_decoder.rb +65 -1
- data/lib/riffer/rig/terminal/key_pipeline.rb +8 -0
- data/lib/riffer/rig/terminal/line_editor.rb +10 -2
- data/lib/riffer/rig/terminal/loading.rb +71 -0
- data/lib/riffer/rig/terminal/repl.rb +19 -11
- data/lib/riffer/rig/terminal/spinner.rb +13 -0
- data/lib/riffer/rig/terminal/status_bar.rb +126 -0
- data/lib/riffer/rig/terminal/ticker.rb +21 -101
- data/lib/riffer/rig/terminal/ticker_display.rb +5 -9
- data/lib/riffer/rig/terminal/wrapper.rb +19 -7
- data/lib/riffer/rig/terminal.rb +59 -20
- data/lib/riffer/rig/tools/bash.rb +9 -2
- data/lib/riffer/rig/version.rb +1 -1
- data/sig/generated/riffer/rig/cli/flags.rbs +5 -2
- data/sig/generated/riffer/rig/commands/reasoning.rbs +16 -0
- data/sig/generated/riffer/rig/env.rbs +3 -1
- data/sig/generated/riffer/rig/loader/options.rbs +5 -2
- data/sig/generated/riffer/rig/loader.rbs +30 -0
- data/sig/generated/riffer/rig/runtime/snapshot.rbs +5 -2
- data/sig/generated/riffer/rig/runtime.rbs +22 -6
- data/sig/generated/riffer/rig/settings/document.rbs +2 -2
- data/sig/generated/riffer/rig/settings/file.rbs +5 -0
- data/sig/generated/riffer/rig/settings/model_options.rbs +8 -14
- data/sig/generated/riffer/rig/stores/reasoning_entry.rbs +23 -0
- data/sig/generated/riffer/rig/stores/recorder.rbs +8 -4
- data/sig/generated/riffer/rig/terminal/console.rbs +7 -1
- data/sig/generated/riffer/rig/terminal/count.rbs +7 -0
- data/sig/generated/riffer/rig/terminal/frame.rbs +75 -12
- data/sig/generated/riffer/rig/terminal/idle_ticker.rbs +21 -0
- data/sig/generated/riffer/rig/terminal/key_decoder.rbs +29 -0
- data/sig/generated/riffer/rig/terminal/key_pipeline.rbs +6 -0
- data/sig/generated/riffer/rig/terminal/line_editor.rbs +3 -0
- data/sig/generated/riffer/rig/terminal/loading.rbs +42 -0
- data/sig/generated/riffer/rig/terminal/repl.rbs +5 -3
- data/sig/generated/riffer/rig/terminal/spinner.rbs +9 -0
- data/sig/generated/riffer/rig/terminal/status_bar.rbs +88 -0
- data/sig/generated/riffer/rig/terminal/ticker.rbs +14 -60
- data/sig/generated/riffer/rig/terminal/ticker_display.rbs +3 -6
- data/sig/generated/riffer/rig/terminal/wrapper.rbs +8 -1
- data/sig/generated/riffer/rig/terminal.rbs +23 -0
- data/sig/manual/riffer/rig/{terminal/equalizer.rbs → commands/reasoning.rbs} +2 -2
- data/sig/manual/riffer/rig/stores.rbs +2 -2
- data/sig/manual/riffer/rig/terminal/count.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/spinner.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/status_bar.rbs +6 -0
- metadata +21 -8
- data/lib/riffer/rig/settings/pricing.rb +0 -40
- data/lib/riffer/rig/terminal/equalizer.rb +0 -20
- data/sig/generated/riffer/rig/settings/pricing.rbs +0 -31
- data/sig/generated/riffer/rig/terminal/equalizer.rbs +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 613c884d96e252c8793e442b39cf843d5c91438fc7d6ae6bc963536cb978de09
|
|
4
|
+
data.tar.gz: f4e2596e6c67e87f19011afa452c207f5f6e8b0394673c58e1ae22a57e2f2269
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d1b670b735b7d06b7b88d38c305e09018db879ca5ffe1a65a89f1424ae25b9d324b234bb3d38e89889305f85cb1f97822d2a76fba083f83e76f256cec6de331
|
|
7
|
+
data.tar.gz: 77c745faeb40661a33801885cee13d7811b05c9e557e5798a5ebb6233aef18e7c74e194f4fc9b4347cac4ff0b8182ca8c42b2e70fda71a051ee13849dbdea8f3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.14.0](https://github.com/bottrall/riffer-rig/compare/v0.13.0...v0.14.0) (2026-10-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **headless:** print the session id on stderr ([#556](https://github.com/bottrall/riffer-rig/issues/556)) ([2816938](https://github.com/bottrall/riffer-rig/commit/2816938f66d32ccf557d83a33e40300e68a68032))
|
|
9
|
+
* make reasoning effort configurable like the model ([#553](https://github.com/bottrall/riffer-rig/issues/553)) ([330f190](https://github.com/bottrall/riffer-rig/commit/330f190244353ed5e967b0f29d574c2741d92dba))
|
|
10
|
+
* **terminal:** keep the status bar filled between turns ([#555](https://github.com/bottrall/riffer-rig/issues/555)) ([e0918ec](https://github.com/bottrall/riffer-rig/commit/e0918ecd050519cf06d0593221b3b50e0e1a6778))
|
|
11
|
+
* **terminal:** type a multiline prompt ([#557](https://github.com/bottrall/riffer-rig/issues/557)) ([a55091c](https://github.com/bottrall/riffer-rig/commit/a55091c091e4df4ad6c63a71d7dd16d1de98e835))
|
|
12
|
+
* upgrade riffer to 0.53.0 and adopt the model catalog ([#552](https://github.com/bottrall/riffer-rig/issues/552)) ([70b129b](https://github.com/bottrall/riffer-rig/commit/70b129b27a4d03188ef7831937874d7bb73cea08))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* stop models from polling background bash jobs ([#559](https://github.com/bottrall/riffer-rig/issues/559)) ([57ad0ba](https://github.com/bottrall/riffer-rig/commit/57ad0ba259d0ebd71f4cc4178172364f39a62782))
|
|
18
|
+
* **terminal:** draw the banner and the input row together ([#549](https://github.com/bottrall/riffer-rig/issues/549)) ([e847277](https://github.com/bottrall/riffer-rig/commit/e847277af41ebd824dd115105d7d40e9af236da4))
|
|
19
|
+
|
|
3
20
|
## [0.13.0](https://github.com/bottrall/riffer-rig/compare/v0.12.0...v0.13.0) (2026-10-17)
|
|
4
21
|
|
|
5
22
|
|
data/README.md
CHANGED
|
@@ -34,13 +34,14 @@ riffer
|
|
|
34
34
|
|
|
35
35
|
The first run asks which model to use, as `provider/name` (for example `anthropic/claude-sonnet-4-6`), and saves the answer as `model` in `~/.riffer/settings.json`; it then asks for any missing credential ([Authentication](#authentication)). After that, `riffer` opens straight into the prompt. [Getting started](docs/GETTING_STARTED.md) walks through a first session.
|
|
36
36
|
|
|
37
|
-
The model is the first of these that is set: `--model`, `RIFFER_MODEL`, `model` in `<cwd>/.riffer/settings.json`, then in `~/.riffer/settings.json` ([Configuration](docs/CONFIGURATION.md#model)).
|
|
37
|
+
The model is the first of these that is set: `--model`, `RIFFER_MODEL`, `model` in `<cwd>/.riffer/settings.json`, then in `~/.riffer/settings.json` ([Configuration](docs/CONFIGURATION.md#model)). The reasoning effort resolves the same way: `--reasoning`, `RIFFER_REASONING`, then `reasoning` in the settings ([Configuration](docs/CONFIGURATION.md#reasoning)).
|
|
38
38
|
|
|
39
39
|
### Flags
|
|
40
40
|
|
|
41
41
|
| Flag | Effect |
|
|
42
42
|
| ----------------------- | -------------------------------------------------------------------------- |
|
|
43
43
|
| `--model provider/name` | the model for this session, over `RIFFER_MODEL` and the settings |
|
|
44
|
+
| `--reasoning LEVEL` | the reasoning effort for this session, over `RIFFER_REASONING` and the settings |
|
|
44
45
|
| `--no-extensions` | skips the [`rig.rb` files](docs/EXTENSIONS.md#the-rigrb-files) and gem autoload; the bundle still loads |
|
|
45
46
|
| `--no-skills` | leaves out [Agent Skills](docs/SKILLS.md) |
|
|
46
47
|
| `--no-agents-md` | leaves out the [AGENTS.md](docs/INSTRUCTIONS.md#agentsmd) instructions |
|
|
@@ -70,16 +71,20 @@ The exit code says how the turn ended: `0` finished, `1` runtime error, `2` usag
|
|
|
70
71
|
|
|
71
72
|
### In the session
|
|
72
73
|
|
|
73
|
-
Type a prompt and press Enter. While the turn runs, the status line under the input shows its time, steps, tool calls, tokens and cost, and Ctrl-T peeks at what the model is writing; the reply is printed when the turn ends, followed by a stat line (`N steps · mm:ss · ↑in · ↓out · session N tok`, plus the turn's cost and the session's when the model is [priced](docs/CONFIGURATION.md#
|
|
74
|
+
Type a prompt and press Enter. While the turn runs, the status line under the input shows its time, steps, tool calls, tokens and cost, and Ctrl-T peeks at what the model is writing; the reply is printed when the turn ends, followed by a stat line (`N steps · mm:ss · ↑in · ↓out · session N tok`, plus the turn's cost and the session's when the model is [priced](docs/CONFIGURATION.md#catalog_files)). The input line stays open throughout: a message sent during a turn is queued and runs when the turn ends. [Terminal](docs/TERMINAL.md) has every key.
|
|
74
75
|
|
|
75
76
|
- Ctrl-C during a turn cancels it. Ctrl-C with no turn running asks for a second one, which exits.
|
|
76
77
|
- `/exit` or `/quit` ends the session, as does Ctrl-D on an empty line.
|
|
77
|
-
- `/help` lists the session's slash commands. `/resume [--all]`, `/new`, `/cost`, `/trace`, `/skill:<name>` and the others are described in [Terminal](docs/TERMINAL.md#commands); `/model` is covered [below](#switching-the-model).
|
|
78
|
+
- `/help` lists the session's slash commands. `/resume [--all]`, `/new`, `/cost`, `/trace`, `/skill:<name>` and the others are described in [Terminal](docs/TERMINAL.md#commands); `/model` is covered [below](#switching-the-model), and `/reasoning` works the same way for the reasoning effort.
|
|
78
79
|
|
|
79
80
|
### Switching the model
|
|
80
81
|
|
|
81
82
|
`/model provider/name` switches the model for the current session only, keeping the conversation so far, and confirms it in one line: `/model openai/gpt-5` prints `Model switched to openai/gpt-5`. The provider prefix is required: a bare name such as `/model sonnet` is rejected with the list of providers. `/model` alone shows the model in use. Switching runs the same setup for the new provider as startup does: its SDK gem is offered for install (or refused with the exact Gemfile line), and missing credentials are resolved — you are prompted for them when the host can ask, and the values apply to the running session immediately. The switch is refused with one error when the host cannot supply what is missing, and the model is left unchanged. `/model --save` writes the model in effect to the home `settings.json`, so new sessions start with it; `/model provider/name --save` switches and saves in one step, printing `Model switched to openai/gpt-5 and saved to ~/.riffer/settings.json` (a bare `/model --save` prints `Saved openai/gpt-5 to ~/.riffer/settings.json`), and a refused switch saves nothing ([Configuration](docs/CONFIGURATION.md#model)).
|
|
82
83
|
|
|
84
|
+
### Switching the reasoning level
|
|
85
|
+
|
|
86
|
+
`/reasoning level` switches the reasoning effort for the current session only, keeping the conversation so far: `/reasoning high` prints `Reasoning switched to high`, `/reasoning off` turns reasoning off, and `/reasoning` alone shows the level in effect. A level riffer does not accept is rejected naming the levels it accepts. `/reasoning --save` writes the level in effect to the home `settings.json` (`/reasoning high --save` switches and saves in one step), and a home file that no longer parses is reported instead of written. The switch survives a resume, and a `--reasoning` chosen for the run wins over it for that run only ([Configuration](docs/CONFIGURATION.md#reasoning)).
|
|
87
|
+
|
|
83
88
|
## Authentication
|
|
84
89
|
|
|
85
90
|
`riffer-rig` talks to the provider named by the model's prefix — `anthropic/claude-sonnet-4-6` means Anthropic. Provide that provider's credentials in either of two ways:
|
|
@@ -115,18 +120,19 @@ Inside a session, `/auth` lists every provider with where its secret comes from
|
|
|
115
120
|
|
|
116
121
|
Settings live in two scopes, both optional: `~/.riffer/settings.json` for every project, and `<cwd>/.riffer/settings.json` for one project, merged key by key with the project winning ([Configuration](docs/CONFIGURATION.md)). Every core key is optional:
|
|
117
122
|
|
|
118
|
-
| Key
|
|
119
|
-
|
|
|
120
|
-
| `model`
|
|
121
|
-
| `reasoning`
|
|
122
|
-
| `
|
|
123
|
-
| `providers`
|
|
124
|
-
| `extensions`
|
|
125
|
-
| `reload`
|
|
126
|
-
| `sessions`
|
|
127
|
-
| `tools`
|
|
123
|
+
| Key | What it sets |
|
|
124
|
+
| -------------- | -------------------------------------------------------------------------------- |
|
|
125
|
+
| `model` | the model new sessions start with, as `provider/name` |
|
|
126
|
+
| `reasoning` | the reasoning effort, which riffer translates to the provider's own parameter |
|
|
127
|
+
| `catalog_files`| the model catalog JSON files holding pricing and reasoning maps |
|
|
128
|
+
| `providers` | non-secret provider fields such as an endpoint or region (home file only) |
|
|
129
|
+
| `extensions` | `disabled` — bundled extensions to leave out; `autoload` — gem extension autoload |
|
|
130
|
+
| `reload` | `"auto"` (default) or `"manual"` — the hot-reload trigger |
|
|
131
|
+
| `sessions` | `save: false` stops saving sessions to the store |
|
|
132
|
+
| `tools` | the provider-native tool switches (`native`), off by default |
|
|
128
133
|
|
|
129
134
|
- `RIFFER_MODEL` — the model for this run as `provider/name`, winning over the `model` setting. A bare name such as `sonnet` is rejected with the list of providers.
|
|
135
|
+
- `RIFFER_REASONING` — the reasoning effort for this run, winning over the `reasoning` setting; a level riffer does not accept is rejected naming the levels it accepts.
|
|
130
136
|
- `AGENTS.md` — `~/.riffer/AGENTS.md` and an `AGENTS.md` in the current working directory or any directory above it are re-read every turn as instructions that take precedence over the default norms ([Instructions](docs/INSTRUCTIONS.md#agentsmd)).
|
|
131
137
|
- Skills — Agent Skills in `.agents/skills/` from the working directory up to the repository root, and in `~/.agents/skills/`, are offered to the model, and `/skill:<name> [text]` runs one ([Skills](docs/SKILLS.md)).
|
|
132
138
|
- MCP servers — declared under the `mcp` key in either settings file and registered with the Runtime ([MCP](docs/MCP.md)).
|
data/lib/riffer/rig/cli/flags.rb
CHANGED
|
@@ -9,9 +9,10 @@ class Riffer::Rig::CLI::Flags
|
|
|
9
9
|
riffer acp serve Runtimes as an ACP agent over stdio
|
|
10
10
|
TEXT
|
|
11
11
|
|
|
12
|
-
# @dynamic model, extensions, skills, agents_md, tools, max_steps, help, version
|
|
12
|
+
# @dynamic model, reasoning, extensions, skills, agents_md, tools, max_steps, help, version
|
|
13
13
|
# @dynamic save, verbose, json, job_idle_timeout, headless, prompt, continue, resume
|
|
14
14
|
attr_reader :model #: String?
|
|
15
|
+
attr_reader :reasoning #: String?
|
|
15
16
|
attr_reader :extensions, :skills, :agents_md, :help, :version, :verbose, :json, :headless #: bool
|
|
16
17
|
attr_reader :tools #: Array[String]?
|
|
17
18
|
attr_reader :max_steps #: Integer?
|
|
@@ -47,6 +48,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
47
48
|
def to_options
|
|
48
49
|
Riffer::Rig::Loader::Options.new(
|
|
49
50
|
model: model,
|
|
51
|
+
reasoning: reasoning,
|
|
50
52
|
extensions: extensions,
|
|
51
53
|
skills: skills,
|
|
52
54
|
agents_md: agents_md,
|
|
@@ -61,6 +63,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
61
63
|
parser.banner = BANNER
|
|
62
64
|
parser.separator('')
|
|
63
65
|
parser.on('--model PROVIDER/NAME', String, 'Model for this session, over RIFFER_MODEL and settings')
|
|
66
|
+
parser.on('--reasoning LEVEL', String, 'Reasoning level for this session, over RIFFER_REASONING and settings')
|
|
64
67
|
parser.on('--no-extensions', 'Skip extension files and autoload; the bundle still loads')
|
|
65
68
|
parser.on('--no-skills', 'Leave out Agent Skills')
|
|
66
69
|
parser.on('--no-agents-md', 'Leave out AGENTS.md instructions')
|
|
@@ -94,6 +97,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
94
97
|
def self.assemble(values, prompt)
|
|
95
98
|
new(
|
|
96
99
|
model: values[:model],
|
|
100
|
+
reasoning: values[:reasoning],
|
|
97
101
|
extensions: !values.key?(:'no-extensions'),
|
|
98
102
|
skills: !values.key?(:'no-skills'),
|
|
99
103
|
agents_md: !values.key?(:'no-agents-md'),
|
|
@@ -114,6 +118,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
114
118
|
private_class_method :assemble
|
|
115
119
|
|
|
116
120
|
# @rbs model: String?
|
|
121
|
+
# @rbs reasoning: String?
|
|
117
122
|
# @rbs extensions: bool
|
|
118
123
|
# @rbs skills: bool
|
|
119
124
|
# @rbs agents_md: bool
|
|
@@ -132,6 +137,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
132
137
|
# @rbs return: void
|
|
133
138
|
def initialize(
|
|
134
139
|
model: nil,
|
|
140
|
+
reasoning: nil,
|
|
135
141
|
extensions: true,
|
|
136
142
|
skills: true,
|
|
137
143
|
agents_md: true,
|
|
@@ -149,6 +155,7 @@ class Riffer::Rig::CLI::Flags
|
|
|
149
155
|
version: false
|
|
150
156
|
)
|
|
151
157
|
@model = model
|
|
158
|
+
@reasoning = reasoning
|
|
152
159
|
@extensions = extensions
|
|
153
160
|
@skills = skills
|
|
154
161
|
@agents_md = agents_md
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Riffer::Rig::Commands::Reasoning
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
SAVE = '--save' #: String
|
|
7
|
+
SAVED_TO = '~/.riffer/settings.json' #: String
|
|
8
|
+
DEFAULT = 'default' #: String
|
|
9
|
+
|
|
10
|
+
# @rbs return: Riffer::Rig::Command
|
|
11
|
+
def command
|
|
12
|
+
Riffer::Rig::Command.new(
|
|
13
|
+
'reasoning',
|
|
14
|
+
description: 'Switch the reasoning level for this session: /reasoning high, /reasoning --save',
|
|
15
|
+
extension: :core
|
|
16
|
+
) { |ctx| run(ctx) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs ctx: Riffer::Rig::Command::Context
|
|
20
|
+
# @rbs return: void
|
|
21
|
+
def run(ctx)
|
|
22
|
+
words = ctx.args.split
|
|
23
|
+
save = words.delete(SAVE)
|
|
24
|
+
level = words.join(' ')
|
|
25
|
+
|
|
26
|
+
if level.empty?
|
|
27
|
+
current = ctx.runtime.reasoning
|
|
28
|
+
return ctx.say("Reasoning: #{current || DEFAULT}") unless save
|
|
29
|
+
return ctx.say('No reasoning level is in effect to save') unless current
|
|
30
|
+
|
|
31
|
+
refusal = Riffer::Rig::Settings::File.store_reasoning(current)
|
|
32
|
+
return ctx.host.notify(refusal, level: :error) if refusal
|
|
33
|
+
|
|
34
|
+
return ctx.say("Saved #{current} to #{SAVED_TO}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
rejection = Riffer::Rig::Settings::ModelOptions.reasoning_rejection(level)
|
|
38
|
+
return ctx.say(rejection) if rejection
|
|
39
|
+
|
|
40
|
+
ctx.runtime.reasoning = level
|
|
41
|
+
refusal = Riffer::Rig::Settings::File.store_reasoning(level) if save
|
|
42
|
+
ctx.host.notify(refusal, level: :error) if refusal
|
|
43
|
+
saved = save && !refusal
|
|
44
|
+
ctx.say(saved ? "Reasoning switched to #{level} and saved to #{SAVED_TO}" : "Reasoning switched to #{level}")
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/riffer/rig/env.rb
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
class Riffer::Rig::Env
|
|
4
4
|
# @rbs @source: Hash[String, String]
|
|
5
5
|
|
|
6
|
-
# @dynamic no_color, model
|
|
6
|
+
# @dynamic no_color, model, reasoning
|
|
7
7
|
attr_reader :no_color #: bool
|
|
8
8
|
attr_reader :model #: String?
|
|
9
|
+
attr_reader :reasoning #: String?
|
|
9
10
|
|
|
10
11
|
# @rbs source: Hash[String, String]
|
|
11
12
|
# @rbs return: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
@@ -22,6 +23,7 @@ class Riffer::Rig::Env
|
|
|
22
23
|
@source = source.freeze
|
|
23
24
|
@no_color = @source.key?('NO_COLOR')
|
|
24
25
|
@model = self['RIFFER_MODEL']
|
|
26
|
+
@reasoning = self['RIFFER_REASONING']
|
|
25
27
|
freeze
|
|
26
28
|
end
|
|
27
29
|
|
data/lib/riffer/rig/headless.rb
CHANGED
|
@@ -203,6 +203,8 @@ class Riffer::Rig::Headless
|
|
|
203
203
|
return @output.write(Ndjson.line(event)) if @json
|
|
204
204
|
|
|
205
205
|
case event
|
|
206
|
+
when Riffer::Rig::Events::SessionStart
|
|
207
|
+
@error.puts("session #{event.id}")
|
|
206
208
|
when Riffer::StreamEvents::TextDelta
|
|
207
209
|
write_text(event.content)
|
|
208
210
|
@streamed_text = true
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Loader::Options
|
|
4
|
-
# @dynamic model, extensions, skills, agents_md, tools, max_steps
|
|
4
|
+
# @dynamic model, reasoning, extensions, skills, agents_md, tools, max_steps
|
|
5
5
|
attr_reader :model #: String?
|
|
6
|
+
attr_reader :reasoning #: String?
|
|
6
7
|
attr_reader :extensions #: bool
|
|
7
8
|
attr_reader :skills #: bool
|
|
8
9
|
attr_reader :agents_md #: bool
|
|
@@ -10,6 +11,7 @@ class Riffer::Rig::Loader::Options
|
|
|
10
11
|
attr_reader :max_steps #: Integer?
|
|
11
12
|
|
|
12
13
|
# @rbs model: String?
|
|
14
|
+
# @rbs reasoning: String?
|
|
13
15
|
# @rbs extensions: bool
|
|
14
16
|
# @rbs skills: bool
|
|
15
17
|
# @rbs agents_md: bool
|
|
@@ -18,6 +20,7 @@ class Riffer::Rig::Loader::Options
|
|
|
18
20
|
# @rbs return: void
|
|
19
21
|
def initialize(
|
|
20
22
|
model: nil,
|
|
23
|
+
reasoning: nil,
|
|
21
24
|
extensions: true,
|
|
22
25
|
skills: true,
|
|
23
26
|
agents_md: true,
|
|
@@ -25,6 +28,7 @@ class Riffer::Rig::Loader::Options
|
|
|
25
28
|
max_steps: Riffer::Rig::Runtime::DEFAULT_MAX_STEPS
|
|
26
29
|
)
|
|
27
30
|
@model = model
|
|
31
|
+
@reasoning = reasoning
|
|
28
32
|
@extensions = extensions
|
|
29
33
|
@skills = skills
|
|
30
34
|
@agents_md = agents_md
|
data/lib/riffer/rig/loader.rb
CHANGED
|
@@ -85,11 +85,13 @@ class Riffer::Rig::Loader
|
|
|
85
85
|
|
|
86
86
|
document = Riffer::Rig::Settings::Document.new(settings)
|
|
87
87
|
selected = select_model(options.model || @env.model || document.model)
|
|
88
|
+
check_reasoning(options)
|
|
88
89
|
provider = Riffer::Rig::Settings::ModelOptions.provider_for(selected).to_s
|
|
89
90
|
ensure_sdk(provider)
|
|
90
91
|
credentials = install_credentials(provider)
|
|
91
92
|
raise ConfigurationError, credentials if credentials.is_a?(String)
|
|
92
93
|
|
|
94
|
+
@riffer_config.catalog_files = document.catalog_files
|
|
93
95
|
@extensions_enabled = options.extensions
|
|
94
96
|
@extra_extensions = extra_extensions
|
|
95
97
|
@reload_mode = reload
|
|
@@ -102,6 +104,9 @@ class Riffer::Rig::Loader
|
|
|
102
104
|
@recorder_extensions = recorder ? [Riffer::Rig::Stores::Recorder.extension(recorder)] : []
|
|
103
105
|
loaded += @recorder_extensions
|
|
104
106
|
|
|
107
|
+
snapshot = snapshot_without_forced(snapshot, options)
|
|
108
|
+
reasoning = forced_reasoning(options, snapshot)
|
|
109
|
+
|
|
105
110
|
built = Riffer::Rig::Runtime.new(
|
|
106
111
|
selected,
|
|
107
112
|
extensions: loaded,
|
|
@@ -113,11 +118,11 @@ class Riffer::Rig::Loader
|
|
|
113
118
|
credentials: { provider.to_sym => credentials },
|
|
114
119
|
env: @env,
|
|
115
120
|
auth_path: home_auth_path,
|
|
116
|
-
|
|
121
|
+
reasoning: reasoning || settings_reasoning(document),
|
|
117
122
|
riffer_config: @riffer_config,
|
|
118
|
-
model_options: Riffer::Rig::Settings::ModelOptions.model_options(selected
|
|
123
|
+
model_options: Riffer::Rig::Settings::ModelOptions.model_options(selected),
|
|
119
124
|
native_tools: document.native_tools,
|
|
120
|
-
snapshot:
|
|
125
|
+
snapshot: snapshot
|
|
121
126
|
)
|
|
122
127
|
built.install_command(Riffer::Rig::Commands::Reload.command(self))
|
|
123
128
|
store = @store
|
|
@@ -370,6 +375,55 @@ class Riffer::Rig::Loader
|
|
|
370
375
|
File.join(@home, '.riffer', 'auth.json')
|
|
371
376
|
end
|
|
372
377
|
|
|
378
|
+
# A reasoning level from the flag or the environment is refused, the way a
|
|
379
|
+
# bad model string is; one from the settings document or a session's recorded
|
|
380
|
+
# switch falls back to the model's default instead.
|
|
381
|
+
# @rbs options: Riffer::Rig::Loader::Options
|
|
382
|
+
# @rbs return: void
|
|
383
|
+
def check_reasoning(options)
|
|
384
|
+
forced = options.reasoning || @env.reasoning
|
|
385
|
+
return unless forced
|
|
386
|
+
|
|
387
|
+
rejection = Riffer::Rig::Settings::ModelOptions.reasoning_rejection(forced)
|
|
388
|
+
return unless rejection
|
|
389
|
+
|
|
390
|
+
raise ConfigurationError, options.reasoning ? rejection : "RIFFER_REASONING: #{rejection}"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# The reasoning in effect for the build, over the settings document: the flag,
|
|
394
|
+
# then the session's recorded switch, then RIFFER_REASONING.
|
|
395
|
+
# @rbs options: Riffer::Rig::Loader::Options
|
|
396
|
+
# @rbs snapshot: Hash[Symbol, untyped]?
|
|
397
|
+
# @rbs return: String?
|
|
398
|
+
def forced_reasoning(options, snapshot)
|
|
399
|
+
options.reasoning || recorded_reasoning(snapshot) || @env.reasoning
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# @rbs snapshot: Hash[Symbol, untyped]?
|
|
403
|
+
# @rbs return: String?
|
|
404
|
+
def recorded_reasoning(snapshot)
|
|
405
|
+
saved = snapshot && snapshot[:reasoning]
|
|
406
|
+
Riffer::Rig::Settings::ModelOptions.reasoning_level(saved) if saved.is_a?(String)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# A settings level riffer does not accept silently keeps the model's default.
|
|
410
|
+
# @rbs document: Riffer::Rig::Settings::Document
|
|
411
|
+
# @rbs return: String?
|
|
412
|
+
def settings_reasoning(document)
|
|
413
|
+
Riffer::Rig::Settings::ModelOptions.reasoning_level(document.reasoning)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# A model or reasoning level chosen for the run wins for that run only: it is
|
|
417
|
+
# not recorded, so a later resume restores the session's own switches.
|
|
418
|
+
# @rbs snapshot: Hash[Symbol, untyped]?
|
|
419
|
+
# @rbs options: Riffer::Rig::Loader::Options
|
|
420
|
+
# @rbs return: Hash[Symbol, untyped]?
|
|
421
|
+
def snapshot_without_forced(snapshot, options)
|
|
422
|
+
snapshot = snapshot&.merge(model: nil) if options.model
|
|
423
|
+
snapshot = snapshot&.merge(reasoning: nil) if options.reasoning
|
|
424
|
+
snapshot
|
|
425
|
+
end
|
|
426
|
+
|
|
373
427
|
# The store records unless it was declined with store: nil or globally with
|
|
374
428
|
# "sessions": {"save": false}.
|
|
375
429
|
# @rbs document: Riffer::Rig::Settings::Document
|
data/lib/riffer/rig/registrar.rb
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
class Riffer::Rig::Registrar
|
|
4
4
|
class NameCollisionError < StandardError; end
|
|
5
5
|
|
|
6
|
-
CORE_SETTINGS_KEYS = %w[
|
|
6
|
+
CORE_SETTINGS_KEYS = %w[
|
|
7
|
+
model reasoning catalog_files reload extensions sessions providers tools
|
|
8
|
+
].freeze #: Array[String]
|
|
7
9
|
|
|
8
10
|
EVENTS = %i[
|
|
9
11
|
session_start session_end
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Runtime::Snapshot
|
|
4
|
-
# @dynamic id, messages, model, skills
|
|
4
|
+
# @dynamic id, messages, model, reasoning, skills
|
|
5
5
|
attr_reader :id #: String
|
|
6
6
|
attr_reader :messages #: Array[Riffer::Messages::Base]
|
|
7
7
|
attr_reader :model #: String?
|
|
8
|
+
attr_reader :reasoning #: String?
|
|
8
9
|
attr_reader :skills #: Array[String]
|
|
9
10
|
|
|
10
11
|
# @rbs hash: Hash[Symbol, untyped]
|
|
@@ -17,6 +18,7 @@ class Riffer::Rig::Runtime::Snapshot
|
|
|
17
18
|
# a Session.from_h could also heal orphaned tool calls on load.
|
|
18
19
|
messages: hash.fetch(:messages).map { |stored| message(stored) },
|
|
19
20
|
model: hash.fetch(:model),
|
|
21
|
+
reasoning: hash[:reasoning].is_a?(String) ? hash[:reasoning] : nil,
|
|
20
22
|
skills: hash.fetch(:skills)
|
|
21
23
|
)
|
|
22
24
|
end
|
|
@@ -34,19 +36,21 @@ class Riffer::Rig::Runtime::Snapshot
|
|
|
34
36
|
# @rbs id: String
|
|
35
37
|
# @rbs messages: Array[Riffer::Messages::Base]
|
|
36
38
|
# @rbs model: String?
|
|
39
|
+
# @rbs reasoning: String?
|
|
37
40
|
# @rbs skills: Array[String]
|
|
38
41
|
# @rbs return: void
|
|
39
|
-
def initialize(id:, messages:, model:, skills:)
|
|
42
|
+
def initialize(id:, messages:, model:, skills:, reasoning: nil)
|
|
40
43
|
@id = id
|
|
41
44
|
@messages = messages
|
|
42
45
|
@model = model
|
|
46
|
+
@reasoning = reasoning
|
|
43
47
|
@skills = skills
|
|
44
48
|
freeze
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
# @rbs return: Hash[Symbol, untyped]
|
|
48
52
|
def to_h
|
|
49
|
-
{ id: @id, messages: @messages.map(&:to_h), model: @model, skills: @skills }
|
|
53
|
+
{ id: @id, messages: @messages.map(&:to_h), model: @model, reasoning: @reasoning, skills: @skills }
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
# @rbs return: Riffer::Agent::Session
|
data/lib/riffer/rig/runtime.rb
CHANGED
|
@@ -61,8 +61,10 @@ class Riffer::Rig::Runtime
|
|
|
61
61
|
# @rbs @session_start_pending: bool
|
|
62
62
|
# @rbs @session_start_reason: Symbol
|
|
63
63
|
# @rbs @model_override: String?
|
|
64
|
+
# @rbs @reasoning: String?
|
|
64
65
|
# @rbs @message_observers: Array[^(Riffer::Messages::Base) -> void]
|
|
65
66
|
# @rbs @model_observers: Array[^(String) -> void]
|
|
67
|
+
# @rbs @reasoning_observers: Array[^(String) -> void]
|
|
66
68
|
# @rbs @system_prompt: Riffer::Rig::Runtime::SystemPrompt
|
|
67
69
|
# @rbs @commands: Hash[String, Riffer::Rig::Command]
|
|
68
70
|
# @rbs @core_commands: Array[Riffer::Rig::Command]
|
|
@@ -97,7 +99,7 @@ class Riffer::Rig::Runtime
|
|
|
97
99
|
# @rbs credentials: Hash[Symbol, Hash[Symbol, String]]
|
|
98
100
|
# @rbs env: Riffer::Rig::Env
|
|
99
101
|
# @rbs auth_path: String
|
|
100
|
-
# @rbs
|
|
102
|
+
# @rbs reasoning: String?
|
|
101
103
|
# @rbs riffer_config: Riffer::Config
|
|
102
104
|
# @rbs mcp_registry: Riffer::Rig::Mcp::_Registry
|
|
103
105
|
# @rbs max_steps: Integer?
|
|
@@ -118,7 +120,7 @@ class Riffer::Rig::Runtime
|
|
|
118
120
|
credentials: {},
|
|
119
121
|
env: Riffer::Rig::Env.new,
|
|
120
122
|
auth_path: Riffer::Rig::Credentials::PATH,
|
|
121
|
-
|
|
123
|
+
reasoning: nil,
|
|
122
124
|
riffer_config: Riffer.config,
|
|
123
125
|
mcp_registry: Riffer::Mcp,
|
|
124
126
|
max_steps: DEFAULT_MAX_STEPS,
|
|
@@ -147,7 +149,8 @@ class Riffer::Rig::Runtime
|
|
|
147
149
|
@session_start_reason = saved ? :restore : :new
|
|
148
150
|
@message_observers = []
|
|
149
151
|
@model_observers = []
|
|
150
|
-
|
|
152
|
+
@reasoning_observers = []
|
|
153
|
+
@reasoning = reasoning
|
|
151
154
|
@errors = []
|
|
152
155
|
@tool_allowlist = tools
|
|
153
156
|
@native_tools = native_tools
|
|
@@ -257,6 +260,21 @@ class Riffer::Rig::Runtime
|
|
|
257
260
|
@model_observers.each { |observer| observer.call(model) }
|
|
258
261
|
end
|
|
259
262
|
|
|
263
|
+
# @rbs return: String?
|
|
264
|
+
def reasoning
|
|
265
|
+
@reasoning || Riffer::Rig::Settings::ModelOptions.reasoning_level(Riffer::Rig::Settings::Document.new(@settings).reasoning)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @rbs level: String
|
|
269
|
+
# @rbs return: void
|
|
270
|
+
def reasoning=(level)
|
|
271
|
+
# Riffer's own validation raises before anything is assigned, so a level it
|
|
272
|
+
# refuses leaves both the level and the config unchanged.
|
|
273
|
+
@agent.config.reasoning = level
|
|
274
|
+
@reasoning = level
|
|
275
|
+
@reasoning_observers.each { |observer| observer.call(level) }
|
|
276
|
+
end
|
|
277
|
+
|
|
260
278
|
# @rbs provider: Symbol
|
|
261
279
|
# @rbs values: Hash[Symbol, String]
|
|
262
280
|
# @rbs return: void
|
|
@@ -288,7 +306,11 @@ class Riffer::Rig::Runtime
|
|
|
288
306
|
# @rbs return: Hash[Symbol, untyped]
|
|
289
307
|
def to_h
|
|
290
308
|
Riffer::Rig::Runtime::Snapshot.new(
|
|
291
|
-
id: @id,
|
|
309
|
+
id: @id,
|
|
310
|
+
messages: @agent.session.messages,
|
|
311
|
+
model: @model_override,
|
|
312
|
+
reasoning: @reasoning,
|
|
313
|
+
skills: activated_skills
|
|
292
314
|
).to_h
|
|
293
315
|
end
|
|
294
316
|
|
|
@@ -306,6 +328,13 @@ class Riffer::Rig::Runtime
|
|
|
306
328
|
nil
|
|
307
329
|
end
|
|
308
330
|
|
|
331
|
+
# @rbs &block: (String) -> void
|
|
332
|
+
# @rbs return: nil
|
|
333
|
+
def on_reasoning_change(&block)
|
|
334
|
+
@reasoning_observers << block
|
|
335
|
+
nil
|
|
336
|
+
end
|
|
337
|
+
|
|
309
338
|
# @rbs &: (Riffer::Rig::Runtime::JobCompletion) -> void
|
|
310
339
|
# @rbs return: nil
|
|
311
340
|
def on_job_exit(&)
|
|
@@ -388,15 +417,15 @@ class Riffer::Rig::Runtime
|
|
|
388
417
|
end
|
|
389
418
|
|
|
390
419
|
# The options the settings built for the starting model are provider-shaped
|
|
391
|
-
# (cache control
|
|
392
|
-
#
|
|
393
|
-
# agent's options alone; riffer
|
|
394
|
-
#
|
|
420
|
+
# (anthropic cache control), so a switch re-derives them; the reasoning level
|
|
421
|
+
# is provider-agnostic and travels on the config. Runs after the successor is
|
|
422
|
+
# built, so a failed build leaves the running agent's options alone; riffer
|
|
423
|
+
# reads config.model_options per request, so the swap still reaches the new
|
|
424
|
+
# agent through the shared config.
|
|
395
425
|
# @rbs model: String
|
|
396
426
|
# @rbs return: void
|
|
397
427
|
def rederive_options(model)
|
|
398
|
-
|
|
399
|
-
@model_options = Riffer::Rig::Settings::ModelOptions.model_options(model, reasoning).merge(native_options(model))
|
|
428
|
+
@model_options = Riffer::Rig::Settings::ModelOptions.model_options(model).merge(native_options(model))
|
|
400
429
|
@agent.config.model_options = @model_options
|
|
401
430
|
end
|
|
402
431
|
|
|
@@ -423,6 +452,7 @@ class Riffer::Rig::Runtime
|
|
|
423
452
|
model: ->(context) { context[:model] },
|
|
424
453
|
model_options: @model_options,
|
|
425
454
|
instructions: @system_prompt.bare,
|
|
455
|
+
reasoning: @reasoning,
|
|
426
456
|
tools_config: select_tools(registrars.flat_map { |registrar| registrar.tools.to_a }.to_h.values, @tool_allowlist),
|
|
427
457
|
max_steps: max_steps,
|
|
428
458
|
tool_runtime: Riffer::Rig::Runtime::ToolRuntime.new(hooks, emit: ->(event) { emit(event) }, clock: @clock),
|
|
@@ -462,6 +492,7 @@ class Riffer::Rig::Runtime
|
|
|
462
492
|
*@core_commands,
|
|
463
493
|
Riffer::Rig::Commands::Auth.command,
|
|
464
494
|
Riffer::Rig::Commands::Model.command,
|
|
495
|
+
Riffer::Rig::Commands::Reasoning.command,
|
|
465
496
|
Riffer::Rig::Commands::Cost.command,
|
|
466
497
|
*skills.map { |skill| Riffer::Rig::Commands::Skill.command(skill) },
|
|
467
498
|
*registrars.flat_map { |registrar| registrar.commands.values }
|
|
@@ -795,6 +826,7 @@ class Riffer::Rig::Runtime
|
|
|
795
826
|
# @rbs return: Riffer::Agent
|
|
796
827
|
def restore(snapshot, model, config)
|
|
797
828
|
@model_override = snapshot.restorable_model(@credentials, @host, model)
|
|
829
|
+
@reasoning = Riffer::Rig::Settings::ModelOptions.reasoning_level(snapshot.reasoning) if snapshot.reasoning
|
|
798
830
|
agent = build_agent(@model_override || model, config, session: snapshot.session)
|
|
799
831
|
agent.context.token_usage = snapshot.usage
|
|
800
832
|
reactivate(agent.context.skills, snapshot.skills)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Settings::Document
|
|
4
|
-
# @dynamic model, reasoning,
|
|
4
|
+
# @dynamic model, reasoning, catalog_files, providers, disabled, autoload, native_tools, save, reload
|
|
5
5
|
attr_reader :model #: String?
|
|
6
6
|
attr_reader :reasoning #: String?
|
|
7
|
-
attr_reader :
|
|
7
|
+
attr_reader :catalog_files #: Array[String]
|
|
8
8
|
attr_reader :providers #: Hash[String, Hash[String, String]]
|
|
9
9
|
attr_reader :disabled #: Array[String]
|
|
10
10
|
attr_reader :autoload #: bool
|
|
@@ -17,9 +17,9 @@ class Riffer::Rig::Settings::Document
|
|
|
17
17
|
def initialize(source)
|
|
18
18
|
@model = source[:model].is_a?(String) ? source[:model] : nil
|
|
19
19
|
@reasoning = source[:reasoning].is_a?(String) ? source[:reasoning] : nil
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
paths = source[:catalog_files]
|
|
21
|
+
no_files = [] #: Array[String]
|
|
22
|
+
@catalog_files = paths.is_a?(Array) && paths.all?(String) ? paths.dup.freeze : no_files.freeze
|
|
23
23
|
@providers = hash_or_empty(source[:providers]).filter_map do |identifier, fields|
|
|
24
24
|
if fields.is_a?(Hash)
|
|
25
25
|
[identifier.to_s, fields.select do |_name, value|
|
|
@@ -24,4 +24,11 @@ module Riffer::Rig::Settings::File
|
|
|
24
24
|
def store_model(model, path: PATH)
|
|
25
25
|
update(path) { |document| document.merge(model: model) }
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
# @rbs level: String
|
|
29
|
+
# @rbs path: String
|
|
30
|
+
# @rbs return: String?
|
|
31
|
+
def store_reasoning(level, path: PATH)
|
|
32
|
+
update(path) { |document| document.merge(reasoning: level) }
|
|
33
|
+
end
|
|
27
34
|
end
|