Class: Ask::Attachment
- Inherits:
-
Object
- Object
- Ask::Attachment
- Defined in:
- lib/ask/attachment.rb
Overview
A file a user attached to a message.
Sources (exactly one per attachment):
path: local file path
url: http(s) or data: URI
data: raw bytes
io: an IO/StringIO (read at render time)
blob: a duck-typed object with +download+/+path+/+read+
(e.g. an ActiveStorage blob)
file_id: a provider-managed file reference (no bytes here)
Delivery modes:
:inline (default) — the bytes are sent to the model (via the
provider's serializers) so it can read the file. Provider
capability gates apply.
:context — only a manifest line reaches the model:
"[Attached file: name (mime, N bytes)]". The model knows the file
exists but never receives its content. Provider-agnostic — right
for agents that must not read uploaded files (e.g. a
requirements-gathering assistant).
Constant Summary collapse
- DELIVERY_MODES =
%i[inline context].freeze
Instance Attribute Summary collapse
-
#delivery ⇒ Symbol
readonly
:inline or :context.
-
#file_id ⇒ String?
readonly
Provider-managed file ID when given.
-
#filename ⇒ String?
readonly
Original filename.
-
#mime_type ⇒ String
readonly
MIME type.
-
#size ⇒ Integer?
readonly
Size in bytes.
-
#url ⇒ String?
readonly
URL (http(s) or data: URI) when given.
Class Method Summary collapse
-
.wrap(value) ⇒ Attachment, Content::Block
Coerce a single value into an Attachment: Attachment → itself Content::Block → itself (already a block) String → treated as a local file path Hash → keyword constructor arguments duck-typed blob (+download+/+path+/+read+) → blob source.
-
.wrap_all(values) ⇒ Array<Attachment, Content::Block>
Coerce a value or array of values into an array of attachments (and/or content blocks).
Instance Method Summary collapse
-
#base64 ⇒ String?
Base64-encoded bytes, when available.
-
#context? ⇒ Boolean
Whether only context (a manifest line) is sent.
-
#data ⇒ String?
Raw bytes, when the source can provide them (path/io/data/blob/data URIs).
-
#initialize(path: nil, url: nil, data: nil, io: nil, blob: nil, file_id: nil, filename: nil, mime_type: nil, delivery: :inline) ⇒ Attachment
constructor
A new instance of Attachment.
-
#inline? ⇒ Boolean
Whether the bytes are sent to the model.
-
#manifest_line ⇒ String
The manifest line rendered for :context attachments.
-
#to_content ⇒ Content::Block
Convert to the content block carried by the message.
-
#type ⇒ Symbol
Broad category of the file (see Mime.classify).
Constructor Details
#initialize(path: nil, url: nil, data: nil, io: nil, blob: nil, file_id: nil, filename: nil, mime_type: nil, delivery: :inline) ⇒ Attachment
Returns a new instance of Attachment.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ask/attachment.rb', line 65 def initialize(path: nil, url: nil, data: nil, io: nil, blob: nil, file_id: nil, filename: nil, mime_type: nil, delivery: :inline) sources = { path: path, url: url, data: data, io: io, blob: blob, file_id: file_id } given = sources.count { |_, value| !value.nil? } raise ArgumentError, "Provide exactly one attachment source" unless given == 1 @delivery = delivery.to_sym raise ArgumentError, "Unknown delivery mode: #{delivery.inspect}" unless DELIVERY_MODES.include?(@delivery) @path = path @io = io @blob = blob @url = url @file_id = file_id @raw_data = data @filename = filename || derive_filename @mime_type = mime_type || derive_mime_type @size = derive_size freeze end |
Instance Attribute Details
#delivery ⇒ Symbol (readonly)
Returns :inline or :context.
48 49 50 |
# File 'lib/ask/attachment.rb', line 48 def delivery @delivery end |
#file_id ⇒ String? (readonly)
Returns provider-managed file ID when given.
54 55 56 |
# File 'lib/ask/attachment.rb', line 54 def file_id @file_id end |
#filename ⇒ String? (readonly)
Returns original filename.
39 40 41 |
# File 'lib/ask/attachment.rb', line 39 def filename @filename end |
#mime_type ⇒ String (readonly)
Returns MIME type.
42 43 44 |
# File 'lib/ask/attachment.rb', line 42 def mime_type @mime_type end |
#size ⇒ Integer? (readonly)
Returns size in bytes.
45 46 47 |
# File 'lib/ask/attachment.rb', line 45 def size @size end |
#url ⇒ String? (readonly)
Returns URL (http(s) or data: URI) when given.
51 52 53 |
# File 'lib/ask/attachment.rb', line 51 def url @url end |
Class Method Details
.wrap(value) ⇒ Attachment, Content::Block
Coerce a single value into an Ask::Attachment:
Attachment → itself
Content::Block → itself (already a block)
String → treated as a local file path
Hash → keyword constructor arguments
duck-typed blob (+download+/+path+/+read+) → blob source
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/ask/attachment.rb', line 175 def self.wrap(value) case value when Attachment, Content::Block value when String new(path: value) when Hash new(**value.transform_keys(&:to_sym)) else if value.respond_to?(:download) || value.respond_to?(:path) || value.respond_to?(:read) new(blob: value) else raise ArgumentError, "Cannot use #{value.class} as an attachment" end end end |
.wrap_all(values) ⇒ Array<Attachment, Content::Block>
Coerce a value or array of values into an array of attachments (and/or content blocks).
197 198 199 |
# File 'lib/ask/attachment.rb', line 197 def self.wrap_all(values) Array(values).map { |value| wrap(value) } end |
Instance Method Details
#base64 ⇒ String?
Base64-encoded bytes, when available.
113 114 115 116 |
# File 'lib/ask/attachment.rb', line 113 def base64 bytes = data bytes && Base64.strict_encode64(bytes) end |
#context? ⇒ Boolean
Returns whether only context (a manifest line) is sent.
157 158 159 |
# File 'lib/ask/attachment.rb', line 157 def context? @delivery == :context end |
#data ⇒ String?
Raw bytes, when the source can provide them (path/io/data/blob/data URIs). nil for URLs and provider file references — those are passed to the provider as-is.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/ask/attachment.rb', line 91 def data return @raw_data unless @raw_data.nil? return nil if @file_id return decode_data_uri if data_uri? if @path File.binread(@path) elsif @io @io.rewind if @io.respond_to?(:rewind) @io.read elsif @blob if @blob.respond_to?(:download) @blob.download elsif @blob.respond_to?(:read) @blob.read end end end |
#inline? ⇒ Boolean
Returns whether the bytes are sent to the model.
162 163 164 |
# File 'lib/ask/attachment.rb', line 162 def inline? @delivery == :inline end |
#manifest_line ⇒ String
The manifest line rendered for :context attachments.
128 129 130 131 |
# File 'lib/ask/attachment.rb', line 128 def manifest_line details = [mime_type, (size ? "#{size} bytes" : nil)].compact.join(", ") "[Attached file: #{filename || "file"} (#{details})]" end |
#to_content ⇒ Content::Block
Convert to the content block carried by the message.
:context attachments become a plain Content::Text manifest line; :inline attachments become the matching media/file block so the provider serializers can send the bytes.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ask/attachment.rb', line 140 def to_content return Content::Text.new(manifest_line) if context? case type when :image Content::Image.new(url: url, base64: base64, mime_type: mime_type, file_id: file_id) when :audio Content::Audio.new(url: url, base64: base64, mime_type: mime_type, file_id: file_id) when :video Content::Video.new(url: url, base64: base64, mime_type: mime_type, file_id: file_id) else Content::File.new(data: data, mime_type: mime_type, filename: filename, url: url, file_id: file_id) end end |
#type ⇒ Symbol
Broad category of the file (see Mime.classify).
121 122 123 |
# File 'lib/ask/attachment.rb', line 121 def type Mime.classify(mime_type) end |