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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +37 -4
  4. data/exe/riffer +1 -1
  5. data/lib/riffer/rig/bundled/agents_md.rb +7 -0
  6. data/lib/riffer/rig/bundled/bash.rb +5 -0
  7. data/lib/riffer/rig/bundled/edit.rb +5 -0
  8. data/lib/riffer/rig/bundled/mcp.rb +9 -0
  9. data/lib/riffer/rig/bundled/read.rb +5 -0
  10. data/lib/riffer/rig/bundled/skills.rb +10 -0
  11. data/lib/riffer/rig/bundled/write.rb +5 -0
  12. data/lib/riffer/rig/bundled.rb +7 -0
  13. data/lib/riffer/rig/cli/flags.rb +77 -0
  14. data/lib/riffer/rig/cli.rb +69 -106
  15. data/lib/riffer/rig/command/context.rb +70 -0
  16. data/lib/riffer/rig/command.rb +29 -0
  17. data/lib/riffer/rig/commands/model.rb +54 -0
  18. data/lib/riffer/rig/commands/skill.rb +27 -0
  19. data/lib/riffer/rig/commands.rb +4 -0
  20. data/lib/riffer/rig/credentials.rb +2 -2
  21. data/lib/riffer/rig/directories.rb +20 -0
  22. data/lib/riffer/rig/env/invalid.rb +13 -0
  23. data/lib/riffer/rig/env.rb +12 -1
  24. data/lib/riffer/rig/events/after_response.rb +25 -0
  25. data/lib/riffer/rig/events/after_tool_call.rb +31 -0
  26. data/lib/riffer/rig/events/before_prompt.rb +25 -0
  27. data/lib/riffer/rig/events/before_request.rb +25 -0
  28. data/lib/riffer/rig/events/before_tool_call.rb +28 -0
  29. data/lib/riffer/rig/events/command_output.rb +3 -2
  30. data/lib/riffer/rig/events/notify.rb +3 -2
  31. data/lib/riffer/rig/events/session_end.rb +3 -2
  32. data/lib/riffer/rig/events/session_start.rb +3 -2
  33. data/lib/riffer/rig/events/skill_activated.rb +3 -2
  34. data/lib/riffer/rig/events/turn_end.rb +3 -2
  35. data/lib/riffer/rig/extension/failure.rb +16 -0
  36. data/lib/riffer/rig/extension.rb +16 -1
  37. data/lib/riffer/rig/hosts/mirror.rb +6 -7
  38. data/lib/riffer/rig/hosts/null.rb +1 -1
  39. data/lib/riffer/rig/loader.rb +163 -0
  40. data/lib/riffer/rig/mcp/declaration.rb +23 -0
  41. data/lib/riffer/rig/mcp/server.rb +16 -0
  42. data/lib/riffer/rig/mcp.rb +12 -0
  43. data/lib/riffer/rig/prompts/agents_md.rb +32 -0
  44. data/lib/riffer/rig/prompts.rb +4 -0
  45. data/lib/riffer/rig/registrar.rb +120 -5
  46. data/lib/riffer/rig/runtime/blocked.rb +13 -0
  47. data/lib/riffer/rig/runtime/hooks.rb +78 -0
  48. data/lib/riffer/rig/runtime/request_guardrail.rb +24 -0
  49. data/lib/riffer/rig/runtime/tool_runtime.rb +44 -0
  50. data/lib/riffer/rig/runtime.rb +506 -57
  51. data/lib/riffer/rig/settings/document.rb +27 -16
  52. data/lib/riffer/rig/settings/pricing.rb +16 -5
  53. data/lib/riffer/rig/settings.rb +73 -43
  54. data/lib/riffer/rig/skills/sources.rb +28 -0
  55. data/lib/riffer/rig/skills.rb +12 -0
  56. data/lib/riffer/rig/{events/event.rb → support/equatable.rb} +2 -11
  57. data/lib/riffer/rig/{ui → terminal}/animator.rb +4 -4
  58. data/lib/riffer/rig/{ui → terminal}/banner.rb +17 -21
  59. data/lib/riffer/rig/{ui → terminal}/cursor.rb +4 -4
  60. data/lib/riffer/rig/terminal/host.rb +85 -0
  61. data/lib/riffer/rig/{ui → terminal}/palette.rb +1 -1
  62. data/lib/riffer/rig/{ui → terminal}/renderer.rb +115 -66
  63. data/lib/riffer/rig/{ui → terminal}/smoother.rb +4 -4
  64. data/lib/riffer/rig/{ui → terminal}/theme.rb +12 -12
  65. data/lib/riffer/rig/terminal.rb +207 -0
  66. data/lib/riffer/rig/tools/bash.rb +6 -4
  67. data/lib/riffer/rig/tools/edit.rb +3 -2
  68. data/lib/riffer/rig/tools/read.rb +3 -2
  69. data/lib/riffer/rig/tools/write.rb +3 -2
  70. data/lib/riffer/rig/version.rb +1 -1
  71. data/lib/riffer/rig.rb +8 -8
  72. data/sig/generated/riffer/rig/bundled/agents_md.rbs +5 -0
  73. data/sig/generated/riffer/rig/bundled/bash.rbs +5 -0
  74. data/sig/generated/riffer/rig/bundled/edit.rbs +5 -0
  75. data/sig/generated/riffer/rig/bundled/mcp.rbs +5 -0
  76. data/sig/generated/riffer/rig/bundled/read.rbs +5 -0
  77. data/sig/generated/riffer/rig/bundled/skills.rbs +5 -0
  78. data/sig/generated/riffer/rig/bundled/write.rbs +5 -0
  79. data/sig/generated/riffer/rig/bundled.rbs +5 -0
  80. data/sig/generated/riffer/rig/cli/flags.rbs +40 -0
  81. data/sig/generated/riffer/rig/cli.rbs +30 -35
  82. data/sig/generated/riffer/rig/command/context.rbs +50 -0
  83. data/sig/generated/riffer/rig/command.rbs +23 -0
  84. data/sig/generated/riffer/rig/commands/model.rbs +27 -0
  85. data/sig/generated/riffer/rig/commands/skill.rbs +12 -0
  86. data/sig/generated/riffer/rig/commands.rbs +4 -0
  87. data/sig/generated/riffer/rig/credentials.rbs +4 -4
  88. data/sig/generated/riffer/rig/directories.rbs +11 -0
  89. data/sig/generated/riffer/rig/env/invalid.rbs +10 -0
  90. data/sig/generated/riffer/rig/env.rbs +7 -1
  91. data/sig/generated/riffer/rig/events/after_response.rbs +18 -0
  92. data/sig/generated/riffer/rig/events/after_tool_call.rbs +24 -0
  93. data/sig/generated/riffer/rig/events/before_prompt.rbs +18 -0
  94. data/sig/generated/riffer/rig/events/before_request.rbs +18 -0
  95. data/sig/generated/riffer/rig/events/before_tool_call.rbs +21 -0
  96. data/sig/generated/riffer/rig/events/command_output.rbs +3 -1
  97. data/sig/generated/riffer/rig/events/notify.rbs +3 -1
  98. data/sig/generated/riffer/rig/events/session_end.rbs +3 -1
  99. data/sig/generated/riffer/rig/events/session_start.rbs +3 -1
  100. data/sig/generated/riffer/rig/events/skill_activated.rbs +3 -1
  101. data/sig/generated/riffer/rig/events/turn_end.rbs +3 -1
  102. data/sig/generated/riffer/rig/extension/failure.rbs +13 -0
  103. data/sig/generated/riffer/rig/extension.rbs +14 -1
  104. data/sig/generated/riffer/rig/hosts/mirror.rbs +9 -9
  105. data/sig/generated/riffer/rig/hosts/null.rbs +1 -1
  106. data/sig/generated/riffer/rig/loader.rbs +77 -0
  107. data/sig/generated/riffer/rig/mcp/declaration.rbs +18 -0
  108. data/sig/generated/riffer/rig/mcp/server.rbs +13 -0
  109. data/sig/generated/riffer/rig/mcp.rbs +8 -0
  110. data/sig/generated/riffer/rig/prompts/agents_md.rbs +19 -0
  111. data/sig/generated/riffer/rig/prompts.rbs +4 -0
  112. data/sig/generated/riffer/rig/registrar.rbs +78 -4
  113. data/sig/generated/riffer/rig/runtime/blocked.rbs +10 -0
  114. data/sig/generated/riffer/rig/runtime/hooks.rbs +49 -0
  115. data/sig/generated/riffer/rig/runtime/request_guardrail.rbs +16 -0
  116. data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +23 -0
  117. data/sig/generated/riffer/rig/runtime.rbs +225 -22
  118. data/sig/generated/riffer/rig/settings/document.rbs +11 -11
  119. data/sig/generated/riffer/rig/settings/pricing.rbs +7 -2
  120. data/sig/generated/riffer/rig/settings.rbs +36 -23
  121. data/sig/generated/riffer/rig/skills/sources.rbs +18 -0
  122. data/sig/generated/riffer/rig/skills.rbs +7 -0
  123. data/sig/generated/riffer/rig/support/equatable.rbs +15 -0
  124. data/sig/generated/riffer/rig/{ui → terminal}/animator.rbs +5 -5
  125. data/sig/generated/riffer/rig/terminal/banner.rbs +39 -0
  126. data/sig/generated/riffer/rig/{ui → terminal}/cursor.rbs +5 -5
  127. data/sig/generated/riffer/rig/terminal/host.rbs +50 -0
  128. data/sig/generated/riffer/rig/{ui → terminal}/palette.rbs +2 -2
  129. data/sig/generated/riffer/rig/terminal/renderer.rbs +126 -0
  130. data/sig/generated/riffer/rig/{ui → terminal}/smoother.rbs +5 -5
  131. data/sig/generated/riffer/rig/{ui → terminal}/theme.rbs +5 -5
  132. data/sig/generated/riffer/rig/terminal.rbs +85 -0
  133. data/sig/generated/riffer/rig/tools/bash.rbs +2 -1
  134. data/sig/generated/riffer/rig.rbs +4 -0
  135. data/sig/manual/riffer/rig/commands/model.rbs +6 -0
  136. data/sig/manual/riffer/rig/commands/skill.rbs +6 -0
  137. data/sig/manual/riffer/rig/directories.rbs +6 -0
  138. data/sig/manual/riffer/rig/events.rbs +6 -0
  139. data/sig/manual/riffer/rig/hosts.rbs +7 -0
  140. data/sig/manual/riffer/rig/mcp.rbs +11 -0
  141. data/sig/manual/riffer/rig/prompts/agents_md.rbs +6 -0
  142. data/sig/manual/riffer/rig/{ui/banner.rbs → skills.rbs} +2 -2
  143. data/sig/manual/riffer/rig/support.rbs +8 -0
  144. data/sig/manual/riffer/rig/terminal/banner.rbs +6 -0
  145. metadata +140 -27
  146. data/lib/riffer/rig/coding_agent.rb +0 -53
  147. data/lib/riffer/rig/hosts/base.rb +0 -36
  148. data/lib/riffer/rig/repl.rb +0 -188
  149. data/lib/riffer/rig/token_tally.rb +0 -54
  150. data/sig/generated/riffer/rig/coding_agent.rbs +0 -9
  151. data/sig/generated/riffer/rig/events/event.rbs +0 -20
  152. data/sig/generated/riffer/rig/hosts/base.rbs +0 -26
  153. data/sig/generated/riffer/rig/repl.rbs +0 -69
  154. data/sig/generated/riffer/rig/token_tally.rbs +0 -42
  155. data/sig/generated/riffer/rig/ui/banner.rbs +0 -42
  156. data/sig/generated/riffer/rig/ui/renderer.rbs +0 -94
  157. 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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Prompts
4
+ end
@@ -1,19 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Riffer::Rig::Registrar
4
- # @rbs @tools: Array[singleton(Riffer::Tool)]
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
- @tools = []
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 << klass
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 return: Array[singleton(Riffer::Tool)]
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Riffer::Rig::Runtime::Blocked
4
+ # @dynamic reason
5
+ attr_reader :reason #: String
6
+
7
+ # @rbs reason: String
8
+ # @rbs return: void
9
+ def initialize(reason)
10
+ @reason = reason
11
+ freeze
12
+ end
13
+ 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