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.
Files changed (194) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/docs/write-comments.md +36 -0
  3. data/.github/CODEOWNERS +1 -1
  4. data/.github/workflows/ci.yaml +10 -6
  5. data/.gitignore +0 -1
  6. data/.rubocop.yml +7 -1
  7. data/CLAUDE.md +2 -2
  8. data/CONTRIBUTING.md +2 -0
  9. data/Gemfile +18 -18
  10. data/Gemfile.lock +46 -57
  11. data/README.md +118 -1432
  12. data/README_LEGACY.md +1464 -0
  13. data/Rakefile +39 -4
  14. data/dev.yml +29 -0
  15. data/dsl/agent_sessions.rb +20 -0
  16. data/dsl/async_cogs.rb +49 -0
  17. data/dsl/async_cogs_complex.rb +67 -0
  18. data/dsl/call.rb +44 -0
  19. data/dsl/collect_from.rb +72 -0
  20. data/dsl/demo/Gemfile +4 -0
  21. data/dsl/demo/Gemfile.lock +120 -0
  22. data/dsl/demo/cogs/local.rb +15 -0
  23. data/dsl/demo/simple_external_cog.rb +17 -0
  24. data/dsl/json_output.rb +28 -0
  25. data/dsl/map.rb +55 -0
  26. data/dsl/map_reduce.rb +37 -0
  27. data/dsl/map_with_index.rb +49 -0
  28. data/dsl/next_break.rb +40 -0
  29. data/dsl/next_break_parallel.rb +44 -0
  30. data/dsl/outputs.rb +39 -0
  31. data/dsl/outputs_bang.rb +36 -0
  32. data/dsl/parallel_map.rb +37 -0
  33. data/dsl/plugin-gem-example/.gitignore +8 -0
  34. data/dsl/plugin-gem-example/Gemfile +13 -0
  35. data/dsl/plugin-gem-example/Gemfile.lock +178 -0
  36. data/dsl/plugin-gem-example/lib/other.rb +17 -0
  37. data/dsl/plugin-gem-example/lib/plugin_gem_example.rb +5 -0
  38. data/dsl/plugin-gem-example/lib/simple.rb +15 -0
  39. data/dsl/plugin-gem-example/lib/version.rb +10 -0
  40. data/dsl/plugin-gem-example/plugin-gem-example.gemspec +28 -0
  41. data/dsl/prompts/simple_prompt.md.erb +3 -0
  42. data/dsl/prototype.rb +10 -4
  43. data/dsl/repeat_loop_results.rb +53 -0
  44. data/dsl/ruby_cog.rb +72 -0
  45. data/dsl/simple_agent.rb +18 -0
  46. data/dsl/simple_chat.rb +26 -0
  47. data/dsl/simple_repeat.rb +29 -0
  48. data/dsl/skip.rb +36 -0
  49. data/dsl/step_communication.rb +10 -5
  50. data/dsl/targets_and_params.rb +57 -0
  51. data/dsl/temperature.rb +17 -0
  52. data/dsl/temporary_directory.rb +22 -0
  53. data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
  54. data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
  55. data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
  56. data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
  57. data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
  58. data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
  59. data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
  60. data/dsl/tutorial/03_targets_and_params/README.md +230 -0
  61. data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
  62. data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
  63. data/dsl/tutorial/04_configuration_options/README.md +209 -0
  64. data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
  65. data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
  66. data/dsl/tutorial/05_control_flow/README.md +156 -0
  67. data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
  68. data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
  69. data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
  70. data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
  71. data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
  72. data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
  73. data/dsl/tutorial/07_processing_collections/README.md +152 -0
  74. data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
  75. data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
  76. data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
  77. data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
  78. data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
  79. data/dsl/tutorial/09_async_cogs/README.md +197 -0
  80. data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
  81. data/dsl/tutorial/README.md +222 -0
  82. data/dsl/working_directory.rb +16 -0
  83. data/exe/roast +1 -1
  84. data/internal/documentation/architectural-notes.md +115 -0
  85. data/internal/documentation/doc-comments-external.md +686 -0
  86. data/internal/documentation/doc-comments-internal.md +342 -0
  87. data/internal/documentation/doc-comments.md +211 -0
  88. data/lib/roast/dsl/cog/config.rb +280 -4
  89. data/lib/roast/dsl/cog/input.rb +73 -0
  90. data/lib/roast/dsl/cog/output.rb +313 -0
  91. data/lib/roast/dsl/cog/registry.rb +71 -0
  92. data/lib/roast/dsl/cog/stack.rb +3 -2
  93. data/lib/roast/dsl/cog/store.rb +11 -8
  94. data/lib/roast/dsl/cog.rb +108 -31
  95. data/lib/roast/dsl/cog_input_context.rb +44 -0
  96. data/lib/roast/dsl/cog_input_manager.rb +156 -0
  97. data/lib/roast/dsl/cogs/agent/config.rb +465 -0
  98. data/lib/roast/dsl/cogs/agent/input.rb +81 -0
  99. data/lib/roast/dsl/cogs/agent/output.rb +59 -0
  100. data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
  101. data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
  102. data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
  103. data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
  104. data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
  105. data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
  106. data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
  107. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
  108. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
  109. data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
  110. data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
  111. data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
  112. data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
  113. data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
  114. data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
  115. data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
  116. data/lib/roast/dsl/cogs/agent.rb +75 -0
  117. data/lib/roast/dsl/cogs/chat/config.rb +453 -0
  118. data/lib/roast/dsl/cogs/chat/input.rb +92 -0
  119. data/lib/roast/dsl/cogs/chat/output.rb +64 -0
  120. data/lib/roast/dsl/cogs/chat/session.rb +68 -0
  121. data/lib/roast/dsl/cogs/chat.rb +81 -0
  122. data/lib/roast/dsl/cogs/cmd.rb +291 -27
  123. data/lib/roast/dsl/cogs/ruby.rb +171 -0
  124. data/lib/roast/dsl/command_runner.rb +191 -0
  125. data/lib/roast/dsl/config_context.rb +2 -47
  126. data/lib/roast/dsl/config_manager.rb +143 -0
  127. data/lib/roast/dsl/control_flow.rb +41 -0
  128. data/lib/roast/dsl/execution_context.rb +9 -0
  129. data/lib/roast/dsl/execution_manager.rb +267 -0
  130. data/lib/roast/dsl/nil_assertions.rb +23 -0
  131. data/lib/roast/dsl/system_cog/params.rb +32 -0
  132. data/lib/roast/dsl/system_cog.rb +36 -0
  133. data/lib/roast/dsl/system_cogs/call.rb +162 -0
  134. data/lib/roast/dsl/system_cogs/map.rb +448 -0
  135. data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
  136. data/lib/roast/dsl/workflow.rb +123 -0
  137. data/lib/roast/dsl/workflow_context.rb +20 -0
  138. data/lib/roast/dsl/workflow_params.rb +24 -0
  139. data/lib/roast/sorbet_runtime_stub.rb +154 -0
  140. data/lib/roast/tools/apply_diff.rb +1 -3
  141. data/lib/roast/tools/cmd.rb +4 -3
  142. data/lib/roast/tools/read_file.rb +1 -1
  143. data/lib/roast/tools/update_files.rb +1 -1
  144. data/lib/roast/tools/write_file.rb +1 -1
  145. data/lib/roast/version.rb +1 -1
  146. data/lib/roast/workflow/base_workflow.rb +4 -0
  147. data/lib/roast/workflow/step_loader.rb +14 -2
  148. data/lib/roast-ai.rb +4 -0
  149. data/lib/roast.rb +60 -22
  150. data/{roast.gemspec → roast-ai.gemspec} +10 -13
  151. data/sorbet/config +1 -0
  152. data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
  153. data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
  154. data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
  155. data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
  156. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
  157. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
  158. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
  159. data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
  160. data/sorbet/rbi/gems/marcel@1.1.0.rbi +239 -0
  161. data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
  162. data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +5703 -0
  163. data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
  164. data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1197 -0
  165. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +314 -2
  166. data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +498 -0
  167. data/sorbet/tapioca/config.yml +6 -0
  168. data/sorbet/tapioca/require.rb +2 -0
  169. metadata +198 -34
  170. data/dsl/less_simple.rb +0 -112
  171. data/dsl/simple.rb +0 -8
  172. data/lib/roast/dsl/cog_execution_context.rb +0 -29
  173. data/lib/roast/dsl/cogs/graph.rb +0 -53
  174. data/lib/roast/dsl/cogs.rb +0 -65
  175. data/lib/roast/dsl/executor.rb +0 -82
  176. data/lib/roast/dsl/workflow_execution_context.rb +0 -47
  177. data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
  178. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
  179. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
  180. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
  181. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
  182. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
  183. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
  184. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
  185. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
  186. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
  187. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
  188. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
  189. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
  190. data/sorbet/rbi/gems/rack@2.2.18.rbi +0 -5659
  191. data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +0 -2170
  192. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  193. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
  194. data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +0 -11
@@ -0,0 +1,71 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class Cog
7
+ # Registry for managing available cogs in a workflow
8
+ class Registry
9
+ # Parent class for all cog registry errors
10
+ class CogRegistryError < Roast::Error; end
11
+
12
+ # Raised when a cog name cannot be derived from a cog class
13
+ class CouldNotDeriveCogNameError < CogRegistryError; end
14
+
15
+ # Initialize a new cog registry with standard cogs
16
+ #
17
+ # Automatically registers all system cogs and standard cogs provided by core Roast.
18
+ #
19
+ # #### See Also
20
+ # - `use`
21
+ # - `cogs`
22
+ #
23
+ def initialize
24
+ @cogs = {}
25
+ use(SystemCogs::Call)
26
+ use(SystemCogs::Map)
27
+ use(SystemCogs::Repeat)
28
+ use(Cogs::Cmd)
29
+ use(Cogs::Chat)
30
+ use(Cogs::Agent)
31
+ use(Cogs::Ruby)
32
+ end
33
+
34
+ # Hash mapping cog names to cog classes
35
+ #
36
+ # #### See Also
37
+ # - `use`
38
+ #
39
+ #: Hash[Symbol, singleton(Cog)]
40
+ attr_reader :cogs
41
+
42
+ # Register a cog class for use in workflows
43
+ #
44
+ # Adds the provided cog class to the registry, deriving its name from the class name.
45
+ # The cog name is the underscored, demodulized version of the class name
46
+ # (e.g., `Roast::DSL::Cogs::MyCustomCog` becomes `:my_custom_cog`).
47
+ #
48
+ # Raises `CouldNotDeriveCogNameError` if the cog class name cannot be determined.
49
+ #
50
+ # #### See Also
51
+ # - `cogs`
52
+ #
53
+ #: (singleton(Roast::DSL::Cog)) -> void
54
+ def use(cog_class)
55
+ reg = create_registration(cog_class)
56
+ cogs[reg.first] = reg.second
57
+ end
58
+
59
+ private
60
+
61
+ #: (singleton(Roast::DSL::Cog)) -> Array(Symbol, singleton(Cog))
62
+ def create_registration(cog_class)
63
+ cog_class_name = cog_class.name
64
+ raise CouldNotDeriveCogNameError if cog_class_name.nil?
65
+
66
+ [cog_class_name.demodulize.underscore.to_sym, cog_class]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -5,10 +5,11 @@ module Roast
5
5
  module DSL
6
6
  class Cog
7
7
  class Stack
8
- delegate :map, :push, :size, :empty?, to: :@queue
8
+ delegate :each, :empty?, :last, :map, :push, :size, to: :@queue
9
9
 
10
+ #: () -> void
10
11
  def initialize
11
- @queue = []
12
+ @queue = [] #: Array[Cog]
12
13
  end
13
14
 
14
15
  #: () -> Roast::DSL::Cog?
@@ -7,18 +7,21 @@ module Roast
7
7
  class Store
8
8
  class CogAlreadyDefinedError < Roast::Error; end
9
9
 
10
- delegate :[], to: :store
10
+ delegate :[], :key?, to: :store
11
11
 
12
- #: (Symbol, Roast::DSL::Cog) -> Roast::DSL::Cog
13
- def insert(id, inst)
14
- raise CogAlreadyDefinedError if store.key?(id)
12
+ #: Hash[Symbol, Cog]
13
+ attr_reader :store
15
14
 
16
- store[id] = inst
15
+ #: () -> void
16
+ def initialize
17
+ @store = {}
17
18
  end
18
19
 
19
- #: () -> Hash[Symbol, Roast::DSL::Cog]
20
- def store
21
- @store ||= {}
20
+ #: (Cog) -> Roast::DSL::Cog
21
+ def insert(cog)
22
+ raise CogAlreadyDefinedError, cog.name if store.key?(cog.name)
23
+
24
+ store[cog.name] = cog
22
25
  end
23
26
  end
24
27
  end
data/lib/roast/dsl/cog.rb CHANGED
@@ -4,67 +4,144 @@
4
4
  module Roast
5
5
  module DSL
6
6
  class Cog
7
- class CogAlreadyRanError < StandardError; end
7
+ class CogError < Roast::Error; end
8
+
9
+ class CogAlreadyStartedError < CogError; end
8
10
 
9
11
  class << self
10
- def on_create
11
- eigen = self
12
- proc do |instance_name = Random.uuid, &action|
13
- #: self as Roast::DSL::WorkflowExecutionContext
14
- add_cog_instance(instance_name, eigen.new(instance_name, action))
15
- end
12
+ #: () -> singleton(Cog::Config)
13
+ def config_class
14
+ @config_class ||= find_child_config_or_default
16
15
  end
17
16
 
18
- def on_config
19
- eigen = self
20
- proc do |cog_name = nil, &configuration|
21
- #: self as Roast::DSL::ConfigContext
22
- config_object = if cog_name.nil?
23
- fetch_execution_scope(eigen)
24
- else
25
- fetch_or_create_cog_config(eigen, cog_name)
26
- end
27
-
28
- config_object.instance_exec(&configuration) if configuration
29
- end
17
+ #: () -> singleton(Cog::Input)
18
+ def input_class
19
+ @input_class ||= find_child_input_or_default #: singleton(Cog::Input)?
30
20
  end
31
21
 
32
- def config_class
33
- @config_class ||= find_child_config_or_default
22
+ #: () -> Symbol
23
+ def generate_fallback_name
24
+ Random.uuid.to_sym
34
25
  end
35
26
 
36
27
  private
37
28
 
29
+ #: () -> singleton(Cog::Config)
38
30
  def find_child_config_or_default
39
31
  config_constant = "#{name}::Config"
40
32
  const_defined?(config_constant) ? const_get(config_constant) : Cog::Config # rubocop:disable Sorbet/ConstantsFromStrings
41
33
  end
34
+
35
+ #: () -> singleton(Cog::Input)
36
+ def find_child_input_or_default
37
+ input_constant = "#{name}::Input"
38
+ const_defined?(input_constant) ? const_get(input_constant) : Cog::Input # rubocop:disable Sorbet/ConstantsFromStrings
39
+ end
42
40
  end
43
41
 
44
- attr_reader :name, :output
42
+ #: Symbol
43
+ attr_reader :name
44
+
45
+ #: Cog::Output?
46
+ attr_reader :output
45
47
 
48
+ #: (Symbol, ^(Cog::Input) -> untyped) -> void
46
49
  def initialize(name, cog_input_proc)
47
50
  @name = name
48
- @cog_input_proc = cog_input_proc
49
- @finished = false
51
+ @cog_input_proc = cog_input_proc #: ^(Cog::Input) -> untyped
52
+ @output = nil #: Cog::Output?
53
+ @skipped = false #: bool
54
+ @failed = false #: bool
55
+
56
+ # Make sure a config is always defined, so we don't have to worry about nils
57
+ @config = self.class.config_class.new #: untyped
58
+ end
59
+
60
+ #: (Async::Barrier, Cog::Config, CogInputContext, untyped, Integer) -> Async::Task
61
+ def run!(barrier, config, input_context, executor_scope_value, executor_scope_index)
62
+ raise CogAlreadyStartedError if @task
63
+
64
+ @task = barrier.async(finished: false) do |task|
65
+ task.annotate("#{self.class.name.not_nil!.demodulize.camelcase} Cog: #{@name}")
66
+ @config = config
67
+ input_instance = self.class.input_class.new
68
+ input_return = input_context.instance_exec(
69
+ input_instance, executor_scope_value, executor_scope_index, &@cog_input_proc
70
+ ) if @cog_input_proc
71
+ coerce_and_validate_input!(input_instance, input_return)
72
+ @output = execute(input_instance)
73
+ rescue ControlFlow::SkipCog
74
+ # TODO: do something with the message passed to skip!
75
+ @skipped = true
76
+ rescue ControlFlow::FailCog => e
77
+ # TODO: do something with the message passed to fail!
78
+ @failed = true
79
+ # TODO: better / cleaner handling in the workflow execution manager for a workflow failure
80
+ # just re-raising this exception for now
81
+ raise e if config.abort_on_failure?
82
+ rescue ControlFlow::Next, ControlFlow::Break => e
83
+ @skipped = true
84
+ raise e
85
+ rescue StandardError => e
86
+ @failed = true
87
+ raise e
88
+ end
89
+ end
90
+
91
+ #: () -> void
92
+ def wait
93
+ @task&.wait
94
+ rescue
95
+ # Do nothing if the cog's task raised an exception. That is handled elsewhere.
96
+ end
97
+
98
+ #: () -> bool
99
+ def started?
100
+ @task.present?
101
+ end
102
+
103
+ #: () -> bool
104
+ def skipped?
105
+ @skipped
50
106
  end
51
107
 
52
- def run!(config, cog_execution_context)
53
- raise CogAlreadyRanError if ran?
108
+ #: () -> bool
109
+ def failed?
110
+ @failed || !!@task&.failed?
111
+ end
54
112
 
55
- @config = config
56
- @output = execute(cog_execution_context.instance_exec(&@cog_input_proc))
57
- @finished = true
113
+ #: () -> bool
114
+ def stopped?
115
+ !!@task&.stopped?
58
116
  end
59
117
 
60
- def ran?
61
- @finished
118
+ #: () -> bool
119
+ def succeeded?
120
+ # NOTE: explicitly checking `@output == nil` because the `ruby` cog's Output class delegates
121
+ # all missing methods to its `value`, which may be nil when the Output object is actually present.
122
+ @output != nil && @task&.finished? # rubocop:disable Style/NonNilCheck
62
123
  end
63
124
 
125
+ protected
126
+
64
127
  # Inheriting cog must implement this
128
+ #: (Cog::Input) -> Cog::Output
65
129
  def execute(input)
66
130
  raise NotImplementedError
67
131
  end
132
+
133
+ private
134
+
135
+ #: (Cog::Input, untyped) -> void
136
+ def coerce_and_validate_input!(input, return_value)
137
+ # Check if the input is already valid
138
+ input.validate!
139
+ rescue Cog::Input::InvalidInputError
140
+ # If it's not valid, attempt to coerce if possible
141
+ input.coerce(return_value)
142
+ # Re-validate because coerce! should not be responsible for validation
143
+ input.validate!
144
+ end
68
145
  end
69
146
  end
70
147
  end
@@ -0,0 +1,44 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ # Context in which the individual cog input blocks within the `execute` block of a workflow definition are evaluated
7
+ class CogInputContext
8
+ include SystemCogs::Call::InputContext
9
+ include SystemCogs::Map::InputContext
10
+
11
+ class CogInputContextError < Roast::Error; end
12
+
13
+ class ContextNotFoundError < CogInputContextError; end
14
+
15
+ #: (?String?) -> void
16
+ def skip!(message = nil)
17
+ raise ControlFlow::SkipCog, message
18
+ end
19
+
20
+ #: (?String?) -> void
21
+ def fail!(message = nil)
22
+ raise ControlFlow::FailCog, message
23
+ end
24
+
25
+ #: (?String?) -> void
26
+ def next!(message = nil)
27
+ raise ControlFlow::Next, message
28
+ end
29
+
30
+ #: (?String?) -> void
31
+ def break!(message = nil)
32
+ raise ControlFlow::Break, message
33
+ end
34
+
35
+ #: (String, ?Hash) -> String
36
+ def template(path, args = {})
37
+ path = "prompts/#{path}.md.erb" unless File.exist?(path)
38
+ fail!("The prompt #{path} could not be found") unless File.exist?(path)
39
+
40
+ ERB.new(File.read(path)).result_with_hash(args)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,156 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ # Context in which an individual cog block within the `execute` block of a workflow is evaluated
7
+ class CogInputManager
8
+ class CogOutputAccessError < Roast::Error; end
9
+
10
+ class CogDoesNotExistError < CogOutputAccessError; end
11
+
12
+ class CogNotYetRunError < CogOutputAccessError; end
13
+
14
+ class CogSkippedError < CogOutputAccessError; end
15
+
16
+ class CogFailedError < CogOutputAccessError; end
17
+
18
+ class CogStoppedError < CogOutputAccessError; end
19
+
20
+ #: (Cog::Registry, Cog::Store, WorkflowContext) -> void
21
+ def initialize(cog_registry, cogs, workflow_context)
22
+ @cog_registry = cog_registry
23
+ @cogs = cogs
24
+ @workflow_context = workflow_context
25
+ @context = CogInputContext.new
26
+ bind_registered_cogs
27
+ bind_workflow_context
28
+ end
29
+
30
+ #: CogInputContext
31
+ attr_reader :context
32
+
33
+ private
34
+
35
+ #: () -> void
36
+ def bind_registered_cogs
37
+ @cog_registry.cogs.keys.each(&method(:bind_cog))
38
+ end
39
+
40
+ #: (Symbol) -> void
41
+ def bind_cog(cog_method_name)
42
+ cog_question_method_name = (cog_method_name.to_s + "?").to_sym
43
+ cog_bang_method_name = (cog_method_name.to_s + "!").to_sym
44
+ cog_output_method = method(:cog_output)
45
+ cog_output_question_method = method(:cog_output?)
46
+ cog_output_bang_method = method(:cog_output!)
47
+ @context.instance_eval do
48
+ define_singleton_method(cog_method_name, proc { |cog_name| cog_output_method.call(cog_name) })
49
+ define_singleton_method(cog_question_method_name, proc { |cog_name| cog_output_question_method.call(cog_name) })
50
+ define_singleton_method(cog_bang_method_name, proc { |cog_name| cog_output_bang_method.call(cog_name) })
51
+ end
52
+ end
53
+
54
+ #: (Symbol) -> Cog::Output?
55
+ def cog_output(cog_name)
56
+ cog_output!(cog_name)
57
+ rescue CogOutputAccessError => e
58
+ # Even this method should raise an exception if the requested cog does not exist at all
59
+ raise e if e.is_a?(CogDoesNotExistError)
60
+
61
+ nil
62
+ end
63
+
64
+ #: (Symbol) -> bool
65
+ def cog_output?(cog_name)
66
+ !cog_output(cog_name).nil?
67
+ end
68
+
69
+ #: (Symbol) -> Cog::Output
70
+ def cog_output!(cog_name)
71
+ raise CogDoesNotExistError, cog_name unless @cogs.key?(cog_name)
72
+
73
+ @cogs[cog_name].tap do |cog|
74
+ cog.wait # attempting to access the output of a running cog will block until that cog completes
75
+ raise CogSkippedError, cog_name if cog.skipped?
76
+ raise CogFailedError, cog_name if cog.failed?
77
+ raise CogStoppedError, cog_name if cog.stopped?
78
+ raise CogNotYetRunError, cog_name unless cog.succeeded?
79
+ end.output.deep_dup
80
+ end
81
+
82
+ #: () -> void
83
+ def bind_workflow_context
84
+ target_bang_method = method(:target!)
85
+ targets_method = method(:targets)
86
+ arg_question_method = method(:arg?)
87
+ args_method = method(:args)
88
+ kwarg_method = method(:kwarg)
89
+ kwarg_bang_method = method(:kwarg!)
90
+ kwarg_question_method = method(:kwarg?)
91
+ kwargs_method = method(:kwargs)
92
+ tmpdir_method = method(:tmpdir)
93
+ @context.instance_eval do
94
+ define_singleton_method(:target!, proc { target_bang_method.call })
95
+ define_singleton_method(:targets, proc { targets_method.call })
96
+ define_singleton_method(:arg?, proc { |value| arg_question_method.call(value) })
97
+ define_singleton_method(:args, proc { args_method.call })
98
+ define_singleton_method(:kwarg, proc { |key| kwarg_method.call(key) })
99
+ define_singleton_method(:kwarg!, proc { |key| kwarg_bang_method.call(key) })
100
+ define_singleton_method(:kwarg?, proc { |key| kwarg_question_method.call(key) })
101
+ define_singleton_method(:kwargs, proc { kwargs_method.call })
102
+ define_singleton_method(:tmpdir, proc { tmpdir_method.call })
103
+ end
104
+ end
105
+
106
+ #: () -> String
107
+ def target!
108
+ raise ArgumentError, "expected exactly one target" unless @workflow_context.params.targets.length == 1
109
+
110
+ @workflow_context.params.targets.first #: as String
111
+ end
112
+
113
+ #: () -> Array[String]
114
+ def targets
115
+ @workflow_context.params.targets.dup
116
+ end
117
+
118
+ #: (Symbol) -> bool
119
+ def arg?(value)
120
+ @workflow_context.params.args.include?(value)
121
+ end
122
+
123
+ #: () -> Array[Symbol]
124
+ def args
125
+ @workflow_context.params.args.dup
126
+ end
127
+
128
+ #: (Symbol) -> String?
129
+ def kwarg(key)
130
+ @workflow_context.params.kwargs[key]
131
+ end
132
+
133
+ #: (Symbol) -> String
134
+ def kwarg!(key)
135
+ raise ArgumentError, "expected keyword argument '#{key}' to be present" unless @workflow_context.params.kwargs.include?(key)
136
+
137
+ @workflow_context.params.kwargs[key] #: as String
138
+ end
139
+
140
+ #: (Symbol) -> bool
141
+ def kwarg?(key)
142
+ @workflow_context.params.kwargs.include?(key)
143
+ end
144
+
145
+ #: () -> Hash[Symbol, String]
146
+ def kwargs
147
+ @workflow_context.params.kwargs.dup
148
+ end
149
+
150
+ #: () -> Pathname
151
+ def tmpdir
152
+ Pathname.new(@workflow_context.tmpdir).realpath
153
+ end
154
+ end
155
+ end
156
+ end