opencode-rails — production-grade Rails integration for OpenCode. Rails companion to opencode-ruby. ActiveRecord-aware session lifecycle (idempotent ensure!/recreate!/abort! with row-level locks), a Turn orchestrator driving the Reply state machine and recovering from session-not-found, an artifact pipeline backed by ActiveStorage, sandbox seeding, and tool-display value objects for Turbo Stream broadcasts. Drop into any Rails 7.1+ app that wants production-grade OpenCode streaming without rolling boilerplate. What this version ships: - Opencode::Session (AR-coupled lifecycle, row-level locks) - Opencode::Turn (Reply state machine, session-not-found recovery) - Opencode::Exchange (one turn = one request/response unit) - Opencode::Impostor (deterministic mock for tests) - Opencode::Sandbox / SandboxFile (per-session FS scratch space) - Opencode::Transform (host-rendered artifact pipeline) - Opencode::Artifact / MessageArtifacts (ActiveStorage-backed) - Opencode::UploadedFilesPrompt (system-prompt builder) - Opencode::ToolDisplay (Turbo Stream value objects) - Opencode::ErrorReporter (pluggable adapter — Honeybadger/Sentry/etc.) - examples/rails_integration.rb — canonical wiring blueprint 53 smoke tests. CI on Ruby 3.2/3.3/3.4. Ruby >= 3.2. Runtime deps: opencode-ruby = 0.0.1.alpha2, activerecord/activestorage/activesupport >= 7.1, < 9.0. See CHANGELOG.md for the alpha1 -> alpha2 delta.
56 lines
2.0 KiB
Ruby
56 lines
2.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "test_helper"
|
|
require "fileutils"
|
|
require "tmpdir"
|
|
|
|
# Smoke test for Opencode::UploadedFilesPrompt. The sandbox_path:
|
|
# inversion (per D14 in the design doc) means we can exercise the
|
|
# happy path against a tmpdir without needing Rails / AR / ActiveStorage
|
|
# fixtures. Behavioral tests covering ActiveStorage attached_blob
|
|
# enumeration live in the host application.
|
|
class Opencode::UploadedFilesPromptTest < Minitest::Test
|
|
# Minimal stub for a user message: #content (the raw user text) and
|
|
# #files (an ActiveStorage-like collection with #attached?). Real
|
|
# behavior is exercised in the host's test suite.
|
|
FakeMessage = Struct.new(:content, :files, keyword_init: true)
|
|
EmptyFiles = Struct.new(:attached) do
|
|
def attached? = attached
|
|
end
|
|
|
|
def setup
|
|
@tmpdir = Dir.mktmpdir("opencode-rails-uploaded-prompt-test-")
|
|
end
|
|
|
|
def teardown
|
|
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.exist?(@tmpdir)
|
|
end
|
|
|
|
def test_initialize_takes_three_required_keywords
|
|
params = Opencode::UploadedFilesPrompt.instance_method(:initialize).parameters
|
|
required = params.select { |kind, _| kind == :keyreq }.map(&:last).sort
|
|
|
|
assert_equal %i[sandbox_name_for sandbox_path user_message], required,
|
|
"UploadedFilesPrompt requires user_message:, sandbox_path:, sandbox_name_for:"
|
|
end
|
|
|
|
def test_text_returns_raw_content_when_no_files_attached
|
|
message = FakeMessage.new(content: "hello world", files: EmptyFiles.new(false))
|
|
prompt = Opencode::UploadedFilesPrompt.new(
|
|
user_message: message,
|
|
sandbox_path: @tmpdir,
|
|
sandbox_name_for: ->(file) { file.filename.to_s }
|
|
)
|
|
|
|
assert_equal "hello world", prompt.text,
|
|
"No attached files => text is the raw user content"
|
|
assert_equal({}, prompt.sandbox_file_names,
|
|
"No attached files => sandbox_file_names map stays empty")
|
|
end
|
|
|
|
def test_public_surface
|
|
assert_equal %i[sandbox_file_names text].sort,
|
|
Opencode::UploadedFilesPrompt.instance_methods(false).sort
|
|
end
|
|
end
|