riffer-rig 0.6.0 → 0.7.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 +36 -0
- data/README.md +37 -4
- data/exe/riffer +1 -1
- data/lib/riffer/rig/bundled/agents_md.rb +7 -0
- data/lib/riffer/rig/bundled/bash.rb +5 -0
- data/lib/riffer/rig/bundled/edit.rb +5 -0
- data/lib/riffer/rig/bundled/mcp.rb +9 -0
- data/lib/riffer/rig/bundled/read.rb +5 -0
- data/lib/riffer/rig/bundled/skills.rb +10 -0
- data/lib/riffer/rig/bundled/write.rb +5 -0
- data/lib/riffer/rig/bundled.rb +7 -0
- data/lib/riffer/rig/cli/flags.rb +77 -0
- data/lib/riffer/rig/cli.rb +69 -106
- data/lib/riffer/rig/command/context.rb +70 -0
- data/lib/riffer/rig/command.rb +29 -0
- data/lib/riffer/rig/commands/model.rb +54 -0
- data/lib/riffer/rig/commands/skill.rb +27 -0
- data/lib/riffer/rig/commands.rb +4 -0
- data/lib/riffer/rig/credentials.rb +2 -2
- data/lib/riffer/rig/directories.rb +20 -0
- data/lib/riffer/rig/env/invalid.rb +13 -0
- data/lib/riffer/rig/env.rb +12 -1
- data/lib/riffer/rig/events/after_response.rb +25 -0
- data/lib/riffer/rig/events/after_tool_call.rb +31 -0
- data/lib/riffer/rig/events/before_prompt.rb +25 -0
- data/lib/riffer/rig/events/before_request.rb +25 -0
- data/lib/riffer/rig/events/before_tool_call.rb +28 -0
- data/lib/riffer/rig/events/command_output.rb +3 -2
- data/lib/riffer/rig/events/notify.rb +3 -2
- data/lib/riffer/rig/events/session_end.rb +3 -2
- data/lib/riffer/rig/events/session_start.rb +3 -2
- data/lib/riffer/rig/events/skill_activated.rb +3 -2
- data/lib/riffer/rig/events/turn_end.rb +3 -2
- data/lib/riffer/rig/extension/failure.rb +16 -0
- data/lib/riffer/rig/extension.rb +16 -1
- data/lib/riffer/rig/hosts/mirror.rb +6 -7
- data/lib/riffer/rig/hosts/null.rb +1 -1
- data/lib/riffer/rig/loader.rb +163 -0
- data/lib/riffer/rig/mcp/declaration.rb +23 -0
- data/lib/riffer/rig/mcp/server.rb +16 -0
- data/lib/riffer/rig/mcp.rb +12 -0
- data/lib/riffer/rig/prompts/agents_md.rb +32 -0
- data/lib/riffer/rig/prompts.rb +4 -0
- data/lib/riffer/rig/registrar.rb +120 -5
- data/lib/riffer/rig/runtime/blocked.rb +13 -0
- data/lib/riffer/rig/runtime/hooks.rb +78 -0
- data/lib/riffer/rig/runtime/request_guardrail.rb +24 -0
- data/lib/riffer/rig/runtime/tool_runtime.rb +44 -0
- data/lib/riffer/rig/runtime.rb +506 -57
- data/lib/riffer/rig/settings/document.rb +27 -16
- data/lib/riffer/rig/settings/pricing.rb +16 -5
- data/lib/riffer/rig/settings.rb +73 -43
- data/lib/riffer/rig/skills/sources.rb +28 -0
- data/lib/riffer/rig/skills.rb +12 -0
- data/lib/riffer/rig/{events/event.rb → support/equatable.rb} +2 -11
- data/lib/riffer/rig/{ui → terminal}/animator.rb +4 -4
- data/lib/riffer/rig/{ui → terminal}/banner.rb +17 -21
- data/lib/riffer/rig/{ui → terminal}/cursor.rb +4 -4
- data/lib/riffer/rig/terminal/host.rb +85 -0
- data/lib/riffer/rig/{ui → terminal}/palette.rb +1 -1
- data/lib/riffer/rig/{ui → terminal}/renderer.rb +115 -66
- data/lib/riffer/rig/{ui → terminal}/smoother.rb +4 -4
- data/lib/riffer/rig/{ui → terminal}/theme.rb +12 -12
- data/lib/riffer/rig/terminal.rb +207 -0
- data/lib/riffer/rig/tools/bash.rb +6 -4
- data/lib/riffer/rig/tools/edit.rb +3 -2
- data/lib/riffer/rig/tools/read.rb +3 -2
- data/lib/riffer/rig/tools/write.rb +3 -2
- data/lib/riffer/rig/version.rb +1 -1
- data/lib/riffer/rig.rb +8 -8
- data/sig/generated/riffer/rig/bundled/agents_md.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/bash.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/edit.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/mcp.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/read.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/skills.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/write.rbs +5 -0
- data/sig/generated/riffer/rig/bundled.rbs +5 -0
- data/sig/generated/riffer/rig/cli/flags.rbs +40 -0
- data/sig/generated/riffer/rig/cli.rbs +30 -35
- data/sig/generated/riffer/rig/command/context.rbs +50 -0
- data/sig/generated/riffer/rig/command.rbs +23 -0
- data/sig/generated/riffer/rig/commands/model.rbs +27 -0
- data/sig/generated/riffer/rig/commands/skill.rbs +12 -0
- data/sig/generated/riffer/rig/commands.rbs +4 -0
- data/sig/generated/riffer/rig/credentials.rbs +4 -4
- data/sig/generated/riffer/rig/directories.rbs +11 -0
- data/sig/generated/riffer/rig/env/invalid.rbs +10 -0
- data/sig/generated/riffer/rig/env.rbs +7 -1
- data/sig/generated/riffer/rig/events/after_response.rbs +18 -0
- data/sig/generated/riffer/rig/events/after_tool_call.rbs +24 -0
- data/sig/generated/riffer/rig/events/before_prompt.rbs +18 -0
- data/sig/generated/riffer/rig/events/before_request.rbs +18 -0
- data/sig/generated/riffer/rig/events/before_tool_call.rbs +21 -0
- data/sig/generated/riffer/rig/events/command_output.rbs +3 -1
- data/sig/generated/riffer/rig/events/notify.rbs +3 -1
- data/sig/generated/riffer/rig/events/session_end.rbs +3 -1
- data/sig/generated/riffer/rig/events/session_start.rbs +3 -1
- data/sig/generated/riffer/rig/events/skill_activated.rbs +3 -1
- data/sig/generated/riffer/rig/events/turn_end.rbs +3 -1
- data/sig/generated/riffer/rig/extension/failure.rbs +13 -0
- data/sig/generated/riffer/rig/extension.rbs +14 -1
- data/sig/generated/riffer/rig/hosts/mirror.rbs +9 -9
- data/sig/generated/riffer/rig/hosts/null.rbs +1 -1
- data/sig/generated/riffer/rig/loader.rbs +77 -0
- data/sig/generated/riffer/rig/mcp/declaration.rbs +18 -0
- data/sig/generated/riffer/rig/mcp/server.rbs +13 -0
- data/sig/generated/riffer/rig/mcp.rbs +8 -0
- data/sig/generated/riffer/rig/prompts/agents_md.rbs +19 -0
- data/sig/generated/riffer/rig/prompts.rbs +4 -0
- data/sig/generated/riffer/rig/registrar.rbs +78 -4
- data/sig/generated/riffer/rig/runtime/blocked.rbs +10 -0
- data/sig/generated/riffer/rig/runtime/hooks.rbs +49 -0
- data/sig/generated/riffer/rig/runtime/request_guardrail.rbs +16 -0
- data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +23 -0
- data/sig/generated/riffer/rig/runtime.rbs +225 -22
- data/sig/generated/riffer/rig/settings/document.rbs +11 -11
- data/sig/generated/riffer/rig/settings/pricing.rbs +7 -2
- data/sig/generated/riffer/rig/settings.rbs +36 -23
- data/sig/generated/riffer/rig/skills/sources.rbs +18 -0
- data/sig/generated/riffer/rig/skills.rbs +7 -0
- data/sig/generated/riffer/rig/support/equatable.rbs +15 -0
- data/sig/generated/riffer/rig/{ui → terminal}/animator.rbs +5 -5
- data/sig/generated/riffer/rig/terminal/banner.rbs +39 -0
- data/sig/generated/riffer/rig/{ui → terminal}/cursor.rbs +5 -5
- data/sig/generated/riffer/rig/terminal/host.rbs +50 -0
- data/sig/generated/riffer/rig/{ui → terminal}/palette.rbs +2 -2
- data/sig/generated/riffer/rig/terminal/renderer.rbs +126 -0
- data/sig/generated/riffer/rig/{ui → terminal}/smoother.rbs +5 -5
- data/sig/generated/riffer/rig/{ui → terminal}/theme.rbs +5 -5
- data/sig/generated/riffer/rig/terminal.rbs +85 -0
- data/sig/generated/riffer/rig/tools/bash.rbs +2 -1
- data/sig/generated/riffer/rig.rbs +4 -0
- data/sig/manual/riffer/rig/commands/model.rbs +6 -0
- data/sig/manual/riffer/rig/commands/skill.rbs +6 -0
- data/sig/manual/riffer/rig/directories.rbs +6 -0
- data/sig/manual/riffer/rig/events.rbs +6 -0
- data/sig/manual/riffer/rig/hosts.rbs +7 -0
- data/sig/manual/riffer/rig/mcp.rbs +11 -0
- data/sig/manual/riffer/rig/prompts/agents_md.rbs +6 -0
- data/sig/manual/riffer/rig/{ui/banner.rbs → skills.rbs} +2 -2
- data/sig/manual/riffer/rig/support.rbs +8 -0
- data/sig/manual/riffer/rig/terminal/banner.rbs +6 -0
- metadata +140 -27
- data/lib/riffer/rig/coding_agent.rb +0 -53
- data/lib/riffer/rig/hosts/base.rb +0 -36
- data/lib/riffer/rig/repl.rb +0 -188
- data/lib/riffer/rig/token_tally.rb +0 -54
- data/sig/generated/riffer/rig/coding_agent.rbs +0 -9
- data/sig/generated/riffer/rig/events/event.rbs +0 -20
- data/sig/generated/riffer/rig/hosts/base.rbs +0 -26
- data/sig/generated/riffer/rig/repl.rbs +0 -69
- data/sig/generated/riffer/rig/token_tally.rbs +0 -42
- data/sig/generated/riffer/rig/ui/banner.rbs +0 -42
- data/sig/generated/riffer/rig/ui/renderer.rbs +0 -94
- data/sig/manual/riffer/rig/ui.rbs +0 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Riffer::Rig::Prompts::AgentsMd
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
FRAMING = 'The user wrote the following instructions in AGENTS.md files, ordered from the most general to ' \
|
|
7
|
+
'the one closest to the working directory. Follow them; where they conflict with the guidance ' \
|
|
8
|
+
'above, they take precedence, and where they conflict with each other, the later file wins.' #: String
|
|
9
|
+
|
|
10
|
+
# @rbs cwd: String
|
|
11
|
+
# @rbs return: Array[String]
|
|
12
|
+
def paths(cwd)
|
|
13
|
+
global = File.join(Dir.home, '.riffer', 'AGENTS.md')
|
|
14
|
+
project = Riffer::Rig::Directories.ancestors(File.expand_path(cwd)).reverse.map { |dir| File.join(dir, 'AGENTS.md') }
|
|
15
|
+
[global, *project].uniq.select { |path| File.file?(path) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @rbs cwd: String
|
|
19
|
+
# @rbs return: String?
|
|
20
|
+
def section(cwd)
|
|
21
|
+
blocks = paths(cwd).map { |path| block(path) }
|
|
22
|
+
[FRAMING, *blocks].join("\n\n") unless blocks.empty?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @rbs path: String
|
|
28
|
+
# @rbs return: String
|
|
29
|
+
def block(path)
|
|
30
|
+
"<project_instructions path=\"#{path}\">\n#{File.read(path)}\n</project_instructions>"
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/riffer/rig/registrar.rb
CHANGED
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Registrar
|
|
4
|
-
|
|
4
|
+
class NameCollisionError < StandardError; end
|
|
5
|
+
|
|
6
|
+
CORE_SETTINGS_KEYS = %w[model reasoning models reload extensions sessions providers tools].freeze #: Array[String]
|
|
7
|
+
|
|
8
|
+
EVENTS = %i[
|
|
9
|
+
session_start session_end
|
|
10
|
+
before_prompt before_tool_call before_request
|
|
11
|
+
after_tool_call after_response turn_end
|
|
12
|
+
stream
|
|
13
|
+
].freeze #: Array[Symbol]
|
|
14
|
+
|
|
15
|
+
# @rbs @tools: Hash[String, singleton(Riffer::Tool)]
|
|
5
16
|
# @rbs @prompts: Hash[Symbol, ^(Riffer::Rig::Runtime) -> String?]
|
|
17
|
+
# @rbs @commands: Hash[String, Riffer::Rig::Command]
|
|
18
|
+
# @rbs @skill_sources: Array[^(Riffer::Rig::Runtime) -> Riffer::Skills::Backend]
|
|
19
|
+
# @rbs @declared_settings: Hash[Symbol, untyped]
|
|
20
|
+
# @rbs @given_settings: Hash[Symbol, untyped]
|
|
21
|
+
# @rbs @mcp_servers: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
22
|
+
# @rbs @hooks: Hash[Symbol, Array[^(Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped]]
|
|
6
23
|
|
|
24
|
+
# @dynamic extension
|
|
25
|
+
attr_reader :extension #: String
|
|
26
|
+
|
|
27
|
+
# @rbs extension: String
|
|
28
|
+
# @rbs settings: Hash[Symbol, untyped]
|
|
7
29
|
# @rbs return: void
|
|
8
|
-
def initialize
|
|
9
|
-
@
|
|
30
|
+
def initialize(extension, settings = {})
|
|
31
|
+
@extension = extension
|
|
32
|
+
@tools = {}
|
|
10
33
|
@prompts = {}
|
|
34
|
+
@commands = {}
|
|
35
|
+
@skill_sources = []
|
|
36
|
+
@declared_settings = {}
|
|
37
|
+
@given_settings = settings
|
|
38
|
+
@mcp_servers = {}
|
|
39
|
+
@hooks = EVENTS.to_h { |event| [event, []] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs return: NameCollisionError?
|
|
43
|
+
def collision
|
|
44
|
+
return unless CORE_SETTINGS_KEYS.include?(@extension)
|
|
45
|
+
|
|
46
|
+
NameCollisionError.new("extension name #{@extension} collides with a core settings key")
|
|
11
47
|
end
|
|
12
48
|
|
|
13
49
|
# @rbs klass: singleton(Riffer::Tool)
|
|
14
50
|
# @rbs return: void
|
|
15
51
|
def tool(klass)
|
|
16
|
-
@tools
|
|
52
|
+
@tools[klass.identifier] = klass
|
|
17
53
|
end
|
|
18
54
|
|
|
19
55
|
# @rbs name: Symbol
|
|
@@ -23,7 +59,46 @@ class Riffer::Rig::Registrar
|
|
|
23
59
|
@prompts[name] = block
|
|
24
60
|
end
|
|
25
61
|
|
|
26
|
-
# @rbs
|
|
62
|
+
# @rbs name: String
|
|
63
|
+
# @rbs description: String
|
|
64
|
+
# @rbs &block: (Riffer::Rig::Command::Context) -> void
|
|
65
|
+
# @rbs return: void
|
|
66
|
+
def command(name, description:, &)
|
|
67
|
+
@commands[name] = Riffer::Rig::Command.new(name, description: description, extension: @extension, &)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @rbs &block: (Riffer::Rig::Runtime) -> Riffer::Skills::Backend
|
|
71
|
+
# @rbs return: void
|
|
72
|
+
def skills(&block)
|
|
73
|
+
@skill_sources << block
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @rbs key: Symbol
|
|
77
|
+
# @rbs default: untyped
|
|
78
|
+
# @rbs return: void
|
|
79
|
+
def setting(key, default:)
|
|
80
|
+
@declared_settings[key] = default
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @rbs name: String
|
|
84
|
+
# @rbs url: String
|
|
85
|
+
# @rbs headers: Hash[String, String]
|
|
86
|
+
# @rbs return: void
|
|
87
|
+
def mcp(name, url:, headers: {})
|
|
88
|
+
@mcp_servers[name] = Riffer::Rig::Mcp::Declaration.new(url: url, headers: headers)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs event: Symbol
|
|
92
|
+
# @rbs &block: (Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped
|
|
93
|
+
# @rbs return: void
|
|
94
|
+
def on(event, &block)
|
|
95
|
+
hooks = @hooks.fetch(event) do
|
|
96
|
+
raise Riffer::ArgumentError, "unknown event #{event.inspect}; expected one of #{EVENTS.join(', ')}"
|
|
97
|
+
end
|
|
98
|
+
hooks << block
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs return: Hash[String, singleton(Riffer::Tool)]
|
|
27
102
|
def tools
|
|
28
103
|
@tools.dup
|
|
29
104
|
end
|
|
@@ -32,4 +107,44 @@ class Riffer::Rig::Registrar
|
|
|
32
107
|
def prompts
|
|
33
108
|
@prompts.dup
|
|
34
109
|
end
|
|
110
|
+
|
|
111
|
+
# @rbs return: Hash[String, Riffer::Rig::Command]
|
|
112
|
+
def commands
|
|
113
|
+
@commands.dup
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @rbs return: Array[^(Riffer::Rig::Runtime) -> Riffer::Skills::Backend]
|
|
117
|
+
def skill_sources
|
|
118
|
+
@skill_sources.dup
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
122
|
+
def settings
|
|
123
|
+
@declared_settings.merge(@given_settings)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
127
|
+
def declared_settings
|
|
128
|
+
@declared_settings.dup
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
132
|
+
def mcp_servers
|
|
133
|
+
@mcp_servers.dup
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @rbs return: Array[String]
|
|
137
|
+
def registrations
|
|
138
|
+
[
|
|
139
|
+
*@tools.keys.map { |identifier| "tool #{identifier}" },
|
|
140
|
+
*@prompts.keys.map { |name| "prompt section #{name}" },
|
|
141
|
+
*@commands.keys.map { |name| "command #{name}" },
|
|
142
|
+
*@mcp_servers.keys.map { |name| "MCP server #{name}" }
|
|
143
|
+
]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @rbs return: Hash[Symbol, Array[^(Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped]]
|
|
147
|
+
def hooks
|
|
148
|
+
@hooks.transform_values(&:dup)
|
|
149
|
+
end
|
|
35
150
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Riffer::Rig::Runtime::Hooks
|
|
4
|
+
# @rbs @hooks: Hash[Symbol, Array[^(Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped]]
|
|
5
|
+
# @rbs @host: Riffer::Rig::Hosts::_Host
|
|
6
|
+
|
|
7
|
+
# @rbs hooks: Hash[Symbol, Array[^(Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped]]
|
|
8
|
+
# @rbs host: Riffer::Rig::Hosts::_Host
|
|
9
|
+
# @rbs return: void
|
|
10
|
+
def initialize(hooks, host)
|
|
11
|
+
@hooks = hooks
|
|
12
|
+
@host = host
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @rbs name: Symbol
|
|
16
|
+
# @rbs event: Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base
|
|
17
|
+
# @rbs return: void
|
|
18
|
+
def observe(name, event)
|
|
19
|
+
@hooks.fetch(name).each { |hook| call(name, hook, event) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @rbs text: String
|
|
23
|
+
# @rbs return: String | Riffer::Rig::Runtime::Blocked
|
|
24
|
+
def before_prompt(text)
|
|
25
|
+
accepts = ->(value) { value.is_a?(String) }
|
|
26
|
+
announce(veto(:before_prompt, text, accepts) { |current| Riffer::Rig::Events::BeforePrompt.new(current) })
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @rbs tool: String
|
|
30
|
+
# @rbs args: Hash[Symbol, untyped]
|
|
31
|
+
# @rbs return: Hash[Symbol, untyped] | Riffer::Rig::Runtime::Blocked
|
|
32
|
+
def before_tool_call(tool, args)
|
|
33
|
+
accepts = ->(value) { value.is_a?(Hash) }
|
|
34
|
+
veto(:before_tool_call, args, accepts) { |current| Riffer::Rig::Events::BeforeToolCall.new(tool, current) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs messages: Array[::Riffer::Messages::Base]
|
|
38
|
+
# @rbs return: Array[::Riffer::Messages::Base] | Riffer::Rig::Runtime::Blocked
|
|
39
|
+
def before_request(messages)
|
|
40
|
+
accepts = ->(value) { value.is_a?(Array) && value.all?(::Riffer::Messages::Base) }
|
|
41
|
+
announce(veto(:before_request, messages, accepts) { |current| Riffer::Rig::Events::BeforeRequest.new(current) })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# @rbs name: Symbol
|
|
47
|
+
# @rbs payload: untyped
|
|
48
|
+
# @rbs accepts: ^(untyped) -> bool
|
|
49
|
+
# @rbs &: (untyped) -> Riffer::Rig::Events::_Event
|
|
50
|
+
# @rbs return: untyped
|
|
51
|
+
def veto(name, payload, accepts)
|
|
52
|
+
@hooks.fetch(name).reduce(payload) do |current, hook|
|
|
53
|
+
result = call(name, hook, yield(current))
|
|
54
|
+
verdict, reason = result
|
|
55
|
+
break Riffer::Rig::Runtime::Blocked.new(reason&.to_s || "blocked by a #{name} hook") if verdict == :block
|
|
56
|
+
|
|
57
|
+
accepts.call(result) ? result : current
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @rbs outcome: untyped
|
|
62
|
+
# @rbs return: untyped
|
|
63
|
+
def announce(outcome)
|
|
64
|
+
@host.notify(outcome.reason, level: :warning) if outcome.is_a?(Riffer::Rig::Runtime::Blocked)
|
|
65
|
+
outcome
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @rbs name: Symbol
|
|
69
|
+
# @rbs hook: ^(Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base) -> untyped
|
|
70
|
+
# @rbs event: Riffer::Rig::Events::_Event | ::Riffer::StreamEvents::Base
|
|
71
|
+
# @rbs return: untyped
|
|
72
|
+
def call(name, hook, event)
|
|
73
|
+
hook.call(event)
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
@host.notify("#{name} hook failed: #{e.message}", level: :error)
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# riffer runs its before guardrails once per run, just ahead of the first
|
|
4
|
+
# request; the Runtime's on_message observer covers the requests after it.
|
|
5
|
+
class Riffer::Rig::Runtime::RequestGuardrail < Riffer::Guardrail
|
|
6
|
+
# @rbs @hooks: Riffer::Rig::Runtime::Hooks
|
|
7
|
+
|
|
8
|
+
# @rbs hooks: Riffer::Rig::Runtime::Hooks
|
|
9
|
+
# @rbs return: void
|
|
10
|
+
def initialize(hooks:)
|
|
11
|
+
super()
|
|
12
|
+
@hooks = hooks
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @rbs messages: Array[Riffer::Messages::Base]
|
|
16
|
+
# @rbs context: untyped
|
|
17
|
+
# @rbs return: Riffer::Guardrails::Result
|
|
18
|
+
def process_input(messages, context:)
|
|
19
|
+
verdict = @hooks.before_request(messages)
|
|
20
|
+
return block(verdict.reason) if verdict.is_a?(Riffer::Rig::Runtime::Blocked)
|
|
21
|
+
|
|
22
|
+
verdict.equal?(messages) ? pass(messages) : transform(verdict.dup)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
class Riffer::Rig::Runtime::ToolRuntime < Riffer::Tools::Runtime
|
|
6
|
+
# @rbs @hooks: Riffer::Rig::Runtime::Hooks
|
|
7
|
+
|
|
8
|
+
# @rbs hooks: Riffer::Rig::Runtime::Hooks
|
|
9
|
+
# @rbs return: void
|
|
10
|
+
def initialize(hooks)
|
|
11
|
+
# Sequential, so hooks never run concurrently.
|
|
12
|
+
super(runner: Riffer::Runner::Sequential.new)
|
|
13
|
+
@hooks = hooks
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# @rbs tool_call: Riffer::Messages::Assistant::ToolCall
|
|
19
|
+
# @rbs tools: Array[singleton(Riffer::Tool)]
|
|
20
|
+
# @rbs context: Riffer::Agent::Context?
|
|
21
|
+
# @rbs assistant_message: Riffer::Messages::Assistant?
|
|
22
|
+
# @rbs return: Riffer::Tools::Response
|
|
23
|
+
def dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil)
|
|
24
|
+
args = parse_arguments(tool_call.arguments)
|
|
25
|
+
return super unless args.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
verdict = @hooks.before_tool_call(tool_call.name, args)
|
|
28
|
+
return Riffer::Tools::Response.error(verdict.reason, type: :blocked) if verdict.is_a?(Riffer::Rig::Runtime::Blocked)
|
|
29
|
+
|
|
30
|
+
call = verdict.equal?(args) ? tool_call : replace_arguments(tool_call, verdict)
|
|
31
|
+
response = super(call, tools: tools, context: context, assistant_message: assistant_message)
|
|
32
|
+
@hooks.observe(:after_tool_call, Riffer::Rig::Events::AfterToolCall.new(tool_call.name, verdict, response))
|
|
33
|
+
response
|
|
34
|
+
rescue JSON::ParserError
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs tool_call: Riffer::Messages::Assistant::ToolCall
|
|
39
|
+
# @rbs args: Hash[Symbol, untyped]
|
|
40
|
+
# @rbs return: Riffer::Messages::Assistant::ToolCall
|
|
41
|
+
def replace_arguments(tool_call, args)
|
|
42
|
+
Riffer::Messages::Assistant::ToolCall.new(call_id: tool_call.call_id, name: tool_call.name, arguments: args.to_json)
|
|
43
|
+
end
|
|
44
|
+
end
|