prompt_builder 0.1.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +763 -0
  5. data/VERSION +1 -0
  6. data/lib/prompt_builder/content/base.rb +44 -0
  7. data/lib/prompt_builder/content/input_file.rb +63 -0
  8. data/lib/prompt_builder/content/input_image.rb +64 -0
  9. data/lib/prompt_builder/content/input_text.rb +42 -0
  10. data/lib/prompt_builder/content/input_video.rb +43 -0
  11. data/lib/prompt_builder/content/output_text.rb +59 -0
  12. data/lib/prompt_builder/content/reasoning_text.rb +42 -0
  13. data/lib/prompt_builder/content/refusal_content.rb +42 -0
  14. data/lib/prompt_builder/content/summary_text.rb +42 -0
  15. data/lib/prompt_builder/content/text.rb +42 -0
  16. data/lib/prompt_builder/content.rb +28 -0
  17. data/lib/prompt_builder/errors.rb +18 -0
  18. data/lib/prompt_builder/items/base.rb +41 -0
  19. data/lib/prompt_builder/items/compaction.rb +60 -0
  20. data/lib/prompt_builder/items/function_call.rb +97 -0
  21. data/lib/prompt_builder/items/function_call_output.rb +110 -0
  22. data/lib/prompt_builder/items/item_reference.rb +42 -0
  23. data/lib/prompt_builder/items/message.rb +113 -0
  24. data/lib/prompt_builder/items/reasoning.rb +75 -0
  25. data/lib/prompt_builder/items.rb +13 -0
  26. data/lib/prompt_builder/response.rb +257 -0
  27. data/lib/prompt_builder/serializers/base.rb +37 -0
  28. data/lib/prompt_builder/serializers/chat_completion/request.rb +389 -0
  29. data/lib/prompt_builder/serializers/chat_completion/response.rb +139 -0
  30. data/lib/prompt_builder/serializers/chat_completion.rb +30 -0
  31. data/lib/prompt_builder/serializers/converse/request.rb +623 -0
  32. data/lib/prompt_builder/serializers/converse/response.rb +140 -0
  33. data/lib/prompt_builder/serializers/converse.rb +30 -0
  34. data/lib/prompt_builder/serializers/gemini/request.rb +562 -0
  35. data/lib/prompt_builder/serializers/gemini/response.rb +233 -0
  36. data/lib/prompt_builder/serializers/gemini.rb +30 -0
  37. data/lib/prompt_builder/serializers/messages/request.rb +634 -0
  38. data/lib/prompt_builder/serializers/messages/response.rb +157 -0
  39. data/lib/prompt_builder/serializers/messages.rb +30 -0
  40. data/lib/prompt_builder/serializers/open_responses/request.rb +229 -0
  41. data/lib/prompt_builder/serializers/open_responses/response.rb +18 -0
  42. data/lib/prompt_builder/serializers/open_responses.rb +30 -0
  43. data/lib/prompt_builder/serializers.rb +35 -0
  44. data/lib/prompt_builder/session.rb +383 -0
  45. data/lib/prompt_builder/tool_registry.rb +75 -0
  46. data/lib/prompt_builder/tools/definition.rb +66 -0
  47. data/lib/prompt_builder/tools.rb +7 -0
  48. data/lib/prompt_builder/usage.rb +100 -0
  49. data/lib/prompt_builder.rb +86 -0
  50. data/prompt_builder.gemspec +41 -0
  51. metadata +107 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Base class for content objects. Provides polymorphic deserialization
6
+ # via the +type+ field in the hash representation.
7
+ class Base
8
+ # Content type registry for polymorphic dispatch.
9
+ TYPES = {
10
+ "input_file" => "InputFile",
11
+ "input_image" => "InputImage",
12
+ "input_text" => "InputText",
13
+ "input_video" => "InputVideo",
14
+ "output_text" => "OutputText",
15
+ "reasoning_text" => "ReasoningText",
16
+ "refusal" => "RefusalContent",
17
+ "summary_text" => "SummaryText",
18
+ "text" => "Text"
19
+ }.freeze
20
+
21
+ class << self
22
+ # Deserialize a content object from a Hash by dispatching on the +type+ field.
23
+ #
24
+ # @param hash [Hash] a Hash with string keys including a +"type"+ field
25
+ # @return [Content::Base] the deserialized content object
26
+ # @raise [InvalidItemError] if the type is unknown
27
+ def from_h(hash)
28
+ type = hash["type"]
29
+ class_name = TYPES[type]
30
+ raise InvalidItemError, "Unknown content type: #{type.inspect}" unless class_name
31
+
32
+ Content.const_get(class_name).from_h(hash)
33
+ end
34
+ end
35
+
36
+ # Serialize the content object to a Hash with string keys.
37
+ #
38
+ # @return [Hash] the hash representation
39
+ def to_h
40
+ raise NotImplementedError
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents file input content in a message.
6
+ class InputFile < Base
7
+ # @return [String, nil] the file URL (may be a data URL)
8
+ attr_reader :url
9
+
10
+ # @return [String, nil] the filename
11
+ attr_reader :filename
12
+
13
+ # @return [Hash, nil] provider-specific extra data
14
+ attr_reader :extra
15
+
16
+ class << self
17
+ # Deserialize an InputFile from a Hash.
18
+ #
19
+ # @param hash [Hash] a Hash with string keys
20
+ # @return [InputFile]
21
+ def from_h(hash)
22
+ new(
23
+ url: hash["url"],
24
+ filename: hash["filename"],
25
+ **hash.except("type", "url", "filename").transform_keys(&:to_sym)
26
+ )
27
+ end
28
+ end
29
+
30
+ # Create a new InputFile content object.
31
+ #
32
+ # @param url [String, nil] the file URL or data URL
33
+ # @param filename [String, nil] the filename
34
+ # @param extra [Hash] provider-specific extra keyword arguments
35
+ def initialize(url: nil, filename: nil, **extra)
36
+ @filename = filename&.to_s
37
+ @extra = normalize_extra_kwargs(extra)
38
+ @url = url&.to_s
39
+ end
40
+
41
+ # Serialize to a Hash with string keys. Nil values are omitted.
42
+ #
43
+ # @return [Hash]
44
+ def to_h
45
+ h = {"type" => "input_file"}
46
+ h["url"] = @url if @url
47
+ h["filename"] = @filename if @filename
48
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
49
+ h
50
+ end
51
+
52
+ private
53
+
54
+ def normalize_extra_kwargs(extra)
55
+ nested = extra.delete(:extra)
56
+ nested = extra.delete("extra") if nested.nil?
57
+ nested_hash = nested.is_a?(Hash) ? nested : {}
58
+
59
+ PromptBuilder.jsonify(nested_hash.merge(extra)).transform_keys(&:to_s)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents image input content in a message.
6
+ class InputImage < Base
7
+ # @return [String, nil] the image URL (may be a fully qualified URL or a
8
+ # base64-encoded data URL such as +"data:image/png;base64,..."+ )
9
+ attr_reader :url
10
+
11
+ # @return [String, nil] the detail level for the image
12
+ attr_reader :detail
13
+
14
+ # @return [Hash, nil] provider-specific extra data
15
+ attr_reader :extra
16
+
17
+ class << self
18
+ # Deserialize an InputImage from a Hash.
19
+ #
20
+ # @param hash [Hash] a Hash with string keys
21
+ # @return [InputImage]
22
+ def from_h(hash)
23
+ new(
24
+ url: hash["url"],
25
+ detail: hash["detail"],
26
+ **hash.except("type", "url", "detail").transform_keys(&:to_sym)
27
+ )
28
+ end
29
+ end
30
+
31
+ # Create a new InputImage content object.
32
+ #
33
+ # @param url [String, nil] the image URL or data URL
34
+ # @param detail [String, nil] the image detail level
35
+ # @param extra [Hash] provider-specific extra keyword arguments
36
+ def initialize(url: nil, detail: nil, **extra)
37
+ @detail = detail&.to_s
38
+ @extra = normalize_extra_kwargs(extra)
39
+ @url = url&.to_s
40
+ end
41
+
42
+ # Serialize to a Hash with string keys. Nil values are omitted.
43
+ #
44
+ # @return [Hash]
45
+ def to_h
46
+ h = {"type" => "input_image"}
47
+ h["url"] = @url if @url
48
+ h["detail"] = @detail if @detail
49
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
50
+ h
51
+ end
52
+
53
+ private
54
+
55
+ def normalize_extra_kwargs(extra)
56
+ nested = extra.delete(:extra)
57
+ nested = extra.delete("extra") if nested.nil?
58
+ nested_hash = nested.is_a?(Hash) ? nested : {}
59
+
60
+ PromptBuilder.jsonify(nested_hash.merge(extra)).transform_keys(&:to_s)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents text input content in a message.
6
+ class InputText < Base
7
+ # @return [String] the text content
8
+ attr_reader :text
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ # Create a new InputText content object.
14
+ #
15
+ # @param text [String] the text content
16
+ # @param extra [Hash] provider-specific extra keyword arguments
17
+ def initialize(text:, **extra)
18
+ @text = text&.to_s
19
+ @extra = extra.transform_keys(&:to_s)
20
+ end
21
+
22
+ class << self
23
+ # Deserialize an InputText from a Hash.
24
+ #
25
+ # @param hash [Hash] a Hash with string keys
26
+ # @return [InputText]
27
+ def from_h(hash)
28
+ new(text: hash["text"], **hash.except("type", "text").transform_keys(&:to_sym))
29
+ end
30
+ end
31
+
32
+ # Serialize to a Hash with string keys.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "input_text", "text" => @text}
37
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
38
+ h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents video input content in a message.
6
+ class InputVideo < Base
7
+ # @return [String, nil] the video URL
8
+ attr_reader :url
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ class << self
14
+ # Deserialize an InputVideo from a Hash.
15
+ #
16
+ # @param hash [Hash] a Hash with string keys
17
+ # @return [InputVideo]
18
+ def from_h(hash)
19
+ new(url: hash["url"], **hash.except("type", "url").transform_keys(&:to_sym))
20
+ end
21
+ end
22
+
23
+ # Create a new InputVideo content object.
24
+ #
25
+ # @param url [String, nil] the video URL
26
+ # @param extra [Hash] provider-specific extra keyword arguments
27
+ def initialize(url: nil, **extra)
28
+ @url = url&.to_s
29
+ @extra = extra.transform_keys(&:to_s)
30
+ end
31
+
32
+ # Serialize to a Hash with string keys. Nil values are omitted.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "input_video"}
37
+ h["url"] = @url if @url
38
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
39
+ h
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents text output content in an assistant message.
6
+ class OutputText < Base
7
+ # @return [String] the text content
8
+ attr_reader :text
9
+
10
+ # @return [Array<Hash>] annotations on the text
11
+ attr_reader :annotations
12
+
13
+ # @return [Array<Hash>] token log probabilities for the text
14
+ attr_reader :logprobs
15
+
16
+ # @return [Hash, nil] provider-specific extra data
17
+ attr_reader :extra
18
+
19
+ # Create a new OutputText content object.
20
+ #
21
+ # @param text [String] the text content
22
+ # @param annotations [Array<Hash>] annotations on the text
23
+ # @param logprobs [Array<Hash>] token log probabilities on the text
24
+ # @param extra [Hash] provider-specific extra keyword arguments
25
+ def initialize(text:, annotations: [], logprobs: [], **extra)
26
+ @text = text&.to_s
27
+ @annotations = PromptBuilder.jsonify(annotations)
28
+ @logprobs = PromptBuilder.jsonify(logprobs)
29
+ @extra = extra.transform_keys(&:to_s)
30
+ end
31
+
32
+ class << self
33
+ # Deserialize an OutputText from a Hash.
34
+ #
35
+ # @param hash [Hash] a Hash with string keys
36
+ # @return [OutputText]
37
+ def from_h(hash)
38
+ new(
39
+ text: hash["text"],
40
+ annotations: hash["annotations"] || [],
41
+ logprobs: hash["logprobs"] || [],
42
+ **hash.except("type", "text", "annotations", "logprobs").transform_keys(&:to_sym)
43
+ )
44
+ end
45
+ end
46
+
47
+ # Serialize to a Hash with string keys. Empty annotations are omitted.
48
+ #
49
+ # @return [Hash]
50
+ def to_h
51
+ h = {"type" => "output_text", "text" => @text}
52
+ h["annotations"] = @annotations unless @annotations.empty?
53
+ h["logprobs"] = @logprobs unless @logprobs.empty?
54
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
55
+ h
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents reasoning text content from the model.
6
+ class ReasoningText < Base
7
+ # @return [String] the reasoning text content
8
+ attr_reader :text
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ # Create a new ReasoningText content object.
14
+ #
15
+ # @param text [String] the reasoning text content
16
+ # @param extra [Hash] provider-specific extra keyword arguments
17
+ def initialize(text:, **extra)
18
+ @text = text&.to_s
19
+ @extra = extra.transform_keys(&:to_s)
20
+ end
21
+
22
+ class << self
23
+ # Deserialize a ReasoningText from a Hash.
24
+ #
25
+ # @param hash [Hash] a Hash with string keys
26
+ # @return [ReasoningText]
27
+ def from_h(hash)
28
+ new(text: hash["text"], **hash.except("type", "text").transform_keys(&:to_sym))
29
+ end
30
+ end
31
+
32
+ # Serialize to a Hash with string keys.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "reasoning_text", "text" => @text}
37
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
38
+ h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents a refusal content block returned by the model.
6
+ class RefusalContent < Base
7
+ # @return [String] the refusal message
8
+ attr_reader :refusal
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ # Create a new RefusalContent object.
14
+ #
15
+ # @param refusal [String] the refusal message
16
+ # @param extra [Hash] provider-specific extra keyword arguments
17
+ def initialize(refusal:, **extra)
18
+ @refusal = refusal&.to_s
19
+ @extra = extra.transform_keys(&:to_s)
20
+ end
21
+
22
+ class << self
23
+ # Deserialize a RefusalContent from a Hash.
24
+ #
25
+ # @param hash [Hash] a Hash with string keys
26
+ # @return [RefusalContent]
27
+ def from_h(hash)
28
+ new(refusal: hash["refusal"], **hash.except("type", "refusal").transform_keys(&:to_sym))
29
+ end
30
+ end
31
+
32
+ # Serialize to a Hash with string keys.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "refusal", "refusal" => @refusal}
37
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
38
+ h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents a summary text content from the model's reasoning.
6
+ class SummaryText < Base
7
+ # @return [String] the summary text content
8
+ attr_reader :text
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ # Create a new SummaryText content object.
14
+ #
15
+ # @param text [String] the summary text content
16
+ # @param extra [Hash] provider-specific extra keyword arguments
17
+ def initialize(text:, **extra)
18
+ @text = text&.to_s
19
+ @extra = extra.transform_keys(&:to_s)
20
+ end
21
+
22
+ class << self
23
+ # Deserialize a SummaryText from a Hash.
24
+ #
25
+ # @param hash [Hash] a Hash with string keys
26
+ # @return [SummaryText]
27
+ def from_h(hash)
28
+ new(text: hash["text"], **hash.except("type", "text").transform_keys(&:to_sym))
29
+ end
30
+ end
31
+
32
+ # Serialize to a Hash with string keys.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "summary_text", "text" => @text}
37
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
38
+ h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ # Represents generic text content in a message or reasoning item.
6
+ class Text < Base
7
+ # @return [String] the text content
8
+ attr_reader :text
9
+
10
+ # @return [Hash, nil] provider-specific extra data
11
+ attr_reader :extra
12
+
13
+ # Create a new Text content object.
14
+ #
15
+ # @param text [String] the text content
16
+ # @param extra [Hash] provider-specific extra keyword arguments
17
+ def initialize(text:, **extra)
18
+ @text = text&.to_s
19
+ @extra = extra.transform_keys(&:to_s)
20
+ end
21
+
22
+ class << self
23
+ # Deserialize a Text from a Hash.
24
+ #
25
+ # @param hash [Hash] a Hash with string keys
26
+ # @return [Text]
27
+ def from_h(hash)
28
+ new(text: hash["text"], **hash.except("type", "text").transform_keys(&:to_sym))
29
+ end
30
+ end
31
+
32
+ # Serialize to a Hash with string keys.
33
+ #
34
+ # @return [Hash]
35
+ def to_h
36
+ h = {"type" => "text", "text" => @text}
37
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
38
+ h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Content
5
+ autoload :Base, File.expand_path("content/base", __dir__)
6
+ autoload :InputFile, File.expand_path("content/input_file", __dir__)
7
+ autoload :InputImage, File.expand_path("content/input_image", __dir__)
8
+ autoload :InputText, File.expand_path("content/input_text", __dir__)
9
+ autoload :InputVideo, File.expand_path("content/input_video", __dir__)
10
+ autoload :OutputText, File.expand_path("content/output_text", __dir__)
11
+ autoload :ReasoningText, File.expand_path("content/reasoning_text", __dir__)
12
+ autoload :RefusalContent, File.expand_path("content/refusal_content", __dir__)
13
+ autoload :SummaryText, File.expand_path("content/summary_text", __dir__)
14
+ autoload :Text, File.expand_path("content/text", __dir__)
15
+
16
+ class << self
17
+ # Construct a base64-encoded data URL from raw binary data and a content type.
18
+ # Delegates to {PromptBuilder.data_url}.
19
+ #
20
+ # @param data [String] the raw binary data
21
+ # @param content_type [String] the MIME content type (e.g. "image/png", "application/pdf")
22
+ # @return [String] a data URL in the form "data:<content_type>;base64,<encoded_data>"
23
+ def data_url(data, content_type)
24
+ PromptBuilder.data_url(data, content_type)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ # Base error class for all PromptBuilder errors.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when a format conversion is not supported.
8
+ class UnsupportedFormatError < Error; end
9
+
10
+ # Raised when an invalid item type is encountered.
11
+ class InvalidItemError < Error; end
12
+
13
+ # Raised when a tool is not found in the registry.
14
+ class ToolNotFoundError < Error; end
15
+
16
+ # Raised when an operation is invalid for the current session state.
17
+ class InvalidStateError < Error; end
18
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Items
5
+ # Base class for conversation items. Provides polymorphic deserialization
6
+ # via the +type+ field in the hash representation.
7
+ class Base
8
+ # Item type registry for polymorphic dispatch.
9
+ TYPES = {
10
+ "compaction" => "Compaction",
11
+ "function_call" => "FunctionCall",
12
+ "function_call_output" => "FunctionCallOutput",
13
+ "item_reference" => "ItemReference",
14
+ "message" => "Message",
15
+ "reasoning" => "Reasoning"
16
+ }.freeze
17
+
18
+ class << self
19
+ # Deserialize an item from a Hash by dispatching on the +type+ field.
20
+ #
21
+ # @param hash [Hash] a Hash with string keys including a +"type"+ field
22
+ # @return [Items::Base] the deserialized item
23
+ # @raise [InvalidItemError] if the type is unknown
24
+ def from_h(hash)
25
+ type = hash["type"]
26
+ class_name = TYPES[type]
27
+ raise InvalidItemError, "Unknown item type: #{type.inspect}" unless class_name
28
+
29
+ Items.const_get(class_name).from_h(hash)
30
+ end
31
+ end
32
+
33
+ # Serialize the item to a Hash with string keys.
34
+ #
35
+ # @return [Hash] the hash representation
36
+ def to_h
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptBuilder
4
+ module Items
5
+ # Represents a compaction item that summarizes earlier conversation history.
6
+ class Compaction < Base
7
+ # @return [String, nil] the compaction item identifier
8
+ attr_reader :id
9
+
10
+ # @return [String, nil] encrypted compacted content
11
+ attr_reader :encrypted_content
12
+
13
+ # @return [String, nil] who created the compaction (e.g. "user" or "assistant")
14
+ attr_reader :created_by
15
+
16
+ # @return [Hash, nil] provider-specific extra data
17
+ attr_reader :extra
18
+
19
+ # Create a new Compaction item.
20
+ #
21
+ # @param id [String, nil] the compaction item identifier
22
+ # @param encrypted_content [String, nil] encrypted compacted content
23
+ # @param created_by [String, nil] who created the compaction
24
+ # @param extra [Hash] provider-specific extra keyword arguments
25
+ def initialize(id: nil, encrypted_content: nil, created_by: nil, **extra)
26
+ @id = id&.to_s
27
+ @encrypted_content = encrypted_content&.to_s
28
+ @created_by = created_by&.to_s
29
+ @extra = extra.transform_keys(&:to_s)
30
+ end
31
+
32
+ class << self
33
+ # Deserialize a Compaction item from a Hash.
34
+ #
35
+ # @param hash [Hash] a Hash with string keys
36
+ # @return [Compaction]
37
+ def from_h(hash)
38
+ new(
39
+ id: hash["id"],
40
+ encrypted_content: hash["encrypted_content"],
41
+ created_by: hash["created_by"],
42
+ **hash.except("type", "id", "encrypted_content", "created_by").transform_keys(&:to_sym)
43
+ )
44
+ end
45
+ end
46
+
47
+ # Serialize to a Hash with string keys. Nil values are omitted.
48
+ #
49
+ # @return [Hash]
50
+ def to_h
51
+ h = {"type" => "compaction"}
52
+ h["id"] = @id if @id
53
+ h["encrypted_content"] = @encrypted_content if @encrypted_content
54
+ h["created_by"] = @created_by if @created_by
55
+ h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
56
+ h
57
+ end
58
+ end
59
+ end
60
+ end