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
data/Rakefile CHANGED
@@ -4,6 +4,8 @@ require "bundler/gem_tasks"
4
4
  require "rubocop/rake_task"
5
5
  require "rake/testtask"
6
6
 
7
+ ### Test Tasks
8
+
7
9
  Rake::TestTask.new(:minitest_all) do |t|
8
10
  t.libs << "test"
9
11
  t.libs << "lib"
@@ -19,10 +21,32 @@ end
19
21
  Rake::TestTask.new(:minitest_old) do |t|
20
22
  t.libs << "test"
21
23
  t.libs << "lib"
22
- t.test_files = FileList["test/functional/**/*_test.rb"]
24
+ t.test_files = FileList["test/**/*_test.rb"].exclude(
25
+ "test/functional/**/*_test.rb",
26
+ "test/dsl/**/*_test.rb",
27
+ "test/roast/dsl/**/*_test.rb",
28
+ )
29
+ end
30
+
31
+ Rake::TestTask.new(:minitest_dsl_fast) do |t|
32
+ t.libs << "test"
33
+ t.libs << "lib"
34
+ t.test_files = FileList["test/dsl/**/*_test.rb", "test/roast/dsl/**/*_test.rb"]
35
+ end
36
+
37
+ Rake::TestTask.new(:minitest_dsl_slow) do |t|
38
+ t.libs << "test"
39
+ t.libs << "lib"
40
+ t.test_files = FileList["test/dsl/**/*_test.rb", "test/roast/dsl/**/*_test.rb"]
23
41
  end
42
+ task :set_slow_env do
43
+ ENV["ROAST_RUN_SLOW_TESTS"] = "true"
44
+ end
45
+ task minitest_dsl_slow: :set_slow_env
46
+
47
+ task test: [:minitest_dsl_fast, :minitest_dsl_slow, :minitest_functional, :minitest_old]
24
48
 
25
- task test: [:minitest_all]
49
+ ### Rubocop Tasks
26
50
 
27
51
  RuboCop::RakeTask.new(:rubocop_ci)
28
52
 
@@ -30,6 +54,17 @@ RuboCop::RakeTask.new(:rubocop) do |task|
30
54
  task.options = ["--autocorrect"]
31
55
  end
32
56
 
33
- task default: [:test, :rubocop]
57
+ ### Sorbet Tasks
58
+
59
+ desc "Run Sorbet type checker"
60
+ task :sorbet do
61
+ sh "bin/srb tc" do |ok, _|
62
+ abort "Sorbet type checking failed" unless ok
63
+ end
64
+ end
65
+
66
+ ### Task Groups
67
+
68
+ task default: [:sorbet, :rubocop, :minitest_dsl_fast]
34
69
 
35
- task lint: [:rubocop]
70
+ task check: [:sorbet, :rubocop]
data/dev.yml ADDED
@@ -0,0 +1,29 @@
1
+ name: roast
2
+
3
+ nix: true
4
+
5
+ type: ruby
6
+
7
+ up:
8
+ - ruby
9
+ - bundler
10
+
11
+ check:
12
+ style: bundle exec rake rubocop
13
+ tc: bundle exec rake sorbet
14
+ test: bundle exec rake minitest_dsl_fast
15
+
16
+ commands:
17
+ __default__: execute
18
+ execute:
19
+ desc: Execute a roast workflow
20
+ run: bundle exec roast execute --executor=dsl
21
+ style:
22
+ desc: Run Rubocop style checks (and autocorrect)
23
+ run: bundle exec rake rubocop:autocorrect
24
+ tc:
25
+ desc: Run Sorbet type checks
26
+ run: bundle exec rake sorbet
27
+ test:
28
+ desc: Run all tests
29
+ run: bundle exec rake minitest_dsl_fast
@@ -0,0 +1,20 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ agent do
8
+ provider :claude
9
+ model "haiku"
10
+ dump_raw_agent_messages_to "tmp/claude-messages.log"
11
+ end
12
+ end
13
+
14
+ execute do
15
+ agent(:one) { "The magic word is 'pomegranate'" }
16
+ agent(:two) do |my|
17
+ my.session = agent!(:one).session
18
+ "What is the magic word?"
19
+ end
20
+ end
data/dsl/async_cogs.rb ADDED
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+
9
+ # Any type of cog can be configured to run asynchronously in the background.
10
+ # The cog that follows it in the workflow will be able to begin running immediately.
11
+ cmd(/slow/) { async! }
12
+ end
13
+
14
+ execute do
15
+ # 'first' is synchronous; nothing else runs until it completes.
16
+ cmd(:first) { "echo first" }
17
+
18
+ # These cogs are async and slow, so we kick them off in the background (via the 'async!' config option)
19
+ # Other cogs can continue while these cog runs in the background
20
+ cmd(:slow_background_task_1) do
21
+ sleep 0.1
22
+ "echo slow background task 1"
23
+ end
24
+ cmd(:slow_background_task_2) do
25
+ sleep 0.2
26
+ "echo slow background task 2"
27
+ end
28
+
29
+ cmd(:second) { "echo second" }
30
+ cmd(:third) { "echo third" }
31
+
32
+ # 'fourth' accesses the output of 'slow_background_task_1'.
33
+ # It will block until 'slow_background_task_1' completes.
34
+ # 'fourth' is synchronous, so cogs that follow it will also be forced to wait (whether they are async or not)
35
+ cmd(:fourth) do
36
+ "echo \"fourth <-- '#{cmd!(:slow_background_task_1).text}'\""
37
+ end
38
+
39
+ cmd(:fifth) { "echo fifth" }
40
+
41
+ # The overall completion order of these cogs is:
42
+ # first
43
+ # second : run right after first; does not have to wait for the slow background tasks to complete
44
+ # third
45
+ # slow_background_task_1
46
+ # fourth : forced to wait for slow_background_task_1 to complete to access its input
47
+ # fifth
48
+ # slow_background_task_2 : the workflow will not complete until all async cogs have completed
49
+ end
@@ -0,0 +1,67 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+
9
+ # Any type of cog can be configured to run asynchronously.
10
+ # The cog that follows it in the workflow will be able to begin running immediately, before the async cog has finished.
11
+ cmd(:second) { async! }
12
+
13
+ # If there are a mix of sync and async cogs in a workflow, any synchronous cog may begin running before any
14
+ # async cogs above it have completed, but no cogs below it -- sync or async -- will start until it has completed.
15
+ cmd(:third) { no_async! } # NOTE: `no_async!` is redundant here (it's the default); but included here for extra clarity.
16
+ cmd(:fourth) { async! }
17
+ cmd(:fifth) { async! }
18
+ cmd(:sixth) { async! }
19
+ end
20
+
21
+ execute do
22
+ # 'first' is synchronous; nothing else runs until it completes.
23
+ cmd(:first) { "echo first" }
24
+
25
+ # 'second' is async and slow; it will not complete until after 'third' is done,
26
+ # but 'third' will be allowed to start right away.
27
+ cmd(:second) do
28
+ sleep 0.2
29
+ "echo second"
30
+ end
31
+
32
+ # 'third is synchronous and medium-speed; it will not wait for the async cogs above it,
33
+ # so it will complete before the slow, async 'second' cog.
34
+ cmd(:third) do
35
+ sleep 0.1
36
+ "echo third"
37
+ end
38
+
39
+ # 'fourth' is async, but it will not be allowed to start until the synchronous 'third' cog about it has completed.
40
+ # It will still complete before the slow-running 'second' cog, though.
41
+ cmd(:fourth) { "echo fourth" }
42
+
43
+ # 'fifth' is async, and fast, and will start before the slow-running 'second' cog.
44
+ # However, its input depends on the output of 'second', so it will automatically wait for 'second' to complete.
45
+ cmd(:fifth) do
46
+ # All the output accessors -- cmd?(:name), cmd(:name), cmd!(:name) -- will block
47
+ # until the named async cog is complete IF AND ONLY IF it has already started.
48
+ # If the named cog is defined later in the workflow and thus not already started,
49
+ # these methods will return immediately.
50
+ "echo \"fifth (second said: '#{cmd!(:second).text}')\""
51
+ end
52
+
53
+ # 'sixth' is async as well, and it will complete before the slow-running 'second' cog AND before the 'fifth' cog
54
+ # that is blocking on the output of 'second'
55
+ cmd(:sixth) do
56
+ sleep 0.01 # just to make sure this runs slightly slower than 'fourth' to maintain deterministic ordering
57
+ "echo sixth"
58
+ end
59
+
60
+ # The overall completion order of these cogs is:
61
+ # first : synchronous; nothing else started until it was done
62
+ # third : synchronous; did not block on the async 'second' cog
63
+ # fourth : async; could not start until after 'third'
64
+ # sixth : async; started at the same time as 'fourth', ran very slightly slower
65
+ # second : async, slow; started right after 'first', but didn't complete until now
66
+ # fifth : async; faster than 'second' but depended on its output, so it blocked until 'second' was complete
67
+ end
data/dsl/call.rb ADDED
@@ -0,0 +1,44 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_random_word) do
13
+ cmd(:word) { "shuf /usr/share/dict/words -n 1" }
14
+ cmd(:capitalize) do |my, value_from_call|
15
+ # Optional second block argument lets you use a value passed to the sub-executor by `call`
16
+ # from withing any cog in the sub-executor
17
+ word = value_from_call || cmd!(:word).text
18
+ my.command = "/bin/sh"
19
+ my.args << "-c"
20
+ my.args << "/bin/echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
21
+ end
22
+ end
23
+
24
+ execute do
25
+ cmd(:before) { "echo '--> before'" }
26
+
27
+ # First positional argument is always the (optional) cog name
28
+ # Keyword argument "run" is the (required) scope name of the executor to run
29
+ call(:first_call, run: :capitalize_a_random_word)
30
+ call(:other_named_call, run: :capitalize_a_random_word)
31
+ call(run: :capitalize_a_random_word) # anonymous call cog
32
+
33
+ cmd { "echo '---'" }
34
+
35
+ # Can invoke a sub-executor with an executor-scoped value, that will be passed to each cog's input proc in that scope
36
+ call(run: :capitalize_a_random_word) do |my|
37
+ my.value = "scope value: roast"
38
+ end
39
+
40
+ # Shorthand input coercion for passing a scope value
41
+ call(run: :capitalize_a_random_word) { "scope value: other" }
42
+
43
+ cmd(:after) { "echo '--> after'" }
44
+ end
@@ -0,0 +1,72 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:capitalize_a_word) do
12
+ cmd(:to_original) { |_, word| "echo \"#{word}\"" }
13
+ cmd(:to_upper) do |my, word|
14
+ my.command = "sh"
15
+ my.args << "-c"
16
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
17
+ end
18
+ cmd(:to_lower) do |my, word|
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:upper:]' '[:lower:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ # Call a subroutine with `call` or `map`
27
+ call(:hello, run: :capitalize_a_word) { "Hello" }
28
+ call(:world, run: :capitalize_a_word) { "World" }
29
+ map(:other_words, run: :capitalize_a_word) { ["Goodnight", "Moon"] }
30
+
31
+ cmd do
32
+ # Normally, you can only reference the output of cogs that run in the same executor scope.
33
+ begin
34
+ # There is no :to_upper cog that this could sensibly be referring to
35
+ cmd(:to_upper)
36
+ rescue Roast::DSL::CogInputManager::CogDoesNotExistError
37
+ puts "Could not access :to_upper directly"
38
+ end
39
+
40
+ # Using `from`, you can access cogs from the executor scope that was run by a specific named `call`.
41
+ # The block you pass to `from` runs in the input context of the specified scope, rather than the current scope.
42
+ original = from(call!(:hello)) { cmd!(:to_original).text }
43
+ # The type of the return value of `from` is the same type as the return value of the block.
44
+ upper = from(call!(:hello)) { cmd!(:to_upper) }.text
45
+ # You can also use this shorthand `from` syntax to get the output of the last cog in the call's executor scope.
46
+ # In this syntax, the return value of `from` is untyped
47
+ lower = from(call!(:hello)).text
48
+ "echo \"#{original} --> #{upper} --> #{lower}\""
49
+ end
50
+
51
+ cmd do
52
+ # You can also grab the `call`'s output once and pass it to multiple `from` invocations.
53
+ my_scope = call!(:world)
54
+ original = from(my_scope) { cmd!(:to_original).text }
55
+ upper = from(my_scope) { cmd!(:to_upper).text }
56
+ lower = from(my_scope) { cmd!(:to_lower).text }
57
+ "echo \"#{original} --> #{upper} --> #{lower}\""
58
+ end
59
+
60
+ cmd do
61
+ # Using `collect`, you can access cogs from the executor scopes that were run by a specific named `map`.
62
+ # The block you pass to `collect` runs in the input context of each specified scope.
63
+ # `collect` returns an array containing the output of each invocation of that block.
64
+ originals = collect(map!(:other_words)) { cmd!(:to_original).text }
65
+ # The type of the return value of `call` is an Array of the same type as the return value of the block.
66
+ uppers = collect(map!(:other_words)) { cmd!(:to_upper) }.map(&:text)
67
+ # You can also use this shorthand `collect` syntax to get the output of the last cog in each of the map's executor scopes.
68
+ # In this syntax, the return value of `collect` is Array[untyped]
69
+ lowers = collect(map!(:other_words)).map(&:text)
70
+ "echo \"#{originals.join(",")} --> #{uppers.join(",")} --> #{lowers.join(",")}\""
71
+ end
72
+ end
data/dsl/demo/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "roast-ai", path: "../.."
4
+ gem "plugin-gem-example", path: "../plugin-gem-example"
@@ -0,0 +1,120 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ roast-ai (0.4.9)
5
+ activesupport (>= 7.0)
6
+ cli-kit (~> 5.0)
7
+ cli-ui (= 2.3.0)
8
+ diff-lcs (~> 1.5)
9
+ json-schema
10
+ open_router (~> 0.3)
11
+ raix (~> 1.0.2)
12
+ ruby-graphviz (~> 1.2)
13
+ ruby_llm
14
+ sqlite3 (~> 2.6)
15
+ thor (~> 1.3)
16
+ zeitwerk (~> 2.6)
17
+
18
+ PATH
19
+ remote: ../plugin-gem-example
20
+ specs:
21
+ plugin-gem-example (0.1.0)
22
+ roast-ai
23
+
24
+ GEM
25
+ specs:
26
+ activesupport (8.0.3)
27
+ base64
28
+ benchmark (>= 0.3)
29
+ bigdecimal
30
+ concurrent-ruby (~> 1.0, >= 1.3.1)
31
+ connection_pool (>= 2.2.5)
32
+ drb
33
+ i18n (>= 1.6, < 2)
34
+ logger (>= 1.4.2)
35
+ minitest (>= 5.1)
36
+ securerandom (>= 0.3)
37
+ tzinfo (~> 2.0, >= 2.0.5)
38
+ uri (>= 0.13.1)
39
+ addressable (2.8.7)
40
+ public_suffix (>= 2.0.2, < 7.0)
41
+ base64 (0.3.0)
42
+ benchmark (0.4.1)
43
+ bigdecimal (3.3.1)
44
+ cli-kit (5.0.1)
45
+ cli-ui (~> 2.0)
46
+ cli-ui (2.3.0)
47
+ concurrent-ruby (1.3.5)
48
+ connection_pool (2.5.4)
49
+ diff-lcs (1.6.2)
50
+ dotenv (3.1.8)
51
+ drb (2.2.3)
52
+ event_stream_parser (1.0.0)
53
+ faraday (2.14.0)
54
+ faraday-net_http (>= 2.0, < 3.5)
55
+ json
56
+ logger
57
+ faraday-multipart (1.1.1)
58
+ multipart-post (~> 2.0)
59
+ faraday-net_http (3.4.1)
60
+ net-http (>= 0.5.0)
61
+ faraday-retry (2.3.2)
62
+ faraday (~> 2.0)
63
+ i18n (1.14.7)
64
+ concurrent-ruby (~> 1.0)
65
+ json (2.15.1)
66
+ json-schema (6.0.0)
67
+ addressable (~> 2.8)
68
+ bigdecimal (~> 3.1)
69
+ logger (1.7.0)
70
+ marcel (1.1.0)
71
+ minitest (5.26.0)
72
+ multipart-post (2.4.1)
73
+ net-http (0.6.0)
74
+ uri
75
+ open_router (0.3.3)
76
+ activesupport (>= 6.0)
77
+ dotenv (>= 2)
78
+ faraday (>= 1)
79
+ faraday-multipart (>= 1)
80
+ ostruct (0.6.3)
81
+ public_suffix (6.0.2)
82
+ raix (1.0.3)
83
+ activesupport (>= 6.0)
84
+ faraday-retry (~> 2.0)
85
+ open_router (~> 0.2)
86
+ ostruct
87
+ ruby-openai (~> 8.1)
88
+ rexml (3.4.4)
89
+ ruby-graphviz (1.2.5)
90
+ rexml
91
+ ruby-openai (8.3.0)
92
+ event_stream_parser (>= 0.3.0, < 2.0.0)
93
+ faraday (>= 1)
94
+ faraday-multipart (>= 1)
95
+ ruby_llm (1.8.2)
96
+ base64
97
+ event_stream_parser (~> 1)
98
+ faraday (>= 1.10.0)
99
+ faraday-multipart (>= 1)
100
+ faraday-net_http (>= 1)
101
+ faraday-retry (>= 1)
102
+ marcel (~> 1.0)
103
+ zeitwerk (~> 2)
104
+ securerandom (0.4.1)
105
+ sqlite3 (2.7.4-arm64-darwin)
106
+ thor (1.4.0)
107
+ tzinfo (2.0.6)
108
+ concurrent-ruby (~> 1.0)
109
+ uri (1.0.4)
110
+ zeitwerk (2.7.3)
111
+
112
+ PLATFORMS
113
+ arm64-darwin
114
+
115
+ DEPENDENCIES
116
+ plugin-gem-example!
117
+ roast-ai!
118
+
119
+ BUNDLED WITH
120
+ 2.6.8
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ class Local < Roast::DSL::Cog
5
+ class Input < Roast::DSL::Cog::Input
6
+ def validate!
7
+ true
8
+ end
9
+ end
10
+
11
+ #: (Input) -> void
12
+ def execute(input)
13
+ puts "I'm a workflow-specific cog!"
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ use "simple", from: "plugin_gem_example"
7
+ use "MyCogNamespace::Other", from: "plugin_gem_example"
8
+ use "local"
9
+
10
+ # Use multiple cogs
11
+ # use ["simple", "other"], from: "plugin_gem_example"
12
+
13
+ execute do
14
+ simple
15
+ other
16
+ local
17
+ end
@@ -0,0 +1,28 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ end
9
+
10
+ execute do
11
+ cmd(:json) do |my|
12
+ my.command = "echo"
13
+ my.args << <<~JSON
14
+ {
15
+ "hello": "world",
16
+ "letters": [
17
+ "aaa",
18
+ "bbb"
19
+ ]
20
+ }
21
+ JSON
22
+ end
23
+
24
+ ruby do
25
+ puts "RAW OUTPUT: #{cmd!(:json).text}"
26
+ puts "SOME VALUE FROM PARSED OUTPUT: #{cmd!(:json).json![:letters].first}"
27
+ end
28
+ end
data/dsl/map.rb ADDED
@@ -0,0 +1,55 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_word) do
13
+ cmd(:capitalize) do |my, word|
14
+ # Use the `fail!` method in a cog's input proc to terminate the cog with a failure state
15
+ # e.g., if a condition prevents its successful execution.
16
+ # If `fail!` is called, the input proc will immediately terminate.
17
+ # The entire workflow may or may not terminate as a result, based on its configuration and the cog's configuration.
18
+ fail! unless word.present?
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ words = ["hello", "world", "goodnight", "moon"]
27
+
28
+ # WITHOUT MAP COG
29
+ # You can use plain Ruby to generate a collection of cogs programmatically
30
+ # This is equivalent to simply defining each of the cogs explicitly, one by one
31
+ # In this case, four copies of the `call` cog invoking :capitalize_a_word
32
+ words.each { |word| call(run: :capitalize_a_word) { word } }
33
+
34
+ cmd(:foo) { "echo" }
35
+
36
+ # USING MAP COG
37
+ # You can use the `map` cog to apply a scoped executor to each item in a collection of values
38
+ map(:some_name, run: :capitalize_a_word) do |my|
39
+ my.items = words
40
+ end
41
+
42
+ cmd { "echo" }
43
+
44
+ # USING MAP COG (SHORTHAND)
45
+ # - name of execute scope is required
46
+ # - name of the map cog itself can be omitted (anonymous cog)
47
+ # - items over which to map coerced from return value of input proc
48
+ map(run: :capitalize_a_word) { words.reverse }
49
+
50
+ # ACCESSING THE OUTPUT OF A SPECIFIC MAP ITERATION
51
+ ruby do
52
+ puts ""
53
+ puts "#{words[2]} -> #{from(map!(:some_name).iteration(2)) { cmd!(:capitalize).text }}"
54
+ end
55
+ end
data/dsl/map_reduce.rb ADDED
@@ -0,0 +1,37 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:capitalize_a_word) do
12
+ cmd(:to_original) { |_, word| "echo \"#{word}\"" }
13
+ cmd(:to_upper) do |my, word|
14
+ my.command = "sh"
15
+ my.args << "-c"
16
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
17
+ end
18
+ cmd(:to_lower) do |my, word|
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:upper:]' '[:lower:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ # Call a subroutine with `map`
27
+ map(:words, run: :capitalize_a_word) { ["Hello", "World"] }
28
+
29
+ cmd do
30
+ # Use `reduce` to apply a block to the input context of each executor scope run by `map` in turn.
31
+ # The return value of each invocation of the block will be passed as the 'accumulator' to the next invocation.
32
+ # You can provide an optional initial value for the accumulator as the second argument to `reduce`.
33
+ # If an initial value is present, the return type is non-nilable. If absent, the return type is nilable.
34
+ words = reduce(map!(:words), "lower case words:") { |acc| acc + " " + cmd!(:to_lower).text }
35
+ "echo \"#{words}\""
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_word) do
13
+ cmd(:capitalize) do |my, word, index|
14
+ fail! unless word.present?
15
+ my.command = "sh"
16
+ my.args << "-c"
17
+ # When this execution scope is called by the `map` cog, the optional 'index' argument to this block
18
+ # will contain the index of its element in the enumerable on which `map` is invoked
19
+ my.args << "echo \"[#{index}] #{word}\" | tr '[:lower:]' '[:upper:]'"
20
+ end
21
+ end
22
+
23
+ execute do
24
+ words = ["hello", "world", "goodnight", "moon"]
25
+
26
+ # When calling an execute scope with `map`, the index of the item in the enumerable
27
+ # will be provided to the cogs in that scope
28
+ map(:some_name, run: :capitalize_a_word) { words }
29
+
30
+ cmd { "echo" }
31
+
32
+ # When calling an execute scope with `call`, the cogs in that scope will receive 0 as the index value by default
33
+ call(run: :capitalize_a_word) { "default" }
34
+
35
+ call(run: :capitalize_a_word) do |my|
36
+ my.value = "specific"
37
+ # It is possible to specify a custom index value when invoking `call`
38
+ my.index = 23
39
+ end
40
+
41
+ cmd { "echo" }
42
+
43
+ # `map` will also take a custom 'initial_index' value, at which to start the value of 'index' passed
44
+ # to the executors it invokes. This parallels the syntax of Ruby's `items.map.with_index(initial_value) { ... }`
45
+ map(run: :capitalize_a_word) do |my|
46
+ my.items = words[1, 2]
47
+ my.initial_index = 8
48
+ end
49
+ end