Class: Ask::Agent::ArtifactStore
- Inherits:
-
Object
- Object
- Ask::Agent::ArtifactStore
- Defined in:
- lib/ask/agent/artifact_store.rb
Overview
Session-scoped storage for tool-produced deliverables ("artifacts").
Artifacts come in two kinds, chosen by the producing tool:
- content — small text deliverables (reports, CSVs, patches, generated code) stored inline in the state store.
- uri — large or binary deliverables stored externally (object storage, a file service); the store keeps the reference and metadata only.
Metadata always lives in the state store (pure KV, same adapter as sessions/checkpoints/memory — works with every backend and with the in-process Memory fallback):
artifact:<session_id>:<artifact_id> — one key per artifact
artifact:<session_id>:index — JSON array of ids
An optional uploader callback lifts inline content to a URI before
storage (e.g. upload to S3), so apps that prefer object storage never
grow the database: the tool returns content, the session uploads, the
store keeps the reference.
Constant Summary collapse
- KEY_PREFIX =
"artifact:"- INDEX_SUFFIX =
":index"
Instance Attribute Summary collapse
-
#state ⇒ Ask::State::Adapter
readonly
The underlying adapter.
-
#uploader ⇒ Proc?
readonly
The uploader callback, if any.
Instance Method Summary collapse
-
#delete(session_id) ⇒ void
Remove every artifact for a session (called by Session#delete).
-
#fetch(session_id, id) ⇒ Hash?
The full record (content or uri).
-
#initialize(state:, max_content_size: 100_000, uploader: nil) ⇒ ArtifactStore
constructor
A new instance of ArtifactStore.
-
#list(session_id) ⇒ Array<Hash>
Artifact summaries (id, filename, mime_type, size, uri) newest first — content is not included.
-
#store(session_id, filename:, mime_type: nil, content: nil, uri: nil) ⇒ Hash
Store an artifact for a session.
Constructor Details
#initialize(state:, max_content_size: 100_000, uploader: nil) ⇒ ArtifactStore
Returns a new instance of ArtifactStore.
38 39 40 41 42 43 |
# File 'lib/ask/agent/artifact_store.rb', line 38 def initialize(state:, max_content_size: 100_000, uploader: nil) @state = state @max_content_size = max_content_size @uploader = uploader @mutex = Monitor.new end |
Instance Attribute Details
#state ⇒ Ask::State::Adapter (readonly)
Returns the underlying adapter.
46 47 48 |
# File 'lib/ask/agent/artifact_store.rb', line 46 def state @state end |
#uploader ⇒ Proc? (readonly)
Returns the uploader callback, if any.
49 50 51 |
# File 'lib/ask/agent/artifact_store.rb', line 49 def uploader @uploader end |
Instance Method Details
#delete(session_id) ⇒ void
This method returns an undefined value.
Remove every artifact for a session (called by Session#delete).
120 121 122 123 124 125 126 |
# File 'lib/ask/agent/artifact_store.rb', line 120 def delete(session_id) @mutex.synchronize do load_index(session_id).each { |id| @state.delete(entry_key(session_id, id)) } @state.delete(index_key(session_id)) end nil end |
#fetch(session_id, id) ⇒ Hash?
Returns the full record (content or uri).
109 110 111 112 113 114 |
# File 'lib/ask/agent/artifact_store.rb', line 109 def fetch(session_id, id) data = @state.get(entry_key(session_id, id)) return nil unless data symbolize(data) end |
#list(session_id) ⇒ Array<Hash>
Returns artifact summaries (id, filename, mime_type, size, uri) newest first — content is not included.
99 100 101 102 103 104 |
# File 'lib/ask/agent/artifact_store.rb', line 99 def list(session_id) load_index(session_id) .filter_map { |id| fetch(session_id, id) } .reverse .map { |r| r.slice(:id, :filename, :mime_type, :size, :uri) } end |
#store(session_id, filename:, mime_type: nil, content: nil, uri: nil) ⇒ Hash
Store an artifact for a session.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/ask/agent/artifact_store.rb', line 62 def store(session_id, filename:, mime_type: nil, content: nil, uri: nil) raise ArgumentError, "filename is required" if filename.to_s.strip.empty? raise ArgumentError, "pass either content: or uri:, not both" if content && uri raise ArgumentError, "pass either content: or uri:" unless content || uri if content content = content.to_s if content.length > @max_content_size raise ArgumentError, "content exceeds #{@max_content_size} chars; use uri: for large artifacts" end if @uploader uri = @uploader.call(content: content, filename: filename.to_s, mime_type: mime_type) content = nil end end id = SecureRandom.uuid record = { id: id, filename: filename.to_s, mime_type: mime_type, size: content ? content.length : nil, created_at: Time.now.iso8601 } record[:content] = content if content record[:uri] = uri if uri @mutex.synchronize do @state.set(entry_key(session_id, id), record) @state.set(index_key(session_id), (load_index(session_id) + [id]).to_json) end record end |