composable_agents 3.7.0 → 3.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +2 -2
- data/lib/composable_agents/agent.rb +4 -0
- data/lib/composable_agents/ai_agents/agent.rb +1 -1
- data/lib/composable_agents/cline/agent.rb +1 -1
- data/lib/composable_agents/mixins/resumable.rb +1 -1
- data/lib/composable_agents/prompt_driven_agent.rb +9 -1
- data/lib/composable_agents/prompt_rendering_strategy/markdown.rb +26 -16
- data/lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb +9 -9
- data/lib/composable_agents/prompt_rendering_strategy/markdown_heavy_files.rb +52 -21
- data/lib/composable_agents/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 816f8fd0898371ab594b7572d3154736711898c01d93de8269a7df6dfdeca623
|
|
4
|
+
data.tar.gz: be371808032d40d5464bd6e1292f6f395732ddb1e1fb0875e5f0736884856106
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5ec7f64ec25d84e17ef9a4e97878223fbfe12304442f0622d5f5953e9c3922b8787fea20cfd2fbe57c9c9e21928b6a7bc8aed4c14d8612d0f77abed78a3742b
|
|
7
|
+
data.tar.gz: e9a2a731c1a3885d00b239ccbf547dc03badc096afd57d52cc917c02b07e85cdd34d530be87cb4c384bee2fbee77867e22e3a338aae23aa1420bc790354b5546
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# [v3.7.2](https://github.com/Muriel-Salvan/composable_agents/compare/v3.7.1...v3.7.2) (2026-11-12 15:34:25)
|
|
2
|
+
|
|
3
|
+
## Global changes
|
|
4
|
+
### Patches
|
|
5
|
+
|
|
6
|
+
* [fix(strategies): expose options through agent accessors](https://github.com/Muriel-Salvan/composable_agents/commit/5a6901d858f6382e63efa49edd4dabf21efd8082)
|
|
7
|
+
|
|
8
|
+
## Changes for strategies
|
|
9
|
+
### Patches
|
|
10
|
+
|
|
11
|
+
* [fix(strategies): expose options through agent accessors](https://github.com/Muriel-Salvan/composable_agents/commit/5a6901d858f6382e63efa49edd4dabf21efd8082)
|
|
12
|
+
|
|
13
|
+
# [v3.7.1](https://github.com/Muriel-Salvan/composable_agents/compare/v3.7.0...v3.7.1) (2026-11-12 13:28:25)
|
|
14
|
+
|
|
15
|
+
## Global changes
|
|
16
|
+
### Patches
|
|
17
|
+
|
|
18
|
+
* [fix(MarkdownHeavyFiles): lazy-init MarkdownHeavyFiles artifact dirs](https://github.com/Muriel-Salvan/composable_agents/commit/bb64c69e65a60130e356c70755d0b862586a5351)
|
|
19
|
+
|
|
20
|
+
## Changes for MarkdownHeavyFiles
|
|
21
|
+
### Patches
|
|
22
|
+
|
|
23
|
+
* [fix(MarkdownHeavyFiles): lazy-init MarkdownHeavyFiles artifact dirs](https://github.com/Muriel-Salvan/composable_agents/commit/bb64c69e65a60130e356c70755d0b862586a5351)
|
|
24
|
+
|
|
1
25
|
# [v3.7.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.6.1...v3.7.0) (2026-11-12 12:08:18)
|
|
2
26
|
|
|
3
27
|
## Global changes
|
data/README.md
CHANGED
|
@@ -568,7 +568,7 @@ When a proc returns a policy, the agent waits `wait_secs` seconds, applies the `
|
|
|
568
568
|
### Additional notes
|
|
569
569
|
|
|
570
570
|
- **No executables / CLI** — This gem is a library only; there are no scripts in `bin/`.
|
|
571
|
-
- **Prompt rendering strategies** (`PromptRenderingStrategy::Markdown`, `PromptRenderingStrategy::MarkdownHeavy`, and `PromptRenderingStrategy::MarkdownHeavyFiles`) are not part of the public API themselves — they are included automatically by the agent's `strategy:` parameter.
|
|
571
|
+
- **Prompt rendering strategies** (`PromptRenderingStrategy::Markdown`, `PromptRenderingStrategy::MarkdownHeavy`, and `PromptRenderingStrategy::MarkdownHeavyFiles`) are not part of the public API themselves — they are included automatically by the agent's `strategy:` parameter. A strategy never defines `initialize`: when it can be customized, it exposes public accessors on the agent, resolved lazily and set at any time after construction (e.g. `PromptRenderingStrategy::MarkdownHeavyFiles#artifacts_directory=`).
|
|
572
572
|
- **The `AiAgentUserInteraction` mixin** (`ComposableAgents::Mixins::AiAgentUserInteraction`) is an internal bridge that combines `UserInteraction` with `AiAgents::Agent`; it is used by prepending it to an `AiAgents::Agent` subclass.
|
|
573
573
|
- See the [examples/](https://github.com/Muriel-Salvan/composable_agents/tree/main/examples) directory for complete, runnable scripts demonstrating all the above APIs.
|
|
574
574
|
|
|
@@ -679,7 +679,7 @@ Three strategies are included, mixed into the agent at initialization via `singl
|
|
|
679
679
|
|
|
680
680
|
- **`PromptRenderingStrategy::Markdown`** — Clean, minimal Markdown. Simple instructions and artifact references.
|
|
681
681
|
- **`PromptRenderingStrategy::MarkdownHeavy`** (default for Cline) — Elaborate prompts with execution checklists, structured artifact definition sections, and **JSON-based output parsing**. Agents format their artifacts as JSON blocks tagged with `output_artifact=NAME`, which the strategy parses back into the output hash. Includes type-aware parsing (`:text`, `:markdown`, `:json`).
|
|
682
|
-
- **`PromptRenderingStrategy::MarkdownHeavyFiles`** — Same heavy Markdown behavior as `MarkdownHeavy`, but artifacts are persisted as files on disk instead of being in-lined or returned as JSON. Input artifacts are written to files that the assistant is asked to open, and output artifacts are expected as files that are read back from disk.
|
|
682
|
+
- **`PromptRenderingStrategy::MarkdownHeavyFiles`** — Same heavy Markdown behavior as `MarkdownHeavy`, but artifacts are persisted as files on disk instead of being in-lined or returned as JSON. Input artifacts are written to files that the assistant is asked to open, and output artifacts are expected as files that are read back from disk. It exposes a public `#artifacts_directory` accessor (defaulting to `#{composable_agents_dir}/artifacts`) that can be set on the agent at any time; its artifacts directories are resolved lazily and created on first use. Example: `agent.artifacts_directory = File.join(composable_agents_dir, 'my_artifacts')`.
|
|
683
683
|
|
|
684
684
|
### LLM backends: ai-agents vs cline-rb 🎯
|
|
685
685
|
|
|
@@ -56,6 +56,10 @@ module ComposableAgents
|
|
|
56
56
|
# @return [RunInfo, nil] The current run info, or nil if no run has started yet
|
|
57
57
|
attr_reader :current_run_info
|
|
58
58
|
|
|
59
|
+
# @return [String] Base directory where this agent stores its data (runs, artifacts...).
|
|
60
|
+
# Available to this agent's mixins and prompt rendering strategies.
|
|
61
|
+
attr_reader :composable_agents_dir
|
|
62
|
+
|
|
59
63
|
# Get the logger to be used by this agent, with this agent's log fields (see #log_fields) prefixed to messages.
|
|
60
64
|
# If the provided logger does not support fields tagging, it is used as-is (fields are ignored).
|
|
61
65
|
# Memoized.
|
|
@@ -95,7 +95,7 @@ module ComposableAgents
|
|
|
95
95
|
instructions: @system_prompt,
|
|
96
96
|
tools: [
|
|
97
97
|
Tools::CreateArtifactTool.new(self, logger:),
|
|
98
|
-
Tools::GetArtifactTool.new(
|
|
98
|
+
Tools::GetArtifactTool.new(input_artifacts, logger:)
|
|
99
99
|
] + agent_tools
|
|
100
100
|
)
|
|
101
101
|
] + @handoff_agents
|
|
@@ -214,7 +214,7 @@ module ComposableAgents
|
|
|
214
214
|
find_skill(skill, selected_skills)
|
|
215
215
|
end
|
|
216
216
|
# Setup the temporary Cline global config dir
|
|
217
|
-
agent_tmp_dir = "#{
|
|
217
|
+
agent_tmp_dir = "#{composable_agents_dir}/tmp/#{Time.now.utc.strftime('%F-%H-%M-%S')}#{"-#{name.gsub(/[^\w.]/, '_')}" if name}"
|
|
218
218
|
::Cline.configure do |config|
|
|
219
219
|
config.debug = logger_debug?
|
|
220
220
|
config.temp_dir_root = "#{agent_tmp_dir}/cline-rb"
|
|
@@ -208,7 +208,7 @@ module ComposableAgents
|
|
|
208
208
|
# @param step_full_name [String] Step full name
|
|
209
209
|
# @return [String] Corresponding JSON file that stores the step states
|
|
210
210
|
def saved_step_states_json(step_full_name)
|
|
211
|
-
"#{
|
|
211
|
+
"#{composable_agents_dir}/runs/#{@run_id}/#{step_full_name.gsub(/[^\w.]/, '_')}.json"
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
# Get saved step states from a given full step name.
|
|
@@ -10,6 +10,8 @@ module ComposableAgents
|
|
|
10
10
|
#
|
|
11
11
|
# Prompt rendering strategies are useful because different prompt-driven agents would benefit from different
|
|
12
12
|
# prompt formats or structures (JSON, Markdown, explicit ordered lists, in-lining artifacts' contents without tools...).
|
|
13
|
+
# A strategy is a module that is included in the agent's singleton class when the agent is constructed
|
|
14
|
+
# (see PromptRenderingStrategy).
|
|
13
15
|
# This agent automatically records non-rendered conversation (prompts + outputs) in a `conversation` store, part of its state.
|
|
14
16
|
class PromptDrivenAgent < Agent
|
|
15
17
|
# @!group Public API
|
|
@@ -42,7 +44,9 @@ module ComposableAgents
|
|
|
42
44
|
# @param system_instructions [Object, nil] Original instructions for the agent, or nil if no system instructions are needed.
|
|
43
45
|
# The kind of instructions that can be given are defined by the Instructions's constructor (see Instructions#initialize).
|
|
44
46
|
# @param constraints [String, nil] Constraints to be respected, or nil for the agent's default
|
|
45
|
-
# @param strategy [Module] The prompt rendering strategy
|
|
47
|
+
# @param strategy [Module] The prompt rendering strategy.
|
|
48
|
+
# Strategies must not define their own constructor: a strategy that can be customized exposes public accessors
|
|
49
|
+
# to be set on the agent (see PromptRenderingStrategy::MarkdownHeavyFiles#artifacts_directory=).
|
|
46
50
|
def initialize(
|
|
47
51
|
*args,
|
|
48
52
|
role: nil,
|
|
@@ -161,6 +165,10 @@ module ComposableAgents
|
|
|
161
165
|
|
|
162
166
|
private
|
|
163
167
|
|
|
168
|
+
# @return [Hash{Symbol => Object}, nil] The input artifacts of the current run, or nil if no run has started yet.
|
|
169
|
+
# Available to this agent's prompt rendering strategies.
|
|
170
|
+
attr_reader :input_artifacts
|
|
171
|
+
|
|
164
172
|
# Get the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run.
|
|
165
173
|
# By default, all the errors that were reported during the run should raise.
|
|
166
174
|
# This is a hook that other mixins can override to restrict the errors that should raise.
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
module ComposableAgents
|
|
2
|
-
# Collection of prompt rendering strategies that can be used with PromptDrivenAgent's agents
|
|
2
|
+
# Collection of prompt rendering strategies that can be used with PromptDrivenAgent's agents.
|
|
3
|
+
# A strategy is a module that is included in the agent's singleton class when the agent is constructed
|
|
4
|
+
# (see PromptDrivenAgent#initialize).
|
|
5
|
+
# Strategies must follow these rules:
|
|
6
|
+
# - They must not define an #initialize method: strategies don't have any constructor parameter.
|
|
7
|
+
# - A strategy that can be customized publishes public accessors on the agent, whose defaults are resolved lazily
|
|
8
|
+
# from the agent's public API, so that they can be set at any time on a constructed agent
|
|
9
|
+
# (see MarkdownHeavyFiles#artifacts_directory).
|
|
10
|
+
# - They use the agent's accessors (e.g. #name, #role, #objective, #constraints, #input_artifacts,
|
|
11
|
+
# #composable_agents_dir) — including its private ones, as strategies are part of the agent — and never access
|
|
12
|
+
# its instance variables.
|
|
3
13
|
module PromptRenderingStrategy
|
|
4
14
|
# Render prompt as Markdown documents
|
|
5
15
|
module Markdown
|
|
@@ -30,44 +40,44 @@ module ComposableAgents
|
|
|
30
40
|
end
|
|
31
41
|
|
|
32
42
|
# Render the system prompt.
|
|
33
|
-
# The following
|
|
34
|
-
# -
|
|
35
|
-
# -
|
|
36
|
-
# -
|
|
37
|
-
# -
|
|
43
|
+
# The following accessors are available to render the prompt:
|
|
44
|
+
# - `#input_artifacts`
|
|
45
|
+
# - `#role`
|
|
46
|
+
# - `#objective`
|
|
47
|
+
# - `#constraints`
|
|
38
48
|
#
|
|
39
49
|
# @param rendered_instructions [String, nil] The rendered system instructions, or nil if none
|
|
40
50
|
# @return [String] The rendered system prompt
|
|
41
51
|
def render_system_prompt(rendered_instructions)
|
|
42
52
|
sections = []
|
|
43
|
-
sections << <<~EO_SECTION if
|
|
53
|
+
sections << <<~EO_SECTION if role && !role.empty?
|
|
44
54
|
# Role
|
|
45
55
|
|
|
46
|
-
#{Utils::Markdown.align_markdown_headers(
|
|
56
|
+
#{Utils::Markdown.align_markdown_headers(role, level: 2).strip}
|
|
47
57
|
EO_SECTION
|
|
48
|
-
sections << <<~EO_SECTION if
|
|
58
|
+
sections << <<~EO_SECTION if objective && !objective.empty?
|
|
49
59
|
# Objective
|
|
50
60
|
|
|
51
|
-
#{Utils::Markdown.align_markdown_headers(
|
|
61
|
+
#{Utils::Markdown.align_markdown_headers(objective, level: 2).strip}
|
|
52
62
|
EO_SECTION
|
|
53
63
|
sections << <<~EO_SECTION if rendered_instructions && !rendered_instructions.empty?
|
|
54
64
|
# Instructions
|
|
55
65
|
|
|
56
66
|
#{Utils::Markdown.align_markdown_headers(rendered_instructions, level: 2).strip}
|
|
57
67
|
EO_SECTION
|
|
58
|
-
sections << <<~EO_SECTION if
|
|
68
|
+
sections << <<~EO_SECTION if constraints && !constraints.empty?
|
|
59
69
|
# Constraints
|
|
60
70
|
|
|
61
|
-
#{Utils::Markdown.align_markdown_headers(
|
|
71
|
+
#{Utils::Markdown.align_markdown_headers(constraints, level: 2).strip}
|
|
62
72
|
EO_SECTION
|
|
63
73
|
sections.map(&:strip).join("\n\n")
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
# Render the user prompt
|
|
67
|
-
# The following
|
|
68
|
-
# -
|
|
69
|
-
# -
|
|
70
|
-
# -
|
|
77
|
+
# The following accessors are available to render the prompt:
|
|
78
|
+
# - `#role`
|
|
79
|
+
# - `#objective`
|
|
80
|
+
# - `#constraints`
|
|
71
81
|
#
|
|
72
82
|
# @param rendered_instructions [String, nil] The rendered instructions, or nil if none
|
|
73
83
|
# @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
|
|
@@ -47,11 +47,11 @@ module ComposableAgents
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Render the system prompt
|
|
50
|
-
# The following
|
|
51
|
-
# -
|
|
52
|
-
# -
|
|
53
|
-
# -
|
|
54
|
-
# -
|
|
50
|
+
# The following accessors are available to render the prompt:
|
|
51
|
+
# - `#input_artifacts`
|
|
52
|
+
# - `#role`
|
|
53
|
+
# - `#objective`
|
|
54
|
+
# - `#constraints`
|
|
55
55
|
#
|
|
56
56
|
# @param rendered_instructions [String, nil] The rendered system instructions, or nil if none
|
|
57
57
|
# @return [String] The rendered system prompt
|
|
@@ -65,10 +65,10 @@ module ComposableAgents
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Render the user prompt
|
|
68
|
-
# The following
|
|
69
|
-
# -
|
|
70
|
-
# -
|
|
71
|
-
# -
|
|
68
|
+
# The following accessors are available to render the prompt:
|
|
69
|
+
# - `#role`
|
|
70
|
+
# - `#objective`
|
|
71
|
+
# - `#constraints`
|
|
72
72
|
#
|
|
73
73
|
# @param rendered_instructions [String, nil] The rendered instructions, or nil if none
|
|
74
74
|
# @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
|
|
@@ -15,33 +15,46 @@ module ComposableAgents
|
|
|
15
15
|
# - `#parse_output_artifacts(text)` Parses a text that comes from the agent to retrieve output artifacts from files.
|
|
16
16
|
# - `#artifact_ref(artifact_name) -> String` Returns the absolute path of the artifact file as seen by the assistant.
|
|
17
17
|
# This can be used to refer to the artifact names properly in the user or system instructions.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
18
|
+
# Artifacts are persisted in a directory that defaults to `#{composable_agents_dir}/artifacts`, where input and
|
|
19
|
+
# output artifacts are persisted in its `input` and `output` sub-directories respectively.
|
|
20
|
+
# Those directories are resolved lazily and created on disk the first time they are needed, so that this strategy
|
|
21
|
+
# needs no state initialization and no constructor parameter.
|
|
22
|
+
# The directory can be customized at any time on an agent using this strategy by setting its
|
|
23
|
+
# #artifacts_directory accessor.
|
|
22
24
|
module MarkdownHeavyFiles
|
|
23
|
-
# @!group Internal
|
|
24
|
-
|
|
25
25
|
include MarkdownHeavy
|
|
26
26
|
|
|
27
|
-
#
|
|
27
|
+
# @!group Public API
|
|
28
|
+
|
|
29
|
+
# Get the directory where artifacts are persisted.
|
|
30
|
+
# Defaults to `#{composable_agents_dir}/artifacts`.
|
|
31
|
+
# Input and output artifacts are persisted in its `input` and `output` sub-directories respectively.
|
|
32
|
+
# Those directories are resolved lazily and created on disk the first time they are needed.
|
|
28
33
|
#
|
|
29
|
-
# @
|
|
34
|
+
# @return [String] The directory where artifacts are persisted
|
|
35
|
+
def artifacts_directory
|
|
36
|
+
@artifacts_directory || "#{composable_agents_dir}/artifacts"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Set the directory where artifacts are persisted (see #artifacts_directory).
|
|
40
|
+
# Already resolved input and output artifacts directories are reset, so that they follow the new directory
|
|
41
|
+
# the next time they are needed.
|
|
42
|
+
#
|
|
43
|
+
# @param directory [String, nil] The directory where artifacts are persisted, or nil to default to
|
|
30
44
|
# `#{composable_agents_dir}/artifacts`
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
@output_artifacts_directory = File.join(artifacts_directory, 'output')
|
|
36
|
-
FileUtils.mkdir_p(@input_artifacts_directory)
|
|
37
|
-
FileUtils.mkdir_p(@output_artifacts_directory)
|
|
45
|
+
def artifacts_directory=(directory)
|
|
46
|
+
@artifacts_directory = directory
|
|
47
|
+
@input_artifacts_directory = nil
|
|
48
|
+
@output_artifacts_directory = nil
|
|
38
49
|
end
|
|
39
50
|
|
|
51
|
+
# @!group Internal
|
|
52
|
+
|
|
40
53
|
# Render the user prompt
|
|
41
|
-
# The following
|
|
42
|
-
# -
|
|
43
|
-
# -
|
|
44
|
-
# -
|
|
54
|
+
# The following accessors are available to render the prompt:
|
|
55
|
+
# - `#role`
|
|
56
|
+
# - `#objective`
|
|
57
|
+
# - `#constraints`
|
|
45
58
|
#
|
|
46
59
|
# @param rendered_instructions [String, nil] The rendered instructions, or nil if none
|
|
47
60
|
# @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
|
|
@@ -134,6 +147,24 @@ module ComposableAgents
|
|
|
134
147
|
|
|
135
148
|
private
|
|
136
149
|
|
|
150
|
+
# Get the directory where input artifacts are persisted.
|
|
151
|
+
# Created on disk the first time it is needed.
|
|
152
|
+
# Memoized.
|
|
153
|
+
#
|
|
154
|
+
# @return [String] The directory where input artifacts are persisted
|
|
155
|
+
def input_artifacts_directory
|
|
156
|
+
@input_artifacts_directory ||= File.join(artifacts_directory, 'input').tap { |dir| FileUtils.mkdir_p(dir) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get the directory where output artifacts are persisted.
|
|
160
|
+
# Created on disk the first time it is needed.
|
|
161
|
+
# Memoized.
|
|
162
|
+
#
|
|
163
|
+
# @return [String] The directory where output artifacts are persisted
|
|
164
|
+
def output_artifacts_directory
|
|
165
|
+
@output_artifacts_directory ||= File.join(artifacts_directory, 'output').tap { |dir| FileUtils.mkdir_p(dir) }
|
|
166
|
+
end
|
|
167
|
+
|
|
137
168
|
# Render the input artifacts concept section of the system prompt.
|
|
138
169
|
#
|
|
139
170
|
# @param input_contracts [Hash{Symbol => Hash{Symbol => Object}}] The normalized input artifacts contracts
|
|
@@ -218,7 +249,7 @@ module ComposableAgents
|
|
|
218
249
|
# @return [String] The absolute path of the input artifact file
|
|
219
250
|
def input_artifact_path(artifact_name)
|
|
220
251
|
File.join(
|
|
221
|
-
|
|
252
|
+
input_artifacts_directory,
|
|
222
253
|
"#{artifact_name}#{artifact_extension(normalized_input_artifacts_contracts.dig(artifact_name, :type))}"
|
|
223
254
|
)
|
|
224
255
|
end
|
|
@@ -229,7 +260,7 @@ module ComposableAgents
|
|
|
229
260
|
# @return [String] The absolute path of the output artifact file
|
|
230
261
|
def output_artifact_path(artifact_name)
|
|
231
262
|
File.join(
|
|
232
|
-
|
|
263
|
+
output_artifacts_directory,
|
|
233
264
|
"#{artifact_name}#{artifact_extension(normalized_output_artifacts_contracts.dig(artifact_name, :type))}"
|
|
234
265
|
)
|
|
235
266
|
end
|