Eleven source files moved from ajent-rails:lib/opencode/rails/ to
opencode-rails:lib/opencode/ (flat layout — modules are Opencode::*, not
Opencode::Rails::*; matches opencode-ruby).
artifact.rb 63 LOC
exchange.rb 77 LOC
impostor.rb 48 LOC
message_artifacts.rb 133 LOC
sandbox_file.rb 81 LOC
sandbox.rb 71 LOC
session.rb 168 LOC
tool_display.rb 423 LOC
transform.rb 77 LOC
turn.rb 642 LOC
uploaded_files_prompt.rb 85 LOC
----
total 1,868 LOC
Surgical Rails strips:
exchange.rb:
Rails.event.notify(name, payload)
-> Opencode::Instrumentation.instrument(name, payload) { }
message_artifacts.rb (1 call), turn.rb (6 calls):
Rails.error.report(error, **opts)
-> Opencode::ErrorReporter.report(error, **opts)
Comments/docstrings referencing Rails.error.report / Rails.event left
in place — they document how to wire the host adapter.
ActiveSupport core_ext requires expanded in lib/opencode-rails.rb to
cover Numeric#seconds, Hash#deep_stringify_keys, String#squish/truncate,
String#demodulize. Bundle install + smoke load confirms all 12
gem-provided constants resolve cleanly.
64 lines
2.1 KiB
Ruby
64 lines
2.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Opencode
|
|
# A file the host wants to attach to an assistant message: filename,
|
|
# content bytes, MIME type, and an optional trust-metadata hash.
|
|
#
|
|
# Artifacts come from two places in the substrate:
|
|
#
|
|
# - Opencode::Exchange.tool_artifacts — content lives inside a tool
|
|
# call's input/metadata (write tool).
|
|
# - Opencode::SandboxFile#as_artifact — identity conversion of a
|
|
# sandbox-resident file (the default path for Blackline + Raven).
|
|
#
|
|
# Transforms also return Artifacts; that's why FlightResultsTransform
|
|
# returns one with the host-rendered HTML + trust metadata stamp.
|
|
#
|
|
# An Artifact knows how to attach itself to a message, idempotently:
|
|
# it consults `message.artifacts` to skip if its filename is already
|
|
# there. The attaching verb belongs to the Artifact (the noun whose
|
|
# state the verb consults), not to a separate Attacher class.
|
|
class Artifact
|
|
attr_reader :filename, :content, :content_type, :metadata
|
|
|
|
def initialize(filename:, content:, content_type:, metadata: {})
|
|
@filename = filename
|
|
@content = content
|
|
@content_type = content_type
|
|
@metadata = metadata
|
|
end
|
|
|
|
# Idempotent attach. Returns true if newly attached, false if the
|
|
# filename was already present on the message (so callers can count
|
|
# what they actually persisted vs what was already there).
|
|
def attach_to(message)
|
|
return false if already_attached_to?(message)
|
|
|
|
message.artifacts.attach(
|
|
io: StringIO.new(content),
|
|
filename: filename,
|
|
content_type: content_type,
|
|
metadata: metadata
|
|
)
|
|
true
|
|
end
|
|
|
|
def already_attached_to?(message)
|
|
message.artifacts.any? { |a| a.filename.to_s == filename }
|
|
end
|
|
|
|
def ==(other)
|
|
other.is_a?(Artifact) &&
|
|
other.filename == filename &&
|
|
other.content == content &&
|
|
other.content_type == content_type &&
|
|
other.metadata == metadata
|
|
end
|
|
alias_method :eql?, :==
|
|
|
|
def hash
|
|
[ filename, content, content_type, metadata ].hash
|
|
end
|
|
end
|
|
end
|