roast-ai 0.4.9 → 0.5.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/.claude/commands/docs/write-comments.md +36 -0
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/ci.yaml +10 -6
- data/.gitignore +0 -1
- data/.rubocop.yml +7 -1
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +18 -18
- data/Gemfile.lock +46 -57
- data/README.md +118 -1432
- data/README_LEGACY.md +1464 -0
- data/Rakefile +39 -4
- data/dev.yml +29 -0
- data/dsl/agent_sessions.rb +20 -0
- data/dsl/async_cogs.rb +49 -0
- data/dsl/async_cogs_complex.rb +67 -0
- data/dsl/call.rb +44 -0
- data/dsl/collect_from.rb +72 -0
- data/dsl/demo/Gemfile +4 -0
- data/dsl/demo/Gemfile.lock +120 -0
- data/dsl/demo/cogs/local.rb +15 -0
- data/dsl/demo/simple_external_cog.rb +17 -0
- data/dsl/json_output.rb +28 -0
- data/dsl/map.rb +55 -0
- data/dsl/map_reduce.rb +37 -0
- data/dsl/map_with_index.rb +49 -0
- data/dsl/next_break.rb +40 -0
- data/dsl/next_break_parallel.rb +44 -0
- data/dsl/outputs.rb +39 -0
- data/dsl/outputs_bang.rb +36 -0
- data/dsl/parallel_map.rb +37 -0
- data/dsl/plugin-gem-example/.gitignore +8 -0
- data/dsl/plugin-gem-example/Gemfile +13 -0
- data/dsl/plugin-gem-example/Gemfile.lock +178 -0
- data/dsl/plugin-gem-example/lib/other.rb +17 -0
- data/dsl/plugin-gem-example/lib/plugin_gem_example.rb +5 -0
- data/dsl/plugin-gem-example/lib/simple.rb +15 -0
- data/dsl/plugin-gem-example/lib/version.rb +10 -0
- data/dsl/plugin-gem-example/plugin-gem-example.gemspec +28 -0
- data/dsl/prompts/simple_prompt.md.erb +3 -0
- data/dsl/prototype.rb +10 -4
- data/dsl/repeat_loop_results.rb +53 -0
- data/dsl/ruby_cog.rb +72 -0
- data/dsl/simple_agent.rb +18 -0
- data/dsl/simple_chat.rb +26 -0
- data/dsl/simple_repeat.rb +29 -0
- data/dsl/skip.rb +36 -0
- data/dsl/step_communication.rb +10 -5
- data/dsl/targets_and_params.rb +57 -0
- data/dsl/temperature.rb +17 -0
- data/dsl/temporary_directory.rb +22 -0
- data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
- data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
- data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
- data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
- data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
- data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
- data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
- data/dsl/tutorial/03_targets_and_params/README.md +230 -0
- data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
- data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
- data/dsl/tutorial/04_configuration_options/README.md +209 -0
- data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
- data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
- data/dsl/tutorial/05_control_flow/README.md +156 -0
- data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
- data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
- data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
- data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
- data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
- data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
- data/dsl/tutorial/07_processing_collections/README.md +152 -0
- data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
- data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
- data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
- data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
- data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
- data/dsl/tutorial/09_async_cogs/README.md +197 -0
- data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
- data/dsl/tutorial/README.md +222 -0
- data/dsl/working_directory.rb +16 -0
- data/exe/roast +1 -1
- data/internal/documentation/architectural-notes.md +115 -0
- data/internal/documentation/doc-comments-external.md +686 -0
- data/internal/documentation/doc-comments-internal.md +342 -0
- data/internal/documentation/doc-comments.md +211 -0
- data/lib/roast/dsl/cog/config.rb +280 -4
- data/lib/roast/dsl/cog/input.rb +73 -0
- data/lib/roast/dsl/cog/output.rb +313 -0
- data/lib/roast/dsl/cog/registry.rb +71 -0
- data/lib/roast/dsl/cog/stack.rb +3 -2
- data/lib/roast/dsl/cog/store.rb +11 -8
- data/lib/roast/dsl/cog.rb +108 -31
- data/lib/roast/dsl/cog_input_context.rb +44 -0
- data/lib/roast/dsl/cog_input_manager.rb +156 -0
- data/lib/roast/dsl/cogs/agent/config.rb +465 -0
- data/lib/roast/dsl/cogs/agent/input.rb +81 -0
- data/lib/roast/dsl/cogs/agent/output.rb +59 -0
- data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
- data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
- data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
- data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
- data/lib/roast/dsl/cogs/agent.rb +75 -0
- data/lib/roast/dsl/cogs/chat/config.rb +453 -0
- data/lib/roast/dsl/cogs/chat/input.rb +92 -0
- data/lib/roast/dsl/cogs/chat/output.rb +64 -0
- data/lib/roast/dsl/cogs/chat/session.rb +68 -0
- data/lib/roast/dsl/cogs/chat.rb +81 -0
- data/lib/roast/dsl/cogs/cmd.rb +291 -27
- data/lib/roast/dsl/cogs/ruby.rb +171 -0
- data/lib/roast/dsl/command_runner.rb +191 -0
- data/lib/roast/dsl/config_context.rb +2 -47
- data/lib/roast/dsl/config_manager.rb +143 -0
- data/lib/roast/dsl/control_flow.rb +41 -0
- data/lib/roast/dsl/execution_context.rb +9 -0
- data/lib/roast/dsl/execution_manager.rb +267 -0
- data/lib/roast/dsl/nil_assertions.rb +23 -0
- data/lib/roast/dsl/system_cog/params.rb +32 -0
- data/lib/roast/dsl/system_cog.rb +36 -0
- data/lib/roast/dsl/system_cogs/call.rb +162 -0
- data/lib/roast/dsl/system_cogs/map.rb +448 -0
- data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
- data/lib/roast/dsl/workflow.rb +123 -0
- data/lib/roast/dsl/workflow_context.rb +20 -0
- data/lib/roast/dsl/workflow_params.rb +24 -0
- data/lib/roast/sorbet_runtime_stub.rb +154 -0
- data/lib/roast/tools/apply_diff.rb +1 -3
- data/lib/roast/tools/cmd.rb +4 -3
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +1 -1
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_workflow.rb +4 -0
- data/lib/roast/workflow/step_loader.rb +14 -2
- data/lib/roast-ai.rb +4 -0
- data/lib/roast.rb +60 -22
- data/{roast.gemspec → roast-ai.gemspec} +10 -13
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
- data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
- data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
- data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
- data/sorbet/rbi/gems/marcel@1.1.0.rbi +239 -0
- data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
- data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +5703 -0
- data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
- data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1197 -0
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +314 -2
- data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +498 -0
- data/sorbet/tapioca/config.yml +6 -0
- data/sorbet/tapioca/require.rb +2 -0
- metadata +198 -34
- data/dsl/less_simple.rb +0 -112
- data/dsl/simple.rb +0 -8
- data/lib/roast/dsl/cog_execution_context.rb +0 -29
- data/lib/roast/dsl/cogs/graph.rb +0 -53
- data/lib/roast/dsl/cogs.rb +0 -65
- data/lib/roast/dsl/executor.rb +0 -82
- data/lib/roast/dsl/workflow_execution_context.rb +0 -47
- data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
- data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
- data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
- data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
- data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
- data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
- data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
- data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
- data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
- data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
- data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
- data/sorbet/rbi/gems/rack@2.2.18.rbi +0 -5659
- data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +0 -2170
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
- data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +0 -11
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module DSL
|
|
6
|
+
# Context in which the `execute` block of a workflow is evaluated
|
|
7
|
+
class ExecutionManager
|
|
8
|
+
include SystemCogs::Call::Manager
|
|
9
|
+
include SystemCogs::Map::Manager
|
|
10
|
+
include SystemCogs::Repeat::Manager
|
|
11
|
+
|
|
12
|
+
class ExecutionManagerError < Roast::Error; end
|
|
13
|
+
|
|
14
|
+
class ExecutionManagerNotPreparedError < ExecutionManagerError; end
|
|
15
|
+
|
|
16
|
+
class ExecutionManagerAlreadyPreparedError < ExecutionManagerError; end
|
|
17
|
+
|
|
18
|
+
class ExecutionManagerCurrentlyRunningError < ExecutionManagerError; end
|
|
19
|
+
|
|
20
|
+
class ExecutionScopeDoesNotExistError < ExecutionManagerError; end
|
|
21
|
+
|
|
22
|
+
class ExecutionScopeNotSpecifiedError < ExecutionManagerError; end
|
|
23
|
+
|
|
24
|
+
class IllegalCogNameError < ExecutionManagerError; end
|
|
25
|
+
|
|
26
|
+
class OutputsAlreadyDefinedError < ExecutionManagerError; end
|
|
27
|
+
|
|
28
|
+
#: untyped
|
|
29
|
+
attr_reader :final_output
|
|
30
|
+
|
|
31
|
+
#: (
|
|
32
|
+
#| Cog::Registry,
|
|
33
|
+
#| ConfigManager,
|
|
34
|
+
#| Hash[Symbol?, Array[^() -> void]],
|
|
35
|
+
#| WorkflowContext,
|
|
36
|
+
#| ?scope: Symbol?,
|
|
37
|
+
#| ?scope_value: untyped?,
|
|
38
|
+
#| ?scope_index: Integer
|
|
39
|
+
#| ) -> void
|
|
40
|
+
def initialize(
|
|
41
|
+
cog_registry,
|
|
42
|
+
config_manager,
|
|
43
|
+
all_execution_procs,
|
|
44
|
+
workflow_context,
|
|
45
|
+
scope: nil,
|
|
46
|
+
scope_value: nil,
|
|
47
|
+
scope_index: 0
|
|
48
|
+
)
|
|
49
|
+
@cog_registry = cog_registry
|
|
50
|
+
@config_manager = config_manager
|
|
51
|
+
@all_execution_procs = all_execution_procs
|
|
52
|
+
@workflow_context = workflow_context
|
|
53
|
+
@scope = scope
|
|
54
|
+
@scope_value = scope_value
|
|
55
|
+
@scope_index = scope_index
|
|
56
|
+
@cogs = Cog::Store.new #: Cog::Store
|
|
57
|
+
@cog_stack = Cog::Stack.new #: Cog::Stack
|
|
58
|
+
@execution_context = ExecutionContext.new #: ExecutionContext
|
|
59
|
+
@cog_input_manager = CogInputManager.new(@cog_registry, @cogs, @workflow_context) #: CogInputManager
|
|
60
|
+
@barrier = Async::Barrier.new #: Async::Barrier
|
|
61
|
+
@final_output = nil #: untyped
|
|
62
|
+
@final_output_computed = false #: bool
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#: () -> void
|
|
66
|
+
def prepare!
|
|
67
|
+
raise ExecutionManagerAlreadyPreparedError if preparing? || prepared?
|
|
68
|
+
|
|
69
|
+
@preparing = true
|
|
70
|
+
bind_outputs
|
|
71
|
+
bind_registered_cogs
|
|
72
|
+
my_execution_procs.each { |ep| @execution_context.instance_eval(&ep) }
|
|
73
|
+
@prepared = true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run!
|
|
77
|
+
raise ExecutionManagerNotPreparedError unless prepared?
|
|
78
|
+
raise ExecutionManagerCurrentlyRunningError if running?
|
|
79
|
+
|
|
80
|
+
@running = true
|
|
81
|
+
Sync do |sync_task|
|
|
82
|
+
sync_task.annotate("ExecutionManager #{@scope}")
|
|
83
|
+
@cog_stack.each do |cog|
|
|
84
|
+
cog_config = @config_manager.config_for(cog.class, cog.name)
|
|
85
|
+
cog_task = cog.run!(
|
|
86
|
+
@barrier,
|
|
87
|
+
cog_config.deep_dup,
|
|
88
|
+
cog_input_context,
|
|
89
|
+
@scope_value.deep_dup,
|
|
90
|
+
@scope_index,
|
|
91
|
+
)
|
|
92
|
+
cog_task.wait unless cog_config.async?
|
|
93
|
+
end
|
|
94
|
+
# Wait on the tasks in their completion order, so that an exception in a task will be raised as soon as it occurs
|
|
95
|
+
# noinspection RubyArgCount
|
|
96
|
+
@barrier.wait { |task| wait_for_task_with_exception_handling(task) }
|
|
97
|
+
compute_final_output # eagerly compute the final output (so it, too, can 'break!' subsequent executions in a loop)
|
|
98
|
+
ensure
|
|
99
|
+
@barrier.stop
|
|
100
|
+
compute_final_output
|
|
101
|
+
@running = false
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#: () -> void
|
|
106
|
+
def stop!
|
|
107
|
+
@barrier.stop
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#: () -> bool
|
|
111
|
+
def preparing?
|
|
112
|
+
@preparing ||= false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
#: () -> bool
|
|
116
|
+
def prepared?
|
|
117
|
+
@prepared ||= false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#: () -> bool
|
|
121
|
+
def running?
|
|
122
|
+
@running ||= false
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#: () -> CogInputContext
|
|
126
|
+
def cog_input_context
|
|
127
|
+
raise ExecutionManagerNotPreparedError unless prepared?
|
|
128
|
+
|
|
129
|
+
@cog_input_manager.context
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
#: (Async::Task) -> void
|
|
135
|
+
def wait_for_task_with_exception_handling(task)
|
|
136
|
+
task.wait
|
|
137
|
+
rescue ControlFlow::Next
|
|
138
|
+
# TODO: do something with the message passed to next!
|
|
139
|
+
@barrier.stop
|
|
140
|
+
rescue ControlFlow::Break => e
|
|
141
|
+
@barrier.stop
|
|
142
|
+
compute_final_output # make sure the final output is always computed, even if the iteration is broken
|
|
143
|
+
raise e
|
|
144
|
+
rescue StandardError => e
|
|
145
|
+
@barrier.stop
|
|
146
|
+
raise e
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
#: () -> Array[^() -> void]
|
|
150
|
+
def my_execution_procs
|
|
151
|
+
raise ExecutionScopeDoesNotExistError, @scope unless @all_execution_procs.key?(@scope)
|
|
152
|
+
|
|
153
|
+
@all_execution_procs[@scope] || []
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#: (Cog) -> void
|
|
157
|
+
def add_cog_instance(cog)
|
|
158
|
+
@cogs.insert(cog)
|
|
159
|
+
@cog_stack.push(cog)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# TODO: add typing for output
|
|
163
|
+
#: (Symbol) -> untyped
|
|
164
|
+
def output(name)
|
|
165
|
+
@cogs[name].output
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#: () -> void
|
|
169
|
+
def bind_registered_cogs
|
|
170
|
+
@cog_registry.cogs.each { |cog_method_name, cog_class| bind_cog(cog_method_name, cog_class) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#: (Symbol, singleton(Cog)) -> void
|
|
174
|
+
def bind_cog(cog_method_name, cog_class)
|
|
175
|
+
on_execute_method = method(:on_execute)
|
|
176
|
+
cog_method = proc do |*args, **kwargs, &cog_input_proc|
|
|
177
|
+
on_execute_method.call(cog_class, args, kwargs, cog_input_proc)
|
|
178
|
+
end
|
|
179
|
+
@execution_context.instance_eval do
|
|
180
|
+
raise IllegalCogNameError, cog_method_name if respond_to?(cog_method_name, true)
|
|
181
|
+
|
|
182
|
+
define_singleton_method(cog_method_name, cog_method)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#: (singleton(Cog), Array[untyped], Hash[Symbol, untyped], ^(Cog::Input) -> untyped) -> void
|
|
187
|
+
def on_execute(cog_class, cog_args, cog_kwargs, cog_input_proc)
|
|
188
|
+
# Called when the cog method is invoked in the workflow's 'execute' block.
|
|
189
|
+
# This creates the cog instance and prepares it for execution.
|
|
190
|
+
if cog_class <= SystemCog
|
|
191
|
+
untyped_cog_class = cog_class #: as untyped // to remove warning about splats of unknown length
|
|
192
|
+
cog_params = untyped_cog_class.params_class.new(*cog_args, **cog_kwargs)
|
|
193
|
+
cog_instance = if cog_class == SystemCogs::Call
|
|
194
|
+
create_call_system_cog(cog_params, cog_input_proc)
|
|
195
|
+
elsif cog_class == SystemCogs::Map
|
|
196
|
+
create_map_system_cog(cog_params, cog_input_proc)
|
|
197
|
+
elsif cog_class == SystemCogs::Repeat
|
|
198
|
+
create_repeat_system_cog(cog_params, cog_input_proc)
|
|
199
|
+
else
|
|
200
|
+
raise NotImplementedError, "No system cog manager defined for #{cog_class}"
|
|
201
|
+
end
|
|
202
|
+
else
|
|
203
|
+
cog_name = Array.wrap(cog_args).shift || Cog.generate_fallback_name
|
|
204
|
+
cog_instance = cog_class.new(cog_name, cog_input_proc)
|
|
205
|
+
end
|
|
206
|
+
add_cog_instance(cog_instance)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def bind_outputs
|
|
210
|
+
on_outputs_method = method(:on_outputs)
|
|
211
|
+
on_outputs_bang_method = method(:on_outputs!)
|
|
212
|
+
method_to_bind = proc { |&outputs_proc| on_outputs_method.call(outputs_proc) }
|
|
213
|
+
bang_method_to_bind = proc { |&outputs_proc| on_outputs_bang_method.call(outputs_proc) }
|
|
214
|
+
@execution_context.instance_eval do
|
|
215
|
+
define_singleton_method(:outputs, method_to_bind)
|
|
216
|
+
define_singleton_method(:outputs!, bang_method_to_bind)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
#: (^(untyped, Integer) -> untyped) -> void
|
|
221
|
+
def on_outputs(outputs)
|
|
222
|
+
raise OutputsAlreadyDefinedError if @outputs || @outputs_bang
|
|
223
|
+
|
|
224
|
+
@outputs = outputs
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
#: (^(untyped, Integer) -> untyped) -> void
|
|
228
|
+
def on_outputs!(outputs)
|
|
229
|
+
raise OutputsAlreadyDefinedError if @outputs || @outputs_bang
|
|
230
|
+
|
|
231
|
+
@outputs_bang = outputs
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
#: () -> untyped
|
|
235
|
+
def compute_final_output
|
|
236
|
+
return if @final_output_computed
|
|
237
|
+
|
|
238
|
+
@final_output_computed = true
|
|
239
|
+
outputs_proc = @outputs_bang || @outputs
|
|
240
|
+
|
|
241
|
+
@final_output = if outputs_proc
|
|
242
|
+
@cog_input_manager.context.instance_exec(@scope_value, @scope_index, &outputs_proc)
|
|
243
|
+
else
|
|
244
|
+
last_cog_name = @cog_stack.last&.name
|
|
245
|
+
raise CogInputManager::CogDoesNotExistError, "no cogs defined in scope" unless last_cog_name
|
|
246
|
+
|
|
247
|
+
@cog_input_manager.send(:cog_output, last_cog_name)
|
|
248
|
+
end
|
|
249
|
+
rescue ControlFlow::SkipCog, ControlFlow::Next
|
|
250
|
+
# TODO: do something with the message passed to the control flow statement
|
|
251
|
+
# Swallow skip! and next! control flow statements in the outputs block
|
|
252
|
+
# Calling these will just make the final output `nil`.
|
|
253
|
+
# (As will calling `break!`, but it gets handled elsewhere.)
|
|
254
|
+
# Calling `fail!` inside `outputs` should actually raise an exception.
|
|
255
|
+
rescue CogInputManager::CogNotYetRunError, CogInputManager::CogSkippedError, CogInputManager::CogStoppedError => e
|
|
256
|
+
# Attempting to accessing a cog that was skipped, stopped, or did not run from inside an `outputs` block
|
|
257
|
+
# is more likely to happen when the user `break!`s from a loop. Allowing this access not to result in an
|
|
258
|
+
# exception getting raised immediately will reduce boilerplate code needed to check if the loop was broken
|
|
259
|
+
# and return nil or some fallback value if it was, and the normal outputs value otherwise.
|
|
260
|
+
#
|
|
261
|
+
# Using `outputs` to define the scope's outputs will swallow these exceptions.
|
|
262
|
+
# Using `outputs!` instead will cause the exceptions to be raised.
|
|
263
|
+
raise e if @outputs_bang.present?
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Kernel
|
|
5
|
+
#: -> self
|
|
6
|
+
def not_nil!
|
|
7
|
+
self
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class NilClass
|
|
12
|
+
# @override
|
|
13
|
+
#: -> bot
|
|
14
|
+
def not_nil!
|
|
15
|
+
raise UnexpectedNilError
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class UnexpectedNilError < StandardError
|
|
20
|
+
def initialize(message = "Unexpected nil value encountered.")
|
|
21
|
+
super(message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module DSL
|
|
6
|
+
class SystemCog
|
|
7
|
+
# Parameters for system cogs set at workflow evaluation time
|
|
8
|
+
#
|
|
9
|
+
# Params are used to provide limited evaluation-time parameterization to system cogs,
|
|
10
|
+
# such as the name of the execute scope to be invoked by a `call`, `map`, a, `repeat` cog.
|
|
11
|
+
#
|
|
12
|
+
# System cogs also accept input at execution time, just like regular cogs.
|
|
13
|
+
class Params
|
|
14
|
+
# The name identifier for this system cog instance
|
|
15
|
+
#
|
|
16
|
+
# Used to reference this cog's output. Auto-generated if not provided.
|
|
17
|
+
#
|
|
18
|
+
#: Symbol
|
|
19
|
+
attr_reader :name
|
|
20
|
+
|
|
21
|
+
# Initialize parameters with the cog name
|
|
22
|
+
#
|
|
23
|
+
# Subclasses should define their own `initialize` accepting specific parameters.
|
|
24
|
+
#
|
|
25
|
+
#: (Symbol?) -> void
|
|
26
|
+
def initialize(name)
|
|
27
|
+
@name = name || Cog.generate_fallback_name
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module DSL
|
|
6
|
+
class SystemCog < Cog
|
|
7
|
+
class << self
|
|
8
|
+
#: () -> singleton(SystemCog::Params)
|
|
9
|
+
def params_class
|
|
10
|
+
@params_class ||= find_child_params_or_default
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
#: () -> singleton(SystemCog::Params)
|
|
16
|
+
def find_child_params_or_default
|
|
17
|
+
config_constant = "#{name}::Params"
|
|
18
|
+
const_defined?(config_constant) ? const_get(config_constant) : SystemCog::Params # rubocop:disable Sorbet/ConstantsFromStrings
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#: (Symbol, ^(Cog::Input) -> untyped) { (Cog::Input, Cog::Config) -> Cog::Output } -> void
|
|
23
|
+
def initialize(name, cog_input_proc, &on_execute)
|
|
24
|
+
super(name, cog_input_proc)
|
|
25
|
+
@on_execute = on_execute
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#: (Cog::Input) -> Cog::Output
|
|
29
|
+
def execute(input)
|
|
30
|
+
# The `on_execute` callback allows a system cog to pass its execution back to the ExecutionManager
|
|
31
|
+
# for special handling.
|
|
32
|
+
@on_execute.call(input, @config)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module DSL
|
|
6
|
+
module SystemCogs
|
|
7
|
+
# Call cog for invoking named execution scopes
|
|
8
|
+
#
|
|
9
|
+
# Executes a named execution scope (defined with `execute(:name)`) with a provided value
|
|
10
|
+
# and index. The scope runs independently and can access the value and index through
|
|
11
|
+
# special variables.
|
|
12
|
+
class Call < SystemCog
|
|
13
|
+
class Config < Cog::Config; end
|
|
14
|
+
|
|
15
|
+
# Parameters for the call system cog
|
|
16
|
+
class Params < SystemCog::Params
|
|
17
|
+
# The name of the execution scope to invoke
|
|
18
|
+
#
|
|
19
|
+
#: Symbol
|
|
20
|
+
attr_accessor :run
|
|
21
|
+
|
|
22
|
+
#: (?Symbol?, run: Symbol) -> void
|
|
23
|
+
def initialize(name = nil, run:)
|
|
24
|
+
super(name)
|
|
25
|
+
@run = run
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Input for the call system cog
|
|
30
|
+
#
|
|
31
|
+
# Provides the value and index to be passed to the execution scope. The scope
|
|
32
|
+
# can access these through the implicit variables available in the execution context.
|
|
33
|
+
class Input < Cog::Input
|
|
34
|
+
# The value to pass to the execution scope
|
|
35
|
+
#
|
|
36
|
+
# This value becomes available in the called scope and can be accessed by steps
|
|
37
|
+
# within that scope. Required.
|
|
38
|
+
#
|
|
39
|
+
#: untyped
|
|
40
|
+
attr_accessor :value
|
|
41
|
+
|
|
42
|
+
# The index value to pass to the execution scope
|
|
43
|
+
#
|
|
44
|
+
# Defaults to 0. Can be used to track position when calling scopes in a sequence.
|
|
45
|
+
#
|
|
46
|
+
# Integer
|
|
47
|
+
attr_accessor :index
|
|
48
|
+
|
|
49
|
+
#: () -> void
|
|
50
|
+
def initialize
|
|
51
|
+
super
|
|
52
|
+
@index = 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Validate that required input values are present
|
|
56
|
+
#
|
|
57
|
+
#: () -> void
|
|
58
|
+
def validate!
|
|
59
|
+
raise Cog::Input::InvalidInputError, "'value' is required" if value.nil? && !coerce_ran?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Coerce the input from the return value of the input block
|
|
63
|
+
#
|
|
64
|
+
# Sets the value from the input block's return value if not already set directly.
|
|
65
|
+
#
|
|
66
|
+
def coerce(input_return_value)
|
|
67
|
+
super
|
|
68
|
+
@value = input_return_value unless @value.present?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Output from running the `call` cog
|
|
73
|
+
#
|
|
74
|
+
# Contains the result from the called scope. Use the `from` method to retrieve
|
|
75
|
+
# the final output from the scope's execution.
|
|
76
|
+
#
|
|
77
|
+
# #### See Also
|
|
78
|
+
# - `Roast::DSL::CogInputContext#from` - retrieves output from a `call` cog
|
|
79
|
+
class Output < Cog::Output
|
|
80
|
+
#: (ExecutionManager) -> void
|
|
81
|
+
def initialize(execution_manager)
|
|
82
|
+
super()
|
|
83
|
+
@execution_manager = execution_manager
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @requires_ancestor: Roast::DSL::ExecutionManager
|
|
88
|
+
module Manager
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
#: (Params, ^(Cog::Input) -> untyped) -> SystemCogs::Call
|
|
92
|
+
def create_call_system_cog(params, input_proc)
|
|
93
|
+
SystemCogs::Call.new(params.name, input_proc) do |input|
|
|
94
|
+
input = input #: as Input
|
|
95
|
+
raise ExecutionManager::ExecutionScopeNotSpecifiedError unless params.run.present?
|
|
96
|
+
|
|
97
|
+
em = ExecutionManager.new(
|
|
98
|
+
@cog_registry,
|
|
99
|
+
@config_manager,
|
|
100
|
+
@all_execution_procs,
|
|
101
|
+
@workflow_context,
|
|
102
|
+
scope: params.run,
|
|
103
|
+
scope_value: input.value,
|
|
104
|
+
scope_index: input.index,
|
|
105
|
+
)
|
|
106
|
+
em.prepare!
|
|
107
|
+
begin
|
|
108
|
+
em.run!
|
|
109
|
+
rescue ControlFlow::Break
|
|
110
|
+
# treat `break!` like `next!` in a `call` invocation
|
|
111
|
+
# TODO: maybe do something with the message passed to break!
|
|
112
|
+
end
|
|
113
|
+
Output.new(em)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @requires_ancestor: Roast::DSL::CogInputContext
|
|
119
|
+
module InputContext
|
|
120
|
+
# Retrieve the output from a `call` cog's execution scope
|
|
121
|
+
#
|
|
122
|
+
# Extracts the final output from the execution scope that was invoked by the call cog.
|
|
123
|
+
# When called without a block, returns the final output directly. When called with a block,
|
|
124
|
+
# executes the block in the context of the called scope's input context, receiving the final
|
|
125
|
+
# output as an argument.
|
|
126
|
+
#
|
|
127
|
+
# This allows you to access the results of a called scope and optionally transform them
|
|
128
|
+
# and/or access other outputs from within that scope.
|
|
129
|
+
#
|
|
130
|
+
# #### Usage
|
|
131
|
+
# ```ruby
|
|
132
|
+
# # Get the final output directly
|
|
133
|
+
# result = from(call!(:my_call))
|
|
134
|
+
#
|
|
135
|
+
# # Transform the output with a block
|
|
136
|
+
# transformed = from(call!(:my_call)) { |output| output.upcase }
|
|
137
|
+
#
|
|
138
|
+
# # Access other cog outputs from within the called scope
|
|
139
|
+
# inner_result = from(call!(:my_call)) { inner_cog!(:some_step) }
|
|
140
|
+
# ```
|
|
141
|
+
#
|
|
142
|
+
# #### See Also
|
|
143
|
+
# - `Roast::DSL::SystemCogs::Call::Output` - the output type from call cogs
|
|
144
|
+
#
|
|
145
|
+
# @rbs [T] (Roast::DSL::SystemCogs::Call::Output) {(untyped, untyped, Integer) -> T} -> T
|
|
146
|
+
# | (Roast::DSL::SystemCogs::Call::Output) -> untyped
|
|
147
|
+
def from(call_cog_output, &block)
|
|
148
|
+
em = call_cog_output.instance_variable_get(:@execution_manager)
|
|
149
|
+
raise CogInputContext::ContextNotFoundError if em.nil?
|
|
150
|
+
|
|
151
|
+
final_output = em.final_output
|
|
152
|
+
scope_value = em.instance_variable_get(:@scope_value).deep_dup
|
|
153
|
+
scope_index = em.instance_variable_get(:@scope_index)
|
|
154
|
+
return em.cog_input_context.instance_exec(final_output, scope_value, scope_index, &block) if block_given?
|
|
155
|
+
|
|
156
|
+
final_output
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|